Your IP : 216.73.216.168


Current Path : /home/poliximo/www/da45a/
Upload File :
Current File : /home/poliximo/www/da45a/libraries.tar

fof/encrypt/aes.php000064400000016631152177723700010305 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage encrypt
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A simple implementation of AES-128, AES-192 and AES-256 encryption using the
 * high performance mcrypt library.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFEncryptAes
{
	/**
	 * The cipher key.
	 *
	 * @var   string
	 */
	protected $key = '';

	/**
	 * The AES encryption adapter in use.
	 *
	 * @var  FOFEncryptAesInterface
	 */
	protected $adapter;
	
	/**
	 * Initialise the AES encryption object.
	 *
	 * Note: If the key is not 16 bytes this class will do a stupid key expansion for legacy reasons (produce the
	 * SHA-256 of the key string and throw away half of it).
	 *
	 * @param   string          $key      The encryption key (password). It can be a raw key (16 bytes) or a passphrase.
	 * @param   int             $strength Bit strength (128, 192 or 256) – ALWAYS USE 128 BITS. THIS PARAMETER IS DEPRECATED.
	 * @param   string          $mode     Encryption mode. Can be ebc or cbc. We recommend using cbc.
	 * @param   FOFUtilsPhpfunc $phpfunc  For testing
	 * @param   string          $priority Priority which adapter we should try first
	 */
	public function __construct($key, $strength = 128, $mode = 'cbc', FOFUtilsPhpfunc $phpfunc = null, $priority = 'openssl')
	{
		if ($priority == 'openssl')
		{
			$this->adapter = new FOFEncryptAesOpenssl();
			
			if (!$this->adapter->isSupported($phpfunc))
			{
				$this->adapter = new FOFEncryptAesMcrypt();
			}
		}
		else
		{
			$this->adapter = new FOFEncryptAesMcrypt();
			
			if (!$this->adapter->isSupported($phpfunc))
			{
				$this->adapter = new FOFEncryptAesOpenssl();
			}
		}

		$this->adapter->setEncryptionMode($mode, $strength);
		$this->setPassword($key, true);
	}

	/**
	 * Sets the password for this instance.
	 *
	 * WARNING: Do not use the legacy mode, it's insecure
	 *
	 * @param   string $password   The password (either user-provided password or binary encryption key) to use
	 * @param   bool   $legacyMode True to use the legacy key expansion. We recommend against using it.
	 */
	public function setPassword($password, $legacyMode = false)
	{
		$this->key = $password;

		$passLength = strlen($password);

		if (function_exists('mb_strlen'))
		{
			$passLength = mb_strlen($password, 'ASCII');
		}

		// Legacy mode was doing something stupid, requiring a key of 32 bytes. DO NOT USE LEGACY MODE!
		if ($legacyMode && ($passLength != 32))
		{
			// Legacy mode: use the sha256 of the password
			$this->key = hash('sha256', $password, true);
			// We have to trim or zero pad the password (we end up throwing half of it away in Rijndael-128 / AES...)
			$this->key = $this->adapter->resizeKey($this->key, $this->adapter->getBlockSize());
		}
	}

	/**
	 * Encrypts a string using AES
	 *
	 * @param   string $stringToEncrypt The plaintext to encrypt
	 * @param   bool   $base64encoded   Should I Base64-encode the result?
	 *
	 * @return   string  The cryptotext. Please note that the first 16 bytes of
	 *                   the raw string is the IV (initialisation vector) which
	 *                   is necessary for decoding the string.
	 */
	public function encryptString($stringToEncrypt, $base64encoded = true)
	{
		$blockSize = $this->adapter->getBlockSize();
		$randVal   = new FOFEncryptRandval();
		$iv        = $randVal->generate($blockSize);

		$key        = $this->getExpandedKey($blockSize, $iv);
		$cipherText = $this->adapter->encrypt($stringToEncrypt, $key, $iv);

		// Optionally pass the result through Base64 encoding
		if ($base64encoded)
		{
			$cipherText = base64_encode($cipherText);
		}

		// Return the result
		return $cipherText;
	}

	/**
	 * Decrypts a ciphertext into a plaintext string using AES
	 *
	 * @param   string $stringToDecrypt The ciphertext to decrypt. The first 16 bytes of the raw string must contain
	 *                                  the IV (initialisation vector).
	 * @param   bool   $base64encoded   Should I Base64-decode the data before decryption?
	 *
	 * @return   string  The plain text string
	 */
	public function decryptString($stringToDecrypt, $base64encoded = true)
	{
		if ($base64encoded)
		{
			$stringToDecrypt = base64_decode($stringToDecrypt);
		}

		// Extract IV
		$iv_size = $this->adapter->getBlockSize();
		$iv      = substr($stringToDecrypt, 0, $iv_size);
		$key     = $this->getExpandedKey($iv_size, $iv);

		// Decrypt the data
		$plainText = $this->adapter->decrypt($stringToDecrypt, $key);

		return $plainText;
	}

	/**
	 * Is AES encryption supported by this PHP installation?
	 *
	 * @param   FOFUtilsPhpfunc  $phpfunc
	 *
	 * @return boolean
	 */
	public static function isSupported(FOFUtilsPhpfunc $phpfunc = null)
	{
		if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
		{
			$phpfunc = new FOFUtilsPhpfunc();
		}

		$adapter = new FOFEncryptAesMcrypt();

		if (!$adapter->isSupported($phpfunc))
		{
			$adapter = new FOFEncryptAesOpenssl();
		}

		if (!$adapter->isSupported($phpfunc))
		{
			return false;
		}

		if (!$phpfunc->function_exists('base64_encode'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('base64_decode'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = $phpfunc->hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	/**
	 * @param $blockSize
	 * @param $iv
	 *
	 * @return string
	 */
	public function getExpandedKey($blockSize, $iv)
	{
		$key        = $this->key;
		$passLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$passLength = mb_strlen($key, 'ASCII');
		}

		if ($passLength != $blockSize)
		{
			$iterations = 1000;
			$salt       = $this->adapter->resizeKey($iv, 16);
			$key        = hash_pbkdf2('sha256', $this->key, $salt, $iterations, $blockSize, true);
		}

		return $key;
	}
}

if (!function_exists('hash_pbkdf2'))
{
	function hash_pbkdf2($algo, $password, $salt, $count, $length = 0, $raw_output = false)
	{
		if (!in_array(strtolower($algo), hash_algos()))
		{
			trigger_error(__FUNCTION__ . '(): Unknown hashing algorithm: ' . $algo, E_USER_WARNING);
		}

		if (!is_numeric($count))
		{
			trigger_error(__FUNCTION__ . '(): expects parameter 4 to be long, ' . gettype($count) . ' given', E_USER_WARNING);
		}

		if (!is_numeric($length))
		{
			trigger_error(__FUNCTION__ . '(): expects parameter 5 to be long, ' . gettype($length) . ' given', E_USER_WARNING);
		}

		if ($count <= 0)
		{
			trigger_error(__FUNCTION__ . '(): Iterations must be a positive integer: ' . $count, E_USER_WARNING);
		}

		if ($length < 0)
		{
			trigger_error(__FUNCTION__ . '(): Length must be greater than or equal to 0: ' . $length, E_USER_WARNING);
		}

		$output      = '';
		$block_count = $length ? ceil($length / strlen(hash($algo, '', $raw_output))) : 1;

		for ($i = 1; $i <= $block_count; $i++)
		{
			$last = $xorsum = hash_hmac($algo, $salt . pack('N', $i), $password, true);

			for ($j = 1; $j < $count; $j++)
			{
				$xorsum ^= ($last = hash_hmac($algo, $last, $password, true));
			}

			$output .= $xorsum;
		}

		if (!$raw_output)
		{
			$output = bin2hex($output);
		}

		return $length ? substr($output, 0, $length) : $output;
	}
}
fof/encrypt/base32.php000064400000011105152177723700010603 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage encrypt
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * FOFEncryptBase32
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFEncryptBase32
{
	/**
	 * CSRFC3548
	 *
	 * The character set as defined by RFC3548
	 * @link http://www.ietf.org/rfc/rfc3548.txt
	 */
	const CSRFC3548 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

	/**
	 * str2bin
	 *
	 * Converts any ascii string to a binary string
	 *
	 * @param   string  $str  The string you want to convert
	 *
	 * @return  string  String of 0's and 1's
	 */
	private function str2bin($str)
	{
		$chrs = unpack('C*', $str);

		return vsprintf(str_repeat('%08b', count($chrs)), $chrs);
	}

	/**
	 * bin2str
	 *
	 * Converts a binary string to an ascii string
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  The ascii output
	 *
	 * @throws Exception
	 */
	private function bin2str($str)
	{
		if (strlen($str) % 8 > 0)
		{
			throw new Exception('Length must be divisible by 8');
		}

		if (!preg_match('/^[01]+$/', $str))
		{
			throw new Exception('Only 0\'s and 1\'s are permitted');
		}

		preg_match_all('/.{8}/', $str, $chrs);
		$chrs = array_map('bindec', $chrs[0]);

		// I'm just being slack here
		array_unshift($chrs, 'C*');

		return call_user_func_array('pack', $chrs);
	}

	/**
	 * fromBin
	 *
	 * Converts a correct binary string to base32
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  String encoded as base32
	 *
	 * @throws exception
	 */
	private function fromBin($str)
	{
		if (strlen($str) % 8 > 0)
		{
			throw new Exception('Length must be divisible by 8');
		}

		if (!preg_match('/^[01]+$/', $str))
		{
			throw new Exception('Only 0\'s and 1\'s are permitted');
		}

		// Base32 works on the first 5 bits of a byte, so we insert blanks to pad it out
		$str = preg_replace('/(.{5})/', '000$1', $str);

		// We need a string divisible by 5
		$length = strlen($str);
		$rbits = $length & 7;

		if ($rbits > 0)
		{
			// Excessive bits need to be padded
			$ebits = substr($str, $length - $rbits);
			$str = substr($str, 0, $length - $rbits);
			$str .= "000$ebits" . str_repeat('0', 5 - strlen($ebits));
		}

		preg_match_all('/.{8}/', $str, $chrs);
		$chrs = array_map(array($this, '_mapcharset'), $chrs[0]);

		return join('', $chrs);
	}

	/**
	 * toBin
	 *
	 * Accepts a base32 string and returns an ascii binary string
	 *
	 * @param   string  $str  The base32 string to convert
	 *
	 * @return  string  Ascii binary string
	 *
	 * @throws  Exception
	 */
	private function toBin($str)
	{
		if (!preg_match('/^[' . self::CSRFC3548 . ']+$/', $str))
		{
			throw new Exception('Must match character set');
		}

		// Convert the base32 string back to a binary string
		$str = join('', array_map(array($this, '_mapbin'), str_split($str)));

		// Remove the extra 0's we added
		$str = preg_replace('/000(.{5})/', '$1', $str);

		// Unpad if nessicary
		$length = strlen($str);
		$rbits = $length & 7;

		if ($rbits > 0)
		{
			$str = substr($str, 0, $length - $rbits);
		}

		return $str;
	}

	/**
	 * fromString
	 *
	 * Convert any string to a base32 string
	 * This should be binary safe...
	 *
	 * @param   string  $str  The string to convert
	 *
	 * @return  string  The converted base32 string
	 */
	public function encode($str)
	{
		return $this->fromBin($this->str2bin($str));
	}

	/**
	 * toString
	 *
	 * Convert any base32 string to a normal sctring
	 * This should be binary safe...
	 *
	 * @param   string  $str  The base32 string to convert
	 *
	 * @return  string  The normal string
	 */
	public function decode($str)
	{
		$str = strtoupper($str);

		return $this->bin2str($this->tobin($str));
	}

	/**
	 * _mapcharset
	 *
	 * Used with array_map to map the bits from a binary string
	 * directly into a base32 character set
	 *
	 * @param   string  $str  The string of 0's and 1's you want to convert
	 *
	 * @return  string  Resulting base32 character
	 *
	 * @access private
	 */
	private function _mapcharset($str)
	{
		// Huh!
		$x = self::CSRFC3548;

		return $x[bindec($str)];
	}

	/**
	 * _mapbin
	 *
	 * Used with array_map to map the characters from a base32
	 * character set directly into a binary string
	 *
	 * @param   string  $chr  The caracter to map
	 *
	 * @return  string  String of 0's and 1's
	 *
	 * @access private
	 */
	private function _mapbin($chr)
	{
		return sprintf('%08b', strpos(self::CSRFC3548, $chr));
	}
}
fof/encrypt/randvalinterface.php000064400000000746152177723700013045 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

interface FOFEncryptRandvalinterface
{
	/**
	 *
	 * Returns a cryptographically secure random value.
	 *
	 * @return string
	 *
	 */
	public function generate();
}fof/encrypt/randval.php000064400000010576152177723700011166 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generates cryptographically-secure random values.
 */
class FOFEncryptRandval implements FOFEncryptRandvalinterface
{
	/**
	 * @var FOFUtilsPhpfunc
	 */
	protected $phpfunc;

	/**
	 *
	 * Constructor.
	 *
	 * @param FOFUtilsPhpfunc $phpfunc An object to intercept PHP function calls;
	 *                         this makes testing easier.
	 *
	 */
	public function __construct(FOFUtilsPhpfunc $phpfunc = null)
	{
		if (!is_object($phpfunc) || !($phpfunc instanceof FOFUtilsPhpfunc))
		{
			$phpfunc = new FOFUtilsPhpfunc();
		}

		$this->phpfunc = $phpfunc;
	}

	/**
	 *
	 * Returns a cryptographically secure random value.
	 *
	 * @param   integer  $bytes  How many bytes to return
	 *
	 * @return  string
	 */
	public function generate($bytes = 32)
	{
		if ($this->phpfunc->extension_loaded('openssl') && (version_compare(PHP_VERSION, '5.3.4') >= 0 || IS_WIN))
		{
			$strong = false;
			$randBytes = openssl_random_pseudo_bytes($bytes, $strong);

			if ($strong)
			{
				return $randBytes;
			}
		}

		if ($this->phpfunc->extension_loaded('mcrypt'))
		{
			return $this->phpfunc->mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
		}

		return $this->genRandomBytes($bytes);
	}

	/**
	 * Generate random bytes. Adapted from Joomla! 3.2.
	 *
	 * @param   integer  $length  Length of the random data to generate
	 *
	 * @return  string  Random binary data
	 */
	public function genRandomBytes($length = 32)
	{
		$length = (int) $length;
		$sslStr = '';

		/*
		 * Collect any entropy available in the system along with a number
		 * of time measurements of operating system randomness.
		 */
		$bitsPerRound = 2;
		$maxTimeMicro = 400;
		$shaHashLength = 20;
		$randomStr = '';
		$total = $length;

		// Check if we can use /dev/urandom.
		$urandom = false;
		$handle = null;

		// This is PHP 5.3.3 and up
		if ($this->phpfunc->function_exists('stream_set_read_buffer') && @is_readable('/dev/urandom'))
		{
			$handle = @fopen('/dev/urandom', 'rb');

			if ($handle)
			{
				$urandom = true;
			}
		}

		while ($length > strlen($randomStr))
		{
			$bytes = ($total > $shaHashLength)? $shaHashLength : $total;
			$total -= $bytes;

			/*
			 * Collect any entropy available from the PHP system and filesystem.
			 * If we have ssl data that isn't strong, we use it once.
			 */
			$entropy = rand() . uniqid(mt_rand(), true) . $sslStr;
			$entropy .= implode('', @fstat(fopen(__FILE__, 'r')));
			$entropy .= memory_get_usage();
			$sslStr = '';

			if ($urandom)
			{
				stream_set_read_buffer($handle, 0);
				$entropy .= @fread($handle, $bytes);
			}
			else
			{
				/*
				 * There is no external source of entropy so we repeat calls
				 * to mt_rand until we are assured there's real randomness in
				 * the result.
				 *
				 * Measure the time that the operations will take on average.
				 */
				$samples = 3;
				$duration = 0;

				for ($pass = 0; $pass < $samples; ++$pass)
				{
					$microStart = microtime(true) * 1000000;
					$hash = sha1(mt_rand(), true);

					for ($count = 0; $count < 50; ++$count)
					{
						$hash = sha1($hash, true);
					}

					$microEnd = microtime(true) * 1000000;
					$entropy .= $microStart . $microEnd;

					if ($microStart >= $microEnd)
					{
						$microEnd += 1000000;
					}

					$duration += $microEnd - $microStart;
				}

				$duration = $duration / $samples;

				/*
				 * Based on the average time, determine the total rounds so that
				 * the total running time is bounded to a reasonable number.
				 */
				$rounds = (int) (($maxTimeMicro / $duration) * 50);

				/*
				 * Take additional measurements. On average we can expect
				 * at least $bitsPerRound bits of entropy from each measurement.
				 */
				$iter = $bytes * (int) ceil(8 / $bitsPerRound);

				for ($pass = 0; $pass < $iter; ++$pass)
				{
					$microStart = microtime(true);
					$hash = sha1(mt_rand(), true);

					for ($count = 0; $count < $rounds; ++$count)
					{
						$hash = sha1($hash, true);
					}

					$entropy .= $microStart . microtime(true);
				}
			}

			$randomStr .= sha1($entropy, true);
		}

		if ($urandom)
		{
			@fclose($handle);
		}

		return substr($randomStr, 0, $length);
	}
}
fof/encrypt/aes/openssl.php000064400000007002152177723700011760 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFEncryptAesOpenssl extends FOFEncryptAesAbstract implements FOFEncryptAesInterface
{
	/**
	 * The OpenSSL options for encryption / decryption
	 *
	 * @var  int
	 */
	protected $openSSLOptions = 0;

	/**
	 * The encryption method to use
	 *
	 * @var  string
	 */
	protected $method = 'aes-128-cbc';

	public function __construct()
	{
		$this->openSSLOptions = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
	}

	public function setEncryptionMode($mode = 'cbc', $strength = 128)
	{
		static $availableAlgorithms = null;
		static $defaultAlgo = 'aes-128-cbc';

		if (!is_array($availableAlgorithms))
		{
			$availableAlgorithms = openssl_get_cipher_methods();

			foreach (array('aes-256-cbc', 'aes-256-ecb', 'aes-192-cbc',
				         'aes-192-ecb', 'aes-128-cbc', 'aes-128-ecb') as $algo)
			{
				if (in_array($algo, $availableAlgorithms))
				{
					$defaultAlgo = $algo;
					break;
				}
			}
		}

		$strength = (int) $strength;
		$mode     = strtolower($mode);

		if (!in_array($strength, array(128, 192, 256)))
		{
			$strength = 256;
		}

		if (!in_array($mode, array('cbc', 'ebc')))
		{
			$mode = 'cbc';
		}

		$algo = 'aes-' . $strength . '-' . $mode;

		if (!in_array($algo, $availableAlgorithms))
		{
			$algo = $defaultAlgo;
		}

		$this->method = $algo;
	}

	public function encrypt($plainText, $key, $iv = null)
	{
		$iv_size = $this->getBlockSize();
		$key     = $this->resizeKey($key, $iv_size);
		$iv      = $this->resizeKey($iv, $iv_size);

		if (empty($iv))
		{
			$randVal   = new FOFEncryptRandval();
			$iv        = $randVal->generate($iv_size);
		}

		$plainText .= $this->getZeroPadding($plainText, $iv_size);
		$cipherText = openssl_encrypt($plainText, $this->method, $key, $this->openSSLOptions, $iv);
		$cipherText = $iv . $cipherText;

		return $cipherText;
	}

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = openssl_decrypt($cipherText, $this->method, $key, $this->openSSLOptions, $iv);

		return $plainText;
	}

	public function isSupported(FOFUtilsPhpfunc $phpfunc = null)
	{
		if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
		{
			$phpfunc = new FOFUtilsPhpfunc();
		}

		if (!$phpfunc->function_exists('openssl_get_cipher_methods'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('openssl_random_pseudo_bytes'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('openssl_cipher_iv_length'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('openssl_encrypt'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('openssl_decrypt'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('hash'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = $phpfunc->openssl_get_cipher_methods();

		if (!in_array('aes-128-cbc', $algorightms))
		{
			return false;
		}

		$algorightms = $phpfunc->hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	/**
	 * @return int
	 */
	public function getBlockSize()
	{
		return openssl_cipher_iv_length($this->method);
	}
}fof/encrypt/aes/interface.php000064400000006265152177723700012247 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Interface for AES encryption adapters
 */
interface FOFEncryptAesInterface
{
	/**
	 * Sets the AES encryption mode.
	 *
	 * WARNING: The strength is deprecated as it has a different effect in MCrypt and OpenSSL. MCrypt was abandoned in
	 * 2003 before the Rijndael-128 algorithm was officially the Advanced Encryption Standard (AES). MCrypt also offered
	 * Rijndael-192 and Rijndael-256 algorithms with different block sizes. These are NOT used in AES. OpenSSL, however,
	 * implements AES correctly. It always uses a 128-bit (16 byte) block. The 192 and 256 bit strengths refer to the
	 * key size, not the block size. Therefore using different strengths in MCrypt and OpenSSL will result in different
	 * and incompatible ciphertexts.
	 *
	 * TL;DR: Always use $strength = 128!
	 *
	 * @param   string  $mode      Choose between CBC (recommended) or ECB
	 * @param   int     $strength  Bit strength of the key (128, 192 or 256 bits). DEPRECATED. READ NOTES ABOVE.
	 *
	 * @return  mixed
	 */
	public function setEncryptionMode($mode = 'cbc', $strength = 128);

	/**
	 * Encrypts a string. Returns the raw binary ciphertext.
	 *
	 * WARNING: The plaintext is zero-padded to the algorithm's block size. You are advised to store the size of the
	 * plaintext and trim the string to that length upon decryption.
	 *
	 * @param   string       $plainText  The plaintext to encrypt
	 * @param   string       $key        The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 * @param   null|string  $iv         The initialization vector (for CBC mode algorithms)
	 *
	 * @return  string  The raw encrypted binary string.
	 */
	public function encrypt($plainText, $key, $iv = null);

	/**
	 * Decrypts a string. Returns the raw binary plaintext.
	 *
	 * $ciphertext MUST start with the IV followed by the ciphertext, even for EBC data (the first block of data is
	 * dropped in EBC mode since there is no concept of IV in EBC).
	 *
	 * WARNING: The returned plaintext is zero-padded to the algorithm's block size during encryption. You are advised
	 * to trim the string to the original plaintext's length upon decryption. While rtrim($decrypted, "\0") sounds
	 * appealing it's NOT the correct approach for binary data (zero bytes may actually be part of your plaintext, not
	 * just padding!).
	 *
	 * @param   string  $cipherText  The ciphertext to encrypt
	 * @param   string  $key         The raw binary key (will be zero-padded or chopped if its size is different than the block size)
	 *
	 * @return  string  The raw unencrypted binary string.
	 */
	public function decrypt($cipherText, $key);

	/**
	 * Returns the encryption block size in bytes
	 *
	 * @return  int
	 */
	public function getBlockSize();

	/**
	 * Is this adapter supported?
	 *
	 * @param   FOFUtilsPhpfunc  $phpfunc
	 *
	 * @return  bool
	 */
	public function isSupported(FOFUtilsPhpfunc $phpfunc = null);
}fof/encrypt/aes/abstract.php000064400000003576152177723700012114 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Abstract AES encryption class
 */
abstract class FOFEncryptAesAbstract
{
	/**
	 * Trims or zero-pads a key / IV
	 *
	 * @param   string $key  The key or IV to treat
	 * @param   int    $size The block size of the currently used algorithm
	 *
	 * @return  null|string  Null if $key is null, treated string of $size byte length otherwise
	 */
	public function resizeKey($key, $size)
	{
		if (empty($key))
		{
			return null;
		}

		$keyLength = strlen($key);

		if (function_exists('mb_strlen'))
		{
			$keyLength = mb_strlen($key, 'ASCII');
		}

		if ($keyLength == $size)
		{
			return $key;
		}

		if ($keyLength > $size)
		{
			if (function_exists('mb_substr'))
			{
				return mb_substr($key, 0, $size, 'ASCII');
			}

			return substr($key, 0, $size);
		}

		return $key . str_repeat("\0", ($size - $keyLength));
	}

	/**
	 * Returns null bytes to append to the string so that it's zero padded to the specified block size
	 *
	 * @param   string $string    The binary string which will be zero padded
	 * @param   int    $blockSize The block size
	 *
	 * @return  string  The zero bytes to append to the string to zero pad it to $blockSize
	 */
	protected function getZeroPadding($string, $blockSize)
	{
		$stringSize = strlen($string);

		if (function_exists('mb_strlen'))
		{
			$stringSize = mb_strlen($string, 'ASCII');
		}

		if ($stringSize == $blockSize)
		{
			return '';
		}

		if ($stringSize < $blockSize)
		{
			return str_repeat("\0", $blockSize - $stringSize);
		}

		$paddingBytes = $stringSize % $blockSize;

		return str_repeat("\0", $blockSize - $paddingBytes);
	}
}fof/encrypt/aes/mcrypt.php000064400000006113152177723700011615 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFEncryptAesMcrypt extends FOFEncryptAesAbstract implements FOFEncryptAesInterface
{
	protected $cipherType = MCRYPT_RIJNDAEL_128;

	protected $cipherMode = MCRYPT_MODE_CBC;

	public function setEncryptionMode($mode = 'cbc', $strength = 128)
	{
		switch ((int) $strength)
		{
			default:
			case '128':
				$this->cipherType = MCRYPT_RIJNDAEL_128;
				break;

			case '192':
				$this->cipherType = MCRYPT_RIJNDAEL_192;
				break;

			case '256':
				$this->cipherType = MCRYPT_RIJNDAEL_256;
				break;
		}

		switch (strtolower($mode))
		{
			case 'ecb':
				$this->cipherMode = MCRYPT_MODE_ECB;
				break;

			default:
			case 'cbc':
				$this->cipherMode = MCRYPT_MODE_CBC;
				break;
		}

	}

	public function encrypt($plainText, $key, $iv = null)
	{
		$iv_size = $this->getBlockSize();
		$key     = $this->resizeKey($key, $iv_size);
		$iv      = $this->resizeKey($iv, $iv_size);

		if (empty($iv))
		{
			$randVal   = new FOFEncryptRandval();
			$iv        = $randVal->generate($iv_size);
		}

		$cipherText = mcrypt_encrypt($this->cipherType, $key, $plainText, $this->cipherMode, $iv);
		$cipherText = $iv . $cipherText;

		return $cipherText;
	}

	public function decrypt($cipherText, $key)
	{
		$iv_size    = $this->getBlockSize();
		$key        = $this->resizeKey($key, $iv_size);
		$iv         = substr($cipherText, 0, $iv_size);
		$cipherText = substr($cipherText, $iv_size);
		$plainText  = mcrypt_decrypt($this->cipherType, $key, $cipherText, $this->cipherMode, $iv);

		return $plainText;
	}

	public function isSupported(FOFUtilsPhpfunc $phpfunc = null)
	{
		if (!is_object($phpfunc) || !($phpfunc instanceof $phpfunc))
		{
			$phpfunc = new FOFUtilsPhpfunc();
		}

		if (!$phpfunc->function_exists('mcrypt_get_key_size'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('mcrypt_get_iv_size'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('mcrypt_create_iv'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('mcrypt_encrypt'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('mcrypt_decrypt'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('mcrypt_list_algorithms'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('hash'))
		{
			return false;
		}

		if (!$phpfunc->function_exists('hash_algos'))
		{
			return false;
		}

		$algorightms = $phpfunc->mcrypt_list_algorithms();

		if (!in_array('rijndael-128', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-192', $algorightms))
		{
			return false;
		}

		if (!in_array('rijndael-256', $algorightms))
		{
			return false;
		}

		$algorightms = $phpfunc->hash_algos();

		if (!in_array('sha256', $algorightms))
		{
			return false;
		}

		return true;
	}

	public function getBlockSize()
	{
		return mcrypt_get_iv_size($this->cipherType, $this->cipherMode);
	}
}fof/encrypt/totp.php000064400000011101152177723700010506 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage encrypt
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * This class provides an RFC6238-compliant Time-based One Time Passwords,
 * compatible with Google Authenticator (with PassCodeLength = 6 and TimePeriod = 30).
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFEncryptTotp
{
	private $_passCodeLength = 6;

	private $_pinModulo;

	private $_secretLength = 10;

	private $_timeStep = 30;

	private $_base32 = null;

	/**
	 * Initialises an RFC6238-compatible TOTP generator. Please note that this
	 * class does not implement the constraint in the last paragraph of §5.2
	 * of RFC6238. It's up to you to ensure that the same user/device does not
	 * retry validation within the same Time Step.
	 *
	 * @param   int     $timeStep        The Time Step (in seconds). Use 30 to be compatible with Google Authenticator.
	 * @param   int     $passCodeLength  The generated passcode length. Default: 6 digits.
	 * @param   int     $secretLength    The length of the secret key. Default: 10 bytes (80 bits).
	 * @param   Object  $base32          The base32 en/decrypter
	 */
	public function __construct($timeStep = 30, $passCodeLength = 6, $secretLength = 10, $base32=null)
	{
		$this->_timeStep       = $timeStep;
		$this->_passCodeLength = $passCodeLength;
		$this->_secretLength   = $secretLength;
		$this->_pinModulo      = pow(10, $this->_passCodeLength);

		if (is_null($base32))
		{
			$this->_base32 = new FOFEncryptBase32;
		}
		else
		{
			$this->_base32 = $base32;
		}
	}

	/**
	 * Get the time period based on the $time timestamp and the Time Step
	 * defined. If $time is skipped or set to null the current timestamp will
	 * be used.
	 *
	 * @param   int|null  $time  Timestamp
	 *
	 * @return  int  The time period since the UNIX Epoch
	 */
	public function getPeriod($time = null)
	{
		if (is_null($time))
		{
			$time = time();
		}

		$period = floor($time / $this->_timeStep);

		return $period;
	}

	/**
	 * Check is the given passcode $code is a valid TOTP generated using secret
	 * key $secret
	 *
	 * @param   string  $secret  The Base32-encoded secret key
	 * @param   string  $code    The passcode to check
	 *
	 * @return boolean True if the code is valid
	 */
	public function checkCode($secret, $code)
	{
		$time = $this->getPeriod();

		for ($i = -1; $i <= 1; $i++)
		{
			if ($this->getCode($secret, ($time + $i) * $this->_timeStep) == $code)
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Gets the TOTP passcode for a given secret key $secret and a given UNIX
	 * timestamp $time
	 *
	 * @param   string  $secret  The Base32-encoded secret key
	 * @param   int     $time    UNIX timestamp
	 *
	 * @return string
	 */
	public function getCode($secret, $time = null)
	{
		$period = $this->getPeriod($time);
		$secret = $this->_base32->decode($secret);

		$time = pack("N", $period);
		$time = str_pad($time, 8, chr(0), STR_PAD_LEFT);

		$hash = hash_hmac('sha1', $time, $secret, true);
		$offset = ord(substr($hash, -1));
		$offset = $offset & 0xF;

		$truncatedHash = $this->hashToInt($hash, $offset) & 0x7FFFFFFF;
		$pinValue = str_pad($truncatedHash % $this->_pinModulo, $this->_passCodeLength, "0", STR_PAD_LEFT);

		return $pinValue;
	}

	/**
	 * Extracts a part of a hash as an integer
	 *
	 * @param   string  $bytes  The hash
	 * @param   string  $start  The char to start from (0 = first char)
	 *
	 * @return  string
	 */
	protected function hashToInt($bytes, $start)
	{
		$input = substr($bytes, $start, strlen($bytes) - $start);
		$val2 = unpack("N", substr($input, 0, 4));

		return $val2[1];
	}

	/**
	 * Returns a QR code URL for easy setup of TOTP apps like Google Authenticator
	 *
	 * @param   string  $user      User
	 * @param   string  $hostname  Hostname
	 * @param   string  $secret    Secret string
	 *
	 * @return  string
	 */
	public function getUrl($user, $hostname, $secret)
	{
		$url = sprintf("otpauth://totp/%s@%s?secret=%s", $user, $hostname, $secret);
		$encoder = "https://chart.googleapis.com/chart?chs=200x200&chld=Q|2&cht=qr&chl=";
		$encoderURL = $encoder . urlencode($url);

		return $encoderURL;
	}

	/**
	 * Generates a (semi-)random Secret Key for TOTP generation
	 *
	 * @return  string
	 */
	public function generateSecret()
	{
		$secret = "";

		for ($i = 1; $i <= $this->_secretLength; $i++)
		{
			$c = rand(0, 255);
			$secret .= pack("c", $c);
		}
		$base32 = new FOFEncryptBase32;

		return $this->_base32->encode($secret);
	}
}
fof/download/interface.php000064400000005073152177723700011616 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

interface FOFDownloadInterface
{
	/**
	 * Does this download adapter support downloading files in chunks?
	 *
	 * @return  boolean  True if chunk download is supported
	 */
	public function supportsChunkDownload();

	/**
	 * Does this download adapter support reading the size of a remote file?
	 *
	 * @return  boolean  True if remote file size determination is supported
	 */
	public function supportsFileSize();

	/**
	 * Is this download class supported in the current server environment?
	 *
	 * @return  boolean  True if this server environment supports this download class
	 */
	public function isSupported();

	/**
	 * Get the priority of this adapter. If multiple download adapters are
	 * supported on a site, the one with the highest priority will be
	 * used.
	 *
	 * @return  boolean
	 */
	public function getPriority();

	/**
	 * Returns the name of this download adapter in use
	 *
	 * @return  string
	 */
	public function getName();

	/**
	 * Download a part (or the whole) of a remote URL and return the downloaded
	 * data. You are supposed to check the size of the returned data. If it's
	 * smaller than what you expected you've reached end of file. If it's empty
	 * you have tried reading past EOF. If it's larger than what you expected
	 * the server doesn't support chunk downloads.
	 *
	 * If this class' supportsChunkDownload returns false you should assume
	 * that the $from and $to parameters will be ignored.
	 *
	 * @param   string   $url     The remote file's URL
	 * @param   integer  $from    Byte range to start downloading from. Use null for start of file.
	 * @param   integer  $to      Byte range to stop downloading. Use null to download the entire file ($from is ignored)
	 * @param   array    $params  Additional params that will be added before performing the download
	 *
	 * @return  string  The raw file data retrieved from the remote URL.
	 *
	 * @throws  Exception  A generic exception is thrown on error
	 */
	public function downloadAndReturn($url, $from = null, $to = null, array $params = array());

	/**
	 * Get the size of a remote file in bytes
	 *
	 * @param   string  $url  The remote file's URL
	 *
	 * @return  integer  The file size, or -1 if the remote server doesn't support this feature
	 */
	public function getFileSize($url);
}fof/download/download.php000064400000027526152177723700011474 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFDownload
{
	/**
	 * Parameters passed from the GUI when importing from URL
	 *
	 * @var  array
	 */
	private $params = array();

	/**
	 * The download adapter which will be used by this class
	 *
	 * @var  FOFDownloadInterface
	 */
	private $adapter = null;

	/**
	 * Additional params that will be passed to the adapter while performing the download
	 *
	 * @var  array
	 */
	private $adapterOptions = array();

	/**
	 * Creates a new download object and assigns it the most fitting download adapter
	 */
	public function __construct()
	{
		// Find the best fitting adapter
		$allAdapters = self::getFiles(__DIR__ . '/adapter', array(), array('abstract.php'));
		$priority    = 0;

		foreach ($allAdapters as $adapterInfo)
		{
			if (!class_exists($adapterInfo['classname'], true))
			{
				continue;
			}

			/** @var FOFDownloadAdapterAbstract $adapter */
			$adapter = new $adapterInfo['classname'];

			if ( !$adapter->isSupported())
			{
				continue;
			}

			if ($adapter->priority > $priority)
			{
				$this->adapter = $adapter;
				$priority      = $adapter->priority;
			}
		}

		// Load the language strings
		FOFPlatform::getInstance()->loadTranslations('lib_f0f');
	}

	/**
	 * Forces the use of a specific adapter
	 *
	 * @param  string $className   The name of the class or the name of the adapter, e.g. 'FOFDownloadAdapterCurl' or
	 *                             'curl'
	 */
	public function setAdapter($className)
	{
		$adapter = null;

		if (class_exists($className, true))
		{
			$adapter = new $className;
		}
		elseif (class_exists('FOFDownloadAdapter' . ucfirst($className)))
		{
			$className = 'FOFDownloadAdapter' . ucfirst($className);
			$adapter   = new $className;
		}

		if (is_object($adapter) && ($adapter instanceof FOFDownloadInterface))
		{
			$this->adapter = $adapter;
		}
	}

	/**
	 * Returns the name of the current adapter
	 *
	 * @return string
	 */
	public function getAdapterName()
	{
		if(is_object($this->adapter))
		{
			$class = get_class($this->adapter);

			return strtolower(str_ireplace('FOFDownloadAdapter', '', $class));
		}

		return '';
	}

	/**
	 * Sets the additional options for the adapter
	 *
	 * @param array $options
	 */
	public function setAdapterOptions(array $options)
	{
		$this->adapterOptions = $options;
	}

	/**
	 * Returns the additional options for the adapter
	 *
	 * @return array
	 */
	public function getAdapterOptions()
	{
		return $this->adapterOptions;
	}

	/**
	 * Used to decode the $params array
	 *
	 * @param   string $key     The parameter key you want to retrieve the value for
	 * @param   mixed  $default The default value, if none is specified
	 *
	 * @return  mixed  The value for this parameter key
	 */
	private function getParam($key, $default = null)
	{
		if (array_key_exists($key, $this->params))
		{
			return $this->params[$key];
		}
		else
		{
			return $default;
		}
	}

	/**
	 * Download data from a URL and return it
	 *
	 * @param   string $url The URL to download from
	 *
	 * @return  bool|string  The downloaded data or false on failure
	 */
	public function getFromURL($url)
	{
		try
		{
			return $this->adapter->downloadAndReturn($url, null, null, $this->adapterOptions);
		}
		catch (Exception $e)
		{
			return false;
		}
	}

	/**
	 * Performs the staggered download of file. The downloaded file will be stored in Joomla!'s temp-path using the
	 * basename of the URL as a filename
	 *
	 * The $params array can have any of the following keys
	 * url			The file being downloaded
	 * frag			Rolling counter of the file fragment being downloaded
	 * totalSize	The total size of the file being downloaded, in bytes
	 * doneSize		How many bytes we have already downloaded
	 * maxExecTime	Maximum execution time downloading file fragments, in seconds
	 * length		How many bytes to download at once
	 *
	 * The array returned is in the following format:
	 *
	 * status		True if there are no errors, false if there are errors
	 * error		A string with the error message if there are errors
	 * frag			The next file fragment to download
	 * totalSize	The total size of the downloaded file in bytes, if the server supports HEAD requests
	 * doneSize		How many bytes have already been downloaded
	 * percent		% of the file already downloaded (if totalSize could be determined)
	 * localfile	The name of the local file, without the path
	 *
	 * @param   array $params A parameters array, as sent by the user interface
	 *
	 * @return  array  A return status array
	 */
	public function importFromURL($params)
	{
		$this->params = $params;

		// Fetch data
		$url         	= $this->getParam('url');
		$localFilename	= $this->getParam('localFilename');
		$frag        	= $this->getParam('frag', -1);
		$totalSize   	= $this->getParam('totalSize', -1);
		$doneSize    	= $this->getParam('doneSize', -1);
		$maxExecTime 	= $this->getParam('maxExecTime', 5);
		$runTimeBias 	= $this->getParam('runTimeBias', 75);
		$length      	= $this->getParam('length', 1048576);

		if (empty($localFilename))
		{
			$localFilename = basename($url);

			if (strpos($localFilename, '?') !== false)
			{
				$paramsPos = strpos($localFilename, '?');
				$localFilename = substr($localFilename, 0, $paramsPos - 1);
			}
		}

		$tmpDir        = JFactory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
		$tmpDir        = rtrim($tmpDir, '/\\');

		// Init retArray
		$retArray = array(
			"status"    => true,
			"error"     => '',
			"frag"      => $frag,
			"totalSize" => $totalSize,
			"doneSize"  => $doneSize,
			"percent"   => 0,
			"localfile"	=> $localFilename
		);

		try
		{
			$timer = new FOFUtilsTimer($maxExecTime, $runTimeBias);
			$start = $timer->getRunningTime(); // Mark the start of this download
			$break = false; // Don't break the step

			// Figure out where on Earth to put that file
			$local_file = $tmpDir . '/' . $localFilename;

			while (($timer->getTimeLeft() > 0) && !$break)
			{
				// Do we have to initialize the file?
				if ($frag == -1)
				{
					// Currently downloaded size
					$doneSize = 0;

					if (@file_exists($local_file))
					{
						@unlink($local_file);
					}

					// Delete and touch the output file
					$fp = @fopen($local_file, 'wb');

					if ($fp !== false)
					{
						@fclose($fp);
					}

					// Init
					$frag = 0;

					//debugMsg("-- First frag, getting the file size");
					$retArray['totalSize'] = $this->adapter->getFileSize($url);
					$totalSize             = $retArray['totalSize'];
				}

				// Calculate from and length
				$from = $frag * $length;
				$to   = $length + $from - 1;

				// Try to download the first frag
				$required_time = 1.0;

				try
				{
					$result = $this->adapter->downloadAndReturn($url, $from, $to, $this->adapterOptions);

					if ($result === false)
					{
						throw new Exception(JText::sprintf('LIB_FOF_DOWNLOAD_ERR_COULDNOTDOWNLOADFROMURL', $url), 500);
					}
				}
				catch (Exception $e)
				{
					$result = false;
					$error  = $e->getMessage();
				}

				if ($result === false)
				{
					// Failed download
					if ($frag == 0)
					{
						// Failure to download first frag = failure to download. Period.
						$retArray['status'] = false;
						$retArray['error']  = $error;

						//debugMsg("-- Download FAILED");

						return $retArray;
					}
					else
					{
						// Since this is a staggered download, consider this normal and finish
						$frag = -1;
						//debugMsg("-- Import complete");
						$totalSize = $doneSize;
						$break     = true;
					}
				}

				// Add the currently downloaded frag to the total size of downloaded files
				if ($result)
				{
					$filesize = strlen($result);
					//debugMsg("-- Successful download of $filesize bytes");
					$doneSize += $filesize;

					// Append the file
					$fp = @fopen($local_file, 'ab');

					if ($fp === false)
					{
						//debugMsg("-- Can't open local file $local_file for writing");
						// Can't open the file for writing
						$retArray['status'] = false;
						$retArray['error']  = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_COULDNOTWRITELOCALFILE', $local_file);

						return $retArray;
					}

					fwrite($fp, $result);
					fclose($fp);

					//debugMsg("-- Appended data to local file $local_file");

					$frag++;

					//debugMsg("-- Proceeding to next fragment, frag $frag");

					if (($filesize < $length) || ($filesize > $length))
					{
						// A partial download or a download larger than the frag size means we are done
						$frag = -1;
						//debugMsg("-- Import complete (partial download of last frag)");
						$totalSize = $doneSize;
						$break     = true;
					}
				}

				// Advance the frag pointer and mark the end
				$end = $timer->getRunningTime();

				// Do we predict that we have enough time?
				$required_time = max(1.1 * ($end - $start), $required_time);

				if ($required_time > (10 - $end + $start))
				{
					$break = true;
				}

				$start = $end;
			}

			if ($frag == -1)
			{
				$percent = 100;
			}
			elseif ($doneSize <= 0)
			{
				$percent = 0;
			}
			else
			{
				if ($totalSize > 0)
				{
					$percent = 100 * ($doneSize / $totalSize);
				}
				else
				{
					$percent = 0;
				}
			}

			// Update $retArray
			$retArray = array(
				"status"    => true,
				"error"     => '',
				"frag"      => $frag,
				"totalSize" => $totalSize,
				"doneSize"  => $doneSize,
				"percent"   => $percent,
			);
		}
		catch (Exception $e)
		{
			//debugMsg("EXCEPTION RAISED:");
			//debugMsg($e->getMessage());
			$retArray['status'] = false;
			$retArray['error']  = $e->getMessage();
		}

		return $retArray;
	}

	/**
	 * This method will crawl a starting directory and get all the valid files
	 * that will be analyzed by __construct. Then it organizes them into an
	 * associative array.
	 *
	 * @param   string $path          Folder where we should start looking
	 * @param   array  $ignoreFolders Folder ignore list
	 * @param   array  $ignoreFiles   File ignore list
	 *
	 * @return  array   Associative array, where the `fullpath` key contains the path to the file,
	 *                  and the `classname` key contains the name of the class
	 */
	protected static function getFiles($path, array $ignoreFolders = array(), array $ignoreFiles = array())
	{
		$return = array();

		$files = self::scanDirectory($path, $ignoreFolders, $ignoreFiles);

		// Ok, I got the files, now I have to organize them
		foreach ($files as $file)
		{
			$clean = str_replace($path, '', $file);
			$clean = trim(str_replace('\\', '/', $clean), '/');

			$parts = explode('/', $clean);

			$return[] = array(
				'fullpath'  => $file,
				'classname' => 'FOFDownloadAdapter' . ucfirst(basename($parts[0], '.php'))
			);
		}

		return $return;
	}

	/**
	 * Recursive function that will scan every directory unless it's in the
	 * ignore list. Files that aren't in the ignore list are returned.
	 *
	 * @param   string $path          Folder where we should start looking
	 * @param   array  $ignoreFolders Folder ignore list
	 * @param   array  $ignoreFiles   File ignore list
	 *
	 * @return  array   List of all the files
	 */
	protected static function scanDirectory($path, array $ignoreFolders = array(), array $ignoreFiles = array())
	{
		$return = array();

		$handle = @opendir($path);

		if ( !$handle)
		{
			return $return;
		}

		while (($file = readdir($handle)) !== false)
		{
			if ($file == '.' || $file == '..')
			{
				continue;
			}

			$fullpath = $path . '/' . $file;

			if ((is_dir($fullpath) && in_array($file, $ignoreFolders)) || (is_file($fullpath) && in_array($file, $ignoreFiles)))
			{
				continue;
			}

			if (is_dir($fullpath))
			{
				$return = array_merge(self::scanDirectory($fullpath, $ignoreFolders, $ignoreFiles), $return);
			}
			else
			{
				$return[] = $path . '/' . $file;
			}
		}

		return $return;
	}
}fof/download/adapter/cacert.pem000064400001110611152177723700012525 0ustar00##
## Bundle of CA Root Certificates
##
## Certificate data from CentOS as of Oct 3 2015
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from CentOS /etc/pki/tls/certs/ca-bundle.crt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##

-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDKTCCApKgAwIBAgIENnAVljANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
EwhEU1RDQSBFMTAeFw05ODEyMTAxODEwMjNaFw0xODEyMTAxODQwMjNaMEYxCzAJ
BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
ETAPBgNVBAsTCERTVENBIEUxMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQCg
bIGpzzQeJN3+hijM3oMv+V7UQtLodGBmE5gGHKlREmlvMVW5SXIACH7TpWJENySZ
j9mDSI+ZbZUTu0M7LklOiDfBu1h//uG9+LthzfNHwJmm8fOR6Hh8AMthyUQncWlV
Sn5JTe2io74CTADKAqjuAQIxZA9SLRN0dja1erQtcQIBA6OCASQwggEgMBEGCWCG
SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
RFNUQ0EgRTExDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMTAxODEw
MjNagQ8yMDE4MTIxMDE4MTAyM1owCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFGp5
fpFpRhgTCgJ3pVlbYJglDqL4MB0GA1UdDgQWBBRqeX6RaUYYEwoCd6VZW2CYJQ6i
+DAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
SIb3DQEBBQUAA4GBACIS2Hod3IEGtgllsofIH160L+nEHvI8wbsEkBFKg05+k7lN
QseSJqBcNJo4cvj9axY+IO6CizEqkzaFI4iKPANo08kJD038bKTaKHKTDomAsH3+
gG9lbRgzl4vCa4nuYD3Im+9/KzJic5PLPON74nZ4RbyhkwS7hp86W0N6w4pl
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDKTCCApKgAwIBAgIENm7TzjANBgkqhkiG9w0BAQUFADBGMQswCQYDVQQGEwJV
UzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMREwDwYDVQQL
EwhEU1RDQSBFMjAeFw05ODEyMDkxOTE3MjZaFw0xODEyMDkxOTQ3MjZaMEYxCzAJ
BgNVBAYTAlVTMSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4x
ETAPBgNVBAsTCERTVENBIEUyMIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgQC/
k48Xku8zExjrEH9OFr//Bo8qhbxe+SSmJIi2A7fBw18DW9Fvrn5C6mYjuGODVvso
LeE4i7TuqAHhzhy2iCoiRoX7n6dwqUcUP87eZfCocfdPJmyMvMa1795JJ/9IKn3o
TQPMx7JSxhcxEzu1TdvIxPbDDyQq2gyd55FbgM2UnQIBA6OCASQwggEgMBEGCWCG
SAGG+EIBAQQEAwIABzBoBgNVHR8EYTBfMF2gW6BZpFcwVTELMAkGA1UEBhMCVVMx
JDAiBgNVBAoTG0RpZ2l0YWwgU2lnbmF0dXJlIFRydXN0IENvLjERMA8GA1UECxMI
RFNUQ0EgRTIxDTALBgNVBAMTBENSTDEwKwYDVR0QBCQwIoAPMTk5ODEyMDkxOTE3
MjZagQ8yMDE4MTIwOTE5MTcyNlowCwYDVR0PBAQDAgEGMB8GA1UdIwQYMBaAFB6C
TShlgDzJQW6sNS5ay97u+DlbMB0GA1UdDgQWBBQegk0oZYA8yUFurDUuWsve7vg5
WzAMBgNVHRMEBTADAQH/MBkGCSqGSIb2fQdBAAQMMAobBFY0LjADAgSQMA0GCSqG
SIb3DQEBBQUAA4GBAEeNg61i8tuwnkUiBbmi1gMOOHLnnvx75pO2mqWilMg0HZHR
xdf0CiUPPXiBng+xZ8SQTGPdXqfiup/1902lMXucKS1M/mQ+7LZT/uqb7YLbdHVL
B3luHtgZg3Pe9T7Qtd7nS2h9Qy4qIOF+oHhEngj1mPnHfxsb1gYgAlihw6ID
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEEzH6qqYPnHTkxD4PTqJkZIwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMSBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMSBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQCq0Lq+Fi24g9TK0g+8djHKlNgdk4xWArzZbxpvUjZudVYK
VdPfQ4chEWWKfo+9Id5rMj8bhDSVBZ1BNeuS65bdqlk/AVNtmU/t5eIqWpDBucSm
Fc/IReumXY6cPvBkJHalzasab7bYe1FhbqZ/h8jit+U03EGI6glAvnOSPWvndQID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAKlPww3HZ74sy9mozS11534Vnjty637rXC0J
h9ZrbWB85a7FkCMMXErQr7Fd88e2CtvgFZMN3QO8x3aKtd1Pw5sTdbgBwObJW2ul
uIncrKTdcu1OofdPvAbT6shkdHvClUGcZXNY8ZCaPGqxmMnEh7zPRW1F4m4iP/68
DzFc6PLZ
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDAzCCAmwCEQC5L2DMiJ+hekYJuFtwbIqvMA0GCSqGSIb3DQEBBQUAMIHBMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0Ns
YXNzIDIgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjE6MDgGA1UECxMxKGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9y
aXplZCB1c2Ugb25seTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazAe
Fw05ODA1MTgwMDAwMDBaFw0yODA4MDEyMzU5NTlaMIHBMQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xPDA6BgNVBAsTM0NsYXNzIDIgUHVibGlj
IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBHMjE6MDgGA1UECxMx
KGMpIDE5OTggVmVyaVNpZ24sIEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTEfMB0GA1UECxMWVmVyaVNpZ24gVHJ1c3QgTmV0d29yazCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAp4gBIXQs5xoD8JjhlzwPIQjxnNuX6Zr8wgQGE75fUsjM
HiwSViy4AWkszJkfrbCWrnkE8hM5wXuYuggs6MKEEyyqaekJ9MepAqRCwiNPStjw
DqL7MWzJ5m+ZJwf15vRMeJ5t60aG+rmGyVTyssSv1EYcWskVMP8NbPUtDm3Of3cC
AwEAATANBgkqhkiG9w0BAQUFAAOBgQByLvl/0fFx+8Se9sVeUYpAmLho+Jscg9ji
nb3/7aHmZuovCfTK1+qlK5X2JGCGTUQug6XELaDTrnhpb3LabK4I8GOSN+a7xDAX
rXfMSTWqz9iP0b63GJZHc2pUIjRkLbYWm1lbtFFZOrMLFPQS32eg9K0yZF6xRnIn
jBJ7xUS0rg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
oJ2daZH9
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCLW3VWhFSFCwDPrzhIzrGkMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDEgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN2E1Lm0+afY8wR4
nN493GwTFtl63SRRZsDHJlkNrAYIwpTRMx/wgzUfbhvI3qpuFU5UJ+/EbRrsC+MO
8ESlV8dAWB6jRx9x7GD2bZTIGDnt/kIYVt/kTEkQeE4BdjVjEjbdZrwBBDajVWjV
ojYJrKshJlQGrT/KFOCsyq0GHZXi+J3x4GD/wn91K0zM2v6HmSHquv4+VNfSWXjb
PG7PoBMAGrgnoeS+Z5bKoMWznN3JdZ7rMJpfo83ZrngZPyPpXNspva1VyBtUjGP2
6KbqxzcSXKMpHgLZ2x87tNcPVkeBFQRKr4Mn0cVYiMHd9qqnoxjaaKptEVHhv2Vr
n5Z20T0CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAq2aN17O6x5q25lXQBfGfMY1a
qtmqRiYPce2lrVNWYgFHKkTp/j90CxObufRNG7LRX7K20ohcs5/Ny9Sn2WCVhDr4
wTcdYcrnsMXlkdpUpqwxga6X3s0IrLjAl4B/bnKk52kTlWUfxJM8/XmPBNQ+T+r3
ns7NZ3xPZQL/kYVUc8f/NveGLezQXk//EZ9yBta4GvFMDSZl4kSAHsef493oCtrs
pSCAaWihT37ha88HQfqDjrw43bAuEbFrskLMmrz5SCJ5ShkPshw+IHTZasO+8ih4
E1Z5T21Q6huwtVexN2ZYI/PcD98Kh8TvhgXVOBRgmaNL3gaWcSzy27YfpO8/7g==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEGTCCAwECEGFwy0mMX5hFKeewptlQW3owDQYJKoZIhvcNAQEFBQAwgcoxCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVy
aVNpZ24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24s
IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNp
Z24gQ2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEczMB4XDTk5MTAwMTAwMDAwMFoXDTM2MDcxNjIzNTk1OVowgcoxCzAJBgNV
BAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjEfMB0GA1UECxMWVmVyaVNp
Z24gVHJ1c3QgTmV0d29yazE6MDgGA1UECxMxKGMpIDE5OTkgVmVyaVNpZ24sIElu
Yy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTFFMEMGA1UEAxM8VmVyaVNpZ24g
Q2xhc3MgMiBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAt
IEczMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwoNwtUs22e5LeWU
J92lvuCwTY+zYVY81nzD9M0+hsuiiOLh2KRpxbXiv8GmR1BeRjmL1Za6tW8UvxDO
JxOeBUebMXoT2B/Z0wI3i60sR/COgQanDTAM6/c8DyAd3HJG7qUCyFvDyVZpTMUY
wZF7C9UTAJu878NIPkZgIIUq1ZC2zYugzDLdt/1AVbJQHFauzI13TccgTacxdu9o
koqQHgiBVrKtaaNS0MscxCM9H5n+TOgWY47GCI72MfbS+uV23bUckqNJzc0BzWjN
qWm6o+sdDZykIKbBoMXRRkwXbdKsZj+WjOCE1Db/IlnF+RFgqF8EffIa9iVCYQ/E
Srg+iQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQA0JhU8wI1NQ0kdvekhktdmnLfe
xbjQ5F1fdiLAJvmEOjr5jLX77GDx6M4EsMjdpwOPMPOY36TmpDHf0xwLRtxyID+u
7gU8pDM/CzmscHhzS5kr3zDCVLCoO1Wh/hYozUK9dG6A2ydEp85EXdQbkJgNHkKU
sQAsBNB0owIFImNjzYO1+8FtYmtpdf1dcEG59b98377BMnMiIYtYgXsVkXq642RI
sH/7NiXaldDxJBQX3RiAa0YjOVT1jmIJBB2UkKab5iXiQkWquJCtvgiPqQtCGJTP
cjnhsUPgKM+351psE2tJs//jGHyJizNdrDPXp/naOlXJWBD5qu9ats9LS98q
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1
GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ
+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd
U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm
NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY
ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/
ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1
CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq
g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm
fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c
2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/
bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
BAMTFOFkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
xqE=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
7CAFYd4=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
SnQ2+Q==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBJDANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MxIENBMB4XDTAx
MDQwNjEwNDkxM1oXDTIxMDQwNjEwNDkxM1owOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMSBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBALWJHytPZwp5/8Ue+H887dF+2rDNbS82rDTG
29lkFwhjMDMiikzujrsPDUJVyZ0upe/3p4zDq7mXy47vPxVnqIJyY1MPQYx9EJUk
oVqlBvqSV536pQHydekfvFYmUk54GWVYVQNYwBSujHxVX3BbdyMGNpfzJLWaRpXk
3w0LBUXl0fIdgrvGE+D+qnr9aTCU89JFhfzyMlsy3uhsXR/LpCJ0sICOXZT3BgBL
qdReLjVQCfOAl/QMF6452F/NM8EcyonCIvdFEu1eEpOdY6uCLrnrQkFEy0oaAIIN
nvmLVz5MxxftLItyM19yejhW1ebZrgUaHXVFsculJRwSVzb9IjcCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQIR+IMi/ZTiFIwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQCLGrLJXWG04bkruVPRsoWdd44W7hE928Jj2VuX
ZfsSZ9gqXLar5V7DtxYvyOirHYr9qxp81V9jz9yw3Xe5qObSIjiHBxTZ/75Wtf0H
DjxVyhbMp6Z3N/vbXB9OWQaHowND9Rart4S9Tu+fMTfwRvFAttEMpWT4Y14h21VO
TzF2nBBhjrZTOqMRvq9tfB69ri3iDGnHhVNoomG6xT60eVR4ngrHAr5i0RGCS2Uv
kVrCqIexVmiUefkl98HVrhq4uz2PqYo4Ffdz0Fpg0YCw8NzVUM1O7pJIae2yIx4w
zMiUyLb1O4Z/P6Yun/Y+LLWSlj7fLJOK/4GMDw9ZIRlXvVWa
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB
kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw
IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG
EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD
VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu
dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6
E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ
D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK
4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq
lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW
bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB
o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT
MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js
LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr
BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB
AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft
Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj
j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH
KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv
2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3
mfnGV/TJVTl4uix5yaaIK/QI
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEojCCA4qgAwIBAgIQRL4Mi1AAJLQR0zYlJWfJiTANBgkqhkiG9w0BAQUFADCB
rjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xNjA0BgNVBAMTLVVUTi1VU0VSRmlyc3Qt
Q2xpZW50IEF1dGhlbnRpY2F0aW9uIGFuZCBFbWFpbDAeFw05OTA3MDkxNzI4NTBa
Fw0xOTA3MDkxNzM2NThaMIGuMQswCQYDVQQGEwJVUzELMAkGA1UECBMCVVQxFzAV
BgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5l
dHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cudXNlcnRydXN0LmNvbTE2MDQGA1UE
AxMtVVROLVVTRVJGaXJzdC1DbGllbnQgQXV0aGVudGljYXRpb24gYW5kIEVtYWls
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsjmFpPJ9q0E7YkY3rs3B
YHW8OWX5ShpHornMSMxqmNVNNRm5pELlzkniii8efNIxB8dOtINknS4p1aJkxIW9
hVE1eaROaJB7HHqkkqgX8pgV8pPMyaQylbsMTzC9mKALi+VuG6JG+ni8om+rWV6l
L8/K2m2qL+usobNqqrcuZzWLeeEeaYji5kbNoKXqvgvOdjp6Dpvq/NonWz1zHyLm
SGHGTPNpsaguG7bUMSAsvIKKjqQOpdeJQ/wWWq8dcdcRWdq6hw2v+vPhwvCkxWeM
1tZUOt4KpLoDd7NlyP0e03RiqhjKaJMeoYV+9Udly/hNVyh00jT/MLbu9mIwFIws
6wIDAQABo4G5MIG2MAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSJgmd9xJ0mcABLtFBIfN49rgRufTBYBgNVHR8EUTBPME2gS6BJhkdodHRw
Oi8vY3JsLnVzZXJ0cnVzdC5jb20vVVROLVVTRVJGaXJzdC1DbGllbnRBdXRoZW50
aWNhdGlvbmFuZEVtYWlsLmNybDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
AwQwDQYJKoZIhvcNAQEFBQADggEBALFtYV2mGn98q0rkMPxTbyUkxsrt4jFcKw7u
7mFVbwQ+zznexRtJlOTrIEy05p5QLnLZjfWqo7NK2lYcYJeA3IKirUq9iiv/Cwm0
xtcgBEXkzYABurorbs6q15L+5K/r9CYdFip/bDCVNy8zEqx/3cfREYxRmLLQo5HQ
rfafnoOTHh1CuEava2bwm3/q4wMC5QJRwarVNZ1yQAOJujEdxRBoUp7fooXFXAim
eOZTT7Hot9MUnpOmw2TjrH5xzbyf6QMbzPvprDHBr3wVdAKZw7JHpsIyYdfHb0gk
USeh1YdV8nuPmD0Wnu51tvjQjvLzxq4oW6fw8zYX/MMF08oDSlQ=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEZjCCA06gAwIBAgIQRL4Mi1AAJLQR0zYt4LNfGzANBgkqhkiG9w0BAQUFADCB
lTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHTAbBgNVBAMTFFVUTi1VU0VSRmlyc3Qt
T2JqZWN0MB4XDTk5MDcwOTE4MzEyMFoXDTE5MDcwOTE4NDAzNlowgZUxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJVVDEXMBUGA1UEBxMOU2FsdCBMYWtlIENpdHkxHjAc
BgNVBAoTFVRoZSBVU0VSVFJVU1QgTmV0d29yazEhMB8GA1UECxMYaHR0cDovL3d3
dy51c2VydHJ1c3QuY29tMR0wGwYDVQQDExRVVE4tVVNFUkZpcnN0LU9iamVjdDCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6qgT+jo2F4qjEAVZURnicP
HxzfOpuCaDDASmEd8S8O+r5596Uj71VRloTN2+O5bj4x2AogZ8f02b+U60cEPgLO
KqJdhwQJ9jCdGIqXsqoc/EHSoTbL+z2RuufZcDX65OeQw5ujm9M89RKZd7G3CeBo
5hy485RjiGpq/gt2yb70IuRnuasaXnfBhQfdDWy/7gbHd2pBnqcP1/vulBe3/IW+
pKvEHDHd17bR5PDv3xaPslKT16HUiaEHLr/hARJCHhrh2JU022R5KP+6LhHC5ehb
kkj7RwvCbNqtMoNB86XlQXD9ZZBt+vpRxPm9lisZBCzTbafc8H9vg2XiaquHhnUC
AwEAAaOBrzCBrDALBgNVHQ8EBAMCAcYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU2u1kdBScFDyr3ZmpvVsoTYs8ydgwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov
L2NybC51c2VydHJ1c3QuY29tL1VUTi1VU0VSRmlyc3QtT2JqZWN0LmNybDApBgNV
HSUEIjAgBggrBgEFBQcDAwYIKwYBBQUHAwgGCisGAQQBgjcKAwQwDQYJKoZIhvcN
AQEFBQADggEBAAgfUrE3RHjb/c652pWWmKpVZIC1WkDdIaXFwfNfLEzIR1pp6ujw
NTX00CXzyKakh0q9G7FzCL3Uw8q2NbtZhncxzaeAFK4T7/yxSPlrJSUtUbYsbUXB
mMiKVl0+7kNOPmsnjtA6S4ULX9Ptaqd1y9Fahy85dRNacrACgZ++8A+EVCBibGnU
4U3GDZlDAQ0Slox4nb9QorFEqmrPF3rPbw/U+CRVX/A0FklmPlBGyWNxODFiuGK5
81OtbLUrohKqGU8J2l7nk8aOFAj+8DCAGKCGhU3IfdeLA/5u1fedFqySLKAj5ZyR
Uh+U3xeUc8OzwcFxBSAAeL0TUh2oPs0AH8g=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
tGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIG0TCCBbmgAwIBAgIBezANBgkqhkiG9w0BAQUFADCByTELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMUIwQAYDVQQD
EzlOZXRMb2NrIE1pbm9zaXRldHQgS296amVneXpvaSAoQ2xhc3MgUUEpIFRhbnVz
aXR2YW55a2lhZG8xHjAcBgkqhkiG9w0BCQEWD2luZm9AbmV0bG9jay5odTAeFw0w
MzAzMzAwMTQ3MTFaFw0yMjEyMTUwMTQ3MTFaMIHJMQswCQYDVQQGEwJIVTERMA8G
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxQjBABgNVBAMTOU5l
dExvY2sgTWlub3NpdGV0dCBLb3pqZWd5em9pIChDbGFzcyBRQSkgVGFudXNpdHZh
bnlraWFkbzEeMBwGCSqGSIb3DQEJARYPaW5mb0BuZXRsb2NrLmh1MIIBIjANBgkq
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx1Ilstg91IRVCacbvWy5FPSKAtt2/Goq
eKvld/Bu4IwjZ9ulZJm53QE+b+8tmjwi8F3JV6BVQX/yQ15YglMxZc4e8ia6AFQe
r7C8HORSjKAyr7c3sVNnaHRnUPYtLmTeriZ539+Zhqurf4XsoPuAzPS4DB6TRWO5
3Lhbm+1bOdRfYrCnjnxmOCyqsQhjF2d9zL2z8cM/z1A57dEZgxXbhxInlrfa6uWd
vLrqOU+L73Sa58XQ0uqGURzk/mQIKAR5BevKxXEOC++r6uwSEaEYBTJp0QwsGj0l
mT+1fMptsK6ZmfoIYOcZwvK9UdPM0wKswREMgM6r3JSda6M5UzrWhQIDAMV9o4IC
wDCCArwwEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8EBAMCAQYwggJ1Bglg
hkgBhvhCAQ0EggJmFoICYkZJR1lFTEVNISBFemVuIHRhbnVzaXR2YW55IGEgTmV0
TG9jayBLZnQuIE1pbm9zaXRldHQgU3pvbGdhbHRhdGFzaSBTemFiYWx5emF0YWJh
biBsZWlydCBlbGphcmFzb2sgYWxhcGphbiBrZXN6dWx0LiBBIG1pbm9zaXRldHQg
ZWxla3Ryb25pa3VzIGFsYWlyYXMgam9naGF0YXMgZXJ2ZW55ZXN1bGVzZW5laywg
dmFsYW1pbnQgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYSBNaW5vc2l0ZXR0IFN6
b2xnYWx0YXRhc2kgU3phYmFseXphdGJhbiwgYXogQWx0YWxhbm9zIFN6ZXJ6b2Rl
c2kgRmVsdGV0ZWxla2JlbiBlbG9pcnQgZWxsZW5vcnplc2kgZWxqYXJhcyBtZWd0
ZXRlbGUuIEEgZG9rdW1lbnR1bW9rIG1lZ3RhbGFsaGF0b2sgYSBodHRwczovL3d3
dy5uZXRsb2NrLmh1L2RvY3MvIGNpbWVuIHZhZ3kga2VyaGV0b2sgYXogaW5mb0Bu
ZXRsb2NrLm5ldCBlLW1haWwgY2ltZW4uIFdBUk5JTkchIFRoZSBpc3N1YW5jZSBh
bmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGFyZSBzdWJqZWN0IHRvIHRo
ZSBOZXRMb2NrIFF1YWxpZmllZCBDUFMgYXZhaWxhYmxlIGF0IGh0dHBzOi8vd3d3
Lm5ldGxvY2suaHUvZG9jcy8gb3IgYnkgZS1tYWlsIGF0IGluZm9AbmV0bG9jay5u
ZXQwHQYDVR0OBBYEFAlqYhaSsFq7VQ7LdTI6MuWyIckoMA0GCSqGSIb3DQEBBQUA
A4IBAQCRalCc23iBmz+LQuM7/KbD7kPgz/PigDVJRXYC4uMvBcXxKufAQTPGtpvQ
MznNwNuhrWw3AkxYQTvyl5LGSKjN5Yo5iWH5Upfpvfb5lHTocQ68d4bDBsxafEp+
NFAwLvt/MpqNPfMgW/hqyobzMUwsWYACff44yTB1HLdV47yfuqhthCgFdbOLDcCR
VCHnpgu0mfVRQdzNo0ci2ccBgcTcR08m6h/t280NmPSjnLRzMkqWmf68f8glWPhY
83ZmiVSkpj7EUFy6iRiCdUgh0k8T6GB+B3bbELVR5qq5aKrN9p2QdRLqOBrKROi3
macqaJVmlaut74nLYKkGEsaUR+ko
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
NitjrFgBazMpUIaD8QFI
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
Fp1hBWeAyNDYpQcCNJgEjTME1A==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
O+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx
CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp
ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa
QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw
NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft
ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu
QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq
hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG
qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL
fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ
Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4
Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ
54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b
MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j
ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej
YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt
A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF
rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ
pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB
lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy
YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50
7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs
YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6
xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc
unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/
Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp
ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42
gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0
jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+
XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD
W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/
RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r
MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk
BYn8eNZcLCZDqQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
KeC2uAloGRwYQw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
rosot4LKGAfmt1t06SAZf7IbiVQ=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDzzCCAregAwIBAgIDAWweMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYDVQQGEwJB
VDFIMEYGA1UECgw/QS1UcnVzdCBHZXMuIGYuIFNpY2hlcmhlaXRzc3lzdGVtZSBp
bSBlbGVrdHIuIERhdGVudmVya2VociBHbWJIMRkwFwYDVQQLDBBBLVRydXN0LW5R
dWFsLTAzMRkwFwYDVQQDDBBBLVRydXN0LW5RdWFsLTAzMB4XDTA1MDgxNzIyMDAw
MFoXDTE1MDgxNzIyMDAwMFowgY0xCzAJBgNVBAYTAkFUMUgwRgYDVQQKDD9BLVRy
dXN0IEdlcy4gZi4gU2ljaGVyaGVpdHNzeXN0ZW1lIGltIGVsZWt0ci4gRGF0ZW52
ZXJrZWhyIEdtYkgxGTAXBgNVBAsMEEEtVHJ1c3QtblF1YWwtMDMxGTAXBgNVBAMM
EEEtVHJ1c3QtblF1YWwtMDMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCtPWFuA/OQO8BBC4SAzewqo51ru27CQoT3URThoKgtUaNR8t4j8DRE/5TrzAUj
lUC5B3ilJfYKvUWG6Nm9wASOhURh73+nyfrBJcyFLGM/BWBzSQXgYHiVEEvc+RFZ
znF/QJuKqiTfC0Li21a8StKlDJu3Qz7dg9MmEALP6iPESU7l0+m0iKsMrmKS1GWH
2WrX9IWf5DMiJaXlyDO6w8dB3F/GaswADm0yqLaHNgBid5seHzTLkDx4iHQF63n1
k3Flyp3HaxgtPVxO59X4PzF9j4fsCiIvI+n+u33J4PTs63zEsMMtYrWacdaxaujs
2e3Vcuy+VwHOBVWf3tFgiBCzAgMBAAGjNjA0MA8GA1UdEwEB/wQFMAMBAf8wEQYD
VR0OBAoECERqlWdVeRFPMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAVdRU0VlIXLOThaq/Yy/kgM40ozRiPvbY7meIMQQDbwvUB/tOdQ/TLtPAF8fG
KOwGDREkDg6lXb+MshOWcdzUzg4NCmgybLlBMRmrsQd7TZjTXLDR8KdCoLXEjq/+
8T/0709GAHbrAvv5ndJAlseIOrifEXnzgGWovR/TeIGgUUw3tKZdJXDRZslo+S4R
FGjxVJgIrCaSD96JntT6s3kr0qN51OyLrIdTaEJMUVF0HhsnLuP1Hyl0Te2v9+GS
mYHovjrHF1D2t8b8m7CKa9aIA5GPBnc6hQLdmNVDeD/GMBWsm2vLV7eJUYs66MmE
DNuxUCAKGkq6ahq97BvIxYSazQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL
MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg
isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z
NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI
+MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R
hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+
mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD
AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP
Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s
EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2
mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC
e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow
dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
d0jQ
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
wy39FCqQmbkHzJ8=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
ZQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
NVOFBkpdn627G190
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDkzCCAnugAwIBAgIQFBOWgxRVjOp7Y+X8NId3RDANBgkqhkiG9w0BAQUFADA0
MRMwEQYDVQQDEwpDb21TaWduIENBMRAwDgYDVQQKEwdDb21TaWduMQswCQYDVQQG
EwJJTDAeFw0wNDAzMjQxMTMyMThaFw0yOTAzMTkxNTAyMThaMDQxEzARBgNVBAMT
CkNvbVNpZ24gQ0ExEDAOBgNVBAoTB0NvbVNpZ24xCzAJBgNVBAYTAklMMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8ORUaSvTx49qROR+WCf4C9DklBKK
8Rs4OC8fMZwG1Cyn3gsqrhqg455qv588x26i+YtkbDqthVVRVKU4VbirgwTyP2Q2
98CNQ0NqZtH3FyrV7zb6MBBC11PN+fozc0yz6YQgitZBJzXkOPqUm7h65HkfM/sb
2CEJKHxNGGleZIp6GZPKfuzzcuc3B1hZKKxC+cX/zT/npfo4sdAMx9lSGlPWgcxC
ejVb7Us6eva1jsz/D3zkYDaHL63woSV9/9JLEYhwVKZBqGdTUkJe5DSe5L6j7Kpi
Xd3DTKaCQeQzC6zJMw9kglcq/QytNuEMrkvF7zuZ2SOzW120V+x0cAwqTwIDAQAB
o4GgMIGdMAwGA1UdEwQFMAMBAf8wPQYDVR0fBDYwNDAyoDCgLoYsaHR0cDovL2Zl
ZGlyLmNvbXNpZ24uY28uaWwvY3JsL0NvbVNpZ25DQS5jcmwwDgYDVR0PAQH/BAQD
AgGGMB8GA1UdIwQYMBaAFEsBmz5WGmU2dst7l6qSBe4y5ygxMB0GA1UdDgQWBBRL
AZs+VhplNnbLe5eqkgXuMucoMTANBgkqhkiG9w0BAQUFAAOCAQEA0Nmlfv4pYEWd
foPPbrxHbvUanlR2QnG0PFg/LUAlQvaBnPGJEMgOqnhPOAlXsDzACPw1jvFIUY0M
cXS6hMTXcpuEfDhOZAYnKuGntewImbQKDdSFc8gS4TXt8QUxHXOZDOuWyt3T5oWq
8Ir7dcHyCTxlZWTzTNity4hp8+SDtwy9F1qWF8pb/627HOkthIDYIb6FUtnUdLlp
hbpN7Sgy6/lhSuTENh4Z3G+EER+V9YMoGKgzkkMn3V0TBEVPh9VGzT2ouvDzuFYk
Res3x+F2T3I5GN9+dHLHcy056mDmrRGiVod7w2ia/viMcKjfZTL0pECMocJEAw6U
AGegcQCCSA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw
PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu
MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx
GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL
MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf
HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh
gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW
v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue
Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr
9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt
6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7
MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl
Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58
ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq
hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p
iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC
dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL
kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL
hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz
OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
6pZjamVFkpUBtA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
sycX
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
Johw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
4iIprn2DQKi6bA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
rD6ogRLQy7rQkgu2npaqBA+K
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
spki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
ewv4n4Q=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
WD9f
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
l7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
AmvZWg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
TbvGRNs2yyqcjg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
/L7fCg0=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
4Pw5qlPafX7PGglTvFOFBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
QSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIGGTCCBAGgAwIBAgIIPtVRGeZNzn4wDQYJKoZIhvcNAQELBQAwajEhMB8GA1UE
AxMYU0cgVFJVU1QgU0VSVklDRVMgUkFDSU5FMRwwGgYDVQQLExMwMDAyIDQzNTI1
Mjg5NTAwMDIyMRowGAYDVQQKExFTRyBUUlVTVCBTRVJWSUNFUzELMAkGA1UEBhMC
RlIwHhcNMTAwOTA2MTI1MzQyWhcNMzAwOTA1MTI1MzQyWjBqMSEwHwYDVQQDExhT
RyBUUlVTVCBTRVJWSUNFUyBSQUNJTkUxHDAaBgNVBAsTEzAwMDIgNDM1MjUyODk1
MDAwMjIxGjAYBgNVBAoTEVNHIFRSVVNUIFNFUlZJQ0VTMQswCQYDVQQGEwJGUjCC
AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANqoVgLsfJXwTukK0rcHoyKL
ULO5Lhk9V9sZqtIr5M5C4myh5F0lHjMdtkXRtPpZilZwyW0IdmlwmubHnAgwE/7m
0ZJoYT5MEfJu8rF7V1ZLCb3cD9lxDOiaN94iEByZXtaxFwfTpDktwhpz/cpLKQfC
eSnIyCauLMT8I8hL4oZWDyj9tocbaF85ZEX9aINsdSQePHWZYfrSFPipS7HYfad4
0hNiZbXWvn5qA7y1svxkMMPQwpk9maTTzdGxxFOHe0wTE2Z/v9VlU2j5XB7ltP82
mUWjn2LAfxGCAVTeD2WlOa6dSEyJoxA74OaD9bDaLB56HFwfAKzMq6dgZLPGxXvH
VUZ0PJCBDkqOWZ1UsEixUkw7mO6r2jS3U81J2i/rlb4MVxH2lkwEeVyZ1eXkvm/q
R+5RS+8iJq612BGqQ7t4vwt+tN3PdB0lqYljseI0gcSINTjiAg0PE8nVKoIV8IrE
QzJW5FMdHay2z32bll0eZOl0c8RW5BZKUm2SOdPhTQ4/YrnerbUdZbldUv5dCamc
tKQM2S9FdqXPjmqanqqwEaHrYcbrPx78ZrQSnUZ/MhaJvnFFr5Eh2f2Tv7QCkUL/
SR/tixVo3R+OrJvdggWcRGkWZBdWX0EPSk8ED2VQhpOX7EW/XcIc3M/E2DrmeAXQ
xVVVqV7+qzohu+VyFPcLAgMBAAGjgcIwgb8wHQYDVR0OBBYEFCkgy/HDD9oGjhOT
h/5fYBopu/O2MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUKSDL8cMP2gaO
E5OH/l9gGim787YwEQYDVR0gBAowCDAGBgRVHSAAMEkGA1UdHwRCMEAwPqA8oDqG
OGh0dHA6Ly9jcmwuc2d0cnVzdHNlcnZpY2VzLmNvbS9yYWNpbmUtR3JvdXBlU0cv
TGF0ZXN0Q1JMMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEATEZn
4ERQ9cW2urJRCiUTHbfHiC4fuStkoMuTiFJZqmD1zClSF/8E5ze0MRFGfisebKeL
PEeaXvSqXZA7RT2fSsmKe47A7j55i5KjyJRKuCgRa6YlX129x8j7g09VMeZc8BN8
471/Kiw3N5RJr4QfFCeiWBCPCjk3GhIgQY8Z9qkfGe2yNLKtfTNEi18KB0PydkVF
La3kjQ4A/QQIqudr+xe9sAhWDjUqcvCz5006Tw3c82ASszhkjNv54SaNL+9O6CRH
PjY0imkPKGuLh8a9hSb50+tpIVZgkdb34GLCqHGuLt5mI7VSRqakSDcsfwEWVxH3
Jw0O5Q/WkEXhHj8h3NL8FhgTPk1qsiZqQF4leP049KxYejcbmEAEx47J1MRnYbGY
rvDNDty5r2WDewoEij9hqvddQYbmxkzCTzpcVuooO6dEz8hKZPVyYC3jQ7hK4HU8
MuSqFtcRucFF2ZtmY2blIrc07rrVdC8lZPOBVMt33lfUk+OsBzE6PlwDg1dTx/D+
aNglUE0SyObhlY1nqzyTPxcCujjXnvcwpT09RAEzGpqfjtCf8e4wiHPvriQZupdz
FcHscQyEZLV77LxpPqRtCRY2yko5isune8YdfucziMm+MG2chZUh6Uc7Bn6B4upG
5nBYgOao8p0LadEziVkw82TTC/bOKwn7fRB2LhA=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y
MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg
TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS
b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS
M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC
UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d
Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p
rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l
pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb
j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC
KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS
/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X
cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH
1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP
px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7
MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u
2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS
v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC
wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy
CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e
vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6
Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa
Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL
eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8
FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc
7uzXLg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX
DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP
cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW
IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX
xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy
KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR
9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az
5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8
6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7
Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP
bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt
BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt
XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd
INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp
LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8
Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp
gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh
/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw
0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A
fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq
4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR
1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/
QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM
94B7IWcnMFk=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
sSi6
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
fyWl8kgAwKQB2j8=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEezCCA2OgAwIBAgIQNxkY5lNUfBq1uMtZWts1tzANBgkqhkiG9w0BAQUFADCB
rjELMAkGA1UEBhMCREUxIDAeBgNVBAgTF0JhZGVuLVd1ZXJ0dGVtYmVyZyAoQlcp
MRIwEAYDVQQHEwlTdHV0dGdhcnQxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fz
c2VuIFZlcmxhZyBHbWJIMT4wPAYDVQQDEzVTLVRSVVNUIEF1dGhlbnRpY2F0aW9u
IGFuZCBFbmNyeXB0aW9uIFJvb3QgQ0EgMjAwNTpQTjAeFw0wNTA2MjIwMDAwMDBa
Fw0zMDA2MjEyMzU5NTlaMIGuMQswCQYDVQQGEwJERTEgMB4GA1UECBMXQmFkZW4t
V3VlcnR0ZW1iZXJnIChCVykxEjAQBgNVBAcTCVN0dXR0Z2FydDEpMCcGA1UEChMg
RGV1dHNjaGVyIFNwYXJrYXNzZW4gVmVybGFnIEdtYkgxPjA8BgNVBAMTNVMtVFJV
U1QgQXV0aGVudGljYXRpb24gYW5kIEVuY3J5cHRpb24gUm9vdCBDQSAyMDA1OlBO
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2bVKwdMz6tNGs9HiTNL1
toPQb9UY6ZOvJ44TzbUlNlA0EmQpoVXhOmCTnijJ4/Ob4QSwI7+Vio5bG0F/WsPo
TUzVJBY+h0jUJ67m91MduwwA7z5hca2/OnpYH5Q9XIHV1W/fuJvS9eXLg3KSwlOy
ggLrra1fFi2SU3bxibYs9cEv4KdKb6AwajLrmnQDaHgTncovmwsdvs91DSaXm8f1
XgqfeN+zvOyauu9VjxuapgdjKRdZYgkqeQd3peDRF2npW932kKvimAoA0SVtnteF
hy+S8dF2g08LOlk3KC8zpxdQ1iALCvQm+Z845y2kuJuJja2tyWp9iRe79n+Ag3rm
7QIDAQABo4GSMIGPMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgEG
MCkGA1UdEQQiMCCkHjAcMRowGAYDVQQDExFTVFJvbmxpbmUxLTIwNDgtNTAdBgNV
HQ4EFgQUD8oeXHngovMpttKFswtKtWXsa1IwHwYDVR0jBBgwFoAUD8oeXHngovMp
ttKFswtKtWXsa1IwDQYJKoZIhvcNAQEFBQADggEBAK8B8O0ZPCjoTVy7pWMciDMD
pwCHpB8gq9Yc4wYfl35UvbfRssnV2oDsF9eK9XvCAPbpEW+EoFolMeKJ+aQAPzFo
LtU96G7m1R08P7K9n3frndOMusDXtk3sU5wPBG7qNWdX4wple5A64U8+wwCSersF
iXOMy6ZNwPv2AtawB6MDwidAnwzkhYItr5pCHdDHjfhA7p0GVxzZotiAFP7hYy0y
h9WUUpY6RsZxlj33mA6ykaqP2vROJAA5VeitF7nTNCtKqUDMFypVZUF0Qn71wK/I
k63yGFs9iQzbRzkk+OBM8h+wPQrKBU6JIRrjKpms/H+h8Q8bHz2eBIPdltkdOpQ=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID2DCCAsCgAwIBAgIQYFbFSyNAW2TU7SXa2dYeHjANBgkqhkiG9w0BAQsFADCB
hTELMAkGA1UEBhMCREUxKTAnBgNVBAoTIERldXRzY2hlciBTcGFya2Fzc2VuIFZl
cmxhZyBHbWJIMScwJQYDVQQLEx5TLVRSVVNUIENlcnRpZmljYXRpb24gU2Vydmlj
ZXMxIjAgBgNVBAMTGVMtVFJVU1QgVW5pdmVyc2FsIFJvb3QgQ0EwHhcNMTMxMDIy
MDAwMDAwWhcNMzgxMDIxMjM1OTU5WjCBhTELMAkGA1UEBhMCREUxKTAnBgNVBAoT
IERldXRzY2hlciBTcGFya2Fzc2VuIFZlcmxhZyBHbWJIMScwJQYDVQQLEx5TLVRS
VVNUIENlcnRpZmljYXRpb24gU2VydmljZXMxIjAgBgNVBAMTGVMtVFJVU1QgVW5p
dmVyc2FsIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo
4wvfETeFgpq1bGZ8YT/ARxodRuOwVWTluII5KAd+F//0m4rwkYHqOD8heGxI7Gsv
otOKcrKn19nqf7TASWswJYmM67fVQGGY4tw8IJLNZUpynxqOjPolFb/zIYMoDYuv
WRGCQ1ybTSVRf1gYY2A7s7WKi1hjN0hIkETCQN1d90NpKZhcEmVeq5CSS2bf1XUS
U1QYpt6K1rtXAzlZmRgFDPn9FcaQZEYXgtfCSkE9/QC+V3IYlHcbU1qJAfYzcg6T
OtzoHv0FBda8c+CI3KtP7LUYhk95hA5IKmYq3TLIeGXIC51YAQVx7YH1aBduyw20
S9ih7K446xxYL6FlAzQvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0P
AQH/BAQDAgEGMB0GA1UdDgQWBBSafdfr639UmEUptCCrbQuWIxmkwjANBgkqhkiG
9w0BAQsFAAOCAQEATpYS2353XpInniEXGIJ22D+8pQkEZoiJrdtVszNqxmXEj03z
MjbceQSWqXcy0Zf1GGuMuu3OEdBEx5LxtESO7YhSSJ7V/Vn4ox5R+wFS5V/let2q
JE8ii912RvaloA812MoPmLkwXSBvwoEevb3A/hXTOCoJk5gnG5N70Cs0XmilFU/R
UsOgyqCDRR319bdZc11ZAY+qwkcvFHHVKeMQtUeTJcwjKdq3ctiR1OwbSIoi5MEq
9zpok59FGW5Dt8z+uJGaYRo2aWNkkijzb2GShROfyQcsi1fc65551cLeCNVUsldO
KjKNoeI60RAgIjl9NEVvcTvDHfz/sk+o4vYwHg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
MBr1mmz0DlP5OlvRHA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
wSsSnqaeG8XmDtkx2Q==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
I+2ksx0WckNLIOFZfsLorSa/ovc=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFwTCCA6mgAwIBAgIITrIAZwwDXU8wDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEjMCEGA1UEAxMaU3dpc3NTaWdu
IFBsYXRpbnVtIENBIC0gRzIwHhcNMDYxMDI1MDgzNjAwWhcNMzYxMDI1MDgzNjAw
WjBJMQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMSMwIQYDVQQD
ExpTd2lzc1NpZ24gUGxhdGludW0gQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQAD
ggIPADCCAgoCggIBAMrfogLi2vj8Bxax3mCq3pZcZB/HL37PZ/pEQtZ2Y5Wu669y
IIpFR4ZieIbWIDkm9K6j/SPnpZy1IiEZtzeTIsBQnIJ71NUERFzLtMKfkr4k2Htn
IuJpX+UFeNSH2XFwMyVTtIc7KZAoNppVRDBopIOXfw0enHb/FZ1glwCNioUD7IC+
6ixuEFGSzH7VozPY1kneWCqv9hbrS3uQMpe5up1Y8fhXSQQeol0GcN1x2/ndi5ob
jM89o03Oy3z2u5yg+gnOI2Ky6Q0f4nIoj5+saCB9bzuohTEJfwvH6GXp43gOCWcw
izSC+13gzJ2BbWLuCB4ELE6b7P6pT1/9aXjvCR+htL/68++QHkwFix7qepF6w9fl
+zC8bBsQWJj3Gl/QKTIDE0ZNYWqFTFJ0LwYfexHihJfGmfNtf9dng34TaNhxKFrY
zt3oEBSa/m0jh26OWnA81Y0JAKeqvLAxN23IhBQeW71FYyBrS3SMvds6DsHPWhaP
pZjydomyExI7C3d3rLvlPClKknLKYRorXkzig3R3+jVIeoVNjZpTxN94ypeRSCtF
KwH3HBqi7Ri6Cr2D+m+8jVeTO9TUps4e8aCxzqv9KyiaTxvXw3LbpMS/XUz13XuW
ae5ogObnmLo2t/5u7Su9IPhlGdpVCX4l3P5hYnL5fhgC72O00Puv5TtjjGePAgMB
AAGjgawwgakwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFFCvzAeHFUdvOMW0ZdHelarp35zMMB8GA1UdIwQYMBaAFFCvzAeHFUdvOMW0
ZdHelarp35zMMEYGA1UdIAQ/MD0wOwYJYIV0AVkBAQEBMC4wLAYIKwYBBQUHAgEW
IGh0dHA6Ly9yZXBvc2l0b3J5LnN3aXNzc2lnbi5jb20vMA0GCSqGSIb3DQEBBQUA
A4ICAQAIhab1Fgz8RBrBY+D5VUYI/HAcQiiWjrfFwUF1TglxeeVtlspLpYhg0DB0
uMoI3LQwnkAHFmtllXcBrqS3NQuB2nEVqXQXOHtYyvkv+8Bldo1bAbl93oI9ZLi+
FHSjClTTLJUYFzX1UWs/j6KWYTl4a0vlpqD4U99REJNi54Av4tHgvI42Rncz7Lj7
jposiU0xEQ8mngS7twSNC/K5/FqdOxa3L8iYq/6KUFkuozv8KV2LwUvJ4ooTHbG/
u0IdUt1O2BReEMYxB+9xJ/cbOQncguqLs5WGXv312l0xpuAxtpTmREl0xRbl9x8D
YSjFyMsSoEJL+WuICI20MhjzdZ/EfwBPBZWcoxcCw7NTm6ogOSkrZvqdr16zktK1
puEa+S1BaYEUtLS17Yk9zvupnTVCRLEcFHOBzyoBNZox1S2PbYTfgE1X4z/FhHXa
icYwu+uPyyIIoK6q8QNsOktNCaUOcsZWayFCTiMlFGiudgp8DAdwZPmaL/YFOSbG
DI8Zf0NebvRbFS/bYV3mZy8/CJT5YLSYMdp08YSTcU1f+2BY0fvEwW2JorsgH51x
kcsymxM9Pn2SUjWskpSi0xjCfMfqr3YFFt1nJ8J+HAciIfNAChs0B0QTwoRqjt8Z
Wr9/6x3iGjjRXK9HkmuAtTClyY3YqzGBH9/CZjfTk6mFhnll0g==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
pYYsfPQS
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1
OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc
VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf
tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg
uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J
XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK
8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99
5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3
kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6
Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS
GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt
ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8
au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV
hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI
dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV
BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0
Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1
OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i
SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc
VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW
Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q
Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2
1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq
ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1
Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX
XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy
dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6
Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz
JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290
Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN
irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8
TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6
g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB
95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj
S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL
MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV
BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1
c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx
MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg
R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD
VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR
JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T
fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu
jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z
wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ
fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD
VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G
CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1
7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn
8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs
ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT
ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/
2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDNjCCAp+gAwIBAgIQNhIilsXjOKUgodJfTNcJVDANBgkqhkiG9w0BAQUFADCB
zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIxMDEwMTIzNTk1OVow
gc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcT
CUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNV
BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRo
YXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1z
ZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560
ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
+ao6hnO2RlNYyIkFvYMRuHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBlkKyID1bZ5jA01CbH0FDxkt5r1DmI
CSLGpmODA/eZd9iy5Ri4XWPz1HP7bJyZePFLeH0ZJMMrAoT4vCLZiiLXoPxx7JGH
IPG47LHlVYCsPVLIOQ7C8MAFT9aCdYy9X9LcdpoFEsmvcsPcJX6kTY4XpeCHf+Ga
WuFg3GQjPEIuTQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
jVaMaA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
MdRAGmI0Nj81Aa6sY6A=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
ZetX2fNXlrtIzYE=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
BSeOE6Fuwg==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
TpPDpFQUWw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
yZyQ2uypQjyttgI=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc
UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg
MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz
MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy
dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD
VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg
xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu
xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7
XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k
heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J
YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C
urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1
JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51
b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV
9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7
kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh
fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy
B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA
aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS
RGQDJereW26fyfJOrN3H
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEPDCCAySgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvjE/MD0GA1UEAww2VMOc
UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xS
S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
SGl6bWV0bGVyaSBBLsWeLiAoYykgS2FzxLFtIDIwMDUwHhcNMDUxMTA3MTAwNzU3
WhcNMTUwOTE2MTAwNzU3WjCBvjE/MD0GA1UEAww2VMOcUktUUlVTVCBFbGVrdHJv
bmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxMQswCQYDVQQGEwJU
UjEPMA0GA1UEBwwGQW5rYXJhMV0wWwYDVQQKDFRUw5xSS1RSVVNUIEJpbGdpIMSw
bGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWe
LiAoYykgS2FzxLFtIDIwMDUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCpNn7DkUNMwxmYCMjHWHtPFoylzkkBH3MOrHUTpvqeLCDe2JAOCtFp0if7qnef
J1Il4std2NiDUBd9irWCPwSOtNXwSadktx4uXyCcUHVPr+G1QRT0mJKIx+XlZEdh
R3n9wFHxwZnn3M5q+6+1ATDcRhzviuyV79z/rxAc653YsKpqhRgNF8k+v/Gb0AmJ
Qv2gQrSdiVFVKc8bcLyEVK3BEx+Y9C52YItdP5qtygy/p1Zbj3e41Z55SZI/4PGX
JHpsmxcPbe9TmJEr5A++WXkHeLuXlfSfadRYhwqp48y2WBmfJiGxxFmNskF1wK1p
zpwACPI2/z7woQ8arBT9pmAPAgMBAAGjQzBBMB0GA1UdDgQWBBTZN7NOBf3Zz58S
Fq62iS/rJTqIHDAPBgNVHQ8BAf8EBQMDBwYAMA8GA1UdEwEB/wQFMAMBAf8wDQYJ
KoZIhvcNAQEFBQADggEBAHJglrfJ3NgpXiOFX7KzLXb7iNcX/nttRbj2hWyfIvwq
ECLsqrkw9qtY1jkQMZkpAL2JZkH7dN6RwRgLn7Vhy506vvWolKMiVW4XSf/SKfE4
Jl3vpao6+XF75tpYHdN0wgH6PmlYX63LaL4ULptswLbcoCb6dxriJNoaN+BnrdFz
gw2lGh1uEpJ+hGIAF728JRhX8tepb1mIvDS3LoV4nZbcFMMsilKbloxSZj2GFotH
uFEJjOp9zYhys2AzsfAKRO8P9Qk3iCQOLGsgOqL6EfJANZxEaGM7rDNvY7wsu/LS
y3Z9fYjYHcgFHW68lKlmjHdxx/qR+i9Rnuk5UrbnBEI=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
KwbQBM0=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICPDCCAaUCED9pHoGc8JpK83P/uUii5N0wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAxIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAxIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDlGb9to1ZhLZlIcfZn3rmN67eehoAKkQ76OCWvRoiC5XOooJskXQ0f
zGVuDLDQVoQYh5oGmxChc9+0WDlrbsH2FdWoqD+qEgaNMax/sDTXjzRniAnNFBHi
TkVWaR94AoDa3EeRKbs2yWNcxeDXLYd7obcysHswuiovMaruo2fa2wIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBAFgVKTk8d6PaXCUDfGD67gmZPCcQcMgMCeazh88K4hiW
NWLMv5sneYlfycQJ9M61Hd8qveXbhpxoJeUwfLaJFf5n0a3hUKw8fGJLj7qE1xIV
Gx/KXQ/BUpQqEZnae88MNhPVNdwQGVnqlMEAv3WP2fr9dgTbYruQagPZRjXZ+Hxb
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
2G0xffX8oRAHh84vWdw+WNs=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
ee5Ehr7XHuQe+w==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIIEMDCCA5mgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBxzELMAkGA1UEBhMCVVMx
FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMR8wHQYDVQQHExZSZXNlYXJjaCBUcmlh
bmdsZSBQYXJrMRYwFAYDVQQKEw1SZWQgSGF0LCBJbmMuMSEwHwYDVQQLExhSZWQg
SGF0IE5ldHdvcmsgU2VydmljZXMxIzAhBgNVBAMTGlJITlMgQ2VydGlmaWNhdGUg
QXV0aG9yaXR5MR4wHAYJKoZIhvcNAQkBFg9yaG5zQHJlZGhhdC5jb20wHhcNMDAw
ODIzMjI0NTU1WhcNMDMwODI4MjI0NTU1WjCBxzELMAkGA1UEBhMCVVMxFzAVBgNV
BAgTDk5vcnRoIENhcm9saW5hMR8wHQYDVQQHExZSZXNlYXJjaCBUcmlhbmdsZSBQ
YXJrMRYwFAYDVQQKEw1SZWQgSGF0LCBJbmMuMSEwHwYDVQQLExhSZWQgSGF0IE5l
dHdvcmsgU2VydmljZXMxIzAhBgNVBAMTGlJITlMgQ2VydGlmaWNhdGUgQXV0aG9y
aXR5MR4wHAYJKoZIhvcNAQkBFg9yaG5zQHJlZGhhdC5jb20wgZ8wDQYJKoZIhvcN
AQEBBQADgY0AMIGJAoGBAMBoKxIw4iEtIsZycVu/F6CTEOmb48mNOy2sxLuVO+DK
VTLclcIQswSyUfvohWEWNKW0HWdcp3f08JLatIuvlZNi82YprsCIt2SEDkiQYPhg
PgB/VN0XpqwY4ELefL6Qgff0BYUKCMzV8p/8JIt3pT3pSKnvDztjo/6mg0zo3At3
AgMBAAGjggEoMIIBJDAdBgNVHQ4EFgQUVBXNnyz37A0f0qi+TAesiD77mwowgfQG
A1UdIwSB7DCB6YAUVBXNnyz37A0f0qi+TAesiD77mwqhgc2kgcowgccxCzAJBgNV
BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEfMB0GA1UEBxMWUmVzZWFy
Y2ggVHJpYW5nbGUgUGFyazEWMBQGA1UEChMNUmVkIEhhdCwgSW5jLjEhMB8GA1UE
CxMYUmVkIEhhdCBOZXR3b3JrIFNlcnZpY2VzMSMwIQYDVQQDExpSSE5TIENlcnRp
ZmljYXRlIEF1dGhvcml0eTEeMBwGCSqGSIb3DQEJARYPcmhuc0ByZWRoYXQuY29t
ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAkwGIiGdnkYye0BIU
kHESh1UK8lIbrfLTBx2vcJm7sM2AI8ntK3PpY7HQs4xgxUJkpsGVVpDFNQYDWPWO
K9n5qaAQqZn3FUKSpVDXEQfxAtXgcORVbirOJfhdzQsvEGH49iBCzMOJ+IpPgiQS
zzl/IagsjVKXUsX3X0KlhwlmsMw=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID7jCCA1egAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsTELMAkGA1UEBhMCVVMx
FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRAwDgYDVQQHEwdSYWxlaWdoMRYwFAYD
VQQKEw1SZWQgSGF0LCBJbmMuMRgwFgYDVQQLEw9SZWQgSGF0IE5ldHdvcmsxIjAg
BgNVBAMTGVJITiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEW
EnJobi1ub2NAcmVkaGF0LmNvbTAeFw0wMjA5MDUyMDQ1MTZaFw0wNzA5MDkyMDQ1
MTZaMIGxMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAO
BgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsT
D1JlZCBIYXQgTmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhv
cml0eTEhMB8GCSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCzFrfF9blpUR/NtD1wz2BXhaQqp10oIg7sGeKS
90iXpqYfUZWDEY+amKKQ4MtKJBmUqIpLiLQGbM531xU7PM1mg88jHQ28CgzLH8tA
+/PZ/iq0hSx7yaH+849oHfISsaQWGc4PuJqc2bxfSWKylZPOXS7deTzxW6a3orU5
DY4SMQIDAQABo4IBEjCCAQ4wHQYDVR0OBBYEFH8bZKEuAsWofbjRsYsGnaOpUGOS
MIHeBgNVHSMEgdYwgdOAFH8bZKEuAsWofbjRsYsGnaOpUGOSoYG3pIG0MIGxMQsw
CQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcTB1Jh
bGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsTD1JlZCBIYXQg
TmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhvcml0eTEhMB8G
CSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEEBQADgYEAKE1C5TQi3caGYwR1UmcXRXLyOyErRVlyc/dZNp1X
Q8bclA8O/xNcT1A3hbLkwh81n3T051P7oQa4Oc7kCoZ7XyhdxxGeEqXWuWzpGAnV
8ELnVLWRniOtEnqqcnw5PIP4daR7A5L/KtTFdhkS+rQ7sIkslYwBkA3YugYFYQCs
ldo=
-----END CERTIFICATE-----

-----BEGIN CERTIFICATE-----
MIID7jCCA1egAwIBAgIBADANBgkqhkiG9w0BAQQFADCBsTELMAkGA1UEBhMCVVMx
FzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRAwDgYDVQQHEwdSYWxlaWdoMRYwFAYD
VQQKEw1SZWQgSGF0LCBJbmMuMRgwFgYDVQQLEw9SZWQgSGF0IE5ldHdvcmsxIjAg
BgNVBAMTGVJITiBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxITAfBgkqhkiG9w0BCQEW
EnJobi1ub2NAcmVkaGF0LmNvbTAeFw0wMzA4MjkwMjEwNTVaFw0xMzA4MjYwMjEw
NTVaMIGxMQswCQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAO
BgNVBAcTB1JhbGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsT
D1JlZCBIYXQgTmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhv
cml0eTEhMB8GCSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC/YWPrPYsrRUjmwvt80iEhuOyQk0EwfCyNedUU
6Q5+P+/WCpsKpgJSAS0mlqTtvameqggDwWEKQYDqrnTMYSbQBZFVPmYUoiCz1p1x
DKt3zPTwEbUlM4pOIpoQNmf6EW1Idjof0uNEe4lmvrSF+y+mqhP6mm3JuxjEBK9P
FWmJmwIDAQABo4IBEjCCAQ4wHQYDVR0OBBYEFGlEJwXcLu2l9IHE13hF50Rd+IdH
MIHeBgNVHSMEgdYwgdOAFGlEJwXcLu2l9IHE13hF50Rd+IdHoYG3pIG0MIGxMQsw
CQYDVQQGEwJVUzEXMBUGA1UECBMOTm9ydGggQ2Fyb2xpbmExEDAOBgNVBAcTB1Jh
bGVpZ2gxFjAUBgNVBAoTDVJlZCBIYXQsIEluYy4xGDAWBgNVBAsTD1JlZCBIYXQg
TmV0d29yazEiMCAGA1UEAxMZUkhOIENlcnRpZmljYXRlIEF1dGhvcml0eTEhMB8G
CSqGSIb3DQEJARYScmhuLW5vY0ByZWRoYXQuY29tggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEEBQADgYEAI8nKB59eljmD4E7a3UeEMMrU1TiG+d6Ig8osRyY2
q/QUHigp3n0QSl6RPlqZBwypLuP7eERJxTLW6HqX/ynQM64munYGfnmXFwxPLSqL
iqxBWa7pxFUtuYjfm3tB+DIu7snAWeIwV143RynALXgz086jK9yE2r87Lku2s7ZO
noA=
-----END CERTIFICATE-----

fof/download/adapter/abstract.php000064400000006011152177723700013072 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Abstract base class for download adapters
 */
abstract class FOFDownloadAdapterAbstract implements FOFDownloadInterface
{
	public $priority = 100;

	public $name = '';

	public $isSupported = false;

	public $supportsChunkDownload = false;

	public $supportsFileSize = false;

	/**
	 * Does this download adapter support downloading files in chunks?
	 *
	 * @return  boolean  True if chunk download is supported
	 */
	public function supportsChunkDownload()
	{
		return $this->supportsChunkDownload;
	}

	/**
	 * Does this download adapter support reading the size of a remote file?
	 *
	 * @return  boolean  True if remote file size determination is supported
	 */
	public function supportsFileSize()
	{
		return $this->supportsFileSize;
	}

	/**
	 * Is this download class supported in the current server environment?
	 *
	 * @return  boolean  True if this server environment supports this download class
	 */
	public function isSupported()
	{
		return $this->isSupported;
	}

	/**
	 * Get the priority of this adapter. If multiple download adapters are
	 * supported on a site, the one with the highest priority will be
	 * used.
	 *
	 * @return  boolean
	 */
	public function getPriority()
	{
		return $this->priority;
	}

	/**
	 * Returns the name of this download adapter in use
	 *
	 * @return  string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Download a part (or the whole) of a remote URL and return the downloaded
	 * data. You are supposed to check the size of the returned data. If it's
	 * smaller than what you expected you've reached end of file. If it's empty
	 * you have tried reading past EOF. If it's larger than what you expected
	 * the server doesn't support chunk downloads.
	 *
	 * If this class' supportsChunkDownload returns false you should assume
	 * that the $from and $to parameters will be ignored.
	 *
	 * @param   string   $url     The remote file's URL
	 * @param   integer  $from    Byte range to start downloading from. Use null for start of file.
	 * @param   integer  $to      Byte range to stop downloading. Use null to download the entire file ($from is ignored)
	 * @param   array    $params  Additional params that will be added before performing the download
	 *
	 * @return  string  The raw file data retrieved from the remote URL.
	 *
	 * @throws  Exception  A generic exception is thrown on error
	 */
	public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
	{
		return '';
	}

	/**
	 * Get the size of a remote file in bytes
	 *
	 * @param   string  $url  The remote file's URL
	 *
	 * @return  integer  The file size, or -1 if the remote server doesn't support this feature
	 */
	public function getFileSize($url)
	{
		return -1;
	}
}fof/download/adapter/curl.php000064400000013227152177723700012243 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A download adapter using the cURL PHP module
 */
class FOFDownloadAdapterCurl extends FOFDownloadAdapterAbstract implements FOFDownloadInterface
{
	protected $headers = array();

	public function __construct()
	{
		$this->priority = 110;
		$this->supportsFileSize = true;
		$this->supportsChunkDownload = true;
		$this->name = 'curl';
		$this->isSupported = function_exists('curl_init') && function_exists('curl_exec') && function_exists('curl_close');
	}

	/**
	 * Download a part (or the whole) of a remote URL and return the downloaded
	 * data. You are supposed to check the size of the returned data. If it's
	 * smaller than what you expected you've reached end of file. If it's empty
	 * you have tried reading past EOF. If it's larger than what you expected
	 * the server doesn't support chunk downloads.
	 *
	 * If this class' supportsChunkDownload returns false you should assume
	 * that the $from and $to parameters will be ignored.
	 *
	 * @param   string   $url     The remote file's URL
	 * @param   integer  $from    Byte range to start downloading from. Use null for start of file.
	 * @param   integer  $to      Byte range to stop downloading. Use null to download the entire file ($from is ignored)
	 * @param   array    $params  Additional params that will be added before performing the download
	 *
	 * @return  string  The raw file data retrieved from the remote URL.
	 *
	 * @throws  Exception  A generic exception is thrown on error
	 */
	public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
	{
		$ch = curl_init();

		if (empty($from))
		{
			$from = 0;
		}

		if (empty($to))
		{
			$to = 0;
		}

		if ($to < $from)
		{
			$temp = $to;
			$to   = $from;
			$from = $temp;

			unset($temp);
		}

		// Default cURL options
		$options = array(
			CURLOPT_AUTOREFERER     => 1,
			CURLOPT_SSL_VERIFYPEER  => 1,
			CURLOPT_SSL_VERIFYHOST  => 2,
			CURLOPT_SSLVERSION      => 0,
			CURLOPT_AUTOREFERER     => 1,
			CURLOPT_URL             => $url,
			CURLOPT_BINARYTRANSFER  => 1,
			CURLOPT_RETURNTRANSFER  => 1,
			CURLOPT_FOLLOWLOCATION  => 1,
			CURLOPT_CAINFO          => __DIR__ . '/cacert.pem',
			CURLOPT_HEADERFUNCTION  => array($this, 'reponseHeaderCallback')
		);

		if (!(empty($from) && empty($to)))
		{
			$options[CURLOPT_RANGE] = "$from-$to";
		}

		// Add any additional options: Since they are numeric, we must use the array operator. If the jey exists in both
		// arrays, only the first one will be used while the second one will be ignored
		$options = $params + $options;

		@curl_setopt_array($ch, $options);

		$this->headers = array();

		$result = curl_exec($ch);

		$errno       = curl_errno($ch);
		$errmsg      = curl_error($ch);
		$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

		if ($result === false)
		{
			$error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_CURL_ERROR', $errno, $errmsg);
		}
		elseif (($http_status >= 300) && ($http_status <= 399) && isset($this->headers['Location']) && !empty($this->headers['Location']))
		{
			return $this->downloadAndReturn($this->headers['Location'], $from, $to, $params);
		}
		elseif ($http_status > 399)
		{
			$result = false;
			$errno = $http_status;
			$error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR', $http_status);
		}

		curl_close($ch);

		if ($result === false)
		{
			throw new Exception($error, $errno);
		}
		else
		{
			return $result;
		}
	}

	/**
	 * Get the size of a remote file in bytes
	 *
	 * @param   string  $url  The remote file's URL
	 *
	 * @return  integer  The file size, or -1 if the remote server doesn't support this feature
	 */
	public function getFileSize($url)
	{
		$result = -1;

		$ch = curl_init();

		curl_setopt($ch, CURLOPT_AUTOREFERER, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
		curl_setopt($ch, CURLOPT_SSLVERSION, 0);

		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_NOBODY, true );
		curl_setopt($ch, CURLOPT_HEADER, true );
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
		@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true );
		@curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/cacert.pem');

		$data = curl_exec($ch);
		curl_close($ch);

		if ($data)
		{
			$content_length = "unknown";
			$status = "unknown";
			$redirection = null;

			if (preg_match( "/^HTTP\/1\.[01] (\d\d\d)/", $data, $matches))
			{
				$status = (int)$matches[1];
			}

			if (preg_match( "/Content-Length: (\d+)/", $data, $matches))
			{
				$content_length = (int)$matches[1];
			}

			if (preg_match( "/Location: (.*)/", $data, $matches))
			{
				$redirection = (int)$matches[1];
			}

			if ($status == 200)
			{
				$result = $content_length;
			}

			if (($status > 300) && ($status <= 308))
			{
				if (!empty($redirection))
				{
					return $this->getFileSize($redirection);
				}

				return -1;
			}
		}

		return $result;
	}

	/**
	 * Handles the HTTP headers returned by cURL
	 *
	 * @param   resource  $ch    cURL resource handle (unused)
	 * @param   string    $data  Each header line, as returned by the server
	 *
	 * @return  int  The length of the $data string
	 */
	protected function reponseHeaderCallback(&$ch, &$data)
	{
		$strlen = strlen($data);

		if (($strlen) <= 2)
		{
			return $strlen;
		}

		if (substr($data, 0, 4) == 'HTTP')
		{
			return $strlen;
		}

		list($header, $value) = explode(': ', trim($data), 2);

		$this->headers[$header] = $value;

		return $strlen;
	}
}fof/download/adapter/fopen.php000064400000006145152177723700012406 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A download adapter using URL fopen() wrappers
 */
class FOFDownloadAdapterFopen extends FOFDownloadAdapterAbstract implements FOFDownloadInterface
{
	public function __construct()
	{
		$this->priority = 100;
		$this->supportsFileSize = false;
		$this->supportsChunkDownload = true;
		$this->name = 'fopen';

		// If we are not allowed to use ini_get, we assume that URL fopen is
		// disabled.
		if (!function_exists('ini_get'))
		{
			$this->isSupported = false;
		}
		else
		{
			$this->isSupported = ini_get('allow_url_fopen');
		}
	}

	/**
	 * Download a part (or the whole) of a remote URL and return the downloaded
	 * data. You are supposed to check the size of the returned data. If it's
	 * smaller than what you expected you've reached end of file. If it's empty
	 * you have tried reading past EOF. If it's larger than what you expected
	 * the server doesn't support chunk downloads.
	 *
	 * If this class' supportsChunkDownload returns false you should assume
	 * that the $from and $to parameters will be ignored.
	 *
	 * @param   string   $url     The remote file's URL
	 * @param   integer  $from    Byte range to start downloading from. Use null for start of file.
	 * @param   integer  $to      Byte range to stop downloading. Use null to download the entire file ($from is ignored)
	 * @param   array    $params  Additional params that will be added before performing the download
	 *
	 * @return  string  The raw file data retrieved from the remote URL.
	 *
	 * @throws  Exception  A generic exception is thrown on error
	 */
	public function downloadAndReturn($url, $from = null, $to = null, array $params = array())
	{
		if (empty($from))
		{
			$from = 0;
		}

		if (empty($to))
		{
			$to = 0;
		}

		if ($to < $from)
		{
			$temp = $to;
			$to   = $from;
			$from = $temp;

			unset($temp);
		}

		if (!(empty($from) && empty($to)))
		{
			$options = array(
				'http'	=> array(
					'method'	=> 'GET',
					'header'	=> "Range: bytes=$from-$to\r\n"
				),
				'ssl' => array(
					'verify_peer'   => true,
					'cafile'        => __DIR__ . '/cacert.pem',
					'verify_depth'  => 5,
				)
			);

			$options = array_merge($options, $params);

			$context = stream_context_create($options);
			$result  = @file_get_contents($url, false, $context, $from - $to + 1);
		}
		else
		{
			$options = array(
				'http'	=> array(
					'method'	=> 'GET',
				),
				'ssl' => array(
					'verify_peer'   => true,
					'cafile'        => __DIR__ . '/cacert.pem',
					'verify_depth'  => 5,
				)
			);

			$options = array_merge($options, $params);

			$context = stream_context_create($options);
			$result  = @file_get_contents($url, false, $context);
		}

		if ($result === false)
		{
			$error = JText::sprintf('LIB_FOF_DOWNLOAD_ERR_HTTPERROR');
			throw new Exception($error, 1);
		}
		else
		{
			return $result;
		}
	}
}fof/input/jinput/json.php000064400000002713152177723700011466 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Input
 *
 * @copyright   Copyright (C) 2005-2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Input JSON Class
 *
 * This class decodes a JSON string from the raw request data and makes it available via
 * the standard JInput interface.
 *
 * @since  12.2
 */
class JInputJSON extends JInput
{
	/**
	 * @var    string  The raw JSON string from the request.
	 * @since  12.2
	 */
	private $_raw;

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is the raw HTTP input decoded from JSON)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   12.2
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}

		if (is_null($source))
		{
			$this->_raw = file_get_contents('php://input');
			$this->data = json_decode($this->_raw, true);
		}
		else
		{
			$this->data = & $source;
		}

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets the raw JSON string from the request.
	 *
	 * @return  string  The raw JSON string from the request.
	 *
	 * @since   12.2
	 */
	public function getRaw()
	{
		return $this->_raw;
	}
}
fof/input/jinput/cli.php000064400000007445152177723700011273 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Input
 *
 * @copyright   Copyright (C) 2005-2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Input CLI Class
 *
 * @since  11.1
 */
class JInputCli extends JInput
{
	/**
	 * The executable that was called to run the CLI script.
	 *
	 * @var    string
	 * @since  11.1
	 */
	public $executable;

	/**
	 * The additional arguments passed to the script that are not associated
	 * with a specific argument name.
	 *
	 * @var    array
	 * @since  11.1
	 */
	public $args = array();

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is $_REQUEST)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   11.1
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}

		// Get the command line options
		$this->parseArguments();

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Method to serialize the input.
	 *
	 * @return  string  The serialized input.
	 *
	 * @since   12.1
	 */
	public function serialize()
	{
		// Load all of the inputs.
		$this->loadAllInputs();

		// Remove $_ENV and $_SERVER from the inputs.
		$inputs = $this->inputs;
		unset($inputs['env']);
		unset($inputs['server']);

		// Serialize the executable, args, options, data, and inputs.
		return serialize(array($this->executable, $this->args, $this->options, $this->data, $inputs));
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  JInput  The input object.
	 *
	 * @since   12.1
	 */
	public function unserialize($input)
	{
		// Unserialize the executable, args, options, data, and inputs.
		list($this->executable, $this->args, $this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}
	}

	/**
	 * Initialise the options and arguments
	 *
	 * Not supported: -abc c-value
	 *
	 * @return  void
	 *
	 * @since   11.1
	 */
	protected function parseArguments()
	{
		$argv = $_SERVER['argv'];

		$this->executable = array_shift($argv);

		$out = array();

		for ($i = 0, $j = count($argv); $i < $j; $i++)
		{
			$arg = $argv[$i];

			// --foo --bar=baz
			if (substr($arg, 0, 2) === '--')
			{
				$eqPos = strpos($arg, '=');

				// --foo
				if ($eqPos === false)
				{
					$key = substr($arg, 2);

					// --foo value
					if ($i + 1 < $j && $argv[$i + 1][0] !== '-')
					{
						$value = $argv[$i + 1];
						$i++;
					}
					else
					{
						$value = isset($out[$key]) ? $out[$key] : true;
					}

					$out[$key] = $value;
				}

				// --bar=baz
				else
				{
					$key = substr($arg, 2, $eqPos - 2);
					$value = substr($arg, $eqPos + 1);
					$out[$key] = $value;
				}
			}
			elseif (substr($arg, 0, 1) === '-')
			// -k=value -abc
			{
				// -k=value
				if (substr($arg, 2, 1) === '=')
				{
					$key = substr($arg, 1, 1);
					$value = substr($arg, 3);
					$out[$key] = $value;
				}
				else
				// -abc
				{
					$chars = str_split(substr($arg, 1));

					foreach ($chars as $char)
					{
						$key = $char;
						$value = isset($out[$key]) ? $out[$key] : true;
						$out[$key] = $value;
					}

					// -a a-value
					if ((count($chars) === 1) && ($i + 1 < $j) && ($argv[$i + 1][0] !== '-'))
					{
						$out[$key] = $argv[$i + 1];
						$i++;
					}
				}
			}
			else
			{
				// Plain-arg
				$this->args[] = $arg;
			}
		}

		$this->data = $out;
	}
}
fof/input/jinput/files.php000064400000005635152177723700011625 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Input
 *
 * @copyright   Copyright (C) 2005-2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Input Files Class
 *
 * @since  11.1
 */
class JInputFiles extends JInput
{
	/**
	 * The pivoted data from a $_FILES or compatible array.
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected $decodedData = array();

	/**
	 * The class constructor.
	 *
	 * @param   array  $source   The source argument is ignored. $_FILES is always used.
	 * @param   array  $options  An optional array of configuration options:
	 *                           filter : a custom JFilterInput object.
	 *
	 * @since   12.1
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}

		// Set the data source.
		$this->data = & $_FILES;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     The name of the input property (usually the name of the files INPUT tag) to get.
	 * @param   mixed   $default  The default value to return if the named property does not exist.
	 * @param   string  $filter   The filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @see     JFilterInput::clean()
	 * @since   11.1
	 */
	public function get($name, $default = null, $filter = 'cmd')
	{
		if (isset($this->data[$name]))
		{
			$results = $this->decodeData(
				array(
					$this->data[$name]['name'],
					$this->data[$name]['type'],
					$this->data[$name]['tmp_name'],
					$this->data[$name]['error'],
					$this->data[$name]['size']
				)
			);

			// Prevent returning an unsafe file unless speciffically requested
			if ($filter != 'raw')
			{
				$isSafe = JFilterInput::isSafeFile($results);

				if (!$isSafe)
				{
					return $default;
				}
			}

			return $results;
		}

		return $default;
	}

	/**
	 * Method to decode a data array.
	 *
	 * @param   array  $data  The data array to decode.
	 *
	 * @return  array
	 *
	 * @since   11.1
	 */
	protected function decodeData(array $data)
	{
		$result = array();

		if (is_array($data[0]))
		{
			foreach ($data[0] as $k => $v)
			{
				$result[$k] = $this->decodeData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
			}

			return $result;
		}

		return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
	}

	/**
	 * Sets a value.
	 *
	 * @param   string  $name   The name of the input property to set.
	 * @param   mixed   $value  The value to assign to the input property.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 */
	public function set($name, $value)
	{
	}
}
fof/input/jinput/cookie.php000064400000007634152177723700011775 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Input
 *
 * @copyright   Copyright (C) 2005-2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Input Cookie Class
 *
 * @since  11.1
 */
class JInputCookie extends JInput
{
	/**
	 * Constructor.
	 *
	 * @param   array  $source   Ignored.
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   11.1
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}

		// Set the data source.
		$this->data = & $_COOKIE;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Sets a value
	 *
	 * @param   string   $name      Name of the value to set.
	 * @param   mixed    $value     Value to assign to the input.
	 * @param   integer  $expire    The time the cookie expires. This is a Unix timestamp so is in number
	 *                              of seconds since the epoch. In other words, you'll most likely set this
	 *                              with the time() function plus the number of seconds before you want it
	 *                              to expire. Or you might use mktime(). time()+60*60*24*30 will set the
	 *                              cookie to expire in 30 days. If set to 0, or omitted, the cookie will
	 *                              expire at the end of the session (when the browser closes).
	 * @param   string   $path      The path on the server in which the cookie will be available on. If set
	 *                              to '/', the cookie will be available within the entire domain. If set to
	 *                              '/foo/', the cookie will only be available within the /foo/ directory and
	 *                              all sub-directories such as /foo/bar/ of domain. The default value is the
	 *                              current directory that the cookie is being set in.
	 * @param   string   $domain    The domain that the cookie is available to. To make the cookie available
	 *                              on all subdomains of example.com (including example.com itself) then you'd
	 *                              set it to '.example.com'. Although some browsers will accept cookies without
	 *                              the initial ., RFC 2109 requires it to be included. Setting the domain to
	 *                              'www.example.com' or '.www.example.com' will make the cookie only available
	 *                              in the www subdomain.
	 * @param   boolean  $secure    Indicates that the cookie should only be transmitted over a secure HTTPS
	 *                              connection from the client. When set to TRUE, the cookie will only be set
	 *                              if a secure connection exists. On the server-side, it's on the programmer
	 *                              to send this kind of cookie only on secure connection (e.g. with respect
	 *                              to $_SERVER["HTTPS"]).
	 * @param   boolean  $httpOnly  When TRUE the cookie will be made accessible only through the HTTP protocol.
	 *                              This means that the cookie won't be accessible by scripting languages, such
	 *                              as JavaScript. This setting can effectively help to reduce identity theft
	 *                              through XSS attacks (although it is not supported by all browsers).
	 *
	 * @return  void
	 *
	 * @link    http://www.ietf.org/rfc/rfc2109.txt
	 * @see     setcookie()
	 * @since   11.1
	 */
	public function set($name, $value, $expire = 0, $path = '', $domain = '', $secure = false, $httpOnly = false)
	{
		setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);

		$this->data[$name] = $value;
	}
}
fof/input/jinput/input.php000064400000020262152177723700011653 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Input
 *
 * @copyright   Copyright (C) 2005-2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Input Base Class
 *
 * This is an abstracted input class used to manage retrieving data from the application environment.
 *
 * @since  11.1
 *
 * @property-read    JInput        $get
 * @property-read    JInput        $post
 * @property-read    JInput        $request
 * @property-read    JInput        $server
 * @property-read    JInputFiles   $files
 * @property-read    JInputCookie  $cookie
 *
 * @method      integer  getInt()       getInt($name, $default = null)    Get a signed integer.
 * @method      integer  getUint()      getUint($name, $default = null)   Get an unsigned integer.
 * @method      float    getFloat()     getFloat($name, $default = null)  Get a floating-point number.
 * @method      boolean  getBool()      getBool($name, $default = null)   Get a boolean.
 * @method      string   getWord()      getWord($name, $default = null)
 * @method      string   getAlnum()     getAlnum($name, $default = null)
 * @method      string   getCmd()       getCmd($name, $default = null)
 * @method      string   getBase64()    getBase64($name, $default = null)
 * @method      string   getString()    getString($name, $default = null)
 * @method      string   getHtml()      getHtml($name, $default = null)
 * @method      string   getPath()      getPath($name, $default = null)
 * @method      string   getUsername()  getUsername($name, $default = null)
 */
class JInput implements Serializable, Countable
{
	/**
	 * Options array for the JInput instance.
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected $options = array();

	/**
	 * Filter object to use.
	 *
	 * @var    JFilterInput
	 * @since  11.1
	 */
	protected $filter = null;

	/**
	 * Input data.
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected $data = array();

	/**
	 * Input objects
	 *
	 * @var    array
	 * @since  11.1
	 */
	protected $inputs = array();

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is $_REQUEST)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   11.1
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}

		if (is_null($source))
		{
			$this->data = &$_REQUEST;
		}
		else
		{
			$this->data = $source;
		}

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Magic method to get an input object
	 *
	 * @param   mixed  $name  Name of the input object to retrieve.
	 *
	 * @return  JInput  The request input object
	 *
	 * @since   11.1
	 */
	public function __get($name)
	{
		if (isset($this->inputs[$name]))
		{
			return $this->inputs[$name];
		}

		$className = 'JInput' . ucfirst($name);

		if (class_exists($className))
		{
			$this->inputs[$name] = new $className(null, $this->options);

			return $this->inputs[$name];
		}

		$superGlobal = '_' . strtoupper($name);

		if (isset($GLOBALS[$superGlobal]))
		{
			$this->inputs[$name] = new JInput($GLOBALS[$superGlobal], $this->options);

			return $this->inputs[$name];
		}

		// TODO throw an exception
	}

	/**
	 * Get the number of variables.
	 *
	 * @return  integer  The number of variables in the input.
	 *
	 * @since   12.2
	 * @see     Countable::count()
	 */
	public function count()
	{
		return count($this->data);
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     Name of the value to get.
	 * @param   mixed   $default  Default value to return if variable does not exist.
	 * @param   string  $filter   Filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @since   11.1
	 */
	public function get($name, $default = null, $filter = 'cmd')
	{
		if (isset($this->data[$name]))
		{
			return $this->filter->clean($this->data[$name], $filter);
		}

		return $default;
	}

	/**
	 * Gets an array of values from the request.
	 *
	 * @param   array  $vars        Associative array of keys and filter types to apply.
	 *                              If empty and datasource is null, all the input data will be returned
	 *                              but filtered using the default case in JFilterInput::clean.
	 * @param   mixed  $datasource  Array to retrieve data from, or null
	 *
	 * @return  mixed  The filtered input data.
	 *
	 * @since   11.1
	 */
	public function getArray(array $vars = array(), $datasource = null)
	{
		if (empty($vars) && is_null($datasource))
		{
			$vars = $this->data;
		}

		$results = array();

		foreach ($vars as $k => $v)
		{
			if (is_array($v))
			{
				if (is_null($datasource))
				{
					$results[$k] = $this->getArray($v, $this->get($k, null, 'array'));
				}
				else
				{
					$results[$k] = $this->getArray($v, $datasource[$k]);
				}
			}
			else
			{
				if (is_null($datasource))
				{
					$results[$k] = $this->get($k, null, $v);
				}
				elseif (isset($datasource[$k]))
				{
					$results[$k] = $this->filter->clean($datasource[$k], $v);
				}
				else
				{
					$results[$k] = $this->filter->clean(null, $v);
				}
			}
		}

		return $results;
	}

	/**
	 * Sets a value
	 *
	 * @param   string  $name   Name of the value to set.
	 * @param   mixed   $value  Value to assign to the input.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 */
	public function set($name, $value)
	{
		$this->data[$name] = $value;
	}

	/**
	 * Define a value. The value will only be set if there's no value for the name or if it is null.
	 *
	 * @param   string  $name   Name of the value to define.
	 * @param   mixed   $value  Value to assign to the input.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function def($name, $value)
	{
		if (isset($this->data[$name]))
		{
			return;
		}

		$this->data[$name] = $value;
	}

	/**
	 * Magic method to get filtered input data.
	 *
	 * @param   string  $name       Name of the filter type prefixed with 'get'.
	 * @param   array   $arguments  [0] The name of the variable [1] The default value.
	 *
	 * @return  mixed   The filtered input value.
	 *
	 * @since   11.1
	 */
	public function __call($name, $arguments)
	{
		if (substr($name, 0, 3) == 'get')
		{
			$filter = substr($name, 3);

			$default = null;

			if (isset($arguments[1]))
			{
				$default = $arguments[1];
			}

			return $this->get($arguments[0], $default, $filter);
		}
	}

	/**
	 * Gets the request method.
	 *
	 * @return  string   The request method.
	 *
	 * @since   11.1
	 */
	public function getMethod()
	{
		$method = strtoupper($_SERVER['REQUEST_METHOD']);

		return $method;
	}

	/**
	 * Method to serialize the input.
	 *
	 * @return  string  The serialized input.
	 *
	 * @since   12.1
	 */
	public function serialize()
	{
		// Load all of the inputs.
		$this->loadAllInputs();

		// Remove $_ENV and $_SERVER from the inputs.
		$inputs = $this->inputs;
		unset($inputs['env']);
		unset($inputs['server']);

		// Serialize the options, data, and inputs.
		return serialize(array($this->options, $this->data, $inputs));
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  JInput  The input object.
	 *
	 * @since   12.1
	 */
	public function unserialize($input)
	{
		// Unserialize the options, data, and inputs.
		list($this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = JFilterInput::getInstance();
		}
	}

	/**
	 * Method to load all of the global inputs.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function loadAllInputs()
	{
		static $loaded = false;

		if (!$loaded)
		{
			// Load up all the globals.
			foreach ($GLOBALS as $global => $data)
			{
				// Check if the global starts with an underscore.
				if (strpos($global, '_') === 0)
				{
					// Convert global name to input name.
					$global = strtolower($global);
					$global = substr($global, 1);

					// Get the input.
					$this->$global;
				}
			}

			$loaded = true;
		}
	}
}
fof/input/input.php000064400000016142152177723700010344 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  input
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (version_compare(JVERSION, '1.7.0', 'lt'))
{
	jimport('joomla.filter.input');
	jimport('joomla.filter.filterinput');
	jimport('joomla.base.object');

	require_once __DIR__ . '/jinput/input.php';
	require_once __DIR__ . '/jinput/cli.php';
	require_once __DIR__ . '/jinput/cookie.php';
	require_once __DIR__ . '/jinput/files.php';
	require_once __DIR__ . '/jinput/json.php';
}
elseif (version_compare(JVERSION, '2.5.0', 'lt'))
{
	jimport('joomla.application.input');
	jimport('joomla.input.input');
}

/**
 * FrameworkOnFramework input handling class. Extends upon the JInput class.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFInput extends JInput
{
	/**
	 * Public constructor. Overriden to allow specifying the global input array
	 * to use as a string and instantiate from an objetc holding variables.
	 *
	 * @param   array|string|object|null  $source   Source data; set null to use $_REQUEST
	 * @param   array                     $options  Filter options
	 */
	public function __construct($source = null, array $options = array())
	{
		$hash = null;

		if (is_string($source))
		{
			$hash = strtoupper($source);

			switch ($hash)
			{
				case 'GET':
					$source = $_GET;
					break;
				case 'POST':
					$source = $_POST;
					break;
				case 'FILES':
					$source = $_FILES;
					break;
				case 'COOKIE':
					$source = $_COOKIE;
					break;
				case 'ENV':
					$source = $_ENV;
					break;
				case 'SERVER':
					$source = $_SERVER;
					break;
				default:
					$source = $_REQUEST;
					$hash = 'REQUEST';
					break;
			}
		}
		elseif (is_object($source))
		{
			try
			{
				$source = (array) $source;
			}
			catch (Exception $exc)
			{
				$source = null;
			}
		}
		elseif (is_array($source))
		{
			// Nothing, it's already an array
		}
		else
		{
			// Any other case
			$source = $_REQUEST;
			$hash = 'REQUEST';
		}

		// Magic quotes GPC handling (something JInput simply can't handle at all)

		if (($hash == 'REQUEST') && get_magic_quotes_gpc() && class_exists('JRequest', true))
		{
			$source = JRequest::get('REQUEST', 2);
		}

		parent::__construct($source, $options);
	}

	/**
	 * Gets a value from the input data. Overriden to allow specifying a filter
	 * mask.
	 *
	 * @param   string  $name     Name of the value to get.
	 * @param   mixed   $default  Default value to return if variable does not exist.
	 * @param   string  $filter   Filter to apply to the value.
	 * @param   int     $mask     The filter mask
	 *
	 * @return  mixed  The filtered input value.
	 */
	public function get($name, $default = null, $filter = 'cmd', $mask = 0)
	{
		if (isset($this->data[$name]))
		{
			return $this->_cleanVar($this->data[$name], $mask, $filter);
		}

		return $default;
	}

	/**
	 * Returns a copy of the raw data stored in the class
	 *
	 * @return  array
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Old static methods are now deprecated. This magic method makes sure there
	 * is a continuity in our approach. The downside is that it's only compatible
	 * with PHP 5.3.0. Sorry!
	 *
	 * @param   string  $name       Name of the method we're calling
	 * @param   array   $arguments  The arguments passed to the method
	 *
	 * @return  mixed
	 */
	public static function __callStatic($name, $arguments)
	{
		FOFPlatform::getInstance()->logDeprecated('FOFInput: static getXXX() methods are deprecated. Use the input object\'s methods instead.');

		if (substr($name, 0, 3) == 'get')
		{
			// Initialise arguments
			$key = array_shift($arguments);
			$default = array_shift($arguments);
			$input = array_shift($arguments);
			$type = 'none';
			$mask = 0;

			$type = strtolower(substr($name, 3));

			if ($type == 'var')
			{
				$type = array_shift($arguments);
				$mask = array_shift($arguments);
			}

			if (is_null($type))
			{
				$type = 'none';
			}

			if (is_null($mask))
			{
				$mask = 0;
			}

			if (!($input instanceof FOFInput) && !($input instanceof JInput))
			{
				$input = new FOFInput($input);
			}

			return $input->get($key, $default, $type, $mask);
		}

		return false;
	}

	/**
	 * Magic method to get filtered input data.
	 *
	 * @param   mixed   $name       Name of the value to get.
	 * @param   string  $arguments  Default value to return if variable does not exist.
	 *
	 * @return  boolean  The filtered boolean input value.
	 */
	public function __call($name, $arguments)
	{
		if (substr($name, 0, 3) == 'get')
		{
			$filter = substr($name, 3);

			$default = null;
			$mask = 0;

			if (isset($arguments[1]))
			{
				$default = $arguments[1];
			}

			if (isset($arguments[2]))
			{
				$mask = $arguments[2];
			}

			return $this->get($arguments[0], $default, $filter, $mask);
		}
	}

	/**
	 * Sets an input variable. WARNING: IT SHOULD NO LONGER BE USED!
	 *
	 * @param   string   $name       The name of the variable to set
	 * @param   mixed    $value      The value to set it to
	 * @param   array    &$input     The input array or FOFInput object
	 * @param   boolean  $overwrite  Should I overwrite existing values (default: true)
	 *
	 * @return  string   Previous value
	 *
	 * @deprecated
	 */
	public static function setVar($name, $value = null, &$input = array(), $overwrite = true)
	{
		FOFPlatform::getInstance()->logDeprecated('FOFInput::setVar() is deprecated. Use set() instead.');

		if (empty($input))
		{
			return JRequest::setVar($name, $value, 'default', $overwrite);
		}
		elseif (is_string($input))
		{
			return JRequest::setVar($name, $value, $input, $overwrite);
		}
		else
		{
			if (!$overwrite && array_key_exists($name, $input))
			{
				return $input[$name];
			}

			$previous = array_key_exists($name, $input) ? $input[$name] : null;

			if (is_array($input))
			{
				$input[$name] = $value;
			}
			elseif ($input instanceof FOFInput)
			{
				$input->set($name, $value);
			}

			return $previous;
		}
	}

	/**
	 * Custom filter implementation. Works better with arrays and allows the use
	 * of a filter mask.
	 *
	 * @param   mixed    $var   The variable (value) to clean
	 * @param   integer  $mask  The clean mask
	 * @param   string   $type  The variable type
	 *
	 * @return   mixed
	 */
	protected function _cleanVar($var, $mask = 0, $type = null)
	{
		if (is_array($var))
		{
			$temp = array();

			foreach ($var as $k => $v)
			{
				$temp[$k] = self::_cleanVar($v, $mask);
			}

			return $temp;
		}

		// If the no trim flag is not set, trim the variable
		if (!($mask & 1) && is_string($var))
		{
			$var = trim($var);
		}

		// Now we handle input filtering
		if ($mask & 2)
		{
			// If the allow raw flag is set, do not modify the variable
			$var = $var;
		}
		elseif ($mask & 4)
		{
			// If the allow HTML flag is set, apply a safe HTML filter to the variable
			$safeHtmlFilter = JFilterInput::getInstance(null, null, 1, 1);
			$var = $safeHtmlFilter->clean($var, $type);
		}
		else
		{
			$var = $this->filter->clean($var, $type);
		}

		return $var;
	}
}
fof/inflector/inflector.php000064400000033107152177723700012020 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  inflector
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * The FOFInflector is an adaptation of the Akelos PHP Inflector which is a PHP
 * port from a Ruby on Rails project.
 */

/**
 * FOFInflector to pluralize and singularize English nouns.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFInflector
{
	/**
	 * Rules for pluralizing and singularizing of nouns.
	 *
	 * @var array
	 */
	protected static $_rules = array
	(
		'pluralization'   => array(
			'/move$/i'                      => 'moves',
			'/sex$/i'                       => 'sexes',
			'/child$/i'                     => 'children',
			'/children$/i'                  => 'children',
			'/man$/i'                       => 'men',
			'/men$/i'                       => 'men',
			'/foot$/i'                      => 'feet',
			'/feet$/i'                      => 'feet',
			'/person$/i'                    => 'people',
			'/people$/i'                    => 'people',
			'/taxon$/i'                     => 'taxa',
			'/taxa$/i'                      => 'taxa',
			'/(quiz)$/i'                    => '$1zes',
			'/^(ox)$/i'                     => '$1en',
			'/oxen$/i'                      => 'oxen',
			'/(m|l)ouse$/i'                 => '$1ice',
			'/(m|l)ice$/i'                  => '$1ice',
			'/(matr|vert|ind|suff)ix|ex$/i' => '$1ices',
			'/(x|ch|ss|sh)$/i'              => '$1es',
			'/([^aeiouy]|qu)y$/i'           => '$1ies',
			'/(?:([^f])fe|([lr])f)$/i'      => '$1$2ves',
			'/sis$/i'                       => 'ses',
			'/([ti]|addend)um$/i'           => '$1a',
			'/([ti]|addend)a$/i'            => '$1a',
			'/(alumn|formul)a$/i'           => '$1ae',
			'/(alumn|formul)ae$/i'          => '$1ae',
			'/(buffal|tomat|her)o$/i'       => '$1oes',
			'/(bu)s$/i'                     => '$1ses',
			'/(alias|status)$/i'            => '$1es',
			'/(octop|vir)us$/i'             => '$1i',
			'/(octop|vir)i$/i'              => '$1i',
			'/(gen)us$/i'                   => '$1era',
			'/(gen)era$/i'                  => '$1era',
			'/(ax|test)is$/i'               => '$1es',
			'/s$/i'                         => 's',
			'/$/'                           => 's',
		),
		'singularization' => array(
			'/cookies$/i'                                                      => 'cookie',
			'/moves$/i'                                                        => 'move',
			'/sexes$/i'                                                        => 'sex',
			'/children$/i'                                                     => 'child',
			'/men$/i'                                                          => 'man',
			'/feet$/i'                                                         => 'foot',
			'/people$/i'                                                       => 'person',
			'/taxa$/i'                                                         => 'taxon',
			'/databases$/i'                                                    => 'database',
      '/menus$/i'                                                        => 'menu',
			'/(quiz)zes$/i'                                                    => '\1',
			'/(matr|suff)ices$/i'                                              => '\1ix',
			'/(vert|ind|cod)ices$/i'                                           => '\1ex',
			'/^(ox)en/i'                                                       => '\1',
			'/(alias|status)es$/i'                                             => '\1',
			'/(tomato|hero|buffalo)es$/i'                                      => '\1',
			'/([octop|vir])i$/i'                                               => '\1us',
			'/(gen)era$/i'                                                     => '\1us',
			'/(cris|^ax|test)es$/i'                                            => '\1is',
			'/is$/i'                                                           => 'is',
			'/us$/i'                                                           => 'us',
			'/ias$/i'                                                          => 'ias',
			'/(shoe)s$/i'                                                      => '\1',
			'/(o)es$/i'                                                        => '\1e',
			'/(bus)es$/i'                                                      => '\1',
			'/([m|l])ice$/i'                                                   => '\1ouse',
			'/(x|ch|ss|sh)es$/i'                                               => '\1',
			'/(m)ovies$/i'                                                     => '\1ovie',
			'/(s)eries$/i'                                                     => '\1eries',
			'/(v)ies$/i'                                                       => '\1ie',
			'/([^aeiouy]|qu)ies$/i'                                            => '\1y',
			'/([lr])ves$/i'                                                    => '\1f',
			'/(tive)s$/i'                                                      => '\1',
			'/(hive)s$/i'                                                      => '\1',
			'/([^f])ves$/i'                                                    => '\1fe',
			'/(^analy)ses$/i'                                                  => '\1sis',
			'/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis',
			'/([ti]|addend)a$/i'                                               => '\1um',
			'/(alumn|formul)ae$/i'                                             => '$1a',
			'/(n)ews$/i'                                                       => '\1ews',
			'/(.*)ss$/i'                                                       => '\1ss',
			'/(.*)s$/i'                                                        => '\1',
		),
		'countable'       => array(
			'aircraft',
			'cannon',
			'deer',
			'equipment',
			'fish',
			'information',
			'money',
			'moose',
			'rice',
			'series',
			'sheep',
			'species',
			'swine',
		)
	);

	/**
	 * Cache of pluralized and singularized nouns.
	 *
	 * @var array
	 */
	protected static $_cache = array(
		'singularized' => array(),
		'pluralized'   => array()
	);

	/**
	 * Constructor
	 *
	 * Prevent creating instances of this class by making the constructor private
	 */
	private function __construct()
	{
	}

	public static function deleteCache()
	{
		static::$_cache['pluralized'] = array();
		static::$_cache['singularized'] = array();
	}

	/**
	 * Add a word to the cache, useful to make exceptions or to add words in other languages.
	 *
	 * @param   string  $singular  word.
	 * @param   string  $plural    word.
	 *
	 * @return  void
	 */
	public static function addWord($singular, $plural)
	{
		static::$_cache['pluralized'][$singular] = $plural;
		static::$_cache['singularized'][$plural] = $singular;
	}

	/**
	 * Singular English word to plural.
	 *
	 * @param   string  $word  word to pluralize.
	 *
	 * @return  string Plural noun.
	 */
	public static function pluralize($word)
	{
		// Get the cached noun of it exists
		if (isset(static::$_cache['pluralized'][$word]))
		{
			return static::$_cache['pluralized'][$word];
		}

		// Create the plural noun
		if (in_array($word, self::$_rules['countable']))
		{
			static::$_cache['pluralized'][$word] = $word;

			return $word;
		}

		foreach (self::$_rules['pluralization'] as $regexp => $replacement)
		{
			$matches = null;
			$plural  = preg_replace($regexp, $replacement, $word, -1, $matches);

			if ($matches > 0)
			{
				static::$_cache['pluralized'][$word] = $plural;

				return $plural;
			}
		}

		static::$_cache['pluralized'][$word] = $word;

		return static::$_cache['pluralized'][$word];
	}

	/**
	 * Plural English word to singular.
	 *
	 * @param   string  $word  Word to singularize.
	 *
	 * @return  string Singular noun.
	 */
	public static function singularize($word)
	{
		// Get the cached noun of it exists
		if (isset(static::$_cache['singularized'][$word]))
		{
			return static::$_cache['singularized'][$word];
		}

		// Create the singular noun
		if (in_array($word, self::$_rules['countable']))
		{
			static::$_cache['singularized'][$word] = $word;

			return $word;
		}

		foreach (self::$_rules['singularization'] as $regexp => $replacement)
		{
			$matches  = null;
			$singular = preg_replace($regexp, $replacement, $word, -1, $matches);

			if ($matches > 0)
			{
				static::$_cache['singularized'][$word] = $singular;

				return $singular;
			}
		}

		static::$_cache['singularized'][$word] = $word;

		return static::$_cache['singularized'][$word];
	}

	/**
	 * Returns given word as CamelCased.
	 *
	 * Converts a word like "foo_bar" or "foo bar" to "FooBar". It
	 * will remove non alphanumeric characters from the word, so
	 * "who's online" will be converted to "WhoSOnline"
	 *
	 * @param   string  $word  Word to convert to camel case.
	 *
	 * @return  string  UpperCamelCasedWord
	 */
	public static function camelize($word)
	{
		$word = preg_replace('/[^a-zA-Z0-9\s]/', ' ', $word);
		$word = str_replace(' ', '', ucwords(strtolower(str_replace('_', ' ', $word))));

		return $word;
	}

	/**
	 * Converts a word "into_it_s_underscored_version"
	 *
	 * Convert any "CamelCased" or "ordinary Word" into an "underscored_word".
	 *
	 * @param   string  $word  Word to underscore
	 *
	 * @return string Underscored word
	 */
	public static function underscore($word)
	{
		$word = preg_replace('/(\s)+/', '_', $word);
		$word = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $word));

		return $word;
	}

	/**
	 * Convert any "CamelCased" word into an array of strings
	 *
	 * Returns an array of strings each of which is a substring of string formed
	 * by splitting it at the camelcased letters.
	 *
	 * @param   string  $word  Word to explode
	 *
	 * @return  array   Array of strings
	 */
	public static function explode($word)
	{
		$result = explode('_', self::underscore($word));

		return $result;
	}

	/**
	 * Convert  an array of strings into a "CamelCased" word.
	 *
	 * @param   array  $words  Array to implode
	 *
	 * @return  string UpperCamelCasedWord
	 */
	public static function implode($words)
	{
		$result = self::camelize(implode('_', $words));

		return $result;
	}

	/**
	 * Returns a human-readable string from $word.
	 *
	 * Returns a human-readable string from $word, by replacing
	 * underscores with a space, and by upper-casing the initial
	 * character by default.
	 *
	 * @param   string  $word  String to "humanize"
	 *
	 * @return string Human-readable word
	 */
	public static function humanize($word)
	{
		$result = ucwords(strtolower(str_replace("_", " ", $word)));

		return $result;
	}

	/**
	 * Converts a class name to its table name according to Koowa
	 * naming conventions.
	 *
	 * Converts "Person" to "people"
	 *
	 * @param   string  $className  Class name for getting related table_name.
	 *
	 * @return  string  plural_table_name
	 *
	 * @see classify
	 */
	public static function tableize($className)
	{
		$result = self::underscore($className);

		if (!self::isPlural($className))
		{
			$result = self::pluralize($result);
		}

		return $result;
	}

	/**
	 * Converts a table name to its class name according to Koowa naming conventions.
	 *
	 * @param   string  $tableName  Table name for getting related ClassName.
	 *
	 * @return string SingularClassName
	 *
	 * @example  Converts "people" to "Person"
	 * @see tableize
	 */
	public static function classify($tableName)
	{
		$result = self::camelize(self::singularize($tableName));

		return $result;
	}

	/**
	 * Returns camelBacked version of a string. Same as camelize but first char is lowercased.
	 *
	 * @param   string  $string  String to be camelBacked.
	 *
	 * @return string
	 *
	 * @see camelize
	 */
	public static function variablize($string)
	{
		$string   = self::camelize(self::underscore($string));
		$result   = strtolower(substr($string, 0, 1));
		$variable = preg_replace('/\\w/', $result, $string, 1);

		return $variable;
	}

	/**
	 * Check to see if an English word is singular
	 *
	 * @param   string  $string  The word to check
	 *
	 * @return boolean
	 */
	public static function isSingular($string)
	{
		// Check cache assuming the string is plural.
		$singular = isset(static::$_cache['singularized'][$string]) ? static::$_cache['singularized'][$string] : null;
		$plural   = $singular && isset(static::$_cache['pluralized'][$singular]) ? static::$_cache['pluralized'][$singular] : null;

		if ($singular && $plural)
		{
			return $plural != $string;
		}

		// If string is not in the cache, try to pluralize and singularize it.
		return self::singularize(self::pluralize($string)) == $string;
	}

	/**
	 * Check to see if an Enlish word is plural.
	 *
	 * @param   string  $string  String to be checked.
	 *
	 * @return boolean
	 */
	public static function isPlural($string)
	{
		// Check cache assuming the string is singular.
		$plural   = isset(static::$_cache['pluralized'][$string]) ? static::$_cache['pluralized'][$string] : null;
		$singular = $plural && isset(static::$_cache['singularized'][$plural]) ? static::$_cache['singularized'][$plural] : null;

		if ($plural && $singular)
		{
			return $singular != $string;
		}

		// If string is not in the cache, try to singularize and pluralize it.
		return self::pluralize(self::singularize($string)) == $string;
	}

	/**
	 * Gets a part of a CamelCased word by index.
	 *
	 * Use a negative index to start at the last part of the word (-1 is the
	 * last part)
	 *
	 * @param   string   $string   Word
	 * @param   integer  $index    Index of the part
	 * @param   string   $default  Default value
	 *
	 * @return string
	 */
	public static function getPart($string, $index, $default = null)
	{
		$parts = self::explode($string);

		if ($index < 0)
		{
			$index = count($parts) + $index;
		}

		return isset($parts[$index]) ? $parts[$index] : $default;
	}
}
fof/config/provider.php000064400000011232152177723700011140 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  config
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * Reads and parses the fof.xml file in the back-end of a FOF-powered component,
 * provisioning the data to the rest of the FOF framework
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFConfigProvider
{
	/**
	 * Cache of FOF components' configuration variables
	 *
	 * @var array
	 */
	public static $configurations = array();

	/**
	 * Parses the configuration of the specified component
	 *
	 * @param   string   $component  The name of the component, e.g. com_foobar
	 * @param   boolean  $force      Force reload even if it's already parsed?
	 *
	 * @return  void
	 */
	public function parseComponent($component, $force = false)
	{
		if (!$force && isset(self::$configurations[$component]))
		{
			return;
		}

		if (FOFPlatform::getInstance()->isCli())
		{
			$order = array('cli', 'backend');
		}
		elseif (FOFPlatform::getInstance()->isBackend())
		{
			$order = array('backend');
		}
		else
		{
			$order = array('frontend');
		}

		$order[] = 'common';

		$order = array_reverse($order);
		self::$configurations[$component] = array();

		foreach ($order as $area)
		{
			$config = $this->parseComponentArea($component, $area);
			self::$configurations[$component] = array_merge_recursive(self::$configurations[$component], $config);
		}
	}

	/**
	 * Returns the value of a variable. Variables use a dot notation, e.g.
	 * view.config.whatever where the first part is the domain, the rest of the
	 * parts specify the path to the variable.
	 *
	 * @param   string  $variable  The variable name
	 * @param   mixed   $default   The default value, or null if not specified
	 *
	 * @return  mixed  The value of the variable
	 */
	public function get($variable, $default = null)
	{
		static $domains = null;

		if (is_null($domains))
		{
			$domains = $this->getDomains();
		}

		list($component, $domain, $var) = explode('.', $variable, 3);

		if (!isset(self::$configurations[$component]))
		{
			$this->parseComponent($component);
		}

		if (!in_array($domain, $domains))
		{
			return $default;
		}

		$class = 'FOFConfigDomain' . ucfirst($domain);
		$o = new $class;

		return $o->get(self::$configurations[$component], $var, $default);
	}

	/**
	 * Parses the configuration options of a specific component area
	 *
	 * @param   string  $component  Which component's cionfiguration to parse
	 * @param   string  $area       Which area to parse (frontend, backend, cli)
	 *
	 * @return  array  A hash array with the configuration data
	 */
	protected function parseComponentArea($component, $area)
	{
		// Initialise the return array
		$ret = array();

		// Get the folders of the component
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);
        $filesystem     = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		// Check that the path exists
		$path = $componentPaths['admin'];
		$path = $filesystem->pathCheck($path);

		if (!$filesystem->folderExists($path))
		{
			return $ret;
		}

		// Read the filename if it exists
		$filename = $path . '/fof.xml';

		if (!$filesystem->fileExists($filename))
		{
			return $ret;
		}

		$data = file_get_contents($filename);

		// Load the XML data in a SimpleXMLElement object
		$xml = simplexml_load_string($data);

		if (!($xml instanceof SimpleXMLElement))
		{
			return $ret;
		}

		// Get this area's data
		$areaData = $xml->xpath('//' . $area);

		if (empty($areaData))
		{
			return $ret;
		}

		$xml = array_shift($areaData);

		// Parse individual configuration domains
		$domains = $this->getDomains();

		foreach ($domains as $dom)
		{
			$class = 'FOFConfigDomain' . ucfirst($dom);

			if (class_exists($class, true))
			{
				$o = new $class;
				$o->parseDomain($xml, $ret);
			}
		}

		// Finally, return the result
		return $ret;
	}

	/**
	 * Gets a list of the available configuration domain adapters
	 *
	 * @return  array  A list of the available domains
	 */
	protected function getDomains()
	{
		static $domains = array();

		if (empty($domains))
		{
			$filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

			$files = $filesystem->folderFiles(__DIR__ . '/domain', '.php');

			if (!empty($files))
			{
				foreach ($files as $file)
				{
					$domain = basename($file, '.php');

					if ($domain == 'interface')
					{
						continue;
					}

					$domain = preg_replace('/[^A-Za-z0-9]/', '', $domain);
					$domains[] = $domain;
				}

				$domains = array_unique($domains);
			}
		}

		return $domains;
	}
}
fof/config/domain/views.php000064400000020231152177723700011711 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  config
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * Configuration parser for the view-specific settings
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFConfigDomainViews implements FOFConfigDomainInterface
{
	/**
	 * Parse the XML data, adding them to the $ret array
	 *
	 * @param   SimpleXMLElement  $xml   The XML data of the component's configuration area
	 * @param   array             &$ret  The parsed data, in the form of a hash array
	 *
	 * @return  void
	 */
	public function parseDomain(SimpleXMLElement $xml, array &$ret)
	{
		// Initialise
		$ret['views'] = array();

		// Parse view configuration
		$viewData = $xml->xpath('view');

		// Sanity check

		if (empty($viewData))
		{
			return;
		}

		foreach ($viewData as $aView)
		{
			$key = (string) $aView['name'];

			// Parse ACL options
			$ret['views'][$key]['acl'] = array();
			$aclData = $aView->xpath('acl/task');

			if (!empty($aclData))
			{
				foreach ($aclData as $acl)
				{
					$k = (string) $acl['name'];
					$ret['views'][$key]['acl'][$k] = (string) $acl;
				}
			}

			// Parse taskmap
			$ret['views'][$key]['taskmap'] = array();
			$taskmapData = $aView->xpath('taskmap/task');

			if (!empty($taskmapData))
			{
				foreach ($taskmapData as $map)
				{
					$k = (string) $map['name'];
					$ret['views'][$key]['taskmap'][$k] = (string) $map;
				}
			}

			// Parse controller configuration
			$ret['views'][$key]['config'] = array();
			$optionData = $aView->xpath('config/option');

			if (!empty($optionData))
			{
				foreach ($optionData as $option)
				{
					$k = (string) $option['name'];
					$ret['views'][$key]['config'][$k] = (string) $option;
				}
			}

			// Parse the toolbar
			$ret['views'][$key]['toolbar'] = array();
			$toolBars = $aView->xpath('toolbar');

			if (!empty($toolBars))
			{
				foreach ($toolBars as $toolBar)
				{
					$taskName = isset($toolBar['task']) ? (string) $toolBar['task'] : '*';

					// If a toolbar title is specified, create a title element.
					if (isset($toolBar['title']))
					{
						$ret['views'][$key]['toolbar'][$taskName]['title'] = array(
							'value' => (string) $toolBar['title']
						);
					}

					// Parse the toolbar buttons data
					$toolbarData = $toolBar->xpath('button');

					if (!empty($toolbarData))
					{
						foreach ($toolbarData as $button)
						{
							$k = (string) $button['type'];
							$ret['views'][$key]['toolbar'][$taskName][$k] = current($button->attributes());
							$ret['views'][$key]['toolbar'][$taskName][$k]['value'] = (string) $button;
						}
					}
				}
			}
		}
	}

	/**
	 * Return a configuration variable
	 *
	 * @param   string  &$configuration  Configuration variables (hashed array)
	 * @param   string  $var             The variable we want to fetch
	 * @param   mixed   $default         Default value
	 *
	 * @return  mixed  The variable's value
	 */
	public function get(&$configuration, $var, $default)
	{
		$parts = explode('.', $var);

		$view = $parts[0];
		$method = 'get' . ucfirst($parts[1]);

		if (!method_exists($this, $method))
		{
			return $default;
		}

		array_shift($parts);
		array_shift($parts);

		$ret = $this->$method($view, $configuration, $parts, $default);

		return $ret;
	}

	/**
	 * Internal function to return the task map for a view
	 *
	 * @param   string  $view            The view for which we will be fetching a task map
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options (not used)
	 * @param   array   $default         ßDefault task map; empty array if not provided
	 *
	 * @return  array  The task map as a hash array in the format task => method
	 */
	protected function getTaskmap($view, &$configuration, $params, $default = array())
	{
		$taskmap = array();

		if (isset($configuration['views']['*']) && isset($configuration['views']['*']['taskmap']))
		{
			$taskmap = $configuration['views']['*']['taskmap'];
		}

		if (isset($configuration['views'][$view]) && isset($configuration['views'][$view]['taskmap']))
		{
			$taskmap = array_merge($taskmap, $configuration['views'][$view]['taskmap']);
		}

		if (empty($taskmap))
		{
			return $default;
		}

		return $taskmap;
	}

	/**
	 * Internal method to return the ACL mapping (privilege required to access
	 * a specific task) for the given view's tasks
	 *
	 * @param   string  $view            The view for which we will be fetching a task map
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the task we want to fetch
	 * @param   string  $default         Default ACL option; empty (no ACL check) if not defined
	 *
	 * @return  string  The privilege required to access this view
	 */
	protected function getAcl($view, &$configuration, $params, $default = '')
	{
		$aclmap = array();

		if (isset($configuration['views']['*']) && isset($configuration['views']['*']['acl']))
		{
			$aclmap = $configuration['views']['*']['acl'];
		}

		if (isset($configuration['views'][$view]) && isset($configuration['views'][$view]['acl']))
		{
			$aclmap = array_merge($aclmap, $configuration['views'][$view]['acl']);
		}

		$acl = $default;

		if (isset($aclmap['*']))
		{
			$acl = $aclmap['*'];
		}

		if (isset($aclmap[$params[0]]))
		{
			$acl = $aclmap[$params[0]];
		}

		return $acl;
	}

	/**
	 * Internal method to return the a configuration option for the view. These
	 * are equivalent to $config array options passed to the Controller
	 *
	 * @param   string  $view            The view for which we will be fetching a task map
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the option variable we want to fetch
	 * @param   mixed   $default         Default option; null if not defined
	 *
	 * @return  string  The setting for the requested option
	 */
	protected function getConfig($view, &$configuration, $params, $default = null)
	{
		$ret = $default;

		if (isset($configuration['views']['*'])
			&& isset($configuration['views']['*']['config'])
			&& isset($configuration['views']['*']['config'][$params[0]]))
		{
			$ret = $configuration['views']['*']['config'][$params[0]];
		}

		if (isset($configuration['views'][$view])
			&& isset($configuration['views'][$view]['config'])
			&& isset($configuration['views'][$view]['config'][$params[0]]))
		{
			$ret = $configuration['views'][$view]['config'][$params[0]];
		}

		return $ret;
	}

	/**
	 * Internal method to return the toolbar infos.
	 *
	 * @param   string  $view            The view for which we will be fetching buttons
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options
	 * @param   string  $default         Default option
	 *
	 * @return  string  The toolbar data for this view
	 */
	protected function getToolbar($view, &$configuration, $params, $default = '')
	{
		$toolbar = array();

		if (isset($configuration['views']['*'])
			&& isset($configuration['views']['*']['toolbar'])
			&& isset($configuration['views']['*']['toolbar']['*']))
		{
			$toolbar = $configuration['views']['*']['toolbar']['*'];
		}

		if (isset($configuration['views']['*'])
			&& isset($configuration['views']['*']['toolbar'])
			&& isset($configuration['views']['*']['toolbar'][$params[0]]))
		{
			$toolbar = array_merge($toolbar, $configuration['views']['*']['toolbar'][$params[0]]);
		}

		if (isset($configuration['views'][$view])
			&& isset($configuration['views'][$view]['toolbar'])
			&& isset($configuration['views'][$view]['toolbar']['*']))
		{
			$toolbar = array_merge($toolbar, $configuration['views'][$view]['toolbar']['*']);
		}

		if (isset($configuration['views'][$view])
			&& isset($configuration['views'][$view]['toolbar'])
			&& isset($configuration['views'][$view]['toolbar'][$params[0]]))
		{
			$toolbar = array_merge($toolbar, $configuration['views'][$view]['toolbar'][$params[0]]);
		}

		if (empty($toolbar))
		{
			return $default;
		}

		return $toolbar;
	}
}
fof/config/domain/interface.php000064400000002346152177723700012523 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  config
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * The Interface of an FOFConfigDomain class. The methods are used to parse and
 * privision sensible information to consumers. FOFConfigProvider acts as an
 * adapter to the FOFConfigDomain classes.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
interface FOFConfigDomainInterface
{
	/**
	 * Parse the XML data, adding them to the $ret array
	 *
	 * @param   SimpleXMLElement  $xml   The XML data of the component's configuration area
	 * @param   array             &$ret  The parsed data, in the form of a hash array
	 *
	 * @return  void
	 */
	public function parseDomain(SimpleXMLElement $xml, array &$ret);

	/**
	 * Return a configuration variable
	 *
	 * @param   string  &$configuration  Configuration variables (hashed array)
	 * @param   string  $var             The variable we want to fetch
	 * @param   mixed   $default         Default value
	 *
	 * @return  mixed  The variable's value
	 */
	public function get(&$configuration, $var, $default);
}
fof/config/domain/tables.php000064400000016606152177723700012041 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  config
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * Configuration parser for the tables-specific settings
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFConfigDomainTables implements FOFConfigDomainInterface
{
	/**
	 * Parse the XML data, adding them to the $ret array
	 *
	 * @param   SimpleXMLElement  $xml   The XML data of the component's configuration area
	 * @param   array             &$ret  The parsed data, in the form of a hash array
	 *
	 * @return  void
	 */
	public function parseDomain(SimpleXMLElement $xml, array &$ret)
	{
		// Initialise
		$ret['tables'] = array();

		// Parse table configuration
		$tableData = $xml->xpath('table');

		// Sanity check
		if (empty($tableData))
		{
			return;
		}

		foreach ($tableData as $aTable)
		{
			$key = (string) $aTable['name'];

			$ret['tables'][$key]['behaviors'] = (string) $aTable->behaviors;
			$ret['tables'][$key]['tablealias'] = $aTable->xpath('tablealias');
			$ret['tables'][$key]['fields'] = array();
			$ret['tables'][$key]['relations'] = array();

			$fieldData = $aTable->xpath('field');

			if (!empty($fieldData))
			{
				foreach ($fieldData as $field)
				{
					$k = (string) $field['name'];
					$ret['tables'][$key]['fields'][$k] = (string) $field;
				}
			}

			$relationsData = $aTable->xpath('relation');

			if (!empty($relationsData))
			{
				foreach ($relationsData as $relationData)
				{
					$type = (string)$relationData['type'];
					$itemName = (string)$relationData['name'];

					if (empty($type) || empty($itemName))
					{
						continue;
					}

					$tableClass		= (string)$relationData['tableClass'];
					$localKey		= (string)$relationData['localKey'];
					$remoteKey		= (string)$relationData['remoteKey'];
					$ourPivotKey	= (string)$relationData['ourPivotKey'];
					$theirPivotKey	= (string)$relationData['theirPivotKey'];
					$pivotTable		= (string)$relationData['pivotTable'];
					$default		= (string)$relationData['default'];

					$default = !in_array($default, array('no', 'false', 0));

					$relation = array(
						'type'			=> $type,
						'itemName'		=> $itemName,
						'tableClass'	=> empty($tableClass) ? null : $tableClass,
						'localKey'		=> empty($localKey) ? null : $localKey,
						'remoteKey'		=> empty($remoteKey) ? null : $remoteKey,
						'default'		=> $default,
					);

					if (!empty($ourPivotKey) || !empty($theirPivotKey) || !empty($pivotTable))
					{
						$relation['ourPivotKey']	= empty($ourPivotKey) ? null : $ourPivotKey;
						$relation['theirPivotKey']	= empty($theirPivotKey) ? null : $theirPivotKey;
						$relation['pivotTable']	= empty($pivotTable) ? null : $pivotTable;
					}

					$ret['tables'][$key]['relations'][] = $relation;
				}
			}
		}
	}

	/**
	 * Return a configuration variable
	 *
	 * @param   string  &$configuration  Configuration variables (hashed array)
	 * @param   string  $var             The variable we want to fetch
	 * @param   mixed   $default         Default value
	 *
	 * @return  mixed  The variable's value
	 */
	public function get(&$configuration, $var, $default)
	{
		$parts = explode('.', $var);

		$view = $parts[0];
		$method = 'get' . ucfirst($parts[1]);

		if (!method_exists($this, $method))
		{
			return $default;
		}

		array_shift($parts);
		array_shift($parts);

		$ret = $this->$method($view, $configuration, $parts, $default);

		return $ret;
	}

	/**
	 * Internal method to return the magic field mapping
	 *
	 * @param   string  $table           The table for which we will be fetching a field map
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the table we want to fetch
	 * @param   string  $default         Default magic field mapping; empty if not defined
	 *
	 * @return  array   Field map
	 */
	protected function getField($table, &$configuration, $params, $default = '')
	{
		$fieldmap = array();

		if (isset($configuration['tables']['*']) && isset($configuration['tables']['*']['fields']))
		{
			$fieldmap = $configuration['tables']['*']['fields'];
		}

		if (isset($configuration['tables'][$table]) && isset($configuration['tables'][$table]['fields']))
		{
			$fieldmap = array_merge($fieldmap, $configuration['tables'][$table]['fields']);
		}

		$map = $default;

		if (empty($params[0]))
		{
			$map = $fieldmap;
		}
		elseif (isset($fieldmap[$params[0]]))
		{
			$map = $fieldmap[$params[0]];
		}

		return $map;
	}

	/**
	 * Internal method to get table alias
	 *
	 * @param   string  $table           The table for which we will be fetching table alias
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the table we want to fetch
	 * @param   string  $default         Default table alias
	 *
	 * @return  string  Table alias
	 */
	protected function getTablealias($table, &$configuration, $params, $default = '')
	{
		$tablealias = $default;

		if (isset($configuration['tables']['*'])
			&& isset($configuration['tables']['*']['tablealias'])
			&& isset($configuration['tables']['*']['tablealias'][0]))
		{
			$tablealias = (string) $configuration['tables']['*']['tablealias'][0];
		}

		if (isset($configuration['tables'][$table])
			&& isset($configuration['tables'][$table]['tablealias'])
			&& isset($configuration['tables'][$table]['tablealias'][0]))
		{
			$tablealias = (string) $configuration['tables'][$table]['tablealias'][0];
		}

		return $tablealias;
	}

	/**
	 * Internal method to get table behaviours
	 *
	 * @param   string  $table           The table for which we will be fetching table alias
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the table we want to fetch
	 * @param   string  $default         Default table alias
	 *
	 * @return  string  Table behaviours
	 */
	protected function getBehaviors($table, &$configuration, $params, $default = '')
	{
		$behaviors = $default;

		if (isset($configuration['tables']['*'])
			&& isset($configuration['tables']['*']['behaviors']))
		{
			$behaviors = (string) $configuration['tables']['*']['behaviors'];
		}

		if (isset($configuration['tables'][$table])
			&& isset($configuration['tables'][$table]['behaviors']))
		{
			$behaviors = (string) $configuration['tables'][$table]['behaviors'];
		}

		return $behaviors;
	}

	/**
	 * Internal method to get table relations
	 *
	 * @param   string  $table           The table for which we will be fetching table alias
	 * @param   array   &$configuration  The configuration parameters hash array
	 * @param   array   $params          Extra options; key 0 defines the table we want to fetch
	 * @param   string  $default         Default table alias
	 *
	 * @return  array   Table relations
	 */
	protected function getRelations($table, &$configuration, $params, $default = '')
	{
		$relations = $default;

		if (isset($configuration['tables']['*'])
			&& isset($configuration['tables']['*']['relations']))
		{
			$relations = $configuration['tables']['*']['relations'];
		}

		if (isset($configuration['tables'][$table])
			&& isset($configuration['tables'][$table]['relations']))
		{
			$relations = $configuration['tables'][$table]['relations'];
		}

		return $relations;
	}
}
fof/config/domain/dispatcher.php000064400000003246152177723700012711 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  config
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * Configuration parser for the dispatcher-specific settings
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFConfigDomainDispatcher implements FOFConfigDomainInterface
{
	/**
	 * Parse the XML data, adding them to the $ret array
	 *
	 * @param   SimpleXMLElement  $xml   The XML data of the component's configuration area
	 * @param   array             &$ret  The parsed data, in the form of a hash array
	 *
	 * @return  void
	 */
	public function parseDomain(SimpleXMLElement $xml, array &$ret)
	{
		// Initialise
		$ret['dispatcher'] = array();

		// Parse the dispatcher configuration
		$dispatcherData = $xml->dispatcher;

		// Sanity check

		if (empty($dispatcherData))
		{
			return;
		}

		$options = $xml->xpath('dispatcher/option');

		if (!empty($options))
		{
			foreach ($options as $option)
			{
				$key = (string) $option['name'];
				$ret['dispatcher'][$key] = (string) $option;
			}
		}
	}

	/**
	 * Return a configuration variable
	 *
	 * @param   string  &$configuration  Configuration variables (hashed array)
	 * @param   string  $var             The variable we want to fetch
	 * @param   mixed   $default         Default value
	 *
	 * @return  mixed  The variable's value
	 */
	public function get(&$configuration, $var, $default)
	{
		if (isset($configuration['dispatcher'][$var]))
		{
			return $configuration['dispatcher'][$var];
		}
		else
		{
			return $default;
		}
	}
}
fof/less/less.php000064400000200323152177723700007756 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken near verbatim (changes marked with **FOF** comment markers) from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * THIS IS THIRD PARTY CODE. Code comments are mostly useless placeholders to
 * stop phpcs from complaining...
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFLess
{
	public static $VERSION = "v0.3.9";

	protected static $TRUE = array("keyword", "true");

	protected static $FALSE = array("keyword", "false");

	protected $libFunctions = array();

	protected $registeredVars = array();

	protected $preserveComments = false;

	/**
	 * Prefix of abstract properties
	 *
	 * @var  string
	 */
	public $vPrefix = '@';

	/**
	 * Prefix of abstract blocks
	 *
	 * @var  string
	 */
	public $mPrefix = '$';

	public $parentSelector = '&';

	public $importDisabled = false;

	public $importDir = '';

	protected $numberPrecision = null;

	/**
	 * Set to the parser that generated the current line when compiling
	 * so we know how to create error messages
	 *
	 * @var  FOFLessParser
	 */
	protected $sourceParser = null;

	protected $sourceLoc = null;

	public static $defaultValue = array("keyword", "");

	/**
	 * Uniquely identify imports
	 *
	 * @var  integer
	 */
	protected static $nextImportId = 0;

	/**
	 * Attempts to find the path of an import url, returns null for css files
	 *
	 * @param   string  $url  The URL of the import
	 *
	 * @return  string|null
	 */
	protected function findImport($url)
	{
		foreach ((array) $this->importDir as $dir)
		{
			$full = $dir . (substr($dir, -1) != '/' ? '/' : '') . $url;

			if ($this->fileExists($file = $full . '.less') || $this->fileExists($file = $full))
			{
				return $file;
			}
		}

		return null;
	}

	/**
	 * Does file $name exists? It's a simple proxy to JFile for now
	 *
	 * @param   string  $name  The file we check for existence
	 *
	 * @return  boolean
	 */
	protected function fileExists($name)
	{
		/** FOF - BEGIN CHANGE * */
		return FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileExists($name);
		/** FOF - END CHANGE * */
	}

	/**
	 * Compresslist
	 *
	 * @param   array   $items  Items
	 * @param   string  $delim  Delimiter
	 *
	 * @return  array
	 */
	public static function compressList($items, $delim)
	{
		if (!isset($items[1]) && isset($items[0]))
		{
			return $items[0];
		}
		else
		{
			return array('list', $delim, $items);
		}
	}

	/**
	 * Quote for regular expression
	 *
	 * @param   string  $what  What to quote
	 *
	 * @return  string  Quoted string
	 */
	public static function preg_quote($what)
	{
		return preg_quote($what, '/');
	}

	/**
	 * Try import
	 *
	 * @param   string     $importPath   Import path
	 * @param   stdObject  $parentBlock  Parent block
	 * @param   string     $out          Out
	 *
	 * @return  boolean
	 */
	protected function tryImport($importPath, $parentBlock, $out)
	{
		if ($importPath[0] == "function" && $importPath[1] == "url")
		{
			$importPath = $this->flattenList($importPath[2]);
		}

		$str = $this->coerceString($importPath);

		if ($str === null)
		{
			return false;
		}

		$url = $this->compileValue($this->lib_e($str));

		// Don't import if it ends in css
		if (substr_compare($url, '.css', -4, 4) === 0)
		{
			return false;
		}

		$realPath = $this->findImport($url);

		if ($realPath === null)
		{
			return false;
		}

		if ($this->importDisabled)
		{
			return array(false, "/* import disabled */");
		}

		$this->addParsedFile($realPath);
		$parser = $this->makeParser($realPath);
		$root = $parser->parse(file_get_contents($realPath));

		// Set the parents of all the block props
		foreach ($root->props as $prop)
		{
			if ($prop[0] == "block")
			{
				$prop[1]->parent = $parentBlock;
			}
		}

		/**
		 * Copy mixins into scope, set their parents, bring blocks from import
		 * into current block
		 * TODO: need to mark the source parser	these came from this file
		 */
		foreach ($root->children as $childName => $child)
		{
			if (isset($parentBlock->children[$childName]))
			{
				$parentBlock->children[$childName] = array_merge(
					$parentBlock->children[$childName], $child
				);
			}
			else
			{
				$parentBlock->children[$childName] = $child;
			}
		}

		$pi = pathinfo($realPath);
		$dir = $pi["dirname"];

		list($top, $bottom) = $this->sortProps($root->props, true);
		$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);

		return array(true, $bottom, $parser, $dir);
	}

	/**
	 * Compile Imported Props
	 *
	 * @param   array          $props         Props
	 * @param   stdClass       $block         Block
	 * @param   string         $out           Out
	 * @param   FOFLessParser  $sourceParser  Source parser
	 * @param   string         $importDir     Import dir
	 *
	 * @return  void
	 */
	protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir)
	{
		$oldSourceParser = $this->sourceParser;

		$oldImport = $this->importDir;

		// TODO: this is because the importDir api is stupid
		$this->importDir = (array) $this->importDir;
		array_unshift($this->importDir, $importDir);

		foreach ($props as $prop)
		{
			$this->compileProp($prop, $block, $out);
		}

		$this->importDir = $oldImport;
		$this->sourceParser = $oldSourceParser;
	}

	/**
	 * Recursively compiles a block.
	 *
	 * A block is analogous to a CSS block in most cases. A single LESS document
	 * is encapsulated in a block when parsed, but it does not have parent tags
	 * so all of it's children appear on the root level when compiled.
	 *
	 * Blocks are made up of props and children.
	 *
	 * Props are property instructions, array tuples which describe an action
	 * to be taken, eg. write a property, set a variable, mixin a block.
	 *
	 * The children of a block are just all the blocks that are defined within.
	 * This is used to look up mixins when performing a mixin.
	 *
	 * Compiling the block involves pushing a fresh environment on the stack,
	 * and iterating through the props, compiling each one.
	 *
	 * @param   stdClass  $block  Block
	 *
	 * @see  FOFLess::compileProp()
	 *
	 * @return  void
	 */
	protected function compileBlock($block)
	{
		switch ($block->type)
		{
			case "root":
				$this->compileRoot($block);
				break;
			case null:
				$this->compileCSSBlock($block);
				break;
			case "media":
				$this->compileMedia($block);
				break;
			case "directive":
				$name = "@" . $block->name;

				if (!empty($block->value))
				{
					$name .= " " . $this->compileValue($this->reduce($block->value));
				}

				$this->compileNestedBlock($block, array($name));
				break;
			default:
				$this->throwError("unknown block type: $block->type\n");
		}
	}

	/**
	 * Compile CSS block
	 *
	 * @param   stdClass  $block  Block to compile
	 *
	 * @return  void
	 */
	protected function compileCSSBlock($block)
	{
		$env = $this->pushEnv();

		$selectors = $this->compileSelectors($block->tags);
		$env->selectors = $this->multiplySelectors($selectors);
		$out = $this->makeOutputBlock(null, $env->selectors);

		$this->scope->children[] = $out;
		$this->compileProps($block, $out);

		// Mixins carry scope with them!
		$block->scope = $env;
		$this->popEnv();
	}

	/**
	 * Compile media
	 *
	 * @param   stdClass  $media  Media
	 *
	 * @return  void
	 */
	protected function compileMedia($media)
	{
		$env = $this->pushEnv($media);
		$parentScope = $this->mediaParent($this->scope);

		$query = $this->compileMediaQuery($this->multiplyMedia($env));

		$this->scope = $this->makeOutputBlock($media->type, array($query));
		$parentScope->children[] = $this->scope;

		$this->compileProps($media, $this->scope);

		if (count($this->scope->lines) > 0)
		{
			$orphanSelelectors = $this->findClosestSelectors();

			if (!is_null($orphanSelelectors))
			{
				$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
				$orphan->lines = $this->scope->lines;
				array_unshift($this->scope->children, $orphan);
				$this->scope->lines = array();
			}
		}

		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	/**
	 * Media parent
	 *
	 * @param   stdClass  $scope  Scope
	 *
	 * @return  stdClass
	 */
	protected function mediaParent($scope)
	{
		while (!empty($scope->parent))
		{
			if (!empty($scope->type) && $scope->type != "media")
			{
				break;
			}

			$scope = $scope->parent;
		}

		return $scope;
	}

	/**
	 * Compile nested block
	 *
	 * @param   stdClass  $block      Block
	 * @param   array     $selectors  Selectors
	 *
	 * @return  void
	 */
	protected function compileNestedBlock($block, $selectors)
	{
		$this->pushEnv($block);
		$this->scope = $this->makeOutputBlock($block->type, $selectors);
		$this->scope->parent->children[] = $this->scope;

		$this->compileProps($block, $this->scope);

		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	/**
	 * Compile root
	 *
	 * @param   stdClass  $root  Root
	 *
	 * @return  void
	 */
	protected function compileRoot($root)
	{
		$this->pushEnv();
		$this->scope = $this->makeOutputBlock($root->type);
		$this->compileProps($root, $this->scope);
		$this->popEnv();
	}

	/**
	 * Compile props
	 *
	 * @param   type  $block  Something
	 * @param   type  $out    Something
	 *
	 * @return  void
	 */
	protected function compileProps($block, $out)
	{
		foreach ($this->sortProps($block->props) as $prop)
		{
			$this->compileProp($prop, $block, $out);
		}
	}

	/**
	 * Sort props
	 *
	 * @param   type  $props  X
	 * @param   type  $split  X
	 *
	 * @return  type
	 */
	protected function sortProps($props, $split = false)
	{
		$vars    = array();
		$imports = array();
		$other   = array();

		foreach ($props as $prop)
		{
			switch ($prop[0])
			{
				case "assign":
					if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix)
					{
						$vars[] = $prop;
					}
					else
					{
						$other[] = $prop;
					}
					break;
				case "import":
					$id        = self::$nextImportId++;
					$prop[]    = $id;
					$imports[] = $prop;
					$other[]   = array("import_mixin", $id);
					break;
				default:
					$other[] = $prop;
			}
		}

		if ($split)
		{
			return array(array_merge($vars, $imports), $other);
		}
		else
		{
			return array_merge($vars, $imports, $other);
		}
	}

	/**
	 * Compile media query
	 *
	 * @param   type  $queries  Queries
	 *
	 * @return  string
	 */
	protected function compileMediaQuery($queries)
	{
		$compiledQueries = array();

		foreach ($queries as $query)
		{
			$parts = array();

			foreach ($query as $q)
			{
				switch ($q[0])
				{
					case "mediaType":
						$parts[] = implode(" ", array_slice($q, 1));
						break;
					case "mediaExp":
						if (isset($q[2]))
						{
							$parts[] = "($q[1]: " .
								$this->compileValue($this->reduce($q[2])) . ")";
						}
						else
						{
							$parts[] = "($q[1])";
						}
						break;
					case "variable":
						$parts[] = $this->compileValue($this->reduce($q));
						break;
				}
			}

			if (count($parts) > 0)
			{
				$compiledQueries[] = implode(" and ", $parts);
			}
		}

		$out = "@media";

		if (!empty($parts))
		{
			$out .= " " .
				implode($this->formatter->selectorSeparator, $compiledQueries);
		}

		return $out;
	}

	/**
	 * Multiply media
	 *
	 * @param   type  $env           X
	 * @param   type  $childQueries  X
	 *
	 * @return  type
	 */
	protected function multiplyMedia($env, $childQueries = null)
	{
		if (is_null($env)
			|| !empty($env->block->type)
			&& $env->block->type != "media")
		{
			return $childQueries;
		}

		// Plain old block, skip
		if (empty($env->block->type))
		{
			return $this->multiplyMedia($env->parent, $childQueries);
		}

		$out = array();
		$queries = $env->block->queries;

		if (is_null($childQueries))
		{
			$out = $queries;
		}
		else
		{
			foreach ($queries as $parent)
			{
				foreach ($childQueries as $child)
				{
					$out[] = array_merge($parent, $child);
				}
			}
		}

		return $this->multiplyMedia($env->parent, $out);
	}

	/**
	 * Expand parent selectors
	 *
	 * @param   type  &$tag     Tag
	 * @param   type  $replace  Replace
	 *
	 * @return  type
	 */
	protected function expandParentSelectors(&$tag, $replace)
	{
		$parts = explode("$&$", $tag);
		$count = 0;

		foreach ($parts as &$part)
		{
			$part = str_replace($this->parentSelector, $replace, $part, $c);
			$count += $c;
		}

		$tag = implode($this->parentSelector, $parts);

		return $count;
	}

	/**
	 * Find closest selectors
	 *
	 * @return  array
	 */
	protected function findClosestSelectors()
	{
		$env = $this->env;
		$selectors = null;

		while ($env !== null)
		{
			if (isset($env->selectors))
			{
				$selectors = $env->selectors;
				break;
			}

			$env = $env->parent;
		}

		return $selectors;
	}

	/**
	 * Multiply $selectors against the nearest selectors in env
	 *
	 * @param   array  $selectors  The selectors
	 *
	 * @return  array
	 */
	protected function multiplySelectors($selectors)
	{
		// Find parent selectors

		$parentSelectors = $this->findClosestSelectors();

		if (is_null($parentSelectors))
		{
			// Kill parent reference in top level selector
			foreach ($selectors as &$s)
			{
				$this->expandParentSelectors($s, "");
			}

			return $selectors;
		}

		$out = array();

		foreach ($parentSelectors as $parent)
		{
			foreach ($selectors as $child)
			{
				$count = $this->expandParentSelectors($child, $parent);

				// Don't prepend the parent tag if & was used
				if ($count > 0)
				{
					$out[] = trim($child);
				}
				else
				{
					$out[] = trim($parent . ' ' . $child);
				}
			}
		}

		return $out;
	}

	/**
	 * Reduces selector expressions
	 *
	 * @param   array  $selectors  The selector expressions
	 *
	 * @return  array
	 */
	protected function compileSelectors($selectors)
	{
		$out = array();

		foreach ($selectors as $s)
		{
			if (is_array($s))
			{
				list(, $value) = $s;
				$out[] = trim($this->compileValue($this->reduce($value)));
			}
			else
			{
				$out[] = $s;
			}
		}

		return $out;
	}

	/**
	 * Equality check
	 *
	 * @param   mixed  $left   Left operand
	 * @param   mixed  $right  Right operand
	 *
	 * @return  boolean  True if equal
	 */
	protected function eq($left, $right)
	{
		return $left == $right;
	}

	/**
	 * Pattern match
	 *
	 * @param   type  $block        X
	 * @param   type  $callingArgs  X
	 *
	 * @return  boolean
	 */
	protected function patternMatch($block, $callingArgs)
	{
		/**
		 * Match the guards if it has them
		 * any one of the groups must have all its guards pass for a match
		 */
		if (!empty($block->guards))
		{
			$groupPassed = false;

			foreach ($block->guards as $guardGroup)
			{
				foreach ($guardGroup as $guard)
				{
					$this->pushEnv();
					$this->zipSetArgs($block->args, $callingArgs);

					$negate = false;

					if ($guard[0] == "negate")
					{
						$guard = $guard[1];
						$negate = true;
					}

					$passed = $this->reduce($guard) == self::$TRUE;

					if ($negate)
					{
						$passed = !$passed;
					}

					$this->popEnv();

					if ($passed)
					{
						$groupPassed = true;
					}
					else
					{
						$groupPassed = false;
						break;
					}
				}

				if ($groupPassed)
				{
					break;
				}
			}

			if (!$groupPassed)
			{
				return false;
			}
		}

		$numCalling = count($callingArgs);

		if (empty($block->args))
		{
			return $block->isVararg || $numCalling == 0;
		}

		// No args
		$i = -1;

		// Try to match by arity or by argument literal
		foreach ($block->args as $i => $arg)
		{
			switch ($arg[0])
			{
				case "lit":
					if (empty($callingArgs[$i]) || !$this->eq($arg[1], $callingArgs[$i]))
					{
						return false;
					}
					break;
				case "arg":
					// No arg and no default value
					if (!isset($callingArgs[$i]) && !isset($arg[2]))
					{
						return false;
					}
					break;
				case "rest":
					// Rest can be empty
					$i--;
					break 2;
			}
		}

		if ($block->isVararg)
		{
			// Not having enough is handled above
			return true;
		}
		else
		{
			$numMatched = $i + 1;

			// Greater than becuase default values always match
			return $numMatched >= $numCalling;
		}
	}

	/**
	 * Pattern match all
	 *
	 * @param   type  $blocks       X
	 * @param   type  $callingArgs  X
	 *
	 * @return  type
	 */
	protected function patternMatchAll($blocks, $callingArgs)
	{
		$matches = null;

		foreach ($blocks as $block)
		{
			if ($this->patternMatch($block, $callingArgs))
			{
				$matches[] = $block;
			}
		}

		return $matches;
	}

	/**
	 * Attempt to find blocks matched by path and args
	 *
	 * @param   array   $searchIn  Block to search in
	 * @param   string  $path      The path to search for
	 * @param   array   $args      Arguments
	 * @param   array   $seen      Your guess is as good as mine; that's third party code
	 *
	 * @return  null
	 */
	protected function findBlocks($searchIn, $path, $args, $seen = array())
	{
		if ($searchIn == null)
		{
			return null;
		}

		if (isset($seen[$searchIn->id]))
		{
			return null;
		}

		$seen[$searchIn->id] = true;

		$name = $path[0];

		if (isset($searchIn->children[$name]))
		{
			$blocks = $searchIn->children[$name];

			if (count($path) == 1)
			{
				$matches = $this->patternMatchAll($blocks, $args);

				if (!empty($matches))
				{
					// This will return all blocks that match in the closest
					// scope that has any matching block, like lessjs
					return $matches;
				}
			}
			else
			{
				$matches = array();

				foreach ($blocks as $subBlock)
				{
					$subMatches = $this->findBlocks($subBlock, array_slice($path, 1), $args, $seen);

					if (!is_null($subMatches))
					{
						foreach ($subMatches as $sm)
						{
							$matches[] = $sm;
						}
					}
				}

				return count($matches) > 0 ? $matches : null;
			}
		}

		if ($searchIn->parent === $searchIn)
		{
			return null;
		}

		return $this->findBlocks($searchIn->parent, $path, $args, $seen);
	}

	/**
	 * Sets all argument names in $args to either the default value
	 * or the one passed in through $values
	 *
	 * @param   array  $args    Arguments
	 * @param   array  $values  Values
	 *
	 * @return  void
	 */
	protected function zipSetArgs($args, $values)
	{
		$i = 0;
		$assignedValues = array();

		foreach ($args as $a)
		{
			if ($a[0] == "arg")
			{
				if ($i < count($values) && !is_null($values[$i]))
				{
					$value = $values[$i];
				}
				elseif (isset($a[2]))
				{
					$value = $a[2];
				}
				else
				{
					$value = null;
				}

				$value = $this->reduce($value);
				$this->set($a[1], $value);
				$assignedValues[] = $value;
			}

			$i++;
		}

		// Check for a rest
		$last = end($args);

		if ($last[0] == "rest")
		{
			$rest = array_slice($values, count($args) - 1);
			$this->set($last[1], $this->reduce(array("list", " ", $rest)));
		}

		$this->env->arguments = $assignedValues;
	}

	/**
	 * Compile a prop and update $lines or $blocks appropriately
	 *
	 * @param   array     $prop   Prop
	 * @param   stdClass  $block  Block
	 * @param   string    $out    Out
	 *
	 * @return  void
	 */
	protected function compileProp($prop, $block, $out)
	{
		// Set error position context
		$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;

		switch ($prop[0])
		{
			case 'assign':
				list(, $name, $value) = $prop;

				if ($name[0] == $this->vPrefix)
				{
					$this->set($name, $value);
				}
				else
				{
					$out->lines[] = $this->formatter->property($name, $this->compileValue($this->reduce($value)));
				}
				break;
			case 'block':
				list(, $child) = $prop;
				$this->compileBlock($child);
				break;
			case 'mixin':
				list(, $path, $args, $suffix) = $prop;

				$args = array_map(array($this, "reduce"), (array) $args);
				$mixins = $this->findBlocks($block, $path, $args);

				if ($mixins === null)
				{
					// Throw error here??
					break;
				}

				foreach ($mixins as $mixin)
				{
					$haveScope = false;

					if (isset($mixin->parent->scope))
					{
						$haveScope = true;
						$mixinParentEnv = $this->pushEnv();
						$mixinParentEnv->storeParent = $mixin->parent->scope;
					}

					$haveArgs = false;

					if (isset($mixin->args))
					{
						$haveArgs = true;
						$this->pushEnv();
						$this->zipSetArgs($mixin->args, $args);
					}

					$oldParent = $mixin->parent;

					if ($mixin != $block)
					{
						$mixin->parent = $block;
					}

					foreach ($this->sortProps($mixin->props) as $subProp)
					{
						if ($suffix !== null
							&& $subProp[0] == "assign"
							&& is_string($subProp[1])
							&& $subProp[1]{0} != $this->vPrefix)
						{
							$subProp[2] = array(
								'list', ' ',
								array($subProp[2], array('keyword', $suffix))
							);
						}

						$this->compileProp($subProp, $mixin, $out);
					}

					$mixin->parent = $oldParent;

					if ($haveArgs)
					{
						$this->popEnv();
					}

					if ($haveScope)
					{
						$this->popEnv();
					}
				}

				break;
			case 'raw':
				$out->lines[] = $prop[1];
				break;
			case "directive":
				list(, $name, $value) = $prop;
				$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)) . ';';
				break;
			case "comment":
				$out->lines[] = $prop[1];
				break;
			case "import";
				list(, $importPath, $importId) = $prop;
				$importPath = $this->reduce($importPath);

				if (!isset($this->env->imports))
				{
					$this->env->imports = array();
				}

				$result = $this->tryImport($importPath, $block, $out);

				$this->env->imports[$importId] = $result === false ?
					array(false, "@import " . $this->compileValue($importPath) . ";") :
					$result;

				break;
			case "import_mixin":
				list(, $importId) = $prop;
				$import = $this->env->imports[$importId];

				if ($import[0] === false)
				{
					$out->lines[] = $import[1];
				}
				else
				{
					list(, $bottom, $parser, $importDir) = $import;
					$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
				}

				break;
			default:
				$this->throwError("unknown op: {$prop[0]}\n");
		}
	}

	/**
	 * Compiles a primitive value into a CSS property value.
	 *
	 * Values in lessphp are typed by being wrapped in arrays, their format is
	 * typically:
	 *
	 *     array(type, contents [, additional_contents]*)
	 *
	 * The input is expected to be reduced. This function will not work on
	 * things like expressions and variables.
	 *
	 * @param   array  $value  Value
	 *
	 * @return  void
	 */
	protected function compileValue($value)
	{
		switch ($value[0])
		{
			case 'list':
				// [1] - delimiter
				// [2] - array of values
				return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
			case 'raw_color':
				if (!empty($this->formatter->compressColors))
				{
					return $this->compileValue($this->coerceColor($value));
				}

				return $value[1];
			case 'keyword':
				// [1] - the keyword
				return $value[1];
			case 'number':
				// Format: [1] - the number -- [2] - the unit
				list(, $num, $unit) = $value;

				if ($this->numberPrecision !== null)
				{
					$num = round($num, $this->numberPrecision);
				}

				return $num . $unit;
			case 'string':
				// [1] - contents of string (includes quotes)
				list(, $delim, $content) = $value;

				foreach ($content as &$part)
				{
					if (is_array($part))
					{
						$part = $this->compileValue($part);
					}
				}

				return $delim . implode($content) . $delim;
			case 'color':
				/**
				 * Format:
				 *
				 * [1] - red component (either number or a %)
				 * [2] - green component
				 * [3] - blue component
				 * [4] - optional alpha component
				 */
				list(, $r, $g, $b) = $value;
				$r = round($r);
				$g = round($g);
				$b = round($b);

				if (count($value) == 5 && $value[4] != 1)
				{
					// Return an rgba value
					return 'rgba(' . $r . ',' . $g . ',' . $b . ',' . $value[4] . ')';
				}

				$h = sprintf("#%02x%02x%02x", $r, $g, $b);

				if (!empty($this->formatter->compressColors))
				{
					// Converting hex color to short notation (e.g. #003399 to #039)
					if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6])
					{
						$h = '#' . $h[1] . $h[3] . $h[5];
					}
				}

				return $h;

			case 'function':
				list(, $name, $args) = $value;

				return $name . '(' . $this->compileValue($args) . ')';

			default:
				// Assumed to be unit
				$this->throwError("unknown value type: $value[0]");
		}
	}

	/**
	 * Lib is number
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_isnumber($value)
	{
		return $this->toBool($value[0] == "number");
	}

	/**
	 * Lib is string
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_isstring($value)
	{
		return $this->toBool($value[0] == "string");
	}

	/**
	 * Lib is color
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_iscolor($value)
	{
		return $this->toBool($this->coerceColor($value));
	}

	/**
	 * Lib is keyword
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_iskeyword($value)
	{
		return $this->toBool($value[0] == "keyword");
	}

	/**
	 * Lib is pixel
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_ispixel($value)
	{
		return $this->toBool($value[0] == "number" && $value[2] == "px");
	}

	/**
	 * Lib is percentage
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_ispercentage($value)
	{
		return $this->toBool($value[0] == "number" && $value[2] == "%");
	}

	/**
	 * Lib is em
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_isem($value)
	{
		return $this->toBool($value[0] == "number" && $value[2] == "em");
	}

	/**
	 * Lib is rem
	 *
	 * @param   type  $value  X
	 *
	 * @return  boolean
	 */
	protected function lib_isrem($value)
	{
		return $this->toBool($value[0] == "number" && $value[2] == "rem");
	}

	/**
	 * LIb rgba hex
	 *
	 * @param   type  $color  X
	 *
	 * @return  boolean
	 */
	protected function lib_rgbahex($color)
	{
		$color = $this->coerceColor($color);

		if (is_null($color))
		{
			$this->throwError("color expected for rgbahex");
		}

		return sprintf("#%02x%02x%02x%02x", isset($color[4]) ? $color[4] * 255 : 255, $color[1], $color[2], $color[3]);
	}

	/**
	 * Lib argb
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function lib_argb($color)
	{
		return $this->lib_rgbahex($color);
	}

	/**
	 * Utility func to unquote a string
	 *
	 * @param   string  $arg  Arg
	 *
	 * @return  string
	 */
	protected function lib_e($arg)
	{
		switch ($arg[0])
		{
			case "list":
				$items = $arg[2];

				if (isset($items[0]))
				{
					return $this->lib_e($items[0]);
				}

				return self::$defaultValue;

			case "string":
				$arg[1] = "";

				return $arg;

			case "keyword":
				return $arg;

			default:
				return array("keyword", $this->compileValue($arg));
		}
	}

	/**
	 * Lib sprintf
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib__sprintf($args)
	{
		if ($args[0] != "list")
		{
			return $args;
		}

		$values = $args[2];
		$string = array_shift($values);
		$template = $this->compileValue($this->lib_e($string));

		$i = 0;

		if (preg_match_all('/%[dsa]/', $template, $m))
		{
			foreach ($m[0] as $match)
			{
				$val = isset($values[$i]) ?
					$this->reduce($values[$i]) : array('keyword', '');

				// Lessjs compat, renders fully expanded color, not raw color
				if ($color = $this->coerceColor($val))
				{
					$val = $color;
				}

				$i++;
				$rep = $this->compileValue($this->lib_e($val));
				$template = preg_replace('/' . self::preg_quote($match) . '/', $rep, $template, 1);
			}
		}

		$d = $string[0] == "string" ? $string[1] : '"';

		return array("string", $d, array($template));
	}

	/**
	 * Lib floor
	 *
	 * @param   type  $arg  X
	 *
	 * @return  array
	 */
	protected function lib_floor($arg)
	{
		$value = $this->assertNumber($arg);

		return array("number", floor($value), $arg[2]);
	}

	/**
	 * Lib ceil
	 *
	 * @param   type  $arg  X
	 *
	 * @return  array
	 */
	protected function lib_ceil($arg)
	{
		$value = $this->assertNumber($arg);

		return array("number", ceil($value), $arg[2]);
	}

	/**
	 * Lib round
	 *
	 * @param   type  $arg  X
	 *
	 * @return  array
	 */
	protected function lib_round($arg)
	{
		$value = $this->assertNumber($arg);

		return array("number", round($value), $arg[2]);
	}

	/**
	 * Lib unit
	 *
	 * @param   type  $arg  X
	 *
	 * @return  array
	 */
	protected function lib_unit($arg)
	{
		if ($arg[0] == "list")
		{
			list($number, $newUnit) = $arg[2];
			return array("number", $this->assertNumber($number), $this->compileValue($this->lib_e($newUnit)));
		}
		else
		{
			return array("number", $this->assertNumber($arg), "");
		}
	}

	/**
	 * Helper function to get arguments for color manipulation functions.
	 * takes a list that contains a color like thing and a percentage
	 *
	 * @param   array  $args  Args
	 *
	 * @return  array
	 */
	protected function colorArgs($args)
	{
		if ($args[0] != 'list' || count($args[2]) < 2)
		{
			return array(array('color', 0, 0, 0), 0);
		}

		list($color, $delta) = $args[2];
		$color = $this->assertColor($color);
		$delta = floatval($delta[1]);

		return array($color, $delta);
	}

	/**
	 * Lib darken
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_darken($args)
	{
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);

		return $this->toRGB($hsl);
	}

	/**
	 * Lib lighten
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_lighten($args)
	{
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);

		return $this->toRGB($hsl);
	}

	/**
	 * Lib saturate
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_saturate($args)
	{
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);

		return $this->toRGB($hsl);
	}

	/**
	 * Lib desaturate
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_desaturate($args)
	{
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);

		return $this->toRGB($hsl);
	}

	/**
	 * Lib spin
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_spin($args)
	{
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);

		$hsl[1] = $hsl[1] + $delta % 360;

		if ($hsl[1] < 0)
		{
			$hsl[1] += 360;
		}

		return $this->toRGB($hsl);
	}

	/**
	 * Lib fadeout
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_fadeout($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta / 100);

		return $color;
	}

	/**
	 * Lib fadein
	 *
	 * @param   type  $args  X
	 *
	 * @return  type
	 */
	protected function lib_fadein($args)
	{
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta / 100);

		return $color;
	}

	/**
	 * Lib hue
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function lib_hue($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));

		return round($hsl[1]);
	}

	/**
	 * Lib saturation
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function lib_saturation($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));

		return round($hsl[2]);
	}

	/**
	 * Lib lightness
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function lib_lightness($color)
	{
		$hsl = $this->toHSL($this->assertColor($color));

		return round($hsl[3]);
	}

	/**
	 * Get the alpha of a color
	 * Defaults to 1 for non-colors or colors without an alpha
	 *
	 * @param   string  $value  Value
	 *
	 * @return  string
	 */
	protected function lib_alpha($value)
	{
		if (!is_null($color = $this->coerceColor($value)))
		{
			return isset($color[4]) ? $color[4] : 1;
		}
	}

	/**
	 * Set the alpha of the color
	 *
	 * @param   array  $args  Args
	 *
	 * @return  string
	 */
	protected function lib_fade($args)
	{
		list($color, $alpha) = $this->colorArgs($args);
		$color[4] = $this->clamp($alpha / 100.0);

		return $color;
	}

	/**
	 * Third party code; your guess is as good as mine
	 *
	 * @param   array  $arg  Arg
	 *
	 * @return  string
	 */
	protected function lib_percentage($arg)
	{
		$num = $this->assertNumber($arg);

		return array("number", $num * 100, "%");
	}

	/**
	 * mixes two colors by weight
	 * mix(@color1, @color2, @weight);
	 * http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
	 *
	 * @param   array  $args  Args
	 *
	 * @return  string
	 */
	protected function lib_mix($args)
	{
		if ($args[0] != "list" || count($args[2]) < 3)
		{
			$this->throwError("mix expects (color1, color2, weight)");
		}

		list($first, $second, $weight) = $args[2];
		$first = $this->assertColor($first);
		$second = $this->assertColor($second);

		$first_a = $this->lib_alpha($first);
		$second_a = $this->lib_alpha($second);
		$weight = $weight[1] / 100.0;

		$w = $weight * 2 - 1;
		$a = $first_a - $second_a;

		$w1 = (($w * $a == -1 ? $w : ($w + $a) / (1 + $w * $a)) + 1) / 2.0;
		$w2 = 1.0 - $w1;

		$new = array('color',
			$w1 * $first[1] + $w2 * $second[1],
			$w1 * $first[2] + $w2 * $second[2],
			$w1 * $first[3] + $w2 * $second[3],
		);

		if ($first_a != 1.0 || $second_a != 1.0)
		{
			$new[] = $first_a * $weight + $second_a * ($weight - 1);
		}

		return $this->fixColor($new);
	}

	/**
	 * Third party code; your guess is as good as mine
	 *
	 * @param   array  $arg  Arg
	 *
	 * @return  string
	 */
	protected function lib_contrast($args)
	{
		if ($args[0] != 'list' || count($args[2]) < 3)
		{
			return array(array('color', 0, 0, 0), 0);
		}

		list($inputColor, $darkColor, $lightColor) = $args[2];

		$inputColor = $this->assertColor($inputColor);
		$darkColor = $this->assertColor($darkColor);
		$lightColor = $this->assertColor($lightColor);
		$hsl = $this->toHSL($inputColor);

		if ($hsl[3] > 50)
		{
			return $darkColor;
		}

		return $lightColor;
	}

	/**
	 * Assert color
	 *
	 * @param   type  $value  X
	 * @param   type  $error  X
	 *
	 * @return  type
	 */
	protected function assertColor($value, $error = "expected color value")
	{
		$color = $this->coerceColor($value);

		if (is_null($color))
		{
			$this->throwError($error);
		}

		return $color;
	}

	/**
	 * Assert number
	 *
	 * @param   type  $value  X
	 * @param   type  $error  X
	 *
	 * @return  type
	 */
	protected function assertNumber($value, $error = "expecting number")
	{
		if ($value[0] == "number")
		{
			return $value[1];
		}

		$this->throwError($error);
	}

	/**
	 * To HSL
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function toHSL($color)
	{
		if ($color[0] == 'hsl')
		{
			return $color;
		}

		$r = $color[1] / 255;
		$g = $color[2] / 255;
		$b = $color[3] / 255;

		$min = min($r, $g, $b);
		$max = max($r, $g, $b);

		$L = ($min + $max) / 2;

		if ($min == $max)
		{
			$S = $H = 0;
		}
		else
		{
			if ($L < 0.5)
			{
				$S = ($max - $min) / ($max + $min);
			}
			else
			{
				$S = ($max - $min) / (2.0 - $max - $min);
			}

			if ($r == $max)
			{
				$H = ($g - $b) / ($max - $min);
			}
			elseif ($g == $max)
			{
				$H = 2.0 + ($b - $r) / ($max - $min);
			}
			elseif ($b == $max)
			{
				$H = 4.0 + ($r - $g) / ($max - $min);
			}
		}

		$out = array('hsl',
			($H < 0 ? $H + 6 : $H) * 60,
			$S * 100,
			$L * 100,
		);

		if (count($color) > 4)
		{
			// Copy alpha
			$out[] = $color[4];
		}

		return $out;
	}

	/**
	 * To RGB helper
	 *
	 * @param   type  $comp   X
	 * @param   type  $temp1  X
	 * @param   type  $temp2  X
	 *
	 * @return  type
	 */
	protected function toRGB_helper($comp, $temp1, $temp2)
	{
		if ($comp < 0)
		{
			$comp += 1.0;
		}
		elseif ($comp > 1)
		{
			$comp -= 1.0;
		}

		if (6 * $comp < 1)
		{
			return $temp1 + ($temp2 - $temp1) * 6 * $comp;
		}

		if (2 * $comp < 1)
		{
			return $temp2;
		}

		if (3 * $comp < 2)
		{
			return $temp1 + ($temp2 - $temp1) * ((2 / 3) - $comp) * 6;
		}

		return $temp1;
	}

	/**
	 * Converts a hsl array into a color value in rgb.
	 * Expects H to be in range of 0 to 360, S and L in 0 to 100
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	protected function toRGB($color)
	{
		if ($color == 'color')
		{
			return $color;
		}

		$H = $color[1] / 360;
		$S = $color[2] / 100;
		$L = $color[3] / 100;

		if ($S == 0)
		{
			$r = $g = $b = $L;
		}
		else
		{
			$temp2 = $L < 0.5 ?
				$L * (1.0 + $S) :
				$L + $S - $L * $S;

			$temp1 = 2.0 * $L - $temp2;

			$r = $this->toRGB_helper($H + 1 / 3, $temp1, $temp2);
			$g = $this->toRGB_helper($H, $temp1, $temp2);
			$b = $this->toRGB_helper($H - 1 / 3, $temp1, $temp2);
		}

		// $out = array('color', round($r*255), round($g*255), round($b*255));
		$out = array('color', $r * 255, $g * 255, $b * 255);

		if (count($color) > 4)
		{
			// Copy alpha
			$out[] = $color[4];
		}

		return $out;
	}

	/**
	 * Clamp
	 *
	 * @param   type  $v    X
	 * @param   type  $max  X
	 * @param   type  $min  X
	 *
	 * @return  type
	 */
	protected function clamp($v, $max = 1, $min = 0)
	{
		return min($max, max($min, $v));
	}

	/**
	 * Convert the rgb, rgba, hsl color literals of function type
	 * as returned by the parser into values of color type.
	 *
	 * @param   type  $func  X
	 *
	 * @return  type
	 */
	protected function funcToColor($func)
	{
		$fname = $func[1];

		if ($func[2][0] != 'list')
		{
			// Need a list of arguments
			return false;
		}

		$rawComponents = $func[2][2];

		if ($fname == 'hsl' || $fname == 'hsla')
		{
			$hsl = array('hsl');
			$i = 0;

			foreach ($rawComponents as $c)
			{
				$val = $this->reduce($c);
				$val = isset($val[1]) ? floatval($val[1]) : 0;

				if ($i == 0)
				{
					$clamp = 360;
				}
				elseif ($i < 3)
				{
					$clamp = 100;
				}
				else
				{
					$clamp = 1;
				}

				$hsl[] = $this->clamp($val, $clamp);
				$i++;
			}

			while (count($hsl) < 4)
			{
				$hsl[] = 0;
			}

			return $this->toRGB($hsl);
		}
		elseif ($fname == 'rgb' || $fname == 'rgba')
		{
			$components = array();
			$i = 1;

			foreach ($rawComponents as $c)
			{
				$c = $this->reduce($c);

				if ($i < 4)
				{
					if ($c[0] == "number" && $c[2] == "%")
					{
						$components[] = 255 * ($c[1] / 100);
					}
					else
					{
						$components[] = floatval($c[1]);
					}
				}
				elseif ($i == 4)
				{
					if ($c[0] == "number" && $c[2] == "%")
					{
						$components[] = 1.0 * ($c[1] / 100);
					}
					else
					{
						$components[] = floatval($c[1]);
					}
				}
				else
				{
					break;
				}

				$i++;
			}

			while (count($components) < 3)
			{
				$components[] = 0;
			}

			array_unshift($components, 'color');

			return $this->fixColor($components);
		}

		return false;
	}

	/**
	 * Reduce
	 *
	 * @param   type  $value          X
	 * @param   type  $forExpression  X
	 *
	 * @return  type
	 */
	protected function reduce($value, $forExpression = false)
	{
		switch ($value[0])
		{
			case "interpolate":
				$reduced = $this->reduce($value[1]);
				$var     = $this->compileValue($reduced);
				$res     = $this->reduce(array("variable", $this->vPrefix . $var));

				if (empty($value[2]))
				{
					$res = $this->lib_e($res);
				}

				return $res;
			case "variable":
				$key = $value[1];
				if (is_array($key))
				{
					$key = $this->reduce($key);
					$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
				}

				$seen = & $this->env->seenNames;

				if (!empty($seen[$key]))
				{
					$this->throwError("infinite loop detected: $key");
				}

				$seen[$key] = true;
				$out = $this->reduce($this->get($key, self::$defaultValue));
				$seen[$key] = false;

				return $out;
			case "list":
				foreach ($value[2] as &$item)
				{
					$item = $this->reduce($item, $forExpression);
				}

				return $value;
			case "expression":
				return $this->evaluate($value);
			case "string":
				foreach ($value[2] as &$part)
				{
					if (is_array($part))
					{
						$strip = $part[0] == "variable";
						$part = $this->reduce($part);

						if ($strip)
						{
							$part = $this->lib_e($part);
						}
					}
				}

				return $value;
			case "escape":
				list(, $inner) = $value;

				return $this->lib_e($this->reduce($inner));
			case "function":
				$color = $this->funcToColor($value);

				if ($color)
				{
					return $color;
				}

				list(, $name, $args) = $value;

				if ($name == "%")
				{
					$name = "_sprintf";
				}

				$f = isset($this->libFunctions[$name]) ?
					$this->libFunctions[$name] : array($this, 'lib_' . $name);

				if (is_callable($f))
				{
					if ($args[0] == 'list')
					{
						$args = self::compressList($args[2], $args[1]);
					}

					$ret = call_user_func($f, $this->reduce($args, true), $this);

					if (is_null($ret))
					{
						return array("string", "", array(
								$name, "(", $args, ")"
							));
					}

					// Convert to a typed value if the result is a php primitive
					if (is_numeric($ret))
					{
						$ret = array('number', $ret, "");
					}
					elseif (!is_array($ret))
					{
						$ret = array('keyword', $ret);
					}

					return $ret;
				}

				// Plain function, reduce args
				$value[2] = $this->reduce($value[2]);

				return $value;
			case "unary":
				list(, $op, $exp) = $value;
				$exp = $this->reduce($exp);

				if ($exp[0] == "number")
				{
					switch ($op)
					{
						case "+":
							return $exp;
						case "-":
							$exp[1] *= -1;

							return $exp;
					}
				}

				return array("string", "", array($op, $exp));
		}

		if ($forExpression)
		{
			switch ($value[0])
			{
				case "keyword":
					if ($color = $this->coerceColor($value))
					{
						return $color;
					}
					break;
				case "raw_color":
					return $this->coerceColor($value);
			}
		}

		return $value;
	}

	/**
	 * Coerce a value for use in color operation
	 *
	 * @param   type  $value  X
	 *
	 * @return  null
	 */
	protected function coerceColor($value)
	{
		switch ($value[0])
		{
			case 'color':
				return $value;
			case 'raw_color':
				$c = array("color", 0, 0, 0);
				$colorStr = substr($value[1], 1);
				$num = hexdec($colorStr);
				$width = strlen($colorStr) == 3 ? 16 : 256;

				for ($i = 3; $i > 0; $i--)
				{
					// It's 3 2 1
					$t = $num % $width;
					$num /= $width;

					$c[$i] = $t * (256 / $width) + $t * floor(16 / $width);
				}

				return $c;
			case 'keyword':
				$name = $value[1];

				if (isset(self::$cssColors[$name]))
				{
					$rgba = explode(',', self::$cssColors[$name]);

					if (isset($rgba[3]))
					{
						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
					}

					return array('color', $rgba[0], $rgba[1], $rgba[2]);
				}

				return null;
		}
	}

	/**
	 * Make something string like into a string
	 *
	 * @param   type  $value  X
	 *
	 * @return  null
	 */
	protected function coerceString($value)
	{
		switch ($value[0])
		{
			case "string":
				return $value;
			case "keyword":
				return array("string", "", array($value[1]));
		}

		return null;
	}

	/**
	 * Turn list of length 1 into value type
	 *
	 * @param   type  $value  X
	 *
	 * @return  type
	 */
	protected function flattenList($value)
	{
		if ($value[0] == "list" && count($value[2]) == 1)
		{
			return $this->flattenList($value[2][0]);
		}

		return $value;
	}

	/**
	 * To bool
	 *
	 * @param   type  $a  X
	 *
	 * @return  type
	 */
	protected function toBool($a)
	{
		if ($a)
		{
			return self::$TRUE;
		}
		else
		{
			return self::$FALSE;
		}
	}

	/**
	 * Evaluate an expression
	 *
	 * @param   type  $exp  X
	 *
	 * @return  type
	 */
	protected function evaluate($exp)
	{
		list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;

		$left = $this->reduce($left, true);
		$right = $this->reduce($right, true);

		if ($leftColor = $this->coerceColor($left))
		{
			$left = $leftColor;
		}

		if ($rightColor = $this->coerceColor($right))
		{
			$right = $rightColor;
		}

		$ltype = $left[0];
		$rtype = $right[0];

		// Operators that work on all types
		if ($op == "and")
		{
			return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
		}

		if ($op == "=")
		{
			return $this->toBool($this->eq($left, $right));
		}

		if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right)))
		{
			return $str;
		}

		// Type based operators
		$fname = "op_${ltype}_${rtype}";

		if (is_callable(array($this, $fname)))
		{
			$out = $this->$fname($op, $left, $right);

			if (!is_null($out))
			{
				return $out;
			}
		}

		// Make the expression look it did before being parsed
		$paddedOp = $op;

		if ($whiteBefore)
		{
			$paddedOp = " " . $paddedOp;
		}

		if ($whiteAfter)
		{
			$paddedOp .= " ";
		}

		return array("string", "", array($left, $paddedOp, $right));
	}

	/**
	 * String concatenate
	 *
	 * @param   type    $left   X
	 * @param   string  $right  X
	 *
	 * @return  string
	 */
	protected function stringConcatenate($left, $right)
	{
		if ($strLeft = $this->coerceString($left))
		{
			if ($right[0] == "string")
			{
				$right[1] = "";
			}

			$strLeft[2][] = $right;

			return $strLeft;
		}

		if ($strRight = $this->coerceString($right))
		{
			array_unshift($strRight[2], $left);

			return $strRight;
		}
	}

	/**
	 * Make sure a color's components don't go out of bounds
	 *
	 * @param   type  $c  X
	 *
	 * @return  int
	 */
	protected function fixColor($c)
	{
		foreach (range(1, 3) as $i)
		{
			if ($c[$i] < 0)
			{
				$c[$i] = 0;
			}

			if ($c[$i] > 255)
			{
				$c[$i] = 255;
			}
		}

		return $c;
	}

	/**
	 * Op number color
	 *
	 * @param   type  $op   X
	 * @param   type  $lft  X
	 * @param   type  $rgt  X
	 *
	 * @return  type
	 */
	protected function op_number_color($op, $lft, $rgt)
	{
		if ($op == '+' || $op == '*')
		{
			return $this->op_color_number($op, $rgt, $lft);
		}
	}

	/**
	 * Op color number
	 *
	 * @param   type  $op   X
	 * @param   type  $lft  X
	 * @param   int   $rgt  X
	 *
	 * @return  type
	 */
	protected function op_color_number($op, $lft, $rgt)
	{
		if ($rgt[0] == '%')
		{
			$rgt[1] /= 100;
		}

		return $this->op_color_color($op, $lft, array_fill(1, count($lft) - 1, $rgt[1]));
	}

	/**
	 * Op color color
	 *
	 * @param   type  $op     X
	 * @param   type  $left   X
	 * @param   type  $right  X
	 *
	 * @return  type
	 */
	protected function op_color_color($op, $left, $right)
	{
		$out = array('color');
		$max = count($left) > count($right) ? count($left) : count($right);

		foreach (range(1, $max - 1) as $i)
		{
			$lval = isset($left[$i]) ? $left[$i] : 0;
			$rval = isset($right[$i]) ? $right[$i] : 0;

			switch ($op)
			{
				case '+':
					$out[] = $lval + $rval;
					break;
				case '-':
					$out[] = $lval - $rval;
					break;
				case '*':
					$out[] = $lval * $rval;
					break;
				case '%':
					$out[] = $lval % $rval;
					break;
				case '/':
					if ($rval == 0)
					{
						$this->throwError("evaluate error: can't divide by zero");
					}

					$out[] = $lval / $rval;
					break;
				default:
					$this->throwError('evaluate error: color op number failed on op ' . $op);
			}
		}

		return $this->fixColor($out);
	}

	/**
	 * Lib red
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	public function lib_red($color)
	{
		$color = $this->coerceColor($color);

		if (is_null($color))
		{
			$this->throwError('color expected for red()');
		}

		return $color[1];
	}

	/**
	 * Lib green
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	public function lib_green($color)
	{
		$color = $this->coerceColor($color);

		if (is_null($color))
		{
			$this->throwError('color expected for green()');
		}

		return $color[2];
	}

	/**
	 * Lib blue
	 *
	 * @param   type  $color  X
	 *
	 * @return  type
	 */
	public function lib_blue($color)
	{
		$color = $this->coerceColor($color);

		if (is_null($color))
		{
			$this->throwError('color expected for blue()');
		}

		return $color[3];
	}

	/**
	 * Operator on two numbers
	 *
	 * @param   type  $op     X
	 * @param   type  $left   X
	 * @param   type  $right  X
	 *
	 * @return  type
	 */
	protected function op_number_number($op, $left, $right)
	{
		$unit = empty($left[2]) ? $right[2] : $left[2];

		$value = 0;

		switch ($op)
		{
			case '+':
				$value = $left[1] + $right[1];
				break;
			case '*':
				$value = $left[1] * $right[1];
				break;
			case '-':
				$value = $left[1] - $right[1];
				break;
			case '%':
				$value = $left[1] % $right[1];
				break;
			case '/':
				if ($right[1] == 0)
				{
					$this->throwError('parse error: divide by zero');
				}

				$value = $left[1] / $right[1];
				break;
			case '<':
				return $this->toBool($left[1] < $right[1]);
			case '>':
				return $this->toBool($left[1] > $right[1]);
			case '>=':
				return $this->toBool($left[1] >= $right[1]);
			case '=<':
				return $this->toBool($left[1] <= $right[1]);
			default:
				$this->throwError('parse error: unknown number operator: ' . $op);
		}

		return array("number", $value, $unit);
	}

	/**
	 * Make output block
	 *
	 * @param   type  $type       X
	 * @param   type  $selectors  X
	 *
	 * @return  stdclass
	 */
	protected function makeOutputBlock($type, $selectors = null)
	{
		$b = new stdclass;
		$b->lines = array();
		$b->children = array();
		$b->selectors = $selectors;
		$b->type = $type;
		$b->parent = $this->scope;

		return $b;
	}

	/**
	 * The state of execution
	 *
	 * @param   type  $block  X
	 *
	 * @return  stdclass
	 */
	protected function pushEnv($block = null)
	{
		$e = new stdclass;
		$e->parent = $this->env;
		$e->store = array();
		$e->block = $block;

		$this->env = $e;

		return $e;
	}

	/**
	 * Pop something off the stack
	 *
	 * @return  type
	 */
	protected function popEnv()
	{
		$old = $this->env;
		$this->env = $this->env->parent;

		return $old;
	}

	/**
	 * Set something in the current env
	 *
	 * @param   type  $name   X
	 * @param   type  $value  X
	 *
	 * @return  void
	 */
	protected function set($name, $value)
	{
		$this->env->store[$name] = $value;
	}

	/**
	 * Get the highest occurrence entry for a name
	 *
	 * @param   type  $name     X
	 * @param   type  $default  X
	 *
	 * @return  type
	 */
	protected function get($name, $default = null)
	{
		$current = $this->env;

		$isArguments = $name == $this->vPrefix . 'arguments';

		while ($current)
		{
			if ($isArguments && isset($current->arguments))
			{
				return array('list', ' ', $current->arguments);
			}

			if (isset($current->store[$name]))
			{
				return $current->store[$name];
			}
			else
			{
				$current = isset($current->storeParent) ?
					$current->storeParent : $current->parent;
			}
		}

		return $default;
	}

	/**
	 * Inject array of unparsed strings into environment as variables
	 *
	 * @param   type  $args  X
	 *
	 * @return  void
	 *
	 * @throws  Exception
	 */
	protected function injectVariables($args)
	{
		$this->pushEnv();
		/** FOF -- BEGIN CHANGE * */
		$parser = new FOFLessParser($this, __METHOD__);
		/** FOF -- END CHANGE * */
		foreach ($args as $name => $strValue)
		{
			if ($name{0} != '@')
			{
				$name = '@' . $name;
			}

			$parser->count = 0;
			$parser->buffer = (string) $strValue;

			if (!$parser->propertyValue($value))
			{
				throw new Exception("failed to parse passed in variable $name: $strValue");
			}

			$this->set($name, $value);
		}
	}

	/**
	 * Initialize any static state, can initialize parser for a file
	 *
	 * @param   type  $fname  X
	 */
	public function __construct($fname = null)
	{
		if ($fname !== null)
		{
			// Used for deprecated parse method
			$this->_parseFile = $fname;
		}
	}

	/**
	 * Compile
	 *
	 * @param   type  $string  X
	 * @param   type  $name    X
	 *
	 * @return  type
	 */
	public function compile($string, $name = null)
	{
		$locale = setlocale(LC_NUMERIC, 0);
		setlocale(LC_NUMERIC, "C");

		$this->parser = $this->makeParser($name);
		$root = $this->parser->parse($string);

		$this->env = null;
		$this->scope = null;

		$this->formatter = $this->newFormatter();

		if (!empty($this->registeredVars))
		{
			$this->injectVariables($this->registeredVars);
		}

		// Used for error messages
		$this->sourceParser = $this->parser;
		$this->compileBlock($root);

		ob_start();
		$this->formatter->block($this->scope);
		$out = ob_get_clean();
		setlocale(LC_NUMERIC, $locale);

		return $out;
	}

	/**
	 * Compile file
	 *
	 * @param   type  $fname     X
	 * @param   type  $outFname  X
	 *
	 * @return  type
	 *
	 * @throws  Exception
	 */
	public function compileFile($fname, $outFname = null)
	{
		if (!is_readable($fname))
		{
			throw new Exception('load error: failed to find ' . $fname);
		}

		$pi = pathinfo($fname);

		$oldImport = $this->importDir;

		$this->importDir = (array) $this->importDir;
		$this->importDir[] = $pi['dirname'] . '/';

		$this->allParsedFiles = array();
		$this->addParsedFile($fname);

		$out = $this->compile(file_get_contents($fname), $fname);

		$this->importDir = $oldImport;

		if ($outFname !== null)
		{
			/** FOF - BEGIN CHANGE * */
			return FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileWrite($outFname, $out);
			/** FOF - END CHANGE * */
		}

		return $out;
	}

	/**
	 * Compile only if changed input has changed or output doesn't exist
	 *
	 * @param   type  $in   X
	 * @param   type  $out  X
	 *
	 * @return  boolean
	 */
	public function checkedCompile($in, $out)
	{
		if (!is_file($out) || filemtime($in) > filemtime($out))
		{
			$this->compileFile($in, $out);

			return true;
		}

		return false;
	}

	/**
	 * Execute lessphp on a .less file or a lessphp cache structure
	 *
	 * The lessphp cache structure contains information about a specific
	 * less file having been parsed. It can be used as a hint for future
	 * calls to determine whether or not a rebuild is required.
	 *
	 * The cache structure contains two important keys that may be used
	 * externally:
	 *
	 * compiled: The final compiled CSS
	 * updated: The time (in seconds) the CSS was last compiled
	 *
	 * The cache structure is a plain-ol' PHP associative array and can
	 * be serialized and unserialized without a hitch.
	 *
	 * @param   mixed  $in     Input
	 * @param   bool   $force  Force rebuild?
	 *
	 * @return  array  lessphp cache structure
	 */
	public function cachedCompile($in, $force = false)
	{
		// Assume no root
		$root = null;

		if (is_string($in))
		{
			$root = $in;
		}
		elseif (is_array($in) and isset($in['root']))
		{
			if ($force or !isset($in['files']))
			{
				/**
				 * If we are forcing a recompile or if for some reason the
				 * structure does not contain any file information we should
				 * specify the root to trigger a rebuild.
				 */
				$root = $in['root'];
			}
			elseif (isset($in['files']) and is_array($in['files']))
			{
				foreach ($in['files'] as $fname => $ftime)
				{
					if (!file_exists($fname) or filemtime($fname) > $ftime)
					{
						/**
						 * One of the files we knew about previously has changed
						 * so we should look at our incoming root again.
						 */
						$root = $in['root'];
						break;
					}
				}
			}
		}
		else
		{
			/**
			 * TODO: Throw an exception? We got neither a string nor something
			 * that looks like a compatible lessphp cache structure.
			 */
			return null;
		}

		if ($root !== null)
		{
			// If we have a root value which means we should rebuild.
			$out = array();
			$out['root'] = $root;
			$out['compiled'] = $this->compileFile($root);
			$out['files'] = $this->allParsedFiles();
			$out['updated'] = time();

			return $out;
		}
		else
		{
			// No changes, pass back the structure
			// we were given initially.
			return $in;
		}
	}

	//
	// This is deprecated
	/**
	 * Parse and compile buffer
	 *
	 * @param   null  $str               X
	 * @param   type  $initialVariables  X
	 *
	 * @return  type
	 *
	 * @throws  Exception
	 *
	 * @deprecated  2.0
	 */
	public function parse($str = null, $initialVariables = null)
	{
		if (is_array($str))
		{
			$initialVariables = $str;
			$str = null;
		}

		$oldVars = $this->registeredVars;

		if ($initialVariables !== null)
		{
			$this->setVariables($initialVariables);
		}

		if ($str == null)
		{
			if (empty($this->_parseFile))
			{
				throw new exception("nothing to parse");
			}

			$out = $this->compileFile($this->_parseFile);
		}
		else
		{
			$out = $this->compile($str);
		}

		$this->registeredVars = $oldVars;

		return $out;
	}

	/**
	 * Make parser
	 *
	 * @param   type  $name  X
	 *
	 * @return  FOFLessParser
	 */
	protected function makeParser($name)
	{
		/** FOF -- BEGIN CHANGE * */
		$parser = new FOFLessParser($this, $name);
		/** FOF -- END CHANGE * */
		$parser->writeComments = $this->preserveComments;

		return $parser;
	}

	/**
	 * Set Formatter
	 *
	 * @param   type  $name  X
	 *
	 * @return  void
	 */
	public function setFormatter($name)
	{
		$this->formatterName = $name;
	}

	/**
	 * New formatter
	 *
	 * @return  FOFLessFormatterLessjs
	 */
	protected function newFormatter()
	{
		/** FOF -- BEGIN CHANGE * */
		$className = "FOFLessFormatterLessjs";
		/** FOF -- END CHANGE * */
		if (!empty($this->formatterName))
		{
			if (!is_string($this->formatterName))
				return $this->formatterName;
			/** FOF -- BEGIN CHANGE * */
			$className = "FOFLessFormatter" . ucfirst($this->formatterName);
			/** FOF -- END CHANGE * */
		}

		return new $className;
	}

	/**
	 * Set preserve comments
	 *
	 * @param   type  $preserve  X
	 *
	 * @return  void
	 */
	public function setPreserveComments($preserve)
	{
		$this->preserveComments = $preserve;
	}

	/**
	 * Register function
	 *
	 * @param   type  $name  X
	 * @param   type  $func  X
	 *
	 * @return  void
	 */
	public function registerFunction($name, $func)
	{
		$this->libFunctions[$name] = $func;
	}

	/**
	 * Unregister function
	 *
	 * @param   type  $name  X
	 *
	 * @return  void
	 */
	public function unregisterFunction($name)
	{
		unset($this->libFunctions[$name]);
	}

	/**
	 * Set variables
	 *
	 * @param   type  $variables  X
	 *
	 * @return  void
	 */
	public function setVariables($variables)
	{
		$this->registeredVars = array_merge($this->registeredVars, $variables);
	}

	/**
	 * Unset variable
	 *
	 * @param   type  $name  X
	 *
	 * @return  void
	 */
	public function unsetVariable($name)
	{
		unset($this->registeredVars[$name]);
	}

	/**
	 * Set import dir
	 *
	 * @param   type  $dirs  X
	 *
	 * @return  void
	 */
	public function setImportDir($dirs)
	{
		$this->importDir = (array) $dirs;
	}

	/**
	 * Add import dir
	 *
	 * @param   type  $dir  X
	 *
	 * @return  void
	 */
	public function addImportDir($dir)
	{
		$this->importDir = (array) $this->importDir;
		$this->importDir[] = $dir;
	}

	/**
	 * All parsed files
	 *
	 * @return  type
	 */
	public function allParsedFiles()
	{
		return $this->allParsedFiles;
	}

	/**
	 * Add parsed file
	 *
	 * @param   type  $file  X
	 *
	 * @return  void
	 */
	protected function addParsedFile($file)
	{
		$this->allParsedFiles[realpath($file)] = filemtime($file);
	}

	/**
	 * Uses the current value of $this->count to show line and line number
	 *
	 * @param   type  $msg  X
	 *
	 * @return  void
	 */
	protected function throwError($msg = null)
	{
		if ($this->sourceLoc >= 0)
		{
			$this->sourceParser->throwError($msg, $this->sourceLoc);
		}

		throw new exception($msg);
	}

	/**
	 * Compile file $in to file $out if $in is newer than $out
	 * Returns true when it compiles, false otherwise
	 *
	 * @param   type  $in    X
	 * @param   type  $out   X
	 * @param   self  $less  X
	 *
	 * @return  type
	 */
	public static function ccompile($in, $out, $less = null)
	{
		if ($less === null)
		{
			$less = new self;
		}

		return $less->checkedCompile($in, $out);
	}

	/**
	 * Compile execute
	 *
	 * @param   type  $in     X
	 * @param   type  $force  X
	 * @param   self  $less   X
	 *
	 * @return  type
	 */
	public static function cexecute($in, $force = false, $less = null)
	{
		if ($less === null)
		{
			$less = new self;
		}

		return $less->cachedCompile($in, $force);
	}

	protected static $cssColors = array(
		'aliceblue'				 => '240,248,255',
		'antiquewhite'			 => '250,235,215',
		'aqua'					 => '0,255,255',
		'aquamarine'			 => '127,255,212',
		'azure'					 => '240,255,255',
		'beige'					 => '245,245,220',
		'bisque'				 => '255,228,196',
		'black'					 => '0,0,0',
		'blanchedalmond'		 => '255,235,205',
		'blue'					 => '0,0,255',
		'blueviolet'			 => '138,43,226',
		'brown'					 => '165,42,42',
		'burlywood'				 => '222,184,135',
		'cadetblue'				 => '95,158,160',
		'chartreuse'			 => '127,255,0',
		'chocolate'				 => '210,105,30',
		'coral'					 => '255,127,80',
		'cornflowerblue'		 => '100,149,237',
		'cornsilk'				 => '255,248,220',
		'crimson'				 => '220,20,60',
		'cyan'					 => '0,255,255',
		'darkblue'				 => '0,0,139',
		'darkcyan'				 => '0,139,139',
		'darkgoldenrod'			 => '184,134,11',
		'darkgray'				 => '169,169,169',
		'darkgreen'				 => '0,100,0',
		'darkgrey'				 => '169,169,169',
		'darkkhaki'				 => '189,183,107',
		'darkmagenta'			 => '139,0,139',
		'darkolivegreen'		 => '85,107,47',
		'darkorange'			 => '255,140,0',
		'darkorchid'			 => '153,50,204',
		'darkred'				 => '139,0,0',
		'darksalmon'			 => '233,150,122',
		'darkseagreen'			 => '143,188,143',
		'darkslateblue'			 => '72,61,139',
		'darkslategray'			 => '47,79,79',
		'darkslategrey'			 => '47,79,79',
		'darkturquoise'			 => '0,206,209',
		'darkviolet'			 => '148,0,211',
		'deeppink'				 => '255,20,147',
		'deepskyblue'			 => '0,191,255',
		'dimgray'				 => '105,105,105',
		'dimgrey'				 => '105,105,105',
		'dodgerblue'			 => '30,144,255',
		'firebrick'				 => '178,34,34',
		'floralwhite'			 => '255,250,240',
		'forestgreen'			 => '34,139,34',
		'fuchsia'				 => '255,0,255',
		'gainsboro'				 => '220,220,220',
		'ghostwhite'			 => '248,248,255',
		'gold'					 => '255,215,0',
		'goldenrod'				 => '218,165,32',
		'gray'					 => '128,128,128',
		'green'					 => '0,128,0',
		'greenyellow'			 => '173,255,47',
		'grey'					 => '128,128,128',
		'honeydew'				 => '240,255,240',
		'hotpink'				 => '255,105,180',
		'indianred'				 => '205,92,92',
		'indigo'				 => '75,0,130',
		'ivory'					 => '255,255,240',
		'khaki'					 => '240,230,140',
		'lavender'				 => '230,230,250',
		'lavenderblush'			 => '255,240,245',
		'lawngreen'				 => '124,252,0',
		'lemonchiffon'			 => '255,250,205',
		'lightblue'				 => '173,216,230',
		'lightcoral'			 => '240,128,128',
		'lightcyan'				 => '224,255,255',
		'lightgoldenrodyellow'	 => '250,250,210',
		'lightgray'				 => '211,211,211',
		'lightgreen'			 => '144,238,144',
		'lightgrey'				 => '211,211,211',
		'lightpink'				 => '255,182,193',
		'lightsalmon'			 => '255,160,122',
		'lightseagreen'			 => '32,178,170',
		'lightskyblue'			 => '135,206,250',
		'lightslategray'		 => '119,136,153',
		'lightslategrey'		 => '119,136,153',
		'lightsteelblue'		 => '176,196,222',
		'lightyellow'			 => '255,255,224',
		'lime'					 => '0,255,0',
		'limegreen'				 => '50,205,50',
		'linen'					 => '250,240,230',
		'magenta'				 => '255,0,255',
		'maroon'				 => '128,0,0',
		'mediumaquamarine'		 => '102,205,170',
		'mediumblue'			 => '0,0,205',
		'mediumorchid'			 => '186,85,211',
		'mediumpurple'			 => '147,112,219',
		'mediumseagreen'		 => '60,179,113',
		'mediumslateblue'		 => '123,104,238',
		'mediumspringgreen'		 => '0,250,154',
		'mediumturquoise'		 => '72,209,204',
		'mediumvioletred'		 => '199,21,133',
		'midnightblue'			 => '25,25,112',
		'mintcream'				 => '245,255,250',
		'mistyrose'				 => '255,228,225',
		'moccasin'				 => '255,228,181',
		'navajowhite'			 => '255,222,173',
		'navy'					 => '0,0,128',
		'oldlace'				 => '253,245,230',
		'olive'					 => '128,128,0',
		'olivedrab'				 => '107,142,35',
		'orange'				 => '255,165,0',
		'orangered'				 => '255,69,0',
		'orchid'				 => '218,112,214',
		'palegoldenrod'			 => '238,232,170',
		'palegreen'				 => '152,251,152',
		'paleturquoise'			 => '175,238,238',
		'palevioletred'			 => '219,112,147',
		'papayawhip'			 => '255,239,213',
		'peachpuff'				 => '255,218,185',
		'peru'					 => '205,133,63',
		'pink'					 => '255,192,203',
		'plum'					 => '221,160,221',
		'powderblue'			 => '176,224,230',
		'purple'				 => '128,0,128',
		'red'					 => '255,0,0',
		'rosybrown'				 => '188,143,143',
		'royalblue'				 => '65,105,225',
		'saddlebrown'			 => '139,69,19',
		'salmon'				 => '250,128,114',
		'sandybrown'			 => '244,164,96',
		'seagreen'				 => '46,139,87',
		'seashell'				 => '255,245,238',
		'sienna'				 => '160,82,45',
		'silver'				 => '192,192,192',
		'skyblue'				 => '135,206,235',
		'slateblue'				 => '106,90,205',
		'slategray'				 => '112,128,144',
		'slategrey'				 => '112,128,144',
		'snow'					 => '255,250,250',
		'springgreen'			 => '0,255,127',
		'steelblue'				 => '70,130,180',
		'tan'					 => '210,180,140',
		'teal'					 => '0,128,128',
		'thistle'				 => '216,191,216',
		'tomato'				 => '255,99,71',
		'transparent'			 => '0,0,0,0',
		'turquoise'				 => '64,224,208',
		'violet'				 => '238,130,238',
		'wheat'					 => '245,222,179',
		'white'					 => '255,255,255',
		'whitesmoke'			 => '245,245,245',
		'yellow'				 => '255,255,0',
		'yellowgreen'			 => '154,205,50'
	);
}
fof/less/parser/parser.php000064400000116057152177723700011612 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken verbatim from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * Responsible for taking a string of LESS code and converting it into a syntax tree
 *
 * @since  2.0
 */
class FOFLessParser
{
	// Used to uniquely identify blocks
	protected static $nextBlockId = 0;

	protected static $precedence = array(
		'=<'					 => 0,
		'>='					 => 0,
		'='						 => 0,
		'<'						 => 0,
		'>'						 => 0,
		'+'						 => 1,
		'-'						 => 1,
		'*'						 => 2,
		'/'						 => 2,
		'%'						 => 2,
	);

	protected static $whitePattern;

	protected static $commentMulti;

	protected static $commentSingle = "//";

	protected static $commentMultiLeft = "/*";

	protected static $commentMultiRight = "*/";

	// Regex string to match any of the operators
	protected static $operatorString;

	// These properties will supress division unless it's inside parenthases
	protected static $supressDivisionProps = array('/border-radius$/i', '/^font$/i');

	protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document");

	protected $lineDirectives = array("charset");

	/**
	 * if we are in parens we can be more liberal with whitespace around
	 * operators because it must evaluate to a single value and thus is less
	 * ambiguous.
	 *
	 * Consider:
	 *     property1: 10 -5; // is two numbers, 10 and -5
	 *     property2: (10 -5); // should evaluate to 5
	 */
	protected $inParens = false;

	// Caches preg escaped literals
	protected static $literalCache = array();

	/**
	 * Constructor
	 *
	 * @param   [type]  $lessc       [description]
	 * @param   string  $sourceName  [description]
	 */
	public function __construct($lessc, $sourceName = null)
	{
		$this->eatWhiteDefault = true;

		// Reference to less needed for vPrefix, mPrefix, and parentSelector
		$this->lessc = $lessc;

		// Name used for error messages
		$this->sourceName = $sourceName;

		$this->writeComments = false;

		if (!self::$operatorString)
		{
			self::$operatorString = '(' . implode('|', array_map(array('FOFLess', 'preg_quote'), array_keys(self::$precedence))) . ')';

			$commentSingle = FOFLess::preg_quote(self::$commentSingle);
			$commentMultiLeft = FOFLess::preg_quote(self::$commentMultiLeft);
			$commentMultiRight = FOFLess::preg_quote(self::$commentMultiRight);

			self::$commentMulti = $commentMultiLeft . '.*?' . $commentMultiRight;
			self::$whitePattern = '/' . $commentSingle . '[^\n]*\s*|(' . self::$commentMulti . ')\s*|\s+/Ais';
		}
	}

	/**
	 * Parse text
	 *
	 * @param   string  $buffer  [description]
	 *
	 * @return  [type]           [description]
	 */
	public function parse($buffer)
	{
		$this->count = 0;
		$this->line = 1;

		// Block stack
		$this->env = null;
		$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
		$this->pushSpecialBlock("root");
		$this->eatWhiteDefault = true;
		$this->seenComments = array();

		/*
		 * trim whitespace on head
		 * if (preg_match('/^\s+/', $this->buffer, $m)) {
		 * 	$this->line += substr_count($m[0], "\n");
		 * 	$this->buffer = ltrim($this->buffer);
		 * }
		 */
		$this->whitespace();

		// Parse the entire file
		$lastCount = $this->count;
		while (false !== $this->parseChunk());

		if ($this->count != strlen($this->buffer))
		{
			$this->throwError();
		}

		// TODO report where the block was opened
		if (!is_null($this->env->parent))
		{
			throw new exception('parse error: unclosed block');
		}

		return $this->env;
	}

	/**
	 * Parse a single chunk off the head of the buffer and append it to the
	 * current parse environment.
	 * Returns false when the buffer is empty, or when there is an error.
	 *
	 * This function is called repeatedly until the entire document is
	 * parsed.
	 *
	 * This parser is most similar to a recursive descent parser. Single
	 * functions represent discrete grammatical rules for the language, and
	 * they are able to capture the text that represents those rules.
	 *
	 * Consider the function lessc::keyword(). (all parse functions are
	 * structured the same)
	 *
	 * The function takes a single reference argument. When calling the
	 * function it will attempt to match a keyword on the head of the buffer.
	 * If it is successful, it will place the keyword in the referenced
	 * argument, advance the position in the buffer, and return true. If it
	 * fails then it won't advance the buffer and it will return false.
	 *
	 * All of these parse functions are powered by lessc::match(), which behaves
	 * the same way, but takes a literal regular expression. Sometimes it is
	 * more convenient to use match instead of creating a new function.
	 *
	 * Because of the format of the functions, to parse an entire string of
	 * grammatical rules, you can chain them together using &&.
	 *
	 * But, if some of the rules in the chain succeed before one fails, then
	 * the buffer position will be left at an invalid state. In order to
	 * avoid this, lessc::seek() is used to remember and set buffer positions.
	 *
	 * Before parsing a chain, use $s = $this->seek() to remember the current
	 * position into $s. Then if a chain fails, use $this->seek($s) to
	 * go back where we started.
	 *
	 * @return  boolean
	 */
	protected function parseChunk()
	{
		if (empty($this->buffer))
		{
			return false;
		}

		$s = $this->seek();

		// Setting a property
		if ($this->keyword($key) && $this->assign()
			&& $this->propertyValue($value, $key) && $this->end())
		{
			$this->append(array('assign', $key, $value), $s);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		// Look for special css blocks
		if ($this->literal('@', false))
		{
			$this->count--;

			// Media
			if ($this->literal('@media'))
			{
				if (($this->mediaQueryList($mediaQueries) || true)
					&& $this->literal('{'))
				{
					$media = $this->pushSpecialBlock("media");
					$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;

					return true;
				}
				else
				{
					$this->seek($s);

					return false;
				}
			}

			if ($this->literal("@", false) && $this->keyword($dirName))
			{
				if ($this->isDirective($dirName, $this->blockDirectives))
				{
					if (($this->openString("{", $dirValue, null, array(";")) || true)
						&& $this->literal("{"))
					{
						$dir = $this->pushSpecialBlock("directive");
						$dir->name = $dirName;

						if (isset($dirValue))
						{
							$dir->value = $dirValue;
						}

						return true;
					}
				}
				elseif ($this->isDirective($dirName, $this->lineDirectives))
				{
					if ($this->propertyValue($dirValue) && $this->end())
					{
						$this->append(array("directive", $dirName, $dirValue));

						return true;
					}
				}
			}

			$this->seek($s);
		}

		// Setting a variable
		if ($this->variable($var) && $this->assign()
			&& $this->propertyValue($value) && $this->end())
		{
			$this->append(array('assign', $var, $value), $s);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		if ($this->import($importValue))
		{
			$this->append($importValue, $s);

			return true;
		}

		// Opening parametric mixin
		if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg)
			&& ($this->guards($guards) || true)
			&& $this->literal('{'))
		{
			$block = $this->pushBlock($this->fixTags(array($tag)));
			$block->args = $args;
			$block->isVararg = $isVararg;

			if (!empty($guards))
			{
				$block->guards = $guards;
			}

			return true;
		}
		else
		{
			$this->seek($s);
		}

		// Opening a simple block
		if ($this->tags($tags) && $this->literal('{'))
		{
			$tags = $this->fixTags($tags);
			$this->pushBlock($tags);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		// Closing a block
		if ($this->literal('}', false))
		{
			try
			{
				$block = $this->pop();
			}
			catch (exception $e)
			{
				$this->seek($s);
				$this->throwError($e->getMessage());
			}

			$hidden = false;

			if (is_null($block->type))
			{
				$hidden = true;

				if (!isset($block->args))
				{
					foreach ($block->tags as $tag)
					{
						if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix)
						{
							$hidden = false;
							break;
						}
					}
				}

				foreach ($block->tags as $tag)
				{
					if (is_string($tag))
					{
						$this->env->children[$tag][] = $block;
					}
				}
			}

			if (!$hidden)
			{
				$this->append(array('block', $block), $s);
			}

			// This is done here so comments aren't bundled into he block that was just closed
			$this->whitespace();

			return true;
		}

		// Mixin
		if ($this->mixinTags($tags)
			&& ($this->argumentValues($argv) || true)
			&& ($this->keyword($suffix) || true)
			&& $this->end())
		{
			$tags = $this->fixTags($tags);
			$this->append(array('mixin', $tags, $argv, $suffix), $s);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		// Spare ;
		if ($this->literal(';'))
		{
			return true;
		}

		// Got nothing, throw error
		return false;
	}

	/**
	 * [isDirective description]
	 *
	 * @param   string  $dirname     [description]
	 * @param   [type]  $directives  [description]
	 *
	 * @return  boolean
	 */
	protected function isDirective($dirname, $directives)
	{
		// TODO: cache pattern in parser
		$pattern = implode("|", array_map(array("FOFLess", "preg_quote"), $directives));
		$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';

		return preg_match($pattern, $dirname);
	}

	/**
	 * [fixTags description]
	 *
	 * @param   [type]  $tags  [description]
	 *
	 * @return  [type]         [description]
	 */
	protected function fixTags($tags)
	{
		// Move @ tags out of variable namespace
		foreach ($tags as &$tag)
		{
			if ($tag{0} == $this->lessc->vPrefix)
			{
				$tag[0] = $this->lessc->mPrefix;
			}
		}

		return $tags;
	}

	/**
	 * a list of expressions
	 *
	 * @param   [type]  &$exps  [description]
	 *
	 * @return  boolean
	 */
	protected function expressionList(&$exps)
	{
		$values = array();

		while ($this->expression($exp))
		{
			$values[] = $exp;
		}

		if (count($values) == 0)
		{
			return false;
		}

		$exps = FOFLess::compressList($values, ' ');

		return true;
	}

	/**
	 * Attempt to consume an expression.
	 *
	 * @param   string  &$out  [description]
	 *
	 * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
	 *
	 * @return  boolean
	 */
	protected function expression(&$out)
	{
		if ($this->value($lhs))
		{
			$out = $this->expHelper($lhs, 0);

			// Look for / shorthand
			if (!empty($this->env->supressedDivision))
			{
				unset($this->env->supressedDivision);
				$s = $this->seek();

				if ($this->literal("/") && $this->value($rhs))
				{
					$out = array("list", "",
						array($out, array("keyword", "/"), $rhs));
				}
				else
				{
					$this->seek($s);
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * Recursively parse infix equation with $lhs at precedence $minP
	 *
	 * @param   type  $lhs   [description]
	 * @param   type  $minP  [description]
	 *
	 * @return   string
	 */
	protected function expHelper($lhs, $minP)
	{
		$this->inExp = true;
		$ss = $this->seek();

		while (true)
		{
			$whiteBefore = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]);

			// If there is whitespace before the operator, then we require
			// whitespace after the operator for it to be an expression
			$needWhite = $whiteBefore && !$this->inParens;

			if ($this->match(self::$operatorString . ($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP)
			{
				if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision))
				{
					foreach (self::$supressDivisionProps as $pattern)
					{
						if (preg_match($pattern, $this->env->currentProperty))
						{
							$this->env->supressedDivision = true;
							break 2;
						}
					}
				}

				$whiteAfter = isset($this->buffer[$this->count - 1]) && ctype_space($this->buffer[$this->count - 1]);

				if (!$this->value($rhs))
				{
					break;
				}

				// Peek for next operator to see what to do with rhs
				if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]])
				{
					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
				}

				$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
				$ss = $this->seek();

				continue;
			}

			break;
		}

		$this->seek($ss);

		return $lhs;
	}

	/**
	 * Consume a list of values for a property
	 *
	 * @param   [type]  &$value   [description]
	 * @param   [type]  $keyName  [description]
	 *
	 * @return  boolean
	 */
	public function propertyValue(&$value, $keyName = null)
	{
		$values = array();

		if ($keyName !== null)
		{
			$this->env->currentProperty = $keyName;
		}

		$s = null;

		while ($this->expressionList($v))
		{
			$values[] = $v;
			$s = $this->seek();

			if (!$this->literal(','))
			{
				break;
			}
		}

		if ($s)
		{
			$this->seek($s);
		}

		if ($keyName !== null)
		{
			unset($this->env->currentProperty);
		}

		if (count($values) == 0)
		{
			return false;
		}

		$value = FOFLess::compressList($values, ', ');

		return true;
	}

	/**
	 * [parenValue description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function parenValue(&$out)
	{
		$s = $this->seek();

		// Speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(")
		{
			return false;
		}

		$inParens = $this->inParens;

		if ($this->literal("(") && ($this->inParens = true) && $this->expression($exp) && $this->literal(")"))
		{
			$out = $exp;
			$this->inParens = $inParens;

			return true;
		}
		else
		{
			$this->inParens = $inParens;
			$this->seek($s);
		}

		return false;
	}

	/**
	 * a single value
	 *
	 * @param   [type]  &$value  [description]
	 *
	 * @return  boolean
	 */
	protected function value(&$value)
	{
		$s = $this->seek();

		// Speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-")
		{
			// Negation
			if ($this->literal("-", false) &&(($this->variable($inner) && $inner = array("variable", $inner))
				|| $this->unit($inner) || $this->parenValue($inner)))
			{
				$value = array("unary", "-", $inner);

				return true;
			}
			else
			{
				$this->seek($s);
			}
		}

		if ($this->parenValue($value))
		{
			return true;
		}

		if ($this->unit($value))
		{
			return true;
		}

		if ($this->color($value))
		{
			return true;
		}

		if ($this->func($value))
		{
			return true;
		}

		if ($this->string($value))
		{
			return true;
		}

		if ($this->keyword($word))
		{
			$value = array('keyword', $word);

			return true;
		}

		// Try a variable
		if ($this->variable($var))
		{
			$value = array('variable', $var);

			return true;
		}

		// Unquote string (should this work on any type?
		if ($this->literal("~") && $this->string($str))
		{
			$value = array("escape", $str);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		// Css hack: \0
		if ($this->literal('\\') && $this->match('([0-9]+)', $m))
		{
			$value = array('keyword', '\\' . $m[1]);

			return true;
		}
		else
		{
			$this->seek($s);
		}

		return false;
	}

	/**
	 * an import statement
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function import(&$out)
	{
		$s = $this->seek();

		if (!$this->literal('@import'))
		{
			return false;
		}

		/*
		 * @import "something.css" media;
		 * @import url("something.css") media;
		 * @import url(something.css) media;
		 */

		if ($this->propertyValue($value))
		{
			$out = array("import", $value);

			return true;
		}
	}

	/**
	 * [mediaQueryList description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function mediaQueryList(&$out)
	{
		if ($this->genericList($list, "mediaQuery", ",", false))
		{
			$out = $list[2];

			return true;
		}

		return false;
	}

	/**
	 * [mediaQuery description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  [type]        [description]
	 */
	protected function mediaQuery(&$out)
	{
		$s = $this->seek();

		$expressions = null;
		$parts = array();

		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType))
		{
			$prop = array("mediaType");

			if (isset($only))
			{
				$prop[] = "only";
			}

			if (isset($not))
			{
				$prop[] = "not";
			}

			$prop[] = $mediaType;
			$parts[] = $prop;
		}
		else
		{
			$this->seek($s);
		}

		if (!empty($mediaType) && !$this->literal("and"))
		{
			// ~
		}
		else
		{
			$this->genericList($expressions, "mediaExpression", "and", false);

			if (is_array($expressions))
			{
				$parts = array_merge($parts, $expressions[2]);
			}
		}

		if (count($parts) == 0)
		{
			$this->seek($s);

			return false;
		}

		$out = $parts;

		return true;
	}

	/**
	 * [mediaExpression description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function mediaExpression(&$out)
	{
		$s = $this->seek();
		$value = null;

		if ($this->literal("(") && $this->keyword($feature) && ($this->literal(":")
			&& $this->expression($value) || true) && $this->literal(")"))
		{
			$out = array("mediaExp", $feature);

			if ($value)
			{
				$out[] = $value;
			}

			return true;
		}
		elseif ($this->variable($variable))
		{
			$out = array('variable', $variable);

			return true;
		}
		$this->seek($s);

		return false;
	}

	/**
	 * An unbounded string stopped by $end
	 *
	 * @param   [type]  $end          [description]
	 * @param   [type]  &$out         [description]
	 * @param   [type]  $nestingOpen  [description]
	 * @param   [type]  $rejectStrs   [description]
	 *
	 * @return  boolean
	 */
	protected function openString($end, &$out, $nestingOpen = null, $rejectStrs = null)
	{
		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = false;

		$stop = array("'", '"', "@{", $end);
		$stop = array_map(array("FOFLess", "preg_quote"), $stop);

		// $stop[] = self::$commentMulti;

		if (!is_null($rejectStrs))
		{
			$stop = array_merge($stop, $rejectStrs);
		}

		$patt = '(.*?)(' . implode("|", $stop) . ')';

		$nestingLevel = 0;

		$content = array();

		while ($this->match($patt, $m, false))
		{
			if (!empty($m[1]))
			{
				$content[] = $m[1];

				if ($nestingOpen)
				{
					$nestingLevel += substr_count($m[1], $nestingOpen);
				}
			}

			$tok = $m[2];

			$this->count -= strlen($tok);

			if ($tok == $end)
			{
				if ($nestingLevel == 0)
				{
					break;
				}
				else
				{
					$nestingLevel--;
				}
			}

			if (($tok == "'" || $tok == '"') && $this->string($str))
			{
				$content[] = $str;
				continue;
			}

			if ($tok == "@{" && $this->interpolation($inter))
			{
				$content[] = $inter;
				continue;
			}

			if (in_array($tok, $rejectStrs))
			{
				$count = null;
				break;
			}

			$content[] = $tok;
			$this->count += strlen($tok);
		}

		$this->eatWhiteDefault = $oldWhite;

		if (count($content) == 0)
			return false;

		// Trim the end
		if (is_string(end($content)))
		{
			$content[count($content) - 1] = rtrim(end($content));
		}

		$out = array("string", "", $content);

		return true;
	}

	/**
	 * [string description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function string(&$out)
	{
		$s = $this->seek();

		if ($this->literal('"', false))
		{
			$delim = '"';
		}
		elseif ($this->literal("'", false))
		{
			$delim = "'";
		}
		else
		{
			return false;
		}

		$content = array();

		// Look for either ending delim , escape, or string interpolation
		$patt = '([^\n]*?)(@\{|\\\\|' . FOFLess::preg_quote($delim) . ')';

		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = false;

		while ($this->match($patt, $m, false))
		{
			$content[] = $m[1];

			if ($m[2] == "@{")
			{
				$this->count -= strlen($m[2]);

				if ($this->interpolation($inter, false))
				{
					$content[] = $inter;
				}
				else
				{
					$this->count += strlen($m[2]);

					// Ignore it
					$content[] = "@{";
				}
			}
			elseif ($m[2] == '\\')
			{
				$content[] = $m[2];

				if ($this->literal($delim, false))
				{
					$content[] = $delim;
				}
			}
			else
			{
				$this->count -= strlen($delim);

				// Delim
				break;
			}
		}

		$this->eatWhiteDefault = $oldWhite;

		if ($this->literal($delim))
		{
			$out = array("string", $delim, $content);

			return true;
		}

		$this->seek($s);

		return false;
	}

	/**
	 * [interpolation description]
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function interpolation(&$out)
	{
		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = true;

		$s = $this->seek();

		if ($this->literal("@{") && $this->openString("}", $interp, null, array("'", '"', ";")) && $this->literal("}", false))
		{
			$out = array("interpolate", $interp);
			$this->eatWhiteDefault = $oldWhite;

			if ($this->eatWhiteDefault)
			{
				$this->whitespace();
			}

			return true;
		}

		$this->eatWhiteDefault = $oldWhite;
		$this->seek($s);

		return false;
	}

	/**
	 * [unit description]
	 *
	 * @param   [type]  &$unit  [description]
	 *
	 * @return  boolean
	 */
	protected function unit(&$unit)
	{
		// Speed shortcut
		if (isset($this->buffer[$this->count]))
		{
			$char = $this->buffer[$this->count];

			if (!ctype_digit($char) && $char != ".")
			{
				return false;
			}
		}

		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m))
		{
			$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);

			return true;
		}

		return false;
	}

	/**
	 * a # color
	 *
	 * @param   [type]  &$out  [description]
	 *
	 * @return  boolean
	 */
	protected function color(&$out)
	{
		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m))
		{
			if (strlen($m[1]) > 7)
			{
				$out = array("string", "", array($m[1]));
			}
			else
			{
				$out = array("raw_color", $m[1]);
			}

			return true;
		}

		return false;
	}

	/**
	 * Consume a list of property values delimited by ; and wrapped in ()
	 *
	 * @param   [type]  &$args  [description]
	 * @param   [type]  $delim  [description]
	 *
	 * @return  boolean
	 */
	protected function argumentValues(&$args, $delim = ',')
	{
		$s = $this->seek();

		if (!$this->literal('('))
		{
			return false;
		}

		$values = array();

		while (true)
		{
			if ($this->expressionList($value))
			{
				$values[] = $value;
			}

			if (!$this->literal($delim))
			{
				break;
			}
			else
			{
				if ($value == null)
				{
					$values[] = null;
				}

				$value = null;
			}
		}

		if (!$this->literal(')'))
		{
			$this->seek($s);

			return false;
		}

		$args = $values;

		return true;
	}

	/**
	 * Consume an argument definition list surrounded by ()
	 * each argument is a variable name with optional value
	 * or at the end a ... or a variable named followed by ...
	 *
	 * @param   [type]  &$args      [description]
	 * @param   [type]  &$isVararg  [description]
	 * @param   [type]  $delim      [description]
	 *
	 * @return  boolean
	 */
	protected function argumentDef(&$args, &$isVararg, $delim = ',')
	{
		$s = $this->seek();
		if (!$this->literal('('))
			return false;

		$values = array();

		$isVararg = false;

		while (true)
		{
			if ($this->literal("..."))
			{
				$isVararg = true;
				break;
			}

			if ($this->variable($vname))
			{
				$arg = array("arg", $vname);
				$ss = $this->seek();

				if ($this->assign() && $this->expressionList($value))
				{
					$arg[] = $value;
				}
				else
				{
					$this->seek($ss);

					if ($this->literal("..."))
					{
						$arg[0] = "rest";
						$isVararg = true;
					}
				}

				$values[] = $arg;

				if ($isVararg)
				{
					break;
				}

				continue;
			}

			if ($this->value($literal))
			{
				$values[] = array("lit", $literal);
			}

			if (!$this->literal($delim))
			{
				break;
			}
		}

		if (!$this->literal(')'))
		{
			$this->seek($s);

			return false;
		}

		$args = $values;

		return true;
	}

	/**
	 * Consume a list of tags
	 * This accepts a hanging delimiter
	 *
	 * @param   [type]  &$tags   [description]
	 * @param   [type]  $simple  [description]
	 * @param   [type]  $delim   [description]
	 *
	 * @return  boolean
	 */
	protected function tags(&$tags, $simple = false, $delim = ',')
	{
		$tags = array();

		while ($this->tag($tt, $simple))
		{
			$tags[] = $tt;

			if (!$this->literal($delim))
			{
				break;
			}
		}

		if (count($tags) == 0)
		{
			return false;
		}

		return true;
	}

	/**
	 * List of tags of specifying mixin path
	 * Optionally separated by > (lazy, accepts extra >)
	 *
	 * @param   [type]  &$tags  [description]
	 *
	 * @return  boolean
	 */
	protected function mixinTags(&$tags)
	{
		$s = $this->seek();
		$tags = array();

		while ($this->tag($tt, true))
		{
			$tags[] = $tt;
			$this->literal(">");
		}

		if (count($tags) == 0)
		{
			return false;
		}

		return true;
	}

	/**
	 * A bracketed value (contained within in a tag definition)
	 *
	 * @param   [type]  &$value  [description]
	 *
	 * @return  boolean
	 */
	protected function tagBracket(&$value)
	{
		// Speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[")
		{
			return false;
		}

		$s = $this->seek();

		if ($this->literal('[') && $this->to(']', $c, true) && $this->literal(']', false))
		{
			$value = '[' . $c . ']';

			// Whitespace?
			if ($this->whitespace())
			{
				$value .= " ";
			}

			// Escape parent selector, (yuck)
			$value = str_replace($this->lessc->parentSelector, "$&$", $value);

			return true;
		}

		$this->seek($s);

		return false;
	}

	/**
	 * [tagExpression description]
	 *
	 * @param   [type]  &$value  [description]
	 *
	 * @return  boolean
	 */
	protected function tagExpression(&$value)
	{
		$s = $this->seek();

		if ($this->literal("(") && $this->expression($exp) && $this->literal(")"))
		{
			$value = array('exp', $exp);

			return true;
		}

		$this->seek($s);

		return false;
	}

	/**
	 * A single tag
	 *
	 * @param   [type]   &$tag    [description]
	 * @param   boolean  $simple  [description]
	 *
	 * @return  boolean
	 */
	protected function tag(&$tag, $simple = false)
	{
		if ($simple)
		{
			$chars = '^@,:;{}\][>\(\) "\'';
		}
		else
		{
			$chars = '^@,;{}["\'';
		}

		$s = $this->seek();

		if (!$simple && $this->tagExpression($tag))
		{
			return true;
		}

		$hasExpression = false;
		$parts         = array();

		while ($this->tagBracket($first))
		{
			$parts[] = $first;
		}

		$oldWhite = $this->eatWhiteDefault;

		$this->eatWhiteDefault = false;

		while (true)
		{
			if ($this->match('([' . $chars . '0-9][' . $chars . ']*)', $m))
			{
				$parts[] = $m[1];

				if ($simple)
				{
					break;
				}

				while ($this->tagBracket($brack))
				{
					$parts[] = $brack;
				}

				continue;
			}

			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@")
			{
				if ($this->interpolation($interp))
				{
					$hasExpression = true;

					// Don't unescape
					$interp[2] = true;
					$parts[] = $interp;

					continue;
				}

				if ($this->literal("@"))
				{
					$parts[] = "@";

					continue;
				}
			}

			// For keyframes
			if ($this->unit($unit))
			{
				$parts[] = $unit[1];
				$parts[] = $unit[2];
				continue;
			}

			break;
		}

		$this->eatWhiteDefault = $oldWhite;

		if (!$parts)
		{
			$this->seek($s);

			return false;
		}

		if ($hasExpression)
		{
			$tag = array("exp", array("string", "", $parts));
		}
		else
		{
			$tag = trim(implode($parts));
		}

		$this->whitespace();

		return true;
	}

	/**
	 * A css function
	 *
	 * @param   [type]  &$func  [description]
	 *
	 * @return  boolean
	 */
	protected function func(&$func)
	{
		$s = $this->seek();

		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('('))
		{
			$fname = $m[1];

			$sPreArgs = $this->seek();

			$args = array();

			while (true)
			{
				$ss = $this->seek();

				// This ugly nonsense is for ie filter properties
				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value))
				{
					$args[] = array("string", "", array($name, "=", $value));
				}
				else
				{
					$this->seek($ss);

					if ($this->expressionList($value))
					{
						$args[] = $value;
					}
				}

				if (!$this->literal(','))
				{
					break;
				}
			}

			$args = array('list', ',', $args);

			if ($this->literal(')'))
			{
				$func = array('function', $fname, $args);

				return true;
			}
			elseif ($fname == 'url')
			{
				// Couldn't parse and in url? treat as string
				$this->seek($sPreArgs);

				if ($this->openString(")", $string) && $this->literal(")"))
				{
					$func = array('function', $fname, $string);

					return true;
				}
			}
		}

		$this->seek($s);

		return false;
	}

	/**
	 * Consume a less variable
	 *
	 * @param   [type]  &$name  [description]
	 *
	 * @return  boolean
	 */
	protected function variable(&$name)
	{
		$s = $this->seek();

		if ($this->literal($this->lessc->vPrefix, false) &&	($this->variable($sub) || $this->keyword($name)))
		{
			if (!empty($sub))
			{
				$name = array('variable', $sub);
			}
			else
			{
				$name = $this->lessc->vPrefix . $name;
			}

			return true;
		}

		$name = null;
		$this->seek($s);

		return false;
	}

	/**
	 * Consume an assignment operator
	 * Can optionally take a name that will be set to the current property name
	 *
	 * @param   string  $name  [description]
	 *
	 * @return  boolean
	 */
	protected function assign($name = null)
	{
		if ($name)
		{
			$this->currentProperty = $name;
		}

		return $this->literal(':') || $this->literal('=');
	}

	/**
	 * Consume a keyword
	 *
	 * @param   [type]  &$word  [description]
	 *
	 * @return  boolean
	 */
	protected function keyword(&$word)
	{
		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m))
		{
			$word = $m[1];

			return true;
		}

		return false;
	}

	/**
	 * Consume an end of statement delimiter
	 *
	 * @return  boolean
	 */
	protected function end()
	{
		if ($this->literal(';'))
		{
			return true;
		}
		elseif ($this->count == strlen($this->buffer) || $this->buffer{$this->count} == '}')
		{
			// If there is end of file or a closing block next then we don't need a ;
			return true;
		}

		return false;
	}

	/**
	 * [guards description]
	 *
	 * @param   [type]  &$guards  [description]
	 *
	 * @return  boolean
	 */
	protected function guards(&$guards)
	{
		$s = $this->seek();

		if (!$this->literal("when"))
		{
			$this->seek($s);

			return false;
		}

		$guards = array();

		while ($this->guardGroup($g))
		{
			$guards[] = $g;

			if (!$this->literal(","))
			{
				break;
			}
		}

		if (count($guards) == 0)
		{
			$guards = null;
			$this->seek($s);

			return false;
		}

		return true;
	}

	/**
	 * A bunch of guards that are and'd together
	 *
	 * @param   [type]  &$guardGroup  [description]
	 *
	 * @todo rename to guardGroup
	 *
	 * @return  boolean
	 */
	protected function guardGroup(&$guardGroup)
	{
		$s = $this->seek();
		$guardGroup = array();

		while ($this->guard($guard))
		{
			$guardGroup[] = $guard;

			if (!$this->literal("and"))
			{
				break;
			}
		}

		if (count($guardGroup) == 0)
		{
			$guardGroup = null;
			$this->seek($s);

			return false;
		}

		return true;
	}

	/**
	 * [guard description]
	 *
	 * @param   [type]  &$guard  [description]
	 *
	 * @return  boolean
	 */
	protected function guard(&$guard)
	{
		$s = $this->seek();
		$negate = $this->literal("not");

		if ($this->literal("(") && $this->expression($exp) && $this->literal(")"))
		{
			$guard = $exp;

			if ($negate)
			{
				$guard = array("negate", $guard);
			}

			return true;
		}

		$this->seek($s);

		return false;
	}

	/* raw parsing functions */

	/**
	 * [literal description]
	 *
	 * @param   [type]  $what           [description]
	 * @param   [type]  $eatWhitespace  [description]
	 *
	 * @return  boolean
	 */
	protected function literal($what, $eatWhitespace = null)
	{
		if ($eatWhitespace === null)
		{
			$eatWhitespace = $this->eatWhiteDefault;
		}

		// Shortcut on single letter
		if (!isset($what[1]) && isset($this->buffer[$this->count]))
		{
			if ($this->buffer[$this->count] == $what)
			{
				if (!$eatWhitespace)
				{
					$this->count++;

					return true;
				}
			}
			else
			{
				return false;
			}
		}

		if (!isset(self::$literalCache[$what]))
		{
			self::$literalCache[$what] = FOFLess::preg_quote($what);
		}

		return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
	}

	/**
	 * [genericList description]
	 *
	 * @param   [type]   &$out       [description]
	 * @param   [type]   $parseItem  [description]
	 * @param   string   $delim      [description]
	 * @param   boolean  $flatten    [description]
	 *
	 * @return  boolean
	 */
	protected function genericList(&$out, $parseItem, $delim = "", $flatten = true)
	{
		$s = $this->seek();
		$items = array();

		while ($this->$parseItem($value))
		{
			$items[] = $value;

			if ($delim)
			{
				if (!$this->literal($delim))
				{
					break;
				}
			}
		}

		if (count($items) == 0)
		{
			$this->seek($s);

			return false;
		}

		if ($flatten && count($items) == 1)
		{
			$out = $items[0];
		}
		else
		{
			$out = array("list", $delim, $items);
		}

		return true;
	}

	/**
	 * Advance counter to next occurrence of $what
	 * $until - don't include $what in advance
	 * $allowNewline, if string, will be used as valid char set
	 *
	 * @param   [type]   $what          [description]
	 * @param   [type]   &$out          [description]
	 * @param   boolean  $until         [description]
	 * @param   boolean  $allowNewline  [description]
	 *
	 * @return  boolean
	 */
	protected function to($what, &$out, $until = false, $allowNewline = false)
	{
		if (is_string($allowNewline))
		{
			$validChars = $allowNewline;
		}
		else
		{
			$validChars = $allowNewline ? "." : "[^\n]";
		}

		if (!$this->match('(' . $validChars . '*?)' . FOFLess::preg_quote($what), $m, !$until))
		{
			return false;
		}

		if ($until)
		{
			// Give back $what
			$this->count -= strlen($what);
		}

		$out = $m[1];

		return true;
	}

	/**
	 * Try to match something on head of buffer
	 *
	 * @param   [type]  $regex          [description]
	 * @param   [type]  &$out           [description]
	 * @param   [type]  $eatWhitespace  [description]
	 *
	 * @return  boolean
	 */
	protected function match($regex, &$out, $eatWhitespace = null)
	{
		if ($eatWhitespace === null)
		{
			$eatWhitespace = $this->eatWhiteDefault;
		}

		$r = '/' . $regex . ($eatWhitespace && !$this->writeComments ? '\s*' : '') . '/Ais';

		if (preg_match($r, $this->buffer, $out, null, $this->count))
		{
			$this->count += strlen($out[0]);

			if ($eatWhitespace && $this->writeComments)
			{
				$this->whitespace();
			}

			return true;
		}

		return false;
	}

	/**
	 * Watch some whitespace
	 *
	 * @return  boolean
	 */
	protected function whitespace()
	{
		if ($this->writeComments)
		{
			$gotWhite = false;

			while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count))
			{
				if (isset($m[1]) && empty($this->commentsSeen[$this->count]))
				{
					$this->append(array("comment", $m[1]));
					$this->commentsSeen[$this->count] = true;
				}

				$this->count += strlen($m[0]);
				$gotWhite = true;
			}

			return $gotWhite;
		}
		else
		{
			$this->match("", $m);

			return strlen($m[0]) > 0;
		}
	}

	/**
	 * Match something without consuming it
	 *
	 * @param   [type]  $regex  [description]
	 * @param   [type]  &$out   [description]
	 * @param   [type]  $from   [description]
	 *
	 * @return  boolean
	 */
	protected function peek($regex, &$out = null, $from = null)
	{
		if (is_null($from))
		{
			$from = $this->count;
		}

		$r = '/' . $regex . '/Ais';
		$result = preg_match($r, $this->buffer, $out, null, $from);

		return $result;
	}

	/**
	 * Seek to a spot in the buffer or return where we are on no argument
	 *
	 * @param   [type]  $where  [description]
	 *
	 * @return  boolean
	 */
	protected function seek($where = null)
	{
		if ($where === null)
		{
			return $this->count;
		}
		else
		{
			$this->count = $where;
		}

		return true;
	}

	/* misc functions */

	/**
	 * [throwError description]
	 *
	 * @param   string  $msg    [description]
	 * @param   [type]  $count  [description]
	 *
	 * @return  void
	 */
	public function throwError($msg = "parse error", $count = null)
	{
		$count = is_null($count) ? $this->count : $count;

		$line = $this->line + substr_count(substr($this->buffer, 0, $count), "\n");

		if (!empty($this->sourceName))
		{
			$loc = "$this->sourceName on line $line";
		}
		else
		{
			$loc = "line: $line";
		}

		// TODO this depends on $this->count
		if ($this->peek("(.*?)(\n|$)", $m, $count))
		{
			throw new exception("$msg: failed at `$m[1]` $loc");
		}
		else
		{
			throw new exception("$msg: $loc");
		}
	}

	/**
	 * [pushBlock description]
	 *
	 * @param   [type]  $selectors  [description]
	 * @param   [type]  $type       [description]
	 *
	 * @return  stdClass
	 */
	protected function pushBlock($selectors = null, $type = null)
	{
		$b = new stdclass;
		$b->parent = $this->env;

		$b->type = $type;
		$b->id = self::$nextBlockId++;

		// TODO: kill me from here
		$b->isVararg = false;
		$b->tags = $selectors;

		$b->props = array();
		$b->children = array();

		$this->env = $b;

		return $b;
	}

	/**
	 * Push a block that doesn't multiply tags
	 *
	 * @param   [type]  $type  [description]
	 *
	 * @return  stdClass
	 */
	protected function pushSpecialBlock($type)
	{
		return $this->pushBlock(null, $type);
	}

	/**
	 * Append a property to the current block
	 *
	 * @param   [type]  $prop  [description]
	 * @param   [type]  $pos   [description]
	 *
	 * @return  void
	 */
	protected function append($prop, $pos = null)
	{
		if ($pos !== null)
		{
			$prop[-1] = $pos;
		}

		$this->env->props[] = $prop;
	}

	/**
	 * Pop something off the stack
	 *
	 * @return  [type]  [description]
	 */
	protected function pop()
	{
		$old = $this->env;
		$this->env = $this->env->parent;

		return $old;
	}

	/**
	 * Remove comments from $text
	 *
	 * @param   [type]  $text  [description]
	 *
	 * @todo: make it work for all functions, not just url
	 *
	 * @return  [type]         [description]
	 */
	protected function removeComments($text)
	{
		$look = array(
			'url(', '//', '/*', '"', "'"
		);

		$out = '';
		$min = null;

		while (true)
		{
			// Find the next item
			foreach ($look as $token)
			{
				$pos = strpos($text, $token);

				if ($pos !== false)
				{
					if (!isset($min) || $pos < $min[1])
					{
						$min = array($token, $pos);
					}
				}
			}

			if (is_null($min))
				break;

			$count = $min[1];
			$skip = 0;
			$newlines = 0;

			switch ($min[0])
			{
				case 'url(':

					if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
					{
						$count += strlen($m[0]) - strlen($min[0]);
					}

					break;
				case '"':
				case "'":

					if (preg_match('/' . $min[0] . '.*?' . $min[0] . '/', $text, $m, 0, $count))
					{
						$count += strlen($m[0]) - 1;
					}

					break;
				case '//':
					$skip = strpos($text, "\n", $count);

					if ($skip === false)
					{
						$skip = strlen($text) - $count;
					}
					else
					{
						$skip -= $count;
					}

					break;
				case '/*':

					if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count))
					{
						$skip = strlen($m[0]);
						$newlines = substr_count($m[0], "\n");
					}

					break;
			}

			if ($skip == 0)
			{
				$count += strlen($min[0]);
			}

			$out .= substr($text, 0, $count) . str_repeat("\n", $newlines);
			$text = substr($text, $count + $skip);

			$min = null;
		}

		return $out . $text;
	}
}
fof/less/formatter/classic.php000064400000006560152177723700012443 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken verbatim from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFLessFormatterClassic
{
	public $indentChar			 = "  ";

	public $break				 = "\n";

	public $open				 = " {";

	public $close				 = "}";

	public $selectorSeparator	 = ", ";

	public $assignSeparator	 = ":";

	public $openSingle			 = " { ";

	public $closeSingle		 = " }";

	public $disableSingle		 = false;

	public $breakSelectors		 = false;

	public $compressColors		 = false;

	/**
	 * Public constructor
	 */
	public function __construct()
	{
		$this->indentLevel = 0;
	}

	/**
	 * Indent a string by $n positions
	 *
	 * @param   integer  $n  How many positions to indent
	 *
	 * @return  string  The indented string
	 */
	public function indentStr($n = 0)
	{
		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
	}

	/**
	 * Return the code for a property
	 *
	 * @param   string  $name   The name of the porperty
	 * @param   string  $value  The value of the porperty
	 *
	 * @return  string  The CSS code
	 */
	public function property($name, $value)
	{
		return $name . $this->assignSeparator . $value . ";";
	}

	/**
	 * Is a block empty?
	 *
	 * @param   stdClass  $block  The block to check
	 *
	 * @return  boolean  True if the block has no lines or children
	 */
	protected function isEmpty($block)
	{
		if (empty($block->lines))
		{
			foreach ($block->children as $child)
			{
				if (!$this->isEmpty($child))
				{
					return false;
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * Output a CSS block
	 *
	 * @param   stdClass  $block  The block definition to output
	 *
	 * @return  void
	 */
	public function block($block)
	{
		if ($this->isEmpty($block))
		{
			return;
		}

		$inner	 = $pre	 = $this->indentStr();

		$isSingle = !$this->disableSingle &&
			is_null($block->type) && count($block->lines) == 1;

		if (!empty($block->selectors))
		{
			$this->indentLevel++;

			if ($this->breakSelectors)
			{
				$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
			}
			else
			{
				$selectorSeparator = $this->selectorSeparator;
			}

			echo $pre .
			implode($selectorSeparator, $block->selectors);

			if ($isSingle)
			{
				echo $this->openSingle;
				$inner = "";
			}
			else
			{
				echo $this->open . $this->break;
				$inner = $this->indentStr();
			}
		}

		if (!empty($block->lines))
		{
			$glue = $this->break . $inner;
			echo $inner . implode($glue, $block->lines);

			if (!$isSingle && !empty($block->children))
			{
				echo $this->break;
			}
		}

		foreach ($block->children as $child)
		{
			$this->block($child);
		}

		if (!empty($block->selectors))
		{
			if (!$isSingle && empty($block->children))
			{
				echo $this->break;
			}

			if ($isSingle)
			{
				echo $this->closeSingle . $this->break;
			}
			else
			{
				echo $pre . $this->close . $this->break;
			}

			$this->indentLevel--;
		}
	}
}
fof/less/formatter/lessjs.php000064400000001470152177723700012320 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken verbatim from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFLessFormatterLessjs extends FOFLessFormatterClassic
{
	public $disableSingle = true;

	public $breakSelectors = true;

	public $assignSeparator = ": ";

	public $selectorSeparator = ",";
}
fof/less/formatter/joomla.php000064400000001525152177723700012277 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken verbatim from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFLessFormatterJoomla extends FOFLessFormatterClassic
{
	public $disableSingle = true;

	public $breakSelectors = true;

	public $assignSeparator = ": ";

	public $selectorSeparator = ",";

	public $indentChar = "\t";
}
fof/less/formatter/compressed.php000064400000002064152177723700013161 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  less
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This class is taken verbatim from:
 *
 * lessphp v0.3.9
 * http://leafo.net/lessphp
 *
 * LESS css compiler, adapted from http://lesscss.org
 *
 * Copyright 2012, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFLessFormatterCompressed extends FOFLessFormatterClassic
{
	public $disableSingle = true;

	public $open = "{";

	public $selectorSeparator = ",";

	public $assignSeparator = ":";

	public $break = "";

	public $compressColors = true;

	/**
	 * Indent a string by $n positions
	 *
	 * @param   integer  $n  How many positions to indent
	 *
	 * @return  string  The indented string
	 */
	public function indentStr($n = 0)
	{
		return "";
	}
}
fof/LICENSE.txt000064400000043761152177723700007167 0ustar00================================================================================
Historical note
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
On February 21st, 2013 FOF changed its license to GPLv2 or later.
================================================================================

                    GNU GENERAL PUBLIC LICENSE
                       Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

                    GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) You must cause the modified files to carry prominent notices
    stating that you changed the files and the date of any change.

    b) You must cause any work that you distribute or publish, that in
    whole or in part contains or is derived from the Program or any
    part thereof, to be licensed as a whole at no charge to all third
    parties under the terms of this License.

    c) If the modified program normally reads commands interactively
    when run, you must cause it, when started running for such
    interactive use in the most ordinary way, to print or display an
    announcement including an appropriate copyright notice and a
    notice that there is no warranty (or else, saying that you provide
    a warranty) and that users may redistribute the program under
    these conditions, and telling the user how to view a copy of this
    License.  (Exception: if the Program itself is interactive but
    does not normally print such an announcement, your work based on
    the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

    a) Accompany it with the complete corresponding machine-readable
    source code, which must be distributed under the terms of Sections
    1 and 2 above on a medium customarily used for software interchange; or,

    b) Accompany it with a written offer, valid for at least three
    years, to give any third party, for a charge no more than your
    cost of physically performing source distribution, a complete
    machine-readable copy of the corresponding source code, to be
    distributed under the terms of Sections 1 and 2 above on a medium
    customarily used for software interchange; or,

    c) Accompany it with the information you received as to the offer
    to distribute corresponding source code.  (This alternative is
    allowed only for noncommercial distribution and only if you
    received the program in object code or executable form with such
    an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

                            NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

    Gnomovision version 69, Copyright (C) year name of author
    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.fof/controller/controller.php000064400000227332152177723700012421 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage controller
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework controller class. FOF is based on the thin controller
 * paradigm, where the controller is mainly used to set up the model state and
 * spawn the view.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFController extends FOFUtilsObject
{
	/**
	 * @var int Bit mask to enable Routing on redirects.
	 * 0 = never
	 * 1 = frontend only
	 * 2 = backend  only
	 * 3 = always
	 */
	protected $autoRouting = 0;

	/**
	 * The current component's name without the com_ prefix
	 *
	 * @var    string
	 */
	protected $bareComponent = 'foobar';

	/**
	 * The base path of the controller
	 *
	 * @var    string
	 */
	protected $basePath;

	/**
	 * The tasks for which caching should be enabled by default
	 *
	 * @var array
	 */
	protected $cacheableTasks = array('browse', 'read');

	/**
	 * The current component's name; you can override it in the configuration
	 *
	 * @var    string
	 */
	protected $component = 'com_foobar';

	/**
	 * A cached copy of the class configuration parameter passed during initialisation
	 *
	 * @var    array
	 */
	protected $config = array();

	/**
	 * An instance of FOFConfigProvider to provision configuration overrides
	 *
	 * @var    FOFConfigProvider
	 */
	protected $configProvider = null;

	/**
	 * Set to true to enable CSRF protection on selected tasks. The possible
	 * values are:
	 * 0	Disabled; no token checks are performed
	 * 1	Enabled; token checks are always performed
	 * 2	Only on HTML requests and backend; token checks are always performed in the back-end and in the front-end only when format is 'html'
	 * 3	Only on back-end; token checks are performer only in the back-end
	 *
	 * @var    integer
	 */
	protected $csrfProtection = 2;

	/**
	 * The default view for the display method.
	 *
	 * @var    string
	 */
	protected $default_view;

	/**
	 * The mapped task that was performed.
	 *
	 * @var    string
	 */
	protected $doTask;

	/**
	 * The input object for this MVC triad; you can override it in the configuration
	 *
	 * @var    FOFInput
	 */
	protected $input = array();

	/**
	 * Redirect message.
	 *
	 * @var    string
	 */
	protected $message;

	/**
	 * Redirect message type.
	 *
	 * @var    string
	 */
	protected $messageType;

	/**
	 * The current layout; you can override it in the configuration
	 *
	 * @var    string
	 */
	protected $layout = null;

	/**
	 * Array of class methods
	 *
	 * @var    array
	 */
	protected $methods;

	/**
	 * The prefix of the models
	 *
	 * @var    string
	 */
	protected $model_prefix;

	/**
	 * Overrides the name of the view's default model
	 *
	 * @var    string
	 */
	protected $modelName = null;

	/**
	 * The set of search directories for resources (views).
	 *
	 * @var    array
	 */
	protected $paths;

	/**
	 * URL for redirection.
	 *
	 * @var    string
	 */
	protected $redirect;

	/**
	 * Current or most recently performed task.
	 *
	 * @var    string
	 */
	protected $task;

	/**
	 * Array of class methods to call for a given task.
	 *
	 * @var    array
	 */
	protected $taskMap;

	/**
	 * The name of the controller
	 *
	 * @var    array
	 */
	protected $name;

	/**
	 * The current view name; you can override it in the configuration
	 *
	 * @var    string
	 */
	protected $view = '';

	/**
	 * Overrides the name of the view's default view
	 *
	 * @var    string
	 */
	protected $viewName = null;

	/**
	 * A copy of the FOFView object used in this triad
	 *
	 * @var    FOFView
	 */
	private $_viewObject = null;

	/**
	 * A cache for the view item objects created in this controller
	 *
	 * @var   array
	 */
	protected $viewsCache = array();

	/**
	 * A copy of the FOFModel object used in this triad
	 *
	 * @var    FOFModel
	 */
	private $_modelObject = null;

	/**
	 * Does this tried have a FOFForm which will be used to render it?
	 *
	 * @var    boolean
	 */
	protected $hasForm = false;

	/**
	 * Gets a static (Singleton) instance of a controller class. It loads the
	 * relevant controller file from the component's directory or, if it doesn't
	 * exist, creates a new controller object out of thin air.
	 *
	 * @param   string  $option  Component name, e.g. com_foobar
	 * @param   string  $view    The view name, also used for the controller name
	 * @param   array   $config  Configuration parameters
	 *
	 * @return  FOFController
	 */
	public static function &getAnInstance($option = null, $view = null, $config = array())
	{
		static $instances = array();

		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$hash = $option . $view;

		if (!array_key_exists($hash, $instances))
		{
			$instances[$hash] = self::getTmpInstance($option, $view, $config);
		}

		return $instances[$hash];
	}

	/**
	 * Gets a temporary instance of a controller object. A temporary instance is
	 * not a Singleton and can be disposed off after use.
	 *
	 * @param   string  $option  The component name, e.g. com_foobar
	 * @param   string  $view    The view name, e.g. cpanel
	 * @param   array   $config  Configuration parameters
	 *
	 * @return  \FOFController  A disposable class instance
	 */
	public static function &getTmpInstance($option = null, $view = null, $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Get an input object
		if (array_key_exists('input', $config))
		{
			$input = $config['input'];
		}
		else
		{
			$input = null;
		}

		if (array_key_exists('input_options', $config))
		{
			$input_options = $config['input_options'];
		}
		else
		{
			$input_options = array();
		}

		if (!($input instanceof FOFInput))
		{
			$input = new FOFInput($input, $input_options);
		}

		// Determine the option (component name) and view
		$config['option'] = !is_null($option) ? $option : $input->getCmd('option', 'com_foobar');
		$config['view'] = !is_null($view) ? $view : $input->getCmd('view', 'cpanel');

		// Get the class base name, e.g. FoobarController
		$classBaseName = ucfirst(str_replace('com_', '', $config['option'])) . 'Controller';

		// Get the class name suffixes, in the order to be searched for: plural, singular, 'default'
		$classSuffixes = array(
			FOFInflector::pluralize($config['view']),
			FOFInflector::singularize($config['view']),
			'default'
		);

		// Get the path names for the component
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);
        $filesystem     = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		// Look for the best classname match
		foreach ($classSuffixes as $suffix)
		{
			$className = $classBaseName . ucfirst($suffix);

			if (class_exists($className))
			{
				// The class is already loaded. We have a match!
				break;
			}

			// The class is not already loaded. Try to find and load it.
			$searchPaths = array(
				$componentPaths['main'] . '/controllers',
				$componentPaths['admin'] . '/controllers'
			);

			// If we have a searchpath in the configuration please search it first

			if (array_key_exists('searchpath', $config))
			{
				array_unshift($searchPaths, $config['searchpath']);
			}
			else
			{
				$configProvider = new FOFConfigProvider;
				$searchPath = $configProvider->get($config['option'] . '.views.' . FOFInflector::singularize($config['view']) . '.config.searchpath', null);

				if ($searchPath)
				{
					array_unshift($searchPaths, $componentPaths['admin'] . '/' . $searchPath);
					array_unshift($searchPaths, $componentPaths['main'] . '/' . $searchPath);
				}
			}

			/**
			 * Try to find the path to this file. First try to find the
			 * format-specific controller file, e.g. foobar.json.php for
			 * format=json, then the regular one-size-fits-all controller
			 */

			$format = $input->getCmd('format', 'html');
			$path = null;

			if (!empty($format))
			{
				$path = $filesystem->pathFind(
					$searchPaths, strtolower($suffix) . '.' . strtolower($format) . '.php'
				);
			}

			if (!$path)
			{
				$path = $filesystem->pathFind(
						$searchPaths, strtolower($suffix) . '.php'
				);
			}

			// The path is found. Load the file and make sure the expected class name exists.

			if ($path)
			{
				require_once $path;

				if (class_exists($className))
				{
					// The class was loaded successfully. We have a match!
					break;
				}
			}
		}

		if (!class_exists($className))
		{
			// If no specialised class is found, instantiate the generic FOFController
			$className = 'FOFController';
		}

		$instance = new $className($config);

		return $instance;
	}

	/**
	 * Public constructor of the Controller class
	 *
	 * @param   array  $config  Optional configuration parameters
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$this->methods = array();
		$this->message = null;
		$this->messageType = 'message';
		$this->paths = array();
		$this->redirect = null;
		$this->taskMap = array();

		// Cache the config
		$this->config = $config;

		// Get the input for this MVC triad

		if (array_key_exists('input', $config))
		{
			$input = $config['input'];
		}
		else
		{
			$input = null;
		}

		if (array_key_exists('input_options', $config))
		{
			$input_options = $config['input_options'];
		}
		else
		{
			$input_options = array();
		}

		if ($input instanceof FOFInput)
		{
			$this->input = $input;
		}
		else
		{
			$this->input = new FOFInput($input, $input_options);
		}

		// Load the configuration provider
		$this->configProvider = new FOFConfigProvider;

		// Determine the methods to exclude from the base class.
		$xMethods = get_class_methods('FOFController');

		// Some methods must always be considered valid tasks
		$iMethods = array('accesspublic', 'accessregistered', 'accessspecial',
			'add', 'apply', 'browse', 'cancel', 'copy', 'edit', 'orderdown',
			'orderup', 'publish', 'read', 'remove', 'save', 'savenew',
			'saveorder', 'unpublish', 'display', 'archive', 'trash', 'loadhistory');

		// Get the public methods in this class using reflection.
		$r = new ReflectionClass($this);
		$rMethods = $r->getMethods(ReflectionMethod::IS_PUBLIC);

		foreach ($rMethods as $rMethod)
		{
			$mName = $rMethod->getName();

			// If the developer screwed up and declared one of the helper method public do NOT make them available as
			// tasks.
			if ((substr($mName, 0, 8) == 'onBefore') || (substr($mName, 0, 7) == 'onAfter') || substr($mName, 0, 1) == '_')
			{
				continue;
			}

			// Add default display method if not explicitly declared.
			if (!in_array($mName, $xMethods) || in_array($mName, $iMethods))
			{
				$this->methods[] = strtolower($mName);

				// Auto register the methods as tasks.
				$this->taskMap[strtolower($mName)] = $mName;
			}
		}

		// Get the default values for the component and view names
		$classNameParts = FOFInflector::explode(get_class($this));

		if (count($classNameParts) == 3)
		{
			$defComponent = "com_" . $classNameParts[0];
			$defView = $classNameParts[2];
		}
		else
		{
			$defComponent = 'com_foobar';
			$defView = 'cpanel';
		}

		$this->component = $this->input->get('option', $defComponent, 'cmd');
		$this->view = $this->input->get('view', $defView, 'cmd');
		$this->layout = $this->input->get('layout', null, 'cmd');

		// Overrides from the config
		if (array_key_exists('option', $config))
		{
			$this->component = $config['option'];
		}

		if (array_key_exists('view', $config))
		{
			$this->view = $config['view'];
		}

		if (array_key_exists('layout', $config))
		{
			$this->layout = $config['layout'];
		}

		$this->layout = $this->configProvider->get($this->component . '.views.' . FOFInflector::singularize($this->view) . '.config.layout', $this->layout);

		$this->input->set('option', $this->component);

		// Set the bareComponent variable
		$this->bareComponent = str_replace('com_', '', strtolower($this->component));

		// Set the $name variable
		$this->name = $this->bareComponent;

		// Set the basePath variable
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($this->component);
		$basePath = $componentPaths['main'];

		if (array_key_exists('base_path', $config))
		{
			$basePath = $config['base_path'];
		}

		$altBasePath = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.config.base_path', null
		);

		if (!is_null($altBasePath))
		{
            $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();
			$basePath     = $platformDirs['public'] . '/' . $altBasePath;
		}

		$this->basePath = $basePath;

		// If the default task is set, register it as such
		$defaultTask = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.config.default_task', 'display'
		);

		if (array_key_exists('default_task', $config))
		{
			$this->registerDefaultTask($config['default_task']);
		}
		else
		{
			$this->registerDefaultTask($defaultTask);
		}

		// Set the models prefix

		if (empty($this->model_prefix))
		{
			if (array_key_exists('model_prefix', $config))
			{
				// User-defined prefix
				$this->model_prefix = $config['model_prefix'];
			}
			else
			{
				$this->model_prefix = $this->name . 'Model';
				$this->model_prefix = $this->configProvider->get(
					$this->component . '.views.' .
					FOFInflector::singularize($this->view) . '.config.model_prefix', $this->model_prefix
				);
			}
		}

		// Set the default model search path

		if (array_key_exists('model_path', $config))
		{
			// User-defined dirs
			$this->addModelPath($config['model_path'], $this->model_prefix);
		}
		else
		{
			$modelPath = $this->basePath . '/models';
			$altModelPath = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.model_path', null
			);

			if (!is_null($altModelPath))
			{
				$modelPath = $this->basePath . '/' . $altModelPath;
			}

			$this->addModelPath($modelPath, $this->model_prefix);
		}

		// Set the default view search path
		if (array_key_exists('view_path', $config))
		{
			// User-defined dirs
			$this->setPath('view', $config['view_path']);
		}
		else
		{
			$viewPath = $this->basePath . '/views';
			$altViewPath = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.view_path', null
			);

			if (!is_null($altViewPath))
			{
				$viewPath = $this->basePath . '/' . $altViewPath;
			}

			$this->setPath('view', $viewPath);
		}

		// Set the default view.

		if (array_key_exists('default_view', $config))
		{
			$this->default_view = $config['default_view'];
		}
		else
		{
			if (empty($this->default_view))
			{
				$this->default_view = $this->getName();
			}

			$this->default_view = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.default_view', $this->default_view
			);
		}

		// Set the CSRF protection
		if (array_key_exists('csrf_protection', $config))
		{
			$this->csrfProtection = $config['csrf_protection'];
		}

		$this->csrfProtection = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.config.csrf_protection', $this->csrfProtection
		);

		// Set any model/view name overrides
		if (array_key_exists('viewName', $config))
		{
			$this->setThisViewName($config['viewName']);
		}
		else
		{
			$overrideViewName = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.viewName', null
			);

			if ($overrideViewName)
			{
				$this->setThisViewName($overrideViewName);
			}
		}

		if (array_key_exists('modelName', $config))
		{
			$this->setThisModelName($config['modelName']);
		}
		else
		{
			$overrideModelName = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.modelName', null
			);

			if ($overrideModelName)
			{
				$this->setThisModelName($overrideModelName);
			}
		}

		// Caching
		if (array_key_exists('cacheableTasks', $config))
		{
			if (is_array($config['cacheableTasks']))
			{
				$this->cacheableTasks = $config['cacheableTasks'];
			}
		}
		else
		{
			$cacheableTasks = $this->configProvider->get(
				$this->component . '.views.' .
				FOFInflector::singularize($this->view) . '.config.cacheableTasks', null
			);

			if ($cacheableTasks)
			{
				$cacheableTasks = explode(',', $cacheableTasks);

				if (count($cacheableTasks))
				{
					$temp = array();

					foreach ($cacheableTasks as $t)
					{
						$temp[] = trim($t);
					}

					$temp = array_unique($temp);
					$this->cacheableTasks = $temp;
				}
			}
		}

		// Bit mask for auto routing on setRedirect
		$this->autoRouting = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.config.autoRouting', $this->autoRouting
		);

		if (array_key_exists('autoRouting', $config))
		{
			$this->autoRouting = $config['autoRouting'];
		}

		// Apply task map
		$taskmap = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.taskmap'
		);

		if (is_array($taskmap) && !empty($taskmap))
		{
			foreach ($taskmap as $aliasedtask => $realmethod)
			{
				$this->registerTask($aliasedtask, $realmethod);
			}
		}
	}

	/**
	 * Adds to the stack of model paths in LIFO order.
	 *
	 * @param   mixed   $path    The directory (string) , or list of directories (array) to add.
	 * @param   string  $prefix  A prefix for models
	 *
	 * @return  void
	 */
	public static function addModelPath($path, $prefix = '')
	{
		FOFModel::addIncludePath($path, $prefix);
	}

	/**
	 * Adds to the search path for templates and resources.
	 *
	 * @param   string  $type  The path type (e.g. 'model', 'view').
	 * @param   mixed   $path  The directory string  or stream array to search.
	 *
	 * @return  FOFController  A FOFController object to support chaining.
	 */
	protected function addPath($type, $path)
	{
		// Just force path to array
		settype($path, 'array');

        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		if (!isset($this->paths[$type]))
		{
			$this->paths[$type] = array();
		}

		// Loop through the path directories
		foreach ($path as $dir)
		{
			// No surrounding spaces allowed!
			$dir = rtrim($filesystem->pathCheck($dir, '/'), '/') . '/';

			// Add to the top of the search dirs
			array_unshift($this->paths[$type], $dir);
		}

		return $this;
	}

	/**
	 * Add one or more view paths to the controller's stack, in LIFO order.
	 *
	 * @param   mixed  $path  The directory (string) or list of directories (array) to add.
	 *
	 * @return  FOFController  This object to support chaining.
	 */
	public function addViewPath($path)
	{
		$this->addPath('view', $path);

		return $this;
	}

	/**
	 * Authorisation check
	 *
	 * @param   string  $task  The ACO Section Value to check access on.
	 *
	 * @return  boolean  True if authorised
	 *
	 * @deprecated  2.0  Use JAccess instead.
	 */
	public function authorise($task)
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' .__METHOD__ . ' is deprecated. Use checkACL() instead.');

		return true;
	}

	/**
	 * Create the filename for a resource.
	 *
	 * @param   string  $type   The resource type to create the filename for.
	 * @param   array   $parts  An associative array of filename information. Optional.
	 *
	 * @return  string  The filename.
	 */
	protected static function createFileName($type, $parts = array())
	{
		$filename = '';

		switch ($type)
		{
			case 'controller':
				if (!empty($parts['format']))
				{
					if ($parts['format'] == 'html')
					{
						$parts['format'] = '';
					}
					else
					{
						$parts['format'] = '.' . $parts['format'];
					}
				}
				else
				{
					$parts['format'] = '';
				}

				$filename = strtolower($parts['name'] . $parts['format'] . '.php');
				break;

			case 'view':
				if (!empty($parts['type']))
				{
					$parts['type'] = '.' . $parts['type'];
				}
				else
				{
					$parts['type'] = '';
				}

				$filename = strtolower($parts['name'] . '/view' . $parts['type'] . '.php');
				break;
		}

		return $filename;
	}

    /**
     * Executes a given controller task. The onBefore<task> and onAfter<task>
     * methods are called automatically if they exist.
     *
     * @param   string $task The task to execute, e.g. "browse"
     *
     * @throws  Exception   Exception thrown if the onBefore<task> returns false
     *
     * @return  null|bool  False on execution failure
     */
	public function execute($task)
	{
		$this->task = $task;

		$method_name = 'onBefore' . ucfirst($task);

		if (!method_exists($this, $method_name))
		{
			$result = $this->onBeforeGenericTask($task);
		}
		elseif (method_exists($this, $method_name))
		{
			$result = $this->$method_name();
		}
		else
		{
			$result = true;
		}

		if ($result)
		{
			$plugin_event  = FOFInflector::camelize('on before ' . $this->bareComponent . ' controller ' . $this->view . ' ' . $task);
			$plugin_result = FOFPlatform::getInstance()->runPlugins($plugin_event, array(&$this, &$this->input));

			if (in_array(false, $plugin_result, true))
			{
				$result = false;
			}
		}

		if (!$result)
		{
			throw new Exception(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}

		// Do not allow the display task to be directly called
		$task = strtolower($task);

		if (isset($this->taskMap[$task]))
		{
			$doTask = $this->taskMap[$task];
		}
		elseif (isset($this->taskMap['__default']))
		{
			$doTask = $this->taskMap['__default'];
		}
		else
		{
			$doTask = null;
		}

		if ($doTask == 'display')
		{
            FOFPlatform::getInstance()->setHeader('Status', '400 Bad Request', true);

			throw new Exception('Bad Request', 400);
		}

		$this->doTask = $doTask;

		$ret = $this->$doTask();

		$method_name = 'onAfter' . ucfirst($task);

		if (method_exists($this, $method_name))
		{
			$result = $this->$method_name();
		}
		else
		{
			$result = true;
		}

		if ($result)
		{
			$plugin_event = FOFInflector::camelize('on after ' . $this->bareComponent . ' controller ' . $this->view . ' ' . $task);
			$plugin_result = FOFPlatform::getInstance()->runPlugins($plugin_event, array(&$this, &$this->input, &$ret));

			if (in_array(false, $plugin_result, true))
			{
				$result = false;
			}
		}

		if (!$result)
		{
			throw new Exception(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'), 403);
		}

		return $ret;
	}

	/**
	 * Default task. Assigns a model to the view and asks the view to render
	 * itself.
	 *
	 * YOU MUST NOT USETHIS TASK DIRECTLY IN A URL. It is supposed to be
	 * used ONLY inside your code. In the URL, use task=browse instead.
	 *
	 * @param   bool    $cachable   Is this view cacheable?
	 * @param   bool    $urlparams  Add your safe URL parameters (see further down in the code)
	 * @param   string  $tpl        The name of the template file to parse
	 *
	 * @return  bool
	 */
	public function display($cachable = false, $urlparams = false, $tpl = null)
	{
		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			$viewType = $document->getType();
		}
		else
		{
			$viewType = $this->input->getCmd('format', 'html');
		}

		$view = $this->getThisView();

		// Get/Create the model

		if ($model = $this->getThisModel())
		{
			// Push the model into the view (as default)
			$view->setModel($model, true);
		}

		// Set the layout
		$view->setLayout(is_null($this->layout) ? 'default' : $this->layout);

		// Display the view
		$conf = FOFPlatform::getInstance()->getConfig();

		if (FOFPlatform::getInstance()->isFrontend() && $cachable && ($viewType != 'feed') && $conf->get('caching') >= 1)
		{
			// Get a JCache object
			$option = $this->input->get('option', 'com_foobar', 'cmd');
			$cache = JFactory::getCache($option, 'view');

			// Set up a cache ID based on component, view, task and user group assignment
			$user = FOFPlatform::getInstance()->getUser();

			if ($user->guest)
			{
				$groups = array();
			}
			else
			{
				$groups = $user->groups;
			}

			$importantParameters = array();

			// Set up safe URL parameters
			if (!is_array($urlparams))
			{
				$urlparams = array(
					'option'		=> 'CMD',
					'view'			=> 'CMD',
					'task'			=> 'CMD',
					'format'		=> 'CMD',
					'layout'		=> 'CMD',
					'id'			=> 'INT',
				);
			}

			if (is_array($urlparams))
			{
				$app = JFactory::getApplication();

				$registeredurlparams = null;

				if (version_compare(JVERSION, '3.0', 'ge'))
				{
					if (property_exists($app, 'registeredurlparams'))
					{
						$registeredurlparams = $app->registeredurlparams;
					}
				}
				else
				{
					$registeredurlparams = $app->get('registeredurlparams');
				}

				if (empty($registeredurlparams))
				{
					$registeredurlparams = new stdClass;
				}

				foreach ($urlparams AS $key => $value)
				{
					// Add your safe url parameters with variable type as value {@see JFilterInput::clean()}.
					$registeredurlparams->$key = $value;

					// Add the URL-important parameters into the array
					$importantParameters[$key] = $this->input->get($key, null, $value);
				}

				if (version_compare(JVERSION, '3.0', 'ge'))
				{
					$app->registeredurlparams = $registeredurlparams;
				}
				else
				{
					$app->set('registeredurlparams', $registeredurlparams);
				}
			}

			// Create the cache ID after setting the registered URL params, as they are used to generate the ID
			$cacheId = md5(serialize(array(JCache::makeId(), $view->getName(), $this->doTask, $groups, $importantParameters)));

			// Get the cached view or cache the current view
			$cache->get($view, 'display', $cacheId);
		}
		else
		{
			// Display without caching
			$view->display($tpl);
		}

		return true;
	}

	/**
	 * Implements a default browse task, i.e. read a bunch of records and send
	 * them to the browser.
	 *
	 * @return  boolean
	 */
	public function browse()
	{
		if ($this->input->get('savestate', -999, 'int') == -999)
		{
			$this->input->set('savestate', true);
		}

		// Do I have a form?
		$model = $this->getThisModel();

		if (empty($this->layout))
		{
			$formname = 'form.default';
		}
		else
		{
			$formname = 'form.' . $this->layout;
		}

		$model->setState('form_name', $formname);

		$form = $model->getForm();

		if ($form !== false)
		{
			$this->hasForm = true;
		}

		$this->display(in_array('browse', $this->cacheableTasks));

		return true;
	}

	/**
	 * Single record read. The id set in the request is passed to the model and
	 * then the item layout is used to render the result.
	 *
	 * @return  bool
	 */
	public function read()
	{
		// Load the model
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		// Set the layout to item, if it's not set in the URL
		if (is_null($this->layout))
		{
			$this->layout = 'item';
		}

		// Do I have a form?
		$model->setState('form_name', 'form.' . $this->layout);

		$item = $model->getItem();

		if (!($item instanceof FOFTable))
		{
			return false;
		}

		$itemKey = $item->getKeyName();

		if ($item->$itemKey != $model->getId())
		{
			return false;
		}

		$formData = is_object($item) ? $item->getData() : array();
		$form = $model->getForm($formData);

		if ($form !== false)
		{
			$this->hasForm = true;
		}

		// Display
		$this->display(in_array('read', $this->cacheableTasks));

		return true;
	}

	/**
	 * Single record add. The form layout is used to present a blank page.
	 *
	 * @return  false|void
	 */
	public function add()
	{
		// Load and reset the model
		$model = $this->getThisModel();
		$model->reset();

		// Set the layout to form, if it's not set in the URL

		if (!$this->layout)
		{
			$this->layout = 'form';
		}

		// Do I have a form?
		$model->setState('form_name', 'form.' . $this->layout);

		$item = $model->getItem();

		if (!($item instanceof FOFTable))
		{
			return false;
		}

		$formData = is_object($item) ? $item->getData() : array();
		$form = $model->getForm($formData);

		if ($form !== false)
		{
			$this->hasForm = true;
		}

		// Display
		$this->display(in_array('add', $this->cacheableTasks));
	}

	/**
	 * Single record edit. The ID set in the request is passed to the model,
	 * then the form layout is used to edit the result.
	 *
	 * @return  bool
	 */
	public function edit()
	{
		// Load the model
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->checkout();

		if (!$status)
		{
			// Redirect on error

			if ($customURL = $this->input->get('returnurl', '', 'string'))
			{
				$customURL = base64_decode($customURL);
			}

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
			$this->setRedirect($url, $model->getError(), 'error');

			return false;
		}

		// Set the layout to form, if it's not set in the URL

		if (is_null($this->layout))
		{
			$this->layout = 'form';
		}

		// Do I have a form?
		$model->setState('form_name', 'form.' . $this->layout);

		$item = $model->getItem();

		if (!($item instanceof FOFTable))
		{
			return false;
		}

		$itemKey = $item->getKeyName();

		if ($item->$itemKey != $model->getId())
		{
			return false;
		}

		$formData = is_object($item) ? $item->getData() : array();
		$form = $model->getForm($formData);

		if ($form !== false)
		{
			$this->hasForm = true;
		}

		// Display
		$this->display(in_array('edit', $this->cacheableTasks));

		return true;
	}

	/**
	 * Save the incoming data and then return to the Edit task
	 *
	 * @return  bool
	 */
	public function apply()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();
		$result = $this->applySave();

		// Redirect to the edit task
		if ($result)
		{
			$id = $this->input->get('id', 0, 'int');
			$textkey = strtoupper($this->component) . '_LBL_' . strtoupper($this->view) . '_SAVED';

			if ($customURL = $this->input->get('returnurl', '', 'string'))
			{
				$customURL = base64_decode($customURL);
			}

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
			$this->setRedirect($url, JText::_($textkey));
		}

		return $result;
	}

	/**
	 * Duplicates selected items
	 *
	 * @return  bool
	 */
	public function copy()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->copy();

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');

			return false;
		}
		else
		{
			if(!FOFPlatform::getInstance()->isCli())
			{
				FOFPlatform::getInstance()->setHeader('Status', '201 Created', true);
			}

			$this->setRedirect($url);

			return true;
		}
	}

	/**
	 * Save the incoming data and then return to the Browse task
	 *
	 * @return  bool
	 */
	public function save()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$result = $this->applySave();

		// Redirect to the display task
		if ($result)
		{
			$textkey = strtoupper($this->component) . '_LBL_' . strtoupper($this->view) . '_SAVED';

			if ($customURL = $this->input->get('returnurl', '', 'string'))
			{
				$customURL = base64_decode($customURL);
			}

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
			$this->setRedirect($url, JText::_($textkey));
		}

		return $result;
	}

	/**
	 * Save the incoming data and then return to the Add task
	 *
	 * @return  bool
	 */
	public function savenew()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$result = $this->applySave();

		// Redirect to the display task

		if ($result)
		{
			$textkey = strtoupper($this->component) . '_LBL_' . strtoupper($this->view) . '_SAVED';

			if ($customURL = $this->input->get('returnurl', '', 'string'))
			{
				$customURL = base64_decode($customURL);
			}

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . $this->view . '&task=add' . $this->getItemidURLSuffix();
			$this->setRedirect($url, JText::_($textkey));
		}

		return $result;
	}

	/**
	 * Cancel the edit, check in the record and return to the Browse task
	 *
	 * @return  bool
	 */
	public function cancel()
	{
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$model->checkin();

		// Remove any saved data
		JFactory::getSession()->set($model->getHash() . 'savedata', null);

		// Redirect to the display task

		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
		$this->setRedirect($url);

		return true;
	}

	/**
	 * Method to load a row from version history
	 *
	 * @return   boolean  True if the content history is reverted, false otherwise
	 *
	 * @since   2.2
	 */
	public function loadhistory()
	{
		$app = JFactory::getApplication();
		$lang  = JFactory::getLanguage();
		$model = $this->getThisModel();
		$table = $model->getTable();
		$historyId = $app->input->get('version_id', null, 'integer');
		$status = $model->checkout();
		$alias = $this->component . '.' . $this->view;

		if (!$model->loadhistory($historyId, $table, $alias))
		{
			$this->setMessage($model->getError(), 'error');

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
			$this->setRedirect($url);

			return false;
		}

		// Determine the name of the primary key for the data.
		if (empty($key))
		{
			$key = $table->getKeyName();
		}

		$recordId = $table->$key;

		// To avoid data collisions the urlVar may be different from the primary key.
		$urlVar = empty($this->urlVar) ? $key : $this->urlVar;

		// Access check.
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.edit', 'core.edit'
		);

		if (!$this->checkACL($privilege))
		{
			$this->setError(JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
			$this->setMessage($this->getError(), 'error');

			$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
			$this->setRedirect($url);
			$table->checkin();

			return false;
		}

		$table->store();
		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
		$this->setRedirect($url);

		$this->setMessage(JText::sprintf('JLIB_APPLICATION_SUCCESS_LOAD_HISTORY', $model->getState('save_date'), $model->getState('version_note')));

		return true;
	}

	/**
	 * Sets the access to public. Joomla! 1.5 compatibility.
	 *
	 * @return  bool
	 *
	 * @deprecated since 2.0
	 */
	public function accesspublic()
	{
		// CSRF prevention

		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setaccess(0);
	}

	/**
	 * Sets the access to registered. Joomla! 1.5 compatibility.
	 *
	 * @return  bool
	 *
	 * @deprecated since 2.0
	 */
	public function accessregistered()
	{
		// CSRF prevention

		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setaccess(1);
	}

	/**
	 * Sets the access to special. Joomla! 1.5 compatibility.
	 *
	 * @return  bool
	 *
	 * @deprecated since 2.0
	 */
	public function accessspecial()
	{
		// CSRF prevention

		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setaccess(2);
	}

	/**
	 * Publish (set enabled = 1) an item.
	 *
	 * @return  bool
	 */
	public function publish()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setstate(1);
	}

	/**
	 * Unpublish (set enabled = 0) an item.
	 *
	 * @return  bool
	 */
	public function unpublish()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setstate(0);
	}

	/**
	 * Archive (set enabled = 2) an item.
	 *
	 * @return  bool
	 */
	public function archive()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setstate(2);
	}

	/**
	 * Trash (set enabled = -2) an item.
	 *
	 * @return  bool
	 */
	public function trash()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		return $this->setstate(-2);
	}

	/**
	 * Saves the order of the items
	 *
	 * @return  bool
	 */
	public function saveorder()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

        $ordering = $model->getTable()->getColumnAlias('ordering');
		$ids      = $model->getIds();
		$orders   = $this->input->get('order', array(), 'array');

		if ($n = count($ids))
		{
			for ($i = 0; $i < $n; $i++)
			{
				$model->setId($ids[$i]);
				$neworder = (int) $orders[$i];

				$item = $model->getItem();

				if (!($item instanceof FOFTable))
				{
					return false;
				}

				$key = $item->getKeyName();

				if ($item->$key == $ids[$i])
				{
					$item->$ordering = $neworder;
					$model->save($item);
				}
			}
		}

		$status = $model->reorder();

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();
		$this->setRedirect($url);

		return $status;
	}

	/**
	 * Moves selected items one position down the ordering list
	 *
	 * @return  bool
	 */
	public function orderdown()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->move(1);

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');
		}
		else
		{
			$this->setRedirect($url);
		}

		return $status;
	}

	/**
	 * Moves selected items one position up the ordering list
	 *
	 * @return  bool
	 */
	public function orderup()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->move(-1);

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');
		}
		else
		{
			$this->setRedirect($url);
		}

		return $status;
	}

	/**
	 * Delete selected item(s)
	 *
	 * @return  bool
	 */
	public function remove()
	{
		// CSRF prevention
		if ($this->csrfProtection)
		{
			$this->_csrfProtection();
		}

		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->delete();

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');
		}
		else
		{
			$this->setRedirect($url);
		}

		return $status;
	}

	/**
	 * Redirects the browser or returns false if no redirect is set.
	 *
	 * @return  boolean  False if no redirect exists.
	 */
	public function redirect()
	{
		if ($this->redirect)
		{
			$app = JFactory::getApplication();
			$app->enqueueMessage($this->message, $this->messageType);
			$app->redirect($this->redirect);

			return true;
		}

		return false;
	}

	/**
	 * Returns true if there is a redirect set in the controller
	 *
	 * @return  boolean
	 */
	public function hasRedirect()
	{
		return !empty($this->redirect);
	}

	/**
	 * Register the default task to perform if a mapping is not found.
	 *
	 * @param   string  $method  The name of the method in the derived class to perform if a named task is not found.
	 *
	 * @return  FOFController  A FOFController object to support chaining.
	 */
	public function registerDefaultTask($method)
	{
		$this->registerTask('__default', $method);

		return $this;
	}

	/**
	 * Register (map) a task to a method in the class.
	 *
	 * @param   string  $task    The task.
	 * @param   string  $method  The name of the method in the derived class to perform for this task.
	 *
	 * @return  FOFController  A FOFController object to support chaining.
	 */
	public function registerTask($task, $method)
	{
		if (in_array(strtolower($method), $this->methods))
		{
			$this->taskMap[strtolower($task)] = $method;
		}

		return $this;
	}

	/**
	 * Unregister (unmap) a task in the class.
	 *
	 * @param   string  $task  The task.
	 *
	 * @return  FOFController  This object to support chaining.
	 */
	public function unregisterTask($task)
	{
		unset($this->taskMap[strtolower($task)]);

		return $this;
	}

	/**
	 * Sets the internal message that is passed with a redirect
	 *
	 * @param   string  $text  Message to display on redirect.
	 * @param   string  $type  Message type. Optional, defaults to 'message'.
	 *
	 * @return  string  Previous message
	 */
	public function setMessage($text, $type = 'message')
	{
		$previous = $this->message;
		$this->message = $text;
		$this->messageType = $type;

		return $previous;
	}

	/**
	 * Sets an entire array of search paths for resources.
	 *
	 * @param   string  $type  The type of path to set, typically 'view' or 'model'.
	 * @param   string  $path  The new set of search paths. If null or false, resets to the current directory only.
	 *
	 * @return  void
	 */
	protected function setPath($type, $path)
	{
		// Clear out the prior search dirs
		$this->paths[$type] = array();

		// Actually add the user-specified directories
		$this->addPath($type, $path);
	}

	/**
	 * Registers a redirection with an optional message. The redirection is
	 * carried out when you use the redirect method.
	 *
	 * @param   string  $url   The URL to redirect to
	 * @param   string  $msg   The message to be pushed to the application
	 * @param   string  $type  The message type to be pushed to the application, e.g. 'error'
	 *
	 * @return  FOFController  This object to support chaining
	 */
	public function setRedirect($url, $msg = null, $type = null)
	{
		// Do the logic only if we're parsing a raw url (index.php?foo=bar&etc=etc)
		if (strpos($url, 'index.php') === 0)
		{
			$isAdmin = FOFPlatform::getInstance()->isBackend();
			$auto = false;

			if (($this->autoRouting == 2 || $this->autoRouting == 3) && $isAdmin)
			{
				$auto = true;
			}
			elseif (($this->autoRouting == 1 || $this->autoRouting == 3) && !$isAdmin)
			{
				$auto = true;
			}

			if ($auto)
			{
				$url = JRoute::_($url, false);
			}
		}

		$this->redirect = $url;

		if ($msg !== null)
		{
			// Controller may have set this directly
			$this->message = $msg;
		}

		// Ensure the type is not overwritten by a previous call to setMessage.
		if (empty($type))
		{
			if (empty($this->messageType))
			{
				$this->messageType = 'message';
			}
		}
		// If the type is explicitly set, set it.
		else
		{
			$this->messageType = $type;
		}

		return $this;
	}

	/**
	 * Sets the published state (the enabled field) of the selected item(s)
	 *
	 * @param   integer  $state  The desired state. 0 is unpublished, 1 is published.
	 *
	 * @return  bool
	 */
	protected function setstate($state = 0)
	{
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$status = $model->publish($state);

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');
		}
		else
		{
			$this->setRedirect($url);
		}

		return $status;
	}

	/**
	 * Sets the access level of the selected item(s).
	 *
	 * @param   integer  $level  The desired viewing access level ID
	 *
	 * @return  bool
	 */
	protected function setaccess($level = 0)
	{
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$id   = $model->getId();
		$item = $model->getItem();

		if (!($item instanceof FOFTable))
		{
			return false;
		}

		$accessField = $item->getColumnAlias('access');
		$key         = $item->getKeyName();
		$loadedid    = $item->$key;

		if ($id == $loadedid)
		{
			$item->$accessField = $level;
			$status = $model->save($item);
		}
		else
		{
			$status = false;
		}

		// Redirect
		if ($customURL = $this->input->get('returnurl', '', 'string'))
		{
			$customURL = base64_decode($customURL);
		}

		$url = !empty($customURL) ? $customURL : 'index.php?option=' . $this->component . '&view=' . FOFInflector::pluralize($this->view) . $this->getItemidURLSuffix();

		if (!$status)
		{
			$this->setRedirect($url, $model->getError(), 'error');
		}
		else
		{
			$this->setRedirect($url);
		}

		return $status;
	}

	/**
	 * Common method to handle apply and save tasks
	 *
	 * @return  boolean  Returns true on success
	 */
	final private function applySave()
	{
		// Load the model
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$id = $model->getId();

		$data = $this->input->getData();

		if (!$this->onBeforeApplySave($data))
		{
			return false;
		}

		// Set the layout to form, if it's not set in the URL

		if (is_null($this->layout))
		{
			$this->layout = 'form';
		}

		// Do I have a form?
		$model->setState('form_name', 'form.' . $this->layout);

		$status = $model->save($data);

		if ($status && ($id != 0))
		{
            FOFPlatform::getInstance()->setHeader('Status', '201 Created', true);

			// Try to check-in the record if it's not a new one
			$status = $model->checkin();
		}

		if ($status)
		{
			$status = $this->onAfterApplySave();
		}

		$this->input->set('id', $model->getId());

		if (!$status)
		{
			// Redirect on error
			$id = $model->getId();

			if ($customURL = $this->input->get('returnurl', '', 'string'))
			{
				$customURL = base64_decode($customURL);
			}

			if (!empty($customURL))
			{
				$url = $customURL;
			}
			elseif ($id != 0)
			{
				$url = 'index.php?option=' . $this->component . '&view=' . $this->view . '&task=edit&id=' . $id . $this->getItemidURLSuffix();
			}
			else
			{
				$url = 'index.php?option=' . $this->component . '&view=' . $this->view . '&task=add' . $this->getItemidURLSuffix();
			}

			$this->setRedirect($url, '<li>' . implode('</li><li>', $model->getErrors()) . '</li>', 'error');

			return false;
		}
		else
		{
			$session = JFactory::getSession();
			$session->set($model->getHash() . 'savedata', null);

			return true;
		}
	}

	/**
	 * Returns the default model associated with the current view
	 *
	 * @param   array  $config  Configuration variables for the model
	 *
	 * @return  FOFModel  The global instance of the model (singleton)
	 */
	final public function getThisModel($config = array())
	{
		if (!is_object($this->_modelObject))
		{
			// Make sure $config is an array
			if (is_object($config))
			{
				$config = (array) $config;
			}
			elseif (!is_array($config))
			{
				$config = array();
			}

			if (!empty($this->modelName))
			{
				$parts = FOFInflector::explode($this->modelName);
				$modelName = ucfirst(array_pop($parts));
				$prefix = FOFInflector::implode($parts);
			}
			else
			{
				$prefix = ucfirst($this->bareComponent) . 'Model';
				$modelName = ucfirst(FOFInflector::pluralize($this->view));
			}

			if (!array_key_exists('input', $config) || !($config['input'] instanceof FOFInput))
			{
				$config['input'] = $this->input;
			}

			$this->_modelObject = $this->getModel($modelName, $prefix, $config);
		}

		return $this->_modelObject;
	}

	/**
	 * Method to get a model object, loading it if required.
	 *
	 * @param   string  $name    The model name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  object  The model.
	 */
	public function getModel($name = '', $prefix = '', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config) || empty($config))
		{
			// array_merge is required to create a copy instead of assigning by reference
			$config = array_merge($this->config);
		}

		if (empty($name))
		{
			$name = $this->getName();
		}

		if (empty($prefix))
		{
			$prefix = $this->model_prefix;
		}

		if ($model = $this->createModel($name, $prefix, $config))
		{
			// Task is a reserved state
			$model->setState('task', $this->task);

			// Let's get the application object and set menu information if it's available
			if (!FOFPlatform::getInstance()->isCli())
			{
				$app = JFactory::getApplication();
				$menu = $app->getMenu();

				if (is_object($menu))
				{
					if ($item = $menu->getActive())
					{
						$params = $menu->getParams($item->id);

						// Set default state data
						$model->setState('parameters.menu', $params);
					}
				}
			}
		}

		return $model;
	}

	/**
	 * Returns current view object
	 *
	 * @param   array  $config  Configuration variables for the model
	 *
	 * @return  FOFView  The global instance of the view object (singleton)
	 */
	final public function getThisView($config = array())
	{
		if (!is_object($this->_viewObject))
		{
			// Make sure $config is an array
			if (is_object($config))
			{
				$config = (array) $config;
			}
			elseif (!is_array($config) || empty($config))
			{
				// array_merge is required to create a copy instead of assigning by reference
				$config = array_merge($this->config);
			}

			$prefix = null;
			$viewName = null;
			$viewType = null;

			if (!empty($this->viewName))
			{
				$parts = FOFInflector::explode($this->viewName);
				$viewName = ucfirst(array_pop($parts));
				$prefix = FOFInflector::implode($parts);
			}
			else
			{
				$prefix = ucfirst($this->bareComponent) . 'View';
				$viewName = ucfirst($this->view);
			}

			$document = FOFPlatform::getInstance()->getDocument();

			if ($document instanceof JDocument)
			{
				$viewType = $document->getType();
			}
			else
			{
				$viewType = $this->input->getCmd('format', 'html');
			}

			if (($viewType == 'html') && $this->hasForm)
			{
				$viewType = 'form';
			}

			if (!array_key_exists('input', $config) || !($config['input'] instanceof FOFInput))
			{
				$config['input'] = $this->input;
			}

			$config['input']->set('base_path', $this->basePath);

			$this->_viewObject = $this->getView($viewName, $viewType, $prefix, $config);
		}

		return $this->_viewObject;
	}

    /**
     * Method to get the controller name
     *
     * The dispatcher name is set by default parsed using the classname, or it can be set
     * by passing a $config['name'] in the class constructor
     *
     * @throws Exception
     *
     * @return  string  The name of the dispatcher
     */
	public function getName()
	{
		if (empty($this->name))
		{
			if (empty($this->bareComponent))
			{
				$r = null;

				if (!preg_match('/(.*)Controller/i', get_class($this), $r))
				{
					throw new Exception(JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
				}

				$this->name = strtolower($r[1]);
			}
			else
			{
				$this->name = $this->bareComponent;
			}
		}

		return $this->name;
	}

	/**
	 * Get the last task that is being performed or was most recently performed.
	 *
	 * @return  string  The task that is being performed or was most recently performed.
	 */
	public function getTask()
	{
		return $this->task;
	}

	/**
	 * Gets the available tasks in the controller.
	 *
	 * @return  array  Array[i] of task names.
	 */
	public function getTasks()
	{
		return $this->methods;
	}

    /**
     * Method to get a reference to the current view and load it if necessary.
     *
     * @param   string  $name   The view name. Optional, defaults to the controller name.
     * @param   string  $type   The view type. Optional.
     * @param   string  $prefix The class prefix. Optional.
     * @param   array   $config Configuration array for view. Optional.
     *
     * @throws Exception
     *
     * @return  FOFView  Reference to the view or an error.
     */
	public function getView($name = '', $type = '', $prefix = '', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		if (empty($name))
		{
			$name = $this->getName();
		}

		if (empty($prefix))
		{
			$prefix = $this->getName() . 'View';
		}

		$signature = md5($name . $type . $prefix . serialize($config));

		if (empty($this->viewsCache[$signature]))
		{
			if ($view = $this->createView($name, $prefix, $type, $config))
			{
				$this->viewsCache[$signature] = & $view;
			}
			else
			{
				throw new Exception(JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_NOT_FOUND', $name, $type, $prefix), 500);
			}
		}

		return $this->viewsCache[$signature];
	}

	/**
	 * Creates a new model object
	 *
	 * @param   string  $name    The name of the model class, e.g. Items
	 * @param   string  $prefix  The prefix of the model class, e.g. FoobarModel
	 * @param   array   $config  The configuration parameters for the model class
	 *
	 * @return  FOFModel  The model object
	 */
	protected function createModel($name, $prefix = '', $config = array())
	{
		// Make sure $config is an array

		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$result = null;

		// Clean the model name
		$modelName = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$classPrefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		$result = FOFModel::getAnInstance($modelName, $classPrefix, $config);

		return $result;
	}

	/**
	 * Method to load and return a model object.
	 *
	 * @param   string  $name    The name of the model.
	 * @param   string  $prefix  Optional model prefix.
	 * @param   array   $config  Configuration array for the model. Optional.
	 *
	 * @return  mixed   Model object on success; otherwise null
	 */
	protected function &_createModel($name, $prefix = '', $config = array())
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' .__METHOD__ . ' is deprecated. Use createModel() instead.');

		return $this->createModel($name, $prefix, $config);
	}

	/**
	 * Creates a View object instance and returns it
	 *
	 * @param   string  $name    The name of the view, e.g. Items
	 * @param   string  $prefix  The prefix of the view, e.g. FoobarView
	 * @param   string  $type    The type of the view, usually one of Html, Raw, Json or Csv
	 * @param   array   $config  The configuration variables to use for creating the view
	 *
	 * @return  FOFView
	 */
	protected function createView($name, $prefix = '', $type = '', $config = array())
	{
		// Make sure $config is an array

		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$result = null;

		// Clean the view name
		$viewName = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$classPrefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);
		$viewType = preg_replace('/[^A-Z0-9_]/i', '', $type);

		if (!isset($config['input']))
		{
			$config['input'] = $this->input;
		}

		if (($config['input'] instanceof FOFInput))
		{
			$tmpInput = $config['input'];
		}
		else
		{
			$tmpInput = new FOFInput($config['input']);
		}

		// Guess the component name and view

		if (!empty($prefix))
		{
			preg_match('/(.*)View$/', $prefix, $m);
			$component = 'com_' . strtolower($m[1]);
		}
		else
		{
			$component = '';
		}

		if (empty($component) && array_key_exists('input', $config))
		{
			$component = $tmpInput->get('option', $component, 'cmd');
		}

		if (array_key_exists('option', $config))
		{
			if ($config['option'])
			{
				$component = $config['option'];
			}
		}

		$config['option'] = $component;

		$view = strtolower($viewName);

		if (empty($view) && array_key_exists('input', $config))
		{
			$view = $tmpInput->get('view', $view, 'cmd');
		}

		if (array_key_exists('view', $config))
		{
			if ($config['view'])
			{
				$view = $config['view'];
			}
		}

		$config['view'] = $view;

		if (array_key_exists('input', $config))
		{
			$tmpInput->set('option', $config['option']);
			$tmpInput->set('view', $config['view']);
			$config['input'] = $tmpInput;
		}

		// Get the component directories
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

		// Get the base paths where the view class files are expected to live
		$basePaths = array(
			$componentPaths['main'],
			$componentPaths['alt']
		);
		$basePaths = array_merge($this->paths['view']);

		// Get the alternate (singular/plural) view name
		$altViewName = FOFInflector::isPlural($viewName) ? FOFInflector::singularize($viewName) : FOFInflector::pluralize($viewName);

		$suffixes = array(
			$viewName,
			$altViewName,
			'default'
		);

        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		foreach ($suffixes as $suffix)
		{
			// Build the view class name
			$viewClass = $classPrefix . ucfirst($suffix);

			if (class_exists($viewClass))
			{
				// The class is already loaded
				break;
			}

			// The class is not loaded. Let's load it!
			$viewPath = $this->createFileName('view', array('name'	 => $suffix, 'type'	 => $viewType));
			$path = $filesystem->pathFind($basePaths, $viewPath);

			if ($path)
			{
				require_once $path;
			}

			if (class_exists($viewClass))
			{
				// The class was loaded successfully
				break;
			}
		}

		if (!class_exists($viewClass))
		{
			$viewClass = 'FOFView' . ucfirst($type);
		}

		$templateOverridePath = FOFPlatform::getInstance()->getTemplateOverridePath($config['option']);

		// Setup View configuration options

		if (!array_key_exists('template_path', $config))
		{
			$config['template_path'][] = $componentPaths['main'] . '/views/' . FOFInflector::pluralize($config['view']) . '/tmpl';

			if ($templateOverridePath)
			{
				$config['template_path'][] = $templateOverridePath . '/' . FOFInflector::pluralize($config['view']);
			}

			$config['template_path'][] = $componentPaths['main'] . '/views/' . FOFInflector::singularize($config['view']) . '/tmpl';

			if ($templateOverridePath)
			{
				$config['template_path'][] = $templateOverridePath . '/' . FOFInflector::singularize($config['view']);
			}

			$config['template_path'][] = $componentPaths['main'] . '/views/' . $config['view'] . '/tmpl';

			if ($templateOverridePath)
			{
				$config['template_path'][] = $templateOverridePath . '/' . $config['view'];
			}
		}

		$extraTemplatePath = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.template_path', null);

		if ($extraTemplatePath)
		{
			array_unshift($config['template_path'], $componentPaths['main'] . '/' . $extraTemplatePath);
		}

		if (!array_key_exists('helper_path', $config))
		{
			$config['helper_path'] = array(
				$componentPaths['main'] . '/helpers',
				$componentPaths['admin'] . '/helpers'
			);
		}

		$extraHelperPath = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.helper_path', null);

		if ($extraHelperPath)
		{
			$config['helper_path'][] = $componentPaths['main'] . '/' . $extraHelperPath;
		}

		// Set up the page title
		$setFrontendPageTitle = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.setFrontendPageTitle', null);

		if ($setFrontendPageTitle)
		{
			$setFrontendPageTitle = strtolower($setFrontendPageTitle);
			$config['setFrontendPageTitle'][] = in_array($setFrontendPageTitle, array('1', 'yes', 'true', 'on'));
		}

		$defaultPageTitle = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.defaultPageTitle', null);

		if ($defaultPageTitle)
		{
			$config['defaultPageTitle'][] = in_array($defaultPageTitle, array('1', 'yes', 'true', 'on'));
		}

		// Set the use_hypermedia flag in $config if it's not already set
		if (!isset($config['use_hypermedia']))
		{
			$config['use_hypermedia'] = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.use_hypermedia', false);
		}

		// Set also the linkbar_style
		if (!isset($config['linkbar_style']))
		{
			$style = $this->configProvider->get($config['option'] . '.views.' . $config['view'] . '.config.linkbar_style', false);

			if ($style) {
				$config['linkbar_style'] = $style;
			}
		}

		/**
		 * Some administrative templates force format=utf (yeah, I know, what the heck, right?) when a format
		 * URL parameter does not exist in the URL. Of course there is no such thing as FOFViewUtf (why the heck would
		 * it be, there is no such thing as a format=utf in Joomla! for crying out loud) which causes a Fatal Error. So
		 * we have to detect that and force $type='html'...
		 */
		if (!class_exists($viewClass) && ($type != 'html'))
		{
			$type = 'html';
			$result = $this->createView($name, $prefix, $type, $config);
		}
		else
		{
			$result = new $viewClass($config);
		}

		return $result;
	}

	/**
	 * Deprecated function to create a View object instance
	 *
	 * @param   string  $name    The name of the view, e.g. 'Items'
	 * @param   string  $prefix  The prefix of the view, e.g. 'FoobarView'
	 * @param   string  $type    The view type, e.g. 'html'
	 * @param   array   $config  The configuration array for the view
	 *
	 * @return  FOFView
	 *
	 * @see FOFController::createView
	 *
	 * @deprecated since version 2.0
	 */
	protected function &_createView($name, $prefix = '', $type = '', $config = array())
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' . __METHOD__ . ' is deprecated. Use createView() instead.');

		return $this->createView($name, $prefix, $type, $config);
	}

	/**
	 * Set the name of the view to be used by this Controller
	 *
	 * @param   string  $viewName  The name of the view
	 *
	 * @return  void
	 */
	public function setThisViewName($viewName)
	{
		$this->viewName = $viewName;
	}

	/**
	 * Set the name of the model to be used by this Controller
	 *
	 * @param   string  $modelName  The name of the model
	 *
	 * @return  void
	 */
	public function setThisModelName($modelName)
	{
		$this->modelName = $modelName;
	}

	/**
	 * Checks if the current user has enough privileges for the requested ACL
	 * area.
	 *
	 * @param   string  $area  The ACL area, e.g. core.manage.
	 *
	 * @return  boolean  True if the user has the ACL privilege specified
	 */
	protected function checkACL($area)
	{
		if (in_array(strtolower($area), array('false','0','no','403')))
		{
			return false;
		}

		if (in_array(strtolower($area), array('true','1','yes')))
		{
			return true;
		}
		elseif (empty($area))
		{
			return true;
		}
		else
		{
			// Check if we're dealing with ids
			$ids = null;

			// First, check if there is an asset for this record
			$table = $this->getThisModel()->getTable();

			if ($table && $table->isAssetsTracked())
			{
				$ids = $this->getThisModel()->getId() ? $this->getThisModel()->getId() : null;
			}

			// Generic or Asset tracking

			if (empty($ids))
			{
				return FOFPlatform::getInstance()->authorise($area, $this->component);
			}
			else
			{
				if (!is_array($ids))
				{
					$ids = array($ids);
				}

				$resource = FOFInflector::singularize($this->view);
				$isEditState = ($area == 'core.edit.state');

				foreach ($ids as $id)
				{
					$asset = $this->component . '.' . $resource . '.' . $id;

					// Dedicated permission found, check it!

					if (FOFPlatform::getInstance()->authorise($area, $asset) )
					{
						return true;
					}

					// Fallback on edit.own, if not edit.state. First test if the permission is available.

					if ((!$isEditState) && (FOFPlatform::getInstance()->authorise('core.edit.own', $asset)))
					{
						$table = $this->getThisModel()->getTable();
                        $table->load($id);

                        $created_by = $table->getColumnAlias('created_by');

						if ($table && isset($table->$created_by))
						{
							// Now test the owner is the user.
							$owner_id = (int) $table->$created_by;

							// If the owner matches 'me' then do the test.
							if ($owner_id == FOFPlatform::getInstance()->getUser()->id)
							{
								return true;
							}
							else
							{
								return false;
							}
						}
						else
						{
							return false;
						}
					}
				}
			}
		}

		return false;
	}

	/**
	 * A catch-all method for all tasks without a corresponding onBefore
	 * method. Applies the ACL preferences defined in fof.xml.
	 *
	 * @param   string  $task  The task being executed
	 *
	 * @return  boolean  True to allow execution of the task
	 */
	protected function onBeforeGenericTask($task)
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.' . $task, ''
		);

		return $this->checkACL($privilege);
	}

	/**
	 * Execute something before applySave is called. Return false to prevent
	 * applySave from executing.
	 *
	 * @param   array  &$data  The data upon which applySave will act
	 *
	 * @return  boolean  True to allow applySave to run
	 */
	protected function onBeforeApplySave(&$data)
	{
		return true;
	}

	/**
	 * Execute something after applySave has run.
	 *
	 * @return  boolean  True to allow normal return, false to cause a 403 error
	 */
	protected function onAfterApplySave()
	{
		return true;
	}

	/**
	 * ACL check before changing the access level; override to customise
	 *
	 * @return  boolean  True to allow accesspublic() to run
	 */
	protected function onBeforeAccesspublic()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.accesspublic', 'core.edit.state');

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the access level; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeAccessregistered()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.accessregistered', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the access level; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeAccessspecial()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.accessspecial', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before adding a new record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeAdd()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.add', 'core.create'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before saving a new/modified record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeApply()
	{
        $model = $this->getThisModel();

        if (!$model->getId())
        {
            $model->setIDsFromRequest();
        }

        $id = $model->getId();

        if(!$id)
        {
            $defaultPrivilege = 'core.create';
        }
        else
        {
            $defaultPrivilege = 'core.edit';
        }

		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.apply', $defaultPrivilege
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before allowing someone to browse
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeBrowse()
	{
		$defaultPrivilege = '';

		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.browse', $defaultPrivilege
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before cancelling an edit
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeCancel()
	{
        $model = $this->getThisModel();

        if (!$model->getId())
        {
            $model->setIDsFromRequest();
        }

        $id = $model->getId();

        if(!$id)
        {
            $defaultPrivilege = 'core.create';
        }
        else
        {
            $defaultPrivilege = 'core.edit';
        }

		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.cancel', $defaultPrivilege
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before editing a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeEdit()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.edit', 'core.edit'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the ordering of a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeOrderdown()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.orderdown', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the ordering of a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeOrderup()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.orderup', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the publish status of a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforePublish()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.publish', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before removing a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeRemove()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.remove', 'core.delete'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before saving a new/modified record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeSave()
	{
		$model = $this->getThisModel();

		if (!$model->getId())
		{
			$model->setIDsFromRequest();
		}

		$id = $model->getId();

		if(!$id)
		{
			$defaultPrivilege = 'core.create';
		}
		else
		{
			$defaultPrivilege = 'core.edit';
		}

		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.save', $defaultPrivilege
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before saving a new/modified record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeSavenew()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.savenew', 'core.create'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the ordering of a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeSaveorder()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.saveorder', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * ACL check before changing the publish status of a record; override to customise
	 *
	 * @return  boolean  True to allow the method to run
	 */
	protected function onBeforeUnpublish()
	{
		$privilege = $this->configProvider->get(
			$this->component . '.views.' .
			FOFInflector::singularize($this->view) . '.acl.unpublish', 'core.edit.state'
		);

		return $this->checkACL($privilege);
	}

	/**
	 * Gets a URL suffix with the Itemid parameter. If it's not the front-end of the site, or if
	 * there is no Itemid set it returns an empty string.
	 *
	 * @return  string  The &Itemid=123 URL suffix, or an empty string if Itemid is not applicable
	 */
	public function getItemidURLSuffix()
	{
		if (FOFPlatform::getInstance()->isFrontend() && ($this->input->getCmd('Itemid', 0) != 0))
		{
			return '&Itemid=' . $this->input->getInt('Itemid', 0);
		}
		else
		{
			return '';
		}
	}

	/**
	 * Applies CSRF protection by means of a standard Joomla! token (nonce) check.
	 * Raises a 403 Access Forbidden error through the platform if the check fails.
	 *
     * TODO Move this check inside the platform
     *
	 * @return  boolean  True if the CSRF check is successful
	 *
	 * @throws Exception
	 */
	protected function _csrfProtection()
	{
		static $isCli = null, $isAdmin = null;

		if (is_null($isCli))
		{
			$isCli   = FOFPlatform::getInstance()->isCli();
			$isAdmin = FOFPlatform::getInstance()->isBackend();
		}

		switch ($this->csrfProtection)
		{
			// Never
			case 0:
				return true;
				break;

			// Always
			case 1:
				break;

			// Only back-end and HTML format
			case 2:
				if ($isCli)
				{
					return true;
				}
				elseif (!$isAdmin && ($this->input->get('format', 'html', 'cmd') != 'html'))
				{
					return true;
				}
				break;

			// Only back-end
			case 3:
				if (!$isAdmin)
				{
					return true;
				}
				break;
		}

		$hasToken = false;
		$session  = JFactory::getSession();

		// Joomla! 1.5/1.6/1.7/2.5 (classic Joomla! API) method
		if (method_exists('JUtility', 'getToken'))
		{
			$token    = JUtility::getToken();
			$hasToken = $this->input->get($token, false, 'none') == 1;

			if (!$hasToken)
			{
				$hasToken = $this->input->get('_token', null, 'none') == $token;
			}
		}

		// Joomla! 2.5+ (Platform 12.1+) method
		if (!$hasToken)
		{
			if (method_exists($session, 'getToken'))
			{
				$token    = $session->getToken();
				$hasToken = $this->input->get($token, false, 'none') == 1;

				if (!$hasToken)
				{
					$hasToken = $this->input->get('_token', null, 'none') == $token;
				}
			}
		}

		// Joomla! 2.5+ formToken method
		if (!$hasToken)
		{
			if (method_exists($session, 'getFormToken'))
			{
				$token    = $session->getFormToken();
				$hasToken = $this->input->get($token, false, 'none') == 1;

				if (!$hasToken)
				{
					$hasToken = $this->input->get('_token', null, 'none') == $token;
				}
			}
		}

		if (!$hasToken)
		{
            FOFPlatform::getInstance()->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));

			return false;
		}
	}
}
fof/autoloader/fof.php000064400000004655152177723700010765 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  autoloader
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * The main class autoloader for FOF itself
 *
 * @package     FrameworkOnFramework
 * @subpackage  autoloader
 * @since       2.1
 */
class FOFAutoloaderFof
{
	/**
	 * An instance of this autoloader
	 *
	 * @var   FOFAutoloaderFof
	 */
	public static $autoloader = null;

	/**
	 * The path to the FOF root directory
	 *
	 * @var   string
	 */
	public static $fofPath = null;

	/**
	 * Initialise this autoloader
	 *
	 * @return  FOFAutoloaderFof
	 */
	public static function init()
	{
		if (self::$autoloader == null)
		{
			self::$autoloader = new self;
		}

		return self::$autoloader;
	}

	/**
	 * Public constructor. Registers the autoloader with PHP.
	 */
	public function __construct()
	{
		self::$fofPath = realpath(__DIR__ . '/../');

		spl_autoload_register(array($this,'autoload_fof_core'));
	}

	/**
	 * The actual autoloader
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_core($class_name)
	{
		// Make sure the class has a FOF prefix
		if (substr($class_name, 0, 3) != 'FOF')
		{
			return;
		}

		// Remove the prefix
		$class = substr($class_name, 3);

		// Change from camel cased (e.g. ViewHtml) into a lowercase array (e.g. 'view','html')
		$class = preg_replace('/(\s)+/', '_', $class);
		$class = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class));
		$class = explode('_', $class);

		// First try finding in structured directory format (preferred)
		$path = self::$fofPath . '/' . implode('/', $class) . '.php';

		if (@file_exists($path))
		{
			include_once $path;
		}

		// Then try the duplicate last name structured directory format (not recommended)

		if (!class_exists($class_name, false))
		{
			reset($class);
			$lastPart = end($class);
			$path = self::$fofPath . '/' . implode('/', $class) . '/' . $lastPart . '.php';

			if (@file_exists($path))
			{
				include_once $path;
			}
		}

		// If it still fails, try looking in the legacy folder (used for backwards compatibility)

		if (!class_exists($class_name, false))
		{
			$path = self::$fofPath . '/legacy/' . implode('/', $class) . '.php';

			if (@file_exists($path))
			{
				include_once $path;
			}
		}
	}
}
fof/autoloader/component.php000064400000047003152177723700012207 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  autoloader
 *  @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 *  @license     GNU General Public License version 2, or later
 */

defined('FOF_INCLUDED') or die();

/**
 * An autoloader for FOF-powered components. It allows the autoloading of
 * various classes related to the operation of a component, from Controllers
 * and Models to Helpers and Fields. If a class doesn't exist, it will be
 * created on the fly.
 *
 * @package  FrameworkOnFramework
 * @subpackage  autoloader
 * @since    2.1
 */
class FOFAutoloaderComponent
{
	/**
	 * An instance of this autoloader
	 *
	 * @var   FOFAutoloaderComponent
	 */
	public static $autoloader = null;

	/**
	 * The path to the FOF root directory
	 *
	 * @var   string
	 */
	public static $fofPath = null;

	/**
	 * An array holding component names and their FOF-ness status
	 *
	 * @var   array
	 */
	protected static $fofComponents = array();

	/**
	 * Initialise this autoloader
	 *
	 * @return  FOFAutoloaderComponent
	 */
	public static function init()
	{
		if (self::$autoloader == null)
		{
			self::$autoloader = new self;
		}

		return self::$autoloader;
	}

	/**
	 * Public constructor. Registers the autoloader with PHP.
	 */
	public function __construct()
	{
		self::$fofPath = realpath(__DIR__ . '/../');

		spl_autoload_register(array($this,'autoload_fof_controller'));
		spl_autoload_register(array($this,'autoload_fof_model'));
		spl_autoload_register(array($this,'autoload_fof_view'));
		spl_autoload_register(array($this,'autoload_fof_table'));
		spl_autoload_register(array($this,'autoload_fof_helper'));
		spl_autoload_register(array($this,'autoload_fof_toolbar'));
		spl_autoload_register(array($this,'autoload_fof_field'));
	}

	/**
	 * Returns true if this is a FOF-powered component, i.e. if it has a fof.xml
	 * file in its main directory.
	 *
	 * @param   string  $component  The component's name
	 *
	 * @return  boolean
	 */
	public function isFOFComponent($component)
	{
		if (!isset($fofComponents[$component]))
		{
			$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);
			$fofComponents[$component] = file_exists($componentPaths['admin'] . '/fof.xml');
		}

		return $fofComponents[$component];
	}

	/**
	 * Creates class aliases. On systems where eval() is enabled it creates a
	 * real class. On other systems it merely creates an alias. The eval()
	 * method is preferred as class_aliases result in the name of the class
	 * being instanciated not being available, making it impossible to create
	 * a class instance without passing a $config array :(
	 *
	 * @param   string   $original  The name of the original (existing) class
	 * @param   string   $alias     The name of the new (aliased) class
	 * @param   boolean  $autoload  Should I try to autoload the $original class?
	 *
	 * @return  void
	 */
	private function class_alias($original, $alias, $autoload = true)
	{
		static $hasEval = null;

		if (is_null($hasEval))
		{
			$hasEval = false;

			if (function_exists('ini_get'))
			{
				$disabled_functions = ini_get('disabled_functions');

				if (!is_string($disabled_functions))
				{
					$hasEval = true;
				}
				else
				{
					$disabled_functions = explode(',', $disabled_functions);
					$hasEval = !in_array('eval', $disabled_functions);
				}
			}
		}

		if (!class_exists($original, $autoload))
		{
			return;
		}

		if ($hasEval)
		{
			$phpCode = "class $alias extends $original {}";
			eval($phpCode);
		}
		else
		{
			class_alias($original, $alias, $autoload);
		}
	}

	/**
	 * Autoload Controllers
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_controller($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'Controller') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need three parts in the name
		if (count($parts) != 3)
		{
			return;
		}

		// We need the second part to be "controller"
		if ($parts[1] != 'controller')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];
		$view = $parts[2];

		// Is this an FOF 2.1 or later component?
		if (!$this->isFOFComponent($component))
		{
			return;
		}

		// Get the alternate view and class name (opposite singular/plural name)
		$alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view);
		$alt_class = FOFInflector::camelize($component_raw . '_controller_' . $alt_view);

		// Get the component's paths
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);

		// Get the proper and alternate paths and file names
		$file = "/controllers/$view.php";
		$altFile = "/controllers/$alt_view.php";
		$path = $componentPaths['main'];
		$altPath = $componentPaths['alt'];

		// Try to find the proper class in the proper path
		if (file_exists($path . $file))
		{
			@include_once $path . $file;
		}

		// Try to find the proper class in the alternate path
		if (!class_exists($class_name) && file_exists($altPath . $file))
		{
			@include_once $altPath . $file;
		}

		// Try to find the alternate class in the proper path
		if (!class_exists($alt_class) && file_exists($path . $altFile))
		{
			@include_once $path . $altFile;
		}

		// Try to find the alternate class in the alternate path
		if (!class_exists($alt_class) && file_exists($altPath . $altFile))
		{
			@include_once $altPath . $altFile;
		}

		// If the alternate class exists just map the class to the alternate
		if (!class_exists($class_name) && class_exists($alt_class))
		{
			$this->class_alias($alt_class, $class_name);
		}

		// No class found? Map to FOFController
		elseif (!class_exists($class_name))
		{
			if ($view != 'default')
			{
				$defaultClass = FOFInflector::camelize($component_raw . '_controller_default');
				$this->class_alias($defaultClass, $class_name);
			}
			else
			{
				$this->class_alias('FOFController', $class_name);
			}
		}
	}

	/**
	 * Autoload Models
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_model($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'Model') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need three parts in the name
		if (count($parts) != 3)
		{
			return;
		}

		// We need the second part to be "model"
		if ($parts[1] != 'model')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];
		$view = $parts[2];

		// Is this an FOF 2.1 or later component?
		if (!$this->isFOFComponent($component))
		{
			return;
		}

		// Get the alternate view and class name (opposite singular/plural name)
		$alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view);
		$alt_class = FOFInflector::camelize($component_raw . '_model_' . $alt_view);

		// Get the proper and alternate paths and file names
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);

		$file = "/models/$view.php";
		$altFile = "/models/$alt_view.php";
		$path = $componentPaths['main'];
		$altPath = $componentPaths['alt'];

		// Try to find the proper class in the proper path
		if (file_exists($path . $file))
		{
			@include_once $path . $file;
		}

		// Try to find the proper class in the alternate path
		if (!class_exists($class_name) && file_exists($altPath . $file))
		{
			@include_once $altPath . $file;
		}

		// Try to find the alternate class in the proper path
		if (!class_exists($alt_class) && file_exists($path . $altFile))
		{
			@include_once $path . $altFile;
		}

		// Try to find the alternate class in the alternate path
		if (!class_exists($alt_class) && file_exists($altPath . $altFile))
		{
			@include_once $altPath . $altFile;
		}

		// If the alternate class exists just map the class to the alternate
		if (!class_exists($class_name) && class_exists($alt_class))
		{
			$this->class_alias($alt_class, $class_name);
		}

		// No class found? Map to FOFModel
		elseif (!class_exists($class_name))
		{
			if ($view != 'default')
			{
				$defaultClass = FOFInflector::camelize($component_raw . '_model_default');
				$this->class_alias($defaultClass, $class_name);
			}
			else
			{
				$this->class_alias('FOFModel', $class_name, true);
			}
		}
	}

	/**
	 * Autoload Views
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_view($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'View') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need at least three parts in the name

		if (count($parts) < 3)
		{
			return;
		}

		// We need the second part to be "view"

		if ($parts[1] != 'view')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];
		$view = $parts[2];

		if (count($parts) > 3)
		{
			$format = $parts[3];
		}
		else
		{
			$input = new FOFInput;
			$format = $input->getCmd('format', 'html', 'cmd');
		}

		// Is this an FOF 2.1 or later component?
		if (!$this->isFOFComponent($component))
		{
			return;
		}

		// Get the alternate view and class name (opposite singular/plural name)
		$alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view);
		$alt_class = FOFInflector::camelize($component_raw . '_view_' . $alt_view);

		// Get the proper and alternate paths and file names
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);

		$protoFile = "/models/$view";
		$protoAltFile = "/models/$alt_view";
		$path = $componentPaths['main'];
		$altPath = $componentPaths['alt'];

		$formats = array($format);

		if ($format != 'html')
		{
			$formats[] = 'raw';
		}

		foreach ($formats as $currentFormat)
		{
			$file = $protoFile . '.' . $currentFormat . '.php';
			$altFile = $protoAltFile . '.' . $currentFormat . '.php';

			// Try to find the proper class in the proper path
			if (!class_exists($class_name) && file_exists($path . $file))
			{
				@include_once $path . $file;
			}

			// Try to find the proper class in the alternate path
			if (!class_exists($class_name) && file_exists($altPath . $file))
			{
				@include_once $altPath . $file;
			}

			// Try to find the alternate class in the proper path
			if (!class_exists($alt_class) && file_exists($path . $altFile))
			{
				@include_once $path . $altFile;
			}

			// Try to find the alternate class in the alternate path
			if (!class_exists($alt_class) && file_exists($altPath . $altFile))
			{
				@include_once $altPath . $altFile;
			}
		}

		// If the alternate class exists just map the class to the alternate
		if (!class_exists($class_name) && class_exists($alt_class))
		{
			$this->class_alias($alt_class, $class_name);
		}

		// No class found? Map to FOFModel
		elseif (!class_exists($class_name))
		{
			if ($view != 'default')
			{
				$defaultClass = FOFInflector::camelize($component_raw . '_view_default');
				$this->class_alias($defaultClass, $class_name);
			}
			else
			{
				if (!file_exists(self::$fofPath . '/view/' . $format . '.php'))
				{
					$default_class = 'FOFView';
				}
				else
				{
					$default_class = 'FOFView' . ucfirst($format);
				}

				$this->class_alias($default_class, $class_name, true);
			}
		}
	}

	/**
	 * Autoload Tables
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_table($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'Table') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need three parts in the name

		if (count($parts) != 3)
		{
			return;
		}

		// We need the second part to be "model"
		if ($parts[1] != 'table')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];
		$view = $parts[2];

		// Is this an FOF 2.1 or later component?
		if (!$this->isFOFComponent($component))
		{
			return;
		}

		// Get the alternate view and class name (opposite singular/plural name)
		$alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view);
		$alt_class = FOFInflector::camelize($component_raw . '_table_' . $alt_view);

		// Get the proper and alternate paths and file names
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);

		$file = "/tables/$view.php";
		$altFile = "/tables/$alt_view.php";
		$path = $componentPaths['admin'];

		// Try to find the proper class in the proper path
		if (file_exists($path . $file))
		{
			@include_once $path . $file;
		}

		// Try to find the alternate class in the proper path
		if (!class_exists($alt_class) && file_exists($path . $altFile))
		{
			@include_once $path . $altFile;
		}

		// If the alternate class exists just map the class to the alternate
		if (!class_exists($class_name) && class_exists($alt_class))
		{
			$this->class_alias($alt_class, $class_name);
		}

		// No class found? Map to FOFModel
		elseif (!class_exists($class_name))
		{
			if ($view != 'default')
			{
				$defaultClass = FOFInflector::camelize($component_raw . '_table_default');
				$this->class_alias($defaultClass, $class_name);
			}
			else
			{
				$this->class_alias('FOFTable', $class_name, true);
			}
		}
	}

	/**
	 * Autoload Helpers
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_helper($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'Helper') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need three parts in the name
		if (count($parts) != 3)
		{
			return;
		}

		// We need the second part to be "model"
		if ($parts[1] != 'helper')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];
		$view = $parts[2];

		// Is this an FOF 2.1 or later component?
		if (!$this->isFOFComponent($component))
		{
			return;
		}

		// Get the alternate view and class name (opposite singular/plural name)
		$alt_view = FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view);
		$alt_class = FOFInflector::camelize($component_raw . '_helper_' . $alt_view);

		// Get the proper and alternate paths and file names
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);

		$file = "/helpers/$view.php";
		$altFile = "/helpers/$alt_view.php";
		$path = $componentPaths['main'];
		$altPath = $componentPaths['alt'];

		// Try to find the proper class in the proper path
		if (file_exists($path . $file))
		{
			@include_once $path . $file;
		}

		// Try to find the proper class in the alternate path
		if (!class_exists($class_name) && file_exists($altPath . $file))
		{
			@include_once $altPath . $file;
		}

		// Try to find the alternate class in the proper path
		if (!class_exists($alt_class) && file_exists($path . $altFile))
		{
			@include_once $path . $altFile;
		}

		// Try to find the alternate class in the alternate path
		if (!class_exists($alt_class) && file_exists($altPath . $altFile))
		{
			@include_once $altPath . $altFile;
		}

		// If the alternate class exists just map the class to the alternate
		if (!class_exists($class_name) && class_exists($alt_class))
		{
			$this->class_alias($alt_class, $class_name);
		}
	}

	/**
	 * Autoload Toolbars
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_toolbar($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		static $isCli = null, $isAdmin = null;

		if (is_null($isCli) && is_null($isAdmin))
		{
			list($isCli, $isAdmin) = FOFDispatcher::isCliAdmin();
		}

		if (strpos($class_name, 'Toolbar') === false)
		{
			return;
		}

		// Change from camel cased into a lowercase array
		$class_modified = preg_replace('/(\s)+/', '_', $class_name);
		$class_modified = strtolower(preg_replace('/(?<=\\w)([A-Z])/', '_\\1', $class_modified));
		$parts = explode('_', $class_modified);

		// We need two parts in the name
		if (count($parts) != 2)
		{
			return;
		}

		// We need the second part to be "model"
		if ($parts[1] != 'toolbar')
		{
			return;
		}

		// Get the information about this class
		$component_raw  = $parts[0];
		$component = 'com_' . $parts[0];

        $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();

		// Get the proper and alternate paths and file names
		$file    = "/components/$component/toolbar.php";
		$path    = ($isAdmin || $isCli) ? $platformDirs['admin'] : $platformDirs['public'];
		$altPath = ($isAdmin || $isCli) ? $platformDirs['public'] : $platformDirs['admin'];

		// Try to find the proper class in the proper path

		if (file_exists($path . $file))
		{
			@include_once $path . $file;
		}

		// Try to find the proper class in the alternate path
		if (!class_exists($class_name) && file_exists($altPath . $file))
		{
			@include_once $altPath . $file;
		}

		// No class found? Map to FOFToolbar
		if (!class_exists($class_name))
		{
			$this->class_alias('FOFToolbar', $class_name, true);
		}
	}

	/**
	 * Autoload Fields
	 *
	 * @param   string  $class_name  The name of the class to load
	 *
	 * @return  void
	 */
	public function autoload_fof_field($class_name)
	{
        FOFPlatform::getInstance()->logDebug(__METHOD__ . "() autoloading $class_name");

		// @todo
	}
}
fof/dispatcher/dispatcher.php000064400000042242152177723700012322 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  dispatcher
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project in 2019 in order to ensure compatibility with PHP 7.3+ versions.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework dispatcher class
 *
 * FrameworkOnFramework is a set of classes which extend Joomla! 1.5 and later's
 * MVC framework with features making maintaining complex software much easier,
 * without tedious repetitive copying of the same code over and over again.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFDispatcher extends FOFUtilsObject
{
	/** @var array Configuration variables */
	protected $config = array();

	/** @var FOFInput Input variables */
	protected $input = array();

	/** @var string The name of the default view, in case none is specified */
	public $defaultView = 'cpanel';

	// Variables for FOF's transparent user authentication. You can override them
	// in your Dispatcher's __construct() method.

	/** @var int The Time Step for the TOTP used in FOF's transparent user authentication */
	protected $fofAuth_timeStep = 6;

	/** @var string The key for the TOTP, Base32 encoded (watch out; Base32, NOT Base64!) */
	protected $fofAuth_Key = null;

	/** @var array Which formats to be handled by transparent authentication */
	protected $fofAuth_Formats = array('json', 'csv', 'xml', 'raw');

	/**
	 * Should I logout the transparently authenticated user on logout?
	 * Recommended to leave it on in order to avoid crashing the sessions table.
	 *
	 * @var boolean
	 */
	protected $fofAuth_LogoutOnReturn = true;

	/** @var array Which methods to use to fetch authentication credentials and in which order */
	protected $fofAuth_AuthMethods = array(
		/* HTTP Basic Authentication using encrypted information protected
		 * with a TOTP (the username must be "_fof_auth") */
		'HTTPBasicAuth_TOTP',
		/* Encrypted information protected with a TOTP passed in the
		 * _fofauthentication query string parameter */
		'QueryString_TOTP',
		/* HTTP Basic Authentication using a username and password pair in plain text */
		'HTTPBasicAuth_Plaintext',
		/* Plaintext, JSON-encoded username and password pair passed in the
		 * _fofauthentication query string parameter */
		'QueryString_Plaintext',
		/* Plaintext username and password in the _fofauthentication_username
		 * and _fofauthentication_username query string parameters */
		'SplitQueryString_Plaintext',
	);

	/** @var bool Did we successfully and transparently logged in a user? */
	private $_fofAuth_isLoggedIn = false;

	/** @var string The calculated encryption key for the _TOTP methods, used if we have to encrypt the reply */
	private $_fofAuth_CryptoKey = '';

	/**
	 * Get a static (Singleton) instance of a particular Dispatcher
	 *
	 * @param   string  $option  The component name
	 * @param   string  $view    The View name
	 * @param   array   $config  Configuration data
	 *
	 * @staticvar  array  $instances  Holds the array of Dispatchers FOF knows about
	 *
	 * @return  FOFDispatcher
	 */
	public static function &getAnInstance($option = null, $view = null, $config = array())
	{
		static $instances = array();

		$hash = $option . $view;

		if (!array_key_exists($hash, $instances))
		{
			$instances[$hash] = self::getTmpInstance($option, $view, $config);
		}

		return $instances[$hash];
	}

	/**
	 * Gets a temporary instance of a Dispatcher
	 *
	 * @param   string  $option  The component name
	 * @param   string  $view    The View name
	 * @param   array   $config  Configuration data
	 *
	 * @return FOFDispatcher
	 */
	public static function &getTmpInstance($option = null, $view = null, $config = array())
	{
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$input = $config['input'];
			}
			else
			{
				if (!is_array($config['input']))
				{
					$config['input'] = (array) $config['input'];
				}

				$config['input'] = array_merge($_REQUEST, $config['input']);
				$input = new FOFInput($config['input']);
			}
		}
		else
		{
			$input = new FOFInput;
		}

		$config['option']   = !is_null($option) ? $option : $input->getCmd('option', 'com_foobar');
		$config['view']     = !is_null($view) ? $view : $input->getCmd('view', '');

		$input->set('option', $config['option']);
		$input->set('view', $config['view']);

		$config['input'] = $input;

		$className = ucfirst(str_replace('com_', '', $config['option'])) . 'Dispatcher';

		if (!class_exists($className))
		{
			$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

			$searchPaths = array(
				$componentPaths['main'],
				$componentPaths['main'] . '/dispatchers',
				$componentPaths['admin'],
				$componentPaths['admin'] . '/dispatchers'
			);

			if (array_key_exists('searchpath', $config))
			{
				array_unshift($searchPaths, $config['searchpath']);
			}

			$filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

			$path = $filesystem->pathFind(
					$searchPaths, 'dispatcher.php'
			);

			if ($path)
			{
				require_once $path;
			}
		}

		if (!class_exists($className))
		{
			$className = 'FOFDispatcher';
		}

		$instance = new $className($config);

		return $instance;
	}

	/**
	 * Public constructor
	 *
	 * @param   array  $config  The configuration variables
	 */
	public function __construct($config = array())
	{
		// Cache the config
		$this->config = $config;

		// Get the input for this MVC triad
		if (array_key_exists('input', $config))
		{
			$this->input = $config['input'];
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Get the default values for the component name
		$this->component = $this->input->getCmd('option', 'com_foobar');

		// Load the component's fof.xml configuration file
		$configProvider = new FOFConfigProvider;
		$this->defaultView = $configProvider->get($this->component . '.dispatcher.default_view', $this->defaultView);

		// Get the default values for the view name
		$this->view = $this->input->getCmd('view', null);

		if (empty($this->view))
		{
			// Do we have a task formatted as controller.task?
			$task = $this->input->getCmd('task', '');

			if (!empty($task) && (strstr($task, '.') !== false))
			{
				list($this->view, $task) = explode('.', $task, 2);
				$this->input->set('task', $task);
			}
		}

		if (empty($this->view))
		{
			$this->view = $this->defaultView;
		}

		$this->layout = $this->input->getCmd('layout', null);

		// Overrides from the config
		if (array_key_exists('option', $config))
		{
			$this->component = $config['option'];
		}

		if (array_key_exists('view', $config))
		{
			$this->view = empty($config['view']) ? $this->view : $config['view'];
		}

		if (array_key_exists('layout', $config))
		{
			$this->layout = $config['layout'];
		}

		$this->input->set('option', $this->component);
		$this->input->set('view', $this->view);
		$this->input->set('layout', $this->layout);

		if (array_key_exists('authTimeStep', $config))
		{
			$this->fofAuth_timeStep = empty($config['authTimeStep']) ? 6 : $config['authTimeStep'];
		}
	}

    /**
     * The main code of the Dispatcher. It spawns the necessary controller and
     * runs it.
     *
     * @throws Exception
     *
     * @return  void|Exception
     */
	public function dispatch()
	{
        $platform = FOFPlatform::getInstance();

		if (!$platform->authorizeAdmin($this->input->getCmd('option', 'com_foobar')))
		{
            return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
		}

		$this->transparentAuthentication();

		// Merge English and local translations
		$platform->loadTranslations($this->component);

		$canDispatch = true;

		if ($platform->isCli())
		{
			$canDispatch = $canDispatch && $this->onBeforeDispatchCLI();
		}

		$canDispatch = $canDispatch && $this->onBeforeDispatch();

		if (!$canDispatch)
		{
            // We can set header only if we're not in CLI
            if(!$platform->isCli())
            {
                $platform->setHeader('Status', '403 Forbidden', true);
            }

            return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
		}

		// Get and execute the controller
		$option = $this->input->getCmd('option', 'com_foobar');
		$view   = $this->input->getCmd('view', $this->defaultView);
		$task   = $this->input->getCmd('task', null);

		if (empty($task))
		{
			$task = $this->getTask($view);
		}

		// Pluralise/sungularise the view name for typical tasks
		if (in_array($task, array('edit', 'add', 'read')))
		{
			$view = FOFInflector::singularize($view);
		}
		elseif (in_array($task, array('browse')))
		{
			$view = FOFInflector::pluralize($view);
		}

		$this->input->set('view', $view);
		$this->input->set('task', $task);

		$config = $this->config;
		$config['input'] = $this->input;

		$controller = FOFController::getTmpInstance($option, $view, $config);
		$status = $controller->execute($task);

		if (!$this->onAfterDispatch())
		{
            // We can set header only if we're not in CLI
            if(!$platform->isCli())
            {
                $platform->setHeader('Status', '403 Forbidden', true);
            }

            return $platform->raiseError(403, JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
		}

		$format = $this->input->get('format', 'html', 'cmd');
		$format = empty($format) ? 'html' : $format;

		if ($controller->hasRedirect())
		{
			$controller->redirect();
		}
	}

	/**
	 * Tries to guess the controller task to execute based on the view name and
	 * the HTTP request method.
	 *
	 * @param   string  $view  The name of the view
	 *
	 * @return  string  The best guess of the task to execute
	 */
	protected function getTask($view)
	{
		// Get a default task based on plural/singular view
		$request_task = $this->input->getCmd('task', null);
		$task = FOFInflector::isPlural($view) ? 'browse' : 'edit';

		// Get a potential ID, we might need it later
		$id = $this->input->get('id', null, 'int');

		if ($id == 0)
		{
			$ids = $this->input->get('ids', array(), 'array');

			if (!empty($ids))
			{
				$id = array_shift($ids);
			}
		}

		// Check the request method

		if (!isset($_SERVER['REQUEST_METHOD']))
		{
			$_SERVER['REQUEST_METHOD'] = 'GET';
		}

		$requestMethod = strtoupper($_SERVER['REQUEST_METHOD']);

		switch ($requestMethod)
		{
			case 'POST':
			case 'PUT':
				if (!is_null($id))
				{
					$task = 'save';
				}
				break;

			case 'DELETE':
				if ($id != 0)
				{
					$task = 'delete';
				}
				break;

			case 'GET':
			default:
				// If it's an edit without an ID or ID=0, it's really an add
				if (($task == 'edit') && ($id == 0))
				{
					$task = 'add';
				}

				// If it's an edit in the frontend, it's really a read
				elseif (($task == 'edit') && FOFPlatform::getInstance()->isFrontend())
				{
					$task = 'read';
				}
				break;
		}

		return $task;
	}

	/**
	 * Executes right before the dispatcher tries to instantiate and run the
	 * controller.
	 *
	 * @return  boolean  Return false to abort
	 */
	public function onBeforeDispatch()
	{
		return true;
	}

	/**
	 * Sets up some environment variables, so we can work as usually on CLI, too.
	 *
	 * @return  boolean  Return false to abort
	 */
	public function onBeforeDispatchCLI()
	{
		JLoader::import('joomla.environment.uri');
		JLoader::import('joomla.application.component.helper');

		// Trick to create a valid url used by JURI
		$this->_originalPhpScript = '';

		// We have no Application Helper (there is no Application!), so I have to define these constants manually
		$option = $this->input->get('option', '', 'cmd');

		if ($option)
		{
			$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($option);

			if (!defined('JPATH_COMPONENT'))
			{
				define('JPATH_COMPONENT', $componentPaths['main']);
			}

			if (!defined('JPATH_COMPONENT_SITE'))
			{
				define('JPATH_COMPONENT_SITE', $componentPaths['site']);
			}

			if (!defined('JPATH_COMPONENT_ADMINISTRATOR'))
			{
				define('JPATH_COMPONENT_ADMINISTRATOR', $componentPaths['admin']);
			}
		}

		return true;
	}

	/**
	 * Executes right after the dispatcher runs the controller.
	 *
	 * @return  boolean  Return false to abort
	 */
	public function onAfterDispatch()
	{
		// If we have to log out the user, please do so now
		if ($this->fofAuth_LogoutOnReturn && $this->_fofAuth_isLoggedIn)
		{
			FOFPlatform::getInstance()->logoutUser();
		}

		return true;
	}

	/**
	 * Transparently authenticates a user
	 *
	 * @return  void
	 */
	public function transparentAuthentication()
	{
		// Only run when there is no logged in user
		if (!FOFPlatform::getInstance()->getUser()->guest)
		{
			return;
		}

		// @todo Check the format
		$format = $this->input->getCmd('format', 'html');

		if (!in_array($format, $this->fofAuth_Formats))
		{
			return;
		}

		foreach ($this->fofAuth_AuthMethods as $method)
		{
			// If we're already logged in, don't bother
			if ($this->_fofAuth_isLoggedIn)
			{
				continue;
			}

			// This will hold our authentication data array (username, password)
			$authInfo = null;

			switch ($method)
			{
				case 'HTTPBasicAuth_TOTP':

					if (empty($this->fofAuth_Key))
					{
						continue 2;
					}

					if (!isset($_SERVER['PHP_AUTH_USER']))
					{
						continue 2;
					}

					if (!isset($_SERVER['PHP_AUTH_PW']))
					{
						continue 2;
					}

					if ($_SERVER['PHP_AUTH_USER'] != '_fof_auth')
					{
						continue 2;
					}

					$encryptedData = $_SERVER['PHP_AUTH_PW'];

					$authInfo = $this->_decryptWithTOTP($encryptedData);
					break;

				case 'QueryString_TOTP':
					$encryptedData = $this->input->get('_fofauthentication', '', 'raw');

					if (empty($encryptedData))
					{
						continue 2;
					}

					$authInfo = $this->_decryptWithTOTP($encryptedData);
					break;

				case 'HTTPBasicAuth_Plaintext':
					if (!isset($_SERVER['PHP_AUTH_USER']))
					{
						continue 2;
					}

					if (!isset($_SERVER['PHP_AUTH_PW']))
					{
						continue 2;
					}

					$authInfo = array(
						'username'	 => $_SERVER['PHP_AUTH_USER'],
						'password'	 => $_SERVER['PHP_AUTH_PW']
					);
					break;

				case 'QueryString_Plaintext':
					$jsonencoded = $this->input->get('_fofauthentication', '', 'raw');

					if (empty($jsonencoded))
					{
						continue 2;
					}

					$authInfo = json_decode($jsonencoded, true);

					if (!is_array($authInfo))
					{
						$authInfo = null;
					}
					elseif (!array_key_exists('username', $authInfo) || !array_key_exists('password', $authInfo))
					{
						$authInfo = null;
					}
					break;

				case 'SplitQueryString_Plaintext':
					$authInfo = array(
						'username'	 => $this->input->get('_fofauthentication_username', '', 'raw'),
						'password'	 => $this->input->get('_fofauthentication_password', '', 'raw'),
					);

					if (empty($authInfo['username']))
					{
						$authInfo = null;
					}

					break;

				default:
					continue 2;

					break;
			}

			// No point trying unless we have a username and password
			if (!is_array($authInfo))
			{
				continue;
			}

			$this->_fofAuth_isLoggedIn = FOFPlatform::getInstance()->loginUser($authInfo);
		}
	}

	/**
	 * Decrypts a transparent authentication message using a TOTP
	 *
	 * @param   string  $encryptedData  The encrypted data
	 *
     * @codeCoverageIgnore
	 * @return  array  The decrypted data
	 */
	private function _decryptWithTOTP($encryptedData)
	{
		if (empty($this->fofAuth_Key))
		{
			$this->_fofAuth_CryptoKey = null;

			return null;
		}

		$totp = new FOFEncryptTotp($this->fofAuth_timeStep);
		$period = $totp->getPeriod();
		$period--;

		for ($i = 0; $i <= 2; $i++)
		{
			$time = ($period + $i) * $this->fofAuth_timeStep;
			$otp = $totp->getCode($this->fofAuth_Key, $time);
			$this->_fofAuth_CryptoKey = hash('sha256', $this->fofAuth_Key . $otp);

			$aes = new FOFEncryptAes($this->_fofAuth_CryptoKey);
			$ret = $aes->decryptString($encryptedData);
			$ret = rtrim($ret, "\000");

			$ret = json_decode($ret, true);

			if (!is_array($ret))
			{
				continue;
			}

			if (!array_key_exists('username', $ret))
			{
				continue;
			}

			if (!array_key_exists('password', $ret))
			{
				continue;
			}

			// Successful decryption!
			return $ret;
		}

		// Obviously if we're here we could not decrypt anything. Bail out.
		$this->_fofAuth_CryptoKey = null;

		return null;
	}

	/**
	 * Creates a decryption key for use with the TOTP decryption method
	 *
	 * @param   integer  $time  The timestamp used for TOTP calculation, leave empty to use current timestamp
	 *
     * @codeCoverageIgnore
	 * @return  string  THe encryption key
	 */
	private function _createDecryptionKey($time = null)
	{
		$totp = new FOFEncryptTotp($this->fofAuth_timeStep);
		$otp = $totp->getCode($this->fofAuth_Key, $time);

		$key = hash('sha256', $this->fofAuth_Key . $otp);

		return $key;
	}

	/**
	 * Main function to detect if we're running in a CLI environment and we're admin
	 *
	 * @return  array  isCLI and isAdmin. It's not an associtive array, so we can use list.
	 */
	public static function isCliAdmin()
	{
		static $isCLI   = null;
		static $isAdmin = null;

		if (is_null($isCLI) && is_null($isAdmin))
		{
			$isCLI   = FOFPlatform::getInstance()->isCli();
			$isAdmin = FOFPlatform::getInstance()->isBackend();
		}

		return array($isCLI, $isAdmin);
	}
}
fof/layout/helper.php000064400000002350152177723700010636 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  layout
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Helper to render a FOFLayout object, storing a base path
 *
 * @package  FrameworkOnFramework
 * @since    x.y
 */
class FOFLayoutHelper extends JLayoutHelper
{
	/**
	 * Method to render the layout.
	 *
	 * @param   string  $layoutFile   Dot separated path to the layout file, relative to base path
	 * @param   object  $displayData  Object which properties are used inside the layout file to build displayed output
	 * @param   string  $basePath     Base path to use when loading layout files
	 *
	 * @return  string
	 */
	public static function render($layoutFile, $displayData = null, $basePath = '')
	{
		$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;

		// Make sure we send null to FOFLayoutFile if no path set
		$basePath = empty($basePath) ? null : $basePath;
		$layout = new FOFLayoutFile($layoutFile, $basePath);
		$renderedLayout = $layout->render($displayData);

		return $renderedLayout;
	}
}
fof/layout/file.php000064400000003743152177723700010305 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  layout
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Base class for rendering a display layout
 * loaded from from a layout file
 *
 * This class searches for Joomla! version override Layouts. For example,
 * if you have run this under Joomla! 3.0 and you try to load
 * mylayout.default it will automatically search for the
 * layout files default.j30.php, default.j3.php and default.php, in this
 * order.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFLayoutFile extends JLayoutFile
{
	/**
	 * Method to finds the full real file path, checking possible overrides
	 *
	 * @return  string  The full path to the layout file
	 */
	protected function getPath()
	{
		$filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		if (is_null($this->fullPath) && !empty($this->layoutId))
		{
			$parts = explode('.', $this->layoutId);
			$file  = array_pop($parts);

			$filePath = implode('/', $parts);
			$suffixes = FOFPlatform::getInstance()->getTemplateSuffixes();

			foreach ($suffixes as $suffix)
			{
				$files[] = $file . $suffix . '.php';
			}

			$files[] = $file . '.php';

            $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();
            $prefix       = FOFPlatform::getInstance()->isBackend() ? $platformDirs['admin'] : $platformDirs['root'];

			$possiblePaths = array(
				$prefix . '/templates/' . JFactory::getApplication()->getTemplate() . '/html/layouts/' . $filePath,
				$this->basePath . '/' . $filePath
			);

			reset($files);

			while ((list(, $fileName) = each($files)) && is_null($this->fullPath))
			{
				$r = $filesystem->pathFind($possiblePaths, $fileName);
				$this->fullPath = $r === false ? null : $r;
			}
		}

		return $this->fullPath;
	}
}
fof/view/json.php000064400000017050152177723700007770 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework JSON View class. Renders the data as a JSON object or
 * array. It can optionally output HAL links as well.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFViewJson extends FOFViewHtml
{
	/**
	 * When set to true we'll add hypermedia to the output, implementing the
	 * HAL specification (http://stateless.co/hal_specification.html)
	 *
	 * @var   boolean
	 */
	public $useHypermedia = false;

	/**
	 * Public constructor
	 *
	 * @param   array  $config  The component's configuration array
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		if (isset($config['use_hypermedia']))
		{
			$this->useHypermedia = (bool) $config['use_hypermedia'];
		}
	}

	/**
	 * The event which runs when we are displaying the record list JSON view
	 *
	 * @param   string  $tpl  The view sub-template to use
	 *
	 * @return  boolean  True to allow display of the view
	 */
	protected function onDisplay($tpl = null)
	{
		// Load the model
		$model = $this->getModel();

		$items = $model->getItemList();
		$this->items = $items;

		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			if ($this->useHypermedia)
			{
				$document->setMimeEncoding('application/hal+json');
			}
			else
			{
				$document->setMimeEncoding('application/json');
			}
		}

		if (is_null($tpl))
		{
			$tpl = 'json';
		}

		FOFPlatform::getInstance()->setErrorHandling(E_ALL, 'ignore');

		$hasFailed = false;

		try
		{
			$result = $this->loadTemplate($tpl, true);

			if ($result instanceof Exception)
			{
				$hasFailed = true;
			}
		}
		catch (Exception $e)
		{
			$hasFailed = true;
		}

		if ($hasFailed)
		{
			// Default JSON behaviour in case the template isn't there!
			if ($this->useHypermedia)
			{
				$haldocument = $this->_createDocumentWithHypermedia($items, $model);
				$json = $haldocument->render('json');
			}
			else
			{
				$json = json_encode($items);
			}

			// JSONP support
			$callback = $this->input->get('callback', null, 'raw');

			if (!empty($callback))
			{
				echo $callback . '(' . $json . ')';
			}
			else
			{
				$defaultName = $this->input->getCmd('view', 'joomla');
				$filename = $this->input->getCmd('basename', $defaultName);

				$document->setName($filename);
				echo $json;
			}

			return false;
		}
		else
		{
			echo $result;

			return false;
		}
	}

	/**
	 * The event which runs when we are displaying a single item JSON view
	 *
	 * @param   string  $tpl  The view sub-template to use
	 *
	 * @return  boolean  True to allow display of the view
	 */
	protected function onRead($tpl = null)
	{
		$model = $this->getModel();

		$item = $model->getItem();
		$this->item = $item;

		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			if ($this->useHypermedia)
			{
				$document->setMimeEncoding('application/hal+json');
			}
			else
			{
				$document->setMimeEncoding('application/json');
			}
		}

		if (is_null($tpl))
		{
			$tpl = 'json';
		}

    	FOFPlatform::getInstance()->setErrorHandling(E_ALL, 'ignore');

		$hasFailed = false;

		try
		{
			$result = $this->loadTemplate($tpl, true);

            if ($result instanceof Exception)
            {
                $hasFailed = true;
            }
		}
		catch (Exception $e)
		{
			$hasFailed = true;
		}

		if ($hasFailed)
		{
			// Default JSON behaviour in case the template isn't there!

			if ($this->useHypermedia)
			{
				$haldocument = $this->_createDocumentWithHypermedia($item, $model);
				$json = $haldocument->render('json');
			}
			else
			{
				$json = json_encode($item);
			}

			// JSONP support
			$callback = $this->input->get('callback', null);

			if (!empty($callback))
			{
				echo $callback . '(' . $json . ')';
			}
			else
			{
				$defaultName = $this->input->getCmd('view', 'joomla');
				$filename = $this->input->getCmd('basename', $defaultName);
				$document->setName($filename);
				echo $json;
			}

			return false;
		}
		else
		{
			echo $result;

			return false;
		}
	}

	/**
	 * Creates a FOFHalDocument using the provided data
	 *
	 * @param   array     $data   The data to put in the document
	 * @param   FOFModel  $model  The model of this view
	 *
	 * @return  FOFHalDocument  A HAL-enabled document
	 */
	protected function _createDocumentWithHypermedia($data, $model = null)
	{
		// Create a new HAL document

		if (is_array($data))
		{
			$count = count($data);
		}
		else
		{
			$count = null;
		}

		if ($count == 1)
		{
			reset($data);
			$document = new FOFHalDocument(end($data));
		}
		else
		{
			$document = new FOFHalDocument($data);
		}

		// Create a self link
		$uri = (string) (JUri::getInstance());
		$uri = $this->_removeURIBase($uri);
		$uri = JRoute::_($uri);
		$document->addLink('self', new FOFHalLink($uri));

		// Create relative links in a record list context

		if (is_array($data) && ($model instanceof FOFModel))
		{
			$pagination = $model->getPagination();

			if ($pagination->get('pages.total') > 1)
			{
				// Try to guess URL parameters and create a prototype URL
				// NOTE: You are better off specialising this method
				$protoUri = $this->_getPrototypeURIForPagination();

				// The "first" link
				$uri = clone $protoUri;
				$uri->setVar('limitstart', 0);
				$uri = JRoute::_((string) $uri);

				$document->addLink('first', new FOFHalLink($uri));

				// Do we need a "prev" link?

				if ($pagination->get('pages.current') > 1)
				{
					$prevPage = $pagination->get('pages.current') - 1;
					$limitstart = ($prevPage - 1) * $pagination->limit;
					$uri = clone $protoUri;
					$uri->setVar('limitstart', $limitstart);
					$uri = JRoute::_((string) $uri);

					$document->addLink('prev', new FOFHalLink($uri));
				}

				// Do we need a "next" link?

				if ($pagination->get('pages.current') < $pagination->get('pages.total'))
				{
					$nextPage = $pagination->get('pages.current') + 1;
					$limitstart = ($nextPage - 1) * $pagination->limit;
					$uri = clone $protoUri;
					$uri->setVar('limitstart', $limitstart);
					$uri = JRoute::_((string) $uri);

					$document->addLink('next', new FOFHalLink($uri));
				}

				// The "last" link?
				$lastPage = $pagination->get('pages.total');
				$limitstart = ($lastPage - 1) * $pagination->limit;
				$uri = clone $protoUri;
				$uri->setVar('limitstart', $limitstart);
				$uri = JRoute::_((string) $uri);

				$document->addLink('last', new FOFHalLink($uri));
			}
		}

		return $document;
	}

	/**
	 * Convert an absolute URI to a relative one
	 *
	 * @param   string  $uri  The URI to convert
	 *
	 * @return  string  The relative URL
	 */
	protected function _removeURIBase($uri)
	{
		static $root = null, $rootlen = 0;

		if (is_null($root))
		{
			$root = rtrim(FOFPlatform::getInstance()->URIbase(), '/');
			$rootlen = strlen($root);
		}

		if (substr($uri, 0, $rootlen) == $root)
		{
			$uri = substr($uri, $rootlen);
		}

		return ltrim($uri, '/');
	}

	/**
	 * Returns a JUri instance with a prototype URI used as the base for the
	 * other URIs created by the JSON renderer
	 *
	 * @return  JUri  The prototype JUri instance
	 */
	protected function _getPrototypeURIForPagination()
	{
		$protoUri = new JUri('index.php');
		$protoUri->setQuery($this->input->getData());
		$protoUri->delVar('savestate');
		$protoUri->delVar('base_path');

		return $protoUri;
	}
}
fof/view/form.php000064400000006141152177723700007761 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework Form class. It preferrably renders an XML view template
 * instead of a traditional PHP-based view template.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFViewForm extends FOFViewHtml
{
	/** @var FOFForm The form to render */
	protected $form;

	/**
	 * Displays the view
	 *
	 * @param   string  $tpl  The template to use
	 *
	 * @return  boolean|null False if we can't render anything
	 */
	public function display($tpl = null)
	{
		$model = $this->getModel();

		// Get the form
		$this->form = $model->getForm();
		$this->form->setModel($model);
		$this->form->setView($this);

		// Get the task set in the model
		$task = $model->getState('task', 'browse');

		// Call the relevant method
		$method_name = 'on' . ucfirst($task);

		if (method_exists($this, $method_name))
		{
			$result = $this->$method_name($tpl);
		}
		else
		{
			$result = $this->onDisplay();
		}

		// Bail out if we're told not to render anything

		if ($result === false)
		{
			return;
		}

		// Show the view
		// -- Output HTML before the view template
		$this->preRender();

		// -- Try to load a view template; if not exists render the form directly
		$basePath = FOFPlatform::getInstance()->isBackend() ? 'admin:' : 'site:';
		$basePath .= $this->config['option'] . '/';
		$basePath .= $this->config['view'] . '/';
		$path = $basePath . $this->getLayout();

		if ($tpl)
		{
			$path .= '_' . $tpl;
		}

		$viewTemplate = $this->loadAnyTemplate($path);

		// If there was no template file found, display the form
		if ($viewTemplate instanceof Exception)
		{
			$viewTemplate = $this->getRenderedForm();
		}

		// -- Output the view template
		echo $viewTemplate;

		// -- Output HTML after the view template
		$this->postRender();
	}

	/**
	 * Returns the HTML rendering of the FOFForm attached to this view. Very
	 * useful for customising a form page without having to meticulously hand-
	 * code the entire form.
	 *
	 * @return  string  The HTML of the rendered form
	 */
	public function getRenderedForm()
	{
		$html = '';
		$renderer = $this->getRenderer();

		if ($renderer instanceof FOFRenderAbstract)
		{
			// Load CSS and Javascript files defined in the form
			$this->form->loadCSSFiles();
			$this->form->loadJSFiles();

			// Get the form's HTML
			$html = $renderer->renderForm($this->form, $this->getModel(), $this->input);
		}

		return $html;
	}

	/**
	 * The event which runs when we are displaying the Add page
	 *
	 * @param   string  $tpl  The view sub-template to use
	 *
	 * @return  boolean  True to allow display of the view
	 */
	protected function onAdd($tpl = null)
	{
		// Hide the main menu
		JRequest::setVar('hidemainmenu', true);

		// Get the model
		$model = $this->getModel();

		// Assign the item and form to the view
		$this->item = $model->getItem();

		return true;
	}
}
fof/view/csv.php000064400000010770152177723700007614 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework CSV View class. Automatically renders the data in CSV
 * format.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFViewCsv extends FOFViewHtml
{
	/**
	 *  Should I produce a CSV header row.
	 *
	 * @var  boolean
	 */
	protected $csvHeader = true;

	/**
	 * The filename of the downloaded CSV file.
	 *
	 * @var  string
	 */
	protected $csvFilename = null;

	/**
	 * The columns to include in the CSV output. If it's empty it will be ignored.
	 *
	 * @var  array
	 */
	protected $csvFields = array();

	/**
	* Public constructor. Instantiates a FOFViewCsv object.
	*
	* @param   array  $config  The configuration data array
	*/
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		parent::__construct($config);

		if (array_key_exists('csv_header', $config))
		{
			$this->csvHeader = $config['csv_header'];
		}
		else
		{
			$this->csvHeader = $this->input->getBool('csv_header', true);
		}

		if (array_key_exists('csv_filename', $config))
		{
			$this->csvFilename = $config['csv_filename'];
		}
		else
		{
			$this->csvFilename = $this->input->getString('csv_filename', '');
		}

		if (empty($this->csvFilename))
		{
			$view              = $this->input->getCmd('view', 'cpanel');
			$view              = FOFInflector::pluralize($view);
			$this->csvFilename = strtolower($view);
		}

		if (array_key_exists('csv_fields', $config))
		{
			$this->csvFields = $config['csv_fields'];
		}
	}

	/**
	* Executes before rendering a generic page, default to actions necessary for the Browse task.
	*
	* @param   string  $tpl  Subtemplate to use
	*
	* @return  boolean  Return true to allow rendering of the page
	*/
	protected function onDisplay($tpl = null)
	{
		// Load the model
		$model = $this->getModel();

		$items = $model->getItemList();
		$this->items = $items;

        $platform = FOFPlatform::getInstance();
		$document = $platform->getDocument();

		if ($document instanceof JDocument)
		{
			$document->setMimeEncoding('text/csv');
		}

		$platform->setHeader('Pragma', 'public');
        $platform->setHeader('Expires', '0');
        $platform->setHeader('Cache-Control', 'must-revalidate, post-check=0, pre-check=0');
        $platform->setHeader('Cache-Control', 'public', false);
        $platform->setHeader('Content-Description', 'File Transfer');
        $platform->setHeader('Content-Disposition', 'attachment; filename="' . $this->csvFilename . '"');

		if (is_null($tpl))
		{
			$tpl = 'csv';
		}

		FOFPlatform::getInstance()->setErrorHandling(E_ALL, 'ignore');

		$hasFailed = false;

		try
		{
			$result = $this->loadTemplate($tpl, true);

			if ($result instanceof Exception)
			{
				$hasFailed = true;
			}
		}
		catch (Exception $e)
		{
			$hasFailed = true;
		}

		if (!$hasFailed)
		{
			echo $result;
		}
		else
		{
			// Default CSV behaviour in case the template isn't there!

			if (empty($items))
			{
				return;
			}

			$item    = array_pop($items);
			$keys    = get_object_vars($item);
			$keys    = array_keys($keys);
			$items[] = $item;
			reset($items);

			if (!empty($this->csvFields))
			{
				$temp = array();

				foreach ($this->csvFields as $f)
				{
					if (in_array($f, $keys))
					{
						$temp[] = $f;
					}
				}

				$keys = $temp;
			}

			if ($this->csvHeader)
			{
				$csv = array();

				foreach ($keys as $k)
				{
					$k = str_replace('"', '""', $k);
					$k = str_replace("\r", '\\r', $k);
					$k = str_replace("\n", '\\n', $k);
					$k = '"' . $k . '"';

					$csv[] = $k;
				}

				echo implode(",", $csv) . "\r\n";
			}

			foreach ($items as $item)
			{
				$csv  = array();
				$item = (array) $item;

				foreach ($keys as $k)
				{
					if (!isset($item[$k]))
					{
						$v = '';
					}
					else
					{
						$v = $item[$k];
					}

					if (is_array($v))
					{
						$v = 'Array';
					}
					elseif (is_object($v))
					{
						$v = 'Object';
					}

					$v = str_replace('"', '""', $v);
					$v = str_replace("\r", '\\r', $v);
					$v = str_replace("\n", '\\n', $v);
					$v = '"' . $v . '"';

					$csv[] = $v;
				}

				echo implode(",", $csv) . "\r\n";
			}
		}

		return false;
	}
}
fof/view/html.php000064400000010376152177723700007767 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework HTML output class. Together with PHP-based view tempalates
 * it will render your data into an HTML representation.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFViewHtml extends FOFViewRaw
{
	/** @var bool Should I set the page title in the front-end of the site? */
	public $setFrontendPageTitle = false;

	/** @var string The translation key for the default page title */
	public $defaultPageTitle = null;

	/**
	 * Class constructor
	 *
	 * @param   array $config Configuration parameters
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array)$config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		if (isset($config['setFrontendPageTitle']))
		{
			$this->setFrontendPageTitle = (bool)$config['setFrontendPageTitle'];
		}

		if (isset($config['defaultPageTitle']))
		{
			$this->defaultPageTitle = $config['defaultPageTitle'];
		}

		parent::__construct($config);
	}

	/**
	 * Runs before rendering the view template, echoing HTML to put before the
	 * view template's generated HTML
	 *
	 * @return void
	 */
	protected function preRender()
	{
		$view = $this->input->getCmd('view', 'cpanel');
		$task = $this->getModel()->getState('task', 'browse');

		// Don't load the toolbar on CLI

		if (!FOFPlatform::getInstance()->isCli())
		{
			$toolbar = FOFToolbar::getAnInstance($this->input->getCmd('option', 'com_foobar'), $this->config);
			$toolbar->perms = $this->perms;
			$toolbar->renderToolbar($view, $task, $this->input);
		}

		if (FOFPlatform::getInstance()->isFrontend())
		{
			if ($this->setFrontendPageTitle)
			{
				$this->setPageTitle();
			}
		}

		$renderer = $this->getRenderer();
		$renderer->preRender($view, $task, $this->input, $this->config);
	}

	/**
	 * Runs after rendering the view template, echoing HTML to put after the
	 * view template's generated HTML
	 *
	 * @return  void
	 */
	protected function postRender()
	{
		$view = $this->input->getCmd('view', 'cpanel');
		$task = $this->getModel()->getState('task', 'browse');

		$renderer = $this->getRenderer();

		if ($renderer instanceof FOFRenderAbstract)
		{
			$renderer->postRender($view, $task, $this->input, $this->config);
		}
	}

	public function setPageTitle()
	{
		$document = JFactory::getDocument();
		$app = JFactory::getApplication();
		$menus = $app->getMenu();
		$menu = $menus->getActive();
		$title = null;

		// Get the option and view name
		$option = empty($this->option) ? $this->input->getCmd('option', 'com_foobar') : $this->option;
		$view = empty($this->view) ? $this->input->getCmd('view', $this->getName()) : $this->view;

		// Get the default page title translation key
		$default = empty($this->defaultPageTitle) ? $option . '_TITLE_' . $view : $this->defaultPageTitle;

		$params = $app->getPageParameters($option);

		// Set the default value for page_heading
		if ($menu)
		{
			$params->def('page_heading', $params->get('page_title', $menu->title));
		}
		else
		{
			$params->def('page_heading', JText::_($default));
		}

		// Set the document title
		$title = $params->get('page_title', '');
		$sitename = $app->getCfg('sitename');

		if ($title == $sitename)
		{
			$title = JText::_($default);
		}

		if (empty($title))
		{
			$title = $sitename;
		}
		elseif ($app->getCfg('sitename_pagetitles', 0) == 1)
		{
			$title = JText::sprintf('JPAGETITLE', $app->getCfg('sitename'), $title);
		}
		elseif ($app->getCfg('sitename_pagetitles', 0) == 2)
		{
			$title = JText::sprintf('JPAGETITLE', $title, $app->getCfg('sitename'));
		}

		$document->setTitle($title);

		// Set meta
		if ($params->get('menu-meta_description'))
		{
			$document->setDescription($params->get('menu-meta_description'));
		}

		if ($params->get('menu-meta_keywords'))
		{
			$document->setMetadata('keywords', $params->get('menu-meta_keywords'));
		}

		if ($params->get('robots'))
		{
			$document->setMetadata('robots', $params->get('robots'));
		}

		return $title;
	}
}
fof/view/view.php000064400000063475152177723700010005 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework View class. The View is the MVC component which gets the
 * raw data from a Model and renders it in a way that makes sense. The usual
 * rendering is HTML, but you can also output JSON, CSV, XML, or even media
 * (images, videos, ...) and documents (Word, PDF, Excel...).
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
abstract class FOFView extends FOFUtilsObject
{
	/**
	 * The name of the view
	 *
	 * @var    array
	 */
	protected $_name = null;

	/**
	 * Registered models
	 *
	 * @var    array
	 */
	protected $_models = array();

	/**
	 * The base path of the view
	 *
	 * @var    string
	 */
	protected $_basePath = null;

	/**
	 * The default model
	 *
	 * @var	string
	 */
	protected $_defaultModel = null;

	/**
	 * Layout name
	 *
	 * @var    string
	 */
	protected $_layout = 'default';

	/**
	 * Layout extension
	 *
	 * @var    string
	 */
	protected $_layoutExt = 'php';

	/**
	 * Layout template
	 *
	 * @var    string
	 */
	protected $_layoutTemplate = '_';

	/**
	 * The set of search directories for resources (templates)
	 *
	 * @var array
	 */
	protected $_path = array('template' => array(), 'helper' => array());

	/**
	 * The name of the default template source file.
	 *
	 * @var string
	 */
	protected $_template = null;

	/**
	 * The output of the template script.
	 *
	 * @var string
	 */
	protected $_output = null;

	/**
	 * Callback for escaping.
	 *
	 * @var string
	 * @deprecated 13.3
	 */
	protected $_escape = 'htmlspecialchars';

	/**
	 * Charset to use in escaping mechanisms; defaults to urf8 (UTF-8)
	 *
	 * @var string
	 */
	protected $_charset = 'UTF-8';

	/**
	 * The available renderer objects we can use to render views
	 *
	 * @var    array  Contains objects of the FOFRenderAbstract class
	 */
	public static $renderers = array();

	/**
	 * Cache of the configuration array
	 *
	 * @var    array
	 */
	protected $config = array();

	/**
	 * The input object of this view
	 *
	 * @var    FOFInput
	 */
	protected $input = null;

	/**
	 * The chosen renderer object
	 *
	 * @var    FOFRenderAbstract
	 */
	protected $rendererObject = null;

	/**
	 * Should I run the pre-render step?
	 *
	 * @var    boolean
	 */
	protected $doPreRender = true;

	/**
	 * Should I run the post-render step?
	 *
	 * @var    boolean
	 */
	protected $doPostRender = true;

	/**
	 * Public constructor. Instantiates a FOFView object.
	 *
	 * @param   array  $config  The configuration data array
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		parent::__construct($config);

		$component = 'com_foobar';

		// Get the component name
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$tmpInput = $config['input'];
			}
			else
			{
				$tmpInput = new FOFInput($config['input']);
			}

			$component = $tmpInput->getCmd('option', '');
		}
		else
		{
			$tmpInput = $this->input;
		}

		if (array_key_exists('option', $config))
		{
			if ($config['option'])
			{
				$component = $config['option'];
			}
		}

		$config['option'] = $component;

		// Get the view name
		$view = null;
		if (array_key_exists('input', $config))
		{
			$view = $tmpInput->getCmd('view', '');
		}

		if (array_key_exists('view', $config))
		{
			if ($config['view'])
			{
				$view = $config['view'];
			}
		}

		$config['view'] = $view;

		// Set the component and the view to the input array

		if (array_key_exists('input', $config))
		{
			$tmpInput->set('option', $config['option']);
			$tmpInput->set('view', $config['view']);
		}

		// Set the view name

		if (array_key_exists('name', $config))
		{
			$this->_name = $config['name'];
		}
		else
		{
			$this->_name = $config['view'];
		}

		$tmpInput->set('view', $this->_name);
		$config['input'] = $tmpInput;
		$config['name'] = $this->_name;
		$config['view'] = $this->_name;

		// Get the component directories
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

		// Set the charset (used by the variable escaping functions)

		if (array_key_exists('charset', $config))
		{
			FOFPlatform::getInstance()->logDeprecated('Setting a custom charset for escaping in FOFView\'s constructor is deprecated. Override FOFView::escape() instead.');
			$this->_charset = $config['charset'];
		}

		// User-defined escaping callback

		if (array_key_exists('escape', $config))
		{
			$this->setEscape($config['escape']);
		}

		// Set a base path for use by the view

		if (array_key_exists('base_path', $config))
		{
			$this->_basePath = $config['base_path'];
		}
		else
		{
			$this->_basePath = $componentPaths['main'];
		}

		// Set the default template search path

		if (array_key_exists('template_path', $config))
		{
			// User-defined dirs
			$this->_setPath('template', $config['template_path']);
		}
		else
		{
			$altView = FOFInflector::isSingular($this->getName()) ? FOFInflector::pluralize($this->getName()) : FOFInflector::singularize($this->getName());
			$this->_setPath('template', $this->_basePath . '/views/' . $altView . '/tmpl');
			$this->_addPath('template', $this->_basePath . '/views/' . $this->getName() . '/tmpl');
		}

		// Set the default helper search path

		if (array_key_exists('helper_path', $config))
		{
			// User-defined dirs
			$this->_setPath('helper', $config['helper_path']);
		}
		else
		{
			$this->_setPath('helper', $this->_basePath . '/helpers');
		}

		// Set the layout

		if (array_key_exists('layout', $config))
		{
			$this->setLayout($config['layout']);
		}
		else
		{
			$this->setLayout('default');
		}

		$this->config = $config;

		if (!FOFPlatform::getInstance()->isCli())
		{
			$this->baseurl = FOFPlatform::getInstance()->URIbase(true);

			$fallback = FOFPlatform::getInstance()->getTemplateOverridePath($component) . '/' . $this->getName();
			$this->_addPath('template', $fallback);
		}
	}

	/**
	 * Loads a template given any path. The path is in the format:
	 * [admin|site]:com_foobar/viewname/templatename
	 * e.g. admin:com_foobar/myview/default
	 *
	 * This function searches for Joomla! version override templates. For example,
	 * if you have run this under Joomla! 3.0 and you try to load
	 * admin:com_foobar/myview/default it will automatically search for the
	 * template files default.j30.php, default.j3.php and default.php, in this
	 * order.
	 *
	 * @param   string  $path         See above
	 * @param   array   $forceParams  A hash array of variables to be extracted in the local scope of the template file
	 *
	 * @return  boolean  False if loading failed
	 */
	public function loadAnyTemplate($path = '', $forceParams = array())
	{
		// Automatically check for a Joomla! version specific override
		$throwErrorIfNotFound = true;

		$suffixes = FOFPlatform::getInstance()->getTemplateSuffixes();

		foreach ($suffixes as $suffix)
		{
			if (substr($path, -strlen($suffix)) == $suffix)
			{
				$throwErrorIfNotFound = false;
				break;
			}
		}

		if ($throwErrorIfNotFound)
		{
			foreach ($suffixes as $suffix)
			{
				$result = $this->loadAnyTemplate($path . $suffix, $forceParams);

				if ($result !== false)
				{
					return $result;
				}
			}
		}

		$layoutTemplate = $this->getLayoutTemplate();

		// Parse the path
		$templateParts = $this->_parseTemplatePath($path);

		// Get the paths
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($templateParts['component']);
		$templatePath   = FOFPlatform::getInstance()->getTemplateOverridePath($templateParts['component']);

		// Get the default paths
		$paths = array();
		$paths[] = $templatePath . '/' . $templateParts['view'];
		$paths[] = ($templateParts['admin'] ? $componentPaths['admin'] : $componentPaths['site']) . '/views/' . $templateParts['view'] . '/tmpl';

		if (isset($this->_path) || property_exists($this, '_path'))
		{
			$paths = array_merge($paths, $this->_path['template']);
		}
		elseif (isset($this->path) || property_exists($this, 'path'))
		{
			$paths = array_merge($paths, $this->path['template']);
		}

		// Look for a template override

		if (isset($layoutTemplate) && $layoutTemplate != '_' && $layoutTemplate != $template)
		{
			$apath = array_shift($paths);
			array_unshift($paths, str_replace($template, $layoutTemplate, $apath));
		}

		$filetofind = $templateParts['template'] . '.php';
        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		$this->_tempFilePath = $filesystem->pathFind($paths, $filetofind);

		if ($this->_tempFilePath)
		{
			// Unset from local scope
			unset($template);
			unset($layoutTemplate);
			unset($paths);
			unset($path);
			unset($filetofind);

			// Never allow a 'this' property

			if (isset($this->this))
			{
				unset($this->this);
			}

			// Force parameters into scope

			if (!empty($forceParams))
			{
				extract($forceParams);
			}

			// Start capturing output into a buffer
			ob_start();

			// Include the requested template filename in the local scope (this will execute the view logic).
			include $this->_tempFilePath;

			// Done with the requested template; get the buffer and clear it.
			$this->_output = ob_get_contents();
			ob_end_clean();

			return $this->_output;
		}
		else
		{
			if ($throwErrorIfNotFound)
			{
				return new Exception(JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $path), 500);
			}

			return false;
		}
	}

	/**
	 * Overrides the default method to execute and display a template script.
	 * Instead of loadTemplate is uses loadAnyTemplate which allows for automatic
	 * Joomla! version overrides. A little slice of awesome pie!
	 *
	 * @param   string  $tpl  The name of the template file to parse
	 *
	 * @return  mixed  A string if successful, otherwise a JError object.
	 */
	public function display($tpl = null)
	{
		FOFPlatform::getInstance()->setErrorHandling(E_ALL, 'ignore');

		$result = $this->loadTemplate($tpl);

		if ($result instanceof Exception)
		{
            FOFPlatform::getInstance()->raiseError($result->getCode(), $result->getMessage());

			return $result;
		}

		echo $result;
	}

	/**
	 * Assigns variables to the view script via differing strategies.
	 *
	 * This method is overloaded; you can assign all the properties of
	 * an object, an associative array, or a single value by name.
	 *
	 * You are not allowed to set variables that begin with an underscore;
	 * these are either private properties for FOFView or private variables
	 * within the template script itself.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @deprecated  13.3 Use native PHP syntax.
	 */
	public function assign()
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' . __METHOD__ . ' is deprecated. Use native PHP syntax.');

		// Get the arguments; there may be 1 or 2.
		$arg0 = @func_get_arg(0);
		$arg1 = @func_get_arg(1);

		// Assign by object

		if (is_object($arg0))
		{
			// Assign public properties
			foreach (get_object_vars($arg0) as $key => $val)
			{
				if (substr($key, 0, 1) != '_')
				{
					$this->$key = $val;
				}
			}

			return true;
		}

		// Assign by associative array

		if (is_array($arg0))
		{
			foreach ($arg0 as $key => $val)
			{
				if (substr($key, 0, 1) != '_')
				{
					$this->$key = $val;
				}
			}

			return true;
		}

		// Assign by string name and mixed value. We use array_key_exists() instead of isset()
		// because isset() fails if the value is set to null.

		if (is_string($arg0) && substr($arg0, 0, 1) != '_' && func_num_args() > 1)
		{
			$this->$arg0 = $arg1;

			return true;
		}

		// $arg0 was not object, array, or string.
		return false;
	}

	/**
	 * Assign variable for the view (by reference).
	 *
	 * You are not allowed to set variables that begin with an underscore;
	 * these are either private properties for FOFView or private variables
	 * within the template script itself.
	 *
	 * @param   string  $key   The name for the reference in the view.
	 * @param   mixed   &$val  The referenced variable.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @deprecated  13.3  Use native PHP syntax.
	 */
	public function assignRef($key, &$val)
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' . __METHOD__ . ' is deprecated. Use native PHP syntax.');

		if (is_string($key) && substr($key, 0, 1) != '_')
		{
			$this->$key = &$val;

			return true;
		}

		return false;
	}

	/**
	 * Escapes a value for output in a view script.
	 *
	 * If escaping mechanism is either htmlspecialchars or htmlentities, uses
	 * {@link $_encoding} setting.
	 *
	 * @param   mixed  $var  The output to escape.
	 *
	 * @return  mixed  The escaped value.
	 */
	public function escape($var)
	{
		if (in_array($this->_escape, array('htmlspecialchars', 'htmlentities')))
		{
			return call_user_func($this->_escape, $var, ENT_COMPAT, $this->_charset);
		}

		return call_user_func($this->_escape, $var);
	}

	/**
	 * Method to get data from a registered model or a property of the view
	 *
	 * @param   string  $property  The name of the method to call on the model or the property to get
	 * @param   string  $default   The name of the model to reference or the default value [optional]
	 *
	 * @return  mixed  The return value of the method
	 */
	public function get($property, $default = null)
	{
		// If $model is null we use the default model
		if (is_null($default))
		{
			$model = $this->_defaultModel;
		}
		else
		{
			$model = strtolower($default);
		}

		// First check to make sure the model requested exists
		if (isset($this->_models[$model]))
		{
			// Model exists, let's build the method name
			$method = 'get' . ucfirst($property);

			// Does the method exist?
			if (method_exists($this->_models[$model], $method))
			{
				// The method exists, let's call it and return what we get
				$result = $this->_models[$model]->$method();

				return $result;
			}
		}

		// Degrade to FOFUtilsObject::get
		$result = parent::get($property, $default);

		return $result;
	}

	/**
	 * Method to get the model object
	 *
	 * @param   string  $name  The name of the model (optional)
	 *
	 * @return  mixed  FOFModel object
	 */
	public function getModel($name = null)
	{
		if ($name === null)
		{
			$name = $this->_defaultModel;
		}

		return $this->_models[strtolower($name)];
	}

	/**
	 * Get the layout.
	 *
	 * @return  string  The layout name
	 */
	public function getLayout()
	{
		return $this->_layout;
	}

	/**
	 * Get the layout template.
	 *
	 * @return  string  The layout template name
	 */
	public function getLayoutTemplate()
	{
		return $this->_layoutTemplate;
	}

	/**
	 * Method to get the view name
	 *
	 * The model name by default parsed using the classname, or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @return  string  The name of the model
	 */
	public function getName()
	{
		if (empty($this->_name))
		{
			$classname = get_class($this);
			$viewpos = strpos($classname, 'View');

			if ($viewpos === false)
			{
				throw new Exception(JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'), 500);
			}

			$this->_name = strtolower(substr($classname, $viewpos + 4));
		}

		return $this->_name;
	}

	/**
	 * Method to add a model to the view.
	 *
	 * @param   FOFMOdel  $model    The model to add to the view.
     * @param   boolean   $default  Is this the default model?
     * @param   String    $name     optional index name to store the model
	 *
	 * @return  object   The added model.
	 */
	public function setModel($model, $default = false, $name = null)
	{
		if (is_null($name))
		{
			$name = $model->getName();
		}

		$name = strtolower($name);

		$this->_models[$name] = $model;

		if ($default)
		{
			$this->_defaultModel = $name;
		}

		return $model;
	}

	/**
	 * Sets the layout name to use
	 *
	 * @param   string  $layout  The layout name or a string in format <template>:<layout file>
	 *
	 * @return  string  Previous value.
	 */
	public function setLayout($layout)
	{
		$previous = $this->_layout;

		if (strpos($layout, ':') === false)
		{
			$this->_layout = $layout;
		}
		else
		{
			// Convert parameter to array based on :
			$temp = explode(':', $layout);
			$this->_layout = $temp[1];

			// Set layout template
			$this->_layoutTemplate = $temp[0];
		}

		return $previous;
	}

	/**
	 * Allows a different extension for the layout files to be used
	 *
	 * @param   string  $value  The extension.
	 *
	 * @return  string   Previous value
	 */
	public function setLayoutExt($value)
	{
		$previous = $this->_layoutExt;

		if ($value = preg_replace('#[^A-Za-z0-9]#', '', trim($value)))
		{
			$this->_layoutExt = $value;
		}

		return $previous;
	}

	/**
	 * Sets the _escape() callback.
	 *
	 * @param   mixed  $spec  The callback for _escape() to use.
	 *
	 * @return  void
	 *
	 * @deprecated  2.1  Override FOFView::escape() instead.
	 */
	public function setEscape($spec)
	{
		FOFPlatform::getInstance()->logDeprecated(__CLASS__ . '::' . __METHOD__ . ' is deprecated. Override FOFView::escape() instead.');

		$this->_escape = $spec;
	}

	/**
	 * Adds to the stack of view script paths in LIFO order.
	 *
	 * @param   mixed  $path  A directory path or an array of paths.
	 *
	 * @return  void
	 */
	public function addTemplatePath($path)
	{
		$this->_addPath('template', $path);
	}

	/**
	 * Adds to the stack of helper script paths in LIFO order.
	 *
	 * @param   mixed  $path  A directory path or an array of paths.
	 *
	 * @return  void
	 */
	public function addHelperPath($path)
	{
		$this->_addPath('helper', $path);
	}

	/**
	 * Overrides the built-in loadTemplate function with an FOF-specific one.
	 * Our overriden function uses loadAnyTemplate to provide smarter view
	 * template loading.
	 *
	 * @param   string   $tpl     The name of the template file to parse
	 * @param   boolean  $strict  Should we use strict naming, i.e. force a non-empty $tpl?
	 *
	 * @return  mixed  A string if successful, otherwise a JError object
	 */
	public function loadTemplate($tpl = null, $strict = false)
	{
		$paths = FOFPlatform::getInstance()->getViewTemplatePaths(
			$this->input->getCmd('option', ''),
			$this->input->getCmd('view', ''),
			$this->getLayout(),
			$tpl,
			$strict
		);

		foreach ($paths as $path)
		{
			$result = $this->loadAnyTemplate($path);

			if (!($result instanceof Exception))
			{
				break;
			}
		}

		if ($result instanceof Exception)
		{
            FOFPlatform::getInstance()->raiseError($result->getCode(), $result->getMessage());
		}

		return $result;
	}

	/**
	 * Parses a template path in the form of admin:/component/view/layout or
	 * site:/component/view/layout to an array which can be used by
	 * loadAnyTemplate to locate and load the view template file.
	 *
	 * @param   string  $path  The template path to parse
	 *
	 * @return  array  A hash array with the parsed path parts
	 */
	private function _parseTemplatePath($path = '')
	{
		$parts = array(
			'admin'		 => 0,
			'component'	 => $this->config['option'],
			'view'		 => $this->config['view'],
			'template'	 => 'default'
		);

		if (substr($path, 0, 6) == 'admin:')
		{
			$parts['admin'] = 1;
			$path = substr($path, 6);
		}
		elseif (substr($path, 0, 5) == 'site:')
		{
			$path = substr($path, 5);
		}

		if (empty($path))
		{
			return;
		}

		$pathparts = explode('/', $path, 3);

		switch (count($pathparts))
		{
			case 3:
				$parts['component'] = array_shift($pathparts);

			case 2:
				$parts['view'] = array_shift($pathparts);

			case 1:
				$parts['template'] = array_shift($pathparts);
				break;
		}

		return $parts;
	}

	/**
	 * Get the renderer object for this view
	 *
	 * @return  FOFRenderAbstract
	 */
	public function &getRenderer()
	{
		if (!($this->rendererObject instanceof FOFRenderAbstract))
		{
			$this->rendererObject = $this->findRenderer();
		}

		return $this->rendererObject;
	}

	/**
	 * Sets the renderer object for this view
	 *
	 * @param   FOFRenderAbstract  &$renderer  The render class to use
	 *
	 * @return  void
	 */
	public function setRenderer(FOFRenderAbstract &$renderer)
	{
		$this->rendererObject = $renderer;
	}

	/**
	 * Finds a suitable renderer
	 *
	 * @return  FOFRenderAbstract
	 */
	protected function findRenderer()
	{
        $filesystem     = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		// Try loading the stock renderers shipped with FOF

		if (empty(self::$renderers) || !class_exists('FOFRenderJoomla', false))
		{
			$path = __DIR__ . '/../render/';
			$renderFiles = $filesystem->folderFiles($path, '.php');

			if (!empty($renderFiles))
			{
				foreach ($renderFiles as $filename)
				{
					if ($filename == 'abstract.php')
					{
						continue;
					}

					@include_once $path . '/' . $filename;

					$camel = FOFInflector::camelize($filename);
					$className = 'FOFRender' . ucfirst(FOFInflector::getPart($camel, 0));
					$o = new $className;

					self::registerRenderer($o);
				}
			}
		}

		// Try to detect the most suitable renderer
		$o = null;
		$priority = 0;

		if (!empty(self::$renderers))
		{
			foreach (self::$renderers as $r)
			{
				$info = $r->getInformation();

				if (!$info->enabled)
				{
					continue;
				}

				if ($info->priority > $priority)
				{
					$priority = $info->priority;
					$o = $r;
				}
			}
		}

		// Return the current renderer
		return $o;
	}

	/**
	 * Registers a renderer object with the view
	 *
	 * @param   FOFRenderAbstract  &$renderer  The render object to register
	 *
	 * @return  void
	 */
	public static function registerRenderer(FOFRenderAbstract &$renderer)
	{
		self::$renderers[] = $renderer;
	}

	/**
	 * Sets the pre-render flag
	 *
	 * @param   boolean  $value  True to enable the pre-render step
	 *
	 * @return  void
	 */
	public function setPreRender($value)
	{
		$this->doPreRender = $value;
	}

	/**
	 * Sets the post-render flag
	 *
	 * @param   boolean  $value  True to enable the post-render step
	 *
	 * @return  void
	 */
	public function setPostRender($value)
	{
		$this->doPostRender = $value;
	}

	/**
	 * Load a helper file
	 *
	 * @param   string  $hlp  The name of the helper source file automatically searches the helper paths and compiles as needed.
	 *
	 * @return  void
	 */
	public function loadHelper($hlp = null)
	{
		// Clean the file name
		$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $hlp);

		// Load the template script using the default Joomla! features
        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		$helper = $filesystem->pathFind($this->_path['helper'], $this->_createFileName('helper', array('name' => $file)));

		if ($helper == false)
		{
			$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($this->config['option']);
			$path = $componentPaths['main'] . '/helpers';
			$helper = $filesystem->pathFind($path, $this->_createFileName('helper', array('name' => $file)));

			if ($helper == false)
			{
				$path = $path = $componentPaths['alt'] . '/helpers';
				$helper = $filesystem->pathFind($path, $this->_createFileName('helper', array('name' => $file)));
			}
		}

		if ($helper != false)
		{
			// Include the requested template filename in the local scope
			include_once $helper;
		}
	}

	/**
	 * Returns the view's option (component name) and view name in an
	 * associative array.
	 *
	 * @return  array
	 */
	public function getViewOptionAndName()
	{
		return array(
			'option' => $this->config['option'],
			'view'	 => $this->config['view'],
		);
	}

	/**
	 * Sets an entire array of search paths for templates or resources.
	 *
	 * @param   string  $type  The type of path to set, typically 'template'.
	 * @param   mixed   $path  The new search path, or an array of search paths.  If null or false, resets to the current directory only.
	 *
	 * @return  void
	 */
	protected function _setPath($type, $path)
	{
		// Clear out the prior search dirs
		$this->_path[$type] = array();

		// Actually add the user-specified directories
		$this->_addPath($type, $path);

		// Always add the fallback directories as last resort
		switch (strtolower($type))
		{
			case 'template':
				// Set the alternative template search dir

				if (!FOFPlatform::getInstance()->isCli())
				{
					$fallback = FOFPlatform::getInstance()->getTemplateOverridePath($this->input->getCmd('option', '')) . '/' . $this->getName();
					$this->_addPath('template', $fallback);
				}

				break;
		}
	}

	/**
	 * Adds to the search path for templates and resources.
	 *
	 * @param   string  $type  The type of path to add.
	 * @param   mixed   $path  The directory or stream, or an array of either, to search.
	 *
	 * @return  void
	 */
	protected function _addPath($type, $path)
	{
		// Just force to array
		settype($path, 'array');

		// Loop through the path directories
		foreach ($path as $dir)
		{
			// No surrounding spaces allowed!
			$dir = trim($dir);

			// Add trailing separators as needed
			if (substr($dir, -1) != DIRECTORY_SEPARATOR)
			{
				// Directory
				$dir .= DIRECTORY_SEPARATOR;
			}

			// Add to the top of the search dirs
			array_unshift($this->_path[$type], $dir);
		}
	}

	/**
	 * Create the filename for a resource
	 *
	 * @param   string  $type   The resource type to create the filename for
	 * @param   array   $parts  An associative array of filename information
	 *
	 * @return  string  The filename
	 */
	protected function _createFileName($type, $parts = array())
	{
		$filename = '';

		switch ($type)
		{
			case 'template':
				$filename = strtolower($parts['name']) . '.' . $this->_layoutExt;
				break;

			default:
				$filename = strtolower($parts['name']) . '.php';
				break;
		}

		return $filename;
	}
}
fof/view/raw.php000064400000020664152177723700007615 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  view
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework raw output class. It works like an HTML view, but the
 * output is bare HTML.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFViewRaw extends FOFView
{
	/** @var array Data lists */
	protected $lists = null;

	/** @var array Permissions map */
	protected $perms = null;

	/**
	 * Class constructor
	 *
	 * @param   array  $config  Configuration parameters
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		parent::__construct($config);

		$this->config = $config;

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		if (!array_key_exists('option', $this->config))
		{
			$this->config['option'] = $this->input->getCmd('option', 'com_foobar');
		}

		if (!array_key_exists('view', $this->config))
		{
			$this->config['view'] = $this->input->getCmd('view', 'cpanel');
		}

		$this->lists = new FOFUtilsObject;

		if (!FOFPlatform::getInstance()->isCli())
		{
			$platform = FOFPlatform::getInstance();
			$perms = (object) array(
					'create'	 => $platform->authorise('core.create'     , $this->input->getCmd('option', 'com_foobar')),
					'edit'		 => $platform->authorise('core.edit'       , $this->input->getCmd('option', 'com_foobar')),
					'editown'	 => $platform->authorise('core.edit.own'   , $this->input->getCmd('option', 'com_foobar')),
					'editstate'	 => $platform->authorise('core.edit.state' , $this->input->getCmd('option', 'com_foobar')),
					'delete'	 => $platform->authorise('core.delete'     , $this->input->getCmd('option', 'com_foobar')),
			);

			$this->aclperms = $perms;
			$this->perms    = $perms;
		}
	}

	/**
	 * Displays the view
	 *
	 * @param   string  $tpl  The template to use
	 *
	 * @return  boolean|null False if we can't render anything
	 */
	public function display($tpl = null)
	{
		// Get the task set in the model
		$model = $this->getModel();
		$task = $model->getState('task', 'browse');

		// Call the relevant method
		$method_name = 'on' . ucfirst($task);

		if (method_exists($this, $method_name))
		{
			$result = $this->$method_name($tpl);
		}
		else
		{
			$result = $this->onDisplay();
		}

		if ($result === false)
		{
			return;
		}

		// Show the view
		if ($this->doPreRender)
		{
			$this->preRender();
		}

		parent::display($tpl);

		if ($this->doPostRender)
		{
			$this->postRender();
		}
	}

	/**
	 * Last chance to output something before rendering the view template
	 *
	 * @return  void
	 */
	protected function preRender()
	{
	}

	/**
	 * Last chance to output something after rendering the view template and
	 * before returning to the caller
	 *
	 * @return  void
	 */
	protected function postRender()
	{
	}

	/**
	 * Executes before rendering the page for the Browse task.
	 *
	 * @param   string  $tpl  Subtemplate to use
	 *
	 * @return  boolean  Return true to allow rendering of the page
	 */
	protected function onBrowse($tpl = null)
	{
		// When in interactive browsing mode, save the state to the session
		$this->getModel()->savestate(1);

		return $this->onDisplay($tpl);
	}

	/**
	 * Executes before rendering a generic page, default to actions necessary
	 * for the Browse task.
	 *
	 * @param   string  $tpl  Subtemplate to use
	 *
	 * @return  boolean  Return true to allow rendering of the page
	 */
	protected function onDisplay($tpl = null)
	{
		$view = $this->input->getCmd('view', 'cpanel');

		if (in_array($view, array('cpanel', 'cpanels')))
		{
			return;
		}

		// Load the model
		$model = $this->getModel();

		// ...ordering
		$this->lists->set('order', $model->getState('filter_order', 'id', 'cmd'));
		$this->lists->set('order_Dir', $model->getState('filter_order_Dir', 'DESC', 'cmd'));

		// Assign data to the view
		$this->items      = $model->getItemList();
		$this->pagination = $model->getPagination();

		// Pass page params on frontend only
		if (FOFPlatform::getInstance()->isFrontend())
		{
			$params = JFactory::getApplication()->getParams();
			$this->params = $params;
		}

		return true;
	}

	/**
	 * Executes before rendering the page for the Add task.
	 *
	 * @param   string  $tpl  Subtemplate to use
	 *
	 * @return  boolean  Return true to allow rendering of the page
	 */
	protected function onAdd($tpl = null)
	{
		JRequest::setVar('hidemainmenu', true);
		$model = $this->getModel();
		$this->item = $model->getItem();

		return true;
	}

	/**
	 * Executes before rendering the page for the Edit task.
	 *
	 * @param   string  $tpl  Subtemplate to use
	 *
	 * @return  boolean  Return true to allow rendering of the page
	 */
	protected function onEdit($tpl = null)
	{
        // This perms are used only for hestetic reasons (ie showing toolbar buttons), "real" checks
        // are made by the controller
        // It seems that I can't edit records, maybe I can edit only this one due asset tracking?
		if (!$this->perms->edit || !$this->perms->editown)
        {
            $model = $this->getModel();

            if($model)
            {
                $table = $model->getTable();

                // Ok, record is tracked, let's see if I can this record
                if($table->isAssetsTracked())
                {
                    $platform = FOFPlatform::getInstance();

                    if(!$this->perms->edit)
                    {
                        $this->perms->edit = $platform->authorise('core.edit', $table->getAssetName());
                    }

                    if(!$this->perms->editown)
                    {
                        $this->perms->editown = $platform->authorise('core.edit.own', $table->getAssetName());
                    }
                }
            }
        }

		return $this->onAdd($tpl);
	}

	/**
	 * Executes before rendering the page for the Read task.
	 *
	 * @param   string  $tpl  Subtemplate to use
	 *
	 * @return  boolean  Return true to allow rendering of the page
	 */
	protected function onRead($tpl = null)
	{
		// All I need is to read the record

		return $this->onAdd($tpl);
	}

	/**
	 * Determines if the current Joomla! version and your current table support
	 * AJAX-powered drag and drop reordering. If they do, it will set up the
	 * drag & drop reordering feature.
	 *
	 * @return  boolean|array  False if not suported, a table with necessary
	 *                         information (saveOrder: should you enabled DnD
	 *                         reordering; orderingColumn: which column has the
	 *                         ordering information).
	 */
	public function hasAjaxOrderingSupport()
	{
		if (version_compare(JVERSION, '3.0', 'lt'))
		{
			return false;
		}

		$model = $this->getModel();

		if (!method_exists($model, 'getTable'))
		{
			return false;
		}

		$table = $this->getModel()->getTable();

		if (!method_exists($table, 'getColumnAlias') || !method_exists($table, 'getTableFields'))
		{
			return false;
		}

		$orderingColumn = $table->getColumnAlias('ordering');
		$fields = $table->getTableFields();

		if (!is_array($fields) || !array_key_exists($orderingColumn, $fields))
		{
			return false;
		}

		$listOrder = $this->escape($model->getState('filter_order', null, 'cmd'));
		$listDirn = $this->escape($model->getState('filter_order_Dir', 'ASC', 'cmd'));
		$saveOrder = $listOrder == $orderingColumn;

		if ($saveOrder)
		{
			$saveOrderingUrl = 'index.php?option=' . $this->config['option'] . '&view=' . $this->config['view'] . '&task=saveorder&format=json';
			JHtml::_('sortablelist.sortable', 'itemsList', 'adminForm', strtolower($listDirn), $saveOrderingUrl);
		}

		return array(
			'saveOrder'		 => $saveOrder,
			'orderingColumn' => $orderingColumn
		);
	}

	/**
	 * Returns the internal list of useful variables to the benefit of
	 * FOFFormHeader fields.
	 *
	 * @return array
	 *
	 * @since 2.0
	 */
	public function getLists()
	{
		return $this->lists;
	}

	/**
	 * Returns a reference to the permissions object of this view
	 *
	 * @return stdClass
	 */
	public function getPerms()
	{
		return $this->perms;
	}
}
fof/utils/config/helper.php000064400000005143152177723700011731 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * A utility class to help you fetch component parameters without going through JComponentHelper
 */
class FOFUtilsConfigHelper
{
	/**
	 * Caches the component parameters without going through JComponentHelper. This is necessary since JComponentHelper
	 * cannot be reset or updated once you update parameters in the database.
	 *
	 * @var array
	 */
	private static $componentParams = array();

	/**
	 * Loads the component's configuration parameters so they can be accessed by getComponentConfigurationValue
	 *
	 * @param   string  $component  The component for loading the parameters
	 * @param   bool    $force      Should I force-reload the configuration information?
	 */
	public final static function loadComponentConfig($component, $force = false)
	{
		if (isset(self::$componentParams[$component]) && !is_null(self::$componentParams[$component]) && !$force)
		{
			return;
		}

		$db = FOFPlatform::getInstance()->getDbo();

		$sql = $db->getQuery(true)
				  ->select($db->qn('params'))
				  ->from($db->qn('#__extensions'))
				  ->where($db->qn('type') . ' = ' . $db->q('component'))
				  ->where($db->qn('element') . " = " . $db->q($component));
		$db->setQuery($sql);
		$config_ini = $db->loadResult();

		// OK, Joomla! 1.6 stores values JSON-encoded so, what do I do? Right!
		$config_ini = trim($config_ini);

		if ((substr($config_ini, 0, 1) == '{') && substr($config_ini, -1) == '}')
		{
			$config_ini = json_decode($config_ini, true);
		}
		else
		{
			$config_ini = FOFUtilsIniParser::parse_ini_file($config_ini, false, true);
		}

		if (is_null($config_ini) || empty($config_ini))
		{
			$config_ini = array();
		}

		self::$componentParams[$component] = $config_ini;
	}

	/**
	 * Retrieves the value of a component configuration parameter without going through JComponentHelper
	 *
	 * @param   string  $component  The component for loading the parameter value
	 * @param   string  $key        The key to retrieve
	 * @param   mixed   $default    The default value to use in case the key is missing
	 *
	 * @return  mixed
	 */
	public final static function getComponentConfigurationValue($component, $key, $default = null)
	{
		self::loadComponentConfig($component, false);

		if (array_key_exists($key, self::$componentParams[$component]))
		{
			return self::$componentParams[$component][$key];
		}
		else
		{
			return $default;
		}
	}
} fof/utils/cache/cleaner.php000064400000004353152177723700011663 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * A utility class to help you quickly clean the Joomla! cache
 */
class FOFUtilsCacheCleaner
{
	/**
	 * Clears the com_modules and com_plugins cache. You need to call this whenever you alter the publish state or
	 * parameters of a module or plugin from your code.
	 *
	 * @return  void
	 */
	public static function clearPluginsAndModulesCache()
	{
		self::clearPluginsCache();
		self::clearModulesCache();
	}

	/**
	 * Clears the com_plugins cache. You need to call this whenever you alter the publish state or parameters of a
	 * plugin from your code.
	 *
	 * @return  void
	 */
	public static function clearPluginsCache()
	{
		self::clearCacheGroups(array('com_plugins'), array(0,1));
	}

	/**
	 * Clears the com_modules cache. You need to call this whenever you alter the publish state or parameters of a
	 * module from your code.
	 *
	 * @return  void
	 */
	public static function clearModulesCache()
	{
		self::clearCacheGroups(array('com_modules'), array(0,1));
	}

	/**
	 * Clears the specified cache groups.
	 *
	 * @param   array $clearGroups    Which cache groups to clear. Usually this is com_yourcomponent to clear your
	 *                                component's cache.
	 * @param   array   $cacheClients Which cache clients to clear. 0 is the back-end, 1 is the front-end. If you do not
	 *                                specify anything, both cache clients will be cleared.
	 *
	 * @return  void
	 */
	public static function clearCacheGroups(array $clearGroups, array $cacheClients = array(0, 1))
	{
		$conf = JFactory::getConfig();

		foreach ($clearGroups as $group)
		{
			foreach ($cacheClients as $client_id)
			{
				try
				{
					$options = array(
						'defaultgroup' => $group,
						'cachebase' => ($client_id) ? JPATH_ADMINISTRATOR . '/cache' : $conf->get('cache_path', JPATH_SITE . '/cache')
					);

					$cache = JCache::getInstance('callback', $options);
					$cache->clean();
				}
				catch (Exception $e)
				{
					// suck it up
				}
			}
		}
	}
} fof/utils/ini/parser.php000064400000010666152177723700011266 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * A utility class to parse INI files. This monstrosity is only required because some impossibly misguided individuals
 * who misrepresent themselves as hosts have disabled PHP's parse_ini_file() function for "security reasons". Apparently
 * their blatant ignorance doesn't allow them to discern between the innocuous parse_ini_file and the _potentially_
 * dangerous ini_set functions, leading them to disable the former and let the latter enabled. In other words, THIS
 * CLASS IS HERE TO FIX STUPID.
 */
class FOFUtilsIniParser
{
	/**
	 * Parse an INI file and return an associative array.
	 *
	 * @param    string  $file              The file to process
	 * @param    bool    $process_sections  True to also process INI sections
	 *
	 * @return   array    An associative array of sections, keys and values
	 */
	public static function parse_ini_file($file, $process_sections, $rawdata = false)
	{
		$isMoronHostFile = !function_exists('parse_ini_file');
		$isMoronHostString = !function_exists('parse_ini_string');

		if ($rawdata)
		{
			if ($isMoronHostString)
			{
				return self::parse_ini_file_php($file, $process_sections, $rawdata);
			}
			else
			{
				return parse_ini_string($file, $process_sections);
			}
		}
		else
		{
			if ($isMoronHostFile)
			{
				return self::parse_ini_file_php($file, $process_sections);
			}
			else
			{
				return parse_ini_file($file, $process_sections);
			}
		}
	}

	/**
	 * A PHP based INI file parser.
	 *
	 * Thanks to asohn ~at~ aircanopy ~dot~ net for posting this handy function on
	 * the parse_ini_file page on http://gr.php.net/parse_ini_file
	 *
	 * @param    string $file             Filename to process
	 * @param    bool   $process_sections True to also process INI sections
	 * @param    bool   $rawdata          If true, the $file contains raw INI data, not a filename
	 *
	 * @return    array    An associative array of sections, keys and values
	 */
	static function parse_ini_file_php($file, $process_sections = false, $rawdata = false)
	{
		$process_sections = ($process_sections !== true) ? false : true;

		if (!$rawdata)
		{
			$ini = file($file);
		}
		else
		{
			$file = str_replace("\r", "", $file);
			$ini = explode("\n", $file);
		}

		if (count($ini) == 0)
		{
			return array();
		}

		$sections = array();
		$values = array();
		$result = array();
		$globals = array();
		$i = 0;
		foreach ($ini as $line)
		{
			$line = trim($line);
			$line = str_replace("\t", " ", $line);

			// Comments
			if (!preg_match('/^[a-zA-Z0-9[]/', $line))
			{
				continue;
			}

			// Sections
			if ($line{0} == '[')
			{
				$tmp = explode(']', $line);
				$sections[] = trim(substr($tmp[0], 1));
				$i++;
				continue;
			}

			// Key-value pair
			$lineParts = explode('=', $line, 2);
			if (count($lineParts) != 2)
			{
				continue;
			}
			$key = trim($lineParts[0]);
			$value = trim($lineParts[1]);
			unset($lineParts);

			if (strstr($value, ";"))
			{
				$tmp = explode(';', $value);
				if (count($tmp) == 2)
				{
					if ((($value{0} != '"') && ($value{0} != "'")) ||
							preg_match('/^".*"\s*;/', $value) || preg_match('/^".*;[^"]*$/', $value) ||
							preg_match("/^'.*'\s*;/", $value) || preg_match("/^'.*;[^']*$/", $value)
					)
					{
						$value = $tmp[0];
					}
				}
				else
				{
					if ($value{0} == '"')
					{
						$value = preg_replace('/^"(.*)".*/', '$1', $value);
					}
					elseif ($value{0} == "'")
					{
						$value = preg_replace("/^'(.*)'.*/", '$1', $value);
					}
					else
					{
						$value = $tmp[0];
					}
				}
			}
			$value = trim($value);
			$value = trim($value, "'\"");

			if ($i == 0)
			{
				if (substr($line, -1, 2) == '[]')
				{
					$globals[$key][] = $value;
				}
				else
				{
					$globals[$key] = $value;
				}
			}
			else
			{
				if (substr($line, -1, 2) == '[]')
				{
					$values[$i - 1][$key][] = $value;
				}
				else
				{
					$values[$i - 1][$key] = $value;
				}
			}
		}

		for ($j = 0; $j < $i; $j++)
		{
			if ($process_sections === true)
			{
				if (isset($sections[$j]) && isset($values[$j]))
				{
					$result[$sections[$j]] = $values[$j];
				}
			}
			else
			{
				if (isset($values[$j]))
				{
					$result[] = $values[$j];
				}
			}
		}

		return $result + $globals;
	}
} fof/utils/timer/timer.php000064400000003775152177723700011456 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * An execution timer monitor class
 */
class FOFUtilsTimer
{
	/** @var float Maximum execution time allowance */
	private $max_exec_time = null;

	/** @var float Timestamp of execution start */
	private $start_time = null;

	/**
	 * Public constructor, creates the timer object and calculates the execution
	 * time limits.
	 *
	 * @param float $max_exec_time Maximum execution time allowance
	 * @param float $runtime_bias  Execution time bias (expressed as % of $max_exec_time)
	 */
	public function __construct($max_exec_time = 5.0, $runtime_bias = 75.0)
	{
		// Initialize start time
		$this->start_time = $this->microtime_float();

		$this->max_exec_time = $max_exec_time * $runtime_bias / 100.0;
	}

	/**
	 * Wake-up function to reset internal timer when we get unserialized
	 */
	public function __wakeup()
	{
		// Re-initialize start time on wake-up
		$this->start_time = $this->microtime_float();
	}

	/**
	 * Gets the number of seconds left, before we hit the "must break" threshold. Negative
	 * values mean that we have already crossed that threshold.
	 *
	 * @return  float
	 */
	public function getTimeLeft()
	{
		return $this->max_exec_time - $this->getRunningTime();
	}

	/**
	 * Gets the time elapsed since object creation/unserialization, effectively
	 * how long we are running
	 *
	 * @return  float
	 */
	public function getRunningTime()
	{
		return $this->microtime_float() - $this->start_time;
	}

	/**
	 * Returns the current timestamp in decimal seconds
	 *
	 * @return  float
	 */
	private function microtime_float()
	{
		list($usec, $sec) = explode(" ", microtime());
		return ((float)$usec + (float)$sec);
	}

	/**
	 * Reset the timer
	 */
	public function resetTime()
	{
		$this->start_time = $this->microtime_float();
	}

}fof/utils/array/array.php000064400000030761152177723700011445 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * A utility class to handle array manipulation.
 *
 * Based on the JArrayHelper class as found in Joomla! 3.2.0
 */
abstract class FOFUtilsArray
{
	/**
	 * Option to perform case-sensitive sorts.
	 *
	 * @var    mixed  Boolean or array of booleans.
	 */
	protected static $sortCase;

	/**
	 * Option to set the sort direction.
	 *
	 * @var    mixed  Integer or array of integers.
	 */
	protected static $sortDirection;

	/**
	 * Option to set the object key to sort on.
	 *
	 * @var    string
	 */
	protected static $sortKey;

	/**
	 * Option to perform a language aware sort.
	 *
	 * @var    mixed  Boolean or array of booleans.
	 */
	protected static $sortLocale;

	/**
	 * Function to convert array to integer values
	 *
	 * @param   array  &$array   The source array to convert
	 * @param   mixed  $default  A default value (int|array) to assign if $array is not an array
	 *
	 * @return  void
	 */
	public static function toInteger(&$array, $default = null)
	{
		if (is_array($array))
		{
			foreach ($array as $i => $v)
			{
				$array[$i] = (int) $v;
			}
		}
		else
		{
			if ($default === null)
			{
				$array = array();
			}
			elseif (is_array($default))
			{
				self::toInteger($default, null);
				$array = $default;
			}
			else
			{
				$array = array((int) $default);
			}
		}
	}

	/**
	 * Utility function to map an array to a stdClass object.
	 *
	 * @param   array   &$array  The array to map.
	 * @param   string  $class   Name of the class to create
	 *
	 * @return  object   The object mapped from the given array
	 */
	public static function toObject(&$array, $class = 'stdClass')
	{
		$obj = null;

		if (is_array($array))
		{
			$obj = new $class;

			foreach ($array as $k => $v)
			{
				if (is_array($v))
				{
					$obj->$k = self::toObject($v, $class);
				}
				else
				{
					$obj->$k = $v;
				}
			}
		}
		return $obj;
	}

	/**
	 * Utility function to map an array to a string.
	 *
	 * @param   array    $array         The array to map.
	 * @param   string   $inner_glue    The glue (optional, defaults to '=') between the key and the value.
	 * @param   string   $outer_glue    The glue (optional, defaults to ' ') between array elements.
	 * @param   boolean  $keepOuterKey  True if final key should be kept.
	 *
	 * @return  string   The string mapped from the given array
	 */
	public static function toString($array = null, $inner_glue = '=', $outer_glue = ' ', $keepOuterKey = false)
	{
		$output = array();

		if (is_array($array))
		{
			foreach ($array as $key => $item)
			{
				if (is_array($item))
				{
					if ($keepOuterKey)
					{
						$output[] = $key;
					}
					// This is value is an array, go and do it again!
					$output[] = self::toString($item, $inner_glue, $outer_glue, $keepOuterKey);
				}
				else
				{
					$output[] = $key . $inner_glue . '"' . $item . '"';
				}
			}
		}

		return implode($outer_glue, $output);
	}

	/**
	 * Utility function to map an object to an array
	 *
	 * @param   object   $p_obj    The source object
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array    The array mapped from the given object
	 */
	public static function fromObject($p_obj, $recurse = true, $regex = null)
	{
		if (is_object($p_obj))
		{
			return self::_fromObject($p_obj, $recurse, $regex);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Utility function to map an object or array to an array
	 *
	 * @param   mixed    $item     The source object or array
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array  The array mapped from the given object
	 */
	protected static function _fromObject($item, $recurse, $regex)
	{
		if (is_object($item))
		{
			$result = array();

			foreach (get_object_vars($item) as $k => $v)
			{
				if (!$regex || preg_match($regex, $k))
				{
					if ($recurse)
					{
						$result[$k] = self::_fromObject($v, $recurse, $regex);
					}
					else
					{
						$result[$k] = $v;
					}
				}
			}
		}
		elseif (is_array($item))
		{
			$result = array();

			foreach ($item as $k => $v)
			{
				$result[$k] = self::_fromObject($v, $recurse, $regex);
			}
		}
		else
		{
			$result = $item;
		}
		return $result;
	}

	/**
	 * Extracts a column from an array of arrays or objects
	 *
	 * @param   array   &$array  The source array
	 * @param   string  $index   The index of the column or name of object property
	 *
	 * @return  array  Column of values from the source array
	 */
	public static function getColumn(&$array, $index)
	{
		$result = array();

		if (is_array($array))
		{
			foreach ($array as &$item)
			{
				if (is_array($item) && isset($item[$index]))
				{
					$result[] = $item[$index];
				}
				elseif (is_object($item) && isset($item->$index))
				{
					$result[] = $item->$index;
				}
				// Else ignore the entry
			}
		}
		return $result;
	}

	/**
	 * Utility function to return a value from a named array or a specified default
	 *
	 * @param   array   &$array   A named array
	 * @param   string  $name     The key to search for
	 * @param   mixed   $default  The default value to give if no key found
	 * @param   string  $type     Return type for the variable (INT, FLOAT, STRING, WORD, BOOLEAN, ARRAY)
	 *
	 * @return  mixed  The value from the source array
	 */
	public static function getValue(&$array, $name, $default = null, $type = '')
	{
		$result = null;

		if (isset($array[$name]))
		{
			$result = $array[$name];
		}

		// Handle the default case
		if (is_null($result))
		{
			$result = $default;
		}

		// Handle the type constraint
		switch (strtoupper($type))
		{
			case 'INT':
			case 'INTEGER':
				// Only use the first integer value
				@preg_match('/-?[0-9]+/', $result, $matches);
				$result = @(int) $matches[0];
				break;

			case 'FLOAT':
			case 'DOUBLE':
				// Only use the first floating point value
				@preg_match('/-?[0-9]+(\.[0-9]+)?/', $result, $matches);
				$result = @(float) $matches[0];
				break;

			case 'BOOL':
			case 'BOOLEAN':
				$result = (bool) $result;
				break;

			case 'ARRAY':
				if (!is_array($result))
				{
					$result = array($result);
				}
				break;

			case 'STRING':
				$result = (string) $result;
				break;

			case 'WORD':
				$result = (string) preg_replace('#\W#', '', $result);
				break;

			case 'NONE':
			default:
				// No casting necessary
				break;
		}
		return $result;
	}

	/**
	 * Takes an associative array of arrays and inverts the array keys to values using the array values as keys.
	 *
	 * Example:
	 * $input = array(
	 *     'New' => array('1000', '1500', '1750'),
	 *     'Used' => array('3000', '4000', '5000', '6000')
	 * );
	 * $output = FOFUtilsArray::invert($input);
	 *
	 * Output would be equal to:
	 * $output = array(
	 *     '1000' => 'New',
	 *     '1500' => 'New',
	 *     '1750' => 'New',
	 *     '3000' => 'Used',
	 *     '4000' => 'Used',
	 *     '5000' => 'Used',
	 *     '6000' => 'Used'
	 * );
	 *
	 * @param   array  $array  The source array.
	 *
	 * @return  array  The inverted array.
	 */
	public static function invert($array)
	{
		$return = array();

		foreach ($array as $base => $values)
		{
			if (!is_array($values))
			{
				continue;
			}

			foreach ($values as $key)
			{
				// If the key isn't scalar then ignore it.
				if (is_scalar($key))
				{
					$return[$key] = $base;
				}
			}
		}
		return $return;
	}

	/**
	 * Method to determine if an array is an associative array.
	 *
	 * @param   array  $array  An array to test.
	 *
	 * @return  boolean  True if the array is an associative array.
	 */
	public static function isAssociative($array)
	{
		if (is_array($array))
		{
			foreach (array_keys($array) as $k => $v)
			{
				if ($k !== $v)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Pivots an array to create a reverse lookup of an array of scalars, arrays or objects.
	 *
	 * @param   array   $source  The source array.
	 * @param   string  $key     Where the elements of the source array are objects or arrays, the key to pivot on.
	 *
	 * @return  array  An array of arrays pivoted either on the value of the keys, or an individual key of an object or array.
	 */
	public static function pivot($source, $key = null)
	{
		$result = array();
		$counter = array();

		foreach ($source as $index => $value)
		{
			// Determine the name of the pivot key, and its value.
			if (is_array($value))
			{
				// If the key does not exist, ignore it.
				if (!isset($value[$key]))
				{
					continue;
				}

				$resultKey = $value[$key];
				$resultValue = &$source[$index];
			}
			elseif (is_object($value))
			{
				// If the key does not exist, ignore it.
				if (!isset($value->$key))
				{
					continue;
				}

				$resultKey = $value->$key;
				$resultValue = &$source[$index];
			}
			else
			{
				// Just a scalar value.
				$resultKey = $value;
				$resultValue = $index;
			}

			// The counter tracks how many times a key has been used.
			if (empty($counter[$resultKey]))
			{
				// The first time around we just assign the value to the key.
				$result[$resultKey] = $resultValue;
				$counter[$resultKey] = 1;
			}
			elseif ($counter[$resultKey] == 1)
			{
				// If there is a second time, we convert the value into an array.
				$result[$resultKey] = array(
					$result[$resultKey],
					$resultValue,
				);
				$counter[$resultKey]++;
			}
			else
			{
				// After the second time, no need to track any more. Just append to the existing array.
				$result[$resultKey][] = $resultValue;
			}
		}

		unset($counter);

		return $result;
	}

	/**
	 * Utility function to sort an array of objects on a given field
	 *
	 * @param   array  &$a             An array of objects
	 * @param   mixed  $k              The key (string) or a array of key to sort on
	 * @param   mixed  $direction      Direction (integer) or an array of direction to sort in [1 = Ascending] [-1 = Descending]
	 * @param   mixed  $caseSensitive  Boolean or array of booleans to let sort occur case sensitive or insensitive
	 * @param   mixed  $locale         Boolean or array of booleans to let sort occur using the locale language or not
	 *
	 * @return  array  The sorted array of objects
	 */
	public static function sortObjects(&$a, $k, $direction = 1, $caseSensitive = true, $locale = false)
	{
		if (!is_array($locale) || !is_array($locale[0]))
		{
			$locale = array($locale);
		}

		self::$sortCase = (array) $caseSensitive;
		self::$sortDirection = (array) $direction;
		self::$sortKey = (array) $k;
		self::$sortLocale = $locale;

		usort($a, array(__CLASS__, '_sortObjects'));

		self::$sortCase = null;
		self::$sortDirection = null;
		self::$sortKey = null;
		self::$sortLocale = null;

		return $a;
	}

	/**
	 * Callback function for sorting an array of objects on a key
	 *
	 * @param   array  &$a  An array of objects
	 * @param   array  &$b  An array of objects
	 *
	 * @return  integer  Comparison status
	 *
	 * @see     FOFUtilsArray::sortObjects()
	 */
	protected static function _sortObjects(&$a, &$b)
	{
		$key = self::$sortKey;

		for ($i = 0, $count = count($key); $i < $count; $i++)
		{
			if (isset(self::$sortDirection[$i]))
			{
				$direction = self::$sortDirection[$i];
			}

			if (isset(self::$sortCase[$i]))
			{
				$caseSensitive = self::$sortCase[$i];
			}

			if (isset(self::$sortLocale[$i]))
			{
				$locale = self::$sortLocale[$i];
			}

			$va = $a->{$key[$i]};
			$vb = $b->{$key[$i]};

			if ((is_bool($va) || is_numeric($va)) && (is_bool($vb) || is_numeric($vb)))
			{
				$cmp = $va - $vb;
			}
			elseif ($caseSensitive)
			{
				$cmp = JString::strcmp($va, $vb, $locale);
			}
			else
			{
				$cmp = JString::strcasecmp($va, $vb, $locale);
			}

			if ($cmp > 0)
			{

				return $direction;
			}

			if ($cmp < 0)
			{
				return -$direction;
			}
		}

		return 0;
	}

	/**
	 * Multidimensional array safe unique test
	 *
	 * @param   array  $myArray  The array to make unique.
	 *
	 * @return  array
	 *
	 * @see     http://php.net/manual/en/function.array-unique.php
	 */
	public static function arrayUnique($myArray)
	{
		if (!is_array($myArray))
		{
			return $myArray;
		}

		foreach ($myArray as &$myvalue)
		{
			$myvalue = serialize($myvalue);
		}

		$myArray = array_unique($myArray);

		foreach ($myArray as &$myvalue)
		{
			$myvalue = unserialize($myvalue);
		}

		return $myArray;
	}
}
fof/utils/ip/ip.php000064400000026514152177723700010232 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * IP address utilities
 */
abstract class FOFUtilsIp
{
	/** @var   string  The IP address of the current visitor */
	protected static $ip = null;

	/**
	 * Should I allow IP overrides through X-Forwarded-For or Client-Ip HTTP headers?
	 *
	 * @var    bool
	 */
	protected static $allowIpOverrides = true;

	/**
	 * Get the current visitor's IP address
	 *
	 * @return string
	 */
	public static function getIp()
	{
		if (is_null(static::$ip))
		{
			$ip = self::detectAndCleanIP();

			if (!empty($ip) && ($ip != '0.0.0.0') && function_exists('inet_pton') && function_exists('inet_ntop'))
			{
				$myIP = @inet_pton($ip);

				if ($myIP !== false)
				{
					$ip = inet_ntop($myIP);
				}
			}

			static::setIp($ip);
		}

		return static::$ip;
	}

	/**
	 * Set the IP address of the current visitor
	 *
	 * @param   string  $ip
	 *
	 * @return  void
	 */
	public static function setIp($ip)
	{
		static::$ip = $ip;
	}

	/**
	 * Is it an IPv6 IP address?
	 *
	 * @param   string   $ip  An IPv4 or IPv6 address
	 *
	 * @return  boolean  True if it's IPv6
	 */
	public static function isIPv6($ip)
	{
		if (strstr($ip, ':'))
		{
			return true;
		}

		return false;
	}

	/**
	 * Checks if an IP is contained in a list of IPs or IP expressions
	 *
	 * @param   string        $ip       The IPv4/IPv6 address to check
	 * @param   array|string  $ipTable  An IP expression (or a comma-separated or array list of IP expressions) to check against
	 *
	 * @return  null|boolean  True if it's in the list
	 */
	public static function IPinList($ip, $ipTable = '')
	{
		// No point proceeding with an empty IP list
		if (empty($ipTable))
		{
			return false;
		}

		// If the IP list is not an array, convert it to an array
		if (!is_array($ipTable))
		{
			if (strpos($ipTable, ',') !== false)
			{
				$ipTable = explode(',', $ipTable);
				$ipTable = array_map(function($x) { return trim($x); }, $ipTable);
			}
			else
			{
				$ipTable = trim($ipTable);
				$ipTable = array($ipTable);
			}
		}

		// If no IP address is found, return false
		if ($ip == '0.0.0.0')
		{
			return false;
		}

		// If no IP is given, return false
		if (empty($ip))
		{
			return false;
		}

		// Sanity check
		if (!function_exists('inet_pton'))
		{
			return false;
		}

		// Get the IP's in_adds representation
		$myIP = @inet_pton($ip);

		// If the IP is in an unrecognisable format, quite
		if ($myIP === false)
		{
			return false;
		}

		$ipv6 = self::isIPv6($ip);

		foreach ($ipTable as $ipExpression)
		{
			$ipExpression = trim($ipExpression);

			// Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
			if (strstr($ipExpression, '-'))
			{
				list($from, $to) = explode('-', $ipExpression, 2);

				if ($ipv6 && (!self::isIPv6($from) || !self::isIPv6($to)))
				{
					// Do not apply IPv4 filtering on an IPv6 address
					continue;
				}
				elseif (!$ipv6 && (self::isIPv6($from) || self::isIPv6($to)))
				{
					// Do not apply IPv6 filtering on an IPv4 address
					continue;
				}

				$from = @inet_pton(trim($from));
				$to = @inet_pton(trim($to));

				// Sanity check
				if (($from === false) || ($to === false))
				{
					continue;
				}

				// Swap from/to if they're in the wrong order
				if ($from > $to)
				{
					list($from, $to) = array($to, $from);
				}

				if (($myIP >= $from) && ($myIP <= $to))
				{
					return true;
				}
			}
			// Netmask or CIDR provided
			elseif (strstr($ipExpression, '/'))
			{
				$binaryip = self::inet_to_bits($myIP);

				list($net, $maskbits) = explode('/', $ipExpression, 2);
				if ($ipv6 && !self::isIPv6($net))
				{
					// Do not apply IPv4 filtering on an IPv6 address
					continue;
				}
				elseif (!$ipv6 && self::isIPv6($net))
				{
					// Do not apply IPv6 filtering on an IPv4 address
					continue;
				}
				elseif ($ipv6 && strstr($maskbits, ':'))
				{
					// Perform an IPv6 CIDR check
					if (self::checkIPv6CIDR($myIP, $ipExpression))
					{
						return true;
					}

					// If we didn't match it proceed to the next expression
					continue;
				}
				elseif (!$ipv6 && strstr($maskbits, '.'))
				{
					// Convert IPv4 netmask to CIDR
					$long = ip2long($maskbits);
					$base = ip2long('255.255.255.255');
					$maskbits = 32 - log(($long ^ $base) + 1, 2);
				}

				// Convert network IP to in_addr representation
				$net = @inet_pton($net);

				// Sanity check
				if ($net === false)
				{
					continue;
				}

				// Get the network's binary representation
				$binarynet = self::inet_to_bits($net);
				$expectedNumberOfBits = $ipv6 ? 128 : 24;
				$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);

				// Check the corresponding bits of the IP and the network
				$ip_net_bits = substr($binaryip, 0, $maskbits);
				$net_bits = substr($binarynet, 0, $maskbits);

				if ($ip_net_bits == $net_bits)
				{
					return true;
				}
			}
			else
			{
				// IPv6: Only single IPs are supported
				if ($ipv6)
				{
					$ipExpression = trim($ipExpression);

					if (!self::isIPv6($ipExpression))
					{
						continue;
					}

					$ipCheck = @inet_pton($ipExpression);
					if ($ipCheck === false)
					{
						continue;
					}

					if ($ipCheck == $myIP)
					{
						return true;
					}
				}
				else
				{
					// Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
					$dots = 0;
					if (substr($ipExpression, -1) == '.')
					{
						// Partial IP address. Convert to CIDR and re-match
						foreach (count_chars($ipExpression, 1) as $i => $val)
						{
							if ($i == 46)
							{
								$dots = $val;
							}
						}
						switch ($dots)
						{
							case 1:
								$netmask = '255.0.0.0';
								$ipExpression .= '0.0.0';
								break;

							case 2:
								$netmask = '255.255.0.0';
								$ipExpression .= '0.0';
								break;

							case 3:
								$netmask = '255.255.255.0';
								$ipExpression .= '0';
								break;

							default:
								$dots = 0;
						}

						if ($dots)
						{
							$binaryip = self::inet_to_bits($myIP);

							// Convert netmask to CIDR
							$long = ip2long($netmask);
							$base = ip2long('255.255.255.255');
							$maskbits = 32 - log(($long ^ $base) + 1, 2);

							$net = @inet_pton($ipExpression);

							// Sanity check
							if ($net === false)
							{
								continue;
							}

							// Get the network's binary representation
							$binarynet = self::inet_to_bits($net);
							$expectedNumberOfBits = $ipv6 ? 128 : 24;
							$binarynet = str_pad($binarynet, $expectedNumberOfBits, '0', STR_PAD_RIGHT);

							// Check the corresponding bits of the IP and the network
							$ip_net_bits = substr($binaryip, 0, $maskbits);
							$net_bits = substr($binarynet, 0, $maskbits);

							if ($ip_net_bits == $net_bits)
							{
								return true;
							}
						}
					}
					if (!$dots)
					{
						$ip = @inet_pton(trim($ipExpression));
						if ($ip == $myIP)
						{
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	/**
	 * Works around the REMOTE_ADDR not containing the user's IP
	 */
	public static function workaroundIPIssues()
	{
		$ip = self::getIp();

		if ($_SERVER['REMOTE_ADDR'] == $ip)
		{
			return;
		}

		if (array_key_exists('REMOTE_ADDR', $_SERVER))
		{
			$_SERVER['FOF_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
		}
		elseif (function_exists('getenv'))
		{
			if (getenv('REMOTE_ADDR'))
			{
				$_SERVER['FOF_REMOTE_ADDR'] = getenv('REMOTE_ADDR');
			}
		}

		$_SERVER['REMOTE_ADDR'] = $ip;
	}

	/**
	 * Should I allow the remote client's IP to be overridden by an X-Forwarded-For or Client-Ip HTTP header?
	 *
	 * @param   bool  $newState  True to allow the override
	 *
	 * @return  void
	 */
	public static function setAllowIpOverrides($newState)
	{
		self::$allowIpOverrides = $newState ? true : false;
	}

	/**
	 * Gets the visitor's IP address. Automatically handles reverse proxies
	 * reporting the IPs of intermediate devices, like load balancers. Examples:
	 * https://www.akeebabackup.com/support/admin-tools/13743-double-ip-adresses-in-security-exception-log-warnings.html
	 * http://stackoverflow.com/questions/2422395/why-is-request-envremote-addr-returning-two-ips
	 * The solution used is assuming that the last IP address is the external one.
	 *
	 * @return  string
	 */
	protected static function detectAndCleanIP()
	{
		$ip = self::detectIP();

		if ((strstr($ip, ',') !== false) || (strstr($ip, ' ') !== false))
		{
			$ip = str_replace(' ', ',', $ip);
			$ip = str_replace(',,', ',', $ip);
			$ips = explode(',', $ip);
			$ip = '';
			while (empty($ip) && !empty($ips))
			{
				$ip = array_pop($ips);
				$ip = trim($ip);
			}
		}
		else
		{
			$ip = trim($ip);
		}

		return $ip;
	}

	/**
	 * Gets the visitor's IP address
	 *
	 * @return  string
	 */
	protected static function detectIP()
	{
		// Normally the $_SERVER superglobal is set
		if (isset($_SERVER))
		{
			// Do we have an x-forwarded-for HTTP header (e.g. NginX)?
			if (self::$allowIpOverrides && array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER))
			{
				return $_SERVER['HTTP_X_FORWARDED_FOR'];
			}

			// Do we have a client-ip header (e.g. non-transparent proxy)?
			if (self::$allowIpOverrides && array_key_exists('HTTP_CLIENT_IP', $_SERVER))
			{
				return $_SERVER['HTTP_CLIENT_IP'];
			}

			// Normal, non-proxied server or server behind a transparent proxy
			return $_SERVER['REMOTE_ADDR'];
		}

		// This part is executed on PHP running as CGI, or on SAPIs which do
		// not set the $_SERVER superglobal
		// If getenv() is disabled, you're screwed
		if (!function_exists('getenv'))
		{
			return '';
		}

		// Do we have an x-forwarded-for HTTP header?
		if (self::$allowIpOverrides && getenv('HTTP_X_FORWARDED_FOR'))
		{
			return getenv('HTTP_X_FORWARDED_FOR');
		}

		// Do we have a client-ip header?
		if (self::$allowIpOverrides && getenv('HTTP_CLIENT_IP'))
		{
			return getenv('HTTP_CLIENT_IP');
		}

		// Normal, non-proxied server or server behind a transparent proxy
		if (getenv('REMOTE_ADDR'))
		{
			return getenv('REMOTE_ADDR');
		}

		// Catch-all case for broken servers, apparently
		return '';
	}

	/**
	 * Converts inet_pton output to bits string
	 *
	 * @param   string $inet The in_addr representation of an IPv4 or IPv6 address
	 *
	 * @return  string
	 */
	protected static function inet_to_bits($inet)
	{
		if (strlen($inet) == 4)
		{
			$unpacked = unpack('A4', $inet);
		}
		else
		{
			$unpacked = unpack('A16', $inet);
		}
		$unpacked = str_split($unpacked[1]);
		$binaryip = '';

		foreach ($unpacked as $char)
		{
			$binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
		}

		return $binaryip;
	}

	/**
	 * Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
	 *
	 * @param   string  $ip       The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
	 * @param   string  $cidrnet  The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
	 *
	 * @return  bool
	 */
	protected static function checkIPv6CIDR($ip, $cidrnet)
	{
		$ip = inet_pton($ip);
		$binaryip=self::inet_to_bits($ip);

		list($net,$maskbits)=explode('/',$cidrnet);
		$net=inet_pton($net);
		$binarynet=self::inet_to_bits($net);

		$ip_net_bits=substr($binaryip,0,$maskbits);
		$net_bits   =substr($binarynet,0,$maskbits);

		return $ip_net_bits === $net_bits;
	}
}fof/utils/observable/dispatcher.php000064400000016452152177723700013464 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * Class to handle dispatching of events.
 *
 * This is the Observable part of the Observer design pattern
 * for the event architecture.
 *
 * This class is based on JEventDispatcher as found in Joomla! 3.2.0
 */
class FOFUtilsObservableDispatcher extends FOFUtilsObject
{
    /**
     * An array of Observer objects to notify
     *
     * @var    array
     */
    protected $_observers = array();

    /**
     * The state of the observable object
     *
     * @var    mixed
     */
    protected $_state = null;

    /**
     * A multi dimensional array of [function][] = key for observers
     *
     * @var    array
     */
    protected $_methods = array();

    /**
     * Stores the singleton instance of the dispatcher.
     *
     * @var    FOFUtilsObservableDispatcher
     */
    protected static $instance = null;

    /**
     * Returns the global Event Dispatcher object, only creating it
     * if it doesn't already exist.
     *
     * @return  FOFUtilsObservableDispatcher  The EventDispatcher object.
     */
    public static function getInstance()
    {
        if (self::$instance === null)
        {
            self::$instance = new static;
        }

        return self::$instance;
    }

    /**
     * Get the state of the FOFUtilsObservableDispatcher object
     *
     * @return  mixed    The state of the object.
     */
    public function getState()
    {
        return $this->_state;
    }

    /**
     * Registers an event handler to the event dispatcher
     *
     * @param   string  $event    Name of the event to register handler for
     * @param   string  $handler  Name of the event handler
     *
     * @return  void
     *
     * @throws  InvalidArgumentException
     */
    public function register($event, $handler)
    {
        // Are we dealing with a class or callback type handler?
        if (is_callable($handler))
        {
            // Ok, function type event handler... let's attach it.
            $method = array('event' => $event, 'handler' => $handler);
            $this->attach($method);
        }
        elseif (class_exists($handler))
        {
            // Ok, class type event handler... let's instantiate and attach it.
            $this->attach(new $handler($this));
        }
        else
        {
            throw new InvalidArgumentException('Invalid event handler.');
        }
    }

    /**
     * Triggers an event by dispatching arguments to all observers that handle
     * the event and returning their return values.
     *
     * @param   string  $event  The event to trigger.
     * @param   array   $args   An array of arguments.
     *
     * @return  array  An array of results from each function call.
     */
    public function trigger($event, $args = array())
    {
        $result = array();

        /*
         * If no arguments were passed, we still need to pass an empty array to
         * the call_user_func_array function.
         */
        $args = (array) $args;

        $event = strtolower($event);

        // Check if any plugins are attached to the event.
        if (!isset($this->_methods[$event]) || empty($this->_methods[$event]))
        {
            // No Plugins Associated To Event!
            return $result;
        }

        // Loop through all plugins having a method matching our event
        foreach ($this->_methods[$event] as $key)
        {
            // Check if the plugin is present.
            if (!isset($this->_observers[$key]))
            {
                continue;
            }

            // Fire the event for an object based observer.
            if (is_object($this->_observers[$key]))
            {
                $args['event'] = $event;
                $value = $this->_observers[$key]->update($args);
            }
            // Fire the event for a function based observer.
            elseif (is_array($this->_observers[$key]))
            {
                $value = call_user_func_array($this->_observers[$key]['handler'], $args);
            }

            if (isset($value))
            {
                $result[] = $value;
            }
        }

        return $result;
    }

    /**
     * Attach an observer object
     *
     * @param   object  $observer  An observer object to attach
     *
     * @return  void
     */
    public function attach($observer)
    {
        if (is_array($observer))
        {
            if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
            {
                return;
            }

            // Make sure we haven't already attached this array as an observer
            foreach ($this->_observers as $check)
            {
                if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
                {
                    return;
                }
            }

            $this->_observers[] = $observer;
            end($this->_observers);
            $methods = array($observer['event']);
        }
        else
        {
            if (!($observer instanceof FOFUtilsObservableEvent))
            {
                return;
            }

            // Make sure we haven't already attached this object as an observer
            $class = get_class($observer);

            foreach ($this->_observers as $check)
            {
                if ($check instanceof $class)
                {
                    return;
                }
            }

            $this->_observers[] = $observer;

            // Required in PHP 7 since foreach() doesn't advance the internal array counter, see http://php.net/manual/en/migration70.incompatible.php
            end($this->_observers);

            $methods = array();

            foreach(get_class_methods($observer) as $obs_method)
            {
                // Magic methods are not allowed
                if(strpos('__', $obs_method) === 0)
                {
                    continue;
                }

                $methods[] = $obs_method;
            }

            //$methods = get_class_methods($observer);
        }

        $key = key($this->_observers);

        foreach ($methods as $method)
        {
            $method = strtolower($method);

            if (!isset($this->_methods[$method]))
            {
                $this->_methods[$method] = array();
            }

            $this->_methods[$method][] = $key;
        }
    }

    /**
     * Detach an observer object
     *
     * @param   object  $observer  An observer object to detach.
     *
     * @return  boolean  True if the observer object was detached.
     */
    public function detach($observer)
    {
        $retval = false;

        $key = array_search($observer, $this->_observers);

        if ($key !== false)
        {
            unset($this->_observers[$key]);
            $retval = true;

            foreach ($this->_methods as &$method)
            {
                $k = array_search($key, $method);

                if ($k !== false)
                {
                    unset($method[$k]);
                }
            }
        }

        return $retval;
    }
}
fof/utils/observable/event.php000064400000003631152177723700012452 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * Defines an observable event.
 *
 * This class is based on JEvent as found in Joomla! 3.2.0
 */
abstract class FOFUtilsObservableEvent extends FOFUtilsObject
{
    /**
     * Event object to observe.
     *
     * @var    object
     */
    protected $_subject = null;

    /**
     * Constructor
     *
     * @param   object  &$subject  The object to observe.
     */
    public function __construct(&$subject)
    {
        // Register the observer ($this) so we can be notified
        $subject->attach($this);

        // Set the subject to observe
        $this->_subject = &$subject;
    }

    /**
     * Method to trigger events.
     * The method first generates the even from the argument array. Then it unsets the argument
     * since the argument has no bearing on the event handler.
     * If the method exists it is called and returns its return value. If it does not exist it
     * returns null.
     *
     * @param   array  &$args  Arguments
     *
     * @return  mixed  Routine return value
     */
    public function update(&$args)
    {
        // First let's get the event from the argument array.  Next we will unset the
        // event argument as it has no bearing on the method to handle the event.
        $event = $args['event'];
        unset($args['event']);

        /*
         * If the method to handle an event exists, call it and return its return
         * value.  If it does not exist, return null.
         */
        if (method_exists($this, $event))
        {
            return call_user_func_array(array($this, $event), $args);
        }
        else
        {
            return null;
        }
    }
}
fof/utils/phpfunc/phpfunc.php000064400000001711152177723700012310 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Intercept calls to PHP functions.
 *
 * @method  function_exists(string $function)
 * @method  mcrypt_list_algorithms()
 * @method  hash_algos()
 * @method  extension_loaded(string $ext)
 * @method  mcrypt_create_iv(int $bytes, int $source)
 * @method  openssl_get_cipher_methods()
 */
class FOFUtilsPhpfunc
{
	/**
	 *
	 * Magic call to intercept any function pass to it.
	 *
	 * @param string $func The function to call.
	 *
	 * @param array  $args Arguments passed to the function.
	 *
	 * @return mixed The result of the function call.
	 *
	 */
	public function __call($func, $args)
	{
		return call_user_func_array($func, $args);
	}
}
fof/utils/installscript/installscript.php000064400000200207152177723700014771 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */

defined('FOF_INCLUDED') or die;

JLoader::import('joomla.filesystem.folder');
JLoader::import('joomla.filesystem.file');
JLoader::import('joomla.installer.installer');
JLoader::import('joomla.utilities.date');

/**
 * A helper class which you can use to create component installation scripts
 */
abstract class FOFUtilsInstallscript
{
	/**
	 * The component's name
	 *
	 * @var   string
	 */
	protected $componentName = 'com_foobar';

	/**
	 * The title of the component (printed on installation and uninstallation messages)
	 *
	 * @var string
	 */
	protected $componentTitle = 'Foobar Component';

	/**
	 * The list of extra modules and plugins to install on component installation / update and remove on component
	 * uninstallation.
	 *
	 * @var   array
	 */
	protected $installation_queue = array(
		// modules => { (folder) => { (module) => { (position), (published) } }* }*
		'modules' => array(
			'admin' => array(),
			'site'  => array()
		),
		// plugins => { (folder) => { (element) => (published) }* }*
		'plugins' => array(
			'system' => array(),
		)
	);

	/**
	 * The list of obsolete extra modules and plugins to uninstall on component upgrade / installation.
	 *
	 * @var array
	 */
	protected $uninstallation_queue = array(
		// modules => { (folder) => { (module) }* }*
		'modules' => array(
			'admin' => array(),
			'site'  => array()
		),
		// plugins => { (folder) => { (element) }* }*
		'plugins' => array(
			'system' => array(),
		)
	);

	/**
	 * Obsolete files and folders to remove from the free version only. This is used when you move a feature from the
	 * free version of your extension to its paid version. If you don't have such a distinction you can ignore this.
	 *
	 * @var   array
	 */
	protected $removeFilesFree = array(
		'files'   => array(
			// Use pathnames relative to your site's root, e.g.
			// 'administrator/components/com_foobar/helpers/whatever.php'
		),
		'folders' => array(
			// Use pathnames relative to your site's root, e.g.
			// 'administrator/components/com_foobar/baz'
		)
	);

	/**
	 * Obsolete files and folders to remove from both paid and free releases. This is used when you refactor code and
	 * some files inevitably become obsolete and need to be removed.
	 *
	 * @var   array
	 */
	protected $removeFilesAllVersions = array(
		'files'   => array(
			// Use pathnames relative to your site's root, e.g.
			// 'administrator/components/com_foobar/helpers/whatever.php'
		),
		'folders' => array(
			// Use pathnames relative to your site's root, e.g.
			// 'administrator/components/com_foobar/baz'
		)
	);

	/**
	 * A list of scripts to be copied to the "cli" directory of the site
	 *
	 * @var   array
	 */
	protected $cliScriptFiles = array(
		// Use just the filename, e.g.
		// 'my-cron-script.php'
	);

	/**
	 * The path inside your package where cli scripts are stored
	 *
	 * @var   string
	 */
	protected $cliSourcePath = 'cli';

	/**
	 * The path inside your package where FOF is stored
	 *
	 * @var   string
	 */
	protected $fofSourcePath = 'fof';

	/**
	 * The path inside your package where Akeeba Strapper is stored
	 *
	 * @var   string
	 */
	protected $strapperSourcePath = 'strapper';

	/**
	 * The path inside your package where extra modules are stored
	 *
	 * @var   string
	 */
	protected $modulesSourcePath = 'modules';

	/**
	 * The path inside your package where extra plugins are stored
	 *
	 * @var   string
	 */
	protected $pluginsSourcePath = 'plugins';

	/**
	 * Is the schemaXmlPath class variable a relative path? If set to true the schemaXmlPath variable contains a path
	 * relative to the component's back-end directory. If set to false the schemaXmlPath variable contains an absolute
	 * filesystem path.
	 *
	 * @var   boolean
	 */
	protected $schemaXmlPathRelative = true;

	/**
	 * The path where the schema XML files are stored. Its contents depend on the schemaXmlPathRelative variable above
	 * true        => schemaXmlPath contains a path relative to the component's back-end directory
	 * false    => schemaXmlPath contains an absolute filesystem path
	 *
	 * @var string
	 */
	protected $schemaXmlPath = 'sql/xml';

	/**
	 * The minimum PHP version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumPHPVersion = '5.3.3';

	/**
	 * The minimum Joomla! version required to install this extension
	 *
	 * @var   string
	 */
	protected $minimumJoomlaVersion = '2.5.6';

	/**
	 * The maximum Joomla! version this extension can be installed on
	 *
	 * @var   string
	 */
	protected $maximumJoomlaVersion = '3.9.99';

	/**
	 * Is this the paid version of the extension? This only determines which files / extensions will be removed.
	 *
	 * @var   boolean
	 */
	protected $isPaid = false;

	/**
	 * Post-installation message definitions for Joomla! 3.2 or later.
	 *
	 * This array contains the message definitions for the Post-installation Messages component added in Joomla! 3.2 and
	 * later versions. Each element is also a hashed array. For the keys used in these message definitions please
	 * @see FOFUtilsInstallscript::addPostInstallationMessage
	 *
	 * @var array
	 */
	protected $postInstallationMessages = array();

	/**
	 * Joomla! pre-flight event. This runs before Joomla! installs or updates the component. This is our last chance to
	 * tell Joomla! if it should abort the installation.
	 *
	 * @param   string     $type   Installation type (install, update, discover_install)
	 * @param   JInstaller $parent Parent object
	 *
	 * @return  boolean  True to let the installation proceed, false to halt the installation
	 */
	public function preflight($type, $parent)
	{
		// Check the minimum PHP version
		if (!empty($this->minimumPHPVersion))
		{
			if (defined('PHP_VERSION'))
			{
				$version = PHP_VERSION;
			}
			elseif (function_exists('phpversion'))
			{
				$version = phpversion();
			}
			else
			{
				$version = '5.0.0'; // all bets are off!
			}

			if (!version_compare($version, $this->minimumPHPVersion, 'ge'))
			{
				$msg = "<p>You need PHP $this->minimumPHPVersion or later to install this component</p>";

				if (version_compare(JVERSION, '3.0', 'gt'))
				{
					JLog::add($msg, JLog::WARNING, 'jerror');
				}
				else
				{
					JError::raiseWarning(100, $msg);
				}

				return false;
			}
		}

		// Check the minimum Joomla! version
		if (!empty($this->minimumJoomlaVersion) && !version_compare(JVERSION, $this->minimumJoomlaVersion, 'ge'))
		{
			$msg = "<p>You need Joomla! $this->minimumJoomlaVersion or later to install this component</p>";

			if (version_compare(JVERSION, '3.0', 'gt'))
			{
				JLog::add($msg, JLog::WARNING, 'jerror');
			}
			else
			{
				JError::raiseWarning(100, $msg);
			}

			return false;
		}

		// Check the maximum Joomla! version
		if (!empty($this->maximumJoomlaVersion) && !version_compare(JVERSION, $this->maximumJoomlaVersion, 'le'))
		{
			$msg = "<p>You need Joomla! $this->maximumJoomlaVersion or earlier to install this component</p>";

			if (version_compare(JVERSION, '3.0', 'gt'))
			{
				JLog::add($msg, JLog::WARNING, 'jerror');
			}
			else
			{
				JError::raiseWarning(100, $msg);
			}

			return false;
		}

		// Always reset the OPcache if it's enabled. Otherwise there's a good chance the server will not know we are
		// replacing .php scripts. This is a major concern since PHP 5.5 included and enabled OPcache by default.
		if (function_exists('opcache_reset'))
		{
			opcache_reset();
		}

		// Workarounds for JInstaller issues
		if (in_array($type, array('install', 'discover_install')))
		{
			// Bugfix for "Database function returned no error"
			$this->bugfixDBFunctionReturnedNoError();
		}
		else
		{
			// Bugfix for "Can not build admin menus"
			$this->bugfixCantBuildAdminMenus();
		}

		return true;
	}

	/**
	 * Runs after install, update or discover_update. In other words, it executes after Joomla! has finished installing
	 * or updating your component. This is the last chance you've got to perform any additional installations, clean-up,
	 * database updates and similar housekeeping functions.
	 *
	 * @param   string     $type   install, update or discover_update
	 * @param   JInstaller $parent Parent object
	 */
	public function postflight($type, $parent)
	{
		// Install or update database
		$dbInstaller = new FOFDatabaseInstaller(array(
			'dbinstaller_directory' =>
				($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' .
				$this->schemaXmlPath
		));
		$dbInstaller->updateSchema();

		// Install subextensions
		$status = $this->installSubextensions($parent);

		// Install FOF
		$fofInstallationStatus = $this->installFOF($parent);

		// Install Akeeba Straper
		$strapperInstallationStatus = $this->installStrapper($parent);

		// Make sure menu items are installed
		$this->_createAdminMenus($parent);

		// Make sure menu items are published (surprise goal in the 92' by JInstaller wins the cup for "most screwed up
		// bug in the history of Joomla!")
		$this->_reallyPublishAdminMenuItems($parent);

		// Which files should I remove?
		if ($this->isPaid)
		{
			// This is the paid version, only remove the removeFilesAllVersions files
			$removeFiles = $this->removeFilesAllVersions;
		}
		else
		{
			// This is the free version, remove the removeFilesAllVersions and removeFilesFree files
			$removeFiles = array('files' => array(), 'folders' => array());

			if (isset($this->removeFilesAllVersions['files']))
			{
				if (isset($this->removeFilesFree['files']))
				{
					$removeFiles['files'] = array_merge($this->removeFilesAllVersions['files'], $this->removeFilesFree['files']);
				}
				else
				{
					$removeFiles['files'] = $this->removeFilesAllVersions['files'];
				}
			}
			elseif (isset($this->removeFilesFree['files']))
			{
				$removeFiles['files'] = $this->removeFilesFree['files'];
			}

			if (isset($this->removeFilesAllVersions['folders']))
			{
				if (isset($this->removeFilesFree['folders']))
				{
					$removeFiles['folders'] = array_merge($this->removeFilesAllVersions['folders'], $this->removeFilesFree['folders']);
				}
				else
				{
					$removeFiles['folders'] = $this->removeFilesAllVersions['folders'];
				}
			}
			elseif (isset($this->removeFilesFree['folders']))
			{
				$removeFiles['folders'] = $this->removeFilesFree['folders'];
			}
		}

		// Remove obsolete files and folders
		$this->removeFilesAndFolders($removeFiles);

		// Copy the CLI files (if any)
		$this->copyCliFiles($parent);

		// Show the post-installation page
		$this->renderPostInstallation($status, $fofInstallationStatus, $strapperInstallationStatus, $parent);

		// Uninstall obsolete subextensions
		$uninstall_status = $this->uninstallObsoleteSubextensions($parent);

		// Clear the FOF cache
		$platform = FOFPlatform::getInstance();

		if (method_exists($platform, 'clearCache'))
		{
			FOFPlatform::getInstance()->clearCache();
		}

		// Make sure the Joomla! menu structure is correct
		$this->_rebuildMenu();

		// Add post-installation messages on Joomla! 3.2 and later
		$this->_applyPostInstallationMessages();
	}

	/**
	 * Runs on uninstallation
	 *
	 * @param   JInstaller $parent The parent object
	 */
	public function uninstall($parent)
	{
		// Uninstall database
		$dbInstaller = new FOFDatabaseInstaller(array(
			'dbinstaller_directory' =>
				($this->schemaXmlPathRelative ? JPATH_ADMINISTRATOR . '/components/' . $this->componentName : '') . '/' .
				$this->schemaXmlPath
		));
		$dbInstaller->removeSchema();

		// Uninstall modules and plugins
		$status = $this->uninstallSubextensions($parent);

		// Uninstall post-installation messages on Joomla! 3.2 and later
		$this->uninstallPostInstallationMessages();

		// Show the post-uninstallation page
		$this->renderPostUninstallation($status, $parent);
	}

	/**
	 * Copies the CLI scripts into Joomla!'s cli directory
	 *
	 * @param JInstaller $parent
	 */
	protected function copyCliFiles($parent)
	{
		$src = $parent->getParent()->getPath('source');

		$cliPath = JPATH_ROOT . '/cli';

		if (!JFolder::exists($cliPath))
		{
			JFolder::create($cliPath);
		}

		foreach ($this->cliScriptFiles as $script)
		{
			if (JFile::exists($cliPath . '/' . $script))
			{
				JFile::delete($cliPath . '/' . $script);
			}

			if (JFile::exists($src . '/' . $this->cliSourcePath . '/' . $script))
			{
				JFile::copy($src . '/' . $this->cliSourcePath . '/' . $script, $cliPath . '/' . $script);
			}
		}
	}

	/**
	 * Renders the message after installing or upgrading the component
	 */
	protected function renderPostInstallation($status, $fofInstallationStatus, $strapperInstallationStatus, $parent)
	{
		$rows = 0;
		?>
		<table class="adminlist table table-striped" width="100%">
			<thead>
			<tr>
				<th class="title" colspan="2">Extension</th>
				<th width="30%">Status</th>
			</tr>
			</thead>
			<tfoot>
			<tr>
				<td colspan="3"></td>
			</tr>
			</tfoot>
			<tbody>
			<tr class="row<?php echo($rows++ % 2); ?>">
				<td class="key" colspan="2"><?php echo $this->componentTitle ?></td>
				<td><strong style="color: green">Installed</strong></td>
			</tr>
			<?php if ($fofInstallationStatus['required']): ?>
				<tr class="row<?php echo($rows++ % 2); ?>">
					<td class="key" colspan="2">
						<strong>Framework on Framework (FOF) <?php echo $fofInstallationStatus['version'] ?></strong>
						[<?php echo $fofInstallationStatus['date'] ?>]
					</td>
					<td><strong>
							<span
								style="color: <?php echo $fofInstallationStatus['required'] ? ($fofInstallationStatus['installed'] ? 'green' : 'red') : '#660' ?>; font-weight: bold;">
		<?php echo $fofInstallationStatus['required'] ? ($fofInstallationStatus['installed'] ? 'Installed' : 'Not Installed') : 'Already up-to-date'; ?>
							</span>
						</strong></td>
				</tr>
			<?php endif; ?>
			<?php if ($strapperInstallationStatus['required']): ?>
				<tr class="row<?php echo($rows++ % 2); ?>">
					<td class="key" colspan="2">
						<strong>Akeeba Strapper <?php echo $strapperInstallationStatus['version'] ?></strong>
						[<?php echo $strapperInstallationStatus['date'] ?>]
					</td>
					<td><strong>
							<span
								style="color: <?php echo $strapperInstallationStatus['required'] ? ($strapperInstallationStatus['installed'] ? 'green' : 'red') : '#660' ?>; font-weight: bold;">
				<?php echo $strapperInstallationStatus['required'] ? ($strapperInstallationStatus['installed'] ? 'Installed' : 'Not Installed') : 'Already up-to-date'; ?>
							</span>
						</strong></td>
				</tr>
			<?php endif; ?>
			<?php if (count($status->modules)) : ?>
				<tr>
					<th>Module</th>
					<th>Client</th>
					<th></th>
				</tr>
				<?php foreach ($status->modules as $module) : ?>
					<tr class="row<?php echo($rows++ % 2); ?>">
						<td class="key"><?php echo $module['name']; ?></td>
						<td class="key"><?php echo ucfirst($module['client']); ?></td>
						<td><strong
								style="color: <?php echo ($module['result']) ? "green" : "red" ?>"><?php echo ($module['result']) ? 'Installed' : 'Not installed'; ?></strong>
						</td>
					</tr>
				<?php endforeach; ?>
			<?php endif; ?>
			<?php if (count($status->plugins)) : ?>
				<tr>
					<th>Plugin</th>
					<th>Group</th>
					<th></th>
				</tr>
				<?php foreach ($status->plugins as $plugin) : ?>
					<tr class="row<?php echo($rows++ % 2); ?>">
						<td class="key"><?php echo ucfirst($plugin['name']); ?></td>
						<td class="key"><?php echo ucfirst($plugin['group']); ?></td>
						<td><strong
								style="color: <?php echo ($plugin['result']) ? "green" : "red" ?>"><?php echo ($plugin['result']) ? 'Installed' : 'Not installed'; ?></strong>
						</td>
					</tr>
				<?php endforeach; ?>
			<?php endif; ?>
			</tbody>
		</table>
	<?php
	}

	/**
	 * Renders the message after uninstalling the component
	 */
	protected function renderPostUninstallation($status, $parent)
	{
		$rows = 1;
		?>
		<table class="adminlist table table-striped" width="100%">
			<thead>
			<tr>
				<th class="title" colspan="2"><?php echo JText::_('Extension'); ?></th>
				<th width="30%"><?php echo JText::_('Status'); ?></th>
			</tr>
			</thead>
			<tfoot>
			<tr>
				<td colspan="3"></td>
			</tr>
			</tfoot>
			<tbody>
			<tr class="row<?php echo($rows++ % 2); ?>">
				<td class="key" colspan="2"><?php echo $this->componentTitle; ?></td>
				<td><strong style="color: green">Removed</strong></td>
			</tr>
			<?php if (count($status->modules)) : ?>
				<tr>
					<th>Module</th>
					<th>Client</th>
					<th></th>
				</tr>
				<?php foreach ($status->modules as $module) : ?>
					<tr class="row<?php echo($rows++ % 2); ?>">
						<td class="key"><?php echo $module['name']; ?></td>
						<td class="key"><?php echo ucfirst($module['client']); ?></td>
						<td><strong
								style="color: <?php echo ($module['result']) ? "green" : "red" ?>"><?php echo ($module['result']) ? 'Removed' : 'Not removed'; ?></strong>
						</td>
					</tr>
				<?php endforeach; ?>
			<?php endif; ?>
			<?php if (count($status->plugins)) : ?>
				<tr>
					<th>Plugin</th>
					<th>Group</th>
					<th></th>
				</tr>
				<?php foreach ($status->plugins as $plugin) : ?>
					<tr class="row<?php echo($rows++ % 2); ?>">
						<td class="key"><?php echo ucfirst($plugin['name']); ?></td>
						<td class="key"><?php echo ucfirst($plugin['group']); ?></td>
						<td><strong
								style="color: <?php echo ($plugin['result']) ? "green" : "red" ?>"><?php echo ($plugin['result']) ? 'Removed' : 'Not removed'; ?></strong>
						</td>
					</tr>
				<?php endforeach; ?>
			<?php endif; ?>
			</tbody>
		</table>
	<?php
	}

	/**
	 * Bugfix for "DB function returned no error"
	 */
	protected function bugfixDBFunctionReturnedNoError()
	{
		$db = FOFPlatform::getInstance()->getDbo();

		// Fix broken #__assets records
		$query = $db->getQuery(true);
		$query->select('id')
			->from('#__assets')
			->where($db->qn('name') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);

		try
		{
			$ids = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			return;
		}

		if (!empty($ids))
		{
			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__assets')
					->where($db->qn('id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}

		// Fix broken #__extensions records
		$query = $db->getQuery(true);
		$query->select('extension_id')
			->from('#__extensions')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);
		$ids = $db->loadColumn();

		if (!empty($ids))
		{
			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__extensions')
					->where($db->qn('extension_id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}

		// Fix broken #__menu records
		$query = $db->getQuery(true);
		$query->select('id')
			->from('#__menu')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('menutype') . ' = ' . $db->q('main'))
			->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName));
		$db->setQuery($query);
		$ids = $db->loadColumn();

		if (!empty($ids))
		{
			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__menu')
					->where($db->qn('id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}
	}

	/**
	 * Joomla! 1.6+ bugfix for "Can not build admin menus"
	 */
	protected function bugfixCantBuildAdminMenus()
	{
		$db = FOFPlatform::getInstance()->getDbo();

		// If there are multiple #__extensions record, keep one of them
		$query = $db->getQuery(true);
		$query->select('extension_id')
			->from('#__extensions')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);

		try
		{
			$ids = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			return;
		}


		if (count($ids) > 1)
		{
			asort($ids);
			$extension_id = array_shift($ids); // Keep the oldest id

			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__extensions')
					->where($db->qn('extension_id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}

		// If there are multiple assets records, delete all except the oldest one
		$query = $db->getQuery(true);
		$query->select('id')
			->from('#__assets')
			->where($db->qn('name') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);
		$ids = $db->loadObjectList();

		if (count($ids) > 1)
		{
			asort($ids);
			$asset_id = array_shift($ids); // Keep the oldest id

			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__assets')
					->where($db->qn('id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}

		// Remove #__menu records for good measure! –– I think this is not necessary and causes the menu item to
		// disappear on extension update.
		/**
		$query = $db->getQuery(true);
		$query->select('id')
			->from('#__menu')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('menutype') . ' = ' . $db->q('main'))
			->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName));
		$db->setQuery($query);

		try
		{
			$ids1 = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			$ids1 = array();
		}

		if (empty($ids1))
		{
			$ids1 = array();
		}

		$query = $db->getQuery(true);
		$query->select('id')
			->from('#__menu')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('menutype') . ' = ' . $db->q('main'))
			->where($db->qn('link') . ' LIKE ' . $db->q('index.php?option=' . $this->componentName . '&%'));
		$db->setQuery($query);

		try
		{
			$ids2 = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			$ids2 = array();
		}

		if (empty($ids2))
		{
			$ids2 = array();
		}

		$ids = array_merge($ids1, $ids2);

		if (!empty($ids))
		{
			foreach ($ids as $id)
			{
				$query = $db->getQuery(true);
				$query->delete('#__menu')
					->where($db->qn('id') . ' = ' . $db->q($id));
				$db->setQuery($query);

				try
				{
					$db->execute();
				}
				catch (Exception $exc)
				{
					// Nothing
				}
			}
		}
		/**/
	}

	/**
	 * Installs subextensions (modules, plugins) bundled with the main extension
	 *
	 * @param JInstaller $parent
	 *
	 * @return JObject The subextension installation status
	 */
	protected function installSubextensions($parent)
	{
		$src = $parent->getParent()->getPath('source');

		$db = FOFPlatform::getInstance()->getDbo();;

		$status = new JObject();
		$status->modules = array();
		$status->plugins = array();

		// Modules installation
		if (isset($this->installation_queue['modules']) && count($this->installation_queue['modules']))
		{
			foreach ($this->installation_queue['modules'] as $folder => $modules)
			{
				if (count($modules))
				{
					foreach ($modules as $module => $modulePreferences)
					{
						// Install the module
						if (empty($folder))
						{
							$folder = 'site';
						}

						$path = "$src/" . $this->modulesSourcePath . "/$folder/$module";

						if (!is_dir($path))
						{
							$path = "$src/" . $this->modulesSourcePath . "/$folder/mod_$module";
						}

						if (!is_dir($path))
						{
							$path = "$src/" . $this->modulesSourcePath . "/$module";
						}

						if (!is_dir($path))
						{
							$path = "$src/" . $this->modulesSourcePath . "/mod_$module";
						}

						if (!is_dir($path))
						{
							continue;
						}

						// Was the module already installed?
						$sql = $db->getQuery(true)
							->select('COUNT(*)')
							->from('#__modules')
							->where($db->qn('module') . ' = ' . $db->q('mod_' . $module));
						$db->setQuery($sql);

						try
						{
							$count = $db->loadResult();
						}
						catch (Exception $exc)
						{
							$count = 0;
						}

						$installer = new JInstaller;
						$result = $installer->install($path);
						$status->modules[] = array(
							'name'   => 'mod_' . $module,
							'client' => $folder,
							'result' => $result
						);

						// Modify where it's published and its published state
						if (!$count)
						{
							// A. Position and state
							list($modulePosition, $modulePublished) = $modulePreferences;

							$sql = $db->getQuery(true)
								->update($db->qn('#__modules'))
								->set($db->qn('position') . ' = ' . $db->q($modulePosition))
								->where($db->qn('module') . ' = ' . $db->q('mod_' . $module));

							if ($modulePublished)
							{
								$sql->set($db->qn('published') . ' = ' . $db->q('1'));
							}

							$db->setQuery($sql);

							try
							{
								$db->execute();
							}
							catch (Exception $exc)
							{
								// Nothing
							}

							// B. Change the ordering of back-end modules to 1 + max ordering
							if ($folder == 'admin')
							{
								try
								{
									$query = $db->getQuery(true);
									$query->select('MAX(' . $db->qn('ordering') . ')')
										->from($db->qn('#__modules'))
										->where($db->qn('position') . '=' . $db->q($modulePosition));
									$db->setQuery($query);
									$position = $db->loadResult();
									$position++;

									$query = $db->getQuery(true);
									$query->update($db->qn('#__modules'))
										->set($db->qn('ordering') . ' = ' . $db->q($position))
										->where($db->qn('module') . ' = ' . $db->q('mod_' . $module));
									$db->setQuery($query);
									$db->execute();
								}
								catch (Exception $exc)
								{
									// Nothing
								}
							}

							// C. Link to all pages
							try
							{
								$query = $db->getQuery(true);
								$query->select('id')->from($db->qn('#__modules'))
									->where($db->qn('module') . ' = ' . $db->q('mod_' . $module));
								$db->setQuery($query);
								$moduleid = $db->loadResult();

								$query = $db->getQuery(true);
								$query->select('*')->from($db->qn('#__modules_menu'))
									->where($db->qn('moduleid') . ' = ' . $db->q($moduleid));
								$db->setQuery($query);
								$assignments = $db->loadObjectList();
								$isAssigned = !empty($assignments);

								if (!$isAssigned)
								{
									$o = (object)array(
										'moduleid' => $moduleid,
										'menuid'   => 0
									);
									$db->insertObject('#__modules_menu', $o);
								}
							}
							catch (Exception $exc)
							{
								// Nothing
							}
						}
					}
				}
			}
		}

		// Plugins installation
		if (isset($this->installation_queue['plugins']) && count($this->installation_queue['plugins']))
		{
			foreach ($this->installation_queue['plugins'] as $folder => $plugins)
			{
				if (count($plugins))
				{
					foreach ($plugins as $plugin => $published)
					{
						$path = "$src/" . $this->pluginsSourcePath . "/$folder/$plugin";

						if (!is_dir($path))
						{
							$path = "$src/" . $this->pluginsSourcePath . "/$folder/plg_$plugin";
						}

						if (!is_dir($path))
						{
							$path = "$src/" . $this->pluginsSourcePath . "/$plugin";
						}

						if (!is_dir($path))
						{
							$path = "$src/" . $this->pluginsSourcePath . "/plg_$plugin";
						}

						if (!is_dir($path))
						{
							continue;
						}

						// Was the plugin already installed?
						$query = $db->getQuery(true)
							->select('COUNT(*)')
							->from($db->qn('#__extensions'))
							->where($db->qn('element') . ' = ' . $db->q($plugin))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($query);

						try
						{
							$count = $db->loadResult();
						}
						catch (Exception $exc)
						{
							$count = 0;
						}

						$installer = new JInstaller;
						$result = $installer->install($path);

						$status->plugins[] = array('name' => 'plg_' . $plugin, 'group' => $folder, 'result' => $result);

						if ($published && !$count)
						{
							$query = $db->getQuery(true)
								->update($db->qn('#__extensions'))
								->set($db->qn('enabled') . ' = ' . $db->q('1'))
								->where($db->qn('element') . ' = ' . $db->q($plugin))
								->where($db->qn('folder') . ' = ' . $db->q($folder));
							$db->setQuery($query);

							try
							{
								$db->execute();
							}
							catch (Exception $exc)
							{
								// Nothing
							}
						}
					}
				}
			}
		}

		// Clear com_modules and com_plugins cache (needed when we alter module/plugin state)
		FOFUtilsCacheCleaner::clearPluginsAndModulesCache();

		return $status;
	}

	/**
	 * Uninstalls subextensions (modules, plugins) bundled with the main extension
	 *
	 * @param   JInstaller $parent The parent object
	 *
	 * @return  stdClass  The subextension uninstallation status
	 */
	protected function uninstallSubextensions($parent)
	{
		$db = FOFPlatform::getInstance()->getDbo();

		$status = new stdClass();
		$status->modules = array();
		$status->plugins = array();

		$src = $parent->getParent()->getPath('source');

		// Modules uninstallation
		if (isset($this->installation_queue['modules']) && count($this->installation_queue['modules']))
		{
			foreach ($this->installation_queue['modules'] as $folder => $modules)
			{
				if (count($modules))
				{
					foreach ($modules as $module => $modulePreferences)
					{
						// Find the module ID
						$sql = $db->getQuery(true)
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('element') . ' = ' . $db->q('mod_' . $module))
							->where($db->qn('type') . ' = ' . $db->q('module'));
						$db->setQuery($sql);

						try
						{
							$id = $db->loadResult();
						}
						catch (Exception $exc)
						{
							$id = 0;
						}

						// Uninstall the module
						if ($id)
						{
							$installer = new JInstaller;
							$result = $installer->uninstall('module', $id, 1);
							$status->modules[] = array(
								'name'   => 'mod_' . $module,
								'client' => $folder,
								'result' => $result
							);
						}
					}
				}
			}
		}

		// Plugins uninstallation
		if (isset($this->installation_queue['plugins']) && count($this->installation_queue['plugins']))
		{
			foreach ($this->installation_queue['plugins'] as $folder => $plugins)
			{
				if (count($plugins))
				{
					foreach ($plugins as $plugin => $published)
					{
						$sql = $db->getQuery(true)
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('type') . ' = ' . $db->q('plugin'))
							->where($db->qn('element') . ' = ' . $db->q($plugin))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($sql);

						try
						{
							$id = $db->loadResult();
						}
						catch (Exception $exc)
						{
							$id = 0;
						}

						if ($id)
						{
							$installer = new JInstaller;
							$result = $installer->uninstall('plugin', $id, 1);
							$status->plugins[] = array(
								'name'   => 'plg_' . $plugin,
								'group'  => $folder,
								'result' => $result
							);
						}
					}
				}
			}
		}

		// Clear com_modules and com_plugins cache (needed when we alter module/plugin state)
		FOFUtilsCacheCleaner::clearPluginsAndModulesCache();

		return $status;
	}

	/**
	 * Removes obsolete files and folders
	 *
	 * @param   array $removeList The files and directories to remove
	 */
	protected function removeFilesAndFolders($removeList)
	{
		// Remove files
		if (isset($removeList['files']) && !empty($removeList['files']))
		{
			foreach ($removeList['files'] as $file)
			{
				$f = JPATH_ROOT . '/' . $file;

				if (!JFile::exists($f))
				{
					continue;
				}

				JFile::delete($f);
			}
		}

		// Remove folders
		if (isset($removeList['folders']) && !empty($removeList['folders']))
		{
			foreach ($removeList['folders'] as $folder)
			{
				$f = JPATH_ROOT . '/' . $folder;

				if (!JFolder::exists($f))
				{
					continue;
				}

				JFolder::delete($f);
			}
		}
	}

	/**
	 * Installs FOF if necessary
	 *
	 * @param   JInstaller $parent The parent object
	 *
	 * @return  array  The installation status
	 */
	protected function installFOF($parent)
	{
		// Get the source path
		$src = $parent->getParent()->getPath('source');
		$source = $src . '/' . $this->fofSourcePath;

		if (!JFolder::exists($source))
		{
			return array(
				'required'  => false,
				'installed' => false,
				'version'   => '0.0.0',
				'date'      => '2011-01-01',
			);
		}

		// Get the target path
		if (!defined('JPATH_LIBRARIES'))
		{
			$target = JPATH_ROOT . '/libraries/f0f';
		}
		else
		{
			$target = JPATH_LIBRARIES . '/f0f';
		}

		// Do I have to install FOF?
		$haveToInstallFOF = false;

		if (!JFolder::exists($target))
		{
			// FOF is not installed; install now
			$haveToInstallFOF = true;
		}
		else
		{
			// FOF is already installed; check the version
			$fofVersion = array();

			if (JFile::exists($target . '/version.txt'))
			{
				$rawData = JFile::read($target . '/version.txt');
				$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
				$info = explode("\n", $rawData);
				$fofVersion['installed'] = array(
					'version' => trim($info[0]),
					'date'    => new JDate(trim($info[1]))
				);
			}
			else
			{
				$fofVersion['installed'] = array(
					'version' => '0.0',
					'date'    => new JDate('2011-01-01')
				);
			}

			$rawData = @file_get_contents($source . '/version.txt');
			$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
			$info = explode("\n", $rawData);

			$fofVersion['package'] = array(
				'version' => trim($info[0]),
				'date'    => new JDate(trim($info[1]))
			);

			$haveToInstallFOF = $fofVersion['package']['date']->toUNIX() > $fofVersion['installed']['date']->toUNIX();
		}

		$installedFOF = false;

		if ($haveToInstallFOF)
		{
			$versionSource = 'package';
			$installer = new JInstaller;
			$installedFOF = $installer->install($source);
		}
		else
		{
			$versionSource = 'installed';
		}

		if (!isset($fofVersion))
		{
			$fofVersion = array();

			if (JFile::exists($target . '/version.txt'))
			{
				$rawData = @file_get_contents($source . '/version.txt');
				$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
				$info = explode("\n", $rawData);
				$fofVersion['installed'] = array(
					'version' => trim($info[0]),
					'date'    => new JDate(trim($info[1]))
				);
			}
			else
			{
				$fofVersion['installed'] = array(
					'version' => '0.0',
					'date'    => new JDate('2011-01-01')
				);
			}

			$rawData = @file_get_contents($source . '/version.txt');
			$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
			$info = explode("\n", $rawData);

			$fofVersion['package'] = array(
				'version' => trim($info[0]),
				'date'    => new JDate(trim($info[1]))
			);

			$versionSource = 'installed';
		}

		if (!($fofVersion[$versionSource]['date'] instanceof JDate))
		{
			$fofVersion[$versionSource]['date'] = new JDate();
		}

		return array(
			'required'  => $haveToInstallFOF,
			'installed' => $installedFOF,
			'version'   => $fofVersion[$versionSource]['version'],
			'date'      => $fofVersion[$versionSource]['date']->format('Y-m-d'),
		);
	}

	/**
	 * Installs Akeeba Strapper if necessary
	 *
	 * @param   JInstaller $parent The parent object
	 *
	 * @return  array  The installation status
	 */
	protected function installStrapper($parent)
	{
		$src = $parent->getParent()->getPath('source');
		$source = $src . '/' . $this->strapperSourcePath;

		$target = JPATH_ROOT . '/media/akeeba_strapper';

		if (!JFolder::exists($source))
		{
			return array(
				'required'  => false,
				'installed' => false,
				'version'   => '0.0.0',
				'date'      => '2011-01-01',
			);
		}

		$haveToInstallStrapper = false;

		if (!JFolder::exists($target))
		{
			$haveToInstallStrapper = true;
		}
		else
		{
			$strapperVersion = array();

			if (JFile::exists($target . '/version.txt'))
			{
				$rawData = JFile::read($target . '/version.txt');
				$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
				$info = explode("\n", $rawData);
				$strapperVersion['installed'] = array(
					'version' => trim($info[0]),
					'date'    => new JDate(trim($info[1]))
				);
			}
			else
			{
				$strapperVersion['installed'] = array(
					'version' => '0.0',
					'date'    => new JDate('2011-01-01')
				);
			}

			$rawData = JFile::read($source . '/version.txt');
			$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
			$info = explode("\n", $rawData);
			$strapperVersion['package'] = array(
				'version' => trim($info[0]),
				'date'    => new JDate(trim($info[1]))
			);

			$haveToInstallStrapper = $strapperVersion['package']['date']->toUNIX() > $strapperVersion['installed']['date']->toUNIX();
		}

		$installedStraper = false;

		if ($haveToInstallStrapper)
		{
			$versionSource = 'package';
			$installer = new JInstaller;
			$installedStraper = $installer->install($source);
		}
		else
		{
			$versionSource = 'installed';
		}

		if (!isset($strapperVersion))
		{
			$strapperVersion = array();

			if (JFile::exists($target . '/version.txt'))
			{
				$rawData = JFile::read($target . '/version.txt');
				$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
				$info = explode("\n", $rawData);
				$strapperVersion['installed'] = array(
					'version' => trim($info[0]),
					'date'    => new JDate(trim($info[1]))
				);
			}
			else
			{
				$strapperVersion['installed'] = array(
					'version' => '0.0',
					'date'    => new JDate('2011-01-01')
				);
			}

			$rawData = JFile::read($source . '/version.txt');
			$rawData = ($rawData === false) ? "0.0.0\n2011-01-01\n" : $rawData;
			$info = explode("\n", $rawData);

			$strapperVersion['package'] = array(
				'version' => trim($info[0]),
				'date'    => new JDate(trim($info[1]))
			);

			$versionSource = 'installed';
		}

		if (!($strapperVersion[$versionSource]['date'] instanceof JDate))
		{
			$strapperVersion[$versionSource]['date'] = new JDate();
		}

		return array(
			'required'  => $haveToInstallStrapper,
			'installed' => $installedStraper,
			'version'   => $strapperVersion[$versionSource]['version'],
			'date'      => $strapperVersion[$versionSource]['date']->format('Y-m-d'),
		);
	}

	/**
	 * Uninstalls obsolete subextensions (modules, plugins) bundled with the main extension
	 *
	 * @param   JInstaller $parent The parent object
	 *
	 * @return  stdClass The subextension uninstallation status
	 */
	protected function uninstallObsoleteSubextensions($parent)
	{
		JLoader::import('joomla.installer.installer');

		$db = FOFPlatform::getInstance()->getDbo();

		$status = new stdClass();
		$status->modules = array();
		$status->plugins = array();

		$src = $parent->getParent()->getPath('source');

		// Modules uninstallation
		if (isset($this->uninstallation_queue['modules']) && count($this->uninstallation_queue['modules']))
		{
			foreach ($this->uninstallation_queue['modules'] as $folder => $modules)
			{
				if (count($modules))
				{
					foreach ($modules as $module)
					{
						// Find the module ID
						$sql = $db->getQuery(true)
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('element') . ' = ' . $db->q('mod_' . $module))
							->where($db->qn('type') . ' = ' . $db->q('module'));
						$db->setQuery($sql);
						$id = $db->loadResult();
						// Uninstall the module
						if ($id)
						{
							$installer = new JInstaller;
							$result = $installer->uninstall('module', $id, 1);
							$status->modules[] = array(
								'name'   => 'mod_' . $module,
								'client' => $folder,
								'result' => $result
							);
						}
					}
				}
			}
		}

		// Plugins uninstallation
		if (isset($this->uninstallation_queue['plugins']) && count($this->uninstallation_queue['plugins']))
		{
			foreach ($this->uninstallation_queue['plugins'] as $folder => $plugins)
			{
				if (count($plugins))
				{
					foreach ($plugins as $plugin)
					{
						$sql = $db->getQuery(true)
							->select($db->qn('extension_id'))
							->from($db->qn('#__extensions'))
							->where($db->qn('type') . ' = ' . $db->q('plugin'))
							->where($db->qn('element') . ' = ' . $db->q($plugin))
							->where($db->qn('folder') . ' = ' . $db->q($folder));
						$db->setQuery($sql);

						$id = $db->loadResult();
						if ($id)
						{
							$installer = new JInstaller;
							$result = $installer->uninstall('plugin', $id, 1);
							$status->plugins[] = array(
								'name'   => 'plg_' . $plugin,
								'group'  => $folder,
								'result' => $result
							);
						}
					}
				}
			}
		}

		return $status;
	}

	/**
	 * @param JInstallerAdapterComponent $parent
	 *
	 * @return bool
	 *
	 * @throws Exception When the Joomla! menu is FUBAR
	 */
	private function _createAdminMenus($parent)
	{
		$db = $db = FOFPlatform::getInstance()->getDbo();

		/** @var JTableMenu $table */
		$table = JTable::getInstance('menu');
		$option = $parent->get('element');

		// If a component exists with this option in the table then we don't need to add menus - check only 'main' menutype
		$query = $db->getQuery(true)
			->select('m.id, e.extension_id')
			->from('#__menu AS m')
			->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id')
			->where('m.parent_id = 1')
			->where('m.client_id = 1')
			->where('m.menutype = ' . $db->q('main'))
			->where($db->qn('e') . '.' . $db->qn('type') . ' = ' . $db->q('component'))
			->where('e.element = ' . $db->quote($option));

		$db->setQuery($query);

		$componentrow = $db->loadObject();

		// Check if menu items exist
		if ($componentrow)
		{
			// @todo Return if the menu item already exists to save some time
			//return true;
		}

		// Let's find the extension id
		$query->clear()
			->select('e.extension_id')
			->from('#__extensions AS e')
			->where('e.type = ' . $db->quote('component'))
			->where('e.element = ' . $db->quote($option));
		$db->setQuery($query);
		$component_id = $db->loadResult();

		// Ok, now its time to handle the menus.  Start with the component root menu, then handle submenus.
		$menuElement = $parent->get('manifest')->administration->menu;

		// We need to insert the menu item as the last child of Joomla!'s menu root node. By default this is the
		// menu item with ID=1. However, some crappy upgrade scripts enjoy screwing it up. Hey, ho, the workaround
		// way I go.
		$query = $db->getQuery(true)
			->select($db->qn('id'))
			->from($db->qn('#__menu'))
			->where($db->qn('id') . ' = ' . $db->q(1));
		$rootItemId = $db->setQuery($query)->loadResult();

		if (is_null($rootItemId))
		{
			// Guess what? The Problem has happened. Let's find the root node by title.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// For crying out loud, did that idiot changed the title too?! Let's find it by alias.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('alias') . ' = ' . $db->q('root'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// Dude. Dude! Duuuuuuude! The alias is screwed up, too?! Find it by component ID.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('component_id') . ' = ' . $db->q('0'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// Your site is more of a "shite" than a "site". Let's try with minimum lft value.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->order($db->qn('lft') . ' ASC');
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// I quit. Your site is broken. What the hell are you doing with it? I'll just throw an error.
			throw new Exception("Your site is broken. There is no root menu item. As a result it is impossible to create menu items. The installation of this component has failed. Please fix your database and retry!", 500);
		}

		if ($menuElement)
		{
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string)trim($menuElement);
			$data['alias'] = (string)$menuElement;
			$data['link'] = 'index.php?option=' . $option;
			$data['type'] = 'component';
			$data['published'] = 0;
			$data['parent_id'] = 1;
			$data['component_id'] = $component_id;
			$data['img'] = ((string)$menuElement->attributes()->img) ? (string)$menuElement->attributes()->img : 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';
		}
		// No menu element was specified, Let's make a generic menu item
		else
		{
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = $option;
			$data['alias'] = $option;
			$data['link'] = 'index.php?option=' . $option;
			$data['type'] = 'component';
			$data['published'] = 0;
			$data['parent_id'] = 1;
			$data['component_id'] = $component_id;
			$data['img'] = 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';
		}

		try
		{
			$table->setLocation($rootItemId, 'last-child');
		}
		catch (InvalidArgumentException $e)
		{
			if (class_exists('JLog'))
			{
				JLog::add($e->getMessage(), JLog::WARNING, 'jerror');
			}

			return false;
		}

		if (!$table->bind($data) || !$table->check() || !$table->store())
		{
			// The menu item already exists. Delete it and retry instead of throwing an error.
			$query->clear()
				->select('id')
				->from('#__menu')
				->where('menutype = ' . $db->quote('main'))
				->where('client_id = 1')
				->where('link = ' . $db->quote('index.php?option=' . $option))
				->where('type = ' . $db->quote('component'))
				->where('parent_id = 1')
				->where('home = 0');

			$db->setQuery($query);
			$menu_ids_level1 = $db->loadColumn();

			if (empty($menu_ids_level1))
			{
				// Oops! Could not get the menu ID. Go back and rollback changes.
				JError::raiseWarning(1, $table->getError());

				return false;
			}
			else
			{
				$ids = implode(',', $menu_ids_level1);

				$query->clear()
					->select('id')
					->from('#__menu')
					->where('menutype = ' . $db->quote('main'))
					->where('client_id = 1')
					->where('type = ' . $db->quote('component'))
					->where('parent_id in (' . $ids . ')')
					->where('level = 2')
					->where('home = 0');

				$db->setQuery($query);
				$menu_ids_level2 = $db->loadColumn();

				$ids = implode(',', array_merge($menu_ids_level1, $menu_ids_level2));

				// Remove the old menu item
				$query->clear()
					->delete('#__menu')
					->where('id in (' . $ids . ')');

				$db->setQuery($query);
				$db->query();

				// Retry creating the menu item
				$table->setLocation($rootItemId, 'last-child');

				if (!$table->bind($data) || !$table->check() || !$table->store())
				{
					// Install failed, warn user and rollback changes
					JError::raiseWarning(1, $table->getError());

					return false;
				}
			}
		}

		/*
		 * Since we have created a menu item, we add it to the installation step stack
		 * so that if we have to rollback the changes we can undo it.
		 */
		$parent->getParent()->pushStep(array('type' => 'menu', 'id' => $component_id));

		/*
		 * Process SubMenus
		 */

		if (!$parent->get('manifest')->administration->submenu)
		{
			return true;
		}

		$parent_id = $table->id;

		foreach ($parent->get('manifest')->administration->submenu->menu as $child)
		{
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string)trim($child);
			$data['alias'] = (string)$child;
			$data['type'] = 'component';
			$data['published'] = 0;
			$data['parent_id'] = $parent_id;
			$data['component_id'] = $component_id;
			$data['img'] = ((string)$child->attributes()->img) ? (string)$child->attributes()->img : 'class:component';
			$data['home'] = 0;

			// Set the sub menu link
			if ((string)$child->attributes()->link)
			{
				$data['link'] = 'index.php?' . $child->attributes()->link;
			}
			else
			{
				$request = array();

				if ((string)$child->attributes()->act)
				{
					$request[] = 'act=' . $child->attributes()->act;
				}

				if ((string)$child->attributes()->task)
				{
					$request[] = 'task=' . $child->attributes()->task;
				}

				if ((string)$child->attributes()->controller)
				{
					$request[] = 'controller=' . $child->attributes()->controller;
				}

				if ((string)$child->attributes()->view)
				{
					$request[] = 'view=' . $child->attributes()->view;
				}

				if ((string)$child->attributes()->layout)
				{
					$request[] = 'layout=' . $child->attributes()->layout;
				}

				if ((string)$child->attributes()->sub)
				{
					$request[] = 'sub=' . $child->attributes()->sub;
				}

				$qstring = (count($request)) ? '&' . implode('&', $request) : '';
				$data['link'] = 'index.php?option=' . $option . $qstring;
			}

			$table = JTable::getInstance('menu');

			try
			{
				$table->setLocation($parent_id, 'last-child');
			}
			catch (InvalidArgumentException $e)
			{
				return false;
			}

			if (!$table->bind($data) || !$table->check() || !$table->store())
			{
				// Install failed, rollback changes
				return false;
			}

			/*
			 * Since we have created a menu item, we add it to the installation step stack
			 * so that if we have to rollback the changes we can undo it.
			 */
			$parent->getParent()->pushStep(array('type' => 'menu', 'id' => $component_id));
		}

		return true;
	}

	/**
	 * Make sure the Component menu items are really published!
	 *
	 * @param JInstallerAdapterComponent $parent
	 *
	 * @return bool
	 */
	private function _reallyPublishAdminMenuItems($parent)
	{
		$db = FOFPlatform::getInstance()->getDbo();

		$option = $parent->get('element');

		$query = $db->getQuery(true)
			->update('#__menu AS m')
			->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id')
			->set($db->qn('published') . ' = ' . $db->q(1))
			->where('m.parent_id = 1')
			->where('m.client_id = 1')
			->where('m.menutype = ' . $db->quote('main'))
			->where('e.type = ' . $db->quote('component'))
			->where('e.element = ' . $db->quote($option));

		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (Exception $e)
		{
			// If it fails, it fails. Who cares.
		}
	}

	/**
	 * Tells Joomla! to rebuild its menu structure to make triple-sure that the Components menu items really do exist
	 * in the correct place and can really be rendered.
	 */
	private function _rebuildMenu()
	{
		/** @var JTableMenu $table */
		$table = JTable::getInstance('menu');
		$db = FOFPlatform::getInstance()->getDbo();

		// We need to rebuild the menu based on its root item. By default this is the menu item with ID=1. However, some
		// crappy upgrade scripts enjoy screwing it up. Hey, ho, the workaround way I go.
		$query = $db->getQuery(true)
			->select($db->qn('id'))
			->from($db->qn('#__menu'))
			->where($db->qn('id') . ' = ' . $db->q(1));
		$rootItemId = $db->setQuery($query)->loadResult();

		if (is_null($rootItemId))
		{
			// Guess what? The Problem has happened. Let's find the root node by title.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('title') . ' = ' . $db->q('Menu_Item_Root'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// For crying out loud, did that idiot changed the title too?! Let's find it by alias.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('alias') . ' = ' . $db->q('root'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// Dude. Dude! Duuuuuuude! The alias is screwed up, too?! Find it by component ID.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->where($db->qn('component_id') . ' = ' . $db->q('0'));
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// Your site is more of a "shite" than a "site". Let's try with minimum lft value.
			$rootItemId = null;
			$query = $db->getQuery(true)
				->select($db->qn('id'))
				->from($db->qn('#__menu'))
				->order($db->qn('lft') . ' ASC');
			$rootItemId = $db->setQuery($query, 0, 1)->loadResult();
		}

		if (is_null($rootItemId))
		{
			// I quit. Your site is broken.
			return false;
		}

		$table->rebuild($rootItemId);
	}

	/**
	 * Adds or updates a post-installation message (PIM) definition for Joomla! 3.2 or later. You can use this in your
	 * post-installation script using this code:
	 *
	 * The $options array contains the following mandatory keys:
	 *
	 * extension_id        The numeric ID of the extension this message is for (see the #__extensions table)
	 *
	 * type                One of message, link or action. Their meaning is:
	 *                    message        Informative message. The user can dismiss it.
	 *                    link        The action button links to a URL. The URL is defined in the action parameter.
	 *                  action      A PHP action takes place when the action button is clicked. You need to specify the
	 *                              action_file (RAD path to the PHP file) and action (PHP function name) keys. See
	 *                              below for more information.
	 *
	 * title_key        The JText language key for the title of this PIM
	 *                    Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_TITLE
	 *
	 * description_key    The JText language key for the main body (description) of this PIM
	 *                    Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_DESCRIPTION
	 *
	 * action_key        The JText language key for the action button. Ignored and not required when type=message
	 *                    Example: COM_FOOBAR_POSTINSTALL_MESSAGEONE_ACTION
	 *
	 * language_extension    The extension name which holds the language keys used above. For example, com_foobar,
	 *                    mod_something, plg_system_whatever, tpl_mytemplate
	 *
	 * language_client_id   Should we load the front-end (0) or back-end (1) language keys?
	 *
	 * version_introduced   Which was the version of your extension where this message appeared for the first time?
	 *                        Example: 3.2.1
	 *
	 * enabled              Must be 1 for this message to be enabled. If you omit it, it defaults to 1.
	 *
	 * condition_file        The RAD path to a PHP file containing a PHP function which determines whether this message
	 *                        should be shown to the user. @see FOFTemplateUtils::parsePath() for RAD path format. Joomla!
	 *                        will include this file before calling the condition_method.
	 *                      Example:   admin://components/com_foobar/helpers/postinstall.php
	 *
	 * condition_method     The name of a PHP function which will be used to determine whether to show this message to
	 *                      the user. This must be a simple PHP user function (not a class method, static method etc)
	 *                        which returns true to show the message and false to hide it. This function is defined in the
	 *                        condition_file.
	 *                        Example: com_foobar_postinstall_messageone_condition
	 *
	 * When type=message no additional keys are required.
	 *
	 * When type=link the following additional keys are required:
	 *
	 * action                The URL which will open when the user clicks on the PIM's action button
	 *                        Example:    index.php?option=com_foobar&view=tools&task=installSampleData
	 *
	 * Then type=action the following additional keys are required:
	 *
	 * action_file            The RAD path to a PHP file containing a PHP function which performs the action of this PIM.
	 *
	 * @see                   FOFTemplateUtils::parsePath() for RAD path format. Joomla! will include this file
	 *                        before calling the function defined in the action key below.
	 *                        Example:   admin://components/com_foobar/helpers/postinstall.php
	 *
	 * action                The name of a PHP function which will be used to run the action of this PIM. This must be a
	 *                      simple PHP user function (not a class method, static method etc) which returns no result.
	 *                        Example: com_foobar_postinstall_messageone_action
	 *
	 * @param array $options See description
	 *
	 * @return  void
	 *
	 * @throws Exception
	 */
	protected function addPostInstallationMessage(array $options)
	{
		// Make sure there are options set
		if (!is_array($options))
		{
			throw new Exception('Post-installation message definitions must be of type array', 500);
		}

		// Initialise array keys
		$defaultOptions = array(
			'extension_id'       => '',
			'type'               => '',
			'title_key'          => '',
			'description_key'    => '',
			'action_key'         => '',
			'language_extension' => '',
			'language_client_id' => '',
			'action_file'        => '',
			'action'             => '',
			'condition_file'     => '',
			'condition_method'   => '',
			'version_introduced' => '',
			'enabled'            => '1',
		);

		$options = array_merge($defaultOptions, $options);

		// Array normalisation. Removes array keys not belonging to a definition.
		$defaultKeys = array_keys($defaultOptions);
		$allKeys = array_keys($options);
		$extraKeys = array_diff($allKeys, $defaultKeys);

		if (!empty($extraKeys))
		{
			foreach ($extraKeys as $key)
			{
				unset($options[$key]);
			}
		}

		// Normalisation of integer values
		$options['extension_id'] = (int)$options['extension_id'];
		$options['language_client_id'] = (int)$options['language_client_id'];
		$options['enabled'] = (int)$options['enabled'];

		// Normalisation of 0/1 values
		foreach (array('language_client_id', 'enabled') as $key)
		{
			$options[$key] = $options[$key] ? 1 : 0;
		}

		// Make sure there's an extension_id
		if (!(int)$options['extension_id'])
		{
			throw new Exception('Post-installation message definitions need an extension_id', 500);
		}

		// Make sure there's a valid type
		if (!in_array($options['type'], array('message', 'link', 'action')))
		{
			throw new Exception('Post-installation message definitions need to declare a type of message, link or action', 500);
		}

		// Make sure there's a title key
		if (empty($options['title_key']))
		{
			throw new Exception('Post-installation message definitions need a title key', 500);
		}

		// Make sure there's a description key
		if (empty($options['description_key']))
		{
			throw new Exception('Post-installation message definitions need a description key', 500);
		}

		// If the type is anything other than message you need an action key
		if (($options['type'] != 'message') && empty($options['action_key']))
		{
			throw new Exception('Post-installation message definitions need an action key when they are of type "' . $options['type'] . '"', 500);
		}

		// You must specify the language extension
		if (empty($options['language_extension']))
		{
			throw new Exception('Post-installation message definitions need to specify which extension contains their language keys', 500);
		}

		// The action file and method are only required for the "action" type
		if ($options['type'] == 'action')
		{
			if (empty($options['action_file']))
			{
				throw new Exception('Post-installation message definitions need an action file when they are of type "action"', 500);
			}

			$file_path = FOFTemplateUtils::parsePath($options['action_file'], true);

			if (!@is_file($file_path))
			{
				throw new Exception('The action file ' . $options['action_file'] . ' of your post-installation message definition does not exist', 500);
			}

			if (empty($options['action']))
			{
				throw new Exception('Post-installation message definitions need an action (function name) when they are of type "action"', 500);
			}
		}

		if ($options['type'] == 'link')
		{
			if (empty($options['link']))
			{
				throw new Exception('Post-installation message definitions need an action (URL) when they are of type "link"', 500);
			}
		}

		// The condition file and method are only required when the type is not "message"
		if ($options['type'] != 'message')
		{
			if (empty($options['condition_file']))
			{
				throw new Exception('Post-installation message definitions need a condition file when they are of type "' . $options['type'] . '"', 500);
			}

			$file_path = FOFTemplateUtils::parsePath($options['condition_file'], true);

			if (!@is_file($file_path))
			{
				throw new Exception('The condition file ' . $options['condition_file'] . ' of your post-installation message definition does not exist', 500);
			}

			if (empty($options['condition_method']))
			{
				throw new Exception('Post-installation message definitions need a condition method (function name) when they are of type "' . $options['type'] . '"', 500);
			}
		}

		// Check if the definition exists
		$tableName = '#__postinstall_messages';

		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn($tableName))
			->where($db->qn('extension_id') . ' = ' . $db->q($options['extension_id']))
			->where($db->qn('type') . ' = ' . $db->q($options['type']))
			->where($db->qn('title_key') . ' = ' . $db->q($options['title_key']));
		$existingRow = $db->setQuery($query)->loadAssoc();

		// Is the existing definition the same as the one we're trying to save (ignore the enabled flag)?
		if (!empty($existingRow))
		{
			$same = true;

			foreach ($options as $k => $v)
			{
				if ($k == 'enabled')
				{
					continue;
				}

				if ($existingRow[$k] != $v)
				{
					$same = false;
					break;
				}
			}

			// Trying to add the same row as the existing one; quit
			if ($same)
			{
				return;
			}

			// Otherwise it's not the same row. Remove the old row before insert a new one.
			$query = $db->getQuery(true)
				->delete($db->qn($tableName))
				->where($db->q('extension_id') . ' = ' . $db->q($options['extension_id']))
				->where($db->q('type') . ' = ' . $db->q($options['type']))
				->where($db->q('title_key') . ' = ' . $db->q($options['title_key']));
			$db->setQuery($query)->execute();
		}

		// Insert the new row
		$options = (object)$options;
		$db->insertObject($tableName, $options);
	}

	/**
	 * Applies the post-installation messages for Joomla! 3.2 or later
	 *
	 * @return void
	 */
	protected function _applyPostInstallationMessages()
	{
		// Make sure it's Joomla! 3.2.0 or later
		if (!version_compare(JVERSION, '3.2.0', 'ge'))
		{
			return;
		}

		// Make sure there are post-installation messages
		if (empty($this->postInstallationMessages))
		{
			return;
		}

		// Get the extension ID for our component
		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);
		$query->select('extension_id')
			->from('#__extensions')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);

		try
		{
			$ids = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			return;
		}

		if (empty($ids))
		{
			return;
		}

		$extension_id = array_shift($ids);

		foreach ($this->postInstallationMessages as $message)
		{
			$message['extension_id'] = $extension_id;
			$this->addPostInstallationMessage($message);
		}
	}

	protected function uninstallPostInstallationMessages()
	{
		// Make sure it's Joomla! 3.2.0 or later
		if (!version_compare(JVERSION, '3.2.0', 'ge'))
		{
			return;
		}

		// Make sure there are post-installation messages
		if (empty($this->postInstallationMessages))
		{
			return;
		}

		// Get the extension ID for our component
		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);
		$query->select('extension_id')
			->from('#__extensions')
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q($this->componentName));
		$db->setQuery($query);

		try
		{
			$ids = $db->loadColumn();
		}
		catch (Exception $exc)
		{
			return;
		}

		if (empty($ids))
		{
			return;
		}

		$extension_id = array_shift($ids);

		$query = $db->getQuery(true)
			->delete($db->qn('#__postinstall_messages'))
			->where($db->qn('extension_id') . ' = ' . $db->q($extension_id));

		try
		{
			$db->setQuery($query)->execute();
		}
		catch (Exception $e)
		{
			return;
		}
	}
}
fof/utils/update/update.php000064400000101122152177723700011743 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (version_compare(JVERSION, '2.5.0', 'lt'))
{
	jimport('joomla.updater.updater');
}

/**
 * A helper Model to interact with Joomla!'s extensions update feature
 */
class FOFUtilsUpdate extends FOFModel
{
	/** @var JUpdater The Joomla! updater object */
	protected $updater = null;

	/** @var int The extension_id of this component */
	protected $extension_id = 0;

	/** @var string The currently installed version, as reported by the #__extensions table */
	protected $version = 'dev';

	/** @var string The machine readable name of the component e.g. com_something */
	protected $component = 'com_foobar';

	/** @var string The human readable name of the component e.g. Your Component's Name. Used for emails. */
	protected $componentDescription = 'Foobar';

	/** @var string The URL to the component's update XML stream */
	protected $updateSite = null;

	/** @var string The name to the component's update site (description of the update XML stream) */
	protected $updateSiteName = null;

	/** @var string The extra query to append to (commercial) components' download URLs */
	protected $extraQuery = null;

	/** @var string The common parameters' key, used for storing data in the #__akeeba_common table */
	protected $commonKey = 'foobar';

	/**
	 * The common parameters table. It's a simple table with key(VARCHAR) and value(LONGTEXT) fields.
	 * Here is an example MySQL CREATE TABLE command to make this kind of table:
	 *
	 * CREATE TABLE `#__akeeba_common` (
	 * 	`key` varchar(255) NOT NULL,
	 * 	`value` longtext NOT NULL,
	 * 	PRIMARY KEY (`key`)
	 * 	) DEFAULT COLLATE utf8_general_ci CHARSET=utf8;
	 *
	 * @var  string
	 */
	protected $commonTable = '#__akeeba_common';

	/**
	 * Subject of the component update emails
	 *
	 * @var  string
	 */
	protected $updateEmailSubject = 'THIS EMAIL IS SENT FROM YOUR SITE "[SITENAME]" - Update available for [COMPONENT], new version [VERSION]';

	/**
	 * Body of the component update email
	 *
	 * @var  string
	 */
	protected $updateEmailBody= <<< ENDBLOCK
This email IS NOT sent by the authors of [COMPONENT].
It is sent automatically by your own site, [SITENAME].

================================================================================
UPDATE INFORMATION
================================================================================

Your site has determined that there is an updated version of [COMPONENT]
available for download.

New version number: [VERSION]

This email is sent to you by your site to remind you of this fact. The authors
of the software will never contact you about available updates.

================================================================================
WHY AM I RECEIVING THIS EMAIL?
================================================================================

This email has been automatically sent by a CLI script or Joomla! plugin you, or
the person who built or manages your site, has installed and explicitly
activated. This script or plugin looks for updated versions of the software and
sends an email notification to all Super Users. You will receive several similar
emails from your site, up to 6 times per day, until you either update the
software or disable these emails.

To disable these emails, please contact your site administrator.

If you do not understand what this means, please do not contact the authors of
the software. They are NOT sending you this email and they cannot help you.
Instead, please contact the person who built or manages your site.

================================================================================
WHO SENT ME THIS EMAIL?
================================================================================

This email is sent to you by your own site, [SITENAME]
ENDBLOCK;

	/**
	 * Public constructor. Initialises the protected members as well. Useful $config keys:
	 * update_component		The component name, e.g. com_foobar
	 * update_version		The default version if the manifest cache is unreadable
	 * update_site			The URL to the component's update XML stream
	 * update_extraquery	The extra query to append to (commercial) components' download URLs
	 * update_sitename		The update site's name (description)
	 *
	 * @param array $config
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		// Get an instance of the updater class
		$this->updater = JUpdater::getInstance();

		// Get the component name
		if (isset($config['update_component']))
		{
			$this->component = $config['update_component'];
		}
		else
		{
			$this->component = $this->input->getCmd('option', '');
		}

		// Get the component description
		if (isset($config['update_component_description']))
		{
			$this->component = $config['update_component_description'];
		}
		else
		{
			// Try to auto-translate (hopefully you've loaded the language files)
			$key = strtoupper($this->component);
			$description = JText::_($key);
		}

		// Get the component version
		if (isset($config['update_version']))
		{
			$this->version = $config['update_version'];
		}

		// Get the common key
		if (isset($config['common_key']))
		{
			$this->commonKey = $config['common_key'];
		}
		else
		{
			// Convert com_foobar, pkg_foobar etc to "foobar"
			$this->commonKey = substr($this->component, 4);
		}

		// Get the update site
		if (isset($config['update_site']))
		{
			$this->updateSite = $config['update_site'];
		}

		// Get the extra query
		if (isset($config['update_extraquery']))
		{
			$this->extraQuery = $config['update_extraquery'];
		}

		// Get the update site's name
		if (isset($config['update_sitename']))
		{
			$this->updateSiteName = $config['update_sitename'];
		}

		// Find the extension ID
		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('element') . ' = ' . $db->q($this->component));
		$db->setQuery($query);
		$extension = $db->loadObject();

		if (is_object($extension))
		{
			$this->extension_id = $extension->extension_id;
			$data = json_decode($extension->manifest_cache, true);

			if (isset($data['version']))
			{
				$this->version = $data['version'];
			}
		}
	}

	/**
	 * Retrieves the update information of the component, returning an array with the following keys:
	 *
	 * hasUpdate	True if an update is available
	 * version		The version of the available update
	 * infoURL		The URL to the download page of the update
	 *
	 * @param   bool    $force            Set to true if you want to forcibly reload the update information
	 * @param   string  $preferredMethod  Preferred update method: 'joomla' or 'classic'
	 *
	 * @return  array  See the method description for more information
	 */
	public function getUpdates($force = false, $preferredMethod = null)
	{
		// Default response (no update)
		$updateResponse = array(
			'hasUpdate' => false,
			'version'   => '',
			'infoURL'   => '',
			'downloadURL' => '',
		);

		if (empty($this->extension_id))
		{
			return $updateResponse;
		}

		$updateRecord = $this->findUpdates($force, $preferredMethod);

		// If we have an update record in the database return the information found there
		if (is_object($updateRecord))
		{
			$updateResponse = array(
				'hasUpdate' => true,
				'version'   => $updateRecord->version,
				'infoURL'   => $updateRecord->infourl,
				'downloadURL' => $updateRecord->downloadurl,
			);
		}

		return $updateResponse;
	}

	/**
	 * Find the available update record object. If we're at the latest version it will return null.
	 *
	 * Please see getUpdateMethod for information on how the $preferredMethod is handled and what it means.
	 *
	 * @param   bool    $force            Should I forcibly reload the updates from the server?
	 * @param   string  $preferredMethod  Preferred update method: 'joomla' or 'classic'
	 *
	 * @return  \stdClass|null
	 */
	public function findUpdates($force, $preferredMethod = null)
	{
		$preferredMethod = $this->getUpdateMethod($preferredMethod);

		switch ($preferredMethod)
		{
			case 'joomla':
				return $this->findUpdatesJoomla($force);
				break;

			default:
			case 'classic':
				return $this->findUpdatesClassic($force);
				break;
		}
	}

	/**
	 * Gets the update site Ids for our extension.
	 *
	 * @return 	mixed	An array of Ids or null if the query failed.
	 */
	public function getUpdateSiteIds()
	{
		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)
					->select($db->qn('update_site_id'))
					->from($db->qn('#__update_sites_extensions'))
					->where($db->qn('extension_id') . ' = ' . $db->q($this->extension_id));
		$db->setQuery($query);
		$updateSiteIds = $db->loadColumn(0);

		return $updateSiteIds;
	}

	/**
	 * Get the currently installed version as reported by the #__extensions table
	 *
	 * @return  string
	 */
	public function getVersion()
	{
		return $this->version;
	}

	/**
	 * Returns the name of the component, e.g. com_foobar
	 *
	 * @return  string
	 */
	public function getComponentName()
	{
		return $this->component;
	}

	/**
	 * Returns the human readable component name, e.g. Foobar Component
	 *
	 * @return  string
	 */
	public function getComponentDescription()
	{
		return $this->componentDescription;
	}

	/**
	 * Returns the numeric extension ID for the component
	 *
	 * @return  int
	 */
	public function getExtensionId()
	{
		return $this->extension_id;
	}

	/**
	 * Returns the update site URL, i.e. the URL to the XML update stream
	 *
	 * @return  string
	 */
	public function getUpdateSite()
	{
		return $this->updateSite;
	}

	/**
	 * Returns the human readable description of the update site
	 *
	 * @return  string
	 */
	public function getUpdateSiteName()
	{
		return $this->updateSiteName;
	}

	/**
	 * Override the currently installed version as reported by the #__extensions table
	 *
	 * @param  string  $version
	 */
	public function setVersion($version)
	{
		$this->version = $version;
	}

	/**
	 * Refreshes the Joomla! update sites for this extension as needed
	 *
	 * @return  void
	 */
	public function refreshUpdateSite()
	{
		// Joomla! 1.5 does not have update sites.
		if (version_compare(JVERSION, '1.6.0', 'lt'))
		{
			return;
		}

		if (empty($this->extension_id))
		{
			return;
		}

		// Remove obsolete update sites that don't match our extension ID but match our name or update site location
		$this->removeObsoleteUpdateSites();

		// Create the update site definition we want to store to the database
		$update_site = array(
				'name'		=> $this->updateSiteName,
				'type'		=> 'extension',
				'location'	=> $this->updateSite,
				'enabled'	=> 1,
				'last_check_timestamp'	=> 0,
				'extra_query'	=> $this->extraQuery
		);

		// Get a reference to the db driver
		$db = FOFPlatform::getInstance()->getDbo();

		// Get the #__update_sites columns
		$columns = $db->getTableColumns('#__update_sites', true);

		if (version_compare(JVERSION, '3.2.0', 'lt') || !array_key_exists('extra_query', $columns))
		{
			unset($update_site['extra_query']);
		}

		if (version_compare(JVERSION, '2.5.0', 'lt') || !array_key_exists('extra_query', $columns))
		{
			unset($update_site['last_check_timestamp']);
		}

		// Get the update sites for our extension
		$updateSiteIds = $this->getUpdateSiteIds();

		if (empty($updateSiteIds))
		{
			$updateSiteIds = array();
		}

		/** @var boolean $needNewUpdateSite Do I need to create a new update site? */
		$needNewUpdateSite = true;

		/** @var int[] $deleteOldSites Old Site IDs to delete */
		$deleteOldSites = array();

		// Loop through all update sites
		foreach ($updateSiteIds as $id)
		{
			$query = $db->getQuery(true)
						->select('*')
						->from($db->qn('#__update_sites'))
						->where($db->qn('update_site_id') . ' = ' . $db->q($id));
			$db->setQuery($query);
			$aSite = $db->loadObject();

			if (empty($aSite))
			{
				// Update site is now up-to-date, don't need to refresh it anymore.
				continue;
			}

			// We have an update site that looks like ours
			if ($needNewUpdateSite && ($aSite->name == $update_site['name']) && ($aSite->location == $update_site['location']))
			{
				$needNewUpdateSite = false;
				$mustUpdate = false;

				// Is it enabled? If not, enable it.
				if (!$aSite->enabled)
				{
					$mustUpdate = true;
					$aSite->enabled = 1;
				}

				// Do we have the extra_query property (J 3.2+) and does it match?
				if (property_exists($aSite, 'extra_query') && isset($update_site['extra_query'])
					&& ($aSite->extra_query != $update_site['extra_query']))
				{
					$mustUpdate = true;
					$aSite->extra_query = $update_site['extra_query'];
				}

				// Update the update site if necessary
				if ($mustUpdate)
				{
					$db->updateObject('#__update_sites', $aSite, 'update_site_id', true);
				}

				continue;
			}

			// In any other case we need to delete this update site, it's obsolete
			$deleteOldSites[] = $aSite->update_site_id;
		}

		if (!empty($deleteOldSites))
		{
			try
			{
				$obsoleteIDsQuoted = array_map(array($db, 'quote'), $deleteOldSites);

				// Delete update sites
				$query = $db->getQuery(true)
							->delete('#__update_sites')
							->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
				$db->setQuery($query)->execute();

				// Delete update sites to extension ID records
				$query = $db->getQuery(true)
							->delete('#__update_sites_extensions')
							->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
				$db->setQuery($query)->execute();
			}
			catch (\Exception $e)
			{
				// Do nothing on failure
				return;
			}

		}

		// Do we still need to create a new update site?
		if ($needNewUpdateSite)
		{
			// No update sites defined. Create a new one.
			$newSite = (object)$update_site;
			$db->insertObject('#__update_sites', $newSite);

			$id                  = $db->insertid();
			$updateSiteExtension = (object)array(
				'update_site_id' => $id,
				'extension_id'   => $this->extension_id,
			);
			$db->insertObject('#__update_sites_extensions', $updateSiteExtension);
		}
	}

	/**
	 * Removes any update sites which go by the same name or the same location as our update site but do not match the
	 * extension ID.
	 */
	public function removeObsoleteUpdateSites()
	{
		$db = $this->getDbo();

		// Get update site IDs
		$updateSiteIDs = $this->getUpdateSiteIds();

		// Find update sites where the name OR the location matches BUT they are not one of the update site IDs
		$query = $db->getQuery(true)
					->select($db->qn('update_site_id'))
					->from($db->qn('#__update_sites'))
					->where(
						'((' . $db->qn('name') . ' = ' . $db->q($this->updateSiteName) . ') OR ' .
						'(' . $db->qn('location') . ' = ' . $db->q($this->updateSite) . '))'
					);

		if (!empty($updateSiteIDs))
		{
			$updateSitesQuoted = array_map(array($db, 'quote'), $updateSiteIDs);
			$query->where($db->qn('update_site_id') . ' NOT IN (' . implode(',', $updateSitesQuoted) . ')');
		}

		try
		{
			$ids = $db->setQuery($query)->loadColumn();

			if (!empty($ids))
			{
				$obsoleteIDsQuoted = array_map(array($db, 'quote'), $ids);

				// Delete update sites
				$query = $db->getQuery(true)
							->delete('#__update_sites')
							->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
				$db->setQuery($query)->execute();

				// Delete update sites to extension ID records
				$query = $db->getQuery(true)
							->delete('#__update_sites_extensions')
							->where($db->qn('update_site_id') . ' IN (' . implode(',', $obsoleteIDsQuoted) . ')');
				$db->setQuery($query)->execute();
			}
		}
		catch (\Exception $e)
		{
			// Do nothing on failure
			return;
		}
	}

	/**
	 * Get the update method we should use, 'joomla' or 'classic'
	 *
	 * You can defined the preferred update method: 'joomla' uses JUpdater whereas 'classic' handles update caching and
	 * parsing internally. If you are on Joomla! 3.1 or earlier this option is forced to 'classic' since these old
	 * Joomla! versions couldn't handle updates of commercial components correctly (that's why I contributed the fix to
	 * that problem, the extra_query field that's present in Joomla! 3.2 onwards).
	 *
	 * If 'classic' is defined then it will be used in *all* Joomla! versions. It's the most stable method for fetching
	 * update information.
	 *
	 * @param   string  $preferred  Preferred update method. One of 'joomla' or 'classic'.
	 *
	 * @return  string
	 */
	public function getUpdateMethod($preferred = null)
	{
		$method = $preferred;

		// Make sure the update fetch method is valid, otherwise load the component's "update_method" parameter.
		$validMethods = array('joomla', 'classic');

		if (!in_array($method, $validMethods))
		{
			$method = FOFUtilsConfigHelper::getComponentConfigurationValue($this->component, 'update_method', 'joomla');
		}

		// We can't handle updates using Joomla!'s extensions updater in Joomla! 3.1 and earlier
		if (($method == 'joomla') && version_compare(JVERSION, '3.2.0', 'lt'))
		{
			$method = 'classic';
		}

		return $method;
	}

	/**
	 * Find the available update record object. If we're at the latest version it will return null.
	 *
	 * @param   bool  $force  Should I forcibly reload the updates from the server?
	 *
	 * @return  \stdClass|null
	 */
	protected function findUpdatesJoomla($force = false)
	{
		$db = FOFPlatform::getInstance()->getDbo();

		// If we are forcing the reload, set the last_check_timestamp to 0
		// and remove cached component update info in order to force a reload
		if ($force)
		{
			// Find the update site IDs
			$updateSiteIds = $this->getUpdateSiteIds();

			if (empty($updateSiteIds))
			{
				return null;
			}

			// Set the last_check_timestamp to 0
			if (version_compare(JVERSION, '2.5.0', 'ge'))
			{
				$query = $db->getQuery(true)
							->update($db->qn('#__update_sites'))
							->set($db->qn('last_check_timestamp') . ' = ' . $db->q('0'))
							->where($db->qn('update_site_id') .' IN ('.implode(', ', $updateSiteIds).')');
				$db->setQuery($query);
				$db->execute();
			}

			// Remove cached component update info from #__updates
			$query = $db->getQuery(true)
						->delete($db->qn('#__updates'))
						->where($db->qn('update_site_id') .' IN ('.implode(', ', $updateSiteIds).')');
			$db->setQuery($query);
			$db->execute();
		}

		// Use the update cache timeout specified in com_installer
		$timeout = 3600 * FOFUtilsConfigHelper::getComponentConfigurationValue('com_installer', 'cachetimeout', '6');

		// Load any updates from the network into the #__updates table
		$this->updater->findUpdates($this->extension_id, $timeout);

		// Get the update record from the database
		$query = $db->getQuery(true)
					->select('*')
					->from($db->qn('#__updates'))
					->where($db->qn('extension_id') . ' = ' . $db->q($this->extension_id));
		$db->setQuery($query);

		try
		{
			$updateObject = $db->loadObject();
		}
		catch (Exception $e)
		{
			return null;
		}

		if (!is_object($updateObject))
		{
			return null;
		}

		$updateObject->downloadurl = '';

		JLoader::import('joomla.updater.update');

		if (class_exists('JUpdate'))
		{
			$update = new JUpdate();
			$update->loadFromXML($updateObject->detailsurl);

			if (isset($update->get('downloadurl')->_data))
			{
				$url = trim($update->downloadurl->_data);

				$extra_query = isset($updateObject->extra_query) ? $updateObject->extra_query : $this->extraQuery;

				if ($extra_query)
				{
					if (strpos($url, '?') === false)
					{
						$url .= '?';
					}
					else
					{
						$url .= '&amp;';
					}

					$url .= $extra_query;
				}

				$updateObject->downloadurl = $url;
			}
		}

		return $updateObject;
	}

	/**
	 * Find the available update record object. If we're at the latest version return null.
	 *
	 * @param   bool  $force  Should I forcibly reload the updates from the server?
	 *
	 * @return  \stdClass|null
	 */
	protected function findUpdatesClassic($force = false)
	{
		$allUpdates = $this->loadUpdatesClassic($force);

		if (empty($allUpdates))
		{
			return null;
		}

		$bestVersion = '0.0.0';
		$bestUpdate = null;
		$bestUpdateObject = null;

		foreach($allUpdates as $update)
		{
			if (!isset($update['version']))
			{
				continue;
			}

			if (version_compare($bestVersion, $update['version'], 'lt'))
			{
				$bestVersion = $update['version'];
				$bestUpdate = $update;
			}
		}

        // If the current version is newer or equal to the best one, unset it. Otherwise the user will be always prompted to update
        if(version_compare($this->version, $bestVersion, 'ge'))
        {
            $bestUpdate = null;
            $bestVersion = '0.0.0';
        }

		if (!is_null($bestUpdate))
		{
			$url = '';

			if (isset($bestUpdate['downloads']) && isset($bestUpdate['downloads'][0])
			&& isset($bestUpdate['downloads'][0]['url']))
			{
				$url = $bestUpdate['downloads'][0]['url'];
			}

			if ($this->extraQuery)
			{
				if (strpos($url, '?') === false)
				{
					$url .= '?';
				}
				else
				{
					$url .= '&amp;';
				}

				$url .= $this->extraQuery;
			}

			$bestUpdateObject = (object)array(
					'update_id'      => 0,
					'update_site_id' => 0,
					'extension_id'   => $this->extension_id,
					'name'           => $this->updateSiteName,
					'description'    => $bestUpdate['description'],
					'element'        => $bestUpdate['element'],
					'type'           => $bestUpdate['type'],
					'folder'         => count($bestUpdate['folder']) ? $bestUpdate['folder'][0] : '',
					'client_id'      => isset($bestUpdate['client']) ? $bestUpdate['client'] : 0,
					'version'        => $bestUpdate['version'],
					'data'           => '',
					'detailsurl'     => $this->updateSite,
					'infourl'        => $bestUpdate['infourl']['url'],
					'extra_query'    => $this->extraQuery,
					'downloadurl'	 => $url,
			);
		}

		return $bestUpdateObject;
	}

	/**
	 * Load all available updates without going through JUpdate
	 *
	 * @param   bool  $force  Should I forcibly reload the updates from the server?
	 *
	 * @return  array
	 */
	protected function loadUpdatesClassic($force = false)
	{
		// Is the cache busted? If it is I set $force = true to make sure I download fresh updates
		if (!$force)
		{
			// Get the cache timeout. On older Joomla! installations it will always default to 6 hours.
			$timeout = 3600 * FOFUtilsConfigHelper::getComponentConfigurationValue('com_installer', 'cachetimeout', '6');

			// Do I need to check for updates?
			$lastCheck = $this->getCommonParameter('lastcheck', 0);
			$now = time();

			if (($now - $lastCheck) >= $timeout)
			{
				$force = true;
			}
		}

		// Get the cached JSON-encoded updates list
		$rawUpdates = $this->getCommonParameter('allUpdates', '');

		// Am I forced to reload the XML file (explicitly or because the cache is busted)?
		if ($force)
		{
			// Set the timestamp
			$now = time();
			$this->setCommonParameter('lastcheck', $now);

			// Get all available updates
			$updateHelper = new FOFUtilsUpdateExtension();
			$updates = $updateHelper->getUpdatesFromExtension($this->updateSite);

			// Save the raw updates list in the database
			$rawUpdates = json_encode($updates);
			$this->setCommonParameter('allUpdates', $rawUpdates);
		}

		// Decode the updates list
		$updates = json_decode($rawUpdates, true);

		// Walk through the updates and find the ones compatible with our Joomla! and PHP version
		$compatibleUpdates = array();

		// Get the Joomla! version family (e.g. 2.5)
		$jVersion = JVERSION;
		$jVersionParts = explode('.', $jVersion);
		$jVersionShort = $jVersionParts[0] . '.' . $jVersionParts[1];

		// Get the PHP version family (e.g. 5.6)
		$phpVersion = PHP_VERSION;
		$phpVersionParts = explode('.', $phpVersion);
		$phpVersionShort = $phpVersionParts[0] . '.' . $phpVersionParts[1];

		foreach ($updates as $update)
		{
			// No platform?
			if (!isset($update['targetplatform']))
			{
				continue;
			}

			// Wrong platform?
			if ($update['targetplatform']['name'] != 'joomla')
			{
				continue;
			}

			// Get the target Joomla! version
			$targetJoomlaVersion = $update['targetplatform']['version'];
			$targetVersionParts = explode('.', $targetJoomlaVersion);
			$targetVersionShort = $targetVersionParts[0] . '.' . $targetVersionParts[1];

			// The target version MUST be in the same Joomla! branch
			if ($jVersionShort != $targetVersionShort)
			{
				continue;
			}

			// If the target version is major.minor.revision we must make sure our current JVERSION is AT LEAST equal to that.
			if (version_compare($targetJoomlaVersion, JVERSION, 'gt'))
			{
				continue;
			}

			// Do I have target PHP versions?
			if (isset($update['ars-phpcompat']))
			{
				$phpCompatible = false;

				foreach ($update['ars-phpcompat'] as $entry)
				{
					// Get the target PHP version family
					$targetPHPVersion = $entry['@attributes']['version'];
					$targetPHPVersionParts = explode('.', $targetPHPVersion);
					$targetPHPVersionShort = $targetPHPVersionParts[0] . '.' . $targetPHPVersionParts[1];

					// The target PHP version MUST be in the same PHP branch
					if ($phpVersionShort != $targetPHPVersionShort)
					{
						continue;
					}

					// If the target version is major.minor.revision we must make sure our current PHP_VERSION is AT LEAST equal to that.
					if (version_compare($targetPHPVersion, PHP_VERSION, 'gt'))
					{
						continue;
					}

					$phpCompatible = true;
					break;
				}

				if (!$phpCompatible)
				{
					continue;
				}
			}

			// All checks pass. Add this update to the list of compatible updates.
			$compatibleUpdates[] = $update;
		}

		return $compatibleUpdates;
	}

	/**
	 * Get a common parameter from the #__akeeba_common table
	 *
	 * @param   string  $key      The key to retrieve
	 * @param   mixed   $default  The default value in case none is set
	 *
	 * @return  mixed  The saved parameter value (or $default, if nothing is currently set)
	 */
	protected function getCommonParameter($key, $default = null)
	{
		$dbKey = $this->commonKey . '_autoupdate_' . $key;

		$db = FOFPlatform::getInstance()->getDbo();

		$query = $db->getQuery(true)
					->select($db->qn('value'))
					->from($db->qn($this->commonTable))
					->where($db->qn('key') . ' = ' . $db->q($dbKey));

		$result = $db->setQuery($query)->loadResult();

		if (!$result)
		{
			return $default;
		}

		return $result;
	}

	/**
	 * Set a common parameter from the #__akeeba_common table
	 *
	 * @param   string  $key    The key to set
	 * @param   mixed   $value  The value to set
	 *
	 * @return  void
	 */
	protected function setCommonParameter($key, $value)
	{
		$dbKey = $this->commonKey . '_autoupdate_' . $key;

		$db = FOFPlatform::getInstance()->getDbo();

		$query = $db->getQuery(true)
					->select('COUNT(*)')
					->from($db->qn($this->commonTable))
					->where($db->qn('key') . ' = ' . $db->q($dbKey));
		$count = $db->setQuery($query)->loadResult();

		if ($count)
		{
			$query = $db->getQuery(true)
						->update($db->qn($this->commonTable))
						->set($db->qn('value') . ' = ' . $db->q($value))
						->where($db->qn('key') . ' = ' . $db->q($dbKey));
			$db->setQuery($query)->execute();
		}
		else
		{
			$data = (object)array(
					'key'   => $dbKey,
					'value' => $value,
			);

			$db->insertObject($this->commonTable, $data);
		}
	}

	/**
	 * Proxy to updateComponent(). Required since old versions of our software had an updateComponent method declared
	 * private. If we set the updateComponent() method public we cause a fatal error.
	 *
	 * @return  string
	 */
	public function doUpdateComponent()
	{
		return $this->updateComponent();
	}

	/**
	 * Automatically install the extension update under Joomla! 1.5.5 or later (web) / 3.0 or later (CLI).
	 *
	 * @return  string  The update message
	 */
	private function updateComponent()
	{
		$isCli          = FOFPlatform::getInstance()->isCli();
		$minVersion     = $isCli ? '3.0.0' : '1.5.5';
		$errorQualifier = $isCli ? ' using an unattended CLI CRON script ' : ' ';

		if (version_compare(JVERSION, $minVersion, 'lt'))
		{
			return "Extension updates{$errorQualifier}only work with Joomla! $minVersion and later.";
		}

		try
		{
			$updatePackagePath = $this->downloadUpdate();
		}
		catch (Exception $e)
		{
			return $e->getMessage();
		}

		// Unpack the downloaded package file
		jimport('joomla.installer.helper');
		jimport('cms.installer.helper');
		$package = JInstallerHelper::unpack($updatePackagePath);

		if (!$package)
		{
			// Clean up
			if (JFile::exists($updatePackagePath))
			{
				JFile::delete($updatePackagePath);
			}

			return "An error occurred while unpacking the file. Please double check your Joomla temp-directory setting in Global Configuration.";
		}

		$installer = new JInstaller;
		$installed = $installer->install($package['extractdir']);

		// Let's cleanup the downloaded archive and the temp folder
		if (JFolder::exists($package['extractdir']))
		{
			JFolder::delete($package['extractdir']);
		}

		if (JFile::exists($package['packagefile']))
		{
			JFile::delete($package['packagefile']);
		}

		if ($installed)
		{
			return "Component successfully updated";
		}
		else
		{
			return "An error occurred while trying to update the component";
		}
	}

	/**
	 * Downloads the latest update package to Joomla!'s temporary directory
	 *
	 * @return  string  The absolute path to the downloaded update package.
	 */
	public function downloadUpdate()
	{
		// Get the update URL
		$updateInformation = $this->getUpdates();
		$url               = $updateInformation['downloadURL'];

		if (empty($url))
		{
			throw new RuntimeException("No download URL was provided in the update information");
		}

		$config   = JFactory::getConfig();
		$tmp_dest = $config->get('tmp_path');

		if (!$tmp_dest)
		{
			throw new RuntimeException("You must set a non-empty Joomla! temp-directory in Global Configuration before continuing.");
		}

		if (!JFolder::exists($tmp_dest))
		{
			throw new RuntimeException("Joomla!'s temp-directory does not exist. Please set the correct path in Global Configuration before continuing.");
		}

		// Get the target filename
		$filename = $this->component . '.zip';
		$filename = rtrim($tmp_dest, '\\/') . '/' . $filename;

		try
		{
			$downloader = new FOFDownload();
			$data = $downloader->getFromURL($url);
		}
		catch (Exception $e)
		{
			$code =$e->getCode();
			$message =$e->getMessage();
			throw new RuntimeException("An error occurred while trying to download the update package. Double check your Download ID and your server's network settings. The error message was: #$code: $message");
		}

		if (!JFile::write($filename, $data))
		{
			if (!file_put_contents($filename, $data))
			{
				throw new RuntimeException("Joomla!'s temp-directory is not writeable. Please check its permissions or set a different, writeable path in Global Configuration before continuing.");
			}
		}

		return $filename;
	}

	/**
	 * Gets a file name out of a url
	 *
	 * @param   string  $url  URL to get name from
	 *
	 * @return  mixed   String filename or boolean false if failed
	 */
	private function getFilenameFromURL($url)
	{
		if (is_string($url))
		{
			$parts = explode('/', $url);

			return $parts[count($parts) - 1];
		}

		return false;
	}

	/**
	 * Proxy to sendNotificationEmail(). Required since old versions of our software had a sendNotificationEmail method
	 * declared private. If we set the sendNotificationEmail() method public we cause a fatal error.
	 *
	 * @param   string  $version  The new version of our software
	 * @param   string  $email    The email address to send the notification to
	 *
	 * @return  mixed  The result of JMail::send()
	 */
	public function doSendNotificationEmail($version, $email)
	{
		try
		{
			return $this->sendNotificationEmail($version, $email);
		}
		catch (\Exception $e)
		{
			// Joomla! 3.5 is buggy
		}
	}

	/**
	 * Sends an update notification email
	 *
	 * @param   string  $version  The new version of our software
	 * @param   string  $email    The email address to send the notification to
	 *
	 * @return  mixed  The result of JMail::send()
	 */
	private function sendNotificationEmail($version, $email)
	{
		$email_subject	= $this->updateEmailSubject;
		$email_body = $this->updateEmailBody;

		$jconfig  = JFactory::getConfig();
		$sitename = $jconfig->get('sitename');

		$substitutions = array(
				'[VERSION]'			=> $version,
				'[SITENAME]'		=> $sitename,
				'[COMPONENT]'		=> $this->componentDescription,
		);

		$email_subject = str_replace(array_keys($substitutions), array_values($substitutions), $email_subject);
		$email_body    = str_replace(array_keys($substitutions), array_values($substitutions), $email_body);

		$mailer = JFactory::getMailer();

		$mailfrom = $jconfig->get('mailfrom');
		$fromname = $jconfig->get('fromname');

		$mailer->setSender(array( $mailfrom, $fromname ));
		$mailer->addRecipient($email);
		$mailer->setSubject($email_subject);
		$mailer->setBody($email_body);

		return $mailer->Send();
	}
}
fof/utils/update/collection.php000064400000023454152177723700012627 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A helper class to read and parse "collection" update XML files over the web
 */
class FOFUtilsUpdateCollection
{
	/**
	 * Reads a "collection" XML update source and returns the complete tree of categories
	 * and extensions applicable for platform version $jVersion
	 *
	 * @param   string  $url       The collection XML update source URL to read from
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  array  A list of update sources applicable to $jVersion
	 */
	public function getAllUpdates($url, $jVersion = null)
	{
		// Get the target platform
		if (is_null($jVersion))
		{
			$jVersion = JVERSION;
		}

		// Initialise return value
		$updates = array(
			'metadata'		=> array(
				'name'			=> '',
				'description'	=> '',
			),
			'categories'	=> array(),
			'extensions'	=> array(),
		);

		// Download and parse the XML file
		$donwloader = new FOFDownload();
		$xmlSource = $donwloader->getFromURL($url);

		try
		{
			$xml = new SimpleXMLElement($xmlSource, LIBXML_NONET);
		}
		catch(Exception $e)
		{
			return $updates;
		}

		// Sanity check
		if (($xml->getName() != 'extensionset'))
		{
			unset($xml);

			return $updates;
		}

		// Initialise return value with the stream metadata (name, description)
		$rootAttributes = $xml->attributes();
		foreach ($rootAttributes as $k => $v)
		{
			$updates['metadata'][$k] = (string)$v;
		}

		// Initialise the raw list of updates
		$rawUpdates = array(
			'categories'	=> array(),
			'extensions'	=> array(),
		);

		// Segregate the raw list to a hierarchy of extension and category entries
		/** @var SimpleXMLElement $extension */
		foreach ($xml->children() as $extension)
		{
			switch ($extension->getName())
			{
				case 'category':
					// These are the parameters we expect in a category
					$params = array(
						'name'					=> '',
						'description'			=> '',
						'category'				=> '',
						'ref'					=> '',
						'targetplatformversion'	=> $jVersion,
					);

					// These are the attributes of the element
					$attributes = $extension->attributes();

					// Merge them all
					foreach ($attributes as $k => $v)
					{
						$params[$k] = (string)$v;
					}

					// We can't have a category with an empty category name
					if (empty($params['category']))
					{
						continue;
					}

					// We can't have a category with an empty ref
					if (empty($params['ref']))
					{
						continue;
					}

					if (empty($params['description']))
					{
						$params['description'] = $params['category'];
					}

					if (!array_key_exists($params['category'], $rawUpdates['categories']))
					{
						$rawUpdates['categories'][$params['category']] = array();
					}

					$rawUpdates['categories'][$params['category']][] = $params;

					break;

				case 'extension':
					// These are the parameters we expect in a category
					$params = array(
						'element'				=> '',
						'type'					=> '',
						'version'				=> '',
						'name'					=> '',
						'detailsurl'			=> '',
						'targetplatformversion'	=> $jVersion,
					);

					// These are the attributes of the element
					$attributes = $extension->attributes();

					// Merge them all
					foreach ($attributes as $k => $v)
					{
						$params[$k] = (string)$v;
					}

					// We can't have an extension with an empty element
					if (empty($params['element']))
					{
						continue;
					}

					// We can't have an extension with an empty type
					if (empty($params['type']))
					{
						continue;
					}

					// We can't have an extension with an empty version
					if (empty($params['version']))
					{
						continue;
					}

					if (empty($params['name']))
					{
						$params['name'] = $params['element'] . ' ' . $params['version'];
					}

					if (!array_key_exists($params['type'], $rawUpdates['extensions']))
					{
						$rawUpdates['extensions'][$params['type']] = array();
					}

					if (!array_key_exists($params['element'], $rawUpdates['extensions'][$params['type']]))
					{
						$rawUpdates['extensions'][$params['type']][$params['element']] = array();
					}

					$rawUpdates['extensions'][$params['type']][$params['element']][] = $params;
					break;

				default:
					break;
			}
		}

		unset($xml);

		if (!empty($rawUpdates['categories']))
		{
			foreach ($rawUpdates['categories'] as $category => $entries)
			{
				$update = $this->filterListByPlatform($entries, $jVersion);
				$updates['categories'][$category] = $update;
			}
		}

		if (!empty($rawUpdates['extensions']))
		{
			foreach ($rawUpdates['extensions'] as $type => $extensions)
			{
				$updates['extensions'][$type] = array();

				if (!empty($extensions))
				{
					foreach ($extensions as $element => $entries)
					{
						$update = $this->filterListByPlatform($entries, $jVersion);
						$updates['extensions'][$type][$element] = $update;
					}
				}
			}
		}

		return $updates;
	}

	/**
	 * Filters a list of updates, returning only those available for the
	 * specified platform version $jVersion
	 *
	 * @param   array   $updates   An array containing update definitions (categories or extensions)
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  array|null  The update definition that is compatible, or null if none is compatible
	 */
	private function filterListByPlatform($updates, $jVersion = null)
	{
		// Get the target platform
		if (is_null($jVersion))
		{
			$jVersion = JVERSION;
		}

		$versionParts = explode('.', $jVersion, 4);
		$platformVersionMajor = $versionParts[0];
		$platformVersionMinor = (count($versionParts) > 1) ? $platformVersionMajor . '.' . $versionParts[1] : $platformVersionMajor;
		$platformVersionNormal = (count($versionParts) > 2) ? $platformVersionMinor . '.' . $versionParts[2] : $platformVersionMinor;
		$platformVersionFull = (count($versionParts) > 3) ? $platformVersionNormal . '.' . $versionParts[3] : $platformVersionNormal;

		$pickedExtension = null;
		$pickedSpecificity = -1;

		foreach ($updates as $update)
		{
			// Test the target platform
			$targetPlatform = (string)$update['targetplatformversion'];

			if ($targetPlatform === $platformVersionFull)
			{
				$pickedExtension = $update;
				$pickedSpecificity = 4;
			}
			elseif (($targetPlatform === $platformVersionNormal) && ($pickedSpecificity <= 3))
			{
				$pickedExtension = $update;
				$pickedSpecificity = 3;
			}
			elseif (($targetPlatform === $platformVersionMinor) && ($pickedSpecificity <= 2))
			{
				$pickedExtension = $update;
				$pickedSpecificity = 2;
			}
			elseif (($targetPlatform === $platformVersionMajor) && ($pickedSpecificity <= 1))
			{
				$pickedExtension = $update;
				$pickedSpecificity = 1;
			}
		}

		return $pickedExtension;
	}

	/**
	 * Returns only the category definitions of a collection
	 *
	 * @param   string  $url       The URL of the collection update source
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  array  An array of category update definitions
	 */
	public function getCategories($url, $jVersion = null)
	{
		$allUpdates = $this->getAllUpdates($url, $jVersion);

		return $allUpdates['categories'];
	}

	/**
	 * Returns the update source for a specific category
	 *
	 * @param   string  $url       The URL of the collection update source
	 * @param   string  $category  The category name you want to get the update source URL of
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  string|null  The update stream URL, or null if it's not found
	 */
	public function getCategoryUpdateSource($url, $category, $jVersion = null)
	{
		$allUpdates = $this->getAllUpdates($url, $jVersion);

		if (array_key_exists($category, $allUpdates['categories']))
		{
			return $allUpdates['categories'][$category]['ref'];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a list of updates for extensions only, optionally of a specific type
	 *
	 * @param   string  $url       The URL of the collection update source
	 * @param   string  $type      The extension type you want to get the update source URL of, empty to get all
	 *                             extension types
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  array|null  An array of extension update definitions or null if none is found
	 */
	public function getExtensions($url, $type = null, $jVersion = null)
	{
		$allUpdates = $this->getAllUpdates($url, $jVersion);

		if (empty($type))
		{
			return $allUpdates['extensions'];
		}
		elseif (array_key_exists($type, $allUpdates['extensions']))
		{
			return $allUpdates['extensions'][$type];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the update source URL for a specific extension, based on the type and element, e.g.
	 * type=file and element=joomla is Joomla! itself.
	 *
	 * @param   string  $url       The URL of the collection update source
	 * @param   string  $type      The extension type you want to get the update source URL of
	 * @param   string  $element   The extension element you want to get the update source URL of
	 * @param   string  $jVersion  Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  string|null  The update source URL or null if the extension is not found
	 */
	public function getExtensionUpdateSource($url, $type, $element, $jVersion = null)
	{
		$allUpdates = $this->getExtensions($url, $type, $jVersion);

		if (empty($allUpdates))
		{
			return null;
		}
		elseif (array_key_exists($element, $allUpdates))
		{
			return $allUpdates[$element]['detailsurl'];
		}
		else
		{
			return null;
		}
	}
}fof/utils/update/joomla.php000064400000040064152177723700011751 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A helper class which provides update information for the Joomla! CMS itself. This is slightly different than the
 * regular "extension" files as we need to know if a Joomla! version is STS, LTS, testing, current and so on.
 */
class FOFUtilsUpdateJoomla extends FOFUtilsUpdateExtension
{
	/**
	 * The source for LTS updates
	 *
	 * @var  string
	 */
	protected static $lts_url = 'http://update.joomla.org/core/list.xml';

	/**
	 * The source for STS updates
	 *
	 * @var  string
	 */
	protected static $sts_url = 'http://update.joomla.org/core/sts/list_sts.xml';

	/**
	 * The source for test release updates
	 *
	 * @var  string
	 */
	protected static $test_url = 'http://update.joomla.org/core/test/list_test.xml';

	/**
	 * Reads an "extension" XML update source and returns all listed update entries.
	 *
	 * If you have a "collection" XML update source you should do something like this:
	 * $collection = new CmsupdateHelperCollection();
	 * $extensionUpdateURL = $collection->getExtensionUpdateSource($url, 'component', 'com_foobar', JVERSION);
	 * $extension = new CmsupdateHelperExtension();
	 * $updates = $extension->getUpdatesFromExtension($extensionUpdateURL);
	 *
	 * @param   string $url The extension XML update source URL to read from
	 *
	 * @return  array  An array of update entries
	 */
	public function getUpdatesFromExtension($url)
	{
		// Initialise
		$ret = array();

		// Get and parse the XML source
		$downloader = new FOFDownload();
		$xmlSource  = $downloader->getFromURL($url);

		try
		{
			$xml = new SimpleXMLElement($xmlSource, LIBXML_NONET);
		}
		catch (Exception $e)
		{
			return $ret;
		}

		// Sanity check
		if (($xml->getName() != 'updates'))
		{
			unset($xml);

			return $ret;
		}

		// Let's populate the list of updates
		/** @var SimpleXMLElement $update */
		foreach ($xml->children() as $update)
		{
			// Sanity check
			if ($update->getName() != 'update')
			{
				continue;
			}

			$entry = array(
				'infourl'        => array('title' => '', 'url' => ''),
				'downloads'      => array(),
				'tags'           => array(),
				'targetplatform' => array(),
			);

			$properties = get_object_vars($update);

			foreach ($properties as $nodeName => $nodeContent)
			{
				switch ($nodeName)
				{
					default:
						$entry[ $nodeName ] = $nodeContent;
						break;

					case 'infourl':
					case 'downloads':
					case 'tags':
					case 'targetplatform':
						break;
				}
			}

			$infourlNode               = $update->xpath('infourl');
			$entry['infourl']['title'] = (string) $infourlNode[0]['title'];
			$entry['infourl']['url']   = (string) $infourlNode[0];

			$downloadNodes = $update->xpath('downloads/downloadurl');
			foreach ($downloadNodes as $downloadNode)
			{
				$entry['downloads'][] = array(
					'type'   => (string) $downloadNode['type'],
					'format' => (string) $downloadNode['format'],
					'url'    => (string) $downloadNode,
				);
			}

			$tagNodes = $update->xpath('tags/tag');
			foreach ($tagNodes as $tagNode)
			{
				$entry['tags'][] = (string) $tagNode;
			}

			/** @var SimpleXMLElement[] $targetPlatformNode */
			$targetPlatformNode = $update->xpath('targetplatform');

			$entry['targetplatform']['name']    = (string) $targetPlatformNode[0]['name'];
			$entry['targetplatform']['version'] = (string) $targetPlatformNode[0]['version'];
			$client                             = $targetPlatformNode[0]->xpath('client');
			$entry['targetplatform']['client']  = (is_array($client) && count($client)) ? (string) $client[0] : '';
			$folder                             = $targetPlatformNode[0]->xpath('folder');
			$entry['targetplatform']['folder']  = is_array($folder) && count($folder) ? (string) $folder[0] : '';

			$ret[] = $entry;
		}

		unset($xml);

		return $ret;
	}

	/**
	 * Reads a "collection" XML update source and picks the correct source URL
	 * for the extension update source.
	 *
	 * @param   string $url      The collection XML update source URL to read from
	 * @param   string $jVersion Joomla! version to fetch updates for, or null to use JVERSION
	 *
	 * @return  string  The URL of the extension update source, or empty if no updates are provided / fetching failed
	 */
	public function getUpdateSourceFromCollection($url, $jVersion = null)
	{
		$provider = new FOFUtilsUpdateCollection();

		return $provider->getExtensionUpdateSource($url, 'file', 'joomla', $jVersion);
	}

	/**
	 * Determines the properties of a version: STS/LTS, normal or testing
	 *
	 * @param   string $jVersion       The version number to check
	 * @param   string $currentVersion The current Joomla! version number
	 *
	 * @return  array  The properties analysis
	 */
	public function getVersionProperties($jVersion, $currentVersion = null)
	{
		// Initialise
		$ret = array(
			'lts'     => true,
			// Is this an LTS release? False means STS.
			'current' => false,
			// Is this a release in the $currentVersion branch?
			'upgrade' => 'none',
			// Upgrade relation of $jVersion to $currentVersion: 'none' (can't upgrade), 'lts' (next or current LTS), 'sts' (next or current STS) or 'current' (same release, no upgrade available)
			'testing' => false,
			// Is this a testing (alpha, beta, RC) release?
		);

		// Get the current version if none is defined
		if (is_null($currentVersion))
		{
			$currentVersion = JVERSION;
		}

		// Sanitise version numbers
		$sameVersion    = $jVersion == $currentVersion;
		$jVersion       = $this->sanitiseVersion($jVersion);
		$currentVersion = $this->sanitiseVersion($currentVersion);
		$sameVersion    = $sameVersion || ($jVersion == $currentVersion);

		// Get the base version
		$baseVersion = substr($jVersion, 0, 3);

		// Get the minimum and maximum current version numbers
		$current_minimum = substr($currentVersion, 0, 3);
		$current_maximum = $current_minimum . '.9999';

		// Initialise STS/LTS version numbers
		$sts_minimum = false;
		$sts_maximum = false;
		$lts_minimum = false;

		// Is it an LTS or STS release?
		switch ($baseVersion)
		{
			case '1.5':
				$ret['lts'] = true;
				break;

			case '1.6':
				$ret['lts']  = false;
				$sts_minimum = '1.7';
				$sts_maximum = '1.7.999';
				$lts_minimum = '2.5';
				break;

			case '1.7':
				$ret['lts']  = false;
				$sts_minimum = false;
				$lts_minimum = '2.5';
				break;

			case '2.5':
				$ret['lts']  = true;
				$sts_minimum = false;
				$lts_minimum = '2.5';
				break;

			default:
				$majorVersion = (int) substr($jVersion, 0, 1);
				//$minorVersion = (int) substr($jVersion, 2, 1);

				$ret['lts']  = true;
				$sts_minimum = false;
				$lts_minimum = $majorVersion . '.0';
				break;
		}

		// Is it a current release?
		if (version_compare($jVersion, $current_minimum, 'ge') && version_compare($jVersion, $current_maximum, 'le'))
		{
			$ret['current'] = true;
		}

		// Is this a testing release?
		$versionParts    = explode('.', $jVersion);
		$lastVersionPart = array_pop($versionParts);

		if (in_array(substr($lastVersionPart, 0, 1), array('a', 'b')))
		{
			$ret['testing'] = true;
		}
		elseif (substr($lastVersionPart, 0, 2) == 'rc')
		{
			$ret['testing'] = true;
		}
		elseif (substr($lastVersionPart, 0, 3) == 'dev')
		{
			$ret['testing'] = true;
		}

		// Find the upgrade relation of $jVersion to $currentVersion
		if (version_compare($jVersion, $currentVersion, 'eq'))
		{
			$ret['upgrade'] = 'current';
		}
		elseif (($sts_minimum !== false) && version_compare($jVersion, $sts_minimum, 'ge') && version_compare($jVersion, $sts_maximum, 'le'))
		{
			$ret['upgrade'] = 'sts';
		}
		elseif (($lts_minimum !== false) && version_compare($jVersion, $lts_minimum, 'ge'))
		{
			$ret['upgrade'] = 'lts';
		}
		elseif ($baseVersion == $current_minimum)
		{
			$ret['upgrade'] = $ret['lts'] ? 'lts' : 'sts';
		}
		else
		{
			$ret['upgrade'] = 'none';
		}

		if ($sameVersion)
		{
			$ret['upgrade'] = 'none';
		}

		return $ret;
	}


	/**
	 * Filters a list of updates, making sure they apply to the specifed CMS
	 * release.
	 *
	 * @param   array  $updates  A list of update records returned by the getUpdatesFromExtension method
	 * @param   string $jVersion The current Joomla! version number
	 *
	 * @return  array  A filtered list of updates. Each update record also includes version relevance information.
	 */
	public function filterApplicableUpdates($updates, $jVersion = null)
	{
		if (empty($jVersion))
		{
			$jVersion = JVERSION;
		}

		$versionParts          = explode('.', $jVersion, 4);
		$platformVersionMajor  = $versionParts[0];
		$platformVersionMinor  = $platformVersionMajor . '.' . $versionParts[1];
		$platformVersionNormal = $platformVersionMinor . '.' . $versionParts[2];
		//$platformVersionFull   = (count($versionParts) > 3) ? $platformVersionNormal . '.' . $versionParts[3] : $platformVersionNormal;

		$ret = array();

		foreach ($updates as $update)
		{
			// Check each update for platform match
			if (strtolower($update['targetplatform']['name']) != 'joomla')
			{
				continue;
			}

			$targetPlatformVersion = $update['targetplatform']['version'];

			if (!preg_match('/' . $targetPlatformVersion . '/', $platformVersionMinor))
			{
				continue;
			}

			// Get some information from the version number
			$updateVersion     = $update['version'];
			$versionProperties = $this->getVersionProperties($updateVersion, $jVersion);

			if ($versionProperties['upgrade'] == 'none')
			{
				continue;
			}

			// The XML files are ill-maintained. Maybe we already have this update?
			if (!array_key_exists($updateVersion, $ret))
			{
				$ret[ $updateVersion ] = array_merge($update, $versionProperties);
			}
		}

		return $ret;
	}

	/**
	 * Joomla! has a lousy track record in naming its alpha, beta and release
	 * candidate releases. The convention used seems to be "what the hell the
	 * current package maintainer thinks looks better". This method tries to
	 * figure out what was in the mind of the maintainer and translate the
	 * funky version number to an actual PHP-format version string.
	 *
	 * @param   string $version The whatever-format version number
	 *
	 * @return  string  A standard formatted version number
	 */
	public function sanitiseVersion($version)
	{
		$test                   = strtolower($version);
		$alphaQualifierPosition = strpos($test, 'alpha-');
		$betaQualifierPosition  = strpos($test, 'beta-');
		$betaQualifierPosition2 = strpos($test, '-beta');
		$rcQualifierPosition    = strpos($test, 'rc-');
		$rcQualifierPosition2 = strpos($test, '-rc');
		$rcQualifierPosition3 = strpos($test, 'rc');
		$devQualifiedPosition   = strpos($test, 'dev');

		if ($alphaQualifierPosition !== false)
		{
			$betaRevision = substr($test, $alphaQualifierPosition + 6);
			if (!$betaRevision)
			{
				$betaRevision = 1;
			}
			$test = substr($test, 0, $alphaQualifierPosition) . '.a' . $betaRevision;
		}
		elseif ($betaQualifierPosition !== false)
		{
			$betaRevision = substr($test, $betaQualifierPosition + 5);
			if (!$betaRevision)
			{
				$betaRevision = 1;
			}
			$test = substr($test, 0, $betaQualifierPosition) . '.b' . $betaRevision;
		}
		elseif ($betaQualifierPosition2 !== false)
		{
			$betaRevision = substr($test, $betaQualifierPosition2 + 5);

			if (!$betaRevision)
			{
				$betaRevision = 1;
			}

			$test = substr($test, 0, $betaQualifierPosition2) . '.b' . $betaRevision;
		}
		elseif ($rcQualifierPosition !== false)
		{
			$betaRevision = substr($test, $rcQualifierPosition + 5);
			if (!$betaRevision)
			{
				$betaRevision = 1;
			}
			$test = substr($test, 0, $rcQualifierPosition) . '.rc' . $betaRevision;
		}
		elseif ($rcQualifierPosition2 !== false)
		{
			$betaRevision = substr($test, $rcQualifierPosition2 + 3);

			if (!$betaRevision)
			{
				$betaRevision = 1;
			}

			$test = substr($test, 0, $rcQualifierPosition2) . '.rc' . $betaRevision;
		}
		elseif ($rcQualifierPosition3 !== false)
		{
			$betaRevision = substr($test, $rcQualifierPosition3 + 5);

			if (!$betaRevision)
			{
				$betaRevision = 1;
			}

			$test = substr($test, 0, $rcQualifierPosition3) . '.rc' . $betaRevision;
		}
		elseif ($devQualifiedPosition !== false)
		{
			$betaRevision = substr($test, $devQualifiedPosition + 6);
			if (!$betaRevision)
			{
				$betaRevision = '';
			}
			$test = substr($test, 0, $devQualifiedPosition) . '.dev' . $betaRevision;
		}

		return $test;
	}

	/**
	 * Reloads the list of all updates available for the specified Joomla! version
	 * from the network.
	 *
	 * @param    array  $sources  The enabled sources to look into
	 * @param    string $jVersion The Joomla! version we are checking updates for
	 *
	 * @return   array  A list of updates for the installed, current, lts and sts versions
	 */
	public function getUpdates($sources = array(), $jVersion = null)
	{
		// Make sure we have a valid list of sources
		if (empty($sources) || !is_array($sources))
		{
			$sources = array();
		}

		$defaultSources = array('lts' => true, 'sts' => true, 'test' => true, 'custom' => '');

		$sources = array_merge($defaultSources, $sources);

		// Use the current JVERSION if none is specified
		if (empty($jVersion))
		{
			$jVersion = JVERSION;
		}

		// Get the current branch' min/max versions
		$versionParts      = explode('.', $jVersion, 4);
		$currentMinVersion = $versionParts[0] . '.' . $versionParts[1];
		$currentMaxVersion = $versionParts[0] . '.' . $versionParts[1] . '.9999';


		// Retrieve all updates
		$allUpdates = array();

		foreach ($sources as $source => $value)
		{
			if (($value === false) || empty($value))
			{
				continue;
			}

			switch ($source)
			{
				case 'lts':
					$url = self::$lts_url;
					break;

				case 'sts':
					$url = self::$sts_url;
					break;

				case 'test':
					$url = self::$test_url;
					break;

				default:
				case 'custom':
					$url = $value;
					break;
			}

			$url = $this->getUpdateSourceFromCollection($url, $jVersion);

			if (!empty($url))
			{
				$updates = $this->getUpdatesFromExtension($url);

				if (!empty($updates))
				{
					$applicableUpdates = $this->filterApplicableUpdates($updates, $jVersion);

					if (!empty($applicableUpdates))
					{
						$allUpdates = array_merge($allUpdates, $applicableUpdates);
					}
				}
			}
		}

		$ret = array(
			// Currently installed version (used to reinstall, if available)
			'installed' => array(
				'version' => '',
				'package' => '',
				'infourl' => '',
			),
			// Current branch
			'current'   => array(
				'version' => '',
				'package' => '',
				'infourl' => '',
			),
			// Upgrade to STS release
			'sts'       => array(
				'version' => '',
				'package' => '',
				'infourl' => '',
			),
			// Upgrade to LTS release
			'lts'       => array(
				'version' => '',
				'package' => '',
				'infourl' => '',
			),
			// Upgrade to LTS release
			'test'      => array(
				'version' => '',
				'package' => '',
				'infourl' => '',
			),
		);

		foreach ($allUpdates as $update)
		{
			$sections = array();

			if ($update['upgrade'] == 'current')
			{
				$sections[0] = 'installed';
			}
			elseif (version_compare($update['version'], $currentMinVersion, 'ge') && version_compare($update['version'], $currentMaxVersion, 'le'))
			{
				$sections[0] = 'current';
			}
			else
			{
				$sections[0] = '';
			}

			$sections[1] = $update['lts'] ? 'lts' : 'sts';

			if ($update['testing'])
			{
				$sections = array('test');
			}

			foreach ($sections as $section)
			{
				if (empty($section))
				{
					continue;
				}

				$existingVersionForSection = $ret[ $section ]['version'];

				if (empty($existingVersionForSection))
				{
					$existingVersionForSection = '0.0.0';
				}

				if (version_compare($update['version'], $existingVersionForSection, 'ge'))
				{
					$ret[ $section ]['version'] = $update['version'];
					$ret[ $section ]['package'] = $update['downloads'][0]['url'];
					$ret[ $section ]['infourl'] = $update['infourl']['url'];
				}
			}
		}

		// Catch the case when the latest current branch version is the installed version (up to date site)
		if (empty($ret['current']['version']) && !empty($ret['installed']['version']))
		{
			$ret['current'] = $ret['installed'];
		}

		return $ret;
	}
}fof/utils/update/extension.php000064400000006465152177723700012513 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A helper class to read and parse "extension" update XML files over the web
 */
class FOFUtilsUpdateExtension
{
	/**
	 * Reads an "extension" XML update source and returns all listed update entries.
	 *
	 * If you have a "collection" XML update source you should do something like this:
	 * $collection = new FOFUtilsUpdateCollection();
	 * $extensionUpdateURL = $collection->getExtensionUpdateSource($url, 'component', 'com_foobar', JVERSION);
	 * $extension = new FOFUtilsUpdateExtension();
	 * $updates = $extension->getUpdatesFromExtension($extensionUpdateURL);
	 *
	 * @param   string  $url  The extension XML update source URL to read from
	 *
	 * @return  array  An array of update entries
	 */
	public function getUpdatesFromExtension($url)
	{
		// Initialise
		$ret = array();

		// Get and parse the XML source
		$downloader = new FOFDownload();
		$xmlSource = $downloader->getFromURL($url);

		try
		{
			$xml = new SimpleXMLElement($xmlSource, LIBXML_NONET);
		}
		catch(Exception $e)
		{
			return $ret;
		}

		// Sanity check
		if (($xml->getName() != 'updates'))
		{
			unset($xml);

			return $ret;
		}

		// Let's populate the list of updates
		/** @var SimpleXMLElement $update */
		foreach ($xml->children() as $update)
		{
			// Sanity check
			if ($update->getName() != 'update')
			{
				continue;
			}

			$entry = array(
				'infourl'			=> array('title' => '', 'url' => ''),
				'downloads'			=> array(),
				'tags'				=> array(),
				'targetplatform'	=> array(),
			);

			$properties = get_object_vars($update);

			foreach ($properties as $nodeName => $nodeContent)
			{
				switch ($nodeName)
				{
					default:
						$entry[$nodeName] = $nodeContent;
						break;

					case 'infourl':
					case 'downloads':
					case 'tags':
					case 'targetplatform':
						break;
				}
			}

			$infourlNode = $update->xpath('infourl');
			$entry['infourl']['title'] = (string)$infourlNode[0]['title'];
			$entry['infourl']['url'] = (string)$infourlNode[0];

			$downloadNodes = $update->xpath('downloads/downloadurl');
			foreach ($downloadNodes as $downloadNode)
			{
				$entry['downloads'][] = array(
					'type'		=> (string)$downloadNode['type'],
					'format'	=> (string)$downloadNode['format'],
					'url'		=> (string)$downloadNode,
				);
			}

			$tagNodes = $update->xpath('tags/tag');
			foreach ($tagNodes as $tagNode)
			{
				$entry['tags'][] = (string)$tagNode;
			}

			/** @var SimpleXMLElement $targetPlatformNode */
			$targetPlatformNode = $update->xpath('targetplatform');

			$entry['targetplatform']['name'] = (string)$targetPlatformNode[0]['name'];
			$entry['targetplatform']['version'] = (string)$targetPlatformNode[0]['version'];
			$client = $targetPlatformNode[0]->xpath('client');
			$entry['targetplatform']['client'] = (is_array($client) && count($client)) ? (string)$client[0] : '';
			$folder = $targetPlatformNode[0]->xpath('folder');
			$entry['targetplatform']['folder'] = is_array($folder) && count($folder) ? (string)$folder[0] : '';

			$ret[] = $entry;
		}

		unset($xml);

		return $ret;
	}
}fof/utils/filescheck/filescheck.php000064400000015641152177723700013411 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * A utility class to check that your extension's files are not missing and have not been tampered with.
 *
 * You need a file called fileslist.php in your component's administrator root directory with the following contents:
 *
 * $phpFileChecker = array(
 *   'version' => 'revCEE2DAB',
 *   'date' => '2014-10-16',
 *   'directories' => array(
 *     'administrator/components/com_foobar',
 *     ....
 *   ),
 *   'files' => array(
 *     'administrator/components/com_foobar/access.xml' => array('705', '09aa0351a316bf011ecc8c1145134761', 'b95f00c7b49a07a60570dc674f2497c45c4e7152'),
 *     ....
 *   )
 * );
 *
 * All directory and file paths are relative to the site's root
 *
 * The directories array is a list of
 */
class FOFUtilsFilescheck
{
	/** @var string The name of the component */
	protected $option = '';

	/** @var string Current component version */
	protected $version = null;

	/** @var string Current component release date */
	protected $date = null;

	/** @var array List of files to check as filepath => (filesize, md5, sha1) */
	protected $fileList = array();

	/** @var array List of directories to check that exist */
	protected $dirList = array();

	/** @var bool Is the reported component version different than the version of the #__extensions table? */
	protected $wrongComponentVersion = false;

	/** @var bool Is the fileslist.php reporting a version different than the reported component version? */
	protected $wrongFilesVersion = false;

	/**
	 * Create and initialise the object
	 *
	 * @param string $option Component name, e.g. com_foobar
	 * @param string $version The current component version, as reported by the component
	 * @param string $date The current component release date, as reported by the component
	 */
	public function __construct($option, $version, $date)
	{
		// Initialise from parameters
		$this->option = $option;
		$this->version = $version;
		$this->date = $date;

		// Retrieve the date and version from the #__extensions table
		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true)->select('*')->from($db->qn('#__extensions'))
					->where($db->qn('element') . ' = ' . $db->q($this->option))
					->where($db->qn('type') . ' = ' . $db->q('component'));
		$extension = $db->setQuery($query)->loadObject();

		// Check the version and date against those from #__extensions. I hate heavily nested IFs as much as the next
		// guy, but what can you do...
		if (!is_null($extension))
		{
			$manifestCache = $extension->manifest_cache;

			if (!empty($manifestCache))
			{
				$manifestCache = json_decode($manifestCache, true);

				if (is_array($manifestCache) && isset($manifestCache['creationDate']) && isset($manifestCache['version']))
				{
					// Make sure the fileslist.php version and date match the component's version
					if ($this->version != $manifestCache['version'])
					{
						$this->wrongComponentVersion = true;
					}

					if ($this->date != $manifestCache['creationDate'])
					{
						$this->wrongComponentVersion = true;
					}
				}
			}
		}

		// Try to load the fileslist.php file from the component's back-end root
		$filePath = JPATH_ADMINISTRATOR . '/components/' . $this->option . '/fileslist.php';

		if (!file_exists($filePath))
		{
			return;
		}

		include $filePath;

		// Make sure the fileslist.php version and date match the component's version
		if ($this->version != $phpFileChecker['version'])
		{
			$this->wrongFilesVersion = true;
		}

		if ($this->date != $phpFileChecker['date'])
		{
			$this->wrongFilesVersion = true;
		}

		// Initialise the files and directories lists
		$this->fileList = $phpFileChecker['files'];
		$this->dirList = $phpFileChecker['directories'];
	}

	/**
	 * Is the reported component version different than the version of the #__extensions table?
	 *
	 * @return boolean
	 */
	public function isWrongComponentVersion()
	{
		return $this->wrongComponentVersion;
	}

	/**
	 * Is the fileslist.php reporting a version different than the reported component version?
	 *
	 * @return boolean
	 */
	public function isWrongFilesVersion()
	{
		return $this->wrongFilesVersion;
	}

	/**
	 * Performs a fast check of file and folders. If even one of the files/folders doesn't exist, or even one file has
	 * the wrong file size it will return false.
	 *
	 * @return bool False when there are mismatched files and directories
	 */
	public function fastCheck()
	{
		// Check that all directories exist
		foreach ($this->dirList as $directory)
		{
			$directory = JPATH_ROOT . '/' . $directory;

			if (!@is_dir($directory))
			{
				return false;
			}
		}

		// Check that all files exist and have the right size
		foreach ($this->fileList as $filePath => $fileData)
		{
			$filePath = JPATH_ROOT . '/' . $filePath;

			if (!@file_exists($filePath))
			{
				return false;
			}

			$fileSize = @filesize($filePath);

			if ($fileSize != $fileData[0])
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Performs a slow, thorough check of all files and folders (including MD5/SHA1 sum checks)
	 *
	 * @param int $idx The index from where to start
	 *
	 * @return array Progress report
	 */
	public function slowCheck($idx = 0)
	{
		$ret = array(
			'done'	=> false,
			'files'	=> array(),
			'folders'	=> array(),
			'idx'	=> $idx
		);

		$totalFiles = count($this->fileList);
		$totalFolders = count($this->dirList);
		$fileKeys = array_keys($this->fileList);

		$timer = new FOFUtilsTimer(3.0, 75.0);

		while ($timer->getTimeLeft() && (($idx < $totalFiles) || ($idx < $totalFolders)))
		{
			if ($idx < $totalFolders)
			{
				$directory = JPATH_ROOT . '/' . $this->dirList[$idx];

				if (!@is_dir($directory))
				{
					$ret['folders'][] = $directory;
				}
			}

			if ($idx < $totalFiles)
			{
				$fileKey = $fileKeys[$idx];
				$filePath = JPATH_ROOT . '/' . $fileKey;
				$fileData = $this->fileList[$fileKey];

				if (!@file_exists($filePath))
				{
					$ret['files'][] = $fileKey . ' (missing)';
				}
				elseif (@filesize($filePath) != $fileData[0])
				{
					$ret['files'][] = $fileKey . ' (size ' . @filesize($filePath) . ' ≠ ' . $fileData[0] . ')';
				}
				else
				{
					if (function_exists('sha1_file'))
					{
						$fileSha1 = @sha1_file($filePath);

						if ($fileSha1 != $fileData[2])
						{
							$ret['files'][] = $fileKey . ' (SHA1 ' . $fileSha1 . ' ≠ ' . $fileData[2] . ')';
						}
					}
					elseif (function_exists('md5_file'))
					{
						$fileMd5 = @md5_file($filePath);

						if ($fileMd5 != $fileData[1])
						{
							$ret['files'][] = $fileKey . ' (MD5 ' . $fileMd5 . ' ≠ ' . $fileData[1] . ')';
						}
					}
				}
			}

			$idx++;
		}

		if (($idx >= $totalFiles) && ($idx >= $totalFolders))
		{
			$ret['done'] = true;
		}

		$ret['idx'] = $idx;

		return $ret;
	}
}fof/utils/object/object.php000064400000012143152177723700011717 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('FOF_INCLUDED') or die;

/**
 * Temporary class for backwards compatibility. You should not be using this
 * in your code. It is currently present to handle the validation error stack
 * for FOFTable::check() and will be removed in an upcoming version.
 *
 * This class is based on JObject as found in Joomla! 3.2.1
 *
 * @deprecated  2.1
 * @codeCoverageIgnore
 */
class FOFUtilsObject
{
    /**
     * An array of error messages or Exception objects.
     *
     * @var    array
     */
    protected $_errors = array();

    /**
     * Class constructor, overridden in descendant classes.
     *
     * @param   mixed  $properties  Either and associative array or another
     *                              object to set the initial properties of the object.
     */
    public function __construct($properties = null)
    {
        if ($properties !== null)
        {
            $this->setProperties($properties);
        }
    }

    /**
     * Magic method to convert the object to a string gracefully.
     *
     * @return  string  The classname.
     */
    public function __toString()
    {
        return get_class($this);
    }

    /**
     * Sets a default value if not alreay assigned
     *
     * @param   string  $property  The name of the property.
     * @param   mixed   $default   The default value.
     *
     * @return  mixed
     */
    public function def($property, $default = null)
    {
        $value = $this->get($property, $default);
        return $this->set($property, $value);
    }

    /**
     * Returns a property of the object or the default value if the property is not set.
     *
     * @param   string  $property  The name of the property.
     * @param   mixed   $default   The default value.
     *
     * @return  mixed    The value of the property.
     */
    public function get($property, $default = null)
    {
        if (isset($this->$property))
        {
            return $this->$property;
        }
        return $default;
    }

    /**
     * Returns an associative array of object properties.
     *
     * @param   boolean  $public  If true, returns only the public properties.
     *
     * @return  array
     */
    public function getProperties($public = true)
    {
        $vars = get_object_vars($this);
        if ($public)
        {
            foreach ($vars as $key => $value)
            {
                if ('_' == substr($key, 0, 1))
                {
                    unset($vars[$key]);
                }
            }
        }

        return $vars;
    }

    /**
     * Get the most recent error message.
     *
     * @param   integer  $i         Option error index.
     * @param   boolean  $toString  Indicates if JError objects should return their error message.
     *
     * @return  string   Error message
     */
    public function getError($i = null, $toString = true)
    {
        // Find the error
        if ($i === null)
        {
            // Default, return the last message
            $error = end($this->_errors);
        }
        elseif (!array_key_exists($i, $this->_errors))
        {
            // If $i has been specified but does not exist, return false
            return false;
        }
        else
        {
            $error = $this->_errors[$i];
        }

        // Check if only the string is requested
        if ($error instanceof Exception && $toString)
        {
            return (string) $error;
        }

        return $error;
    }

    /**
     * Return all errors, if any.
     *
     * @return  array  Array of error messages or JErrors.
     */
    public function getErrors()
    {
        return $this->_errors;
    }

    /**
     * Modifies a property of the object, creating it if it does not already exist.
     *
     * @param   string  $property  The name of the property.
     * @param   mixed   $value     The value of the property to set.
     *
     * @return  mixed  Previous value of the property.
     */
    public function set($property, $value = null)
    {
        $previous = isset($this->$property) ? $this->$property : null;
        $this->$property = $value;
        return $previous;
    }

    /**
     * Set the object properties based on a named array/hash.
     *
     * @param   mixed  $properties  Either an associative array or another object.
     *
     * @return  boolean
     */
    public function setProperties($properties)
    {
        if (is_array($properties) || is_object($properties))
        {
            foreach ((array) $properties as $k => $v)
            {
                // Use the set function which might be overridden.
                $this->set($k, $v);
            }
            return true;
        }

        return false;
    }

    /**
     * Add an error message.
     *
     * @param   string  $error  Error message.
     *
     * @return  void
     */
    public function setError($error)
    {
        array_push($this->_errors, $error);
    }
}
fof/table/behavior.php000064400000014277152177723700010743 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class. It defines the events which are
 * called by a Table.
 *
 * @codeCoverageIgnore
 * @package  FrameworkOnFramework
 * @since    2.1
 */
abstract class FOFTableBehavior extends FOFUtilsObservableEvent
{
	/**
	 * This event runs before binding data to the table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   array     &$data   The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onBeforeBind(&$table, &$data)
	{
		return true;
	}

	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   object|array  &$src  The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src)
	{
		return true;
	}

	/**
	 * The event which runs after loading a record from the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  &$result  Did the load succeeded?
	 *
	 * @return  void
	 */
	public function onAfterLoad(&$table, &$result)
	{

	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true) or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onBeforeStore(&$table, $updateNulls)
	{
		return true;
	}

	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	public function onAfterStore(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before moving a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true) or just skipped over (false)?
	 *
	 * @return  boolean  True to allow moving
	 */
	public function onBeforeMove(&$table, $updateNulls)
	{
		return true;
	}

	/**
	 * The event which runs after moving a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow moving without an error
	 */
	public function onAfterMove(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before reordering a table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   string  $where  The WHERE clause of the SQL query to run on reordering (record filter)
	 *
	 * @return  boolean  True to allow reordering
	 */
	public function onBeforeReorder(&$table, $where = '')
	{
		return true;
	}

	/**
	 * The event which runs after reordering a table
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reordering to complete without an error
	 */
	public function onAfterReorder(&$table)
	{
		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	public function onAfterDelete(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs before hitting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to hit
	 * @param   boolean  $log  Should we log the hit?
	 *
	 * @return  boolean  True to allow the hit
	 */
	public function onBeforeHit(&$table, $oid, $log)
	{
		return true;
	}

	/**
	 * The event which runs after hitting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was hit
	 *
	 * @return  boolean  True to allow the hitting without errors
	 */
	public function onAfterHit(&$table, $oid)
	{
		return true;
	}

	/**
	 * The even which runs before copying a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record being copied
	 *
	 * @return  boolean  True to allow the copy to take place
	 */
	public function onBeforeCopy(&$table, $oid)
	{
		return true;
	}

	/**
	 * The even which runs after copying a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was copied (not the new one)
	 *
	 * @return  boolean  True to allow the copy without errors
	 */
	public function onAfterCopy(&$table, $oid)
	{
		return true;
	}

	/**
	 * The event which runs before a record is (un)published
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer|array  &$cid     The PK IDs of the records being (un)published
	 * @param   integer        $publish  1 to publish, 0 to unpublish
	 *
	 * @return  boolean  True to allow the (un)publish to proceed
	 */
	public function onBeforePublish(&$table, &$cid, $publish)
	{
		return true;
	}

	/**
	 * The event which runs after the object is reset to its default values.
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reset to complete without errors
	 */
	public function onAfterReset(&$table)
	{
		return true;
	}

	/**
	 * The even which runs before the object is reset to its default values.
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow the reset to complete
	 */
	public function onBeforeReset(&$table)
	{
		return true;
	}
}
fof/table/dispatcher/behavior.php000064400000001032152177723700013052 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior dispatcher class
 *
 * @codeCoverageIgnore
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableDispatcherBehavior extends FOFUtilsObservableDispatcher
{

}
fof/table/relations.php000064400000100644152177723700011136 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFTableRelations
{
	/**
	 * Holds all known relation definitions
	 *
	 * @var   array
	 */
	protected $relations = array(
		'child'		=> array(),
		'parent'	=> array(),
		'children'	=> array(),
		'multiple'	=> array(),
	);

	/**
	 * Holds the default relations' keys
	 *
	 * @var  array
	 */
	protected $defaultRelation = array(
		'child'		=> null,
		'parent'	=> null,
		'children'	=> null,
		'multiple'	=> null,
	);

	/**
	 * The table these relations are attached to
	 *
	 * @var   FOFTable
	 */
	protected $table = null;

	/**
	 * The name of the component used by our attached table
	 *
	 * @var   string
	 */
	protected $componentName = 'joomla';

	/**
	 * The type (table name without prefix and component name) of our attached table
	 *
	 * @var   string
	 */
	protected $tableType = '';


	/**
	 * Create a relations object based on the provided FOFTable instance
	 *
	 * @param   FOFTable   $table  The table instance used to initialise the relations
	 */
	public function __construct(FOFTable $table)
	{
		// Store the table
		$this->table = $table;

		// Get the table's type from its name
		$tableName = $table->getTableName();
		$tableName = str_replace('#__', '', $tableName);
		$type = explode("_", $tableName);

		if (count($type) == 1)
		{
			$this->tableType = array_pop($type);
		}
		else
		{
			$this->componentName = array_shift($type);
			$this->tableType = array_pop($type);
		}

		$this->tableType = FOFInflector::singularize($this->tableType);

		$tableKey = $table->getKeyName();

		unset($type);

		// Scan all table keys and look for foo_bar_id fields. These fields are used to populate parent relations.
		foreach ($table->getKnownFields() as $field)
		{
			// Skip the table key name
			if ($field == $tableKey)
			{
				continue;
			}

			if (substr($field, -3) != '_id')
			{
				continue;
			}

			$parts = explode('_', $field);

			// If the component type of the field is not set assume 'joomla'
			if (count($parts) == 2)
			{
				array_unshift($parts, 'joomla');
			}

			// Sanity check
			if (count($parts) != 3)
			{
				continue;
			}

			// Make sure we skip any references back to ourselves (should be redundant, due to key field check above)
			if ($parts[1] == $this->tableType)
			{
				continue;
			}

			// Default item name: the name of the table, singular
			$itemName = FOFInflector::singularize($parts[1]);

			// Prefix the item name with the component name if we refer to a different component
			if ($parts[0] != $this->componentName)
			{
				$itemName = $parts[0] . '_' . $itemName;
			}

			// Figure out the table class
			$tableClass = ucfirst($parts[0]) . 'Table' . ucfirst($parts[1]);

			$default = empty($this->relations['parent']);

			$this->addParentRelation($itemName, $tableClass, $field, $field, $default);
		}

		// Get the relations from the configuration provider
		$key = $table->getConfigProviderKey() . '.relations';
		$configRelations = $table->getConfigProvider()->get($key, array());

		if (!empty($configRelations))
		{
			foreach ($configRelations as $relation)
			{
				if (empty($relation['type']))
				{
					continue;
				}

				if (isset($relation['pivotTable']))
				{
					$this->addMultipleRelation($relation['itemName'], $relation['tableClass'],
						$relation['localKey'], $relation['ourPivotKey'], $relation['theirPivotKey'],
						$relation['remoteKey'], $relation['pivotTable'], $relation['default']);
				}
				else
				{
					$method = 'add' . ucfirst($relation['type']). 'Relation';

					if (!method_exists($this, $method))
					{
						continue;
					}

					$this->$method($relation['itemName'], $relation['tableClass'],
						$relation['localKey'], $relation['remoteKey'], $relation['default']);
				}
			}
		}

	}

	/**
	 * Add a 1:1 forward (child) relation. This adds relations for the getChild() method.
	 *
	 * In other words: does a table HAVE ONE child
	 *
	 * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to
	 * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName')
	 *
	 * @param   string   $itemName    is how it will be known locally to the getRelatedItem method (singular)
	 * @param   string   $tableClass  if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the FK relation, default: our primary key
	 * @param   string   $remoteKey   is the remote table's FK column, default: componentname_itemname_id
	 * @param   boolean  $default     add as the default child relation?
	 *
	 * @return  void
	 */
	public function addChildRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, false);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokeSimpleRelation('child', $itemName, $tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining an inverse 1:1 (parent) relation. You must specify at least the $tableClass or the $localKey.
	 * This adds relations for the getParent() method.
	 *
	 * In other words: does a table BELONG TO ONE parent
	 *
	 * Parent and child relations works the same way. We have them separated as it makes more sense for us humans to
	 * read code like $item->getParent() and $item->getChild() than $item->getRelatedObject('someRandomKeyName')
	 *
	 * @param   string   $itemName    is how it will be known locally to the getRelatedItem method (singular)
	 * @param   string   $tableClass  if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey   is the remote table's FK column, default: componentname_itemname_id
	 * @param   boolean  $default     Is this the default parent relationship?
	 *
	 * @return  void
	 */
	public function addParentRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, false);

		$this->addBespokeSimpleRelation('parent', $itemName, $tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining a forward 1:∞ (children) relation. This adds relations to the getChildren() method.
	 *
	 * In other words: does a table HAVE MANY children?
	 *
	 * The children relation works very much the same as the parent and child relation. The difference is that the
	 * parent and child relations return a single table object, whereas the children relation returns an iterator to
	 * many objects.
	 *
	 * @param   string   $itemName    is how it will be known locally to the getRelatedItems method (plural)
	 * @param   string   $tableClass  if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey    is the column containing our side of the FK relation, default: our primary key
	 * @param   string   $remoteKey   is the remote table's FK column, default: componentname_itemname_id
	 * @param   boolean  $default     is this the default children relationship?
	 *
	 * @return  void
	 */
	public function addChildrenRelation($itemName, $tableClass = null, $localKey = null, $remoteKey = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, true);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokeSimpleRelation('children', $itemName, $tableClass, $localKey, $remoteKey, $default);
	}

	/**
	 * Defining a ∞:∞ (multiple) relation. This adds relations to the getMultiple() method.
	 *
	 * In other words: is a table RELATED TO MANY other records?
	 *
	 * @param   string   $itemName       is how it will be known locally to the getRelatedItems method (plural)
	 * @param   string   $tableClass     if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of the FK relation, default: our primary key field name
	 * @param   string   $ourPivotKey    is the column containing our side of the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $remoteKey      is the remote table's FK column, default: componentname_itemname_id
	 * @param   string   $glueTable      is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
	 * @param   boolean  $default        is this the default multiple relation?
	 */
	public function addMultipleRelation($itemName, $tableClass = null, $localKey = null, $ourPivotKey = null, $theirPivotKey = null, $remoteKey = null, $glueTable = null, $default = true)
	{
		$itemName = $this->normaliseItemName($itemName, true);

		if (empty($localKey))
		{
			$localKey = $this->table->getKeyName();
		}

		$this->addBespokePivotRelation('multiple', $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $glueTable, $default);
	}

	/**
	 * Removes a previously defined relation by name. You can optionally specify the relation type.
	 *
	 * @param   string  $itemName  The name of the relation to remove
	 * @param   string  $type      [optional] The relation type (child, parent, children, ...)
	 *
	 * @return  void
	 */
	public function removeRelation($itemName, $type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					unset ($this->relations[$type][$itemName]);

                    // If it's the default one, remove it from the default array, too
                    if($this->defaultRelation[$type] == $itemName)
                    {
                        $this->defaultRelation[$type] = null;
                    }

					return;
				}
			}
		}
	}

	/**
	 * Removes all existing relations
	 *
	 * @param   string  $type  The type or relations to remove, omit to remove all relation types
	 *
	 * @return  void
	 */
	public function clearRelations($type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			$this->relations[$type] = array();

            // Remove the relation from the default stack, too
            $this->defaultRelation[$type] = null;
		}
	}

	/**
	 * Does the named relation exist? You can optionally specify the type.
	 *
	 * @param   string  $itemName  The name of the relation to check
	 * @param   string  $type      [optional] The relation type (child, parent, children, ...)
	 *
	 * @return  boolean
	 */
	public function hasRelation($itemName, $type = null)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Get the definition of a relation
	 *
	 * @param   string  $itemName  The name of the relation to check
	 * @param   string  $type      [optional] The relation type (child, parent, children, ...)
	 *
	 * @return  array
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getRelation($itemName, $type)
	{
		$types = array_keys($this->relations);

		if (in_array($type, $types))
		{
			$types = array($type);
		}

		foreach ($types as $type)
		{
			foreach ($this->relations[$type] as $key => $relations)
			{
				if ($itemName == $key)
				{
					$temp         = $relations;
					$temp['type'] = $type;

					return $temp;
				}
			}
		}

		throw new RuntimeException("Relation $itemName not found in table {$this->tableType}", 500);
	}

	/**
	 * Gets the item referenced by a named relation. You can optionally specify the type. Only single item relation
	 * types will be searched.
	 *
	 * @param   string  $itemName  The name of the relation to use
	 * @param   string  $type      [optional] The relation type (child, parent)
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  If the named relation doesn't exist or isn't supposed to return single items
	 */
	public function getRelatedItem($itemName, $type = null)
	{
		if (empty($type))
		{
			$relation = $this->getRelation($itemName, $type);
			$type = $relation['type'];
		}

		switch ($type)
		{
			case 'parent':
				return $this->getParent($itemName);
				break;

			case 'child':
				return $this->getChild($itemName);
				break;

			default:
				throw new RuntimeException("Invalid relation type $type for returning a single related item", 500);
				break;
		}
	}

	/**
	 * Gets the iterator for the items referenced by a named relation. You can optionally specify the type. Only
	 * multiple item relation types will be searched.
	 *
	 * @param   string  $itemName  The name of the relation to use
	 * @param   string  $type      [optional] The relation type (children, multiple)
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  If the named relation doesn't exist or isn't supposed to return single items
	 */
	public function getRelatedItems($itemName, $type = null)
	{
		if (empty($type))
		{
			$relation = $this->getRelation($itemName, $type);
			$type = $relation['type'];
		}

		switch ($type)
		{
			case 'children':
				return $this->getChildren($itemName);
				break;

			case 'multiple':
				return $this->getMultiple($itemName);
				break;

			case 'siblings':
				return $this->getSiblings($itemName);
				break;

			default:
				throw new RuntimeException("Invalid relation type $type for returning a collection of related items", 500);
				break;
		}
	}

	/**
	 * Gets a parent item
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use, skip to use the default parent relation
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getParent($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['parent'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default parent relation for %s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['parent'][$itemName]))
		{
			throw new RuntimeException(sprintf('Parent relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
		}

		return $this->getTableFromRelation($this->relations['parent'][$itemName]);
	}

	/**
	 * Gets a child item
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use, skip to use the default child relation
	 *
	 * @return  FOFTable
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getChild($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['child'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default child relation for %s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['child'][$itemName]))
		{
			throw new RuntimeException(sprintf('Child relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
		}

		return $this->getTableFromRelation($this->relations['child'][$itemName]);
	}

	/**
	 * Gets an iterator for the children items
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use, skip to use the default children relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getChildren($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['children'];
		}
		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default children relation for %s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['children'][$itemName]))
		{
			throw new RuntimeException(sprintf('Children relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
		}

		return $this->getIteratorFromRelation($this->relations['children'][$itemName]);
	}

	/**
	 * Gets an iterator for the sibling items. This relation is inferred from the parent relation. It returns all
	 * elements on the same table which have the same parent.
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use, skip to use the default children relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getSiblings($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['parent'];
		}
		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default siblings relation for %s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['parent'][$itemName]))
		{
			throw new RuntimeException(sprintf('Sibling relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
		}

		// Get my table class
		$tableName = $this->table->getTableName();
		$tableName = str_replace('#__', '', $tableName);
		$tableNameParts = explode('_', $tableName, 2);
		$tableClass = ucfirst($tableNameParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableNameParts[1]));

		$parentRelation = $this->relations['parent'][$itemName];
		$relation = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $parentRelation['localKey'],
			'remoteKey'		=> $parentRelation['localKey'],
		);

		return $this->getIteratorFromRelation($relation);
	}

	/**
	 * Gets an iterator for the multiple items
	 *
	 * @param   string  $itemName  [optional] The name of the relation to use, skip to use the default multiple relation
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  RuntimeException  When the relation is not found
	 */
	public function getMultiple($itemName = null)
	{
		if (empty($itemName))
		{
			$itemName = $this->defaultRelation['multiple'];
		}

		if (empty($itemName))
		{
			throw new RuntimeException(sprintf('Default multiple relation for %s not found', $this->table->getTableName()), 500);
		}

		if (!isset($this->relations['multiple'][$itemName]))
		{
			throw new RuntimeException(sprintf('Multiple relation %s for %s not found', $itemName, $this->table->getTableName()), 500);
		}

		return $this->getIteratorFromRelation($this->relations['multiple'][$itemName]);
	}

	/**
	 * Returns a FOFTable object based on a given relation
	 *
	 * @param   array    $relation   Indexed array holding relation definition.
     *                                  tableClass => name of the related table class
     *                                  localKey   => name of the local key
     *                                  remoteKey  => name of the remote key
	 *
	 * @return FOFTable
	 *
	 * @throws RuntimeException
     * @throws InvalidArgumentException
	 */
	protected function getTableFromRelation($relation)
	{
        // Sanity checks
        if(
            !isset($relation['tableClass']) || !isset($relation['remoteKey']) || !isset($relation['localKey']) ||
            !$relation['tableClass'] || !$relation['remoteKey'] || !$relation['localKey']
        )
        {
            throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500);
        }

		// Get a table object from the table class name
		$tableClass      = $relation['tableClass'];
		$tableClassParts = FOFInflector::explode($tableClass);

        if(count($tableClassParts) < 3)
        {
            throw new InvalidArgumentException('Invalid table class named. It should be something like FooTableBar');
        }

		$table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));

		// Get the table name
		$tableName = $table->getTableName();

		// Get the remote and local key names
		$remoteKey = $relation['remoteKey'];
		$localKey  = $relation['localKey'];

		// Get the local key's value
		$value = $this->table->$localKey;

        // If there's no value for the primary key, let's stop here
        if(!$value)
        {
            throw new RuntimeException('Missing value for the primary key of the table '.$this->table->getTableName(), 500);
        }

		// This is required to prevent one relation from killing the db cursor used in a different relation...
		$oldDb = $this->table->getDbo();
		$oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH!
		$db = clone $oldDb;

		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn($tableName))
			->where($db->qn($remoteKey) . ' = ' . $db->q($value));
		$db->setQuery($query, 0, 1);

		$data = $db->loadObject();

		if (!is_object($data))
		{
			throw new RuntimeException(sprintf('Cannot load item from relation against table %s column %s', $tableName, $remoteKey), 500);
		}

		$table->bind($data);

		return $table;
	}

	/**
	 * Returns a FOFDatabaseIterator based on a given relation
	 *
	 * @param   array    $relation   Indexed array holding relation definition.
     *                                  tableClass => name of the related table class
     *                                  localKey   => name of the local key
     *                                  remoteKey  => name of the remote key
     *                                  pivotTable    => name of the pivot table (optional)
     *                                  theirPivotKey => name of the remote key in the pivot table (mandatory if pivotTable is set)
     *                                  ourPivotKey   => name of our key in the pivot table (mandatory if pivotTable is set)
	 *
	 * @return FOFDatabaseIterator
	 *
	 * @throws RuntimeException
	 * @throws InvalidArgumentException
	 */
	protected function getIteratorFromRelation($relation)
	{
        // Sanity checks
        if(
            !isset($relation['tableClass']) || !isset($relation['remoteKey']) || !isset($relation['localKey']) ||
            !$relation['tableClass'] || !$relation['remoteKey'] || !$relation['localKey']
        )
        {
            throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500);
        }

        if(array_key_exists('pivotTable', $relation))
        {
            if(
                !isset($relation['theirPivotKey']) || !isset($relation['ourPivotKey']) ||
                !$relation['pivotTable'] || !$relation['theirPivotKey'] || !$relation['ourPivotKey']
            )
            {
                throw new InvalidArgumentException('Missing array index for the '.__METHOD__.' method. Please check method signature', 500);
            }
        }

		// Get a table object from the table class name
		$tableClass      = $relation['tableClass'];
		$tableClassParts = FOFInflector::explode($tableClass);

        if(count($tableClassParts) < 3)
        {
            throw new InvalidArgumentException('Invalid table class named. It should be something like FooTableBar');
        }

		$table = FOFTable::getInstance($tableClassParts[2], ucfirst($tableClassParts[0]) . ucfirst($tableClassParts[1]));

		// Get the table name
		$tableName = $table->getTableName();

		// Get the remote and local key names
		$remoteKey = $relation['remoteKey'];
		$localKey  = $relation['localKey'];

		// Get the local key's value
		$value = $this->table->$localKey;

        // If there's no value for the primary key, let's stop here
        if(!$value)
        {
            throw new RuntimeException('Missing value for the primary key of the table '.$this->table->getTableName(), 500);
        }

		// This is required to prevent one relation from killing the db cursor used in a different relation...
		$oldDb = $this->table->getDbo();
		$oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH!
		$db = clone $oldDb;

		// Begin the query
		$query = $db->getQuery(true)
			->select('*')
			->from($db->qn($tableName));

		// Do we have a pivot table?
		$hasPivot = array_key_exists('pivotTable', $relation);

		// If we don't have pivot it's a straightforward query
		if (!$hasPivot)
		{
			$query->where($db->qn($remoteKey) . ' = ' . $db->q($value));
		}
		// If we have a pivot table we have to do a subquery
		else
		{
			$subQuery = $db->getQuery(true)
				->select($db->qn($relation['theirPivotKey']))
				->from($db->qn($relation['pivotTable']))
				->where($db->qn($relation['ourPivotKey']) . ' = ' . $db->q($value));
			$query->where($db->qn($remoteKey) . ' IN (' . $subQuery . ')');
		}

		$db->setQuery($query);

		$cursor = $db->execute();

		$iterator = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $tableClass);

		return $iterator;
	}

	/**
	 * Add any bespoke relation which doesn't involve a pivot table.
	 *
	 * @param   string   $relationType  The type of the relationship (parent, child, children)
	 * @param   string   $itemName      is how it will be known locally to the getRelatedItems method
	 * @param   string   $tableClass    if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey      is the column containing our side of the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey     is the remote table's FK column, default: componentname_itemname_id
	 * @param   boolean  $default       is this the default children relationship?
	 *
	 * @return  void
	 */
	protected function addBespokeSimpleRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $default)
	{
		$ourPivotKey   = null;
		$theirPivotKey = null;
		$pivotTable    = null;

		$this->normaliseParameters(false, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);

		$this->relations[$relationType][$itemName] = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $localKey,
			'remoteKey'		=> $remoteKey,
		);

		if ($default)
		{
			$this->defaultRelation[$relationType] = $itemName;
		}
	}

	/**
	 * Add any bespoke relation which involves a pivot table.
	 *
	 * @param   string   $relationType   The type of the relationship (multiple)
	 * @param   string   $itemName       is how it will be known locally to the getRelatedItems method
	 * @param   string   $tableClass     if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey      is the remote table's FK column, default: componentname_itemname_id
	 * @param   string   $ourPivotKey    is the column containing our side of the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $pivotTable     is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
	 * @param   boolean  $default        is this the default children relationship?
	 *
	 * @return  void
	 */
	protected function addBespokePivotRelation($relationType, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable, $default)
	{
		$this->normaliseParameters(true, $itemName, $tableClass, $localKey, $remoteKey, $ourPivotKey, $theirPivotKey, $pivotTable);

		$this->relations[$relationType][$itemName] = array(
			'tableClass'	=> $tableClass,
			'localKey'		=> $localKey,
			'remoteKey'		=> $remoteKey,
			'ourPivotKey'	=> $ourPivotKey,
			'theirPivotKey'	=> $theirPivotKey,
			'pivotTable'	=> $pivotTable,
		);

		if ($default)
		{
			$this->defaultRelation[$relationType] = $itemName;
		}
	}

	/**
	 * Normalise the parameters of a relation, guessing missing values
	 *
	 * @param   boolean  $pivot          Is this a many to many relation involving a pivot table?
	 * @param   string   $itemName       is how it will be known locally to the getRelatedItems method (plural)
	 * @param   string   $tableClass     if skipped it is defined automatically as ComponentnameTableItemname
	 * @param   string   $localKey       is the column containing our side of the FK relation, default: componentname_itemname_id
	 * @param   string   $remoteKey      is the remote table's FK column, default: componentname_itemname_id
	 * @param   string   $ourPivotKey    is the column containing our side of the FK relation in the pivot table, default: $localKey
	 * @param   string   $theirPivotKey  is the column containing the other table's side of the FK relation in the pivot table, default $remoteKey
	 * @param   string   $pivotTable     is the name of the glue (pivot) table, default: #__componentname_thisclassname_itemname with plural items (e.g. #__foobar_users_roles)
	 *
	 * @return  void
	 */
	protected function normaliseParameters($pivot = false, &$itemName, &$tableClass, &$localKey, &$remoteKey, &$ourPivotKey, &$theirPivotKey, &$pivotTable)
	{
		// Get a default table class if none is provided
		if (empty($tableClass))
		{
			$tableClassParts = explode('_', $itemName, 3);

			if (count($tableClassParts) == 1)
			{
				array_unshift($tableClassParts, $this->componentName);
			}

			if ($tableClassParts[0] == 'joomla')
			{
				$tableClassParts[0] = 'J';
			}

			$tableClass = ucfirst($tableClassParts[0]) . 'Table' . ucfirst(FOFInflector::singularize($tableClassParts[1]));
		}

		// Make sure we have both a local and remote key
		if (empty($localKey) && empty($remoteKey))
		{
            // WARNING! If we have a pivot table, this behavior is wrong!
            // Infact if we have `parts` and `groups` the local key should be foobar_part_id and the remote one foobar_group_id.
            // However, this isn't a real issue because:
            // 1. we have no way to detect the local key of a multiple relation
            // 2. this scenario never happens, since, in this class, if we're adding a multiple relation we always supply the local key
			$tableClassParts = FOFInflector::explode($tableClass);
			$localKey  = $tableClassParts[0] . '_' . $tableClassParts[2] . '_id';
			$remoteKey = $localKey;
		}
		elseif (empty($localKey) && !empty($remoteKey))
		{
			$localKey = $remoteKey;
		}
		elseif (!empty($localKey) && empty($remoteKey))
		{
            if($pivot)
            {
                $tableClassParts = FOFInflector::explode($tableClass);
                $remoteKey = $tableClassParts[0] . '_' . $tableClassParts[2] . '_id';
            }
            else
            {
                $remoteKey = $localKey;
            }
		}

		// If we don't have a pivot table nullify the relevant variables and return
		if (!$pivot)
		{
			$ourPivotKey   = null;
			$theirPivotKey = null;
			$pivotTable    = null;

			return;
		}

		if (empty($ourPivotKey))
		{
			$ourPivotKey = $localKey;
		}

		if (empty($theirPivotKey))
		{
			$theirPivotKey = $remoteKey;
		}

		if (empty($pivotTable))
		{
			$pivotTable = '#__' . strtolower($this->componentName) . '_' .
							strtolower(FOFInflector::pluralize($this->tableType)) . '_';

			$itemNameParts = explode('_', $itemName);
			$lastPart = array_pop($itemNameParts);
			$pivotTable .= strtolower($lastPart);
		}
	}

	/**
	 * Normalises the format of a relation name
	 *
	 * @param   string   $itemName   The raw relation name
	 * @param   boolean  $pluralise  Should I pluralise the name? If not, I will singularise it
	 *
	 * @return  string  The normalised relation key name
	 */
	protected function normaliseItemName($itemName, $pluralise = false)
	{
		// Explode the item name
		$itemNameParts = explode('_', $itemName);

		// If we have multiple parts the first part is considered to be the component name
		if (count($itemNameParts) > 1)
		{
			$prefix = array_shift($itemNameParts);
		}
		else
		{
			$prefix = null;
		}

		// If we still have multiple parts we need to pluralise/singularise the last part and join everything in
		// CamelCase format
		if (count($itemNameParts) > 1)
		{
			$name = array_pop($itemNameParts);
			$name = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name);
			$itemNameParts[] = $name;

			$itemName = FOFInflector::implode($itemNameParts);
		}
		// Otherwise we singularise/pluralise the remaining part
		else
		{
			$name = array_pop($itemNameParts);
			$itemName = $pluralise ? FOFInflector::pluralize($name) : FOFInflector::singularize($name);
		}

		if (!empty($prefix))
		{
			$itemName = $prefix . '_' . $itemName;
		}

		return $itemName;
	}
}fof/table/behavior/assets.php000064400000015022152177723700012232 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for assets
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableBehaviorAssets extends FOFTableBehavior
{
	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table       The table which calls this event
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onAfterStore(&$table)
	{
		$result = true;

		$asset_id_field	= $table->getColumnAlias('asset_id');

		if (in_array($asset_id_field, $table->getKnownFields()))
		{
			if (!empty($table->$asset_id_field))
			{
				$currentAssetId = $table->$asset_id_field;
			}

			// The asset id field is managed privately by this class.
			if ($table->isAssetsTracked())
			{
				unset($table->$asset_id_field);
			}
		}

		// Create the object used for inserting/udpating data to the database
		$fields     = $table->getTableFields();

		// Let's remove the asset_id field, since we unset the property above and we would get a PHP notice
		if (isset($fields[$asset_id_field]))
		{
			unset($fields[$asset_id_field]);
		}

		// Asset Tracking
		if (in_array($asset_id_field, $table->getKnownFields()) && $table->isAssetsTracked())
		{
			$parentId = $table->getAssetParentId();

            try{
                $name     = $table->getAssetName();
            }
            catch(Exception $e)
            {
                $table->setError($e->getMessage());
                return false;
            }

			$title    = $table->getAssetTitle();

			$asset = JTable::getInstance('Asset');
			$asset->loadByName($name);

			// Re-inject the asset id.
			$this->$asset_id_field = $asset->id;

			// Check for an error.
			$error = $asset->getError();

            // Since we are using JTable, there is no way to mock it and test for failures :(
            // @codeCoverageIgnoreStart
			if ($error)
			{
				$table->setError($error);

				return false;
			}
            // @codeCoverageIgnoreEnd

			// Specify how a new or moved node asset is inserted into the tree.
            // Since we're unsetting the table field before, this statement is always true...
			if (empty($table->$asset_id_field) || $asset->parent_id != $parentId)
			{
				$asset->setLocation($parentId, 'last-child');
			}

			// Prepare the asset to be stored.
			$asset->parent_id = $parentId;
			$asset->name      = $name;
			$asset->title     = $title;

			if ($table->getRules() instanceof JAccessRules)
			{
				$asset->rules = (string) $table->getRules();
			}

            // Since we are using JTable, there is no way to mock it and test for failures :(
            // @codeCoverageIgnoreStart
			if (!$asset->check() || !$asset->store())
			{
				$table->setError($asset->getError());

				return false;
			}
            // @codeCoverageIgnoreEnd

			// Create an asset_id or heal one that is corrupted.
			if (empty($table->$asset_id_field) || (($currentAssetId != $table->$asset_id_field) && !empty($table->$asset_id_field)))
			{
				// Update the asset_id field in this table.
				$table->$asset_id_field = (int) $asset->id;

				$k = $table->getKeyName();

                $db = $table->getDbo();

				$query = $db->getQuery(true)
				            ->update($db->qn($table->getTableName()))
				            ->set($db->qn($asset_id_field).' = ' . (int) $table->$asset_id_field)
				            ->where($db->qn($k) . ' = ' . (int) $table->$k);

				$db->setQuery($query)->execute();
			}

			$result = true;
		}

		return $result;
	}

	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable      &$table  The table which calls this event
	 * @param   object|array  &$src    The data to bind
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src)
	{
		// Set rules for assets enabled tables
		if ($table->isAssetsTracked())
		{
			// Bind the rules.
			if (isset($src['rules']) && is_array($src['rules']))
			{
                // We have to manually remove any empty value, since they will be converted to int,
                // and "Inherited" values will become "Denied". Joomla is doing this manually, too.
                // @todo Should we move this logic inside the setRules method?
                $rules = array();

                foreach ($src['rules'] as $action => $ids)
                {
                    // Build the rules array.
                    $rules[$action] = array();

                    foreach ($ids as $id => $p)
                    {
                        if ($p !== '')
                        {
                            $rules[$action][$id] = ($p == '1' || $p == 'true') ? true : false;
                        }
                    }
                }

				$table->setRules($rules);
			}
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   integer   $oid     The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		// If tracking assets, remove the asset first.
		if ($table->isAssetsTracked())
		{
            $k = $table->getKeyName();

            // If the table is not loaded, let's try to load it with the id
            if(!$table->$k)
            {
                $table->load($oid);
            }

            // If I have an invalid assetName I have to stop
            try
            {
                $name = $table->getAssetName();
            }
            catch(Exception $e)
            {
                $table->setError($e->getMessage());
                return false;
            }

			// Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOFTable
			$asset = JTable::getInstance('Asset');

			if ($asset->loadByName($name))
			{
                // Since we are using JTable, there is no way to mock it and test for failures :(
                // @codeCoverageIgnoreStart
				if (!$asset->delete())
				{
					$table->setError($asset->getError());

					return false;
				}
                // @codeCoverageIgnoreEnd
			}
			else
			{
                // I'll simply return true even if I couldn't load the asset. In this way I can still
                // delete a broken record
				return true;
			}
		}

		return true;
	}
}
fof/table/behavior/tags.php000064400000005740152177723700011674 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for tags
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFTableBehaviorTags extends FOFTableBehavior
{
	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   FOFTable  		&$table  	The table which calls this event
	 * @param   object|array  	&$src  		The data to bind
	 * @param  	array 			$options 	The options of the table
	 *
	 * @return  boolean  True on success
	 */
	public function onAfterBind(&$table, &$src, $options = array())
	{
		// Bind tags
		if ($table->hasTags())
		{
			if ((!empty($src['tags']) && $src['tags'][0] != ''))
			{
				$table->newTags = $src['tags'];
			}

			// Check if the content type exists, and create it if it does not
			$table->checkContentType();

			$tagsTable = clone($table);

			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			// TODO: This little guy here fails because JHelperTags
			// need a JTable object to work, while our is FOFTable
			// Need probably to write our own FOFHelperTags
			// Thank you com_tags
			if (!$tagsHelper->postStoreProcess($tagsTable))
			{
				$table->setError('Error storing tags');
				return false;
			}
		}

		return true;
	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true) or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	public function onBeforeStore(&$table, $updateNulls)
	{
		if ($table->hasTags())
		{
			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			// TODO: JHelperTags sucks in Joomla! 3.1, it requires that tags are
			// stored in the metadata property. Not our case, therefore we need
			// to add it in a fake object. We sent a PR to Joomla! CMS to fix
			// that. Once it's accepted, we'll have to remove the attrocity
			// here...
			$tagsTable = clone($table);
			$tagsHelper->preStoreProcess($tagsTable);
		}
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	public function onAfterDelete(&$table, $oid)
	{
		// If this resource has tags, delete the tags first
		if ($table->hasTags())
		{
			$tagsHelper = new JHelperTags();
			$tagsHelper->typeAlias = $table->getContentType();

			if (!$tagsHelper->deleteTagData($table, $oid))
			{
				$table->setError('Error deleting Tags');
				return false;
			}
		}
	}
}
fof/table/behavior/contenthistory.php000064400000003146152177723700014030 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework table behavior class for content History
 *
 * @package  FrameworkOnFramework
 * @since    2.2.0
 */
class FOFTableBehaviorContenthistory extends FOFTableBehavior
{
	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   FOFTable  &$table  The table which calls this event
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	public function onAfterStore(&$table)
	{
		$aliasParts = explode('.', $table->getContentType());
		$table->checkContentType();

		if (JComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
		{
			$historyHelper = new JHelperContenthistory($table->getContentType());
			$historyHelper->store($table);
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   FOFTable &$table  The table which calls this event
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(&$table, $oid)
	{
		$aliasParts = explode('.', $table->getContentType());

		if (JComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
		{
			$historyHelper = new JHelperContenthistory($table->getContentType());
			$historyHelper->deleteHistory($table);
		}

		return true;
	}
}
fof/table/table.php000064400000261621152177723700010230 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Normally this shouldn't be required. Some PHP versions, however, seem to
 * require this. Why? No idea whatsoever. If I remove it, FOF crashes on some
 * hosts. Same PHP version on another host and no problem occurs. Any takers?
 */
if (class_exists('FOFTable', false))
{
	return;
}

if (!interface_exists('JTableInterface', true))
{
	interface JTableInterface {}
}

/**
 * FrameworkOnFramework Table class. The Table is one part controller, one part
 * model and one part data adapter. It's supposed to handle operations for single
 * records.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFTable extends FOFUtilsObject implements JTableInterface
{
	/**
	 * Cache array for instances
	 *
	 * @var    array
	 */
	protected static $instances = array();

	/**
	 * Include paths for searching for FOFTable classes.
	 *
	 * @var    array
	 */
	protected static $_includePaths = array();

	/**
	 * The configuration parameters array
	 *
	 * @var  array
	 */
	protected $config = array();

	/**
	 * Name of the database table to model.
	 *
	 * @var    string
	 */
	protected $_tbl = '';

	/**
	 * Name of the primary key field in the table.
	 *
	 * @var    string
	 */
	protected $_tbl_key = '';

	/**
	 * FOFDatabaseDriver object.
	 *
	 * @var    FOFDatabaseDriver
	 */
	protected $_db;

	/**
	 * Should rows be tracked as ACL assets?
	 *
	 * @var    boolean
	 */
	protected $_trackAssets = false;

	/**
	 * Does the resource support joomla tags?
	 *
	 * @var    boolean
	 */
	protected $_has_tags = false;

	/**
	 * The rules associated with this record.
	 *
	 * @var    JAccessRules  A JAccessRules object.
	 */
	protected $_rules;

	/**
	 * Indicator that the tables have been locked.
	 *
	 * @var    boolean
	 */
	protected $_locked = false;

	/**
	 * If this is set to true, it triggers automatically plugin events for
	 * table actions
	 *
	 * @var    boolean
	 */
	protected $_trigger_events = false;

	/**
	 * Table alias used in queries
	 *
	 * @var    string
	 */
	protected $_tableAlias = false;

	/**
	 * Array with alias for "special" columns such as ordering, hits etc etc
	 *
	 * @var    array
	 */
	protected $_columnAlias = array();

	/**
	 * If set to true, it enabled automatic checks on fields based on columns properties
	 *
	 * @var    boolean
	 */
	protected $_autoChecks = false;

	/**
	 * Array with fields that should be skipped by automatic checks
	 *
	 * @var    array
	 */
	protected $_skipChecks = array();

	/**
	 * Does the table actually exist? We need that to avoid PHP notices on
	 * table-less views.
	 *
	 * @var    boolean
	 */
	protected $_tableExists = true;

	/**
	 * The asset key for items in this table. It's usually something in the
	 * com_example.viewname format. They asset name will be this key appended
	 * with the item's ID, e.g. com_example.viewname.123
	 *
	 * @var    string
	 */
	protected $_assetKey = '';

	/**
	 * The input data
	 *
	 * @var    FOFInput
	 */
	protected $input = null;

	/**
	 * Extended query including joins with other tables
	 *
	 * @var    FOFDatabaseQuery
	 */
	protected $_queryJoin = null;

	/**
	 * The prefix for the table class
	 *
	 * @var		string
	 */
	protected $_tablePrefix = '';

	/**
	 * The known fields for this table
	 *
	 * @var		array
	 */
	protected $knownFields = array();

	/**
	 * A list of table fields, keyed per table
	 *
	 * @var array
	 */
	protected static $tableFieldCache = array();

	/**
	 * A list of tables in the database
	 *
	 * @var array
	 */
	protected static $tableCache = array();

	/**
	 * An instance of FOFConfigProvider to provision configuration overrides
	 *
	 * @var    FOFConfigProvider
	 */
	protected $configProvider = null;

	/**
	 * FOFTableDispatcherBehavior for dealing with extra behaviors
	 *
	 * @var    FOFTableDispatcherBehavior
	 */
	protected $tableDispatcher = null;

	/**
	 * List of default behaviors to apply to the table
	 *
	 * @var    array
	 */
	protected $default_behaviors = array('tags', 'assets');

	/**
	 * The relations object of the table. It's lazy-loaded by getRelations().
	 *
	 * @var   FOFTableRelations
	 */
	protected $_relations = null;

	/**
	 * The configuration provider's key for this table, e.g. foobar.tables.bar for the #__foobar_bars table. This is set
	 * automatically by the constructor
	 *
	 * @var  string
	 */
	protected $_configProviderKey = '';

	/**
	 * The content type of the table. Required if using tags or content history behaviour
	 *
	 * @var  string
	 */
	protected $contentType = null;

	/**
	 * Returns a static object instance of a particular table type
	 *
	 * @param   string  $type    The table name
	 * @param   string  $prefix  The prefix of the table class
	 * @param   array   $config  Optional configuration variables
	 *
	 * @return FOFTable
	 */
	public static function getInstance($type, $prefix = 'JTable', $config = array())
	{
		return self::getAnInstance($type, $prefix, $config);
	}

	/**
	 * Returns a static object instance of a particular table type
	 *
	 * @param   string  $type    The table name
	 * @param   string  $prefix  The prefix of the table class
	 * @param   array   $config  Optional configuration variables
	 *
	 * @return FOFTable
	 */
	public static function &getAnInstance($type = null, $prefix = 'JTable', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Guess the component name
		if (!array_key_exists('input', $config))
		{
			$config['input'] = new FOFInput;
		}

		if ($config['input'] instanceof FOFInput)
		{
			$tmpInput = $config['input'];
		}
		else
		{
			$tmpInput = new FOFInput($config['input']);
		}

		$option = $tmpInput->getCmd('option', '');
		$tmpInput->set('option', $option);
		$config['input'] = $tmpInput;

		if (!in_array($prefix, array('Table', 'JTable')))
		{
			preg_match('/(.*)Table$/', $prefix, $m);
			$option = 'com_' . strtolower($m[1]);
		}

		if (array_key_exists('option', $config))
		{
			$option = $config['option'];
		}

		$config['option'] = $option;

		if (!array_key_exists('view', $config))
		{
			$config['view'] = $config['input']->getCmd('view', 'cpanel');
		}

		if (is_null($type))
		{
			if ($prefix == 'JTable')
			{
				$prefix = 'Table';
			}

			$type = $config['view'];
		}

		$type       = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
		$tableClass = $prefix . ucfirst($type);

		$config['_table_type'] = $type;
		$config['_table_class'] = $tableClass;

		$configProvider = new FOFConfigProvider;
		$configProviderKey = $option . '.views.' . FOFInflector::singularize($type) . '.config.';

		if (!array_key_exists($tableClass, self::$instances))
		{
			if (!class_exists($tableClass))
			{
				$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

				$searchPaths = array(
					$componentPaths['main'] . '/tables',
					$componentPaths['admin'] . '/tables'
				);

				if (array_key_exists('tablepath', $config))
				{
					array_unshift($searchPaths, $config['tablepath']);
				}

				$altPath = $configProvider->get($configProviderKey . 'table_path', null);

				if ($altPath)
				{
					array_unshift($searchPaths, $componentPaths['admin'] . '/' . $altPath);
				}

                $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

				$path = $filesystem->pathFind(
					$searchPaths, strtolower($type) . '.php'
				);

				if ($path)
				{
					require_once $path;
				}
			}

			if (!class_exists($tableClass))
			{
				$tableClass = 'FOFTable';
			}

			$component = str_replace('com_', '', $config['option']);
			$tbl_common = $component . '_';

			if (!array_key_exists('tbl', $config))
			{
				$config['tbl'] = strtolower('#__' . $tbl_common . strtolower(FOFInflector::pluralize($type)));
			}

			$altTbl = $configProvider->get($configProviderKey . 'tbl', null);

			if ($altTbl)
			{
				$config['tbl'] = $altTbl;
			}

			if (!array_key_exists('tbl_key', $config))
			{
				$keyName           = FOFInflector::singularize($type);
				$config['tbl_key'] = strtolower($tbl_common . $keyName . '_id');
			}

			$altTblKey = $configProvider->get($configProviderKey . 'tbl_key', null);

			if ($altTblKey)
			{
				$config['tbl_key'] = $altTblKey;
			}

			if (!array_key_exists('db', $config))
			{
				$config['db'] = FOFPlatform::getInstance()->getDbo();
			}

			// Assign the correct table alias
			if (array_key_exists('table_alias', $config))
			{
				$table_alias = $config['table_alias'];
			}
			else
			{
				$configProviderTableAliasKey = $option . '.tables.' . FOFInflector::singularize($type) . '.tablealias';
				$table_alias = $configProvider->get($configProviderTableAliasKey, false	);
			}

			// Can we use the FOF cache?
			if (!array_key_exists('use_table_cache', $config))
			{
				$config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled();
			}

			$alt_use_table_cache = $configProvider->get($configProviderKey . 'use_table_cache', null);

			if (!is_null($alt_use_table_cache))
			{
				$config['use_table_cache'] = $alt_use_table_cache;
			}

			// Create a new table instance
			$instance = new $tableClass($config['tbl'], $config['tbl_key'], $config['db'], $config);
			$instance->setInput($tmpInput);
			$instance->setTablePrefix($prefix);
			$instance->setTableAlias($table_alias);

			// Determine and set the asset key for this table
			$assetKey = 'com_' . $component . '.' . strtolower(FOFInflector::singularize($type));
			$assetKey = $configProvider->get($configProviderKey . 'asset_key', $assetKey);
			$instance->setAssetKey($assetKey);

			if (array_key_exists('trigger_events', $config))
			{
				$instance->setTriggerEvents($config['trigger_events']);
			}

			if (version_compare(JVERSION, '3.1', 'ge'))
			{
				if (array_key_exists('has_tags', $config))
				{
					$instance->setHasTags($config['has_tags']);
				}

				$altHasTags = $configProvider->get($configProviderKey . 'has_tags', null);

				if ($altHasTags)
				{
					$instance->setHasTags($altHasTags);
				}
			}
			else
			{
				$instance->setHasTags(false);
			}

			$configProviderFieldmapKey = $option . '.tables.' . FOFInflector::singularize($type) . '.field';
			$aliases = $configProvider->get($configProviderFieldmapKey, $instance->_columnAlias);
			$instance->_columnAlias = array_merge($instance->_columnAlias, $aliases);

			self::$instances[$tableClass] = $instance;
		}

		return self::$instances[$tableClass];
	}

	/**
	 * Force an instance inside class cache. Setting arguments to null nukes all or part of the cache
	 *
	 * @param    string|null       $key        TableClass to replace. Set it to null to nuke the entire cache
	 * @param    FOFTable|null     $instance   Instance to replace. Set it to null to nuke $key instances
	 *
	 * @return   bool              Did I correctly switch the instance?
	 */
	public static function forceInstance($key = null, $instance = null)
	{
		if(is_null($key))
		{
			self::$instances = array();

			return true;
		}
		elseif($key && isset(self::$instances[$key]))
		{
			// I'm forcing an instance, but it's not a FOFTable, abort! abort!
			if(!$instance || ($instance && $instance instanceof FOFTable))
			{
				self::$instances[$key] = $instance;

				return true;
			}
		}

		return false;
	}

	/**
	 * Class Constructor.
	 *
	 * @param   string           $table   Name of the database table to model.
	 * @param   string           $key     Name of the primary key field in the table.
	 * @param   FOFDatabaseDriver  &$db     Database driver
	 * @param   array            $config  The configuration parameters array
	 */
	public function __construct($table, $key, &$db, $config = array())
	{
		$this->_tbl     = $table;
		$this->_tbl_key = $key;
		$this->_db      = $db;

		// Make sure the use FOF cache information is in the config
		if (!array_key_exists('use_table_cache', $config))
		{
			$config['use_table_cache'] = FOFPlatform::getInstance()->isGlobalFOFCacheEnabled();
		}
		$this->config   = $config;

		// Load the configuration provider
		$this->configProvider = new FOFConfigProvider;

		// Load the behavior dispatcher
		$this->tableDispatcher = new FOFTableDispatcherBehavior;

		// Initialise the table properties.

		if ($fields = $this->getTableFields())
		{
			// Do I have anything joined?
			$j_fields = $this->getQueryJoinFields();

			if ($j_fields)
			{
				$fields = array_merge($fields, $j_fields);
			}

			$this->setKnownFields(array_keys($fields), true);
			$this->reset();
		}
		else
		{
			$this->_tableExists = false;
		}

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Set the $name/$_name variable
		$component = $this->input->getCmd('option', 'com_foobar');

		if (array_key_exists('option', $config))
		{
			$component = $config['option'];
		}

		$this->input->set('option', $component);

		// Apply table behaviors
		$type = explode("_", $this->_tbl);
		$type = $type[count($type) - 1];

		$this->_configProviderKey = $component . '.tables.' . FOFInflector::singularize($type);

		$configKey = $this->_configProviderKey . '.behaviors';

		if (isset($config['behaviors']))
		{
			$behaviors = (array) $config['behaviors'];
		}
		elseif ($behaviors = $this->configProvider->get($configKey, null))
		{
			$behaviors = explode(',', $behaviors);
		}
		else
		{
			$behaviors = $this->default_behaviors;
		}

		if (is_array($behaviors) && count($behaviors))
		{
			foreach ($behaviors as $behavior)
			{
				$this->addBehavior($behavior);
			}
		}

		// If we are tracking assets, make sure an access field exists and initially set the default.
		$asset_id_field	= $this->getColumnAlias('asset_id');
		$access_field	= $this->getColumnAlias('access');

		if (in_array($asset_id_field, $this->getKnownFields()))
		{
			JLoader::import('joomla.access.rules');
			$this->_trackAssets = true;
		}

		// If the access property exists, set the default.
		if (in_array($access_field, $this->getKnownFields()))
		{
			$this->$access_field = (int) FOFPlatform::getInstance()->getConfig()->get('access');
		}

		$this->config = $config;
	}

	/**
	 * Replace the entire known fields array
	 *
	 * @param   array    $fields      A simple array of known field names
	 * @param   boolean  $initialise  Should we initialise variables to null?
	 *
	 * @return  void
	 */
	public function setKnownFields($fields, $initialise = false)
	{
		$this->knownFields = $fields;

		if ($initialise)
		{
			foreach ($this->knownFields as $field)
			{
				$this->$field = null;
			}
		}
	}

	/**
	 * Get the known fields array
	 *
	 * @return  array
	 */
	public function getKnownFields()
	{
		return $this->knownFields;
	}

	/**
	 * Does the specified field exist?
	 *
	 * @param   string  $fieldName  The field name to search (it's OK to use aliases)
	 *
	 * @return  bool
	 */
	public function hasField($fieldName)
	{
		$search = $this->getColumnAlias($fieldName);

		return in_array($search, $this->knownFields);
	}

	/**
	 * Add a field to the known fields array
	 *
	 * @param   string   $field       The name of the field to add
	 * @param   boolean  $initialise  Should we initialise the variable to null?
	 *
	 * @return  void
	 */
	public function addKnownField($field, $initialise = false)
	{
		if (!in_array($field, $this->knownFields))
		{
			$this->knownFields[] = $field;

			if ($initialise)
			{
				$this->$field = null;
			}
		}
	}

	/**
	 * Remove a field from the known fields array
	 *
	 * @param   string  $field  The name of the field to remove
	 *
	 * @return  void
	 */
	public function removeKnownField($field)
	{
		if (in_array($field, $this->knownFields))
		{
			$pos = array_search($field, $this->knownFields);
			unset($this->knownFields[$pos]);
		}
	}

	/**
	 * Adds a behavior to the table
	 *
	 * @param   string  $name    The name of the behavior
	 * @param   array   $config  Optional Behavior configuration
	 *
	 * @return  boolean
	 */
	public function addBehavior($name, $config = array())
	{
		// First look for ComponentnameTableViewnameBehaviorName (e.g. FoobarTableItemsBehaviorTags)
		if (isset($this->config['option']))
		{
			$option_name = str_replace('com_', '', $this->config['option']);
			$behaviorClass = $this->config['_table_class'] . 'Behavior' . ucfirst(strtolower($name));

			if (class_exists($behaviorClass))
			{
				$behavior = new $behaviorClass($this->tableDispatcher, $config);

				return true;
			}

			// Then look for ComponentnameTableBehaviorName (e.g. FoobarTableBehaviorTags)
			$option_name = str_replace('com_', '', $this->config['option']);
			$behaviorClass = ucfirst($option_name) . 'TableBehavior' . ucfirst(strtolower($name));

			if (class_exists($behaviorClass))
			{
				$behavior = new $behaviorClass($this->tableDispatcher, $config);

				return true;
			}
		}

		// Nothing found? Return false.

		$behaviorClass = 'FOFTableBehavior' . ucfirst(strtolower($name));

		if (class_exists($behaviorClass) && $this->tableDispatcher)
		{
			$behavior = new $behaviorClass($this->tableDispatcher, $config);

			return true;
		}

		return false;
	}

	/**
	 * Sets the events trigger switch state
	 *
	 * @param   boolean  $newState  The new state of the switch (what else could it be?)
	 *
	 * @return  void
	 */
	public function setTriggerEvents($newState = false)
	{
		$this->_trigger_events = $newState ? true : false;
	}

	/**
	 * Gets the events trigger switch state
	 *
	 * @return  boolean
	 */
	public function getTriggerEvents()
	{
		return $this->_trigger_events;
	}

	/**
	 * Gets the has tags switch state
	 *
	 * @return bool
	 */
	public function hasTags()
	{
		return $this->_has_tags;
	}

	/**
	 * Sets the has tags switch state
	 *
	 * @param   bool  $newState
	 */
	public function setHasTags($newState = false)
	{
		$this->_has_tags = false;

		// Tags are available only in 3.1+
		if (version_compare(JVERSION, '3.1', 'ge'))
		{
			$this->_has_tags = $newState ? true : false;
		}
	}

	/**
	 * Set the class prefix
	 *
	 * @param string $prefix The prefix
	 */
	public function setTablePrefix($prefix)
	{
		$this->_tablePrefix = $prefix;
	}

	/**
	 * Sets fields to be skipped from automatic checks.
	 *
	 * @param   array/string  $skip  Fields to be skipped by automatic checks
	 *
	 * @return void
	 */
	public function setSkipChecks($skip)
	{
		$this->_skipChecks = (array) $skip;
	}

	/**
	 * Method to load a row from the database by primary key and bind the fields
	 * to the FOFTable instance properties.
	 *
	 * @param   mixed    $keys   An optional primary key value to load the row by, or an array of fields to match.  If not
	 *                           set the instance property value is used.
	 * @param   boolean  $reset  True to reset the default values before loading the new row.
	 *
	 * @return  boolean  True if successful. False if row not found.
	 *
	 * @throws  RuntimeException
	 * @throws  UnexpectedValueException
	 */
	public function load($keys = null, $reset = true)
	{
		if (!$this->_tableExists)
		{
			$result = false;

            return $this->onAfterLoad($result);
		}

		if (empty($keys))
		{
			// If empty, use the value of the current key
			$keyName = $this->_tbl_key;

			if (isset($this->$keyName))
			{
				$keyValue = $this->$keyName;
			}
			else
			{
				$keyValue = null;
			}

			// If empty primary key there's is no need to load anything

			if (empty($keyValue))
			{
				$result = true;

				return $this->onAfterLoad($result);
			}

			$keys = array($keyName => $keyValue);
		}
		elseif (!is_array($keys))
		{
			// Load by primary key.
			$keys = array($this->_tbl_key => $keys);
		}

		if ($reset)
		{
			$this->reset();
		}

		// Initialise the query.
		$query = $this->_db->getQuery(true);
		$query->select($this->_tbl . '.*');
		$query->from($this->_tbl);

		// Joined fields are ok, since I initialized them in the constructor
		$fields = $this->getKnownFields();

		foreach ($keys as $field => $value)
		{
			// Check that $field is in the table.

			if (!in_array($field, $fields))
			{
				throw new UnexpectedValueException(sprintf('Missing field in table %s : %s.', $this->_tbl, $field));
			}

			// Add the search tuple to the query.
			$query->where($this->_db->qn($this->_tbl . '.' . $field) . ' = ' . $this->_db->q($value));
		}

		// Do I have any joined table?
		$j_query = $this->getQueryJoin();

		if ($j_query)
		{
			if ($j_query->select && $j_query->select->getElements())
			{
				//$query->select($this->normalizeSelectFields($j_query->select->getElements(), true));
				$query->select($j_query->select->getElements());
			}

			if ($j_query->join)
			{
				foreach ($j_query->join as $join)
				{
					$t = (string) $join;

					// Joomla doesn't provide any access to the "name" variable, so I have to work with strings...
					if (stripos($t, 'inner') !== false)
					{
						$query->innerJoin($join->getElements());
					}
					elseif (stripos($t, 'left') !== false)
					{
						$query->leftJoin($join->getElements());
					}
					elseif (stripos($t, 'right') !== false)
					{
						$query->rightJoin($join->getElements());
					}
					elseif (stripos($t, 'outer') !== false)
					{
						$query->outerJoin($join->getElements());
					}
				}
			}
		}

		$this->_db->setQuery($query);

		$row = $this->_db->loadAssoc();

		// Check that we have a result.
		if (empty($row))
		{
			$result = false;

			return $this->onAfterLoad($result);
		}

		// Bind the object with the row and return.
		$result = $this->bind($row);

		$this->onAfterLoad($result);

		return $result;
	}

	/**
	 * Based on fields properties (nullable column), checks if the field is required or not
	 *
	 * @return boolean
	 */
	public function check()
	{
		if (!$this->_autoChecks)
		{
			return true;
		}

		$fields = $this->getTableFields();

        // No fields? Why in the hell am I here?
        if(!$fields)
        {
            return false;
        }

        $result       = true;
        $known        = $this->getKnownFields();
        $skipFields[] = $this->_tbl_key;

		if(in_array($this->getColumnAlias('title'), $known)
			&& in_array($this->getColumnAlias('slug'), $known))      $skipFields[] = $this->getColumnAlias('slug');
        if(in_array($this->getColumnAlias('hits'), $known))         $skipFields[] = $this->getColumnAlias('hits');
        if(in_array($this->getColumnAlias('created_on'), $known))   $skipFields[] = $this->getColumnAlias('created_on');
        if(in_array($this->getColumnAlias('created_by'), $known))   $skipFields[] = $this->getColumnAlias('created_by');
        if(in_array($this->getColumnAlias('modified_on'), $known))  $skipFields[] = $this->getColumnAlias('modified_on');
        if(in_array($this->getColumnAlias('modified_by'), $known))  $skipFields[] = $this->getColumnAlias('modified_by');
        if(in_array($this->getColumnAlias('locked_by'), $known))    $skipFields[] = $this->getColumnAlias('locked_by');
        if(in_array($this->getColumnAlias('locked_on'), $known))    $skipFields[] = $this->getColumnAlias('locked_on');

        // Let's merge it with custom skips
        $skipFields = array_merge($skipFields, $this->_skipChecks);

		foreach ($fields as $field)
		{
			$fieldName = $field->Field;

			if (empty($fieldName))
			{
				$fieldName = $field->column_name;
			}

			// Field is not nullable but it's null, set error

			if ($field->Null == 'NO' && $this->$fieldName == '' && !in_array($fieldName, $skipFields))
			{
				$text = str_replace('#__', 'COM_', $this->getTableName()) . '_ERR_' . $fieldName;
				$this->setError(JText::_(strtoupper($text)));
				$result = false;
			}
		}

		return $result;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties.
	 *
	 * @return void
	 */
	public function reset()
	{
		if (!$this->onBeforeReset())
		{
			return false;
		}

		// Get the default values for the class from the table.
		$fields   = $this->getTableFields();
		$j_fields = $this->getQueryJoinFields();

		if ($j_fields)
		{
			$fields = array_merge($fields, $j_fields);
		}

		if (is_array($fields) && !empty($fields))
		{
			foreach ($fields as $k => $v)
			{
				// If the property is not the primary key or private, reset it.
				if ($k != $this->_tbl_key && (strpos($k, '_') !== 0))
				{
					$this->$k = $v->Default;
				}
			}

			if (!$this->onAfterReset())
			{
				return false;
			}
		}
	}

    /**
     * Clones the current object, after resetting it
     *
     * @return static
     */
    public function getClone()
    {
        $clone = clone $this;
        $clone->reset();

        $key = $this->getKeyName();
        $clone->$key = null;

        return $clone;
    }

	/**
	 * Generic check for whether dependencies exist for this object in the db schema
	 *
	 * @param   integer  $oid    The primary key of the record to delete
	 * @param   array    $joins  Any joins to foreign table, used to determine if dependent records exist
	 *
	 * @return  boolean  True if the record can be deleted
	 */
	public function canDelete($oid = null, $joins = null)
	{
		$k = $this->_tbl_key;

		if ($oid)
		{
			$this->$k = intval($oid);
		}

		if (is_array($joins))
		{
			$db      = $this->_db;
			$query   = $db->getQuery(true)
				->select($db->qn('master') . '.' . $db->qn($k))
				->from($db->qn($this->_tbl) . ' AS ' . $db->qn('master'));
			$tableNo = 0;

			foreach ($joins as $table)
			{
				$tableNo++;
				$query->select(
					array(
						'COUNT(DISTINCT ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['idfield']) . ') AS ' . $db->qn($table['idalias'])
					)
				);
				$query->join('LEFT', $db->qn($table['name']) .
					' AS ' . $db->qn('t' . $tableNo) .
					' ON ' . $db->qn('t' . $tableNo) . '.' . $db->qn($table['joinfield']) .
					' = ' . $db->qn('master') . '.' . $db->qn($k)
				);
			}

			$query->where($db->qn('master') . '.' . $db->qn($k) . ' = ' . $db->q($this->$k));
			$query->group($db->qn('master') . '.' . $db->qn($k));
			$this->_db->setQuery((string) $query);

			if (version_compare(JVERSION, '3.0', 'ge'))
			{
				try
				{
					$obj = $this->_db->loadObject();
				}
				catch (Exception $e)
				{
					$this->setError($e->getMessage());
				}
			}
			else
			{
				if (!$obj = $this->_db->loadObject())
				{
					$this->setError($this->_db->getErrorMsg());

					return false;
				}
			}

			$msg = array();
			$i   = 0;

			foreach ($joins as $table)
			{
				$k = $table['idalias'];

				if ($obj->$k > 0)
				{
					$msg[] = JText::_($table['label']);
				}

				$i++;
			}

			if (count($msg))
			{
				$option  = $this->input->getCmd('option', 'com_foobar');
				$comName = str_replace('com_', '', $option);
				$tview   = str_replace('#__' . $comName . '_', '', $this->_tbl);
				$prefix  = $option . '_' . $tview . '_NODELETE_';

				foreach ($msg as $key)
				{
					$this->setError(JText::_($prefix . $key));
				}

				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * Method to bind an associative array or object to the FOFTable instance.This
	 * method only binds properties that are publicly accessible and optionally
	 * takes an array of properties to ignore when binding.
	 *
	 * @param   mixed  $src     An associative array or object to bind to the FOFTable instance.
	 * @param   mixed  $ignore  An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @throws  InvalidArgumentException
	 */
	public function bind($src, $ignore = array())
	{
		if (!$this->onBeforeBind($src))
		{
			return false;
		}

		// If the source value is not an array or object return false.
		if (!is_object($src) && !is_array($src))
		{
			throw new InvalidArgumentException(sprintf('%s::bind(*%s*)', get_class($this), gettype($src)));
		}

		// If the source value is an object, get its accessible properties.
		if (is_object($src))
		{
			$src = get_object_vars($src);
		}

		// If the ignore value is a string, explode it over spaces.
		if (!is_array($ignore))
		{
			$ignore = explode(' ', $ignore);
		}

		// Bind the source value, excluding the ignored fields.
		foreach ($this->getKnownFields() as $k)
		{
			// Only process fields not in the ignore array.
			if (!in_array($k, $ignore))
			{
				if (isset($src[$k]))
				{
					$this->$k = $src[$k];
				}
			}
		}

		$result = $this->onAfterBind($src);

		return $result;
	}

	/**
	 * Method to store a row in the database from the FOFTable instance properties.
	 * If a primary key value is set the row with that primary key value will be
	 * updated with the instance property values.  If no primary key value is set
	 * a new row will be inserted into the database with the properties from the
	 * FOFTable instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 */
	public function store($updateNulls = false)
	{
		if (!$this->onBeforeStore($updateNulls))
		{
			return false;
		}

		$k = $this->_tbl_key;

		if ($this->$k == 0)
		{
			$this->$k = null;
		}

		// Create the object used for inserting/updating data to the database
		$fields     = $this->getTableFields();
		$properties = $this->getKnownFields();
		$keys       = array();

		foreach ($properties as $property)
		{
			// 'input' property is a reserved name

			if (isset($fields[$property]))
			{
				$keys[] = $property;
			}
		}

		$updateObject = array();
		foreach ($keys as $key)
		{
			$updateObject[$key] = $this->$key;
		}
		$updateObject = (object)$updateObject;

		/**
		 * While the documentation for update/insertObject and execute() say they return a boolean,
		 * not all of the implemtnations.  Depending on the version of J! and the specific driver,
		 * they may return a database object, or boolean, or a mix, or toss an exception.  So try/catch,
		 * and test for false.
		 */

		try
		{
			// If a primary key exists update the object, otherwise insert it.
			if ($this->$k)
			{
				$result = $this->_db->updateObject($this->_tbl, $updateObject, $this->_tbl_key, $updateNulls);
			}
			else
			{
				$result = $this->_db->insertObject($this->_tbl, $updateObject, $this->_tbl_key);
			}

			if ($result === false)
			{
				$this->setError($this->_db->getErrorMsg());

				return false;
			}
		}
		catch (Exception $e)
		{
			$this->setError($e->getMessage());
		}

		$this->bind($updateObject);

		if ($this->_locked)
		{
			$this->_unlock();
		}

		$result = $this->onAfterStore();

		return $result;
	}

	/**
	 * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
	 * Negative numbers move the row up in the sequence and positive numbers move it down.
	 *
	 * @param   integer  $delta  The direction and magnitude to move the row in the ordering sequence.
	 * @param   string   $where  WHERE clause to use for limiting the selection of rows to compact the
	 *                           ordering values.
	 *
	 * @return  mixed    Boolean  True on success.
	 *
	 * @throws  UnexpectedValueException
	 */
	public function move($delta, $where = '')
	{
		if (!$this->onBeforeMove($delta, $where))
		{
			return false;
		}

		// If there is no ordering field set an error and return false.
		$ordering_field = $this->getColumnAlias('ordering');

		if (!in_array($ordering_field, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl));
		}

		// If the change is none, do nothing.
		if (empty($delta))
		{
			$result = $this->onAfterMove();

			return $result;
		}

		$k     = $this->_tbl_key;
		$row   = null;
		$query = $this->_db->getQuery(true);

        // If the table is not loaded, return false
        if (empty($this->$k))
        {
            return false;
        }

		// Select the primary key and ordering values from the table.
		$query->select(array($this->_db->qn($this->_tbl_key), $this->_db->qn($ordering_field)));
		$query->from($this->_tbl);

		// If the movement delta is negative move the row up.

		if ($delta < 0)
		{
			$query->where($this->_db->qn($ordering_field) . ' < ' . $this->_db->q((int) $this->$ordering_field));
			$query->order($this->_db->qn($ordering_field) . ' DESC');
		}

		// If the movement delta is positive move the row down.

		elseif ($delta > 0)
		{
			$query->where($this->_db->qn($ordering_field) . ' > ' . $this->_db->q((int) $this->$ordering_field));
			$query->order($this->_db->qn($ordering_field) . ' ASC');
		}

		// Add the custom WHERE clause if set.

		if ($where)
		{
			$query->where($where);
		}

		// Select the first row with the criteria.
		$this->_db->setQuery($query, 0, 1);
		$row = $this->_db->loadObject();

		// If a row is found, move the item.

		if (!empty($row))
		{
			// Update the ordering field for this instance to the row's ordering value.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $row->$ordering_field));
			$query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the ordering field for the row to this instance's ordering value.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field));
			$query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the instance value.
			$this->$ordering_field = $row->$ordering_field;
		}
		else
		{
			// Update the ordering field for this instance.
			$query = $this->_db->getQuery(true);
			$query->update($this->_tbl);
			$query->set($this->_db->qn($ordering_field) . ' = ' . $this->_db->q((int) $this->$ordering_field));
			$query->where($this->_tbl_key . ' = ' . $this->_db->q($this->$k));
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		$result = $this->onAfterMove();

		return $result;
	}

    /**
     * Change the ordering of the records of the table
     *
     * @param   string   $where  The WHERE clause of the SQL used to fetch the order
     *
     * @return  boolean  True is successful
     *
     * @throws  UnexpectedValueException
     */
	public function reorder($where = '')
	{
		if (!$this->onBeforeReorder($where))
		{
			return false;
		}

		// If there is no ordering field set an error and return false.

		$order_field = $this->getColumnAlias('ordering');

		if (!in_array($order_field, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support ordering.', $this->_tbl_key));
		}

		$k = $this->_tbl_key;

		// Get the primary keys and ordering values for the selection.
		$query = $this->_db->getQuery(true);
		$query->select($this->_tbl_key . ', ' . $this->_db->qn($order_field));
		$query->from($this->_tbl);
		$query->where($this->_db->qn($order_field) . ' >= ' . $this->_db->q(0));
		$query->order($this->_db->qn($order_field));

		// Setup the extra where and ordering clause data.

		if ($where)
		{
			$query->where($where);
		}

		$this->_db->setQuery($query);
		$rows = $this->_db->loadObjectList();

		// Compact the ordering values.

		foreach ($rows as $i => $row)
		{
			// Make sure the ordering is a positive integer.

			if ($row->$order_field >= 0)
			{
				// Only update rows that are necessary.

				if ($row->$order_field != $i + 1)
				{
					// Update the row ordering field.
					$query = $this->_db->getQuery(true);
					$query->update($this->_tbl);
					$query->set($this->_db->qn($order_field) . ' = ' . $this->_db->q($i + 1));
					$query->where($this->_tbl_key . ' = ' . $this->_db->q($row->$k));
					$this->_db->setQuery($query);
					$this->_db->execute();
				}
			}
		}

		$result = $this->onAfterReorder();

		return $result;
	}

	/**
	 * Check out (lock) a record
	 *
	 * @param   integer  $userId  The locking user's ID
	 * @param   integer  $oid     The primary key value of the record to lock
	 *
	 * @return  boolean  True on success
	 */
	public function checkout($userId, $oid = null)
	{
		$fldLockedBy = $this->getColumnAlias('locked_by');
		$fldLockedOn = $this->getColumnAlias('locked_on');

		if (!(in_array($fldLockedBy, $this->getKnownFields())
			|| in_array($fldLockedOn, $this->getKnownFields())))
		{
			return true;
		}

		$k = $this->_tbl_key;

		if ($oid !== null)
		{
			$this->$k = $oid;
		}

        // No primary key defined, stop here
        if (!$this->$k)
        {
            return false;
        }

		$date = FOFPlatform::getInstance()->getDate();

		if (method_exists($date, 'toSql'))
		{
			$time = $date->toSql();
		}
		else
		{
			$time = $date->toMySQL();
		}


		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set(
				array(
					$this->_db->qn($fldLockedBy) . ' = ' . $this->_db->q((int) $userId),
					$this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($time)
				)
			)
			->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k));
		$this->_db->setQuery((string) $query);

		$this->$fldLockedBy = $userId;
		$this->$fldLockedOn = $time;

		return $this->_db->execute();
	}

	/**
	 * Check in (unlock) a record
	 *
	 * @param   integer  $oid  The primary key value of the record to unlock
	 *
	 * @return  boolean  True on success
	 */
	public function checkin($oid = null)
	{
		$fldLockedBy = $this->getColumnAlias('locked_by');
		$fldLockedOn = $this->getColumnAlias('locked_on');

		if (!(in_array($fldLockedBy, $this->getKnownFields())
			|| in_array($fldLockedOn, $this->getKnownFields())))
		{
			return true;
		}

		$k = $this->_tbl_key;

		if ($oid !== null)
		{
			$this->$k = $oid;
		}

		if ($this->$k == null)
		{
			return false;
		}

		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set(
				array(
					$this->_db->qn($fldLockedBy) . ' = 0',
					$this->_db->qn($fldLockedOn) . ' = ' . $this->_db->q($this->_db->getNullDate())
				)
			)
			->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($this->$k));
		$this->_db->setQuery((string) $query);

		$this->$fldLockedBy = 0;
		$this->$fldLockedOn = '';

		return $this->_db->execute();
	}

    /**
     * Is a record locked?
     *
     * @param   integer $with            The userid to preform the match with. If an item is checked
     *                                   out by this user the function will return false.
     * @param   integer $unused_against  Junk inherited from JTable; ignore
     *
     * @throws  UnexpectedValueException
     *
     * @return  boolean  True if the record is locked by another user
     */
	public function isCheckedOut($with = 0, $unused_against = null)
	{
        $against     = null;
		$fldLockedBy = $this->getColumnAlias('locked_by');

        $k  = $this->_tbl_key;

        // If no primary key is given, return false.

        if ($this->$k === null)
        {
            throw new UnexpectedValueException('Null primary key not allowed.');
        }

		if (isset($this) && is_a($this, 'FOFTable') && !$against)
		{
			$against = $this->get($fldLockedBy);
		}

		// Item is not checked out, or being checked out by the same user

		if (!$against || $against == $with)
		{
			return false;
		}

		$session = JTable::getInstance('session');

		return $session->exists($against);
	}

	/**
	 * Copy (duplicate) one or more records
	 *
	 * @param   integer|array  $cid  The primary key value (or values) or the record(s) to copy
	 *
	 * @return  boolean  True on success
	 */
	public function copy($cid = null)
	{
		//We have to cast the id as array, or the helper function will return an empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$k = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				$this->setError("No items selected.");

				return false;
			}
		}

		$created_by  = $this->getColumnAlias('created_by');
		$created_on  = $this->getColumnAlias('created_on');
		$modified_by = $this->getColumnAlias('modified_by');
		$modified_on = $this->getColumnAlias('modified_on');

		$locked_byName = $this->getColumnAlias('locked_by');
		$checkin       = in_array($locked_byName, $this->getKnownFields());

		foreach ($cid as $item)
		{
			// Prevent load with id = 0

			if (!$item)
			{
				continue;
			}

			$this->load($item);

			if ($checkin)
			{
				// We're using the checkin and the record is used by someone else

				if ($this->isCheckedOut($item))
				{
					continue;
				}
			}

			if (!$this->onBeforeCopy($item))
			{
				continue;
			}

			$this->$k           = null;
			$this->$created_by  = null;
			$this->$created_on  = null;
			$this->$modified_on = null;
			$this->$modified_by = null;

			// Let's fire the event only if everything is ok
			if ($this->store())
			{
				$this->onAfterCopy($item);
			}

			$this->reset();
		}

		return true;
	}

	/**
	 * Publish or unpublish records
	 *
	 * @param   integer|array  $cid      The primary key value(s) of the item(s) to publish/unpublish
	 * @param   integer        $publish  1 to publish an item, 0 to unpublish
	 * @param   integer        $user_id  The user ID of the user (un)publishing the item.
	 *
	 * @return  boolean  True on success, false on failure (e.g. record is locked)
	 */
	public function publish($cid = null, $publish = 1, $user_id = 0)
	{
		$enabledName   = $this->getColumnAlias('enabled');
		$locked_byName = $this->getColumnAlias('locked_by');

		// Mhm... you called the publish method on a table without publish support...
		if(!in_array($enabledName, $this->getKnownFields()))
		{
			return false;
		}

		//We have to cast the id as array, or the helper function will return an empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$user_id = (int) $user_id;
		$publish = (int) $publish;
		$k       = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				$this->setError("No items selected.");

				return false;
			}
		}

		if (!$this->onBeforePublish($cid, $publish))
		{
			return false;
		}

		$query = $this->_db->getQuery(true)
			->update($this->_db->qn($this->_tbl))
			->set($this->_db->qn($enabledName) . ' = ' . (int) $publish);

		$checkin = in_array($locked_byName, $this->getKnownFields());

		if ($checkin)
		{
			$query->where(
				' (' . $this->_db->qn($locked_byName) .
					' = 0 OR ' . $this->_db->qn($locked_byName) . ' = ' . (int) $user_id . ')', 'AND'
			);
		}

		// TODO Rewrite this statment using IN. Check if it work in SQLServer and PostgreSQL
		$cids = $this->_db->qn($k) . ' = ' . implode(' OR ' . $this->_db->qn($k) . ' = ', $cid);

		$query->where('(' . $cids . ')');

		$this->_db->setQuery((string) $query);

		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			try
			{
				$this->_db->execute();
			}
			catch (Exception $e)
			{
				$this->setError($e->getMessage());
			}
		}
		else
		{
			if (!$this->_db->execute())
			{
				$this->setError($this->_db->getErrorMsg());

				return false;
			}
		}

		if (count($cid) == 1 && $checkin)
		{
			if ($this->_db->getAffectedRows() == 1)
			{
				$this->checkin($cid[0]);

				if ($this->$k == $cid[0])
				{
					$this->$enabledName = $publish;
				}
			}
		}

		$this->setError('');

		return true;
	}

	/**
	 * Delete a record
	 *
	 * @param   integer $oid  The primary key value of the item to delete
	 *
	 * @throws  UnexpectedValueException
	 *
	 * @return  boolean  True on success
	 */
	public function delete($oid = null)
	{
		if ($oid)
		{
			$this->load($oid);
		}

		$k  = $this->_tbl_key;
		$pk = (!$oid) ? $this->$k : $oid;

		// If no primary key is given, return false.
		if (!$pk)
		{
			throw new UnexpectedValueException('Null primary key not allowed.');
		}

		// Execute the logic only if I have a primary key, otherwise I could have weird results
		if (!$this->onBeforeDelete($oid))
		{
			return false;
		}

		// Delete the row by primary key.
		$query = $this->_db->getQuery(true);
		$query->delete();
		$query->from($this->_tbl);
		$query->where($this->_tbl_key . ' = ' . $this->_db->q($pk));
		$this->_db->setQuery($query);

		$this->_db->execute();

		$result = $this->onAfterDelete($oid);

		return $result;
	}

	/**
	 * Register a hit on a record
	 *
	 * @param   integer  $oid  The primary key value of the record
	 * @param   boolean  $log  Should I log the hit?
	 *
	 * @return  boolean  True on success
	 */
	public function hit($oid = null, $log = false)
	{
		if (!$this->onBeforeHit($oid, $log))
		{
			return false;
		}

		// If there is no hits field, just return true.
		$hits_field = $this->getColumnAlias('hits');

		if (!in_array($hits_field, $this->getKnownFields()))
		{
			return true;
		}

		$k  = $this->_tbl_key;
		$pk = ($oid) ? $oid : $this->$k;

		// If no primary key is given, return false.
		if (!$pk)
		{
			$result = false;
		}
		else
		{
			// Check the row in by primary key.
			$query = $this->_db->getQuery(true)
						  ->update($this->_tbl)
						  ->set($this->_db->qn($hits_field) . ' = (' . $this->_db->qn($hits_field) . ' + 1)')
						  ->where($this->_tbl_key . ' = ' . $this->_db->q($pk));

			$this->_db->setQuery($query)->execute();

			// In order to update the table object, I have to load the table
			if(!$this->$k)
			{
				$query = $this->_db->getQuery(true)
							  ->select($this->_db->qn($hits_field))
							  ->from($this->_db->qn($this->_tbl))
							  ->where($this->_db->qn($this->_tbl_key) . ' = ' . $this->_db->q($pk));

				$this->$hits_field = $this->_db->setQuery($query)->loadResult();
			}
			else
			{
				// Set table values in the object.
				$this->$hits_field++;
			}

			$result = true;
		}

		if ($result)
		{
			$result = $this->onAfterHit($oid);
		}

		return $result;
	}

	/**
	 * Export the item as a CSV line
	 *
	 * @param   string  $separator  CSV separator. Tip: use "\t" to get a TSV file instead.
	 *
	 * @return  string  The CSV line
	 */
	public function toCSV($separator = ',')
	{
		$csv = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$csv[] = '"' . str_replace('"', '""', $v) . '"';
		}

		$csv = implode($separator, $csv);

		return $csv;
	}

	/**
	 * Exports the table in array format
	 *
	 * @return  array
	 */
	public function getData()
	{
		$ret = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$ret[$k] = $v;
		}

		return $ret;
	}

	/**
	 * Get the header for exporting item list to CSV
	 *
	 * @param   string  $separator  CSV separator. Tip: use "\t" to get a TSV file instead.
	 *
	 * @return  string  The CSV file's header
	 */
	public function getCSVHeader($separator = ',')
	{
		$csv = array();

		foreach (get_object_vars($this) as $k => $v)
		{
			if (!in_array($k, $this->getKnownFields()))
			{
				continue;
			}

			$csv[] = '"' . str_replace('"', '\"', $k) . '"';
		}

		$csv = implode($separator, $csv);

		return $csv;
	}

	/**
	 * Get the columns from a database table.
	 *
	 * @param   string  $tableName  Table name. If null current table is used
	 *
	 * @return  mixed  An array of the field names, or false if an error occurs.
	 */
	public function getTableFields($tableName = null)
	{
		// Should I load the cached data?
		$useCache = array_key_exists('use_table_cache', $this->config) ? $this->config['use_table_cache'] : false;

		// Make sure we have a list of tables in this db

		if (empty(self::$tableCache))
		{
			if ($useCache)
			{
				// Try to load table cache from a cache file
				$cacheData = FOFPlatform::getInstance()->getCache('tables', null);

				// Unserialise the cached data, or set the table cache to empty
				// if the cache data wasn't loaded.
				if (!is_null($cacheData))
				{
					self::$tableCache = json_decode($cacheData, true);
				}
				else
				{
					self::$tableCache = array();
				}
			}

			// This check is true if the cache data doesn't exist / is not loaded
			if (empty(self::$tableCache))
			{
				self::$tableCache = $this->_db->getTableList();

				if ($useCache)
				{
					FOFPlatform::getInstance()->setCache('tables', json_encode(self::$tableCache));
				}
			}
		}

		// Make sure the cached table fields cache is loaded
		if (empty(self::$tableFieldCache))
		{
			if ($useCache)
			{
				// Try to load table cache from a cache file
				$cacheData = FOFPlatform::getInstance()->getCache('tablefields', null);

				// Unserialise the cached data, or set to empty if the cache
				// data wasn't loaded.
				if (!is_null($cacheData))
				{
					$decoded = json_decode($cacheData, true);
					$tableCache = array();

					if (count($decoded))
					{
						foreach ($decoded as $myTableName => $tableFields)
						{
							$temp = array();

							if (is_array($tableFields))
							{
								foreach($tableFields as $field => $def)
								{
									$temp[$field] = (object)$def;
								}
								$tableCache[$myTableName] = $temp;
							}
							elseif (is_object($tableFields) || is_bool($tableFields))
							{
								$tableCache[$myTableName] = $tableFields;
							}
						}
					}

					self::$tableFieldCache = $tableCache;
				}
				else
				{
					self::$tableFieldCache = array();
				}
			}
		}

		if (!$tableName)
		{
			$tableName = $this->_tbl;
		}

		// Try to load again column specifications if the table is not loaded OR if it's loaded and
		// the previous call returned an error
		if (!array_key_exists($tableName, self::$tableFieldCache) ||
			(isset(self::$tableFieldCache[$tableName]) && !self::$tableFieldCache[$tableName]))
		{
			// Lookup the fields for this table only once.
			$name = $tableName;

			$prefix = $this->_db->getPrefix();

			if (substr($name, 0, 3) == '#__')
			{
				$checkName = $prefix . substr($name, 3);
			}
			else
			{
				$checkName = $name;
			}

			if (!in_array($checkName, self::$tableCache))
			{
				// The table doesn't exist. Return false.
				self::$tableFieldCache[$tableName] = false;
			}
			elseif (version_compare(JVERSION, '3.0', 'ge'))
			{
				$fields = $this->_db->getTableColumns($name, false);

				if (empty($fields))
				{
					$fields = false;
				}

				self::$tableFieldCache[$tableName] = $fields;
			}
			else
			{
				$fields = $this->_db->getTableFields($name, false);

				if (!isset($fields[$name]))
				{
					$fields = false;
				}

				self::$tableFieldCache[$tableName] = $fields[$name];
			}

			// PostgreSQL date type compatibility
			if (($this->_db->name == 'postgresql') && (self::$tableFieldCache[$tableName] != false))
			{
				foreach (self::$tableFieldCache[$tableName] as $field)
				{
					if (strtolower($field->type) == 'timestamp without time zone')
					{
						if (stristr($field->Default, '\'::timestamp without time zone'))
						{
							list ($date, $junk) = explode('::', $field->Default, 2);
							$field->Default = trim($date, "'");
						}
					}
				}
			}

			// Save the data for this table into the cache
			if ($useCache)
			{
				$cacheData = FOFPlatform::getInstance()->setCache('tablefields', json_encode(self::$tableFieldCache));
			}
		}

		return self::$tableFieldCache[$tableName];
	}

	public function getTableAlias()
	{
		return $this->_tableAlias;
	}

	public function setTableAlias($string)
	{
		$string = preg_replace('#[^A-Z0-9_]#i', '', $string);
		$this->_tableAlias = $string;
	}

	/**
	 * Method to return the real name of a "special" column such as ordering, hits, published
	 * etc etc. In this way you are free to follow your db naming convention and use the
	 * built in Joomla functions.
	 *
	 * @param   string  $column  Name of the "special" column (ie ordering, hits etc etc)
	 *
	 * @return  string  The string that identify the special
	 */
	public function getColumnAlias($column)
	{
		if (isset($this->_columnAlias[$column]))
		{
			$return = $this->_columnAlias[$column];
		}
		else
		{
			$return = $column;
		}

		$return = preg_replace('#[^A-Z0-9_]#i', '', $return);

		return $return;
	}

	/**
	 * Method to register a column alias for a "special" column.
	 *
	 * @param   string  $column       The "special" column (ie ordering)
	 * @param   string  $columnAlias  The real column name (ie foo_ordering)
	 *
	 * @return  void
	 */
	public function setColumnAlias($column, $columnAlias)
	{
		$column = strtolower($column);

		$column                      = preg_replace('#[^A-Z0-9_]#i', '', $column);
		$this->_columnAlias[$column] = $columnAlias;
	}

	/**
	 * Get a JOIN query, used to join other tables
	 *
	 * @param   boolean  $asReference  Return an object reference instead of a copy
	 *
	 * @return  FOFDatabaseQuery  Query used to join other tables
	 */
	public function getQueryJoin($asReference = false)
	{
		if ($asReference)
		{
			return $this->_queryJoin;
		}
		else
		{
			if ($this->_queryJoin)
			{
				return clone $this->_queryJoin;
			}
			else
			{
				return null;
			}
		}
	}

	/**
	 * Sets the query with joins to other tables
	 *
	 * @param   FOFDatabaseQuery  $query  The JOIN query to use
	 *
	 * @return  void
	 */
	public function setQueryJoin($query)
	{
		$this->_queryJoin = $query;
	}

	/**
	 * Extracts the fields from the join query
	 *
	 * @return   array    Fields contained in the join query
	 */
	protected function getQueryJoinFields()
	{
		$query = $this->getQueryJoin();

		if (!$query)
		{
			return array();
		}

		$tables   = array();
		$j_tables = array();
		$j_fields = array();

		// Get joined tables. Ignore FROM clause, since it should not be used (the starting point is the table "table")
		$joins    = $query->join;

		foreach ($joins as $join)
		{
			$tables = array_merge($tables, $join->getElements());
		}

		// Clean up table names
		foreach($tables as $table)
		{
			preg_match('#(.*)((\w)*(on|using))(.*)#i', $table, $matches);

			if($matches && isset($matches[1]))
			{
				// I always want the first part, no matter what
				$parts = explode(' ', $matches[1]);
				$t_table = $parts[0];

				if($this->isQuoted($t_table))
				{
					$t_table = substr($t_table, 1, strlen($t_table) - 2);
				}

				if(!in_array($t_table, $j_tables))
				{
					$j_tables[] =  $t_table;
				}
			}
		}

		// Do I have the current table inside the query join? Remove it (its fields are already ok)
		$find = array_search($this->getTableName(), $j_tables);
		if($find !== false)
		{
			unset($j_tables[$find]);
		}

		// Get table fields
		$fields = array();

		foreach ($j_tables as $table)
		{
			$t_fields = $this->getTableFields($table);

			if ($t_fields)
			{
				$fields = array_merge($fields, $t_fields);
			}
		}

		// Remove any fields that aren't in the joined select
		$j_select = $query->select;

		if ($j_select && $j_select->getElements())
		{
			$j_fields = $this->normalizeSelectFields($j_select->getElements());
		}

		// I can intesect the keys
		$fields   = array_intersect_key($fields, $j_fields);

		// Now I walk again the array to change the key of columns that have an alias
		foreach ($j_fields as $column => $alias)
		{
			if ($column != $alias)
			{
				$fields[$alias] = $fields[$column];
				unset($fields[$column]);
			}
		}

		return $fields;
	}

	/**
	 * Normalizes the fields, returning an associative array with all the fields.
	 * Ie array('foobar as foo, bar') becomes array('foobar' => 'foo', 'bar' => 'bar')
	 *
	 * @param   array $fields    Array with column fields
	 *
	 * @return  array  Normalized array
	 */
	protected function normalizeSelectFields($fields)
	{
		$db     = FOFPlatform::getInstance()->getDbo();
		$return = array();

		foreach ($fields as $field)
		{
			$t_fields = explode(',', $field);

			foreach ($t_fields as $t_field)
			{
				// Is there any alias?
				$parts  = preg_split('#\sas\s#i', $t_field);

				// Do I have a table.column situation? Let's get the field name
				$tableField  = explode('.', $parts[0]);

				if(isset($tableField[1]))
				{
					$column = trim($tableField[1]);
				}
				else
				{
					$column = trim($tableField[0]);
				}

				// Is this field quoted? If so, remove the quotes
				if($this->isQuoted($column))
				{
					$column = substr($column, 1, strlen($column) - 2);
				}

				if(isset($parts[1]))
				{
					$alias = trim($parts[1]);

					// Is this field quoted? If so, remove the quotes
					if($this->isQuoted($alias))
					{
						$alias = substr($alias, 1, strlen($alias) - 2);
					}
				}
				else
				{
					$alias = $column;
				}

				$return[$column] = $alias;
			}
		}

		return $return;
	}

	/**
	 * Is the field quoted?
	 *
	 * @param   string  $column     Column field
	 *
	 * @return  bool    Is the field quoted?
	 */
	protected function isQuoted($column)
	{
		// Empty string, un-quoted by definition
		if(!$column)
		{
			return false;
		}

		// I need some "magic". If the first char is not a letter, a number
		// an underscore or # (needed for table), then most likely the field is quoted
		preg_match_all('/^[a-z0-9_#]/i', $column, $matches);

		if(!$matches[0])
		{
			return true;
		}

		return false;
	}

	/**
	 * The event which runs before binding data to the table
	 *
	 * NOTE TO 3RD PARTY DEVELOPERS:
	 *
	 * When you override the following methods in your child classes,
	 * be sure to call parent::method *AFTER* your code, otherwise the
	 * plugin events do NOT get triggered
	 *
	 * Example:
	 * protected function onBeforeBind(){
	 *       // Your code here
	 *     return parent::onBeforeBind() && $your_result;
	 * }
	 *
	 * Do not do it the other way around, e.g. return $your_result && parent::onBeforeBind()
	 * Due to  PHP short-circuit boolean evaluation the parent::onBeforeBind()
	 * will not be called if $your_result is false.
	 *
	 * @param   object|array  &$from  The data to bind
	 *
	 * @return  boolean  True on success
	 */
	protected function onBeforeBind(&$from)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeBind', array(&$this, &$from));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeBind' . ucfirst($name), array(&$this, &$from));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after loading a record from the database
	 *
	 * @param   boolean  &$result  Did the load succeeded?
	 *
	 * @return  void
	 */
	protected function onAfterLoad(&$result)
	{
		// Call the behaviors
		$eventResult = $this->tableDispatcher->trigger('onAfterLoad', array(&$this, &$result));

		if (in_array(false, $eventResult, true))
		{
			// Behavior failed, return false
			$result = false;
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			FOFPlatform::getInstance()->runPlugins('onAfterLoad' . ucfirst($name), array(&$this, &$result));
		}
	}

	/**
	 * The event which runs before storing (saving) data to the database
	 *
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true) or just skipped over (false)?
	 *
	 * @return  boolean  True to allow saving
	 */
	protected function onBeforeStore($updateNulls)
	{
		// Do we have a "Created" set of fields?
		$created_on  = $this->getColumnAlias('created_on');
		$created_by  = $this->getColumnAlias('created_by');
		$modified_on = $this->getColumnAlias('modified_on');
		$modified_by = $this->getColumnAlias('modified_by');
		$locked_on   = $this->getColumnAlias('locked_on');
		$locked_by   = $this->getColumnAlias('locked_by');
		$title       = $this->getColumnAlias('title');
		$slug        = $this->getColumnAlias('slug');

		$hasCreatedOn = in_array($created_on, $this->getKnownFields());
		$hasCreatedBy = in_array($created_by, $this->getKnownFields());

		if ($hasCreatedOn && $hasCreatedBy)
		{
			$hasModifiedOn = in_array($modified_on, $this->getKnownFields());
			$hasModifiedBy = in_array($modified_by, $this->getKnownFields());

			$nullDate = $this->_db->getNullDate();

			if (empty($this->$created_by) || ($this->$created_on == $nullDate) || empty($this->$created_on))
			{
				$uid = FOFPlatform::getInstance()->getUser()->id;

				if ($uid)
				{
					$this->$created_by = FOFPlatform::getInstance()->getUser()->id;
				}

				$date = FOFPlatform::getInstance()->getDate('now', null, false);

				$this->$created_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL();
			}
			elseif ($hasModifiedOn && $hasModifiedBy)
			{
				$uid = FOFPlatform::getInstance()->getUser()->id;

				if ($uid)
				{
					$this->$modified_by = FOFPlatform::getInstance()->getUser()->id;
				}

                $date = FOFPlatform::getInstance()->getDate('now', null, false);

				$this->$modified_on = method_exists($date, 'toSql') ? $date->toSql() : $date->toMySQL();
			}
		}

		// Do we have a set of title and slug fields?
		$hasTitle = in_array($title, $this->getKnownFields());
		$hasSlug  = in_array($slug, $this->getKnownFields());

		if ($hasTitle && $hasSlug)
		{
			if (empty($this->$slug))
			{
				// Create a slug from the title
				$this->$slug = FOFStringUtils::toSlug($this->$title);
			}
			else
			{
				// Filter the slug for invalid characters
				$this->$slug = FOFStringUtils::toSlug($this->$slug);
			}

			// Make sure we don't have a duplicate slug on this table
			$db    = $this->getDbo();
			$query = $db->getQuery(true)
				->select($db->qn($slug))
				->from($this->_tbl)
				->where($db->qn($slug) . ' = ' . $db->q($this->$slug))
				->where('NOT ' . $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key}));
			$db->setQuery($query);
			$existingItems = $db->loadAssocList();

			$count   = 0;
			$newSlug = $this->$slug;

			while (!empty($existingItems))
			{
				$count++;
				$newSlug = $this->$slug . '-' . $count;
				$query   = $db->getQuery(true)
					->select($db->qn($slug))
					->from($this->_tbl)
					->where($db->qn($slug) . ' = ' . $db->q($newSlug))
					->where('NOT '. $db->qn($this->_tbl_key) . ' = ' . $db->q($this->{$this->_tbl_key}));
				$db->setQuery($query);
				$existingItems = $db->loadAssocList();
			}

			$this->$slug = $newSlug;
		}

		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeStore', array(&$this, $updateNulls));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		// Execute onBeforeStore<tablename> events in loaded plugins
		if ($this->_trigger_events)
		{
			$name       = FOFInflector::pluralize($this->getKeyName());
			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeStore' . ucfirst($name), array(&$this, $updateNulls));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after binding data to the class
	 *
	 * @param   object|array  &$src  The data to bind
	 *
	 * @return  boolean  True to allow binding without an error
	 */
	protected function onAfterBind(&$src)
	{
		// Call the behaviors
		$options = array(
			'component' 	=> $this->input->get('option'),
			'view'			=> $this->input->get('view'),
			'table_prefix'	=> $this->_tablePrefix
		);

		$result = $this->tableDispatcher->trigger('onAfterBind', array(&$this, &$src, $options));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterBind' . ucfirst($name), array(&$this, &$src));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	protected function onAfterStore()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterStore', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterStore' . ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before moving a record
	 *
	 * @param   boolean  $updateNulls  Should nulls be saved as nulls (true) or just skipped over (false)?
	 *
	 * @return  boolean  True to allow moving
	 */
	protected function onBeforeMove($updateNulls)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeMove', array(&$this, $updateNulls));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeMove' . ucfirst($name), array(&$this, $updateNulls));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after moving a record
	 *
	 * @return  boolean  True to allow moving without an error
	 */
	protected function onAfterMove()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterMove', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterMove' . ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before reordering a table
	 *
	 * @param   string  $where  The WHERE clause of the SQL query to run on reordering (record filter)
	 *
	 * @return  boolean  True to allow reordering
	 */
	protected function onBeforeReorder($where = '')
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeReorder', array(&$this, $where));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeReorder' . ucfirst($name), array(&$this, $where));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after reordering a table
	 *
	 * @return  boolean  True to allow the reordering to complete without an error
	 */
	protected function onAfterReorder()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterReorder', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterReorder' . ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   integer  $oid  The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	protected function onBeforeDelete($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeDelete', array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeDelete' . ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   integer  $oid  The PK value of the record which was deleted
	 *
	 * @return  boolean  True to allow the deletion without errors
	 */
	protected function onAfterDelete($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterDelete', array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterDelete' . ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before hitting a record
	 *
	 * @param   integer  $oid  The PK value of the record to hit
	 * @param   boolean  $log  Should we log the hit?
	 *
	 * @return  boolean  True to allow the hit
	 */
	protected function onBeforeHit($oid, $log)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeHit', array(&$this, $oid, $log));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeHit' . ucfirst($name), array(&$this, $oid, $log));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after hitting a record
	 *
	 * @param   integer  $oid  The PK value of the record which was hit
	 *
	 * @return  boolean  True to allow the hitting without errors
	 */
	protected function onAfterHit($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterHit', array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterHit' . ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs before copying a record
	 *
	 * @param   integer  $oid  The PK value of the record being copied
	 *
	 * @return  boolean  True to allow the copy to take place
	 */
	protected function onBeforeCopy($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeCopy', array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeCopy' . ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs after copying a record
	 *
	 * @param   integer  $oid  The PK value of the record which was copied (not the new one)
	 *
	 * @return  boolean  True to allow the copy without errors
	 */
	protected function onAfterCopy($oid)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterCopy', array(&$this, $oid));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterCopy' . ucfirst($name), array(&$this, $oid));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs before a record is (un)published
	 *
	 * @param   integer|array  &$cid     The PK IDs of the records being (un)published
	 * @param   integer        $publish  1 to publish, 0 to unpublish
	 *
	 * @return  boolean  True to allow the (un)publish to proceed
	 */
	protected function onBeforePublish(&$cid, $publish)
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforePublish', array(&$this, &$cid, $publish));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforePublish' . ucfirst($name), array(&$this, &$cid, $publish));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The event which runs after the object is reset to its default values.
	 *
	 * @return  boolean  True to allow the reset to complete without errors
	 */
	protected function onAfterReset()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onAfterReset', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onAfterReset' . ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * The even which runs before the object is reset to its default values.
	 *
	 * @return  boolean  True to allow the reset to complete
	 */
	protected function onBeforeReset()
	{
		// Call the behaviors
		$result = $this->tableDispatcher->trigger('onBeforeReset', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		if ($this->_trigger_events)
		{
			$name = FOFInflector::pluralize($this->getKeyName());

			$result     = FOFPlatform::getInstance()->runPlugins('onBeforeReset' . ucfirst($name), array(&$this));

			if (in_array(false, $result, true))
			{
				return false;
			}
			else
			{
				return true;
			}
		}

		return true;
	}

	/**
	 * Replace the input object of this table with the provided FOFInput object
	 *
	 * @param   FOFInput  $input  The new input object
	 *
	 * @return  void
	 */
	public function setInput(FOFInput $input)
	{
		$this->input = $input;
	}

	/**
	 * Get the columns from database table.
	 *
	 * @return  mixed  An array of the field names, or false if an error occurs.
	 *
	 * @deprecated  2.1
	 */
	public function getFields()
	{
		return $this->getTableFields();
	}

	/**
	 * Add a filesystem path where FOFTable should search for table class files.
	 * You may either pass a string or an array of paths.
	 *
	 * @param   mixed  $path  A filesystem path or array of filesystem paths to add.
	 *
	 * @return  array  An array of filesystem paths to find FOFTable classes in.
	 */
	public static function addIncludePath($path = null)
	{
		// If the internal paths have not been initialised, do so with the base table path.
		if (empty(self::$_includePaths))
		{
			self::$_includePaths = array(__DIR__);
		}

		// Convert the passed path(s) to add to an array.
		settype($path, 'array');

		// If we have new paths to add, do so.
		if (!empty($path) && !in_array($path, self::$_includePaths))
		{
			// Check and add each individual new path.
			foreach ($path as $dir)
			{
				// Sanitize path.
				$dir = trim($dir);

				// Add to the front of the list so that custom paths are searched first.
				array_unshift(self::$_includePaths, $dir);
			}
		}

		return self::$_includePaths;
	}

	/**
	 * Loads the asset table related to this table.
	 * This will help tests, too, since we can mock this function.
	 *
	 * @return bool|JTableAsset     False on failure, otherwise JTableAsset
	 */
	protected function getAsset()
	{
		$name     = $this->_getAssetName();

		// Do NOT touch JTable here -- we are loading the core asset table which is a JTable, not a FOFTable
		$asset    = JTable::getInstance('Asset');

		if (!$asset->loadByName($name))
		{
			return false;
		}

		return $asset;
	}

    /**
     * Method to compute the default name of the asset.
     * The default name is in the form table_name.id
     * where id is the value of the primary key of the table.
     *
     * @throws  UnexpectedValueException
     *
     * @return  string
     */
	public function getAssetName()
	{
		$k = $this->_tbl_key;

        // If there is no assetKey defined, stop here, or we'll get a wrong name
        if(!$this->_assetKey || !$this->$k)
        {
            throw new UnexpectedValueException('Table must have an asset key defined and a value for the table id in order to track assets');
        }

		return $this->_assetKey . '.' . (int) $this->$k;
	}

	/**
     * Method to compute the default name of the asset.
     * The default name is in the form table_name.id
     * where id is the value of the primary key of the table.
     *
     * @throws  UnexpectedValueException
     *
     * @return  string
     */
	public function getAssetKey()
	{
		return $this->_assetKey;
	}

	/**
	 * Method to return the title to use for the asset table.  In
	 * tracking the assets a title is kept for each asset so that there is some
	 * context available in a unified access manager.  Usually this would just
	 * return $this->title or $this->name or whatever is being used for the
	 * primary name of the row. If this method is not overridden, the asset name is used.
	 *
	 * @return  string  The string to use as the title in the asset table.
	 */
	public function getAssetTitle()
	{
		return $this->getAssetName();
	}

	/**
	 * Method to get the parent asset under which to register this one.
	 * By default, all assets are registered to the ROOT node with ID,
	 * which will default to 1 if none exists.
	 * The extended class can define a table and id to lookup.  If the
	 * asset does not exist it will be created.
	 *
	 * @param   FOFTable  $table  A FOFTable object for the asset parent.
	 * @param   integer   $id     Id to look up
	 *
	 * @return  integer
	 */
	public function getAssetParentId($table = null, $id = null)
	{
		// For simple cases, parent to the asset root.
		$assets = JTable::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
		$rootId = $assets->getRootId();

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

		return 1;
	}

	/**
	 * This method sets the asset key for the items of this table. Obviously, it
	 * is only meant to be used when you have a table with an asset field.
	 *
	 * @param   string  $assetKey  The name of the asset key to use
	 *
	 * @return  void
	 */
	public function setAssetKey($assetKey)
	{
		$this->_assetKey = $assetKey;
	}

	/**
	 * Method to get the database table name for the class.
	 *
	 * @return  string  The name of the database table being modeled.
	 */
	public function getTableName()
	{
		return $this->_tbl;
	}

	/**
	 * Method to get the primary key field name for the table.
	 *
	 * @return  string  The name of the primary key for the table.
	 */
	public function getKeyName()
	{
		return $this->_tbl_key;
	}

	/**
	 * Returns the identity value of this record
	 *
	 * @return mixed
	 */
	public function getId()
	{
		$key = $this->getKeyName();

		return $this->$key;
	}

	/**
	 * Method to get the FOFDatabaseDriver object.
	 *
	 * @return  FOFDatabaseDriver  The internal database driver object.
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to set the FOFDatabaseDriver object.
	 *
	 * @param   FOFDatabaseDriver  $db  A FOFDatabaseDriver object to be used by the table object.
	 *
	 * @return  boolean  True on success.
	 */
	public function setDBO($db)
	{
		$this->_db = $db;

		return true;
	}

	/**
	 * Method to set rules for the record.
	 *
	 * @param   mixed  $input  A JAccessRules object, JSON string, or array.
	 *
	 * @return  void
	 */
	public function setRules($input)
	{
		if ($input instanceof JAccessRules)
		{
			$this->_rules = $input;
		}
		else
		{
			$this->_rules = new JAccessRules($input);
		}
	}

	/**
	 * Method to get the rules for the record.
	 *
	 * @return  JAccessRules object
	 */
	public function getRules()
	{
		return $this->_rules;
	}

	/**
	 * Method to check if the record is treated as an ACL asset
	 *
	 * @return  boolean [description]
	 */
	public function isAssetsTracked()
	{
		return $this->_trackAssets;
	}

    /**
     * Method to manually set this record as ACL asset or not.
     * We have to do this since the automatic check is made in the constructor, but here we can't set any alias.
     * So, even if you have an alias for `asset_id`, it wouldn't be reconized and assets won't be tracked.
     *
     * @param $state
     */
    public function setAssetsTracked($state)
    {
        $state = (bool) $state;

        if($state)
        {
            JLoader::import('joomla.access.rules');
        }

        $this->_trackAssets = $state;
    }

	/**
	 * Method to provide a shortcut to binding, checking and storing a FOFTable
	 * instance to the database table.  The method will check a row in once the
	 * data has been stored and if an ordering filter is present will attempt to
	 * reorder the table rows based on the filter.  The ordering filter is an instance
	 * property name.  The rows that will be reordered are those whose value matches
	 * the FOFTable instance for the property specified.
	 *
	 * @param   mixed   $src             An associative array or object to bind to the FOFTable instance.
	 * @param   string  $orderingFilter  Filter for the order updating
	 * @param   mixed   $ignore          An optional array or space separated list of properties
	 *                                   to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 */
	public function save($src, $orderingFilter = '', $ignore = '')
	{
		// Attempt to bind the source to the instance.
		if (!$this->bind($src, $ignore))
		{
			return false;
		}

		// Run any sanity checks on the instance and verify that it is ready for storage.
		if (!$this->check())
		{
			return false;
		}

		// Attempt to store the properties to the database table.
		if (!$this->store())
		{
			return false;
		}

		// Attempt to check the row in, just in case it was checked out.
		if (!$this->checkin())
		{
			return false;
		}

		// If an ordering filter is set, attempt reorder the rows in the table based on the filter and value.
		if ($orderingFilter)
		{
			$filterValue = $this->$orderingFilter;
			$this->reorder($orderingFilter ? $this->_db->qn($orderingFilter) . ' = ' . $this->_db->q($filterValue) : '');
		}

		// Set the error to empty and return true.
		$this->setError('');

		return true;
	}

	/**
	 * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause.
	 * This is useful for placing a new item last in a group of items in the table.
	 *
	 * @param   string  $where  WHERE clause to use for selecting the MAX(ordering) for the table.
	 *
	 * @return  mixed  Boolean false an failure or the next ordering value as an integer.
	 */
	public function getNextOrder($where = '')
	{
		// If there is no ordering field set an error and return false.
		$ordering = $this->getColumnAlias('ordering');
		if (!in_array($ordering, $this->getKnownFields()))
		{
			throw new UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
		}

		// Get the largest ordering value for a given where clause.
		$query = $this->_db->getQuery(true);
		$query->select('MAX('.$this->_db->qn($ordering).')');
		$query->from($this->_tbl);

		if ($where)
		{
			$query->where($where);
		}

		$this->_db->setQuery($query);
		$max = (int) $this->_db->loadResult();

		// Return the largest ordering value + 1.
		return ($max + 1);
	}

	/**
	 * Method to lock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 *
	 * @throws  RuntimeException
	 */
	protected function _lock()
	{
		$this->_db->lockTable($this->_tbl);
		$this->_locked = true;

		return true;
	}

	/**
	 * Method to unlock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 */
	protected function _unlock()
	{
		$this->_db->unlockTables();
		$this->_locked = false;

		return true;
	}

	public function setConfig(array $config)
	{
		$this->config = $config;
	}

	/**
	 * Get the content type for ucm
	 *
	 * @return string The content type alias
	 */
	public function getContentType()
	{
		if ($this->contentType)
		{
			return $this->contentType;
		}

		/**
		 * When tags was first introduced contentType variable didn't exist - so we guess one
		 * This will fail if content history behvaiour is enabled. This code is deprecated
		 * and will be removed in FOF 3.0 in favour of the content type class variable
		 */
		$component = $this->input->get('option');

		$view = FOFInflector::singularize($this->input->get('view'));
		$alias = $component . '.' . $view;

		return $alias;
	}

	/**
	 * Returns the table relations object of the current table, lazy-loading it if necessary
	 *
	 * @return  FOFTableRelations
	 */
	public function getRelations()
	{
		if (is_null($this->_relations))
		{
			$this->_relations = new FOFTableRelations($this);
		}

		return $this->_relations;
	}

	/**
	 * Gets a reference to the configuration parameters provider for this table
	 *
	 * @return  FOFConfigProvider
	 */
	public function getConfigProvider()
	{
		return $this->configProvider;
	}

	/**
	 * Returns the configuration parameters provider's key for this table
	 *
	 * @return  string
	 */
	public function getConfigProviderKey()
	{
		return $this->_configProviderKey;
	}

	/**
	 * Check if a UCM content type exists for this resource, and
	 * create it if it does not
	 *
	 * @param  string  $alias  The content type alias (optional)
	 *
	 * @return  null
	 */
	public function checkContentType($alias = null)
	{
		$contentType = new JTableContenttype($this->getDbo());

		if (!$alias)
		{
			$alias = $this->getContentType();
		}

		$aliasParts = explode('.', $alias);

		// Fetch the extension name
		$component = $aliasParts[0];
		$component = JComponentHelper::getComponent($component);

		// Fetch the name using the menu item
		$query = $this->getDbo()->getQuery(true);
		$query->select('title')->from('#__menu')->where('component_id = ' . (int) $component->id);
		$this->getDbo()->setQuery($query);
		$component_name = JText::_($this->getDbo()->loadResult());

		$name = $component_name . ' ' . ucfirst($aliasParts[1]);

		// Create a new content type for our resource
		if (!$contentType->load(array('type_alias' => $alias)))
		{
			$contentType->type_title = $name;
			$contentType->type_alias = $alias;
			$contentType->table = json_encode(
				array(
					'special' => array(
						'dbtable' => $this->getTableName(),
						'key'     => $this->getKeyName(),
						'type'    => $name,
						'prefix'  => $this->_tablePrefix,
						'class'   => 'FOFTable',
						'config'  => 'array()'
					),
					'common' => array(
						'dbtable' => '#__ucm_content',
						'key' => 'ucm_id',
						'type' => 'CoreContent',
						'prefix' => 'JTable',
						'config' => 'array()'
					)
				)
			);

			$contentType->field_mappings = json_encode(
				array(
					'common' => array(
						0 => array(
							"core_content_item_id" => $this->getKeyName(),
							"core_title"           => $this->getUcmCoreAlias('title'),
							"core_state"           => $this->getUcmCoreAlias('enabled'),
							"core_alias"           => $this->getUcmCoreAlias('alias'),
							"core_created_time"    => $this->getUcmCoreAlias('created_on'),
							"core_modified_time"   => $this->getUcmCoreAlias('created_by'),
							"core_body"            => $this->getUcmCoreAlias('body'),
							"core_hits"            => $this->getUcmCoreAlias('hits'),
							"core_publish_up"      => $this->getUcmCoreAlias('publish_up'),
							"core_publish_down"    => $this->getUcmCoreAlias('publish_down'),
							"core_access"          => $this->getUcmCoreAlias('access'),
							"core_params"          => $this->getUcmCoreAlias('params'),
							"core_featured"        => $this->getUcmCoreAlias('featured'),
							"core_metadata"        => $this->getUcmCoreAlias('metadata'),
							"core_language"        => $this->getUcmCoreAlias('language'),
							"core_images"          => $this->getUcmCoreAlias('images'),
							"core_urls"            => $this->getUcmCoreAlias('urls'),
							"core_version"         => $this->getUcmCoreAlias('version'),
							"core_ordering"        => $this->getUcmCoreAlias('ordering'),
							"core_metakey"         => $this->getUcmCoreAlias('metakey'),
							"core_metadesc"        => $this->getUcmCoreAlias('metadesc'),
							"core_catid"           => $this->getUcmCoreAlias('cat_id'),
							"core_xreference"      => $this->getUcmCoreAlias('xreference'),
							"asset_id"             => $this->getUcmCoreAlias('asset_id')
						)
					),
					'special' => array(
						0 => array(
						)
					)
				)
			);

			$ignoreFields = array(
				$this->getUcmCoreAlias('modified_on', null),
				$this->getUcmCoreAlias('modified_by', null),
				$this->getUcmCoreAlias('locked_by', null),
				$this->getUcmCoreAlias('locked_on', null),
				$this->getUcmCoreAlias('hits', null),
				$this->getUcmCoreAlias('version', null)
			);

			$contentType->content_history_options = json_encode(
				array(
					"ignoreChanges" => array_filter($ignoreFields, 'strlen')
				)
			);

			$contentType->router = '';

			$contentType->store();
		}
	}

	/**
	 * Utility methods that fetches the column name for the field.
	 * If it does not exists, returns a "null" string
	 *
	 * @param  string  $alias  The alias for the column
	 * @param  string  $null   What to return if no column exists
	 *
	 * @return string The column name
	 */
	protected function getUcmCoreAlias($alias, $null = "null")
	{
		$alias = $this->getColumnAlias($alias);

		if (in_array($alias, $this->getKnownFields()))
		{
			return $alias;
		}

		return $null;
	}
}
fof/table/nested.php000064400000164107152177723700010424 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  table
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A class to manage tables holding nested sets (hierarchical data)
 *
 * @property int    $lft   Left value (for nested set implementation)
 * @property int    $rgt   Right value (for nested set implementation)
 * @property string $hash  Slug hash (optional; for faster searching)
 * @property string $slug  Node's slug (optional)
 * @property string $title Title of the node (optional)
 */
class FOFTableNested extends FOFTable
{
	/** @var int The level (depth) of this node in the tree */
	protected $treeDepth = null;

	/** @var FOFTableNested The root node in the tree */
	protected $treeRoot = null;

	/** @var FOFTableNested The parent node of ourselves */
	protected $treeParent = null;

	/** @var bool Should I perform a nested get (used to query ascendants/descendants) */
	protected $treeNestedGet = false;

	/** @var   array  A collection of custom, additional where clauses to apply during buildQuery */
	protected $whereClauses = array();

	/**
	 * Public constructor. Overrides the parent constructor, making sure there are lft/rgt columns which make it
	 * compatible with nested sets.
	 *
	 * @param   string          $table  Name of the database table to model.
	 * @param   string          $key    Name of the primary key field in the table.
	 * @param   FOFDatabaseDriver &$db    Database driver
	 * @param   array           $config The configuration parameters array
	 *
	 * @throws \RuntimeException When lft/rgt columns are not found
	 */
	public function __construct($table, $key, &$db, $config = array())
	{
		parent::__construct($table, $key, $db, $config);

		if (!$this->hasField('lft') || !$this->hasField('rgt'))
		{
			throw new \RuntimeException("Table " . $this->getTableName() . " is not compatible with FOFTableNested: it does not have lft/rgt columns");
		}
	}

	/**
	 * Overrides the automated table checks to handle the 'hash' column for faster searching
	 *
	 * @return boolean
	 */
	public function check()
	{
		// Create a slug if there is a title and an empty slug
		if ($this->hasField('title') && $this->hasField('slug') && empty($this->slug))
		{
			$this->slug = FOFStringUtils::toSlug($this->title);
		}

		// Create the SHA-1 hash of the slug for faster searching (make sure the hash column is CHAR(64) to take
		// advantage of MySQL's optimised searching for fixed size CHAR columns)
		if ($this->hasField('hash') && $this->hasField('slug'))
		{
			$this->hash = sha1($this->slug);
		}

		// Reset cached values
		$this->resetTreeCache();

		return parent::check();
	}

	/**
	 * Delete a node, either the currently loaded one or the one specified in $id. If an $id is specified that node
	 * is loaded before trying to delete it. In the end the data model is reset. If the node has any children nodes
	 * they will be removed before the node itself is deleted.
	 *
	 * @param   integer $oid       The primary key value of the item to delete
	 *
	 * @throws  UnexpectedValueException
	 *
	 * @return  boolean  True on success
	 */
	public function delete($oid = null)
	{
		// Load the specified record (if necessary)
		if (!empty($oid))
		{
			$this->load($oid);
		}

        $k  = $this->_tbl_key;
        $pk = (!$oid) ? $this->$k : $oid;

        // If no primary key is given, return false.
        if (!$pk)
        {
            throw new UnexpectedValueException('Null primary key not allowed.');
        }

        // Execute the logic only if I have a primary key, otherwise I could have weird results
        // Perform the checks on the current node *BEFORE* starting to delete the children
        if (!$this->onBeforeDelete($oid))
        {
            return false;
        }

        $result = true;

		// Recursively delete all children nodes as long as we are not a leaf node and $recursive is enabled
		if (!$this->isLeaf())
		{
			// Get all sub-nodes
			$table = $this->getClone();
			$table->bind($this->getData());
			$subNodes = $table->getDescendants();

			// Delete all subnodes (goes through the model to trigger the observers)
			if (!empty($subNodes))
			{
				/** @var FOFTableNested $item */
				foreach ($subNodes as $item)
				{
                    // We have to pass the id, so we are getting it again from the database.
                    // We have to do in this way, since a previous child could have changed our lft and rgt values
					if(!$item->delete($item->$k))
                    {
                        // A subnode failed or prevents the delete, continue deleting other nodes,
                        // but preserve the current node (ie the parent)
                        $result = false;
                    }
				};

                // Load it again, since while deleting a children we could have updated ourselves, too
                $this->load($pk);
			}
		}

        if($result)
        {
            // Delete the row by primary key.
            $query = $this->_db->getQuery(true);
            $query->delete();
            $query->from($this->_tbl);
            $query->where($this->_tbl_key . ' = ' . $this->_db->q($pk));

            $this->_db->setQuery($query)->execute();

            $result = $this->onAfterDelete($oid);
        }

		return $result;
	}

    protected function onAfterDelete($oid)
    {
        $db = $this->getDbo();

        $myLeft  = $this->lft;
        $myRight = $this->rgt;

        $fldLft = $db->qn($this->getColumnAlias('lft'));
        $fldRgt = $db->qn($this->getColumnAlias('rgt'));

        // Move all siblings to the left
        $width = $this->rgt - $this->lft + 1;

        // Wrap everything in a transaction
        $db->transactionStart();

        try
        {
            // Shrink lft values
            $query = $db->getQuery(true)
                        ->update($db->qn($this->getTableName()))
                        ->set($fldLft . ' = ' . $fldLft . ' - '.$width)
                        ->where($fldLft . ' > ' . $db->q($myLeft));
            $db->setQuery($query)->execute();

            // Shrink rgt values
            $query = $db->getQuery(true)
                        ->update($db->qn($this->getTableName()))
                        ->set($fldRgt . ' = ' . $fldRgt . ' - '.$width)
                        ->where($fldRgt . ' > ' . $db->q($myRight));
            $db->setQuery($query)->execute();

            // Commit the transaction
            $db->transactionCommit();
        }
        catch (\Exception $e)
        {
            // Roll back the transaction on error
            $db->transactionRollback();

            throw $e;
        }

        return parent::onAfterDelete($oid);
    }

	/**
	 * Not supported in nested sets
	 *
	 * @param   string $where Ignored
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	public function reorder($where = '')
	{
		throw new RuntimeException('reorder() is not supported by FOFTableNested');
	}

	/**
	 * Not supported in nested sets
	 *
	 * @param   integer $delta Ignored
	 * @param   string  $where Ignored
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 */
	public function move($delta, $where = '')
	{
		throw new RuntimeException('move() is not supported by FOFTableNested');
	}

	/**
	 * Create a new record with the provided data. It is inserted as the last child of the current node's parent
	 *
	 * @param   array $data The data to use in the new record
	 *
	 * @return  static  The new node
	 */
	public function create($data)
	{
		$newNode = $this->getClone();
		$newNode->reset();
		$newNode->bind($data);

		if ($this->isRoot())
		{
			return $newNode->insertAsChildOf($this);
		}
		else
		{
			return $newNode->insertAsChildOf($this->getParent());
		}
	}

	/**
	 * Makes a copy of the record, inserting it as the last child of the given node's parent.
	 *
	 * @param   integer|array  $cid  The primary key value (or values) or the record(s) to copy. 
	 *                               If null, the current record will be copied
	 * 
	 * @return self|FOFTableNested	 The last copied node
	 */
	public function copy($cid = null)
	{
		//We have to cast the id as array, or the helper function will return an empty set
		if($cid)
		{
			$cid = (array) $cid;
		}

        FOFUtilsArray::toInteger($cid);
		$k = $this->_tbl_key;

		if (count($cid) < 1)
		{
			if ($this->$k)
			{
				$cid = array($this->$k);
			}
			else
			{
				// Even if it's null, let's still create the record
				$this->create($this->getData());
				
				return $this;
			}
		}

		foreach ($cid as $item)
		{
			// Prevent load with id = 0

			if (!$item)
			{
				continue;
			}

			$this->load($item);
			
			$this->create($this->getData());
		}

		return $this;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties.
	 *
	 * @return void
	 */
	public function reset()
	{
		$this->resetTreeCache();

		parent::reset();
	}

	/**
	 * Insert the current node as a tree root. It is a good idea to never use this method, instead providing a root node
	 * in your schema installation and then sticking to only one root.
	 *
	 * @return self
	 */
	public function insertAsRoot()
	{
        // You can't insert a node that is already saved i.e. the table has an id
        if($this->getId())
        {
            throw new RuntimeException(__METHOD__.' can be only used with new nodes');
        }

		// First we need to find the right value of the last parent, a.k.a. the max(rgt) of the table
		$db = $this->getDbo();

		// Get the lft/rgt names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$query = $db->getQuery(true)
			->select('MAX(' . $fldRgt . ')')
			->from($db->qn($this->getTableName()));
		$maxRgt = $db->setQuery($query, 0, 1)->loadResult();

		if (empty($maxRgt))
		{
			$maxRgt = 0;
		}

		$this->lft = ++$maxRgt;
		$this->rgt = ++$maxRgt;

		$this->store();

		return $this;
	}

	/**
	 * Insert the current node as the first (leftmost) child of a parent node.
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $parentNode The node which will become our parent
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertAsFirstChildOf(FOFTableNested &$parentNode)
	{
        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the parent node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's rgt
		$myLeft = $parentNode->lft;

		// Update my lft/rgt values
		$this->lft = $myLeft + 1;
		$this->rgt = $myLeft + 2;

		// Update parent node's right (we added two elements in there, remember?)
		$parentNode->rgt += 2;

		// Wrap everything in a transaction
		$db->transactionStart();

		try
		{
			// Make a hole (2 queries)
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldLft . ' = ' . $fldLft . '+2')
				->where($fldLft . ' > ' . $db->q($myLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldRgt . ' = ' . $fldRgt . '+ 2')
				->where($fldRgt . '>' . $db->q($myLeft));
			$db->setQuery($query)->execute();

			// Insert the new node
			$this->store();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			// Roll back the transaction on error
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Insert the current node as the last (rightmost) child of a parent node.
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $parentNode The node which will become our parent
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertAsLastChildOf(FOFTableNested &$parentNode)
	{
        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the parent node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's lft
		$myRight = $parentNode->rgt;

		// Update my lft/rgt values
		$this->lft = $myRight;
		$this->rgt = $myRight + 1;

		// Update parent node's right (we added two elements in there, remember?)
		$parentNode->rgt += 2;

		// Wrap everything in a transaction
		$db->transactionStart();

		try
		{
			// Make a hole (2 queries)
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldRgt . ' = ' . $fldRgt . '+2')
				->where($fldRgt . '>=' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($fldLft . ' = ' . $fldLft . '+2')
				->where($fldLft . '>' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Insert the new node
			$this->store();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			// Roll back the transaction on error
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Alias for insertAsLastchildOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 */
	public function insertAsChildOf(FOFTableNested &$parentNode)
	{
		return $this->insertAsLastChildOf($parentNode);
	}

	/**
	 * Insert the current node to the left of (before) a sibling node
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $siblingNode We will be inserted before this node
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertLeftOf(FOFTableNested &$siblingNode)
	{
        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the sibling node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's rgt
		$myLeft = $siblingNode->lft;

		// Update my lft/rgt values
		$this->lft = $myLeft;
		$this->rgt = $myLeft + 1;

		// Update sibling's lft/rgt values
		$siblingNode->lft += 2;
		$siblingNode->rgt += 2;

		$db->transactionStart();

		try
		{
			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldLft . ' = ' . $fldLft . '+2')
					->where($fldLft . ' >= ' . $db->q($myLeft))
			)->execute();

			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldRgt . ' = ' . $fldRgt . '+2')
					->where($fldRgt . ' > ' . $db->q($myLeft))
			)->execute();

			$this->store();

            // Commit the transaction
            $db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Insert the current node to the right of (after) a sibling node
	 *
	 * WARNING: If it's an existing node it will be COPIED, not moved.
	 *
	 * @param FOFTableNested $siblingNode We will be inserted after this node
	 *
	 * @return $this for chaining
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function insertRightOf(FOFTableNested &$siblingNode)
	{
        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the sibling node');
        }

		// Get a reference to the database
		$db = $this->getDbo();

		// Get the field names
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));
		$fldLft = $db->qn($this->getColumnAlias('lft'));

        // Nullify the PK, so a new record will be created
        $pk = $this->getKeyName();
        $this->$pk = null;

		// Get the value of the parent node's lft
		$myRight = $siblingNode->rgt;

		// Update my lft/rgt values
		$this->lft = $myRight + 1;
		$this->rgt = $myRight + 2;

		$db->transactionStart();

		try
		{
			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldRgt . ' = ' . $fldRgt . '+2')
					->where($fldRgt . ' > ' . $db->q($myRight))
			)->execute();

			$db->setQuery(
				$db->getQuery(true)
					->update($db->qn($this->getTableName()))
					->set($fldLft . ' = ' . $fldLft . '+2')
					->where($fldLft . ' > ' . $db->q($myRight))
			)->execute();

			$this->store();

            // Commit the transaction
            $db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

		return $this;
	}

	/**
	 * Alias for insertRightOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function insertAsSiblingOf(FOFTableNested &$siblingNode)
	{
		return $this->insertRightOf($siblingNode);
	}

	/**
	 * Move the current node (and its subtree) one position to the left in the tree, i.e. before its left-hand sibling
	 *
     * @throws  RuntimeException
     *
	 * @return $this
	 */
	public function moveLeft()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		// If it is a root node we will not move the node (roots don't participate in tree ordering)
		if ($this->isRoot())
		{
			return $this;
		}

		// Are we already the leftmost node?
		$parentNode = $this->getParent();

		if ($parentNode->lft == ($this->lft - 1))
		{
			return $this;
		}

		// Get the sibling to the left
		$db = $this->getDbo();
		$table = $this->getClone();
		$table->reset();
		$leftSibling = $table->whereRaw($db->qn($this->getColumnAlias('rgt')) . ' = ' . $db->q($this->lft - 1))
			->get(0, 1)->current();

		// Move the node
		if (is_object($leftSibling) && ($leftSibling instanceof FOFTableNested))
		{
			return $this->moveToLeftOf($leftSibling);
		}

		return false;
	}

	/**
	 * Move the current node (and its subtree) one position to the right in the tree, i.e. after its right-hand sibling
     *
     * @throws RuntimeException
	 *
	 * @return $this
	 */
	public function moveRight()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		// If it is a root node we will not move the node (roots don't participate in tree ordering)
		if ($this->isRoot())
		{
			return $this;
		}

		// Are we already the rightmost node?
		$parentNode = $this->getParent();

		if ($parentNode->rgt == ($this->rgt + 1))
		{
			return $this;
		}

		// Get the sibling to the right
		$db = $this->getDbo();

		$table = $this->getClone();
		$table->reset();
		$rightSibling = $table->whereRaw($db->qn($this->getColumnAlias('lft')) . ' = ' . $db->q($this->rgt + 1))
			->get(0, 1)->current();

		// Move the node
		if (is_object($rightSibling) && ($rightSibling instanceof FOFTableNested))
		{
			return $this->moveToRightOf($rightSibling);
		}

		return false;
	}

	/**
	 * Moves the current node (and its subtree) to the left of another node. The other node can be in a different
	 * position in the tree or even under a different root.
	 *
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function moveToLeftOf(FOFTableNested $siblingNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the sibling node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get sibling metrics
		$sibLeft = $siblingNode->lft;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newSibLeft = ($sibLeft > $myRight) ? $sibLeft - $myWidth : $sibLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
				->where($right . ' >= ' . $db->q($newSibLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
				->where($left . ' >= ' . $db->q($newSibLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = $newSibLeft - $myLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft and rgt
        $this->load();

		return $this;
	}

	/**
	 * Moves the current node (and its subtree) to the right of another node. The other node can be in a different
	 * position in the tree or even under a different root.
	 *
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function moveToRightOf(FOFTableNested $siblingNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($siblingNode->lft >= $siblingNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the sibling node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$sibRight = $siblingNode->rgt;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newSibRight = ($sibRight > $myRight) ? $sibRight - $myWidth : $sibRight;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($newSibRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
				->where($right . ' > ' . $db->q($newSibRight));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = ($sibRight > $myRight) ? $sibRight - $myRight : $sibRight - $myRight + $myWidth;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft and rgt
        $this->load();

		return $this;
	}

	/**
	 * Alias for moveToRightOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makeNextSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->moveToRightOf($siblingNode);
	}

	/**
	 * Alias for makeNextSiblingOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makeSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->makeNextSiblingOf($siblingNode);
	}

	/**
	 * Alias for moveToLeftOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $siblingNode
	 *
	 * @return $this for chaining
	 */
	public function makePreviousSiblingOf(FOFTableNested $siblingNode)
	{
		return $this->moveToLeftOf($siblingNode);
	}

	/**
	 * Moves a node and its subtree as a the first (leftmost) child of $parentNode
	 *
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 */
	public function makeFirstChildOf(FOFTableNested $parentNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the parent node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$parentLeft = $parentNode->lft;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newParentLeft = ($parentLeft > $myRight) ? $parentLeft - $myWidth : $parentLeft;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
				->where($right . ' >= ' . $db->q($newParentLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($newParentLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = $newParentLeft - $myLeft + 1;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft and rgt
        $this->load();

		return $this;
	}

	/**
	 * Moves a node and its subtree as a the last (rightmost) child of $parentNode
	 *
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 *
	 * @throws Exception
	 * @throws RuntimeException
	 */
	public function makeLastChildOf(FOFTableNested $parentNode)
	{
        // Sanity checks on current and sibling node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($parentNode->lft >= $parentNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the parent node');
        }

		$db    = $this->getDbo();
		$left  = $db->qn($this->getColumnAlias('lft'));
		$right = $db->qn($this->getColumnAlias('rgt'));

		// Get node metrics
		$myLeft  = $this->lft;
		$myRight = $this->rgt;
		$myWidth = $myRight - $myLeft + 1;

		// Get parent metrics
		$parentRight = $parentNode->rgt;

		// Start the transaction
		$db->transactionStart();

		try
		{
			// Temporary remove subtree being moved
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set("$left = " . $db->q(0) . " - $left")
				->set("$right = " . $db->q(0) . " - $right")
				->where($left . ' >= ' . $db->q($myLeft))
				->where($right . ' <= ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Close hole left behind
			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' - ' . $db->q($myWidth))
				->where($left . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' - ' . $db->q($myWidth))
				->where($right . ' > ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Make a hole for the new items
			$newLeft = ($parentRight > $myRight) ? $parentRight - $myWidth : $parentRight;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $left . ' + ' . $db->q($myWidth))
				->where($left . ' >= ' . $db->q($newLeft));
			$db->setQuery($query)->execute();

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($right . ' = ' . $right . ' + ' . $db->q($myWidth))
				->where($right . ' >= ' . $db->q($newLeft));
			$db->setQuery($query)->execute();

			// Move node and subnodes
			$moveRight = ($parentRight > $myRight) ? $parentRight - $myRight - 1 : $parentRight - $myRight - 1 + $myWidth;

			$query = $db->getQuery(true)
				->update($db->qn($this->getTableName()))
				->set($left . ' = ' . $db->q(0) . ' - ' . $left . ' + ' . $db->q($moveRight))
				->set($right . ' = ' . $db->q(0) . ' - ' . $right . ' + ' . $db->q($moveRight))
				->where($left . ' <= 0 - ' . $db->q($myLeft))
				->where($right . ' >= 0 - ' . $db->q($myRight));
			$db->setQuery($query)->execute();

			// Commit the transaction
			$db->transactionCommit();
		}
		catch (\Exception $e)
		{
			$db->transactionRollback();

			throw $e;
		}

        // Let's load the record again to fetch the new values for lft and rgt
        $this->load();

		return $this;
	}

	/**
	 * Alias for makeLastChildOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $parentNode
	 *
	 * @return $this for chaining
	 */
	public function makeChildOf(FOFTableNested $parentNode)
	{
		return $this->makeLastChildOf($parentNode);
	}

	/**
	 * Makes the current node a root (and moving its entire subtree along the way). This is achieved by moving the node
	 * to the right of its root node
	 *
	 * @return  $this  for chaining
	 */
	public function makeRoot()
	{
		// Make sure we are not a root
		if ($this->isRoot())
		{
			return $this;
		}

		// Get a reference to my root
		$myRoot = $this->getRoot();

		// Double check I am not a root
		if ($this->equals($myRoot))
		{
			return $this;
		}

		// Move myself to the right of my root
		$this->moveToRightOf($myRoot);
		$this->treeDepth = 0;

		return $this;
	}

	/**
	 * Gets the level (depth) of this node in the tree. The result is cached in $this->treeDepth for faster retrieval.
	 *
     * @throws  RuntimeException
     *
	 * @return int|mixed
	 */
	public function getLevel()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		if (is_null($this->treeDepth))
		{
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$query = $db->getQuery(true)
				->select('(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth'))
				->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
				->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
				->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
				->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
				->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
				->group($db->qn('node') . '.' . $fldLft)
				->order($db->qn('node') . '.' . $fldLft . ' ASC');

			$this->treeDepth = $db->setQuery($query, 0, 1)->loadResult();
		}

		return $this->treeDepth;
	}

	/**
	 * Returns the immediate parent of the current node
	 *
     * @throws RuntimeException
     *
	 * @return FOFTableNested
	 */
	public function getParent()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		if ($this->isRoot())
		{
			return $this;
		}

		if (empty($this->treeParent) || !is_object($this->treeParent) || !($this->treeParent instanceof FOFTableNested))
		{
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$query = $db->getQuery(true)
				->select($db->qn('parent') . '.' . $fldLft)
				->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
				->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
				->where($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft)
				->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
				->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
				->order($db->qn('parent') . '.' . $fldLft . ' DESC');
			$targetLft = $db->setQuery($query, 0, 1)->loadResult();

			$table = $this->getClone();
			$table->reset();
			$this->treeParent = $table
				->whereRaw($fldLft . ' = ' . $db->q($targetLft))
				->get()->current();
		}

		return $this->treeParent;
	}

	/**
	 * Is this a top-level root node?
	 *
	 * @return bool
	 */
	public function isRoot()
	{
		// If lft=1 it is necessarily a root node
		if ($this->lft == 1)
		{
			return true;
		}

		// Otherwise make sure its level is 0
		return $this->getLevel() == 0;
	}

	/**
	 * Is this a leaf node (a node without children)?
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isLeaf()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		return ($this->rgt - 1) == $this->lft;
	}

	/**
	 * Is this a child node (not root)?
	 *
     * @codeCoverageIgnore
     *
	 * @return bool
	 */
	public function isChild()
	{
		return !$this->isRoot();
	}

	/**
	 * Returns true if we are a descendant of $otherNode
	 *
	 * @param FOFTableNested $otherNode
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isDescendantOf(FOFTableNested $otherNode)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($otherNode->lft >= $otherNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the other node');
        }

		return ($otherNode->lft < $this->lft) && ($otherNode->rgt > $this->rgt);
	}

	/**
	 * Returns true if $otherNode is ourselves or if we are a descendant of $otherNode
	 *
	 * @param FOFTableNested $otherNode
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function isSelfOrDescendantOf(FOFTableNested $otherNode)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($otherNode->lft >= $otherNode->rgt)
        {
            throw new RuntimeException('Invalid position values for the other node');
        }

		return ($otherNode->lft <= $this->lft) && ($otherNode->rgt >= $this->rgt);
	}

	/**
	 * Returns true if we are an ancestor of $otherNode
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function isAncestorOf(FOFTableNested $otherNode)
	{
		return $otherNode->isDescendantOf($this);
	}

	/**
	 * Returns true if $otherNode is ourselves or we are an ancestor of $otherNode
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function isSelfOrAncestorOf(FOFTableNested $otherNode)
	{
		return $otherNode->isSelfOrDescendantOf($this);
	}

	/**
	 * Is $node this very node?
	 *
	 * @param FOFTableNested $node
	 *
     * @throws  RuntimeException
     *
	 * @return bool
	 */
	public function equals(FOFTableNested &$node)
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

        if($node->lft >= $node->rgt)
        {
            throw new RuntimeException('Invalid position values for the other node');
        }

		return (
			($this->getId() == $node->getId())
			&& ($this->lft == $node->lft)
			&& ($this->rgt == $node->rgt)
		);
	}

	/**
	 * Alias for isDescendantOf
	 *
     * @codeCoverageIgnore
	 * @param FOFTableNested $otherNode
     *
	 * @return bool
	 */
	public function insideSubtree(FOFTableNested $otherNode)
	{
		return $this->isDescendantOf($otherNode);
	}

	/**
	 * Returns true if both this node and $otherNode are root, leaf or child (same tree scope)
	 *
	 * @param FOFTableNested $otherNode
	 *
	 * @return bool
	 */
	public function inSameScope(FOFTableNested $otherNode)
	{
		if ($this->isLeaf())
		{
			return $otherNode->isLeaf();
		}
		elseif ($this->isRoot())
		{
			return $otherNode->isRoot();
		}
		elseif ($this->isChild())
		{
			return $otherNode->isChild();
		}
		else
		{
			return false;
		}
	}

	/**
	 * get() will return all ancestor nodes and ourselves
	 *
	 * @return void
	 */
	protected function scopeAncestorsAndSelf()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' >= ' . $db->qn('node') . '.' . $fldLft);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' <= ' . $db->qn('node') . '.' . $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all ancestor nodes but not ourselves
	 *
	 * @return void
	 */
	protected function scopeAncestors()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' > ' . $db->qn('node') . '.' . $fldLft);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' < ' . $db->qn('node') . '.' . $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all sibling nodes and ourselves
	 *
	 * @return void
	 */
	protected function scopeSiblingsAndSelf()
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$parent = $this->getParent();
		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->q($parent->lft));
		$this->whereRaw($db->qn('node') . '.' . $fldRgt . ' < ' . $db->q($parent->rgt));
	}

	/**
	 * get() will return all sibling nodes but not ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeSiblings()
	{
		$this->scopeSiblingsAndSelf();
		$this->scopeWithoutSelf();
	}

	/**
	 * get() will return only leaf nodes
	 *
	 * @return void
	 */
	protected function scopeLeaves()
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' = ' . $db->qn('node') . '.' . $fldRgt . ' - ' . $db->q(1));
	}

	/**
	 * get() will return all descendants (even subtrees of subtrees!) and ourselves
	 *
	 * @return void
	 */
	protected function scopeDescendantsAndSelf()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft);
		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will return all descendants (even subtrees of subtrees!) but not ourselves
	 *
	 * @return void
	 */
	protected function scopeDescendants()
	{
		$this->treeNestedGet = true;

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' > ' . $db->qn('parent') . '.' . $fldLft);
		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' < ' . $db->qn('parent') . '.' . $fldRgt);
		$this->whereRaw($db->qn('parent') . '.' . $fldLft . ' = ' . $db->q($this->lft));
	}

	/**
	 * get() will only return immediate descendants (first level children) of the current node
	 *
	 * @return void
	 */
	protected function scopeImmediateDescendants()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		$subQuery = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldLft,
				'(COUNT(*) - 1) AS ' . $db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
			->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
			->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
			->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
			->where($db->qn('node') . '.' . $fldLft . ' = ' . $db->q($this->lft))
			->group($db->qn('node') . '.' . $fldLft)
			->order($db->qn('node') . '.' . $fldLft . ' ASC');

		$query = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldLft,
				'(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - (' .
				$db->qn('sub_tree') . '.' . $db->qn('depth') . ' + 1)) AS ' . $db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
			->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
			->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('sub_parent'))
			->join('CROSS', '(' . $subQuery . ') AS ' . $db->qn('sub_tree'))
			->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
			->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
			->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('sub_parent') . '.' . $fldLft)
			->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('sub_parent') . '.' . $fldRgt)
			->where($db->qn('sub_parent') . '.' . $fldLft . ' = ' . $db->qn('sub_tree') . '.' . $fldLft)
			->group($db->qn('node') . '.' . $fldLft)
			->having(array(
				$db->qn('depth') . ' > ' . $db->q(0),
				$db->qn('depth') . ' <= ' . $db->q(1),
			))
			->order($db->qn('node') . '.' . $fldLft . ' ASC');

		$leftValues = $db->setQuery($query)->loadColumn();

		if (empty($leftValues))
		{
			$leftValues = array(0);
		}

		array_walk($leftValues, function (&$item, $key) use (&$db)
		{
			$item = $db->q($item);
		});

		$this->whereRaw($db->qn('node') . '.' . $fldLft . ' IN (' . implode(',', $leftValues) . ')');
	}

	/**
	 * get() will not return the selected node if it's part of the query results
	 *
	 * @param FOFTableNested $node The node to exclude from the results
	 *
	 * @return void
	 */
	public function withoutNode(FOFTableNested $node)
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));

		$this->whereRaw('NOT(' . $db->qn('node') . '.' . $fldLft . ' = ' . $db->q($node->lft) . ')');
	}

	/**
	 * get() will not return ourselves if it's part of the query results
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeWithoutSelf()
	{
		$this->withoutNode($this);
	}

	/**
	 * get() will not return our root if it's part of the query results
	 *
     * @codeCoverageIgnore
     *
	 * @return void
	 */
	protected function scopeWithoutRoot()
	{
		$rootNode = $this->getRoot();
		$this->withoutNode($rootNode);
	}

	/**
	 * Returns the root node of the tree this node belongs to
	 *
	 * @return self
	 *
	 * @throws \RuntimeException
	 */
	public function getRoot()
	{
        // Sanity checks on current node position
        if($this->lft >= $this->rgt)
        {
            throw new RuntimeException('Invalid position values for the current node');
        }

		// If this is a root node return itself (there is no such thing as the root of a root node)
		if ($this->isRoot())
		{
			return $this;
		}

		if (empty($this->treeRoot) || !is_object($this->treeRoot) || !($this->treeRoot instanceof FOFTableNested))
		{
			$this->treeRoot = null;

			// First try to get the record with the minimum ID
			$db = $this->getDbo();

			$fldLft = $db->qn($this->getColumnAlias('lft'));
			$fldRgt = $db->qn($this->getColumnAlias('rgt'));

			$subQuery = $db->getQuery(true)
				->select('MIN(' . $fldLft . ')')
				->from($db->qn($this->getTableName()));

			try
			{
				$table = $this->getClone();
				$table->reset();
				$root = $table
					->whereRaw($fldLft . ' = (' . (string)$subQuery . ')')
					->get(0, 1)->current();

				if ($this->isDescendantOf($root))
				{
					$this->treeRoot = $root;
				}
			}
			catch (\RuntimeException $e)
			{
				// If there is no root found throw an exception. Basically: your table is FUBAR.
				throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
			}

			// If the above method didn't work, get all roots and select the one with the appropriate lft/rgt values
			if (is_null($this->treeRoot))
			{
				// Find the node with depth = 0, lft < our lft and rgt > our right. That's our root node.
				$query = $db->getQuery(true)
					->select(array(
                        $db->qn('node') . '.' . $fldLft,
						'(COUNT(' . $db->qn('parent') . '.' . $fldLft . ') - 1) AS ' . $db->qn('depth')
					))
					->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
					->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
					->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
					->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
					->where($db->qn('node') . '.' . $fldLft . ' < ' . $db->q($this->lft))
					->where($db->qn('node') . '.' . $fldRgt . ' > ' . $db->q($this->rgt))
					->having($db->qn('depth') . ' = ' . $db->q(0))
					->group($db->qn('node') . '.' . $fldLft);

				// Get the lft value
				$targetLeft = $db->setQuery($query)->loadResult();

				if (empty($targetLeft))
				{
					// If there is no root found throw an exception. Basically: your table is FUBAR.
					throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
				}

				try
				{
					$table = $this->getClone();
					$table->reset();
					$this->treeRoot = $table
						->whereRaw($fldLft . ' = ' . $db->q($targetLeft))
						->get(0, 1)->current();
				}
				catch (\RuntimeException $e)
				{
					// If there is no root found throw an exception. Basically: your table is FUBAR.
					throw new \RuntimeException("No root found for table " . $this->getTableName() . ", node lft=" . $this->lft);
				}
			}
		}

		return $this->treeRoot;
	}

	/**
	 * Get all ancestors to this node and the node itself. In other words it gets the full path to the node and the node
	 * itself.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsAndSelf()
	{
		$this->scopeAncestorsAndSelf();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node and the node itself, but not the root node. If you want to
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsAndSelfWithoutRoot()
	{
		$this->scopeAncestorsAndSelf();
		$this->scopeWithoutRoot();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node but not the node itself. In other words it gets the path to the node, without the
	 * node itself.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestors()
	{
		$this->scopeAncestorsAndSelf();
		$this->scopeWithoutSelf();

		return $this->get();
	}

	/**
	 * Get all ancestors to this node but not the node itself and its root.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getAncestorsWithoutRoot()
	{
		$this->scopeAncestors();
		$this->scopeWithoutRoot();

		return $this->get();
	}

	/**
	 * Get all sibling nodes, including ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getSiblingsAndSelf()
	{
		$this->scopeSiblingsAndSelf();

		return $this->get();
	}

	/**
	 * Get all sibling nodes, except ourselves
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getSiblings()
	{
		$this->scopeSiblings();

		return $this->get();
	}

	/**
	 * Get all leaf nodes in the tree. You may want to use the scopes to narrow down the search in a specific subtree or
	 * path.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getLeaves()
	{
		$this->scopeLeaves();

		return $this->get();
	}

	/**
	 * Get all descendant (children) nodes and ourselves.
	 *
	 * Note: all descendant nodes, even descendants of our immediate descendants, will be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getDescendantsAndSelf()
	{
		$this->scopeDescendantsAndSelf();

		return $this->get();
	}

	/**
	 * Get only our descendant (children) nodes, not ourselves.
	 *
	 * Note: all descendant nodes, even descendants of our immediate descendants, will be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getDescendants()
	{
		$this->scopeDescendants();

		return $this->get();
	}

	/**
	 * Get the immediate descendants (children). Unlike getDescendants it only goes one level deep into the tree
	 * structure. Descendants of descendant nodes will not be returned.
	 *
     * @codeCoverageIgnore
     *
	 * @return FOFDatabaseIterator
	 */
	public function getImmediateDescendants()
	{
		$this->scopeImmediateDescendants();

		return $this->get();
	}

	/**
	 * Returns a hashed array where each element's key is the value of the $key column (default: the ID column of the
	 * table) and its value is the value of the $column column (default: title). Each nesting level will have the value
	 * of the $column column prefixed by a number of $separator strings, as many as its nesting level (depth).
	 *
	 * This is useful for creating HTML select elements showing the hierarchy in a human readable format.
	 *
	 * @param string $column
	 * @param null   $key
	 * @param string $seperator
	 *
	 * @return array
	 */
	public function getNestedList($column = 'title', $key = null, $seperator = '  ')
	{
		$db = $this->getDbo();

		$fldLft = $db->qn($this->getColumnAlias('lft'));
		$fldRgt = $db->qn($this->getColumnAlias('rgt'));

		if (empty($key) || !$this->hasField($key))
		{
			$key = $this->getKeyName();
		}

		if (empty($column))
		{
			$column = 'title';
		}

		$fldKey = $db->qn($this->getColumnAlias($key));
		$fldColumn = $db->qn($this->getColumnAlias($column));

		$query = $db->getQuery(true)
			->select(array(
				$db->qn('node') . '.' . $fldKey,
				$db->qn('node') . '.' . $fldColumn,
				'(COUNT(' . $db->qn('parent') . '.' . $fldKey . ') - 1) AS ' . $db->qn('depth')
			))
			->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'))
			->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'))
			->where($db->qn('node') . '.' . $fldLft . ' >= ' . $db->qn('parent') . '.' . $fldLft)
			->where($db->qn('node') . '.' . $fldLft . ' <= ' . $db->qn('parent') . '.' . $fldRgt)
			->group($db->qn('node') . '.' . $fldLft)
			->order($db->qn('node') . '.' . $fldLft . ' ASC');

		$tempResults = $db->setQuery($query)->loadAssocList();
		$ret = array();

		if (!empty($tempResults))
		{
			foreach ($tempResults as $row)
			{
				$ret[$row[$key]] = str_repeat($seperator, $row['depth']) . $row[$column];
			}
		}

		return $ret;
	}

	/**
	 * Locate a node from a given path, e.g. "/some/other/leaf"
	 *
	 * Notes:
	 * - This will only work when you have a "slug" and a "hash" field in your table.
	 * - If the path starts with "/" we will use the root with lft=1. Otherwise the first component of the path is
	 *   supposed to be the slug of the root node.
	 * - If the root node is not found you'll get null as the return value
	 * - You will also get null if any component of the path is not found
	 *
	 * @param string $path The path to locate
	 *
	 * @return FOFTableNested|null The found node or null if nothing is found
	 */
	public function findByPath($path)
	{
		// @todo
	}

	public function isValid()
	{
		// @todo
	}

	public function rebuild()
	{
		// @todo
	}

	/**
	 * Resets cached values used to speed up querying the tree
	 *
	 * @return  static  for chaining
	 */
	protected function resetTreeCache()
	{
		$this->treeDepth = null;
		$this->treeRoot = null;
		$this->treeParent = null;
		$this->treeNestedGet = false;

		return $this;
	}

	/**
	 * Add custom, pre-compiled WHERE clauses for use in buildQuery. The raw WHERE clause you specify is added as is to
	 * the query generated by buildQuery. You are responsible for quoting and escaping the field names and data found
	 * inside the WHERE clause.
	 *
	 * @param   string $rawWhereClause The raw WHERE clause to add
	 *
	 * @return  $this  For chaining
	 */
	public function whereRaw($rawWhereClause)
	{
		$this->whereClauses[] = $rawWhereClause;

		return $this;
	}

	/**
	 * Builds the query for the get() method
	 *
	 * @return FOFDatabaseQuery
	 */
	protected function buildQuery()
	{
		$db = $this->getDbo();

		$query = $db->getQuery(true)
			->select($db->qn('node') . '.*')
			->from($db->qn($this->getTableName()) . ' AS ' . $db->qn('node'));

		if ($this->treeNestedGet)
		{
			$query
				->join('CROSS', $db->qn($this->getTableName()) . ' AS ' . $db->qn('parent'));
		}

		// Apply custom WHERE clauses
		if (count($this->whereClauses))
		{
			foreach ($this->whereClauses as $clause)
			{
				$query->where($clause);
			}
		}

		return $query;
	}

	/**
	 * Returns a database iterator to retrieve records. Use the scope methods and the whereRaw method to define what
	 * exactly will be returned.
	 *
	 * @param   integer $limitstart How many items to skip from the start, only when $overrideLimits = true
	 * @param   integer $limit      How many items to return, only when $overrideLimits = true
	 *
	 * @return  FOFDatabaseIterator  The data collection
	 */
	public function get($limitstart = 0, $limit = 0)
	{
		$limitstart = max($limitstart, 0);
		$limit = max($limit, 0);

		$query = $this->buildQuery();
		$db = $this->getDbo();
		$db->setQuery($query, $limitstart, $limit);
		$cursor = $db->execute();

		$dataCollection = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $this->config['_table_class']);

		return $dataCollection;
	}
}
fof/database/interface.php000064400000001570152177723700011551 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (!interface_exists('JDatabaseInterface'))
{
	/**
	 * Joomla Platform Database Interface
	 *
	 * @since  11.2
	 */
	interface JDatabaseInterface
	{
		/**
		 * Test to see if the connector is available.
		 *
		 * @return  boolean  True on success, false otherwise.
		 *
		 * @since   11.2
		 */
		public static function isSupported();
	}
}

interface FOFDatabaseInterface extends JDatabaseInterface
{
}
fof/database/query.php000064400000117652152177723700010767 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since  11.1
 *
 * @method      string  q()   q($text, $escape = true)  Alias for quote method
 * @method      string  qn()  qn($name, $as = null)     Alias for quoteName method
 * @method      string  e()   e($text, $extra = false)  Alias for escape method
 * @property-read   FOFDatabaseQueryElement  $type
 * @property-read   FOFDatabaseQueryElement  $select
 * @property-read   FOFDatabaseQueryElement  $group
 * @property-read   FOFDatabaseQueryElement  $having
 */
abstract class FOFDatabaseQuery
{
	/**
	 * @var    FOFDatabaseDriver  The database driver.
	 * @since  11.1
	 */
	protected $db = null;

	/**
	 * @var    string  The SQL query (if a direct query string was provided).
	 * @since  12.1
	 */
	protected $sql = null;

	/**
	 * @var    string  The query type.
	 * @since  11.1
	 */
	protected $type = '';

	/**
	 * @var    FOFDatabaseQueryElement  The query element for a generic query (type = null).
	 * @since  11.1
	 */
	protected $element = null;

	/**
	 * @var    FOFDatabaseQueryElement  The select element.
	 * @since  11.1
	 */
	protected $select = null;

	/**
	 * @var    FOFDatabaseQueryElement  The delete element.
	 * @since  11.1
	 */
	protected $delete = null;

	/**
	 * @var    FOFDatabaseQueryElement  The update element.
	 * @since  11.1
	 */
	protected $update = null;

	/**
	 * @var    FOFDatabaseQueryElement  The insert element.
	 * @since  11.1
	 */
	protected $insert = null;

	/**
	 * @var    FOFDatabaseQueryElement  The from element.
	 * @since  11.1
	 */
	protected $from = null;

	/**
	 * @var    FOFDatabaseQueryElement  The join element.
	 * @since  11.1
	 */
	protected $join = null;

	/**
	 * @var    FOFDatabaseQueryElement  The set element.
	 * @since  11.1
	 */
	protected $set = null;

	/**
	 * @var    FOFDatabaseQueryElement  The where element.
	 * @since  11.1
	 */
	protected $where = null;

	/**
	 * @var    FOFDatabaseQueryElement  The group by element.
	 * @since  11.1
	 */
	protected $group = null;

	/**
	 * @var    FOFDatabaseQueryElement  The having element.
	 * @since  11.1
	 */
	protected $having = null;

	/**
	 * @var    FOFDatabaseQueryElement  The column list for an INSERT statement.
	 * @since  11.1
	 */
	protected $columns = null;

	/**
	 * @var    FOFDatabaseQueryElement  The values list for an INSERT statement.
	 * @since  11.1
	 */
	protected $values = null;

	/**
	 * @var    FOFDatabaseQueryElement  The order element.
	 * @since  11.1
	 */
	protected $order = null;

	/**
	 * @var   object  The auto increment insert field element.
	 * @since 11.1
	 */
	protected $autoIncrementField = null;

	/**
	 * @var    FOFDatabaseQueryElement  The call element.
	 * @since  12.1
	 */
	protected $call = null;

	/**
	 * @var    FOFDatabaseQueryElement  The exec element.
	 * @since  12.1
	 */
	protected $exec = null;

	/**
	 * @var    FOFDatabaseQueryElement  The union element.
	 * @since  12.1
	 */
	protected $union = null;

	/**
	 * @var    FOFDatabaseQueryElement  The unionAll element.
	 * @since  13.1
	 */
	protected $unionAll = null;

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  string  The aliased method's return value or null.
	 *
	 * @since   11.1
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
				break;

			case 'qn':
				return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
				break;

			case 'e':
				return $this->escape($args[0], isset($args[1]) ? $args[1] : false);
				break;
		}
	}

	/**
	 * Class constructor.
	 *
	 * @param   FOFDatabaseDriver  $db  The database driver.
	 *
	 * @since   11.1
	 */
	public function __construct(FOFDatabaseDriver $db = null)
	{
		$this->db = $db;
	}

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string	The completed query.
	 *
	 * @since   11.1
	 */
	public function __toString()
	{
		$query = '';

		if ($this->sql)
		{
			return $this->sql;
		}

		switch ($this->type)
		{
			case 'element':
				$query .= (string) $this->element;
				break;

			case 'select':
				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->group)
				{
					$query .= (string) $this->group;
				}

				if ($this->having)
				{
					$query .= (string) $this->having;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				if ($this->union)
				{
					$query .= (string) $this->union;
				}

				break;

			case 'delete':
				$query .= (string) $this->delete;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'update':
				$query .= (string) $this->update;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				$query .= (string) $this->set;

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				// Set method
				if ($this->set)
				{
					$query .= (string) $this->set;
				}
				// Columns-Values method
				elseif ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->values->getElements();

					if (!($elements[0] instanceof $this))
					{
						$query .= ' VALUES ';
					}

					$query .= (string) $this->values;
				}

				break;

			case 'call':
				$query .= (string) $this->call;
				break;

			case 'exec':
				$query .= (string) $this->exec;
				break;
		}

		if ($this instanceof FOFDatabaseQueryLimitable)
		{
			$query = $this->processLimit($query, $this->limit, $this->offset);
		}

		return $query;
	}

	/**
	 * Magic function to get protected variable value
	 *
	 * @param   string  $name  The name of the variable.
	 *
	 * @return  mixed
	 *
	 * @since   11.1
	 */
	public function __get($name)
	{
		return isset($this->$name) ? $this->$name : null;
	}

	/**
	 * Add a single column, or array of columns to the CALL clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The call method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->call('a.*')->call('b.id');
	 * $query->call(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function call($columns)
	{
		$this->type = 'call';

		if (is_null($this->call))
		{
			$this->call = new FOFDatabaseQueryElement('CALL', $columns);
		}
		else
		{
			$this->call->append($columns);
		}

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   11.1
	 */
	public function castAsChar($value)
	{
		return $value;
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   11.1
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'CHAR_LENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function clear($clause = null)
	{
		$this->sql = null;

		switch ($clause)
		{
			case 'select':
				$this->select = null;
				$this->type = null;
				break;

			case 'delete':
				$this->delete = null;
				$this->type = null;
				break;

			case 'update':
				$this->update = null;
				$this->type = null;
				break;

			case 'insert':
				$this->insert = null;
				$this->type = null;
				$this->autoIncrementField = null;
				break;

			case 'from':
				$this->from = null;
				break;

			case 'join':
				$this->join = null;
				break;

			case 'set':
				$this->set = null;
				break;

			case 'where':
				$this->where = null;
				break;

			case 'group':
				$this->group = null;
				break;

			case 'having':
				$this->having = null;
				break;

			case 'order':
				$this->order = null;
				break;

			case 'columns':
				$this->columns = null;
				break;

			case 'values':
				$this->values = null;
				break;

			case 'exec':
				$this->exec = null;
				$this->type = null;
				break;

			case 'call':
				$this->call = null;
				$this->type = null;
				break;

			case 'limit':
				$this->offset = 0;
				$this->limit = 0;
				break;

			case 'offset':
				$this->offset = 0;
				break;

			case 'union':
				$this->union = null;
				break;

			case 'unionAll':
				$this->unionAll = null;
				break;

			default:
				$this->type = null;
				$this->select = null;
				$this->delete = null;
				$this->update = null;
				$this->insert = null;
				$this->from = null;
				$this->join = null;
				$this->set = null;
				$this->where = null;
				$this->group = null;
				$this->having = null;
				$this->order = null;
				$this->columns = null;
				$this->values = null;
				$this->autoIncrementField = null;
				$this->exec = null;
				$this->call = null;
				$this->union = null;
				$this->unionAll = null;
				$this->offset = 0;
				$this->limit = 0;
				break;
		}

		return $this;
	}

	/**
	 * Adds a column, or array of column names that would be used for an INSERT INTO statement.
	 *
	 * @param   mixed  $columns  A column name, or array of column names.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function columns($columns)
	{
		if (is_null($this->columns))
		{
			$this->columns = new FOFDatabaseQueryElement('()', $columns);
		}
		else
		{
			$this->columns->append($columns);
		}

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   11.1
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return 'CONCATENATE(' . implode(' || ' . $this->quote($separator) . ' || ', $values) . ')';
		}
		else
		{
			return 'CONCATENATE(' . implode(' || ', $values) . ')';
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * Usage:
	 * $query->where('published_up < '.$query->currentTimestamp());
	 *
	 * @return  string
	 *
	 * @since   11.1
	 */
	public function currentTimestamp()
	{
		return 'CURRENT_TIMESTAMP()';
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the getDateFormat method directly.
	 *
	 * @return  string  The format string.
	 *
	 * @since   11.1
	 */
	public function dateFormat()
	{
		if (!($this->db instanceof FOFDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->getDateFormat();
	}

	/**
	 * Creates a formatted dump of the query for debugging purposes.
	 *
	 * Usage:
	 * echo $query->dump();
	 *
	 * @return  string
	 *
	 * @since   11.3
	 */
	public function dump()
	{
		return '<pre class="FOFDatabasequery">' . str_replace('#__', $this->db->getPrefix(), $this) . '</pre>';
	}

	/**
	 * Add a table name to the DELETE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->delete('#__a')->where('id = 1');
	 *
	 * @param   string  $table  The name of the table to delete from.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function delete($table = null)
	{
		$this->type = 'delete';
		$this->delete = new FOFDatabaseQueryElement('DELETE', null);

		if (!empty($table))
		{
			$this->from($table);
		}

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the escape method directly.
	 *
	 * Note that 'e' is an alias for this method as it is in FOFDatabaseDriver.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   11.1
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function escape($text, $extra = false)
	{
		if (!($this->db instanceof FOFDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->escape($text, $extra);
	}

	/**
	 * Add a single column, or array of columns to the EXEC clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The exec method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->exec('a.*')->exec('b.id');
	 * $query->exec(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function exec($columns)
	{
		$this->type = 'exec';

		if (is_null($this->exec))
		{
			$this->exec = new FOFDatabaseQueryElement('EXEC', $columns);
		}
		else
		{
			$this->exec->append($columns);
		}

		return $this;
	}

	/**
	 * Add a table to the FROM clause of the query.
	 *
	 * Note that while an array of tables can be provided, it is recommended you use explicit joins.
	 *
	 * Usage:
	 * $query->select('*')->from('#__a');
	 *
	 * @param   mixed   $tables         A string or array of table names.
	 *                                  This can be a FOFDatabaseQuery object (or a child of it) when used
	 *                                  as a subquery in FROM clause along with a value for $subQueryAlias.
	 * @param   string  $subQueryAlias  Alias used when $tables is a FOFDatabaseQuery.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @throws  RuntimeException
	 *
	 * @since   11.1
	 */
	public function from($tables, $subQueryAlias = null)
	{
		if (is_null($this->from))
		{
			if ($tables instanceof $this)
			{
				if (is_null($subQueryAlias))
				{
					throw new RuntimeException('JLIB_DATABASE_ERROR_NULL_SUBQUERY_ALIAS');
				}

				$tables = '( ' . (string) $tables . ' ) AS ' . $this->quoteName($subQueryAlias);
			}

			$this->from = new FOFDatabaseQueryElement('FROM', $tables);
		}
		else
		{
			$this->from->append($tables);
		}

		return $this;
	}

	/**
	 * Used to get a string to extract year from date column.
	 *
	 * Usage:
	 * $query->select($query->year($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing year to be extracted.
	 *
	 * @return  string  Returns string to extract year from a date.
	 *
	 * @since   12.1
	 */
	public function year($date)
	{
		return 'YEAR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract month from date column.
	 *
	 * Usage:
	 * $query->select($query->month($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing month to be extracted.
	 *
	 * @return  string  Returns string to extract month from a date.
	 *
	 * @since   12.1
	 */
	public function month($date)
	{
		return 'MONTH(' . $date . ')';
	}

	/**
	 * Used to get a string to extract day from date column.
	 *
	 * Usage:
	 * $query->select($query->day($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing day to be extracted.
	 *
	 * @return  string  Returns string to extract day from a date.
	 *
	 * @since   12.1
	 */
	public function day($date)
	{
		return 'DAY(' . $date . ')';
	}

	/**
	 * Used to get a string to extract hour from date column.
	 *
	 * Usage:
	 * $query->select($query->hour($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing hour to be extracted.
	 *
	 * @return  string  Returns string to extract hour from a date.
	 *
	 * @since   12.1
	 */
	public function hour($date)
	{
		return 'HOUR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract minute from date column.
	 *
	 * Usage:
	 * $query->select($query->minute($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing minute to be extracted.
	 *
	 * @return  string  Returns string to extract minute from a date.
	 *
	 * @since   12.1
	 */
	public function minute($date)
	{
		return 'MINUTE(' . $date . ')';
	}

	/**
	 * Used to get a string to extract seconds from date column.
	 *
	 * Usage:
	 * $query->select($query->second($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing second to be extracted.
	 *
	 * @return  string  Returns string to extract second from a date.
	 *
	 * @since   12.1
	 */
	public function second($date)
	{
		return 'SECOND(' . $date . ')';
	}

	/**
	 * Add a grouping column to the GROUP clause of the query.
	 *
	 * Usage:
	 * $query->group('id');
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function group($columns)
	{
		if (is_null($this->group))
		{
			$this->group = new FOFDatabaseQueryElement('GROUP BY', $columns);
		}
		else
		{
			$this->group->append($columns);
		}

		return $this;
	}

	/**
	 * A conditions to the HAVING clause of the query.
	 *
	 * Usage:
	 * $query->group('id')->having('COUNT(id) > 5');
	 *
	 * @param   mixed   $conditions  A string or array of columns.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function having($conditions, $glue = 'AND')
	{
		if (is_null($this->having))
		{
			$glue = strtoupper($glue);
			$this->having = new FOFDatabaseQueryElement('HAVING', $conditions, " $glue ");
		}
		else
		{
			$this->having->append($conditions);
		}

		return $this;
	}

	/**
	 * Add an INNER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function innerJoin($condition)
	{
		$this->join('INNER', $condition);

		return $this;
	}

	/**
	 * Add a table name to the INSERT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->insert('#__a')->set('id = 1');
	 * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4');
	 * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4'));
	 *
	 * @param   mixed    $table           The name of the table to insert data into.
	 * @param   boolean  $incrementField  The name of the field to auto increment.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function insert($table, $incrementField=false)
	{
		$this->type = 'insert';
		$this->insert = new FOFDatabaseQueryElement('INSERT INTO', $table);
		$this->autoIncrementField = $incrementField;

		return $this;
	}

	/**
	 * Add a JOIN clause to the query.
	 *
	 * Usage:
	 * $query->join('INNER', 'b ON b.id = a.id);
	 *
	 * @param   string  $type        The type of join. This string is prepended to the JOIN keyword.
	 * @param   string  $conditions  A string or array of conditions.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function join($type, $conditions)
	{
		if (is_null($this->join))
		{
			$this->join = array();
		}

		$this->join[] = new FOFDatabaseQueryElement(strtoupper($type) . ' JOIN', $conditions);

		return $this;
	}

	/**
	 * Add a LEFT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function leftJoin($condition)
	{
		$this->join('LEFT', $condition);

		return $this;
	}

	/**
	 * Get the length of a string in bytes.
	 *
	 * Note, use 'charLength' to find the number of characters in a string.
	 *
	 * Usage:
	 * query->where($query->length('a').' > 3');
	 *
	 * @param   string  $value  The string to measure.
	 *
	 * @return  int
	 *
	 * @since   11.1
	 */
	public function length($value)
	{
		return 'LENGTH(' . $value . ')';
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the nullDate method directly.
	 *
	 * Usage:
	 * $query->where('modified_date <> '.$query->nullDate());
	 *
	 * @param   boolean  $quoted  Optionally wraps the null date in database quotes (true by default).
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 *
	 * @since   11.1
	 */
	public function nullDate($quoted = true)
	{
		if (!($this->db instanceof FOFDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		$result = $this->db->getNullDate($quoted);

		if ($quoted)
		{
			return $this->db->quote($result);
		}

		return $result;
	}

	/**
	 * Add a ordering column to the ORDER clause of the query.
	 *
	 * Usage:
	 * $query->order('foo')->order('bar');
	 * $query->order(array('foo','bar'));
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function order($columns)
	{
		if (is_null($this->order))
		{
			$this->order = new FOFDatabaseQueryElement('ORDER BY', $columns);
		}
		else
		{
			$this->order->append($columns);
		}

		return $this;
	}

	/**
	 * Add an OUTER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function outerJoin($condition)
	{
		$this->join('OUTER', $condition);

		return $this;
	}

	/**
	 * Method to quote and optionally escape a string to database requirements for insertion into the database.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quote method directly.
	 *
	 * Note that 'q' is an alias for this method as it is in FOFDatabaseDriver.
	 *
	 * Usage:
	 * $query->quote('fulltext');
	 * $query->q('fulltext');
	 * $query->q(array('option', 'fulltext'));
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True to escape the string, false to leave it unchanged.
	 *
	 * @return  string  The quoted input string.
	 *
	 * @since   11.1
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function quote($text, $escape = true)
	{
		if (!($this->db instanceof FOFDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->quote($text, $escape);
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quoteName method directly.
	 *
	 * Note that 'qn' is an alias for this method as it is in FOFDatabaseDriver.
	 *
	 * Usage:
	 * $query->quoteName('#__a');
	 * $query->qn('#__a');
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
	 *                        same length of $name; if is null there will not be any AS part for string or array element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @since   11.1
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function quoteName($name, $as = null)
	{
		if (!($this->db instanceof FOFDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->quoteName($name, $as);
	}

	/**
	 * Add a RIGHT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function rightJoin($condition)
	{
		$this->join('RIGHT', $condition);

		return $this;
	}

	/**
	 * Add a single column, or array of columns to the SELECT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The select method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->select('a.*')->select('b.id');
	 * $query->select(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function select($columns)
	{
		$this->type = 'select';

		if (is_null($this->select))
		{
			$this->select = new FOFDatabaseQueryElement('SELECT', $columns);
		}
		else
		{
			$this->select->append($columns);
		}

		return $this;
	}

	/**
	 * Add a single condition string, or an array of strings to the SET clause of the query.
	 *
	 * Usage:
	 * $query->set('a = 1')->set('b = 2');
	 * $query->set(array('a = 1', 'b = 2');
	 *
	 * @param   mixed   $conditions  A string or array of string conditions.
	 * @param   string  $glue        The glue by which to join the condition strings. Defaults to ,.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function set($conditions, $glue = ',')
	{
		if (is_null($this->set))
		{
			$glue = strtoupper($glue);
			$this->set = new FOFDatabaseQueryElement('SET', $conditions, "\n\t$glue ");
		}
		else
		{
			$this->set->append($conditions);
		}

		return $this;
	}

	/**
	 * Allows a direct query to be provided to the database
	 * driver's setQuery() method, but still allow queries
	 * to have bounded variables.
	 *
	 * Usage:
	 * $query->setQuery('select * from #__users');
	 *
	 * @param   mixed  $sql  An SQL Query
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setQuery($sql)
	{
		$this->sql = $sql;

		return $this;
	}

	/**
	 * Add a table name to the UPDATE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->update('#__foo')->set(...);
	 *
	 * @param   string  $table  A table to update.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function update($table)
	{
		$this->type = 'update';
		$this->update = new FOFDatabaseQueryElement('UPDATE', $table);

		return $this;
	}

	/**
	 * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement.
	 *
	 * Usage:
	 * $query->values('1,2,3')->values('4,5,6');
	 * $query->values(array('1,2,3', '4,5,6'));
	 *
	 * @param   string  $values  A single tuple, or array of tuples.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function values($values)
	{
		if (is_null($this->values))
		{
			$this->values = new FOFDatabaseQueryElement('()', $values, '),(');
		}
		else
		{
			$this->values->append($values);
		}

		return $this;
	}

	/**
	 * Add a single condition, or an array of conditions to the WHERE clause of the query.
	 *
	 * Usage:
	 * $query->where('a = 1')->where('b = 2');
	 * $query->where(array('a = 1', 'b = 2'));
	 *
	 * @param   mixed   $conditions  A string or array of where conditions.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   11.1
	 */
	public function where($conditions, $glue = 'AND')
	{
		if (is_null($this->where))
		{
			$glue = strtoupper($glue);
			$this->where = new FOFDatabaseQueryElement('WHERE', $conditions, " $glue ");
		}
		else
		{
			$this->where->append($conditions);
		}

		return $this;
	}

	/**
	 * Method to provide deep copy support to nested objects and
	 * arrays when cloning.
	 *
	 * @return  void
	 *
	 * @since   11.3
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if ($k === 'db')
			{
				continue;
			}

			if (is_object($v) || is_array($v))
			{
				$this->{$k} = unserialize(serialize($v));
			}
		}
	}

	/**
	 * Add a query to UNION with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage (the $query base query MUST be a select query):
	 * $query->union('SELECT name FROM  #__foo')
	 * $query->union('SELECT name FROM  #__foo', true)
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 * $query->union($query2)->union($query3)
	 * $query->union(array($query2, $query3))
	 *
	 * @param   mixed    $query     The FOFDatabaseQuery object or string to union.
	 * @param   boolean  $distinct  True to only return distinct rows from the union.
	 * @param   string   $glue      The glue by which to join the conditions.
	 *
	 * @return  mixed    The FOFDatabaseQuery object on success or boolean false on failure.
	 *
	 * @see  http://dev.mysql.com/doc/refman/5.0/en/union.html
	 *
	 * @since   12.1
	 */
	public function union($query, $distinct = false, $glue = '')
	{
		// Set up the DISTINCT flag, the name with parentheses, and the glue.
		if ($distinct)
		{
			$name = 'UNION DISTINCT ()';
			$glue = ')' . PHP_EOL . 'UNION DISTINCT (';
		}
		else
		{
			$glue = ')' . PHP_EOL . 'UNION (';
			$name = 'UNION ()';
		}

		// Get the FOFDatabaseQueryElement if it does not exist
		if (is_null($this->union))
		{
			$this->union = new FOFDatabaseQueryElement($name, $query, "$glue");
		}
		// Otherwise append the second UNION.
		else
		{
			$this->union->append($query);
		}

		return $this;
	}

	/**
	 * Add a query to UNION DISTINCT with the current query. Simply a proxy to union with the DISTINCT keyword.
	 *
	 * Usage:
	 * $query->unionDistinct('SELECT name FROM  #__foo')
	 *
	 * @param   mixed   $query  The FOFDatabaseQuery object or string to union.
	 * @param   string  $glue   The glue by which to join the conditions.
	 *
	 * @return  mixed   The FOFDatabaseQuery object on success or boolean false on failure.
	 *
	 * @see     union
	 *
	 * @since   12.1
	 */
	public function unionDistinct($query, $glue = '')
	{
		$distinct = true;

		// Apply the distinct flag to the union.
		return $this->union($query, $distinct, $glue);
	}

	/**
	 * Find and replace sprintf-like tokens in a format string.
	 * Each token takes one of the following forms:
	 *     %%       - A literal percent character.
	 *     %[t]     - Where [t] is a type specifier.
	 *     %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier.
	 *
	 * Types:
	 * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped.
	 * e - Escape: Replacement text is passed to $this->escape().
	 * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument.
	 * n - Name Quote: Replacement text is passed to $this->quoteName().
	 * q - Quote: Replacement text is passed to $this->quote().
	 * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument.
	 * r - Raw: Replacement text is used as-is. (Be careful)
	 *
	 * Date Types:
	 * - Replacement text automatically quoted (use uppercase for Name Quote).
	 * - Replacement text should be a string in date format or name of a date column.
	 * y/Y - Year
	 * m/M - Month
	 * d/D - Day
	 * h/H - Hour
	 * i/I - Minute
	 * s/S - Second
	 *
	 * Invariable Types:
	 * - Takes no argument.
	 * - Argument index not incremented.
	 * t - Replacement text is the result of $this->currentTimestamp().
	 * z - Replacement text is the result of $this->nullDate(false).
	 * Z - Replacement text is the result of $this->nullDate(true).
	 *
	 * Usage:
	 * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1);
	 * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1
	 *
	 * Notes:
	 * The argument specifier is optional but recommended for clarity.
	 * The argument index used for unspecified tokens is incremented only when used.
	 *
	 * @param   string  $format  The formatting string.
	 *
	 * @return  string  Returns a string produced according to the formatting string.
	 *
	 * @since   12.3
	 */
	public function format($format)
	{
		$query = $this;
		$args = array_slice(func_get_args(), 1);
		array_unshift($args, null);

		$i = 1;
		$func = function ($match) use ($query, $args, &$i)
		{
			if (isset($match[6]) && $match[6] == '%')
			{
				return '%';
			}

			// No argument required, do not increment the argument index.
			switch ($match[5])
			{
				case 't':
					return $query->currentTimestamp();
					break;

				case 'z':
					return $query->nullDate(false);
					break;

				case 'Z':
					return $query->nullDate(true);
					break;
			}

			// Increment the argument index only if argument specifier not provided.
			$index = is_numeric($match[4]) ? (int) $match[4] : $i++;

			if (!$index || !isset($args[$index]))
			{
				// TODO - What to do? sprintf() throws a Warning in these cases.
				$replacement = '';
			}
			else
			{
				$replacement = $args[$index];
			}

			switch ($match[5])
			{
				case 'a':
					return 0 + $replacement;
					break;

				case 'e':
					return $query->escape($replacement);
					break;

				case 'E':
					return $query->escape($replacement, true);
					break;

				case 'n':
					return $query->quoteName($replacement);
					break;

				case 'q':
					return $query->quote($replacement);
					break;

				case 'Q':
					return $query->quote($replacement, false);
					break;

				case 'r':
					return $replacement;
					break;

				// Dates
				case 'y':
					return $query->year($query->quote($replacement));
					break;

				case 'Y':
					return $query->year($query->quoteName($replacement));
					break;

				case 'm':
					return $query->month($query->quote($replacement));
					break;

				case 'M':
					return $query->month($query->quoteName($replacement));
					break;

				case 'd':
					return $query->day($query->quote($replacement));
					break;

				case 'D':
					return $query->day($query->quoteName($replacement));
					break;

				case 'h':
					return $query->hour($query->quote($replacement));
					break;

				case 'H':
					return $query->hour($query->quoteName($replacement));
					break;

				case 'i':
					return $query->minute($query->quote($replacement));
					break;

				case 'I':
					return $query->minute($query->quoteName($replacement));
					break;

				case 's':
					return $query->second($query->quote($replacement));
					break;

				case 'S':
					return $query->second($query->quoteName($replacement));
					break;
			}

			return '';
		};

		/**
		 * Regexp to find an replace all tokens.
		 * Matched fields:
		 * 0: Full token
		 * 1: Everything following '%'
		 * 2: Everything following '%' unless '%'
		 * 3: Argument specifier and '$'
		 * 4: Argument specifier
		 * 5: Type specifier
		 * 6: '%' if full token is '%%'
		 */
		return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format);
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 * Note: Not all drivers support all units.
	 *
	 * @param   datetime  $date      The date to add to. May be date or datetime
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @link     http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add
	 * @since   13.1
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		return trim("DATE_ADD('" . $date . "', INTERVAL " . $interval . ' ' . $datePart . ')');
	}

	/**
	 * Add a query to UNION ALL with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage:
	 * $query->union('SELECT name FROM  #__foo')
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 *
	 * @param   mixed    $query     The FOFDatabaseQuery object or string to union.
	 * @param   boolean  $distinct  Not used - ignored.
	 * @param   string   $glue      Not used - ignored.
	 *
	 * @return  mixed    The FOFDatabaseQuery object on success or boolean false on failure.
	 *
	 * @see     union
	 *
	 * @since   13.1
	 */
	public function unionAll($query, $distinct = false, $glue = '')
	{
		$glue = ')' . PHP_EOL . 'UNION ALL (';
		$name = 'UNION ALL ()';

		// Get the FOFDatabaseQueryElement if it does not exist
		if (is_null($this->unionAll))
		{
			$this->unionAll = new FOFDatabaseQueryElement($name, $query, "$glue");
		}

		// Otherwise append the second UNION.
		else
		{
			$this->unionAll->append($query);
		}

		return $this;
	}
}
fof/database/iterator/mysql.php000064400000002421152177723700012603 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQL database iterator.
 */
class FOFDatabaseIteratorMysql extends FOFDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @see     Countable::count()
	 */
	public function count()
	{
		return @mysql_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject()
	{
		return @mysql_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	protected function freeResult()
	{
		@mysql_free_result($this->cursor);
	}
}
fof/database/iterator/sqlsrv.php000064400000002430152177723700012770 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQL server database iterator.
 */
class FOFDatabaseIteratorSqlsrv extends FOFDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @see     Countable::count()
	 */
	public function count()
	{
		return @sqlsrv_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject()
	{
		return @sqlsrv_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	protected function freeResult()
	{
		@sqlsrv_free_stmt($this->cursor);
	}
}
fof/database/iterator/oracle.php000064400000001114152177723700012701 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQLi database iterator.
 */
class FOFDatabaseIteratorOracle extends FOFDatabaseIteratorPdo
{
}
fof/database/iterator/mysqli.php000064400000002426152177723700012761 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQLi database iterator.
 */
class FOFDatabaseIteratorMysqli extends FOFDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @see     Countable::count()
	 */
	public function count()
	{
		return @mysqli_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject()
	{
		return @mysqli_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	protected function freeResult()
	{
		@mysqli_free_result($this->cursor);
	}
}
fof/database/iterator/pdomysql.php000064400000001116152177723700013306 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQLi database iterator.
 */
class FOFDatabaseIteratorPdomysql extends FOFDatabaseIteratorPdo
{
}
fof/database/iterator/sqlite.php000064400000001114152177723700012735 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQLi database iterator.
 */
class FOFDatabaseIteratorSqlite extends FOFDatabaseIteratorPdo
{
}
fof/database/iterator/postgresql.php000064400000002430152177723700013641 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * PostgreSQL database iterator.
 */
class FOFDatabaseIteratorPostgresql extends FOFDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @see     Countable::count()
	 */
	public function count()
	{
		return @pg_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject()
	{
		return @pg_fetch_object($this->cursor, null, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	protected function freeResult()
	{
		@pg_free_result($this->cursor);
	}
}
fof/database/iterator/pdo.php000064400000003047152177723700012225 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * PDO database iterator.
 */
class FOFDatabaseIteratorPdo extends FOFDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @see     Countable::count()
	 */
	public function count()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			return @$this->cursor->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 */
	protected function fetchObject()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			return @$this->cursor->fetchObject($this->class);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	protected function freeResult()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			@$this->cursor->closeCursor();
		}
	}
}
fof/database/iterator/azure.php000064400000001121152177723700012560 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQL azure database iterator.
 */
class FOFDatabaseIteratorAzure extends FOFDatabaseIteratorSqlsrv
{
}
fof/database/query/mysql.php000064400000001333152177723700012120 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since       11.1
 * @deprecated  Will be removed when the minimum supported PHP version no longer includes the deprecated PHP `mysql` extension
 */
class FOFDatabaseQueryMysql extends FOFDatabaseQueryMysqli
{
}
fof/database/query/limitable.php000064400000004363152177723700012723 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (!interface_exists('JDatabaseQueryLimitable'))
{
	/**
	 * Joomla Database Query Limitable Interface.
	 * Adds bind/unbind methods as well as a getBounded() method
	 * to retrieve the stored bounded variables on demand prior to
	 * query execution.
	 *
	 * @since  12.1
	 */
	interface JDatabaseQueryLimitable
	{
		/**
		 * Method to modify a query already in string format with the needed
		 * additions to make the query limited to a particular number of
		 * results, or start at a particular offset. This method is used
		 * automatically by the __toString() method if it detects that the
		 * query implements the FOFDatabaseQueryLimitable interface.
		 *
		 * @param   string   $query   The query in string format
		 * @param   integer  $limit   The limit for the result set
		 * @param   integer  $offset  The offset for the result set
		 *
		 * @return  string
		 *
		 * @since   12.1
		 */
		public function processLimit($query, $limit, $offset = 0);

		/**
		 * Sets the offset and limit for the result set, if the database driver supports it.
		 *
		 * Usage:
		 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
		 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
		 *
		 * @param   integer  $limit   The limit for the result set
		 * @param   integer  $offset  The offset for the result set
		 *
		 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
		 *
		 * @since   12.1
		 */
		public function setLimit($limit = 0, $offset = 0);
	}
}

/**
 * Joomla Database Query Limitable Interface.
 * Adds bind/unbind methods as well as a getBounded() method
 * to retrieve the stored bounded variables on demand prior to
 * query execution.
 *
 * @since  12.1
 */
interface FOFDatabaseQueryLimitable extends JDatabaseQueryLimitable
{
}
fof/database/query/sqlsrv.php000064400000021007152177723700012305 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since  11.1
 */
class FOFDatabaseQuerySqlsrv extends FOFDatabaseQuery implements FOFDatabaseQueryLimitable
{
	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  11.1
	 */
	protected $name_quotes = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  11.1
	 */
	protected $null_date = '1900-01-01 00:00:00';

	/**
	 * @var    integer  The affected row limit for the current SQL statement.
	 * @since  3.2
	 */
	protected $limit = 0;

	/**
	 * @var    integer  The affected row offset to apply for the current SQL statement.
	 * @since  3.2
	 */
	protected $offset = 0;

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string	The completed query.
	 *
	 * @since   11.1
	 */
	public function __toString()
	{
		$query = '';

		switch ($this->type)
		{
			case 'select':
				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->group)
				{
					$query .= (string) $this->group;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				if ($this->having)
				{
					$query .= (string) $this->having;
				}

				if ($this instanceof FOFDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0))
				{
					$query = $this->processLimit($query, $this->limit, $this->offset);
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				// Set method
				if ($this->set)
				{
					$query .= (string) $this->set;
				}
				// Columns-Values method
				elseif ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->insert->getElements();
					$tableName = array_shift($elements);

					$query .= 'VALUES ';
					$query .= (string) $this->values;

					if ($this->autoIncrementField)
					{
						$query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;';
					}

					if ($this->where)
					{
						$query .= (string) $this->where;
					}
				}

				break;

			case 'delete':
				$query .= (string) $this->delete;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'update':
				$query .= (string) $this->update;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				$query .= (string) $this->set;

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			default:
				$query = parent::__toString();
				break;
		}

		return $query;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   11.1
	 */
	public function castAsChar($value)
	{
		return 'CAST(' . $value . ' as NVARCHAR(10))';
	}

	/**
	 * Gets the function to determine the length of a character string.
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   11.1
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   11.1
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')';
		}
		else
		{
			return '(' . implode('+', $values) . ')';
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * @return  string
	 *
	 * @since   11.1
	 */
	public function currentTimestamp()
	{
		return 'GETDATE()';
	}

	/**
	 * Get the length of a string in bytes.
	 *
	 * @param   string  $value  The string to measure.
	 *
	 * @return  integer
	 *
	 * @since   11.1
	 */
	public function length($value)
	{
		return 'LEN(' . $value . ')';
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   datetime  $date      The date to add to; type may be time or datetime.
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   13.1
	 * @note    Not all drivers support all units.
	 * @link    http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		return "DATEADD('" . $datePart . "', '" . $interval . "', '" . $date . "'" . ')';
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   12.1
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit == 0 && $offset == 0)
		{
			return $query;
		}

		$start = $offset + 1;
		$end   = $offset + $limit;

		$orderBy = stristr($query, 'ORDER BY');

		if (is_null($orderBy) || empty($orderBy))
		{
			$orderBy = 'ORDER BY (select 0)';
		}

		$query = str_ireplace($orderBy, '', $query);

		$rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM ';

		$query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1);
		$query = 'SELECT * FROM (' . $query . ') A WHERE A.RowNumber BETWEEN ' . $start . ' AND ' . $end;

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Return correct rand() function for MSSQL.
	 *
	 * Ensure that the rand() function is MSSQL compatible.
	 * 
	 * Usage:
	 * $query->Rand();
	 * 
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' NEWID() ';
	}
}
fof/database/query/oracle.php000064400000012720152177723700012222 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Oracle Query Building Class.
 *
 * @since  12.1
 */
class FOFDatabaseQueryOracle extends FOFDatabaseQueryPdo implements FOFDatabaseQueryPreparable, FOFDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  12.1
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  12.1
	 */
	protected $limit;

	/**
	 * @var    array  Bounded object array
	 * @since  12.1
	 */
	protected $bounded = array();

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  FOFDatabaseQueryOracle
	 *
	 * @since   12.1
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array())
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = array();

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value = &$value;
		$obj->dataType = $dataType;
		$obj->length = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   12.1
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}
		else
		{
			if (isset($this->bounded[$key]))
			{
				return $this->bounded[$key];
			}
		}
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  FOFDatabaseQueryOracle  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = array();
				break;
		}

		parent::clear($clause);

		return $this;
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the FOFDatabaseQueryLimitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   12.1
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		// Check if we need to mangle the query.
		if ($limit || $offset)
		{
			$query = "SELECT joomla2.*
		              FROM (
		                  SELECT joomla1.*, ROWNUM AS joomla_db_rownum
		                  FROM (
		                      " . $query . "
		                  ) joomla1
		              ) joomla2";

			// Check if the limit value is greater than zero.
			if ($limit > 0)
			{
				$query .= ' WHERE joomla2.joomla_db_rownum BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $limit);
			}
			else
			{
				// Check if there is an offset and then use this.
				if ($offset)
				{
					$query .= ' WHERE joomla2.joomla_db_rownum > ' . ($offset + 1);
				}
			}
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  FOFDatabaseQueryOracle  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}
}
fof/database/query/element.php000064400000005135152177723700012410 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Element Class.
 *
 * @property-read    string  $name      The name of the element.
 * @property-read    array   $elements  An array of elements.
 * @property-read    string  $glue      Glue piece.
 *
 * @since  11.1
 */
class FOFDatabaseQueryElement
{
	/**
	 * @var    string  The name of the element.
	 * @since  11.1
	 */
	protected $name = null;

	/**
	 * @var    array  An array of elements.
	 * @since  11.1
	 */
	protected $elements = null;

	/**
	 * @var    string  Glue piece.
	 * @since  11.1
	 */
	protected $glue = null;

	/**
	 * Constructor.
	 *
	 * @param   string  $name      The name of the element.
	 * @param   mixed   $elements  String or array.
	 * @param   string  $glue      The glue for elements.
	 *
	 * @since   11.1
	 */
	public function __construct($name, $elements, $glue = ',')
	{
		$this->elements = array();
		$this->name = $name;
		$this->glue = $glue;

		$this->append($elements);
	}

	/**
	 * Magic function to convert the query element to a string.
	 *
	 * @return  string
	 *
	 * @since   11.1
	 */
	public function __toString()
	{
		if (substr($this->name, -2) == '()')
		{
			return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')';
		}
		else
		{
			return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements);
		}
	}

	/**
	 * Appends element parts to the internal list.
	 *
	 * @param   mixed  $elements  String or array.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 */
	public function append($elements)
	{
		if (is_array($elements))
		{
			$this->elements = array_merge($this->elements, $elements);
		}
		else
		{
			$this->elements = array_merge($this->elements, array($elements));
		}
	}

	/**
	 * Gets the elements of this element.
	 *
	 * @return  array
	 *
	 * @since   11.1
	 */
	public function getElements()
	{
		return $this->elements;
	}

	/**
	 * Method to provide deep copy support to nested objects and arrays
	 * when cloning.
	 *
	 * @return  void
	 *
	 * @since   11.3
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if (is_object($v) || is_array($v))
			{
				$this->{$k} = unserialize(serialize($v));
			}
		}
	}
}
fof/database/query/sqlazure.php000064400000002014152177723700012616 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since  11.1
 */
class FOFDatabaseQuerySqlazure extends FOFDatabaseQuerySqlsrv
{
	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 *
	 * @since  11.1
	 */
	protected $name_quotes = '';
}
fof/database/query/mysqli.php000064400000006366152177723700012304 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since  11.1
 */
class FOFDatabaseQueryMysqli extends FOFDatabaseQuery implements FOFDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  12.1
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  12.1
	 */
	protected $limit;

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return string
	 *
	 * @since 12.1
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 || $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}

		return $query;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   11.1
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			$concat_string = 'CONCAT_WS(' . $this->quote($separator);

			foreach ($values as $value)
			{
				$concat_string .= ', ' . $value;
			}

			return $concat_string . ')';
		}
		else
		{
			return 'CONCAT(' . implode(',', $values) . ')';
		}
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  FOFDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Return correct regexp operator for mysqli.
	 *
	 * Ensure that the regexp operator is mysqli compatible.
	 *
	 * Usage:
	 * $query->where('field ' . $query->regexp($search));
	 *
	 * @param   string  $value  The regex pattern.
	 *
	 * @return  string  Returns the regex operator.
	 *
	 * @since   11.3
	 */
	public function regexp($value)
	{
		return ' REGEXP ' . $value;
	}

	/**
	 * Return correct rand() function for Mysql.
	 *
	 * Ensure that the rand() function is Mysql compatible.
	 * 
	 * Usage:
	 * $query->Rand();
	 * 
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' RAND() ';
	}
}
fof/database/query/pdomysql.php000064400000001227152177723700012625 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @since       3.4
 */
class FOFDatabaseQueryPdomysql extends FOFDatabaseQueryMysqli
{
}
fof/database/query/preparable.php000064400000004477152177723700013104 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (!interface_exists('JDatabaseQueryPreparable'))
{
	/**
	 * Joomla Database Query Preparable Interface.
	 * Adds bind/unbind methods as well as a getBounded() method
	 * to retrieve the stored bounded variables on demand prior to
	 * query execution.
	 *
	 * @since  12.1
	 */
	interface JDatabaseQueryPreparable
	{
		/**
		 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
		 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
		 *
		 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
		 *                                          the form ':key', but can also be an integer.
		 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
		 *                                          parameters such as those possible with stored procedures.
		 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
		 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
		 * @param   array           $driverOptions  Optional driver options to be used.
		 *
		 * @return  FOFDatabaseQuery
		 *
		 * @since   12.1
		 */
		public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array());

		/**
		 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
		 * returned.
		 *
		 * @param   mixed  $key  The bounded variable key to retrieve.
		 *
		 * @return  mixed
		 *
		 * @since   12.1
		 */
		public function &getBounded($key = null);
	}
}

interface FOFDatabaseQueryPreparable extends JDatabaseQueryPreparable
{

}fof/database/query/sqlite.php000064400000016630152177723700012262 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQLite Query Building Class.
 *
 * @since  12.1
 */
class FOFDatabaseQuerySqlite extends FOFDatabaseQueryPdo implements FOFDatabaseQueryPreparable, FOFDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  12.1
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  12.1
	 */
	protected $limit;

	/**
	 * @var    array  Bounded object array
	 * @since  12.1
	 */
	protected $bounded = array();

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  FOFDatabaseQuerySqlite
	 *
	 * @since   12.1
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array())
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = array();

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value = &$value;
		$obj->dataType = $dataType;
		$obj->length = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   12.1
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}
		else
		{
			if (isset($this->bounded[$key]))
			{
				return $this->bounded[$key];
			}
		}
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   13.1
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  FOFDatabaseQuerySqlite  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = array();
				break;
		}

		parent::clear($clause);

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   11.1
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return implode(' || ' . $this->quote($separator) . ' || ', $values);
		}
		else
		{
			return implode(' || ', $values);
		}
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the FOFDatabaseQueryLimitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   12.1
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 || $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  FOFDatabaseQuerySqlite  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   datetime  $date      The date or datetime to add to
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   13.1
	 * @link    http://www.sqlite.org/lang_datefunc.html
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		// SQLite does not support microseconds as a separate unit. Convert the interval to seconds
		if (strcasecmp($datePart, 'microseconds') == 0)
		{
			$interval = .001 * $interval;
			$datePart = 'seconds';
		}

		if (substr($interval, 0, 1) != '-')
		{
			return "datetime('" . $date . "', '+" . $interval . " " . $datePart . "')";
		}
		else
		{
			return "datetime('" . $date . "', '" . $interval . " " . $datePart . "')";
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * Usage:
	 * $query->where('published_up < '.$query->currentTimestamp());
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	public function currentTimestamp()
	{
		return 'CURRENT_TIMESTAMP';
	}
}
fof/database/query/postgresql.php000064400000033457152177723700013172 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Query Building Class.
 *
 * @since  11.3
 */
class FOFDatabaseQueryPostgresql extends FOFDatabaseQuery implements FOFDatabaseQueryLimitable
{
	/**
	 * @var    object  The FOR UPDATE element used in "FOR UPDATE"  lock
	 * @since  11.3
	 */
	protected $forUpdate = null;

	/**
	 * @var    object  The FOR SHARE element used in "FOR SHARE"  lock
	 * @since  11.3
	 */
	protected $forShare = null;

	/**
	 * @var    object  The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock
	 * @since  11.3
	 */
	protected $noWait = null;

	/**
	 * @var    object  The LIMIT element
	 * @since  11.3
	 */
	protected $limit = null;

	/**
	 * @var    object  The OFFSET element
	 * @since  11.3
	 */
	protected $offset = null;

	/**
	 * @var    object  The RETURNING element of INSERT INTO
	 * @since  11.3
	 */
	protected $returning = null;

	/**
	 * Magic function to convert the query to a string, only for postgresql specific query
	 *
	 * @return  string	The completed query.
	 *
	 * @since   11.3
	 */
	public function __toString()
	{
		$query = '';

		switch ($this->type)
		{
			case 'select':
				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->group)
				{
					$query .= (string) $this->group;
				}

				if ($this->having)
				{
					$query .= (string) $this->having;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				if ($this->forUpdate)
				{
					$query .= (string) $this->forUpdate;
				}
				else
				{
					if ($this->forShare)
					{
						$query .= (string) $this->forShare;
					}
				}

				if ($this->noWait)
				{
					$query .= (string) $this->noWait;
				}

				break;

			case 'update':
				$query .= (string) $this->update;
				$query .= (string) $this->set;

				if ($this->join)
				{
					$onWord = ' ON ';

					// Workaround for special case of JOIN with UPDATE
					foreach ($this->join as $join)
					{
						$joinElem = $join->getElements();

						$joinArray = explode($onWord, $joinElem[0]);

						$this->from($joinArray[0]);
						$this->where($joinArray[1]);
					}

					$query .= (string) $this->from;
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				if ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->values->getElements();

					if (!($elements[0] instanceof $this))
					{
						$query .= ' VALUES ';
					}

					$query .= (string) $this->values;

					if ($this->returning)
					{
						$query .= (string) $this->returning;
					}
				}

				break;

			default:
				$query = parent::__toString();
				break;
		}

		if ($this instanceof FOFDatabaseQueryLimitable)
		{
			$query = $this->processLimit($query, $this->limit, $this->offset);
		}

		return $query;
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  FOFDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   11.3
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case 'limit':
				$this->limit = null;
				break;

			case 'offset':
				$this->offset = null;
				break;

			case 'forUpdate':
				$this->forUpdate = null;
				break;

			case 'forShare':
				$this->forShare = null;
				break;

			case 'noWait':
				$this->noWait = null;
				break;

			case 'returning':
				$this->returning = null;
				break;

			case 'select':
			case 'update':
			case 'delete':
			case 'insert':
			case 'from':
			case 'join':
			case 'set':
			case 'where':
			case 'group':
			case 'having':
			case 'order':
			case 'columns':
			case 'values':
				parent::clear($clause);
				break;

			default:
				$this->type = null;
				$this->limit = null;
				$this->offset = null;
				$this->forUpdate = null;
				$this->forShare = null;
				$this->noWait = null;
				$this->returning = null;
				parent::clear($clause);
				break;
		}

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   11.3
	 */
	public function castAsChar($value)
	{
		return $value . '::text';
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   11.3
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return implode(' || ' . $this->quote($separator) . ' || ', $values);
		}
		else
		{
			return implode(' || ', $values);
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * @return  string  Return string used in query to obtain
	 *
	 * @since   11.3
	 */
	public function currentTimestamp()
	{
		return 'NOW()';
	}

	/**
	 * Sets the FOR UPDATE lock on select's output row
	 *
	 * @param   string  $table_name  The table to lock
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to ',' .
	 *
	 * @return  FOFDatabaseQueryPostgresql  FOR UPDATE query element
	 *
	 * @since   11.3
	 */
	public function forUpdate($table_name, $glue = ',')
	{
		$this->type = 'forUpdate';

		if (is_null($this->forUpdate))
		{
			$glue            = strtoupper($glue);
			$this->forUpdate = new FOFDatabaseQueryElement('FOR UPDATE', 'OF ' . $table_name, "$glue ");
		}
		else
		{
			$this->forUpdate->append($table_name);
		}

		return $this;
	}

	/**
	 * Sets the FOR SHARE lock on select's output row
	 *
	 * @param   string  $table_name  The table to lock
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to ',' .
	 *
	 * @return  FOFDatabaseQueryPostgresql  FOR SHARE query element
	 *
	 * @since   11.3
	 */
	public function forShare($table_name, $glue = ',')
	{
		$this->type = 'forShare';

		if (is_null($this->forShare))
		{
			$glue           = strtoupper($glue);
			$this->forShare = new FOFDatabaseQueryElement('FOR SHARE', 'OF ' . $table_name, "$glue ");
		}
		else
		{
			$this->forShare->append($table_name);
		}

		return $this;
	}

	/**
	 * Used to get a string to extract year from date column.
	 *
	 * Usage:
	 * $query->select($query->year($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing year to be extracted.
	 *
	 * @return  string  Returns string to extract year from a date.
	 *
	 * @since   12.1
	 */
	public function year($date)
	{
		return 'EXTRACT (YEAR FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract month from date column.
	 *
	 * Usage:
	 * $query->select($query->month($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing month to be extracted.
	 *
	 * @return  string  Returns string to extract month from a date.
	 *
	 * @since   12.1
	 */
	public function month($date)
	{
		return 'EXTRACT (MONTH FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract day from date column.
	 *
	 * Usage:
	 * $query->select($query->day($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing day to be extracted.
	 *
	 * @return  string  Returns string to extract day from a date.
	 *
	 * @since   12.1
	 */
	public function day($date)
	{
		return 'EXTRACT (DAY FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract hour from date column.
	 *
	 * Usage:
	 * $query->select($query->hour($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing hour to be extracted.
	 *
	 * @return  string  Returns string to extract hour from a date.
	 *
	 * @since   12.1
	 */
	public function hour($date)
	{
		return 'EXTRACT (HOUR FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract minute from date column.
	 *
	 * Usage:
	 * $query->select($query->minute($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing minute to be extracted.
	 *
	 * @return  string  Returns string to extract minute from a date.
	 *
	 * @since   12.1
	 */
	public function minute($date)
	{
		return 'EXTRACT (MINUTE FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract seconds from date column.
	 *
	 * Usage:
	 * $query->select($query->second($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing second to be extracted.
	 *
	 * @return  string  Returns string to extract second from a date.
	 *
	 * @since   12.1
	 */
	public function second($date)
	{
		return 'EXTRACT (SECOND FROM ' . $date . ')';
	}

	/**
	 * Sets the NOWAIT lock on select's output row
	 *
	 * @return  FOFDatabaseQueryPostgresql  NO WAIT query element
	 *
	 * @since   11.3
	 */
	public function noWait ()
	{
		$this->type = 'noWait';

		if (is_null($this->noWait))
		{
			$this->noWait = new FOFDatabaseQueryElement('NOWAIT', null);
		}

		return $this;
	}

	/**
	 * Set the LIMIT clause to the query
	 *
	 * @param   integer  $limit  An int of how many row will be returned
	 *
	 * @return  FOFDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   11.3
	 */
	public function limit($limit = 0)
	{
		if (is_null($this->limit))
		{
			$this->limit = new FOFDatabaseQueryElement('LIMIT', (int) $limit);
		}

		return $this;
	}

	/**
	 * Set the OFFSET clause to the query
	 *
	 * @param   integer  $offset  An int for skipping row
	 *
	 * @return  FOFDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   11.3
	 */
	public function offset($offset = 0)
	{
		if (is_null($this->offset))
		{
			$this->offset = new FOFDatabaseQueryElement('OFFSET', (int) $offset);
		}

		return $this;
	}

	/**
	 * Add the RETURNING element to INSERT INTO statement.
	 *
	 * @param   mixed  $pkCol  The name of the primary key column.
	 *
	 * @return  FOFDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   11.3
	 */
	public function returning($pkCol)
	{
		if (is_null($this->returning))
		{
			$this->returning = new FOFDatabaseQueryElement('RETURNING', $pkCol);
		}

		return $this;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  FOFDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   12.1
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   12.1
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0)
		{
			$query .= ' LIMIT ' . $limit;
		}

		if ($offset > 0)
		{
			$query .= ' OFFSET ' . $offset;
		}

		return $query;
	}

	/**
	 * Add to the current date and time in Postgresql.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   datetime  $date      The date to add to
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   13.1
	 * @note    Not all drivers support all units. Check appropriate references
	 * @link    http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		if (substr($interval, 0, 1) != '-')
		{
			return "timestamp '" . $date . "' + interval '" . $interval . " " . $datePart . "'";
		}
		else
		{
			return "timestamp '" . $date . "' - interval '" . ltrim($interval, '-') . " " . $datePart . "'";
		}
	}

	/**
	 * Return correct regexp operator for Postgresql.
	 *
	 * Ensure that the regexp operator is Postgresql compatible.
	 *
	 * Usage:
	 * $query->where('field ' . $query->regexp($search));
	 *
	 * @param   string  $value  The regex pattern.
	 *
	 * @return  string  Returns the regex operator.
	 *
	 * @since   11.3
	 */
	public function regexp($value)
	{
		return ' ~* ' . $value;
	}

	/**
	 * Return correct rand() function for Postgresql.
	 *
	 * Ensure that the rand() function is Postgresql compatible.
	 * 
	 * Usage:
	 * $query->Rand();
	 * 
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' RANDOM() ';
	}
}
fof/database/query/pdo.php000064400000001123152177723700011532 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * PDO Query Building Class.
 *
 * @since  12.1
 */
class FOFDatabaseQueryPdo extends FOFDatabaseQuery
{
}
fof/database/driver/mysql.php000064400000034116152177723700012253 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQL database driver
 *
 * @see         http://dev.mysql.com/doc/
 * @since       12.1
 * @deprecated  Will be removed when the minimum supported PHP version no longer includes the deprecated PHP `mysql` extension
 */
class FOFDatabaseDriverMysql extends FOFDatabaseDriverMysqli
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'mysql';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  Array of database options with keys: host, user, password, database, select.
	 *
	 * @since   12.1
	 */
	public function __construct($options)
	{
		// PHP's `mysql` extension is not present in PHP 7, block instantiation in this environment
		if (PHP_MAJOR_VERSION >= 7)
		{
			throw new RuntimeException(
				'This driver is unsupported in PHP 7, please use the MySQLi or PDO MySQL driver instead.'
			);
		}

		// Get some basic values from the options.
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : 'root';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the MySQL extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new RuntimeException('Could not connect to MySQL.');
		}

		// Attempt to connect to the server.
		if (!($this->connection = @ mysql_connect($this->options['host'], $this->options['user'], $this->options['password'], true)))
		{
			throw new RuntimeException('Could not connect to MySQL.');
		}

		// Set sql_mode to non_strict mode
		mysql_query("SET @@SESSION.sql_mode = '';", $this->connection);

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Pre-populate the UTF-8 Multibyte compatibility flag based on server version
		$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();

		// Set the character set (needed for MySQL 4.1.2+).
		$this->utf = $this->setUtf();

		// Turn MySQL profiling ON in debug mode:
		if ($this->debug && $this->hasProfiling())
		{
			mysql_query("SET profiling = 1;", $this->connection);
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			mysql_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		$this->connect();

		$result = mysql_real_escape_string($text, $this->getConnection());

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return (function_exists('mysql_connect'));
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   12.1
	 */
	public function connected()
	{
		if (is_resource($this->connection))
		{
			return @mysql_ping($this->connection);
		}

		return false;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   12.1
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return mysql_affected_rows($this->connection);
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   12.1
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		return mysql_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();

		return mysql_get_server_info($this->connection);
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   12.1
	 */
	public function insertid()
	{
		$this->connect();

		return mysql_insert_id($this->connection);
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		if (!is_resource($this->connection))
		{
			if (class_exists('JLog'))
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			}
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			if (class_exists('JLog'))
			{
				JLog::add($query, JLog::DEBUG, 'databasequery');
			}

			$this->timings[] = microtime(true);
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysql_query($query, $this->connection);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$this->errorNum = $this->getErrorNumber();
			$this->errorMsg = $this->getErrorMessage($query);

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage($query);

					// Throw the normal query exception.
					if (class_exists('JLog'))
					{
						JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
					}

					throw new RuntimeException($this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Throw the normal query exception.
				if (class_exists('JLog'))
				{
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
				}

				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!mysql_select_db($database, $this->connection))
		{
			throw new RuntimeException('Could not connect to database');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		// If UTF is not supported return false immediately
		if (!$this->utf)
		{
			return false;
		}

		// Make sure we're connected to the server
		$this->connect();

		// Which charset should I use, plain utf8 or multibyte utf8mb4?
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		$result = @mysql_set_charset($charset, $this->connection);

		/**
		 * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise.
		 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
		 * masks the server version and reports only its own we can not be sure if the server actually does support
		 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
		 * catch the error and determine that utf8mb4 is not supported!
		 */
		if (!$result && $this->utf8mb4)
		{
			$this->utf8mb4 = false;
			$result = @mysql_set_charset('utf8', $this->connection);
		}

		return $result;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchArray($cursor = null)
	{
		return mysql_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchAssoc($cursor = null)
	{
		return mysql_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysql_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function freeResult($cursor = null)
	{
		mysql_free_result($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Internal function to check if profiling is available
	 *
	 * @return  boolean
	 *
	 * @since   3.1.3
	 */
	private function hasProfiling()
	{
		try
		{
			$res = mysql_query("SHOW VARIABLES LIKE 'have_profiling'", $this->connection);
			$row = mysql_fetch_assoc($res);

			return isset($row);
		}
		catch (Exception $e)
		{
			return false;
		}
	}

	/**
	 * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * @return  boolean
	 *
	 * @since   CMS 3.5.0
	 */
	private function serverClaimsUtf8mb4Support()
	{
		$client_version = mysql_get_client_info();

		if (strpos($client_version, 'mysqlnd') !== false)
		{
			$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

			return version_compare($client_version, '5.0.9', '>=');
		}
		else
		{
			return version_compare($client_version, '5.5.3', '>=');
		}
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) mysql_errno($this->connection);
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @param   string  $query  The SQL Query that fails
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage($query)
	{
		$errorMessage = (string) mysql_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
			$query        = str_replace($this->tablePrefix, '#__', $query);
		}

		return $errorMessage . ' SQL=' . $query;
	}
}
fof/database/driver/sqlsrv.php000064400000064606152177723700012447 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQL Server database driver
 *
 * @see    http://msdn.microsoft.com/en-us/library/cc296152(SQL.90).aspx
 * @since  12.1
 */
class FOFDatabaseDriverSqlsrv extends FOFDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'sqlsrv';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mssql';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nameQuote = '[]';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nullDate = '1900-01-01 00:00:00';

	/**
	 * @var    string  The minimum supported database version.
	 * @since  12.1
	 */
	protected static $dbMinimum = '10.50.1600.1';

	/**
	 * Test to see if the SQLSRV connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return (function_exists('sqlsrv_connect'));
	}

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   12.1
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Build the connection configuration array.
		$config = array(
			'Database' => $this->options['database'],
			'uid' => $this->options['user'],
			'pwd' => $this->options['password'],
			'CharacterSet' => 'UTF-8',
			'ReturnDatesAsStrings' => true);

		// Make sure the SQLSRV extension for PHP is installed and enabled.
		if (!function_exists('sqlsrv_connect'))
		{
			throw new RuntimeException('PHP extension sqlsrv_connect is not available.');
		}

		// Attempt to connect to the server.
		if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config)))
		{
			throw new RuntimeException('Database sqlsrv_connect failed');
		}

		// Make sure that DB warnings are not returned as errors.
		sqlsrv_configure('WarningsReturnAsErrors', 0);

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Set charactersets.
		$this->utf = $this->setUtf();
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			sqlsrv_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Get table constraints
	 *
	 * @param   string  $tableName  The name of the database table.
	 *
	 * @return  array  Any constraints available for the table.
	 *
	 * @since   12.1
	 */
	protected function getTableConstraints($tableName)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery(
			'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $query->quote($tableName)
		);

		return $this->loadColumn();
	}

	/**
	 * Rename constraints.
	 *
	 * @param   array   $constraints  Array(strings) of table constraints
	 * @param   string  $prefix       A string
	 * @param   string  $backup       A string
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function renameConstraints($constraints = array(), $prefix = null, $backup = null)
	{
		$this->connect();

		foreach ($constraints as $constraint)
		{
			$this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint));
			$this->execute();
		}
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * The escaping for MSSQL isn't handled in the driver though that would be nice.  Because of this we need
	 * to handle the escaping ourselves.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		$result = addslashes($text);
		$result = str_replace("\'", "''", $result);
		$result = str_replace('\"', '"', $result);
		$result = str_replace('\/', '/', $result);

		if ($extra)
		{
			// We need the below str_replace since the search in sql server doesn't recognize _ character.
			$result = str_replace('_', '[_]', $result);
		}

		return $result;
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   12.1
	 */
	public function connected()
	{
		// TODO: Run a blank query here
		return true;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   12.1
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		if ($ifExists)
		{
			$this->setQuery(
				'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName
			);
		}
		else
		{
			$this->setQuery('DROP TABLE ' . $tableName);
		}

		$this->execute();

		return $this;
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   12.1
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return sqlsrv_rows_affected($this->cursor);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   12.1
	 */
	public function getCollation()
	{
		// TODO: Not fake this
		return 'MSSQL UTF-8 (UCS2)';
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		// TODO: Not fake this
		return 'MSSQL UTF-8 (UCS2)';
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   12.1
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed    $table     A table name
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$result = array();

		$table_temp = $this->replacePrefix((string) $table);

		// Set the query to get the table fields statement.
		$this->setQuery(
			'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' .
			' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp)
		);
		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				if (stristr(strtolower($field->Type), "nvarchar"))
				{
					$field->Default = "";
				}
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * This is unsupported by MSSQL.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		return '';
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// TODO To implement.
		return array();
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();

		$version = sqlsrv_server_info($this->connection);

		return $version['SQLServerVersion'];
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$fields = array();
		$values = array();
		$statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)';

		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			if (!$this->checkFieldExists($table, $k))
			{
				continue;
			}

			if ($k[0] == '_')
			{
				// Internal field
				continue;
			}

			if ($k == $key && $key == 0)
			{
				continue;
			}

			$fields[] = $this->quoteName($k);
			$values[] = $this->Quote($v);
		}
		// Set the query and execute the insert.
		$this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values)));

		if (!$this->execute())
		{
			return false;
		}

		$id = $this->insertid();

		if ($key && $id)
		{
			$object->$key = $id;
		}

		return true;
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   12.1
	 */
	public function insertid()
	{
		$this->connect();

		// TODO: SELECT IDENTITY
		$this->setQuery('SELECT @@IDENTITY');

		return (int) $this->loadResult();
	}

	/**
	 * Method to get the first field of the first row of the result set from the database query.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function loadResult()
	{
		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = sqlsrv_fetch_array($cursor, SQLSRV_FETCH_NUMERIC))
		{
			$ret = $row[0];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		// For SQLServer - we need to strip slashes
		$ret = stripslashes($ret);

		return $ret;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 * @throws  Exception
	 */
	public function execute()
	{
		$this->connect();

		if (!is_resource($this->connection))
		{
			if (class_exists('JLog'))
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			}
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query = $this->limit($query, $this->limit, $this->offset);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			if (class_exists('JLog'))
			{
				JLog::add($query, JLog::DEBUG, 'databasequery');
			}

			$this->timings[] = microtime(true);
		}

		// SQLSrv_num_rows requires a static or keyset cursor.
		if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0)
		{
			$array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
		}
		else
		{
			$array = array();
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @sqlsrv_query($this->connection, $query, array(), $array);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage($query);

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage($query);

					// Throw the normal query exception.
					if (class_exists('JLog'))
					{
						JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
					}

					throw new RuntimeException($this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				if (class_exists('JLog'))
				{
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
				}

				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   12.1
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$startPos = 0;
		$literal = '';

		$query = trim($query);
		$n = strlen($query);

		while ($startPos < $n)
		{
			$ip = strpos($query, $prefix, $startPos);

			if ($ip === false)
			{
				break;
			}

			$j = strpos($query, "N'", $startPos);
			$k = strpos($query, '"', $startPos);

			if (($k !== false) && (($k < $j) || ($j === false)))
			{
				$quoteChar = '"';
				$j = $k;
			}
			else
			{
				$quoteChar = "'";
			}

			if ($j === false)
			{
				$j = $n;
			}

			$literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k = strpos($query, $quoteChar, $j);
				$escaped = false;

				if ($k === false)
				{
					break;
				}

				$l = $k - 1;

				while ($l >= 0 && $query{$l} == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}

				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}

				break;
			}

			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}

			$literal .= substr($query, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}

		if ($startPos < $n)
		{
			$literal .= substr($query, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC)))
		{
			throw new RuntimeException('Could not connect to database');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT TRANSACTION')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK TRANSACTION')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('BEGIN TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchArray($cursor = null)
	{
		return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchAssoc($cursor = null)
	{
		return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function freeResult($cursor = null)
	{
		sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to check and see if a field exists in a table.
	 *
	 * @param   string  $table  The table in which to verify the field.
	 * @param   string  $field  The field to verify.
	 *
	 * @return  boolean  True if the field exists in the table.
	 *
	 * @since   12.1
	 */
	protected function checkFieldExists($table, $field)
	{
		$this->connect();

		$table = $this->replacePrefix((string) $table);
		$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field'" .
			" ORDER BY ORDINAL_POSITION";
		$this->setQuery($query);

		if ($this->loadResult())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set.
	 *
	 * @param   string   $query   The SQL statement to process.
	 * @param   integer  $limit   The maximum affected rows to set.
	 * @param   integer  $offset  The affected row offset to set.
	 *
	 * @return  string   The processed SQL statement.
	 *
	 * @since   12.1
	 */
	protected function limit($query, $limit, $offset)
	{
		if ($limit == 0 && $offset == 0)
		{
			return $query;
		}

		$start = $offset + 1;
		$end   = $offset + $limit;

		$orderBy = stristr($query, 'ORDER BY');

		if (is_null($orderBy) || empty($orderBy))
		{
			$orderBy = 'ORDER BY (select 0)';
		}

		$query = str_ireplace($orderBy, '', $query);

		$rowNumberText = ', ROW_NUMBER() OVER (' . $orderBy . ') AS RowNumber FROM ';

		$query = preg_replace('/\sFROM\s/i', $rowNumberText, $query, 1);

		return $query;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  FOFDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$constraints = array();

		if (!is_null($prefix) && !is_null($backup))
		{
			$constraints = $this->getTableConstraints($oldTable);
		}

		if (!empty($constraints))
		{
			$this->renameConstraints($constraints, $prefix, $backup);
		}

		$this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'");

		return $this->execute();
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to lock.
	 *
	 * @return  FOFDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function lockTable($tableName)
	{
		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		return $this;
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		$errors = sqlsrv_errors();

		return $errors[0]['SQLSTATE'];
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @param   string  $query  The SQL Query that fails
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage($query)
	{
		$errors       = sqlsrv_errors();
		$errorMessage = (string) $errors[0]['message'];

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
			$query        = str_replace($this->tablePrefix, '#__', $query);
		}

		return $errorMessage . ' SQL=' . $query;
	}
}
fof/database/driver/oracle.php000064400000035376152177723700012364 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Oracle database driver
 *
 * @see    http://php.net/pdo
 * @since  12.1
 */
class FOFDatabaseDriverOracle extends FOFDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'oracle';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'oracle';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nameQuote = '"';

	/**
	 * Returns the current dateformat
	 *
	 * @var   string
	 * @since 12.1
	 */
	protected $dateformat;

	/**
	 * Returns the current character set
	 *
	 * @var   string
	 * @since 12.1
	 */
	protected $charset;

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   12.1
	 */
	public function __construct($options)
	{
		$options['driver'] = 'oci';
		$options['charset']    = (isset($options['charset'])) ? $options['charset']   : 'AL32UTF8';
		$options['dateformat'] = (isset($options['dateformat'])) ? $options['dateformat'] : 'RRRR-MM-DD HH24:MI:SS';

		$this->charset = $options['charset'];
		$this->dateformat = $options['dateformat'];

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		parent::connect();

		if (isset($this->options['schema']))
		{
			$this->setQuery('ALTER SESSION SET CURRENT_SCHEMA = ' . $this->quoteName($this->options['schema']))->execute();
		}

		$this->setDateFormat($this->dateformat);
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		// Close the connection.
		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Drops a table from the database.
	 *
	 * Note: The IF EXISTS flag is unused in the Oracle driver.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   12.1
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true)
			->setQuery('DROP TABLE :tableName');
		$query->bind(':tableName', $tableName);

		$this->setQuery($query);

		$this->execute();

		return $this;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   12.1
	 */
	public function getCollation()
	{
		return $this->charset;
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return $this->charset;
	}

	/**
	 * Get a query to run and verify the database is operational.
	 *
	 * @return  string  The query to check the health of the DB.
	 *
	 * @since   12.2
	 */
	public function getConnectedQuery()
	{
		return 'SELECT 1 FROM dual';
	}

	/**
	 * Returns the current date format
	 * This method should be useful in the case that
	 * somebody actually wants to use a different
	 * date format and needs to check what the current
	 * one is to see if it needs to be changed.
	 *
	 * @return string The current date format
	 *
	 * @since 12.1
	 */
	public function getDateFormat()
	{
		return $this->dateformat;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * Note: You must have the correct privileges before this method
	 * will return usable results!
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		$result = array();
		$query = $this->getQuery(true)
			->select('dbms_metadata.get_ddl(:type, :tableName)')
			->from('dual')
			->bind(':type', 'TABLE');

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			$query->bind(':tableName', $table);
			$this->setQuery($query);
			$statement = (string) $this->loadResult();
			$result[$table] = $statement;
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$columns = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);

		$query->select('*');
		$query->from('ALL_TAB_COLUMNS');
		$query->where('table_name = :tableName');

		$prefixedTable = str_replace('#__', strtoupper($this->tablePrefix), $table);
		$query->bind(':tableName', $prefixedTable);
		$this->setQuery($query);
		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				$columns[$field->COLUMN_NAME] = $field;
				$columns[$field->COLUMN_NAME]->Default = null;
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $columns;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);
		$query->select('*')
			->from('ALL_CONSTRAINTS')
			->where('table_name = :tableName')
			->bind(':tableName', $table);

		$this->setQuery($query);
		$keys = $this->loadObjectList();

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database (schema).
	 *
	 * @param   string   $databaseName         The database (schema) name
	 * @param   boolean  $includeDatabaseName  Whether to include the schema name in the results
	 *
	 * @return  array    An array of all the tables in the database.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableList($databaseName = null, $includeDatabaseName = false)
	{
		$this->connect();

		$query = $this->getQuery(true);

		if ($includeDatabaseName)
		{
			$query->select('owner, table_name');
		}
		else
		{
			$query->select('table_name');
		}

		$query->from('all_tables');

		if ($databaseName)
		{
			$query->where('owner = :database')
				->bind(':database', $databaseName);
		}

		$query->order('table_name');

		$this->setQuery($query);

		if ($includeDatabaseName)
		{
			$tables = $this->loadAssocList();
		}
		else
		{
			$tables = $this->loadColumn();
		}

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();

		$this->setQuery("select value from nls_database_parameters where parameter = 'NLS_RDBMS_VERSION'");

		return $this->loadResult();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Sets the Oracle Date Format for the session
	 * Default date format for Oracle is = DD-MON-RR
	 * The default date format for this driver is:
	 * 'RRRR-MM-DD HH24:MI:SS' since it is the format
	 * that matches the MySQL one used within most Joomla
	 * tables.
	 *
	 * @param   string  $dateFormat  Oracle Date Format String
	 *
	 * @return boolean
	 *
	 * @since  12.1
	 */
	public function setDateFormat($dateFormat = 'DD-MON-RR')
	{
		$this->connect();

		$this->setQuery("ALTER SESSION SET NLS_DATE_FORMAT = '$dateFormat'");

		if (!$this->execute())
		{
			return false;
		}

		$this->setQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = '$dateFormat'");

		if (!$this->execute())
		{
			return false;
		}

		$this->dateformat = $dateFormat;

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * Returns false automatically for the Oracle driver since
	 * you can only set the character set when the connection
	 * is created.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute();

		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by Oracle.
	 * @param   string  $prefix    Not used by Oracle.
	 *
	 * @return  FOFDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME ' . $oldTable . ' TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('COMMIT')->execute();

		return $this;
	}

	/**
	 * Test to see if the PDO ODBC connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('oci', PDO::getAvailableDrivers());
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   11.1
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$startPos = 0;
		$quoteChar = "'";
		$literal = '';

		$query = trim($query);
		$n = strlen($query);

		while ($startPos < $n)
		{
			$ip = strpos($query, $prefix, $startPos);

			if ($ip === false)
			{
				break;
			}

			$j = strpos($query, "'", $startPos);

			if ($j === false)
			{
				$j = $n;
			}

			$literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k = strpos($query, $quoteChar, $j);
				$escaped = false;

				if ($k === false)
				{
					break;
				}

				$l = $k - 1;

				while ($l >= 0 && $query{$l} == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}

				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}

				break;
			}

			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}

			$literal .= substr($query, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}

		if ($startPos < $n)
		{
			$literal .= substr($query, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			return parent::transactionStart($asSavepoint);
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}
}
fof/database/driver/sqlazure.php000064400000001425152177723700012751 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQL Server database driver
 *
 * @see    http://msdn.microsoft.com/en-us/library/ee336279.aspx
 * @since  12.1
 */
class FOFDatabaseDriverSqlazure extends FOFDatabaseDriverSqlsrv
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'sqlazure';
}
fof/database/driver/mysqli.php000064400000060504152177723700012424 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQLi database driver
 *
 * @see    http://php.net/manual/en/book.mysqli.php
 * @since  12.1
 */
class FOFDatabaseDriverMysqli extends FOFDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'mysqli';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mysql';

	/**
	 * @var    mysqli  The database connection resource.
	 * @since  11.1
	 */
	protected $connection;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  12.2
	 */
	protected $nameQuote = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  12.2
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * @var    string  The minimum supported database version.
	 * @since  12.2
	 */
	protected static $dbMinimum = '5.0.4';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   12.1
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['host']     = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user']     = (isset($options['user'])) ? $options['user'] : 'root';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select']   = (isset($options['select'])) ? (bool) $options['select'] : true;
		$options['port']     = null;
		$options['socket']   = null;

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		/*
		 * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we
		 * have to extract them from the host string.
		 */
		$port = isset($this->options['port']) ? $this->options['port'] : 3306;
		$regex = '/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/';

		if (preg_match($regex, $this->options['host'], $matches))
		{
			// It's an IPv4 address with ot without port
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches))
		{
			// We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches))
		{
			// Named host (e.g domain.com or localhost) with ot without port
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^:(?P<port>[^:]+)$/', $this->options['host'], $matches))
		{
			// Empty host, just port, e.g. ':3306'
			$this->options['host'] = 'localhost';
			$port = $matches['port'];
		}
		// ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default

		// Get the port number or socket name
		if (is_numeric($port))
		{
			$this->options['port'] = (int) $port;
		}
		else
		{
			$this->options['socket'] = $port;
		}

		// Make sure the MySQLi extension for PHP is installed and enabled.
		if (!function_exists('mysqli_connect'))
		{
			throw new RuntimeException('The MySQL adapter mysqli is not available');
		}

		$this->connection = @mysqli_connect(
			$this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket']
		);

		// Attempt to connect to the server.
		if (!$this->connection)
		{
			throw new RuntimeException('Could not connect to MySQL.');
		}

		// Set sql_mode to non_strict mode
		mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';");

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Pre-populate the UTF-8 Multibyte compatibility flag based on server version
		$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();

		// Set the character set (needed for MySQL 4.1.2+).
		$this->utf = $this->setUtf();

		// Turn MySQL profiling ON in debug mode:
		if ($this->debug && $this->hasProfiling())
		{
			mysqli_query($this->connection, "SET profiling_history_size = 100;");
			mysqli_query($this->connection, "SET profiling = 1;");
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		// Close the connection.
		if ($this->connection)
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			mysqli_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		$this->connect();

		$result = mysqli_real_escape_string($this->getConnection(), $text);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return (function_exists('mysqli_connect'));
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   12.1
	 */
	public function connected()
	{
		if (is_object($this->connection))
		{
			return mysqli_ping($this->connection);
		}

		return false;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));

		$this->execute();

		return $this;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   12.1
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return mysqli_affected_rows($this->connection);
	}

	/**
	 * Method to get the database collation.
	 *
	 * @return  mixed  The collation in use by the database (string) or boolean false if not supported.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_database"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_connection"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   12.1
	 */
	public function getNumRows($cursor = null)
	{
		return mysqli_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		$result = array();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			// Set the query to get the table CREATE statement.
			$this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table)));
			$row = $this->loadRow();

			// Populate the result array based on the create statements.
			$result[$table] = $row[1];
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		// Set the query to get the table fields statement.
		$this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)));
		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// Get the details columns information.
		$this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));
		$keys = $this->loadObjectList();

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SHOW TABLES');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();

		return mysqli_get_server_info($this->connection);
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  mixed  The value of the auto-increment field from the last inserted row.
	 *                 If the value is greater than maximal int value, it will return a string.
	 *
	 * @since   12.1
	 */
	public function insertid()
	{
		$this->connect();

		return mysqli_insert_id($this->connection);
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();

		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		if (!is_object($this->connection))
		{
			if (class_exists('JLog'))
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			}
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';
		$memoryBefore   = null;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			if (class_exists('JLog'))
			{
				JLog::add($query, JLog::DEBUG, 'databasequery');
			}

			$this->timings[] = microtime(true);

			if (is_object($this->cursor))
			{
				// Avoid warning if result already freed by third-party library
				@$this->freeResult();
			}

			$memoryBefore = memory_get_usage();
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysqli_query($this->connection, $query);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}

			$this->callStacks[count($this->callStacks) - 1][0]['memory'] = array(
				$memoryBefore,
				memory_get_usage(),
				is_object($this->cursor) ? $this->getNumRows() : null
			);
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$this->errorNum = $this->getErrorNumber();
			$this->errorMsg = $this->getErrorMessage($query);

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage($query);

					if (class_exists('JLog'))
					{
						JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
					}

					throw new RuntimeException($this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				if (class_exists('JLog'))
				{
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
				}

				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by MySQL.
	 * @param   string  $prefix    Not used by MySQL.
	 *
	 * @return  FOFDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!mysqli_select_db($this->connection, $database))
		{
			throw new RuntimeException('Could not connect to database.');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		// If UTF is not supported return false immediately
		if (!$this->utf)
		{
			return false;
		}

		// Make sure we're connected to the server
		$this->connect();

		// Which charset should I use, plain utf8 or multibyte utf8mb4?
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		$result = @$this->connection->set_charset($charset);

		/**
		 * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise.
		 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
		 * masks the server version and reports only its own we can not be sure if the server actually does support
		 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
		 * catch the error and determine that utf8mb4 is not supported!
		 */
		if (!$result && $this->utf8mb4)
		{
			$this->utf8mb4 = false;
			$result = @$this->connection->set_charset('utf8');
		}

		return $result;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('START TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchArray($cursor = null)
	{
		return mysqli_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchAssoc($cursor = null)
	{
		return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function freeResult($cursor = null)
	{
		mysqli_free_result($cursor ? $cursor : $this->cursor);

		if ((! $cursor) || ($cursor === $this->cursor))
		{
			$this->cursor = null;
		}
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('UNLOCK TABLES')->execute();

		return $this;
	}

	/**
	 * Internal function to check if profiling is available
	 *
	 * @return  boolean
	 *
	 * @since   3.1.3
	 */
	private function hasProfiling()
	{
		try
		{
			$res = mysqli_query($this->connection, "SHOW VARIABLES LIKE 'have_profiling'");
			$row = mysqli_fetch_assoc($res);

			return isset($row);
		}
		catch (Exception $e)
		{
			return false;
		}
	}

	/**
	 * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * @return  boolean
	 *
	 * @since   CMS 3.5.0
	 */
	private function serverClaimsUtf8mb4Support()
	{
		$client_version = mysqli_get_client_info();

		if (strpos($client_version, 'mysqlnd') !== false)
		{
			$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

			return version_compare($client_version, '5.0.9', '>=');
		}
		else
		{
			return version_compare($client_version, '5.5.3', '>=');
		}
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) mysqli_errno($this->connection);
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @param   string  $query  The SQL Query that fails
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage($query)
	{
		$errorMessage = (string) mysqli_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
			$query        = str_replace($this->tablePrefix, '#__', $query);
		}

		return $errorMessage . ' SQL=' . $query;
	}
}
fof/database/driver/pdomysql.php000064400000032074152177723700012757 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * MySQL database driver supporting PDO based connections
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @see         http://php.net/manual/en/ref.pdo-mysql.php
 * @since       3.4
 */
class FOFDatabaseDriverPdomysql extends FOFDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.4
	 */
	public $name = 'pdomysql';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mysql';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $nameQuote = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * The minimum supported database version.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected static $dbMinimum = '5.0.4';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  Array of database options with keys: host, user, password, database, select.
	 *
	 * @since   3.4
	 */
	public function __construct($options)
	{
		/**
		 * Pre-populate the UTF-8 Multibyte compatibility flag. Unfortuantely PDO won't report the server version
		 * unless we're connected to it and we cannot connect to it unless we know if it supports utf8mb4 which requires
		 * us knowing the server version. Between this chicken and egg issue we _assume_ it's supported and we'll just
		 * catch any problems at connection time.
		 */
		$this->utf8mb4 = true;

		// Get some basic values from the options.
		$options['driver']  = 'mysql';
		$options['charset'] = (isset($options['charset'])) ? $options['charset'] : 'utf8';

		if ($this->utf8mb4 && ($options['charset'] == 'utf8'))
		{
			$options['charset'] = 'utf8mb4';
		}

		$this->charset = $options['charset'];

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		try
		{
			// Try to connect to MySQL
			parent::connect();
		}
		catch (\RuntimeException $e)
		{
			// If the connection failed but not because of the wrong character set bubble up the exception
			if (!$this->utf8mb4 || ($this->options['charset'] != 'utf8mb4'))
			{
				throw $e;
			}

			/**
			 * If the connection failed and I was trying to use the utf8mb4 charset then it is likely that the server
			 * doesn't support utf8mb4 despite claiming otherwise.
			 *
			 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
			 * masks the server version and reports only its own we can not be sure if the server actually does support
			 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
			 * catch the error and determine that utf8mb4 is not supported!
			 */
			$this->utf8mb4 = false;
			$this->options['charset'] = 'utf8';

			parent::connect();
		}

		$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.4
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers());
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$query->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName));

		$this->setQuery($query);

		$this->execute();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		$this->setQuery('USE ' . $this->quoteName($database));

		$this->execute();

		return $this;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database (string) or boolean false if not supported.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_database"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_connection"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		// Initialise variables.
		$result = array();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			$this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($table));

			$row = $this->loadRow();

			// Populate the result array based on the create statements.
			$result[$table] = $row[1];
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		// Set the query to get the table fields statement.
		$this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($table));

		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace("/[(0-9)]/", '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// Get the details columns information.
		$this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));

		$keys = $this->loadObjectList();

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SHOW TABLES');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.4
	 */
	public function getVersion()
	{
		$this->connect();

		return $this->getOption(PDO::ATTR_SERVER_VERSION);
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();

		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by MySQL.
	 * @param   string  $prefix    Not used by MySQL.
	 *
	 * @return  FOFDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME TABLE ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable));

		$this->execute();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * Oracle escaping reference:
	 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
	 *
	 * SQLite escaping notes:
	 * http://www.sqlite.org/faq.html#q14
	 *
	 * Method body is as implemented by the Zend Framework
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.4
	 */
	public function escape($text, $extra = false)
	{
		$this->connect();

		if (is_int($text) || is_float($text))
		{
			return $text;
		}

		$result = substr($this->connection->quote($text), 1, -1);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('UNLOCK TABLES')->execute();

		return $this;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			parent::transactionStart($asSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . $this->transactionDepth;
			$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth++;
			}
		}
	}
}
fof/database/driver/sqlite.php000064400000024400152177723700012402 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * SQLite database driver
 *
 * @see    http://php.net/pdo
 * @since  12.1
 */
class FOFDatabaseDriverSqlite extends FOFDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'sqlite';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'sqlite';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nameQuote = '`';

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   12.1
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));

		$this->execute();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQLite statement.
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text) || is_float($text))
		{
			return $text;
		}

		return SQLite3::escapeString($text);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   12.1
	 */
	public function getCollation()
	{
		return $this->charset;
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return $this->charset;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * Note: Doesn't appear to have support in SQLite
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		return $tables;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$columns = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);

		$query->setQuery('pragma table_info(' . $table . ')');

		$this->setQuery($query);
		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$columns[$field->NAME] = $field->TYPE;
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				// Do some dirty translation to MySQL output.
				// TODO: Come up with and implement a standard across databases.
				$columns[$field->NAME] = (object) array(
					'Field' => $field->NAME,
					'Type' => $field->TYPE,
					'Null' => ($field->NOTNULL == '1' ? 'NO' : 'YES'),
					'Default' => $field->DFLT_VALUE,
					'Key' => ($field->PK != '0' ? 'PRI' : '')
				);
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $columns;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		$keys = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);
		$query->setQuery('pragma table_info( ' . $table . ')');

		// $query->bind(':tableName', $table);

		$this->setQuery($query);
		$rows = $this->loadObjectList();

		foreach ($rows as $column)
		{
			if ($column->PK == 1)
			{
				$keys[$column->NAME] = $column;
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database (schema).
	 *
	 * @return  array   An array of all the tables in the database.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		$type = 'table';

		$query = $this->getQuery(true)
			->select('name')
			->from('sqlite_master')
			->where('type = :type')
			->bind(':type', $type)
			->order('name');

		$this->setQuery($query);

		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();

		$this->setQuery("SELECT sqlite_version()");

		return $this->loadResult();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * Returns false automatically for the Oracle driver since
	 * you can only set the character set when the connection
	 * is created.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		$this->connect();

		return false;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by Sqlite.
	 * @param   string  $prefix    Not used by Sqlite.
	 *
	 * @return  FOFDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		return $this;
	}

	/**
	 * Test to see if the PDO ODBC connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers());
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.3
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			parent::transactionStart($asSavepoint);
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}
}
fof/database/driver/postgresql.php000064400000112113152177723700013303 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * PostgreSQL database driver
 *
 * @since  12.1
 */
class FOFDatabaseDriverPostgresql extends FOFDatabaseDriver
{
	/**
	 * The database driver name
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'postgresql';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'postgresql';

	/**
	 * Quote for named objects
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nameQuote = '"';

	/**
	 * The null/zero date string
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nullDate = '1970-01-01 00:00:00';

	/**
	 * The minimum supported database version.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected static $dbMinimum = '8.3.18';

	/**
	 * Operator used for concatenation
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $concat_operator = '||';

	/**
	 * FOFDatabaseDriverPostgresqlQuery object returned by getQuery
	 *
	 * @var    FOFDatabaseDriverPostgresqlQuery
	 * @since  12.1
	 */
	protected $queryObject = null;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since	12.1
	 */
	public function __construct( $options )
	{
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';

		// Finalize initialization
		parent::__construct($options);
	}

	/**
	 * Database object destructor
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the postgresql extension for PHP is installed and enabled.
		if (!function_exists('pg_connect'))
		{
			throw new RuntimeException('PHP extension pg_connect is not available.');
		}

		// Build the DSN for the connection.
		$dsn = '';

		if (!empty($this->options['host']))
		{
			$dsn .= "host={$this->options['host']} ";
		}

		$dsn .= "dbname={$this->options['database']} user={$this->options['user']} password={$this->options['password']}";

		// Attempt to connect to the server.
		if (!($this->connection = @pg_connect($dsn)))
		{
			throw new RuntimeException('Error connecting to PGSQL database.');
		}

		pg_set_error_verbosity($this->connection, PGSQL_ERRORS_DEFAULT);
		pg_query('SET standard_conforming_strings=off');
		pg_query('SET escape_string_warning=off');
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			pg_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		$this->connect();

		$result = pg_escape_string($this->connection, $text);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the PostgreSQL connector is available
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function test()
	{
		return (function_exists('pg_connect'));
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return	boolean
	 *
	 * @since	12.1
	 */
	public function connected()
	{
		$this->connect();

		if (is_resource($this->connection))
		{
			return pg_ping($this->connection);
		}

		return false;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  boolean
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName));
		$this->execute();

		return true;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows in the previous operation
	 *
	 * @since   12.1
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return pg_affected_rows($this->cursor);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		$this->setQuery('SHOW LC_COLLATE');
		$array = $this->loadAssocList();

		return $array[0]['lc_collate'];
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return pg_client_encoding($this->connection);
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cur  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   12.1
	 */
	public function getNumRows($cur = null)
	{
		$this->connect();

		return pg_num_rows((int) $cur ? $cur : $this->cursor);
	}

	/**
	 * Get the current or query, or new FOFDatabaseQuery object.
	 *
	 * @param   boolean  $new    False to return the last query set, True to return a new FOFDatabaseQuery object.
	 * @param   boolean  $asObj  False to return last query as string, true to get FOFDatabaseQueryPostgresql object.
	 *
	 * @return  FOFDatabaseQuery  The current query object or a new object extending the FOFDatabaseQuery class.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getQuery($new = false, $asObj = false)
	{
		if ($new)
		{
			// Make sure we have a query class for this driver.
			if (!class_exists('FOFDatabaseQueryPostgresql'))
			{
				throw new RuntimeException('FOFDatabaseQueryPostgresql Class not found.');
			}

			$this->queryObject = new FOFDatabaseQueryPostgresql($this);

			return $this->queryObject;
		}
		else
		{
			if ($asObj)
			{
				return $this->queryObject;
			}
			else
			{
				return $this->sql;
			}
		}
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * This is unsuported by PostgreSQL.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  string  An empty char because this function is not supported by PostgreSQL.
	 *
	 * @since   12.1
	 */
	public function getTableCreate($tables)
	{
		return '';
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		$tableSub = $this->replacePrefix($table);

		$this->setQuery('
			SELECT a.attname AS "column_name",
				pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
				CASE WHEN a.attnotnull IS TRUE
					THEN \'NO\'
					ELSE \'YES\'
				END AS "null",
				CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT NULL
					THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
				END as "Default",
				CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
				THEN \'\'
				ELSE pg_catalog.col_description(a.attrelid, a.attnum)
				END  AS "comments"
			FROM pg_catalog.pg_attribute a
			LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum
			LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
			WHERE a.attrelid =
				(SELECT oid FROM pg_catalog.pg_class WHERE relname=' . $this->quote($tableSub) . '
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
					nspname = \'public\')
				)
			AND a.attnum > 0 AND NOT a.attisdropped
			ORDER BY a.attnum'
		);

		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->column_name] = preg_replace("/[(0-9)]/", '', $field->type);
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				if (stristr(strtolower($field->type), "character varying"))
				{
					$field->Default = "";
				}
				if (stristr(strtolower($field->type), "text"))
				{
					$field->Default = "";
				}
				// Do some dirty translation to MySQL output.
				// TODO: Come up with and implement a standard across databases.
				$result[$field->column_name] = (object) array(
					'column_name' => $field->column_name,
					'type' => $field->type,
					'null' => $field->null,
					'Default' => $field->Default,
					'comments' => '',
					'Field' => $field->column_name,
					'Type' => $field->type,
					'Null' => $field->null,
					// TODO: Improve query above to return primary key info as well
					// 'Key' => ($field->PK == '1' ? 'PRI' : '')
				);
			}
		}

		/* Change Postgresql's NULL::* type with PHP's null one */
		foreach ($fields as $field)
		{
			if (preg_match("/^NULL::*/", $field->Default))
			{
				$field->Default = null;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList))
		{
			// Get the details columns information.
			$this->setQuery('
				SELECT indexname AS "idxName", indisprimary AS "isPrimary", indisunique  AS "isUnique",
					CASE WHEN indisprimary = true THEN
						( SELECT \'ALTER TABLE \' || tablename || \' ADD \' || pg_catalog.pg_get_constraintdef(const.oid, true)
							FROM pg_constraint AS const WHERE const.conname= pgClassFirst.relname )
					ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
					END AS "Query"
				FROM pg_indexes
				LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
				LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
				WHERE tablename=' . $this->quote($table) . ' ORDER BY indkey'
			);

			$keys = $this->loadObjectList();

			return $keys;
		}

		return false;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type=' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ')')
			->order('table_name ASC');

		$this->setQuery($query);
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the details list of sequences for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of sequences specification for the table.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getTableSequences($table)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList))
		{
			$name = array(
				's.relname', 'n.nspname', 't.relname', 'a.attname', 'info.data_type', 'info.minimum_value', 'info.maximum_value',
				'info.increment', 'info.cycle_option'
			);
			$as = array('sequence', 'schema', 'table', 'column', 'data_type', 'minimum_value', 'maximum_value', 'increment', 'cycle_option');

			if (version_compare($this->getVersion(), '9.1.0') >= 0)
			{
				$name[] .= 'info.start_value';
				$as[] .= 'start_value';
			}

			// Get the details columns information.
			$query = $this->getQuery(true)
				->select($this->quoteName($name, $as))
				->from('pg_class AS s')
				->join('LEFT', "pg_depend d ON d.objid=s.oid AND d.classid='pg_class'::regclass AND d.refclassid='pg_class'::regclass")
				->join('LEFT', 'pg_class t ON t.oid=d.refobjid')
				->join('LEFT', 'pg_namespace n ON n.oid=t.relnamespace')
				->join('LEFT', 'pg_attribute a ON a.attrelid=t.oid AND a.attnum=d.refobjsubid')
				->join('LEFT', 'information_schema.sequences AS info ON info.sequence_name=s.relname')
				->where("s.relkind='S' AND d.deptype='a' AND t.relname=" . $this->quote($table));
			$this->setQuery($query);
			$seq = $this->loadObjectList();

			return $seq;
		}

		return false;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   12.1
	 */
	public function getVersion()
	{
		$this->connect();
		$version = pg_version($this->connection);

		return $version['server'];
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 * To be called after the INSERT statement, it's MANDATORY to have a sequence on
	 * every primary key table.
	 *
	 * To get the auto incremented value it's possible to call this function after
	 * INSERT INTO query, or use INSERT INTO with RETURNING clause.
	 *
	 * @example with insertid() call:
	 *		$query = $this->getQuery(true)
	 *			->insert('jos_dbtest')
	 *			->columns('title,start_date,description')
	 *			->values("'testTitle2nd','1971-01-01','testDescription2nd'");
	 *		$this->setQuery($query);
	 *		$this->execute();
	 *		$id = $this->insertid();
	 *
	 * @example with RETURNING clause:
	 *		$query = $this->getQuery(true)
	 *			->insert('jos_dbtest')
	 *			->columns('title,start_date,description')
	 *			->values("'testTitle2nd','1971-01-01','testDescription2nd'")
	 *			->returning('id');
	 *		$this->setQuery($query);
	 *		$id = $this->loadResult();
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   12.1
	 */
	public function insertid()
	{
		$this->connect();
		$insertQuery = $this->getQuery(false, true);
		$table = $insertQuery->__get('insert')->getElements();

		/* find sequence column name */
		$colNameQuery = $this->getQuery(true);
		$colNameQuery->select('column_default')
			->from('information_schema.columns')
			->where("table_name=" . $this->quote($this->replacePrefix(str_replace('"', '', $table[0]))), 'AND')
			->where("column_default LIKE '%nextval%'");

		$this->setQuery($colNameQuery);
		$colName = $this->loadRow();
		$changedColName = str_replace('nextval', 'currval', $colName);

		$insertidQuery = $this->getQuery(true);
		$insertidQuery->select($changedColName);
		$this->setQuery($insertidQuery);
		$insertVal = $this->loadRow();

		return $insertVal[0];
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function lockTable($tableName)
	{
		$this->transactionStart();
		$this->setQuery('LOCK TABLE ' . $this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE MODE')->execute();

		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		if (!is_resource($this->connection))
		{
			if (class_exists('JLog'))
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			}
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->limit . ' OFFSET ' . $this->offset;
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			if (class_exists('JLog'))
			{
				JLog::add($query, JLog::DEBUG, 'databasequery');
			}

			$this->timings[] = microtime(true);
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @pg_query($this->connection, $query);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage($query);

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage($query);

					// Throw the normal query exception.
					if (class_exists('JLog'))
					{
						JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
					}

					throw new RuntimeException($this->errorMsg, null, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				if (class_exists('JLog'))
				{
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
				}

				throw new RuntimeException($this->errorMsg);
			}
		}

		return $this->cursor;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by PostgreSQL.
	 * @param   string  $prefix    Not used by PostgreSQL.
	 *
	 * @return  FOFDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		// Origin Table does not exist
		if (!in_array($oldTable, $tableList))
		{
			// Origin Table not found
			throw new RuntimeException('Table not found in Postgresql database.');
		}
		else
		{
			/* Rename indexes */
			$this->setQuery(
				'SELECT relname
					FROM pg_class
					WHERE oid IN (
						SELECT indexrelid
						FROM pg_index, pg_class
						WHERE pg_class.relname=' . $this->quote($oldTable, true) . '
						AND pg_class.oid=pg_index.indrelid );'
			);

			$oldIndexes = $this->loadColumn();

			foreach ($oldIndexes as $oldIndex)
			{
				$changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
				$this->setQuery('ALTER INDEX ' . $this->escape($oldIndex) . ' RENAME TO ' . $this->escape($changedIdxName));
				$this->execute();
			}

			/* Rename sequence */
			$this->setQuery(
				'SELECT relname
					FROM pg_class
					WHERE relkind = \'S\'
					AND relnamespace IN (
						SELECT oid
						FROM pg_namespace
						WHERE nspname NOT LIKE \'pg_%\'
						AND nspname != \'information_schema\'
					)
					AND relname LIKE \'%' . $oldTable . '%\' ;'
			);

			$oldSequences = $this->loadColumn();

			foreach ($oldSequences as $oldSequence)
			{
				$changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
				$this->setQuery('ALTER SEQUENCE ' . $this->escape($oldSequence) . ' RENAME TO ' . $this->escape($changedSequenceName));
				$this->execute();
			}

			/* Rename table */
			$this->setQuery('ALTER TABLE ' . $this->escape($oldTable) . ' RENAME TO ' . $this->escape($newTable));
			$this->execute();
		}

		return true;
	}

	/**
	 * Selects the database, but redundant for PostgreSQL
	 *
	 * @param   string  $database  Database name to select.
	 *
	 * @return  boolean  Always true
	 *
	 * @since   12.1
	 */
	public function select($database)
	{
		return true;
	}

	/**
	 * Custom settings for UTF support
	 *
	 * @return  integer  Zero on success, -1 on failure
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		$this->connect();

		return pg_set_client_encoding($this->connection, 'UTF8');
	}

	/**
	 * This function return a field value as a prepared string to be used in a SQL statement.
	 *
	 * @param   array   $columns      Array of table's column returned by ::getTableColumns.
	 * @param   string  $field_name   The table field's name.
	 * @param   string  $field_value  The variable value to quote and return.
	 *
	 * @return  string  The quoted string.
	 *
	 * @since   12.1
	 */
	public function sqlValue($columns, $field_name, $field_value)
	{
		switch ($columns[$field_name])
		{
			case 'boolean':
				$val = 'NULL';

				if ($field_value == 't')
				{
					$val = 'TRUE';
				}
				elseif ($field_value == 'f')
				{
					$val = 'FALSE';
				}

				break;

			case 'bigint':
			case 'bigserial':
			case 'integer':
			case 'money':
			case 'numeric':
			case 'real':
			case 'smallint':
			case 'serial':
			case 'numeric,':
				$val = strlen($field_value) == 0 ? 'NULL' : $field_value;
				break;

			case 'date':
			case 'timestamp without time zone':
				if (empty($field_value))
				{
					$field_value = $this->getNullDate();
				}

				$val = $this->quote($field_value);
				break;

			default:
				$val = $this->quote($field_value);
				break;
		}

		return $val;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
			$this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($savepoint))->execute();
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('START TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchArray($cursor = null)
	{
		return pg_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchAssoc($cursor = null)
	{
		return pg_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return pg_fetch_object(is_null($cursor) ? $this->cursor : $cursor, null, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function freeResult($cursor = null)
	{
		pg_free_result($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$columns = $this->getTableColumns($table);

		$fields = array();
		$values = array();

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			// Ignore any internal fields or primary keys with value 0.
			if (($k[0] == "_") || ($k == $key && (($v === 0) || ($v === '0'))))
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->sqlValue($columns, $k, $v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true)
			->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		$retVal = false;

		if ($key)
		{
			$query->returning($key);

			// Set the query and execute the insert.
			$this->setQuery($query);

			$id = $this->loadResult();

			if ($id)
			{
				$object->$key = $id;
				$retVal = true;
			}
		}
		else
		{
			// Set the query and execute the insert.
			$this->setQuery($query);

			if ($this->execute())
			{
				$retVal = true;
			}
		}

		return $retVal;
	}

	/**
	 * Test to see if the PostgreSQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return (function_exists('pg_connect'));
	}

	/**
	 * Returns an array containing database's table list.
	 *
	 * @return  array  The database's table list.
	 *
	 * @since   12.1
	 */
	public function showTables()
	{
		$this->connect();

		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type = ' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ' )');

		$this->setQuery($query);
		$tableList = $this->loadColumn();

		return $tableList;
	}

	/**
	 * Get the substring position inside a string
	 *
	 * @param   string  $substring  The string being sought
	 * @param   string  $string     The string/column being searched
	 *
	 * @return  integer  The position of $substring in $string
	 *
	 * @since   12.1
	 */
	public function getStringPositionSql( $substring, $string )
	{
		$this->connect();

		$query = "SELECT POSITION( $substring IN $string )";
		$this->setQuery($query);
		$position = $this->loadRow();

		return $position['position'];
	}

	/**
	 * Generate a random value
	 *
	 * @return  float  The random generated number
	 *
	 * @since   12.1
	 */
	public function getRandom()
	{
		$this->connect();

		$this->setQuery('SELECT RANDOM()');
		$random = $this->loadAssoc();

		return $random['random'];
	}

	/**
	 * Get the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   12.1
	 */
	public function getAlterDbCharacterSet( $dbName )
	{
		$query = 'ALTER DATABASE ' . $this->quoteName($dbName) . ' SET CLIENT_ENCODING TO ' . $this->quote('UTF8');

		return $query;
	}

	/**
	 * Get the query string to create new Database in correct PostgreSQL syntax.
	 *
	 * @param   object   $options  object coming from "initialise" function to pass user and database name to database driver.
	 * @param   boolean  $utf      True if the database supports the UTF-8 character set, not used in PostgreSQL "CREATE DATABASE" query.
	 *
	 * @return  string	The query that creates database, owned by $options['user']
	 *
	 * @since   12.1
	 */
	public function getCreateDbQuery($options, $utf)
	{
		$query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' OWNER ' . $this->quoteName($options->db_user);

		if ($utf)
		{
			$query .= ' ENCODING ' . $this->quote('UTF-8');
		}

		return $query;
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   12.1
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$query = trim($query);

		if (strpos($query, '\''))
		{
			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'currval'))
			{
				$query = explode('currval', $query);

				for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('currval', $query);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'nextval'))
			{
				$query = explode('nextval', $query);

				for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('nextval', $query);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'setval'))
			{
				$query = explode('setval', $query);

				for ($nIndex = 1; $nIndex < count($query); $nIndex = $nIndex + 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('setval', $query);
			}

			$explodedQuery = explode('\'', $query);

			for ($nIndex = 0; $nIndex < count($explodedQuery); $nIndex = $nIndex + 2)
			{
				if (strpos($explodedQuery[$nIndex], $prefix))
				{
					$explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix, $explodedQuery[$nIndex]);
				}
			}

			$replacedQuery = implode('\'', $explodedQuery);
		}
		else
		{
			$replacedQuery = str_replace($prefix, $this->tablePrefix, $query);
		}

		return $replacedQuery;
	}

	/**
	 * Method to release a savepoint.
	 *
	 * @param   string  $savepointName  Savepoint's name to release
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function releaseTransactionSavepoint( $savepointName )
	{
		$this->connect();
		$this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
		$this->execute();
	}

	/**
	 * Method to create a savepoint.
	 *
	 * @param   string  $savepointName  Savepoint's name to create
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function transactionSavepoint( $savepointName )
	{
		$this->connect();
		$this->setQuery('SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
		$this->execute();
	}

	/**
	 * Unlocks tables in the database, this command does not exist in PostgreSQL,
	 * it is automatically done on commit or rollback.
	 *
	 * @return  FOFDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->transactionCommit();

		return $this;
	}

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string   $table    The name of the database table to update.
	 * @param   object   &$object  A reference to an object whose public properties match the table fields.
	 * @param   array    $key      The name of the primary key.
	 * @param   boolean  $nulls    True to update null fields or false to ignore them.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$columns = $this->getTableColumns($table);
		$fields  = array();
		$where   = array();

		if (is_string($key))
		{
			$key = array($key);
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process scalars that are not internal fields.
			if (is_array($v) or is_object($v) or $k[0] == '_')
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key))
			{
				$key_val = $this->sqlValue($columns, $k, $v);
				$where[] = $this->quoteName($k) . '=' . $key_val;
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we want to update nulls then set it.
				if ($nulls)
				{
					$val = 'NULL';
				}
				// If the value is null and we do not want to update nulls then ignore this field.
				else
				{
					continue;
				}
			}
			// The field is not null so we prep it for update.
			else
			{
				$val = $this->sqlValue($columns, $k, $v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where)));

		return $this->execute();
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) pg_result_error_field($this->cursor, PGSQL_DIAG_SQLSTATE) . ' ';
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @param   string  $query  The SQL Query that fails
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage($query)
	{
		$errorMessage = (string) pg_last_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
			$query        = str_replace($this->tablePrefix, '#__', $query);
		}

		return $errorMessage . "SQL=" . $query;
	}
}
fof/database/driver/joomla.php000064400000036213152177723700012367 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * This crazy three line bit is required to convince Joomla! to load JDatabaseInterface which is on the same file as the
 * abstract JDatabaseDriver class for reasons that beat me. It makes no sense. Furthermore, jimport on Joomla! 3.4
 * doesn't seem to actually load the file, merely registering the association in the autoloader. Hence the class_exists
 * in here.
 */
jimport('joomla.database.driver');
jimport('joomla.database.driver.mysqli');
class_exists('JDatabaseDriver', true);

/**
 * Joomla! pass-through database driver.
 */
class FOFDatabaseDriverJoomla extends FOFDatabase implements FOFDatabaseInterface
{
	/** @var FOFDatabase The real database connection object */
	private $dbo;

	/**
	 * @var    string  The character(s) used to quote SQL statement names such as table names or field names,
	 *                 etc.  The child classes should define this as necessary.  If a single character string the
	 *                 same character is used for both sides of the quoted name, else the first character will be
	 *                 used for the opening quote and the second for the closing quote.
	 * @since  11.1
	 */
	protected $nameQuote = '';

	/**
	 * Is this driver supported
	 *
	 * @since  11.2
	 */
	public static function isSupported()
	{
		return true;
	}

	/**
	 * Database object constructor
	 *
	 * @param    array $options List of options used to configure the connection
	 */
	public function __construct($options = array())
	{
		// Get best matching Akeeba Backup driver instance
		$this->dbo = JFactory::getDbo();

		$reflection = new ReflectionClass($this->dbo);

		try
		{
			$refProp = $reflection->getProperty('nameQuote');
			$refProp->setAccessible(true);
			$this->nameQuote = $refProp->getValue($this->dbo);
		}
		catch (Exception $e)
		{
			$this->nameQuote = '`';
		}
	}

	public function close()
	{
		if (method_exists($this->dbo, 'close'))
		{
			$this->dbo->close();
		}
		elseif (method_exists($this->dbo, 'disconnect'))
		{
			$this->dbo->disconnect();
		}
	}

	public function disconnect()
	{
		$this->close();
	}

	public function open()
	{
		if (method_exists($this->dbo, 'open'))
		{
			$this->dbo->open();
		}
		elseif (method_exists($this->dbo, 'connect'))
		{
			$this->dbo->connect();
		}
	}

	public function connect()
	{
		$this->open();
	}

	public function connected()
	{
		if (method_exists($this->dbo, 'connected'))
		{
			return $this->dbo->connected();
		}

		return true;
	}

	public function escape($text, $extra = false)
	{
		return $this->dbo->escape($text, $extra);
	}

	public function execute()
	{
		if (method_exists($this->dbo, 'execute'))
		{
			return $this->dbo->execute();
		}

		return $this->dbo->query();
	}

	public function getAffectedRows()
	{
		if (method_exists($this->dbo, 'getAffectedRows'))
		{
			return $this->dbo->getAffectedRows();
		}

		return 0;
	}

	public function getCollation()
	{
		if (method_exists($this->dbo, 'getCollation'))
		{
			return $this->dbo->getCollation();
		}

		return 'utf8_general_ci';
	}

	public function getConnection()
	{
		if (method_exists($this->dbo, 'getConnection'))
		{
			return $this->dbo->getConnection();
		}

		return null;
	}

	public function getCount()
	{
		if (method_exists($this->dbo, 'getCount'))
		{
			return $this->dbo->getCount();
		}

		return 0;
	}

	public function getDateFormat()
	{
		if (method_exists($this->dbo, 'getDateFormat'))
		{
			return $this->dbo->getDateFormat();
		}

		return 'Y-m-d H:i:s';;
	}

	public function getMinimum()
	{
		if (method_exists($this->dbo, 'getMinimum'))
		{
			return $this->dbo->getMinimum();
		}

		return '5.0.40';
	}

	public function getNullDate()
	{
		if (method_exists($this->dbo, 'getNullDate'))
		{
			return $this->dbo->getNullDate();
		}

		return '0000-00-00 00:00:00';
	}

	public function getNumRows($cursor = null)
	{
		if (method_exists($this->dbo, 'getNumRows'))
		{
			return $this->dbo->getNumRows($cursor);
		}

		return 0;
	}

	public function getQuery($new = false)
	{
		if (method_exists($this->dbo, 'getQuery'))
		{
			return $this->dbo->getQuery($new);
		}

		return null;
	}

	public function getTableColumns($table, $typeOnly = true)
	{
		if (method_exists($this->dbo, 'getTableColumns'))
		{
			return $this->dbo->getTableColumns($table, $typeOnly);
		}

		$result = $this->dbo->getTableFields(array($table), $typeOnly);

		return $result[$table];
	}

	public function getTableKeys($tables)
	{
		if (method_exists($this->dbo, 'getTableKeys'))
		{
			return $this->dbo->getTableKeys($tables);
		}

		return array();
	}

	public function getTableList()
	{
		if (method_exists($this->dbo, 'getTableList'))
		{
			return $this->dbo->getTableList();
		}

		return array();
	}

	public function getVersion()
	{
		if (method_exists($this->dbo, 'getVersion'))
		{
			return $this->dbo->getVersion();
		}

		return '5.0.40';
	}

	public function insertid()
	{
		if (method_exists($this->dbo, 'insertid'))
		{
			return $this->dbo->insertid();
		}

		return null;
	}

	public function insertObject($table, &$object, $key = null)
	{
		if (method_exists($this->dbo, 'insertObject'))
		{
			return $this->dbo->insertObject($table, $object, $key);
		}

		return null;
	}

	public function loadAssoc()
	{
		if (method_exists($this->dbo, 'loadAssoc'))
		{
			return $this->dbo->loadAssoc();
		}

		return null;
	}

	public function loadAssocList($key = null, $column = null)
	{
		if (method_exists($this->dbo, 'loadAssocList'))
		{
			return $this->dbo->loadAssocList($key, $column);
		}

		return null;
	}

	public function loadObject($class = 'stdClass')
	{
		if (method_exists($this->dbo, 'loadObject'))
		{
			return $this->dbo->loadObject($class);
		}

		return null;
	}

	public function loadObjectList($key = '', $class = 'stdClass')
	{
		if (method_exists($this->dbo, 'loadObjectList'))
		{
			return $this->dbo->loadObjectList($key, $class);
		}

		return null;
	}

	public function loadResult()
	{
		if (method_exists($this->dbo, 'loadResult'))
		{
			return $this->dbo->loadResult();
		}

		return null;
	}

	public function loadRow()
	{
		if (method_exists($this->dbo, 'loadRow'))
		{
			return $this->dbo->loadRow();
		}

		return null;
	}

	public function loadRowList($key = null)
	{
		if (method_exists($this->dbo, 'loadRowList'))
		{
			return $this->dbo->loadRowList($key);
		}

		return null;
	}

	public function lockTable($tableName)
	{
		if (method_exists($this->dbo, 'lockTable'))
		{
			return $this->dbo->lockTable($this);
		}

		return $this;
	}

	public function quote($text, $escape = true)
	{
		if (method_exists($this->dbo, 'quote'))
		{
			return $this->dbo->quote($text, $escape);
		}

		return $text;
	}

	public function select($database)
	{
		if (method_exists($this->dbo, 'select'))
		{
			return $this->dbo->select($database);
		}

		return false;
	}

	public function setQuery($query, $offset = 0, $limit = 0)
	{
		if (method_exists($this->dbo, 'setQuery'))
		{
			return $this->dbo->setQuery($query, $offset, $limit);
		}

		return false;
	}

	public function transactionCommit($toSavepoint = false)
	{
		if (method_exists($this->dbo, 'transactionCommit'))
		{
			$this->dbo->transactionCommit($toSavepoint);
		}
	}

	public function transactionRollback($toSavepoint = false)
	{
		if (method_exists($this->dbo, 'transactionRollback'))
		{
			$this->dbo->transactionRollback($toSavepoint);
		}
	}

	public function transactionStart($asSavepoint = false)
	{
		if (method_exists($this->dbo, 'transactionStart'))
		{
			$this->dbo->transactionStart($asSavepoint);
		}
	}

	public function unlockTables()
	{
		if (method_exists($this->dbo, 'unlockTables'))
		{
			return $this->dbo->unlockTables();
		}

		return $this;
	}

	public function updateObject($table, &$object, $key, $nulls = false)
	{
		if (method_exists($this->dbo, 'updateObject'))
		{
			return $this->dbo->updateObject($table, $object, $key, $nulls);
		}

		return false;
	}

	public function getLog()
	{
		if (method_exists($this->dbo, 'getLog'))
		{
			return $this->dbo->getLog();
		}

		return array();
	}

	public function dropTable($table, $ifExists = true)
	{
		if (method_exists($this->dbo, 'dropTable'))
		{
			return $this->dbo->dropTable($table, $ifExists);
		}

		return $this;
	}

	public function getTableCreate($tables)
	{
		if (method_exists($this->dbo, 'getTableCreate'))
		{
			return $this->dbo->getTableCreate($tables);
		}

		return array();
	}

	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		if (method_exists($this->dbo, 'renameTable'))
		{
			return $this->dbo->renameTable($oldTable, $newTable, $backup, $prefix);
		}

		return $this;
	}

	public function setUtf()
	{
		if (method_exists($this->dbo, 'setUtf'))
		{
			return $this->dbo->setUtf();
		}

		return false;
	}


	protected function freeResult($cursor = null)
	{
		return false;
	}

	/**
	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
	 * the database query.
	 *
	 * @param   integer  $offset  The row offset to use to build the result array.
	 *
	 * @return  mixed    The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadColumn($offset = 0)
	{
		if (method_exists($this->dbo, 'loadColumn'))
		{
			return $this->dbo->loadColumn($offset);
		}

		return $this->dbo->loadResultArray($offset);
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
	 *                        same length of $name; if is null there will not be any AS part for string or array element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @since   11.1
	 */
	public function quoteName($name, $as = null)
	{
		if (is_string($name))
		{
			$quotedName = $this->quoteNameStr(explode('.', $name));

			$quotedAs = '';

			if (!is_null($as))
			{
				settype($as, 'array');
				$quotedAs .= ' AS ' . $this->quoteNameStr($as);
			}

			return $quotedName . $quotedAs;
		}
		else
		{
			$fin = array();

			if (is_null($as))
			{
				foreach ($name as $str)
				{
					$fin[] = $this->quoteName($str);
				}
			}
			elseif (is_array($name) && (count($name) == count($as)))
			{
				$count = count($name);

				for ($i = 0; $i < $count; $i++)
				{
					$fin[] = $this->quoteName($name[$i], $as[$i]);
				}
			}

			return $fin;
		}
	}

	/**
	 * Quote strings coming from quoteName call.
	 *
	 * @param   array  $strArr  Array of strings coming from quoteName dot-explosion.
	 *
	 * @return  string  Dot-imploded string of quoted parts.
	 *
	 * @since 11.3
	 */
	protected function quoteNameStr($strArr)
	{
		$parts = array();
		$q = $this->nameQuote;

		foreach ($strArr as $part)
		{
			if (is_null($part))
			{
				continue;
			}

			if (strlen($q) == 1)
			{
				$parts[] = $q . $part . $q;
			}
			else
			{
				$parts[] = $q{0} . $part . $q{1};
			}
		}

		return implode('.', $parts);
	}

	/**
	 * Gets the error message from the database connection.
	 *
	 * @param   boolean  $escaped  True to escape the message string for use in JavaScript.
	 *
	 * @return  string  The error message for the most recent query.
	 *
	 * @since   11.1
	 */
	public function getErrorMsg($escaped = false)
	{
		if (method_exists($this->dbo, 'getErrorMsg'))
		{
			$errorMessage = $this->dbo->getErrorMsg();
		}
		else
		{
			$errorMessage = $this->errorMsg;
		}

		if ($escaped)
		{
			return addslashes($errorMessage);
		}

		return $errorMessage;
	}

	/**
	 * Gets the error number from the database connection.
	 *
	 * @return      integer  The error number for the most recent query.
	 *
	 * @since       11.1
	 * @deprecated  13.3 (Platform) & 4.0 (CMS)
	 */
	public function getErrorNum()
	{
		if (method_exists($this->dbo, 'getErrorNum'))
		{
			$errorNum = $this->dbo->getErrorNum();
		}
		else
		{
			$errorNum = $this->getErrorNum;
		}

		return $errorNum;
	}

	/**
	 * Return the most recent error message for the database connector.
	 *
	 * @param   boolean  $showSQL  True to display the SQL statement sent to the database as well as the error.
	 *
	 * @return  string  The error message for the most recent query.
	 */
	public function stderr($showSQL = false)
	{
		if (method_exists($this->dbo, 'stderr'))
		{
			return $this->dbo->stderr($showSQL);
		}

		return parent::stderr($showSQL);
	}

	/**
	 * Magic method to proxy all calls to the loaded database driver object
	 */
	public function __call($name, array $arguments)
	{
		if (is_null($this->dbo))
		{
			throw new Exception('FOF database driver is not loaded');
		}

		if (method_exists($this->dbo, $name) || in_array($name, array('q', 'nq', 'qn', 'query')))
		{
			switch ($name)
			{
				case 'execute':
					$name = 'query';
					break;

				case 'q':
					$name = 'quote';
					break;

				case 'qn':
				case 'nq':
					switch (count($arguments))
					{
						case 0 :
							$result = $this->quoteName();
							break;
						case 1 :
							$result = $this->quoteName($arguments[0]);
							break;
						case 2:
						default:
							$result = $this->quoteName($arguments[0], $arguments[1]);
							break;
					}
					return $result;

					break;
			}

			switch (count($arguments))
			{
				case 0 :
					$result = $this->dbo->$name();
					break;
				case 1 :
					$result = $this->dbo->$name($arguments[0]);
					break;
				case 2:
					$result = $this->dbo->$name($arguments[0], $arguments[1]);
					break;
				case 3:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2]);
					break;
				case 4:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
					break;
				case 5:
					$result = $this->dbo->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
					break;
				default:
					// Resort to using call_user_func_array for many segments
					$result = call_user_func_array(array($this->dbo, $name), $arguments);
			}

			if (class_exists('JDatabase') && is_object($result) && ($result instanceof JDatabase))
			{
				return $this;
			}

			return $result;
		}
		else
		{
			throw new \Exception('Method ' . $name . ' not found in FOFDatabase');
		}
	}

	public function __get($name)
	{
		if (isset($this->dbo->$name) || property_exists($this->dbo, $name))
		{
			return $this->dbo->$name;
		}
		else
		{
			$this->dbo->$name = null;
			user_error('Database driver does not support property ' . $name);
		}
	}

	public function __set($name, $value)
	{
		if (isset($this->dbo->name) || property_exists($this->dbo, $name))
		{
			$this->dbo->$name = $value;
		}
		else
		{
			$this->dbo->$name = null;
			user_error('Database driver not support property ' . $name);
		}
	}
}
fof/database/driver/pdo.php000064400000064717152177723700011702 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Joomla Platform PDO Database Driver Class
 *
 * @see    http://php.net/pdo
 * @since  12.1
 */
abstract class FOFDatabaseDriverPdo extends FOFDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  12.1
	 */
	public $name = 'pdo';

	/**
	 * @var    PDO  The database connection resource.
	 * @since  12.1
	 */
	protected $connection;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nameQuote = "'";

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  12.1
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * @var    resource  The prepared statement.
	 * @since  12.1
	 */
	protected $prepared;

	/**
	 * Contains the current query execution status
	 *
	 * @var array
	 * @since 12.1
	 */
	protected $executed = false;

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   12.1
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['driver'] = (isset($options['driver'])) ? $options['driver'] : 'odbc';
		$options['dsn'] = (isset($options['dsn'])) ? $options['dsn'] : '';
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['driverOptions'] = (isset($options['driverOptions'])) ? $options['driverOptions'] : array();

		$hostParts = explode(':', $options['host']);

		if (!empty($hostParts[1]))
		{
			$options['host'] = $hostParts[0];
			$options['port'] = $hostParts[1];
		}

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Destructor.
	 *
	 * @since   12.1
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the PDO extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new RuntimeException('PDO Extension is not available.', 1);
		}

		$replace = array();
		$with = array();

		// Find the correct PDO DSN Format to use:
		switch ($this->options['driver'])
		{
			case 'cubrid':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 33000;

				$format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'dblib':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'firebird':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3050;

				$format = 'firebird:dbname=#DBNAME#';

				$replace = array('#DBNAME#');
				$with = array($this->options['database']);

				break;

			case 'ibm':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 56789;

				if (!empty($this->options['dsn']))
				{
					$format = 'ibm:DSN=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database']);
				}

				break;

			case 'informix':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1526;
				$this->options['protocol'] = (isset($this->options['protocol'])) ? $this->options['protocol'] : 'onsoctcp';

				if (!empty($this->options['dsn']))
				{
					$format = 'informix:DSN=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['server'], $this->options['protocol']);
				}

				break;

			case 'mssql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			// The pdomysql case is a special case within the CMS environment
			case 'pdomysql':
			case 'mysql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3306;

				$format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#', '#CHARSET#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['charset']);

				break;

			case 'oci':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1521;
				$this->options['charset'] = (isset($this->options['charset'])) ? $this->options['charset'] : 'AL32UTF8';

				if (!empty($this->options['dsn']))
				{
					$format = 'oci:dbname=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database']);
				}

				$format .= ';charset=' . $this->options['charset'];

				break;

			case 'odbc':
				$format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#';

				$replace = array('#DSN#', '#USER#', '#PASSWORD#');
				$with = array($this->options['dsn'], $this->options['user'], $this->options['password']);

				break;

			case 'pgsql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 5432;

				$format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'sqlite':
				if (isset($this->options['version']) && $this->options['version'] == 2)
				{
					$format = 'sqlite2:#DBNAME#';
				}
				else
				{
					$format = 'sqlite:#DBNAME#';
				}

				$replace = array('#DBNAME#');
				$with = array($this->options['database']);

				break;

			case 'sybase':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;
		}

		// Create the connection string:
		$connectionString = str_replace($replace, $with, $format);

		try
		{
			$this->connection = new PDO(
				$connectionString,
				$this->options['user'],
				$this->options['password'],
				$this->options['driverOptions']
			);
		}
		catch (PDOException $e)
		{
			throw new RuntimeException('Could not connect to PDO: ' . $e->getMessage(), 2, $e);
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public function disconnect()
	{
		foreach ($this->disconnectHandlers as $h)
		{
			call_user_func_array($h, array( &$this));
		}

		$this->freeResult();
		unset($this->connection);
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * Oracle escaping reference:
	 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
	 *
	 * SQLite escaping notes:
	 * http://www.sqlite.org/faq.html#q14
	 *
	 * Method body is as implemented by the Zend Framework
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   12.1
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text) || is_float($text))
		{
			return $text;
		}

		$text = str_replace("'", "''", $text);

		return addcslashes($text, "\000\n\r\\\032");
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 * @throws  Exception
	 */
	public function execute()
	{
		$this->connect();

		if (!is_object($this->connection))
		{
			if (class_exists('JLog'))
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			}
			throw new RuntimeException($this->errorMsg, $this->errorNum);
		}

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof FOFDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			// @TODO
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			if (class_exists('JLog'))
			{
				JLog::add($query, JLog::DEBUG, 'databasequery');
			}

			$this->timings[] = microtime(true);
		}

		// Execute the query.
		$this->executed = false;

		if ($this->prepared instanceof PDOStatement)
		{
			// Bind the variables:
			if ($this->sql instanceof FOFDatabaseQueryPreparable)
			{
				$bounded = $this->sql->getBounded();

				foreach ($bounded as $key => $obj)
				{
					$this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
				}
			}

			$this->executed = $this->prepared->execute();
		}

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->executed)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage($query);

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage($query);

					// Throw the normal query exception.
					if (class_exists('JLog'))
					{
						JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
					}

					throw new RuntimeException($this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				if (class_exists('JLog'))
				{
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');
				}

				throw new RuntimeException($this->errorMsg, $this->errorNum);
			}
		}

		return $this->prepared;
	}

	/**
	 * Retrieve a PDO database connection attribute
	 * http://www.php.net/manual/en/pdo.getattribute.php
	 *
	 * Usage: $db->getOption(PDO::ATTR_CASE);
	 *
	 * @param   mixed  $key  One of the PDO::ATTR_* Constants
	 *
	 * @return mixed
	 *
	 * @since  12.1
	 */
	public function getOption($key)
	{
		$this->connect();

		return $this->connection->getAttribute($key);
	}

	/**
	 * Get a query to run and verify the database is operational.
	 *
	 * @return  string  The query to check the health of the DB.
	 *
	 * @since   12.2
	 */
	public function getConnectedQuery()
	{
		return 'SELECT 1';
	}

	/**
	 * Sets an attribute on the PDO database handle.
	 * http://www.php.net/manual/en/pdo.setattribute.php
	 *
	 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
	 *
	 * @param   integer  $key    One of the PDO::ATTR_* Constants
	 * @param   mixed    $value  One of the associated PDO Constants
	 *                           related to the particular attribute
	 *                           key.
	 *
	 * @return boolean
	 *
	 * @since  12.1
	 */
	public function setOption($key, $value)
	{
		$this->connect();

		return $this->connection->setAttribute($key, $value);
	}

	/**
	 * Test to see if the PDO extension is available.
	 * Override as needed to check for specific PDO Drivers.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   12.1
	 */
	public static function isSupported()
	{
		return defined('PDO::ATTR_DRIVER_NAME');
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   12.1
	 */
	public function connected()
	{
		// Flag to prevent recursion into this function.
		static $checkingConnected = false;

		if ($checkingConnected)
		{
			// Reset this flag and throw an exception.
			$checkingConnected = true;
			die('Recursion trying to check if connected.');
		}

		// Backup the query state.
		$query = $this->sql;
		$limit = $this->limit;
		$offset = $this->offset;
		$prepared = $this->prepared;

		try
		{
			// Set the checking connection flag.
			$checkingConnected = true;

			// Run a simple query to check the connection.
			$this->setQuery($this->getConnectedQuery());
			$status = (bool) $this->loadResult();
		}
		// If we catch an exception here, we must not be connected.
		catch (Exception $e)
		{
			$status = false;
		}

		// Restore the query state.
		$this->sql = $query;
		$this->limit = $limit;
		$this->offset = $offset;
		$this->prepared = $prepared;
		$checkingConnected = false;

		return $status;
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 * Only applicable for DELETE, INSERT, or UPDATE statements.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   12.1
	 */
	public function getAffectedRows()
	{
		$this->connect();

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * Only applicable for DELETE, INSERT, or UPDATE statements.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   12.1
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		if ($cursor instanceof PDOStatement)
		{
			return $cursor->rowCount();
		}
		elseif ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  string  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   12.1
	 */
	public function insertid()
	{
		$this->connect();

		// Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
		return @$this->connection->lastInsertId();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query          The SQL statement to set either as a FOFDatabaseQuery object or a string.
	 * @param   integer  $offset         The affected row offset to set.
	 * @param   integer  $limit          The maximum affected rows to set.
	 * @param   array    $driverOptions  The optional PDO driver options.
	 *
	 * @return  FOFDatabaseDriver  This object to support method chaining.
	 *
	 * @since   12.1
	 */
	public function setQuery($query, $offset = null, $limit = null, $driverOptions = array())
	{
		$this->connect();

		$this->freeResult();

		if (is_string($query))
		{
			// Allows taking advantage of bound variables in a direct query:
			$query = $this->getQuery(true)->setQuery($query);
		}

		if ($query instanceof FOFDatabaseQueryLimitable && !is_null($offset) && !is_null($limit))
		{
			$query = $query->processLimit($query, $limit, $offset);
		}

		// Create a stringified version of the query (with prefixes replaced):
		$sql = $this->replacePrefix((string) $query);

		// Use the stringified version in the prepare call:
		$this->prepared = $this->connection->prepare($sql, $driverOptions);

		// Store reference to the original FOFDatabaseQuery instance within the class.
		// This is important since binding variables depends on it within execute():
		parent::setQuery($query, $offset, $limit);

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   12.1
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth == 1)
		{
			$this->connection->commit();
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth == 1)
		{
			$this->connection->rollBack();
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			$this->connection->beginTransaction();
		}

		$this->transactionDepth++;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchArray($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_NUM);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_NUM);
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchAssoc($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_ASSOC);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_ASSOC);
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   Unused, only necessary so method signature will be the same as parent.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   12.1
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetchObject($class);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetchObject($class);
		}
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	protected function freeResult($cursor = null)
	{
		$this->executed = false;

		if ($cursor instanceof PDOStatement)
		{
			$cursor->closeCursor();
			$cursor = null;
		}

		if ($this->prepared instanceof PDOStatement)
		{
			$this->prepared->closeCursor();
			$this->prepared = null;
		}
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use getIterator() instead
	 */
	public function loadNextObject($class = 'stdClass')
	{
		if (class_exists('JLog'))
		{
			JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		}
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject(null, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function loadNextAssoc()
	{
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchAssoc())
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use getIterator() instead
	 */
	public function loadNextRow()
	{
		if (class_exists('JLog'))
		{
			JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		}
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray())
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * PDO does not support serialize
	 *
	 * @return  array
	 *
	 * @since   12.3
	 */
	public function __sleep()
	{
		$serializedProperties = array();

		$reflect = new ReflectionClass($this);

		// Get properties of the current class
		$properties = $reflect->getProperties();

		foreach ($properties as $property)
		{
			// Do not serialize properties that are PDO
			if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO))
			{
				array_push($serializedProperties, $property->name);
			}
		}

		return $serializedProperties;
	}

	/**
	 * Wake up after serialization
	 *
	 * @return  array
	 *
	 * @since   12.3
	 */
	public function __wakeup()
	{
		// Get connection back
		$this->__construct($this->options);
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) $this->connection->errorCode();
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @param   string  $query  The SQL Query that fails
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage($query)
	{
		// Note we ignoring $query here as it not used in the original code.

		// The SQL Error Information
		$errorInfo = implode(", ", $this->connection->errorInfo());

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorInfo = str_replace($this->tablePrefix, '#__', $errorInfo);
		}

		return 'SQL: ' . $errorInfo;
	}
}
fof/database/iterator.php000064400000012440152177723700011440 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Database iterator
 */
abstract class FOFDatabaseIterator implements Iterator
{
	/**
	 * The database cursor.
	 *
	 * @var    mixed
	 */
	protected $cursor;

	/**
	 * The class of object to create.
	 *
	 * @var    string
	 */
	protected $class;

	/**
	 * The name of the column to use for the key of the database record.
	 *
	 * @var    mixed
	 */
	private $_column;

	/**
	 * The current database record.
	 *
	 * @var    mixed
	 */
	private $_current;

	/**
	 * The current database record as a FOFTable object.
	 *
	 * @var    FOFTable
	 */
	private $_currentTable;

	/**
	 * A numeric or string key for the current database record.
	 *
	 * @var    scalar
	 */
	private $_key;

	/**
	 * The number of fetched records.
	 *
	 * @var    integer
	 */
	private $_fetched = 0;

	/**
	 * A FOFTable object created using the class type $class, used by getTable
	 *
	 * @var   FOFTable
	 */
	private $_tableObject = null;

	/**
	 * Returns an iterator object for a specific database type
	 *
	 * @param   string  $dbName  The database type, e.g. mysql, mysqli, sqlazure etc.
	 * @param   mixed   $cursor  The database cursor
	 * @param   string  $column  An option column to use as the iterator key
	 * @param   string  $class   The table class of the returned objects
	 * @param   array   $config  Configuration parameters to push to the table class
	 *
	 * @return  FOFDatabaseIterator
	 *
	 * @throws  InvalidArgumentException
	 */
	public static function &getIterator($dbName, $cursor, $column = null, $class, $config = array())
	{
		$className = 'FOFDatabaseIterator' . ucfirst($dbName);

		$object = new $className($cursor, $column, $class, $config);

		return $object;
	}

	/**
	 * Database iterator constructor.
	 *
	 * @param   mixed   $cursor  The database cursor.
	 * @param   string  $column  An option column to use as the iterator key.
	 * @param   string  $class   The table class of the returned objects.
	 * @param   array   $config  Configuration parameters to push to the table class
	 *
	 * @throws  InvalidArgumentException
	 */
	public function __construct($cursor, $column = null, $class, $config = array())
	{
		// Figure out the type and prefix of the class by the class name
		$parts = FOFInflector::explode($class);

        if(count($parts) != 3)
        {
            throw new InvalidArgumentException('Invalid table name, expected a pattern like ComponentTableFoobar got '.$class);
        }

		$this->_tableObject = FOFTable::getInstance($parts[2], ucfirst($parts[0]) . ucfirst($parts[1]))->getClone();

		$this->cursor   = $cursor;
		$this->class    = 'stdClass';
		$this->_column  = $column;
		$this->_fetched = 0;

		$this->next();
	}

	/**
	 * Database iterator destructor.
	 */
	public function __destruct()
	{
		if ($this->cursor)
		{
			$this->freeResult($this->cursor);
		}
	}

	/**
	 * The current element in the iterator.
	 *
	 * @return  object
	 *
	 * @see     Iterator::current()
	 */
	public function current()
	{
		return $this->_currentTable;
	}

	/**
	 * The key of the current element in the iterator.
	 *
	 * @return  scalar
	 *
	 * @see     Iterator::key()
	 */
	public function key()
	{
		return $this->_key;
	}

	/**
	 * Moves forward to the next result from the SQL query.
	 *
	 * @return  void
	 *
	 * @see     Iterator::next()
	 */
	public function next()
	{
		// Set the default key as being the number of fetched object
		$this->_key = $this->_fetched;

		// Try to get an object
		$this->_current = $this->fetchObject();

		// If an object has been found
		if ($this->_current)
		{
			$this->_currentTable = $this->getTable();

			// Set the key as being the indexed column (if it exists)
			if (isset($this->_current->{$this->_column}))
			{
				$this->_key = $this->_current->{$this->_column};
			}

			// Update the number of fetched object
			$this->_fetched++;
		}
	}

	/**
	 * Rewinds the iterator.
	 *
	 * This iterator cannot be rewound.
	 *
	 * @return  void
	 *
	 * @see     Iterator::rewind()
	 */
	public function rewind()
	{
	}

	/**
	 * Checks if the current position of the iterator is valid.
	 *
	 * @return  boolean
	 *
	 * @see     Iterator::valid()
	 */
	public function valid()
	{
		return (boolean) $this->_current;
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 */
	abstract protected function fetchObject();

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 */
	abstract protected function freeResult();

	/**
	 * Returns the data in $this->_current as a FOFTable instance
	 *
	 * @return  FOFTable
	 *
	 * @throws  OutOfBoundsException
	 */
	protected function getTable()
	{
		if (!$this->valid())
		{
			throw new OutOfBoundsException('Cannot get item past iterator\'s bounds', 500);
		}

		$this->_tableObject->bind($this->_current);

		return $this->_tableObject;
	}
}
fof/database/factory.php000064400000007672152177723700011271 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Joomla Platform Database Factory class
 *
 * @since  12.1
 */
class FOFDatabaseFactory
{
	/**
	 * Contains the current FOFDatabaseFactory instance
	 *
	 * @var    FOFDatabaseFactory
	 * @since  12.1
	 */
	private static $_instance = null;

	/**
	 * Method to return a FOFDatabaseDriver instance based on the given options. There are three global options and then
	 * the rest are specific to the database driver. The 'database' option determines which database is to
	 * be used for the connection. The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   string  $name     Name of the database driver you'd like to instantiate
	 * @param   array   $options  Parameters to be passed to the database driver.
	 *
	 * @return  FOFDatabaseDriver  A database driver object.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getDriver($name = 'joomla', $options = array())
	{
		// Sanitize the database connector options.
		$options['driver']   = preg_replace('/[^A-Z0-9_\.-]/i', '', $name);
		$options['database'] = (isset($options['database'])) ? $options['database'] : null;
		$options['select']   = (isset($options['select'])) ? $options['select'] : true;

		// Derive the class name from the driver.
		$class = 'FOFDatabaseDriver' . ucfirst(strtolower($options['driver']));

		// If the class still doesn't exist we have nothing left to do but throw an exception.  We did our best.
		if (!class_exists($class))
		{
			throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));
		}

		// Create our new FOFDatabaseDriver connector based on the options given.
		try
		{
			$instance = new $class($options);
		}
		catch (RuntimeException $e)
		{
			throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
		}

		return $instance;
	}

	/**
	 * Gets an instance of the factory object.
	 *
	 * @return  FOFDatabaseFactory
	 *
	 * @since   12.1
	 */
	public static function getInstance()
	{
		return self::$_instance ? self::$_instance : new FOFDatabaseFactory;
	}

	/**
	 * Get the current query object or a new FOFDatabaseQuery object.
	 *
	 * @param   string           $name  Name of the driver you want an query object for.
	 * @param   FOFDatabaseDriver  $db    Optional FOFDatabaseDriver instance
	 *
	 * @return  FOFDatabaseQuery  The current query object or a new object extending the FOFDatabaseQuery class.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getQuery($name, FOFDatabaseDriver $db = null)
	{
		// Derive the class name from the driver.
		$class = 'FOFDatabaseQuery' . ucfirst(strtolower($name));

		// Make sure we have a query class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new RuntimeException('Database Query class not found');
		}

		return new $class($db);
	}

	/**
	 * Gets an instance of a factory object to return on subsequent calls of getInstance.
	 *
	 * @param   FOFDatabaseFactory  $instance  A FOFDatabaseFactory object.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	public static function setInstance(FOFDatabaseFactory $instance = null)
	{
		self::$_instance = $instance;
	}
}
fof/database/installer.php000064400000055700152177723700011612 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

class FOFDatabaseInstaller
{
	/** @var  FOFDatabase  The database connector object */
	private $db = null;

	/**
	 * @var   FOFInput  Input variables
	 */
	protected $input = array();

	/** @var  string  The directory where the XML schema files are stored */
	private $xmlDirectory = null;

	/** @var  array  A list of the base names of the XML schema files */
	public $xmlFiles = array('mysql', 'mysqli', 'pdomysql', 'postgresql', 'sqlsrv', 'mssql');

	/** @var array Internal cache for table list  */
	protected static $allTables = array();

	/**
	 * Public constructor
	 *
	 * @param   array  $config  The configuration array
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Set the database object
		if (array_key_exists('dbo', $config))
		{
			$this->db = $config['dbo'];
		}
		else
		{
			$this->db = FOFPlatform::getInstance()->getDbo();
		}

		// Set the $name/$_name variable
		$component = $this->input->getCmd('option', 'com_foobar');

		if (array_key_exists('option', $config))
		{
			$component = $config['option'];
		}

		// Figure out where the XML schema files are stored
		if (array_key_exists('dbinstaller_directory', $config))
		{
			$this->xmlDirectory = $config['dbinstaller_directory'];
		}
		else
		{
			// Nothing is defined, assume the files are stored in the sql/xml directory inside the component's administrator section
			$directories = FOFPlatform::getInstance()->getComponentBaseDirs($component);
			$this->setXmlDirectory($directories['admin'] . '/sql/xml');
		}

		// Do we have a set of XML files to look for?
		if (array_key_exists('dbinstaller_files', $config))
		{
			$files = $config['dbinstaller_files'];

			if (!is_array($files))
			{
				$files = explode(',', $files);
			}

			$this->xmlFiles = $files;
		}

	}

	/**
	 * Sets the directory where XML schema files are stored
	 *
	 * @param   string  $xmlDirectory
	 */
	public function setXmlDirectory($xmlDirectory)
	{
		$this->xmlDirectory = $xmlDirectory;
	}

	/**
	 * Returns the directory where XML schema files are stored
	 *
	 * @return  string
	 */
	public function getXmlDirectory()
	{
		return $this->xmlDirectory;
	}

	/**
	 * Creates or updates the database schema
	 *
	 * @return  void
	 *
	 * @throws  Exception  When a database query fails and it doesn't have the canfail flag
	 */
	public function updateSchema()
	{
		// Get the schema XML file
		$xml = $this->findSchemaXml();

		if (empty($xml))
		{
			return;
		}

		// Make sure there are SQL commands in this file
		if (!$xml->sql)
		{
			return;
		}

		// Walk the sql > action tags to find all tables
		/** @var SimpleXMLElement $actions */
		$actions = $xml->sql->children();

		/**
		 * The meta/autocollation node defines if I should automatically apply the correct collation (utf8 or utf8mb4)
		 * to the database tables managed by the schema updater. When enabled (default) the queries are automatically
		 * converted to the correct collation (utf8mb4_unicode_ci or utf8_general_ci) depending on whether your Joomla!
		 * and MySQL server support Multibyte UTF-8 (UTF8MB4). Moreover, if UTF8MB4 is supported, all CREATE TABLE
		 * queries are analyzed and the tables referenced in them are auto-converted to the proper utf8mb4 collation.
		 */
		$autoCollationConversion = true;

		if ($xml->meta->autocollation)
		{
			$value = (string) $xml->meta->autocollation;
			$value = trim($value);
			$value = strtolower($value);

			$autoCollationConversion = in_array($value, array('true', '1', 'on', 'yes'));
		}

		try
		{
			$hasUtf8mb4Support = $this->db->hasUTF8mb4Support();
		}
		catch (\Exception $e)
		{
			$hasUtf8mb4Support = false;
		}

		$tablesToConvert = array();

		/** @var SimpleXMLElement $action */
		foreach ($actions as $action)
		{
			// Get the attributes
			$attributes = $action->attributes();

			// Get the table / view name
			$table = $attributes->table ? (string)$attributes->table : '';

			if (empty($table))
			{
				continue;
			}

			// Am I allowed to let this action fail?
			$canFailAction = $attributes->canfail ? $attributes->canfail : 0;

			// Evaluate conditions
			$shouldExecute = true;

			/** @var SimpleXMLElement $node */
			foreach ($action->children() as $node)
			{
				if ($node->getName() == 'condition')
				{
					// Get the operator
					$operator = $node->attributes()->operator ? (string)$node->attributes()->operator : 'and';
					$operator = empty($operator) ? 'and' : $operator;

					$condition = $this->conditionMet($table, $node);

					switch ($operator)
					{
						case 'not':
							$shouldExecute = $shouldExecute && !$condition;
							break;

						case 'or':
							$shouldExecute = $shouldExecute || $condition;
							break;

						case 'nor':
							$shouldExecute = !$shouldExecute && !$condition;
							break;

						case 'xor':
							$shouldExecute = ($shouldExecute xor $condition);
							break;

						case 'maybe':
							$shouldExecute = $condition ? true : $shouldExecute;
							break;

						default:
							$shouldExecute = $shouldExecute && $condition;
							break;
					}
				}

				// DO NOT USE BOOLEAN SHORT CIRCUIT EVALUATION!
				// if (!$shouldExecute) break;
			}

			// Do I have to only collect the tables from CREATE TABLE queries?
			$onlyCollectTables = !$shouldExecute && $autoCollationConversion && $hasUtf8mb4Support;

			// Make sure all conditions are met OR I have to collect tables from CREATE TABLE queries.
			if (!$shouldExecute && !$onlyCollectTables)
			{
				continue;
			}

			// Execute queries
			foreach ($action->children() as $node)
			{
				if ($node->getName() == 'query')
				{
					$query = (string) $node;

					if ($autoCollationConversion && $hasUtf8mb4Support)
					{
						$this->extractTablesToConvert($query, $tablesToConvert);
					}

					// If we're only collecting tables do not run the queries
					if ($onlyCollectTables)
					{
						continue;
					}

					$canFail = $node->attributes->canfail ? (string)$node->attributes->canfail : $canFailAction;

					if (is_string($canFail))
					{
						$canFail = strtoupper($canFail);
					}

					$canFail = (in_array($canFail, array(true, 1, 'YES', 'TRUE')));

					// Do I need to automatically convert the collation of all CREATE / ALTER queries?
					if ($autoCollationConversion)
					{
						if ($hasUtf8mb4Support)
						{
							// We have UTF8MB4 support. Convert all queries to UTF8MB4.
							$query = $this->convertUtf8QueryToUtf8mb4($query);
						}
						else
						{
							// We do not have UTF8MB4 support. Convert all queries to plain old UTF8.
							$query = $this->convertUtf8mb4QueryToUtf8($query);
						}
					}

					$this->db->setQuery($query);

					try
					{
						$this->db->execute();
					}
					catch (Exception $e)
					{
						// If we are not allowed to fail, throw back the exception we caught
						if (!$canFail)
						{
							throw $e;
						}
					}
				}
			}
		}

		// Auto-convert the collation of tables if we are told to do so, have utf8mb4 support and a list of tables.
		if ($autoCollationConversion && $hasUtf8mb4Support && !empty($tablesToConvert))
		{
			$this->convertTablesToUtf8mb4($tablesToConvert);
		}
	}

	/**
	 * Uninstalls the database schema
	 *
	 * @return  void
	 */
	public function removeSchema()
	{
		// Get the schema XML file
		$xml = $this->findSchemaXml();

		if (empty($xml))
		{
			return;
		}

		// Make sure there are SQL commands in this file
		if (!$xml->sql)
		{
			return;
		}

		// Walk the sql > action tags to find all tables
		$tables = array();
		/** @var SimpleXMLElement $actions */
		$actions = $xml->sql->children();

		/** @var SimpleXMLElement $action */
		foreach ($actions as $action)
		{
			$attributes = $action->attributes();
			$tables[] = (string)$attributes->table;
		}

		// Simplify the tables list
		$tables = array_unique($tables);

		// Start dropping tables
		foreach ($tables as $table)
		{
			try
			{
				$this->db->dropTable($table);
			}
			catch (Exception $e)
			{
				// Do not fail if I can't drop the table
			}
		}
	}

	/**
	 * Find an suitable schema XML file for this database type and return the SimpleXMLElement holding its information
	 *
	 * @return  null|SimpleXMLElement  Null if no suitable schema XML file is found
	 */
	protected function findSchemaXml()
	{
		$driverType = $this->db->name;
		$xml = null;

		// And now look for the file
		foreach ($this->xmlFiles as $baseName)
		{
			// Remove any accidental whitespace
			$baseName = trim($baseName);

			// Get the full path to the file
			$fileName = $this->xmlDirectory . '/' . $baseName . '.xml';

			// Make sure the file exists
			if (!@file_exists($fileName))
			{
				continue;
			}

			// Make sure the file is a valid XML document
			try
			{
				$xml = new SimpleXMLElement($fileName, LIBXML_NONET, true);
			}
			catch (Exception $e)
			{
				$xml = null;
				continue;
			}

			// Make sure the file is an XML schema file
			if ($xml->getName() != 'schema')
			{
				$xml = null;
				continue;
			}

			if (!$xml->meta)
			{
				$xml = null;
				continue;
			}

			if (!$xml->meta->drivers)
			{
				$xml = null;
				continue;
			}

			/** @var SimpleXMLElement $drivers */
			$drivers = $xml->meta->drivers;

			// Strict driver name match
			foreach ($drivers->children() as $driverTypeTag)
			{
				$thisDriverType = (string)$driverTypeTag;

				if ($thisDriverType == $driverType)
				{
					return $xml;
				}
			}

			// Some custom database drivers use a non-standard $name variable. Let try a relaxed match.
			foreach ($drivers->children() as $driverTypeTag)
			{
				$thisDriverType = (string)$driverTypeTag;

				if (
					// e.g. $driverType = 'mysqlistupid', $thisDriverType = 'mysqli' => driver matched
					strpos($driverType, $thisDriverType) === 0
					// e.g. $driverType = 'stupidmysqli', $thisDriverType = 'mysqli' => driver matched
					|| (substr($driverType, -strlen($thisDriverType)) == $thisDriverType)
				)
				{
					return $xml;
				}
			}

			$xml = null;
		}

		return $xml;
	}

	/**
	 * Checks if a condition is met
	 *
	 * @param   string            $table  The table we're operating on
	 * @param   SimpleXMLElement  $node   The condition definition node
	 *
	 * @return  bool
	 */
	protected function conditionMet($table, SimpleXMLElement $node)
	{
		if (empty(static::$allTables))
		{
			static::$allTables = $this->db->getTableList();
		}

		// Does the table exist?
		$tableNormal = $this->db->replacePrefix($table);
		$tableExists = in_array($tableNormal, static::$allTables);

		// Initialise
		$condition = false;

		// Get the condition's attributes
		$attributes = $node->attributes();
		$type = $attributes->type ? $attributes->type : null;
		$value = $attributes->value ? (string) $attributes->value : null;

		switch ($type)
		{
			// Check if a table or column is missing
			case 'missing':
				$fieldName = (string)$value;

				if (empty($fieldName))
				{
					$condition = !$tableExists;
				}
				else
				{
					try
					{
						$tableColumns = $this->db->getTableColumns($tableNormal, true);
					}
					catch (\Exception $e)
					{
						$tableColumns = array();
					}

					$condition = !array_key_exists($fieldName, $tableColumns);
				}

				break;

			// Check if a column type matches the "coltype" attribute
			case 'type':
				try
				{
					$tableColumns = $this->db->getTableColumns($tableNormal, false);
				}
				catch (\Exception $e)
				{
					$tableColumns = array();
				}

				$condition = false;

				if (array_key_exists($value, $tableColumns))
				{
					$coltype = $attributes->coltype ? $attributes->coltype : null;

					if (!empty($coltype))
					{
						$coltype = strtolower($coltype);
						$currentType = strtolower($tableColumns[$value]->Type);

						$condition = ($coltype == $currentType);
					}
				}

				break;

			// Check if a (named) index exists on the table. Currently only supported on MySQL.
			case 'index':
				$indexName = (string) $value;
				$condition = true;

				if (!empty($indexName))
				{
					$indexName = str_replace('#__', $this->db->getPrefix(), $indexName);
					$condition = $this->hasIndex($tableNormal, $indexName);
				}

				break;

			// Check if a table or column needs to be upgraded to utf8mb4
			case 'utf8mb4upgrade':
				$condition = false;

				// Check if the driver and the database connection have UTF8MB4 support
				try
				{
					$hasUtf8mb4Support = $this->db->hasUTF8mb4Support();
				}
				catch (\Exception $e)
				{
					$hasUtf8mb4Support = false;
				}

				if ($hasUtf8mb4Support)
				{
					$fieldName = (string)$value;

					if (empty($fieldName))
					{
						$collation = $this->getTableCollation($tableNormal);
					}
					else
					{
						$collation = $this->getColumnCollation($tableNormal, $fieldName);
					}

					$parts    = explode('_', $collation, 3);
					$encoding = empty($parts[0]) ? '' : strtolower($parts[0]);

					$condition = $encoding != 'utf8mb4';
				}

				break;

			// Check if the result of a query matches our expectation
			case 'equals':
				$query = (string)$node;
				$this->db->setQuery($query);

				try
				{
					$result = $this->db->loadResult();
					$condition = ($result == $value);
				}
				catch (Exception $e)
				{
					return false;
				}

				break;

			// Always returns true
			case 'true':
				return true;
				break;

			default:
				return false;
				break;
		}

		return $condition;
	}

	/**
	 * Get the collation of a table. Uses an internal cache for efficiency.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string  The collation, e.g. "utf8_general_ci"
	 */
	private function getTableCollation($tableName)
	{
		static $cache = array();

		$tableName = $this->db->replacePrefix($tableName);

		if (!isset($cache[$tableName]))
		{
			$cache[$tableName] = $this->realGetTableCollation($tableName);
		}

		return $cache[$tableName];
	}

	/**
	 * Get the collation of a table. This is the internal method used by getTableCollation.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string  The collation, e.g. "utf8_general_ci"
	 */
	private function realGetTableCollation($tableName)
	{
		try
		{
			$utf8Support = $this->db->hasUTFSupport();
		}
		catch (\Exception $e)
		{
			$utf8Support = false;
		}

		try
		{
			$utf8mb4Support = $utf8Support && $this->db->hasUTF8mb4Support();
		}
		catch (\Exception $e)
		{
			$utf8mb4Support = false;
		}

		$collation = $utf8mb4Support ? 'utf8mb4_unicode_ci' : ($utf8Support ? 'utf_general_ci' : 'latin1_swedish_ci');

		$query = 'SHOW TABLE STATUS LIKE ' . $this->db->q($tableName);

		try
		{
			$row = $this->db->setQuery($query)->loadAssoc();
		}
		catch (\Exception $e)
		{
			return $collation;
		}

		if (empty($row))
		{
			return $collation;
		}

		if (!isset($row['Collation']))
		{
			return $collation;
		}

		if (empty($row['Collation']))
		{
			return $collation;
		}

		return $row['Collation'];
	}

	/**
	 * Get the collation of a column. Uses an internal cache for efficiency.
	 *
	 * @param   string  $tableName   The name of the table
	 * @param   string  $columnName  The name of the column
	 *
	 * @return  string  The collation, e.g. "utf8_general_ci"
	 */
	private function getColumnCollation($tableName, $columnName)
	{
		static $cache = array();

		$tableName = $this->db->replacePrefix($tableName);
		$columnName = $this->db->replacePrefix($columnName);

		if (!isset($cache[$tableName]))
		{
			$cache[$tableName] = array();
		}

		if (!isset($cache[$tableName][$columnName]))
		{
			$cache[$tableName][$columnName] = $this->realGetColumnCollation($tableName, $columnName);
		}

		return $cache[$tableName][$columnName];
	}

	/**
	 * Get the collation of a column. This is the internal method used by getColumnCollation.
	 *
	 * @param   string  $tableName   The name of the table
	 * @param   string  $columnName  The name of the column
	 *
	 * @return  string  The collation, e.g. "utf8_general_ci"
	 */
	private function realGetColumnCollation($tableName, $columnName)
	{
		$collation = $this->getTableCollation($tableName);

		$query = 'SHOW FULL COLUMNS FROM ' . $this->db->qn($tableName) . ' LIKE ' . $this->db->q($columnName);

		try
		{
			$row = $this->db->setQuery($query)->loadAssoc();
		}
		catch (\Exception $e)
		{
			return $collation;
		}

		if (empty($row))
		{
			return $collation;
		}

		if (!isset($row['Collation']))
		{
			return $collation;
		}

		if (empty($row['Collation']))
		{
			return $collation;
		}

		return $row['Collation'];
	}

	/**
	 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8.
	 *
	 * We use our own method so we can be site it works even on Joomla! 3.4 or earlier, where UTF8MB4 support is not
	 * implemented.
	 *
	 * @param   string  $query  The query to convert
	 *
	 * @return  string  The converted query
	 */
	private function convertUtf8mb4QueryToUtf8($query)
	{
		// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
		$beginningOfQuery = substr($query, 0, 12);
		$beginningOfQuery = strtoupper($beginningOfQuery);

		if (!in_array($beginningOfQuery, array('ALTER TABLE ', 'CREATE TABLE')))
		{
			return $query;
		}

		// Replace utf8mb4 with utf8
		$from = array(
			'utf8mb4_unicode_ci',
			'utf8mb4_',
			'utf8mb4',
		);

		$to = array(
			'utf8_general_ci', // Yeah, we convert utf8mb4_unicode_ci to utf8_general_ci per Joomla!'s conventions
			'utf8_',
			'utf8',
		);

		return str_replace($from, $to, $query);
	}

	/**
	 * Automatically upgrade a CREATE TABLE or ALTER TABLE query from plain utf8 to utf8mb4 (UTF-8 Multibyte).
	 *
	 * @param   string  $query  The query to convert
	 *
	 * @return  string  The converted query
	 */
	private function convertUtf8QueryToUtf8mb4($query)
	{
		// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
		$beginningOfQuery = substr($query, 0, 12);
		$beginningOfQuery = strtoupper($beginningOfQuery);

		if (!in_array($beginningOfQuery, array('ALTER TABLE ', 'CREATE TABLE')))
		{
			return $query;
		}

		// Replace utf8 with utf8mb4
		$from = array(
			'utf8_general_ci',
			'utf8_',
			'utf8',
		);

		$to = array(
			'utf8mb4_unicode_ci', // Yeah, we convert utf8_general_ci to utf8mb4_unicode_ci per Joomla!'s conventions
			'utf8mb4_',
			'utf8mb4',
		);

		return str_replace($from, $to, $query);
	}

	/**
	 * Analyzes a query. If it's a CREATE TABLE query the table is added to the $tables array.
	 *
	 * @param   string  $query   The query to analyze
	 * @param   string  $tables  The array where the name of the detected table is added
	 *
	 * @return  void
	 */
	private function extractTablesToConvert($query, &$tables)
	{
		// Normalize the whitespace of the query
		$query = trim($query);
		$query = str_replace(array("\r\n", "\r", "\n"), ' ', $query);

		while (strstr($query, '  ') !== false)
		{
			$query = str_replace('  ', ' ', $query);
		}

		// Is it a create table query?
		$queryStart = substr($query, 0, 12);
		$queryStart = strtoupper($queryStart);

		if ($queryStart != 'CREATE TABLE')
		{
			return;
		}

		// Remove the CREATE TABLE keyword. Also, If there's an IF NOT EXISTS clause remove it.
		$query = substr($query, 12);
		$query = str_ireplace('IF NOT EXISTS', '', $query);
		$query = trim($query);

		// Make sure there is a space between the table name and its definition, denoted by an open parenthesis
		$query = str_replace('(', ' (', $query);

		// Now we should have the name of the table, a space and the rest of the query. Extract the table name.
		$parts = explode(' ', $query, 2);
		$tableName = $parts[0];

		/**
		 * The table name may be quoted. Since UTF8MB4 is only supported in MySQL, the table name can only be
		 * quoted with surrounding backticks. Therefore we can trim backquotes from the table name to unquote it!
		 **/
		$tableName = trim($tableName, '`');

		// Finally, add the table name to $tables if it doesn't already exist.
		if (!in_array($tableName, $tables))
		{
			$tables[] = $tableName;
		}
	}

	/**
	 * Converts the collation of tables listed in $tablesToConvert to utf8mb4_unicode_ci
	 *
	 * @param   array  $tablesToConvert  The list of tables to convert
	 *
	 * @return  void
	 */
	private function convertTablesToUtf8mb4($tablesToConvert)
	{
		try
		{
			$utf8mb4Support = $this->db->hasUTF8mb4Support();
		}
		catch (\Exception $e)
		{
			$utf8mb4Support = false;
		}

		// Make sure the database driver REALLY has support for converting character sets
		if (!$utf8mb4Support)
		{
			return;
		}

		asort($tablesToConvert);

		foreach ($tablesToConvert as $tableName)
		{
			$collation = $this->getTableCollation($tableName);

			$parts    = explode('_', $collation, 3);
			$encoding = empty($parts[0]) ? '' : strtolower($parts[0]);

			if ($encoding != 'utf8mb4')
			{
				$queries = $this->db->getAlterTableCharacterSet($tableName);

				try
				{
					foreach ($queries as $query)
					{
						$this->db->setQuery($query)->execute();
					}
				}
				catch (\Exception $e)
				{
					// We ignore failed conversions. Remember, you MUST change your indices MANUALLY.
				}
			}
		}
	}

	/**
	 * Returns true if table $tableName has an index named $indexName or if it's impossible to retrieve index names for
	 * the table (not enough privileges, not a MySQL database, ...)
	 *
	 * @param   string  $tableName  The name of the table
	 * @param   string  $indexName  The name of the index
	 *
	 * @return  bool
	 */
	private function hasIndex($tableName, $indexName)
	{
		static $isMySQL = null;
		static $cache = array();

		if (is_null($isMySQL))
		{
			$driverType = $this->db->name;
			$driverType = strtolower($driverType);
			$isMySQL = true;

			if (
				!strpos($driverType, 'mysql') === 0
				&& !(substr($driverType, -5) == 'mysql')
				&& !(substr($driverType, -6) == 'mysqli')
			)
			{
				$isMySQL = false;
			}
		}

		// Not MySQL? Lie and return true.
		if (!$isMySQL)
		{
			return true;
		}

		if (!isset($cache[$tableName]))
		{
			$cache[$tableName] = array();
		}

		if (!isset($cache[$tableName][$indexName]))
		{
			$cache[$tableName][$indexName] = true;

			try
			{
				$indices          = array();
				$query            = 'SHOW INDEXES FROM ' . $this->db->qn($tableName);
				$indexDefinitions = $this->db->setQuery($query)->loadAssocList();

				if (!empty($indexDefinitions) && is_array($indexDefinitions))
				{
					foreach ($indexDefinitions as $def)
					{
						$indices[] = $def['Key_name'];
					}

					$indices = array_unique($indices);
				}

				$cache[$tableName][$indexName] = in_array($indexName, $indices);
			}
			catch (\Exception $e)
			{
				// Ignore errors
			}
		}

		return $cache[$tableName][$indexName];
	}
}
fof/database/database.php000064400000013364152177723700011361 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Database connector class.
 *
 * @since       11.1
 * @deprecated  13.3 (Platform) & 4.0 (CMS)
 */
abstract class FOFDatabase
{
	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 * @deprecated  13.1 (Platform) & 4.0 (CMS)
	 */
	public function query()
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::query() is deprecated, use FOFDatabaseDriver::execute() instead.', JLog::WARNING, 'deprecated');
		}

		return $this->execute();
	}

	/**
	 * Get a list of available database connectors.  The list will only be populated with connectors that both
	 * the class exists and the static test method returns true.  This gives us the ability to have a multitude
	 * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
	 *
	 * @return  array  An array of available database connectors.
	 *
	 * @since   11.1
	 * @deprecated  13.1 (Platform) & 4.0 (CMS)
	 */
	public static function getConnectors()
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::getConnectors() is deprecated, use FOFDatabaseDriver::getConnectors() instead.', JLog::WARNING, 'deprecated');
		}

		return FOFDatabaseDriver::getConnectors();
	}

	/**
	 * Gets the error message from the database connection.
	 *
	 * @param   boolean  $escaped  True to escape the message string for use in JavaScript.
	 *
	 * @return  string  The error message for the most recent query.
	 *
	 * @deprecated  13.3 (Platform) & 4.0 (CMS)
	 * @since   11.1
	 */
	public function getErrorMsg($escaped = false)
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::getErrorMsg() is deprecated, use exception handling instead.', JLog::WARNING, 'deprecated');
		}

		if ($escaped)
		{
			return addslashes($this->errorMsg);
		}
		else
		{
			return $this->errorMsg;
		}
	}

	/**
	 * Gets the error number from the database connection.
	 *
	 * @return      integer  The error number for the most recent query.
	 *
	 * @since       11.1
	 * @deprecated  13.3 (Platform) & 4.0 (CMS)
	 */
	public function getErrorNum()
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::getErrorNum() is deprecated, use exception handling instead.', JLog::WARNING, 'deprecated');
		}

		return $this->errorNum;
	}

	/**
	 * Method to return a FOFDatabaseDriver instance based on the given options.  There are three global options and then
	 * the rest are specific to the database driver.  The 'driver' option defines which FOFDatabaseDriver class is
	 * used for the connection -- the default is 'mysqli'.  The 'database' option determines which database is to
	 * be used for the connection.  The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   array  $options  Parameters to be passed to the database driver.
	 *
	 * @return  FOFDatabaseDriver  A database object.
	 *
	 * @since       11.1
	 * @deprecated  13.1 (Platform) & 4.0 (CMS)
	 */
	public static function getInstance($options = array())
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::getInstance() is deprecated, use FOFDatabaseDriver::getInstance() instead.', JLog::WARNING, 'deprecated');
		}

		return FOFDatabaseDriver::getInstance($options);
	}

	/**
	 * Splits a string of multiple queries into an array of individual queries.
	 *
	 * @param   string  $query  Input SQL string with which to split into individual queries.
	 *
	 * @return  array  The queries from the input string separated into an array.
	 *
	 * @since   11.1
	 * @deprecated  13.1 (Platform) & 4.0 (CMS)
	 */
	public static function splitSql($query)
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::splitSql() is deprecated, use FOFDatabaseDriver::splitSql() instead.', JLog::WARNING, 'deprecated');
		}

		return FOFDatabaseDriver::splitSql($query);
	}

	/**
	 * Return the most recent error message for the database connector.
	 *
	 * @param   boolean  $showSQL  True to display the SQL statement sent to the database as well as the error.
	 *
	 * @return  string  The error message for the most recent query.
	 *
	 * @since   11.1
	 * @deprecated  13.3 (Platform) & 4.0 (CMS)
	 */
	public function stderr($showSQL = false)
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::stderr() is deprecated.', JLog::WARNING, 'deprecated');
		}

		if ($this->errorNum != 0)
		{
			return JText::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $this->errorNum, $this->errorMsg)
			. ($showSQL ? "<br />SQL = <pre>$this->sql</pre>" : '');
		}
		else
		{
			return JText::_('JLIB_DATABASE_FUNCTION_NOERROR');
		}
	}

	/**
	 * Test to see if the connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   11.1
	 * @deprecated  12.3 (Platform) & 4.0 (CMS) - Use FOFDatabaseDriver::isSupported() instead.
	 */
	public static function test()
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabase::test() is deprecated. Use FOFDatabaseDriver::isSupported() instead.', JLog::WARNING, 'deprecated');
		}

		return static::isSupported();
	}
}
fof/database/driver.php000064400000153525152177723700011114 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  database
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 *
 * This file is adapted from the Joomla! Platform. It is used to iterate a database cursor returning FOFTable objects
 * instead of plain stdClass objects
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Joomla Platform Database Driver Class
 *
 * @since  12.1
 *
 * @method      string  q()   q($text, $escape = true)  Alias for quote method
 * @method      string  qn()  qn($name, $as = null)     Alias for quoteName method
 */
abstract class FOFDatabaseDriver extends FOFDatabase implements FOFDatabaseInterface
{
	/**
	 * The name of the database.
	 *
	 * @var    string
	 * @since  11.4
	 */
	private $_database;

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  11.1
	 */
	public $name;

	/**
	 * The type of the database server family supported by this driver. Examples: mysql, oracle, postgresql, mssql,
	 * sqlite.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType;

	/**
	 * @var    resource  The database connection resource.
	 * @since  11.1
	 */
	protected $connection;

	/**
	 * @var    integer  The number of SQL statements executed by the database driver.
	 * @since  11.1
	 */
	protected $count = 0;

	/**
	 * @var    resource  The database connection cursor from the last query.
	 * @since  11.1
	 */
	protected $cursor;

	/**
	 * @var    boolean  The database driver debugging state.
	 * @since  11.1
	 */
	protected $debug = false;

	/**
	 * @var    integer  The affected row limit for the current SQL statement.
	 * @since  11.1
	 */
	protected $limit = 0;

	/**
	 * @var    array  The log of executed SQL statements by the database driver.
	 * @since  11.1
	 */
	protected $log = array();

	/**
	 * @var    array  The log of executed SQL statements timings (start and stop microtimes) by the database driver.
	 * @since  CMS 3.1.2
	 */
	protected $timings = array();

	/**
	 * @var    array  The log of executed SQL statements timings (start and stop microtimes) by the database driver.
	 * @since  CMS 3.1.2
	 */
	protected $callStacks = array();

	/**
	 * @var    string  The character(s) used to quote SQL statement names such as table names or field names,
	 *                 etc.  The child classes should define this as necessary.  If a single character string the
	 *                 same character is used for both sides of the quoted name, else the first character will be
	 *                 used for the opening quote and the second for the closing quote.
	 * @since  11.1
	 */
	protected $nameQuote;

	/**
	 * @var    string  The null or zero representation of a timestamp for the database driver.  This should be
	 *                 defined in child classes to hold the appropriate value for the engine.
	 * @since  11.1
	 */
	protected $nullDate;

	/**
	 * @var    integer  The affected row offset to apply for the current SQL statement.
	 * @since  11.1
	 */
	protected $offset = 0;

	/**
	 * @var    array  Passed in upon instantiation and saved.
	 * @since  11.1
	 */
	protected $options;

	/**
	 * @var    mixed  The current SQL statement to execute.
	 * @since  11.1
	 */
	protected $sql;

	/**
	 * @var    string  The common database table prefix.
	 * @since  11.1
	 */
	protected $tablePrefix;

	/**
	 * @var    boolean  True if the database engine supports UTF-8 character encoding.
	 * @since  11.1
	 */
	protected $utf = true;

	/**
	 * @var    boolean  True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding.
	 * @since  CMS 3.5.0
	 */
	protected $utf8mb4 = false;

	/**
	 * @var         integer  The database error number
	 * @since       11.1
	 * @deprecated  12.1
	 */
	protected $errorNum = 0;

	/**
	 * @var         string  The database error message
	 * @since       11.1
	 * @deprecated  12.1
	 */
	protected $errorMsg;

	/**
	 * @var    array  FOFDatabaseDriver instances container.
	 * @since  11.1
	 */
	protected static $instances = array();

	/**
	 * @var    string  The minimum supported database version.
	 * @since  12.1
	 */
	protected static $dbMinimum;

	/**
	 * @var    integer  The depth of the current transaction.
	 * @since  12.3
	 */
	protected $transactionDepth = 0;

	/**
	 * @var    callable[]  List of callables to call just before disconnecting database
	 * @since  CMS 3.1.2
	 */
	protected $disconnectHandlers = array();

	/**
	 * Get a list of available database connectors.  The list will only be populated with connectors that both
	 * the class exists and the static test method returns true.  This gives us the ability to have a multitude
	 * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
	 *
	 * @return  array  An array of available database connectors.
	 *
	 * @since   11.1
	 */
	public static function getConnectors()
	{
		$connectors = array();

		// Get an iterator and loop trough the driver classes.
		$iterator = new DirectoryIterator(__DIR__ . '/driver');

		/* @type  $file  DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Block the ext/mysql driver for PHP 7
			if ($fileName === 'mysql.php' && PHP_MAJOR_VERSION >= 7)
			{
				continue;
			}

			// Derive the class name from the type.
			$class = str_ireplace('.php', '', 'FOFDatabaseDriver' . ucfirst(trim($fileName)));

			// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
			if (!class_exists($class))
			{
				continue;
			}

			// Sweet!  Our class exists, so now we just need to know if it passes its test method.
			if ($class::isSupported())
			{
				// Connector names should not have file extensions.
				$connectors[] = str_ireplace('.php', '', $fileName);
			}
		}

		return $connectors;
	}

	/**
	 * Method to return a FOFDatabaseDriver instance based on the given options.  There are three global options and then
	 * the rest are specific to the database driver.  The 'driver' option defines which FOFDatabaseDriver class is
	 * used for the connection -- the default is 'mysqli'.  The 'database' option determines which database is to
	 * be used for the connection.  The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   array  $options  Parameters to be passed to the database driver.
	 *
	 * @return  FOFDatabaseDriver  A database object.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public static function getInstance($options = array())
	{
		// Sanitize the database connector options.
		$options['driver']   = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli';
		$options['database'] = (isset($options['database'])) ? $options['database'] : null;
		$options['select']   = (isset($options['select'])) ? $options['select'] : true;

		// If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver.
		if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7)
		{
			// Check if we have support for the other MySQL drivers
			$mysqliSupported   = FOFDatabaseDriverMysqli::isSupported();
			$pdoMysqlSupported = FOFDatabaseDriverPdomysql::isSupported();

			// If neither is supported, then the user cannot use MySQL; throw an exception
			if (!$mysqliSupported && !$pdoMysqlSupported)
			{
				throw new RuntimeException(
					'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.'
					. ' Also, this system does not support MySQLi or PDO MySQL.  Cannot instantiate database driver.'
				);
			}

			// Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver
			if ($mysqliSupported)
			{
				if (class_exists('JLog'))
				{
					JLog::add(
							'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.  Trying `mysqli` instead.',
							JLog::WARNING,
							'deprecated'
					);
				}

				$options['driver'] = 'mysqli';
			}
			else
			{
				if (class_exists('JLog'))
				{
					JLog::add(
							'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.  Trying `pdomysql` instead.',
							JLog::WARNING,
							'deprecated'
					);
				}

				$options['driver'] = 'pdomysql';
			}
		}

		// Get the options signature for the database connector.
		$signature = md5(serialize($options));

		// If we already have a database connector instance for these options then just use that.
		if (empty(self::$instances[$signature]))
		{
			// Derive the class name from the driver.
			$class = 'FOFDatabaseDriver' . ucfirst(strtolower($options['driver']));

			// If the class still doesn't exist we have nothing left to do but throw an exception.  We did our best.
			if (!class_exists($class))
			{
				throw new RuntimeException(sprintf('Unable to load Database Driver: %s', $options['driver']));
			}

			// Create our new FOFDatabaseDriver connector based on the options given.
			try
			{
				$instance = new $class($options);
			}
			catch (RuntimeException $e)
			{
				throw new RuntimeException(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
			}

			// Set the new connector to the global instances based on signature.
			self::$instances[$signature] = $instance;
		}

		return self::$instances[$signature];
	}

	/**
	 * Splits a string of multiple queries into an array of individual queries.
	 *
	 * @param   string  $sql  Input SQL string with which to split into individual queries.
	 *
	 * @return  array  The queries from the input string separated into an array.
	 *
	 * @since   11.1
	 */
	public static function splitSql($sql)
	{
		$start = 0;
		$open = false;
		$char = '';
		$end = strlen($sql);
		$queries = array();

		for ($i = 0; $i < $end; $i++)
		{
			$current = substr($sql, $i, 1);

			if (($current == '"' || $current == '\''))
			{
				$n = 2;

				while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
				{
					$n++;
				}

				if ($n % 2 == 0)
				{
					if ($open)
					{
						if ($current == $char)
						{
							$open = false;
							$char = '';
						}
					}
					else
					{
						$open = true;
						$char = $current;
					}
				}
			}

			if (($current == ';' && !$open) || $i == $end - 1)
			{
				$queries[] = substr($sql, $start, ($i - $start + 1));
				$start = $i + 1;
			}
		}

		return $queries;
	}

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  mixed  The aliased method's return value or null.
	 *
	 * @since   11.1
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
				break;
			case 'qn':
				return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
				break;
		}
	}

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   11.1
	 */
	public function __construct($options)
	{
		// Initialise object variables.
		$this->_database = (isset($options['database'])) ? $options['database'] : '';
		$this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_';
		$this->connection = array_key_exists('connection', $options) ? $options['connection'] : null;

		$this->count = 0;
		$this->errorNum = 0;
		$this->log = array();

		// Set class options.
		$this->options = $options;
	}

	/**
	 * Alter database's character set, obtaining query string from protected member.
	 *
	 * @param   string  $dbName  The database name that will be altered
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function alterDbCharacterSet($dbName)
	{
		if (is_null($dbName))
		{
			throw new RuntimeException('Database name must not be null.');
		}

		$this->setQuery($this->getAlterDbCharacterSet($dbName));

		return $this->execute();
	}

	/**
	 * Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is
	 * wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the
	 * conversion. This prevents data corruption.
	 *
	 * @param   string   $tableName  The name of the table to alter
	 * @param   boolean  $rethrow    True to rethrow database exceptions. Default: false (exceptions are suppressed)
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   CMS 3.5.0
	 * @throws  RuntimeException  If the table name is empty
	 * @throws  Exception  Relayed from the database layer if a database error occurs and $rethrow == true
	 */
	public function alterTableCharacterSet($tableName, $rethrow = false)
	{
		if (is_null($tableName))
		{
			throw new RuntimeException('Table name must not be null.');
		}

		$queries = $this->getAlterTableCharacterSet($tableName);

		if (empty($queries))
		{
			return false;
		}

		$hasTransaction = true;

		try
		{
			$this->transactionStart();
		}
		catch (Exception $e)
		{
			$hasTransaction = false;
			$this->lockTable($tableName);
		}

		foreach ($queries as $query)
		{
			try
			{
				$this->setQuery($query)->execute();
			}
			catch (Exception $e)
			{
				if ($hasTransaction)
				{
					$this->transactionRollback();
				}
				else
				{
					$this->unlockTables();
				}

				if ($rethrow)
				{
					throw $e;
				}

				return false;
			}
		}

		if ($hasTransaction)
		{
			try
			{
				$this->transactionCommit();
			}
			catch (Exception $e)
			{
				$this->transactionRollback();

				if ($rethrow)
				{
					throw $e;
				}

				return false;
			}
		}
		else
		{
			$this->unlockTables();
		}

		return true;
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	abstract public function connect();

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   11.1
	 */
	abstract public function connected();

	/**
	 * Create a new database using information from $options object, obtaining query string
	 * from protected member.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 * 									This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   12.2
	 * @throws  RuntimeException
	 */
	public function createDatabase($options, $utf = true)
	{
		if (is_null($options))
		{
			throw new RuntimeException('$options object must not be null.');
		}
		elseif (empty($options->db_name))
		{
			throw new RuntimeException('$options object must have db_name set.');
		}
		elseif (empty($options->db_user))
		{
			throw new RuntimeException('$options object must have db_user set.');
		}

		$this->setQuery($this->getCreateDatabaseQuery($options, $utf));

		return $this->execute();
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   12.1
	 */
	abstract public function disconnect();

	/**
	 * Adds a function callable just before disconnecting the database. Parameter of the callable is $this FOFDatabaseDriver
	 *
	 * @param   callable  $callable  Function to call in disconnect() method just before disconnecting from database
	 *
	 * @return  void
	 *
	 * @since   CMS 3.1.2
	 */
	public function addDisconnectHandler($callable)
	{
		$this->disconnectHandlers[] = $callable;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  FOFDatabaseDriver     Returns this object to support chaining.
	 *
	 * @since   11.4
	 * @throws  RuntimeException
	 */
	public abstract function dropTable($table, $ifExists = true);

	/**
	 * Escapes a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string   The escaped string.
	 *
	 * @since   11.1
	 */
	abstract public function escape($text, $extra = false);

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   11.1
	 */
	abstract protected function fetchArray($cursor = null);

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   11.1
	 */
	abstract protected function fetchAssoc($cursor = null);

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   11.1
	 */
	abstract protected function fetchObject($cursor = null, $class = 'stdClass');

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 */
	abstract protected function freeResult($cursor = null);

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   11.1
	 */
	abstract public function getAffectedRows();

	/**
	 * Return the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   12.2
	 */
	public function getAlterDbCharacterSet($dbName)
	{
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`';
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
		$collation = $charset . '_general_ci';

		$quotedTableName = $this->quoteName($tableName);

		$queries = array();
		$queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation";

		/**
		 * We also need to convert each text column, modifying their character set and collation. This allows us to
		 * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column.
		 */
		$sql = "SHOW FULL COLUMNS FROM $quotedTableName";
		$this->setQuery($sql);
		$columns = $this->loadAssocList();
		$columnMods = array();

		if (is_array($columns))
		{
			foreach ($columns as $column)
			{
				// Make sure we are redefining only columns which do support a collation
				$col = (object) $column;

				if (empty($col->Collation))
				{
					continue;
				}

				// Default new collation: utf8_general_ci or utf8mb4_general_ci
				$newCollation = $charset . '_general_ci';
				$collationParts = explode('_', $col->Collation);

				/**
				 * If the collation is in the form charset_collationType_ci or charset_collationType we have to change
				 * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT
				 * utf8mb4_general_ci).
				 */
				if (count($collationParts) >= 2)
				{
					$ci = array_pop($collationParts);
					$collationType = array_pop($collationParts);
					$newCollation = $charset . '_' . $collationType . '_' . $ci;

					/**
					 * When the last part of the old collation is not _ci we have a charset_collationType format,
					 * something like utf8_bin. Therefore the new collation only has *two* parts.
					 */
					if ($ci != 'ci')
					{
						$newCollation = $charset . '_' . $ci;
					}
				}

				// If the old and new collation is the same we don't have to change the collation type
				if (strtolower($newCollation) == strtolower($col->Collation))
				{
					continue;
				}

				$null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL';
				$default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'";
				$columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default";
			}
		}

		if (count($columnMods))
		{
			$queries[] = "ALTER TABLE $quotedTableName " .
				implode(',', $columnMods) .
				" CHARACTER SET $charset COLLATE $collation";
		}

		return $queries;
	}

	/**
	 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
	 * when the server doesn't support UTF-8 Multibyte.
	 *
	 * @param   string  $query  The query to convert
	 *
	 * @return  string  The converted query
	 */
	public function convertUtf8mb4QueryToUtf8($query)
	{
		if ($this->hasUTF8mb4Support())
		{
			return $query;
		}

		// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
		$beginningOfQuery = substr($query, 0, 12);
		$beginningOfQuery = strtoupper($beginningOfQuery);

		if (!in_array($beginningOfQuery, array('ALTER TABLE ', 'CREATE TABLE')))
		{
			return $query;
		}

		// Replace utf8mb4 with utf8
		return str_replace('utf8mb4', 'utf8', $query);
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   12.2
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		if ($utf)
		{
			$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

			return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '`';
		}

		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   11.1
	 */
	abstract public function getCollation();

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return '';
	}

	/**
	 * Method that provides access to the underlying database connection. Useful for when you need to call a
	 * proprietary method such as postgresql's lo_* methods.
	 *
	 * @return  resource  The underlying database connection resource.
	 *
	 * @since   11.1
	 */
	public function getConnection()
	{
		return $this->connection;
	}

	/**
	 * Get the total number of SQL statements executed by the database driver.
	 *
	 * @return  integer
	 *
	 * @since   11.1
	 */
	public function getCount()
	{
		return $this->count;
	}

	/**
	 * Gets the name of the database used by this conneciton.
	 *
	 * @return  string
	 *
	 * @since   11.4
	 */
	protected function getDatabase()
	{
		return $this->_database;
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * @return  string  The format string.
	 *
	 * @since   11.1
	 */
	public function getDateFormat()
	{
		return 'Y-m-d H:i:s';
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   11.1
	 */
	public function getLog()
	{
		return $this->log;
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   CMS 3.1.2
	 */
	public function getTimings()
	{
		return $this->timings;
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   CMS 3.1.2
	 */
	public function getCallStacks()
	{
		return $this->callStacks;
	}

	/**
	 * Get the minimum supported database version.
	 *
	 * @return  string  The minimum version number for the database driver.
	 *
	 * @since   12.1
	 */
	public function getMinimum()
	{
		return static::$dbMinimum;
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 *
	 * @since   11.1
	 */
	public function getNullDate()
	{
		return $this->nullDate;
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   11.1
	 */
	abstract public function getNumRows($cursor = null);

	/**
	 * Get the common table prefix for the database driver.
	 *
	 * @return  string  The common database table prefix.
	 *
	 * @since   11.1
	 */
	public function getPrefix()
	{
		return $this->tablePrefix;
	}

	/**
	 * Gets an exporter class object.
	 *
	 * @return  FOFDatabaseExporter  An exporter object.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getExporter()
	{
		// Derive the class name from the driver.
		$class = 'FOFDatabaseExporter' . ucfirst($this->name);

		// Make sure we have an exporter class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new RuntimeException('Database Exporter not found.');
		}

		$o = new $class;
		$o->setDbo($this);

		return $o;
	}

	/**
	 * Gets an importer class object.
	 *
	 * @return  FOFDatabaseImporter  An importer object.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getImporter()
	{
		// Derive the class name from the driver.
		$class = 'FOFDatabaseImporter' . ucfirst($this->name);

		// Make sure we have an importer class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new RuntimeException('Database Importer not found');
		}

		$o = new $class;
		$o->setDbo($this);

		return $o;
	}

	/**
	 * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the
	 * class name.
	 *
	 * @return  string
	 *
	 * @since   CMS 3.5.0
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$className = get_class($this);
			$className = str_replace('FOFDatabaseDriver', '', $className);
			$this->name = strtolower($className);
		}

		return $this->name;
	}

	/**
	 * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it
	 * will attempt guessing the server family type from the driver name. If this is not possible the driver name will
	 * be returned instead.
	 *
	 * @return  string
	 *
	 * @since   CMS 3.5.0
	 */
	public function getServerType()
	{
		if (empty($this->serverType))
		{
			$name = $this->getName();

			if (stristr($name, 'mysql') !== false)
			{
				$this->serverType = 'mysql';
			}
			elseif (stristr($name, 'postgre') !== false)
			{
				$this->serverType = 'postgresql';
			}
			elseif (stristr($name, 'oracle') !== false)
			{
				$this->serverType = 'oracle';
			}
			elseif (stristr($name, 'sqlite') !== false)
			{
				$this->serverType = 'sqlite';
			}
			elseif (stristr($name, 'sqlsrv') !== false)
			{
				$this->serverType = 'mssql';
			}
			elseif (stristr($name, 'mssql') !== false)
			{
				$this->serverType = 'mssql';
			}
			else
			{
				$this->serverType = $name;
			}
		}

		return $this->serverType;
	}

	/**
	 * Get the current query object or a new FOFDatabaseQuery object.
	 *
	 * @param   boolean  $new  False to return the current query object, True to return a new FOFDatabaseQuery object.
	 *
	 * @return  FOFDatabaseQuery  The current query object or a new object extending the FOFDatabaseQuery class.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			// Derive the class name from the driver.
			$class = 'FOFDatabaseQuery' . ucfirst($this->name);

			// Make sure we have a query class for this driver.
			if (!class_exists($class))
			{
				// If it doesn't exist we are at an impasse so throw an exception.
				throw new RuntimeException('Database Query Class not found.');
			}

			return new $class($this);
		}
		else
		{
			return $this->sql;
		}
	}

	/**
	 * Get a new iterator on the current query.
	 *
	 * @param   string  $column  An option column to use as the iterator key.
	 * @param   string  $class   The class of object that is returned.
	 *
	 * @return  FOFDatabaseIterator  A new database iterator.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	public function getIterator($column = null, $class = 'stdClass')
	{
		// Derive the class name from the driver.
		$iteratorClass = 'FOFDatabaseIterator' . ucfirst($this->name);

		// Make sure we have an iterator class for this driver.
		if (!class_exists($iteratorClass))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new RuntimeException(sprintf('class *%s* is not defined', $iteratorClass));
		}

		// Return a new iterator
		return new $iteratorClass($this->execute(), $column, $class);
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True (default) to only return field types.
	 *
	 * @return  array  An array of fields by table.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function getTableColumns($table, $typeOnly = true);

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function getTableCreate($tables);

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  An array of keys for the table(s).
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function getTableKeys($tables);

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function getTableList();

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 *
	 * @since   11.1
	 * @deprecated 12.3 (Platform) & 4.0 (CMS) - Use hasUTFSupport() instead
	 */
	public function getUTFSupport()
	{
		if (class_exists('JLog'))
		{
			JLog::add('FOFDatabaseDriver::getUTFSupport() is deprecated. Use FOFDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated');
		}

		return $this->hasUTFSupport();
	}

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 *
	 * @since   12.1
	 */
	public function hasUTFSupport()
	{
		return $this->utf;
	}

	/**
	 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to
	 * MySQL databases.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 Multibyte.
	 *
	 * @since   CMS 3.5.0
	 */
	public function hasUTF8mb4Support()
	{
		return $this->utf8mb4;
	}

	/**
	 * Get the version of the database connector
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   11.1
	 */
	abstract public function getVersion();

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  mixed  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   11.1
	 */
	abstract public function insertid();

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$fields = array();
		$values = array();

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			// Ignore any internal fields.
			if ($k[0] == '_')
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->quote($v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true)
			->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		// Set the query and execute the insert.
		$this->setQuery($query);

		if (!$this->execute())
		{
			return false;
		}

		// Update the primary key if it exists.
		$id = $this->insertid();

		if ($key && $id && is_string($key))
		{
			$object->$key = $id;
		}

		return true;
	}

	/**
	 * Method to check whether the installed database version is supported by the database driver
	 *
	 * @return  boolean  True if the database version is supported
	 *
	 * @since   12.1
	 */
	public function isMinimumVersion()
	{
		return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
	}

	/**
	 * Method to get the first row of the result set from the database query as an associative array
	 * of ['field_name' => 'row_value'].
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadAssoc()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an associative array.
		if ($array = $this->fetchAssoc($cursor))
		{
			$ret = $array;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an associative array
	 * of ['field_name' => 'row_value'].  The array of rows can optionally be keyed by a field name, but defaults to
	 * a sequential numeric array.
	 *
	 * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key     The name of a field on which to key the result array.
	 * @param   string  $column  An optional column name. Instead of the whole row, only this column value will be in
	 * the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadAssocList($key = null, $column = null)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set.
		while ($row = $this->fetchAssoc($cursor))
		{
			$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;

			if ($key)
			{
				$array[$row[$key]] = $value;
			}
			else
			{
				$array[] = $value;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
	 * the database query.
	 *
	 * @param   integer  $offset  The row offset to use to build the result array.
	 *
	 * @return  mixed    The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadColumn($offset = 0)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			$array[] = $row[$offset];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 * @deprecated  12.3 (Platform) & 4.0 (CMS) - Use getIterator() instead
	 */
	public function loadNextObject($class = 'stdClass')
	{
		if (class_exists('JLog'))
		{
			JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		}

		$this->connect();

		static $cursor = null;

		// Execute the query and get the result set cursor.
		if ( is_null($cursor) )
		{
			if (!($cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject($cursor, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);
		$cursor = null;

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use FOFDatabaseDriver::getIterator() instead
	 */
	public function loadNextRow()
	{
		if (class_exists('JLog'))
		{
			JLog::add(__METHOD__ . '() is deprecated. Use FOFDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		}

		$this->connect();

		static $cursor = null;

		// Execute the query and get the result set cursor.
		if ( is_null($cursor) )
		{
			if (!($cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray($cursor))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);
		$cursor = null;

		return false;
	}

	/**
	 * Method to get the first row of the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadObject($class = 'stdClass')
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an object of type $class.
		if ($object = $this->fetchObject($cursor, $class))
		{
			$ret = $object;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an object.  The array
	 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key    The name of a field on which to key the result array.
	 * @param   string  $class  The class name to use for the returned row objects.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadObjectList($key = '', $class = 'stdClass')
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as objects of type $class.
		while ($row = $this->fetchObject($cursor, $class))
		{
			if ($key)
			{
				$array[$row->$key] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the first field of the first row of the result set from the database query.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadResult()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row[0];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get the first row of the result set from the database query as an array.  Columns are indexed
	 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadRow()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an array.  The array
	 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key  The name of a field on which to key the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function loadRowList($key = null)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return null;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			if ($key !== null)
			{
				$array[$row[$key]] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  FOFDatabaseDriver     Returns this object to support chaining.
	 *
	 * @since   11.4
	 * @throws  RuntimeException
	 */
	public abstract function lockTable($tableName);

	/**
	 * Quotes and optionally escapes a string to database requirements for use in database queries.
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True (default) to escape the string, false to leave it unchanged.
	 *
	 * @return  string  The quoted input string.
	 *
	 * @note    Accepting an array of strings was added in 12.3.
	 * @since   11.1
	 */
	public function quote($text, $escape = true)
	{
		if (is_array($text))
		{
			foreach ($text as $k => $v)
			{
				$text[$k] = $this->quote($v, $escape);
			}

			return $text;
		}
		else
		{
			return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
		}
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
	 *                        same length of $name; if is null there will not be any AS part for string or array element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @since   11.1
	 */
	public function quoteName($name, $as = null)
	{
		if (is_string($name))
		{
			$quotedName = $this->quoteNameStr(explode('.', $name));

			$quotedAs = '';

			if (!is_null($as))
			{
				settype($as, 'array');
				$quotedAs .= ' AS ' . $this->quoteNameStr($as);
			}

			return $quotedName . $quotedAs;
		}
		else
		{
			$fin = array();

			if (is_null($as))
			{
				foreach ($name as $str)
				{
					$fin[] = $this->quoteName($str);
				}
			}
			elseif (is_array($name) && (count($name) == count($as)))
			{
				$count = count($name);

				for ($i = 0; $i < $count; $i++)
				{
					$fin[] = $this->quoteName($name[$i], $as[$i]);
				}
			}

			return $fin;
		}
	}

	/**
	 * Quote strings coming from quoteName call.
	 *
	 * @param   array  $strArr  Array of strings coming from quoteName dot-explosion.
	 *
	 * @return  string  Dot-imploded string of quoted parts.
	 *
	 * @since 11.3
	 */
	protected function quoteNameStr($strArr)
	{
		$parts = array();
		$q = $this->nameQuote;

		foreach ($strArr as $part)
		{
			if (is_null($part))
			{
				continue;
			}

			if (strlen($q) == 1)
			{
				$parts[] = $q . $part . $q;
			}
			else
			{
				$parts[] = $q{0} . $part . $q{1};
			}
		}

		return implode('.', $parts);
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $sql     The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   11.1
	 */
	public function replacePrefix($sql, $prefix = '#__')
	{
		$startPos = 0;
		$literal = '';

		$sql = trim($sql);
		$n = strlen($sql);

		while ($startPos < $n)
		{
			$ip = strpos($sql, $prefix, $startPos);

			if ($ip === false)
			{
				break;
			}

			$j = strpos($sql, "'", $startPos);
			$k = strpos($sql, '"', $startPos);

			if (($k !== false) && (($k < $j) || ($j === false)))
			{
				$quoteChar = '"';
				$j = $k;
			}
			else
			{
				$quoteChar = "'";
			}

			if ($j === false)
			{
				$j = $n;
			}

			$literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k = strpos($sql, $quoteChar, $j);
				$escaped = false;

				if ($k === false)
				{
					break;
				}

				$l = $k - 1;

				while ($l >= 0 && $sql{$l} == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}

				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}

				break;
			}

			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}

			$literal .= substr($sql, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}

		if ($startPos < $n)
		{
			$literal .= substr($sql, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  FOFDatabaseDriver    Returns this object to support chaining.
	 *
	 * @since   11.4
	 * @throws  RuntimeException
	 */
	public abstract function renameTable($oldTable, $newTable, $backup = null, $prefix = null);

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function select($database);

	/**
	 * Sets the database debugging state for the driver.
	 *
	 * @param   boolean  $level  True to enable debugging.
	 *
	 * @return  boolean  The old debugging level.
	 *
	 * @since   11.1
	 */
	public function setDebug($level)
	{
		$previous = $this->debug;
		$this->debug = (bool) $level;

		return $previous;
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query   The SQL statement to set either as a FOFDatabaseQuery object or a string.
	 * @param   integer  $offset  The affected row offset to set.
	 * @param   integer  $limit   The maximum affected rows to set.
	 *
	 * @return  FOFDatabaseDriver  This object to support method chaining.
	 *
	 * @since   11.1
	 */
	public function setQuery($query, $offset = 0, $limit = 0)
	{
		$this->sql = $query;

		if ($query instanceof FOFDatabaseQueryLimitable)
		{
			if (!$limit && $query->limit)
			{
				$limit = $query->limit;
			}

			if (!$offset && $query->offset)
			{
				$offset = $query->offset;
			}

			$query->setLimit($limit, $offset);
		}
		else
		{
			$this->limit = (int) max(0, $limit);
			$this->offset = (int) max(0, $offset);
		}

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   11.1
	 */
	abstract public function setUtf();

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function transactionCommit($toSavepoint = false);

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function transactionRollback($toSavepoint = false);

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	abstract public function transactionStart($asSavepoint = false);

	/**
	 * Method to truncate a table.
	 *
	 * @param   string  $table  The table to truncate
	 *
	 * @return  void
	 *
	 * @since   11.3
	 * @throws  RuntimeException
	 */
	public function truncateTable($table)
	{
		$this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
		$this->execute();
	}

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string   $table    The name of the database table to update.
	 * @param   object   &$object  A reference to an object whose public properties match the table fields.
	 * @param   array    $key      The name of the primary key.
	 * @param   boolean  $nulls    True to update null fields or false to ignore them.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   11.1
	 * @throws  RuntimeException
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$fields = array();
		$where = array();

		if (is_string($key))
		{
			$key = array($key);
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process scalars that are not internal fields.
			if (is_array($v) or is_object($v) or $k[0] == '_')
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key))
			{
				$where[] = $this->quoteName($k) . '=' . $this->quote($v);
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we want to update nulls then set it.
				if ($nulls)
				{
					$val = 'NULL';
				}
				// If the value is null and we do not want to update nulls then ignore this field.
				else
				{
					continue;
				}
			}
			// The field is not null so we prep it for update.
			else
			{
				$val = $this->quote($v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(",", $fields), implode(' AND ', $where)));

		return $this->execute();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   12.1
	 * @throws  RuntimeException
	 */
	abstract public function execute();

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  FOFDatabaseDriver  Returns this object to support chaining.
	 *
	 * @since   11.4
	 * @throws  RuntimeException
	 */
	public abstract function unlockTables();
}
fof/platform/filesystem/interface.php000064400000011342152177723700014013 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  platformFilesystem
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

interface FOFPlatformFilesystemInterface
{
    /**
     * Does the file exists?
     *
     * @param   $path  string   Path to the file to test
     *
     * @return  bool
     */
    public function fileExists($path);

    /**
     * Delete a file or array of files
     *
     * @param   mixed  $file  The file name or an array of file names
     *
     * @return  boolean  True on success
     *
     */
    public function fileDelete($file);

    /**
     * Copies a file
     *
     * @param   string   $src          The path to the source file
     * @param   string   $dest         The path to the destination file
     *
     * @return  boolean  True on success
     */
    public function fileCopy($src, $dest);

    /**
     * Write contents to a file
     *
     * @param   string   $file         The full file path
     * @param   string   &$buffer      The buffer to write
     *
     * @return  boolean  True on success
     */
    public function fileWrite($file, &$buffer);

    /**
     * Checks for snooping outside of the file system root.
     *
     * @param   string  $path  A file system path to check.
     *
     * @return  string  A cleaned version of the path or exit on error.
     *
     * @throws  Exception
     */
    public function pathCheck($path);

    /**
     * Function to strip additional / or \ in a path name.
     *
     * @param   string  $path  The path to clean.
     * @param   string  $ds    Directory separator (optional).
     *
     * @return  string  The cleaned path.
     *
     * @throws  UnexpectedValueException
     */
    public function pathClean($path, $ds = DIRECTORY_SEPARATOR);

    /**
     * Searches the directory paths for a given file.
     *
     * @param   mixed   $paths  An path string or array of path strings to search in
     * @param   string  $file   The file name to look for.
     *
     * @return  mixed   The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
     */
    public function pathFind($paths, $file);

    /**
     * Wrapper for the standard file_exists function
     *
     * @param   string  $path  Folder name relative to installation dir
     *
     * @return  boolean  True if path is a folder
     */
    public function folderExists($path);

    /**
     * Utility function to read the files in a folder.
     *
     * @param   string   $path           The path of the folder to read.
     * @param   string   $filter         A filter for file names.
     * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
     * @param   boolean  $full           True to return the full path to the file.
     * @param   array    $exclude        Array with names of files which should not be shown in the result.
     * @param   array    $excludefilter  Array of filter to exclude
     *
     * @return  array  Files in the given folder.
     */
    public function folderFiles($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
                                $excludefilter = array('^\..*', '.*~'));

    /**
     * Utility function to read the folders in a folder.
     *
     * @param   string   $path           The path of the folder to read.
     * @param   string   $filter         A filter for folder names.
     * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
     * @param   boolean  $full           True to return the full path to the folders.
     * @param   array    $exclude        Array with names of folders which should not be shown in the result.
     * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
     *
     * @return  array  Folders in the given folder.
     */
    public function folderFolders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
                                  $excludefilter = array('^\..*'));

    /**
     * Create a folder -- and all necessary parent folders.
     *
     * @param   string   $path  A path to create from the base path.
     * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
     *
     * @return  boolean  True if successful.
     */
    public function folderCreate($path = '', $mode = 0755);
}fof/platform/filesystem/filesystem.php000064400000007730152177723700014245 0ustar00<?php
/**
* @package     FrameworkOnFramework
* @subpackage  platform
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
* @license     GNU General Public License version 2 or later; see LICENSE.txt
*/
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

abstract class FOFPlatformFilesystem implements FOFPlatformFilesystemInterface
{
    /**
     * The list of paths where platform class files will be looked for
     *
     * @var  array
     */
    protected static $paths = array();

    /**
     * This method will crawl a starting directory and get all the valid files that will be analyzed by getInstance.
     * Then it organizes them into an associative array.
     *
     * @param   string  $path               Folder where we should start looking
     * @param   array   $ignoreFolders      Folder ignore list
     * @param   array   $ignoreFiles        File ignore list
     *
     * @return  array   Associative array, where the `fullpath` key contains the path to the file,
     *                  and the `classname` key contains the name of the class
     */
    protected static function getFiles($path, array $ignoreFolders = array(), array $ignoreFiles = array())
    {
        $return = array();

        $files  = self::scanDirectory($path, $ignoreFolders, $ignoreFiles);

        // Ok, I got the files, now I have to organize them
        foreach($files as $file)
        {
            $clean = str_replace($path, '', $file);
            $clean = trim(str_replace('\\', '/', $clean), '/');

            $parts = explode('/', $clean);

            // If I have less than 3 fragments, it means that the file was inside the generic folder
            // (interface + abstract) so I have to skip it
            if(count($parts) < 3)
            {
                continue;
            }

            $return[] = array(
                'fullpath'  => $file,
                'classname' => 'FOFPlatform'.ucfirst($parts[0]).ucfirst(basename($parts[1], '.php'))
            );
        }

        return $return;
    }

    /**
     * Recursive function that will scan every directory unless it's in the ignore list. Files that aren't in the
     * ignore list are returned.
     *
     * @param   string  $path               Folder where we should start looking
     * @param   array   $ignoreFolders      Folder ignore list
     * @param   array   $ignoreFiles        File ignore list
     *
     * @return  array   List of all the files
     */
    protected static function scanDirectory($path, array $ignoreFolders = array(), array $ignoreFiles = array())
    {
        $return = array();

        $handle = @opendir($path);

        if(!$handle)
        {
            return $return;
        }

        while (($file = readdir($handle)) !== false)
        {
            if($file == '.' || $file == '..')
            {
                continue;
            }

            $fullpath = $path . '/' . $file;

            if((is_dir($fullpath) && in_array($file, $ignoreFolders)) || (is_file($fullpath) && in_array($file, $ignoreFiles)))
            {
                continue;
            }

            if(is_dir($fullpath))
            {
                $return = array_merge(self::scanDirectory($fullpath, $ignoreFolders, $ignoreFiles), $return);
            }
            else
            {
                $return[] = $path . '/' . $file;
            }
        }

        return $return;
    }

    /**
     * Gets the extension of a file name
     *
     * @param   string  $file  The file name
     *
     * @return  string  The file extension
     */
    public function getExt($file)
    {
        $dot = strrpos($file, '.') + 1;

        return substr($file, $dot);
    }

    /**
     * Strips the last extension off of a file name
     *
     * @param   string  $file  The file name
     *
     * @return  string  The file name without the extension
     */
    public function stripExt($file)
    {
        return preg_replace('#\.[^.]*$#', '', $file);
    }
}fof/platform/interface.php000064400000040626152177723700011636 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  platform
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Part of the FOF Platform Abstraction Layer. It implements everything that
 * depends on the platform FOF is running under, e.g. the Joomla! CMS front-end,
 * the Joomla! CMS back-end, a CLI Joomla! Platform app, a bespoke Joomla!
 * Platform / Framework web application and so on.
 *
 * This is the abstract class implementing some basic housekeeping functionality
 * and provides the static interface to get the appropriate Platform object for
 * use in the rest of the framework.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
interface FOFPlatformInterface
{
    /**
     * Checks if the current script is run inside a valid CMS execution
     *
     * @return bool
     */
    public function checkExecution();

	/**
	 * Set the error Handling, if possible
	 *
	 * @param   integer  $level      PHP error level (E_ALL)
	 * @param   string   $log_level  What to do with the error (ignore, callback)
	 * @param   array    $options    Options for the error handler
	 *
	 * @return  void
	 */
	public function setErrorHandling($level, $log_level, $options = array());

    /**
     * Raises an error, using the logic requested by the CMS (PHP Exception or dedicated class)
     *
     * @param   integer  $code
     * @param   string   $message
     *
     * @return mixed
     */
    public function raiseError($code, $message);

	/**
	 * Returns the ordering of the platform class. Files with a lower ordering
	 * number will be loaded first.
	 *
	 * @return  integer
	 */
	public function getOrdering();

	/**
	 * Returns a platform integration object
	 *
	 * @param   string  $key  The key name of the platform integration object, e.g. 'filesystem'
	 *
	 * @return  object
	 *
	 * @since  2.1.2
	 */
	public function getIntegrationObject($key);

	/**
	 * Forces a platform integration object instance
	 *
	 * @param   string  $key     The key name of the platform integration object, e.g. 'filesystem'
	 * @param   object  $object  The object to force for this key
	 *
	 * @return  object
	 *
	 * @since  2.1.2
	 */
	public function setIntegrationObject($key, $object);

	/**
	 * Is this platform enabled? This is used for automatic platform detection.
	 * If the environment we're currently running in doesn't seem to be your
	 * platform return false. If many classes return true, the one with the
	 * lowest order will be picked by FOFPlatform.
	 *
	 * @return  boolean
	 */
	public function isEnabled();

	/**
	 * Returns the (internal) name of the platform implementation, e.g.
	 * "joomla", "foobar123" etc. This MUST be the last part of the platform
	 * class name. For example, if you have a plaform implementation class
	 * FOFPlatformFoobar you MUST return "foobar" (all lowercase).
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformName();

	/**
	 * Returns the version number string of the platform, e.g. "4.5.6". If
	 * implementation integrates with a CMS or a versioned foundation (e.g.
	 * a framework) it is advisable to return that version.
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformVersion();

	/**
	 * Returns the human readable platform name, e.g. "Joomla!", "Joomla!
	 * Framework", "Something Something Something Framework" etc.
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformHumanName();

    /**
     * Returns absolute path to directories used by the CMS.
     *
     * The return is a table with the following key:
     * * root    Path to the site root
     * * public  Path to the public area of the site
     * * admin   Path to the administrative area of the site
     * * tmp     Path to the temp directory
     * * log     Path to the log directory
     *
     * @return  array  A hash array with keys root, public, admin, tmp and log.
     */
    public function getPlatformBaseDirs();

	/**
	 * Returns the base (root) directories for a given component. The
	 * "component" is used in the sense of what we call "component" in Joomla!,
	 * "plugin" in WordPress and "module" in Drupal, i.e. an application which
	 * is running inside our main application (CMS).
	 *
	 * The return is a table with the following keys:
	 * * main	The normal location of component files. For a back-end Joomla!
	 *          component this is the administrator/components/com_example
	 *          directory.
	 * * alt	The alternate location of component files. For a back-end
	 *          Joomla! component this is the front-end directory, e.g.
	 *          components/com_example
	 * * site	The location of the component files serving the public part of
	 *          the application.
	 * * admin	The location of the component files serving the administrative
	 *          part of the application.
	 *
	 * All paths MUST be absolute. All four paths MAY be the same if the
	 * platform doesn't make a distinction between public and private parts,
	 * or when the component does not provide both a public and private part.
	 * All of the directories MUST be defined and non-empty.
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @return  array  A hash array with keys main, alt, site and admin.
	 */
	public function getComponentBaseDirs($component);

	/**
	 * Return a list of the view template paths for this component. The paths
	 * are in the format site:/component_name/view_name/layout_name or
	 * admin:/component_name/view_name/layout_name
	 *
	 * The list of paths returned is a prioritised list. If a file is
	 * found in the first path the other paths will not be scanned.
	 *
	 * @param   string   $component  The name of the component. For Joomla! this
	 *                               is something like "com_example"
	 * @param   string   $view       The name of the view you're looking a
	 *                               template for
	 * @param   string   $layout     The layout name to load, e.g. 'default'
	 * @param   string   $tpl        The sub-template name to load (null by default)
	 * @param   boolean  $strict     If true, only the specified layout will be
	 *                               searched for. Otherwise we'll fall back to
	 *                               the 'default' layout if the specified layout
	 *                               is not found.
	 *
	 * @return  array
	 */
	public function getViewTemplatePaths($component, $view, $layout = 'default', $tpl = null, $strict = false);

	/**
	 * Get application-specific suffixes to use with template paths. This allows
	 * you to look for view template overrides based on the application version.
	 *
	 * @return  array  A plain array of suffixes to try in template names
	 */
	public function getTemplateSuffixes();

	/**
	 * Return the absolute path to the application's template overrides
	 * directory for a specific component. We will use it to look for template
	 * files instead of the regular component directorues. If the application
	 * does not have such a thing as template overrides return an empty string.
	 *
	 * @param   string   $component  The name of the component for which to fetch the overrides
	 * @param   boolean  $absolute   Should I return an absolute or relative path?
	 *
	 * @return  string  The path to the template overrides directory
	 */
	public function getTemplateOverridePath($component, $absolute = true);

	/**
	 * Load the translation files for a given component. The
	 * "component" is used in the sense of what we call "component" in Joomla!,
	 * "plugin" in WordPress and "module" in Drupal, i.e. an application which
	 * is running inside our main application (CMS).
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @return  void
	 */
	public function loadTranslations($component);

	/**
	 * By default FOF will only use the Controller's onBefore* methods to
	 * perform user authorisation. In some cases, like the Joomla! back-end,
	 * you alos need to perform component-wide user authorisation in the
	 * Dispatcher. This method MUST implement this authorisation check. If you
	 * do not need this in your platform, please always return true.
	 *
	 * @param   string  $component  The name of the component.
	 *
	 * @return  boolean  True to allow loading the component, false to halt loading
	 */
	public function authorizeAdmin($component);

	/**
	 * This method will try retrieving a variable from the request (input) data.
	 * If it doesn't exist it will be loaded from the user state, typically
	 * stored in the session. If it doesn't exist there either, the $default
	 * value will be used. If $setUserState is set to true, the retrieved
	 * variable will be stored in the user session.
	 *
	 * @param   string    $key           The user state key for the variable
	 * @param   string    $request       The request variable name for the variable
	 * @param   FOFInput  $input         The FOFInput object with the request (input) data
	 * @param   mixed     $default       The default value. Default: null
	 * @param   string    $type          The filter type for the variable data. Default: none (no filtering)
	 * @param   boolean   $setUserState  Should I set the user state with the fetched value?
	 *
	 * @return  mixed  The value of the variable
	 */
	public function getUserStateFromRequest($key, $request, $input, $default = null, $type = 'none', $setUserState = true);

	/**
	 * Load plugins of a specific type. Obviously this seems to only be required
	 * in the Joomla! CMS.
	 *
	 * @param   string  $type  The type of the plugins to be loaded
	 *
	 * @return void
	 */
	public function importPlugin($type);

	/**
	 * Execute plugins (system-level triggers) and fetch back an array with
	 * their return values.
	 *
	 * @param   string  $event  The event (trigger) name, e.g. onBeforeScratchMyEar
	 * @param   array   $data   A hash array of data sent to the plugins as part of the trigger
	 *
	 * @return  array  A simple array containing the resutls of the plugins triggered
	 */
	public function runPlugins($event, $data);

	/**
	 * Perform an ACL check. Please note that FOF uses by default the Joomla!
	 * CMS convention for ACL privileges, e.g core.edit for the edit privilege.
	 * If your platform uses different conventions you'll have to override the
	 * FOF defaults using fof.xml or by specialising the controller.
	 *
	 * @param   string  $action     The ACL privilege to check, e.g. core.edit
	 * @param   string  $assetname  The asset name to check, typically the component's name
	 *
	 * @return  boolean  True if the user is allowed this action
	 */
	public function authorise($action, $assetname);

	/**
	 * Returns a user object.
	 *
	 * @param   integer  $id  The user ID to load. Skip or use null to retrieve
	 *                        the object for the currently logged in user.
	 *
	 * @return  JUser  The JUser object for the specified user
	 */
	public function getUser($id = null);

	/**
	 * Returns the JDocument object which handles this component's response. You
	 * may also return null and FOF will a. try to figure out the output type by
	 * examining the "format" input parameter (or fall back to "html") and b.
	 * FOF will not attempt to load CSS and Javascript files (as it doesn't make
	 * sense if there's no JDocument to handle them).
	 *
	 * @return  JDocument
	 */
	public function getDocument();

    /**
     * Returns an object to handle dates
     *
     * @param   mixed   $time       The initial time
     * @param   null    $tzOffest   The timezone offset
     * @param   bool    $locale     Should I try to load a specific class for current language?
     *
     * @return  JDate object
     */
    public function getDate($time = 'now', $tzOffest = null, $locale = true);

    public function getLanguage();

	/**
	 * @return FOFDatabaseDriver
	 */
    public function getDbo();

	/**
	 * Is this the administrative section of the component?
	 *
	 * @return  boolean
	 */
	public function isBackend();

	/**
	 * Is this the public section of the component?
	 *
	 * @return  boolean
	 */
	public function isFrontend();

	/**
	 * Is this a component running in a CLI application?
	 *
	 * @return  boolean
	 */
	public function isCli();

	/**
	 * Is AJAX re-ordering supported? This is 100% Joomla!-CMS specific. All
	 * other platforms should return false and never ask why.
	 *
	 * @return  boolean
	 */
	public function supportsAjaxOrdering();

	/**
	 * Performs a check between two versions. Use this function instead of PHP version_compare
	 * so we can mock it while testing
	 *
	 * @param   string  $version1  First version number
	 * @param   string  $version2  Second version number
	 * @param   string  $operator  Operator (see version_compare for valid operators)
	 *
     * @deprecated Use PHP's version_compare against JVERSION in your code. This method is scheduled for removal in FOF 3.0
     *
	 * @return  boolean
	 */
	public function checkVersion($version1, $version2, $operator);

	/**
	 * Saves something to the cache. This is supposed to be used for system-wide
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to save
	 * @param   string  $content  The actual data to save
	 *
	 * @return  boolean  True on success
	 */
	public function setCache($key, $content);

	/**
	 * Retrieves data from the cache. This is supposed to be used for system-side
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to retrieve
	 * @param   string  $default  The default value to return if the key is not found or the cache is not populated
	 *
	 * @return  string  The cached value
	 */
	public function getCache($key, $default = null);

	/**
	 * Clears the cache of system-wide FOF data. You are supposed to call this in
	 * your components' installation script post-installation and post-upgrade
	 * methods or whenever you are modifying the structure of database tables
	 * accessed by FOF. Please note that FOF's cache never expires and is not
	 * purged by Joomla!. You MUST use this method to manually purge the cache.
	 *
	 * @return  boolean  True on success
	 */
	public function clearCache();

    /**
     * Returns an object that holds the configuration of the current site.
     *
     * @return  mixed
     */
    public function getConfig();

	/**
	 * Is the global FOF cache enabled?
	 *
	 * @return  boolean
	 */
	public function isGlobalFOFCacheEnabled();

	/**
	 * logs in a user
	 *
	 * @param   array  $authInfo  authentification information
	 *
	 * @return  boolean  True on success
	 */
	public function loginUser($authInfo);

	/**
	 * logs out a user
	 *
	 * @return  boolean  True on success
	 */
	public function logoutUser();

    public function logAddLogger($file);

	/**
	 * Logs a deprecated practice. In Joomla! this results in the $message being output in the
	 * deprecated log file, found in your site's log directory.
	 *
	 * @param   string  $message  The deprecated practice log message
	 *
	 * @return  void
	 */
	public function logDeprecated($message);

    public function logDebug($message);

    /**
     * Returns the root URI for the request.
     *
     * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
     * @param   string   $path      The path
     *
     * @return  string  The root URI string.
     */
    public function URIroot($pathonly = false, $path = null);

    /**
     * Returns the base URI for the request.
     *
     * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
     * |
     * @return  string  The base URI string
     */
    public function URIbase($pathonly = false);

    /**
     * Method to set a response header.  If the replace flag is set then all headers
     * with the given name will be replaced by the new one (only if the current platform supports header caching)
     *
     * @param   string   $name     The name of the header to set.
     * @param   string   $value    The value of the header to set.
     * @param   boolean  $replace  True to replace any headers with the same name.
     *
     * @return  void
     */
    public function setHeader($name, $value, $replace = false);

    /**
     * In platforms that perform header caching, send all headers.
     *
     * @return  void
     */
    public function sendHeaders();
}
fof/platform/platform.php000064400000043373152177723700011524 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  platform
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Part of the FOF Platform Abstraction Layer. It implements everything that
 * depends on the platform FOF is running under, e.g. the Joomla! CMS front-end,
 * the Joomla! CMS back-end, a CLI Joomla! Platform app, a bespoke Joomla!
 * Platform / Framework web application and so on.
 *
 * This is the abstract class implementing some basic housekeeping functionality
 * and provides the static interface to get the appropriate Platform object for
 * use in the rest of the framework.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
abstract class FOFPlatform implements FOFPlatformInterface
{
	/**
	 * The ordering for this platform class. The lower this number is, the more
	 * important this class becomes. Most important enabled class ends up being
	 * used.
	 *
	 * @var  integer
	 */
	public $ordering = 100;

	/**
	 * The internal name of this platform implementation. It must match the
	 * last part of the platform class name and be in all lowercase letters,
	 * e.g. "foobar" for FOFPlatformFoobar
	 *
	 * @var  string
	 *
	 * @since  2.1.2
	 */
	public $name = '';

	/**
	 * The human readable platform name
	 *
	 * @var  string
	 *
	 * @since  2.1.2
	 */
	public $humanReadableName = 'Unknown Platform';

	/**
	 * The platform version string
	 *
	 * @var  string
	 *
	 * @since  2.1.2
	 */
	public $version = '';

	/**
	 * Caches the enabled status of this platform class.
	 *
	 * @var  boolean
	 */
	protected $isEnabled = null;

    /**
     * Filesystem integration objects cache
     *
     * @var  object
	 *
	 * @since  2.1.2
     */
    protected $objectCache = array();

	/**
	 * The list of paths where platform class files will be looked for
	 *
	 * @var  array
	 */
	protected static $paths = array();

	/**
	 * The platform class instance which will be returned by getInstance
	 *
	 * @var  FOFPlatformInterface
	 */
	protected static $instance = null;

	// ========================================================================
	// Public API for platform integration handling
	// ========================================================================

	/**
	 * Register a path where platform files will be looked for. These take
	 * precedence over the built-in platform files.
	 *
	 * @param   string  $path  The path to add
	 *
	 * @return  void
	 */
	public static function registerPlatformPath($path)
	{
		if (!in_array($path, self::$paths))
		{
			self::$paths[] = $path;
			self::$instance = null;
		}
	}

	/**
	 * Unregister a path where platform files will be looked for.
	 *
	 * @param   string  $path  The path to remove
	 *
	 * @return  void
	 */
	public static function unregisterPlatformPath($path)
	{
		$pos = array_search($path, self::$paths);

		if ($pos !== false)
		{
			unset(self::$paths[$pos]);
			self::$instance = null;
		}
	}

	/**
	 * Force a specific platform object to be used. If null, nukes the cache
	 *
	 * @param   FOFPlatformInterface|null  $instance  The Platform object to be used
	 *
	 * @return  void
	 */
	public static function forceInstance($instance)
	{
		if ($instance instanceof FOFPlatformInterface || is_null($instance))
		{
			self::$instance = $instance;
		}
	}

	/**
	 * Find and return the most relevant platform object
	 *
	 * @return  FOFPlatformInterface
	 */
	public static function getInstance()
	{
		if (!is_object(self::$instance))
		{
			// Where to look for platform integrations
			$paths = array(__DIR__ . '/../integration');

			if (is_array(self::$paths))
			{
				$paths = array_merge($paths, self::$paths);
			}

			// Get a list of folders inside this directory
			$integrations = array();

			foreach ($paths as $path)
			{
				if (!is_dir($path))
				{
					continue;
				}

				$di = new DirectoryIterator($path);
				$temp = array();

				foreach ($di as $fileSpec)
				{
					if (!$fileSpec->isDir())
					{
						continue;
					}

					$fileName = $fileSpec->getFilename();

					if (substr($fileName, 0, 1) == '.')
					{
						continue;
					}

					$platformFilename = $path . '/' . $fileName . '/platform.php';

					if (!file_exists($platformFilename))
					{
						continue;
					}

					$temp[] = array(
						'classname'		=> 'FOFIntegration' . ucfirst($fileName) . 'Platform',
						'fullpath'		=> $path . '/' . $fileName . '/platform.php',
					);
				}

				$integrations = array_merge($integrations, $temp);
			}

			// Loop all paths
			foreach ($integrations as $integration)
			{
				// Get the class name for this platform class
				$class_name = $integration['classname'];

				// Load the file if the class doesn't exist
				if (!class_exists($class_name, false))
				{
					@include_once $integration['fullpath'];
				}

				// If the class still doesn't exist this file didn't
				// actually contain a platform class; skip it
				if (!class_exists($class_name, false))
				{
					continue;
				}

				// If it doesn't implement FOFPlatformInterface, skip it
				if (!class_implements($class_name, 'FOFPlatformInterface'))
				{
					continue;
				}

				// Get an object of this platform
				$o = new $class_name;

				// If it's not enabled, skip it
				if (!$o->isEnabled())
				{
					continue;
				}

				if (is_object(self::$instance))
				{
					// Replace self::$instance if this object has a
					// lower order number
					$current_order = self::$instance->getOrdering();
					$new_order = $o->getOrdering();

					if ($new_order < $current_order)
					{
						self::$instance = null;
						self::$instance = $o;
					}
				}
				else
				{
					// There is no self::$instance already, so use the
					// object we just created.
					self::$instance = $o;
				}
			}
		}

		return self::$instance;
	}

	/**
	 * Returns the ordering of the platform class.
	 *
	 * @see FOFPlatformInterface::getOrdering()
	 *
	 * @return  integer
	 */
	public function getOrdering()
	{
		return $this->ordering;
	}

	/**
	 * Is this platform enabled?
	 *
	 * @see FOFPlatformInterface::isEnabled()
	 *
	 * @return  boolean
	 */
	public function isEnabled()
	{
		if (is_null($this->isEnabled))
		{
			$this->isEnabled = false;
		}

		return $this->isEnabled;
	}

	/**
	 * Returns a platform integration object
	 *
	 * @param   string  $key  The key name of the platform integration object, e.g. 'filesystem'
	 *
	 * @return  object
	 *
	 * @since  2.1.2
	 */
	public function getIntegrationObject($key)
	{
		$hasObject = false;

		if (array_key_exists($key, $this->objectCache))
		{
			if (is_object($this->objectCache[$key]))
			{
				$hasObject = true;
			}
		}

		if (!$hasObject)
		{
			// Instantiate a new platform integration object
			$className = 'FOFIntegration' . ucfirst($this->getPlatformName()) . ucfirst($key);
			$this->objectCache[$key] = new $className;
		}

		return $this->objectCache[$key];
	}

	/**
	 * Forces a platform integration object instance
	 *
	 * @param   string  $key     The key name of the platform integration object, e.g. 'filesystem'
	 * @param   object  $object  The object to force for this key
	 *
	 * @return  object
	 *
	 * @since  2.1.2
	 */
	public function setIntegrationObject($key, $object)
	{
		$this->objectCache[$key] = $object;
	}

	// ========================================================================
	// Default implementation
	// ========================================================================

	/**
	 * Set the error Handling, if possible
	 *
	 * @param   integer  $level      PHP error level (E_ALL)
	 * @param   string   $log_level  What to do with the error (ignore, callback)
	 * @param   array    $options    Options for the error handler
	 *
	 * @return  void
	 */
	public function setErrorHandling($level, $log_level, $options = array())
	{
		if (version_compare(JVERSION, '3.0', 'lt') )
		{
			return JError::setErrorHandling($level, $log_level, $options);
		}
	}

	/**
	 * Returns the base (root) directories for a given component.
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @see FOFPlatformInterface::getComponentBaseDirs()
	 *
	 * @return  array  A hash array with keys main, alt, site and admin.
	 */
	public function getComponentBaseDirs($component)
	{
		return array(
			'main'	=> '',
			'alt'	=> '',
			'site'	=> '',
			'admin'	=> '',
		);
	}

	/**
	 * Return a list of the view template directories for this component.
	 *
	 * @param   string   $component  The name of the component. For Joomla! this
	 *                               is something like "com_example"
	 * @param   string   $view       The name of the view you're looking a
	 *                               template for
	 * @param   string   $layout     The layout name to load, e.g. 'default'
	 * @param   string   $tpl        The sub-template name to load (null by default)
	 * @param   boolean  $strict     If true, only the specified layout will be
	 *                               searched for. Otherwise we'll fall back to
	 *                               the 'default' layout if the specified layout
	 *                               is not found.
	 *
	 * @see FOFPlatformInterface::getViewTemplateDirs()
	 *
	 * @return  array
	 */
	public function getViewTemplatePaths($component, $view, $layout = 'default', $tpl = null, $strict = false)
	{
		return array();
	}

	/**
	 * Get application-specific suffixes to use with template paths. This allows
	 * you to look for view template overrides based on the application version.
	 *
	 * @return  array  A plain array of suffixes to try in template names
	 */
	public function getTemplateSuffixes()
	{
		return array();
	}

	/**
	 * Return the absolute path to the application's template overrides
	 * directory for a specific component. We will use it to look for template
	 * files instead of the regular component directorues. If the application
	 * does not have such a thing as template overrides return an empty string.
	 *
	 * @param   string   $component  The name of the component for which to fetch the overrides
	 * @param   boolean  $absolute   Should I return an absolute or relative path?
	 *
	 * @return  string  The path to the template overrides directory
	 */
	public function getTemplateOverridePath($component, $absolute = true)
	{
		return '';
	}

	/**
	 * Load the translation files for a given component.
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @see FOFPlatformInterface::loadTranslations()
	 *
	 * @return  void
	 */
	public function loadTranslations($component)
	{
		return null;
	}

	/**
	 * Authorise access to the component in the back-end.
	 *
	 * @param   string  $component  The name of the component.
	 *
	 * @see FOFPlatformInterface::authorizeAdmin()
	 *
	 * @return  boolean  True to allow loading the component, false to halt loading
	 */
	public function authorizeAdmin($component)
	{
		return true;
	}

	/**
	 * Returns the JUser object for the current user
	 *
	 * @param   integer  $id  The ID of the user to fetch
	 *
	 * @see FOFPlatformInterface::getUser()
	 *
	 * @return  JDocument
	 */
	public function getUser($id = null)
	{
		return null;
	}

	/**
	 * Returns the JDocument object which handles this component's response.
	 *
	 * @see FOFPlatformInterface::getDocument()
	 *
	 * @return  JDocument
	 */
	public function getDocument()
	{
		return null;
	}

	/**
	 * This method will try retrieving a variable from the request (input) data.
	 *
	 * @param   string    $key           The user state key for the variable
	 * @param   string    $request       The request variable name for the variable
	 * @param   FOFInput  $input         The FOFInput object with the request (input) data
	 * @param   mixed     $default       The default value. Default: null
	 * @param   string    $type          The filter type for the variable data. Default: none (no filtering)
	 * @param   boolean   $setUserState  Should I set the user state with the fetched value?
	 *
	 * @see FOFPlatformInterface::getUserStateFromRequest()
	 *
	 * @return  mixed  The value of the variable
	 */
	public function getUserStateFromRequest($key, $request, $input, $default = null, $type = 'none', $setUserState = true)
	{
		return $input->get($request, $default, $type);
	}

	/**
	 * Load plugins of a specific type. Obviously this seems to only be required
	 * in the Joomla! CMS.
	 *
	 * @param   string  $type  The type of the plugins to be loaded
	 *
	 * @see FOFPlatformInterface::importPlugin()
	 *
	 * @return void
	 */
	public function importPlugin($type)
	{
	}

	/**
	 * Execute plugins (system-level triggers) and fetch back an array with
	 * their return values.
	 *
	 * @param   string  $event  The event (trigger) name, e.g. onBeforeScratchMyEar
	 * @param   array   $data   A hash array of data sent to the plugins as part of the trigger
	 *
	 * @see FOFPlatformInterface::runPlugins()
	 *
	 * @return  array  A simple array containing the results of the plugins triggered
	 */
	public function runPlugins($event, $data)
	{
		return array();
	}

	/**
	 * Perform an ACL check.
	 *
	 * @param   string  $action     The ACL privilege to check, e.g. core.edit
	 * @param   string  $assetname  The asset name to check, typically the component's name
	 *
	 * @see FOFPlatformInterface::authorise()
	 *
	 * @return  boolean  True if the user is allowed this action
	 */
	public function authorise($action, $assetname)
	{
		return true;
	}

	/**
	 * Is this the administrative section of the component?
	 *
	 * @see FOFPlatformInterface::isBackend()
	 *
	 * @return  boolean
	 */
	public function isBackend()
	{
		return true;
	}

	/**
	 * Is this the public section of the component?
	 *
	 * @see FOFPlatformInterface::isFrontend()
	 *
	 * @return  boolean
	 */
	public function isFrontend()
	{
		return true;
	}

	/**
	 * Is this a component running in a CLI application?
	 *
	 * @see FOFPlatformInterface::isCli()
	 *
	 * @return  boolean
	 */
	public function isCli()
	{
		return true;
	}

	/**
	 * Is AJAX re-ordering supported? This is 100% Joomla!-CMS specific. All
	 * other platforms should return false and never ask why.
	 *
	 * @see FOFPlatformInterface::supportsAjaxOrdering()
	 *
	 * @return  boolean
	 */
	public function supportsAjaxOrdering()
	{
		return true;
	}

	/**
	 * Performs a check between two versions. Use this function instead of PHP version_compare
	 * so we can mock it while testing
	 *
	 * @param   string  $version1  First version number
	 * @param   string  $version2  Second version number
	 * @param   string  $operator  Operator (see version_compare for valid operators)
	 *
	 * @return  boolean
	 */
	public function checkVersion($version1, $version2, $operator)
	{
		return version_compare($version1, $version2, $operator);
	}

	/**
	 * Saves something to the cache. This is supposed to be used for system-wide
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to save
	 * @param   string  $content  The actual data to save
	 *
	 * @return  boolean  True on success
	 */
	public function setCache($key, $content)
	{
		return false;
	}

	/**
	 * Retrieves data from the cache. This is supposed to be used for system-side
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to retrieve
	 * @param   string  $default  The default value to return if the key is not found or the cache is not populated
	 *
	 * @return  string  The cached value
	 */
	public function getCache($key, $default = null)
	{
		return false;
	}

	/**
	 * Is the global FOF cache enabled?
	 *
	 * @return  boolean
	 */
	public function isGlobalFOFCacheEnabled()
	{
		return true;
	}

	/**
	 * Clears the cache of system-wide FOF data. You are supposed to call this in
	 * your components' installation script post-installation and post-upgrade
	 * methods or whenever you are modifying the structure of database tables
	 * accessed by FOF. Please note that FOF's cache never expires and is not
	 * purged by Joomla!. You MUST use this method to manually purge the cache.
	 *
	 * @return  boolean  True on success
	 */
	public function clearCache()
	{
		return false;
	}

	/**
	 * logs in a user
	 *
	 * @param   array  $authInfo  authentification information
	 *
	 * @return  boolean  True on success
	 */
	public function loginUser($authInfo)
	{
		return true;
	}

	/**
	 * logs out a user
	 *
	 * @return  boolean  True on success
	 */
	public function logoutUser()
	{
		return true;
	}

	/**
	 * Logs a deprecated practice. In Joomla! this results in the $message being output in the
	 * deprecated log file, found in your site's log directory.
	 *
	 * @param   $message  The deprecated practice log message
	 *
	 * @return  void
	 */
	public function logDeprecated($message)
	{
		// The default implementation does nothing. Override this in your platform classes.
	}

	/**
	 * Returns the (internal) name of the platform implementation, e.g.
	 * "joomla", "foobar123" etc. This MUST be the last part of the platform
	 * class name. For example, if you have a plaform implementation class
	 * FOFPlatformFoobar you MUST return "foobar" (all lowercase).
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformName()
	{
		return $this->name;
	}

	/**
	 * Returns the version number string of the platform, e.g. "4.5.6". If
	 * implementation integrates with a CMS or a versioned foundation (e.g.
	 * a framework) it is advisable to return that version.
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformVersion()
	{
		return $this->version;
	}

	/**
	 * Returns the human readable platform name, e.g. "Joomla!", "Joomla!
	 * Framework", "Something Something Something Framework" etc.
	 *
	 * @return  string
	 *
	 * @since  2.1.2
	 */
	public function getPlatformHumanName()
	{
		return $this->humanReadableName;
	}
}
fof/hal/link.php000064400000006153152177723700007550 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Implementation of the Hypertext Application Language link in PHP.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFHalLink
{
	/**
	 * For indicating the target URI. Corresponds with the ’Target IRI’ as
	 * defined in Web Linking (RFC 5988). This attribute MAY contain a URI
	 * Template (RFC6570) and in which case, SHOULD be complemented by an
	 * additional templated attribtue on the link with a boolean value true.
	 *
	 * @var string
	 */
	protected $_href = '';

	/**
	 * This attribute SHOULD be present with a boolean value of true when the
	 * href of the link contains a URI Template (RFC6570).
	 *
	 * @var  boolean
	 */
	protected $_templated = false;

	/**
	 * For distinguishing between Resource and Link elements that share the
	 * same relation
	 *
	 * @var  string
	 */
	protected $_name = null;

	/**
	 * For indicating what the language of the result of dereferencing the link should be.
	 *
	 * @var  string
	 */
	protected $_hreflang = null;

	/**
	 * For labeling the destination of a link with a human-readable identifier.
	 *
	 * @var  string
	 */
	protected $_title = null;

	/**
	 * Public constructor of a FOFHalLink object
	 *
	 * @param   string   $href       See $this->_href
	 * @param   boolean  $templated  See $this->_templated
	 * @param   string   $name       See $this->_name
	 * @param   string   $hreflang   See $this->_hreflang
	 * @param   string   $title      See $this->_title
	 *
	 * @throws  RuntimeException  If $href is empty
	 */
	public function __construct($href, $templated = false, $name = null, $hreflang = null, $title = null)
	{
		if (empty($href))
		{
			throw new RuntimeException('A HAL link must always have a non-empty href');
		}

		$this->_href = $href;
		$this->_templated = $templated;
		$this->_name = $name;
		$this->_hreflang = $hreflang;
		$this->_title = $title;
	}

	/**
	 * Is this a valid link? Checks the existence of required fields, not their
	 * values.
	 *
	 * @return  boolean
	 */
	public function check()
	{
		return !empty($this->_href);
	}

	/**
	 * Magic getter for the protected properties
	 *
	 * @param   string  $name  The name of the property to retrieve, sans the underscore
	 *
	 * @return  mixed  Null will always be returned if the property doesn't exist
	 */
	public function __get($name)
	{
		$property = '_' . $name;

		if (property_exists($this, $property))
		{
			return $this->$property;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Magic setter for the protected properties
	 *
	 * @param   string  $name   The name of the property to set, sans the underscore
	 * @param   mixed   $value  The value of the property to set
	 *
	 * @return  void
	 */
	public function __set($name, $value)
	{
		if (($name == 'href') && empty($value))
		{
			return;
		}

		$property = '_' . $name;

		if (property_exists($this, $property))
		{
			$this->$property = $value;
		}
	}
}
fof/hal/document.php000064400000012620152177723700010425 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Implementation of the Hypertext Application Language document in PHP. It can
 * be used to provide hypermedia in a web service context.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFHalDocument
{
	/**
	 * The collection of links of this document
	 *
	 * @var   FOFHalLinks
	 */
	private $_links = null;

	/**
	 * The data (resource state or collection of resource state objects) of the
	 * document.
	 *
	 * @var   array
	 */
	private $_data = null;

	/**
	 * Embedded documents. This is an array of FOFHalDocument instances.
	 *
	 * @var   array
	 */
	private $_embedded = array();

	/**
	 * When $_data is an array we'll output the list of data under this key
	 * (JSON) or tag (XML)
	 *
	 * @var   string
	 */
	private $_dataKey = '_list';

	/**
	 * Public constructor
	 *
	 * @param   mixed  $data  The data of the document (usually, the resource state)
	 */
	public function __construct($data = null)
	{
		$this->_data = $data;
		$this->_links = new FOFHalLinks;
	}

	/**
	 * Add a link to the document
	 *
	 * @param   string      $rel        The relation of the link to the document.
	 *                                  See RFC 5988 http://tools.ietf.org/html/rfc5988#section-6.2.2 A document MUST always have
	 *                                  a "self" link.
	 * @param   FOFHalLink  $link       The actual link object
	 * @param   boolean     $overwrite  When false and a link of $rel relation exists, an array of links is created. Otherwise the
	 *                                  existing link is overwriten with the new one
	 *
	 * @see FOFHalLinks::addLink
	 *
	 * @return  boolean  True if the link was added to the collection
	 */
	public function addLink($rel, FOFHalLink $link, $overwrite = true)
	{
		return $this->_links->addLink($rel, $link, $overwrite);
	}

	/**
	 * Add links to the document
	 *
	 * @param   string   $rel        The relation of the link to the document. See RFC 5988
	 * @param   array    $links      An array of FOFHalLink objects
	 * @param   boolean  $overwrite  When false and a link of $rel relation exists, an array of
	 *                               links is created. Otherwise the existing link is overwriten
	 *                               with the new one
	 *
	 * @see FOFHalLinks::addLinks
	 *
	 * @return  boolean
	 */
	public function addLinks($rel, array $links, $overwrite = true)
	{
		return $this->_links->addLinks($rel, $links, $overwrite);
	}

	/**
	 * Add data to the document
	 *
	 * @param   stdClass  $data       The data to add
	 * @param   boolean   $overwrite  Should I overwrite existing data?
	 *
	 * @return  void
	 */
	public function addData($data, $overwrite = true)
	{
		if (is_array($data))
		{
			$data = (object) $data;
		}

		if ($overwrite)
		{
			$this->_data = $data;
		}
		else
		{
			if (!is_array($this->_data))
			{
				$this->_data = array($this->_data);
			}

			$this->_data[] = $data;
		}
	}

	/**
	 * Add an embedded document
	 *
	 * @param   string          $rel        The relation of the embedded document to its container document
	 * @param   FOFHalDocument  $document   The document to add
	 * @param   boolean         $overwrite  Should I overwrite existing data with the same relation?
	 *
	 * @return  boolean
	 */
	public function addEmbedded($rel, FOFHalDocument $document, $overwrite = true)
	{
		if (!array_key_exists($rel, $this->_embedded) || !$overwrite)
		{
			$this->_embedded[$rel] = $document;
		}
		elseif (array_key_exists($rel, $this->_embedded) && !$overwrite)
		{
			if (!is_array($this->_embedded[$rel]))
			{
				$this->_embedded[$rel] = array($this->_embedded[$rel]);
			}

			$this->_embedded[$rel][] = $document;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Returns the collection of links of this document
	 *
	 * @param   string  $rel  The relation of the links to fetch. Skip to get all links.
	 *
	 * @return  array
	 */
	public function getLinks($rel = null)
	{
		return $this->_links->getLinks($rel);
	}

	/**
	 * Returns the collection of embedded documents
	 *
	 * @param   string  $rel  Optional; the relation to return the embedded documents for
	 *
	 * @return  array|FOFHalDocument
	 */
	public function getEmbedded($rel = null)
	{
		if (empty($rel))
		{
			return $this->_embedded;
		}
		elseif (isset($this->_embedded[$rel]))
		{
			return $this->_embedded[$rel];
		}
		else
		{
			return array();
		}
	}

	/**
	 * Return the data attached to this document
	 *
	 * @return   array|stdClass
	 */
	public function getData()
	{
		return $this->_data;
	}

	/**
	 * Instantiate and call a suitable renderer class to render this document
	 * into the specified format.
	 *
	 * @param   string  $format  The format to render the document into, e.g. 'json'
	 *
	 * @return  string  The rendered document
	 *
	 * @throws  RuntimeException  If the format is unknown, i.e. there is no suitable renderer
	 */
	public function render($format = 'json')
	{
		$class_name = 'FOFHalRender' . ucfirst($format);

		if (!class_exists($class_name, true))
		{
			throw new RuntimeException("Unsupported HAL Document format '$format'. Render aborted.");
		}

		$renderer = new $class_name($this);

		return $renderer->render(
			array(
				'data_key'		=> $this->_dataKey
			)
		);
	}
}
fof/hal/links.php000064400000006163152177723700007734 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Implementation of the Hypertext Application Language links in PHP. This is
 * actually a collection of links.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFHalLinks
{
	/**
	 * The collection of links, sorted by relation
	 *
	 * @var array
	 */
	private $_links = array();

	/**
	 * Add a single link to the links collection
	 *
	 * @param   string      $rel        The relation of the link to the document. See RFC 5988
	 *                                  http://tools.ietf.org/html/rfc5988#section-6.2.2 A document
	 *                                  MUST always have a "self" link.
	 * @param   FOFHalLink  $link       The actual link object
	 * @param   boolean     $overwrite  When false and a link of $rel relation exists, an array of
	 *                                  links is created. Otherwise the existing link is overwriten
	 *                                  with the new one
	 *
	 * @return  boolean  True if the link was added to the collection
	 */
	public function addLink($rel, FOFHalLink $link, $overwrite = true)
	{
		if (!$link->check())
		{
			return false;
		}

		if (!array_key_exists($rel, $this->_links) || $overwrite)
		{
			$this->_links[$rel] = $link;
		}
		elseif (array_key_exists($rel, $this->_links) && !$overwrite)
		{
			if (!is_array($this->_links[$rel]))
			{
				$this->_links[$rel] = array($this->_links[$rel]);
			}

			$this->_links[$rel][] = $link;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Add multiple links to the links collection
	 *
	 * @param   string   $rel        The relation of the links to the document. See RFC 5988.
	 * @param   array    $links      An array of FOFHalLink objects
	 * @param   boolean  $overwrite  When false and a link of $rel relation exists, an array
	 *                               of links is created. Otherwise the existing link is
	 *                               overwriten with the new one
	 *
	 * @return  boolean  True if the link was added to the collection
	 */
	public function addLinks($rel, array $links, $overwrite = true)
	{
		if (empty($links))
		{
			return false;
		}

		$localOverwrite = $overwrite;

		foreach ($links as $link)
		{
			if ($link instanceof FOFHalLink)
			{
				$this->addLink($rel, $link, $localOverwrite);
			}

			// After the first time we call this with overwrite on we have to
			// turn it off so that the other links are added to the set instead
			// of overwriting the first item that's already added.
			if ($localOverwrite)
			{
				$localOverwrite = false;
			}
		}
	}

	/**
	 * Returns the collection of links
	 *
	 * @param   string  $rel  Optional; the relation to return the links for
	 *
	 * @return  array|FOFHalLink
	 */
	public function getLinks($rel = null)
	{
		if (empty($rel))
		{
			return $this->_links;
		}
		elseif (isset($this->_links[$rel]))
		{
			return $this->_links[$rel];
		}
		else
		{
			return array();
		}
	}
}
fof/hal/render/json.php000064400000006500152177723700011037 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Implements the HAL over JSON renderer
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFHalRenderJson implements FOFHalRenderInterface
{
	/**
	 * When data is an array we'll output the list of data under this key
	 *
	 * @var   string
	 */
	private $_dataKey = '_list';

	/**
	 * The document to render
	 *
	 * @var   FOFHalDocument
	 */
	protected $_document;

	/**
	 * Public constructor
	 *
	 * @param   FOFHalDocument  &$document  The document to render
	 */
	public function __construct(&$document)
	{
		$this->_document = $document;
	}

	/**
	 * Render a HAL document in JSON format
	 *
	 * @param   array  $options  Rendering options. You can currently only set json_options (json_encode options)
	 *
	 * @return  string  The JSON representation of the HAL document
	 */
	public function render($options = array())
	{
		if (isset($options['data_key']))
		{
			$this->_dataKey = $options['data_key'];
		}

		if (isset($options['json_options']))
		{
			$jsonOptions = $options['json_options'];
		}
		else
		{
			$jsonOptions = 0;
		}

		$serialiseThis = new stdClass;

		// Add links
		$collection = $this->_document->getLinks();
		$serialiseThis->_links = new stdClass;

		foreach ($collection as $rel => $links)
		{
			if (!is_array($links))
			{
				$serialiseThis->_links->$rel = $this->_getLink($links);
			}
			else
			{
				$serialiseThis->_links->$rel = array();

				foreach ($links as $link)
				{
					array_push($serialiseThis->_links->$rel, $this->_getLink($link));
				}
			}
		}

		// Add embedded documents

		$collection = $this->_document->getEmbedded();

		if (!empty($collection))
		{
			$serialiseThis->_embedded->$rel = new stdClass;

			foreach ($collection as $rel => $embeddeddocs)
			{
				if (!is_array($embeddeddocs))
				{
					$embeddeddocs = array($embeddeddocs);
				}

				foreach ($embeddeddocs as $embedded)
				{
					$renderer = new FOFHalRenderJson($embedded);
					array_push($serialiseThis->_embedded->$rel, $renderer->render($options));
				}
			}
		}

		// Add data
		$data = $this->_document->getData();

		if (is_object($data))
		{
			if ($data instanceof FOFTable)
			{
				$data = $data->getData();
			}
			else
			{
				$data = (array) $data;
			}

			if (!empty($data))
			{
				foreach ($data as $k => $v)
				{
					$serialiseThis->$k = $v;
				}
			}
		}
		elseif (is_array($data))
		{
			$serialiseThis->{$this->_dataKey} = $data;
		}

		return json_encode($serialiseThis, $jsonOptions);
	}

	/**
	 * Converts a FOFHalLink object into a stdClass object which will be used
	 * for JSON serialisation
	 *
	 * @param   FOFHalLink  $link  The link you want converted
	 *
	 * @return  stdClass  The converted link object
	 */
	protected function _getLink(FOFHalLink $link)
	{
		$ret = array(
			'href'	=> $link->href
		);

		if ($link->templated)
		{
			$ret['templated'] = 'true';
		}

		if (!empty($link->name))
		{
			$ret['name'] = $link->name;
		}

		if (!empty($link->hreflang))
		{
			$ret['hreflang'] = $link->hreflang;
		}

		if (!empty($link->title))
		{
			$ret['title'] = $link->title;
		}

		return (object) $ret;
	}
}
fof/hal/render/interface.php000064400000001171152177723700012025 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  hal
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Interface for HAL document renderers
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
interface FOFHalRenderInterface
{
	/**
	 * Render a HAL document into a representation suitable for consumption.
	 *
	 * @param   array  $options  Renderer-specific options
	 *
	 * @return  void
	 */
	public function render($options = array());
}
fof/query/abstract.php000064400000001761152177723700011017 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  query
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework query base class; for compatibility purposes
 *
 * @package     FrameworkOnFramework
 * @since       2.1
 * @deprecated  2.1
 */
abstract class FOFQueryAbstract
{
	/**
	 * Returns a new database query class
	 *
	 * @param   FOFDatabaseDriver  $db  The DB driver which will provide us with a query object
	 *
	 * @return FOFQueryAbstract
	 */
	public static function &getNew($db = null)
	{
		FOFPlatform::getInstance()->logDeprecated('FOFQueryAbstract is deprecated. Use FOFDatabaseQuery instead.');

		if (is_null($db))
		{
			$ret = FOFPlatform::getInstance()->getDbo()->getQuery(true);
		}
		else
		{
			$ret = $db->getQuery(true);
		}

		return $ret;
	}
}
fof/toolbar/toolbar.php000064400000070133152177723700011152 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  toolbar
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * The Toolbar class renders the back-end component title area and the back-
 * and front-end toolbars.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFToolbar
{
	/** @var array Configuration parameters */
	protected $config = array();

	/** @var array Input (e.g. request) variables */
	protected $input = array();

	/** @var array Permissions map, see the __construct method for more information */
	public $perms = array();

	/** @var array The links to be rendered in the toolbar */
	protected $linkbar = array();

	/** @var bool Should I render the submenu in the front-end? */
	protected $renderFrontendSubmenu = false;

	/** @var bool Should I render buttons in the front-end? */
	protected $renderFrontendButtons = false;

	/**
	 * Gets an instance of a component's toolbar
	 *
	 * @param   string  $option  The name of the component
	 * @param   array   $config  The configuration array for the component
	 *
	 * @return  FOFToolbar  The toolbar instance for the component
	 */
	public static function &getAnInstance($option = null, $config = array())
	{
		static $instances = array();

		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$hash = $option;

		if (!array_key_exists($hash, $instances))
		{
			if (array_key_exists('input', $config))
			{
				if ($config['input'] instanceof FOFInput)
				{
					$input = $config['input'];
				}
				else
				{
					$input = new FOFInput($config['input']);
				}
			}
			else
			{
				$input = new FOFInput;
			}

			$config['option'] = !is_null($option) ? $option : $input->getCmd('option', 'com_foobar');
			$input->set('option', $config['option']);
			$config['input'] = $input;

			$className = ucfirst(str_replace('com_', '', $config['option'])) . 'Toolbar';

			if (!class_exists($className))
			{
				$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);

				$searchPaths = array(
					$componentPaths['main'],
					$componentPaths['main'] . '/toolbars',
					$componentPaths['alt'],
					$componentPaths['alt'] . '/toolbars'
				);

				if (array_key_exists('searchpath', $config))
				{
					array_unshift($searchPaths, $config['searchpath']);
				}

                $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

				$path = $filesystem->pathFind(
						$searchPaths, 'toolbar.php'
				);

				if ($path)
				{
					require_once $path;
				}
			}

			if (!class_exists($className))
			{
				$className = 'FOFToolbar';
			}

			$instance = new $className($config);

			$instances[$hash] = $instance;
		}

		return $instances[$hash];
	}

	/**
	 * Public constructor
	 *
	 * @param   array  $config  The configuration array of the component
	 */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Cache the config
		$this->config = $config;

		// Get the input for this MVC triad
		if (array_key_exists('input', $config))
		{
			$this->input = $config['input'];
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Get the default values for the component and view names
		$this->component = $this->input->getCmd('option', 'com_foobar');

		// Overrides from the config

		if (array_key_exists('option', $config))
		{
			$this->component = $config['option'];
		}

		$this->input->set('option', $this->component);

		// Get default permissions (can be overriden by the view)
		$platform = FOFPlatform::getInstance();
		$perms = (object) array(
				'manage'	 => $platform->authorise('core.manage', $this->input->getCmd('option', 'com_foobar')),
				'create'	 => $platform->authorise('core.create', $this->input->getCmd('option', 'com_foobar')),
				'edit'		 => $platform->authorise('core.edit', $this->input->getCmd('option', 'com_foobar')),
				'editstate'	 => $platform->authorise('core.edit.state', $this->input->getCmd('option', 'com_foobar')),
				'delete'	 => $platform->authorise('core.delete', $this->input->getCmd('option', 'com_foobar')),
		);

		// Save front-end toolbar and submenu rendering flags if present in the config
		if (array_key_exists('renderFrontendButtons', $config))
		{
			$this->renderFrontendButtons = $config['renderFrontendButtons'];
		}

		if (array_key_exists('renderFrontendSubmenu', $config))
		{
			$this->renderFrontendSubmenu = $config['renderFrontendSubmenu'];
		}

		// If not in the administrative area, load the JToolbarHelper
		if (!FOFPlatform::getInstance()->isBackend())
		{
            // Needed for tests, so we can inject our "special" helper class
            if(!class_exists('JToolbarHelper'))
            {
                $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();
                require_once $platformDirs['root'] . '/administrator/includes/toolbar.php';
            }

			// Things to do if we have to render a front-end toolbar
			if ($this->renderFrontendButtons)
			{
				// Load back-end toolbar language files in front-end
				FOFPlatform::getInstance()->loadTranslations('');

                // Needed for tests (we can fake we're not in the backend, but we are still in CLI!)
                if(!FOFPlatform::getInstance()->isCli())
                {
                    // Load the core Javascript
	                if (version_compare(JVERSION, '3.0', 'ge'))
	                {
		                JHtml::_('jquery.framework');

						if (version_compare(JVERSION, '3.3.0', 'ge'))
						{
							JHtml::_('behavior.core');
						}
						else
						{
							JHtml::_('behavior.framework', true);
						}
	                }
	                else
	                {
		                JHtml::_('behavior.framework');
	                }
                }
			}
		}

		// Store permissions in the local toolbar object
		$this->perms = $perms;
	}

	/**
	 * Renders the toolbar for the current view and task
	 *
	 * @param   string    $view   The view of the component
	 * @param   string    $task   The exact task of the view
	 * @param   FOFInput  $input  An optional input object used to determine the defaults
	 *
	 * @return  void
	 */
	public function renderToolbar($view = null, $task = null, $input = null)
	{
		if (!empty($input))
		{
			$saveInput = $this->input;
			$this->input = $input;
		}

		// If tmpl=component the default behaviour is to not render the toolbar
		if ($this->input->getCmd('tmpl', '') == 'component')
		{
			$render_toolbar = false;
		}
		else
		{
			$render_toolbar = true;
		}

		// If there is a render_toolbar=0 in the URL, do not render a toolbar

		$render_toolbar = $this->input->getBool('render_toolbar', $render_toolbar);

		if (!$render_toolbar)
		{
			return;
		}

		// Get the view and task

		if (empty($view))
		{
			$view = $this->input->getCmd('view', 'cpanel');
		}

		if (empty($task))
		{
			$task = $this->input->getCmd('task', 'default');
		}

		$this->view = $view;
		$this->task = $task;
		$view = FOFInflector::pluralize($view);
		$component = $this->input->get('option', 'com_foobar', 'cmd');

		$configProvider = new FOFConfigProvider;
		$toolbar = $configProvider->get(
			$component . '.views.' . $view . '.toolbar.' . $task
		);

		// If we have a toolbar config specified
		if (!empty($toolbar))
		{
			return $this->renderFromConfig($toolbar);
		}

		// Check for an onViewTask method
		$methodName = 'on' . ucfirst($view) . ucfirst($task);

		if (method_exists($this, $methodName))
		{
			return $this->$methodName();
		}

		// Check for an onView method
		$methodName = 'on' . ucfirst($view);

		if (method_exists($this, $methodName))
		{
			return $this->$methodName();
		}

		// Check for an onTask method
		$methodName = 'on' . ucfirst($task);

		if (method_exists($this, $methodName))
		{
			return $this->$methodName();
		}

		if (!empty($input))
		{
			$this->input = $saveInput;
		}
	}

	/**
	 * Renders the toolbar for the component's Control Panel page
	 *
	 * @return  void
	 */
	public function onCpanelsBrowse()
	{
		if (FOFPlatform::getInstance()->isBackend() || $this->renderFrontendSubmenu)
		{
			$this->renderSubmenu();
		}

		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		$option = $this->input->getCmd('option', 'com_foobar');

		JToolbarHelper::title(JText::_(strtoupper($option)), str_replace('com_', '', $option));
		JToolbarHelper::preferences($option, 550, 875);
	}

	/**
	 * Renders the toolbar for the component's Browse pages (the plural views)
	 *
	 * @return  void
	 */
	public function onBrowse()
	{
		// On frontend, buttons must be added specifically
		if (FOFPlatform::getInstance()->isBackend() || $this->renderFrontendSubmenu)
		{
			$this->renderSubmenu();
		}

		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		// Set toolbar title
		$option = $this->input->getCmd('option', 'com_foobar');
		$subtitle_key = strtoupper($option . '_TITLE_' . $this->input->getCmd('view', 'cpanel'));
		JToolbarHelper::title(JText::_(strtoupper($option)) . ': ' . JText::_($subtitle_key), str_replace('com_', '', $option));

		// Add toolbar buttons
		if ($this->perms->create)
		{
			if (version_compare(JVERSION, '3.0', 'ge'))
			{
				JToolbarHelper::addNew();
			}
			else
			{
				JToolbarHelper::addNewX();
			}
		}

		if ($this->perms->edit)
		{
			if (version_compare(JVERSION, '3.0', 'ge'))
			{
				JToolbarHelper::editList();
			}
			else
			{
				JToolbarHelper::editListX();
			}
		}

		if ($this->perms->create || $this->perms->edit)
		{
			JToolbarHelper::divider();
		}

		if ($this->perms->editstate)
		{
			JToolbarHelper::publishList();
			JToolbarHelper::unpublishList();
			JToolbarHelper::divider();
		}

		if ($this->perms->delete)
		{
			$msg = JText::_($this->input->getCmd('option', 'com_foobar') . '_CONFIRM_DELETE');
			JToolbarHelper::deleteList(strtoupper($msg));
		}
	}

	/**
	 * Renders the toolbar for the component's Read pages
	 *
	 * @return  void
	 */
	public function onRead()
	{
		// On frontend, buttons must be added specifically
		if (FOFPlatform::getInstance()->isBackend() || $this->renderFrontendSubmenu)
		{
			$this->renderSubmenu();
		}

		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		$option = $this->input->getCmd('option', 'com_foobar');
		$componentName = str_replace('com_', '', $option);

		// Set toolbar title
		$subtitle_key = strtoupper($option . '_TITLE_' . $this->input->getCmd('view', 'cpanel') . '_READ');
		JToolbarHelper::title(JText::_(strtoupper($option)) . ': ' . JText::_($subtitle_key), $componentName);

		// Set toolbar icons
		JToolbarHelper::back();
	}

	/**
	 * Renders the toolbar for the component's Add pages
	 *
	 * @return  void
	 */
	public function onAdd()
	{
		// On frontend, buttons must be added specifically
		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		$option = $this->input->getCmd('option', 'com_foobar');
		$componentName = str_replace('com_', '', $option);

		// Set toolbar title
		$subtitle_key = strtoupper($option . '_TITLE_' . FOFInflector::pluralize($this->input->getCmd('view', 'cpanel'))) . '_EDIT';
		JToolbarHelper::title(JText::_(strtoupper($option)) . ': ' . JText::_($subtitle_key), $componentName);

		// Set toolbar icons
        if ($this->perms->edit || $this->perms->editown)
        {
            // Show the apply button only if I can edit the record, otherwise I'll return to the edit form and get a
            // 403 error since I can't do that
            JToolbarHelper::apply();
        }

		JToolbarHelper::save();

		if ($this->perms->create)
		{
			JToolbarHelper::custom('savenew', 'save-new.png', 'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false);
		}

		JToolbarHelper::cancel();
	}

	/**
	 * Renders the toolbar for the component's Edit pages
	 *
	 * @return  void
	 */
	public function onEdit()
	{
		// On frontend, buttons must be added specifically
		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		$this->onAdd();
	}

	/**
	 * Removes all links from the link bar
	 *
	 * @return  void
	 */
	public function clearLinks()
	{
		$this->linkbar = array();
	}

	/**
	 * Get the link bar's link definitions
	 *
	 * @return  array
	 */
	public function &getLinks()
	{
		return $this->linkbar;
	}

	/**
	 * Append a link to the link bar
	 *
	 * @param   string       $name    The text of the link
	 * @param   string|null  $link    The link to render; set to null to render a separator
	 * @param   boolean      $active  True if it's an active link
	 * @param   string|null  $icon    Icon class (used by some renderers, like the Bootstrap renderer)
	 * @param   string|null  $parent  The parent element (referenced by name)) Thsi will create a dropdown list
	 *
	 * @return  void
	 */
	public function appendLink($name, $link = null, $active = false, $icon = null, $parent = '')
	{
		$linkDefinition = array(
			'name'	 => $name,
			'link'	 => $link,
			'active' => $active,
			'icon'	 => $icon
		);

		if (empty($parent))
		{
            if(array_key_exists($name, $this->linkbar))
            {
                $this->linkbar[$name] = array_merge($this->linkbar[$name], $linkDefinition);

                // If there already are some children, I have to put this view link in the "items" array in the first place
                if(array_key_exists('items', $this->linkbar[$name]))
                {
                    array_unshift($this->linkbar[$name]['items'], $linkDefinition);
                }
            }
            else
            {
                $this->linkbar[$name] = $linkDefinition;
            }
		}
		else
		{
			if (!array_key_exists($parent, $this->linkbar))
			{
				$parentElement = $linkDefinition;
                $parentElement['name'] = $parent;
				$parentElement['link'] = null;
				$this->linkbar[$parent] = $parentElement;
				$parentElement['items'] = array();
			}
			else
			{
				$parentElement = $this->linkbar[$parent];

				if (!array_key_exists('dropdown', $parentElement) && !empty($parentElement['link']))
				{
					$newSubElement = $parentElement;
					$parentElement['items'] = array($newSubElement);
				}
			}

			$parentElement['items'][] = $linkDefinition;
			$parentElement['dropdown'] = true;

			if($active)
			{
				$parentElement['active'] = true;
			}

			$this->linkbar[$parent] = $parentElement;
		}
	}

	/**
	 * Prefixes (some people erroneously call this "prepend" – there is no such word) a link to the link bar
	 *
	 * @param   string       $name    The text of the link
	 * @param   string|null  $link    The link to render; set to null to render a separator
	 * @param   boolean      $active  True if it's an active link
	 * @param   string|null  $icon    Icon class (used by some renderers, like the Bootstrap renderer)
	 *
	 * @return  void
	 */
	public function prefixLink($name, $link = null, $active = false, $icon = null)
	{
		$linkDefinition = array(
			'name'	 => $name,
			'link'	 => $link,
			'active' => $active,
			'icon'	 => $icon
		);
		array_unshift($this->linkbar, $linkDefinition);
	}

	/**
	 * Renders the submenu (toolbar links) for all detected views of this component
	 *
	 * @return  void
	 */
	public function renderSubmenu()
	{
		$views = $this->getMyViews();

		if (empty($views))
		{
			return;
		}

		$activeView = $this->input->getCmd('view', 'cpanel');

		foreach ($views as $view)
		{
			// Get the view name
			$key = strtoupper($this->component) . '_TITLE_' . strtoupper($view);

            //Do we have a translation for this key?
			if (strtoupper(JText::_($key)) == $key)
			{
				$altview = FOFInflector::isPlural($view) ? FOFInflector::singularize($view) : FOFInflector::pluralize($view);
				$key2 = strtoupper($this->component) . '_TITLE_' . strtoupper($altview);

                // Maybe we have for the alternative view?
				if (strtoupper(JText::_($key2)) == $key2)
				{
                    // Nope, let's use the raw name
					$name = ucfirst($view);
				}
				else
				{
					$name = JText::_($key2);
				}
			}
			else
			{
				$name = JText::_($key);
			}

			$link = 'index.php?option=' . $this->component . '&view=' . $view;

			$active = $view == $activeView;

			$this->appendLink($name, $link, $active);
		}
	}

	/**
	 * Automatically detects all views of the component
	 *
	 * @return  array  A list of all views, in the order to be displayed in the toolbar submenu
	 */
	protected function getMyViews()
	{
		$views      = array();
		$t_views    = array();
		$using_meta = false;

		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($this->component);
		$searchPath     = $componentPaths['main'] . '/views';
        $filesystem     = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		$allFolders = $filesystem->folderFolders($searchPath);

		if (!empty($allFolders))
		{
			foreach ($allFolders as $folder)
			{
				$view = $folder;

				// View already added
				if (in_array(FOFInflector::pluralize($view), $t_views))
				{
					continue;
				}

				// Do we have a 'skip.xml' file in there?
				$files = $filesystem->folderFiles($searchPath . '/' . $view, '^skip\.xml$');

				if (!empty($files))
				{
					continue;
				}

				// Do we have extra information about this view? (ie. ordering)
				$meta = $filesystem->folderFiles($searchPath . '/' . $view, '^metadata\.xml$');

				// Not found, do we have it inside the plural one?
				if (!$meta)
				{
					$plural = FOFInflector::pluralize($view);

					if (in_array($plural, $allFolders))
					{
						$view = $plural;
						$meta = $filesystem->folderFiles($searchPath . '/' . $view, '^metadata\.xml$');
					}
				}

				if (!empty($meta))
				{
					$using_meta = true;
					$xml = simplexml_load_file($searchPath . '/' . $view . '/' . $meta[0]);
					$order = (int) $xml->foflib->ordering;
				}
				else
				{
					// Next place. It's ok since the index are 0-based and count is 1-based

					if (!isset($to_order))
					{
						$to_order = array();
					}

					$order = count($to_order);
				}

				$view = FOFInflector::pluralize($view);

				$t_view = new stdClass;
				$t_view->ordering = $order;
				$t_view->view = $view;

				$to_order[] = $t_view;
				$t_views[] = $view;
			}
		}

        FOFUtilsArray::sortObjects($to_order, 'ordering');
		$views = FOFUtilsArray::getColumn($to_order, 'view');

		// If not using the metadata file, let's put the cpanel view on top
		if (!$using_meta)
		{
			$cpanel = array_search('cpanels', $views);

			if ($cpanel !== false)
			{
				unset($views[$cpanel]);
				array_unshift($views, 'cpanels');
			}
		}

		return $views;
	}

	/**
	 * Return the front-end toolbar rendering flag
	 *
	 * @return  boolean
	 */
	public function getRenderFrontendButtons()
	{
		return $this->renderFrontendButtons;
	}

	/**
	 * Return the front-end submenu rendering flag
	 *
	 * @return  boolean
	 */
	public function getRenderFrontendSubmenu()
	{
		return $this->renderFrontendSubmenu;
	}

	/**
	 * Render the toolbar from the configuration.
	 *
	 * @param   array  $toolbar  The toolbar definition
	 *
	 * @return  void
	 */
	private function renderFromConfig(array $toolbar)
	{
		if (FOFPlatform::getInstance()->isBackend() || $this->renderFrontendSubmenu)
		{
			$this->renderSubmenu();
		}

		if (!FOFPlatform::getInstance()->isBackend() && !$this->renderFrontendButtons)
		{
			return;
		}

		// Render each element
		foreach ($toolbar as $elementType => $elementAttributes)
		{
			$value = isset($elementAttributes['value']) ? $elementAttributes['value'] : null;
			$this->renderToolbarElement($elementType, $value, $elementAttributes);
		}

		return;
	}

	/**
	 * Render a toolbar element.
	 *
	 * @param   string  $type        The element type.
	 * @param   mixed   $value       The element value.
	 * @param   array   $attributes  The element attributes.
	 *
	 * @return  void
	 *
     * @codeCoverageIgnore
	 * @throws  InvalidArgumentException
	 */
	private function renderToolbarElement($type, $value = null, array $attributes = array())
	{
		switch ($type)
		{
			case 'title':
				$icon = isset($attributes['icon']) ? $attributes['icon'] : 'generic.png';
				JToolbarHelper::title($value, $icon);
				break;

			case 'divider':
				JToolbarHelper::divider();
				break;

			case 'custom':
				$task = isset($attributes['task']) ? $attributes['task'] : '';
				$icon = isset($attributes['icon']) ? $attributes['icon'] : '';
				$iconOver = isset($attributes['icon_over']) ? $attributes['icon_over'] : '';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : '';
				$listSelect = isset($attributes['list_select']) ?
					FOFStringUtils::toBool($attributes['list_select']) : true;

				JToolbarHelper::custom($task, $icon, $iconOver, $alt, $listSelect);
				break;

			case 'preview':
				$url = isset($attributes['url']) ? $attributes['url'] : '';
				$update_editors = isset($attributes['update_editors']) ?
					FOFStringUtils::toBool($attributes['update_editors']) : false;

				JToolbarHelper::preview($url, $update_editors);
				break;

			case 'help':
				if (!isset($attributes['help']))
				{
					throw new InvalidArgumentException(
						'The help attribute is missing in the help button type.'
					);
				}

				$ref = $attributes['help'];
				$com = isset($attributes['com']) ? FOFStringUtils::toBool($attributes['com']) : false;
				$override = isset($attributes['override']) ? $attributes['override'] : null;
				$component = isset($attributes['component']) ? $attributes['component'] : null;

				JToolbarHelper::help($ref, $com, $override, $component);
				break;

			case 'back':
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_BACK';
				$href = isset($attributes['href']) ? $attributes['href'] : 'javascript:history.back();';

				JToolbarHelper::back($alt, $href);
				break;

			case 'media_manager':
				$directory = isset($attributes['directory']) ? $attributes['directory'] : '';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_UPLOAD';

				JToolbarHelper::media_manager($directory, $alt);
				break;

			case 'assign':
				$task = isset($attributes['task']) ? $attributes['task'] : 'assign';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_ASSIGN';

				JToolbarHelper::assign($task, $alt);
				break;

			case 'new':
				if ($this->perms->create)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'add';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_NEW';
					$check = isset($attributes['check']) ?
						FOFStringUtils::toBool($attributes['check']) : false;

					JToolbarHelper::addNew($task, $alt, $check);
				}

				break;

			case 'publish':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'publish';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_PUBLISH';
					$check = isset($attributes['check']) ?
						FOFStringUtils::toBool($attributes['check']) : false;

					JToolbarHelper::publish($task, $alt, $check);
				}

				break;

			case 'publishList':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'publish';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_PUBLISH';

					JToolbarHelper::publishList($task, $alt);
				}

				break;

			case 'unpublish':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'unpublish';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_UNPUBLISH';
					$check = isset($attributes['check']) ?
						FOFStringUtils::toBool($attributes['check']) : false;

					JToolbarHelper::unpublish($task, $alt, $check);
				}

				break;

			case 'unpublishList':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'unpublish';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_UNPUBLISH';

					JToolbarHelper::unpublishList($task, $alt);
				}

				break;

			case 'archiveList':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'archive';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_ARCHIVE';

					JToolbarHelper::archiveList($task, $alt);
				}

				break;

			case 'unarchiveList':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'unarchive';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_UNARCHIVE';

					JToolbarHelper::unarchiveList($task, $alt);
				}

				break;

			case 'editList':
				if ($this->perms->edit)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'edit';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_EDIT';

					JToolbarHelper::editList($task, $alt);
				}

				break;

			case 'editHtml':
				$task = isset($attributes['task']) ? $attributes['task'] : 'edit_source';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_EDIT_HTML';

				JToolbarHelper::editHtml($task, $alt);
				break;

			case 'editCss':
				$task = isset($attributes['task']) ? $attributes['task'] : 'edit_css';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_EDIT_CSS';

				JToolbarHelper::editCss($task, $alt);
				break;

			case 'deleteList':
				if ($this->perms->delete)
				{
					$msg = isset($attributes['msg']) ? $attributes['msg'] : '';
					$task = isset($attributes['task']) ? $attributes['task'] : 'remove';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_DELETE';

					JToolbarHelper::deleteList($msg, $task, $alt);
				}

				break;

			case 'trash':
				if ($this->perms->editstate)
				{
					$task = isset($attributes['task']) ? $attributes['task'] : 'remove';
					$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_TRASH';
					$check = isset($attributes['check']) ?
						FOFStringUtils::toBool($attributes['check']) : true;

					JToolbarHelper::trash($task, $alt, $check);
				}

				break;

			case 'apply':
				$task = isset($attributes['task']) ? $attributes['task'] : 'apply';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_APPLY';

				JToolbarHelper::apply($task, $alt);
				break;

			case 'save':
				$task = isset($attributes['task']) ? $attributes['task'] : 'save';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_SAVE';

				JToolbarHelper::save($task, $alt);
				break;

			case 'save2new':
				$task = isset($attributes['task']) ? $attributes['task'] : 'save2new';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_SAVE_AND_NEW';

				JToolbarHelper::save2new($task, $alt);
				break;

			case 'save2copy':
				$task = isset($attributes['task']) ? $attributes['task'] : 'save2copy';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_SAVE_AS_COPY';
				JToolbarHelper::save2copy($task, $alt);
				break;

			case 'checkin':
				$task = isset($attributes['task']) ? $attributes['task'] : 'checkin';
				$alt = isset($attributes['alt']) ? $attributes['alt'] :'JTOOLBAR_CHECKIN';
				$check = isset($attributes['check']) ?
					FOFStringUtils::toBool($attributes['check']) : true;

				JToolbarHelper::checkin($task, $alt, $check);
				break;

			case 'cancel':
				$task = isset($attributes['task']) ? $attributes['task'] : 'cancel';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JTOOLBAR_CANCEL';

				JToolbarHelper::cancel($task, $alt);
				break;

			case 'preferences':
				if (!isset($attributes['component']))
				{
					throw new InvalidArgumentException(
						'The component attribute is missing in the preferences button type.'
					);
				}

				$component = $attributes['component'];
				$height = isset($attributes['height']) ? $attributes['height'] : '550';
				$width = isset($attributes['width']) ? $attributes['width'] : '875';
				$alt = isset($attributes['alt']) ? $attributes['alt'] : 'JToolbar_Options';
				$path = isset($attributes['path']) ? $attributes['path'] : '';

				JToolbarHelper::preferences($component, $height, $width, $alt, $path);
				break;

			default:
				throw new InvalidArgumentException(sprintf('Unknown button type %s', $type));
		}
	}
}
fof/include.php000064400000001402152177723700007462 0ustar00<?php
/**
 *  @package     FrameworkOnFramework
 *  @subpackage  include
 *  @copyright   Copyright (C) 2010-2015 Nicholas K. Dionysopoulos
 *  @license     GNU General Public License version 2, or later
 *  @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 *
 *  @deprecated  4.0  Deprecated without replacement include FOF by your own if required
 *
 *  Initializes FOF
 */

defined('_JEXEC') or die();

if (!defined('FOF_INCLUDED'))
{
	define('FOF_INCLUDED', '2.5.5');

	// Register the FOF autoloader
	require_once __DIR__ . '/autoloader/fof.php';
	FOFAutoloaderFof::init();

	// Register a debug log
	if (defined('JDEBUG') && JDEBUG)
	{
		FOFPlatform::getInstance()->logAddLogger('fof.log.php');
	}
}
fof/form/form.php000064400000037543152177723700007764 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (version_compare(JVERSION, '2.5.0', 'lt'))
{
	jimport('joomla.form.form');
	jimport('joomla.form.formfield');
	jimport('joomla.form.formrule');
}

/**
 * FOFForm is an extension to JForm which support not only edit views but also
 * browse (record list) and read (single record display) views based on XML
 * forms.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFForm extends JForm
{
	/**
	 * The model attached to this view
	 *
	 * @var FOFModel
	 */
	protected $model;

	/**
	 * The view used to render this form
	 *
	 * @var FOFView
	 */
	protected $view;

	/**
	 * Method to get an instance of a form.
	 *
	 * @param   string  	$name		The name of the form.
	 * @param   string  	$data		The name of an XML file or string to load as the form definition.
	 * @param   array   	$options	An array of form options.
	 * @param   bool  		$replace	Flag to toggle whether form fields should be replaced if a field
	 *                      	      	already exists with the same group/name.
	 * @param   bool|string $xpath		An optional xpath to search for the fields.
	 *
	 * @return  object  FOFForm instance.
	 *
	 * @since   2.0
	 * @throws  InvalidArgumentException if no data provided.
	 * @throws  RuntimeException if the form could not be loaded.
	 */
	public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false)
	{
		// Reference to array with form instances
		$forms = &self::$forms;

		// Only instantiate the form if it does not already exist.
		if (!isset($forms[$name]))
		{
			$data = trim($data);

			if (empty($data))
			{
				throw new InvalidArgumentException(sprintf('FOFForm::getInstance(name, *%s*)', gettype($data)));
			}

			// Instantiate the form.
			$forms[$name] = new FOFForm($name, $options);

			// Load the data.
			if (substr(trim($data), 0, 1) == '<')
			{
				if ($forms[$name]->load($data, $replace, $xpath) == false)
				{
					throw new RuntimeException('FOFForm::getInstance could not load form');
				}
			}
			else
			{
				if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
				{
					throw new RuntimeException('FOFForm::getInstance could not load file ' . $data . '.xml');
				}
			}
		}

		return $forms[$name];
	}

	/**
	 * Returns the value of an attribute of the form itself
	 *
	 * @param   string  $attribute  The name of the attribute
	 * @param   mixed   $default    Optional default value to return
	 *
	 * @return  mixed
	 *
	 * @since 2.0
	 */
	public function getAttribute($attribute, $default = null)
	{
		$value = $this->xml->attributes()->$attribute;

		if (is_null($value))
		{
			return $default;
		}
		else
		{
			return (string) $value;
		}
	}

	/**
	 * Loads the CSS files defined in the form, based on its cssfiles attribute
	 *
	 * @return  void
	 *
	 * @since 2.0
	 */
	public function loadCSSFiles()
	{
		// Support for CSS files
		$cssfiles = $this->getAttribute('cssfiles');

		if (!empty($cssfiles))
		{
			$cssfiles = explode(',', $cssfiles);

			foreach ($cssfiles as $cssfile)
			{
				FOFTemplateUtils::addCSS(trim($cssfile));
			}
		}

		// Support for LESS files
		$lessfiles = $this->getAttribute('lessfiles');

		if (!empty($lessfiles))
		{
			$lessfiles = explode(',', $lessfiles);

			foreach ($lessfiles as $def)
			{
				$parts = explode('||', $def, 2);
				$lessfile = $parts[0];
				$alt = (count($parts) > 1) ? trim($parts[1]) : null;
				FOFTemplateUtils::addLESS(trim($lessfile), $alt);
			}
		}
	}

	/**
	 * Loads the Javascript files defined in the form, based on its jsfiles attribute
	 *
	 * @return  void
	 *
	 * @since 2.0
	 */
	public function loadJSFiles()
	{
		$jsfiles = $this->getAttribute('jsfiles');

		if (empty($jsfiles))
		{
			return;
		}

		$jsfiles = explode(',', $jsfiles);

		foreach ($jsfiles as $jsfile)
		{
			FOFTemplateUtils::addJS(trim($jsfile));
		}
	}

	/**
	 * Returns a reference to the protected $data object, allowing direct
	 * access to and manipulation of the form's data.
	 *
	 * @return   JRegistry  The form's data registry
	 *
	 * @since 2.0
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Attaches a FOFModel to this form
	 *
	 * @param   FOFModel  &$model  The model to attach to the form
	 *
	 * @return  void
	 */
	public function setModel(FOFModel &$model)
	{
		$this->model = $model;
	}

	/**
	 * Returns the FOFModel attached to this form
	 *
	 * @return FOFModel
	 */
	public function &getModel()
	{
		return $this->model;
	}

	/**
	 * Attaches a FOFView to this form
	 *
	 * @param   FOFView  &$view  The view to attach to the form
	 *
	 * @return  void
	 */
	public function setView(FOFView &$view)
	{
		$this->view = $view;
	}

	/**
	 * Returns the FOFView attached to this form
	 *
	 * @return FOFView
	 */
	public function &getView()
	{
		return $this->view;
	}

	/**
	 * Method to get an array of FOFFormHeader objects in the headerset.
	 *
	 * @return  array  The array of FOFFormHeader objects in the headerset.
	 *
	 * @since   2.0
	 */
	public function getHeaderset()
	{
		$fields = array();

		$elements = $this->findHeadersByGroup();

		// If no field elements were found return empty.

		if (empty($elements))
		{
			return $fields;
		}

		// Build the result array from the found field elements.

		foreach ($elements as $element)
		{
			// Get the field groups for the element.
			$attrs = $element->xpath('ancestor::headers[@name]/@name');
			$groups = array_map('strval', $attrs ? $attrs : array());
			$group = implode('.', $groups);

			// If the field is successfully loaded add it to the result array.
			if ($field = $this->loadHeader($element, $group))
			{
				$fields[$field->id] = $field;
			}
		}

		return $fields;
	}

	/**
	 * Method to get an array of <header /> elements from the form XML document which are
	 * in a control group by name.
	 *
	 * @param   mixed    $group   The optional dot-separated form group path on which to find the fields.
	 *                            Null will return all fields. False will return fields not in a group.
	 * @param   boolean  $nested  True to also include fields in nested groups that are inside of the
	 *                            group for which to find fields.
	 *
	 * @return  mixed  Boolean false on error or array of SimpleXMLElement objects.
	 *
	 * @since   2.0
	 */
	protected function &findHeadersByGroup($group = null, $nested = false)
	{
		$false = false;
		$fields = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof SimpleXMLElement))
		{
			return $false;
		}

		// Get only fields in a specific group?
		if ($group)
		{
			// Get the fields elements for a given group.
			$elements = &$this->findHeader($group);

			// Get all of the field elements for the fields elements.
			foreach ($elements as $element)
			{
				// If there are field elements add them to the return result.
				if ($tmp = $element->xpath('descendant::header'))
				{
					// If we also want fields in nested groups then just merge the arrays.
					if ($nested)
					{
						$fields = array_merge($fields, $tmp);
					}

					// If we want to exclude nested groups then we need to check each field.
					else
					{
						$groupNames = explode('.', $group);

						foreach ($tmp as $field)
						{
							// Get the names of the groups that the field is in.
							$attrs = $field->xpath('ancestor::headers[@name]/@name');
							$names = array_map('strval', $attrs ? $attrs : array());

							// If the field is in the specific group then add it to the return list.
							if ($names == (array) $groupNames)
							{
								$fields = array_merge($fields, array($field));
							}
						}
					}
				}
			}
		}
		elseif ($group === false)
		{
			// Get only field elements not in a group.
			$fields = $this->xml->xpath('descendant::headers[not(@name)]/header | descendant::headers[not(@name)]/headerset/header ');
		}
		else
		{
			// Get an array of all the <header /> elements.
			$fields = $this->xml->xpath('//header');
		}

		return $fields;
	}

	/**
	 * Method to get a header field represented as a FOFFormHeader object.
	 *
	 * @param   string  $name   The name of the header field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value  The optional value to use as the default for the field.
	 *
	 * @return  mixed  The FOFFormHeader object for the field or boolean false on error.
	 *
	 * @since   2.0
	 */
	public function getHeader($name, $group = null, $value = null)
	{
		// Make sure there is a valid FOFForm XML document.
		if (!($this->xml instanceof SimpleXMLElement))
		{
			return false;
		}

		// Attempt to find the field by name and group.
		$element = $this->findHeader($name, $group);

		// If the field element was not found return false.
		if (!$element)
		{
			return false;
		}

		return $this->loadHeader($element, $group, $value);
	}

	/**
	 * Method to get a header field represented as an XML element object.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  mixed  The XML element object for the field or boolean false on error.
	 *
	 * @since   2.0
	 */
	protected function findHeader($name, $group = null)
	{
		$element = false;
		$fields = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof SimpleXMLElement))
		{
			return false;
		}

		// Let's get the appropriate field element based on the method arguments.
		if ($group)
		{
			// Get the fields elements for a given group.
			$elements = &$this->findGroup($group);

			// Get all of the field elements with the correct name for the fields elements.
			foreach ($elements as $element)
			{
				// If there are matching field elements add them to the fields array.
				if ($tmp = $element->xpath('descendant::header[@name="' . $name . '"]'))
				{
					$fields = array_merge($fields, $tmp);
				}
			}

			// Make sure something was found.
			if (!$fields)
			{
				return false;
			}

			// Use the first correct match in the given group.
			$groupNames = explode('.', $group);

			foreach ($fields as &$field)
			{
				// Get the group names as strings for ancestor fields elements.
				$attrs = $field->xpath('ancestor::headerfields[@name]/@name');
				$names = array_map('strval', $attrs ? $attrs : array());

				// If the field is in the exact group use it and break out of the loop.
				if ($names == (array) $groupNames)
				{
					$element = &$field;
					break;
				}
			}
		}
		else
		{
			// Get an array of fields with the correct name.
			$fields = $this->xml->xpath('//header[@name="' . $name . '"]');

			// Make sure something was found.
			if (!$fields)
			{
				return false;
			}

			// Search through the fields for the right one.
			foreach ($fields as &$field)
			{
				// If we find an ancestor fields element with a group name then it isn't what we want.
				if ($field->xpath('ancestor::headerfields[@name]'))
				{
					continue;
				}

				// Found it!
				else
				{
					$element = &$field;
					break;
				}
			}
		}

		return $element;
	}

	/**
	 * Method to load, setup and return a FOFFormHeader object based on field data.
	 *
	 * @param   string  $element  The XML element object representation of the form field.
	 * @param   string  $group    The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value    The optional value to use as the default for the field.
	 *
	 * @return  mixed  The FOFFormHeader object for the field or boolean false on error.
	 *
	 * @since   2.0
	 */
	protected function loadHeader($element, $group = null, $value = null)
	{
		// Make sure there is a valid SimpleXMLElement.
		if (!($element instanceof SimpleXMLElement))
		{
			return false;
		}

		// Get the field type.
		$type = $element['type'] ? (string) $element['type'] : 'field';

		// Load the JFormField object for the field.
		$field = $this->loadHeaderType($type);

		// If the object could not be loaded, get a text field object.
		if ($field === false)
		{
			$field = $this->loadHeaderType('field');
		}

		// Setup the FOFFormHeader object.
		$field->setForm($this);

		if ($field->setup($element, $value, $group))
		{
			return $field;
		}
		else
		{
			return false;
		}
	}
	
	/**
	 * Method to remove a header from the form definition.
	 *
	 * @param   string  $name   The name of the form field for which remove.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @throws  UnexpectedValueException
	 */
	public function removeHeader($name, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof SimpleXMLElement))
		{
			throw new UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Find the form field element from the definition.
		$element = $this->findHeader($name, $group);

		// If the element exists remove it from the form definition.
		if ($element instanceof SimpleXMLElement)
		{
			$dom = dom_import_simplexml($element);
			$dom->parentNode->removeChild($dom);

			return true;
		}

		return false;
	}

	/**
	 * Proxy for {@link FOFFormHelper::loadFieldType()}.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  FOFFormField object on success, false otherwise.
	 *
	 * @since   2.0
	 */
	protected function loadFieldType($type, $new = true)
	{
		return FOFFormHelper::loadFieldType($type, $new);
	}

	/**
	 * Proxy for {@link FOFFormHelper::loadHeaderType()}.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  FOFFormHeader object on success, false otherwise.
	 *
	 * @since   2.0
	 */
	protected function loadHeaderType($type, $new = true)
	{
		return FOFFormHelper::loadHeaderType($type, $new);
	}

	/**
	 * Proxy for {@link FOFFormHelper::loadRuleType()}.
	 *
	 * @param   string   $type  The rule type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  JFormRule object on success, false otherwise.
	 *
	 * @see     FOFFormHelper::loadRuleType()
	 * @since   2.0
	 */
	protected function loadRuleType($type, $new = true)
	{
		return FOFFormHelper::loadRuleType($type, $new);
	}

	/**
	 * Proxy for {@link FOFFormHelper::addFieldPath()}.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   2.0
	 */
	public static function addFieldPath($new = null)
	{
		return FOFFormHelper::addFieldPath($new);
	}

	/**
	 * Proxy for {@link FOFFormHelper::addHeaderPath()}.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   2.0
	 */
	public static function addHeaderPath($new = null)
	{
		return FOFFormHelper::addHeaderPath($new);
	}

	/**
	 * Proxy for FOFFormHelper::addFormPath().
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FOFFormHelper::addFormPath()
	 * @since   2.0
	 */
	public static function addFormPath($new = null)
	{
		return FOFFormHelper::addFormPath($new);
	}

	/**
	 * Proxy for FOFFormHelper::addRulePath().
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see FOFFormHelper::addRulePath()
	 * @since   2.0
	 */
	public static function addRulePath($new = null)
	{
		return FOFFormHelper::addRulePath($new);
	}
}
fof/form/field.php000064400000001647152177723700010100 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic interface that a FOF form field class must implement
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
interface FOFFormField
{
	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @return  string  The field HTML
	 *
	 * @since 2.0
	 */
	public function getStatic();

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @return  string  The field HTML
	 *
	 * @since 2.0
	 */
	public function getRepeatable();
}
fof/form/helper.php000064400000013536152177723700010274 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JLoader::import('joomla.form.helper');

/**
 * FOFForm's helper class.
 * Provides a storage for filesystem's paths where FOFForm's entities reside and
 * methods for creating those entities. Also stores objects with entities'
 * prototypes for further reusing.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHelper extends JFormHelper
{
	/**
	 * Method to load a form field object given a type.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  JFormField object on success, false otherwise.
	 *
	 * @since   11.1
	 */
	public static function loadFieldType($type, $new = true)
	{
		return self::loadType('field', $type, $new);
	}

	/**
	 * Method to load a form field object given a type.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  JFormField object on success, false otherwise.
	 *
	 * @since   11.1
	 */
	public static function loadHeaderType($type, $new = true)
	{
		return self::loadType('header', $type, $new);
	}

	/**
	 * Method to load a form entity object given a type.
	 * Each type is loaded only once and then used as a prototype for other objects of same type.
	 * Please, use this method only with those entities which support types (forms don't support them).
	 *
	 * @param   string   $entity  The entity.
	 * @param   string   $type    The entity type.
	 * @param   boolean  $new     Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  Entity object on success, false otherwise.
	 *
	 * @since   11.1
	 */
	protected static function loadType($entity, $type, $new = true)
	{
		// Reference to an array with current entity's type instances
		$types = &self::$entities[$entity];

		$key = md5($type);

		// Return an entity object if it already exists and we don't need a new one.
		if (isset($types[$key]) && $new === false)
		{
			return $types[$key];
		}

		$class = self::loadClass($entity, $type);

		if ($class !== false)
		{
			// Instantiate a new type object.
			$types[$key] = new $class;

			return $types[$key];
		}
		else
		{
			return false;
		}
	}

	/**
	 * Attempt to import the JFormField class file if it isn't already imported.
	 * You can use this method outside of JForm for loading a field for inheritance or composition.
	 *
	 * @param   string  $type  Type of a field whose class should be loaded.
	 *
	 * @return  mixed  Class name on success or false otherwise.
	 *
	 * @since   11.1
	 */
	public static function loadFieldClass($type)
	{
		return self::loadClass('field', $type);
	}

	/**
	 * Attempt to import the FOFFormHeader class file if it isn't already imported.
	 * You can use this method outside of JForm for loading a field for inheritance or composition.
	 *
	 * @param   string  $type  Type of a field whose class should be loaded.
	 *
	 * @return  mixed  Class name on success or false otherwise.
	 *
	 * @since   11.1
	 */
	public static function loadHeaderClass($type)
	{
		return self::loadClass('header', $type);
	}

	/**
	 * Load a class for one of the form's entities of a particular type.
	 * Currently, it makes sense to use this method for the "field" and "rule" entities
	 * (but you can support more entities in your subclass).
	 *
	 * @param   string  $entity  One of the form entities (field or rule).
	 * @param   string  $type    Type of an entity.
	 *
	 * @return  mixed  Class name on success or false otherwise.
	 *
	 * @since   2.0
	 */
	public static function loadClass($entity, $type)
	{
		if (strpos($type, '.'))
		{
			list($prefix, $type) = explode('.', $type);
			$altPrefix = $prefix;
		}
		else
		{
			$prefix = 'FOF';
			$altPrefix = 'J';
		}

		$class = JString::ucfirst($prefix, '_') . 'Form' . JString::ucfirst($entity, '_') . JString::ucfirst($type, '_');
		$altClass = JString::ucfirst($altPrefix, '_') . 'Form' . JString::ucfirst($entity, '_') . JString::ucfirst($type, '_');

		if (class_exists($class))
		{
			return $class;
		}
		elseif (class_exists($altClass))
		{
			return $altClass;
		}

		// Get the field search path array.
		$paths = self::addPath($entity);

		// If the type is complex, add the base type to the paths.
		if ($pos = strpos($type, '_'))
		{
			// Add the complex type prefix to the paths.
			for ($i = 0, $n = count($paths); $i < $n; $i++)
			{
				// Derive the new path.
				$path = $paths[$i] . '/' . strtolower(substr($type, 0, $pos));

				// If the path does not exist, add it.
				if (!in_array($path, $paths))
				{
					$paths[] = $path;
				}
			}

			// Break off the end of the complex type.
			$type = substr($type, $pos + 1);
		}

		// Try to find the class file.
		$type       = strtolower($type) . '.php';
        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		foreach ($paths as $path)
		{
			if ($file = $filesystem->pathFind($path, $type))
			{
				require_once $file;

				if (class_exists($class))
				{
					break;
				}
				elseif (class_exists($altClass))
				{
					break;
				}
			}
		}

		// Check for all if the class exists.
		if (class_exists($class))
		{
			return $class;
		}
		elseif (class_exists($altClass))
		{
			return $altClass;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to add a path to the list of header include paths.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 */
	public static function addHeaderPath($new = null)
	{
		return self::addPath('header', $new);
	}
}
fof/form/header/filtersql.php000064400000001144152177723700012242 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic filter, drop-down based on SQL query
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFiltersql extends FOFFormHeaderFieldsql
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '';
	}
}
fof/form/header/rowselect.php000064400000001323152177723700012243 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Row selection checkbox
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderRowselect extends FOFFormHeader
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '<input type="checkbox" name="checkall-toggle" value="" title="'
			. JText::_('JGLOBAL_CHECK_ALL')
			. '" onclick="Joomla.checkAll(this)" />';
	}
}
fof/form/header/field.php000064400000001640152177723700011321 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, without any filters
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderField extends FOFFormHeader
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		$sortable = ($this->element['sortable'] != 'false');

		$label = $this->getLabel();

		if ($sortable)
		{
			$view = $this->form->getView();

			return JHTML::_('grid.sort', $label, $this->name,
				$view->getLists()->order_Dir, $view->getLists()->order,
				$this->form->getModel()->task
			);
		}
		else
		{
			return JText::_($label);
		}
	}
}
fof/form/header/filterfilterable.php000064400000001172152177723700013555 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic filter, text box entry with optional buttons
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFilterfilterable extends FOFFormHeaderFieldfilterable
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '';
	}
}
fof/form/header/fieldfilterable.php000064400000005367152177723700013365 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, with text input (search) filter
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFieldfilterable extends FOFFormHeaderFieldsearchable
{
	/**
	 * Get the filter field
	 *
	 * @return  string  The HTML
	 */
	protected function getFilter()
	{
		$valide = array('yes', 'true', '1');

		// Initialize some field(s) attributes.
		$size        = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
		$maxLength   = $this->element['maxlength'] ? ' maxlength="' . (int) $this->element['maxlength'] . '"' : '';
		$filterclass = $this->element['filterclass'] ? ' class="' . (string) $this->element['filterclass'] . '"' : '';
		$placeholder = $this->element['placeholder'] ? $this->element['placeholder'] : $this->getLabel();
		$name        = $this->element['searchfieldname'] ? $this->element['searchfieldname'] : $this->name;
		$placeholder = ' placeholder="' . JText::_($placeholder) . '"';

		$single      = in_array($this->element['single'], $valide) ? true : false;
		$showMethod  = in_array($this->element['showmethod'], $valide) ? true : false;
		$method      = $this->element['method'] ? $this->element['method'] : 'between';
		$fromName    = $this->element['fromname'] ? $this->element['fromname'] : 'from';
		$toName      = $this->element['toname'] ? $this->element['toname'] : 'to';

		$values      = $this->form->getModel()->getState($name);
		$fromValue   = $values[$fromName];
		$toValue     = $values[$toName];

		// Initialize JavaScript field attributes.
		if ($this->element['onchange'])
		{
			$onchange = ' onchange="' . (string) $this->element['onchange'] . '"';
		}
		else
		{
			$onchange = ' onchange="document.adminForm.submit();"';
		}

		if ($showMethod)
		{
			$html  = '<input type="text" name="' . $name . '[method]" value="'. $method . '" />';
		} else
		{
			$html  = '<input type="hidden" name="' . $name . '[method]" value="'. $method . '" />';
		}

		$html .= '<input type="text" name="' . $name . '[from]" id="' . $this->id . '_' . $fromName . '"' . ' value="'
				. htmlspecialchars($fromValue, ENT_COMPAT, 'UTF-8') . '"' . $filterclass . $size . $placeholder . $onchange . $maxLength . '/>';

		if (!$single)
		{
			$html .= '<input type="text" name="' . $name . '[to]" id="' . $this->id . '_' . $toName . '"' . ' value="'
				. htmlspecialchars($toValue, ENT_COMPAT, 'UTF-8') . '"' . $filterclass . $size . $placeholder . $onchange . $maxLength . '/>';
		}

		return $html;
	}
}fof/form/header/language.php000064400000002022152177723700012014 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Language field header
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderLanguage extends FOFFormHeaderFieldselectable
{
	/**
	 * Method to get the filter options.
	 *
	 * @return  array  The filter option objects.
	 *
	 * @since   2.0
	 */
	protected function getOptions()
	{
		// Initialize some field attributes.
		$client = (string) $this->element['client'];

		if ($client != 'site' && $client != 'administrator')
		{
			$client = 'site';
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(
			parent::getOptions(), JLanguageHelper::createLanguageList($this->value, constant('JPATH_' . strtoupper($client)), true, true)
		);

		return $options;
	}
}
fof/form/header/fieldsearchable.php000064400000005366152177723700013344 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, with text input (search) filter
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFieldsearchable extends FOFFormHeaderField
{
	/**
	 * Get the filter field
	 *
	 * @return  string  The HTML
	 */
	protected function getFilter()
	{
		// Initialize some field attributes.
		$size        = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
		$maxLength   = $this->element['maxlength'] ? ' maxlength="' . (int) $this->element['maxlength'] . '"' : '';
		$filterclass = $this->element['filterclass'] ? ' class="' . (string) $this->element['filterclass'] . '"' : '';
		$placeholder = $this->element['placeholder'] ? $this->element['placeholder'] : $this->getLabel();
		$name        = $this->element['searchfieldname'] ? $this->element['searchfieldname'] : $this->name;
		$placeholder = ' placeholder="' . JText::_($placeholder) . '"';

		if ($this->element['searchfieldname'])
		{
			$model       = $this->form->getModel();
			$searchvalue = $model->getState((string) $this->element['searchfieldname']);
		}
		else
		{
			$searchvalue = $this->value;
		}

		// Initialize JavaScript field attributes.
		if ($this->element['onchange'])
		{
			$onchange = ' onchange="' . (string) $this->element['onchange'] . '"';
		}
		else
		{
			$onchange = ' onchange="document.adminForm.submit();"';
		}

		return '<input type="text" name="' . $name . '" id="' . $this->id . '"' . ' value="'
			. htmlspecialchars($searchvalue, ENT_COMPAT, 'UTF-8') . '"' . $filterclass . $size . $placeholder . $onchange . $maxLength . '/>';
	}

	/**
	 * Get the buttons HTML code
	 *
	 * @return  string  The HTML
	 */
	protected function getButtons()
	{
		$buttonclass = $this->element['buttonclass'] ? (string) $this->element['buttonclass'] : 'btn hasTip hasTooltip';
		$buttonsState = strtolower($this->element['buttons']);
		$show_buttons = !in_array($buttonsState, array('no', 'false', '0'));

		if (!$show_buttons)
		{
			return '';
		}

		$html = '';

		$html .= '<button class="' . $buttonclass . '" onclick="this.form.submit();" title="' . JText::_('JSEARCH_FILTER') . '" >' . "\n";
		$html .= '<i class="icon-search"></i>';
		$html .= '</button>' . "\n";
		$html .= '<button class="' . $buttonclass . '" onclick="document.adminForm.' . $this->id . '.value=\'\';this.form.submit();" title="' . JText::_('JSEARCH_RESET') . '">' . "\n";
		$html .= '<i class="icon-remove"></i>';
		$html .= '</button>' . "\n";

		return $html;
	}
}
fof/form/header/published.php000064400000002435152177723700012220 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Field header for Published (enabled) columns
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderPublished extends FOFFormHeaderFieldselectable
{
	/**
	 * Create objects for the options
	 *
	 * @return  array  The array of option objects
	 */
	protected function getOptions()
	{
		$config = array(
			'published'		 => 1,
			'unpublished'	 => 1,
			'archived'		 => 0,
			'trash'			 => 0,
			'all'			 => 0,
		);

		$stack = array();

		if ($this->element['show_published'] == 'false')
		{
			$config['published'] = 0;
		}

		if ($this->element['show_unpublished'] == 'false')
		{
			$config['unpublished'] = 0;
		}

		if ($this->element['show_archived'] == 'true')
		{
			$config['archived'] = 1;
		}

		if ($this->element['show_trash'] == 'true')
		{
			$config['trash'] = 1;
		}

		if ($this->element['show_all'] == 'true')
		{
			$config['all'] = 1;
		}

		$options = JHtml::_('jgrid.publishedOptions', $config);

		reset($options);

		return $options;
	}
}
fof/form/header/fieldsql.php000064400000003250152177723700012040 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, with drop down filters based on a SQL query
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFieldsql extends FOFFormHeaderFieldselectable
{
	/**
	 * Create objects for the options
	 *
	 * @return  array  The array of option objects
	 */
	protected function getOptions()
	{
		$options = array();

		// Initialize some field attributes.
		$key       = $this->element['key_field'] ? (string) $this->element['key_field'] : 'value';
		$value     = $this->element['value_field'] ? (string) $this->element['value_field'] : (string) $this->element['name'];
		$translate = $this->element['translate'] ? (string) $this->element['translate'] : false;
		$query     = (string) $this->element['query'];

		// Get the database object.
		$db = FOFPlatform::getInstance()->getDbo();

		// Set the query and get the result list.
		$db->setQuery($query);
		$items = $db->loadObjectlist();

		// Build the field options.
		if (!empty($items))
		{
			foreach ($items as $item)
			{
				if ($translate == true)
				{
					$options[] = JHtml::_('select.option', $item->$key, JText::_($item->$value));
				}
				else
				{
					$options[] = JHtml::_('select.option', $item->$key, $item->$value);
				}
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
fof/form/header/filtersearchable.php000064400000001172152177723700013535 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic filter, text box entry with optional buttons
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFiltersearchable extends FOFFormHeaderFieldsearchable
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '';
	}
}
fof/form/header/model.php000064400000005547152177723700011350 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

if (!class_exists('JFormFieldSql'))
{
	require_once JPATH_LIBRARIES . '/joomla/form/fields/sql.php';
}

/**
 * Form Field class for FOF
 * Generic list from a model's results
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderModel extends FOFFormHeaderFieldselectable
{
	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		$options = array();

		// Initialize some field attributes.
		$key = $this->element['key_field'] ? (string) $this->element['key_field'] : 'value';
		$value = $this->element['value_field'] ? (string) $this->element['value_field'] : (string) $this->element['name'];
		$applyAccess = $this->element['apply_access'] ? (string) $this->element['apply_access'] : 'false';
		$modelName = (string) $this->element['model'];
		$nonePlaceholder = (string) $this->element['none'];
		$translate = empty($this->element['translate']) ? 'true' : (string) $this->element['translate'];
		$translate = in_array(strtolower($translate), array('true','yes','1','on')) ? true : false;

		if (!empty($nonePlaceholder))
		{
			$options[] = JHtml::_('select.option', null, JText::_($nonePlaceholder));
		}

		// Process field atrtibutes
		$applyAccess = strtolower($applyAccess);
		$applyAccess = in_array($applyAccess, array('yes', 'on', 'true', '1'));

		// Explode model name into model name and prefix
		$parts = FOFInflector::explode($modelName);
		$mName = ucfirst(array_pop($parts));
		$mPrefix = FOFInflector::implode($parts);

		// Get the model object
		$config = array('savestate' => 0);
		$model = FOFModel::getTmpInstance($mName, $mPrefix, $config);

		if ($applyAccess)
		{
			$model->applyAccessFiltering();
		}

		// Process state variables
		foreach ($this->element->children() as $stateoption)
		{
			// Only add <option /> elements.
			if ($stateoption->getName() != 'state')
			{
				continue;
			}

			$stateKey = (string) $stateoption['key'];
			$stateValue = (string) $stateoption;

			$model->setState($stateKey, $stateValue);
		}

		// Set the query and get the result list.
		$items = $model->getItemList(true);

		// Build the field options.
		if (!empty($items))
		{
			foreach ($items as $item)
			{
				if ($translate == true)
				{
					$options[] = JHtml::_('select.option', $item->$key, JText::_($item->$value));
				}
				else
				{
					$options[] = JHtml::_('select.option', $item->$key, $item->$value);
				}
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
fof/form/header/accesslevel.php000064400000002027152177723700012527 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Access level field header
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderAccesslevel extends FOFFormHeaderFieldselectable
{
	/**
	 * Method to get the list of access levels
	 *
	 * @return  array	A list of access levels.
	 *
	 * @since   2.0
	 */
	protected function getOptions()
	{
		$db    = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);

		$query->select('a.id AS value, a.title AS text');
		$query->from('#__viewlevels AS a');
		$query->group('a.id, a.title, a.ordering');
		$query->order('a.ordering ASC');
		$query->order($query->qn('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();

		return $options;
	}
}
fof/form/header/fieldselectable.php000064400000006425152177723700013353 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, with drop down filters
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFieldselectable extends FOFFormHeaderField
{
	/**
	 * Create objects for the options
	 *
	 * @return  array  The array of option objects
	 */
	protected function getOptions()
	{
		$options = array();

		// Get the field $options
		foreach ($this->element->children() as $option)
		{
			// Only add <option /> elements.
			if ($option->getName() != 'option')
			{
				continue;
			}

			// Create a new option object based on the <option /> element.
			$options[] = JHtml::_(
				'select.option',
				(string) $option['value'],
				JText::alt(
					trim((string) $option),
					preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)
				),
				'value', 'text', ((string) $option['disabled'] == 'true')
			);
		}

		// Do we have a class and method source for our options?
		$source_file = empty($this->element['source_file']) ? '' : (string) $this->element['source_file'];
		$source_class = empty($this->element['source_class']) ? '' : (string) $this->element['source_class'];
		$source_method = empty($this->element['source_method']) ? '' : (string) $this->element['source_method'];
		$source_key = empty($this->element['source_key']) ? '*' : (string) $this->element['source_key'];
		$source_value = empty($this->element['source_value']) ? '*' : (string) $this->element['source_value'];
		$source_translate = empty($this->element['source_translate']) ? 'true' : (string) $this->element['source_translate'];
		$source_translate = in_array(strtolower($source_translate), array('true','yes','1','on')) ? true : false;
		$source_format = empty($this->element['source_format']) ? '' : (string) $this->element['source_format'];

		if ($source_class && $source_method)
		{
			// Maybe we have to load a file?
			if (!empty($source_file))
			{
				$source_file = FOFTemplateUtils::parsePath($source_file, true);

				if (FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileExists($source_file))
				{
					include_once $source_file;
				}
			}

			// Make sure the class exists
			if (class_exists($source_class, true))
			{
				// ...and so does the option
				if (in_array($source_method, get_class_methods($source_class)))
				{
					// Get the data from the class
					if ($source_format == 'optionsobject')
					{
						$options = array_merge($options, $source_class::$source_method());
					}
					else
					{
						$source_data = $source_class::$source_method();

						// Loop through the data and prime the $options array
						foreach ($source_data as $k => $v)
						{
							$key = (empty($source_key) || ($source_key == '*')) ? $k : $v[$source_key];
							$value = (empty($source_value) || ($source_value == '*')) ? $v : $v[$source_value];

							if ($source_translate)
							{
								$value = JText::_($value);
							}

							$options[] = JHtml::_('select.option', $key, $value, 'value', 'text');
						}
					}
				}
			}
		}

		reset($options);

		return $options;
	}
}
fof/form/header/filterdate.php000064400000001157152177723700012364 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic filter, text box entry with calendar button
 *
 * @package  FrameworkOnFramework
 * @since    2.3.3
 */
class FOFFormHeaderFilterdate extends FOFFormHeaderFielddate
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '';
	}
}
fof/form/header/filterselectable.php000064400000001166152177723700013552 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic filter, drop-down based on fixed options
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFilterselectable extends FOFFormHeaderFieldselectable
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		return '';
	}
}
fof/form/header/ordering.php000064400000003467152177723700012060 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Ordering field header
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderOrdering extends FOFFormHeader
{
	/**
	 * Get the header
	 *
	 * @return  string  The header HTML
	 */
	protected function getHeader()
	{
		$sortable = ($this->element['sortable'] != 'false');

		$view = $this->form->getView();
		$model = $this->form->getModel();

		$hasAjaxOrderingSupport = $view->hasAjaxOrderingSupport();

		if (!$sortable)
		{
			// Non sortable?! I'm not sure why you'd want that, but if you insist...
			return JText::_('JGRID_HEADING_ORDERING');
		}

		if (!$hasAjaxOrderingSupport)
		{
			// Ye olde Joomla! 2.5 method
			$html = JHTML::_('grid.sort', 'JFIELD_ORDERING_LABEL', 'ordering', $view->getLists()->order_Dir, $view->getLists()->order, 'browse');
			$html .= JHTML::_('grid.order', $model->getList());

			return $html;
		}
		else
		{
			// The new, drag'n'drop ordering support WITH a save order button
			$html = JHtml::_(
				'grid.sort',
				'<i class="icon-menu-2"></i>',
				'ordering',
				$view->getLists()->order_Dir,
				$view->getLists()->order,
				null,
				'asc',
				'JGRID_HEADING_ORDERING'
			);

			$ordering = $view->getLists()->order == 'ordering';

			if ($ordering)
			{
				$html .= '<a href="javascript:saveorder(' . (count($model->getList()) - 1) . ', \'saveorder\')" ' .
					'rel="tooltip" class="save-order btn btn-micro pull-right" title="' . JText::_('JLIB_HTML_SAVE_ORDER') . '">'
					. '<span class="icon-ok"></span></a>';
			}

			return $html;
		}
	}
}
fof/form/header/fielddate.php000064400000006317152177723700012165 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Generic field header, with text input (search) filter
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormHeaderFielddate extends FOFFormHeaderField
{
	/**
	 * Get the filter field
	 *
	 * @return  string  The HTML
	 */
	protected function getFilter()
	{
		// Initialize some field attributes.
		$format		 = $this->element['format'] ? (string) $this->element['format'] : '%Y-%m-%d';
		$attributes  = array();

		if ($this->element['size'])
		{
			$attributes['size'] = (int) $this->element['size'];
		}

		if ($this->element['maxlength'])
		{
			$attributes['maxlength'] = (int) $this->element['maxlength'];
		}

		if ($this->element['filterclass'])
		{
			$attributes['class'] = (string) $this->element['filterclass'];
		}

		if ((string) $this->element['readonly'] == 'true')
		{
			$attributes['readonly'] = 'readonly';
		}

		if ((string) $this->element['disabled'] == 'true')
		{
			$attributes['disabled'] = 'disabled';
		}

		if ($this->element['onchange'])
		{
			$attributes['onchange'] = (string) $this->element['onchange'];
		}
		else
		{
			$onchange = 'document.adminForm.submit()';
		}

		if ((string) $this->element['placeholder'])
		{
			$attributes['placeholder'] = JText::_((string) $this->element['placeholder']);
		}

		$name = $this->element['searchfieldname'] ? $this->element['searchfieldname'] : $this->name;

		if ($this->element['searchfieldname'])
		{
			$model       = $this->form->getModel();
			$searchvalue = $model->getState((string) $this->element['searchfieldname']);
		}
		else
		{
			$searchvalue = $this->value;
		}

		// Get some system objects.
		$config = FOFPlatform::getInstance()->getConfig();
		$user   = JFactory::getUser();

		// If a known filter is given use it.
		switch (strtoupper((string) $this->element['filter']))
		{
			case 'SERVER_UTC':
				// Convert a date to UTC based on the server timezone.
				if ((int) $this->value)
				{
					// Get a date object based on the correct timezone.
					$date = FOFPlatform::getInstance()->getDate($searchvalue, 'UTC');
					$date->setTimezone(new DateTimeZone($config->get('offset')));

					// Transform the date string.
					$searchvalue = $date->format('Y-m-d H:i:s', true, false);
				}
				break;

			case 'USER_UTC':
				// Convert a date to UTC based on the user timezone.
				if ((int) $searchvalue)
				{
					// Get a date object based on the correct timezone.
					$date = FOFPlatform::getInstance()->getDate($this->value, 'UTC');
					$date->setTimezone($user->getTimezone());

					// Transform the date string.
					$searchvalue = $date->format('Y-m-d H:i:s', true, false);
				}
				break;
		}

		return JHtml::_('calendar', $searchvalue, $name, $name, $format, $attributes);
	}

	/**
	 * Get the buttons HTML code
	 *
	 * @return  string  The HTML
	 */
	protected function getButtons()
	{
		return '';
	}
}
fof/form/header.php000064400000026476152177723700010254 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * An interface for FOFFormHeader fields, used to define the filters and the
 * elements of the header row in repeatable (browse) views
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
abstract class FOFFormHeader
{
	/**
	 * The description text for the form field.  Usually used in tooltips.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $description;

	/**
	 * The SimpleXMLElement object of the <field /> XML element that describes the header field.
	 *
	 * @var    SimpleXMLElement
	 * @since  2.0
	 */
	protected $element;

	/**
	 * The FOFForm object of the form attached to the header field.
	 *
	 * @var    FOFForm
	 * @since  2.0
	 */
	protected $form;

	/**
	 * The label for the header field.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $label;

	/**
	 * The header HTML.
	 *
	 * @var    string|null
	 * @since  2.0
	 */
	protected $header;

	/**
	 * The filter HTML.
	 *
	 * @var    string|null
	 * @since  2.0
	 */
	protected $filter;

	/**
	 * The buttons HTML.
	 *
	 * @var    string|null
	 * @since  2.0
	 */
	protected $buttons;

	/**
	 * The options for a drop-down filter.
	 *
	 * @var    array|null
	 * @since  2.0
	 */
	protected $options;

	/**
	 * The name of the form field.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $name;

	/**
	 * The name of the field.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $fieldname;

	/**
	 * The group of the field.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $group;

	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  2.0
	 */
	protected $type;

	/**
	 * The value of the filter.
	 *
	 * @var    mixed
	 * @since  2.0
	 */
	protected $value;

	/**
	 * The intended table data width (in pixels or percent).
	 *
	 * @var    mixed
	 * @since  2.0
	 */
	protected $tdwidth;

	/**
	 * The key of the filter value in the model state.
	 *
	 * @var    mixed
	 * @since  2.0
	 */
	protected $filterSource;

	/**
	 * Is this a sortable column?
	 *
	 * @var    bool
	 * @since  2.0
	 */
	protected $sortable = false;

	/**
	 * Method to instantiate the form field object.
	 *
	 * @param   FOFForm  $form  The form to attach to the form field object.
	 *
	 * @since   2.0
	 */
	public function __construct(FOFForm $form = null)
	{
		// If there is a form passed into the constructor set the form and form control properties.
		if ($form instanceof FOFForm)
		{
			$this->form = $form;
		}
	}

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'description':
			case 'name':
			case 'type':
			case 'fieldname':
			case 'group':
			case 'tdwidth':
				return $this->$name;
				break;

			case 'label':
				if (empty($this->label))
				{
					$this->label = $this->getLabel();
				}

				return $this->label;

			case 'value':
				if (empty($this->value))
				{
					$this->value = $this->getValue();
				}

				return $this->value;
				break;

			case 'header':
				if (empty($this->header))
				{
					$this->header = $this->getHeader();
				}

				return $this->header;
				break;

			case 'filter':
				if (empty($this->filter))
				{
					$this->filter = $this->getFilter();
				}

				return $this->filter;
				break;

			case 'buttons':
				if (empty($this->buttons))
				{
					$this->buttons = $this->getButtons();
				}

				return $this->buttons;
				break;

			case 'options':
				if (empty($this->options))
				{
					$this->options = $this->getOptions();
				}

				return $this->options;
				break;

			case 'sortable':
				if (empty($this->sortable))
				{
					$this->sortable = $this->getSortable();
				}

				return $this->sortable;
				break;
		}

		return null;
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   FOFForm  $form  The JForm object to attach to the form field.
	 *
	 * @return  FOFFormHeader  The form field object so that the method can be used in a chain.
	 *
	 * @since   2.0
	 */
	public function setForm(FOFForm $form)
	{
		$this->form = $form;

		return $this;
	}

	/**
	 * Method to attach a FOFForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the <field /> tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   2.0
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		// Make sure there is a valid JFormField XML element.
		if ((string) $element->getName() != 'header')
		{
			return false;
		}

		// Reset the internal fields
		$this->label = null;
		$this->header = null;
		$this->filter = null;
		$this->buttons = null;
		$this->options = null;
		$this->value = null;
		$this->filterSource = null;

		// Set the XML element object.
		$this->element = $element;

		// Get some important attributes from the form field element.
		$class = (string) $element['class'];
		$id = (string) $element['id'];
		$name = (string) $element['name'];
		$filterSource = (string) $element['filter_source'];
		$tdwidth = (string) $element['tdwidth'];

		// Set the field description text.
		$this->description = (string) $element['description'];

		// Set the group of the field.
		$this->group = $group;

		// Set the td width of the field.
		$this->tdwidth = $tdwidth;

		// Set the field name and id.
		$this->fieldname = $this->getFieldName($name);
		$this->name = $this->getName($this->fieldname);
		$this->id = $this->getId($id, $this->fieldname);
		$this->filterSource = $this->getFilterSource($filterSource);

		// Set the field default value.
		$this->value = $this->getValue();

		return true;
	}

	/**
	 * Method to get the id used for the field input tag.
	 *
	 * @param   string  $fieldId    The field element id.
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The id to be used for the field input tag.
	 *
	 * @since   2.0
	 */
	protected function getId($fieldId, $fieldName)
	{
		$id = '';

		// If the field is in a group add the group control to the field id.

		if ($this->group)
		{
			// If we already have an id segment add the group control as another level.

			if ($id)
			{
				$id .= '_' . str_replace('.', '_', $this->group);
			}
			else
			{
				$id .= str_replace('.', '_', $this->group);
			}
		}

		// If we already have an id segment add the field id/name as another level.

		if ($id)
		{
			$id .= '_' . ($fieldId ? $fieldId : $fieldName);
		}
		else
		{
			$id .= ($fieldId ? $fieldId : $fieldName);
		}

		// Clean up any invalid characters.
		$id = preg_replace('#\W#', '_', $id);

		return $id;
	}

	/**
	 * Method to get the name used for the field input tag.
	 *
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The name to be used for the field input tag.
	 *
	 * @since   2.0
	 */
	protected function getName($fieldName)
	{
		$name = '';

		// If the field is in a group add the group control to the field name.

		if ($this->group)
		{
			// If we already have a name segment add the group control as another level.
			$groups = explode('.', $this->group);

			if ($name)
			{
				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
			else
			{
				$name .= array_shift($groups);

				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
		}

		// If we already have a name segment add the field name as another level.

		if ($name)
		{
			$name .= '[' . $fieldName . ']';
		}
		else
		{
			$name .= $fieldName;
		}

		return $name;
	}

	/**
	 * Method to get the field name used.
	 *
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The field name
	 *
	 * @since   2.0
	 */
	protected function getFieldName($fieldName)
	{
		return $fieldName;
	}

	/**
	 * Method to get the field label.
	 *
	 * @return  string  The field label.
	 *
	 * @since   2.0
	 */
	protected function getLabel()
	{
		// Get the label text from the XML element, defaulting to the element name.
		$title = $this->element['label'] ? (string) $this->element['label'] : '';

		if (empty($title))
		{
			$view = $this->form->getView();
			$params = $view->getViewOptionAndName();
			$title = $params['option'] . '_' .
				FOFInflector::pluralize($params['view']) . '_FIELD_' .
				(string) $this->element['name'];
			$title = strtoupper($title);
			$result = JText::_($title);

			if ($result === $title)
			{
				$title = ucfirst((string) $this->element['name']);
			}
		}

		return $title;
	}

	/**
	 * Get the filter value for this header field
	 *
	 * @return  mixed  The filter value
	 */
	protected function getValue()
	{
		$model = $this->form->getModel();

		return $model->getState($this->filterSource);
	}

	/**
	 * Return the key of the filter value in the model state or, if it's not set,
	 * the name of the field.
	 *
	 * @param   string  $filterSource  The filter source value to return
	 *
	 * @return  string
	 */
	protected function getFilterSource($filterSource)
	{
		if ($filterSource)
		{
			return $filterSource;
		}
		else
		{
			return $this->name;
		}
	}

	/**
	 * Is this a sortable field?
	 *
	 * @return  boolean  True if it's sortable
	 */
	protected function getSortable()
	{
		$sortable = ($this->element['sortable'] != 'false');

		if ($sortable)
		{
			if (empty($this->header))
			{
				$this->header = $this->getHeader();
			}

			$sortable = !empty($this->header);
		}

		return $sortable;
	}

	/**
	 * Returns the HTML for the header row, or null if this element should
	 * render no header element
	 *
	 * @return  string|null  HTML code or null if nothing is to be rendered
	 *
	 * @since 2.0
	 */
	protected function getHeader()
	{
		return null;
	}

	/**
	 * Returns the HTML for a text filter to be rendered in the filter row,
	 * or null if this element should render no text input filter.
	 *
	 * @return  string|null  HTML code or null if nothing is to be rendered
	 *
	 * @since 2.0
	 */
	protected function getFilter()
	{
		return null;
	}

	/**
	 * Returns the HTML for the buttons to be rendered in the filter row,
	 * next to the text input filter, or null if this element should render no
	 * text input filter buttons.
	 *
	 * @return  string|null  HTML code or null if nothing is to be rendered
	 *
	 * @since 2.0
	 */
	protected function getButtons()
	{
		return null;
	}

	/**
	 * Returns the JHtml options for a drop-down filter. Do not include an
	 * empty option, it is added automatically.
	 *
	 * @return  array  The JHtml options for a drop-down filter
	 *
	 * @since 2.0
	 */
	protected function getOptions()
	{
		return array();
	}
}
fof/form/field/url.php000064400000006753152177723700010705 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('url');

/**
 * Form Field class for the FOF framework
 * Supports a URL text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldUrl extends JFormFieldUrl implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class  = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
		$dolink = $this->element['show_link'] == 'true';
		$empty_replacement = '';

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$innerHtml = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		if ($dolink)
		{
			$innerHtml = '<a href="' . $innerHtml . '">' .
				$innerHtml . '</a>';
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			$innerHtml .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$class             = $this->id;
		$show_link         = false;
		$empty_replacement = '';

		$link_url = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Get field parameters
		if ($this->element['class'])
		{
			$class .= ' ' . (string) $this->element['class'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		// Get the (optionally formatted) value
		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Create the HTML
		$html = '<span class="' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= $value;

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}
}
fof/form/field/plugins.php000064400000004663152177723700011562 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('plugins');

/**
 * Form Field class for FOF
 * Plugins installed on the site
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldPlugins extends JFormFieldPlugins implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/user.php000064400000015557152177723700011063 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('user');

/**
 * Form Field class for the FOF framework
 * A user selection box / display field
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldUser extends JFormFieldUser implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		// Initialise
		$show_username = true;
		$show_email    = false;
		$show_name     = false;
		$show_id       = false;
		$class         = '';

		// Get the field parameters
		if ($this->element['class'])
		{
			$class = ' class="' . (string) $this->element['class'] . '"';
		}

		if ($this->element['show_username'] == 'false')
		{
			$show_username = false;
		}

		if ($this->element['show_email'] == 'true')
		{
			$show_email = true;
		}

		if ($this->element['show_name'] == 'true')
		{
			$show_name = true;
		}

		if ($this->element['show_id'] == 'true')
		{
			$show_id = true;
		}

		// Get the user record
		$user = JFactory::getUser($this->value);

		// Render the HTML
		$html = '<div id="' . $this->id . '" ' . $class . '>';

		if ($show_username)
		{
			$html .= '<span class="fof-userfield-username">' . $user->username . '</span>';
		}

		if ($show_id)
		{
			$html .= '<span class="fof-userfield-id">' . $user->id . '</span>';
		}

		if ($show_name)
		{
			$html .= '<span class="fof-userfield-name">' . $user->name . '</span>';
		}

		if ($show_email)
		{
			$html .= '<span class="fof-userfield-email">' . $user->email . '</span>';
		}

		$html .= '</div>';

		return $html;
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$show_username = true;
		$show_email    = true;
		$show_name     = true;
		$show_id       = true;
		$show_avatar   = true;
		$show_link     = false;
		$link_url      = null;
		$avatar_method = 'gravatar';
		$avatar_size   = 64;
		$class         = '';

		// Get the user record
		$user = JFactory::getUser($this->value);

		// Get the field parameters
		if ($this->element['class'])
		{
			$class = ' class="' . (string) $this->element['class'] . '"';
		}

		if ($this->element['show_username'] == 'false')
		{
			$show_username = false;
		}

		if ($this->element['show_email'] == 'false')
		{
			$show_email = false;
		}

		if ($this->element['show_name'] == 'false')
		{
			$show_name = false;
		}

		if ($this->element['show_id'] == 'false')
		{
			$show_id = false;
		}

		if ($this->element['show_avatar'] == 'false')
		{
			$show_avatar = false;
		}

		if ($this->element['avatar_method'])
		{
			$avatar_method = strtolower($this->element['avatar_method']);
		}

		if ($this->element['avatar_size'])
		{
			$avatar_size = $this->element['avatar_size'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['link_url'])
		{
			$link_url = $this->element['link_url'];
		}
		else
		{
			if (FOFPlatform::getInstance()->isBackend())
			{
				// If no link is defined in the back-end, assume the user edit
				// link in the User Manager component
				$link_url = 'index.php?option=com_users&task=user.edit&id=[USER:ID]';
			}
			else
			{
				// If no link is defined in the front-end, we can't create a
				// default link. Therefore, show no link.
				$show_link = false;
			}
		}

		// Post-process the link URL
		if ($show_link)
		{
			$replacements = array(
				'[USER:ID]'			 => $user->id,
				'[USER:USERNAME]'	 => $user->username,
				'[USER:EMAIL]'		 => $user->email,
				'[USER:NAME]'		 => $user->name,
			);

			foreach ($replacements as $key => $value)
			{
				$link_url = str_replace($key, $value, $link_url);
			}
		}

		// Get the avatar image, if necessary
		if ($show_avatar)
		{
			$avatar_url = '';

			if ($avatar_method == 'plugin')
			{
				// Use the user plugins to get an avatar
				FOFPlatform::getInstance()->importPlugin('user');
				$jResponse = FOFPlatform::getInstance()->runPlugins('onUserAvatar', array($user, $avatar_size));

				if (!empty($jResponse))
				{
					foreach ($jResponse as $response)
					{
						if ($response)
						{
							$avatar_url = $response;
						}
					}
				}

				if (empty($avatar_url))
				{
					$show_avatar = false;
				}
			}
			else
			{
				// Fall back to the Gravatar method
				$md5 = md5($user->email);

				if (FOFPlatform::getInstance()->isCli())
				{
					$scheme = 'http';
				}
				else
				{
					$scheme = JURI::getInstance()->getScheme();
				}

				if ($scheme == 'http')
				{
					$avatar_url = 'http://www.gravatar.com/avatar/' . $md5 . '.jpg?s='
						. $avatar_size . '&d=mm';
				}
				else
				{
					$avatar_url = 'https://secure.gravatar.com/avatar/' . $md5 . '.jpg?s='
						. $avatar_size . '&d=mm';
				}
			}
		}

		// Generate the HTML
		$html = '<div id="' . $this->id . '" ' . $class . '>';

		if ($show_avatar)
		{
			$html .= '<img src="' . $avatar_url . '" align="left" class="fof-usersfield-avatar" />';
		}

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		if ($show_username)
		{
			$html .= '<span class="fof-usersfield-username">' . $user->username
				. '</span>';
		}

		if ($show_id)
		{
			$html .= '<span class="fof-usersfield-id">' . $user->id
				. '</span>';
		}

		if ($show_name)
		{
			$html .= '<span class="fof-usersfield-name">' . $user->name
				. '</span>';
		}

		if ($show_email)
		{
			$html .= '<span class="fof-usersfield-email">' . $user->email
				. '</span>';
		}

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</div>';

		return $html;
	}
}
fof/form/field/groupedbutton.php000064400000005737152177723700013005 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldGroupedbutton extends JFormFieldText implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getInput()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$html = '<div id="' . $this->id . '" class="btn-group ' . $class . '">';

		foreach ($this->element->children() as $option)
		{
			$renderedAttributes = array();

			foreach ($option->attributes() as $name => $value)
			{
				if (!is_null($value))
				{
					$renderedAttributes[] = $name . '="' . htmlentities($value) . '"';
				}
			}

			$buttonXML   = new SimpleXMLElement('<field ' . implode(' ', $renderedAttributes) . ' />');
			$buttonField = new FOFFormFieldButton($this->form);

			// Pass required objects to the field
			$buttonField->item = $this->item;
			$buttonField->rowid = $this->rowid;
			$buttonField->setup($buttonXML, null);

			$html .= $buttonField->getRepeatable();
		}
		$html .= '</div>';

		return $html;
	}
}
fof/form/field/hidden.php000064400000003711152177723700011325 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('hidden');

/**
 * Form Field class for the FOF framework
 * A hidden field
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldHidden extends JFormFieldHidden implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getInput();
	}
}
fof/form/field/components.php000064400000013665152177723700012270 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Components installed on the site
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFFormFieldComponents extends JFormFieldList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	public $client_ids = null;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.1
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.1
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.1
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get a list of all installed components and also translates them.
	 *
	 * The manifest_cache is used to get the extension names, since JInstaller is also
	 * translating those names in stead of the name column. Else some of the translations
	 * fails.
	 *
	 * @since    2.1
	 *
	 * @return 	array	An array of JHtml options.
	 */
	protected function getOptions()
	{
		$db = FOFPlatform::getInstance()->getDbo();

		// Check for client_ids override
		if ($this->client_ids !== null)
		{
			$client_ids = $this->client_ids;
		}
		else
		{
			$client_ids = $this->element['client_ids'];
		}

		$client_ids = explode(',', $client_ids);

		// Calculate client_ids where clause
		foreach ($client_ids as &$client_id)
		{
			$client_id = (int) trim($client_id);
			$client_id = $db->q($client_id);
		}

		$query = $db->getQuery(true)
			->select(
				array(
					$db->qn('name'),
					$db->qn('element'),
					$db->qn('client_id'),
					$db->qn('manifest_cache'),
				)
			)
			->from($db->qn('#__extensions'))
			->where($db->qn('type') . ' = ' . $db->q('component'))
			->where($db->qn('client_id') . ' IN (' . implode(',', $client_ids) . ')');
		$db->setQuery($query);
		$components = $db->loadObjectList('element');

		// Convert to array of objects, so we can use sortObjects()
		// Also translate component names with JText::_()
		$aComponents = array();
		$user = JFactory::getUser();

		foreach ($components as $component)
		{
			// Don't show components in the list where the user doesn't have access for
			// TODO: perhaps add an option for this
			if (!$user->authorise('core.manage', $component->element))
			{
				continue;
			}

			$oData = (object) array(
				'value'	=> $component->element,
				'text' 	=> $this->translate($component, 'component')
			);
			$aComponents[$component->element] = $oData;
		}

		// Reorder the components array, because the alphabetical
		// ordering changed due to the JText::_() translation
		uasort(
			$aComponents,
			function ($a, $b) {
				return strcasecmp($a->text, $b->text);
			}
		);

		return $aComponents;
	}

	/**
	 * Translate a list of objects with JText::_().
	 *
	 * @param   array   $item  The array of objects
	 * @param   string  $type  The extension type (e.g. component)
	 *
	 * @since   2.1
	 *
	 * @return  string  $text  The translated name of the extension
	 *
	 * @see administrator/com_installer/models/extension.php
	 */
	public function translate($item, $type)
	{
        $platform = FOFPlatform::getInstance();

		// Map the manifest cache to $item. This is needed to get the name from the
		// manifest_cache and NOT from the name column, else some JText::_() translations fails.
		$mData = json_decode($item->manifest_cache);

		if ($mData)
		{
			foreach ($mData as $key => $value)
			{
				if ($key == 'type')
				{
					// Ignore the type field
					continue;
				}

				$item->$key = $value;
			}
		}

		$lang = $platform->getLanguage();

		switch ($type)
		{
			case 'component':
				$source = JPATH_ADMINISTRATOR . '/components/' . $item->element;
				$lang->load("$item->element.sys", JPATH_ADMINISTRATOR, null, false, false)
					||	$lang->load("$item->element.sys", $source, null, false, false)
					||	$lang->load("$item->element.sys", JPATH_ADMINISTRATOR, $lang->getDefault(), false, false)
					||	$lang->load("$item->element.sys", $source, $lang->getDefault(), false, false);
				break;
		}

		$text = JText::_($item->name);

		return $text;
	}
}
fof/form/field/checkbox.php000064400000007235152177723700011665 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('checkbox');

/**
 * Form Field class for the FOF framework
 * A single checkbox
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldCheckbox extends JFormFieldCheckbox implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
		$value = $this->element['value'] ? (string) $this->element['value'] : '1';
		$disabled = ((string) $this->element['disabled'] == 'true') ? ' disabled="disabled"' : '';
		$onclick = $this->element['onclick'] ? ' onclick="' . (string) $this->element['onclick'] . '"' : '';
		$required = $this->required ? ' required="required" aria-required="true"' : '';

		if (empty($this->value))
		{
			$checked = (isset($this->element['checked'])) ? ' checked="checked"' : '';
		}
		else
		{
			$checked = ' checked="checked"';
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			'<input type="checkbox" name="' . $this->name . '" id="' . $this->id . '"' . ' value="'
			. htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"' . $class . $checked . $disabled . $onclick . $required . ' />' .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';
		$value = $this->element['value'] ? (string) $this->element['value'] : '1';
		$disabled = ((string) $this->element['disabled'] == 'true') ? ' disabled="disabled"' : '';
		$onclick = $this->element['onclick'] ? ' onclick="' . (string) $this->element['onclick'] . '"' : '';
		$required = $this->required ? ' required="required" aria-required="true"' : '';

		if (empty($this->value))
		{
			$checked = (isset($this->element['checked'])) ? ' checked="checked"' : '';
		}
		else
		{
			$checked = ' checked="checked"';
		}

		return '<span class="' . $this->id . ' ' . $class . '">' .
			'<input type="checkbox" name="' . $this->name . '" class="' . $this->id . ' ' . $class . '"' . ' value="'
			. htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"' . $checked . $disabled . $onclick . $required . ' />' .
			'</span>';
	}
}
fof/form/field/tag.php000064400000011633152177723700010647 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('tag');

/**
 * Form Field class for FOF
 * Tag Fields
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFFormFieldTag extends JFormFieldTag implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Method to get a list of tags
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.1
	 */
	protected function getOptions()
	{
		$options = array();

		$published = $this->element['published']? $this->element['published'] : array(0,1);

		$db		= FOFPlatform::getInstance()->getDbo();
		$query	= $db->getQuery(true)
			->select('DISTINCT a.id AS value, a.path, a.title AS text, a.level, a.published, a.lft')
			->from('#__tags AS a')
			->join('LEFT', $db->quoteName('#__tags') . ' AS b ON a.lft > b.lft AND a.rgt < b.rgt');

		if ($this->item instanceof FOFTable)
		{
			$item = $this->item;
		}
		else
		{
			$item = $this->form->getModel()->getItem();
		}

		if ($item instanceof FOFTable)
		{
			// Fake value for selected tags
			$keyfield = $item->getKeyName();
			$content_id  = $item->$keyfield;
			$type = $item->getContentType();

			$selected_query = $db->getQuery(true);
			$selected_query
				->select('tag_id')
				->from('#__contentitem_tag_map')
				->where('content_item_id = ' . (int) $content_id)
				->where('type_alias = ' . $db->quote($type));

			$db->setQuery($selected_query);

			$this->value = $db->loadColumn();
		}

		// Filter language
		if (!empty($this->element['language']))
		{
			$query->where('a.language = ' . $db->quote($this->element['language']));
		}

		$query->where($db->qn('a.lft') . ' > 0');

		// Filter to only load active items

		// Filter on the published state
		if (is_numeric($published))
		{
			$query->where('a.published = ' . (int) $published);
		}
		elseif (is_array($published))
		{
            FOFUtilsArray::toInteger($published);
			$query->where('a.published IN (' . implode(',', $published) . ')');
		}

		$query->order('a.lft ASC');

		// Get the options.
		$db->setQuery($query);

		try
		{
			$options = $db->loadObjectList();
		}
		catch (RuntimeException $e)
		{
			return false;
		}

		// Prepare nested data
		if ($this->isNested())
		{
			$this->prepareOptionsNested($options);
		}
		else
		{
			$options = JHelperTags::convertPathsToNames($options);
		}

		return $options;
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class     = $this->element['class'] ? (string) $this->element['class'] : '';
		$translate = $this->element['translate'] ? (string) $this->element['translate'] : false;

		$options = $this->getOptions();

		$html = '';

		foreach ($options as $option) {

			$html .= '<span>';

			if ($translate == true)
			{
				$html .= JText::_($option->text);
			}
			else
			{
				$html .= $option->text;
			}

			$html .= '</span>';
		}

		return '<span id="' . $this->id . '" class="' . $class . '">' .
			$html .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.1
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class     = $this->element['class'] ? (string) $this->element['class'] : '';
		$translate = $this->element['translate'] ? (string) $this->element['translate'] : false;

		$options = $this->getOptions();

		$html = '';

		foreach ($options as $option) {

			$html .= '<span>';

			if ($translate == true)
			{
				$html .= JText::_($option->text);
			}
			else
			{
				$html .= $option->text;
			}

			$html .= '</span>';
		}

		return '<span class="' . $this->id . ' ' . $class . '">' .
			$html .
			'</span>';
	}
}
fof/form/field/rules.php000064400000062321152177723700011226 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('rules');

/**
 * Form Field class for FOF
 * Joomla! ACL Rules
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFFormFieldRules extends JFormFieldRules implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			// This field cannot provide a static display
			case 'static':
				return '';
				break;

			// This field cannot provide a repeateable display
			case 'repeatable':
				return '';
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return '';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.1
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return '';
	}

    /**
	 * At the timing of this writing (2013-12-03), the Joomla "rules" field is buggy. When you are
	 * dealing with a new record it gets the default permissions from the root asset node, which
	 * is fine for the default permissions of Joomla articles, but unsuitable for third party software.
	 * We had to copy & paste the whole code, since we can't "inject" the correct asset id if one is
	 * not found. Our fixes are surrounded by `FOF Library fix` remarks.
     *
     * @return  string  The input field's HTML for this field type
     */
    public function getInput()
    {
        if (version_compare(JVERSION, '3.0', 'ge'))
        {
            return $this->getInput3x();
        }
        else
        {
            return $this->getInput25();
        }
    }

    protected function getInput25()
    {
        JHtml::_('behavior.tooltip');

        // Initialise some field attributes.
        $section = $this->element['section'] ? (string) $this->element['section'] : '';
        $component = $this->element['component'] ? (string) $this->element['component'] : '';
        $assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';

        // Get the actions for the asset.
        $actions = JAccess::getActions($component, $section);

        // Iterate over the children and add to the actions.
        foreach ($this->element->children() as $el)
        {
            if ($el->getName() == 'action')
            {
                $actions[] = (object) array('name' => (string) $el['name'], 'title' => (string) $el['title'],
                    'description' => (string) $el['description']);
            }
        }

        // Get the explicit rules for this asset.
        if ($section == 'component')
        {
            // Need to find the asset id by the name of the component.
            $db    = FOFPlatform::getInstance()->getDbo();
            $query = $db->getQuery(true);
            $query->select($db->quoteName('id'));
            $query->from($db->quoteName('#__assets'));
            $query->where($db->quoteName('name') . ' = ' . $db->quote($component));
            $db->setQuery($query);
            $assetId = (int) $db->loadResult();

            if ($error = $db->getErrorMsg())
            {
                JError::raiseNotice(500, $error);
            }
        }
        else
        {
            // Find the asset id of the content.
            // Note that for global configuration, com_config injects asset_id = 1 into the form.
            $assetId = $this->form->getValue($assetField);

            // ==== FOF Library fix - Start ====
            // If there is no assetId (let's say we are dealing with a new record), let's ask the table
            // to give it to us. Here you should implement your logic (ie getting default permissions from
            // the component or from the category)
            if(!$assetId)
            {
                $table   = $this->form->getModel()->getTable();
                $assetId = $table->getAssetParentId();
            }
            // ==== FOF Library fix - End   ====
        }

        // Use the compact form for the content rules (deprecated).
        //if (!empty($component) && $section != 'component') {
        //	return JHtml::_('rules.assetFormWidget', $actions, $assetId, $assetId ? null : $component, $this->name, $this->id);
        //}

        // Full width format.

        // Get the rules for just this asset (non-recursive).
        $assetRules = JAccess::getAssetRules($assetId);

        // Get the available user groups.
        $groups = $this->getUserGroups();

        // Build the form control.
        $curLevel = 0;

        // Prepare output
        $html = array();
        $html[] = '<div id="permissions-sliders" class="pane-sliders">';
        $html[] = '<p class="rule-desc">' . JText::_('JLIB_RULES_SETTINGS_DESC') . '</p>';
        $html[] = '<ul id="rules">';

        // Start a row for each user group.
        foreach ($groups as $group)
        {
            $difLevel = $group->level - $curLevel;

            if ($difLevel > 0)
            {
                $html[] = '<li><ul>';
            }
            elseif ($difLevel < 0)
            {
                $html[] = str_repeat('</ul></li>', -$difLevel);
            }

            $html[] = '<li>';

            $html[] = '<div class="panel">';
            $html[] = '<h3 class="pane-toggler title"><a href="javascript:void(0);"><span>';
            $html[] = str_repeat('<span class="level">|&ndash;</span> ', $curLevel = $group->level) . $group->text;
            $html[] = '</span></a></h3>';
            $html[] = '<div class="pane-slider content pane-hide">';
            $html[] = '<div class="mypanel">';
            $html[] = '<table class="group-rules">';
            $html[] = '<thead>';
            $html[] = '<tr>';

            $html[] = '<th class="actions" id="actions-th' . $group->value . '">';
            $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_ACTION') . '</span>';
            $html[] = '</th>';

            $html[] = '<th class="settings" id="settings-th' . $group->value . '">';
            $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_SELECT_SETTING') . '</span>';
            $html[] = '</th>';

            // The calculated setting is not shown for the root group of global configuration.
            $canCalculateSettings = ($group->parent_id || !empty($component));
            if ($canCalculateSettings)
            {
                $html[] = '<th id="aclactionth' . $group->value . '">';
                $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_CALCULATED_SETTING') . '</span>';
                $html[] = '</th>';
            }

            $html[] = '</tr>';
            $html[] = '</thead>';
            $html[] = '<tbody>';

            foreach ($actions as $action)
            {
                $html[] = '<tr>';
                $html[] = '<td headers="actions-th' . $group->value . '">';
                $html[] = '<label class="hasTip" for="' . $this->id . '_' . $action->name . '_' . $group->value . '" title="'
                    . htmlspecialchars(JText::_($action->title) . '::' . JText::_($action->description), ENT_COMPAT, 'UTF-8') . '">';
                $html[] = JText::_($action->title);
                $html[] = '</label>';
                $html[] = '</td>';

                $html[] = '<td headers="settings-th' . $group->value . '">';

                $html[] = '<select name="' . $this->name . '[' . $action->name . '][' . $group->value . ']" id="' . $this->id . '_' . $action->name
                    . '_' . $group->value . '" title="'
                    . JText::sprintf('JLIB_RULES_SELECT_ALLOW_DENY_GROUP', JText::_($action->title), trim($group->text)) . '">';

                $inheritedRule = JAccess::checkGroup($group->value, $action->name, $assetId);

                // Get the actual setting for the action for this group.
                $assetRule = $assetRules->allow($action->name, $group->value);

                // Build the dropdowns for the permissions sliders

                // The parent group has "Not Set", all children can rightly "Inherit" from that.
                $html[] = '<option value=""' . ($assetRule === null ? ' selected="selected"' : '') . '>'
                    . JText::_(empty($group->parent_id) && empty($component) ? 'JLIB_RULES_NOT_SET' : 'JLIB_RULES_INHERITED') . '</option>';
                $html[] = '<option value="1"' . ($assetRule === true ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_ALLOWED')
                    . '</option>';
                $html[] = '<option value="0"' . ($assetRule === false ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_DENIED')
                    . '</option>';

                $html[] = '</select>&#160; ';

                // If this asset's rule is allowed, but the inherited rule is deny, we have a conflict.
                if (($assetRule === true) && ($inheritedRule === false))
                {
                    $html[] = JText::_('JLIB_RULES_CONFLICT');
                }

                $html[] = '</td>';

                // Build the Calculated Settings column.
                // The inherited settings column is not displayed for the root group in global configuration.
                if ($canCalculateSettings)
                {
                    $html[] = '<td headers="aclactionth' . $group->value . '">';

                    // This is where we show the current effective settings considering currrent group, path and cascade.
                    // Check whether this is a component or global. Change the text slightly.

                    if (JAccess::checkGroup($group->value, 'core.admin', $assetId) !== true)
                    {
                        if ($inheritedRule === null)
                        {
                            $html[] = '<span class="icon-16-unset">' . JText::_('JLIB_RULES_NOT_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === true)
                        {
                            $html[] = '<span class="icon-16-allowed">' . JText::_('JLIB_RULES_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === false)
                        {
                            if ($assetRule === false)
                            {
                                $html[] = '<span class="icon-16-denied">' . JText::_('JLIB_RULES_NOT_ALLOWED') . '</span>';
                            }
                            else
                            {
                                $html[] = '<span class="icon-16-denied"><span class="icon-16-locked">' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED')
                                    . '</span></span>';
                            }
                        }
                    }
                    elseif (!empty($component))
                    {
                        $html[] = '<span class="icon-16-allowed"><span class="icon-16-locked">' . JText::_('JLIB_RULES_ALLOWED_ADMIN')
                            . '</span></span>';
                    }
                    else
                    {
                        // Special handling for  groups that have global admin because they can't  be denied.
                        // The admin rights can be changed.
                        if ($action->name === 'core.admin')
                        {
                            $html[] = '<span class="icon-16-allowed">' . JText::_('JLIB_RULES_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === false)
                        {
                            // Other actions cannot be changed.
                            $html[] = '<span class="icon-16-denied"><span class="icon-16-locked">'
                                . JText::_('JLIB_RULES_NOT_ALLOWED_ADMIN_CONFLICT') . '</span></span>';
                        }
                        else
                        {
                            $html[] = '<span class="icon-16-allowed"><span class="icon-16-locked">' . JText::_('JLIB_RULES_ALLOWED_ADMIN')
                                . '</span></span>';
                        }
                    }

                    $html[] = '</td>';
                }

                $html[] = '</tr>';
            }

            $html[] = '</tbody>';
            $html[] = '</table></div>';

            $html[] = '</div></div>';
            $html[] = '</li>';

        }

        $html[] = str_repeat('</ul></li>', $curLevel);
        $html[] = '</ul><div class="rule-notes">';
        if ($section == 'component' || $section == null)
        {
            $html[] = JText::_('JLIB_RULES_SETTING_NOTES');
        }
        else
        {
            $html[] = JText::_('JLIB_RULES_SETTING_NOTES_ITEM');
        }
        $html[] = '</div></div>';

        $js = "window.addEvent('domready', function(){ new Fx.Accordion($$('div#permissions-sliders.pane-sliders .panel h3.pane-toggler'),"
            . "$$('div#permissions-sliders.pane-sliders .panel div.pane-slider'), {onActive: function(toggler, i) {toggler.addClass('pane-toggler-down');"
            . "toggler.removeClass('pane-toggler');i.addClass('pane-down');i.removeClass('pane-hide');Cookie.write('jpanesliders_permissions-sliders"
            . $component
            . "',$$('div#permissions-sliders.pane-sliders .panel h3').indexOf(toggler));},"
            . "onBackground: function(toggler, i) {toggler.addClass('pane-toggler');toggler.removeClass('pane-toggler-down');i.addClass('pane-hide');"
            . "i.removeClass('pane-down');}, duration: 300, display: "
            . JRequest::getInt('jpanesliders_permissions-sliders' . $component, 0, 'cookie') . ", show: "
            . JRequest::getInt('jpanesliders_permissions-sliders' . $component, 0, 'cookie') . ", alwaysHide:true, opacity: false}); });";

        JFactory::getDocument()->addScriptDeclaration($js);

        return implode("\n", $html);
    }

    protected function getInput3x()
    {
        JHtml::_('bootstrap.tooltip');

        // Initialise some field attributes.
        $section    = $this->section;
        $component  = $this->component;
        $assetField = $this->assetField;

        // Get the actions for the asset.
        $actions = JAccess::getActions($component, $section);

        // Iterate over the children and add to the actions.
        foreach ($this->element->children() as $el)
        {
            if ($el->getName() == 'action')
            {
                $actions[] = (object) array('name' => (string) $el['name'], 'title' => (string) $el['title'],
                    'description' => (string) $el['description']);
            }
        }

        // Get the explicit rules for this asset.
        if ($section == 'component')
        {
            // Need to find the asset id by the name of the component.
            $db    = FOFPlatform::getInstance()->getDbo();
            $query = $db->getQuery(true)
                        ->select($db->quoteName('id'))
                        ->from($db->quoteName('#__assets'))
                        ->where($db->quoteName('name') . ' = ' . $db->quote($component));

            $assetId = (int) $db->setQuery($query)->loadResult();
        }
        else
        {
            // Find the asset id of the content.
            // Note that for global configuration, com_config injects asset_id = 1 into the form.
            $assetId = $this->form->getValue($assetField);

            // ==== FOF Library fix - Start ====
            // If there is no assetId (let's say we are dealing with a new record), let's ask the table
            // to give it to us. Here you should implement your logic (ie getting default permissions from
            // the component or from the category)
            if(!$assetId)
            {
                $table   = $this->form->getModel()->getTable();
                $assetId = $table->getAssetParentId();
            }
            // ==== FOF Library fix - End   ====
        }

        // Full width format.

        // Get the rules for just this asset (non-recursive).
        $assetRules = JAccess::getAssetRules($assetId);

        // Get the available user groups.
        $groups = $this->getUserGroups();

        // Prepare output
        $html = array();

        // Description
        $html[] = '<p class="rule-desc">' . JText::_('JLIB_RULES_SETTINGS_DESC') . '</p>';

        // Begin tabs
        $html[] = '<div id="permissions-sliders" class="tabbable tabs-left">';

        // Building tab nav
        $html[] = '<ul class="nav nav-tabs">';

        foreach ($groups as $group)
        {
            // Initial Active Tab
            $active = "";

            if ($group->value == 1)
            {
                $active = "active";
            }

            $html[] = '<li class="' . $active . '">';
            $html[] = '<a href="#permission-' . $group->value . '" data-toggle="tab">';
            $html[] = str_repeat('<span class="level">&ndash;</span> ', $curLevel = $group->level) . $group->text;
            $html[] = '</a>';
            $html[] = '</li>';
        }

        $html[] = '</ul>';

        $html[] = '<div class="tab-content">';

        // Start a row for each user group.
        foreach ($groups as $group)
        {
            // Initial Active Pane
            $active = "";

            if ($group->value == 1)
            {
                $active = " active";
            }

            $html[] = '<div class="tab-pane' . $active . '" id="permission-' . $group->value . '">';
            $html[] = '<table class="table table-striped">';
            $html[] = '<thead>';
            $html[] = '<tr>';

            $html[] = '<th class="actions" id="actions-th' . $group->value . '">';
            $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_ACTION') . '</span>';
            $html[] = '</th>';

            $html[] = '<th class="settings" id="settings-th' . $group->value . '">';
            $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_SELECT_SETTING') . '</span>';
            $html[] = '</th>';

            // The calculated setting is not shown for the root group of global configuration.
            $canCalculateSettings = ($group->parent_id || !empty($component));

            if ($canCalculateSettings)
            {
                $html[] = '<th id="aclactionth' . $group->value . '">';
                $html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_CALCULATED_SETTING') . '</span>';
                $html[] = '</th>';
            }

            $html[] = '</tr>';
            $html[] = '</thead>';
            $html[] = '<tbody>';

            foreach ($actions as $action)
            {
                $html[] = '<tr>';
                $html[] = '<td headers="actions-th' . $group->value . '">';
                $html[] = '<label for="' . $this->id . '_' . $action->name . '_' . $group->value . '" class="hasTooltip" title="'
                    . htmlspecialchars(JText::_($action->title) . ' ' . JText::_($action->description), ENT_COMPAT, 'UTF-8') . '">';
                $html[] = JText::_($action->title);
                $html[] = '</label>';
                $html[] = '</td>';

                $html[] = '<td headers="settings-th' . $group->value . '">';

                $html[] = '<select class="input-small" name="' . $this->name . '[' . $action->name . '][' . $group->value . ']" id="' . $this->id . '_' . $action->name
                    . '_' . $group->value . '" title="'
                    . JText::sprintf('JLIB_RULES_SELECT_ALLOW_DENY_GROUP', JText::_($action->title), trim($group->text)) . '">';

                $inheritedRule = JAccess::checkGroup($group->value, $action->name, $assetId);

                // Get the actual setting for the action for this group.
                $assetRule = $assetRules->allow($action->name, $group->value);

                // Build the dropdowns for the permissions sliders

                // The parent group has "Not Set", all children can rightly "Inherit" from that.
                $html[] = '<option value=""' . ($assetRule === null ? ' selected="selected"' : '') . '>'
                    . JText::_(empty($group->parent_id) && empty($component) ? 'JLIB_RULES_NOT_SET' : 'JLIB_RULES_INHERITED') . '</option>';
                $html[] = '<option value="1"' . ($assetRule === true ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_ALLOWED')
                    . '</option>';
                $html[] = '<option value="0"' . ($assetRule === false ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_DENIED')
                    . '</option>';

                $html[] = '</select>&#160; ';

                // If this asset's rule is allowed, but the inherited rule is deny, we have a conflict.
                if (($assetRule === true) && ($inheritedRule === false))
                {
                    $html[] = JText::_('JLIB_RULES_CONFLICT');
                }

                $html[] = '</td>';

                // Build the Calculated Settings column.
                // The inherited settings column is not displayed for the root group in global configuration.
                if ($canCalculateSettings)
                {
                    $html[] = '<td headers="aclactionth' . $group->value . '">';

                    // This is where we show the current effective settings considering currrent group, path and cascade.
                    // Check whether this is a component or global. Change the text slightly.

                    if (JAccess::checkGroup($group->value, 'core.admin', $assetId) !== true)
                    {
                        if ($inheritedRule === null)
                        {
                            $html[] = '<span class="label label-important">' . JText::_('JLIB_RULES_NOT_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === true)
                        {
                            $html[] = '<span class="label label-success">' . JText::_('JLIB_RULES_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === false)
                        {
                            if ($assetRule === false)
                            {
                                $html[] = '<span class="label label-important">' . JText::_('JLIB_RULES_NOT_ALLOWED') . '</span>';
                            }
                            else
                            {
                                $html[] = '<span class="label"><i class="icon-lock icon-white"></i> ' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED')
                                    . '</span>';
                            }
                        }
                    }
                    elseif (!empty($component))
                    {
                        $html[] = '<span class="label label-success"><i class="icon-lock icon-white"></i> ' . JText::_('JLIB_RULES_ALLOWED_ADMIN')
                            . '</span>';
                    }
                    else
                    {
                        // Special handling for  groups that have global admin because they can't  be denied.
                        // The admin rights can be changed.
                        if ($action->name === 'core.admin')
                        {
                            $html[] = '<span class="label label-success">' . JText::_('JLIB_RULES_ALLOWED') . '</span>';
                        }
                        elseif ($inheritedRule === false)
                        {
                            // Other actions cannot be changed.
                            $html[] = '<span class="label label-important"><i class="icon-lock icon-white"></i> '
                                . JText::_('JLIB_RULES_NOT_ALLOWED_ADMIN_CONFLICT') . '</span>';
                        }
                        else
                        {
                            $html[] = '<span class="label label-success"><i class="icon-lock icon-white"></i> ' . JText::_('JLIB_RULES_ALLOWED_ADMIN')
                                . '</span>';
                        }
                    }

                    $html[] = '</td>';
                }

                $html[] = '</tr>';
            }

            $html[] = '</tbody>';
            $html[] = '</table></div>';
        }

        $html[] = '</div></div>';

        $html[] = '<div class="alert">';

        if ($section == 'component' || $section == null)
        {
            $html[] = JText::_('JLIB_RULES_SETTING_NOTES');
        }
        else
        {
            $html[] = JText::_('JLIB_RULES_SETTING_NOTES_ITEM');
        }

        $html[] = '</div>';

        return implode("\n", $html);
    }
}
fof/form/field/calendar.php000064400000012352152177723700011644 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('calendar');

/**
 * Form Field class for the FOF framework
 * Supports a calendar / date field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldCalendar extends JFormFieldCalendar implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			// ATTENTION: Redirected getInput() to getStatic()
			case 'input':
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getCalendar('static');
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getCalendar('repeatable');
	}

	/**
	 * Method to get the calendar input markup.
	 *
	 * @param   string  $display  The display to render ('static' or 'repeatable')
	 *
	 * @return  string	The field input markup.
	 *
	 * @since   2.1.rc4
	 */
	protected function getCalendar($display)
	{
		// Initialize some field attributes.
		$format  = $this->element['format'] ? (string) $this->element['format'] : '%Y-%m-%d';
		$class   = $this->element['class'] ? (string) $this->element['class'] : '';
		$default = $this->element['default'] ? (string) $this->element['default'] : '';

		// PHP date doesn't use percentages (%) for the format, but the calendar Javascript
		// DOES use it (@see: calendar-uncompressed.js). Therefore we have to convert it.
		$formatJS  = $format;
		$formatPHP = str_replace(array('%', 'H:M:S', 'B'), array('', 'H:i:s', 'F'), $formatJS);

		// Check for empty date values
		if (empty($this->value) || $this->value == FOFPlatform::getInstance()->getDbo()->getNullDate() || $this->value == '0000-00-00')
		{
			$this->value = $default;
		}

		// Get some system objects.
		$config = FOFPlatform::getInstance()->getConfig();
		$user   = JFactory::getUser();

		// Format date if exists
		if (!empty($this->value))
		{
			$date   = FOFPlatform::getInstance()->getDate($this->value, 'UTC');

			// If a known filter is given use it.
			switch (strtoupper((string) $this->element['filter']))
			{
				case 'SERVER_UTC':
					// Convert a date to UTC based on the server timezone.
					if ((int) $this->value)
					{
						// Get a date object based on the correct timezone.
						$date->setTimezone(new DateTimeZone($config->get('offset')));
					}
					break;

				case 'USER_UTC':
					// Convert a date to UTC based on the user timezone.
					if ((int) $this->value)
					{
						// Get a date object based on the correct timezone.
						$date->setTimezone($user->getTimezone());
					}
					break;

				default:
					break;
			}

			// Transform the date string.
			$this->value = $date->format($formatPHP, true, false);
		}

		if ($display == 'static')
		{
			// Build the attributes array.
			$attributes = array();

			if ($this->element['size'])
			{
				$attributes['size'] = (int) $this->element['size'];
			}

			if ($this->element['maxlength'])
			{
				$attributes['maxlength'] = (int) $this->element['maxlength'];
			}

			if ($this->element['class'])
			{
				$attributes['class'] = (string) $this->element['class'];
			}

			if ((string) $this->element['readonly'] == 'true')
			{
				$attributes['readonly'] = 'readonly';
			}

			if ((string) $this->element['disabled'] == 'true')
			{
				$attributes['disabled'] = 'disabled';
			}

			if ($this->element['onchange'])
			{
				$attributes['onchange'] = (string) $this->element['onchange'];
			}

			if ($this->required)
			{
				$attributes['required'] = 'required';
				$attributes['aria-required'] = 'true';
			}

			return JHtml::_('calendar', $this->value, $this->name, $this->id, $formatJS, $attributes);
		}
		else
		{
			return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') .
			'</span>';
		}
	}
}
fof/form/field/sessionhandler.php000064400000004703152177723700013115 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('sessionhandler');

/**
 * Form Field class for FOF
 * Joomla! session handlers
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldSessionhandler extends JFormFieldSessionHandler implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/usergroup.php000064400000006750152177723700012133 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('_JEXEC') or die;

JFormHelper::loadFieldClass('usergroup');

/**
 * Form Field class for FOF
 * Joomla! user groups
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldUsergroup extends JFormFieldUsergroup implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		$params = $this->getOptions();

		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);

		$query->select('a.id AS value, a.title AS text');
		$query->from('#__usergroups AS a');
		$query->group('a.id, a.title');
		$query->order('a.id ASC');
		$query->order($query->qn('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();

		// If params is an array, push these options to the array
		if (is_array($params))
		{
			$options = array_merge($params, $options);
		}

		// If all levels is allowed, push it into the array.
		elseif ($params)
		{
			array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);

		$query->select('a.id AS value, a.title AS text');
		$query->from('#__usergroups AS a');
		$query->group('a.id, a.title');
		$query->order('a.id ASC');
		$query->order($query->qn('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();


		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/checkboxes.php000064400000005354152177723700012215 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('checkboxes');

/**
 * Form Field class for FOF
 * Supports a list of checkbox.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldCheckboxes extends JFormFieldCheckboxes implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getRepeatable();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class     = $this->element['class'] ? (string) $this->element['class'] : $this->id;
		$translate = $this->element['translate'] ? (string) $this->element['translate'] : false;

		$html = '<span class="' . $class . '">';
		foreach ($this->value as $value) {

			$html .= '<span>';

			if ($translate == true)
			{
				$html .= JText::_($value);
			}
			else
			{
				$html .= $value;
			}

			$html .= '</span>';
		}
		$html .= '</span>';
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getInput()
	{
		// Used for J! 2.5 compatibility
		$this->value = !is_array($this->value) ? explode(',', $this->value) : $this->value;

		return parent::getInput();
	}
}
fof/form/field/actions.php000064400000013263152177723700011535 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldActions extends JFormFieldList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the field configuration
	 *
	 * @return  array
	 */
	protected function getConfig()
	{
		// If no custom options were defined let's figure out which ones of the
		// defaults we shall use...
		$config = array(
			'published'		 => 1,
			'unpublished'	 => 1,
			'archived'		 => 0,
			'trash'			 => 0,
			'all'			 => 0,
		);

		$stack = array();

		if (isset($this->element['show_published']))
		{
			$config['published'] = FOFStringUtils::toBool($this->element['show_published']);
		}

		if (isset($this->element['show_unpublished']))
		{
			$config['unpublished'] = FOFStringUtils::toBool($this->element['show_unpublished']);
		}

		if (isset($this->element['show_archived']))
		{
			$config['archived'] = FOFStringUtils::toBool($this->element['show_archived']);
		}

		if (isset($this->element['show_trash']))
		{
			$config['trash'] = FOFStringUtils::toBool($this->element['show_trash']);
		}

		if (isset($this->element['show_all']))
		{
			$config['all'] = FOFStringUtils::toBool($this->element['show_all']);
		}

		return $config;
	}

	/**
	 * Method to get the field options.
	 *
	 * @since 2.0
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		return null;
	}

	/**
	 * Method to get a
	 *
	 * @param   string  $enabledFieldName  Name of the enabled/published field
	 *
	 * @return  FOFFormFieldPublished  Field
	 */
	protected function getPublishedField($enabledFieldName)
	{
		$attributes = array(
			'name' => $enabledFieldName,
			'type' => 'published',
		);

		if ($this->element['publish_up'])
		{
			$attributes['publish_up'] = (string) $this->element['publish_up'];
		}

		if ($this->element['publish_down'])
		{
			$attributes['publish_down'] = (string) $this->element['publish_down'];
		}

		foreach ($attributes as $name => $value)
		{
			if (!is_null($value))
			{
				$renderedAttributes[] = $name . '="' . $value . '"';
			}
		}

		$publishedXml = new SimpleXMLElement('<field ' . implode(' ', $renderedAttributes) . ' />');

		$publishedField = new FOFFormFieldPublished($this->form);

		// Pass required objects to the field
		$publishedField->item = $this->item;
		$publishedField->rowid = $this->rowid;
		$publishedField->setup($publishedXml, $this->item->{$enabledFieldName});

		return $publishedField;
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		throw new Exception(__CLASS__ . ' cannot be used in single item display forms');
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		if (!($this->item instanceof FOFTable))
		{
			throw new Exception(__CLASS__ . ' needs a FOFTable to act upon');
		}

		$config = $this->getConfig();

		// Initialise
		$prefix       = '';
		$checkbox     = 'cb';
		$publish_up   = null;
		$publish_down = null;
		$enabled      = true;

		$html = '<div class="btn-group">';

		// Render a published field
		if ($publishedFieldName = $this->item->getColumnAlias('enabled'))
		{
			if ($config['published'] || $config['unpublished'])
			{
				// Generate a FOFFormFieldPublished field
				$publishedField = $this->getPublishedField($publishedFieldName);

				// Render the publish button
				$html .= $publishedField->getRepeatable();
			}

			if ($config['archived'])
			{
				$archived	= $this->item->{$publishedFieldName} == 2 ? true : false;

				// Create dropdown items
				$action = $archived ? 'unarchive' : 'archive';
				JHtml::_('actionsdropdown.' . $action, 'cb' . $this->rowid, $prefix);
			}

			if ($config['trash'])
			{
				$trashed	= $this->item->{$publishedFieldName} == -2 ? true : false;

				$action = $trashed ? 'untrash' : 'trash';
				JHtml::_('actionsdropdown.' . $action, 'cb' . $this->rowid, $prefix);
			}

			// Render dropdown list
			if ($config['archived'] || $config['trash'])
			{
				$html .= JHtml::_('actionsdropdown.render', $this->item->title);
			}
		}

		$html .= '</div>';

		return $html;
	}
}
fof/form/field/editor.php000064400000004430152177723700011357 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('editor');

/**
 * Form Field class for the FOF framework
 * An editarea field for content creation and formatted HTML display
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldEditor extends JFormFieldEditor implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<div id="' . $this->id . '" ' . $class . '>' . $this->value . '</div>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<div class="' . $this->id . ' ' . $class . '">' . $this->value . '</div>';
	}
}
fof/form/field/imagelist.php000064400000006352152177723700012054 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('imagelist');

/**
 * Form Field class for the FOF framework
 * Media selection field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldImagelist extends JFormFieldImageList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$imgattr = array(
			'id' => $this->id
		);

		if ($this->element['class'])
		{
			$imgattr['class'] = (string) $this->element['class'];
		}

		if ($this->element['style'])
		{
			$imgattr['style'] = (string) $this->element['style'];
		}

		if ($this->element['width'])
		{
			$imgattr['width'] = (string) $this->element['width'];
		}

		if ($this->element['height'])
		{
			$imgattr['height'] = (string) $this->element['height'];
		}

		if ($this->element['align'])
		{
			$imgattr['align'] = (string) $this->element['align'];
		}

		if ($this->element['rel'])
		{
			$imgattr['rel'] = (string) $this->element['rel'];
		}

		if ($this->element['alt'])
		{
			$alt = JText::_((string) $this->element['alt']);
		}
		else
		{
			$alt = null;
		}

		if ($this->element['title'])
		{
			$imgattr['title'] = JText::_((string) $this->element['title']);
		}

		$path = (string) $this->element['directory'];
		$path = trim($path, '/' . DIRECTORY_SEPARATOR);

		if ($this->value && file_exists(JPATH_ROOT . '/' . $path . '/' . $this->value))
		{
			$src = FOFPlatform::getInstance()->URIroot() . '/' . $path . '/' . $this->value;
		}
		else
		{
			$src = '';
		}

		return JHtml::_('image', $src, $alt, $imgattr);
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getStatic();
	}
}
fof/form/field/button.php000064400000006166152177723700011414 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the FOF framework
 * Supports a button input.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldButton extends FOFFormFieldText implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getInput()
	{
		$this->label = '';

		$allowedElement = array('button', 'a');

		if (in_array($this->element['htmlelement'], $allowedElement))
			$type = $this->element['htmlelement'];
		else
			$type = 'button';

		$text    = $this->element['text'];
		$class   = $this->element['class'] ? (string) $this->element['class'] : '';
		$icon    = $this->element['icon'] ? (string) $this->element['icon'] : '';
		$onclick = $this->element['onclick'] ? 'onclick="' . (string) $this->element['onclick'] . '"' : '';
		$url     = $this->element['url'] ? 'href="' . $this->parseFieldTags((string) $this->element['url']) . '"' : '';
		$title   = $this->element['title'] ? 'title="' . JText::_((string) $this->element['title']) . '"' : '';

		$this->value = JText::_($text);

		if ($icon)
		{
			$icon = '<span class="icon ' . $icon . '"></span>';
		}

		return '<' . $type . ' id="' . $this->id . '" class="btn ' . $class . '" ' .
			$onclick . $url . $title . '>' .
			$icon .
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') .
			'</' . $type . '>';
	}

	/**
	 * Method to get the field title.
	 *
	 * @return  string  The field title.
	 */
	protected function getTitle()
	{
		return null;
	}
}
fof/form/field/timezone.php000064400000005023152177723700011722 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('timezone');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldTimezone extends JFormFieldTimezone implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$selected = FOFFormFieldGroupedlist::getOptionName($this->getOptions(), $this->value);

		if (is_null($selected))
		{
			$selected = array(
				'group'	 => '',
				'item'	 => ''
			);
		}

		return '<span id="' . $this->id . '-group" class="fof-groupedlist-group ' . $class . '>' .
			htmlspecialchars($selected['group'], ENT_COMPAT, 'UTF-8') .
			'</span>' .
			'<span id="' . $this->id . '-item" class="fof-groupedlist-item ' . $class . '>' .
			htmlspecialchars($selected['item'], ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getStatic();
	}
}
fof/form/field/password.php000064400000004620152177723700011734 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('password');

/**
 * Form Field class for the FOF framework
 * Supports a one line text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldPassword extends JFormFieldPassword implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/language.php000064400000005474152177723700011665 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('language');

/**
 * Form Field class for FOF
 * Available site languages
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldLanguage extends JFormFieldLanguage implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Method to get the field options.
	 *
	 * @since 2.0
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		$options = parent::getOptions();

		$noneoption = $this->element['none'] ? $this->element['none'] : null;

		if ($noneoption)
		{
			array_unshift($options, JHtml::_('select.option', '*', JText::_($noneoption)));
		}

		return $options;
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/published.php000064400000011366152177723700012056 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldPublished extends JFormFieldList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Method to get the field options.
	 *
	 * @since 2.0
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		$options = parent::getOptions();

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

		// If no custom options were defined let's figure out which ones of the
		// defaults we shall use...

		$config = array(
			'published'		 => 1,
			'unpublished'	 => 1,
			'archived'		 => 0,
			'trash'			 => 0,
			'all'			 => 0,
		);

		$configMap = array(
			'show_published'	=> array('published', 1),
			'show_unpublished'	=> array('unpublished', 1),
			'show_archived'		=> array('archived', 0),
			'show_trash'		=> array('trash', 0),
			'show_all'			=> array('all', 0),
		);

		foreach ($configMap as $attribute => $preferences)
		{
			list($configKey, $default) = $preferences;

			switch (strtolower($this->element[$attribute]))
			{
				case 'true':
				case '1':
				case 'yes':
					$config[$configKey] = true;

				case 'false':
				case '0':
				case 'no':
					$config[$configKey] = false;

				default:
					$config[$configKey] = $default;
			}
		}

		if ($config['published'])
		{
			$stack[] = JHtml::_('select.option', '1', JText::_('JPUBLISHED'));
		}

		if ($config['unpublished'])
		{
			$stack[] = JHtml::_('select.option', '0', JText::_('JUNPUBLISHED'));
		}

		if ($config['archived'])
		{
			$stack[] = JHtml::_('select.option', '2', JText::_('JARCHIVED'));
		}

		if ($config['trash'])
		{
			$stack[] = JHtml::_('select.option', '-2', JText::_('JTRASHED'));
		}

		if ($config['all'])
		{
			$stack[] = JHtml::_('select.option', '*', JText::_('JALL'));
		}

		return $stack;
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		if (!($this->item instanceof FOFTable))
		{
			throw new Exception(__CLASS__ . ' needs a FOFTable to act upon');
		}

		// Initialise
		$prefix = '';
		$checkbox = 'cb';
		$publish_up = null;
		$publish_down = null;
		$enabled = true;

		// Get options
		if ($this->element['prefix'])
		{
			$prefix = (string) $this->element['prefix'];
		}

		if ($this->element['checkbox'])
		{
			$checkbox = (string) $this->element['checkbox'];
		}

		if ($this->element['publish_up'])
		{
			$publish_up = (string) $this->element['publish_up'];
		}

		if ($this->element['publish_down'])
		{
			$publish_down = (string) $this->element['publish_down'];
		}

		// @todo Enforce ACL checks to determine if the field should be enabled or not
		// Get the HTML
		return JHTML::_('jgrid.published', $this->value, $this->rowid, $prefix, $enabled, $checkbox, $publish_up, $publish_down);
	}
}
fof/form/field/selectrow.php000064400000005575152177723700012113 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Form Field class for FOF
 * Renders the checkbox in browse views which allows you to select rows
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldSelectrow extends JFormField implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Method to get the field input markup for this field type.
	 *
	 * @since 2.0
	 *
	 * @return  string  The field input markup.
	 */
	protected function getInput()
	{
		throw new Exception(__CLASS__ . ' cannot be used in input forms');
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		throw new Exception(__CLASS__ . ' cannot be used in single item display forms');
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		if (!($this->item instanceof FOFTable))
		{
			throw new Exception(__CLASS__ . ' needs a FOFTable to act upon');
		}

		// Is this record checked out?
		$checked_out     = false;
		$locked_by_field = $this->item->getColumnAlias('locked_by');
		$myId            = JFactory::getUser()->get('id', 0);

		if (property_exists($this->item, $locked_by_field))
		{
			$locked_by   = $this->item->$locked_by_field;
			$checked_out = ($locked_by != 0 && $locked_by != $myId);
		}

		// Get the key id for this record
		$key_field = $this->item->getKeyName();
		$key_id    = $this->item->$key_field;

		// Get the HTML
		return JHTML::_('grid.id', $this->rowid, $key_id, $checked_out);
	}
}
fof/form/field/sql.php000064400000004700152177723700010670 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('sql');

/**
 * Form Field class for FOF
 * Radio selection listGeneric list from an SQL statement
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldSql extends JFormFieldSql implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/radio.php000064400000004644152177723700011176 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('radio');

/**
 * Form Field class for FOF
 * Radio selection list
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldRadio extends JFormFieldRadio implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/textarea.php000064400000004240152177723700011705 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('textarea');

/**
 * Form Field class for the FOF framework
 * Supports a text area
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldTextarea extends JFormFieldTextarea implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<div id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(nl2br($this->value), ENT_COMPAT, 'UTF-8') .
			'</div>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getStatic();
	}
}
fof/form/field/list.php000064400000023506152177723700011051 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldList extends JFormFieldList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(self::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$show_link         = false;
		$link_url          = '';

		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['url'])
		{
			$link_url = $this->element['url'];
		}
		else
		{
			$show_link = false;
		}

		if ($show_link && ($this->item instanceof FOFTable))
		{
			$link_url = $this->parseFieldTags($link_url);
		}
		else
		{
			$show_link = false;
		}

		$html = '<span class="' . $this->id . ' ' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= htmlspecialchars(self::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8');

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}

	/**
	 * Gets the active option's label given an array of JHtml options
	 *
	 * @param   array   $data      The JHtml options to parse
	 * @param   mixed   $selected  The currently selected value
	 * @param   string  $optKey    Key name
	 * @param   string  $optText   Value name
	 *
	 * @return  mixed   The label of the currently selected option
	 */
	public static function getOptionName($data, $selected = null, $optKey = 'value', $optText = 'text')
	{
		$ret = null;

		foreach ($data as $elementKey => &$element)
		{
			if (is_array($element))
			{
				$key = $optKey === null ? $elementKey : $element[$optKey];
				$text = $element[$optText];
			}
			elseif (is_object($element))
			{
				$key = $optKey === null ? $elementKey : $element->$optKey;
				$text = $element->$optText;
			}
			else
			{
				// This is a simple associative array
				$key = $elementKey;
				$text = $element;
			}

			if (is_null($ret))
			{
				$ret = $text;
			}
			elseif ($selected == $key)
			{
				$ret = $text;
			}
		}

		return $ret;
	}

	/**
	 * Method to get the field options.
	 *
	 * Ordering is disabled by default. You can enable ordering by setting the
	 * 'order' element in your form field. The other order values are optional.
	 *
	 * - order					What to order.			Possible values: 'name' or 'value' (default = false)
	 * - order_dir				Order direction.		Possible values: 'asc' = Ascending or 'desc' = Descending (default = 'asc')
	 * - order_case_sensitive	Order case sensitive.	Possible values: 'true' or 'false' (default = false)
	 *
	 * @return  array  The field option objects.
	 *
	 * @since	Ordering is available since FOF 2.1.b2.
	 */
	protected function getOptions()
	{
		// Ordering is disabled by default for backward compatibility
		$order = false;

		// Set default order direction
		$order_dir = 'asc';

		// Set default value for case sensitive sorting
		$order_case_sensitive = false;

		if ($this->element['order'] && $this->element['order'] !== 'false')
		{
			$order = $this->element['order'];
		}

		if ($this->element['order_dir'])
		{
			$order_dir = $this->element['order_dir'];
		}

		if ($this->element['order_case_sensitive'])
		{
			// Override default setting when the form element value is 'true'
			if ($this->element['order_case_sensitive'] == 'true')
			{
				$order_case_sensitive = true;
			}
		}

		// Create a $sortOptions array in order to apply sorting
		$i = 0;
		$sortOptions = array();

		foreach ($this->element->children() as $option)
		{
			$name = JText::alt(trim((string) $option), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname));

			$sortOptions[$i] = new stdClass;
			$sortOptions[$i]->option = $option;
			$sortOptions[$i]->value = $option['value'];
			$sortOptions[$i]->name = $name;
			$i++;
		}

		// Only order if it's set
		if ($order)
		{
			jimport('joomla.utilities.arrayhelper');
			FOFUtilsArray::sortObjects($sortOptions, $order, $order_dir == 'asc' ? 1 : -1, $order_case_sensitive, false);
		}

		// Initialise the options
		$options = array();

		// Get the field $options
		foreach ($sortOptions as $sortOption)
		{
			$option = $sortOption->option;
			$name = $sortOption->name;

			// Only add <option /> elements.
			if ($option->getName() != 'option')
			{
				continue;
			}

			$tmp = JHtml::_('select.option', (string) $option['value'], $name, 'value', 'text', ((string) $option['disabled'] == 'true'));

			// Set some option attributes.
			$tmp->class = (string) $option['class'];

			// Set some JavaScript option attributes.
			$tmp->onclick = (string) $option['onclick'];

			// Add the option object to the result set.
			$options[] = $tmp;
		}

		// Do we have a class and method source for our options?
		$source_file      = empty($this->element['source_file']) ? '' : (string) $this->element['source_file'];
		$source_class     = empty($this->element['source_class']) ? '' : (string) $this->element['source_class'];
		$source_method    = empty($this->element['source_method']) ? '' : (string) $this->element['source_method'];
		$source_key       = empty($this->element['source_key']) ? '*' : (string) $this->element['source_key'];
		$source_value     = empty($this->element['source_value']) ? '*' : (string) $this->element['source_value'];
		$source_translate = empty($this->element['source_translate']) ? 'true' : (string) $this->element['source_translate'];
		$source_translate = in_array(strtolower($source_translate), array('true','yes','1','on')) ? true : false;
		$source_format	  = empty($this->element['source_format']) ? '' : (string) $this->element['source_format'];

		if ($source_class && $source_method)
		{
			// Maybe we have to load a file?
			if (!empty($source_file))
			{
				$source_file = FOFTemplateUtils::parsePath($source_file, true);

				if (FOFPlatform::getInstance()->getIntegrationObject('filesystem')->fileExists($source_file))
				{
					include_once $source_file;
				}
			}

			// Make sure the class exists
			if (class_exists($source_class, true))
			{
				// ...and so does the option
				if (in_array($source_method, get_class_methods($source_class)))
				{
					// Get the data from the class
					if ($source_format == 'optionsobject')
					{
						$options = array_merge($options, $source_class::$source_method());
					}
					else
					{
						// Get the data from the class
						$source_data = $source_class::$source_method();

						// Loop through the data and prime the $options array
						foreach ($source_data as $k => $v)
						{
							$key = (empty($source_key) || ($source_key == '*')) ? $k : $v[$source_key];
							$value = (empty($source_value) || ($source_value == '*')) ? $v : $v[$source_value];

							if ($source_translate)
							{
								$value = JText::_($value);
							}

							$options[] = JHtml::_('select.option', $key, $value, 'value', 'text');
						}
					}
				}
			}
		}

		reset($options);

		return $options;
	}

	/**
	 * Replace string with tags that reference fields
	 *
	 * @param   string  $text  Text to process
	 *
	 * @return  string         Text with tags replace
	 */
	protected function parseFieldTags($text)
	{
		$ret = $text;

		// Replace [ITEM:ID] in the URL with the item's key value (usually:
		// the auto-incrementing numeric ID)
		$keyfield = $this->item->getKeyName();
		$replace  = $this->item->$keyfield;
		$ret = str_replace('[ITEM:ID]', $replace, $ret);

		// Replace the [ITEMID] in the URL with the current Itemid parameter
		$ret = str_replace('[ITEMID]', JFactory::getApplication()->input->getInt('Itemid', 0), $ret);

		// Replace other field variables in the URL
		$fields = $this->item->getTableFields();

		foreach ($fields as $fielddata)
		{
			$fieldname = $fielddata->Field;

			if (empty($fieldname))
			{
				$fieldname = $fielddata->column_name;
			}

			$search    = '[ITEM:' . strtoupper($fieldname) . ']';
			$replace   = $this->item->$fieldname;
			$ret  = str_replace($search, $replace, $ret);
		}

		return $ret;
	}
}
fof/form/field/model.php000064400000015026152177723700011174 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for FOF
 * Generic list from a model's results
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldModel extends FOFFormFieldList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class				= $this->id;
		$format_string		= '';
		$show_link			= false;
		$link_url			= '';
		$empty_replacement	= '';

		// Get field parameters
		if ($this->element['class'])
		{
			$class = (string) $this->element['class'];
		}

		if ($this->element['format'])
		{
			$format_string = (string) $this->element['format'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['url'])
		{
			$link_url = $this->element['url'];
		}
		else
		{
			$show_link = false;
		}

		if ($show_link && ($this->item instanceof FOFTable))
		{
			$link_url = $this->parseFieldTags($link_url);
		}
		else
		{
			$show_link = false;
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		$value = FOFFormFieldList::getOptionName($this->getOptions(), $this->value);

		// Get the (optionally formatted) value
		if (!empty($empty_replacement) && empty($value))
		{
			$value = JText::_($empty_replacement);
		}

		if (empty($format_string))
		{
			$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
		}
		else
		{
			$value = sprintf($format_string, $value);
		}

		// Create the HTML
		$html = '<span class="' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= $value;

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		$options = array();

		// Initialize some field attributes.
		$key = $this->element['key_field'] ? (string) $this->element['key_field'] : 'value';
		$value = $this->element['value_field'] ? (string) $this->element['value_field'] : (string) $this->element['name'];
		$translate = $this->element['translate'] ? (string) $this->element['translate'] : false;
		$applyAccess = $this->element['apply_access'] ? (string) $this->element['apply_access'] : 'false';
		$modelName = (string) $this->element['model'];
		$nonePlaceholder = (string) $this->element['none'];

		if (!empty($nonePlaceholder))
		{
			$options[] = JHtml::_('select.option', null, JText::_($nonePlaceholder));
		}

		// Process field atrtibutes
		$applyAccess = strtolower($applyAccess);
		$applyAccess = in_array($applyAccess, array('yes', 'on', 'true', '1'));

		// Explode model name into model name and prefix
		$parts = FOFInflector::explode($modelName);
		$mName = ucfirst(array_pop($parts));
		$mPrefix = FOFInflector::implode($parts);

		// Get the model object
		$config = array('savestate' => 0);
		$model = FOFModel::getTmpInstance($mName, $mPrefix, $config);

		if ($applyAccess)
		{
			$model->applyAccessFiltering();
		}

		// Process state variables
		foreach ($this->element->children() as $stateoption)
		{
			// Only add <option /> elements.
			if ($stateoption->getName() != 'state')
			{
				continue;
			}

			$stateKey = (string) $stateoption['key'];
			$stateValue = (string) $stateoption;

			$model->setState($stateKey, $stateValue);
		}

		// Set the query and get the result list.
		$items = $model->getItemList(true);

		// Build the field options.
		if (!empty($items))
		{
			foreach ($items as $item)
			{
				if ($translate == true)
				{
					$options[] = JHtml::_('select.option', $item->$key, JText::_($item->$value));
				}
				else
				{
					$options[] = JHtml::_('select.option', $item->$key, $item->$value);
				}
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}

	/**
	 * Replace string with tags that reference fields
	 *
	 * @param   string  $text  Text to process
	 *
	 * @return  string         Text with tags replace
	 */
	protected function parseFieldTags($text)
	{
		$ret = $text;

		// Replace [ITEM:ID] in the URL with the item's key value (usually:
		// the auto-incrementing numeric ID)
		$keyfield = $this->item->getKeyName();
		$replace  = $this->item->$keyfield;
		$ret = str_replace('[ITEM:ID]', $replace, $ret);

		// Replace the [ITEMID] in the URL with the current Itemid parameter
		$ret = str_replace('[ITEMID]', JFactory::getApplication()->input->getInt('Itemid', 0), $ret);

		// Replace other field variables in the URL
		$fields = $this->item->getTableFields();

		foreach ($fields as $fielddata)
		{
			$fieldname = $fielddata->Field;

			if (empty($fieldname))
			{
				$fieldname = $fielddata->column_name;
			}

			$search    = '[ITEM:' . strtoupper($fieldname) . ']';
			$replace   = $this->item->$fieldname;
			$ret  = str_replace($search, $replace, $ret);
		}

		return $ret;
	}
}
fof/form/field/integer.php000064400000004615152177723700011533 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('integer');

/**
 * Form Field class for the FOF framework
 * Supports a one line text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldInteger extends JFormFieldInteger implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/tel.php000064400000006767152177723700010674 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('tel');

/**
 * Form Field class for the FOF framework
 * Supports a URL text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldTel extends JFormFieldTel implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class  = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
		$dolink = $this->element['show_link'] == 'true';
		$empty_replacement = '';

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$innerHtml = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		if ($dolink)
		{
			$innerHtml = '<a href="tel:' . $innerHtml . '">' .
				$innerHtml . '</a>';
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			$innerHtml .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$class             = $this->id;
		$show_link         = false;
		$empty_replacement = '';

		$link_url = 'tel:' . htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Get field parameters
		if ($this->element['class'])
		{
			$class = ' ' . (string) $this->element['class'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		// Get the (optionally formatted) value
		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Create the HTML
		$html = '<span class="' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= $value;

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}
}
fof/form/field/accesslevel.php000064400000007607152177723700012373 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('accesslevel');

/**
 * Form Field class for FOF
 * Joomla! access levels
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldAccesslevel extends JFormFieldAccessLevel implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		$params = $this->getOptions();

		$db    = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);

		$query->select('a.id AS value, a.title AS text');
		$query->from('#__viewlevels AS a');
		$query->group('a.id, a.title, a.ordering');
		$query->order('a.ordering ASC');
		$query->order($query->qn('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();

		// If params is an array, push these options to the array
		if (is_array($params))
		{
			$options = array_merge($params, $options);
		}

		// If all levels is allowed, push it into the array.
		elseif ($params)
		{
			array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$params = $this->getOptions();

		$db    = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);

		$query->select('a.id AS value, a.title AS text');
		$query->from('#__viewlevels AS a');
		$query->group('a.id, a.title, a.ordering');
		$query->order('a.ordering ASC');
		$query->order($query->qn('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();

		// If params is an array, push these options to the array
		if (is_array($params))
		{
			$options = array_merge($params, $options);
		}

		// If all levels is allowed, push it into the array.
		elseif ($params)
		{
			array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
		}

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($options, $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/captcha.php000064400000003727152177723700011504 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('captcha');

/**
 * Form Field class for the FOF framework
 * Supports a captcha field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldCaptcha extends JFormFieldCaptcha implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getInput();
	}
}
fof/form/field/text.php000064400000012303152177723700011053 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the FOF framework
 * Supports a one line text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldText extends JFormFieldText implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
		$empty_replacement = '';

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$class					= $this->id;
		$format_string			= '';
		$format_if_not_empty	= false;
		$parse_value			= false;
		$show_link				= false;
		$link_url				= '';
		$empty_replacement		= '';

		// Get field parameters
		if ($this->element['class'])
		{
			$class = (string) $this->element['class'];
		}

		if ($this->element['format'])
		{
			$format_string = (string) $this->element['format'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['format_if_not_empty'] == 'true')
		{
			$format_if_not_empty = true;
		}

		if ($this->element['parse_value'] == 'true')
		{
			$parse_value = true;
		}

		if ($this->element['url'])
		{
			$link_url = $this->element['url'];
		}
		else
		{
			$show_link = false;
		}

		if ($show_link && ($this->item instanceof FOFTable))
		{
			$link_url = $this->parseFieldTags($link_url);
		}
		else
		{
			$show_link = false;
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		// Get the (optionally formatted) value
		$value = $this->value;

		if (!empty($empty_replacement) && empty($this->value))
		{
			$value = JText::_($empty_replacement);
		}

		if ($parse_value)
		{
			$value = $this->parseFieldTags($value);
		}

		if (!empty($format_string) && (!$format_if_not_empty || ($format_if_not_empty && !empty($this->value))))
		{
			$format_string = $this->parseFieldTags($format_string);
			$value = sprintf($format_string, $value);
		}
		else
		{
			$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
		}

		// Create the HTML
		$html = '<span class="' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= $value;

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}

	/**
	 * Replace string with tags that reference fields
	 *
	 * @param   string  $text  Text to process
	 *
	 * @return  string         Text with tags replace
	 */
	protected function parseFieldTags($text)
	{
		$ret = $text;

		// Replace [ITEM:ID] in the URL with the item's key value (usually:
		// the auto-incrementing numeric ID)
		$keyfield = $this->item->getKeyName();
		$replace  = $this->item->$keyfield;
		$ret = str_replace('[ITEM:ID]', $replace, $ret);

		// Replace the [ITEMID] in the URL with the current Itemid parameter
		$ret = str_replace('[ITEMID]', JFactory::getApplication()->input->getInt('Itemid', 0), $ret);

		// Replace other field variables in the URL
		$fields = $this->item->getTableFields();

		foreach ($fields as $fielddata)
		{
			$fieldname = $fielddata->Field;

			if (empty($fieldname))
			{
				$fieldname = $fielddata->column_name;
			}

			$search    = '[ITEM:' . strtoupper($fieldname) . ']';
			$replace   = $this->item->$fieldname;
			$ret  = str_replace($search, $replace, $ret);
		}

		return $ret;
	}
}
fof/form/field/title.php000064400000003040152177723700011206 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the FOF framework
 * Supports a title field with an optional slug display below it.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldTitle extends FOFFormFieldText implements FOFFormField
{
	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$slug_format	= '(%s)';
		$slug_class		= 'small';

		// Get field parameters
		if ($this->element['slug_field'])
		{
			$slug_field = (string) $this->element['slug_field'];
		}
		else
		{
			$slug_field = $this->item->getColumnAlias('slug');
		}

		if ($this->element['slug_format'])
		{
			$slug_format = (string) $this->element['slug_format'];
		}

		if ($this->element['slug_class'])
		{
			$slug_class = (string) $this->element['slug_class'];
		}

		// Get the regular display
		$html = parent::getRepeatable();

		$slug = $this->item->$slug_field;

		$html .= '<br />' . '<span class="' . $slug_class . '">';
		$html .= JText::sprintf($slug_format, $slug);
		$html .= '</span>';

		return $html;
	}
}
fof/form/field/media.php000064400000006131152177723700011150 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 * @note	This file has been modified by the Joomla! Project and no longer reflects the original work of its author.
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('media');

/**
 * Form Field class for the FOF framework
 * Media selection field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldMedia extends JFormFieldMedia implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$imgattr = array(
			'id' => $this->id
		);

		if ($this->element['class'])
		{
			$imgattr['class'] = (string) $this->element['class'];
		}

		if ($this->element['style'])
		{
			$imgattr['style'] = (string) $this->element['style'];
		}

		if ($this->element['width'])
		{
			$imgattr['width'] = (string) $this->element['width'];
		}

		if ($this->element['height'])
		{
			$imgattr['height'] = (string) $this->element['height'];
		}

		if ($this->element['align'])
		{
			$imgattr['align'] = (string) $this->element['align'];
		}

		if ($this->element['rel'])
		{
			$imgattr['rel'] = (string) $this->element['rel'];
		}

		if ($this->element['alt'])
		{
			$alt = JText::_((string) $this->element['alt']);
		}
		else
		{
			$alt = null;
		}

		if ($this->element['title'])
		{
			$imgattr['title'] = JText::_((string) $this->element['title']);
		}

		if ($this->value && file_exists(JPATH_ROOT . '/' . $this->value))
		{
			$src = FOFPlatform::getInstance()->URIroot() . $this->value;
		}
		else
		{
			$src = '';
		}

		return JHtml::_('image', $src, $alt, $imgattr);
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getStatic();
	}
}
fof/form/field/spacer.php000064400000003734152177723700011354 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('spacer');

/**
 * Form Field class for the FOF framework
 * Spacer used between form elements
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldSpacer extends JFormFieldSpacer implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		return $this->getInput();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		return $this->getInput();
	}
}
fof/form/field/ordering.php000064400000013324152177723700011704 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Form Field class for FOF
 * Renders the row ordering interface checkbox in browse views
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldOrdering extends JFormField implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Method to get the field input markup for this field type.
	 *
	 * @since 2.0
	 *
	 * @return  string  The field input markup.
	 */
	protected function getInput()
	{
		$html = array();
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= $this->disabled ? ' disabled' : '';
		$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';

		// Initialize JavaScript field attributes.
		$attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';

		$this->item = $this->form->getModel()->getItem();

		$keyfield = $this->item->getKeyName();
		$itemId   = $this->item->$keyfield;

		$query = $this->getQuery();

		// Create a read-only list (no name) with a hidden input to store the value.
		if ($this->readonly)
		{
			$html[] = JHtml::_('list.ordering', '', $query, trim($attr), $this->value, $itemId ? 0 : 1);
			$html[] = '<input type="hidden" name="' . $this->name . '" value="' . $this->value . '"/>';
		}
		else
		{
			// Create a regular list.
			$html[] = JHtml::_('list.ordering', $this->name, $query, trim($attr), $this->value, $itemId ? 0 : 1);
		}

		return implode($html);
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		throw new Exception(__CLASS__ . ' cannot be used in single item display forms');
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		if (!($this->item instanceof FOFTable))
		{
			throw new Exception(__CLASS__ . ' needs a FOFTable to act upon');
		}

		$class = isset($this->element['class']) ? $this->element['class'] : 'input-mini';
		$icon  = isset($this->element['icon']) ? $this->element['icon'] : 'icon-menu';

		$html = '';

		$view = $this->form->getView();

		$ordering = $view->getLists()->order == 'ordering';

		if (!$view->hasAjaxOrderingSupport())
		{
			// Ye olde Joomla! 2.5 method
			$disabled = $ordering ? '' : 'disabled="disabled"';
			$html .= '<span>';
			$html .= $view->pagination->orderUpIcon($this->rowid, true, 'orderup', 'Move Up', $ordering);
			$html .= '</span><span>';
			$html .= $view->pagination->orderDownIcon($this->rowid, $view->pagination->total, true, 'orderdown', 'Move Down', $ordering);
			$html .= '</span>';
			$html .= '<input type="text" name="order[]" size="5" value="' . $this->value . '" ' . $disabled;
			$html .= 'class="text-area-order" style="text-align: center" />';
		}
		else
		{
			// The modern drag'n'drop method
			if ($view->getPerms()->editstate)
			{
				$disableClassName = '';
				$disabledLabel = '';

				$hasAjaxOrderingSupport = $view->hasAjaxOrderingSupport();

				if (!$hasAjaxOrderingSupport['saveOrder'])
				{
					$disabledLabel = JText::_('JORDERINGDISABLED');
					$disableClassName = 'inactive tip-top';
				}

				$orderClass = $ordering ? 'order-enabled' : 'order-disabled';

				$html .= '<div class="' . $orderClass . '">';
				$html .= '<span class="sortable-handler ' . $disableClassName . '" title="' . $disabledLabel . '" rel="tooltip">';
				$html .= '<i class="' . $icon . '"></i>';
				$html .= '</span>';

				if ($ordering)
				{
					$html .= '<input type="text" name="order[]" size="5" class="' . $class . ' text-area-order" value="' . $this->value . '" />';
				}

				$html .= '</div>';
			}
			else
			{
				$html .= '<span class="sortable-handler inactive" >';
				$html .= '<i class="' . $icon . '"></i>';
				$html .= '</span>';
			}
		}

		return $html;
	}

	/**
	 * Builds the query for the ordering list.
	 *
	 * @since 2.3.2
	 *
	 * @return FOFDatabaseQuery  The query for the ordering form field
	 */
	protected function getQuery()
	{
		$ordering = $this->name;
		$title    = $this->element['ordertitle'] ? (string) $this->element['ordertitle'] : $this->item->getColumnAlias('title');

		$db = FOFPlatform::getInstance()->getDbo();
		$query = $db->getQuery(true);
		$query->select(array($db->quoteName($ordering, 'value'), $db->quoteName($title, 'text')))
				->from($db->quoteName($this->item->getTableName()))
				->order($ordering);

		return $query;
	}
}
fof/form/field/cachehandler.php000064400000004674152177723700012504 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('cachehandler');

/**
 * Form Field class for FOF
 * Joomla! cache handlers
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldCachehandler extends JFormFieldCacheHandler implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

		return '<span id="' . $this->id . '" ' . $class . '>' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		return '<span class="' . $this->id . ' ' . $class . '">' .
			htmlspecialchars(FOFFormFieldList::getOptionName($this->getOptions(), $this->value), ENT_COMPAT, 'UTF-8') .
			'</span>';
	}
}
fof/form/field/groupedlist.php000064400000010452152177723700012433 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('groupedlist');

/**
 * Form Field class for FOF
 * Supports a generic list of options.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldGroupedlist extends JFormFieldGroupedList implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$selected = self::getOptionName($this->getGroups(), $this->value);

		if (is_null($selected))
		{
			$selected = array(
				'group'	 => '',
				'item'	 => ''
			);
		}

		return '<span id="' . $this->id . '-group" class="fof-groupedlist-group ' . $class . '>' .
			htmlspecialchars($selected['group'], ENT_COMPAT, 'UTF-8') .
			'</span>' .
			'<span id="' . $this->id . '-item" class="fof-groupedlist-item ' . $class . '>' .
			htmlspecialchars($selected['item'], ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class = $this->element['class'] ? (string) $this->element['class'] : '';

		$selected = self::getOptionName($this->getGroups(), $this->value);

		if (is_null($selected))
		{
			$selected = array(
				'group'	 => '',
				'item'	 => ''
			);
		}

		return '<span class="' . $this->id . '-group fof-groupedlist-group ' . $class . '">' .
			htmlspecialchars($selected['group'], ENT_COMPAT, 'UTF-8') .
			'</span>' .
			'<span class="' . $this->id . '-item fof-groupedlist-item ' . $class . '">' .
			htmlspecialchars($selected['item'], ENT_COMPAT, 'UTF-8') .
			'</span>';
	}

	/**
	 * Gets the active option's label given an array of JHtml options
	 *
	 * @param   array   $data      The JHtml options to parse
	 * @param   mixed   $selected  The currently selected value
	 * @param   string  $groupKey  Group name
	 * @param   string  $optKey    Key name
	 * @param   string  $optText   Value name
	 *
	 * @return  mixed   The label of the currently selected option
	 */
	public static function getOptionName($data, $selected = null, $groupKey = 'items', $optKey = 'value', $optText = 'text')
	{
		$ret = null;

		foreach ($data as $dataKey => $group)
		{
			$label = $dataKey;
			$noGroup = is_int($dataKey);

			if (is_array($group))
			{
				$subList = $group[$groupKey];
				$label = $group[$optText];
				$noGroup = false;
			}
			elseif (is_object($group))
			{
				// Sub-list is in a property of an object
				$subList = $group->$groupKey;
				$label = $group->$optText;
				$noGroup = false;
			}
			else
			{
				throw new RuntimeException('Invalid group contents.', 1);
			}

			if ($noGroup)
			{
				$label = '';
			}

			$match = FOFFormFieldList::getOptionName($data, $selected, $optKey, $optText);

			if (!is_null($match))
			{
				$ret = array(
					'group'	 => $label,
					'item'	 => $match
				);
				break;
			}
		}

		return $ret;
	}
}
fof/form/field/image.php000064400000001042152177723700011147 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Form Field class for the FOF framework
 * Media selection field. This is an alias of the "media" field type.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldImage extends FOFFormFieldMedia
{
}
fof/form/field/email.php000064400000007145152177723700011166 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

JFormHelper::loadFieldClass('email');

/**
 * Form Field class for the FOF framework
 * Supports a one line text field.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldEmail extends JFormFieldEMail implements FOFFormField
{
	protected $static;

	protected $repeatable;

	/** @var   FOFTable  The item being rendered in a repeatable form field */
	public $item;

	/** @var int A monotonically increasing number, denoting the row number in a repeatable view */
	public $rowid;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to the the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   2.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'static':
				if (empty($this->static))
				{
					$this->static = $this->getStatic();
				}

				return $this->static;
				break;

			case 'repeatable':
				if (empty($this->repeatable))
				{
					$this->repeatable = $this->getRepeatable();
				}

				return $this->repeatable;
				break;

			default:
				return parent::__get($name);
		}
	}

	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic()
	{
		$class = $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';
		$dolink = $this->element['show_link'] == 'true';
		$empty_replacement = '';

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$innerHtml = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		if ($dolink)
		{
			$innerHtml = '<a href="mailto:' . $innerHtml . '">' .
				$innerHtml . '</a>';
		}

		return '<span id="' . $this->id . '" ' . $class . '>' .
			$innerHtml .
			'</span>';
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		// Initialise
		$class = '';
		$show_link = false;
		$link_url = '';
		$empty_replacement = '';

		// Get field parameters
		if ($this->element['class'])
		{
			$class = (string) $this->element['class'];
		}

		if ($this->element['show_link'] == 'true')
		{
			$show_link = true;
		}

		if ($this->element['url'])
		{
			$link_url = $this->element['url'];
		}
		else
		{
			$link_url = 'mailto:' . htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		// Get the (optionally formatted) value
		if (!empty($empty_replacement) && empty($this->value))
		{
			$this->value = JText::_($empty_replacement);
		}

		$value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Create the HTML
		$html = '<span class="' . $this->id . ' ' . $class . '">';

		if ($show_link)
		{
			$html .= '<a href="' . $link_url . '">';
		}

		$html .= $value;

		if ($show_link)
		{
			$html .= '</a>';
		}

		$html .= '</span>';

		return $html;
	}
}
fof/form/field/relation.php000064400000011564152177723700011714 0ustar00<?php
/**
 * @package    FrameworkOnFramework
 * @subpackage form
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Form Field class for FOF
 * Relation list
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFFormFieldRelation extends FOFFormFieldList
{
	/**
	 * Get the rendering of this field type for static display, e.g. in a single
	 * item view (typically a "read" task).
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getStatic() {
		return $this->getRepeatable();
	}

	/**
	 * Get the rendering of this field type for a repeatable (grid) display,
	 * e.g. in a view listing many item (typically a "browse" task)
	 *
	 * @since 2.0
	 *
	 * @return  string  The field HTML
	 */
	public function getRepeatable()
	{
		$class         = $this->element['class'] ? (string) $this->element['class'] : $this->id;
		$relationclass = $this->element['relationclass'] ? (string) $this->element['relationclass'] : '';
		$value_field   = $this->element['value_field'] ? (string) $this->element['value_field'] : 'title';
		$translate     = $this->element['translate'] ? (string) $this->element['translate'] : false;
		$link_url      = $this->element['url'] ? (string) $this->element['url'] : false;

		if (!($link_url && $this->item instanceof FOFTable))
		{
			$link_url = false;
		}

		if ($this->element['empty_replacement'])
		{
			$empty_replacement = (string) $this->element['empty_replacement'];
		}

		$relationName = FOFInflector::pluralize($this->name);
		$relations    = $this->item->getRelations()->getMultiple($relationName);

		foreach ($relations as $relation) {

			$html = '<span class="' . $relationclass . '">';

			if ($link_url)
			{
				$keyfield = $relation->getKeyName();
				$this->_relationId =  $relation->$keyfield;

				$url = $this->parseFieldTags($link_url);
				$html .= '<a href="' . $url . '">';
			}

			$value = $relation->get($relation->getColumnAlias($value_field));

			// Get the (optionally formatted) value
			if (!empty($empty_replacement) && empty($value))
			{
				$value = JText::_($empty_replacement);
			}

			if ($translate == true)
			{
				$html .= JText::_($value);
			}
			else
			{
				$html .= $value;
			}

			if ($link_url)
			{
				$html .= '</a>';
			}

			$html .= '</span>';

			$rels[] = $html;
		}

		$html = '<span class="' . $class . '">';
		$html .= implode(', ', $rels);
		$html .= '</span>';

		return $html;
	}

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 */
	protected function getOptions()
	{
		$options     = array();
		$this->value = array();

		$value_field = $this->element['value_field'] ? (string) $this->element['value_field'] : 'title';

		$input     = new FOFInput;
		$component = ucfirst(str_replace('com_', '', $input->getString('option')));
		$view      = ucfirst($input->getString('view'));
		$relation  = FOFInflector::pluralize((string) $this->element['name']);

		$model = FOFModel::getTmpInstance(ucfirst($relation), $component . 'Model');
		$table = $model->getTable();

		$key   = $table->getKeyName();
		$value = $table->getColumnAlias($value_field);

		foreach ($model->getItemList(true) as $option)
		{
			$options[] = JHtml::_('select.option', $option->$key, $option->$value);
		}

		if ($id = FOFModel::getAnInstance($view)->getId())
		{
			$table = FOFTable::getInstance($view, $component . 'Table');
			$table->load($id);

			$relations = $table->getRelations()->getMultiple($relation);

			foreach ($relations as $item)
			{
				$this->value[] = $item->getId();
			}
		}

		return $options;
	}

	/**
	 * Replace string with tags that reference fields
	 *
	 * @param   string  $text  Text to process
	 *
	 * @return  string         Text with tags replace
	 */
	protected function parseFieldTags($text)
	{
		$ret = $text;

		// Replace [ITEM:ID] in the URL with the item's key value (usually:
		// the auto-incrementing numeric ID)
		$keyfield = $this->item->getKeyName();
		$replace  = $this->item->$keyfield;
		$ret = str_replace('[ITEM:ID]', $replace, $ret);

		// Replace the [ITEMID] in the URL with the current Itemid parameter
		$ret = str_replace('[ITEMID]', JFactory::getApplication()->input->getInt('Itemid', 0), $ret);

		// Replace the [RELATION:ID] in the URL with the relation's key value
		$ret = str_replace('[RELATION:ID]', $this->_relationId, $ret);

		// Replace other field variables in the URL
		$fields = $this->item->getTableFields();

		foreach ($fields as $fielddata)
		{
			$fieldname = $fielddata->Field;

			if (empty($fieldname))
			{
				$fieldname = $fielddata->column_name;
			}

			$search    = '[ITEM:' . strtoupper($fieldname) . ']';
			$replace   = $this->item->$fieldname;
			$ret  = str_replace($search, $replace, $ret);
		}

		return $ret;
	}
}
fof/template/utils.php000064400000034272152177723700011025 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  template
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * A utility class to load view templates, media files and modules.
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFTemplateUtils
{
	/**
	 * Add a CSS file to the page generated by the CMS
	 *
	 * @param   string  $path  A fancy path definition understood by parsePath
	 *
	 * @see FOFTemplateUtils::parsePath
	 *
	 * @return  void
	 */
	public static function addCSS($path)
	{
		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			if (method_exists($document, 'addStyleSheet'))
			{
				$url = self::parsePath($path);
				$document->addStyleSheet($url);
			}
		}
	}

	/**
	 * Add a JS script file to the page generated by the CMS.
	 *
	 * There are three combinations of defer and async (see http://www.w3schools.com/tags/att_script_defer.asp):
	 * * $defer false, $async true: The script is executed asynchronously with the rest of the page
	 *   (the script will be executed while the page continues the parsing)
	 * * $defer true, $async false: The script is executed when the page has finished parsing.
	 * * $defer false, $async false. (default) The script is loaded and executed immediately. When it finishes
	 *   loading the browser continues parsing the rest of the page.
	 *
	 * When you are using $defer = true there is no guarantee about the load order of the scripts. Whichever
	 * script loads first will be executed first. The order they appear on the page is completely irrelevant.
	 *
	 * @param   string   $path   A fancy path definition understood by parsePath
	 * @param   boolean  $defer  Adds the defer attribute, meaning that your script
	 *                           will only load after the page has finished parsing.
	 * @param   boolean  $async  Adds the async attribute, meaning that your script
	 *                           will be executed while the resto of the page
	 *                           continues parsing.
	 *
	 * @see FOFTemplateUtils::parsePath
	 *
	 * @return  void
	 */
	public static function addJS($path, $defer = false, $async = false)
	{
		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			if (method_exists($document, 'addScript'))
			{
				$url = self::parsePath($path);
				$document->addScript($url, "text/javascript", $defer, $async);
			}
		}
	}

	/**
	 * Compile a LESS file into CSS and add it to the page generated by the CMS.
	 * This method has integrated cache support. The compiled LESS files will be
	 * written to the media/lib_fof/compiled directory of your site. If the file
	 * cannot be written we will use the $altPath, if specified
	 *
	 * @param   string   $path        A fancy path definition understood by parsePath pointing to the source LESS file
	 * @param   string   $altPath     A fancy path definition understood by parsePath pointing to a precompiled CSS file,
	 *                                used when we can't write the generated file to the output directory
	 * @param   boolean  $returnPath  Return the URL of the generated CSS file but do not include it. If it can't be
	 *                                generated, false is returned and the alt files are not included
	 *
	 * @see FOFTemplateUtils::parsePath
	 *
	 * @since 2.0
	 *
	 * @return  mixed  True = successfully included generated CSS, False = the alternate CSS file was used, null = the source file does not exist
	 */
	public static function addLESS($path, $altPath = null, $returnPath = false)
	{
		// Does the cache directory exists and is writeable
		static $sanityCheck = null;

		// Get the local LESS file
		$localFile = self::parsePath($path, true);

		$filesystem   = FOFPlatform::getInstance()->getIntegrationObject('filesystem');
        $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();

		if (is_null($sanityCheck))
		{
			// Make sure the cache directory exists
			if (!is_dir($platformDirs['public'] . '/media/lib_fof/compiled/'))
			{
				$sanityCheck = $filesystem->folderCreate($platformDirs['public'] . '/media/lib_fof/compiled/');
			}
			else
			{
				$sanityCheck = true;
			}
		}

		// No point continuing if the source file is not there or we can't write to the cache

		if (!$sanityCheck || !is_file($localFile))
		{
			if (!$returnPath)
			{
				if (is_string($altPath))
				{
					self::addCSS($altPath);
				}
				elseif (is_array($altPath))
				{
					foreach ($altPath as $anAltPath)
					{
						self::addCSS($anAltPath);
					}
				}
			}

			return false;
		}

		// Get the source file's unique ID
		$id = md5(filemtime($localFile) . filectime($localFile) . $localFile);

		// Get the cached file path
		$cachedPath = $platformDirs['public'] . '/media/lib_fof/compiled/' . $id . '.css';

		// Get the LESS compiler
		$lessCompiler = new FOFLess;
		$lessCompiler->formatterName = 'compressed';

		// Should I add an alternative import path?
		$altFiles = self::getAltPaths($path);

		if (isset($altFiles['alternate']))
		{
			$currentLocation = realpath(dirname($localFile));
			$normalLocation = realpath(dirname($altFiles['normal']));
			$alternateLocation = realpath(dirname($altFiles['alternate']));

			if ($currentLocation == $normalLocation)
			{
				$lessCompiler->importDir = array($alternateLocation, $currentLocation);
			}
			else
			{
				$lessCompiler->importDir = array($currentLocation, $normalLocation);
			}
		}

		// Compile the LESS file
		$lessCompiler->checkedCompile($localFile, $cachedPath);

		// Add the compiled CSS to the page
		$base_url = rtrim(FOFPlatform::getInstance()->URIbase(), '/');

		if (substr($base_url, -14) == '/administrator')
		{
			$base_url = substr($base_url, 0, -14);
		}

		$url = $base_url . '/media/lib_fof/compiled/' . $id . '.css';

		if ($returnPath)
		{
			return $url;
		}
		else
		{
			$document = FOFPlatform::getInstance()->getDocument();

			if ($document instanceof JDocument)
			{
				if (method_exists($document, 'addStyleSheet'))
				{
					$document->addStyleSheet($url);
				}
			}
			return true;
		}
	}

	/**
	 * Creates a SEF compatible sort header. Standard Joomla function will add a href="#" tag, so with SEF
	 * enabled, the browser will follow the fake link instead of processing the onSubmit event; so we
	 * need a fix.
	 *
	 * @param   string          $text   Header text
	 * @param   string          $field  Field used for sorting
	 * @param   FOFUtilsObject  $list   Object holding the direction and the ordering field
	 *
	 * @return  string  HTML code for sorting
	 */
	public static function sefSort($text, $field, $list)
	{
		$sort = JHTML::_('grid.sort', JText::_(strtoupper($text)) . '&nbsp;', $field, $list->order_Dir, $list->order);

		return str_replace('href="#"', 'href="javascript:void(0);"', $sort);
	}

	/**
	 * Parse a fancy path definition into a path relative to the site's root,
	 * respecting template overrides, suitable for inclusion of media files.
	 * For example, media://com_foobar/css/test.css is parsed into
	 * media/com_foobar/css/test.css if no override is found, or
	 * templates/mytemplate/media/com_foobar/css/test.css if the current
	 * template is called mytemplate and there's a media override for it.
	 *
	 * The valid protocols are:
	 * media://		The media directory or a media override
	 * admin://		Path relative to administrator directory (no overrides)
	 * site://		Path relative to site's root (no overrides)
	 *
	 * @param   string   $path       Fancy path
	 * @param   boolean  $localFile  When true, it returns the local path, not the URL
	 *
	 * @return  string  Parsed path
	 */
	public static function parsePath($path, $localFile = false)
	{
        $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();

		if ($localFile)
		{
			$url = rtrim($platformDirs['root'], DIRECTORY_SEPARATOR) . '/';
		}
		else
		{
			$url = FOFPlatform::getInstance()->URIroot();
		}

		$altPaths = self::getAltPaths($path);
		$filePath = $altPaths['normal'];

		// If JDEBUG is enabled, prefer that path, else prefer an alternate path if present
		if (defined('JDEBUG') && JDEBUG && isset($altPaths['debug']))
		{
			if (file_exists($platformDirs['public'] . '/' . $altPaths['debug']))
			{
				$filePath = $altPaths['debug'];
			}
		}
		elseif (isset($altPaths['alternate']))
		{
			if (file_exists($platformDirs['public'] . '/' . $altPaths['alternate']))
			{
				$filePath = $altPaths['alternate'];
			}
		}

		$url .= $filePath;

		return $url;
	}

	/**
	 * Parse a fancy path definition into a path relative to the site's root.
	 * It returns both the normal and alternative (template media override) path.
	 * For example, media://com_foobar/css/test.css is parsed into
	 * array(
	 *   'normal' => 'media/com_foobar/css/test.css',
	 *   'alternate' => 'templates/mytemplate/media/com_foobar/css//test.css'
	 * );
	 *
	 * The valid protocols are:
	 * media://		The media directory or a media override
	 * admin://		Path relative to administrator directory (no alternate)
	 * site://		Path relative to site's root (no alternate)
	 *
	 * @param   string  $path  Fancy path
	 *
	 * @return  array  Array of normal and alternate parsed path
	 */
	public static function getAltPaths($path)
	{
		$protoAndPath = explode('://', $path, 2);

		if (count($protoAndPath) < 2)
		{
			$protocol = 'media';
		}
		else
		{
			$protocol = $protoAndPath[0];
			$path = $protoAndPath[1];
		}

		$path = ltrim($path, '/' . DIRECTORY_SEPARATOR);

		switch ($protocol)
		{
			case 'media':
				// Do we have a media override in the template?
				$pathAndParams = explode('?', $path, 2);

				$ret = array(
					'normal'	 => 'media/' . $pathAndParams[0],
					'alternate'	 => FOFPlatform::getInstance()->getTemplateOverridePath('media:/' . $pathAndParams[0], false),
				);
				break;

			case 'admin':
				$ret = array(
					'normal' => 'administrator/' . $path
				);
				break;

			default:
			case 'site':
				$ret = array(
					'normal' => $path
				);
				break;
		}

		// For CSS and JS files, add a debug path if the supplied file is compressed
		$filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');
		$ext        = $filesystem->getExt($ret['normal']);

		if (in_array($ext, array('css', 'js')))
		{
			$file = basename($filesystem->stripExt($ret['normal']));

			/*
			 * Detect if we received a file in the format name.min.ext
			 * If so, strip the .min part out, otherwise append -uncompressed
			 */

			if (strlen($file) > 4 && strrpos($file, '.min', '-4'))
			{
				$position = strrpos($file, '.min', '-4');
				$filename = str_replace('.min', '.', $file, $position) . $ext;
			}
			else
			{
				$filename = $file . '-uncompressed.' . $ext;
			}

			// Clone the $ret array so we can manipulate the 'normal' path a bit
			$t1 = (object) $ret;
			$temp = clone $t1;
			unset($t1);
			$temp = (array)$temp;
			$normalPath = explode('/', $temp['normal']);
			array_pop($normalPath);
			$normalPath[] = $filename;
			$ret['debug'] = implode('/', $normalPath);
		}

		return $ret;
	}

	/**
	 * Returns the contents of a module position
	 *
	 * @param   string  $position  The position name, e.g. "position-1"
	 * @param   int     $style     Rendering style; please refer to Joomla!'s code for more information
	 *
	 * @return  string  The contents of the module position
	 */
	public static function loadPosition($position, $style = -2)
	{
		$document = FOFPlatform::getInstance()->getDocument();

		if (!($document instanceof JDocument))
		{
			return '';
		}

		if (!method_exists($document, 'loadRenderer'))
		{
			return '';
		}

		try
		{
			$renderer = $document->loadRenderer('module');
		}
		catch (Exception $exc)
		{
			return '';
		}

		$params = array('style' => $style);

		$contents = '';

		foreach (JModuleHelper::getModules($position) as $mod)
		{
			$contents .= $renderer->render($mod, $params);
		}

		return $contents;
	}

	/**
	 * Merges the current url with new or changed parameters.
	 *
	 * This method merges the route string with the url parameters defined
	 * in current url. The parameters defined in current url, but not given
	 * in route string, will automatically reused in the resulting url.
	 * But only these following parameters will be reused:
	 *
	 * option, view, layout, format
	 *
	 * Example:
	 *
	 * Assuming that current url is:
	 * http://fobar.com/index.php?option=com_foo&view=cpanel
	 *
	 * <code>
	 * <?php echo FOFTemplateutils::route('view=categories&layout=tree'); ?>
	 * </code>
	 *
	 * Result:
	 * http://fobar.com/index.php?option=com_foo&view=categories&layout=tree
	 *
	 * @param   string  $route  The parameters string
	 *
	 * @return  string  The human readable, complete url
	 */
	public static function route($route = '')
	{
		$route = trim($route);

		// Special cases

		if ($route == 'index.php' || $route == 'index.php?')
		{
			$result = $route;
		}
		elseif (substr($route, 0, 1) == '&')
		{
			$url = JURI::getInstance();
			$vars = array();
			parse_str($route, $vars);

			$url->setQuery(array_merge($url->getQuery(true), $vars));

			$result = 'index.php?' . $url->getQuery();
		}
		else
		{
			$url = JURI::getInstance();
			$props = $url->getQuery(true);

			// Strip 'index.php?'
			if (substr($route, 0, 10) == 'index.php?')
			{
				$route = substr($route, 10);
			}

			// Parse route
			$parts = array();
			parse_str($route, $parts);
			$result = array();

			// Check to see if there is component information in the route if not add it

			if (!isset($parts['option']) && isset($props['option']))
			{
				$result[] = 'option=' . $props['option'];
			}

			// Add the layout information to the route only if it's not 'default'

			if (!isset($parts['view']) && isset($props['view']))
			{
				$result[] = 'view=' . $props['view'];

				if (!isset($parts['layout']) && isset($props['layout']))
				{
					$result[] = 'layout=' . $props['layout'];
				}
			}

			// Add the format information to the URL only if it's not 'html'

			if (!isset($parts['format']) && isset($props['format']) && $props['format'] != 'html')
			{
				$result[] = 'format=' . $props['format'];
			}

			// Reconstruct the route

			if (!empty($route))
			{
				$result[] = $route;
			}

			$result = 'index.php?' . implode('&', $result);
		}

		return JRoute::_($result);
	}
}
fof/version.txt000064400000000020152177723700007547 0ustar002.5.5
2016-08-19fof/integration/joomla/filesystem/filesystem.php000064400000013412152177723700016217 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  platformFilesystem
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

class FOFIntegrationJoomlaFilesystem extends FOFPlatformFilesystem implements FOFPlatformFilesystemInterface
{
	public function __construct()
	{
		if (class_exists('JLoader'))
		{
			JLoader::import('joomla.filesystem.path');
			JLoader::import('joomla.filesystem.folder');
			JLoader::import('joomla.filesystem.file');
		}
	}

	/**
	 * Does the file exists?
	 *
	 * @param   $path  string   Path to the file to test
	 *
	 * @return  bool
	 */
    public function fileExists($path)
    {
        return JFile::exists($path);
    }

	/**
	 * Delete a file or array of files
	 *
	 * @param   mixed  $file  The file name or an array of file names
	 *
	 * @return  boolean  True on success
	 *
	 */
    public function fileDelete($file)
    {
        return JFile::delete($file);
    }

	/**
	 * Copies a file
	 *
	 * @param   string   $src          The path to the source file
	 * @param   string   $dest         The path to the destination file
     * @param   string   $path         An optional base path to prefix to the file names
     * @param   boolean  $use_streams  True to use streams
	 *
	 * @return  boolean  True on success
	 */
    public function fileCopy($src, $dest, $path = null, $use_streams = false)
    {
        return JFile::copy($src, $dest, $path, $use_streams);
    }

	/**
	 * Write contents to a file
	 *
	 * @param   string   $file         The full file path
	 * @param   string   &$buffer      The buffer to write
     * @param   boolean  $use_streams  Use streams
	 *
	 * @return  boolean  True on success
	 */
    public function fileWrite($file, &$buffer, $use_streams = false)
    {
        return JFile::write($file, $buffer, $use_streams);
    }

	/**
	 * Checks for snooping outside of the file system root.
	 *
	 * @param   string  $path  A file system path to check.
	 *
	 * @return  string  A cleaned version of the path or exit on error.
	 *
	 * @throws  Exception
	 */
    public function pathCheck($path)
    {
        return JPath::check($path);
    }

	/**
	 * Function to strip additional / or \ in a path name.
	 *
	 * @param   string  $path  The path to clean.
	 * @param   string  $ds    Directory separator (optional).
	 *
	 * @return  string  The cleaned path.
	 *
	 * @throws  UnexpectedValueException
	 */
    public function pathClean($path, $ds = DIRECTORY_SEPARATOR)
    {
        return JPath::clean($path, $ds);
    }

	/**
	 * Searches the directory paths for a given file.
	 *
	 * @param   mixed   $paths  An path string or array of path strings to search in
	 * @param   string  $file   The file name to look for.
	 *
	 * @return  mixed   The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
	 */
    public function pathFind($paths, $file)
    {
        return JPath::find($paths, $file);
    }

	/**
	 * Wrapper for the standard file_exists function
	 *
	 * @param   string  $path  Folder name relative to installation dir
	 *
	 * @return  boolean  True if path is a folder
	 */
    public function folderExists($path)
    {
        return JFolder::exists($path);
    }

	/**
	 * Utility function to read the files in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludefilter  Array of filter to exclude
     * @param   boolean  $naturalSort    False for asort, true for natsort
	 *
	 * @return  array  Files in the given folder.
	 */
    public function folderFiles($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
                                $excludefilter = array('^\..*', '.*~'), $naturalSort = false)
    {
        return JFolder::files($path, $filter, $recurse, $full, $exclude, $excludefilter, $naturalSort);
    }

	/**
	 * Utility function to read the folders in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for folder names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the folders.
	 * @param   array    $exclude        Array with names of folders which should not be shown in the result.
	 * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
	 *
	 * @return  array  Folders in the given folder.
	 */
    public function folderFolders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
                                  $excludefilter = array('^\..*'))
    {
        return JFolder::folders($path, $filter, $recurse, $full, $exclude, $excludefilter);
    }

	/**
	 * Create a folder -- and all necessary parent folders.
	 *
	 * @param   string   $path  A path to create from the base path.
	 * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
	 *
	 * @return  boolean  True if successful.
	 */
    public function folderCreate($path = '', $mode = 0755)
    {
        return JFolder::create($path, $mode);
    }
}fof/integration/joomla/platform.php000064400000057752152177723700013512 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  platform
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Part of the FOF Platform Abstraction Layer.
 *
 * This implements the platform class for Joomla! 2.5 or later
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFIntegrationJoomlaPlatform extends FOFPlatform implements FOFPlatformInterface
{
	/**
	 * The table and table field cache object, used to speed up database access
	 *
	 * @var  JRegistry|null
	 */
	private $_cache = null;

	/**
	 * Public constructor
	 */
	public function __construct()
	{
		$this->name = 'joomla';
		$this->humanReadableName = 'Joomla!';
		$this->version = defined('JVERSION') ? JVERSION : '0.0';
	}

    /**
     * Checks if the current script is run inside a valid CMS execution
     *
     * @see FOFPlatformInterface::checkExecution()
     *
     * @return bool
     */
    public function checkExecution()
    {
        return defined('_JEXEC');
    }

    public function raiseError($code, $message)
    {
        if (version_compare($this->version, '3.0', 'ge'))
        {
            throw new Exception($message, $code);
        }
        else
        {
            return JError::raiseError($code, $message);
        }
    }

	/**
	 * Is this platform enabled?
	 *
	 * @see FOFPlatformInterface::isEnabled()
	 *
	 * @return  boolean
	 */
	public function isEnabled()
	{
		if (is_null($this->isEnabled))
		{
			$this->isEnabled = true;

			// Make sure _JEXEC is defined
			if (!defined('_JEXEC'))
			{
				$this->isEnabled = false;
			}

			// We need JVERSION to be defined
			if ($this->isEnabled)
			{
				if (!defined('JVERSION'))
				{
					$this->isEnabled = false;
				}
			}

			// Check if JFactory exists
			if ($this->isEnabled)
			{
				if (!class_exists('JFactory'))
				{
					$this->isEnabled = false;
				}
			}

			// Check if JApplication exists
			if ($this->isEnabled)
			{
				$appExists = class_exists('JApplication');
				$appExists = $appExists || class_exists('JCli');
				$appExists = $appExists || class_exists('JApplicationCli');

				if (!$appExists)
				{
					$this->isEnabled = false;
				}
			}
		}

		return $this->isEnabled;
	}

	/**
	 * Main function to detect if we're running in a CLI environment and we're admin
	 *
	 * @return  array  isCLI and isAdmin. It's not an associtive array, so we can use list.
	 */
	protected function isCliAdmin()
	{
		static $isCLI   = null;
		static $isAdmin = null;

		if (is_null($isCLI) && is_null($isAdmin))
		{
			try
			{
				if (is_null(JFactory::$application))
				{
					$isCLI = true;
				}
				else
				{
                    $app = JFactory::getApplication();
					$isCLI = $app instanceof JException || $app instanceof JApplicationCli;
				}
			}
			catch (Exception $e)
			{
				$isCLI = true;
			}

			if ($isCLI)
			{
				$isAdmin = false;
			}
			else
			{
				$isAdmin = !JFactory::$application ? false : JFactory::getApplication()->isAdmin();
			}
		}

		return array($isCLI, $isAdmin);
	}

    /**
     * Returns absolute path to directories used by the CMS.
     *
     * @see FOFPlatformInterface::getPlatformBaseDirs()
     *
     * @return  array  A hash array with keys root, public, admin, tmp and log.
     */
    public function getPlatformBaseDirs()
    {
        return array(
            'root'   => JPATH_ROOT,
            'public' => JPATH_SITE,
            'admin'  => JPATH_ADMINISTRATOR,
            'tmp'    => JFactory::getConfig()->get('tmp_dir'),
            'log'    => JFactory::getConfig()->get('log_dir')
        );
    }

	/**
	 * Returns the base (root) directories for a given component.
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @see FOFPlatformInterface::getComponentBaseDirs()
	 *
	 * @return  array  A hash array with keys main, alt, site and admin.
	 */
	public function getComponentBaseDirs($component)
	{
		if ($this->isFrontend())
		{
			$mainPath	= JPATH_SITE . '/components/' . $component;
			$altPath	= JPATH_ADMINISTRATOR . '/components/' . $component;
		}
		else
		{
			$mainPath	= JPATH_ADMINISTRATOR . '/components/' . $component;
			$altPath	= JPATH_SITE . '/components/' . $component;
		}

		return array(
			'main'	=> $mainPath,
			'alt'	=> $altPath,
			'site'	=> JPATH_SITE . '/components/' . $component,
			'admin'	=> JPATH_ADMINISTRATOR . '/components/' . $component,
		);
	}

	/**
	 * Return a list of the view template paths for this component.
	 *
	 * @param   string   $component  The name of the component. For Joomla! this
	 *                               is something like "com_example"
	 * @param   string   $view       The name of the view you're looking a
	 *                               template for
	 * @param   string   $layout     The layout name to load, e.g. 'default'
	 * @param   string   $tpl        The sub-template name to load (null by default)
	 * @param   boolean  $strict     If true, only the specified layout will be searched for.
	 *                               Otherwise we'll fall back to the 'default' layout if the
	 *                               specified layout is not found.
	 *
	 * @see FOFPlatformInterface::getViewTemplateDirs()
	 *
	 * @return  array
	 */
	public function getViewTemplatePaths($component, $view, $layout = 'default', $tpl = null, $strict = false)
	{
		$isAdmin = $this->isBackend();

		$basePath = $isAdmin ? 'admin:' : 'site:';
		$basePath .= $component . '/';
		$altBasePath = $basePath;
		$basePath .= $view . '/';
		$altBasePath .= (FOFInflector::isSingular($view) ? FOFInflector::pluralize($view) : FOFInflector::singularize($view)) . '/';

		if ($strict)
		{
			$paths = array(
				$basePath . $layout . ($tpl ? "_$tpl" : ''),
				$altBasePath . $layout . ($tpl ? "_$tpl" : ''),
			);
		}
		else
		{
			$paths = array(
				$basePath . $layout . ($tpl ? "_$tpl" : ''),
				$basePath . $layout,
				$basePath . 'default' . ($tpl ? "_$tpl" : ''),
				$basePath . 'default',
				$altBasePath . $layout . ($tpl ? "_$tpl" : ''),
				$altBasePath . $layout,
				$altBasePath . 'default' . ($tpl ? "_$tpl" : ''),
				$altBasePath . 'default',
			);
			$paths = array_unique($paths);
		}

		return $paths;
	}

	/**
	 * Get application-specific suffixes to use with template paths. This allows
	 * you to look for view template overrides based on the application version.
	 *
	 * @return  array  A plain array of suffixes to try in template names
	 */
	public function getTemplateSuffixes()
	{
		$jversion = new JVersion;
		$versionParts = explode('.', $jversion->RELEASE);
		$majorVersion = array_shift($versionParts);
		$suffixes = array(
			'.j' . str_replace('.', '', $jversion->getHelpVersion()),
			'.j' . $majorVersion,
		);

		return $suffixes;
	}

	/**
	 * Return the absolute path to the application's template overrides
	 * directory for a specific component. We will use it to look for template
	 * files instead of the regular component directorues. If the application
	 * does not have such a thing as template overrides return an empty string.
	 *
	 * @param   string   $component  The name of the component for which to fetch the overrides
	 * @param   boolean  $absolute   Should I return an absolute or relative path?
	 *
	 * @return  string  The path to the template overrides directory
	 */
	public function getTemplateOverridePath($component, $absolute = true)
	{
		list($isCli, $isAdmin) = $this->isCliAdmin();

		if (!$isCli)
		{
			if ($absolute)
			{
				$path = JPATH_THEMES . '/';
			}
			else
			{
				$path = $isAdmin ? 'administrator/templates/' : 'templates/';
			}

			if (substr($component, 0, 7) == 'media:/')
			{
				$directory = 'media/' . substr($component, 7);
			}
			else
			{
				$directory = 'html/' . $component;
			}

			$path .= JFactory::getApplication()->getTemplate() .
				'/' . $directory;
		}
		else
		{
			$path = '';
		}

		return $path;
	}

	/**
	 * Load the translation files for a given component.
	 *
	 * @param   string  $component  The name of the component. For Joomla! this
	 *                              is something like "com_example"
	 *
	 * @see FOFPlatformInterface::loadTranslations()
	 *
	 * @return  void
	 */
	public function loadTranslations($component)
	{
		if ($this->isBackend())
		{
			$paths = array(JPATH_ROOT, JPATH_ADMINISTRATOR);
		}
		else
		{
			$paths = array(JPATH_ADMINISTRATOR, JPATH_ROOT);
		}

		$jlang = JFactory::getLanguage();
		$jlang->load($component, $paths[0], 'en-GB', true);
		$jlang->load($component, $paths[0], null, true);
		$jlang->load($component, $paths[1], 'en-GB', true);
		$jlang->load($component, $paths[1], null, true);
	}

	/**
	 * Authorise access to the component in the back-end.
	 *
	 * @param   string  $component  The name of the component.
	 *
	 * @see FOFPlatformInterface::authorizeAdmin()
	 *
	 * @return  boolean  True to allow loading the component, false to halt loading
	 */
	public function authorizeAdmin($component)
	{
		if ($this->isBackend())
		{
			// Master access check for the back-end, Joomla! 1.6 style.
			$user = JFactory::getUser();

			if (!$user->authorise('core.manage', $component)
				&& !$user->authorise('core.admin', $component))
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Return a user object.
	 *
	 * @param   integer  $id  The user ID to load. Skip or use null to retrieve
	 *                        the object for the currently logged in user.
	 *
	 * @see FOFPlatformInterface::getUser()
	 *
	 * @return  JUser  The JUser object for the specified user
	 */
	public function getUser($id = null)
	{
		return JFactory::getUser($id);
	}

	/**
	 * Returns the JDocument object which handles this component's response.
	 *
	 * @see FOFPlatformInterface::getDocument()
	 *
	 * @return  JDocument
	 */
	public function getDocument()
	{
		$document = null;

		if (!$this->isCli())
		{
			try
			{
				$document = JFactory::getDocument();
			}
			catch (Exception $exc)
			{
				$document = null;
			}
		}

		return $document;
	}

    /**
     * Returns an object to handle dates
     *
     * @param   mixed   $time       The initial time
     * @param   null    $tzOffest   The timezone offset
     * @param   bool    $locale     Should I try to load a specific class for current language?
     *
     * @return  JDate object
     */
    public function getDate($time = 'now', $tzOffest = null, $locale = true)
    {
        if($locale)
        {
            return JFactory::getDate($time, $tzOffest);
        }
        else
        {
            return new JDate($time, $tzOffest);
        }
    }

    public function getLanguage()
    {
        return JFactory::getLanguage();
    }

    public function getDbo()
    {
		return FOFDatabaseFactory::getInstance()->getDriver('joomla');
    }

	/**
	 * This method will try retrieving a variable from the request (input) data.
	 *
	 * @param   string    $key           The user state key for the variable
	 * @param   string    $request       The request variable name for the variable
	 * @param   FOFInput  $input         The FOFInput object with the request (input) data
	 * @param   mixed     $default       The default value. Default: null
	 * @param   string    $type          The filter type for the variable data. Default: none (no filtering)
	 * @param   boolean   $setUserState  Should I set the user state with the fetched value?
	 *
	 * @see FOFPlatformInterface::getUserStateFromRequest()
	 *
	 * @return  mixed  The value of the variable
	 */
	public function getUserStateFromRequest($key, $request, $input, $default = null, $type = 'none', $setUserState = true)
	{
		list($isCLI, $isAdmin) = $this->isCliAdmin();

		if ($isCLI)
		{
			return $input->get($request, $default, $type);
		}

		$app = JFactory::getApplication();

		if (method_exists($app, 'getUserState'))
		{
			$old_state = $app->getUserState($key, $default);
		}
		else
		{
			$old_state = null;
		}

		$cur_state = (!is_null($old_state)) ? $old_state : $default;
		$new_state = $input->get($request, null, $type);

		// Save the new value only if it was set in this request
		if ($setUserState)
		{
			if ($new_state !== null)
			{
				$app->setUserState($key, $new_state);
			}
			else
			{
				$new_state = $cur_state;
			}
		}
		elseif (is_null($new_state))
		{
			$new_state = $cur_state;
		}

		return $new_state;
	}

	/**
	 * Load plugins of a specific type. Obviously this seems to only be required
	 * in the Joomla! CMS.
	 *
	 * @param   string  $type  The type of the plugins to be loaded
	 *
	 * @see FOFPlatformInterface::importPlugin()
	 *
	 * @return void
	 */
	public function importPlugin($type)
	{
		if (!$this->isCli())
		{
            JLoader::import('joomla.plugin.helper');
			JPluginHelper::importPlugin($type);
		}
	}

	/**
	 * Execute plugins (system-level triggers) and fetch back an array with
	 * their return values.
	 *
	 * @param   string  $event  The event (trigger) name, e.g. onBeforeScratchMyEar
	 * @param   array   $data   A hash array of data sent to the plugins as part of the trigger
	 *
	 * @see FOFPlatformInterface::runPlugins()
	 *
	 * @return  array  A simple array containing the results of the plugins triggered
	 */
	public function runPlugins($event, $data)
	{
		if (!$this->isCli())
		{
			$app = JFactory::getApplication();

			if (method_exists($app, 'triggerEvent'))
			{
				return $app->triggerEvent($event, $data);
			}

			// IMPORTANT: DO NOT REPLACE THIS INSTANCE OF JDispatcher WITH ANYTHING ELSE. WE NEED JOOMLA!'S PLUGIN EVENT
			// DISPATCHER HERE, NOT OUR GENERIC EVENTS DISPATCHER
			if (class_exists('JEventDispatcher'))
			{
				$dispatcher = JEventDispatcher::getInstance();
			}
			else
			{
				$dispatcher = JDispatcher::getInstance();
			}

			return $dispatcher->trigger($event, $data);
		}
		else
		{
			return array();
		}
	}

	/**
	 * Perform an ACL check.
	 *
	 * @param   string  $action     The ACL privilege to check, e.g. core.edit
	 * @param   string  $assetname  The asset name to check, typically the component's name
	 *
	 * @see FOFPlatformInterface::authorise()
	 *
	 * @return  boolean  True if the user is allowed this action
	 */
	public function authorise($action, $assetname)
	{
		if ($this->isCli())
		{
			return true;
		}

		return JFactory::getUser()->authorise($action, $assetname);
	}

	/**
	 * Is this the administrative section of the component?
	 *
	 * @see FOFPlatformInterface::isBackend()
	 *
	 * @return  boolean
	 */
	public function isBackend()
	{
		list ($isCli, $isAdmin) = $this->isCliAdmin();

		return $isAdmin && !$isCli;
	}

	/**
	 * Is this the public section of the component?
	 *
	 * @see FOFPlatformInterface::isFrontend()
	 *
	 * @return  boolean
	 */
	public function isFrontend()
	{
		list ($isCli, $isAdmin) = $this->isCliAdmin();

		return !$isAdmin && !$isCli;
	}

	/**
	 * Is this a component running in a CLI application?
	 *
	 * @see FOFPlatformInterface::isCli()
	 *
	 * @return  boolean
	 */
	public function isCli()
	{
		list ($isCli, $isAdmin) = $this->isCliAdmin();

		return !$isAdmin && $isCli;
	}

	/**
	 * Is AJAX re-ordering supported? This is 100% Joomla!-CMS specific. All
	 * other platforms should return false and never ask why.
	 *
	 * @see FOFPlatformInterface::supportsAjaxOrdering()
	 *
	 * @return  boolean
	 */
	public function supportsAjaxOrdering()
	{
		return version_compare(JVERSION, '3.0', 'ge');
	}

	/**
	 * Is the global FOF cache enabled?
	 *
	 * @return  boolean
	 */
	public function isGlobalFOFCacheEnabled()
	{
		return !(defined('JDEBUG') && JDEBUG);
	}

	/**
	 * Saves something to the cache. This is supposed to be used for system-wide
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to save
	 * @param   string  $content  The actual data to save
	 *
	 * @return  boolean  True on success
	 */
	public function setCache($key, $content)
	{
		$registry = $this->getCacheObject();

		$registry->set($key, $content);

		return $this->saveCache();
	}

	/**
	 * Retrieves data from the cache. This is supposed to be used for system-side
	 * FOF data, not application data.
	 *
	 * @param   string  $key      The key of the data to retrieve
	 * @param   string  $default  The default value to return if the key is not found or the cache is not populated
	 *
	 * @return  string  The cached value
	 */
	public function getCache($key, $default = null)
	{
		$registry = $this->getCacheObject();

		return $registry->get($key, $default);
	}

	/**
	 * Gets a reference to the cache object, loading it from the disk if
	 * needed.
	 *
	 * @param   boolean  $force  Should I forcibly reload the registry?
	 *
	 * @return  JRegistry
	 */
	private function &getCacheObject($force = false)
	{
		// Check if we have to load the cache file or we are forced to do that
		if (is_null($this->_cache) || $force)
		{
			// Create a new JRegistry object
			JLoader::import('joomla.registry.registry');
			$this->_cache = new JRegistry;

			// Try to get data from Joomla!'s cache
			$cache = JFactory::getCache('fof', '');
			$data = $cache->get('cache', 'fof');

			// If data is not found, fall back to the legacy (FOF 2.1.rc3 and earlier) method
			if ($data === false)
			{
				// Find the path to the file
				$cachePath  = JPATH_CACHE . '/fof';
				$filename   = $cachePath . '/cache.php';
                $filesystem = $this->getIntegrationObject('filesystem');

				// Load the cache file if it exists. JRegistryFormatPHP fails
				// miserably, so I have to work around it.
				if ($filesystem->fileExists($filename))
				{
					@include_once $filename;

					$filesystem->fileDelete($filename);

					$className = 'FOFCacheStorage';

					if (class_exists($className))
					{
						$object = new $className;
						$this->_cache->loadObject($object);

						$options = array(
							'class' => 'FOFCacheStorage'
						);
						$cache->store($this->_cache, 'cache', 'fof');
					}
				}
			}
			else
			{
				$this->_cache = $data;
			}
		}

		return $this->_cache;
	}

	/**
	 * Save the cache object back to disk
	 *
	 * @return  boolean  True on success
	 */
	private function saveCache()
	{
		// Get the JRegistry object of our cached data
		$registry = $this->getCacheObject();

		$cache = JFactory::getCache('fof', '');
		return $cache->store($registry, 'cache', 'fof');
	}

	/**
	 * Clears the cache of system-wide FOF data. You are supposed to call this in
	 * your components' installation script post-installation and post-upgrade
	 * methods or whenever you are modifying the structure of database tables
	 * accessed by FOF. Please note that FOF's cache never expires and is not
	 * purged by Joomla!. You MUST use this method to manually purge the cache.
	 *
	 * @return  boolean  True on success
	 */
	public function clearCache()
	{
		$false = false;
		$cache = JFactory::getCache('fof', '');
		$cache->store($false, 'cache', 'fof');
	}

    public function getConfig()
    {
        return JFactory::getConfig();
    }

	/**
	 * logs in a user
	 *
	 * @param   array  $authInfo  authentification information
	 *
	 * @return  boolean  True on success
	 */
	public function loginUser($authInfo)
	{
		JLoader::import('joomla.user.authentication');
		$options = array('remember'		 => false);
		$authenticate = JAuthentication::getInstance();
		$response = $authenticate->authenticate($authInfo, $options);

        // User failed to authenticate: maybe he enabled two factor authentication?
        // Let's try again "manually", skipping the check vs two factor auth
        // Due the big mess with encryption algorithms and libraries, we are doing this extra check only
        // if we're in Joomla 2.5.18+ or 3.2.1+
        if($response->status != JAuthentication::STATUS_SUCCESS && method_exists('JUserHelper', 'verifyPassword'))
        {
            $db    = $this->getDbo();
            $query = $db->getQuery(true)
                        ->select('id, password')
                        ->from('#__users')
                        ->where('username=' . $db->quote($authInfo['username']));
            $result = $db->setQuery($query)->loadObject();

            if ($result)
            {

                $match = JUserHelper::verifyPassword($authInfo['password'], $result->password, $result->id);

                if ($match === true)
                {
                    // Bring this in line with the rest of the system
                    $user = JUser::getInstance($result->id);
                    $response->email = $user->email;
                    $response->fullname = $user->name;

                    if (JFactory::getApplication()->isAdmin())
                    {
                        $response->language = $user->getParam('admin_language');
                    }
                    else
                    {
                        $response->language = $user->getParam('language');
                    }

                    $response->status = JAuthentication::STATUS_SUCCESS;
                    $response->error_message = '';
                }
            }
        }

		if ($response->status == JAuthentication::STATUS_SUCCESS)
		{
			$this->importPlugin('user');
			$results = $this->runPlugins('onLoginUser', array((array) $response, $options));

			JLoader::import('joomla.user.helper');
			$userid = JUserHelper::getUserId($response->username);
			$user = $this->getUser($userid);

			$session = JFactory::getSession();
			$session->set('user', $user);

			return true;
		}

		return false;
	}

	/**
	 * logs out a user
	 *
	 * @return  boolean  True on success
	 */
	public function logoutUser()
	{
		JLoader::import('joomla.user.authentication');
		$app = JFactory::getApplication();
		$options = array('remember'	 => false);
		$parameters = array('username'	 => $this->getUser()->username);

		return $app->triggerEvent('onLogoutUser', array($parameters, $options));
	}

    public function logAddLogger($file)
    {
		if (!class_exists('JLog'))
		{
			return;
		}

        JLog::addLogger(array('text_file' => $file), JLog::ALL, array('fof'));
    }

	/**
	 * Logs a deprecated practice. In Joomla! this results in the $message being output in the
	 * deprecated log file, found in your site's log directory.
	 *
	 * @param   string  $message  The deprecated practice log message
	 *
	 * @return  void
	 */
	public function logDeprecated($message)
	{
		if (!class_exists('JLog'))
		{
			return;
		}

		JLog::add($message, JLog::WARNING, 'deprecated');
	}

    public function logDebug($message)
    {
		if (!class_exists('JLog'))
		{
			return;
		}

        JLog::add($message, JLog::DEBUG, 'fof');
    }

    /**
     * Returns the root URI for the request.
     *
     * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
     * @param   string   $path      The path
     *
     * @return  string  The root URI string.
     */
    public function URIroot($pathonly = false, $path = null)
    {
        JLoader::import('joomla.environment.uri');

        return JUri::root($pathonly, $path);
    }

    /**
     * Returns the base URI for the request.
     *
     * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
     * |
     * @return  string  The base URI string
     */
    public function URIbase($pathonly = false)
    {
        JLoader::import('joomla.environment.uri');

        return JUri::base($pathonly);
    }

    /**
     * Method to set a response header.  If the replace flag is set then all headers
     * with the given name will be replaced by the new one (only if the current platform supports header caching)
     *
     * @param   string   $name     The name of the header to set.
     * @param   string   $value    The value of the header to set.
     * @param   boolean  $replace  True to replace any headers with the same name.
     *
     * @return  void
     */
    public function setHeader($name, $value, $replace = false)
    {
		if (version_compare($this->version, '3.2', 'ge'))
		{
			JFactory::getApplication()->setHeader($name, $value, $replace);
		}
		else
		{
			JResponse::setHeader($name, $value, $replace);
		}
    }

    public function sendHeaders()
    {
    	if (version_compare($this->version, '3.2', 'ge'))
		{
			JFactory::getApplication()->sendHeaders();
		}
		else
		{
			JResponse::sendHeaders();
		}
    }
}
fof/model/behavior.php000064400000010503152177723700010740 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class. It defines the events which are
 * called by a Model.
 *
 * @codeCoverageIgnore
 * @package  FrameworkOnFramework
 * @since    2.1
 */
abstract class FOFModelBehavior extends FOFUtilsObservableEvent
{
	/**
	 * This event runs before saving data in the model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 * @param   array     &$data   The data to save
	 *
	 * @return  void
	 */
	public function onBeforeSave(&$model, &$data)
	{
	}

	/**
	 * This event runs before deleting a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeDelete(&$model)
	{
	}

	/**
	 * This event runs before copying a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeCopy(&$model)
	{
	}

	/**
	 * This event runs before publishing a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforePublish(&$model)
	{
	}

	/**
	 * This event runs before registering a hit on a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeHit(&$model)
	{
	}

	/**
	 * This event runs before moving a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeMove(&$model)
	{
	}

	/**
	 * This event runs before changing the records' order in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeReorder(&$model)
	{
	}

	/**
	 * This event runs when we are building the query used to fetch a record
	 * list in a model
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The query being built
	 *
	 * @return  void
	 */
	public function onBeforeBuildQuery(&$model, &$query)
	{
	}

	/**
	 * This event runs after saving a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterSave(&$model)
	{
	}

	/**
	 * This event runs after deleting a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterDelete(&$model)
	{
	}

	/**
	 * This event runs after copying a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterCopy(&$model)
	{
	}

	/**
	 * This event runs after publishing a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterPublish(&$model)
	{
	}

	/**
	 * This event runs after registering a hit on a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterHit(&$model)
	{
	}

	/**
	 * This event runs after moving a record in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterMove(&$model)
	{
	}

	/**
	 * This event runs after reordering records in a model
	 *
	 * @param   FOFModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterReorder(&$model)
	{
	}

	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The query being built
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
	}

	/**
	 * This event runs after getting a single item
	 *
	 * @param   FOFModel  &$model   The model which calls this event
	 * @param   FOFTable  &$record  The record loaded by this model
	 *
	 * @return  void
	 */
	public function onAfterGetItem(&$model, &$record)
	{
	}
}
fof/model/field.php000064400000020264152177723700010231 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
abstract class FOFModelField
{
	protected $_db = null;

	/**
	 * The column name of the table field
	 *
	 * @var string
	 */
	protected $name = '';

	/**
	 * The column type of the table field
	 *
	 * @var string
	 */
	protected $type = '';

	/**
	 * The alias of the table used for filtering
	 *
	 * @var string
	 */
	protected $table_alias = false;

	/**
	 * The null value for this type
	 *
	 * @var  mixed
	 */
	public $null_value = null;

	/**
	 * Constructor
	 *
	 * @param   FOFDatabaseDriver  $db           The database object
	 * @param   object           $field        The field informations as taken from the db
	 * @param   string           $table_alias  The table alias to use when filtering
	 */
	public function __construct($db, $field, $table_alias = false)
	{
		$this->_db = $db;

		$this->name = $field->name;
		$this->type = $field->type;
		$this->filterzero = $field->filterzero;
		$this->table_alias = $table_alias;
	}

	/**
	 * Is it a null or otherwise empty value?
	 *
	 * @param   mixed  $value  The value to test for emptiness
	 *
	 * @return  boolean
	 */
	public function isEmpty($value)
	{
		return (($value === $this->null_value) || empty($value))
			&& !($this->filterzero && $value === "0");
	}

	/**
	 * Returns the default search method for a field. This always returns 'exact'
	 * and you are supposed to override it in specialised classes. The possible
	 * values are exact, partial, between and outside, unless something
	 * different is returned by getSearchMethods().
	 *
	 * @see  self::getSearchMethods()
	 *
	 * @return  string
	 */
	public function getDefaultSearchMethod()
	{
		return 'exact';
	}

	/**
	 * Return the search methods available for this field class,
	 *
	 * @return  array
	 */
	public function getSearchMethods()
	{
		$ignore = array('isEmpty', 'getField', 'getFieldType', '__construct', 'getDefaultSearchMethod', 'getSearchMethods');

		$class = new ReflectionClass(__CLASS__);
		$methods = $class->getMethods(ReflectionMethod::IS_PUBLIC);

		$tmp = array();

		foreach ($methods as $method)
		{
			$tmp[] = $method->name;
		}

		$methods = $tmp;

		if ($methods = array_diff($methods, $ignore))
		{
			return $methods;
		}

		return array();
	}

	/**
	 * Perform an exact match (equality matching)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function exact($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		if (is_array($value))
		{
			$db    = FOFPlatform::getInstance()->getDbo();
			$value = array_map(array($db, 'quote'), $value);

			return '(' . $this->getFieldName() . ' IN (' . implode(',', $value) . '))';
		}
		else
		{
			return $this->search($value, '=');
		}
	}

	/**
	 * Perform a partial match (usually: search in string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function partial($value);

	/**
	 * Perform a between limits match (usually: search for a value between
	 * two numbers or a date between two preset dates). When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function between($from, $to, $include = true);

	/**
	 * Perform an outside limits match (usually: search for a value outside an
	 * area or a date outside a preset period). When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The higherst value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function outside($from, $to, $include = false);

	/**
	 * Perform an interval search (usually: a date interval check)
	 *
	 * @param   string               $from      The value to search
	 * @param   string|array|object  $interval  The interval
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function interval($from, $interval);

	/**
	 * Perform a between limits match (usually: search for a value between
	 * two numbers or a date between two preset dates). When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function range($from, $to, $include = true);

	/**
	 * Perform an modulo search
	 *
	 * @param   integer|float  $value     The starting value of the search space
	 * @param   integer|float  $interval  The interval period of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	abstract public function modulo($from, $interval, $include = true);

	/**
	 * Return the SQL where clause for a search
	 *
	 * @param   mixed   $value     The value to search for
	 * @param   string  $operator  The operator to use
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function search($value, $operator = '=')
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		return '(' . $this->getFieldName() . ' ' . $operator . ' ' . $this->_db->quote($value) . ')';
	}

	/**
	 * Get the field name with the given table alias
	 *
	 * @return  string 	The field name
	 */
	public function getFieldName()
	{
		$name = $this->_db->qn($this->name);

		if ($this->table_alias)
		{
			$name = $this->_db->qn($this->table_alias) . '.' . $name;
		}

		return $name;
	}

	/**
	 * Creates a field Object based on the field column type
	 *
	 * @param   object  $field   The field informations
	 * @param   array   $config  The field configuration (like the db object to use)
	 *
	 * @return  FOFModelField  The Field object
	 */
	public static function getField($field, $config = array())
	{
		$type = $field->type;

		$classType = self::getFieldType($type);

		$className = 'FOFModelField' . $classType;

		if (class_exists($className))
		{
			if (isset($config['dbo']))
			{
				$db = $config['dbo'];
			}
			else
			{
				$db = FOFPlatform::getInstance()->getDbo();
			}

			if (isset($config['table_alias']))
			{
				$table_alias = $config['table_alias'];
			}
			else
			{
				$table_alias = false;
			}

			$field = new $className($db, $field, $table_alias);

			return $field;
		}

		return false;
	}

	/**
	 * Get the classname based on the field Type
	 *
	 * @param   string  $type  The type of the field
	 *
	 * @return  string  the class suffix
	 */
	public static function getFieldType($type)
	{
		switch ($type)
		{
			case 'varchar':
			case 'text':
			case 'smalltext':
			case 'longtext':
			case 'char':
			case 'mediumtext':
			case 'character varying':
			case 'nvarchar':
			case 'nchar':
				$type = 'Text';
				break;

			case 'date':
			case 'datetime':
			case 'time':
			case 'year':
			case 'timestamp':
			case 'timestamp without time zone':
			case 'timestamp with time zone':
				$type = 'Date';
				break;

			case 'tinyint':
			case 'smallint':
				$type = 'Boolean';
				break;

			default:
				$type = 'Number';
				break;
		}

		return $type;
	}
}
fof/model/dispatcher/behavior.php000064400000001002152177723700013060 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior dispatcher class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelDispatcherBehavior extends FOFUtilsObservableDispatcher
{
}
fof/model/model.php000064400000220316152177723700010246 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework Model class. The Model is the workhorse. It performs all
 * of the business logic based on its state and then returns the raw (processed)
 * data to the caller, or modifies its own state. It's important to note that
 * the model doesn't get data directly from the request (this is the
 * Controller's business) and that it doesn't output anything (that the View's
 * business).
 *
 * @package  FrameworkOnFramework
 * @since    1.0
 */
class FOFModel extends FOFUtilsObject
{
	/**
	 * Indicates if the internal state has been set
	 *
	 * @var    boolean
	 * @since  12.2
	 */
	protected $__state_set = null;

	/**
	 * Database Connector
	 *
	 * @var    object
	 * @since  12.2
	 */
	protected $_db;

	/**
	 * The event to trigger after deleting the data.
	 * @var    string
	 */
	protected $event_after_delete = 'onContentAfterDelete';

	/**
	 * The event to trigger after saving the data.
	 * @var    string
	 */
	protected $event_after_save = 'onContentAfterSave';

	/**
	 * The event to trigger before deleting the data.
	 * @var    string
	 */
	protected $event_before_delete = 'onContentBeforeDelete';

	/**
	 * The event to trigger before saving the data.
	 * @var    string
	 */
	protected $event_before_save = 'onContentBeforeSave';

	/**
	 * The event to trigger after changing the published state of the data.
	 * @var    string
	 */
	protected $event_change_state = 'onContentChangeState';

	/**
	 * The event to trigger when cleaning cache.
	 *
	 * @var      string
	 * @since    12.2
	 */
	protected $event_clean_cache = null;

	/**
	 * Stores a list of IDs passed to the model's state
	 * @var array
	 */
	protected $id_list = array();

	/**
	 * The first row ID passed to the model's state
	 * @var int
	 */
	protected $id = null;

	/**
	 * Input variables, passed on from the controller, in an associative array
	 * @var FOFInput
	 */
	protected $input = array();

	/**
	 * The list of records made available through getList
	 * @var array
	 */
	protected $list = null;

	/**
	 * The model (base) name
	 *
	 * @var    string
	 * @since  12.2
	 */
	protected $name;

	/**
	 * The URL option for the component.
	 *
	 * @var    string
	 * @since  12.2
	 */
	protected $option = null;

	/**
	 * The table object, populated when saving data
	 * @var FOFTable
	 */
	protected $otable = null;

	/**
	 * Pagination object
	 * @var JPagination
	 */
	protected $pagination = null;

	/**
	 * The table object, populated when retrieving data
	 * @var FOFTable
	 */
	protected $record = null;

	/**
	 * A state object
	 *
	 * @var    string
	 * @since  12.2
	 */
	protected $state;

	/**
	 * The name of the table to use
	 * @var string
	 */
	protected $table = null;

	/**
	 * Total rows based on the filters set in the model's state
	 * @var int
	 */
	protected $total = null;

	/**
	 * Should I save the model's state in the session?
	 * @var bool
	 */
	protected $_savestate = null;

	/**
	 * Array of form objects.
	 *
	 * @var    array
	 * @since  2.0
	 */
	protected $_forms = array();

	/**
	 * The data to load into a form
	 *
	 * @var    array
	 * @since  2.0
	 */
	protected $_formData = array();

	/**
	 * An instance of FOFConfigProvider to provision configuration overrides
	 *
	 * @var    FOFConfigProvider
	 */
	protected $configProvider = null;

	/**
	 * FOFModelDispatcherBehavior for dealing with extra behaviors
	 *
	 * @var    FOFModelDispatcherBehavior
	 */
	protected $modelDispatcher = null;

	/**
	 *	Default behaviors to apply to the model
	 *
	 * @var  	array
	 */
	protected $default_behaviors = array('filters');

	/**
	 * Behavior parameters
	 *
	 * @var    array
	 */
	protected $_behaviorParams = array();

	/**
	 * Returns a new model object. Unless overriden by the $config array, it will
	 * try to automatically populate its state from the request variables.
	 *
	 * @param   string  $type    Model type, e.g. 'Items'
	 * @param   string  $prefix  Model prefix, e.g. 'FoobarModel'
	 * @param   array   $config  Model configuration variables
	 *
	 * @return  FOFModel
	 */
	public static function &getAnInstance($type, $prefix = '', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
		$modelClass = $prefix . ucfirst($type);
		$result = false;

		// Guess the component name and include path
		if (!empty($prefix))
		{
			preg_match('/(.*)Model$/', $prefix, $m);
			$component = 'com_' . strtolower($m[1]);
		}
		else
		{
			$component = '';
		}

		if (array_key_exists('input', $config))
		{
			if (!($config['input'] instanceof FOFInput))
			{
				if (!is_array($config['input']))
				{
					$config['input'] = (array) $config['input'];
				}

				$config['input'] = array_merge($_REQUEST, $config['input']);
				$config['input'] = new FOFInput($config['input']);
			}
		}
		else
		{
			$config['input'] = new FOFInput;
		}

		if (empty($component))
		{
			$component = $config['input']->get('option', 'com_foobar');
		}

		$config['option'] = $component;

		$needsAView = true;

		if (array_key_exists('view', $config))
		{
			if (!empty($config['view']))
			{
				$needsAView = false;
			}
		}

		if ($needsAView)
		{
			$config['view'] = strtolower($type);
		}

		$config['input']->set('option', $config['option']);

		// Get the component directories
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($component);
        $filesystem     = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		// Try to load the requested model class
		if (!class_exists($modelClass))
		{
			$include_paths = self::addIncludePath();

			$extra_paths = array(
				$componentPaths['main'] . '/models',
				$componentPaths['alt'] . '/models'
			);

			$include_paths = array_merge($extra_paths, $include_paths);

			// Try to load the model file
			$path = $filesystem->pathFind(
					$include_paths, self::_createFileName('model', array('name' => $type))
			);

			if ($path)
			{
				require_once $path;
			}
		}

		// Fallback to the Default model class, e.g. FoobarModelDefault
		if (!class_exists($modelClass))
		{
			$modelClass = $prefix . 'Default';

			if (!class_exists($modelClass))
			{
				$include_paths = self::addIncludePath();

				$extra_paths = array(
					$componentPaths['main'] . '/models',
					$componentPaths['alt'] . '/models'
				);

				$include_paths = array_merge($extra_paths, $include_paths);

				// Try to load the model file
				$path = $filesystem->pathFind(
						$include_paths, self::_createFileName('model', array('name' => 'default'))
				);

				if ($path)
				{
					require_once $path;
				}
			}
		}

		// Fallback to the generic FOFModel model class

		if (!class_exists($modelClass))
		{
			$modelClass = 'FOFModel';
		}

		$result = new $modelClass($config);

		return $result;
	}

	/**
	 * Adds a behavior to the model
	 *
	 * @param   string  $name    The name of the behavior
	 * @param   array   $config  Optional Behavior configuration
	 *
	 * @return  boolean  True if the behavior is found and added
	 */
	public function addBehavior($name, $config = array())
	{
		// Sanity check: this objects needs a non-null behavior handler
		if (!is_object($this->modelDispatcher))
		{
			return false;
		}

		// Sanity check: this objects needs a behavior handler of the correct class type
		if (!($this->modelDispatcher instanceof FOFModelDispatcherBehavior))
		{
			return false;
		}

		// First look for ComponentnameModelViewnameBehaviorName (e.g. FoobarModelItemsBehaviorFilter)
		$option_name = str_replace('com_', '', $this->option);
		$behaviorClass = ucfirst($option_name) . 'Model' . FOFInflector::pluralize($this->name) . 'Behavior' . ucfirst(strtolower($name));

		if (class_exists($behaviorClass))
		{
			$behavior = new $behaviorClass($this->modelDispatcher, $config);

			return true;
		}

		// Then look for ComponentnameModelBehaviorName (e.g. FoobarModelBehaviorFilter)
		$option_name = str_replace('com_', '', $this->option);
		$behaviorClass = ucfirst($option_name) . 'ModelBehavior' . ucfirst(strtolower($name));

		if (class_exists($behaviorClass))
		{
			$behavior = new $behaviorClass($this->modelDispatcher, $config);

			return true;
		}

		// Then look for FOFModelBehaviorName (e.g. FOFModelBehaviorFilter)
		$behaviorClassAlt = 'FOFModelBehavior' . ucfirst(strtolower($name));

		if (class_exists($behaviorClassAlt))
		{
			$behavior = new $behaviorClassAlt($this->modelDispatcher, $config);

			return true;
		}

		// Nothing found? Return false.

		return false;
	}

	/**
	 * Returns a new instance of a model, with the state reset to defaults
	 *
	 * @param   string  $type    Model type, e.g. 'Items'
	 * @param   string  $prefix  Model prefix, e.g. 'FoobarModel'
	 * @param   array   $config  Model configuration variables
	 *
	 * @return FOFModel
	 */
	public static function &getTmpInstance($type, $prefix = '', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		if (!array_key_exists('savestate', $config))
		{
			$config['savestate'] = false;
		}

		$ret = self::getAnInstance($type, $prefix, $config)
			->getClone()
			->clearState()
			->clearInput()
			->reset()
			->savestate(0)
			->limitstart(0)
			->limit(0);

		return $ret;
	}

	/**
	 * Add a directory where FOFModel should search for models. You may
	 * either pass a string or an array of directories.
	 *
	 * @param   mixed   $path    A path or array[sting] of paths to search.
	 * @param   string  $prefix  A prefix for models.
	 *
	 * @return  array  An array with directory elements. If prefix is equal to '', all directories are returned.
	 *
	 * @since   12.2
	 */
	public static function addIncludePath($path = '', $prefix = '')
	{
		static $paths;

		if (!isset($paths))
		{
			$paths = array();
		}

		if (!isset($paths[$prefix]))
		{
			$paths[$prefix] = array();
		}

		if (!isset($paths['']))
		{
			$paths[''] = array();
		}

		if (!empty($path))
		{
            $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

			if (!in_array($path, $paths[$prefix]))
			{
				array_unshift($paths[$prefix], $filesystem->pathClean($path));
			}

			if (!in_array($path, $paths['']))
			{
				array_unshift($paths[''], $filesystem->pathClean($path));
			}
		}

		return $paths[$prefix];
	}

	/**
	 * Adds to the stack of model table paths in LIFO order.
	 *
	 * @param   mixed  $path  The directory as a string or directories as an array to add.
	 *
	 * @return  void
	 *
	 * @since   12.2
	 */
	public static function addTablePath($path)
	{
		FOFTable::addIncludePath($path);
	}

	/**
	 * Create the filename for a resource
	 *
	 * @param   string  $type   The resource type to create the filename for.
	 * @param   array   $parts  An associative array of filename information.
	 *
	 * @return  string  The filename
	 *
	 * @since   12.2
	 */
	protected static function _createFileName($type, $parts = array())
	{
		$filename = '';

		switch ($type)
		{
			case 'model':
				$filename = strtolower($parts['name']) . '.php';
				break;
		}

		return $filename;
	}

    /**
     * Public class constructor
     *
     * @param array $config The configuration array
     */
	public function __construct($config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		// Get the input
		if (array_key_exists('input', $config))
		{
			if ($config['input'] instanceof FOFInput)
			{
				$this->input = $config['input'];
			}
			else
			{
				$this->input = new FOFInput($config['input']);
			}
		}
		else
		{
			$this->input = new FOFInput;
		}

		// Load the configuration provider
		$this->configProvider = new FOFConfigProvider;

		// Load the behavior dispatcher
		$this->modelDispatcher = new FOFModelDispatcherBehavior;

		// Set the $name/$_name variable
		$component = $this->input->getCmd('option', 'com_foobar');

		if (array_key_exists('option', $config))
		{
			$component = $config['option'];
		}

		// Set the $name variable
		$this->input->set('option', $component);
		$component = $this->input->getCmd('option', 'com_foobar');

		if (array_key_exists('option', $config))
		{
			$component = $config['option'];
		}

		$this->input->set('option', $component);
		$bareComponent = str_replace('com_', '', strtolower($component));

		// Get the view name
		$className = get_class($this);

		if ($className == 'FOFModel')
		{
			if (array_key_exists('view', $config))
			{
				$view = $config['view'];
			}

			if (empty($view))
			{
				$view = $this->input->getCmd('view', 'cpanel');
			}
		}
		else
		{
            if (array_key_exists('view', $config))
            {
                $view = $config['view'];
            }

            if (empty($view))
            {
                $eliminatePart = ucfirst($bareComponent) . 'Model';
                $view = strtolower(str_replace($eliminatePart, '', $className));
            }
		}

		if (array_key_exists('name', $config))
		{
			$name = $config['name'];
		}
		else
		{
			$name = $view;
		}

		$this->name = $name;
		$this->option = $component;

		// Set the model state
		if (array_key_exists('state', $config))
		{
			$this->state = $config['state'];
		}
		else
		{
			$this->state = new FOFUtilsObject;
		}

		// Set the model dbo
		if (array_key_exists('dbo', $config))
		{
			$this->_db = $config['dbo'];
		}
		else
		{
			$this->_db = FOFPlatform::getInstance()->getDbo();
		}

		// Set the default view search path
		if (array_key_exists('table_path', $config))
		{
			$this->addTablePath($config['table_path']);
		}
		else
		{
			$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($this->option);

			$path = $componentPaths['admin'] . '/tables';
			$altPath = $this->configProvider->get($this->option . '.views.' . FOFInflector::singularize($this->name) . '.config.table_path', null);

			if ($altPath)
			{
				$path = $componentPaths['main'] . '/' . $altPath;
			}

			$this->addTablePath($path);
		}

		// Assign the correct table
		if (array_key_exists('table', $config))
		{
			$this->table = $config['table'];
		}
		else
		{
			$table = $this->configProvider->get(
				$this->option . '.views.' . FOFInflector::singularize($this->name) .
				'.config.table', FOFInflector::singularize($view)
			);
			$this->table = $table;
		}

		// Set the internal state marker - used to ignore setting state from the request

		if (!empty($config['ignore_request']) || !is_null(
				$this->configProvider->get(
					$this->option . '.views.' . FOFInflector::singularize($this->name) .
					'.config.ignore_request', null
				)
		))
		{
			$this->__state_set = true;
		}

		// Get and store the pagination request variables
		$defaultSaveState = array_key_exists('savestate', $config) ? $config['savestate'] : -999;
		$this->populateSavestate($defaultSaveState);

		if (FOFPlatform::getInstance()->isCli())
		{
			$limit = 20;
			$limitstart = 0;
		}
		else
		{
			$app = JFactory::getApplication();

			if (method_exists($app, 'getCfg'))
			{
				$default_limit = $app->getCfg('list_limit');
			}
			else
			{
				$default_limit = 20;
			}

			$limit = $this->getUserStateFromRequest($component . '.' . $view . '.limit', 'limit', $default_limit, 'int', $this->_savestate);
			$limitstart = $this->getUserStateFromRequest($component . '.' . $view . '.limitstart', 'limitstart', 0, 'int', $this->_savestate);
		}

		$this->setState('limit', $limit);
		$this->setState('limitstart', $limitstart);

		// Get the ID or list of IDs from the request or the configuration

		if (array_key_exists('cid', $config))
		{
			$cid = $config['cid'];
		}
		elseif ($cid = $this->configProvider->get(
				$this->option . '.views.' . FOFInflector::singularize($this->name) . '.config.cid', null
			)
		)
		{
			$cid = explode(',', $cid);
		}
		else
		{
			$cid = $this->input->get('cid', array(), 'array');
		}

		if (array_key_exists('id', $config))
		{
			$id = $config['id'];
		}
		elseif ($id = $this->configProvider->get(
				$this->option . '.views.' . FOFInflector::singularize($this->name) . '.config.id', null
			)
		)
		{
			$id = explode(',', $id);
			$id = array_shift($id);
		}
		else
		{
			$id = $this->input->getInt('id', 0);
		}

		if (is_array($cid) && !empty($cid))
		{
			$this->setIds($cid);
		}
		else
		{
			$this->setId($id);
		}

		// Populate the event names from the $config array
		$configKey = $this->option . '.views.' . FOFInflector::singularize($view) . '.config.';

		// Assign after delete event handler

		if (isset($config['event_after_delete']))
		{
			$this->event_after_delete = $config['event_after_delete'];
		}
		else
		{
			$this->event_after_delete = $this->configProvider->get(
				$configKey . 'event_after_delete',
				$this->event_after_delete
			);
		}

		// Assign after save event handler

		if (isset($config['event_after_save']))
		{
			$this->event_after_save = $config['event_after_save'];
		}
		else
		{
			$this->event_after_save = $this->configProvider->get(
				$configKey . 'event_after_save',
				$this->event_after_save
			);
		}

		// Assign before delete event handler

		if (isset($config['event_before_delete']))
		{
			$this->event_before_delete = $config['event_before_delete'];
		}
		else
		{
			$this->event_before_delete = $this->configProvider->get(
				$configKey . 'event_before_delete',
				$this->event_before_delete
			);
		}

		// Assign before save event handler

		if (isset($config['event_before_save']))
		{
			$this->event_before_save = $config['event_before_save'];
		}
		else
		{
			$this->event_before_save = $this->configProvider->get(
				$configKey . 'event_before_save',
				$this->event_before_save
			);
		}

		// Assign state change event handler

		if (isset($config['event_change_state']))
		{
			$this->event_change_state = $config['event_change_state'];
		}
		else
		{
			$this->event_change_state = $this->configProvider->get(
				$configKey . 'event_change_state',
				$this->event_change_state
			);
		}

		// Assign cache clean event handler

		if (isset($config['event_clean_cache']))
		{
			$this->event_clean_cache = $config['event_clean_cache'];
		}
		else
		{
			$this->event_clean_cache = $this->configProvider->get(
				$configKey . 'event_clean_cache',
				$this->event_clean_cache
			);
		}

		// Apply model behaviors

		if (isset($config['behaviors']))
		{
			$behaviors = (array) $config['behaviors'];
		}
		elseif ($behaviors = $this->configProvider->get($configKey . 'behaviors', null))
		{
			$behaviors = explode(',', $behaviors);
		}
		else
		{
			$behaviors = $this->default_behaviors;
		}

		if (is_array($behaviors) && count($behaviors))
		{
			foreach ($behaviors as $behavior)
			{
				$this->addBehavior($behavior);
			}
		}
	}

	/**
	 * Sets the list of IDs from the request data
	 *
	 * @return FOFModel
	 */
	public function setIDsFromRequest()
	{
		// Get the ID or list of IDs from the request or the configuration
		$cid = $this->input->get('cid', array(), 'array');
		$id = $this->input->getInt('id', 0);
		$kid = $this->input->getInt($this->getTable($this->table)->getKeyName(), 0);

		if (is_array($cid) && !empty($cid))
		{
			$this->setIds($cid);
		}
		else
		{
			if (empty($id))
			{
				$this->setId($kid);
			}
			else
			{
				$this->setId($id);
			}
		}

		return $this;
	}

	/**
	 * Sets the ID and resets internal data
	 *
	 * @param   integer $id The ID to use
	 *
	 * @throws InvalidArgumentException
	 *
	 * @return FOFModel
	 */
	public function setId($id = 0)
	{
		// If this is an array extract the first item
		if (is_array($id))
		{
			FOFPlatform::getInstance()->logDeprecated('Passing arrays to FOFModel::setId is deprecated. Use setIds() instead.');
			$id = array_shift($id);
		}

		// No string or no integer? What are you trying to do???
		if (!is_string($id) && !is_numeric($id))
		{
			throw new InvalidArgumentException(sprintf('%s::setId()', get_class($this)));
		}

		$this->reset();
		$this->id = (int) $id;
		$this->id_list = array($this->id);

		return $this;
	}

	/**
	 * Returns the currently set ID
	 *
	 * @return  integer
	 */
	public function getId()
	{
		return $this->id;
	}

	/**
	 * Sets a list of IDs for batch operations from an array and resets the model
	 *
	 * @param   array  $idlist  An array of item IDs to be set to the model's state
	 *
	 * @return  FOFModel
	 */
	public function setIds($idlist)
	{
		$this->reset();
		$this->id_list = array();
		$this->id = 0;

		if (is_array($idlist) && !empty($idlist))
		{
			foreach ($idlist as $value)
			{
                // Protect vs fatal error (objects) and wrong behavior (nested array)
                if(!is_object($value) && !is_array($value))
                {
                    $this->id_list[] = (int) $value;
                }
			}

            if(count($this->id_list))
            {
                $this->id = $this->id_list[0];
            }
		}

		return $this;
	}

	/**
	 * Returns the list of IDs for batch operations
	 *
	 * @return  array  An array of integers
	 */
	public function getIds()
	{
		return $this->id_list;
	}

	/**
	 * Resets the model, like it was freshly loaded
	 *
	 * @return  FOFModel
	 */
	public function reset()
	{
		$this->id = 0;
		$this->id_list = null;
		$this->record = null;
		$this->list = null;
		$this->pagination = null;
		$this->total = null;
		$this->otable = null;

		return $this;
	}

	/**
	 * Clears the model state, but doesn't touch the internal lists of records,
	 * record tables or record id variables. To clear these values, please use
	 * reset().
	 *
	 * @return  FOFModel
	 */
	public function clearState()
	{
		$this->state = new FOFUtilsObject;

		return $this;
	}

	/**
	 * Clears the input array.
	 *
	 * @return  FOFModel
	 */
	public function clearInput()
	{
		$defSource = array();
		$this->input = new FOFInput($defSource);

		return $this;
	}

	/**
	 * Set the internal input field
	 *
	 * @param $input
	 *
	 * @return FOFModel
	 */
	public function setInput($input)
	{
		if (!($input instanceof FOFInput))
		{
			if (!is_array($input))
			{
				$input = (array) $input;
			}

			$input = array_merge($_REQUEST, $input);
			$input = new FOFInput($input);
		}

		$this->input = $input;

		return $this;
	}

	/**
	 * Resets the saved state for this view
	 *
	 * @return  FOFModel
	 */
	public function resetSavedState()
	{
		JFactory::getApplication()->setUserState(substr($this->getHash(), 0, -1), null);

		return $this;
	}

	/**
	 * Method to load a row for editing from the version history table.
	 *
	 * @param   integer    $version_id  Key to the version history table.
	 * @param   FOFTable   &$table      Content table object being loaded.
	 * @param   string     $alias       The type_alias in #__content_types
	 *
	 * @return  boolean  False on failure or error, true otherwise.
	 *
	 * @since   2.3
	 */
	public function loadhistory($version_id, FOFTable &$table, $alias)
	{
		// Only attempt to check the row in if it exists.
		if ($version_id)
		{
			$user = JFactory::getUser();

			// Get an instance of the row to checkout.
			$historyTable = JTable::getInstance('Contenthistory');

			if (!$historyTable->load($version_id))
			{
				$this->setError($historyTable->getError());

				return false;
			}

			$rowArray = JArrayHelper::fromObject(json_decode($historyTable->version_data));

			$typeId = JTable::getInstance('Contenttype')->getTypeId($alias);

			if ($historyTable->ucm_type_id != $typeId)
			{
				$this->setError(JText::_('JLIB_APPLICATION_ERROR_HISTORY_ID_MISMATCH'));
				$key = $table->getKeyName();

				if (isset($rowArray[$key]))
				{
					$table->checkIn($rowArray[$key]);
				}

				return false;
			}
		}

		$this->setState('save_date', $historyTable->save_date);
		$this->setState('version_note', $historyTable->version_note);

		return $table->bind($rowArray);
	}

	/**
	 * Returns a single item. It uses the id set with setId, or the first ID in
	 * the list of IDs for batch operations
	 *
	 * @param   integer  $id  Force a primary key ID to the model. Use null to use the id from the state.
	 *
	 * @return  FOFTable  A copy of the item's FOFTable array
	 */
	public function &getItem($id = null)
	{
		if (!is_null($id))
		{
			$this->record = null;
			$this->setId($id);
		}

		if (empty($this->record))
		{
			$table = $this->getTable($this->table);
			$table->load($this->id);
			$this->record = $table;

			// Do we have saved data?
			$session = JFactory::getSession();
			if ($this->_savestate)
			{
				$serialized = $session->get($this->getHash() . 'savedata', null);
				if (!empty($serialized))
				{
					$data = @unserialize($serialized);

					if ($data !== false)
					{
						$k = $table->getKeyName();

						if (!array_key_exists($k, $data))
						{
							$data[$k] = null;
						}

						if ($data[$k] != $this->id)
						{
							$session->set($this->getHash() . 'savedata', null);
						}
						else
						{
							$this->record->bind($data);
						}
					}
				}
			}

			$this->onAfterGetItem($this->record);
		}

		return $this->record;
	}

	/**
	 * Alias for getItemList
	 *
	 * @param   boolean  $overrideLimits  Should I override set limits?
	 * @param   string   $group           The group by clause
	 * @codeCoverageIgnore
     *
	 * @return  array
	 */
	public function &getList($overrideLimits = false, $group = '')
	{
		return $this->getItemList($overrideLimits, $group);
	}

	/**
	 * Returns a list of items
	 *
	 * @param   boolean  $overrideLimits  Should I override set limits?
	 * @param   string   $group           The group by clause
	 *
	 * @return  array
	 */
	public function &getItemList($overrideLimits = false, $group = '')
	{
		if (empty($this->list))
		{
			$query = $this->buildQuery($overrideLimits);

			if (!$overrideLimits)
			{
				$limitstart = $this->getState('limitstart');
				$limit = $this->getState('limit');
				$this->list = $this->_getList((string) $query, $limitstart, $limit, $group);
			}
			else
			{
				$this->list = $this->_getList((string) $query, 0, 0, $group);
			}
		}

		return $this->list;
	}

	/**
	 * Returns a FOFDatabaseIterator over a list of items.
	 *
	 * THERE BE DRAGONS. Unlike the getItemList() you have a few restrictions:
	 * - The onProcessList event does not run when you get an iterator
	 * - The Iterator returns FOFTable instances. By default, $this->table is used. If you have JOINs, GROUPs or a
	 *   complex query in general you will need to create a custom FOFTable subclass and pass its type in $tableType.
	 *
	 * The getIterator() method is a great way to sift through a large amount of records which would otherwise not fit
	 * in memory since it only keeps one record in PHP memory at a time. It works best with simple models, returning
	 * all the contents of a single database table.
	 *
	 * @param   boolean  $overrideLimits  Should I ignore set limits?
	 * @param   string   $tableClass      The table class for the iterator, e.g. FoobarTableBar. Leave empty to use
	 *                                    the default Table class for this Model.
	 *
	 * @return  FOFDatabaseIterator
	 */
	public function &getIterator($overrideLimits = false, $tableClass = null)
	{
		// Get the table name (required by the Iterator)
		if (empty($tableClass))
		{
			$name = $this->table;

			if (empty($name))
			{
				$name = FOFInflector::singularize($this->getName());
			}

			$bareComponent = str_replace('com_', '', $this->option);
			$prefix        = ucfirst($bareComponent) . 'Table';

			$tableClass = $prefix . ucfirst($name);
		}

		// Get the query
		$query = $this->buildQuery($overrideLimits);

		// Apply limits
		if ($overrideLimits)
		{
			$limitStart = 0;
			$limit = 0;
		}
		else
		{
			$limitStart = $this->getState('limitstart');
			$limit      = $this->getState('limit');
		}

		// This is required to prevent one relation from killing the db cursor used in a different relation...
		$oldDb = $this->getDbo();
		$oldDb->disconnect(); // YES, WE DO NEED TO DISCONNECT BEFORE WE CLONE THE DB OBJECT. ARGH!
		$db = clone $oldDb;

		// Execute the query, get a db cursor and return the iterator
		$db->setQuery($query, $limitStart, $limit);

		$cursor = $db->execute();

		$iterator = FOFDatabaseIterator::getIterator($db->name, $cursor, null, $tableClass);

		return $iterator;
	}

	/**
	 * A cross-breed between getItem and getItemList. It runs the complete query,
	 * like getItemList does. However, instead of returning an array of ad-hoc
	 * objects, it binds the data from the first item fetched on the list to an
	 * instance of the table object and returns that table object instead.
	 *
	 * @param   boolean  $overrideLimits  Should I override set limits?
	 *
	 * @return  FOFTable
	 */
	public function &getFirstItem($overrideLimits = false)
	{
		/**
		 * We have to clone the instance, or when multiple getFirstItem calls occur,
		 * we'll update EVERY instance created
		 */
		$table = clone $this->getTable($this->table);

		$list = $this->getItemList($overrideLimits);

		if (!empty($list))
		{
			$firstItem = array_shift($list);
			$table->bind($firstItem);
		}

		unset($list);

		return $table;
	}

	/**
	 * Binds the data to the model and tries to save it
	 *
	 * @param   array|object  $data  The source data array or object
	 *
	 * @return  boolean  True on success
	 */
	public function save($data)
	{
		$this->otable = null;

		$table = $this->getTable($this->table);

		if (is_object($data))
		{
			$data = clone($data);
		}

		$key = $table->getKeyName();

		if (array_key_exists($key, (array) $data))
		{
			$aData = (array) $data;
			$oid = $aData[$key];
			$table->load($oid);
		}

		if ($data instanceof FOFTable)
		{
			$allData = $data->getData();
		}
		elseif (is_object($data))
		{
			$allData = (array) $data;
		}
		else
		{
			$allData = $data;
		}

		// Get the form if there is any
		$form = $this->getForm($allData, false);

		if ($form instanceof FOFForm)
		{
			// Make sure that $allData has for any field a key
			$fieldset = $form->getFieldset();

			foreach ($fieldset as $nfield => $fldset)
			{
				if (!array_key_exists($nfield, $allData))
				{
					$field = $form->getField($fldset->fieldname, $fldset->group);
					$type  = strtolower($field->type);

					switch ($type)
					{
						case 'checkbox':
							$allData[$nfield] = 0;
							break;

						default:
							$allData[$nfield] = '';
							break;
					}
				}
			}

			$serverside_validate = strtolower($form->getAttribute('serverside_validate'));

			$validateResult = true;
			if (in_array($serverside_validate, array('true', 'yes', '1', 'on')))
			{
				$validateResult = $this->validateForm($form, $allData);
			}

			if ($validateResult === false)
			{
				if ($this->_savestate)
				{
					$session = JFactory::getSession();
					$hash = $this->getHash() . 'savedata';
					$session->set($hash, serialize($allData));
				}

				return false;
			}
		}

		if (!$this->onBeforeSave($allData, $table))
		{
			if ($this->_savestate)
			{
				$session = JFactory::getSession();
				$hash = $this->getHash() . 'savedata';
				$session->set($hash, serialize($allData));
			}

			return false;
		}
		else
		{
			// If onBeforeSave successful, refetch the possibly modified data
			if ($data instanceof FOFTable)
			{
				$data->bind($allData);
			}
			elseif (is_object($data))
			{
				$data = (object) $allData;
			}
			else
			{
				$data = $allData;
			}
		}

		if (!$table->save($data))
		{
			foreach ($table->getErrors() as $error)
			{
				if (!empty($error))
				{
					$this->setError($error);
					$session = JFactory::getSession();
					$tableprops = $table->getProperties(true);

					unset($tableprops['input']);
					unset($tableprops['config']['input']);
					unset($tableprops['config']['db']);
					unset($tableprops['config']['dbo']);


					if ($this->_savestate)
					{
						$hash = $this->getHash() . 'savedata';
						$session->set($hash, serialize($tableprops));
					}
				}
			}

			return false;
		}
		else
		{
			$this->id = $table->$key;

			// Remove the session data
			if ($this->_savestate)
			{
				JFactory::getSession()->set($this->getHash() . 'savedata', null);
			}
		}

		$this->onAfterSave($table);

		$this->otable = $table;

		return true;
	}

	/**
	 * Copy one or more records
	 *
	 * @return  boolean  True on success
	 */
	public function copy()
	{
		if (is_array($this->id_list) && !empty($this->id_list))
		{
			$table = $this->getTable($this->table);

			if (!$this->onBeforeCopy($table))
			{
				return false;
			}

			if (!$table->copy($this->id_list))
			{
				$this->setError($table->getError());

				return false;
			}
			else
			{
				// Call our internal event
				$this->onAfterCopy($table);

				// @todo Should we fire the content plugin?
			}
		}

		return true;
	}

	/**
	 * Returns the table object after the last save() operation
	 *
	 * @return  FOFTable
	 */
	public function getSavedTable()
	{
		return $this->otable;
	}

	/**
	 * Deletes one or several items
	 *
	 * @return  boolean True on success
	 */
	public function delete()
	{
		if (is_array($this->id_list) && !empty($this->id_list))
		{
			$table = $this->getTable($this->table);

			foreach ($this->id_list as $id)
			{
				if (!$this->onBeforeDelete($id, $table))
				{
					continue;
				}

				if (!$table->delete($id))
				{
					$this->setError($table->getError());

					return false;
				}
				else
				{
					$this->onAfterDelete($id);
				}
			}
		}

		return true;
	}

	/**
	 * Toggles the published state of one or several items
	 *
	 * @param   integer  $publish  The publishing state to set (e.g. 0 is unpublished)
	 * @param   integer  $user     The user ID performing this action
	 *
	 * @return  boolean True on success
	 */
	public function publish($publish = 1, $user = null)
	{
		if (is_array($this->id_list) && !empty($this->id_list))
		{
			if (empty($user))
			{
				$oUser = FOFPlatform::getInstance()->getUser();
				$user = $oUser->id;
			}

			$table = $this->getTable($this->table);

			if (!$this->onBeforePublish($table))
			{
				return false;
			}

			if (!$table->publish($this->id_list, $publish, $user))
			{
				$this->setError($table->getError());

				return false;
			}
			else
			{
				// Call our internal event
				$this->onAfterPublish($table);

				// Call the plugin events
				FOFPlatform::getInstance()->importPlugin('content');
				$name = $this->name;
				$context = $this->option . '.' . $name;

                // @TODO should we do anything with this return value?
				$result  = FOFPlatform::getInstance()->runPlugins($this->event_change_state, array($context, $this->id_list, $publish));
			}
		}

		return true;
	}

	/**
	 * Checks out the current item
	 *
	 * @return  boolean
	 */
	public function checkout()
	{
		$table  = $this->getTable($this->table);
		$status = $table->checkout(FOFPlatform::getInstance()->getUser()->id, $this->id);

		if (!$status)
		{
			$this->setError($table->getError());
		}

		return $status;
	}

	/**
	 * Checks in the current item
	 *
	 * @return  boolean
	 */
	public function checkin()
	{
		$table  = $this->getTable($this->table);
		$status = $table->checkin($this->id);

		if (!$status)
		{
			$this->setError($table->getError());
		}

		return $status;
	}

	/**
	 * Tells you if the current item is checked out or not
	 *
	 * @return  boolean
	 */
	public function isCheckedOut()
	{
		$table  = $this->getTable($this->table);
		$status = $table->isCheckedOut($this->id);

		if (!$status)
		{
			$this->setError($table->getError());
		}

		return $status;
	}

	/**
	 * Increments the hit counter
	 *
	 * @return  boolean
	 */
	public function hit()
	{
		$table = $this->getTable($this->table);

		if (!$this->onBeforeHit($table))
		{
			return false;
		}

		$status = $table->hit($this->id);

		if (!$status)
		{
			$this->setError($table->getError());
		}
		else
		{
			$this->onAfterHit($table);
		}

		return $status;
	}

	/**
	 * Moves the current item up or down in the ordering list
	 *
	 * @param   string  $dirn  The direction and magnitude to use (2 means move up by 2 positions, -3 means move down three positions)
	 *
	 * @return  boolean  True on success
	 */
	public function move($dirn)
	{
		$table = $this->getTable($this->table);

		$id = $this->getId();
		$status = $table->load($id);

		if (!$status)
		{
			$this->setError($table->getError());
		}

		if (!$status)
		{
			return false;
		}

		if (!$this->onBeforeMove($table))
		{
			return false;
		}

		$status = $table->move($dirn);

		if (!$status)
		{
			$this->setError($table->getError());
		}
		else
		{
			$this->onAfterMove($table);
		}

		return $status;
	}

	/**
	 * Reorders all items in the table
	 *
	 * @return  boolean
	 */
	public function reorder()
	{
		$table = $this->getTable($this->table);

		if (!$this->onBeforeReorder($table))
		{
			return false;
		}

		$status = $table->reorder($this->getReorderWhere());

		if (!$status)
		{
			$this->setError($table->getError());
		}
		else
		{
			if (!$this->onAfterReorder($table))
			{
				return false;
			}
		}

		return $status;
	}

	/**
	 * Get a pagination object
	 *
	 * @return  JPagination
	 */
	public function getPagination()
	{
		if (empty($this->pagination))
		{
			// Import the pagination library
			JLoader::import('joomla.html.pagination');

			// Prepare pagination values
			$total = $this->getTotal();
			$limitstart = $this->getState('limitstart');
			$limit = $this->getState('limit');

			// Create the pagination object
			$this->pagination = new JPagination($total, $limitstart, $limit);
		}

		return $this->pagination;
	}

	/**
	 * Get the number of all items
	 *
	 * @return  integer
	 */
	public function getTotal()
	{
		if (is_null($this->total))
		{
			$query = $this->buildCountQuery();

			if ($query === false)
			{
				$subquery = $this->buildQuery(false);
				$subquery->clear('order');
				$query = $this->_db->getQuery(true)
					->select('COUNT(*)')
					->from("(" . (string) $subquery . ") AS a");
			}

			$this->_db->setQuery((string) $query);

			$this->total = $this->_db->loadResult();
		}

		return $this->total;
	}

	/**
	 * Returns a record count for the query
	 *
	 * @param   string  $query  The query.
	 *
	 * @return  integer  Number of rows for query
	 *
	 * @since   12.2
	 */
	protected function _getListCount($query)
	{
		return $this->getTotal();
	}

	/**
	 * Get a filtered state variable
	 *
	 * @param   string  $key          The name of the state variable
	 * @param   mixed   $default      The default value to use
	 * @param   string  $filter_type  Filter type
	 *
	 * @return  mixed  The variable's value
	 */
	public function getState($key = null, $default = null, $filter_type = 'raw')
	{
		if (empty($key))
		{
			return $this->_real_getState();
		}

		// Get the savestate status
		$value = $this->_real_getState($key);

		if (is_null($value))
		{
			$value = $this->getUserStateFromRequest($this->getHash() . $key, $key, $value, 'none', $this->_savestate);

			if (is_null($value))
			{
				return $default;
			}
		}

		if (strtoupper($filter_type) == 'RAW')
		{
			return $value;
		}
		else
		{
			JLoader::import('joomla.filter.filterinput');
			$filter = new JFilterInput;

			return $filter->clean($value, $filter_type);
		}
	}

	/**
	 * Method to get model state variables
	 *
	 * @param   string  $property  Optional parameter name
	 * @param   mixed   $default   Optional default value
	 *
	 * @return  object  The property where specified, the state object where omitted
	 *
	 * @since   12.2
	 */
	protected function _real_getState($property = null, $default = null)
	{
		if (!$this->__state_set)
		{
			// Protected method to auto-populate the model state.
			$this->populateState();

			// Set the model state set flag to true.
			$this->__state_set = true;
		}

		return $property === null ? $this->state : $this->state->get($property, $default);
	}

	/**
	 * Returns a hash for this component and view, e.g. "foobar.items.", used
	 * for determining the keys of the variables which will be placed in the
	 * session storage.
	 *
	 * @return  string  The hash
	 */
	public function getHash()
	{
		$option = $this->input->getCmd('option', 'com_foobar');
		$view = FOFInflector::pluralize($this->input->getCmd('view', 'cpanel'));

		return "$option.$view.";
	}

	/**
	 * Gets the value of a user state variable.
	 *
	 * @param   string   $key           The key of the user state variable.
	 * @param   string   $request       The name of the variable passed in a request.
	 * @param   string   $default       The default value for the variable if not found. Optional.
	 * @param   string   $type          Filter for the variable, for valid values see {@link JFilterInput::clean()}. Optional.
	 * @param   boolean  $setUserState  Should I save the variable in the user state? Default: true. Optional.
	 *
	 * @return  string   The request user state.
	 */
	protected function getUserStateFromRequest($key, $request, $default = null, $type = 'none', $setUserState = true)
	{
		return FOFPlatform::getInstance()->getUserStateFromRequest($key, $request, $this->input, $default, $type, $setUserState);
	}

	/**
	 * Returns an object list
	 *
	 * @param   string   $query       The query
	 * @param   integer  $limitstart  Offset from start
	 * @param   integer  $limit       The number of records
	 * @param   string   $group       The group by clause
	 *
	 * @return  array  Array of objects
	 */
	protected function &_getList($query, $limitstart = 0, $limit = 0, $group = '')
	{
		$this->_db->setQuery($query, $limitstart, $limit);
		$result = $this->_db->loadObjectList($group);

		$this->onProcessList($result);

		return $result;
	}

    /**
     * Method to get a table object, load it if necessary.
     *
     * @param   string  $name The table name. Optional.
     * @param   string  $prefix The class prefix. Optional.
     * @param   array   $options Configuration array for model. Optional.
     *
     * @throws Exception
     *
     * @return  FOFTable  A FOFTable object
     */
	public function getTable($name = '', $prefix = null, $options = array())
	{
		if (empty($name))
		{
			$name = $this->table;

			if (empty($name))
			{
				$name = FOFInflector::singularize($this->getName());
			}
		}

		if (empty($prefix))
		{
			$bareComponent = str_replace('com_', '', $this->option);
			$prefix        = ucfirst($bareComponent) . 'Table';
		}

		if (empty($options))
		{
			$options = array('input' => $this->input);
		}

		if ($table = $this->_createTable($name, $prefix, $options))
		{
			return $table;
		}

        FOFPlatform::getInstance()->raiseError(0, JText::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED', $name));

		return null;
	}

	/**
	 * Method to load and return a model object.
	 *
	 * @param   string  $name    The name of the view
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  The configuration array to pass to the table
	 *
	 * @return  FOFTable  Table object or boolean false if failed
	 */
	protected function &_createTable($name, $prefix = 'Table', $config = array())
	{
		// Make sure $config is an array
		if (is_object($config))
		{
			$config = (array) $config;
		}
		elseif (!is_array($config))
		{
			$config = array();
		}

		$result = null;

		// Clean the model name
		$name   = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		// Make sure we are returning a DBO object
		if (!array_key_exists('dbo', $config))
		{
			$config['dbo'] = $this->getDBO();
		}

		$instance = FOFTable::getAnInstance($name, $prefix, $config);

		return $instance;
	}

	/**
	 * Creates the WHERE part of the reorder query
	 *
	 * @return  string
	 */
	public function getReorderWhere()
	{
		return '';
	}

	/**
	 * Builds the SELECT query
	 *
	 * @param   boolean  $overrideLimits  Are we requested to override the set limits?
	 *
	 * @return  FOFDatabaseQuery
	 */
	public function buildQuery($overrideLimits = false)
	{
		$table = $this->getTable();
		$tableName = $table->getTableName();
		$tableKey = $table->getKeyName();
		$db = $this->getDbo();

		$query = $db->getQuery(true);

		// Call the behaviors
		$this->modelDispatcher->trigger('onBeforeBuildQuery', array(&$this, &$query));

		$alias = $this->getTableAlias();

		if ($alias)
		{
			$alias = ' AS ' . $db->qn($alias);
		}
		else
		{
			$alias = '';
		}

		$select = $this->getTableAlias() ? $db->qn($this->getTableAlias()) . '.*' : $db->qn($tableName) . '.*';

		$query->select($select)->from($db->qn($tableName) . $alias);

		if (!$overrideLimits)
		{
			$order = $this->getState('filter_order', null, 'cmd');

			if (!in_array($order, array_keys($table->getData())))
			{
				$order = $tableKey;
			}

			$order = $db->qn($order);

			if ($alias)
			{
				$order = $db->qn($this->getTableAlias()) . '.' . $order;
			}

			$dir = strtoupper($this->getState('filter_order_Dir', 'ASC', 'cmd'));
			$dir = in_array($dir, array('DESC', 'ASC')) ? $dir : 'ASC';

			// If the table cache is broken you may end up with an empty order by.
			if (!empty($order) && ($order != $db->qn('')))
			{
				$query->order($order . ' ' . $dir);
			}
		}

		// Call the behaviors
		$this->modelDispatcher->trigger('onAfterBuildQuery', array(&$this, &$query));

		return $query;
	}

	/**
	 * Returns a list of the fields of the table associated with this model
	 *
	 * @return  array
	 */
	public function getTableFields()
	{
		$tableName = $this->getTable()->getTableName();

		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			$fields = $this->getDbo()->getTableColumns($tableName, true);
		}
		else
		{
			$fieldsArray = $this->getDbo()->getTableFields($tableName, true);
			$fields = array_shift($fieldsArray);
		}

		return $fields;
	}

	/**
	 * Get the alias set for this model's table
	 *
	 * @return  string 	The table alias
	 */
	public function getTableAlias()
	{
		return $this->getTable($this->table)->getTableAlias();
	}

	/**
	 * Builds the count query used in getTotal()
	 *
	 * @return  boolean
	 */
	public function buildCountQuery()
	{
		return false;
	}

	/**
	 * Clones the model object and returns the clone
	 *
	 * @return  FOFModel
	 */
	public function &getClone()
	{
		$clone = clone($this);

		return $clone;
	}

	/**
	 * Magic getter; allows to use the name of model state keys as properties
	 *
	 * @param   string  $name  The name of the variable to get
	 *
	 * @return  mixed  The value of the variable
	 */
	public function __get($name)
	{
		return $this->getState($name);
	}

	/**
	 * Magic setter; allows to use the name of model state keys as properties
	 *
	 * @param   string  $name   The name of the variable
	 * @param   mixed   $value  The value to set the variable to
	 *
	 * @return  void
	 */
	public function __set($name, $value)
	{
		return $this->setState($name, $value);
	}

	/**
	 * Magic caller; allows to use the name of model state keys as methods to
	 * set their values.
	 *
	 * @param   string  $name       The name of the state variable to set
	 * @param   mixed   $arguments  The value to set the state variable to
	 *
	 * @return  FOFModel  Reference to self
	 */
	public function __call($name, $arguments)
	{
		$arg1 = array_shift($arguments);
		$this->setState($name, $arg1);

		return $this;
	}

	/**
	 * Sets the model state auto-save status. By default the model is set up to
	 * save its state to the session.
	 *
	 * @param   boolean  $newState  True to save the state, false to not save it.
	 *
	 * @return  FOFModel  Reference to self
	 */
	public function &savestate($newState)
	{
		$this->_savestate = $newState ? true : false;

		return $this;
	}

	/**
	 * Initialises the _savestate variable
	 *
	 * @param   integer  $defaultSaveState  The default value for the savestate
	 *
	 * @return  void
	 */
	public function populateSavestate($defaultSaveState = -999)
	{
		if (is_null($this->_savestate))
		{
			$savestate = $this->input->getInt('savestate', $defaultSaveState);

			if ($savestate == -999)
			{
				$savestate = true;
			}

			$this->savestate($savestate);
		}
	}

	/**
	 * Method to auto-populate the model state.
	 *
	 * This method should only be called once per instantiation and is designed
	 * to be called on the first call to the getState() method unless the model
	 * configuration flag to ignore the request is set.
	 *
	 * @return  void
	 *
	 * @note    Calling getState in this method will result in recursion.
	 * @since   12.2
	 */
	protected function populateState()
	{
	}

	/**
	 * Applies view access level filtering for the specified user. Useful to
	 * filter a front-end items listing.
	 *
	 * @param   integer  $userID  The user ID to use. Skip it to use the currently logged in user.
	 *
	 * @return  FOFModel  Reference to self
	 */
	public function applyAccessFiltering($userID = null)
	{
		$user = FOFPlatform::getInstance()->getUser($userID);

		$table = $this->getTable();
		$accessField = $table->getColumnAlias('access');

		$this->setState($accessField, $user->getAuthorisedViewLevels());

		return $this;
	}

	/**
	 * A method for getting the form from the model.
	 *
	 * @param   array    $data      Data for the form.
	 * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
	 * @param   boolean  $source    The name of the form. If not set we'll try the form_name state variable or fall back to default.
	 *
	 * @return  mixed  A FOFForm object on success, false on failure
	 *
	 * @since   2.0
	 */
	public function getForm($data = array(), $loadData = true, $source = null)
	{
		$this->_formData = $data;

		if (empty($source))
		{
			$source = $this->getState('form_name', null);
		}

		if (empty($source))
		{
			$source = 'form.' . $this->name;
		}

		$name = $this->input->getCmd('option', 'com_foobar') . '.' . $this->name . '.' . $source;

		$options = array(
			'control'	 => false,
			'load_data'	 => $loadData,
		);

		$this->onBeforeLoadForm($name, $source, $options);

		$form = $this->loadForm($name, $source, $options);

		if ($form instanceof FOFForm)
		{
			$this->onAfterLoadForm($form, $name, $source, $options);
		}

		return $form;
	}

    /**
     * Method to get a form object.
     *
     * @param   string          $name       The name of the form.
     * @param   string          $source     The form filename (e.g. form.browse)
     * @param   array           $options    Optional array of options for the form creation.
     * @param   boolean         $clear      Optional argument to force load a new form.
     * @param   bool|string     $xpath      An optional xpath to search for the fields.
     *
     * @return  mixed  FOFForm object on success, False on error.
	 *
	 * @throws  Exception
     *
     * @see     FOFForm
     * @since   2.0
     */
	protected function loadForm($name, $source, $options = array(), $clear = false, $xpath = false)
	{
		// Handle the optional arguments.
		$options['control'] = isset($options['control']) ? $options['control'] : false;

		// Create a signature hash.
		$hash = md5($source . serialize($options));

		// Check if we can use a previously loaded form.
		if (isset($this->_forms[$hash]) && !$clear)
		{
			return $this->_forms[$hash];
		}

		// Try to find the name and path of the form to load
		$formFilename = $this->findFormFilename($source);

		// No form found? Quit!
		if ($formFilename === false)
		{
			return false;
		}

		// Set up the form name and path
		$source = basename($formFilename, '.xml');
		FOFForm::addFormPath(dirname($formFilename));

		// Set up field paths
		$option         = $this->input->getCmd('option', 'com_foobar');
		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($option);
		$view           = $this->name;
		$file_root      = $componentPaths['main'];
		$alt_file_root  = $componentPaths['alt'];

		FOFForm::addFieldPath($file_root . '/fields');
		FOFForm::addFieldPath($file_root . '/models/fields');
		FOFForm::addFieldPath($alt_file_root . '/fields');
		FOFForm::addFieldPath($alt_file_root . '/models/fields');

		FOFForm::addHeaderPath($file_root . '/fields/header');
		FOFForm::addHeaderPath($file_root . '/models/fields/header');
		FOFForm::addHeaderPath($alt_file_root . '/fields/header');
		FOFForm::addHeaderPath($alt_file_root . '/models/fields/header');

		// Get the form.
		try
		{
			$form = FOFForm::getInstance($name, $source, $options, false, $xpath);

			if (isset($options['load_data']) && $options['load_data'])
			{
				// Get the data for the form.
				$data = $this->loadFormData();
			}
			else
			{
				$data = array();
			}

			// Allows data and form manipulation before preprocessing the form
			$this->onBeforePreprocessForm($form, $data);

			// Allow for additional modification of the form, and events to be triggered.
			// We pass the data because plugins may require it.
			$this->preprocessForm($form, $data);

			// Allows data and form manipulation After preprocessing the form
			$this->onAfterPreprocessForm($form, $data);

			// Load the data into the form after the plugins have operated.
			$form->bind($data);
		}
		catch (Exception $e)
		{
            // The above try-catch statement will catch EVERYTHING, even PhpUnit exceptions while testing
            if(stripos(get_class($e), 'phpunit') !== false)
            {
                throw $e;
            }
            else
            {
                $this->setError($e->getMessage());

                return false;
            }
		}

		// Store the form for later.
		$this->_forms[$hash] = $form;

		return $form;
	}

	/**
	 * Guesses the best candidate for the path to use for a particular form.
	 *
	 * @param   string  $source  The name of the form file to load, without the .xml extension.
	 * @param   array   $paths   The paths to look into. You can declare this to override the default FOF paths.
	 *
	 * @return  mixed  A string if the path and filename of the form to load is found, false otherwise.
	 *
	 * @since   2.0
	 */
	public function findFormFilename($source, $paths = array())
	{
        // TODO Should we read from internal variables instead of the input? With a temp instance we have no input
		$option = $this->input->getCmd('option', 'com_foobar');
		$view 	= $this->name;

		$componentPaths = FOFPlatform::getInstance()->getComponentBaseDirs($option);
		$file_root      = $componentPaths['main'];
		$alt_file_root  = $componentPaths['alt'];
		$template_root  = FOFPlatform::getInstance()->getTemplateOverridePath($option);

		if (empty($paths))
		{
			// Set up the paths to look into
            // PLEASE NOTE: If you ever change this, please update Model Unit tests, too, since we have to
            // copy these default folders (we have to add the protocol for the virtual filesystem)
			$paths = array(
				// In the template override
				$template_root . '/' . $view,
				$template_root . '/' . FOFInflector::singularize($view),
				$template_root . '/' . FOFInflector::pluralize($view),
				// In this side of the component
				$file_root . '/views/' . $view . '/tmpl',
				$file_root . '/views/' . FOFInflector::singularize($view) . '/tmpl',
				$file_root . '/views/' . FOFInflector::pluralize($view) . '/tmpl',
				// In the other side of the component
				$alt_file_root . '/views/' . $view . '/tmpl',
				$alt_file_root . '/views/' . FOFInflector::singularize($view) . '/tmpl',
				$alt_file_root . '/views/' . FOFInflector::pluralize($view) . '/tmpl',
				// In the models/forms of this side
				$file_root . '/models/forms',
				// In the models/forms of the other side
				$alt_file_root . '/models/forms',
			);
		}

        $paths = array_unique($paths);

		// Set up the suffixes to look into
		$suffixes = array();
		$temp_suffixes = FOFPlatform::getInstance()->getTemplateSuffixes();

		if (!empty($temp_suffixes))
		{
			foreach ($temp_suffixes as $suffix)
			{
				$suffixes[] = $suffix . '.xml';
			}
		}

		$suffixes[] = '.xml';

		// Look for all suffixes in all paths
		$result     = false;
        $filesystem = FOFPlatform::getInstance()->getIntegrationObject('filesystem');

		foreach ($paths as $path)
		{
			foreach ($suffixes as $suffix)
			{
				$filename = $path . '/' . $source . $suffix;

				if ($filesystem->fileExists($filename))
				{
					$result = $filename;
					break;
				}
			}

			if ($result)
			{
				break;
			}
		}

		return $result;
	}

	/**
	 * Method to get the data that should be injected in the form.
	 *
	 * @return  array    The default data is an empty array.
	 *
	 * @since   2.0
	 */
	protected function loadFormData()
	{
		if (empty($this->_formData))
		{
			return array();
		}
		else
		{
			return $this->_formData;
		}
	}

	/**
	 * Method to allow derived classes to preprocess the form.
	 *
	 * @param   FOFForm  $form   A FOFForm object.
	 * @param   mixed    &$data  The data expected for the form.
	 * @param   string   $group  The name of the plugin group to import (defaults to "content").
	 *
	 * @return  void
	 *
	 * @see     FOFFormField
	 * @since   2.0
	 * @throws  Exception if there is an error in the form event.
	 */
	protected function preprocessForm(FOFForm &$form, &$data, $group = 'content')
	{
		// Import the appropriate plugin group.
		FOFPlatform::getInstance()->importPlugin($group);

		// Trigger the form preparation event.
		$results = FOFPlatform::getInstance()->runPlugins('onContentPrepareForm', array($form, $data));

		// Check for errors encountered while preparing the form.
		if (count($results) && in_array(false, $results, true))
		{
			// Get the last error.
			$dispatcher = FOFUtilsObservableDispatcher::getInstance();
			$error = $dispatcher->getError();

			if (!($error instanceof Exception))
			{
				throw new Exception($error);
			}
		}
	}

	/**
	 * Method to validate the form data.
	 *
	 * @param   FOFForm  $form   The form to validate against.
	 * @param   array    $data   The data to validate.
	 * @param   string   $group  The name of the field group to validate.
	 *
	 * @return  mixed   Array of filtered data if valid, false otherwise.
	 *
	 * @see     JFormRule
	 * @see     JFilterInput
	 * @since   2.0
	 */
	public function validateForm($form, $data, $group = null)
	{
		// Filter and validate the form data.
		$data   = $form->filter($data);
		$return = $form->validate($data, $group);

		// Check for an error.
		if ($return instanceof Exception)
		{
			$this->setError($return->getMessage());

			return false;
		}

		// Check the validation results.
		if ($return === false)
		{
			// Get the validation messages from the form.
			foreach ($form->getErrors() as $message)
			{
				if ($message instanceof Exception)
				{
					$this->setError($message->getMessage());
				}
				else
				{
					$this->setError($message);
				}
			}

			return false;
		}

		return $data;
	}

	/**
	 * Allows the manipulation before the form is loaded
	 *
	 * @param   string  &$name     The name of the form.
	 * @param   string  &$source   The form source. Can be XML string if file flag is set to false.
	 * @param   array   &$options  Optional array of options for the form creation.
	 * @codeCoverageIgnore
     *
	 * @return  void
	 */
	public function onBeforeLoadForm(&$name, &$source, &$options)
	{
	}

	/**
	 * Allows the manipulation after the form is loaded
	 *
	 * @param   FOFForm  $form      A FOFForm object.
	 * @param   string   &$name     The name of the form.
	 * @param   string   &$source   The form source. Can be XML string if file flag is set to false.
	 * @param   array    &$options  Optional array of options for the form creation.
	 * @codeCoverageIgnore
     *
	 * @return  void
	 */
	public function onAfterLoadForm(FOFForm &$form, &$name, &$source, &$options)
	{
	}

	/**
	 * Allows data and form manipulation before preprocessing the form
	 *
	 * @param   FOFForm  $form    A FOFForm object.
	 * @param   array    &$data   The data expected for the form.
	 * @codeCoverageIgnore
     *
	 * @return  void
	 */
	public function onBeforePreprocessForm(FOFForm &$form, &$data)
	{
	}

	/**
	 * Allows data and form manipulation after preprocessing the form
	 *
	 * @param   FOFForm  $form    A FOFForm object.
	 * @param   array    &$data   The data expected for the form.
	 * @codeCoverageIgnore
     *
	 * @return  void
	 */
	public function onAfterPreprocessForm(FOFForm &$form, &$data)
	{
	}

	/**
	 * This method can be overriden to automatically do something with the
	 * list results array. You are supposed to modify the list which was passed
	 * in the parameters; DO NOT return a new array!
	 *
	 * @param   array  &$resultArray  An array of objects, each row representing a record
	 *
	 * @return  void
	 */
	protected function onProcessList(&$resultArray)
	{
	}

	/**
	 * This method runs after an item has been gotten from the database in a read
	 * operation. You can modify it before it's returned to the MVC triad for
	 * further processing.
	 *
	 * @param   FOFTable  &$record  The table instance we fetched
	 *
	 * @return  void
	 */
	protected function onAfterGetItem(&$record)
	{
		try
		{
			// Call the behaviors
			$result = $this->modelDispatcher->trigger('onAfterGetItem', array(&$this, &$record));
		}
		catch (Exception $e)
		{
			// Oops, an exception occured!
			$this->setError($e->getMessage());
		}
	}

	/**
	 * This method runs before the $data is saved to the $table. Return false to
	 * stop saving.
	 *
	 * @param   array     &$data   The data to save
	 * @param   FOFTable  &$table  The table to save the data to
	 *
	 * @return  boolean  Return false to prevent saving, true to allow it
	 */
	protected function onBeforeSave(&$data, &$table)
	{
		// Let's import the plugin only if we're not in CLI (content plugin needs a user)
		FOFPlatform::getInstance()->importPlugin('content');

		try
		{
			// Do I have a new record?
			$key = $table->getKeyName();

			$pk = (!empty($data[$key])) ? $data[$key] : 0;

			$this->_isNewRecord = $pk <= 0;

			// Bind the data
			$table->bind($data);

			// Call the behaviors
			$result = $this->modelDispatcher->trigger('onBeforeSave', array(&$this, &$data));

			if (in_array(false, $result, true))
			{
				// Behavior failed, return false
				return false;
			}

			// Call the plugin
			$name = $this->name;
			$result = FOFPlatform::getInstance()->runPlugins($this->event_before_save, array($this->option . '.' . $name, &$table, $this->_isNewRecord));

			if (in_array(false, $result, true))
			{
				// Plugin failed, return false
				$this->setError($table->getError());

				return false;
			}
		}
		catch (Exception $e)
		{
			// Oops, an exception occured!
			$this->setError($e->getMessage());

			return false;
		}

		return true;
	}

	/**
	 * This method runs after the data is saved to the $table.
	 *
	 * @param   FOFTable  &$table  The table which was saved
	 *
	 * @return  boolean
	 */
	protected function onAfterSave(&$table)
	{
		// Let's import the plugin only if we're not in CLI (content plugin needs a user)

		FOFPlatform::getInstance()->importPlugin('content');

		try
		{
			// Call the behaviors
			$result = $this->modelDispatcher->trigger('onAfterSave', array(&$this));

			if (in_array(false, $result, true))
			{
				// Behavior failed, return false
				return false;
			}

			$name = $this->name;
			FOFPlatform::getInstance()->runPlugins($this->event_after_save, array($this->option . '.' . $name, &$table, $this->_isNewRecord));

			return true;
		}
		catch (Exception $e)
		{
			// Oops, an exception occured!
			$this->setError($e->getMessage());

			return false;
		}
	}

	/**
	 * This method runs before the record with key value of $id is deleted from $table
	 *
	 * @param   integer   &$id     The ID of the record being deleted
	 * @param   FOFTable  &$table  The table instance used to delete the record
	 *
	 * @return  boolean
	 */
	protected function onBeforeDelete(&$id, &$table)
	{
		// Let's import the plugin only if we're not in CLI (content plugin needs a user)

		FOFPlatform::getInstance()->importPlugin('content');

		try
		{
			$table->load($id);

			// Call the behaviors
			$result = $this->modelDispatcher->trigger('onBeforeDelete', array(&$this));

			if (in_array(false, $result, true))
			{
				// Behavior failed, return false
				return false;
			}

			$name = $this->name;
			$context = $this->option . '.' . $name;
			$result = FOFPlatform::getInstance()->runPlugins($this->event_before_delete, array($context, $table));

			if (in_array(false, $result, true))
			{
				// Plugin failed, return false
				$this->setError($table->getError());

				return false;
			}

			$this->_recordForDeletion = clone $table;
		}
		catch (Exception $e)
		{
			// Oops, an exception occured!
			$this->setError($e->getMessage());

			return false;
		}
		return true;
	}

	/**
	 * This method runs after a record with key value $id is deleted
	 *
	 * @param   integer  $id  The id of the record which was deleted
	 *
	 * @return  boolean  Return false to raise an error, true otherwise
	 */
	protected function onAfterDelete($id)
	{
		FOFPlatform::getInstance()->importPlugin('content');

		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterDelete', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		try
		{
			$name = $this->name;
			$context = $this->option . '.' . $name;
			$result = FOFPlatform::getInstance()->runPlugins($this->event_after_delete, array($context, $this->_recordForDeletion));
			unset($this->_recordForDeletion);
		}
		catch (Exception $e)
		{
			// Oops, an exception occured!
			$this->setError($e->getMessage());

			return false;
		}
	}

	/**
	 * This method runs before a record is copied
	 *
	 * @param   FOFTable  &$table  The table instance of the record being copied
	 *
	 * @return  boolean  True to allow the copy
	 */
	protected function onBeforeCopy(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onBeforeCopy', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs after a record has been copied
	 *
	 * @param   FOFTable  &$table  The table instance of the record which was copied
	 *
	 * @return  boolean  True to allow the copy
	 */
	protected function onAfterCopy(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterCopy', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs before a record is published
	 *
	 * @param   FOFTable  &$table  The table instance of the record being published
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onBeforePublish(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onBeforePublish', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs after a record has been published
	 *
	 * @param   FOFTable  &$table  The table instance of the record which was published
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onAfterPublish(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterPublish', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs before a record is hit
	 *
	 * @param   FOFTable  &$table  The table instance of the record being hit
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onBeforeHit(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onBeforeHit', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs after a record has been hit
	 *
	 * @param   FOFTable  &$table  The table instance of the record which was hit
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onAfterHit(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterHit', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs before a record is moved
	 *
	 * @param   FOFTable  &$table  The table instance of the record being moved
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onBeforeMove(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onBeforeMove', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs after a record has been moved
	 *
	 * @param   FOFTable  &$table  The table instance of the record which was moved
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onAfterMove(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterMove', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs before a table is reordered
	 *
	 * @param   FOFTable  &$table  The table instance being reordered
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onBeforeReorder(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onBeforeReorder', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * This method runs after a table is reordered
	 *
	 * @param   FOFTable  &$table  The table instance which was reordered
	 *
	 * @return  boolean  True to allow the operation
	 */
	protected function onAfterReorder(&$table)
	{
		// Call the behaviors
		$result = $this->modelDispatcher->trigger('onAfterReorder', array(&$this));

		if (in_array(false, $result, true))
		{
			// Behavior failed, return false
			return false;
		}

		return true;
	}

	/**
	 * Method to get the database driver object
	 *
	 * @return  FOFDatabaseDriver
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to get the model name
	 *
	 * The model name. By default parsed using the classname or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @return  string  The name of the model
	 *
	 * @throws  Exception
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$r = null;

			if (!preg_match('/Model(.*)/i', get_class($this), $r))
			{
				throw new Exception(JText::_('JLIB_APPLICATION_ERROR_MODEL_GET_NAME'), 500);
			}

			$this->name = strtolower($r[1]);
		}

		return $this->name;
	}

	/**
	 * Method to set the database driver object
	 *
	 * @param   FOFDatabaseDriver  $db  A FOFDatabaseDriver based object
	 *
	 * @return  void
	 */
	public function setDbo($db)
	{
		$this->_db = $db;
	}

	/**
	 * Method to set model state variables
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set or null.
	 *
	 * @return  mixed  The previous value of the property or null if not set.
	 */
	public function setState($property, $value = null)
	{
		return $this->state->set($property, $value);
	}

	/**
	 * Clean the cache
	 *
	 * @param   string   $group      The cache group
	 * @param   integer  $client_id  The ID of the client
	 *
	 * @return  void
	 */
	protected function cleanCache($group = null, $client_id = 0)
	{
		$conf         = JFactory::getConfig();
        $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();

		$options = array(
			'defaultgroup' => ($group) ? $group : (isset($this->option) ? $this->option : JFactory::getApplication()->input->get('option')),
			'cachebase'    => ($client_id) ? $platformDirs['admin'] . '/cache' : $conf->get('cache_path', $platformDirs['public'] . '/cache'));

		$cache = JCache::getInstance('callback', $options);
		$cache->clean();

		// Trigger the onContentCleanCache event.
		FOFPlatform::getInstance()->runPlugins($this->event_clean_cache, $options);
	}

	/**
	 * Set a behavior param
	 *
	 * @param   string  $name     The name of the param
	 * @param   mixed   $value    The param value to set
	 *
	 * @return  FOFModel
	 */
	public function setBehaviorParam($name, $value)
	{
		$this->_behaviorParams[$name] = $value;

		return $this;
	}

	/**
	 * Get a behavior param
	 *
	 * @param   string  $name     The name of the param
	 * @param   mixed   $default  The default value returned if not set
	 *
	 * @return  mixed
	 */
	public function getBehaviorParam($name, $default = null)
	{
		return isset($this->_behaviorParams[$name]) ? $this->_behaviorParams[$name] : $default;
	}

	/**
	 * Set or get the backlisted filters
	 *
	 * @param   mixed    $list    A filter or list of filters to backlist. If null return the list of backlisted filter
	 * @param   boolean  $reset   Reset the blacklist if true
	 *
	 * @return  void|array  Return an array of value if $list is null
	 */
	public function blacklistFilters($list = null, $reset = false)
	{
		if (!isset($list))
		{
			return $this->getBehaviorParam('blacklistFilters', array());
		}

		if (is_string($list))
		{
			$list = (array) $list;
		}

		if (!$reset)
		{
			$list = array_unique(array_merge($this->getBehaviorParam('blacklistFilters', array()), $list));
		}

		$this->setBehaviorParam('blacklistFilters', $list);
	}
}
fof/model/behavior/private.php000064400000004660152177723700012421 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class to filter front-end access to items
 * craeted by the currently logged in user only.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorPrivate extends FOFModelBehavior
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
		// This behavior only applies to the front-end.
		if (!FOFPlatform::getInstance()->isFrontend())
		{
			return;
		}

		// Get the name of the access field
		$table = $model->getTable();
		$createdField = $table->getColumnAlias('created_by');

		// Make sure the access field actually exists
		if (!in_array($createdField, $table->getKnownFields()))
		{
			return;
		}

		// Get the current user's id
		$user_id = FOFPlatform::getInstance()->getUser()->id;

		// And filter the query output by the user id
		$db    = FOFPlatform::getInstance()->getDbo();

		$alias = $model->getTableAlias();
		$alias = $alias ? $db->qn($alias) . '.' : '';

		$query->where($alias . $db->qn($createdField) . ' = ' . $db->q($user_id));
	}

	/**
	 * The event runs after FOFModel has called FOFTable and retrieved a single
	 * item from the database. It is used to apply automatic filters.
	 *
	 * @param   FOFModel  &$model   The model which was called
	 * @param   FOFTable  &$record  The record loaded from the databae
	 *
	 * @return  void
	 */
	public function onAfterGetItem(&$model, &$record)
	{
		if ($record instanceof FOFTable)
		{
			$keyName = $record->getKeyName();
			if ($record->$keyName === null)
			{
				return;
			}

			$fieldName = $record->getColumnAlias('created_by');

			// Make sure the field actually exists
			if (!in_array($fieldName, $record->getKnownFields()))
			{
				return;
			}

			$user_id = FOFPlatform::getInstance()->getUser()->id;

			if ($record->$fieldName != $user_id)
			{
				$record = null;
			}
		}
	}
}
fof/model/behavior/language.php000064400000010601152177723700012522 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class to filter front-end access to items
 * based on the language.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorLanguage extends FOFModelBehavior
{
	/**
	 * This event runs before we have built the query used to fetch a record
	 * list in a model. It is used to blacklist the language filter
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onBeforeBuildQuery(&$model, &$query)
	{
		if (FOFPlatform::getInstance()->isFrontend())
		{
			$model->blacklistFilters('language');
		}
	}

	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
		// This behavior only applies to the front-end.
		if (!FOFPlatform::getInstance()->isFrontend())
		{
			return;
		}

		// Get the name of the language field
		$table = $model->getTable();
		$languageField = $table->getColumnAlias('language');

		// Make sure the access field actually exists
		if (!in_array($languageField, $table->getKnownFields()))
		{
			return;
		}

		// Make sure it is a multilingual site and get a list of languages
		$app = JFactory::getApplication();
		$hasLanguageFilter = method_exists($app, 'getLanguageFilter');

		if ($hasLanguageFilter)
		{
			$hasLanguageFilter = $app->getLanguageFilter();
		}

		if (!$hasLanguageFilter)
		{
			return;
		}

		$lang_filter_plugin = JPluginHelper::getPlugin('system', 'languagefilter');
		$lang_filter_params = new JRegistry($lang_filter_plugin->params);

		$languages = array('*');

		if ($lang_filter_params->get('remove_default_prefix'))
		{
			// Get default site language
			$lg = FOFPlatform::getInstance()->getLanguage();
			$languages[] = $lg->getTag();
		}
		else
		{
			$languages[] = JFactory::getApplication()->input->getCmd('language', '*');
		}

		// Filter out double languages
		$languages = array_unique($languages);

		// And filter the query output by these languages
		$db = FOFPlatform::getInstance()->getDbo();

		// Alias
		$alias = $model->getTableAlias();
		$alias = $alias ? $db->qn($alias) . '.' : '';

		$languages = array_map(array($db, 'quote'), $languages);
		$query->where($alias . $db->qn($languageField) . ' IN (' . implode(',', $languages) . ')');
	}

	/**
	 * The event runs after FOFModel has called FOFTable and retrieved a single
	 * item from the database. It is used to apply automatic filters.
	 *
	 * @param   FOFModel  &$model   The model which was called
	 * @param   FOFTable  &$record  The record loaded from the databae
	 *
	 * @return  void
	 */
	public function onAfterGetItem(&$model, &$record)
	{
		if ($record instanceof FOFTable)
		{
			$fieldName = $record->getColumnAlias('language');

			// Make sure the field actually exists
			if (!in_array($fieldName, $record->getKnownFields()))
			{
				return;
			}

			// Make sure it is a multilingual site and get a list of languages
			$app = JFactory::getApplication();
			$hasLanguageFilter = method_exists($app, 'getLanguageFilter');

			if ($hasLanguageFilter)
			{
				$hasLanguageFilter = $app->getLanguageFilter();
			}

			if (!$hasLanguageFilter)
			{
				return;
			}

			$lang_filter_plugin = JPluginHelper::getPlugin('system', 'languagefilter');
			$lang_filter_params = new JRegistry($lang_filter_plugin->params);

			$languages = array('*');

			if ($lang_filter_params->get('remove_default_prefix'))
			{
				// Get default site language
				$lg = FOFPlatform::getInstance()->getLanguage();
				$languages[] = $lg->getTag();
			}
			else
			{
				$languages[] = JFactory::getApplication()->input->getCmd('language', '*');
			}

			// Filter out double languages
			$languages = array_unique($languages);

			if (!in_array($record->$fieldName, $languages))
			{
				$record = null;
			}
		}
	}
}
fof/model/behavior/enabled.php000064400000004204152177723700012333 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class to filter front-end access to items
 * that are enabled.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorEnabled extends FOFModelBehavior
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
		// This behavior only applies to the front-end.
		if (!FOFPlatform::getInstance()->isFrontend())
		{
			return;
		}

		// Get the name of the enabled field
		$table = $model->getTable();
		$enabledField = $table->getColumnAlias('enabled');

		// Make sure the field actually exists
		if (!in_array($enabledField, $table->getKnownFields()))
		{
			return;
		}

		// Filter by enabled fields only
		$db = FOFPlatform::getInstance()->getDbo();

		// Alias
		$alias = $model->getTableAlias();
		$alias = $alias ? $db->qn($alias) . '.' : '';

		$query->where($alias . $db->qn($enabledField) . ' = ' . $db->q(1));
	}

	/**
	 * The event runs after FOFModel has called FOFTable and retrieved a single
	 * item from the database. It is used to apply automatic filters.
	 *
	 * @param   FOFModel  &$model   The model which was called
	 * @param   FOFTable  &$record  The record loaded from the databae
	 *
	 * @return  void
	 */
	public function onAfterGetItem(&$model, &$record)
	{
		if ($record instanceof FOFTable)
		{
			$fieldName = $record->getColumnAlias('enabled');

			// Make sure the field actually exists
			if (!in_array($fieldName, $record->getKnownFields()))
			{
				return;
			}

			if ($record->$fieldName != 1)
			{
				$record = null;
			}
		}
	}
}
fof/model/behavior/access.php000064400000004133152177723700012203 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class to filter front-end access to items
 * based on the viewing access levels.
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorAccess extends FOFModelBehavior
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
		// This behavior only applies to the front-end.
		if (!FOFPlatform::getInstance()->isFrontend())
		{
			return;
		}

		// Get the name of the access field
		$table       = $model->getTable();
		$accessField = $table->getColumnAlias('access');

		// Make sure the field actually exists
		if (!in_array($accessField, $table->getKnownFields()))
		{
			return;
		}

		$model->applyAccessFiltering(null);
	}

	/**
	 * The event runs after FOFModel has called FOFTable and retrieved a single
	 * item from the database. It is used to apply automatic filters.
	 *
	 * @param   FOFModel  &$model   The model which was called
	 * @param   FOFTable  &$record  The record loaded from the databae
	 *
	 * @return  void
	 */
	public function onAfterGetItem(&$model, &$record)
	{
		if ($record instanceof FOFTable)
		{
			$fieldName = $record->getColumnAlias('access');

			// Make sure the field actually exists
			if (!in_array($fieldName, $record->getKnownFields()))
			{
				return;
			}

			// Get the user
			$user = FOFPlatform::getInstance()->getUser();

			// Filter by authorised access levels
			if (!in_array($record->$fieldName, $user->getAuthorisedViewLevels()))
			{
				$record = null;
			}
		}
	}
}
fof/model/behavior/emptynonzero.php000064400000001532152177723700013513 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorEmptynonzero extends FOFModelBehavior
{
	/**
	 * This event runs when we are building the query used to fetch a record
	 * list in a model
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The query being built
	 *
	 * @return  void
	 */
	public function onBeforeBuildQuery(&$model, &$query)
	{
		$model->setState('_emptynonzero', '1');
	}
}
fof/model/behavior/filters.php000064400000005145152177723700012416 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelBehaviorFilters extends FOFModelBehavior
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   FOFModel        &$model  The model which calls this event
	 * @param   FOFDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(&$model, &$query)
	{
		$table = $model->getTable();
		$tableName = $table->getTableName();
		$tableKey = $table->getKeyName();
		$db = $model->getDBO();

		$filterzero = $model->getState('_emptynonzero', null);

		$fields = $model->getTableFields();
		$backlist = $model->blacklistFilters();

		foreach ($fields as $fieldname => $fieldtype)
		{
			if (in_array($fieldname, $backlist)) {
				continue;
			}
			$field = new stdClass;
			$field->name = $fieldname;
			$field->type = $fieldtype;
			$field->filterzero = $filterzero;

			$filterName = ($field->name == $tableKey) ? 'id' : $field->name;
			$filterState = $model->getState($filterName, null);

			$field = FOFModelField::getField($field, array('dbo' => $db, 'table_alias' => $model->getTableAlias()));

			if ((is_array($filterState) && (
					array_key_exists('value', $filterState) ||
					array_key_exists('from', $filterState) ||
					array_key_exists('to', $filterState)
				)) || is_object($filterState))
			{
				$options = new JRegistry($filterState);
			}
			else
			{
				$options = new JRegistry;
				$options->set('value', $filterState);
			}

			$methods = $field->getSearchMethods();
			$method = $options->get('method', $field->getDefaultSearchMethod());

			if (!in_array($method, $methods))
			{
				$method = 'exact';
			}

			switch ($method)
			{
				case 'between':
				case 'outside':
				case 'range' :
					$sql = $field->$method($options->get('from', null), $options->get('to'));
					break;

				case 'interval':
				case 'modulo':
					$sql = $field->$method($options->get('value', null), $options->get('interval'));
					break;

				case 'exact':
				case 'partial':
				case 'search':
				default:
					$sql = $field->$method($options->get('value', null));
					break;
			}

			if ($sql)
			{
				$query->where($sql);
			}
		}
	}
}
fof/model/field/boolean.php000064400000001312152177723700011641 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelFieldBoolean extends FOFModelFieldNumber
{
	/**
	 * Is it a null or otherwise empty value?
	 *
	 * @param   mixed  $value  The value to test for emptiness
	 *
	 * @return  boolean
	 */
	public function isEmpty($value)
	{
		return is_null($value) || ($value === '');
	}
}
fof/model/field/date.php000064400000011673152177723700011152 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelFieldDate extends FOFModelFieldText
{
	/**
	 * Returns the default search method for this field.
	 *
	 * @return  string
	 */
	public function getDefaultSearchMethod()
	{
		return 'exact';
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function between($from, $to, $include = true)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' "' . $from . '") AND ';
		$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' "' . $to . '"))';

		return $sql;
	}

	/**
	 * Perform an outside limits match. When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The higherst value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function outside($from, $to, $include = false)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' <' . $extra . ' "' . $from . '") OR ';
		$sql .= '(' . $this->getFieldName() . ' >' . $extra . ' "' . $to . '"))';

		return $sql;
	}

	/**
	 * Interval date search
	 *
	 * @param   string               $value     The value to search
	 * @param   string|array|object  $interval  The interval. Can be (+1 MONTH or array('value' => 1, 'unit' => 'MONTH', 'sign' => '+'))
	 * @param   boolean              $include   If the borders should be included
	 *
	 * @return  string  the sql string
	 */
	public function interval($value, $interval, $include = true)
	{
		if ($this->isEmpty($value) || $this->isEmpty($interval))
		{
			return '';
		}

		$interval = $this->getInterval($interval);

		if ($interval['sign'] == '+')
		{
			$function = 'DATE_ADD';
		}
		else
		{
			$function = 'DATE_SUB';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $function;
		$sql .= '(' . $this->getFieldName() . ', INTERVAL ' . $interval['value'] . ' ' . $interval['unit'] . '))';

		return $sql;
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function range($from, $to, $include = true)
	{
		if ($this->isEmpty($from) && $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		if ($from)
			$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' "' . $from . '")';
		if ($to)
			$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' "' . $to . '")';

		$sql = '(' . implode(' AND ', $sql) . ')';

		return $sql;
	}

	/**
	 * Parses an interval –which may be given as a string, array or object– into
	 * a standardised hash array that can then be used bu the interval() method.
	 *
	 * @param   string|array|object  $interval  The interval expression to parse
	 *
	 * @return  array  The parsed, hash array form of the interval
	 */
	protected function getInterval($interval)
	{
		if (is_string($interval))
		{
			if (strlen($interval) > 2)
			{
				$interval = explode(" ", $interval);
				$sign = ($interval[0] == '-') ? '-' : '+';
				$value = (int) substr($interval[0], 1);

				$interval = array(
					'unit' => $interval[1],
					'value' => $value,
					'sign' => $sign
				);
			}
			else
			{
				$interval = array(
					'unit' => 'MONTH',
					'value' => 1,
					'sign' => '+'
				);
			}
		}
		else
		{
			$interval = (array) $interval;
		}

		return $interval;
	}
}
fof/model/field/number.php000064400000012011152177723700011510 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelFieldNumber extends FOFModelField
{
	/**
	 * The partial match is mapped to an exact match
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function partial($value)
	{
		return $this->exact($value);
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function between($from, $to, $include = true)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';
		$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))';

		return $sql;
	}

	/**
	 * Perform an outside limits match. When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The higherst value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function outside($from, $to, $include = false)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' <' . $extra . ' ' . $from . ') OR ';
		$sql .= '(' . $this->getFieldName() . ' >' . $extra . ' ' . $to . '))';

		return $sql;
	}

	/**
	 * Perform an interval match. It's similar to a 'between' match, but the
	 * from and to values are calculated based on $value and $interval:
	 * $value - $interval < VALUE < $value + $interval
	 *
	 * @param   integer|float  $value     The center value of the search space
	 * @param   integer|float  $interval  The width of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	public function interval($value, $interval, $include = true)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		$from = $value - $interval;
		$to = $value + $interval;

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';
		$sql .= '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))';

		return $sql;
	}

	/**
	 * Perform a range limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function range($from, $to, $include = true)
	{
		if ($this->isEmpty($from) && $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		if ($from)
			$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ')';
		if ($to)
			$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . ')';

		$sql = '(' . implode(' AND ', $sql) . ')';

		return $sql;
	}

	/**
	 * Perform an interval match. It's similar to a 'between' match, but the
	 * from and to values are calculated based on $value and $interval:
	 * $value - $interval < VALUE < $value + $interval
	 *
	 * @param   integer|float  $value     The starting value of the search space
	 * @param   integer|float  $interval  The interval period of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	public function modulo($value, $interval, $include = true)
	{
		if ($this->isEmpty($value) || $this->isEmpty($interval))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $value . ' AND ';
		$sql .= '(' . $this->getFieldName() . ' - ' . $value . ') % ' . $interval . ' = 0)';

		return $sql;
	}
}
fof/model/field/text.php000064400000006210152177723700011210 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  model
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * FrameworkOnFramework model behavior class
 *
 * @package  FrameworkOnFramework
 * @since    2.1
 */
class FOFModelFieldText extends FOFModelField
{
	/**
	 * Constructor
	 *
	 * @param   FOFDatabaseDriver  $db     The database object
	 * @param   object           $field  The field informations as taken from the db
	 */
	public function __construct($db, $field, $table_alias = false)
	{
		parent::__construct($db, $field, $table_alias);

		$this->null_value = '';
	}

	/**
	 * Returns the default search method for this field.
	 *
	 * @return  string
	 */
	public function getDefaultSearchMethod()
	{
		return 'partial';
	}

	/**
	 * Perform a partial match (search in string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function partial($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		return '(' . $this->getFieldName() . ' LIKE ' . $this->_db->quote('%' . $value . '%') . ')';
	}

	/**
	 * Perform an exact match (match string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function exact($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		return '(' . $this->getFieldName() . ' LIKE ' . $this->_db->quote($value) . ')';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function between($from, $to, $include = true)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function outside($from, $to, $include = false)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $value     Ignored
	 * @param   mixed    $interval  Ignored
	 * @param   boolean  $include   Ignored
	 *
	 * @return  string  Empty string
	 */
	public function interval($value, $interval, $include = true)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function range($from, $to, $include = false)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function modulo($from, $to, $include = false)
	{
		return '';
	}
}
fof/string/utils.php000064400000004161152177723700010512 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  utils
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
// Protect from unauthorized access
defined('FOF_INCLUDED') or die;

/**
 * Helper class with utilitarian functions concerning strings
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
abstract class FOFStringUtils
{
	/**
	 * Convert a string into a slug (alias), suitable for use in URLs. Please
	 * note that transliteration suupport is rudimentary at this stage.
	 *
	 * @param   string  $value  A string to convert to slug
	 *
	 * @return  string  The slug
	 */
	public static function toSlug($value)
	{
		// Remove any '-' from the string they will be used as concatonater
		$value = str_replace('-', ' ', $value);

		// Convert to ascii characters
		$value = self::toASCII($value);

		// Lowercase and trim
		$value = trim(strtolower($value));

		// Remove any duplicate whitespace, and ensure all characters are alphanumeric
		$value = preg_replace(array('/\s+/', '/[^A-Za-z0-9\-_]/'), array('-', ''), $value);

		// Limit length
		if (strlen($value) > 100)
		{
			$value = substr($value, 0, 100);
		}

		return $value;
	}

	/**
	 * Convert common norhern European languages' letters into plain ASCII. This
	 * is a rudimentary transliteration.
	 *
	 * @param   string  $value  The value to convert to ASCII
	 *
	 * @return  string  The converted string
	 */
	public static function toASCII($value)
	{
		$string = htmlentities(utf8_decode($value), null, 'ISO-8859-1');
		$string = preg_replace(
			array('/&szlig;/', '/&(..)lig;/', '/&([aouAOU])uml;/', '/&(.)[^;]*;/'), array('ss', "$1", "$1" . 'e', "$1"), $string
		);

		return $string;
	}

	/**
	 * Convert a string to a boolean.
	 *
	 * @param   string  $string  The string.
	 *
	 * @return  boolean  The converted string
	 */
	public static function toBool($string)
	{
		$string = trim((string) $string);

		if ($string == 'true')
		{
			return true;
		}

		if ($string == 'false')
		{
			return false;
		}

		return (bool) $string;
	}
}
fof/render/strapper.php000064400000111107152177723700011162 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  render
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Akeeba Strapper view renderer class.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFRenderStrapper extends FOFRenderAbstract
{
	/**
	 * Public constructor. Determines the priority of this class and if it should be enabled
	 */
	public function __construct()
	{
		$this->priority	 = 60;
		$this->enabled	 = class_exists('AkeebaStrapper');
	}

	/**
	 * Echoes any HTML to show before the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function preRender($view, $task, $input, $config = array())
	{
		$format	 = $input->getCmd('format', 'html');

		if (empty($format))
		{
			$format	 = 'html';
		}

		if ($format != 'html')
		{
			return;
		}

		$platform = FOFPlatform::getInstance();

		if ($platform->isCli())
		{
			return;
		}

		if (version_compare(JVERSION, '3.0.0', 'lt'))
		{
			JHtml::_('behavior.framework');
		}
		else
		{
			if (version_compare(JVERSION, '3.3.0', 'ge'))
			{
				JHtml::_('behavior.core');
			}
			else
			{
				JHtml::_('behavior.framework', true);
			}

			JHtml::_('jquery.framework');
		}

		// Wrap output in various classes
		$version = new JVersion;
		$versionParts = explode('.', $version->RELEASE);
		$minorVersion = str_replace('.', '', $version->RELEASE);
		$majorVersion = array_shift($versionParts);

		if ($platform->isBackend())
		{
			$area = $platform->isBackend() ? 'admin' : 'site';
			$option = $input->getCmd('option', '');
			$view = $input->getCmd('view', '');
			$layout = $input->getCmd('layout', '');
			$task = $input->getCmd('task', '');

			$classes = array(
				'joomla-version-' . $majorVersion,
				'joomla-version-' . $minorVersion,
				$area,
				$option,
				'view-' . $view,
				'layout-' . $layout,
				'task-' . $task,
				// We have a floating sidebar, they said. It looks great, they said. They must've been blind, I say!
				'j-toggle-main',
				'j-toggle-transition',
				'span12',
			);
		}
		elseif ($platform->isFrontend())
		{
			// @TODO: Remove the frontend Joomla! version classes in FOF 3
			$classes = array(
				'joomla-version-' . $majorVersion,
				'joomla-version-' . $minorVersion,
			);
		}

		// Wrap output in divs
		echo '<div id="akeeba-bootstrap" class="' . implode($classes, ' ') . "\">\n";
		echo "<div class=\"akeeba-bootstrap\">\n";
		echo "<div class=\"row-fluid\">\n";

		// Render submenu and toolbar (only if asked to)
		if ($input->getBool('render_toolbar', true))
		{
			$this->renderButtons($view, $task, $input, $config);
			$this->renderLinkbar($view, $task, $input, $config);
		}
	}

	/**
	 * Echoes any HTML to show after the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function postRender($view, $task, $input, $config = array())
	{
		$format = $input->getCmd('format', 'html');

		if ($format != 'html' || FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		if (!FOFPlatform::getInstance()->isCli() && version_compare(JVERSION, '3.0', 'ge'))
		{
			$sidebarEntries = JHtmlSidebar::getEntries();

			if (!empty($sidebarEntries))
			{
				echo '</div>';
			}
		}

		echo "</div>\n";    // Closes row-fluid div
		echo "</div>\n";    // Closes akeeba-bootstrap div
		echo "</div>\n";    // Closes joomla-version div
	}

	/**
	 * Loads the validation script for an edit form
	 *
	 * @param   FOFForm  &$form  The form we are rendering
	 *
	 * @return  void
	 */
	protected function loadValidationScript(FOFForm &$form)
	{
		$message = $form->getView()->escape(JText::_('JGLOBAL_VALIDATION_FORM_FAILED'));

		$js = <<<JS
Joomla.submitbutton = function(task)
{
	if (task == 'cancel' || document.formvalidator.isValid(document.id('adminForm')))
	{
		Joomla.submitform(task, document.getElementById('adminForm'));
	}
	else {
		alert('$message');
	}
};
JS;

		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			$document->addScriptDeclaration($js);
		}
	}

	/**
	 * Renders the submenu (link bar)
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderLinkbar($view, $task, $input, $config = array())
	{
		$style = 'classic';

		if (array_key_exists('linkbar_style', $config))
		{
			$style = $config['linkbar_style'];
		}

		if (!version_compare(JVERSION, '3.0', 'ge'))
		{
			$style = 'classic';
		}

		switch ($style)
		{
			case 'joomla':
				$this->renderLinkbar_joomla($view, $task, $input);
				break;

			case 'classic':
			default:
				$this->renderLinkbar_classic($view, $task, $input);
				break;
		}
	}

	/**
	 * Renders the submenu (link bar) in FOF's classic style, using a Bootstrapped
	 * tab bar.
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderLinkbar_classic($view, $task, $input, $config = array())
	{
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render a submenu unless we are in the the admin area
		$toolbar				 = FOFToolbar::getAnInstance($input->getCmd('option', 'com_foobar'), $config);
		$renderFrontendSubmenu	 = $toolbar->getRenderFrontendSubmenu();

		if (!FOFPlatform::getInstance()->isBackend() && !$renderFrontendSubmenu)
		{
			return;
		}

		$links = $toolbar->getLinks();

		if (!empty($links))
		{
			echo "<ul class=\"nav nav-tabs\">\n";

			foreach ($links as $link)
			{
				$dropdown = false;

				if (array_key_exists('dropdown', $link))
				{
					$dropdown = $link['dropdown'];
				}

				if ($dropdown)
				{
					echo "<li";
					$class = 'dropdown';

					if ($link['active'])
					{
						$class .= ' active';
					}

					echo ' class="' . $class . '">';

					echo '<a class="dropdown-toggle" data-toggle="dropdown" href="#">';

					if ($link['icon'])
					{
						echo "<i class=\"icon icon-" . $link['icon'] . "\"></i>";
					}

					echo $link['name'];
					echo '<b class="caret"></b>';
					echo '</a>';

					echo "\n<ul class=\"dropdown-menu\">";

					foreach ($link['items'] as $item)
					{
						echo "<li";

						if ($item['active'])
						{
							echo ' class="active"';
						}

						echo ">";

						if ($item['icon'])
						{
							echo "<i class=\"icon icon-" . $item['icon'] . "\"></i>";
						}

						if ($item['link'])
						{
							echo "<a href=\"" . $item['link'] . "\">" . $item['name'] . "</a>";
						}
						else
						{
							echo $item['name'];
						}

						echo "</li>";
					}

					echo "</ul>\n";
				}
				else
				{
					echo "<li";

					if ($link['active'])
					{
						echo ' class="active"';
					}

					echo ">";

					if ($link['icon'])
					{
						echo "<i class=\"icon icon-" . $link['icon'] . "\"></i>";
					}

					if ($link['link'])
					{
						echo "<a href=\"" . $link['link'] . "\">" . $link['name'] . "</a>";
					}
					else
					{
						echo $link['name'];
					}
				}

				echo "</li>\n";
			}

			echo "</ul>\n";
		}
	}

	/**
	 * Renders the submenu (link bar) using Joomla!'s style. On Joomla! 2.5 this
	 * is a list of bar separated links, on Joomla! 3 it's a sidebar at the
	 * left-hand side of the page.
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderLinkbar_joomla($view, $task, $input, $config = array())
	{
		// On command line don't do anything
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render a submenu unless we are in the the admin area
		$toolbar				 = FOFToolbar::getAnInstance($input->getCmd('option', 'com_foobar'), $config);
		$renderFrontendSubmenu	 = $toolbar->getRenderFrontendSubmenu();

		if (!FOFPlatform::getInstance()->isBackend() && !$renderFrontendSubmenu)
		{
			return;
		}

		$this->renderLinkbarItems($toolbar);
	}

	/**
	 * do the rendering job for the linkbar
	 *
	 * @param   FOFToolbar  $toolbar  A toolbar object
	 *
	 * @return  void
	 */
	protected function renderLinkbarItems($toolbar)
	{
		$links = $toolbar->getLinks();

		if (!empty($links))
		{
			foreach ($links as $link)
			{
				JHtmlSidebar::addEntry($link['name'], $link['link'], $link['active']);

				$dropdown = false;

				if (array_key_exists('dropdown', $link))
				{
					$dropdown = $link['dropdown'];
				}

				if ($dropdown)
				{
					foreach ($link['items'] as $item)
					{
						JHtmlSidebar::addEntry('– ' . $item['name'], $item['link'], $item['active']);
					}
				}
			}
		}
	}

	/**
	 * Renders the toolbar buttons
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderButtons($view, $task, $input, $config = array())
	{
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render buttons unless we are in the the frontend area and we are asked to do so
		$toolbar				 = FOFToolbar::getAnInstance($input->getCmd('option', 'com_foobar'), $config);
		$renderFrontendButtons	 = $toolbar->getRenderFrontendButtons();

        // Load main backend language, in order to display toolbar strings
        // (JTOOLBAR_BACK, JTOOLBAR_PUBLISH etc etc)
        FOFPlatform::getInstance()->loadTranslations('joomla');

		if (FOFPlatform::getInstance()->isBackend() || !$renderFrontendButtons)
		{
			return;
		}

		$bar	 = JToolbar::getInstance('toolbar');
		$items	 = $bar->getItems();

		$substitutions = array(
			'icon-32-new'		 => 'icon-plus',
			'icon-32-publish'	 => 'icon-eye-open',
			'icon-32-unpublish'	 => 'icon-eye-close',
			'icon-32-delete'	 => 'icon-trash',
			'icon-32-edit'		 => 'icon-edit',
			'icon-32-copy'		 => 'icon-th-large',
			'icon-32-cancel'	 => 'icon-remove',
			'icon-32-back'		 => 'icon-circle-arrow-left',
			'icon-32-apply'		 => 'icon-ok',
			'icon-32-save'		 => 'icon-hdd',
			'icon-32-save-new'	 => 'icon-repeat',
		);

        if(isset(JFactory::getApplication()->JComponentTitle))
        {
            $title	 = JFactory::getApplication()->JComponentTitle;
        }
		else
		{
			$title = '';
		}

        $html	 = array();
        $actions = array();

        // For BC we have to use the same id we're using inside other renderers (FOFHeaderHolder)
        //$html[]	 = '<div class="well" id="' . $bar->getName() . '">';

        $html[]	 = '<div class="well" id="FOFHeaderHolder">';
        $html[]  =      '<div class="titleHolder">'.$title.'</div>';
        $html[]  =      '<div class="buttonsHolder">';

		foreach ($items as $node)
		{
			$type	 = $node[0];
			$button	 = $bar->loadButtonType($type);

			if ($button !== false)
			{
				if (method_exists($button, 'fetchId'))
				{
					$id = call_user_func_array(array(&$button, 'fetchId'), $node);
				}
				else
				{
					$id = null;
				}

				$action	    = call_user_func_array(array(&$button, 'fetchButton'), $node);
				$action	    = str_replace('class="toolbar"', 'class="toolbar btn"', $action);
				$action	    = str_replace('<span ', '<i ', $action);
				$action	    = str_replace('</span>', '</i>', $action);
				$action	    = str_replace(array_keys($substitutions), array_values($substitutions), $action);
				$actions[]	= $action;
			}
		}

        $html   = array_merge($html, $actions);
		$html[] = '</div>';
		$html[] = '</div>';

        echo implode("\n", $html);
	}

	/**
	 * Renders a FOFForm for a Browse view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormBrowse(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		$html = '';

		JHtml::_('behavior.multiselect');

		// Joomla! 3.0+ support
		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			JHtml::_('bootstrap.tooltip');
			JHtml::_('dropdown.init');
			JHtml::_('formbehavior.chosen', 'select');
			$view	 = $form->getView();
			$order	 = $view->escape($view->getLists()->order);
			$html .= <<<HTML
<script type="text/javascript">
	Joomla.orderTable = function() {
		table = document.getElementById("sortTable");
		direction = document.getElementById("directionTable");
		order = table.options[table.selectedIndex].value;
		if (order != '$order')
		{
			dirn = 'asc';
		}
		else {
			dirn = direction.options[direction.selectedIndex].value;
		}
		Joomla.tableOrdering(order, dirn);
	};
</script>

HTML;
		}
		else
		{
			JHtml::_('behavior.tooltip');
		}

		// Getting all header row elements
		$headerFields = $form->getHeaderset();

		// Get form parameters
		$show_header		 = $form->getAttribute('show_header', 1);
		$show_filters		 = $form->getAttribute('show_filters', 1);
		$show_pagination	 = $form->getAttribute('show_pagination', 1);
		$norows_placeholder	 = $form->getAttribute('norows_placeholder', '');

		// Joomla! 3.0 sidebar support

		if (version_compare(JVERSION, '3.0', 'gt'))
		{
			$form_class = '';

			if ($show_filters)
			{
				JHtmlSidebar::setAction("index.php?option=" .
					$input->getCmd('option') . "&view=" .
					FOFInflector::pluralize($input->getCmd('view'))
				);
			}

			// Reorder the fields with ordering first
			$tmpFields = array();
			$i = 1;

			foreach ($headerFields as $tmpField)
			{
				if ($tmpField instanceof FOFFormHeaderOrdering)
				{
					$tmpFields[0] = $tmpField;
				}

				else
				{
					$tmpFields[$i] = $tmpField;
				}

				$i++;
			}

			$headerFields = $tmpFields;
			ksort($headerFields, SORT_NUMERIC);
		}
		else
		{
			$form_class = 'class="form-horizontal"';
		}

		// Pre-render the header and filter rows
		$header_html = '';
		$filter_html = '';
		$sortFields	 = array();

		if ($show_header || $show_filters)
		{
			foreach ($headerFields as $headerField)
			{
				$header		 = $headerField->header;
				$filter		 = $headerField->filter;
				$buttons	 = $headerField->buttons;
				$options	 = $headerField->options;
				$sortable	 = $headerField->sortable;
				$tdwidth	 = $headerField->tdwidth;

				// Under Joomla! < 3.0 we can't have filter-only fields

				if (version_compare(JVERSION, '3.0', 'lt') && empty($header))
				{
					continue;
				}

				// If it's a sortable field, add to the list of sortable fields

				if ($sortable)
				{
					$sortFields[$headerField->name] = JText::_($headerField->label);
				}

				// Get the table data width, if set

				if (!empty($tdwidth))
				{
					$tdwidth = 'width="' . $tdwidth . '"';
				}
				else
				{
					$tdwidth = '';
				}

				if (!empty($header))
				{
					$header_html .= "\t\t\t\t\t<th $tdwidth>" . PHP_EOL;
					$header_html .= "\t\t\t\t\t\t" . $header;
					$header_html .= "\t\t\t\t\t</th>" . PHP_EOL;
				}

				if (version_compare(JVERSION, '3.0', 'ge'))
				{
					// Joomla! 3.0 or later
					if (!empty($filter))
					{
						$filter_html .= '<div class="filter-search btn-group pull-left">' . "\n";
						$filter_html .= "\t" . '<label for="title" class="element-invisible">';
						$filter_html .= JText::_($headerField->label);
						$filter_html .= "</label>\n";
						$filter_html .= "\t$filter\n";
						$filter_html .= "</div>\n";

						if (!empty($buttons))
						{
							$filter_html .= '<div class="btn-group pull-left hidden-phone">' . "\n";
							$filter_html .= "\t$buttons\n";
							$filter_html .= '</div>' . "\n";
						}
					}
					elseif (!empty($options))
					{
						$label = $headerField->label;

						JHtmlSidebar::addFilter(
							'- ' . JText::_($label) . ' -', (string) $headerField->name,
							JHtml::_(
								'select.options',
								$options,
								'value',
								'text',
								$model->getState($headerField->name, ''), true
							)
						);
					}
				}
				else
				{
					// Joomla! 2.5
					$filter_html .= "\t\t\t\t\t<td>" . PHP_EOL;

					if (!empty($filter))
					{
						$filter_html .= "\t\t\t\t\t\t$filter" . PHP_EOL;

						if (!empty($buttons))
						{
							$filter_html .= '<div class="btn-group hidden-phone">' . PHP_EOL;
							$filter_html .= "\t\t\t\t\t\t$buttons" . PHP_EOL;
							$filter_html .= '</div>' . PHP_EOL;
						}
					}
					elseif (!empty($options))
					{
						$label		 = $headerField->label;
						$emptyOption = JHtml::_('select.option', '', '- ' . JText::_($label) . ' -');
						array_unshift($options, $emptyOption);
						$attribs	 = array(
							'onchange' => 'document.adminForm.submit();'
						);
						$filter		 = JHtml::_('select.genericlist', $options, $headerField->name, $attribs, 'value', 'text', $headerField->value, false, true);
						$filter_html .= "\t\t\t\t\t\t$filter" . PHP_EOL;
					}

					$filter_html .= "\t\t\t\t\t</td>" . PHP_EOL;
				}
			}
		}

		// Start the form
		$filter_order		 = $form->getView()->getLists()->order;
		$filter_order_Dir	 = $form->getView()->getLists()->order_Dir;
        $actionUrl           = FOFPlatform::getInstance()->isBackend() ? 'index.php' : JUri::root().'index.php';

		if (FOFPlatform::getInstance()->isFrontend() && ($input->getCmd('Itemid', 0) != 0))
		{
			$itemid = $input->getCmd('Itemid', 0);
			$uri = new JUri($actionUrl);

			if ($itemid)
			{
				$uri->setVar('Itemid', $itemid);
			}

			$actionUrl = JRoute::_($uri->toString());
		}

		$html .= '<form action="'.$actionUrl.'" method="post" name="adminForm" id="adminForm" ' . $form_class . '>' . PHP_EOL;

		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			// Joomla! 3.0+
			// Get and output the sidebar, if present
			$sidebar = JHtmlSidebar::render();

			if ($show_filters && !empty($sidebar))
			{
				$html .= '<div id="j-sidebar-container" class="span2">' . "\n";
				$html .= "\t$sidebar\n";
				$html .= "</div>\n";
				$html .= '<div id="j-main-container" class="span10">' . "\n";
			}
			else
			{
				$html .= '<div id="j-main-container">' . "\n";
			}

			// Render header search fields, if the header is enabled

			if ($show_header)
			{
				$html .= "\t" . '<div id="filter-bar" class="btn-toolbar">' . "\n";
				$html .= "$filter_html\n";

				if ($show_pagination)
				{
					// Render the pagination rows per page selection box, if the pagination is enabled
					$html .= "\t" . '<div class="btn-group pull-right hidden-phone">' . "\n";
					$html .= "\t\t" . '<label for="limit" class="element-invisible">' . JText::_('JFIELD_PLG_SEARCH_SEARCHLIMIT_DESC') . '</label>' . "\n";
					$html .= "\t\t" . $model->getPagination()->getLimitBox() . "\n";
					$html .= "\t" . '</div>' . "\n";
				}

				if (!empty($sortFields))
				{
					// Display the field sort order
					$asc_sel	 = ($view->getLists()->order_Dir == 'asc') ? 'selected="selected"' : '';
					$desc_sel	 = ($view->getLists()->order_Dir == 'desc') ? 'selected="selected"' : '';
					$html .= "\t" . '<div class="btn-group pull-right hidden-phone">' . "\n";
					$html .= "\t\t" . '<label for="directionTable" class="element-invisible">' . JText::_('JFIELD_ORDERING_DESC') . '</label>' . "\n";
					$html .= "\t\t" . '<select name="directionTable" id="directionTable" class="input-medium" onchange="Joomla.orderTable()">' . "\n";
					$html .= "\t\t\t" . '<option value="">' . JText::_('JFIELD_ORDERING_DESC') . '</option>' . "\n";
					$html .= "\t\t\t" . '<option value="asc" ' . $asc_sel . '>' . JText::_('JGLOBAL_ORDER_ASCENDING') . '</option>' . "\n";
					$html .= "\t\t\t" . '<option value="desc" ' . $desc_sel . '>' . JText::_('JGLOBAL_ORDER_DESCENDING') . '</option>' . "\n";
					$html .= "\t\t" . '</select>' . "\n";
					$html .= "\t" . '</div>' . "\n\n";

					// Display the sort fields
					$html .= "\t" . '<div class="btn-group pull-right">' . "\n";
					$html .= "\t\t" . '<label for="sortTable" class="element-invisible">' . JText::_('JGLOBAL_SORT_BY') . '</label>' . "\n";
					$html .= "\t\t" . '<select name="sortTable" id="sortTable" class="input-medium" onchange="Joomla.orderTable()">' . "\n";
					$html .= "\t\t\t" . '<option value="">' . JText::_('JGLOBAL_SORT_BY') . '</option>' . "\n";
					$html .= "\t\t\t" . JHtml::_('select.options', $sortFields, 'value', 'text', $view->getLists()->order) . "\n";
					$html .= "\t\t" . '</select>' . "\n";
					$html .= "\t" . '</div>' . "\n";
				}

				$html .= "\t</div>\n\n";
				$html .= "\t" . '<div class="clearfix"> </div>' . "\n\n";
			}
		}

		// Start the table output
		$html .= "\t\t" . '<table class="table table-striped" id="itemsList">' . PHP_EOL;

		// Open the table header region if required

		if ($show_header || ($show_filters && version_compare(JVERSION, '3.0', 'lt')))
		{
			$html .= "\t\t\t<thead>" . PHP_EOL;
		}

		// Render the header row, if enabled

		if ($show_header)
		{
			$html .= "\t\t\t\t<tr>" . PHP_EOL;
			$html .= $header_html;
			$html .= "\t\t\t\t</tr>" . PHP_EOL;
		}

		// Render filter row if enabled

		if ($show_filters && version_compare(JVERSION, '3.0', 'lt'))
		{
			$html .= "\t\t\t\t<tr>";
			$html .= $filter_html;
			$html .= "\t\t\t\t</tr>";
		}

		// Close the table header region if required

		if ($show_header || ($show_filters && version_compare(JVERSION, '3.0', 'lt')))
		{
			$html .= "\t\t\t</thead>" . PHP_EOL;
		}

		// Loop through rows and fields, or show placeholder for no rows
		$html .= "\t\t\t<tbody>" . PHP_EOL;
		$fields		 = $form->getFieldset('items');
		$num_columns = count($fields);
		$items		 = $model->getItemList();

		if ($count = count($items))
		{
			$m = 1;

			foreach ($items as $i => $item)
			{
				$table_item = $model->getTable();
				$table_item->reset();
				$table_item->bind($item);

				$form->bind($item);

				$m		 = 1 - $m;
				$class	 = 'row' . $m;

				$html .= "\t\t\t\t<tr class=\"$class\">" . PHP_EOL;

				$fields = $form->getFieldset('items');

				// Reorder the fields to have ordering first
				if (version_compare(JVERSION, '3.0', 'gt'))
				{
					$tmpFields = array();
					$j = 1;

					foreach ($fields as $tmpField)
					{
						if ($tmpField instanceof FOFFormFieldOrdering)
						{
							$tmpFields[0] = $tmpField;
						}

						else
						{
							$tmpFields[$j] = $tmpField;
						}

						$j++;
					}

					$fields = $tmpFields;
					ksort($fields, SORT_NUMERIC);
				}

				foreach ($fields as $field)
				{
					$field->rowid	 = $i;
					$field->item	 = $table_item;
					$labelClass = $field->labelClass ? $field->labelClass : $field->labelclass; // Joomla! 2.5/3.x use different case for the same name
					$class			 = $labelClass ? 'class ="' . $labelClass . '"' : '';
					$html .= "\t\t\t\t\t<td $class>" . $field->getRepeatable() . '</td>' . PHP_EOL;
				}

				$html .= "\t\t\t\t</tr>" . PHP_EOL;
			}
		}
		elseif ($norows_placeholder)
		{
			$html .= "\t\t\t\t<tr><td colspan=\"$num_columns\">";
			$html .= JText::_($norows_placeholder);
			$html .= "</td></tr>\n";
		}

		$html .= "\t\t\t</tbody>" . PHP_EOL;

		// Render the pagination bar, if enabled, on J! 2.5
		if ($show_pagination && version_compare(JVERSION, '3.0', 'lt'))
		{
			$pagination = $model->getPagination();
			$html .= "\t\t\t<tfoot>" . PHP_EOL;
			$html .= "\t\t\t\t<tr><td colspan=\"$num_columns\">";

			if (($pagination->total > 0))
			{
				$html .= $pagination->getListFooter();
			}

			$html .= "</td></tr>\n";
			$html .= "\t\t\t</tfoot>" . PHP_EOL;
		}

		// End the table output
		$html .= "\t\t" . '</table>' . PHP_EOL;

		// Render the pagination bar, if enabled, on J! 3.0+

		if ($show_pagination && version_compare(JVERSION, '3.0', 'ge'))
		{
			$html .= $model->getPagination()->getListFooter();
		}

		// Close the wrapper element div on Joomla! 3.0+

		if (version_compare(JVERSION, '3.0', 'ge'))
		{
			$html .= "</div>\n";
		}

		$html .= "\t" . '<input type="hidden" name="option" value="' . $input->getCmd('option') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="view" value="' . FOFInflector::pluralize($input->getCmd('view')) . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="task" value="' . $input->getCmd('task', 'browse') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="layout" value="' . $input->getCmd('layout', '') . '" />' . PHP_EOL;

		// The id field is required in Joomla! 3 front-end to prevent the pagination limit box from screwing it up. Huh!!

		if (version_compare(JVERSION, '3.0', 'ge') && FOFPlatform::getInstance()->isFrontend())
		{
			$html .= "\t" . '<input type="hidden" name="id" value="' . $input->getCmd('id', '') . '" />' . PHP_EOL;
		}

		$html .= "\t" . '<input type="hidden" name="boxchecked" value="" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="hidemainmenu" value="" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="filter_order" value="' . $filter_order . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="filter_order_Dir" value="' . $filter_order_Dir . '" />' . PHP_EOL;

		$html .= "\t" . '<input type="hidden" name="' . JFactory::getSession()->getFormToken() . '" value="1" />' . PHP_EOL;

		// End the form
		$html .= '</form>' . PHP_EOL;

		return $html;
	}

	/**
	 * Renders a FOFForm for a Read view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormRead(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		$html = $this->renderFormRaw($form, $model, $input, 'read');

		return $html;
	}

	/**
	 * Renders a FOFForm for an Edit view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormEdit(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		// Get the key for this model's table
		$key		 = $model->getTable()->getKeyName();
		$keyValue	 = $model->getId();

		$html = '';

		$validate	 = strtolower($form->getAttribute('validate'));

		if (in_array($validate, array('true', 'yes', '1', 'on')))
		{
			JHtml::_('behavior.formvalidation');
			$class = ' form-validate';
			$this->loadValidationScript($form);
		}
		else
		{
			$class = '';
		}

		// Check form enctype. Use enctype="multipart/form-data" to upload binary files in your form.
		$template_form_enctype = $form->getAttribute('enctype');

		if (!empty($template_form_enctype))
		{
			$enctype = ' enctype="' . $form->getAttribute('enctype') . '" ';
		}
		else
		{
			$enctype = '';
		}

		// Check form name. Use name="yourformname" to modify the name of your form.
		$formname = $form->getAttribute('name');

		if (empty($formname))
		{
			$formname = 'adminForm';
		}

		// Check form ID. Use id="yourformname" to modify the id of your form.
		$formid = $form->getAttribute('name');

		if (empty($formid))
		{
			$formid = 'adminForm';
		}

		// Check if we have a custom task
		$customTask = $form->getAttribute('customTask');

		if (empty($customTask))
		{
			$customTask = '';
		}

		// Get the form action URL
        $actionUrl = FOFPlatform::getInstance()->isBackend() ? 'index.php' : JUri::root().'index.php';

		if (FOFPlatform::getInstance()->isFrontend() && ($input->getCmd('Itemid', 0) != 0))
		{
			$itemid = $input->getCmd('Itemid', 0);
			$uri = new JUri($actionUrl);

			if ($itemid)
			{
				$uri->setVar('Itemid', $itemid);
			}

			$actionUrl = JRoute::_($uri->toString());
		}

		$html .= '<form action="'.$actionUrl.'" method="post" name="' . $formname .
			'" id="' . $formid . '"' . $enctype . ' class="form-horizontal' .
			$class . '">' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="option" value="' . $input->getCmd('option') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="view" value="' . $input->getCmd('view', 'edit') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="task" value="' . $customTask . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="' . $key . '" value="' . $keyValue . '" />' . PHP_EOL;

		$html .= "\t" . '<input type="hidden" name="' . JFactory::getSession()->getFormToken() . '" value="1" />' . PHP_EOL;

		$html .= $this->renderFormRaw($form, $model, $input, 'edit');
		$html .= '</form>';

		return $html;
	}

	/**
	 * Renders a raw FOFForm and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form     The form to render
	 * @param   FOFModel  $model     The model providing our data
	 * @param   FOFInput  $input     The input object
	 * @param   string    $formType  The form type e.g. 'edit' or 'read'
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormRaw(FOFForm &$form, FOFModel $model, FOFInput $input, $formType)
	{
		$html = '';
		$tabHtml = array();

		// Do we have a tabbed form?
		$isTabbed = $form->getAttribute('tabbed', '0');
		$isTabbed = in_array($isTabbed, array('true', 'yes', 'on', '1'));

		foreach ($form->getFieldsets() as $fieldset)
		{
			if ($isTabbed && $this->isTabFieldset($fieldset))
			{
				continue;
			}
			elseif ($isTabbed && isset($fieldset->innertab))
			{
				$inTab = $fieldset->innertab;
			}
			else
			{
				$inTab = '__outer';
			}

			$tabHtml[$inTab][] = $this->renderFieldset($fieldset, $form, $model, $input, $formType, false);
		}

		// If the form is tabbed, render the tabs bars
		if ($isTabbed)
		{
			$html .= '<ul class="nav nav-tabs">' . PHP_EOL;

			foreach ($form->getFieldsets() as $fieldset)
			{
				// Only create tabs for tab fieldsets
				$isTabbedFieldset = $this->isTabFieldset($fieldset);
				if (!$isTabbedFieldset)
				{
					continue;
				}

				// Only create tabs if we do have a label
				if (!isset($fieldset->label) || empty($fieldset->label))
				{
					continue;
				}

				$label = JText::_($fieldset->label);
				$name = $fieldset->name;
				$liClass = ($isTabbedFieldset == 2) ? 'class="active"' : '';

				$html .= "<li $liClass><a href=\"#$name\" data-toggle=\"tab\">$label</a></li>" . PHP_EOL;
			}

			$html .= '</ul>' . "\n\n<div class=\"tab-content\">" . PHP_EOL;

			foreach ($form->getFieldsets() as $fieldset)
			{
				if (!$this->isTabFieldset($fieldset))
				{
					continue;
				}

				$html .= $this->renderFieldset($fieldset, $form, $model, $input, $formType, false, $tabHtml);
			}

			$html .= "</div>\n";
		}

		if (isset($tabHtml['__outer']))
		{
			$html .= implode('', $tabHtml['__outer']);
		}

		return $html;
	}

	/**
	 * Renders a raw fieldset of a FOFForm and returns the corresponding HTML
	 *
	 * @param   stdClass  &$fieldset   The fieldset to render
	 * @param   FOFForm   &$form       The form to render
	 * @param   FOFModel  $model       The model providing our data
	 * @param   FOFInput  $input       The input object
	 * @param   string    $formType    The form type e.g. 'edit' or 'read'
	 * @param   boolean   $showHeader  Should I render the fieldset's header?
	 *
	 * @return  string    The HTML rendering of the fieldset
	 */
	protected function renderFieldset(stdClass &$fieldset, FOFForm &$form, FOFModel $model, FOFInput $input, $formType, $showHeader = true, &$innerHtml = null)
	{
		$html = '';

		$fields = $form->getFieldset($fieldset->name);

		if (isset($fieldset->class))
		{
			$class = 'class="' . $fieldset->class . '"';
		}
		else
		{
			$class = '';
		}

		if (isset($innerHtml[$fieldset->name]))
		{
			$innerclass = isset($fieldset->innerclass) ? ' class="' . $fieldset->innerclass . '"' : '';

			$html .= "\t" . '<div id="' . $fieldset->name . '" ' . $class . '>' . PHP_EOL;
			$html .= "\t\t" . '<div' . $innerclass . '>' . PHP_EOL;
		}
		else
		{
			$html .= "\t" . '<div id="' . $fieldset->name . '" ' . $class . '>' . PHP_EOL;
		}

		$isTabbedFieldset = $this->isTabFieldset($fieldset);

		if (isset($fieldset->label) && !empty($fieldset->label) && !$isTabbedFieldset)
		{
			$html .= "\t\t" . '<h3>' . JText::_($fieldset->label) . '</h3>' . PHP_EOL;
		}

		foreach ($fields as $field)
		{
			$groupClass	 = $form->getFieldAttribute($field->fieldname, 'groupclass', '', $field->group);

			// Auto-generate label and description if needed
			// Field label
			$title 		 = $form->getFieldAttribute($field->fieldname, 'label', '', $field->group);
			$emptylabel  = $form->getFieldAttribute($field->fieldname, 'emptylabel', false, $field->group);

			if (empty($title) && !$emptylabel)
			{
				$model->getName();
				$title = strtoupper($input->get('option') . '_' . $model->getName() . '_' . $field->id . '_LABEL');
			}

			// Field description
			$description = $form->getFieldAttribute($field->fieldname, 'description', '', $field->group);

			/**
			 * The following code is backwards incompatible. Most forms don't require a description in their form
			 * fields. Having to use emptydescription="1" on each one of them is an overkill. Removed.
			 */
			/*
			$emptydescription   = $form->getFieldAttribute($field->fieldname, 'emptydescription', false, $field->group);
			if (empty($description) && !$emptydescription)
			{
				$description = strtoupper($input->get('option') . '_' . $model->getName() . '_' . $field->id . '_DESC');
			}
			*/

			if ($formType == 'read')
			{
				$inputField = $field->static;
			}
			elseif ($formType == 'edit')
			{
				$inputField = $field->input;
			}

			if (empty($title))
			{
				$html .= "\t\t\t" . $inputField . PHP_EOL;

				if (!empty($description) && $formType == 'edit')
				{
					$html .= "\t\t\t\t" . '<span class="help-block">';
					$html .= JText::_($description) . '</span>' . PHP_EOL;
				}
			}
			else
			{
				$html .= "\t\t\t" . '<div class="control-group ' . $groupClass . '">' . PHP_EOL;
				$html .= $this->renderFieldsetLabel($field, $form, $title);
				$html .= "\t\t\t\t" . '<div class="controls">' . PHP_EOL;
				$html .= "\t\t\t\t\t" . $inputField . PHP_EOL;

				if (!empty($description))
				{
					$html .= "\t\t\t\t" . '<span class="help-block">';
					$html .= JText::_($description) . '</span>' . PHP_EOL;
				}

				$html .= "\t\t\t\t" . '</div>' . PHP_EOL;
				$html .= "\t\t\t" . '</div>' . PHP_EOL;
			}
		}

		if (isset($innerHtml[$fieldset->name]))
		{
			$html .= "\t\t" . '</div>' . PHP_EOL;
			$html .= implode('', $innerHtml[$fieldset->name]) . PHP_EOL;
			$html .= "\t" . '</div>' . PHP_EOL;
		}
		else
		{
			$html .= "\t" . '</div>' . PHP_EOL;
		}

		return $html;
	}

	/**
	 * Renders a label for a fieldset.
	 *
	 * @param   object  	$field  	The field of the label to render
	 * @param   FOFForm   	&$form      The form to render
	 * @param 	string		$title		The title of the label
	 *
	 * @return 	string		The rendered label
	 */
	protected function renderFieldsetLabel($field, FOFForm &$form, $title)
	{
		$html = '';

		$labelClass	 = $field->labelClass ? $field->labelClass : $field->labelclass; // Joomla! 2.5/3.x use different case for the same name
		$required	 = $field->required;

		$tooltip = $form->getFieldAttribute($field->fieldname, 'tooltip', '', $field->group);

		if (!empty($tooltip))
		{
			if (version_compare(JVERSION, '3.0', 'ge'))
			{
				static $loadedTooltipScript = false;

				if (!$loadedTooltipScript)
				{
					$js = <<<JS
(function($)
{
	$(document).ready(function()
	{
		$('.fof-tooltip').tooltip({placement: 'top'});
	});
})(akeeba.jQuery);
JS;
					$document = FOFPlatform::getInstance()->getDocument();

					if ($document instanceof JDocument)
					{
						$document->addScriptDeclaration($js);
					}

					$loadedTooltipScript = true;
				}

				$tooltipText = '<strong>' . JText::_($title) . '</strong><br />' . JText::_($tooltip);

				$html .= "\t\t\t\t" . '<label class="control-label fof-tooltip ' . $labelClass . '" for="' . $field->id . '" title="' . $tooltipText . '" data-toggle="fof-tooltip">';
			}
			else
			{
				// Joomla! 2.5 has a conflict with the jQueryUI tooltip, therefore we
				// have to use native Joomla! 2.5 tooltips
				JHtml::_('behavior.tooltip');

				$tooltipText = JText::_($title) . '::' . JText::_($tooltip);

				$html .= "\t\t\t\t" . '<label class="control-label hasTip ' . $labelClass . '" for="' . $field->id . '" title="' . $tooltipText . '" rel="tooltip">';
			}
		}
		else
		{
			$html .= "\t\t\t\t" . '<label class="control-label ' . $labelClass . '" for="' . $field->id . '">';
		}

		$html .= JText::_($title);

		if ($required)
		{
			$html .= ' *';
		}

		$html .= '</label>' . PHP_EOL;

		return $html;
	}
}
fof/render/abstract.php000064400000017160152177723700011131 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  render
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Abstract view renderer class. The renderer is what turns XML view templates
 * into actual HTML code, renders the submenu links and potentially wraps the
 * HTML output in a div with a component-specific ID.
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
abstract class FOFRenderAbstract
{
	/** @var int Priority of this renderer. Higher means more important */
	protected $priority = 50;

	/** @var int Is this renderer enabled? */
	protected $enabled = false;

	/**
	 * Returns the information about this renderer
	 *
	 * @return object
	 */
	public function getInformation()
	{
		return (object) array(
				'priority'	 => $this->priority,
				'enabled'	 => $this->enabled,
		);
	}

	/**
	 * Echoes any HTML to show before the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	abstract public function preRender($view, $task, $input, $config = array());

	/**
	 * Echoes any HTML to show after the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	abstract public function postRender($view, $task, $input, $config = array());

	/**
	 * Renders a FOFForm and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form     The form to render
	 * @param   FOFModel  $model     The model providing our data
	 * @param   FOFInput  $input     The input object
	 * @param   string    $formType  The form type: edit, browse or read
	 * @param   boolean   $raw       If true, the raw form fields rendering (without the surrounding form tag) is returned.
	 *
	 * @return  string    The HTML rendering of the form
	 */
	public function renderForm(FOFForm &$form, FOFModel $model, FOFInput $input, $formType = null, $raw = false)
	{
		if (is_null($formType))
		{
			$formType = $form->getAttribute('type', 'edit');
		}
		else
		{
			$formType = strtolower($formType);
		}

		switch ($formType)
		{
			case 'browse':
				return $this->renderFormBrowse($form, $model, $input);
				break;

			case 'read':
				if ($raw)
				{
					return $this->renderFormRaw($form, $model, $input, 'read');
				}
				else
				{
					return $this->renderFormRead($form, $model, $input);
				}

				break;

			default:
				if ($raw)
				{
					return $this->renderFormRaw($form, $model, $input, 'edit');
				}
				else
				{
					return $this->renderFormEdit($form, $model, $input);
				}
				break;
		}
	}

	/**
	 * Renders the submenu (link bar) for a category view when it is used in a
	 * extension
	 *
	 * Note: this function has to be called from the addSubmenu function in
	 * 		 the ExtensionNameHelper class located in
	 * 		 administrator/components/com_ExtensionName/helpers/Extensionname.php
	 *
	 * Example Code:
	 *
	 *	class ExtensionNameHelper
	 *	{
	 * 		public static function addSubmenu($vName)
	 *		{
	 *			// Load FOF
	 *			include_once JPATH_LIBRARIES . '/fof/include.php';
	 *
	 *			if (!defined('FOF_INCLUDED'))
	 *			{
	 *				JError::raiseError('500', 'FOF is not installed');
	 *			}
	 *
	 *			if (version_compare(JVERSION, '3.0', 'ge'))
	 *			{
	 *				$strapper = new FOFRenderJoomla3;
	 *			}
	 *			else
	 *			{
	 *				$strapper = new FOFRenderJoomla;
	 *			}
	 *
	 *			$strapper->renderCategoryLinkbar('com_babioonevent');
	 *		}
	 *	}
	 *
	 * @param   string  $extension  The name of the extension
	 * @param   array   $config     Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	public function renderCategoryLinkbar($extension, $config = array())
	{
		// On command line don't do anything
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render a category submenu unless we are in the the admin area
		if (!FOFPlatform::getInstance()->isBackend())
		{
			return;
		}

		$toolbar = FOFToolbar::getAnInstance($extension, $config);
		$toolbar->renderSubmenu();

		$this->renderLinkbarItems($toolbar);
	}

	/**
	 * Renders a FOFForm for a Browse view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	abstract protected function renderFormBrowse(FOFForm &$form, FOFModel $model, FOFInput $input);

	/**
	 * Renders a FOFForm for a Read view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	abstract protected function renderFormRead(FOFForm &$form, FOFModel $model, FOFInput $input);

	/**
	 * Renders a FOFForm for an Edit view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	abstract protected function renderFormEdit(FOFForm &$form, FOFModel $model, FOFInput $input);

	/**
	 * Renders a raw FOFForm and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form     The form to render
	 * @param   FOFModel  $model     The model providing our data
	 * @param   FOFInput  $input     The input object
	 * @param   string    $formType  The form type e.g. 'edit' or 'read'
	 *
	 * @return  string    The HTML rendering of the form
	 */
	abstract protected function renderFormRaw(FOFForm &$form, FOFModel $model, FOFInput $input, $formType);

	/**
	 * Renders a raw fieldset of a FOFForm and returns the corresponding HTML
	 *
	 * @TODO: Convert to an abstract method or interface at FOF3
	 *
	 * @param   stdClass  &$fieldset   The fieldset to render
	 * @param   FOFForm   &$form       The form to render
	 * @param   FOFModel  $model       The model providing our data
	 * @param   FOFInput  $input       The input object
	 * @param   string    $formType    The form type e.g. 'edit' or 'read'
	 * @param   boolean   $showHeader  Should I render the fieldset's header?
	 *
	 * @return  string    The HTML rendering of the fieldset
	 */
	protected function renderFieldset(stdClass &$fieldset, FOFForm &$form, FOFModel $model, FOFInput $input, $formType, $showHeader = true)
	{

	}

	/**
	 * Renders a label for a fieldset.
	 *
	 * @TODO: Convert to an abstract method or interface at FOF3
	 *
	 * @param   object  	$field  	The field of the label to render
	 * @param   FOFForm   	&$form      The form to render
	 * @param 	string		$title		The title of the label
	 *
	 * @return 	string		The rendered label
	 */
	protected function renderFieldsetLabel($field, FOFForm &$form, $title)
	{

	}

	/**
	 * Checks if the fieldset defines a tab pane
	 *
	 * @param   SimpleXMLElement  $fieldset
	 *
	 * @return  boolean
	 */
	protected function isTabFieldset($fieldset)
	{
		if (!isset($fieldset->class) || !$fieldset->class)
		{
			return false;
		}

		$class = $fieldset->class;
		$classes = explode(' ', $class);

		if (!in_array('tab-pane', $classes))
		{
			return false;
		}
		else
		{
			return in_array('active', $classes) ? 2 : 1;
		}
	}
}
fof/render/joomla.php000064400000052276152177723700010616 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  render
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Default Joomla! 1.5, 1.7, 2.5 view renderer class
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFRenderJoomla extends FOFRenderAbstract
{
	/**
	 * Public constructor. Determines the priority of this class and if it should be enabled
	 */
	public function __construct()
	{
		$this->priority	 = 50;
		$this->enabled	 = true;
	}

	/**
	 * Echoes any HTML to show before the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function preRender($view, $task, $input, $config = array())
	{
		$format	 = $input->getCmd('format', 'html');

		if (empty($format))
		{
			$format	 = 'html';
		}

		if ($format != 'html')
		{
			return;
		}

		$platform = FOFPlatform::getInstance();

		if ($platform->isCli())
		{
			return;
		}

		if (version_compare(JVERSION, '3.0.0', 'lt'))
		{
			JHtml::_('behavior.framework');
		}
		else
		{
			if (version_compare(JVERSION, '3.3.0', 'ge'))
			{
				JHtml::_('behavior.core');
			}
			else
			{
				JHtml::_('behavior.framework', true);
			}

			JHtml::_('jquery.framework');
		}

		// Wrap output in various classes
		$version = new JVersion;
		$versionParts = explode('.', $version->RELEASE);
		$minorVersion = str_replace('.', '', $version->RELEASE);
		$majorVersion = array_shift($versionParts);

		if ($platform->isBackend())
		{
			$area = $platform->isBackend() ? 'admin' : 'site';
			$option = $input->getCmd('option', '');
			$view = $input->getCmd('view', '');
			$layout = $input->getCmd('layout', '');
			$task = $input->getCmd('task', '');

			$classes = array(
				'joomla-version-' . $majorVersion,
				'joomla-version-' . $minorVersion,
				$area,
				$option,
				'view-' . $view,
				'layout-' . $layout,
				'task-' . $task,
			);
		}
		elseif ($platform->isFrontend())
		{
			// @TODO: Remove the frontend Joomla! version classes in FOF 3
			$classes = array(
				'joomla-version-' . $majorVersion,
				'joomla-version-' . $minorVersion,
			);
		}

		echo '<div id="akeeba-renderjoomla" class="' . implode($classes, ' ') . "\">\n";

		// Render submenu and toolbar (only if asked to)
		if ($input->getBool('render_toolbar', true))
		{
			$this->renderButtons($view, $task, $input, $config);
			$this->renderLinkbar($view, $task, $input, $config);
		}
	}

	/**
	 * Echoes any HTML to show after the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function postRender($view, $task, $input, $config = array())
	{
		$format	 = $input->getCmd('format', 'html');

		if (empty($format))
		{
			$format	 = 'html';
		}

		if ($format != 'html')
		{
			return;
		}

		// Closing tag only if we're not in CLI
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		echo "</div>\n";    // Closes akeeba-renderjoomla div
	}

	/**
	 * Renders a FOFForm for a Browse view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormBrowse(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		JHtml::_('behavior.multiselect');

		// Getting all header row elements
		$headerFields = $form->getHeaderset();

		// Start the form
		$html				 = '';
		$filter_order		 = $form->getView()->getLists()->order;
		$filter_order_Dir	 = $form->getView()->getLists()->order_Dir;
        $actionUrl           = FOFPlatform::getInstance()->isBackend() ? 'index.php' : JUri::root().'index.php';

		if (FOFPlatform::getInstance()->isFrontend() && ($input->getCmd('Itemid', 0) != 0))
		{
			$itemid = $input->getCmd('Itemid', 0);
			$uri = new JUri($actionUrl);

			if ($itemid)
			{
				$uri->setVar('Itemid', $itemid);
			}

			$actionUrl = JRoute::_($uri->toString());
		}

		$html .= '<form action="'.$actionUrl.'" method="post" name="adminForm" id="adminForm">' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="option" value="' . $input->getCmd('option') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="view" value="' . FOFInflector::pluralize($input->getCmd('view')) . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="task" value="" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="layout" value="' . $input->getCmd('layout', '') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="boxchecked" value="" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="hidemainmenu" value="" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="filter_order" value="' . $filter_order . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="filter_order_Dir" value="' . $filter_order_Dir . '" />' . PHP_EOL;

		$html .= "\t" . '<input type="hidden" name="' . JFactory::getSession()->getFormToken() . '" value="1" />' . PHP_EOL;

		// Start the table output
		$html .= "\t\t" . '<table class="adminlist" id="adminList">' . PHP_EOL;

		// Get form parameters
		$show_header		 = $form->getAttribute('show_header', 1);
		$show_filters		 = $form->getAttribute('show_filters', 1);
		$show_pagination	 = $form->getAttribute('show_pagination', 1);
		$norows_placeholder	 = $form->getAttribute('norows_placeholder', '');

		// Open the table header region if required
		if ($show_header || $show_filters)
		{
			$html .= "\t\t\t<thead>" . PHP_EOL;
		}

		// Pre-render the header and filter rows
		if ($show_header || $show_filters)
		{
			$header_html = '';
			$filter_html = '';

			foreach ($headerFields as $header)
			{
				// Make sure we have a header field. Under Joomla! 2.5 we cannot
				// render filter-only fields.
				$tmpHeader = $header->header;

				if (empty($tmpHeader))
				{
					continue;
				}

				$tdwidth = $header->tdwidth;

				if (!empty($tdwidth))
				{
					$tdwidth = 'width="' . $tdwidth . '"';
				}
				else
				{
					$tdwidth = '';
				}

				$header_html .= "\t\t\t\t\t<th $tdwidth>" . PHP_EOL;
				$header_html .= "\t\t\t\t\t\t" . $tmpHeader;
				$header_html .= "\t\t\t\t\t</th>" . PHP_EOL;

				$filter	 = $header->filter;
				$buttons = $header->buttons;
				$options = $header->options;

				$filter_html .= "\t\t\t\t\t<td>" . PHP_EOL;

				if (!empty($filter))
				{
					$filter_html .= "\t\t\t\t\t\t$filter" . PHP_EOL;

					if (!empty($buttons))
					{
						$filter_html .= "\t\t\t\t\t\t<nobr>$buttons</nobr>" . PHP_EOL;
					}
				}
				elseif (!empty($options))
				{
					$label		 = $header->label;
					$emptyOption = JHtml::_('select.option', '', '- ' . JText::_($label) . ' -');
					array_unshift($options, $emptyOption);
					$attribs	 = array(
						'onchange' => 'document.adminForm.submit();'
					);
					$filter		 = JHtml::_('select.genericlist', $options, $header->name, $attribs, 'value', 'text', $header->value, false, true);
					$filter_html .= "\t\t\t\t\t\t$filter" . PHP_EOL;
				}

				$filter_html .= "\t\t\t\t\t</td>" . PHP_EOL;
			}
		}

		// Render header if enabled
		if ($show_header)
		{
			$html .= "\t\t\t\t<tr>" . PHP_EOL;
			$html .= $header_html;
			$html .= "\t\t\t\t</tr>" . PHP_EOL;
		}

		// Render filter row if enabled
		if ($show_filters)
		{
			$html .= "\t\t\t\t<tr>";
			$html .= $filter_html;
			$html .= "\t\t\t\t</tr>";
		}

		// Close the table header region if required
		if ($show_header || $show_filters)
		{
			$html .= "\t\t\t</thead>" . PHP_EOL;
		}

		// Loop through rows and fields, or show placeholder for no rows
		$html .= "\t\t\t<tbody>" . PHP_EOL;
		$fields		 = $form->getFieldset('items');
		$num_columns = count($fields);
		$items		 = $form->getModel()->getItemList();

		if ($count = count($items))
		{
			$m = 1;

			foreach ($items as $i => $item)
			{
				$table_item = $form->getModel()->getTable();
				$table_item->reset();
				$table_item->bind($item);

				$form->bind($item);

				$m		 = 1 - $m;
				$class	 = 'row' . $m;

				$html .= "\t\t\t\t<tr class=\"$class\">" . PHP_EOL;

				$fields = $form->getFieldset('items');

				foreach ($fields as $field)
				{
					$field->rowid	 = $i;
					$field->item	 = $table_item;
					$labelClass = $field->labelClass ? $field->labelClass : $field->labelclass; // Joomla! 2.5/3.x use different case for the same name
					$class			 = $labelClass ? 'class ="' . $labelClass . '"' : '';
					$html .= "\t\t\t\t\t<td $class>" . $field->getRepeatable() . '</td>' . PHP_EOL;
				}

				$html .= "\t\t\t\t</tr>" . PHP_EOL;
			}
		}
		elseif ($norows_placeholder)
		{
			$html .= "\t\t\t\t<tr><td colspan=\"$num_columns\">";
			$html .= JText::_($norows_placeholder);
			$html .= "</td></tr>\n";
		}

		$html .= "\t\t\t</tbody>" . PHP_EOL;

		// Render the pagination bar, if enabled

		if ($show_pagination)
		{
			$pagination = $form->getModel()->getPagination();
			$html .= "\t\t\t<tfoot>" . PHP_EOL;
			$html .= "\t\t\t\t<tr><td colspan=\"$num_columns\">";

			if (($pagination->total > 0))
			{
				$html .= $pagination->getListFooter();
			}

			$html .= "</td></tr>\n";
			$html .= "\t\t\t</tfoot>" . PHP_EOL;
		}

		// End the table output
		$html .= "\t\t" . '</table>' . PHP_EOL;

		// End the form
		$html .= '</form>' . PHP_EOL;

		return $html;
	}

	/**
	 * Renders a FOFForm for a Read view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormRead(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		$html = $this->renderFormRaw($form, $model, $input, 'read');

		return $html;
	}

	/**
	 * Renders a FOFForm for an Edit view and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form  The form to render
	 * @param   FOFModel  $model  The model providing our data
	 * @param   FOFInput  $input  The input object
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormEdit(FOFForm &$form, FOFModel $model, FOFInput $input)
	{
		// Get the key for this model's table
		$key		 = $model->getTable()->getKeyName();
		$keyValue	 = $model->getId();

		JHTML::_('behavior.tooltip');

		$html = '';

		$validate	 = strtolower($form->getAttribute('validate'));
		$class		 = '';

		if (in_array($validate, array('true', 'yes', '1', 'on')))
		{
			JHtml::_('behavior.formvalidation');
			$class = 'form-validate ';
			$this->loadValidationScript($form);
		}

		// Check form enctype. Use enctype="multipart/form-data" to upload binary files in your form.
		$template_form_enctype = $form->getAttribute('enctype');

		if (!empty($template_form_enctype))
		{
			$enctype = ' enctype="' . $form->getAttribute('enctype') . '" ';
		}
		else
		{
			$enctype = '';
		}

		// Check form name. Use name="yourformname" to modify the name of your form.
		$formname = $form->getAttribute('name');

		if (empty($formname))
		{
			$formname = 'adminForm';
		}

		// Check form ID. Use id="yourformname" to modify the id of your form.
		$formid = $form->getAttribute('name');

		if (empty($formid))
		{
			$formid = 'adminForm';
		}

		// Check if we have a custom task
		$customTask = $form->getAttribute('customTask');

		if (empty($customTask))
		{
			$customTask = '';
		}

		// Get the form action URL
        $actionUrl = FOFPlatform::getInstance()->isBackend() ? 'index.php' : JUri::root().'index.php';

		if (FOFPlatform::getInstance()->isFrontend() && ($input->getCmd('Itemid', 0) != 0))
		{
			$itemid = $input->getCmd('Itemid', 0);
			$uri = new JUri($actionUrl);

			if ($itemid)
			{
				$uri->setVar('Itemid', $itemid);
			}

			$actionUrl = JRoute::_($uri->toString());
		}

		$html .= '<form action="'.$actionUrl.'" method="post" name="' . $formname .
			'" id="' . $formid . '"' . $enctype . ' class="' . $class .
			'">' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="option" value="' . $input->getCmd('option') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="view" value="' . $input->getCmd('view', 'edit') . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="task" value="' . $customTask . '" />' . PHP_EOL;
		$html .= "\t" . '<input type="hidden" name="' . $key . '" value="' . $keyValue . '" />' . PHP_EOL;

		$html .= "\t" . '<input type="hidden" name="' . JFactory::getSession()->getFormToken() . '" value="1" />' . PHP_EOL;

		$html .= $this->renderFormRaw($form, $model, $input, 'edit');
		$html .= '</form>';

		return $html;
	}

	/**
	 * Renders a raw FOFForm and returns the corresponding HTML
	 *
	 * @param   FOFForm   &$form     The form to render
	 * @param   FOFModel  $model     The model providing our data
	 * @param   FOFInput  $input     The input object
	 * @param   string    $formType  The form type e.g. 'edit' or 'read'
	 *
	 * @return  string    The HTML rendering of the form
	 */
	protected function renderFormRaw(FOFForm &$form, FOFModel $model, FOFInput $input, $formType)
	{
		$html = '';

		foreach ($form->getFieldsets() as $fieldset)
		{
			$html .= $this->renderFieldset($fieldset, $form, $model, $input, $formType, false);
		}

		return $html;
	}

	/**
	 * Renders a raw fieldset of a FOFForm and returns the corresponding HTML
	 *
	 * @param   stdClass  &$fieldset   The fieldset to render
	 * @param   FOFForm   &$form       The form to render
	 * @param   FOFModel  $model       The model providing our data
	 * @param   FOFInput  $input       The input object
	 * @param   string    $formType    The form type e.g. 'edit' or 'read'
	 * @param   boolean   $showHeader  Should I render the fieldset's header?
	 *
	 * @return  string    The HTML rendering of the fieldset
	 */
	protected function renderFieldset(stdClass &$fieldset, FOFForm &$form, FOFModel $model, FOFInput $input, $formType, $showHeader = true)
	{
		$html = '';

		$fields = $form->getFieldset($fieldset->name);

		if (isset($fieldset->class))
		{
			$class = 'class="' . $fieldset->class . '"';
		}
		else
		{
			$class = '';
		}

		$element = empty($fields) ? 'div' : 'fieldset';
		$html .= "\t" . '<' . $element . ' id="' . $fieldset->name . '" ' . $class . '>' . PHP_EOL;

		$isTabbedFieldset = $this->isTabFieldset($fieldset);

		if (isset($fieldset->label) && !empty($fieldset->label) && !$isTabbedFieldset)
		{
			$html .= "\t\t" . '<h3>' . JText::_($fieldset->label) . '</h3>' . PHP_EOL;
		}

		foreach ($fields as $field)
		{
			$groupClass	 = $form->getFieldAttribute($field->fieldname, 'groupclass', '', $field->group);

			// Auto-generate label and description if needed
			// Field label
			$title 		 = $form->getFieldAttribute($field->fieldname, 'label', '', $field->group);
			$emptylabel  = $form->getFieldAttribute($field->fieldname, 'emptylabel', false, $field->group);

			if (empty($title) && !$emptylabel)
			{
				$model->getName();
				$title = strtoupper($input->get('option') . '_' . $model->getName() . '_' . $field->id . '_LABEL');
			}

			// Field description
			$description = $form->getFieldAttribute($field->fieldname, 'description', '', $field->group);

			/**
			 * The following code is backwards incompatible. Most forms don't require a description in their form
			 * fields. Having to use emptydescription="1" on each one of them is an overkill. Removed.
			 */
			/*
			$emptydescription   = $form->getFieldAttribute($field->fieldname, 'emptydescription', false, $field->group);
			if (empty($description) && !$emptydescription)
			{
				$description = strtoupper($input->get('option') . '_' . $model->getName() . '_' . $field->id . '_DESC');
			}
			*/

			if ($formType == 'read')
			{
				$inputField = $field->static;
			}
			elseif ($formType == 'edit')
			{
				$inputField = $field->input;
			}

			if (empty($title))
			{
				$html .= "\t\t\t" . $inputField . PHP_EOL;

				if (!empty($description) && $formType == 'edit')
				{
					$html .= "\t\t\t\t" . '<span class="help-block">';
					$html .= JText::_($description) . '</span>' . PHP_EOL;
				}
			}
			else
			{
				$html .= "\t\t\t" . '<div class="fof-row ' . $groupClass . '">' . PHP_EOL;
				$html .= $this->renderFieldsetLabel($field, $form, $title);
				$html .= "\t\t\t\t" . $inputField . PHP_EOL;

				if (!empty($description))
				{
					$html .= "\t\t\t\t" . '<span class="help-block">';
					$html .= JText::_($description) . '</span>' . PHP_EOL;
				}

				$html .= "\t\t\t" . '</div>' . PHP_EOL;
			}
		}

		$element = empty($fields) ? 'div' : 'fieldset';
		$html .= "\t" . '</' . $element . '>' . PHP_EOL;

		return $html;
	}

	/**
	 * Renders a label for a fieldset.
	 *
	 * @param   object  	$field  	The field of the label to render
	 * @param   FOFForm   	&$form      The form to render
	 * @param 	string		$title		The title of the label
	 *
	 * @return 	string		The rendered label
	 */
	protected function renderFieldsetLabel($field, FOFForm &$form, $title)
	{
		$html = '';

		$labelClass	 = $field->labelClass ? $field->labelClass : $field->labelclass; // Joomla! 2.5/3.x use different case for the same name
		$required	 = $field->required;

		if ($required)
		{
			$labelClass .= ' required';
		}

		$tooltip = $form->getFieldAttribute($field->fieldname, 'tooltip', '', $field->group);

		if (!empty($tooltip))
		{
			JHtml::_('behavior.tooltip');

			$tooltipText = JText::_($title) . '::' . JText::_($tooltip);

			$labelClass .= ' hasTip';

			$html .= "\t\t\t\t" . '<label id="' . $field->id . '-lbl" class="' . $labelClass . '" for="' . $field->id . '" title="' . $tooltipText . '" rel="tooltip">';
		}
		else
		{
			$html .= "\t\t\t\t" . '<label class="' . $labelClass . '" for="' . $field->id . '">';
		}

		$html .= JText::_($title);

		if ($required)
		{
			$html .= '<span class="star">&nbsp;*</span>';
		}

		$html .= "\t\t\t\t" . '</label>' . PHP_EOL;

		return $html;
	}

	/**
	 * Loads the validation script for an edit form
	 *
	 * @param   FOFForm  &$form  The form we are rendering
	 *
	 * @return  void
	 */
	protected function loadValidationScript(FOFForm &$form)
	{
		$message = $form->getView()->escape(JText::_('JGLOBAL_VALIDATION_FORM_FAILED'));

		$js = <<<JS
Joomla.submitbutton = function(task)
{
	if (task == 'cancel' || document.formvalidator.isValid(document.id('adminForm')))
	{
		Joomla.submitform(task, document.getElementById('adminForm'));
	}
	else {
		alert('$message');
	}
};
JS;

		$document = FOFPlatform::getInstance()->getDocument();

		if ($document instanceof JDocument)
		{
			$document->addScriptDeclaration($js);
		}
	}

	/**
	 * Renders the submenu (link bar)
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderLinkbar($view, $task, $input, $config = array())
	{
		// On command line don't do anything

		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render a submenu unless we are in the the admin area
		$toolbar				 = FOFToolbar::getAnInstance($input->getCmd('option', 'com_foobar'), $config);
		$renderFrontendSubmenu	 = $toolbar->getRenderFrontendSubmenu();

		if (!FOFPlatform::getInstance()->isBackend() && !$renderFrontendSubmenu)
		{
			return;
		}

		$this->renderLinkbarItems($toolbar);
	}

	/**
	 * do the rendering job for the linkbar
	 *
	 * @param   FOFToolbar  $toolbar  A toolbar object
	 *
	 * @return  void
	 */
	protected function renderLinkbarItems($toolbar)
	{
		$links = $toolbar->getLinks();

		if (!empty($links))
		{
			foreach ($links as $link)
			{
				JSubMenuHelper::addEntry($link['name'], $link['link'], $link['active']);
			}
		}
	}

	/**
	 * Renders the toolbar buttons
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderButtons($view, $task, $input, $config = array())
	{
		// On command line don't do anything

		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		// Do not render buttons unless we are in the the frontend area and we are asked to do so
		$toolbar				 = FOFToolbar::getAnInstance($input->getCmd('option', 'com_foobar'), $config);
		$renderFrontendButtons	 = $toolbar->getRenderFrontendButtons();

		if (FOFPlatform::getInstance()->isBackend() || !$renderFrontendButtons)
		{
			return;
		}

		// Load main backend language, in order to display toolbar strings
		// (JTOOLBAR_BACK, JTOOLBAR_PUBLISH etc etc)
		FOFPlatform::getInstance()->loadTranslations('joomla');

		$title	 = JFactory::getApplication()->get('JComponentTitle');
		$bar	 = JToolbar::getInstance('toolbar');

		// Delete faux links, since if SEF is on, Joomla will follow the link instead of submitting the form
		$bar_content = str_replace('href="#"', '', $bar->render());

		echo '<div id="FOFHeaderHolder">', $bar_content, $title, '<div style="clear:both"></div>', '</div>';
	}
}
fof/render/joomla3.php000064400000011607152177723700010672 0ustar00<?php
/**
 * @package     FrameworkOnFramework
 * @subpackage  render
 * @copyright   Copyright (C) 2010-2016 Nicholas K. Dionysopoulos / Akeeba Ltd. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */
defined('FOF_INCLUDED') or die;

/**
 * Joomla! 3 view renderer class
 *
 * @package  FrameworkOnFramework
 * @since    2.0
 */
class FOFRenderJoomla3 extends FOFRenderStrapper
{
	/**
	 * Public constructor. Determines the priority of this class and if it should be enabled
	 */
	public function __construct()
	{
		$this->priority	 = 55;
		$this->enabled	 = version_compare(JVERSION, '3.0', 'ge');
	}

	/**
	 * Echoes any HTML to show before the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function preRender($view, $task, $input, $config = array())
	{
		$format	 = $input->getCmd('format', 'html');

		if (empty($format))
		{
			$format	 = 'html';
		}

		if ($format != 'html')
		{
			return;
		}

		$platform = FOFPlatform::getInstance();

		if ($platform->isCli())
		{
			return;
		}

		if (version_compare(JVERSION, '3.3.0', 'ge'))
		{
			JHtml::_('behavior.core');
		}
		else
		{
			JHtml::_('behavior.framework', true);
		}

		JHtml::_('jquery.framework');

		if ($platform->isBackend())
		{
			// Wrap output in various classes
			$version = new JVersion;
			$versionParts = explode('.', $version->RELEASE);
			$minorVersion = str_replace('.', '', $version->RELEASE);
			$majorVersion = array_shift($versionParts);

			$option = $input->getCmd('option', '');
			$view = $input->getCmd('view', '');
			$layout = $input->getCmd('layout', '');
			$task = $input->getCmd('task', '');

			$classes = ' class="' . implode(array(
				'joomla-version-' . $majorVersion,
				'joomla-version-' . $minorVersion,
				'admin',
				$option,
				'view-' . $view,
				'layout-' . $layout,
				'task-' . $task,
			), ' ') . '"';
		}
		else
		{
			$classes = '';
		}

		echo '<div id="akeeba-renderjoomla"' . $classes . ">\n";

		// Render the submenu and toolbar
		if ($input->getBool('render_toolbar', true))
		{
			$this->renderButtons($view, $task, $input, $config);
			$this->renderLinkbar($view, $task, $input, $config);
		}
	}

	/**
	 * Echoes any HTML to show after the view template
	 *
	 * @param   string    $view    The current view
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input array (request parameters)
	 * @param   array     $config  The view configuration array
	 *
	 * @return  void
	 */
	public function postRender($view, $task, $input, $config = array())
	{
		$format	 = $input->getCmd('format', 'html');

		if (empty($format))
		{
			$format	 = 'html';
		}

		if ($format != 'html')
		{
			return;
		}

		// Closing tag only if we're not in CLI
		if (FOFPlatform::getInstance()->isCli())
		{
			return;
		}

		echo "</div>\n";    // Closes akeeba-renderjoomla div
	}

	/**
	 * Renders the submenu (link bar)
	 *
	 * @param   string    $view    The active view name
	 * @param   string    $task    The current task
	 * @param   FOFInput  $input   The input object
	 * @param   array     $config  Extra configuration variables for the toolbar
	 *
	 * @return  void
	 */
	protected function renderLinkbar($view, $task, $input, $config = array())
	{
		$style = 'joomla';

		if (array_key_exists('linkbar_style', $config))
		{
			$style = $config['linkbar_style'];
		}

		switch ($style)
		{
			case 'joomla':
				$this->renderLinkbar_joomla($view, $task, $input);
				break;

			case 'classic':
			default:
				$this->renderLinkbar_classic($view, $task, $input);
				break;
		}
	}

	/**
	 * Renders a label for a fieldset.
	 *
	 * @param   object  	$field  	The field of the label to render
	 * @param   FOFForm   	&$form      The form to render
	 * @param 	string		$title		The title of the label
	 *
	 * @return 	string		The rendered label
	 */
	protected function renderFieldsetLabel($field, FOFForm &$form, $title)
	{
		$html = '';

		$labelClass	 = $field->labelClass ? $field->labelClass : $field->labelclass; // Joomla! 2.5/3.x use different case for the same name
		$required	 = $field->required;

		$tooltip = $form->getFieldAttribute($field->fieldname, 'tooltip', '', $field->group);

		if (!empty($tooltip))
		{
			JHtml::_('bootstrap.tooltip');

			$tooltipText = '<strong>' . JText::_($title) . '</strong><br />' . JText::_($tooltip);

			$html .= "\t\t\t\t" . '<label class="control-label hasTooltip ' . $labelClass . '" for="' . $field->id . '" title="' . $tooltipText . '" rel="tooltip">';
		}
		else
		{
			$html .= "\t\t\t\t" . '<label class="control-label ' . $labelClass . '" for="' . $field->id . '">';
		}

		$html .= JText::_($title);

		if ($required)
		{
			$html .= ' *';
		}

		$html .= '</label>' . PHP_EOL;

		return $html;
	}
}
index.html000064400000000037152177723700006554 0ustar00<!DOCTYPE html><title></title>
idna_convert/uctc.php000064400000025603152177723700010707 0ustar00<?php
/**
 * UCTC - The Unicode Transcoder
 *
 * Converts between various flavours of Unicode representations like UCS-4 or UTF-8
 * Supported schemes:
 * - UCS-4 Little Endian / Big Endian / Array (partially)
 * - UTF-16 Little Endian / Big Endian (not yet)
 * - UTF-8
 * - UTF-7
 * - UTF-7 IMAP (modified UTF-7)
 *
 * @package phlyMail Nahariya 4.0+ Default branch
 * @author Matthias Sommerfeld  <mso@phlyLabs.de>
 * @copyright 2003-2009 phlyLabs Berlin, http://phlylabs.de
 * @version 0.0.6 2009-05-10
 */
class uctc {
    private static $mechs = array('ucs4', /*'ucs4le', 'ucs4be', */'ucs4array', /*'utf16', 'utf16le', 'utf16be', */'utf8', 'utf7', 'utf7imap');
    private static $allow_overlong = false;
    private static $safe_mode;
    private static $safe_char;

    /**
     * The actual conversion routine
     *
     * @param mixed $data  The data to convert, usually a string, array when converting from UCS-4 array
     * @param string $from  Original encoding of the data
     * @param string $to  Target encoding of the data
     * @param bool $safe_mode  SafeMode tries to correct invalid codepoints
     * @return mixed  False on failure, String or array on success, depending on target encoding
     * @access public
     * @since 0.0.1
     */
    public static function convert($data, $from, $to, $safe_mode = false, $safe_char = 0xFFFC)
    {
        self::$safe_mode = ($safe_mode) ? true : false;
        self::$safe_char = ($safe_char) ? $safe_char : 0xFFFC;
        if (self::$safe_mode) self::$allow_overlong = true;
        if (!in_array($from, self::$mechs)) throw new Exception('Invalid input format specified');
        if (!in_array($to, self::$mechs)) throw new Exception('Invalid output format specified');
        if ($from != 'ucs4array') eval('$data = self::'.$from.'_ucs4array($data);');
        if ($to != 'ucs4array') eval('$data = self::ucs4array_'.$to.'($data);');
        return $data;
    }

    /**
     * This converts an UTF-8 encoded string to its UCS-4 representation
     *
     * @param string $input  The UTF-8 string to convert
     * @return array  Array of 32bit values representing each codepoint
     * @access private
     */
    private static function utf8_ucs4array($input)
    {
        $output = array();
        $out_len = 0;
        $inp_len = strlen($input);
        $mode = 'next';
        $test = 'none';
        for ($k = 0; $k < $inp_len; ++$k) {
            $v = ord($input{$k}); // Extract byte from input string

            if ($v < 128) { // We found an ASCII char - put into stirng as is
                $output[$out_len] = $v;
                ++$out_len;
                if ('add' == $mode) {
                    if (self::$safe_mode) {
                        $output[$out_len-2] = self::$safe_char;
                        $mode = 'next';
                    } else {
                        throw new Exception('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    }
                }
                continue;
            }
            if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
                $start_byte = $v;
                $mode = 'add';
                $test = 'range';
                if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
                    $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
                    $v = ($v - 192) << 6;
                } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
                    $next_byte = 1;
                    $v = ($v - 224) << 12;
                } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 2;
                    $v = ($v - 240) << 18;
                } elseif (self::$safe_mode) {
                    $mode = 'next';
                    $output[$out_len] = self::$safe_char;
                    ++$out_len;
                    continue;
                } else {
                    throw new Exception('This might be UTF-8, but I don\'t understand it at byte '.$k);
                }
                if ($inp_len-$k-$next_byte < 2) {
                    $output[$out_len] = self::$safe_char;
                    $mode = 'no';
                    continue;
                }

                if ('add' == $mode) {
                    $output[$out_len] = (int) $v;
                    ++$out_len;
                    continue;
                }
            }
            if ('add' == $mode) {
                if (!self::$allow_overlong && $test == 'range') {
                    $test = 'none';
                    if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
                        throw new Exception('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
                    }
                }
                if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
                    $v = ($v-128) << ($next_byte*6);
                    $output[($out_len-1)] += $v;
                    --$next_byte;
                } else {
                    if (self::$safe_mode) {
                        $output[$out_len-1] = ord(self::$safe_char);
                        $k--;
                        $mode = 'next';
                        continue;
                    } else {
                        throw new Exception('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    }
                }
                if ($next_byte < 0) {
                    $mode = 'next';
                }
            }
        } // for
        return $output;
    }

    /**
     * Convert UCS-4 string into UTF-8 string
     * See utf8_ucs4array() for details
     * @access   private
     */
    private static function ucs4array_utf8($input)
    {
        $output = '';
        foreach ($input as $v) {
            if ($v < 128) { // 7bit are transferred literally
                $output .= chr($v);
            } elseif ($v < (1 << 11)) { // 2 bytes
                $output .= chr(192+($v >> 6)).chr(128+($v & 63));
            } elseif ($v < (1 << 16)) { // 3 bytes
                $output .= chr(224+($v >> 12)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
            } elseif ($v < (1 << 21)) { // 4 bytes
                $output .= chr(240+($v >> 18)).chr(128+(($v >> 12) & 63)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
            } elseif (self::$safe_mode) {
                $output .= self::$safe_char;
            } else {
                throw new Exception('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k);
            }
        }
        return $output;
    }

    private static function utf7imap_ucs4array($input)
    {
        return self::utf7_ucs4array(str_replace(',', '/', $input), '&');
    }

    private static function utf7_ucs4array($input, $sc = '+')
    {
        $output  = array();
        $out_len = 0;
        $inp_len = strlen($input);
        $mode    = 'd';
        $b64     = '';

        for ($k = 0; $k < $inp_len; ++$k) {
            $c = $input{$k};
            if (0 == ord($c)) continue; // Ignore zero bytes
            if ('b' == $mode) {
                // Sequence got terminated
                if (!preg_match('![A-Za-z0-9/'.preg_quote($sc, '!').']!', $c)) {
                    if ('-' == $c) {
                        if ($b64 == '') {
                            $output[$out_len] = ord($sc);
                            $out_len++;
                            $mode = 'd';
                            continue;
                        }
                    }
                    $tmp = base64_decode($b64);
                    $tmp = substr($tmp, -1 * (strlen($tmp) % 2));
                    for ($i = 0; $i < strlen($tmp); $i++) {
                        if ($i % 2) {
                            $output[$out_len] += ord($tmp{$i});
                            $out_len++;
                        } else {
                            $output[$out_len] = ord($tmp{$i}) << 8;
                        }
                    }
                    $mode = 'd';
                    $b64 = '';
                    continue;
                } else {
                    $b64 .= $c;
                }
            }
            if ('d' == $mode) {
                if ($sc == $c) {
                    $mode = 'b';
                    continue;
                }
                $output[$out_len] = ord($c);
                $out_len++;
            }
        }
        return $output;
    }

    private static function ucs4array_utf7imap($input)
    {
        return str_replace('/', ',', self::ucs4array_utf7($input, '&'));
    }

    private static function ucs4array_utf7($input, $sc = '+')
    {
        $output = '';
        $mode = 'd';
        $b64 = '';
        while (true) {
            $v = (!empty($input)) ? array_shift($input) : false;
            $is_direct = (false !== $v) ? (0x20 <= $v && $v <= 0x7e && $v != ord($sc)) : true;
            if ($mode == 'b') {
                if ($is_direct) {
                    if ($b64 == chr(0).$sc) {
                        $output .= $sc.'-';
                        $b64 = '';
                    } elseif ($b64) {
                        $output .= $sc.str_replace('=', '', base64_encode($b64)).'-';
                        $b64 = '';
                    }
                    $mode = 'd';
                } elseif (false !== $v) {
                    $b64 .= chr(($v >> 8) & 255). chr($v & 255);
                }
            }
            if ($mode == 'd' && false !== $v) {
                if ($is_direct) {
                    $output .= chr($v);
                } else {
                    $b64 = chr(($v >> 8) & 255). chr($v & 255);
                    $mode = 'b';
                }
            }
            if (false === $v && $b64 == '') break;
        }
        return $output;
    }

    /**
     * Convert UCS-4 array into UCS-4 string (Little Endian at the moment)
     * @access   private
     */
    private static function ucs4array_ucs4($input)
    {
        $output = '';
        foreach ($input as $v) {
            $output .= chr(($v >> 24) & 255).chr(($v >> 16) & 255).chr(($v >> 8) & 255).chr($v & 255);
        }
        return $output;
    }

    /**
     * Convert UCS-4 string (LE in the moment) into UCS-4 garray
     * @access   private
     */
    private static function ucs4_ucs4array($input)
    {
        $output = array();

        $inp_len = strlen($input);
        // Input length must be dividable by 4
        if ($inp_len % 4) {
            throw new Exception('Input UCS4 string is broken');
        }
        // Empty input - return empty output
        if (!$inp_len) return $output;

        for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
            if (!($i % 4)) { // Increment output position every 4 input bytes
                $out_len++;
                $output[$out_len] = 0;
            }
            $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
        }
        return $output;
    }
}
?>idna_convert/ReadMe.txt000064400000016750152177723700011141 0ustar00*******************************************************************************
*                                                                             *
*                    IDNA Convert (idna_convert.class.php)                    *
*                                                                             *
* http://idnaconv.phlymail.de                     mailto:phlymail@phlylabs.de *
*******************************************************************************
* (c) 2004-2011 phlyLabs, Berlin                                              *
* This file is encoded in UTF-8                                               *
*******************************************************************************

Introduction
------------

The class idna_convert allows to convert internationalized domain names
(see RFC 3490, 3491, 3492 and 3454 for detials) as they can be used with various
registries worldwide to be translated between their original (localized) form
and their encoded form as it will be used in the DNS (Domain Name System).

The class provides two public methods, encode() and decode(), which do exactly
what you would expect them to do. You are allowed to use complete domain names,
simple strings and complete email addresses as well. That means, that you might
use any of the following notations:

- www.nörgler.com
- xn--nrgler-wxa
- xn--brse-5qa.xn--knrz-1ra.info

Errors, incorrectly encoded or invalid strings will lead to either a FALSE
response (when in strict mode) or to only partially converted strings.
You can query the occured error by calling the method get_last_error().

Unicode strings are expected to be either UTF-8 strings, UCS-4 strings or UCS-4
arrays. The default format is UTF-8. For setting different encodings, you can
call the method setParams() - please see the inline documentation for details.
ACE strings (the Punycode form) are always 7bit ASCII strings.

ATTENTION: As of version 0.6.0 this class is written in the OOP style of PHP5.
Since PHP4 is no longer actively maintained, you should switch to PHP5 as fast as
possible.
We expect to see no compatibility issues with the upcoming PHP6, too.

ATTENTION: BC break! As of version 0.6.4 the class per default allows the German
ligature ß to be encoded as the DeNIC, the registry for .DE allows domains
containing ß.
In older builds "ß" was mapped to "ss". Should you still need this behaviour,
see example 5 below.

ATTENTION: As of version 0.8.0 the class fully supports IDNA 2008. Thus the 
aforementioned parameter is deprecated and replaced by a parameter to switch
between the standards. See the updated example 5 below.

Files
-----
idna_convert.class.php         - The actual class
example.php                    - An example web page for converting
transcode_wrapper.php          - Convert various encodings, see below
uctc.php                       - phlyLabs' Unicode Transcoder, see below
ReadMe.txt                     - This file
LICENCE                        - The LGPL licence file

The class is contained in idna_convert.class.php.


Examples
--------
1. Say we wish to encode the domain name nörgler.com:

// Include the class
require_once('idna_convert.class.php');
// Instantiate it
$IDN = new idna_convert();
// The input string, if input is not UTF-8 or UCS-4, it must be converted before
$input = utf8_encode('nörgler.com');
// Encode it to its punycode presentation
$output = $IDN->encode($input);
// Output, what we got now
echo $output; // This will read: xn--nrgler-wxa.com


2. We received an email from a punycoded domain and are willing to learn, how
   the domain name reads originally

// Include the class
require_once('idna_convert.class.php');
// Instantiate it
$IDN = new idna_convert();
// The input string
$input = 'andre@xn--brse-5qa.xn--knrz-1ra.info';
// Encode it to its punycode presentation
$output = $IDN->decode($input);
// Output, what we got now, if output should be in a format different to UTF-8
// or UCS-4, you will have to convert it before outputting it
echo utf8_decode($output); // This will read: andre@börse.knörz.info


3. The input is read from a UCS-4 coded file and encoded line by line. By
   appending the optional second parameter we tell enode() about the input
   format to be used

// Include the class
require_once('idna_convert.class.php');
// Instantiate it
$IDN = new dinca_convert();
// Iterate through the input file line by line
foreach (file('ucs4-domains.txt') as $line) {
    echo $IDN->encode(trim($line), 'ucs4_string');
    echo "\n";
}


4. We wish to convert a whole URI into the IDNA form, but leave the path or
   query string component of it alone. Just using encode() would lead to mangled
   paths or query strings. Here the public method encode_uri() comes into play:

// Include the class
require_once('idna_convert.class.php');
// Instantiate it
$IDN = new idna_convert();
// The input string, a whole URI in UTF-8 (!)
$input = 'http://nörgler:secret@nörgler.com/my_päth_is_not_ÄSCII/');
// Encode it to its punycode presentation
$output = $IDN->encode_uri($input);
// Output, what we got now
echo $output; // http://nörgler:secret@xn--nrgler-wxa.com/my_päth_is_not_ÄSCII/


5. To support IDNA 2008, the class needs to be invoked with an additional
   parameter. This can also be achieved on an instance.

// Include the class
require_once('idna_convert.class.php');
// Instantiate it
$IDN = new idna_convert(array('idn_version' => 2008));
// Sth. containing the German letter ß
$input = 'meine-straße.de');
// Encode it to its punycode presentation
$output = $IDN->encode_uri($input);
// Output, what we got now
echo $output; // xn--meine-strae-46a.de
// Switch back to old IDNA 2003, the original standard
$IDN->set_parameter('idn_version', 2003);
// Sth. containing the German letter ß
$input = 'meine-straße.de');
// Encode it to its punycode presentation
$output = $IDN->encode_uri($input);
// Output, what we got now
echo $output; // meine-strasse.de


Transcode wrapper
-----------------
In case you have strings in different encoding than ISO-8859-1 and UTF-8 you might need to
translate these strings to UTF-8 before feeding the IDNA converter with it.
PHP's built in functions utf8_encode() and utf8_decode() can only deal with ISO-8859-1.
Use the file transcode_wrapper.php for the conversion. It requires either iconv, libiconv
or mbstring installed together with one of the relevant PHP extensions.
The functions you will find useful are
encode_utf8() as a replacement for utf8_encode() and
decode_utf8() as a replacement for utf8_decode().

Example usage:
<?php
require_once('idna_convert.class.php');
require_once('transcode_wrapper.php');
$mystring = '<something in e.g. ISO-8859-15';
$mystring = encode_utf8($mystring, 'ISO-8859-15');
echo $IDN->encode($mystring);
?>


UCTC - Unicode Transcoder
-------------------------
Another class you might find useful when dealing with one or more of the Unicode encoding
flavours. The class is static, it requires PHP5. It can transcode into each other:
- UCS-4 string / array
- UTF-8
- UTF-7
- UTF-7 IMAP (modified UTF-7)
All encodings expect / return a string in the given format, with one major exception:
UCS-4 array is jsut an array, where each value represents one codepoint in the string, i.e.
every value is a 32bit integer value.

Example usage:
<?php
require_once('uctc.php');
$mystring = 'nörgler.com';
echo uctc::convert($mystring, 'utf8', 'utf7imap');
?>


Contact us
----------
In case of errors, bugs, questions, wishes, please don't hesitate to contact us
under the email address above.

The team of phlyLabs
http://phlylabs.de
mailto:phlymail@phlylabs.deidna_convert/idna_convert.class.php000064400000271162152177723700013533 0ustar00<?php
// {{{ license

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
//
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU Lesser General Public License as       |
// | published by the Free Software Foundation; either version 2.1 of the |
// | License, or (at your option) any later version.                      |
// |                                                                      |
// | This library is distributed in the hope that it will be useful, but  |
// | WITHOUT ANY WARRANTY; without even the implied warranty of           |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
// | Lesser General Public License for more details.                      |
// |                                                                      |
// | You should have received a copy of the GNU Lesser General Public     |
// | License along with this library; if not, write to the Free Software  |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
// | USA.                                                                 |
// +----------------------------------------------------------------------+
//

// }}}

/**
 * Encode/decode Internationalized Domain Names.
 *
 * The class allows to convert internationalized domain names
 * (see RFC 3490 for details) as they can be used with various registries worldwide
 * to be translated between their original (localized) form and their encoded form
 * as it will be used in the DNS (Domain Name System).
 *
 * The class provides two public methods, encode() and decode(), which do exactly
 * what you would expect them to do. You are allowed to use complete domain names,
 * simple strings and complete email addresses as well. That means, that you might
 * use any of the following notations:
 *
 * - www.nörgler.com
 * - xn--nrgler-wxa
 * - xn--brse-5qa.xn--knrz-1ra.info
 *
 * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4 array.
 * Unicode output is available in the same formats.
 * You can select your preferred format via {@link set_paramter()}.
 *
 * ACE input and output is always expected to be ASCII.
 *
 * @author  Matthias Sommerfeld <mso@phlylabs.de>
 * @copyright 2004-2011 phlyLabs Berlin, http://phlylabs.de
 * @version 0.8.0 2011-03-11
 */
class idna_convert
{
    // NP See below

    // Internal settings, do not mess with them
    protected $_punycode_prefix = 'xn--';
    protected $_invalid_ucs = 0x80000000;
    protected $_max_ucs = 0x10FFFF;
    protected $_base = 36;
    protected $_tmin = 1;
    protected $_tmax = 26;
    protected $_skew = 38;
    protected $_damp = 700;
    protected $_initial_bias = 72;
    protected $_initial_n = 0x80;
    protected $_sbase = 0xAC00;
    protected $_lbase = 0x1100;
    protected $_vbase = 0x1161;
    protected $_tbase = 0x11A7;
    protected $_lcount = 19;
    protected $_vcount = 21;
    protected $_tcount = 28;
    protected $_ncount = 588;   // _vcount * _tcount
    protected $_scount = 11172; // _lcount * _tcount * _vcount
    protected $_error = false;

    protected static $_mb_string_overload = null;

    // See {@link set_paramter()} for details of how to change the following
    // settings from within your script / application
    protected $_api_encoding = 'utf8';   // Default input charset is UTF-8
    protected $_allow_overlong = false;  // Overlong UTF-8 encodings are forbidden
    protected $_strict_mode = false;     // Behave strict or not
    protected $_idn_version = 2003;      // Can be either 2003 (old, default) or 2008

    /**
     * the constructor
     *
     * @param array $options
     * @return boolean
     * @since 0.5.2
     */
    public function __construct($options = false)
    {
        $this->slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount;
        // If parameters are given, pass these to the respective method
        if (is_array($options)) {
            $this->set_parameter($options);
        }

        // populate mbstring overloading cache if not set
        if (self::$_mb_string_overload === null) {
            self::$_mb_string_overload = (extension_loaded('mbstring')
                && (ini_get('mbstring.func_overload') & 0x02) === 0x02);
        }
    }

    /**
     * Sets a new option value. Available options and values:
     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
     *             to allow this, set this parameter to true, else to false;
     *             default is false.]
     * [strict - true: strict mode, good for registration purposes - Causes errors
     *           on failures; false: loose mode, ideal for "wildlife" applications
     *           by silently ignoring errors and returning the original input instead
     *
     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
     * @param    string    Value to use (if parameter 1 is a string)
     * @return   boolean  true on success, false otherwise
     */
    public function set_parameter($option, $value = false)
    {
        if (!is_array($option)) {
            $option = array($option => $value);
        }
        foreach ($option as $k => $v) {
            switch ($k) {
            case 'encoding':
                switch ($v) {
                case 'utf8':
                case 'ucs4_string':
                case 'ucs4_array':
                    $this->_api_encoding = $v;
                    break;
                default:
                    $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
                    return false;
                }
                break;
            case 'overlong':
                $this->_allow_overlong = ($v) ? true : false;
                break;
            case 'strict':
                $this->_strict_mode = ($v) ? true : false;
                break;
            case 'idn_version':
                if (in_array($v, array('2003', '2008'))) {
                    $this->_idn_version = $v;
                } else {
                    $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
                }
                break;
            case 'encode_german_sz': // Deprecated
                if (!$v) {
                    self::$NP['replacemaps'][0xDF] = array(0x73, 0x73);
                } else {
                    unset(self::$NP['replacemaps'][0xDF]);
                }
                break;
            default:
                $this->_error('Set Parameter: Unknown option '.$k);
                return false;
            }
        }
        return true;
    }

    /**
     * Decode a given ACE domain name
     * @param    string   Domain name (ACE string)
     * [@param    string   Desired output encoding, see {@link set_parameter}]
     * @return   string   Decoded Domain name (UTF-8 or UCS-4)
     */
    public function decode($input, $one_time_encoding = false)
    {
        // Optionally set
        if ($one_time_encoding) {
            switch ($one_time_encoding) {
            case 'utf8':
            case 'ucs4_string':
            case 'ucs4_array':
                break;
            default:
                $this->_error('Unknown encoding '.$one_time_encoding);
                return false;
            }
        }
        // Make sure to drop any newline characters around
        $input = trim($input);

        // Negotiate input and try to determine, whether it is a plain string,
        // an email address or something like a complete URL
        if (strpos($input, '@')) { // Maybe it is an email address
            // No no in strict mode
            if ($this->_strict_mode) {
                $this->_error('Only simple domain name parts can be handled in strict mode');
                return false;
            }
            list ($email_pref, $input) = explode('@', $input, 2);
            $arr = explode('.', $input);
            foreach ($arr as $k => $v) {
                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
            }
            $input = join('.', $arr);
            $arr = explode('.', $email_pref);
            foreach ($arr as $k => $v) {
                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
            }
            $email_pref = join('.', $arr);
            $return = $email_pref . '@' . $input;
        } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters)
            // No no in strict mode
            if ($this->_strict_mode) {
                $this->_error('Only simple domain name parts can be handled in strict mode');
                return false;
            }
            $parsed = parse_url($input);
            if (isset($parsed['host'])) {
                $arr = explode('.', $parsed['host']);
                foreach ($arr as $k => $v) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
                $parsed['host'] = join('.', $arr);
                $return =
                        (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
                        .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
                        .$parsed['host']
                        .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
                        .(empty($parsed['path']) ? '' : $parsed['path'])
                        .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
                        .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
            } else { // parse_url seems to have failed, try without it
                $arr = explode('.', $input);
                foreach ($arr as $k => $v) {
                    $conv = $this->_decode($v);
                    $arr[$k] = ($conv) ? $conv : $v;
                }
                $return = join('.', $arr);
            }
        } else { // Otherwise we consider it being a pure domain name string
            $return = $this->_decode($input);
            if (!$return) $return = $input;
        }
        // The output is UTF-8 by default, other output formats need conversion here
        // If one time encoding is given, use this, else the objects property
        switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
        case 'utf8':
            return $return;
            break;
        case 'ucs4_string':
           return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return));
           break;
        case 'ucs4_array':
            return $this->_utf8_to_ucs4($return);
            break;
        default:
            $this->_error('Unsupported output format');
            return false;
        }
    }

    /**
     * Encode a given UTF-8 domain name
     * @param    string   Domain name (UTF-8 or UCS-4)
     * [@param    string   Desired input encoding, see {@link set_parameter}]
     * @return   string   Encoded Domain name (ACE string)
     */
    public function encode($decoded, $one_time_encoding = false)
    {
        // Forcing conversion of input to UCS4 array
        // If one time encoding is given, use this, else the objects property
        switch ($one_time_encoding ? $one_time_encoding : $this->_api_encoding) {
        case 'utf8':
            $decoded = $this->_utf8_to_ucs4($decoded);
            break;
        case 'ucs4_string':
           $decoded = $this->_ucs4_string_to_ucs4($decoded);
        case 'ucs4_array':
           break;
        default:
            $this->_error('Unsupported input format: '.($one_time_encoding ? $one_time_encoding : $this->_api_encoding));
            return false;
        }

        // No input, no output, what else did you expect?
        if (empty($decoded)) return '';

        // Anchors for iteration
        $last_begin = 0;
        // Output string
        $output = '';
        foreach ($decoded as $k => $v) {
            // Make sure to use just the plain dot
            switch($v) {
            case 0x3002:
            case 0xFF0E:
            case 0xFF61:
                $decoded[$k] = 0x2E;
                // Right, no break here, the above are converted to dots anyway
            // Stumbling across an anchoring character
            case 0x2E:
            case 0x2F:
            case 0x3A:
            case 0x3F:
            case 0x40:
                // Neither email addresses nor URLs allowed in strict mode
                if ($this->_strict_mode) {
                   $this->_error('Neither email addresses nor URLs are allowed in strict mode.');
                   return false;
                } else {
                    // Skip first char
                    if ($k) {
                        $encoded = '';
                        $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin)));
                        if ($encoded) {
                            $output .= $encoded;
                        } else {
                            $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin)));
                        }
                        $output .= chr($decoded[$k]);
                    }
                    $last_begin = $k + 1;
                }
            }
        }
        // Catch the rest of the string
        if ($last_begin) {
            $inp_len = sizeof($decoded);
            $encoded = '';
            $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
            if ($encoded) {
                $output .= $encoded;
            } else {
                $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
            }
            return $output;
        } else {
            if ($output = $this->_encode($decoded)) {
                return $output;
            } else {
                return $this->_ucs4_to_utf8($decoded);
            }
        }
    }

    /**
     * Removes a weakness of encode(), which cannot properly handle URIs but instead encodes their
     * path or query components, too.
     * @param string  $uri  Expects the URI as a UTF-8 (or ASCII) string
     * @return  string  The URI encoded to Punycode, everything but the host component is left alone
     * @since 0.6.4
     */
    public function encode_uri($uri)
    {
        $parsed = parse_url($uri);
        if (!isset($parsed['host'])) {
            $this->_error('The given string does not look like a URI');
            return false;
        }
        $arr = explode('.', $parsed['host']);
        foreach ($arr as $k => $v) {
            $conv = $this->encode($v, 'utf8');
            if ($conv) $arr[$k] = $conv;
        }
        $parsed['host'] = join('.', $arr);
        $return =
                (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
                .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
                .$parsed['host']
                .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
                .(empty($parsed['path']) ? '' : $parsed['path'])
                .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
                .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
        return $return;
    }

    /**
     * Use this method to get the last error ocurred
     * @param    void
     * @return   string   The last error, that occured
     */
    public function get_last_error()
    {
        return $this->_error;
    }

    /**
     * The actual decoding algorithm
     * @param string
     * @return mixed
     */
    protected function _decode($encoded)
    {
        $decoded = array();
        // find the Punycode prefix
        if (!preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $encoded)) {
            $this->_error('This is not a punycode string');
            return false;
        }
        $encode_test = preg_replace('!^'.preg_quote($this->_punycode_prefix, '!').'!', '', $encoded);
        // If nothing left after removing the prefix, it is hopeless
        if (!$encode_test) {
            $this->_error('The given encoded string was empty');
            return false;
        }
        // Find last occurence of the delimiter
        $delim_pos = strrpos($encoded, '-');
        if ($delim_pos > self::byteLength($this->_punycode_prefix)) {
            for ($k = self::byteLength($this->_punycode_prefix); $k < $delim_pos; ++$k) {
                $decoded[] = ord($encoded{$k});
            }
        }
        $deco_len = count($decoded);
        $enco_len = self::byteLength($encoded);

        // Wandering through the strings; init
        $is_first = true;
        $bias = $this->_initial_bias;
        $idx = 0;
        $char = $this->_initial_n;

        for ($enco_idx = ($delim_pos) ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
            for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) {
                $digit = $this->_decode_digit($encoded{$enco_idx++});
                $idx += $digit * $w;
                $t = ($k <= $bias) ? $this->_tmin :
                        (($k >= $bias + $this->_tmax) ? $this->_tmax : ($k - $bias));
                if ($digit < $t) break;
                $w = (int) ($w * ($this->_base - $t));
            }
            $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first);
            $is_first = false;
            $char += (int) ($idx / ($deco_len + 1));
            $idx %= ($deco_len + 1);
            if ($deco_len > 0) {
                // Make room for the decoded char
                for ($i = $deco_len; $i > $idx; $i--) $decoded[$i] = $decoded[($i - 1)];
            }
            $decoded[$idx++] = $char;
        }
        return $this->_ucs4_to_utf8($decoded);
    }

    /**
     * The actual encoding algorithm
     * @param  string
     * @return mixed
     */
    protected function _encode($decoded)
    {
        // We cannot encode a domain name containing the Punycode prefix
        $extract = self::byteLength($this->_punycode_prefix);
        $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix);
        $check_deco = array_slice($decoded, 0, $extract);

        if ($check_pref == $check_deco) {
            $this->_error('This is already a punycode string');
            return false;
        }
        // We will not try to encode strings consisting of basic code points only
        $encodable = false;
        foreach ($decoded as $k => $v) {
            if ($v > 0x7a) {
                $encodable = true;
                break;
            }
        }
        if (!$encodable) {
            $this->_error('The given string does not contain encodable chars');
            return false;
        }
        // Do NAMEPREP
        $decoded = $this->_nameprep($decoded);
        if (!$decoded || !is_array($decoded)) return false; // NAMEPREP failed
        $deco_len  = count($decoded);
        if (!$deco_len) return false; // Empty array
        $codecount = 0; // How many chars have been consumed
        $encoded = '';
        // Copy all basic code points to output
        for ($i = 0; $i < $deco_len; ++$i) {
            $test = $decoded[$i];
            // Will match [-0-9a-zA-Z]
            if ((0x2F < $test && $test < 0x40) || (0x40 < $test && $test < 0x5B)
                    || (0x60 < $test && $test <= 0x7B) || (0x2D == $test)) {
                $encoded .= chr($decoded[$i]);
                $codecount++;
            }
        }
        if ($codecount == $deco_len) return $encoded; // All codepoints were basic ones

        // Start with the prefix; copy it to output
        $encoded = $this->_punycode_prefix.$encoded;
        // If we have basic code points in output, add an hyphen to the end
        if ($codecount) $encoded .= '-';
        // Now find and encode all non-basic code points
        $is_first = true;
        $cur_code = $this->_initial_n;
        $bias = $this->_initial_bias;
        $delta = 0;
        while ($codecount < $deco_len) {
            // Find the smallest code point >= the current code point and
            // remember the last ouccrence of it in the input
            for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) {
                if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) {
                    $next_code = $decoded[$i];
                }
            }
            $delta += ($next_code - $cur_code) * ($codecount + 1);
            $cur_code = $next_code;

            // Scan input again and encode all characters whose code point is $cur_code
            for ($i = 0; $i < $deco_len; $i++) {
                if ($decoded[$i] < $cur_code) {
                    $delta++;
                } elseif ($decoded[$i] == $cur_code) {
                    for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) {
                        $t = ($k <= $bias) ? $this->_tmin :
                                (($k >= $bias + $this->_tmax) ? $this->_tmax : $k - $bias);
                        if ($q < $t) break;
                        $encoded .= $this->_encode_digit(intval($t + (($q - $t) % ($this->_base - $t)))); //v0.4.5 Changed from ceil() to intval()
                        $q = (int) (($q - $t) / ($this->_base - $t));
                    }
                    $encoded .= $this->_encode_digit($q);
                    $bias = $this->_adapt($delta, $codecount+1, $is_first);
                    $codecount++;
                    $delta = 0;
                    $is_first = false;
                }
            }
            $delta++;
            $cur_code++;
        }
        return $encoded;
    }

    /**
     * Adapt the bias according to the current code point and position
     * @param int $delta
     * @param int $npoints
     * @param int $is_first
     * @return int
     */
    protected function _adapt($delta, $npoints, $is_first)
    {
        $delta = intval($is_first ? ($delta / $this->_damp) : ($delta / 2));
        $delta += intval($delta / $npoints);
        for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) {
            $delta = intval($delta / ($this->_base - $this->_tmin));
        }
        return intval($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew));
    }

    /**
     * Encoding a certain digit
     * @param    int $d
     * @return string
     */
    protected function _encode_digit($d)
    {
        return chr($d + 22 + 75 * ($d < 26));
    }

    /**
     * Decode a certain digit
     * @param    int $cp
     * @return int
     */
    protected function _decode_digit($cp)
    {
        $cp = ord($cp);
        return ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $this->_base));
    }

    /**
     * Internal error handling method
     * @param  string $error
     */
    protected function _error($error = '')
    {
        $this->_error = $error;
    }

    /**
     * Do Nameprep according to RFC3491 and RFC3454
     * @param    array    Unicode Characters
     * @return   string   Unicode Characters, Nameprep'd
     */
    protected function _nameprep($input)
    {
        $output = array();
        $error = false;
        //
        // Mapping
        // Walking through the input array, performing the required steps on each of
        // the input chars and putting the result into the output array
        // While mapping required chars we apply the cannonical ordering
        foreach ($input as $v) {
            // Map to nothing == skip that code point
            if (in_array($v, self::$NP['map_nothing'])) continue;
            // Try to find prohibited input
            if (in_array($v, self::$NP['prohibit']) || in_array($v, self::$NP['general_prohibited'])) {
                $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
                return false;
            }
            foreach (self::$NP['prohibit_ranges'] as $range) {
                if ($range[0] <= $v && $v <= $range[1]) {
                    $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
                    return false;
                }
            }

            if (0xAC00 <= $v && $v <= 0xD7AF) {
                // Hangul syllable decomposition
                foreach ($this->_hangul_decompose($v) as $out) {
                    $output[] = (int) $out;
                }
            } elseif (($this->_idn_version == '2003') && isset(self::$NP['replacemaps'][$v])) {
                // There's a decomposition mapping for that code point
                // Decompositions only in version 2003 (original) of IDNA
                foreach ($this->_apply_cannonical_ordering(self::$NP['replacemaps'][$v]) as $out) {
                    $output[] = (int) $out;
                }
            } else {
                $output[] = (int) $v;
            }
        }
        // Before applying any Combining, try to rearrange any Hangul syllables
        $output = $this->_hangul_compose($output);
        //
        // Combine code points
        //
        $last_class = 0;
        $last_starter = 0;
        $out_len = count($output);
        for ($i = 0; $i < $out_len; ++$i) {
            $class = $this->_get_combining_class($output[$i]);
            if ((!$last_class || $last_class > $class) && $class) {
                // Try to match
                $seq_len = $i - $last_starter;
                $out = $this->_combine(array_slice($output, $last_starter, $seq_len));
                // On match: Replace the last starter with the composed character and remove
                // the now redundant non-starter(s)
                if ($out) {
                    $output[$last_starter] = $out;
                    if (count($out) != $seq_len) {
                        for ($j = $i+1; $j < $out_len; ++$j) $output[$j-1] = $output[$j];
                        unset($output[$out_len]);
                    }
                    // Rewind the for loop by one, since there can be more possible compositions
                    $i--;
                    $out_len--;
                    $last_class = ($i == $last_starter) ? 0 : $this->_get_combining_class($output[$i-1]);
                    continue;
                }
            }
            // The current class is 0
            if (!$class) $last_starter = $i;
            $last_class = $class;
        }
        return $output;
    }

    /**
     * Decomposes a Hangul syllable
     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
     * @param    integer  32bit UCS4 code point
     * @return   array    Either Hangul Syllable decomposed or original 32bit value as one value array
     */
    protected function _hangul_decompose($char)
    {
        $sindex = (int) $char - $this->_sbase;
        if ($sindex < 0 || $sindex >= $this->_scount) return array($char);
        $result = array();
        $result[] = (int) $this->_lbase + $sindex / $this->_ncount;
        $result[] = (int) $this->_vbase + ($sindex % $this->_ncount) / $this->_tcount;
        $T = intval($this->_tbase + $sindex % $this->_tcount);
        if ($T != $this->_tbase) $result[] = $T;
        return $result;
    }
    /**
     * Ccomposes a Hangul syllable
     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
     * @param    array    Decomposed UCS4 sequence
     * @return   array    UCS4 sequence with syllables composed
     */
    protected function _hangul_compose($input)
    {
        $inp_len = count($input);
        if (!$inp_len) return array();
        $result = array();
        $last = (int) $input[0];
        $result[] = $last; // copy first char from input to output

        for ($i = 1; $i < $inp_len; ++$i) {
            $char = (int) $input[$i];
            $sindex = $last - $this->_sbase;
            $lindex = $last - $this->_lbase;
            $vindex = $char - $this->_vbase;
            $tindex = $char - $this->_tbase;
            // Find out, whether two current characters are LV and T
            if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount == 0)
                    && 0 <= $tindex && $tindex <= $this->_tcount) {
                // create syllable of form LVT
                $last += $tindex;
                $result[(count($result) - 1)] = $last; // reset last
                continue; // discard char
            }
            // Find out, whether two current characters form L and V
            if (0 <= $lindex && $lindex < $this->_lcount && 0 <= $vindex && $vindex < $this->_vcount) {
                // create syllable of form LV
                $last = (int) $this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount;
                $result[(count($result) - 1)] = $last; // reset last
                continue; // discard char
            }
            // if neither case was true, just add the character
            $last = $char;
            $result[] = $char;
        }
        return $result;
    }

    /**
     * Returns the combining class of a certain wide char
     * @param    integer    Wide char to check (32bit integer)
     * @return   integer    Combining class if found, else 0
     */
    protected function _get_combining_class($char)
    {
        return isset(self::$NP['norm_combcls'][$char]) ? self::$NP['norm_combcls'][$char] : 0;
    }

    /**
     * Applies the cannonical ordering of a decomposed UCS4 sequence
     * @param    array      Decomposed UCS4 sequence
     * @return   array      Ordered USC4 sequence
     */
    protected function _apply_cannonical_ordering($input)
    {
        $swap = true;
        $size = count($input);
        while ($swap) {
            $swap = false;
            $last = $this->_get_combining_class(intval($input[0]));
            for ($i = 0; $i < $size-1; ++$i) {
                $next = $this->_get_combining_class(intval($input[$i+1]));
                if ($next != 0 && $last > $next) {
                    // Move item leftward until it fits
                    for ($j = $i + 1; $j > 0; --$j) {
                        if ($this->_get_combining_class(intval($input[$j-1])) <= $next) break;
                        $t = intval($input[$j]);
                        $input[$j] = intval($input[$j-1]);
                        $input[$j-1] = $t;
                        $swap = true;
                    }
                    // Reentering the loop looking at the old character again
                    $next = $last;
                }
                $last = $next;
            }
        }
        return $input;
    }

    /**
     * Do composition of a sequence of starter and non-starter
     * @param    array      UCS4 Decomposed sequence
     * @return   array      Ordered USC4 sequence
     */
    protected function _combine($input)
    {
        $inp_len = count($input);
        foreach (self::$NP['replacemaps'] as $np_src => $np_target) {
            if ($np_target[0] != $input[0]) continue;
            if (count($np_target) != $inp_len) continue;
            $hit = false;
            foreach ($input as $k2 => $v2) {
                if ($v2 == $np_target[$k2]) {
                    $hit = true;
                } else {
                    $hit = false;
                    break;
                }
            }
            if ($hit) return $np_src;
        }
        return false;
    }

    /**
     * This converts an UTF-8 encoded string to its UCS-4 representation
     * By talking about UCS-4 "strings" we mean arrays of 32bit integers representing
     * each of the "chars". This is due to PHP not being able to handle strings with
     * bit depth different from 8. This apllies to the reverse method _ucs4_to_utf8(), too.
     * The following UTF-8 encodings are supported:
     * bytes bits  representation
     * 1        7  0xxxxxxx
     * 2       11  110xxxxx 10xxxxxx
     * 3       16  1110xxxx 10xxxxxx 10xxxxxx
     * 4       21  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
     * 5       26  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
     * 6       31  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
     * Each x represents a bit that can be used to store character data.
     * The five and six byte sequences are part of Annex D of ISO/IEC 10646-1:2000
     * @param string $input
     * @return string
     */
    protected function _utf8_to_ucs4($input)
    {
        $output = array();
        $out_len = 0;
        $inp_len = self::byteLength($input);
        $mode = 'next';
        $test = 'none';
        for ($k = 0; $k < $inp_len; ++$k) {
            $v = ord($input{$k}); // Extract byte from input string
            if ($v < 128) { // We found an ASCII char - put into stirng as is
                $output[$out_len] = $v;
                ++$out_len;
                if ('add' == $mode) {
                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    return false;
                }
                continue;
            }
            if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
                $start_byte = $v;
                $mode = 'add';
                $test = 'range';
                if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
                    $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
                    $v = ($v - 192) << 6;
                } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
                    $next_byte = 1;
                    $v = ($v - 224) << 12;
                } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 2;
                    $v = ($v - 240) << 18;
                } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 3;
                    $v = ($v - 248) << 24;
                } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 4;
                    $v = ($v - 252) << 30;
                } else {
                    $this->_error('This might be UTF-8, but I don\'t understand it at byte '.$k);
                    return false;
                }
                if ('add' == $mode) {
                    $output[$out_len] = (int) $v;
                    ++$out_len;
                    continue;
                }
            }
            if ('add' == $mode) {
                if (!$this->_allow_overlong && $test == 'range') {
                    $test = 'none';
                    if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
                        $this->_error('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
                        return false;
                    }
                }
                if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
                    $v = ($v - 128) << ($next_byte * 6);
                    $output[($out_len - 1)] += $v;
                    --$next_byte;
                } else {
                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    return false;
                }
                if ($next_byte < 0) {
                    $mode = 'next';
                }
            }
        } // for
        return $output;
    }

    /**
     * Convert UCS-4 string into UTF-8 string
     * See _utf8_to_ucs4() for details
     * @param string  $input
     * @return string
     */
    protected function _ucs4_to_utf8($input)
    {
        $output = '';
        foreach ($input as $k => $v) {
            if ($v < 128) { // 7bit are transferred literally
                $output .= chr($v);
            } elseif ($v < (1 << 11)) { // 2 bytes
                $output .= chr(192+($v >> 6)).chr(128+($v & 63));
            } elseif ($v < (1 << 16)) { // 3 bytes
                $output .= chr(224+($v >> 12)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
            } elseif ($v < (1 << 21)) { // 4 bytes
                $output .= chr(240+($v >> 18)).chr(128+(($v >> 12) & 63)).chr(128+(($v >> 6) & 63)).chr(128+($v & 63));
            } elseif (self::$safe_mode) {
                $output .= self::$safe_char;
            } else {
                $this->_error('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k);
                return false;
            }
        }
        return $output;
    }

    /**
     * Convert UCS-4 array into UCS-4 string
     *
     * @param array $input
     * @return string
     */
    protected function _ucs4_to_ucs4_string($input)
    {
        $output = '';
        // Take array values and split output to 4 bytes per value
        // The bit mask is 255, which reads &11111111
        foreach ($input as $v) {
            $output .= chr(($v >> 24) & 255).chr(($v >> 16) & 255).chr(($v >> 8) & 255).chr($v & 255);
        }
        return $output;
    }

    /**
     * Convert UCS-4 strin into UCS-4 garray
     *
     * @param  string $input
     * @return array
     */
    protected function _ucs4_string_to_ucs4($input)
    {
        $output = array();
        $inp_len = self::byteLength($input);
        // Input length must be dividable by 4
        if ($inp_len % 4) {
            $this->_error('Input UCS4 string is broken');
            return false;
        }
        // Empty input - return empty output
        if (!$inp_len) return $output;
        for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
            // Increment output position every 4 input bytes
            if (!($i % 4)) {
                $out_len++;
                $output[$out_len] = 0;
            }
            $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
        }
        return $output;
    }

    /**
     * Gets the length of a string in bytes even if mbstring function
     * overloading is turned on
     *
     * @param string $string the string for which to get the length.
     * @return integer the length of the string in bytes.
     */
    protected static function byteLength($string)
    {
        if (self::$_mb_string_overload) {
            return mb_strlen($string, '8bit');
        }
        return strlen((binary) $string);
    }

    /**
     * Attempts to return a concrete IDNA instance.
     *
     * @param array $params Set of paramaters
     * @return idna_convert
     * @access public
     */
    public function getInstance($params = array())
    {
        return new idna_convert($params);
    }

    /**
     * Attempts to return a concrete IDNA instance for either php4 or php5,
     * only creating a new instance if no IDNA instance with the same
     * parameters currently exists.
     *
     * @param array $params Set of paramaters
     *
     * @return object idna_convert
     * @access public
     */
    public function singleton($params = array())
    {
        static $instances;
        if (!isset($instances)) {
            $instances = array();
        }
        $signature = serialize($params);
        if (!isset($instances[$signature])) {
            $instances[$signature] = idna_convert::getInstance($params);
        }
        return $instances[$signature];
    }

    /**
     * Holds all relevant mapping tables
     * See RFC3454 for details
     *
     * @private array
     * @since 0.5.2
     */
    protected static $NP = array
            ('map_nothing' => array(0xAD, 0x34F, 0x1806, 0x180B, 0x180C, 0x180D, 0x200B, 0x200C
                    ,0x200D, 0x2060, 0xFE00, 0xFE01, 0xFE02, 0xFE03, 0xFE04, 0xFE05, 0xFE06, 0xFE07
                    ,0xFE08, 0xFE09, 0xFE0A, 0xFE0B, 0xFE0C, 0xFE0D, 0xFE0E, 0xFE0F, 0xFEFF
                    )
            ,'general_prohibited' => array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
                    ,20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 ,33, 34, 35, 36, 37, 38, 39, 40, 41, 42
                    ,43, 44, 47, 59, 60, 61, 62, 63, 64, 91, 92, 93, 94, 95, 96, 123, 124, 125, 126, 127, 0x3002
                    )
            ,'prohibit' => array(0xA0, 0x340, 0x341, 0x6DD, 0x70F, 0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003
                    ,0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x200B, 0x200C, 0x200D, 0x200E, 0x200F
                    ,0x2028, 0x2029, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 0x202F, 0x205F, 0x206A, 0x206B, 0x206C
                    ,0x206D, 0x206E, 0x206F, 0x3000, 0xFEFF, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC, 0xFFFD, 0xFFFE, 0xFFFF
                    ,0x1FFFE, 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE, 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE
                    ,0x6FFFF, 0x7FFFE, 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, 0xAFFFF, 0xBFFFE, 0xBFFFF
                    ,0xCFFFE, 0xCFFFF, 0xDFFFE, 0xDFFFF, 0xE0001, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, 0x10FFFF
                    )
            ,'prohibit_ranges' => array(array(0x80, 0x9F), array(0x2060, 0x206F), array(0x1D173, 0x1D17A)
                    ,array(0xE000, 0xF8FF) ,array(0xF0000, 0xFFFFD), array(0x100000, 0x10FFFD)
                    ,array(0xFDD0, 0xFDEF), array(0xD800, 0xDFFF), array(0x2FF0, 0x2FFB), array(0xE0020, 0xE007F)
                    )
            ,'replacemaps' => array(0x41 => array(0x61), 0x42 => array(0x62), 0x43 => array(0x63)
                    ,0x44 => array(0x64), 0x45 => array(0x65), 0x46 => array(0x66), 0x47 => array(0x67)
                    ,0x48 => array(0x68), 0x49 => array(0x69), 0x4A => array(0x6A), 0x4B => array(0x6B)
                    ,0x4C => array(0x6C), 0x4D => array(0x6D), 0x4E => array(0x6E), 0x4F => array(0x6F)
                    ,0x50 => array(0x70), 0x51 => array(0x71), 0x52 => array(0x72), 0x53 => array(0x73)
                    ,0x54 => array(0x74), 0x55 => array(0x75), 0x56 => array(0x76), 0x57 => array(0x77)
                    ,0x58 => array(0x78), 0x59 => array(0x79), 0x5A => array(0x7A), 0xB5 => array(0x3BC)
                    ,0xC0 => array(0xE0), 0xC1 => array(0xE1), 0xC2 => array(0xE2), 0xC3 => array(0xE3)
                    ,0xC4 => array(0xE4), 0xC5 => array(0xE5), 0xC6 => array(0xE6), 0xC7 => array(0xE7)
                    ,0xC8 => array(0xE8), 0xC9 => array(0xE9), 0xCA => array(0xEA), 0xCB => array(0xEB)
                    ,0xCC => array(0xEC), 0xCD => array(0xED), 0xCE => array(0xEE), 0xCF => array(0xEF)
                    ,0xD0 => array(0xF0), 0xD1 => array(0xF1), 0xD2 => array(0xF2), 0xD3 => array(0xF3)
                    ,0xD4 => array(0xF4), 0xD5 => array(0xF5), 0xD6 => array(0xF6), 0xD8 => array(0xF8)
                    ,0xD9 => array(0xF9), 0xDA => array(0xFA), 0xDB => array(0xFB), 0xDC => array(0xFC)
                    ,0xDD => array(0xFD), 0xDE => array(0xFE), 0xDF => array(0x73, 0x73)
                    ,0x100 => array(0x101), 0x102 => array(0x103), 0x104 => array(0x105)
                    ,0x106 => array(0x107), 0x108 => array(0x109), 0x10A => array(0x10B)
                    ,0x10C => array(0x10D), 0x10E => array(0x10F), 0x110 => array(0x111)
                    ,0x112 => array(0x113), 0x114 => array(0x115), 0x116 => array(0x117)
                    ,0x118 => array(0x119), 0x11A => array(0x11B), 0x11C => array(0x11D)
                    ,0x11E => array(0x11F), 0x120 => array(0x121), 0x122 => array(0x123)
                    ,0x124 => array(0x125), 0x126 => array(0x127), 0x128 => array(0x129)
                    ,0x12A => array(0x12B), 0x12C => array(0x12D), 0x12E => array(0x12F)
                    ,0x130 => array(0x69, 0x307), 0x132 => array(0x133), 0x134 => array(0x135)
                    ,0x136 => array(0x137), 0x139 => array(0x13A), 0x13B => array(0x13C)
                    ,0x13D => array(0x13E), 0x13F => array(0x140), 0x141 => array(0x142)
                    ,0x143 => array(0x144), 0x145 => array(0x146), 0x147 => array(0x148)
                    ,0x149 => array(0x2BC, 0x6E), 0x14A => array(0x14B), 0x14C => array(0x14D)
                    ,0x14E => array(0x14F), 0x150 => array(0x151), 0x152 => array(0x153)
                    ,0x154 => array(0x155), 0x156 => array(0x157), 0x158 => array(0x159)
                    ,0x15A => array(0x15B), 0x15C => array(0x15D), 0x15E => array(0x15F)
                    ,0x160 => array(0x161), 0x162 => array(0x163), 0x164 => array(0x165)
                    ,0x166 => array(0x167), 0x168 => array(0x169), 0x16A => array(0x16B)
                    ,0x16C => array(0x16D), 0x16E => array(0x16F), 0x170 => array(0x171)
                    ,0x172 => array(0x173), 0x174 => array(0x175), 0x176 => array(0x177)
                    ,0x178 => array(0xFF), 0x179 => array(0x17A), 0x17B => array(0x17C)
                    ,0x17D => array(0x17E), 0x17F => array(0x73), 0x181 => array(0x253)
                    ,0x182 => array(0x183), 0x184 => array(0x185), 0x186 => array(0x254)
                    ,0x187 => array(0x188), 0x189 => array(0x256), 0x18A => array(0x257)
                    ,0x18B => array(0x18C), 0x18E => array(0x1DD), 0x18F => array(0x259)
                    ,0x190 => array(0x25B), 0x191 => array(0x192), 0x193 => array(0x260)
                    ,0x194 => array(0x263), 0x196 => array(0x269), 0x197 => array(0x268)
                    ,0x198 => array(0x199), 0x19C => array(0x26F), 0x19D => array(0x272)
                    ,0x19F => array(0x275), 0x1A0 => array(0x1A1), 0x1A2 => array(0x1A3)
                    ,0x1A4 => array(0x1A5), 0x1A6 => array(0x280), 0x1A7 => array(0x1A8)
                    ,0x1A9 => array(0x283), 0x1AC => array(0x1AD), 0x1AE => array(0x288)
                    ,0x1AF => array(0x1B0), 0x1B1 => array(0x28A), 0x1B2 => array(0x28B)
                    ,0x1B3 => array(0x1B4), 0x1B5 => array(0x1B6), 0x1B7 => array(0x292)
                    ,0x1B8 => array(0x1B9), 0x1BC => array(0x1BD), 0x1C4 => array(0x1C6)
                    ,0x1C5 => array(0x1C6), 0x1C7 => array(0x1C9), 0x1C8 => array(0x1C9)
                    ,0x1CA => array(0x1CC), 0x1CB => array(0x1CC), 0x1CD => array(0x1CE)
                    ,0x1CF => array(0x1D0), 0x1D1   => array(0x1D2), 0x1D3   => array(0x1D4)
                    ,0x1D5   => array(0x1D6), 0x1D7   => array(0x1D8), 0x1D9   => array(0x1DA)
                    ,0x1DB   => array(0x1DC), 0x1DE   => array(0x1DF), 0x1E0   => array(0x1E1)
                    ,0x1E2   => array(0x1E3), 0x1E4   => array(0x1E5), 0x1E6   => array(0x1E7)
                    ,0x1E8   => array(0x1E9), 0x1EA   => array(0x1EB), 0x1EC   => array(0x1ED)
                    ,0x1EE   => array(0x1EF), 0x1F0   => array(0x6A, 0x30C), 0x1F1   => array(0x1F3)
                    ,0x1F2   => array(0x1F3), 0x1F4   => array(0x1F5), 0x1F6   => array(0x195)
                    ,0x1F7   => array(0x1BF), 0x1F8   => array(0x1F9), 0x1FA   => array(0x1FB)
                    ,0x1FC   => array(0x1FD), 0x1FE   => array(0x1FF), 0x200   => array(0x201)
                    ,0x202   => array(0x203), 0x204   => array(0x205), 0x206   => array(0x207)
                    ,0x208   => array(0x209), 0x20A   => array(0x20B), 0x20C   => array(0x20D)
                    ,0x20E   => array(0x20F), 0x210   => array(0x211), 0x212   => array(0x213)
                    ,0x214   => array(0x215), 0x216   => array(0x217), 0x218   => array(0x219)
                    ,0x21A   => array(0x21B), 0x21C   => array(0x21D), 0x21E   => array(0x21F)
                    ,0x220   => array(0x19E), 0x222   => array(0x223), 0x224   => array(0x225)
                    ,0x226   => array(0x227), 0x228   => array(0x229), 0x22A   => array(0x22B)
                    ,0x22C   => array(0x22D), 0x22E   => array(0x22F), 0x230   => array(0x231)
                    ,0x232   => array(0x233), 0x345   => array(0x3B9), 0x37A   => array(0x20, 0x3B9)
                    ,0x386   => array(0x3AC), 0x388   => array(0x3AD), 0x389   => array(0x3AE)
                    ,0x38A   => array(0x3AF), 0x38C   => array(0x3CC), 0x38E   => array(0x3CD)
                    ,0x38F   => array(0x3CE), 0x390   => array(0x3B9, 0x308, 0x301)
                    ,0x391   => array(0x3B1), 0x392   => array(0x3B2), 0x393   => array(0x3B3)
                    ,0x394   => array(0x3B4), 0x395   => array(0x3B5), 0x396   => array(0x3B6)
                    ,0x397   => array(0x3B7), 0x398   => array(0x3B8), 0x399   => array(0x3B9)
                    ,0x39A   => array(0x3BA), 0x39B   => array(0x3BB), 0x39C   => array(0x3BC)
                    ,0x39D   => array(0x3BD), 0x39E   => array(0x3BE), 0x39F   => array(0x3BF)
                    ,0x3A0   => array(0x3C0), 0x3A1   => array(0x3C1), 0x3A3   => array(0x3C3)
                    ,0x3A4   => array(0x3C4), 0x3A5   => array(0x3C5), 0x3A6   => array(0x3C6)
                    ,0x3A7   => array(0x3C7), 0x3A8   => array(0x3C8), 0x3A9   => array(0x3C9)
                    ,0x3AA   => array(0x3CA), 0x3AB   => array(0x3CB), 0x3B0   => array(0x3C5, 0x308, 0x301)
                    ,0x3C2   => array(0x3C3), 0x3D0   => array(0x3B2), 0x3D1   => array(0x3B8)
                    ,0x3D2   => array(0x3C5), 0x3D3   => array(0x3CD), 0x3D4   => array(0x3CB)
                    ,0x3D5   => array(0x3C6), 0x3D6   => array(0x3C0), 0x3D8   => array(0x3D9)
                    ,0x3DA   => array(0x3DB), 0x3DC   => array(0x3DD), 0x3DE   => array(0x3DF)
                    ,0x3E0   => array(0x3E1), 0x3E2   => array(0x3E3), 0x3E4   => array(0x3E5)
                    ,0x3E6   => array(0x3E7), 0x3E8   => array(0x3E9), 0x3EA   => array(0x3EB)
                    ,0x3EC   => array(0x3ED), 0x3EE   => array(0x3EF), 0x3F0   => array(0x3BA)
                    ,0x3F1   => array(0x3C1), 0x3F2   => array(0x3C3), 0x3F4   => array(0x3B8)
                    ,0x3F5   => array(0x3B5), 0x400   => array(0x450), 0x401   => array(0x451)
                    ,0x402   => array(0x452), 0x403   => array(0x453), 0x404   => array(0x454)
                    ,0x405   => array(0x455), 0x406   => array(0x456), 0x407   => array(0x457)
                    ,0x408   => array(0x458), 0x409   => array(0x459), 0x40A   => array(0x45A)
                    ,0x40B   => array(0x45B), 0x40C   => array(0x45C), 0x40D   => array(0x45D)
                    ,0x40E   => array(0x45E), 0x40F   => array(0x45F), 0x410   => array(0x430)
                    ,0x411   => array(0x431), 0x412   => array(0x432), 0x413   => array(0x433)
                    ,0x414   => array(0x434), 0x415   => array(0x435), 0x416   => array(0x436)
                    ,0x417   => array(0x437), 0x418   => array(0x438), 0x419   => array(0x439)
                    ,0x41A   => array(0x43A), 0x41B   => array(0x43B), 0x41C   => array(0x43C)
                    ,0x41D   => array(0x43D), 0x41E   => array(0x43E), 0x41F   => array(0x43F)
                    ,0x420   => array(0x440), 0x421   => array(0x441), 0x422   => array(0x442)
                    ,0x423   => array(0x443), 0x424   => array(0x444), 0x425   => array(0x445)
                    ,0x426   => array(0x446), 0x427   => array(0x447), 0x428   => array(0x448)
                    ,0x429   => array(0x449), 0x42A   => array(0x44A), 0x42B   => array(0x44B)
                    ,0x42C   => array(0x44C), 0x42D   => array(0x44D), 0x42E   => array(0x44E)
                    ,0x42F   => array(0x44F), 0x460   => array(0x461), 0x462   => array(0x463)
                    ,0x464   => array(0x465), 0x466   => array(0x467), 0x468   => array(0x469)
                    ,0x46A   => array(0x46B), 0x46C   => array(0x46D), 0x46E   => array(0x46F)
                    ,0x470   => array(0x471), 0x472   => array(0x473), 0x474   => array(0x475)
                    ,0x476   => array(0x477), 0x478   => array(0x479), 0x47A   => array(0x47B)
                    ,0x47C   => array(0x47D), 0x47E   => array(0x47F), 0x480   => array(0x481)
                    ,0x48A   => array(0x48B), 0x48C   => array(0x48D), 0x48E   => array(0x48F)
                    ,0x490   => array(0x491), 0x492   => array(0x493), 0x494   => array(0x495)
                    ,0x496   => array(0x497), 0x498   => array(0x499), 0x49A   => array(0x49B)
                    ,0x49C   => array(0x49D), 0x49E   => array(0x49F), 0x4A0   => array(0x4A1)
                    ,0x4A2   => array(0x4A3), 0x4A4   => array(0x4A5), 0x4A6   => array(0x4A7)
                    ,0x4A8   => array(0x4A9), 0x4AA   => array(0x4AB), 0x4AC   => array(0x4AD)
                    ,0x4AE   => array(0x4AF), 0x4B0   => array(0x4B1), 0x4B2   => array(0x4B3)
                    ,0x4B4   => array(0x4B5), 0x4B6   => array(0x4B7), 0x4B8   => array(0x4B9)
                    ,0x4BA   => array(0x4BB), 0x4BC   => array(0x4BD), 0x4BE   => array(0x4BF)
                    ,0x4C1   => array(0x4C2), 0x4C3   => array(0x4C4), 0x4C5   => array(0x4C6)
                    ,0x4C7   => array(0x4C8), 0x4C9   => array(0x4CA), 0x4CB   => array(0x4CC)
                    ,0x4CD   => array(0x4CE), 0x4D0   => array(0x4D1), 0x4D2   => array(0x4D3)
                    ,0x4D4   => array(0x4D5), 0x4D6   => array(0x4D7), 0x4D8   => array(0x4D9)
                    ,0x4DA   => array(0x4DB), 0x4DC   => array(0x4DD), 0x4DE   => array(0x4DF)
                    ,0x4E0   => array(0x4E1), 0x4E2   => array(0x4E3), 0x4E4   => array(0x4E5)
                    ,0x4E6   => array(0x4E7), 0x4E8   => array(0x4E9), 0x4EA   => array(0x4EB)
                    ,0x4EC   => array(0x4ED), 0x4EE   => array(0x4EF), 0x4F0   => array(0x4F1)
                    ,0x4F2   => array(0x4F3), 0x4F4   => array(0x4F5), 0x4F8   => array(0x4F9)
                    ,0x500   => array(0x501), 0x502   => array(0x503), 0x504   => array(0x505)
                    ,0x506   => array(0x507), 0x508   => array(0x509), 0x50A   => array(0x50B)
                    ,0x50C   => array(0x50D), 0x50E   => array(0x50F), 0x531   => array(0x561)
                    ,0x532   => array(0x562), 0x533   => array(0x563), 0x534   => array(0x564)
                    ,0x535   => array(0x565), 0x536   => array(0x566), 0x537   => array(0x567)
                    ,0x538   => array(0x568), 0x539   => array(0x569), 0x53A   => array(0x56A)
                    ,0x53B   => array(0x56B), 0x53C   => array(0x56C), 0x53D   => array(0x56D)
                    ,0x53E   => array(0x56E), 0x53F   => array(0x56F), 0x540   => array(0x570)
                    ,0x541   => array(0x571), 0x542   => array(0x572), 0x543   => array(0x573)
                    ,0x544   => array(0x574), 0x545   => array(0x575), 0x546   => array(0x576)
                    ,0x547   => array(0x577), 0x548   => array(0x578), 0x549   => array(0x579)
                    ,0x54A   => array(0x57A), 0x54B   => array(0x57B), 0x54C   => array(0x57C)
                    ,0x54D   => array(0x57D), 0x54E   => array(0x57E), 0x54F   => array(0x57F)
                    ,0x550   => array(0x580), 0x551   => array(0x581), 0x552   => array(0x582)
                    ,0x553   => array(0x583), 0x554   => array(0x584), 0x555   => array(0x585)
                    ,0x556 => array(0x586), 0x587 => array(0x565, 0x582), 0xE33 => array(0xE4D, 0xE32)
                    ,0x1E00  => array(0x1E01), 0x1E02  => array(0x1E03), 0x1E04  => array(0x1E05)
                    ,0x1E06  => array(0x1E07), 0x1E08  => array(0x1E09), 0x1E0A  => array(0x1E0B)
                    ,0x1E0C  => array(0x1E0D), 0x1E0E  => array(0x1E0F), 0x1E10  => array(0x1E11)
                    ,0x1E12  => array(0x1E13), 0x1E14  => array(0x1E15), 0x1E16  => array(0x1E17)
                    ,0x1E18  => array(0x1E19), 0x1E1A  => array(0x1E1B), 0x1E1C  => array(0x1E1D)
                    ,0x1E1E  => array(0x1E1F), 0x1E20  => array(0x1E21), 0x1E22  => array(0x1E23)
                    ,0x1E24  => array(0x1E25), 0x1E26  => array(0x1E27), 0x1E28  => array(0x1E29)
                    ,0x1E2A  => array(0x1E2B), 0x1E2C  => array(0x1E2D), 0x1E2E  => array(0x1E2F)
                    ,0x1E30  => array(0x1E31), 0x1E32  => array(0x1E33), 0x1E34  => array(0x1E35)
                    ,0x1E36  => array(0x1E37), 0x1E38  => array(0x1E39), 0x1E3A  => array(0x1E3B)
                    ,0x1E3C  => array(0x1E3D), 0x1E3E  => array(0x1E3F), 0x1E40  => array(0x1E41)
                    ,0x1E42  => array(0x1E43), 0x1E44  => array(0x1E45), 0x1E46  => array(0x1E47)
                    ,0x1E48  => array(0x1E49), 0x1E4A  => array(0x1E4B), 0x1E4C  => array(0x1E4D)
                    ,0x1E4E  => array(0x1E4F), 0x1E50  => array(0x1E51), 0x1E52  => array(0x1E53)
                    ,0x1E54  => array(0x1E55), 0x1E56  => array(0x1E57), 0x1E58  => array(0x1E59)
                    ,0x1E5A  => array(0x1E5B), 0x1E5C  => array(0x1E5D), 0x1E5E  => array(0x1E5F)
                    ,0x1E60  => array(0x1E61), 0x1E62  => array(0x1E63), 0x1E64  => array(0x1E65)
                    ,0x1E66  => array(0x1E67), 0x1E68  => array(0x1E69), 0x1E6A  => array(0x1E6B)
                    ,0x1E6C  => array(0x1E6D), 0x1E6E  => array(0x1E6F), 0x1E70  => array(0x1E71)
                    ,0x1E72  => array(0x1E73), 0x1E74  => array(0x1E75), 0x1E76  => array(0x1E77)
                    ,0x1E78  => array(0x1E79), 0x1E7A  => array(0x1E7B), 0x1E7C  => array(0x1E7D)
                    ,0x1E7E  => array(0x1E7F), 0x1E80  => array(0x1E81), 0x1E82  => array(0x1E83)
                    ,0x1E84  => array(0x1E85), 0x1E86  => array(0x1E87), 0x1E88  => array(0x1E89)
                    ,0x1E8A  => array(0x1E8B), 0x1E8C  => array(0x1E8D), 0x1E8E  => array(0x1E8F)
                    ,0x1E90  => array(0x1E91), 0x1E92  => array(0x1E93), 0x1E94  => array(0x1E95)
                    ,0x1E96  => array(0x68, 0x331), 0x1E97  => array(0x74, 0x308), 0x1E98  => array(0x77, 0x30A)
                    ,0x1E99  => array(0x79, 0x30A), 0x1E9A  => array(0x61, 0x2BE), 0x1E9B  => array(0x1E61)
                    ,0x1EA0  => array(0x1EA1), 0x1EA2  => array(0x1EA3), 0x1EA4  => array(0x1EA5)
                    ,0x1EA6  => array(0x1EA7), 0x1EA8  => array(0x1EA9), 0x1EAA  => array(0x1EAB)
                    ,0x1EAC  => array(0x1EAD), 0x1EAE  => array(0x1EAF), 0x1EB0  => array(0x1EB1)
                    ,0x1EB2  => array(0x1EB3), 0x1EB4  => array(0x1EB5), 0x1EB6  => array(0x1EB7)
                    ,0x1EB8  => array(0x1EB9), 0x1EBA  => array(0x1EBB), 0x1EBC  => array(0x1EBD)
                    ,0x1EBE  => array(0x1EBF), 0x1EC0  => array(0x1EC1), 0x1EC2  => array(0x1EC3)
                    ,0x1EC4  => array(0x1EC5), 0x1EC6  => array(0x1EC7), 0x1EC8  => array(0x1EC9)
                    ,0x1ECA  => array(0x1ECB), 0x1ECC  => array(0x1ECD), 0x1ECE  => array(0x1ECF)
                    ,0x1ED0  => array(0x1ED1), 0x1ED2  => array(0x1ED3), 0x1ED4  => array(0x1ED5)
                    ,0x1ED6  => array(0x1ED7), 0x1ED8  => array(0x1ED9), 0x1EDA  => array(0x1EDB)
                    ,0x1EDC  => array(0x1EDD), 0x1EDE  => array(0x1EDF), 0x1EE0  => array(0x1EE1)
                    ,0x1EE2  => array(0x1EE3), 0x1EE4  => array(0x1EE5), 0x1EE6  => array(0x1EE7)
                    ,0x1EE8  => array(0x1EE9), 0x1EEA  => array(0x1EEB), 0x1EEC  => array(0x1EED)
                    ,0x1EEE  => array(0x1EEF), 0x1EF0  => array(0x1EF1), 0x1EF2  => array(0x1EF3)
                    ,0x1EF4  => array(0x1EF5), 0x1EF6  => array(0x1EF7), 0x1EF8  => array(0x1EF9)
                    ,0x1F08  => array(0x1F00), 0x1F09  => array(0x1F01), 0x1F0A  => array(0x1F02)
                    ,0x1F0B  => array(0x1F03), 0x1F0C  => array(0x1F04), 0x1F0D  => array(0x1F05)
                    ,0x1F0E  => array(0x1F06), 0x1F0F  => array(0x1F07), 0x1F18  => array(0x1F10)
                    ,0x1F19  => array(0x1F11), 0x1F1A  => array(0x1F12), 0x1F1B  => array(0x1F13)
                    ,0x1F1C  => array(0x1F14), 0x1F1D  => array(0x1F15), 0x1F28  => array(0x1F20)
                    ,0x1F29  => array(0x1F21), 0x1F2A  => array(0x1F22), 0x1F2B  => array(0x1F23)
                    ,0x1F2C  => array(0x1F24), 0x1F2D  => array(0x1F25), 0x1F2E  => array(0x1F26)
                    ,0x1F2F  => array(0x1F27), 0x1F38  => array(0x1F30), 0x1F39  => array(0x1F31)
                    ,0x1F3A  => array(0x1F32), 0x1F3B  => array(0x1F33), 0x1F3C  => array(0x1F34)
                    ,0x1F3D  => array(0x1F35), 0x1F3E  => array(0x1F36), 0x1F3F  => array(0x1F37)
                    ,0x1F48  => array(0x1F40), 0x1F49  => array(0x1F41), 0x1F4A  => array(0x1F42)
                    ,0x1F4B  => array(0x1F43), 0x1F4C  => array(0x1F44), 0x1F4D  => array(0x1F45)
                    ,0x1F50  => array(0x3C5, 0x313), 0x1F52  => array(0x3C5, 0x313, 0x300)
                    ,0x1F54  => array(0x3C5, 0x313, 0x301), 0x1F56  => array(0x3C5, 0x313, 0x342)
                    ,0x1F59  => array(0x1F51), 0x1F5B  => array(0x1F53), 0x1F5D  => array(0x1F55)
                    ,0x1F5F  => array(0x1F57), 0x1F68  => array(0x1F60), 0x1F69  => array(0x1F61)
                    ,0x1F6A  => array(0x1F62), 0x1F6B  => array(0x1F63), 0x1F6C  => array(0x1F64)
                    ,0x1F6D  => array(0x1F65), 0x1F6E  => array(0x1F66), 0x1F6F  => array(0x1F67)
                    ,0x1F80  => array(0x1F00, 0x3B9), 0x1F81  => array(0x1F01, 0x3B9)
                    ,0x1F82  => array(0x1F02, 0x3B9), 0x1F83  => array(0x1F03, 0x3B9)
                    ,0x1F84  => array(0x1F04, 0x3B9), 0x1F85  => array(0x1F05, 0x3B9)
                    ,0x1F86  => array(0x1F06, 0x3B9), 0x1F87  => array(0x1F07, 0x3B9)
                    ,0x1F88  => array(0x1F00, 0x3B9), 0x1F89  => array(0x1F01, 0x3B9)
                    ,0x1F8A  => array(0x1F02, 0x3B9), 0x1F8B  => array(0x1F03, 0x3B9)
                    ,0x1F8C  => array(0x1F04, 0x3B9), 0x1F8D  => array(0x1F05, 0x3B9)
                    ,0x1F8E  => array(0x1F06, 0x3B9), 0x1F8F  => array(0x1F07, 0x3B9)
                    ,0x1F90  => array(0x1F20, 0x3B9), 0x1F91  => array(0x1F21, 0x3B9)
                    ,0x1F92  => array(0x1F22, 0x3B9), 0x1F93  => array(0x1F23, 0x3B9)
                    ,0x1F94  => array(0x1F24, 0x3B9), 0x1F95  => array(0x1F25, 0x3B9)
                    ,0x1F96  => array(0x1F26, 0x3B9), 0x1F97  => array(0x1F27, 0x3B9)
                    ,0x1F98  => array(0x1F20, 0x3B9), 0x1F99  => array(0x1F21, 0x3B9)
                    ,0x1F9A  => array(0x1F22, 0x3B9), 0x1F9B  => array(0x1F23, 0x3B9)
                    ,0x1F9C  => array(0x1F24, 0x3B9), 0x1F9D  => array(0x1F25, 0x3B9)
                    ,0x1F9E  => array(0x1F26, 0x3B9), 0x1F9F  => array(0x1F27, 0x3B9)
                    ,0x1FA0  => array(0x1F60, 0x3B9), 0x1FA1  => array(0x1F61, 0x3B9)
                    ,0x1FA2  => array(0x1F62, 0x3B9), 0x1FA3  => array(0x1F63, 0x3B9)
                    ,0x1FA4  => array(0x1F64, 0x3B9), 0x1FA5  => array(0x1F65, 0x3B9)
                    ,0x1FA6  => array(0x1F66, 0x3B9), 0x1FA7  => array(0x1F67, 0x3B9)
                    ,0x1FA8  => array(0x1F60, 0x3B9), 0x1FA9  => array(0x1F61, 0x3B9)
                    ,0x1FAA  => array(0x1F62, 0x3B9), 0x1FAB  => array(0x1F63, 0x3B9)
                    ,0x1FAC  => array(0x1F64, 0x3B9), 0x1FAD  => array(0x1F65, 0x3B9)
                    ,0x1FAE  => array(0x1F66, 0x3B9), 0x1FAF  => array(0x1F67, 0x3B9)
                    ,0x1FB2  => array(0x1F70, 0x3B9), 0x1FB3  => array(0x3B1, 0x3B9)
                    ,0x1FB4  => array(0x3AC, 0x3B9), 0x1FB6  => array(0x3B1, 0x342)
                    ,0x1FB7  => array(0x3B1, 0x342, 0x3B9), 0x1FB8  => array(0x1FB0)
                    ,0x1FB9  => array(0x1FB1), 0x1FBA  => array(0x1F70), 0x1FBB  => array(0x1F71)
                    ,0x1FBC  => array(0x3B1, 0x3B9), 0x1FBE  => array(0x3B9)
                    ,0x1FC2  => array(0x1F74, 0x3B9), 0x1FC3  => array(0x3B7, 0x3B9)
                    ,0x1FC4  => array(0x3AE, 0x3B9), 0x1FC6  => array(0x3B7, 0x342)
                    ,0x1FC7  => array(0x3B7, 0x342, 0x3B9), 0x1FC8  => array(0x1F72)
                    ,0x1FC9  => array(0x1F73), 0x1FCA  => array(0x1F74), 0x1FCB  => array(0x1F75)
                    ,0x1FCC  => array(0x3B7, 0x3B9), 0x1FD2  => array(0x3B9, 0x308, 0x300)
                    ,0x1FD3  => array(0x3B9, 0x308, 0x301), 0x1FD6  => array(0x3B9, 0x342)
                    ,0x1FD7  => array(0x3B9, 0x308, 0x342), 0x1FD8  => array(0x1FD0)
                    ,0x1FD9  => array(0x1FD1), 0x1FDA  => array(0x1F76)
                    ,0x1FDB  => array(0x1F77), 0x1FE2  => array(0x3C5, 0x308, 0x300)
                    ,0x1FE3  => array(0x3C5, 0x308, 0x301), 0x1FE4  => array(0x3C1, 0x313)
                    ,0x1FE6  => array(0x3C5, 0x342), 0x1FE7  => array(0x3C5, 0x308, 0x342)
                    ,0x1FE8  => array(0x1FE0), 0x1FE9  => array(0x1FE1)
                    ,0x1FEA  => array(0x1F7A), 0x1FEB  => array(0x1F7B)
                    ,0x1FEC  => array(0x1FE5), 0x1FF2  => array(0x1F7C, 0x3B9)
                    ,0x1FF3  => array(0x3C9, 0x3B9), 0x1FF4  => array(0x3CE, 0x3B9)
                    ,0x1FF6  => array(0x3C9, 0x342), 0x1FF7  => array(0x3C9, 0x342, 0x3B9)
                    ,0x1FF8  => array(0x1F78), 0x1FF9  => array(0x1F79), 0x1FFA  => array(0x1F7C)
                    ,0x1FFB  => array(0x1F7D), 0x1FFC  => array(0x3C9, 0x3B9)
                    ,0x20A8  => array(0x72, 0x73), 0x2102  => array(0x63), 0x2103  => array(0xB0, 0x63)
                    ,0x2107  => array(0x25B), 0x2109  => array(0xB0, 0x66), 0x210B  => array(0x68)
                    ,0x210C  => array(0x68), 0x210D  => array(0x68), 0x2110  => array(0x69)
                    ,0x2111  => array(0x69), 0x2112  => array(0x6C), 0x2115  => array(0x6E)
                    ,0x2116  => array(0x6E, 0x6F), 0x2119  => array(0x70), 0x211A  => array(0x71)
                    ,0x211B  => array(0x72), 0x211C  => array(0x72), 0x211D  => array(0x72)
                    ,0x2120  => array(0x73, 0x6D), 0x2121  => array(0x74, 0x65, 0x6C)
                    ,0x2122  => array(0x74, 0x6D), 0x2124  => array(0x7A), 0x2126  => array(0x3C9)
                    ,0x2128  => array(0x7A), 0x212A  => array(0x6B), 0x212B  => array(0xE5)
                    ,0x212C  => array(0x62), 0x212D  => array(0x63), 0x2130  => array(0x65)
                    ,0x2131  => array(0x66), 0x2133  => array(0x6D), 0x213E  => array(0x3B3)
                    ,0x213F  => array(0x3C0), 0x2145  => array(0x64) ,0x2160  => array(0x2170)
                    ,0x2161  => array(0x2171), 0x2162  => array(0x2172), 0x2163  => array(0x2173)
                    ,0x2164  => array(0x2174), 0x2165  => array(0x2175), 0x2166  => array(0x2176)
                    ,0x2167  => array(0x2177), 0x2168  => array(0x2178), 0x2169  => array(0x2179)
                    ,0x216A  => array(0x217A), 0x216B  => array(0x217B), 0x216C  => array(0x217C)
                    ,0x216D  => array(0x217D), 0x216E  => array(0x217E), 0x216F  => array(0x217F)
                    ,0x24B6  => array(0x24D0), 0x24B7  => array(0x24D1), 0x24B8  => array(0x24D2)
                    ,0x24B9  => array(0x24D3), 0x24BA  => array(0x24D4), 0x24BB  => array(0x24D5)
                    ,0x24BC  => array(0x24D6), 0x24BD  => array(0x24D7), 0x24BE  => array(0x24D8)
                    ,0x24BF  => array(0x24D9), 0x24C0  => array(0x24DA), 0x24C1  => array(0x24DB)
                    ,0x24C2  => array(0x24DC), 0x24C3  => array(0x24DD), 0x24C4  => array(0x24DE)
                    ,0x24C5  => array(0x24DF), 0x24C6  => array(0x24E0), 0x24C7  => array(0x24E1)
                    ,0x24C8  => array(0x24E2), 0x24C9  => array(0x24E3), 0x24CA  => array(0x24E4)
                    ,0x24CB  => array(0x24E5), 0x24CC  => array(0x24E6), 0x24CD  => array(0x24E7)
                    ,0x24CE  => array(0x24E8), 0x24CF  => array(0x24E9), 0x3371  => array(0x68, 0x70, 0x61)
                    ,0x3373  => array(0x61, 0x75), 0x3375  => array(0x6F, 0x76)
                    ,0x3380  => array(0x70, 0x61), 0x3381  => array(0x6E, 0x61)
                    ,0x3382  => array(0x3BC, 0x61), 0x3383  => array(0x6D, 0x61)
                    ,0x3384  => array(0x6B, 0x61), 0x3385  => array(0x6B, 0x62)
                    ,0x3386  => array(0x6D, 0x62), 0x3387  => array(0x67, 0x62)
                    ,0x338A  => array(0x70, 0x66), 0x338B  => array(0x6E, 0x66)
                    ,0x338C  => array(0x3BC, 0x66), 0x3390  => array(0x68, 0x7A)
                    ,0x3391  => array(0x6B, 0x68, 0x7A), 0x3392  => array(0x6D, 0x68, 0x7A)
                    ,0x3393  => array(0x67, 0x68, 0x7A), 0x3394  => array(0x74, 0x68, 0x7A)
                    ,0x33A9  => array(0x70, 0x61), 0x33AA  => array(0x6B, 0x70, 0x61)
                    ,0x33AB  => array(0x6D, 0x70, 0x61), 0x33AC  => array(0x67, 0x70, 0x61)
                    ,0x33B4  => array(0x70, 0x76), 0x33B5  => array(0x6E, 0x76)
                    ,0x33B6  => array(0x3BC, 0x76), 0x33B7  => array(0x6D, 0x76)
                    ,0x33B8  => array(0x6B, 0x76), 0x33B9  => array(0x6D, 0x76)
                    ,0x33BA  => array(0x70, 0x77), 0x33BB  => array(0x6E, 0x77)
                    ,0x33BC  => array(0x3BC, 0x77), 0x33BD  => array(0x6D, 0x77)
                    ,0x33BE  => array(0x6B, 0x77), 0x33BF  => array(0x6D, 0x77)
                    ,0x33C0  => array(0x6B, 0x3C9), 0x33C1  => array(0x6D, 0x3C9) /*
                    ,0x33C2  => array(0x61, 0x2E, 0x6D, 0x2E) */
                    ,0x33C3  => array(0x62, 0x71), 0x33C6  => array(0x63, 0x2215, 0x6B, 0x67)
                    ,0x33C7  => array(0x63, 0x6F, 0x2E), 0x33C8  => array(0x64, 0x62)
                    ,0x33C9  => array(0x67, 0x79), 0x33CB  => array(0x68, 0x70)
                    ,0x33CD  => array(0x6B, 0x6B), 0x33CE  => array(0x6B, 0x6D)
                    ,0x33D7  => array(0x70, 0x68), 0x33D9  => array(0x70, 0x70, 0x6D)
                    ,0x33DA  => array(0x70, 0x72), 0x33DC  => array(0x73, 0x76)
                    ,0x33DD  => array(0x77, 0x62), 0xFB00  => array(0x66, 0x66)
                    ,0xFB01  => array(0x66, 0x69), 0xFB02  => array(0x66, 0x6C)
                    ,0xFB03  => array(0x66, 0x66, 0x69), 0xFB04  => array(0x66, 0x66, 0x6C)
                    ,0xFB05  => array(0x73, 0x74), 0xFB06  => array(0x73, 0x74)
                    ,0xFB13  => array(0x574, 0x576), 0xFB14  => array(0x574, 0x565)
                    ,0xFB15  => array(0x574, 0x56B), 0xFB16  => array(0x57E, 0x576)
                    ,0xFB17  => array(0x574, 0x56D), 0xFF21  => array(0xFF41)
                    ,0xFF22  => array(0xFF42), 0xFF23  => array(0xFF43), 0xFF24  => array(0xFF44)
                    ,0xFF25  => array(0xFF45), 0xFF26  => array(0xFF46), 0xFF27  => array(0xFF47)
                    ,0xFF28  => array(0xFF48), 0xFF29  => array(0xFF49), 0xFF2A  => array(0xFF4A)
                    ,0xFF2B  => array(0xFF4B), 0xFF2C  => array(0xFF4C), 0xFF2D  => array(0xFF4D)
                    ,0xFF2E  => array(0xFF4E), 0xFF2F  => array(0xFF4F), 0xFF30  => array(0xFF50)
                    ,0xFF31  => array(0xFF51), 0xFF32  => array(0xFF52), 0xFF33  => array(0xFF53)
                    ,0xFF34  => array(0xFF54), 0xFF35  => array(0xFF55), 0xFF36  => array(0xFF56)
                    ,0xFF37  => array(0xFF57), 0xFF38  => array(0xFF58), 0xFF39  => array(0xFF59)
                    ,0xFF3A  => array(0xFF5A), 0x10400 => array(0x10428), 0x10401 => array(0x10429)
                    ,0x10402 => array(0x1042A), 0x10403 => array(0x1042B), 0x10404 => array(0x1042C)
                    ,0x10405 => array(0x1042D), 0x10406 => array(0x1042E), 0x10407 => array(0x1042F)
                    ,0x10408 => array(0x10430), 0x10409 => array(0x10431), 0x1040A => array(0x10432)
                    ,0x1040B => array(0x10433), 0x1040C => array(0x10434), 0x1040D => array(0x10435)
                    ,0x1040E => array(0x10436), 0x1040F => array(0x10437), 0x10410 => array(0x10438)
                    ,0x10411 => array(0x10439), 0x10412 => array(0x1043A), 0x10413 => array(0x1043B)
                    ,0x10414 => array(0x1043C), 0x10415 => array(0x1043D), 0x10416 => array(0x1043E)
                    ,0x10417 => array(0x1043F), 0x10418 => array(0x10440), 0x10419 => array(0x10441)
                    ,0x1041A => array(0x10442), 0x1041B => array(0x10443), 0x1041C => array(0x10444)
                    ,0x1041D => array(0x10445), 0x1041E => array(0x10446), 0x1041F => array(0x10447)
                    ,0x10420 => array(0x10448), 0x10421 => array(0x10449), 0x10422 => array(0x1044A)
                    ,0x10423 => array(0x1044B), 0x10424 => array(0x1044C), 0x10425 => array(0x1044D)
                    ,0x1D400 => array(0x61), 0x1D401 => array(0x62), 0x1D402 => array(0x63)
                    ,0x1D403 => array(0x64), 0x1D404 => array(0x65), 0x1D405 => array(0x66)
                    ,0x1D406 => array(0x67), 0x1D407 => array(0x68), 0x1D408 => array(0x69)
                    ,0x1D409 => array(0x6A), 0x1D40A => array(0x6B), 0x1D40B => array(0x6C)
                    ,0x1D40C => array(0x6D), 0x1D40D => array(0x6E), 0x1D40E => array(0x6F)
                    ,0x1D40F => array(0x70), 0x1D410 => array(0x71), 0x1D411 => array(0x72)
                    ,0x1D412 => array(0x73), 0x1D413 => array(0x74), 0x1D414 => array(0x75)
                    ,0x1D415 => array(0x76), 0x1D416 => array(0x77), 0x1D417 => array(0x78)
                    ,0x1D418 => array(0x79), 0x1D419 => array(0x7A), 0x1D434 => array(0x61)
                    ,0x1D435 => array(0x62), 0x1D436 => array(0x63), 0x1D437 => array(0x64)
                    ,0x1D438 => array(0x65), 0x1D439 => array(0x66), 0x1D43A => array(0x67)
                    ,0x1D43B => array(0x68), 0x1D43C => array(0x69), 0x1D43D => array(0x6A)
                    ,0x1D43E => array(0x6B), 0x1D43F => array(0x6C), 0x1D440 => array(0x6D)
                    ,0x1D441 => array(0x6E), 0x1D442 => array(0x6F), 0x1D443 => array(0x70)
                    ,0x1D444 => array(0x71), 0x1D445 => array(0x72), 0x1D446 => array(0x73)
                    ,0x1D447 => array(0x74), 0x1D448 => array(0x75), 0x1D449 => array(0x76)
                    ,0x1D44A => array(0x77), 0x1D44B => array(0x78), 0x1D44C => array(0x79)
                    ,0x1D44D => array(0x7A), 0x1D468 => array(0x61), 0x1D469 => array(0x62)
                    ,0x1D46A => array(0x63), 0x1D46B => array(0x64), 0x1D46C => array(0x65)
                    ,0x1D46D => array(0x66), 0x1D46E => array(0x67), 0x1D46F => array(0x68)
                    ,0x1D470 => array(0x69), 0x1D471 => array(0x6A), 0x1D472 => array(0x6B)
                    ,0x1D473 => array(0x6C), 0x1D474 => array(0x6D), 0x1D475 => array(0x6E)
                    ,0x1D476 => array(0x6F), 0x1D477 => array(0x70), 0x1D478 => array(0x71)
                    ,0x1D479 => array(0x72), 0x1D47A => array(0x73), 0x1D47B => array(0x74)
                    ,0x1D47C => array(0x75), 0x1D47D => array(0x76), 0x1D47E => array(0x77)
                    ,0x1D47F => array(0x78), 0x1D480 => array(0x79), 0x1D481 => array(0x7A)
                    ,0x1D49C => array(0x61), 0x1D49E => array(0x63), 0x1D49F => array(0x64)
                    ,0x1D4A2 => array(0x67), 0x1D4A5 => array(0x6A), 0x1D4A6 => array(0x6B)
                    ,0x1D4A9 => array(0x6E), 0x1D4AA => array(0x6F), 0x1D4AB => array(0x70)
                    ,0x1D4AC => array(0x71), 0x1D4AE => array(0x73), 0x1D4AF => array(0x74)
                    ,0x1D4B0 => array(0x75), 0x1D4B1 => array(0x76), 0x1D4B2 => array(0x77)
                    ,0x1D4B3 => array(0x78), 0x1D4B4 => array(0x79), 0x1D4B5 => array(0x7A)
                    ,0x1D4D0 => array(0x61), 0x1D4D1 => array(0x62), 0x1D4D2 => array(0x63)
                    ,0x1D4D3 => array(0x64), 0x1D4D4 => array(0x65), 0x1D4D5 => array(0x66)
                    ,0x1D4D6 => array(0x67), 0x1D4D7 => array(0x68), 0x1D4D8 => array(0x69)
                    ,0x1D4D9 => array(0x6A), 0x1D4DA => array(0x6B), 0x1D4DB => array(0x6C)
                    ,0x1D4DC => array(0x6D), 0x1D4DD => array(0x6E), 0x1D4DE => array(0x6F)
                    ,0x1D4DF => array(0x70), 0x1D4E0 => array(0x71), 0x1D4E1 => array(0x72)
                    ,0x1D4E2 => array(0x73), 0x1D4E3 => array(0x74), 0x1D4E4 => array(0x75)
                    ,0x1D4E5 => array(0x76), 0x1D4E6 => array(0x77), 0x1D4E7 => array(0x78)
                    ,0x1D4E8 => array(0x79), 0x1D4E9 => array(0x7A), 0x1D504 => array(0x61)
                    ,0x1D505 => array(0x62), 0x1D507 => array(0x64), 0x1D508 => array(0x65)
                    ,0x1D509 => array(0x66), 0x1D50A => array(0x67), 0x1D50D => array(0x6A)
                    ,0x1D50E => array(0x6B), 0x1D50F => array(0x6C), 0x1D510 => array(0x6D)
                    ,0x1D511 => array(0x6E), 0x1D512 => array(0x6F), 0x1D513 => array(0x70)
                    ,0x1D514 => array(0x71), 0x1D516 => array(0x73), 0x1D517 => array(0x74)
                    ,0x1D518 => array(0x75), 0x1D519 => array(0x76), 0x1D51A => array(0x77)
                    ,0x1D51B => array(0x78), 0x1D51C => array(0x79), 0x1D538 => array(0x61)
                    ,0x1D539 => array(0x62), 0x1D53B => array(0x64), 0x1D53C => array(0x65)
                    ,0x1D53D => array(0x66), 0x1D53E => array(0x67), 0x1D540 => array(0x69)
                    ,0x1D541 => array(0x6A), 0x1D542 => array(0x6B), 0x1D543 => array(0x6C)
                    ,0x1D544 => array(0x6D), 0x1D546 => array(0x6F), 0x1D54A => array(0x73)
                    ,0x1D54B => array(0x74), 0x1D54C => array(0x75), 0x1D54D => array(0x76)
                    ,0x1D54E => array(0x77), 0x1D54F => array(0x78), 0x1D550 => array(0x79)
                    ,0x1D56C => array(0x61), 0x1D56D => array(0x62), 0x1D56E => array(0x63)
                    ,0x1D56F => array(0x64), 0x1D570 => array(0x65), 0x1D571 => array(0x66)
                    ,0x1D572 => array(0x67), 0x1D573 => array(0x68), 0x1D574 => array(0x69)
                    ,0x1D575 => array(0x6A), 0x1D576 => array(0x6B), 0x1D577 => array(0x6C)
                    ,0x1D578 => array(0x6D), 0x1D579 => array(0x6E), 0x1D57A => array(0x6F)
                    ,0x1D57B => array(0x70), 0x1D57C => array(0x71), 0x1D57D => array(0x72)
                    ,0x1D57E => array(0x73), 0x1D57F => array(0x74), 0x1D580 => array(0x75)
                    ,0x1D581 => array(0x76), 0x1D582 => array(0x77), 0x1D583 => array(0x78)
                    ,0x1D584 => array(0x79), 0x1D585 => array(0x7A), 0x1D5A0 => array(0x61)
                    ,0x1D5A1 => array(0x62), 0x1D5A2 => array(0x63), 0x1D5A3 => array(0x64)
                    ,0x1D5A4 => array(0x65), 0x1D5A5 => array(0x66), 0x1D5A6 => array(0x67)
                    ,0x1D5A7 => array(0x68), 0x1D5A8 => array(0x69), 0x1D5A9 => array(0x6A)
                    ,0x1D5AA => array(0x6B), 0x1D5AB => array(0x6C), 0x1D5AC => array(0x6D)
                    ,0x1D5AD => array(0x6E), 0x1D5AE => array(0x6F), 0x1D5AF => array(0x70)
                    ,0x1D5B0 => array(0x71), 0x1D5B1 => array(0x72), 0x1D5B2 => array(0x73)
                    ,0x1D5B3 => array(0x74), 0x1D5B4 => array(0x75), 0x1D5B5 => array(0x76)
                    ,0x1D5B6 => array(0x77), 0x1D5B7 => array(0x78), 0x1D5B8 => array(0x79)
                    ,0x1D5B9 => array(0x7A), 0x1D5D4 => array(0x61), 0x1D5D5 => array(0x62)
                    ,0x1D5D6 => array(0x63), 0x1D5D7 => array(0x64), 0x1D5D8 => array(0x65)
                    ,0x1D5D9 => array(0x66), 0x1D5DA => array(0x67), 0x1D5DB => array(0x68)
                    ,0x1D5DC => array(0x69), 0x1D5DD => array(0x6A), 0x1D5DE => array(0x6B)
                    ,0x1D5DF => array(0x6C), 0x1D5E0 => array(0x6D), 0x1D5E1 => array(0x6E)
                    ,0x1D5E2 => array(0x6F), 0x1D5E3 => array(0x70), 0x1D5E4 => array(0x71)
                    ,0x1D5E5 => array(0x72), 0x1D5E6 => array(0x73), 0x1D5E7 => array(0x74)
                    ,0x1D5E8 => array(0x75), 0x1D5E9 => array(0x76), 0x1D5EA => array(0x77)
                    ,0x1D5EB => array(0x78), 0x1D5EC => array(0x79), 0x1D5ED => array(0x7A)
                    ,0x1D608 => array(0x61), 0x1D609 => array(0x62) ,0x1D60A => array(0x63)
                    ,0x1D60B => array(0x64), 0x1D60C => array(0x65), 0x1D60D => array(0x66)
                    ,0x1D60E => array(0x67), 0x1D60F => array(0x68), 0x1D610 => array(0x69)
                    ,0x1D611 => array(0x6A), 0x1D612 => array(0x6B), 0x1D613 => array(0x6C)
                    ,0x1D614 => array(0x6D), 0x1D615 => array(0x6E), 0x1D616 => array(0x6F)
                    ,0x1D617 => array(0x70), 0x1D618 => array(0x71), 0x1D619 => array(0x72)
                    ,0x1D61A => array(0x73), 0x1D61B => array(0x74), 0x1D61C => array(0x75)
                    ,0x1D61D => array(0x76), 0x1D61E => array(0x77), 0x1D61F => array(0x78)
                    ,0x1D620 => array(0x79), 0x1D621 => array(0x7A), 0x1D63C => array(0x61)
                    ,0x1D63D => array(0x62), 0x1D63E => array(0x63), 0x1D63F => array(0x64)
                    ,0x1D640 => array(0x65), 0x1D641 => array(0x66), 0x1D642 => array(0x67)
                    ,0x1D643 => array(0x68), 0x1D644 => array(0x69), 0x1D645 => array(0x6A)
                    ,0x1D646 => array(0x6B), 0x1D647 => array(0x6C), 0x1D648 => array(0x6D)
                    ,0x1D649 => array(0x6E), 0x1D64A => array(0x6F), 0x1D64B => array(0x70)
                    ,0x1D64C => array(0x71), 0x1D64D => array(0x72), 0x1D64E => array(0x73)
                    ,0x1D64F => array(0x74), 0x1D650 => array(0x75), 0x1D651 => array(0x76)
                    ,0x1D652 => array(0x77), 0x1D653 => array(0x78), 0x1D654 => array(0x79)
                    ,0x1D655 => array(0x7A), 0x1D670 => array(0x61), 0x1D671 => array(0x62)
                    ,0x1D672 => array(0x63), 0x1D673 => array(0x64), 0x1D674 => array(0x65)
                    ,0x1D675 => array(0x66), 0x1D676 => array(0x67), 0x1D677 => array(0x68)
                    ,0x1D678 => array(0x69), 0x1D679 => array(0x6A), 0x1D67A => array(0x6B)
                    ,0x1D67B => array(0x6C), 0x1D67C => array(0x6D), 0x1D67D => array(0x6E)
                    ,0x1D67E => array(0x6F), 0x1D67F => array(0x70), 0x1D680 => array(0x71)
                    ,0x1D681 => array(0x72), 0x1D682 => array(0x73), 0x1D683 => array(0x74)
                    ,0x1D684 => array(0x75), 0x1D685 => array(0x76), 0x1D686 => array(0x77)
                    ,0x1D687 => array(0x78), 0x1D688 => array(0x79), 0x1D689 => array(0x7A)
                    ,0x1D6A8 => array(0x3B1), 0x1D6A9 => array(0x3B2), 0x1D6AA => array(0x3B3)
                    ,0x1D6AB => array(0x3B4), 0x1D6AC => array(0x3B5), 0x1D6AD => array(0x3B6)
                    ,0x1D6AE => array(0x3B7), 0x1D6AF => array(0x3B8), 0x1D6B0 => array(0x3B9)
                    ,0x1D6B1 => array(0x3BA), 0x1D6B2 => array(0x3BB), 0x1D6B3 => array(0x3BC)
                    ,0x1D6B4 => array(0x3BD), 0x1D6B5 => array(0x3BE), 0x1D6B6 => array(0x3BF)
                    ,0x1D6B7 => array(0x3C0), 0x1D6B8 => array(0x3C1), 0x1D6B9 => array(0x3B8)
                    ,0x1D6BA => array(0x3C3), 0x1D6BB => array(0x3C4), 0x1D6BC => array(0x3C5)
                    ,0x1D6BD => array(0x3C6), 0x1D6BE => array(0x3C7), 0x1D6BF => array(0x3C8)
                    ,0x1D6C0 => array(0x3C9), 0x1D6D3 => array(0x3C3), 0x1D6E2 => array(0x3B1)
                    ,0x1D6E3 => array(0x3B2), 0x1D6E4 => array(0x3B3), 0x1D6E5 => array(0x3B4)
                    ,0x1D6E6 => array(0x3B5), 0x1D6E7 => array(0x3B6), 0x1D6E8 => array(0x3B7)
                    ,0x1D6E9 => array(0x3B8), 0x1D6EA => array(0x3B9), 0x1D6EB => array(0x3BA)
                    ,0x1D6EC => array(0x3BB), 0x1D6ED => array(0x3BC), 0x1D6EE => array(0x3BD)
                    ,0x1D6EF => array(0x3BE), 0x1D6F0 => array(0x3BF), 0x1D6F1 => array(0x3C0)
                    ,0x1D6F2 => array(0x3C1), 0x1D6F3 => array(0x3B8) ,0x1D6F4 => array(0x3C3)
                    ,0x1D6F5 => array(0x3C4), 0x1D6F6 => array(0x3C5), 0x1D6F7 => array(0x3C6)
                    ,0x1D6F8 => array(0x3C7), 0x1D6F9 => array(0x3C8) ,0x1D6FA => array(0x3C9)
                    ,0x1D70D => array(0x3C3), 0x1D71C => array(0x3B1), 0x1D71D => array(0x3B2)
                    ,0x1D71E => array(0x3B3), 0x1D71F => array(0x3B4), 0x1D720 => array(0x3B5)
                    ,0x1D721 => array(0x3B6), 0x1D722 => array(0x3B7), 0x1D723 => array(0x3B8)
                    ,0x1D724 => array(0x3B9), 0x1D725 => array(0x3BA), 0x1D726 => array(0x3BB)
                    ,0x1D727 => array(0x3BC), 0x1D728 => array(0x3BD), 0x1D729 => array(0x3BE)
                    ,0x1D72A => array(0x3BF), 0x1D72B => array(0x3C0), 0x1D72C => array(0x3C1)
                    ,0x1D72D => array(0x3B8), 0x1D72E => array(0x3C3), 0x1D72F => array(0x3C4)
                    ,0x1D730 => array(0x3C5), 0x1D731 => array(0x3C6), 0x1D732 => array(0x3C7)
                    ,0x1D733 => array(0x3C8), 0x1D734 => array(0x3C9), 0x1D747 => array(0x3C3)
                    ,0x1D756 => array(0x3B1), 0x1D757 => array(0x3B2), 0x1D758 => array(0x3B3)
                    ,0x1D759 => array(0x3B4), 0x1D75A => array(0x3B5), 0x1D75B => array(0x3B6)
                    ,0x1D75C => array(0x3B7), 0x1D75D => array(0x3B8), 0x1D75E => array(0x3B9)
                    ,0x1D75F => array(0x3BA), 0x1D760 => array(0x3BB), 0x1D761 => array(0x3BC)
                    ,0x1D762 => array(0x3BD), 0x1D763 => array(0x3BE), 0x1D764 => array(0x3BF)
                    ,0x1D765 => array(0x3C0), 0x1D766 => array(0x3C1), 0x1D767 => array(0x3B8)
                    ,0x1D768 => array(0x3C3), 0x1D769 => array(0x3C4), 0x1D76A => array(0x3C5)
                    ,0x1D76B => array(0x3C6), 0x1D76C => array(0x3C7), 0x1D76D => array(0x3C8)
                    ,0x1D76E => array(0x3C9), 0x1D781 => array(0x3C3), 0x1D790 => array(0x3B1)
                    ,0x1D791 => array(0x3B2), 0x1D792 => array(0x3B3), 0x1D793 => array(0x3B4)
                    ,0x1D794 => array(0x3B5), 0x1D795 => array(0x3B6), 0x1D796 => array(0x3B7)
                    ,0x1D797 => array(0x3B8), 0x1D798 => array(0x3B9), 0x1D799 => array(0x3BA)
                    ,0x1D79A => array(0x3BB), 0x1D79B => array(0x3BC), 0x1D79C => array(0x3BD)
                    ,0x1D79D => array(0x3BE), 0x1D79E => array(0x3BF), 0x1D79F => array(0x3C0)
                    ,0x1D7A0 => array(0x3C1), 0x1D7A1 => array(0x3B8), 0x1D7A2 => array(0x3C3)
                    ,0x1D7A3 => array(0x3C4), 0x1D7A4 => array(0x3C5), 0x1D7A5 => array(0x3C6)
                    ,0x1D7A6 => array(0x3C7), 0x1D7A7 => array(0x3C8), 0x1D7A8 => array(0x3C9)
                    ,0x1D7BB => array(0x3C3), 0x3F9   => array(0x3C3), 0x1D2C  => array(0x61)
                    ,0x1D2D  => array(0xE6), 0x1D2E  => array(0x62), 0x1D30  => array(0x64)
                    ,0x1D31  => array(0x65), 0x1D32  => array(0x1DD), 0x1D33  => array(0x67)
                    ,0x1D34  => array(0x68), 0x1D35  => array(0x69), 0x1D36  => array(0x6A)
                    ,0x1D37  => array(0x6B), 0x1D38  => array(0x6C), 0x1D39  => array(0x6D)
                    ,0x1D3A  => array(0x6E), 0x1D3C  => array(0x6F), 0x1D3D  => array(0x223)
                    ,0x1D3E  => array(0x70), 0x1D3F  => array(0x72), 0x1D40  => array(0x74)
                    ,0x1D41  => array(0x75), 0x1D42  => array(0x77), 0x213B  => array(0x66, 0x61, 0x78)
                    ,0x3250  => array(0x70, 0x74, 0x65), 0x32CC  => array(0x68, 0x67)
                    ,0x32CE  => array(0x65, 0x76), 0x32CF  => array(0x6C, 0x74, 0x64)
                    ,0x337A  => array(0x69, 0x75), 0x33DE  => array(0x76, 0x2215, 0x6D)
                    ,0x33DF  => array(0x61, 0x2215, 0x6D)
                    )
            ,'norm_combcls' => array(0x334   => 1,   0x335   => 1,   0x336   => 1,   0x337   => 1
                    ,0x338   => 1,   0x93C   => 7,   0x9BC   => 7,   0xA3C   => 7,   0xABC   => 7
                    ,0xB3C   => 7,   0xCBC   => 7,   0x1037  => 7,   0x3099  => 8,   0x309A  => 8
                    ,0x94D   => 9,   0x9CD   => 9,   0xA4D   => 9,   0xACD   => 9,   0xB4D   => 9
                    ,0xBCD   => 9,   0xC4D   => 9,   0xCCD   => 9,   0xD4D   => 9,   0xDCA   => 9
                    ,0xE3A   => 9,   0xF84   => 9,   0x1039  => 9,   0x1714  => 9,   0x1734  => 9
                    ,0x17D2  => 9,   0x5B0   => 10,  0x5B1   => 11,  0x5B2   => 12,  0x5B3   => 13
                    ,0x5B4   => 14,  0x5B5   => 15,  0x5B6   => 16,  0x5B7   => 17,  0x5B8   => 18
                    ,0x5B9   => 19,  0x5BB   => 20,  0x5Bc   => 21,  0x5BD   => 22,  0x5BF   => 23
                    ,0x5C1   => 24,  0x5C2   => 25,  0xFB1E  => 26,  0x64B   => 27,  0x64C   => 28
                    ,0x64D   => 29,  0x64E   => 30,  0x64F   => 31,  0x650   => 32,  0x651   => 33
                    ,0x652   => 34,  0x670   => 35,  0x711   => 36,  0xC55   => 84,  0xC56   => 91
                    ,0xE38   => 103, 0xE39   => 103, 0xE48   => 107, 0xE49   => 107, 0xE4A   => 107
                    ,0xE4B   => 107, 0xEB8   => 118, 0xEB9   => 118, 0xEC8   => 122, 0xEC9   => 122
                    ,0xECA   => 122, 0xECB   => 122, 0xF71   => 129, 0xF72   => 130, 0xF7A   => 130
                    ,0xF7B   => 130, 0xF7C   => 130, 0xF7D   => 130, 0xF80   => 130, 0xF74   => 132
                    ,0x321   => 202, 0x322   => 202, 0x327   => 202, 0x328   => 202, 0x31B   => 216
                    ,0xF39   => 216, 0x1D165 => 216, 0x1D166 => 216, 0x1D16E => 216, 0x1D16F => 216
                    ,0x1D170 => 216, 0x1D171 => 216, 0x1D172 => 216, 0x302A  => 218, 0x316   => 220
                    ,0x317   => 220, 0x318   => 220, 0x319   => 220, 0x31C   => 220, 0x31D   => 220
                    ,0x31E   => 220, 0x31F   => 220, 0x320   => 220, 0x323   => 220, 0x324   => 220
                    ,0x325   => 220, 0x326   => 220, 0x329   => 220, 0x32A   => 220, 0x32B   => 220
                    ,0x32C   => 220, 0x32D   => 220, 0x32E   => 220, 0x32F   => 220, 0x330   => 220
                    ,0x331   => 220, 0x332   => 220, 0x333   => 220, 0x339   => 220, 0x33A   => 220
                    ,0x33B   => 220, 0x33C   => 220, 0x347   => 220, 0x348   => 220, 0x349   => 220
                    ,0x34D   => 220, 0x34E   => 220, 0x353   => 220, 0x354   => 220, 0x355   => 220
                    ,0x356   => 220, 0x591   => 220, 0x596   => 220, 0x59B   => 220, 0x5A3   => 220
                    ,0x5A4   => 220, 0x5A5   => 220, 0x5A6   => 220, 0x5A7   => 220, 0x5AA   => 220
                    ,0x655   => 220, 0x656   => 220, 0x6E3   => 220, 0x6EA   => 220, 0x6ED   => 220
                    ,0x731   => 220, 0x734   => 220, 0x737   => 220, 0x738   => 220, 0x739   => 220
                    ,0x73B   => 220, 0x73C   => 220, 0x73E   => 220, 0x742   => 220, 0x744   => 220
                    ,0x746   => 220, 0x748   => 220, 0x952   => 220, 0xF18   => 220, 0xF19   => 220
                    ,0xF35   => 220, 0xF37   => 220, 0xFC6   => 220, 0x193B  => 220, 0x20E8  => 220
                    ,0x1D17B => 220, 0x1D17C => 220, 0x1D17D => 220, 0x1D17E => 220, 0x1D17F => 220
                    ,0x1D180 => 220, 0x1D181 => 220, 0x1D182 => 220, 0x1D18A => 220, 0x1D18B => 220
                    ,0x59A   => 222, 0x5AD   => 222, 0x1929  => 222, 0x302D  => 222, 0x302E  => 224
                    ,0x302F  => 224, 0x1D16D => 226, 0x5AE   => 228, 0x18A9  => 228, 0x302B  => 228
                    ,0x300   => 230, 0x301   => 230, 0x302   => 230, 0x303   => 230, 0x304   => 230
                    ,0x305   => 230, 0x306   => 230, 0x307   => 230, 0x308   => 230, 0x309   => 230
                    ,0x30A   => 230, 0x30B   => 230, 0x30C   => 230, 0x30D   => 230, 0x30E   => 230
                    ,0x30F   => 230, 0x310   => 230, 0x311   => 230, 0x312   => 230, 0x313   => 230
                    ,0x314   => 230, 0x33D   => 230, 0x33E   => 230, 0x33F   => 230, 0x340   => 230
                    ,0x341   => 230, 0x342   => 230, 0x343   => 230, 0x344   => 230, 0x346   => 230
                    ,0x34A   => 230, 0x34B   => 230, 0x34C   => 230, 0x350   => 230, 0x351   => 230
                    ,0x352   => 230, 0x357   => 230, 0x363   => 230, 0x364   => 230, 0x365   => 230
                    ,0x366   => 230, 0x367   => 230, 0x368   => 230, 0x369   => 230, 0x36A   => 230
                    ,0x36B   => 230, 0x36C   => 230, 0x36D   => 230, 0x36E   => 230, 0x36F   => 230
                    ,0x483   => 230, 0x484   => 230, 0x485   => 230, 0x486   => 230, 0x592   => 230
                    ,0x593   => 230, 0x594   => 230, 0x595   => 230, 0x597   => 230, 0x598   => 230
                    ,0x599   => 230, 0x59C   => 230, 0x59D   => 230, 0x59E   => 230, 0x59F   => 230
                    ,0x5A0   => 230, 0x5A1   => 230, 0x5A8   => 230, 0x5A9   => 230, 0x5AB   => 230
                    ,0x5AC   => 230, 0x5AF   => 230, 0x5C4   => 230, 0x610   => 230, 0x611   => 230
                    ,0x612   => 230, 0x613   => 230, 0x614   => 230, 0x615   => 230, 0x653   => 230
                    ,0x654   => 230, 0x657   => 230, 0x658   => 230, 0x6D6   => 230, 0x6D7   => 230
                    ,0x6D8   => 230, 0x6D9   => 230, 0x6DA   => 230, 0x6DB   => 230, 0x6DC   => 230
                    ,0x6DF   => 230, 0x6E0   => 230, 0x6E1   => 230, 0x6E2   => 230, 0x6E4   => 230
                    ,0x6E7   => 230, 0x6E8   => 230, 0x6EB   => 230, 0x6EC   => 230, 0x730   => 230
                    ,0x732   => 230, 0x733   => 230, 0x735   => 230, 0x736   => 230, 0x73A   => 230
                    ,0x73D   => 230, 0x73F   => 230, 0x740   => 230, 0x741   => 230, 0x743   => 230
                    ,0x745   => 230, 0x747   => 230, 0x749   => 230, 0x74A   => 230, 0x951   => 230
                    ,0x953   => 230, 0x954   => 230, 0xF82   => 230, 0xF83   => 230, 0xF86   => 230
                    ,0xF87   => 230, 0x170D  => 230, 0x193A  => 230, 0x20D0  => 230, 0x20D1  => 230
                    ,0x20D4  => 230, 0x20D5  => 230, 0x20D6  => 230, 0x20D7  => 230, 0x20DB  => 230
                    ,0x20DC  => 230, 0x20E1  => 230, 0x20E7  => 230, 0x20E9  => 230, 0xFE20  => 230
                    ,0xFE21  => 230, 0xFE22  => 230, 0xFE23  => 230, 0x1D185 => 230, 0x1D186 => 230
                    ,0x1D187 => 230, 0x1D189 => 230, 0x1D188 => 230, 0x1D1AA => 230, 0x1D1AB => 230
                    ,0x1D1AC => 230, 0x1D1AD => 230, 0x315   => 232, 0x31A   => 232, 0x302C  => 232
                    ,0x35F   => 233, 0x362   => 233, 0x35D   => 234, 0x35E   => 234, 0x360   => 234
                    ,0x361   => 234, 0x345   => 240
                    )
            );
}
?>
idna_convert/LICENCE000064400000063623152177723700010231 0ustar00                  GNU LESSER GENERAL PUBLIC LICENSE
                       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

                  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

                            NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

                     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!
idna_convert/transcode_wrapper.php000064400000011056152177723700013470 0ustar00<?php
/**
 * transcode wrapper functions
 * @package IDNA Convert
 * @subpackage charset transcoding
 * @author Matthias Sommerfeld, <mso@phlylabs.de>
 * @version 0.1.0
 */

/**
 * Convert a string from any of various encodings to UTF-8
 *
 * @param  string  String to encode
 *[@param  string  Encoding; Default: ISO-8859-1]
 *[@param  bool  Safe Mode: if set to TRUE, the original string is retunred on errors]
 * @return  string  The encoded string or false on failure
 * @since 0.0.1
 */
function encode_utf8($string = '', $encoding = 'iso-8859-1', $safe_mode = false)
{
    $safe = ($safe_mode) ? $string : false;
    if (strtoupper($encoding) == 'UTF-8' || strtoupper($encoding) == 'UTF8') {
        return $string;
    } elseif (strtoupper($encoding) == 'ISO-8859-1') {
        return utf8_encode($string);
    } elseif (strtoupper($encoding) == 'WINDOWS-1252') {
        return utf8_encode(map_w1252_iso8859_1($string));
    } elseif (strtoupper($encoding) == 'UNICODE-1-1-UTF-7') {
        $encoding = 'utf-7';
    }
    if (function_exists('mb_convert_encoding')) {
        $conv = @mb_convert_encoding($string, 'UTF-8', strtoupper($encoding));
        if ($conv) return $conv;
    }
    if (function_exists('iconv')) {
        $conv = @iconv(strtoupper($encoding), 'UTF-8', $string);
        if ($conv) return $conv;
    }
    if (function_exists('libiconv')) {
        $conv = @libiconv(strtoupper($encoding), 'UTF-8', $string);
        if ($conv) return $conv;
    }
    return $safe;
}

/**
 * Convert a string from UTF-8 to any of various encodings
 *
 * @param  string  String to decode
 *[@param  string  Encoding; Default: ISO-8859-1]
 *[@param  bool  Safe Mode: if set to TRUE, the original string is retunred on errors]
 * @return  string  The decoded string or false on failure
 * @since 0.0.1
 */
function decode_utf8($string = '', $encoding = 'iso-8859-1', $safe_mode = false)
{
    $safe = ($safe_mode) ? $string : false;
    if (!$encoding) $encoding = 'ISO-8859-1';
    if (strtoupper($encoding) == 'UTF-8' || strtoupper($encoding) == 'UTF8') {
        return $string;
    } elseif (strtoupper($encoding) == 'ISO-8859-1') {
        return utf8_decode($string);
    } elseif (strtoupper($encoding) == 'WINDOWS-1252') {
        return map_iso8859_1_w1252(utf8_decode($string));
    } elseif (strtoupper($encoding) == 'UNICODE-1-1-UTF-7') {
        $encoding = 'utf-7';
    }
    if (function_exists('mb_convert_encoding')) {
        $conv = @mb_convert_encoding($string, strtoupper($encoding), 'UTF-8');
        if ($conv) return $conv;
    }
    if (function_exists('iconv')) {
        $conv = @iconv('UTF-8', strtoupper($encoding), $string);
        if ($conv) return $conv;
    }
    if (function_exists('libiconv')) {
        $conv = @libiconv('UTF-8', strtoupper($encoding), $string);
        if ($conv) return $conv;
    }
    return $safe;
}

/**
 * Special treatment for our guys in Redmond
 * Windows-1252 is basically ISO-8859-1 -- with some exceptions, which get accounted for here
 * @param  string  Your input in Win1252
 * @param  string  The resulting ISO-8859-1 string
 * @since 3.0.8
 */
function map_w1252_iso8859_1($string = '')
{
    if ($string == '') return '';
    $return = '';
    for ($i = 0; $i < strlen($string); ++$i) {
        $c = ord($string{$i});
        switch ($c) {
            case 129: $return .= chr(252); break;
            case 132: $return .= chr(228); break;
            case 142: $return .= chr(196); break;
            case 148: $return .= chr(246); break;
            case 153: $return .= chr(214); break;
            case 154: $return .= chr(220); break;
            case 225: $return .= chr(223); break;
            default: $return .= chr($c); break;
        }
    }
    return $return;
}

/**
 * Special treatment for our guys in Redmond
 * Windows-1252 is basically ISO-8859-1 -- with some exceptions, which get accounted for here
 * @param  string  Your input in ISO-8859-1
 * @param  string  The resulting Win1252 string
 * @since 3.0.8
 */
function map_iso8859_1_w1252($string = '')
{
    if ($string == '') return '';
    $return = '';
    for ($i = 0; $i < strlen($string); ++$i) {
        $c = ord($string{$i});
        switch ($c) {
            case 196: $return .= chr(142); break;
            case 214: $return .= chr(153); break;
            case 220: $return .= chr(154); break;
            case 223: $return .= chr(225); break;
            case 228: $return .= chr(132); break;
            case 246: $return .= chr(148); break;
            case 252: $return .= chr(129); break;
            default: $return .= chr($c); break;
        }
    }
    return $return;
}

?>import.legacy.php000064400000005437152177723700010056 0ustar00<?php
/**
 * Bootstrap file for the Joomla Platform [with legacy libraries].  Including this file into your application
 * will make Joomla Platform libraries [including legacy libraries] available for use.
 *
 * @package    Joomla.Platform
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// Set the platform root path as a constant if necessary.
if (!defined('JPATH_PLATFORM'))
{
	define('JPATH_PLATFORM', __DIR__);
}

// Detect the native operating system type.
$os = strtoupper(substr(PHP_OS, 0, 3));

if (!defined('IS_WIN'))
{
	define('IS_WIN', $os === 'WIN');
}

if (!defined('IS_UNIX'))
{
	define('IS_UNIX', $os !== 'MAC' && $os !== 'WIN');
}

/**
 * @deprecated 4.0	Use IS_UNIX instead
 */
if (!defined('IS_MAC'))
{
	define('IS_MAC', IS_UNIX === true && ($os === 'DAR' || $os === 'MAC'));
}

// Import the library loader if necessary.
if (!class_exists('JLoader'))
{
	require_once JPATH_PLATFORM . '/loader.php';
}

// Make sure that the Joomla Loader has been successfully loaded.
if (!class_exists('JLoader'))
{
	throw new RuntimeException('Joomla Loader not loaded.');
}

// Setup the autoloaders.
JLoader::setup();

JLoader::registerPrefix('J', JPATH_PLATFORM . '/legacy');

// Check if the JsonSerializable interface exists already
if (!interface_exists('JsonSerializable'))
{
	JLoader::register('JsonSerializable', JPATH_PLATFORM . '/vendor/joomla/compat/src/JsonSerializable.php');
}

// Add deprecated constants
// @deprecated 4.0
define('JPATH_ISWIN', IS_WIN);
define('JPATH_ISMAC', IS_MAC);

/**
 * Mask for the raw routing mode
 *
 * @deprecated  4.0
 */
const JROUTER_MODE_RAW = 0;

/**
 * Mask for the SEF routing mode
 *
 * @deprecated  4.0
 */
const JROUTER_MODE_SEF = 1;

// Register the PasswordHash lib
JLoader::register('PasswordHash', JPATH_PLATFORM . '/phpass/PasswordHash.php');

// Register classes where the names have been changed to fit the autoloader rules
// @deprecated  4.0
JLoader::register('JSimpleCrypt', JPATH_PLATFORM . '/legacy/simplecrypt/simplecrypt.php');
JLoader::register('JTree', JPATH_PLATFORM . '/legacy/base/tree.php');
JLoader::register('JNode', JPATH_PLATFORM . '/legacy/base/node.php');
JLoader::register('JObserver', JPATH_PLATFORM . '/legacy/base/observer.php');
JLoader::register('JObservable', JPATH_PLATFORM . '/legacy/base/observable.php');
JLoader::register('LogException', JPATH_PLATFORM . '/legacy/log/logexception.php');
JLoader::register('JXMLElement', JPATH_PLATFORM . '/legacy/utilities/xmlelement.php');
JLoader::register('JCli', JPATH_PLATFORM . '/legacy/application/cli.php');
JLoader::register('JDaemon', JPATH_PLATFORM . '/legacy/application/daemon.php');
JLoader::register('JApplication', JPATH_LIBRARIES . '/legacy/application/application.php');
phputf8/substr_replace.php000064400000000560152177723700011704 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/substr_replace.php';
phputf8/LICENSE000064400000063476152177723700007202 0ustar00		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

			    Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

		  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

			    NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

		     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!


phputf8/utf8.php000064400000000546152177723700007561 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utf8.php';
phputf8/trim.php000064400000000546152177723700007646 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/trim.php';
phputf8/ord.php000064400000000545152177723700007456 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/ord.php';
phputf8/str_ireplace.php000064400000000556152177723700011350 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/str_ireplace.php';
phputf8/mbstring/core.php000064400000000570152177723700011445 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/mbstring/core.php';
phputf8/utils/specials.php000064400000000571152177723700011634 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/specials.php';
phputf8/utils/unicode.php000064400000000570152177723700011456 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/unicode.php';
phputf8/utils/bad.php000064400000000564152177723700010561 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/bad.php';
phputf8/utils/ascii.php000064400000000566152177723700011125 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/ascii.php';
phputf8/utils/position.php000064400000000571152177723700011675 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/position.php';
phputf8/utils/validation.php000064400000000573152177723700012165 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/validation.php';
phputf8/utils/patterns.php000064400000000571152177723700011671 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/utils/patterns.php';
phputf8/README000064400000006452152177723700007044 0ustar00++PHP UTF-8++

Version 0.5

++DOCUMENTATION++

Documentation in progress in ./docs dir

http://www.phpwact.org/php/i18n/charsets
http://www.phpwact.org/php/i18n/utf-8

Important Note: DO NOT use these functions without understanding WHY
you are using them. In particular, do not blindly replace all use of PHP's
string functions which functions found here - most of the time you will
not need to, and you will be introducing a significant performance
overhead to your application. You can get a good idea of when to use what
from reading: http://www.phpwact.org/php/i18n/utf-8

Important Note: For sake of performance most of the functions here are
not "defensive" (e.g. there is not extensive parameter checking, well
formed UTF-8 is assumed). This is particularily relevant when is comes to
catching badly formed UTF-8 - you should screen input on the "outer
perimeter" with help from functions in the utf8_validation.php and
utf8_bad.php files.

Important Note: this library treats ALL ASCII characters as valid, including ASCII control characters. But if you use some ASCII control characters in XML, it will render the XML ill-formed. Don't be a bozo: http://hsivonen.iki.fi/producing-xml/#controlchar

++BUGS / SUPPORT / FEATURE REQUESTS ++

Please report bugs to:
http://sourceforge.net/tracker/?group_id=142846&atid=753842
- if you are able, please submit a failing unit test
(http://www.lastcraft.com/simple_test.php) with your bug report.

For feature requests / faster implementation of functions found here,
please drop them in via the RFE tracker: http://sourceforge.net/tracker/?group_id=142846&atid=753845
Particularily interested in faster implementations!

For general support / help, use:
http://sourceforge.net/tracker/?group_id=142846&atid=753843

In the VERY WORST case, you can email me: hfuecks gmail com - I tend to be slow to respond though so be warned.

Important Note: when reporting bugs, please provide the following
information;

PHP version, whether the iconv extension is loaded (in PHP5 it's
there by default), whether the mbstring extension is loaded. The
following PHP script can be used to determine this information;

<?php
print "PHP Version: " .phpversion()."<br>";
if ( extension_loaded('mbstring') ) {
    print "mbstring available<br>";
} else {
    print "mbstring not available<br>";
}
if ( extension_loaded('iconv') ) {
    print "iconv available<br>";
} else {
    print "iconv not available<br>";
}
?>

++LICENSING++

Parts of the code in this library come from other places, under different
licenses.
The authors involved have been contacted (see below). Attribution for
which code came from elsewhere can be found in the source code itself.

+Andreas Gohr / Chris Smith - Dokuwiki
There is a fair degree of collaboration / exchange of ideas and code
beteen Dokuwiki's UTF-8 library;
http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
and phputf8. Although Dokuwiki is released under GPL, its UTF-8
library is released under LGPL, hence no conflict with phputf8

+Henri Sivonen (http://hsivonen.iki.fi/php-utf8/ /
http://hsivonen.iki.fi/php-utf8/) has also given permission for his
code to be released under the terms of the LGPL. He ported a Unicode / UTF-8
converter from the Mozilla codebase to PHP, which is re-used in phputf8
phputf8/ucwords.php000064400000000551152177723700010355 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/ucwords.php';
phputf8/stristr.php000064400000000551152177723700010401 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/stristr.php';
phputf8/str_split.php000064400000000553152177723700010714 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/str_split.php';
phputf8/strcspn.php000064400000000551152177723700010363 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/strcspn.php';
phputf8/str_pad.php000064400000000551152177723700010323 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/str_pad.php';
phputf8/native/core.php000064400000000566152177723700011113 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			dirname(__DIR__),
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/native/core.php';
phputf8/strrev.php000064400000000550152177723700010213 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/strrev.php';
phputf8/ucfirst.php000064400000000551152177723700010346 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/ucfirst.php';
phputf8/strcasecmp.php000064400000000554152177723700011036 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/strcasecmp.php';
phputf8/strspn.php000064400000000550152177723700010217 0ustar00<?php
if (class_exists('JLog'))
{
	JLog::add(
		sprintf(
			'Using the phputf8 library through files located in %1$s is deprecated, load the files from %2$s instead.',
			__DIR__,
			JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8'
		),
		JLog::WARNING,
		'deprecated'
	);
}

require_once JPATH_LIBRARIES . '/vendor/joomla/string/src/phputf8/strspn.php';
cms.php000064400000010400152177723700006045 0ustar00<?php
/**
 * @package    Joomla.Libraries
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

// Set the platform root path as a constant if necessary.
if (!defined('JPATH_PLATFORM'))
{
	define('JPATH_PLATFORM', __DIR__);
}

// Import the library loader if necessary.
if (!class_exists('JLoader'))
{
	require_once JPATH_PLATFORM . '/loader.php';
}

// Make sure that the Joomla Platform has been successfully loaded.
if (!class_exists('JLoader'))
{
	throw new RuntimeException('Joomla Platform not loaded.');
}

// Register the library base path for CMS libraries.
JLoader::registerPrefix('J', JPATH_PLATFORM . '/cms', false, true);

/** @note This will be loaded through composer in Joomla 4 */
JLoader::registerNamespace('Joomla\\CMS', JPATH_PLATFORM . '/src', false, false, 'psr4');

// Create the Composer autoloader
$loader = require JPATH_LIBRARIES . '/vendor/autoload.php';
$loader->unregister();

// Decorate Composer autoloader
spl_autoload_register(array(new JClassLoader($loader), 'loadClass'), true, true);

// Register the class aliases for Framework classes that have replaced their Platform equivilents
require_once JPATH_LIBRARIES . '/classmap.php';

// Suppress phar stream wrapper for non .phar files
$behavior = new \TYPO3\PharStreamWrapper\Behavior;
\TYPO3\PharStreamWrapper\Manager::initialize(
	$behavior->withAssertion(new \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor)
);

if (in_array('phar', stream_get_wrappers()))
{
	stream_wrapper_unregister('phar');
	stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
}

// Define the Joomla version if not already defined.
if (!defined('JVERSION'))
{
	$jversion = new JVersion;
	define('JVERSION', $jversion->getShortVersion());
}

// Ensure FOF autoloader included - needed for things like content versioning where we need to get an FOFTable Instance
if (!class_exists('FOFAutoloaderFof'))
{
	include_once JPATH_LIBRARIES . '/fof/include.php';
}

// Register a handler for uncaught exceptions that shows a pretty error page when possible
set_exception_handler(array('JErrorPage', 'render'));

// Set up the message queue logger for web requests
if (array_key_exists('REQUEST_METHOD', $_SERVER))
{
	JLog::addLogger(array('logger' => 'messagequeue'), JLog::ALL, array('jerror'));
}

// Register JArrayHelper due to JRegistry moved to composer's vendor folder
JLoader::register('JArrayHelper', JPATH_PLATFORM . '/joomla/utilities/arrayhelper.php');

// Register the Crypto lib
JLoader::register('Crypto', JPATH_PLATFORM . '/php-encryption/Crypto.php');

// Register classes where the names have been changed to fit the autoloader rules
// @deprecated  4.0
JLoader::register('JInstallerComponent',  JPATH_PLATFORM . '/cms/installer/adapter/component.php');
JLoader::register('JInstallerFile',  JPATH_PLATFORM . '/cms/installer/adapter/file.php');
JLoader::register('JInstallerLanguage',  JPATH_PLATFORM . '/cms/installer/adapter/language.php');
JLoader::register('JInstallerLibrary',  JPATH_PLATFORM . '/cms/installer/adapter/library.php');
JLoader::register('JInstallerModule',  JPATH_PLATFORM . '/cms/installer/adapter/module.php');
JLoader::register('JInstallerPackage',  JPATH_PLATFORM . '/cms/installer/adapter/package.php');
JLoader::register('JInstallerPlugin',  JPATH_PLATFORM . '/cms/installer/adapter/plugin.php');
JLoader::register('JInstallerTemplate',  JPATH_PLATFORM . '/cms/installer/adapter/template.php');
JLoader::register('JExtension',  JPATH_PLATFORM . '/cms/installer/extension.php');
JLoader::registerAlias('JAdministrator',  'JApplicationAdministrator');
JLoader::registerAlias('JSite',  'JApplicationSite');

// Can be removed when the move of all core fields to namespace is finished
\Joomla\CMS\Form\FormHelper::addFieldPath(JPATH_LIBRARIES . '/joomla/form/fields');
\Joomla\CMS\Form\FormHelper::addRulePath(JPATH_LIBRARIES . '/joomla/form/rule');
\Joomla\CMS\Form\FormHelper::addRulePath(JPATH_LIBRARIES . '/joomla/form/rules');
\Joomla\CMS\Form\FormHelper::addFormPath(JPATH_LIBRARIES . '/joomla/form/forms');
\Joomla\CMS\Form\FormHelper::addFieldPath(JPATH_LIBRARIES . '/cms/form/field');
\Joomla\CMS\Form\FormHelper::addRulePath(JPATH_LIBRARIES . '/cms/form/rule');
import.php000064400000003021152177723700006576 0ustar00<?php
/**
 * Bootstrap file for the Joomla Platform.  Including this file into your application will make Joomla
 * Platform libraries available for use.
 *
 * @package    Joomla.Platform
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// Set the platform root path as a constant if necessary.
if (!defined('JPATH_PLATFORM'))
{
	define('JPATH_PLATFORM', __DIR__);
}

// Detect the native operating system type.
$os = strtoupper(substr(PHP_OS, 0, 3));

if (!defined('IS_WIN'))
{
	define('IS_WIN', $os === 'WIN');
}

if (!defined('IS_UNIX'))
{
	define('IS_UNIX', IS_WIN === false);
}

// Import the library loader if necessary.
if (!class_exists('JLoader'))
{
	require_once JPATH_PLATFORM . '/loader.php';
}

// Make sure that the Joomla Platform has been successfully loaded.
if (!class_exists('JLoader'))
{
	throw new RuntimeException('Joomla Platform not loaded.');
}

// Setup the autoloaders.
JLoader::setup();

// Check if the JsonSerializable interface exists already
if (!interface_exists('JsonSerializable'))
{
	JLoader::register('JsonSerializable', JPATH_PLATFORM . '/vendor/joomla/compat/src/JsonSerializable.php');
}

// Register the PasswordHash lib
JLoader::register('PasswordHash', JPATH_PLATFORM . '/phpass/PasswordHash.php');

/**
 * Mask for the raw routing mode
 *
 * @deprecated  4.0
 */
const JROUTER_MODE_RAW = 0;

/**
 * Mask for the SEF routing mode
 *
 * @deprecated  4.0
 */
const JROUTER_MODE_SEF = 1;
loader.php000064400000054460152177723700006547 0ustar00<?php
/**
 * @package    Joomla.Platform
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Static class to handle loading of libraries.
 *
 * @package  Joomla.Platform
 * @since    1.7.0
 */
abstract class JLoader
{
	/**
	 * Container for already imported library paths.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $classes = array();

	/**
	 * Container for already imported library paths.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $imported = array();

	/**
	 * Container for registered library class prefixes and path lookups.
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected static $prefixes = array();

	/**
	 * Holds proxy classes and the class names the proxy.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $classAliases = array();

	/**
	 * Holds the inverse lookup for proxy classes and the class names the proxy.
	 *
	 * @var    array
	 * @since  3.4
	 */
	protected static $classAliasesInverse = array();

	/**
	 * Container for namespace => path map.
	 *
	 * @var    array
	 * @since  3.1.4
	 */
	protected static $namespaces = array('psr0' => array(), 'psr4' => array());

	/**
	 * Holds a reference for all deprecated aliases (mainly for use by a logging platform).
	 *
	 * @var    array
	 * @since  3.6.3
	 */
	protected static $deprecatedAliases = array();

	/**
	 * Method to discover classes of a given type in a given path.
	 *
	 * @param   string   $classPrefix  The class name prefix to use for discovery.
	 * @param   string   $parentPath   Full path to the parent folder for the classes to discover.
	 * @param   boolean  $force        True to overwrite the autoload path value for the class if it already exists.
	 * @param   boolean  $recurse      Recurse through all child directories as well as the parent path.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function discover($classPrefix, $parentPath, $force = true, $recurse = false)
	{
		try
		{
			if ($recurse)
			{
				$iterator = new RecursiveIteratorIterator(
					new RecursiveDirectoryIterator($parentPath),
					RecursiveIteratorIterator::SELF_FIRST
				);
			}
			else
			{
				$iterator = new DirectoryIterator($parentPath);
			}

			/** @type  $file  DirectoryIterator */
			foreach ($iterator as $file)
			{
				$fileName = $file->getFilename();

				// Only load for php files.
				if ($file->isFile() && $file->getExtension() === 'php')
				{
					// Get the class name and full path for each file.
					$class = strtolower($classPrefix . preg_replace('#\.php$#', '', $fileName));

					// Register the class with the autoloader if not already registered or the force flag is set.
					if ($force || empty(self::$classes[$class]))
					{
						self::register($class, $file->getPath() . '/' . $fileName);
					}
				}
			}
		}
		catch (UnexpectedValueException $e)
		{
			// Exception will be thrown if the path is not a directory. Ignore it.
		}
	}

	/**
	 * Method to get the list of registered classes and their respective file paths for the autoloader.
	 *
	 * @return  array  The array of class => path values for the autoloader.
	 *
	 * @since   1.7.0
	 */
	public static function getClassList()
	{
		return self::$classes;
	}

	/**
	 * Method to get the list of deprecated class aliases.
	 *
	 * @return  array  An associative array with deprecated class alias data.
	 *
	 * @since   3.6.3
	 */
	public static function getDeprecatedAliases()
	{
		return self::$deprecatedAliases;
	}

	/**
	 * Method to get the list of registered namespaces.
	 *
	 * @param   string  $type  Defines the type of namespace, can be prs0 or psr4.
	 *
	 * @return  array  The array of namespace => path values for the autoloader.
	 *
	 * @since   3.1.4
	 */
	public static function getNamespaces($type = 'psr0')
	{
		if ($type !== 'psr0' && $type !== 'psr4')
		{
			throw new InvalidArgumentException('Type needs to be prs0 or psr4!');
		}

		return self::$namespaces[$type];
	}

	/**
	 * Loads a class from specified directories.
	 *
	 * @param   string  $key   The class name to look for (dot notation).
	 * @param   string  $base  Search this directory for the class.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public static function import($key, $base = null)
	{
		// Only import the library if not already attempted.
		if (!isset(self::$imported[$key]))
		{
			// Setup some variables.
			$success = false;
			$parts   = explode('.', $key);
			$class   = array_pop($parts);
			$base    = (!empty($base)) ? $base : __DIR__;
			$path    = str_replace('.', DIRECTORY_SEPARATOR, $key);

			// Handle special case for helper classes.
			if ($class === 'helper')
			{
				$class = ucfirst(array_pop($parts)) . ucfirst($class);
			}
			// Standard class.
			else
			{
				$class = ucfirst($class);
			}

			// If we are importing a library from the Joomla namespace set the class to autoload.
			if (strpos($path, 'joomla') === 0)
			{
				// Since we are in the Joomla namespace prepend the classname with J.
				$class = 'J' . $class;

				// Only register the class for autoloading if the file exists.
				if (is_file($base . '/' . $path . '.php'))
				{
					self::$classes[strtolower($class)] = $base . '/' . $path . '.php';
					$success = true;
				}
			}
			/*
			 * If we are not importing a library from the Joomla namespace directly include the
			 * file since we cannot assert the file/folder naming conventions.
			 */
			else
			{
				// If the file exists attempt to include it.
				if (is_file($base . '/' . $path . '.php'))
				{
					$success = (bool) include_once $base . '/' . $path . '.php';
				}
			}

			// Add the import key to the memory cache container.
			self::$imported[$key] = $success;
		}

		return self::$imported[$key];
	}

	/**
	 * Load the file for a class.
	 *
	 * @param   string  $class  The class to be loaded.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function load($class)
	{
		// Sanitize class name.
		$key = strtolower($class);

		// If the class already exists do nothing.
		if (class_exists($class, false))
		{
			return true;
		}

		// If the class is registered include the file.
		if (isset(self::$classes[$key]))
		{
			$found = (bool) include_once self::$classes[$key];

			if ($found)
			{
				self::loadAliasFor($class);
			}

			// If the class doesn't exists, we probably have a class alias available
			if (!class_exists($class, false))
			{
				// Search the alias class, first none namespaced and then namespaced
				$original = array_search($class, self::$classAliases) ? : array_search('\\' . $class, self::$classAliases);

				// When we have an original and the class exists an alias should be created
				if ($original && class_exists($original, false))
				{
					class_alias($original, $class);
				}
			}

			return true;
		}

		return false;
	}

	/**
	 * Directly register a class to the autoload list.
	 *
	 * @param   string   $class  The class name to register.
	 * @param   string   $path   Full path to the file that holds the class to register.
	 * @param   boolean  $force  True to overwrite the autoload path value for the class if it already exists.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function register($class, $path, $force = true)
	{
		// When an alias exists, register it as well
		if (key_exists(strtolower($class), self::$classAliases))
		{
			self::register(self::stripFirstBackslash(self::$classAliases[strtolower($class)]), $path, $force);
		}

		// Sanitize class name.
		$class = strtolower($class);

		// Only attempt to register the class if the name and file exist.
		if (!empty($class) && is_file($path))
		{
			// Register the class with the autoloader if not already registered or the force flag is set.
			if ($force || empty(self::$classes[$class]))
			{
				self::$classes[$class] = $path;
			}
		}
	}

	/**
	 * Register a class prefix with lookup path.  This will allow developers to register library
	 * packages with different class prefixes to the system autoloader.  More than one lookup path
	 * may be registered for the same class prefix, but if this method is called with the reset flag
	 * set to true then any registered lookups for the given prefix will be overwritten with the current
	 * lookup path. When loaded, prefix paths are searched in a "last in, first out" order.
	 *
	 * @param   string   $prefix   The class prefix to register.
	 * @param   string   $path     Absolute file path to the library root where classes with the given prefix can be found.
	 * @param   boolean  $reset    True to reset the prefix with only the given lookup path.
	 * @param   boolean  $prepend  If true, push the path to the beginning of the prefix lookup paths array.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 *
	 * @since   3.0.0
	 */
	public static function registerPrefix($prefix, $path, $reset = false, $prepend = false)
	{
		// Verify the library path exists.
		if (!file_exists($path))
		{
			$path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);

			throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
		}

		// If the prefix is not yet registered or we have an explicit reset flag then set set the path.
		if ($reset || !isset(self::$prefixes[$prefix]))
		{
			self::$prefixes[$prefix] = array($path);
		}
		// Otherwise we want to simply add the path to the prefix.
		else
		{
			if ($prepend)
			{
				array_unshift(self::$prefixes[$prefix], $path);
			}
			else
			{
				self::$prefixes[$prefix][] = $path;
			}
		}
	}

	/**
	 * Offers the ability for "just in time" usage of `class_alias()`.
	 * You cannot overwrite an existing alias.
	 *
	 * @param   string          $alias     The alias name to register.
	 * @param   string          $original  The original class to alias.
	 * @param   string|boolean  $version   The version in which the alias will no longer be present.
	 *
	 * @return  boolean  True if registration was successful. False if the alias already exists.
	 *
	 * @since   3.2
	 */
	public static function registerAlias($alias, $original, $version = false)
	{
		// PHP is case insensitive so support all kind of alias combination
		$lowercasedAlias = strtolower($alias);

		if (!isset(self::$classAliases[$lowercasedAlias]))
		{
			self::$classAliases[$lowercasedAlias] = $original;

			$original = self::stripFirstBackslash($original);

			if (!isset(self::$classAliasesInverse[$original]))
			{
				self::$classAliasesInverse[$original] = array($lowercasedAlias);
			}
			else
			{
				self::$classAliasesInverse[$original][] = $lowercasedAlias;
			}

			// If given a version, log this alias as deprecated
			if ($version)
			{
				self::$deprecatedAliases[] = array('old' => $alias, 'new' => $original, 'version' => $version);
			}

			return true;
		}

		return false;
	}

	/**
	 * Register a namespace to the autoloader. When loaded, namespace paths are searched in a "last in, first out" order.
	 *
	 * @param   string   $namespace  A case sensitive Namespace to register.
	 * @param   string   $path       A case sensitive absolute file path to the library root where classes of the given namespace can be found.
	 * @param   boolean  $reset      True to reset the namespace with only the given lookup path.
	 * @param   boolean  $prepend    If true, push the path to the beginning of the namespace lookup paths array.
	 * @param   string   $type       Defines the type of namespace, can be prs0 or psr4.
	 *
	 * @return  void
	 *
	 * @throws  RuntimeException
	 *
	 * @note    The default argument of $type will be changed in J4 to be 'psr4'
	 * @since   3.1.4
	 */
	public static function registerNamespace($namespace, $path, $reset = false, $prepend = false, $type = 'psr0')
	{
		if ($type !== 'psr0' && $type !== 'psr4')
		{
			throw new InvalidArgumentException('Type needs to be prs0 or psr4!');
		}

		// Verify the library path exists.
		if (!file_exists($path))
		{
			$path = (str_replace(JPATH_ROOT, '', $path) == $path) ? basename($path) : str_replace(JPATH_ROOT, '', $path);

			throw new RuntimeException('Library path ' . $path . ' cannot be found.', 500);
		}

		// Trim leading and trailing backslashes from namespace, allowing "\Parent\Child", "Parent\Child\" and "\Parent\Child\" to be treated the same way.
		$namespace = trim($namespace, '\\');

		// If the namespace is not yet registered or we have an explicit reset flag then set the path.
		if ($reset || !isset(self::$namespaces[$type][$namespace]))
		{
			self::$namespaces[$type][$namespace] = array($path);
		}

		// Otherwise we want to simply add the path to the namespace.
		else
		{
			if ($prepend)
			{
				array_unshift(self::$namespaces[$type][$namespace], $path);
			}
			else
			{
				self::$namespaces[$type][$namespace][] = $path;
			}
		}
	}

	/**
	 * Method to setup the autoloaders for the Joomla Platform.
	 * Since the SPL autoloaders are called in a queue we will add our explicit
	 * class-registration based loader first, then fall back on the autoloader based on conventions.
	 * This will allow people to register a class in a specific location and override platform libraries
	 * as was previously possible.
	 *
	 * @param   boolean  $enablePsr       True to enable autoloading based on PSR-0.
	 * @param   boolean  $enablePrefixes  True to enable prefix based class loading (needed to auto load the Joomla core).
	 * @param   boolean  $enableClasses   True to enable class map based class loading (needed to auto load the Joomla core).
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public static function setup($enablePsr = true, $enablePrefixes = true, $enableClasses = true)
	{
		if ($enableClasses)
		{
			// Register the class map based autoloader.
			spl_autoload_register(array('JLoader', 'load'));
		}

		if ($enablePrefixes)
		{
			// Register the J prefix and base path for Joomla platform libraries.
			self::registerPrefix('J', JPATH_PLATFORM . '/joomla');

			// Register the prefix autoloader.
			spl_autoload_register(array('JLoader', '_autoload'));
		}

		if ($enablePsr)
		{
			// Register the PSR based autoloader.
			spl_autoload_register(array('JLoader', 'loadByPsr0'));
			spl_autoload_register(array('JLoader', 'loadByPsr4'));
			spl_autoload_register(array('JLoader', 'loadByAlias'));
		}
	}

	/**
	 * Method to autoload classes that are namespaced to the PSR-4 standard.
	 *
	 * @param   string  $class  The fully qualified class name to autoload.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.7.0
	 */
	public static function loadByPsr4($class)
	{
		$class = self::stripFirstBackslash($class);

		// Find the location of the last NS separator.
		$pos = strrpos($class, '\\');

		// If one is found, we're dealing with a NS'd class.
		if ($pos !== false)
		{
			$classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR;
			$className = substr($class, $pos + 1);
		}
		// If not, no need to parse path.
		else
		{
			$classPath = null;
			$className = $class;
		}

		$classPath .= $className . '.php';

		// Loop through registered namespaces until we find a match.
		foreach (self::$namespaces['psr4'] as $ns => $paths)
		{
			if (strpos($class, "{$ns}\\") === 0)
			{
				$nsPath = trim(str_replace('\\', DIRECTORY_SEPARATOR, $ns), DIRECTORY_SEPARATOR);

				// Loop through paths registered to this namespace until we find a match.
				foreach ($paths as $path)
				{
					$classFilePath = realpath($path . DIRECTORY_SEPARATOR . substr_replace($classPath, '', 0, strlen($nsPath) + 1));

					// We do not allow files outside the namespace root to be loaded
					if (strpos($classFilePath, realpath($path)) !== 0)
					{
						continue;
					}

					// We check for class_exists to handle case-sensitive file systems
					if (file_exists($classFilePath) && !class_exists($class, false))
					{
						$found = (bool) include_once $classFilePath;

						if ($found)
						{
							self::loadAliasFor($class);
						}

						return $found;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Method to autoload classes that are namespaced to the PSR-0 standard.
	 *
	 * @param   string  $class  The fully qualified class name to autoload.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.2.0
	 *
	 * @deprecated 4.0 this method will be removed
	 */
	public static function loadByPsr0($class)
	{
		$class = self::stripFirstBackslash($class);

		// Find the location of the last NS separator.
		$pos = strrpos($class, '\\');

		// If one is found, we're dealing with a NS'd class.
		if ($pos !== false)
		{
			$classPath = str_replace('\\', DIRECTORY_SEPARATOR, substr($class, 0, $pos)) . DIRECTORY_SEPARATOR;
			$className = substr($class, $pos + 1);
		}
		// If not, no need to parse path.
		else
		{
			$classPath = null;
			$className = $class;
		}

		$classPath .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

		// Loop through registered namespaces until we find a match.
		foreach (self::$namespaces['psr0'] as $ns => $paths)
		{
			if (strpos($class, $ns) === 0)
			{
				// Loop through paths registered to this namespace until we find a match.
				foreach ($paths as $path)
				{
					$classFilePath = realpath($path . DIRECTORY_SEPARATOR . $classPath);

					// We do not allow files outside the namespace root to be loaded
					if (strpos($classFilePath, realpath($path)) !== 0)
					{
						continue;
					}

					// We check for class_exists to handle case-sensitive file systems
					if (file_exists($classFilePath) && !class_exists($class, false))
					{
						$found = (bool) include_once $classFilePath;

						if ($found)
						{
							self::loadAliasFor($class);
						}

						return $found;
					}
				}
			}
		}

		return false;
	}

	/**
	 * Method to autoload classes that have been aliased using the registerAlias method.
	 *
	 * @param   string  $class  The fully qualified class name to autoload.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.2
	 */
	public static function loadByAlias($class)
	{
		$class = strtolower(self::stripFirstBackslash($class));

		if (isset(self::$classAliases[$class]))
		{
			// Force auto-load of the regular class
			class_exists(self::$classAliases[$class], true);

			// Normally this shouldn't execute as the autoloader will execute applyAliasFor when the regular class is
			// auto-loaded above.
			if (!class_exists($class, false) && !interface_exists($class, false))
			{
				class_alias(self::$classAliases[$class], $class);
			}
		}
	}

	/**
	 * Applies a class alias for an already loaded class, if a class alias was created for it.
	 *
	 * @param   string  $class  We'll look for and register aliases for this (real) class name
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public static function applyAliasFor($class)
	{
		$class = self::stripFirstBackslash($class);

		if (isset(self::$classAliasesInverse[$class]))
		{
			foreach (self::$classAliasesInverse[$class] as $alias)
			{
				class_alias($class, $alias);
			}
		}
	}

	/**
	 * Autoload a class based on name.
	 *
	 * @param   string  $class  The class to be loaded.
	 *
	 * @return  boolean  True if the class was loaded, false otherwise.
	 *
	 * @since   1.7.3
	 */
	public static function _autoload($class)
	{
		foreach (self::$prefixes as $prefix => $lookup)
		{
			$chr = strlen($prefix) < strlen($class) ? $class[strlen($prefix)] : 0;

			if (strpos($class, $prefix) === 0 && ($chr === strtoupper($chr)))
			{
				return self::_load(substr($class, strlen($prefix)), $lookup);
			}
		}

		return false;
	}

	/**
	 * Load a class based on name and lookup array.
	 *
	 * @param   string  $class   The class to be loaded (without prefix).
	 * @param   array   $lookup  The array of base paths to use for finding the class file.
	 *
	 * @return  boolean  True if the class was loaded, false otherwise.
	 *
	 * @since   3.0.0
	 */
	private static function _load($class, $lookup)
	{
		// Split the class name into parts separated by camelCase.
		$parts = preg_split('/(?<=[a-z0-9])(?=[A-Z])/x', $class);
		$partsCount = count($parts);

		foreach ($lookup as $base)
		{
			// Generate the path based on the class name parts.
			$path = realpath($base . '/' . implode('/', array_map('strtolower', $parts)) . '.php');

			// Load the file if it exists and is in the lookup path.
			if (strpos($path, realpath($base)) === 0 && file_exists($path))
			{
				$found = (bool) include_once $path;

				if ($found)
				{
					self::loadAliasFor($class);
				}

				return $found;
			}

			// Backwards compatibility patch

			// If there is only one part we want to duplicate that part for generating the path.
			if ($partsCount === 1)
			{
				// Generate the path based on the class name parts.
				$path = realpath($base . '/' . implode('/', array_map('strtolower', array($parts[0], $parts[0]))) . '.php');

				// Load the file if it exists and is in the lookup path.
				if (strpos($path, realpath($base)) === 0 && file_exists($path))
				{
					$found = (bool) include_once $path;

					if ($found)
					{
						self::loadAliasFor($class);
					}

					return $found;
				}
			}
		}

		return false;
	}

	/**
	 * Loads the aliases for the given class.
	 *
	 * @param   string  $class  The class.
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	private static function loadAliasFor($class)
	{
		if (!key_exists($class, self::$classAliasesInverse))
		{
			return;
		}

		foreach (self::$classAliasesInverse[$class] as $alias)
		{
			// Force auto-load of the alias class
			class_exists($alias, true);
		}
	}

	/**
	 * Strips the first backslash from the given class if present.
	 *
	 * @param   string  $class  The class to strip the first prefix from.
	 *
	 * @return  string  The striped class name.
	 *
	 * @since   3.8.0
	 */
	private static function stripFirstBackslash($class)
	{
		return $class && $class[0] === '\\' ? substr($class, 1) : $class;
	}
}

// Check if jexit is defined first (our unit tests mock this)
if (!function_exists('jexit'))
{
	/**
	 * Global application exit.
	 *
	 * This function provides a single exit point for the platform.
	 *
	 * @param   mixed  $message  Exit code or string. Defaults to zero.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.7.0
	 */
	function jexit($message = 0)
	{
		exit($message);
	}
}

/**
 * Intelligent file importer.
 *
 * @param   string  $path  A dot syntax path.
 * @param   string  $base  Search this directory for the class.
 *
 * @return  boolean  True on success.
 *
 * @since   1.7.0
 */
function jimport($path, $base = null)
{
	return JLoader::import($path, $base);
}
regularlabs/script.install.helper.php000064400000053061152177723700014027 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

class RegularLabsInstallerScriptHelper
{
	public $name              = '';
	public $alias             = '';
	public $extname           = '';
	public $extension_type    = '';
	public $plugin_folder     = 'system';
	public $module_position   = 'status';
	public $client_id         = 1;
	public $install_type      = 'install';
	public $show_message      = true;
	public $db                = null;
	public $softbreak         = null;
	public $installed_version = '';

	public function __construct(&$params)
	{
		$this->extname = $this->extname ?: $this->alias;
		$this->db      = JFactory::getDbo();
	}

	public function preflight($route, JAdapterInstance $adapter)
	{
		if ( ! in_array($route, ['install', 'update']))
		{
			return true;
		}

		JFactory::getLanguage()->load('plg_system_regularlabsinstaller', JPATH_PLUGINS . '/system/regularlabsinstaller');

		$this->installed_version = $this->getVersion($this->getInstalledXMLFile());

		if ($this->show_message && $this->isInstalled())
		{
			$this->install_type = 'update';
		}

		if ($this->onBeforeInstall($route) === false)
		{
			return false;
		}

		return true;
	}

	public function postflight($route, JAdapterInstance $adapter)
	{
		$this->removeGlobalLanguageFiles();
		$this->removeUnusedLanguageFiles();

		JFactory::getLanguage()->load($this->getPrefix() . '_' . $this->extname, $this->getMainFolder());

		if ( ! in_array($route, ['install', 'update']))
		{
			return true;
		}

		$this->fixExtensionNames();
		$this->updateUpdateSites();
		$this->removeAdminCache();

		if ($this->onAfterInstall($route) === false)
		{
			return false;
		}

		if ($route == 'install')
		{
			$this->publishExtension();
		}

		if ($this->show_message)
		{
			$this->addInstalledMessage();
		}

		JFactory::getCache()->clean('com_plugins');
		JFactory::getCache()->clean('_system');

		return true;
	}

	public function isInstalled()
	{
		if ( ! is_file($this->getInstalledXMLFile()))
		{
			return false;
		}

		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('extension_id'))
			->from('#__extensions')
			->where($this->db->quoteName('type') . ' = ' . $this->db->quote($this->extension_type))
			->where($this->db->quoteName('element') . ' = ' . $this->db->quote($this->getElementName()));
		$this->db->setQuery($query, 0, 1);
		$result = $this->db->loadResult();

		return empty($result) ? false : true;
	}

	public function getMainFolder()
	{
		switch ($this->extension_type)
		{
			case 'plugin' :
				return JPATH_PLUGINS . '/' . $this->plugin_folder . '/' . $this->extname;

			case 'component' :
				return JPATH_ADMINISTRATOR . '/components/com_' . $this->extname;

			case 'module' :
				return JPATH_ADMINISTRATOR . '/modules/mod_' . $this->extname;

			case 'library' :
				return JPATH_SITE . '/libraries/' . $this->extname;
		}
	}

	public function getInstalledXMLFile()
	{
		return $this->getXMLFile($this->getMainFolder());
	}

	public function getCurrentXMLFile()
	{
		return $this->getXMLFile(__DIR__);
	}

	public function getXMLFile($folder)
	{
		switch ($this->extension_type)
		{
			case 'module' :
				return $folder . '/mod_' . $this->extname . '.xml';

			default :
				return $folder . '/' . $this->extname . '.xml';
		}
	}

	public function uninstallExtension($extname, $type = 'plugin', $folder = 'system', $show_message = true)
	{
		if (empty($extname))
		{
			return;
		}

		$folders = [];

		switch ($type)
		{
			case 'plugin':
				$folders[] = JPATH_PLUGINS . '/' . $folder . '/' . $extname;
				break;

			case 'component':
				$folders[] = JPATH_ADMINISTRATOR . '/components/com_' . $extname;
				$folders[] = JPATH_SITE . '/components/com_' . $extname;
				break;

			case 'module':
				$folders[] = JPATH_ADMINISTRATOR . '/modules/mod_' . $extname;
				$folders[] = JPATH_SITE . '/modules/mod_' . $extname;
				break;
		}

		if ( ! $this->foldersExist($folders))
		{
			return;
		}

		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('extension_id'))
			->from('#__extensions')
			->where($this->db->quoteName('element') . ' = ' . $this->db->quote($this->getElementName($type, $extname)))
			->where($this->db->quoteName('type') . ' = ' . $this->db->quote($type));

		if ($type == 'plugin')
		{
			$query->where($this->db->quoteName('folder') . ' = ' . $this->db->quote($folder));
		}

		$this->db->setQuery($query);
		$ids = $this->db->loadColumn();

		if (empty($ids))
		{
			foreach ($folders as $folder)
			{
				JFactory::getApplication()->enqueueMessage('2. Deleting: ' . $folder, 'notice');
				JFolder::delete($folder);
			}

			return;
		}

		$ignore_ids = JFactory::getApplication()->getUserState('rl_ignore_uninstall_ids', []);

		if (JFactory::getApplication()->input->get('option') == 'com_installer' && JFactory::getApplication()->input->get('task') == 'remove')
		{
			// Don't attempt to uninstall extensions that are already selected to get uninstalled by them selves
			$ignore_ids = array_merge($ignore_ids, JFactory::getApplication()->input->get('cid', [], 'array'));
			JFactory::getApplication()->input->set('cid', array_merge($ignore_ids, $ids));
		}

		$ids = array_diff($ids, $ignore_ids);

		if (empty($ids))
		{
			return;
		}

		$ignore_ids = array_merge($ignore_ids, $ids);
		JFactory::getApplication()->setUserState('rl_ignore_uninstall_ids', $ignore_ids);

		foreach ($ids as $id)
		{
			$tmpInstaller = new JInstaller;
			$tmpInstaller->uninstall($type, $id);
		}

		if ($show_message)
		{
			JFactory::getApplication()->enqueueMessage(
				JText::sprintf(
					'COM_INSTALLER_UNINSTALL_SUCCESS',
					JText::_('COM_INSTALLER_TYPE_TYPE_' . strtoupper($type))
				)
			);
		}
	}

	public function foldersExist($folders = [])
	{
		foreach ($folders as $folder)
		{
			if (is_dir($folder))
			{
				return true;
			}
		}

		return false;
	}

	public function uninstallPlugin($extname, $folder = 'system', $show_message = true)
	{
		$this->uninstallExtension($extname, 'plugin', $folder, $show_message);
	}

	public function uninstallComponent($extname, $show_message = true)
	{
		$this->uninstallExtension($extname, 'component', null, $show_message);
	}

	public function uninstallModule($extname, $show_message = true)
	{
		$this->uninstallExtension($extname, 'module', null, $show_message);
	}

	public function publishExtension()
	{
		switch ($this->extension_type)
		{
			case 'plugin' :
				$this->publishPlugin();

			case 'module' :
				$this->publishModule();
		}
	}

	public function publishPlugin()
	{
		$query = $this->db->getQuery(true)
			->update('#__extensions')
			->set($this->db->quoteName('enabled') . ' = 1')
			->where($this->db->quoteName('type') . ' = ' . $this->db->quote('plugin'))
			->where($this->db->quoteName('element') . ' = ' . $this->db->quote($this->extname))
			->where($this->db->quoteName('folder') . ' = ' . $this->db->quote($this->plugin_folder));
		$this->db->setQuery($query);
		$this->db->execute();
	}

	public function publishModule()
	{
		// Get module id
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('id'))
			->from('#__modules')
			->where($this->db->quoteName('module') . ' = ' . $this->db->quote('mod_' . $this->extname))
			->where($this->db->quoteName('client_id') . ' = ' . (int) $this->client_id);
		$this->db->setQuery($query, 0, 1);
		$id = $this->db->loadResult();

		if ( ! $id)
		{
			return;
		}

		// check if module is already in the modules_menu table (meaning is is already saved)
		$query->clear()
			->select($this->db->quoteName('moduleid'))
			->from('#__modules_menu')
			->where($this->db->quoteName('moduleid') . ' = ' . (int) $id);
		$this->db->setQuery($query, 0, 1);
		$exists = $this->db->loadResult();

		if ($exists)
		{
			return;
		}

		// Get highest ordering number in position
		$query->clear()
			->select($this->db->quoteName('ordering'))
			->from('#__modules')
			->where($this->db->quoteName('position') . ' = ' . $this->db->quote($this->module_position))
			->where($this->db->quoteName('client_id') . ' = ' . (int) $this->client_id)
			->order('ordering DESC');
		$this->db->setQuery($query, 0, 1);
		$ordering = $this->db->loadResult();
		$ordering++;

		// publish module and set ordering number
		$query->clear()
			->update('#__modules')
			->set($this->db->quoteName('published') . ' = 1')
			->set($this->db->quoteName('ordering') . ' = ' . (int) $ordering)
			->set($this->db->quoteName('position') . ' = ' . $this->db->quote($this->module_position))
			->where($this->db->quoteName('id') . ' = ' . (int) $id);
		$this->db->setQuery($query);
		$this->db->execute();

		// add module to the modules_menu table
		$query->clear()
			->insert('#__modules_menu')
			->columns([$this->db->quoteName('moduleid'), $this->db->quoteName('menuid')])
			->values((int) $id . ', 0');
		$this->db->setQuery($query);
		$this->db->execute();
	}

	public function addInstalledMessage()
	{
		JFactory::getApplication()->enqueueMessage(
			JText::sprintf(
				JText::_($this->install_type == 'update' ? 'RLI_THE_EXTENSION_HAS_BEEN_UPDATED_SUCCESSFULLY' : 'RLI_THE_EXTENSION_HAS_BEEN_INSTALLED_SUCCESSFULLY'),
				'<strong>' . JText::_($this->name) . '</strong>',
				'<strong>' . $this->getVersion() . '</strong>',
				$this->getFullType()
			)
		);
	}

	public function getPrefix()
	{
		switch ($this->extension_type)
		{
			case 'plugin':
				return JText::_('plg_' . strtolower($this->plugin_folder));

			case 'component':
				return JText::_('com');

			case 'module':
				return JText::_('mod');

			case 'library':
				return JText::_('lib');

			default:
				return $this->extension_type;
		}
	}

	public function getElementName($type = null, $extname = null)
	{
		$type    = is_null($type) ? $this->extension_type : $type;
		$extname = is_null($extname) ? $this->extname : $extname;

		switch ($type)
		{
			case 'component' :
				return 'com_' . $extname;

			case 'module' :
				return 'mod_' . $extname;

			case 'plugin' :
			default:
				return $extname;
		}
	}

	public function getFullType()
	{
		return JText::_('RLI_' . strtoupper($this->getPrefix()));
	}

	public function getVersion($file = '')
	{
		$file = $file ?: $this->getCurrentXMLFile();

		if ( ! is_file($file))
		{
			return '';
		}

		$xml = JApplicationHelper::parseXMLInstallFile($file);

		if ( ! $xml || ! isset($xml['version']))
		{
			return '';
		}

		return $xml['version'];
	}

	public function isNewer()
	{
		if ( ! $this->installed_version)
		{
			return true;
		}

		$package_version = $this->getVersion();

		return version_compare($this->installed_version, $package_version, '<=');
	}

	public function canInstall()
	{
		// The extension is not installed yet
		if ( ! $this->installed_version)
		{
			return true;
		}

		// The free version is installed. So any version is ok to install
		if (strpos($this->installed_version, 'PRO') === false)
		{
			return true;
		}

		// Current package is a pro version, so all good
		if (strpos($this->getVersion(), 'PRO') !== false)
		{
			return true;
		}

		JFactory::getLanguage()->load($this->getPrefix() . '_' . $this->extname, __DIR__);

		JFactory::getApplication()->enqueueMessage(JText::_('RLI_ERROR_PRO_TO_FREE'), 'error');

		JFactory::getApplication()->enqueueMessage(
			html_entity_decode(
				JText::sprintf(
					'RLI_ERROR_UNINSTALL_FIRST',
					'<a href="https://www.regularlabs.com/extensions/' . $this->alias . '" target="_blank">',
					'</a>',
					JText::_($this->name)
				)
			), 'error'
		);

		return false;
	}

	/*
	 * Fixes incorrectly formed versions because of issues in old packager
	 */
	public function fixFileVersions($file)
	{
		if (is_array($file))
		{
			foreach ($file as $f)
			{
				self::fixFileVersions($f);
			}

			return;
		}

		if ( ! is_string($file) || ! is_file($file))
		{
			return;
		}

		$contents = file_get_contents($file);

		if (
			strpos($contents, 'FREEFREE') === false
			&& strpos($contents, 'FREEPRO') === false
			&& strpos($contents, 'PROFREE') === false
			&& strpos($contents, 'PROPRO') === false
		)
		{
			return;
		}

		$contents = str_replace(
			['FREEFREE', 'FREEPRO', 'PROFREE', 'PROPRO'],
			['FREE', 'PRO', 'FREE', 'PRO'],
			$contents
		);

		JFile::write($file, $contents);
	}

	public function onBeforeInstall($route)
	{
		if ( ! $this->canInstall())
		{
			return false;
		}

		return true;
	}

	public function onAfterInstall($route)
	{
	}

	public function delete($files = [])
	{
		foreach ($files as $file)
		{
			if (is_dir($file))
			{
				JFolder::delete($file);
			}

			if (is_file($file))
			{
				JFile::delete($file);
			}
		}
	}

	public function fixAssetsRules()
	{
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('rules'))
			->from('#__assets')
			->where($this->db->quoteName('title') . ' = ' . $this->db->quote('com_' . $this->extname));
		$this->db->setQuery($query, 0, 1);
		$rules = $this->db->loadResult();

		$rules = json_decode($rules);

		if(empty($rules)) {
			return;
		}

		foreach($rules as $key => $value) {
			if(!empty($value)) {
				continue;
			}

			unset($rules->$key);
		}

		$rules = json_encode($rules);


		$query = $this->db->getQuery(true)
			->update($this->db->quoteName('#__assets'))
			->set($this->db->quoteName('rules') . ' = ' . $this->db->quote($rules))
			->where($this->db->quoteName('title') . ' = ' . $this->db->quote('com_' . $this->extname));
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function fixExtensionNames()
	{
		switch ($this->extension_type)
		{
			case 'module' :
				$this->fixModuleNames();
		}
	}

	private function fixModuleNames()
	{
		// Get module id
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('id'))
			->from('#__modules')
			->where($this->db->quoteName('module') . ' = ' . $this->db->quote('mod_' . $this->extname))
			->where($this->db->quoteName('client_id') . ' = ' . (int) $this->client_id);
		$this->db->setQuery($query, 0, 1);
		$module_id = $this->db->loadResult();

		if (empty($module_id))
		{
			return;
		}

		$title = 'Regular Labs - ' . JText::_($this->name);

		$query->clear()
			->update('#__modules')
			->set($this->db->quoteName('title') . ' = ' . $this->db->quote($title))
			->where($this->db->quoteName('id') . ' = ' . (int) $module_id)
			->where($this->db->quoteName('title') . ' LIKE ' . $this->db->quote('NoNumber%'));
		$this->db->setQuery($query);
		$this->db->execute();

		// Fix module assets

		// Get asset id
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('id'))
			->from('#__assets')
			->where($this->db->quoteName('name') . ' = ' . $this->db->quote('com_modules.module.' . (int) $module_id))
			->where($this->db->quoteName('title') . ' LIKE ' . $this->db->quote('NoNumber%'));
		$this->db->setQuery($query, 0, 1);
		$asset_id = $this->db->loadResult();

		if (empty($asset_id))
		{
			return;
		}

		$query->clear()
			->update('#__assets')
			->set($this->db->quoteName('title') . ' = ' . $this->db->quote($title))
			->where($this->db->quoteName('id') . ' = ' . (int) $asset_id);
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function updateUpdateSites()
	{
		$this->removeOldUpdateSites();
		$this->updateNamesInUpdateSites();
		$this->updateHttptoHttpsInUpdateSites();
		$this->removeDuplicateUpdateSite();
		$this->updateDownloadKey();
	}

	private function removeOldUpdateSites()
	{
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('update_site_id'))
			->from('#__update_sites')
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('nonumber.nl%'))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%e=' . $this->alias . '%'));
		$this->db->setQuery($query, 0, 1);
		$id = $this->db->loadResult();

		if ( ! $id)
		{
			return;
		}

		$query->clear()
			->delete('#__update_sites')
			->where($this->db->quoteName('update_site_id') . ' = ' . (int) $id);
		$this->db->setQuery($query);
		$this->db->execute();

		$query->clear()
			->delete('#__update_sites_extensions')
			->where($this->db->quoteName('update_site_id') . ' = ' . (int) $id);
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function updateNamesInUpdateSites()
	{
		$name = JText::_($this->name);
		if ($this->alias != 'extensionmanager')
		{
			$name = 'Regular Labs - ' . $name;
		}

		$query = $this->db->getQuery(true)
			->update('#__update_sites')
			->set($this->db->quoteName('name') . ' = ' . $this->db->quote($name))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%download.regularlabs.com%'))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%e=' . $this->alias . '%'));
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function updateHttptoHttpsInUpdateSites()
	{
		$query = $this->db->getQuery(true)
			->update('#__update_sites')
			->set($this->db->quoteName('location') . ' = REPLACE('
				. $this->db->quoteName('location') . ', '
				. $this->db->quote('http://') . ', '
				. $this->db->quote('https://')
				. ')')
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('http://download.regularlabs.com%'));
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function removeDuplicateUpdateSite()
	{
		// First check to see if there is a pro entry

		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('update_site_id'))
			->from('#__update_sites')
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%download.regularlabs.com%'))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%e=' . $this->alias . '%'))
			->where($this->db->quoteName('location') . ' NOT LIKE ' . $this->db->quote('%pro=1%'));
		$this->db->setQuery($query, 0, 1);
		$id = $this->db->loadResult();

		// Otherwise just get the first match
		if ( ! $id)
		{
			$query->clear()
				->select($this->db->quoteName('update_site_id'))
				->from('#__update_sites')
				->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%download.regularlabs.com%'))
				->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%e=' . $this->alias . '%'));
			$this->db->setQuery($query, 0, 1);
			$id = $this->db->loadResult();

			// Remove pro=1 from the found update site
			$query->clear()
				->update('#__update_sites')
				->set($this->db->quoteName('location')
					. ' = replace(' . $this->db->quoteName('location') . ', ' . $this->db->quote('&pro=1') . ', ' . $this->db->quote('') . ')')
				->where($this->db->quoteName('update_site_id') . ' = ' . (int) $id);
			$this->db->setQuery($query);
			$this->db->execute();
		}

		if ( ! $id)
		{
			return;
		}

		$query->clear()
			->select($this->db->quoteName('update_site_id'))
			->from('#__update_sites')
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%download.regularlabs.com%'))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%e=' . $this->alias . '%'))
			->where($this->db->quoteName('update_site_id') . ' != ' . $id);
		$this->db->setQuery($query);
		$ids = $this->db->loadColumn();

		if (empty($ids))
		{
			return;
		}

		$query->clear()
			->delete('#__update_sites')
			->where($this->db->quoteName('update_site_id') . ' IN (' . implode(',', $ids) . ')');
		$this->db->setQuery($query);
		$this->db->execute();

		$query->clear()
			->delete('#__update_sites_extensions')
			->where($this->db->quoteName('update_site_id') . ' IN (' . implode(',', $ids) . ')');
		$this->db->setQuery($query);
		$this->db->execute();
	}

	// Save the download key from the Regular Labs Extension Manager config to the update sites
	private function updateDownloadKey()
	{
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('params'))
			->from('#__extensions')
			->where($this->db->quoteName('element') . ' = ' . $this->db->quote('com_regularlabsmanager'));
		$this->db->setQuery($query);
		$params = $this->db->loadResult();

		if ( ! $params)
		{
			return;
		}

		$params = json_decode($params);

		if ( ! isset($params->key))
		{
			return;
		}

		// Add the key on all regularlabs.com urls
		$query->clear()
			->update('#__update_sites')
			->set($this->db->quoteName('extra_query') . ' = ' . $this->db->quote('k=' . $params->key))
			->where($this->db->quoteName('location') . ' LIKE ' . $this->db->quote('%download.regularlabs.com%'));
		$this->db->setQuery($query);
		$this->db->execute();
	}

	private function removeAdminCache()
	{
		$this->delete([JPATH_ADMINISTRATOR . '/cache/regularlabs']);
		$this->delete([JPATH_ADMINISTRATOR . '/cache/nonumber']);
	}

	private function removeGlobalLanguageFiles()
	{
		if ($this->extension_type == 'library')
		{
			return;
		}

		$language_files = JFolder::files(JPATH_ADMINISTRATOR . '/language', '\.' . $this->getPrefix() . '_' . $this->extname . '\.', true, true);

		// Remove override files
		foreach ($language_files as $i => $language_file)
		{
			if (strpos($language_file, '/overrides/') === false)
			{
				continue;
			}

			unset($language_files[$i]);
		}

		if (empty($language_files))
		{
			return;
		}

		JFile::delete($language_files);
	}

	private function removeUnusedLanguageFiles()
	{
		if ($this->extension_type == 'library')
		{
			return;
		}

		$installed_languages = array_merge(
			JFolder::folders(JPATH_SITE . '/language'),
			JFolder::folders(JPATH_ADMINISTRATOR . '/language')
		);

		$languages = array_diff(
			JFolder::folders(__DIR__ . '/language'),
			$installed_languages
		);

		$delete_languages = [];

		foreach ($languages as $language)
		{
			$delete_languages[] = $this->getMainFolder() . '/language/' . $language;
		}

		if (empty($delete_languages))
		{
			return;
		}

		// Remove folders
		$this->delete($delete_languages);
	}
}
regularlabs/autoload.php000064400000000065152177723700011404 0ustar00<?php
require_once __DIR__ . '/vendor/autoload.php';
regularlabs/fields/version.php000064400000003060152177723700012525 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\Version as RL_Version;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Version extends \RegularLabs\Library\Field
{
	public $type = 'Version';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$extension = $this->get('extension');
		$xml       = $this->get('xml');

		if ( ! $xml && $this->form->getValue('element'))
		{
			if ($this->form->getValue('folder'))
			{
				$xml = 'plugins/' . $this->form->getValue('folder') . '/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
			}
			else
			{
				$xml = 'administrator/modules/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
			}
			if ( ! JFile::exists(JPATH_SITE . '/' . $xml))
			{
				return '';
			}
		}

		if ( ! strlen($extension) || ! strlen($xml))
		{
			return '';
		}

		$authorise = JFactory::getUser()->authorise('core.manage', 'com_installer');
		if ( ! $authorise)
		{
			return '';
		}

		return '</div><div class="hide">' . RL_Version::getMessage($extension);
	}
}
regularlabs/fields/dependency.php000064400000005273152177723700013166 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\RegEx as RL_RegEx;

jimport('joomla.form.formfield');

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Dependency extends \RegularLabs\Library\Field
{
	public $type = 'Dependency';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		if ($file = $this->get('file'))
		{
			$label = $this->get('label', 'the main extension');

			RLFieldDependency::setMessage($file, $label);

			return '';
		}

		$path      = ($this->get('path') == 'site') ? '' : '/administrator';
		$label     = $this->get('label');
		$file      = $this->get('alias', $label);
		$file      = RL_RegEx::replace('[^a-z-]', '', strtolower($file));
		$extension = $this->get('extension');

		switch ($extension)
		{
			case 'com':
				$file = $path . '/components/com_' . $file . '/com_' . $file . '.xml';
				break;
			case 'mod':
				$file = $path . '/modules/mod_' . $file . '/mod_' . $file . '.xml';
				break;
			default:
				$file = '/plugins/' . str_replace('plg_', '', $extension) . '/' . $file . '.xml';
				break;
		}

		$label = JText::_($label) . ' (' . JText::_('RL_' . strtoupper($extension)) . ')';

		RLFieldDependency::setMessage($file, $label);

		return '';
	}
}

class RLFieldDependency
{
	static function setMessage($file, $name)
	{
		jimport('joomla.filesystem.file');

		$file = str_replace('\\', '/', $file);
		if (strpos($file, '/administrator') === 0)
		{
			$file = str_replace('/administrator', JPATH_ADMINISTRATOR, $file);
		}
		else
		{
			$file = JPATH_SITE . '/' . $file;
		}
		$file = str_replace('//', '/', $file);

		$file_alt = RL_RegEx::replace('(com|mod)_([a-z-_]+\.)', '\2', $file);

		if ( ! JFile::exists($file) && ! JFile::exists($file_alt))
		{
			$msg          = JText::sprintf('RL_THIS_EXTENSION_NEEDS_THE_MAIN_EXTENSION_TO_FUNCTION', JText::_($name));
			$message_set  = 0;
			$messageQueue = JFactory::getApplication()->getMessageQueue();
			foreach ($messageQueue as $queue_message)
			{
				if ($queue_message['type'] == 'error' && $queue_message['message'] == $msg)
				{
					$message_set = 1;
					break;
				}
			}
			if ( ! $message_set)
			{
				JFactory::getApplication()->enqueueMessage($msg, 'error');
			}
		}
	}
}
regularlabs/fields/users.php000064400000003775152177723700012216 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Users extends \RegularLabs\Library\Field
{
	public $type = 'Users';

	protected function getInput()
	{
		if ( ! is_array($this->value))
		{
			$this->value = explode(',', $this->value);
		}

		$size     = (int) $this->get('size');
		$multiple = $this->get('multiple');

		return $this->selectListSimpleAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getUsers();

		return $this->selectListSimple($options, $name, $value, $id, $size, $multiple);
	}

	function getUsers()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__users AS u');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('u.name, u.username, u.id, u.block as disabled')
			->order('name');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		$list = array_map(function ($item) {
			if ($item->disabled)
			{
				$item->name .= ' (' . JText::_('JDISABLED') . ')';
			}

			return $item;
		}, $list);

		return $this->getOptionsByList($list, ['username', 'id']);
	}
}
regularlabs/fields/mijoshop.php000064400000006450152177723700012676 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_MijoShop extends \RegularLabs\Library\FieldGroup
{
	public $type        = 'MijoShop';
	public $store_id    = 0;
	public $language_id = 1;

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories' => 'category', 'products' => 'product']))
		{
			return $error;
		}

		if ( ! class_exists('MijoShop'))
		{
			require_once(JPATH_ROOT . '/components/com_mijoshop/mijoshop/mijoshop.php');
		}

		$this->store_id    = (int) MijoShop::get('opencart')->get('config')->get('config_store_id');
		$this->language_id = (int) MijoShop::get('opencart')->get('config')->get('config_language_id');

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__mijoshop_category AS c')
			->join('INNER', '#__mijoshop_category_description AS cd ON c.category_id = cd.category_id')
			->join('INNER', '#__mijoshop_category_to_store AS cts ON c.category_id = cts.category_id')
			->where('c.status = 1')
			->where('cd.language_id = ' . $this->language_id)
			->where('cts.store_id = ' . $this->store_id)
			->group('c.category_id');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('c.category_id AS id, c.parent_id, cd.name AS title, c.status AS published')
			->order('c.sort_order, cd.name');
		$this->db->setQuery($query);
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items);
	}

	function getProducts()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__mijoshop_product AS p')
			->join('INNER', '#__mijoshop_product_description AS pd ON p.product_id = pd.product_id')
			->join('INNER', '#__mijoshop_product_to_store AS pts ON p.product_id = pts.product_id')->where('p.status = 1')
			->where('p.date_available <= NOW()')
			->where('pd.language_id = ' . $this->language_id)
			->group('p.product_id');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('p.product_id as id, pd.name, p.model as model, cd.name AS cat, p.status AS published')
			->join('LEFT', '#__mijoshop_product_to_category AS ptc ON p.product_id = ptc.product_id')
			->join('LEFT', '#__mijoshop_category_description AS cd ON ptc.category_id = cd.category_id')
			->join('LEFT', '#__mijoshop_category_to_store AS cts ON ptc.category_id = cts.category_id')
			->where('cts.store_id = ' . $this->store_id)
			->where('cd.language_id = ' . $this->language_id)
			->where('cts.store_id = ' . $this->store_id)
			->order('pd.name, p.model');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['model', 'cat', 'id']);
	}
}
regularlabs/fields/content.php000064400000004770152177723700012523 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Content extends \RegularLabs\Library\FieldGroup
{
	public $type = 'Content';

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__categories')
			->where('extension = ' . $this->db->quote('com_content'))
			->where('parent_id > 0')
			->where('published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		// assemble items to the array
		$options = [];
		if ($this->get('show_ignore'))
		{
			if (in_array('-1', $this->value))
			{
				$this->value = ['-1'];
			}
			$options[] = JHtml::_('select.option', '-1', '- ' . JText::_('RL_IGNORE') . ' -');
			$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true);
		}

		$query->clear('select')
			->select('id, title as name, level, published, language')
			->order('lft');

		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		$options = array_merge($options, $this->getOptionsByList($list, ['language'], -1));

		return $options;
	}

	function getItems()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__content AS i')
			->where('i.access > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('i.id, i.title as name, i.language, c.title as cat, i.state as published')
			->join('LEFT', '#__categories AS c ON c.id = i.catid')
			->order('i.title, i.ordering, i.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		$options = $this->getOptionsByList($list, ['language', 'cat', 'id']);

		if ($this->get('showselect'))
		{
			array_unshift($options, JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true));
			array_unshift($options, JHtml::_('select.option', '-', '- ' . JText::_('Select Item') . ' -'));
		}

		return $options;
	}
}
regularlabs/fields/languages.php000064400000003363152177723700013014 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Languages extends \RegularLabs\Library\Field
{
	public $type = 'Languages';

	protected function getInput()
	{
		$size     = (int) $this->get('size');
		$multiple = $this->get('multiple');

		return $this->selectListSimpleAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getLanguages($value);

		return $this->selectListSimple($options, $name, $value, $id, $size, $multiple);
	}

	function getLanguages($value)
	{
		$langs = JHtml::_('contentlanguage.existing');

		if ( ! is_array($value))
		{
			$value = [$value];
		}

		$options = [];

		foreach ($langs as $lang)
		{
			if (empty($lang->value))
			{
				continue;
			}

			$options[] = (object) [
				'value'    => $lang->value,
				'text'     => $lang->text . ' [' . $lang->value . ']',
				'selected' => in_array($lang->value, $value),
			];
		}

		return $options;
	}
}
regularlabs/fields/conditionselection.php000064400000007462152177723700014746 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\ShowOn as RL_ShowOn;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_ConditionSelection extends \RegularLabs\Library\Field
{
	public $type = 'ConditionSelection';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$this->value     = (int) $this->value;
		$label           = $this->get('label');
		$param_name      = $this->get('name');
		$use_main_switch = $this->get('use_main_switch', 1);
		$showclose       = $this->get('showclose', 0);

		$html = [];

		if ( ! $label)
		{
			if ($use_main_switch)
			{
				$html[] = $this->closeShowOn();
			}

			$html[] = $this->closeShowOn();

			return '</div>' . implode('', $html);
		}

		$label = RL_String::html_entity_decoder(JText::_($label));

		$html[] = '</div>';

		if ($use_main_switch)
		{
			$html[] = $this->openShowOn('show_conditions:1[OR]show_assignments:1[OR]' . $param_name . ':1,2');
		}

		$class = 'well well-small rl_well';
		if ($this->value === 1)
		{
			$class .= ' alert-success';
		}
		else if ($this->value === 2)
		{
			$class .= ' alert-error';
		}
		$html[] = '<div class="' . $class . '">';
		if ($showclose && JFactory::getUser()->authorise('core.admin'))
		{
			$html[] = '<button type="button" class="close">&times;</button>';
		}

		$html[] = '<div class="control-group">';

		$html[] = '<div class="control-label">';
		$html[] = '<label><h4>' . $label . '</h4></label>';
		$html[] = '</div>';

		$html[] = '<div class="controls">';
		$html[] = '<fieldset id="' . $this->id . '"  class="radio btn-group">';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 0)"';
		$html[]  = '<input type="radio" id="' . $this->id . '0" name="' . $this->name . '" value="0"' . (( ! $this->value) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-ignore" for="' . $this->id . '0">' . JText::_('RL_IGNORE') . '</label>';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 1)"';
		$html[]  = '<input type="radio" id="' . $this->id . '1" name="' . $this->name . '" value="1"' . (($this->value === 1) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-include" for="' . $this->id . '1">' . JText::_('RL_INCLUDE') . '</label>';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 2)"';
		$onclick .= ' onload="RegularLabsForm.setToggleTitleClass(this, ' . $this->value . ', 7)"';
		$html[]  = '<input type="radio" id="' . $this->id . '2" name="' . $this->name . '" value="2"' . (($this->value === 2) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-exclude" for="' . $this->id . '2">' . JText::_('RL_EXCLUDE') . '</label>';

		$html[] = '</fieldset>';
		$html[] = '</div>';

		$html[] = '</div>';
		$html[] = '<div class="clearfix"> </div>';

		$html[] = $this->openShowOn($param_name . ':1,2');

		$html[] = '<div><div>';

		return '</div>' . implode('', $html);
	}

	protected function openShowOn($condition = '')
	{
		if ( ! $condition)
		{
			return $this->closeShowon();
		}

		$formControl = $this->get('form', $this->formControl);
		$formControl = $formControl == 'root' ? '' : $formControl;

		return RL_ShowOn::open($condition, $formControl);
	}

	protected function closeShowOn()
	{
		return RL_ShowOn::close();
	}
}
regularlabs/fields/codeeditor.php000064400000004264152177723700013170 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Editor\Editor as JEditor;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;
use RegularLabs\Library\Document as RL_Document;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_CodeEditor extends \RegularLabs\Library\Field
{
	public $type = 'CodeEditor';

	protected function getInput()
	{
		$width  = $this->get('width', '100%');
		$height = $this->get('height', 400);
		$syntax = $this->get('syntax', 'html');

		$this->value = htmlspecialchars(str_replace('\n', "\n", $this->value), ENT_COMPAT, 'UTF-8');

		$editor_plugin = JPluginHelper::getPlugin('editors', 'codemirror');

		if (empty($editor_plugin))
		{
			return
				'<textarea name="' . $this->name . '" style="'
				. 'width:' . (strpos($width, '%') ? $width : $width . 'px') . ';'
				. 'height:' . (strpos($height, '%') ? $height : $height . 'px') . ';'
				. '" id="' . $this->id . '">' . $this->value . '</textarea>';
		}

		RL_Document::script('regularlabs/codemirror.min.js');
		RL_Document::stylesheet('regularlabs/codemirror.min.css');

		JFactory::getDocument()->addScriptDeclaration("
			jQuery(document).ready(function($) {
				RegularLabsCodeMirror.init('" . $this->id . "');
			});
		");

		JFactory::getDocument()->addStyleDeclaration("
			#rl_codemirror_" . $this->id . " .CodeMirror {
			    height: " . $height . "px;
			    min-height: " . min($height, '100') . "px;
			}
		");

		return '<div class="rl_codemirror" id="rl_codemirror_' . $this->id . '">'
			. JEditor::getInstance('codemirror')->display(
				$this->name, $this->value,
				$width, $height,
				80, 10,
				false,
				$this->id, null, null,
				['markerGutter' => false, 'activeLine' => true, 'syntax' => $syntax, 'class' => 'xxx']
			)
			. '</div>';
	}
}
regularlabs/fields/components.php000064400000006534152177723700013236 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Components extends \RegularLabs\Library\Field
{
	public $type = 'Components';

	protected function getInput()
	{
		$size = (int) $this->get('size');

		return $this->selectListSimpleAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name  = $attributes->get('name', $this->type);
		$id    = $attributes->get('id', strtolower($name));
		$value = $attributes->get('value', []);
		$size  = $attributes->get('size');

		$options = $this->getComponents();

		return $this->selectListSimple($options, $name, $value, $id, $size, true);
	}

	function getComponents()
	{
		$frontend = $this->get('frontend', 1);
		$admin    = $this->get('admin', 1);

		if ( ! $frontend && ! $admin)
		{
			return [];
		}

		jimport('joomla.filesystem.folder');
		jimport('joomla.filesystem.file');

		$query = $this->db->getQuery(true)
			->select('e.name, e.element')
			->from('#__extensions AS e')
			->where('e.type = ' . $this->db->quote('component'))
			->where('e.name != ""')
			->where('e.element != ""')
			->group('e.element')
			->order('e.element, e.name');
		$this->db->setQuery($query);
		$components = $this->db->loadObjectList();

		$comps = [];
		$lang  = JFactory::getLanguage();

		foreach ($components as $i => $component)
		{
			if (empty($component->element))
			{
				continue;
			}

			$component_folder = ($frontend ? JPATH_SITE : JPATH_ADMINISTRATOR) . '/components/' . $component->element;

			// return if there is no main component folder
			if ( ! JFolder::exists($component_folder))
			{
				continue;
			}

			// return if there is no view(s) folder
			if ( ! JFolder::exists($component_folder . '/views') && ! JFolder::exists($component_folder . '/view'))
			{
				continue;
			}

			if (strpos($component->name, ' ') === false)
			{
				// Load the core file then
				// Load extension-local file.
				$lang->load($component->element . '.sys', JPATH_BASE, null, false, false)
				|| $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, null, false, false)
				|| $lang->load($component->element . '.sys', JPATH_BASE, $lang->getDefault(), false, false)
				|| $lang->load($component->element . '.sys', JPATH_ADMINISTRATOR . '/components/' . $component->element, $lang->getDefault(), false, false);

				$component->name = JText::_(strtoupper($component->name));
			}

			$comps[RL_RegEx::replace('[^a-z0-9_]', '', $component->name . '_' . $component->element)] = $component;
		}

		ksort($comps);

		$options = [];

		foreach ($comps as $component)
		{
			$options[] = JHtml::_('select.option', $component->element, $component->name);
		}

		return $options;
	}
}
regularlabs/fields/checkbox.php000064400000005107152177723700012632 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Checkbox extends \RegularLabs\Library\Field
{
	public $type = 'Checkbox';

	protected function getInput()
	{
		$showcheckall = $this->get('showcheckall', 0);

		$checkall = ($this->value == '*');

		if ( ! $checkall)
		{
			if ( ! is_array($this->value))
			{
				$this->value = explode(',', $this->value);
			}
		}

		$options = [];
		foreach ($this->element->children() as $option)
		{
			if ($option->getName() != 'option')
			{
				continue;
			}

			$text   = trim((string) $option);
			$hasval = 0;
			if (isset($option['value']))
			{
				$val      = (string) $option['value'];
				$disabled = (int) $option['disabled'];
				$hasval   = 1;
			}
			if ($hasval)
			{
				$option = '<input type="checkbox" class="rl_' . $this->id . '" id="' . $this->id . $val . '" name="' . $this->name . '[]" value="' . $val . '"';
				if ($checkall || in_array($val, $this->value))
				{
					$option .= ' checked="checked"';
				}
				if ($disabled)
				{
					$option .= ' disabled="disabled"';
				}
				$option .= '> <label for="' . $this->id . $val . '" class="checkboxes">' . JText::_($text) . '</label>';
			}
			else
			{
				$option = '<label style="clear:both;"><strong>' . JText::_($text) . '</strong></label>';
			}
			$options[] = $option;
		}

		$options = implode('', $options);

		if ($showcheckall)
		{
			$js = "
				jQuery(document).ready(function() {
					RegularLabsForm.initCheckAlls('rl_checkall_" . $this->id . "', 'rl_" . $this->id . "');
				});
			";
			JFactory::getDocument()->addScriptDeclaration($js);

			$checker = '<input id="rl_checkall_' . $this->id . '" type="checkbox" onclick=" RegularLabsForm.checkAll( this, \'rl_' . $this->id . '\' );"> ' . JText::_('JALL');

			$options = $checker . '<br>' . $options;
		}
		$options .= '<input type="hidden" id="' . $this->id . 'x" name="' . $this->name . '' . '[]" value="x" checked="checked">';

		$html   = [];
		$html[] = '<fieldset id="' . $this->id . '" class="checkbox">';
		$html[] = $options;
		$html[] = '</fieldset>';

		return implode('', $html);
	}
}
regularlabs/fields/easyblog.php000064400000004151152177723700012647 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_EasyBlog extends \RegularLabs\Library\FieldGroup
{
	public $type = 'EasyBlog';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories' => 'category', 'items' => 'post', 'tags' => 'tag']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__easyblog_category AS c')
			->where('c.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('c.id, c.parent_id, c.title, c.published')
			->order('c.ordering, c.title');
		$this->db->setQuery($query);
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items);
	}

	function getItems()
	{
		$query = $this->db->getQuery(true)
			->select('i.id, i.title as name, c.title as cat, i.published')
			->from('#__easyblog_post AS i')
			->join('LEFT', '#__easyblog_category AS c ON c.id = i.category_id')
			->where('i.published > -1')
			->order('i.title, c.title, i.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['cat', 'id']);
	}

	function getTags()
	{
		$query = $this->db->getQuery(true)
			->select('t.alias as id, t.title as name')
			->from('#__easyblog_tag AS t')
			->where('t.published > -1')
			->where('t.title != ' . $this->db->quote(''))
			->group('t.title')
			->order('t.title');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list);
	}
}
regularlabs/fields/customfieldkey.php000064400000002674152177723700014101 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_CustomFieldKey extends \RegularLabs\Library\Field
{
	public $type = 'CustomFieldKey';

	protected function getLabel()
	{
		$label       = $this->get('label') ? $this->get('label') : '';
		$size        = $this->get('size') ? 'style="width:' . $this->get('size') . 'px"' : '';
		$class       = 'class="' . ($this->get('class') ? $this->get('class') : 'text_area') . '"';
		$this->value = htmlspecialchars(RL_String::html_entity_decoder($this->value), ENT_QUOTES);

		return
			'<label for="' . $this->id . '" style="margin-top: -5px;">'
			. '<input type="text" name="' . $this->name . '" id="' . $this->id . '" value="' . $this->value
			. '" placeholder="' . JText::_($label) . '" title="' . JText::_($label) . '" ' . $class . ' ' . $size . '>'
			. '</label>';
	}

	protected function getInput()
	{
		return '<div style="display:none;"><div><div>';
	}
}
regularlabs/fields/hr.php000064400000001533152177723700011454 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Form\FormField as JFormField;
use RegularLabs\Library\Document as RL_Document;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_HR extends JFormField
{
	public $type = 'HR';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		RL_Document::stylesheet('regularlabs/style.min.css');

		return '<div class="rl_panel rl_hr"></div>';
	}
}
regularlabs/fields/menuitems.php000064400000006241152177723700013052 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;
use RegularLabs\Library\Language as RL_Language;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_MenuItems extends \RegularLabs\Library\Field
{
	public $type = 'MenuItems';

	protected function getInput()
	{
		$size     = (int) $this->get('size');
		$multiple = $this->get('multiple', 0);

		return $this->selectListAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getMenuItems();

		return $this->selectList($options, $name, $value, $id, $size, $multiple);
	}

	/**
	 * Get a list of menu links for one or all menus.
	 */
	public static function getMenuItems()
	{
		RL_Language::load('com_modules', JPATH_ADMINISTRATOR);
		JLoader::register('MenusHelper', JPATH_ADMINISTRATOR . '/components/com_menus/helpers/menus.php');
		$menuTypes = MenusHelper::getMenuLinks();

		foreach ($menuTypes as &$type)
		{
			$type->value      = 'type.' . $type->menutype;
			$type->text       = $type->title;
			$type->level      = 0;
			$type->class      = 'hidechildren';
			$type->labelclass = 'nav-header';

			$rlu[$type->menutype] = &$type;

			foreach ($type->links as &$link)
			{
				$check1 = RL_RegEx::replace('[^a-z0-9]', '', strtolower($link->text));
				$check2 = RL_RegEx::replace('[^a-z0-9]', '', $link->alias);

				$text   = [];
				$text[] = $link->text;

				if ($check1 !== $check2)
				{
					$text[] = '<span class="small ghosted">[' . $link->alias . ']</span>';
				}

				if (in_array($link->type, ['separator', 'heading', 'alias', 'url']))
				{
					$text[] = '<span class="label label-info">' . JText::_('COM_MODULES_MENU_ITEM_' . strtoupper($link->type)) . '</span>';
					// Don't disable, as you need to be able to select the 'Also on Child Items' option
					// $link->disable = 1;
				}

				if ($link->published == 0)
				{
					$text[] = '<span class="label">' . JText::_('JUNPUBLISHED') . '</span>';
				}

				if (JLanguageMultilang::isEnabled() && $link->language != '' && $link->language != '*')
				{
					$text[] = $link->language_image
						? JHtml::_('image', 'mod_languages/' . $link->language_image . '.gif', $link->language_title, ['title' => $link->language_title], true)
						: '<span class="label" title="' . $link->language_title . '">' . $link->language_sef . '</span>';
				}

				$link->text = implode(' ', $text);
			}
		}

		return $menuTypes;
	}
}
regularlabs/fields/license.php000064400000001612152177723700012463 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\License as RL_License;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_License extends \RegularLabs\Library\Field
{
	public $type = 'License';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$extension = $this->get('extension');

		if ( ! strlen($extension))
		{
			return '';
		}

		return '</div><div class="hide">' . RL_License::getMessage($extension, true);
	}
}
regularlabs/fields/showon.php000064400000002115152177723700012355 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\ShowOn as RL_ShowOn;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_ShowOn extends \RegularLabs\Library\Field
{
	public $type = 'ShowOn';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$value       = (string) $this->get('value');
		$class       = $this->get('class', '');
		$formControl = $this->get('form', $this->formControl);
		$formControl = $formControl == 'root' ? '' : $formControl;

		if ( ! $value)
		{
			return RL_ShowOn::close();
		}

		return '</div></div>'
			. RL_ShowOn::open($value, $formControl, $this->group, $class)
			. '<div><div>';
	}
}
regularlabs/fields/geo.php000064400000270140152177723700011617 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\Registry\Registry;
use RegularLabs\Library\Form as RL_Form;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Geo extends \RegularLabs\Library\Field
{
	public $type = 'Geo';

	protected function getInput()
	{

		if ( ! is_array($this->value))
		{
			$this->value = explode(',', $this->value);
		}

		$size      = (int) $this->get('size');
		$multiple  = $this->get('multiple');
		$group     = $this->get('group', 'countries');
		$use_names = $this->get('use_names', false);

		return $this->selectListSimpleAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple', 'group', 'use_names')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name  = $attributes->get('name', $this->type);
		$id    = $attributes->get('id', strtolower($name));
		$value = $attributes->get('value', []);
		$size  = $attributes->get('size');

		$options = $this->getOptions(
			$attributes->get('group', 'countries'),
			(bool) $attributes->get('use_names', false)
		);

		return $this->selectListSimple($options, $name, $value, $id, $size, true);
	}

	function getOptions($group = 'countries', $use_names = '')
	{
		$options = [];
		foreach ($this->{$group} as $key => $val)
		{
			if ( ! $val)
			{
				$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true);
				continue;
			}

			if ($key[0] == '-')
			{
				$options[] = JHtml::_('select.option', '-', $val, 'value', 'text', true);
				continue;
			}

			$val       = RL_Form::prepareSelectItem($val);
			$options[] = JHtml::_('select.option', $use_names ? $val : $key, $val);
		}

		return $options;
	}

	public $continents = [
		'AF' => 'Africa',
		'AS' => 'Asia',
		'EU' => 'Europe',
		'NA' => 'North America',
		'SA' => 'South America',
		'OC' => 'Oceania',
		'AN' => 'Antarctica',
	];

	public $countries = [
		'AF' => "Afghanistan",
		'AX' => "Aland Islands",
		'AL' => "Albania",
		'DZ' => "Algeria",
		'AS' => "American Samoa",
		'AD' => "Andorra",
		'AO' => "Angola",
		'AI' => "Anguilla",
		'AQ' => "Antarctica",
		'AG' => "Antigua and Barbuda",
		'AR' => "Argentina",
		'AM' => "Armenia",
		'AW' => "Aruba",
		'AU' => "Australia",
		'AT' => "Austria",
		'AZ' => "Azerbaijan",
		'BS' => "Bahamas",
		'BH' => "Bahrain",
		'BD' => "Bangladesh",
		'BB' => "Barbados",
		'BY' => "Belarus",
		'BE' => "Belgium",
		'BZ' => "Belize",
		'BJ' => "Benin",
		'BM' => "Bermuda",
		'BT' => "Bhutan",
		'BO' => "Bolivia",
		'BA' => "Bosnia and Herzegovina",
		'BW' => "Botswana",
		'BV' => "Bouvet Island",
		'BR' => "Brazil",
		'IO' => "British Indian Ocean Territory",
		'BN' => "Brunei Darussalam",
		'BG' => "Bulgaria",
		'BF' => "Burkina Faso",
		'BI' => "Burundi",
		'KH' => "Cambodia",
		'CM' => "Cameroon",
		'CA' => "Canada",
		'CV' => "Cape Verde",
		'KY' => "Cayman Islands",
		'CF' => "Central African Republic",
		'TD' => "Chad",
		'CL' => "Chile",
		'CN' => "China",
		'CX' => "Christmas Island",
		'CC' => "Cocos (Keeling) Islands",
		'CO' => "Colombia",
		'KM' => "Comoros",
		'CG' => "Congo",
		'CD' => "Congo, The Democratic Republic of the",
		'CK' => "Cook Islands",
		'CR' => "Costa Rica",
		'CI' => "Cote d'Ivoire",
		'HR' => "Croatia",
		'CU' => "Cuba",
		'CY' => "Cyprus",
		'CZ' => "Czech Republic",
		'DK' => "Denmark",
		'DJ' => "Djibouti",
		'DM' => "Dominica",
		'DO' => "Dominican Republic",
		'EC' => "Ecuador",
		'EG' => "Egypt",
		'SV' => "El Salvador",
		'GQ' => "Equatorial Guinea",
		'ER' => "Eritrea",
		'EE' => "Estonia",
		'ET' => "Ethiopia",
		'FK' => "Falkland Islands (Malvinas)",
		'FO' => "Faroe Islands",
		'FJ' => "Fiji",
		'FI' => "Finland",
		'FR' => "France",
		'GF' => "French Guiana",
		'PF' => "French Polynesia",
		'TF' => "French Southern Territories",
		'GA' => "Gabon",
		'GM' => "Gambia",
		'GE' => "Georgia",
		'DE' => "Germany",
		'GH' => "Ghana",
		'GI' => "Gibraltar",
		'GR' => "Greece",
		'GL' => "Greenland",
		'GD' => "Grenada",
		'GP' => "Guadeloupe",
		'GU' => "Guam",
		'GT' => "Guatemala",
		'GG' => "Guernsey",
		'GN' => "Guinea",
		'GW' => "Guinea-Bissau",
		'GY' => "Guyana",
		'HT' => "Haiti",
		'HM' => "Heard Island and McDonald Islands",
		'VA' => "Holy See (Vatican City State)",
		'HN' => "Honduras",
		'HK' => "Hong Kong",
		'HU' => "Hungary",
		'IS' => "Iceland",
		'IN' => "India",
		'ID' => "Indonesia",
		'IR' => "Iran, Islamic Republic of",
		'IQ' => "Iraq",
		'IE' => "Ireland",
		'IM' => "Isle of Man",
		'IL' => "Israel",
		'IT' => "Italy",
		'JM' => "Jamaica",
		'JP' => "Japan",
		'JE' => "Jersey",
		'JO' => "Jordan",
		'KZ' => "Kazakhstan",
		'KE' => "Kenya",
		'KI' => "Kiribati",
		'KP' => "Korea, Democratic People's Republic of",
		'KR' => "Korea, Republic of",
		'KW' => "Kuwait",
		'KG' => "Kyrgyzstan",
		'LA' => "Lao People's Democratic Republic",
		'LV' => "Latvia",
		'LB' => "Lebanon",
		'LS' => "Lesotho",
		'LR' => "Liberia",
		'LY' => "Libyan Arab Jamahiriya",
		'LI' => "Liechtenstein",
		'LT' => "Lithuania",
		'LU' => "Luxembourg",
		'MO' => "Macao",
		'MK' => "Macedonia",
		'MG' => "Madagascar",
		'MW' => "Malawi",
		'MY' => "Malaysia",
		'MV' => "Maldives",
		'ML' => "Mali",
		'MT' => "Malta",
		'MH' => "Marshall Islands",
		'MQ' => "Martinique",
		'MR' => "Mauritania",
		'MU' => "Mauritius",
		'YT' => "Mayotte",
		'MX' => "Mexico",
		'FM' => "Micronesia, Federated States of",
		'MD' => "Moldova, Republic of",
		'MC' => "Monaco",
		'MN' => "Mongolia",
		'ME' => "Montenegro",
		'MS' => "Montserrat",
		'MA' => "Morocco",
		'MZ' => "Mozambique",
		'MM' => "Myanmar",
		'NA' => "Namibia",
		'NR' => "Nauru",
		'NP' => "Nepal",
		'NL' => "Netherlands",
		'AN' => "Netherlands Antilles",
		'NC' => "New Caledonia",
		'NZ' => "New Zealand",
		'NI' => "Nicaragua",
		'NE' => "Niger",
		'NG' => "Nigeria",
		'NU' => "Niue",
		'NF' => "Norfolk Island",
		'MP' => "Northern Mariana Islands",
		'NO' => "Norway",
		'OM' => "Oman",
		'PK' => "Pakistan",
		'PW' => "Palau",
		'PS' => "Palestinian Territory",
		'PA' => "Panama",
		'PG' => "Papua New Guinea",
		'PY' => "Paraguay",
		'PE' => "Peru",
		'PH' => "Philippines",
		'PN' => "Pitcairn",
		'PL' => "Poland",
		'PT' => "Portugal",
		'PR' => "Puerto Rico",
		'QA' => "Qatar",
		'RE' => "Reunion",
		'RO' => "Romania",
		'RU' => "Russian Federation",
		'RW' => "Rwanda",
		'SH' => "Saint Helena",
		'KN' => "Saint Kitts and Nevis",
		'LC' => "Saint Lucia",
		'PM' => "Saint Pierre and Miquelon",
		'VC' => "Saint Vincent and the Grenadines",
		'WS' => "Samoa",
		'SM' => "San Marino",
		'ST' => "Sao Tome and Principe",
		'SA' => "Saudi Arabia",
		'SN' => "Senegal",
		'RS' => "Serbia",
		'SC' => "Seychelles",
		'SL' => "Sierra Leone",
		'SG' => "Singapore",
		'SK' => "Slovakia",
		'SI' => "Slovenia",
		'SB' => "Solomon Islands",
		'SO' => "Somalia",
		'ZA' => "South Africa",
		'GS' => "South Georgia and the South Sandwich Islands",
		'ES' => "Spain",
		'LK' => "Sri Lanka",
		'SD' => "Sudan",
		'SR' => "Suriname",
		'SJ' => "Svalbard and Jan Mayen",
		'SZ' => "Swaziland",
		'SE' => "Sweden",
		'CH' => "Switzerland",
		'SY' => "Syrian Arab Republic",
		'TW' => "Taiwan",
		'TJ' => "Tajikistan",
		'TZ' => "Tanzania, United Republic of",
		'TH' => "Thailand",
		'TL' => "Timor-Leste",
		'TG' => "Togo",
		'TK' => "Tokelau",
		'TO' => "Tonga",
		'TT' => "Trinidad and Tobago",
		'TN' => "Tunisia",
		'TR' => "Turkey",
		'TM' => "Turkmenistan",
		'TC' => "Turks and Caicos Islands",
		'TV' => "Tuvalu",
		'UG' => "Uganda",
		'UA' => "Ukraine",
		'AE' => "United Arab Emirates",
		'GB' => "United Kingdom",
		'US' => "United States",
		'UM' => "United States Minor Outlying Islands",
		'UY' => "Uruguay",
		'UZ' => "Uzbekistan",
		'VU' => "Vanuatu",
		'VE' => "Venezuela",
		'VN' => "Vietnam",
		'VG' => "Virgin Islands, British",
		'VI' => "Virgin Islands, U.S.",
		'WF' => "Wallis and Futuna",
		'EH' => "Western Sahara",
		'YE' => "Yemen",
		'ZM' => "Zambia",
		'ZW' => "Zimbabwe",
	];

	public $regions = [
		'-AU'    => "Australia",
		'AU-ACT' => "Australia: Australian Capital Territory",
		'AU-NSW' => "Australia: New South Wales",
		'AU-NT'  => "Australia: Northern Territory",
		'AU-QLD' => "Australia: Queensland",
		'AU-SA'  => "Australia: South Australia",
		'AU-TAS' => "Australia: Tasmania",
		'AU-VIC' => "Australia: Victoria",
		'AU-WA'  => "Australia: Western Australia",

		'--BE'   => "", '-BE' => "Belgium",
		'BE-VAN' => "Belgium: Antwerpen",
		'BE-WBR' => "Belgium: Brabant Wallon",
		'BE-BRU' => "Belgium: Brussels-Capital Region",
		'BE-WHT' => "Belgium: Hainaut",
		'BE-WLG' => "Belgium: Liege",
		'BE-VLI' => "Belgium: Limburg",
		'BE-WLX' => "Belgium: Luxembourg, Luxemburg",
		'BE-WNA' => "Belgium: Namur",
		'BE-VOV' => "Belgium: Oost-Vlaanderen",
		'BE-VBR' => "Belgium: Vlaams-Brabant",
		'BE-VWV' => "Belgium: West-Vlaanderen",

		'--BR'  => "", '-BR' => "Brazil",
		'BR-AC' => "Brazil: Acre",
		'BR-AL' => "Brazil: Alagoas",
		'BR-AP' => "Brazil: Amapá",
		'BR-AM' => "Brazil: Amazonas",
		'BR-BA' => "Brazil: Bahia",
		'BR-CE' => "Brazil: Ceará",
		'BR-DF' => "Brazil: Distrito Federal",
		'BR-ES' => "Brazil: Espírito Santo",
		'BR-FN' => "Brazil: Fernando de Noronha",
		'BR-GO' => "Brazil: Goiás",
		'BR-MA' => "Brazil: Maranhão",
		'BR-MT' => "Brazil: Mato Grosso",
		'BR-MS' => "Brazil: Mato Grosso do Sul",
		'BR-MG' => "Brazil: Minas Gerais",
		'BR-PR' => "Brazil: Paraná",
		'BR-PB' => "Brazil: Paraíba",
		'BR-PA' => "Brazil: Pará",
		'BR-PE' => "Brazil: Pernambuco",
		'BR-PI' => "Brazil: Piauí",
		'BR-RN' => "Brazil: Rio Grande do Norte",
		'BR-RS' => "Brazil: Rio Grande do Sul",
		'BR-RJ' => "Brazil: Rio de Janeiro",
		'BR-RO' => "Brazil: Rondônia",
		'BR-RR' => "Brazil: Roraima",
		'BR-SC' => "Brazil: Santa Catarina",
		'BR-SE' => "Brazil: Sergipe",
		'BR-SP' => "Brazil: São Paulo",
		'BR-TO' => "Brazil: Tocantins",

		'--BG'  => "", '-BG' => "Bulgaria",
		'BG-01' => "Bulgaria: Blagoevgrad",
		'BG-02' => "Bulgaria: Burgas",
		'BG-08' => "Bulgaria: Dobrich",
		'BG-07' => "Bulgaria: Gabrovo",
		'BG-26' => "Bulgaria: Haskovo",
		'BG-09' => "Bulgaria: Kardzhali",
		'BG-10' => "Bulgaria: Kyustendil",
		'BG-11' => "Bulgaria: Lovech",
		'BG-12' => "Bulgaria: Montana",
		'BG-13' => "Bulgaria: Pazardzhik",
		'BG-14' => "Bulgaria: Pernik",
		'BG-15' => "Bulgaria: Pleven",
		'BG-16' => "Bulgaria: Plovdiv",
		'BG-17' => "Bulgaria: Razgrad",
		'BG-18' => "Bulgaria: Ruse",
		'BG-27' => "Bulgaria: Shumen",
		'BG-19' => "Bulgaria: Silistra",
		'BG-20' => "Bulgaria: Sliven",
		'BG-21' => "Bulgaria: Smolyan",
		'BG-23' => "Bulgaria: Sofia",
		'BG-22' => "Bulgaria: Sofia-Grad",
		'BG-24' => "Bulgaria: Stara Zagora",
		'BG-25' => "Bulgaria: Targovishte",
		'BG-03' => "Bulgaria: Varna",
		'BG-04' => "Bulgaria: Veliko Tarnovo",
		'BG-05' => "Bulgaria: Vidin",
		'BG-06' => "Bulgaria: Vratsa",
		'BG-28' => "Bulgaria: Yambol",

		'--CA'  => "", '-CA' => "Canada",
		'CA-AB' => "Canada: Alberta",
		'CA-BC' => "Canada: British Columbia",
		'CA-MB' => "Canada: Manitoba",
		'CA-NB' => "Canada: New Brunswick",
		'CA-NL' => "Canada: Newfoundland and Labrador",
		'CA-NT' => "Canada: Northwest Territories",
		'CA-NS' => "Canada: Nova Scotia",
		'CA-NU' => "Canada: Nunavut",
		'CA-ON' => "Canada: Ontario",
		'CA-PE' => "Canada: Prince Edward Island",
		'CA-QC' => "Canada: Quebec",
		'CA-SK' => "Canada: Saskatchewan",
		'CA-YT' => "Canada: Yukon Territory",

		'--CN'  => "", '-CN' => "China",
		'CN-34' => "China: Anhui",
		'CN-92' => "China: Aomen (Macau)",
		'CN-11' => "China: Beijing",
		'CN-50' => "China: Chongqing",
		'CN-35' => "China: Fujian",
		'CN-62' => "China: Gansu",
		'CN-44' => "China: Guangdong",
		'CN-45' => "China: Guangxi",
		'CN-52' => "China: Guizhou",
		'CN-46' => "China: Hainan",
		'CN-13' => "China: Hebei",
		'CN-23' => "China: Heilongjiang",
		'CN-41' => "China: Henan",
		'CN-42' => "China: Hubei",
		'CN-43' => "China: Hunan",
		'CN-32' => "China: Jiangsu",
		'CN-36' => "China: Jiangxi",
		'CN-22' => "China: Jilin",
		'CN-21' => "China: Liaoning",
		'CN-15' => "China: Nei Mongol",
		'CN-64' => "China: Ningxia",
		'CN-63' => "China: Qinghai",
		'CN-61' => "China: Shaanxi",
		'CN-37' => "China: Shandong",
		'CN-31' => "China: Shanghai",
		'CN-14' => "China: Shanxi",
		'CN-51' => "China: Sichuan",
		'CN-71' => "China: Taiwan",
		'CN-12' => "China: Tianjin",
		'CN-91' => "China: Xianggang (Hong-Kong)",
		'CN-65' => "China: Xinjiang",
		'CN-54' => "China: Xizang",
		'CN-53' => "China: Yunnan",
		'CN-33' => "China: Zhejiang",

		'--CY'  => "", '-CY' => "Cyprus",
		'CY-04' => "Cyprus: Ammóchostos",
		'CY-06' => "Cyprus: Kerýneia",
		'CY-01' => "Cyprus: Lefkosía",
		'CY-02' => "Cyprus: Lemesós",
		'CY-03' => "Cyprus: Lárnaka",
		'CY-05' => "Cyprus: Páfos",

		'--CZ'   => "", '-CZ' => "Czech Republic",
		'CZ-201' => "Czech Republic: Benešov",
		'CZ-202' => "Czech Republic: Beroun",
		'CZ-621' => "Czech Republic: Blansko",
		'CZ-622' => "Czech Republic: Brno-město",
		'CZ-623' => "Czech Republic: Brno-venkov",
		'CZ-801' => "Czech Republic: Bruntál",
		'CZ-624' => "Czech Republic: Břeclav",
		'CZ-411' => "Czech Republic: Cheb",
		'CZ-422' => "Czech Republic: Chomutov",
		'CZ-531' => "Czech Republic: Chrudim",
		'CZ-321' => "Czech Republic: Domažlice",
		'CZ-421' => "Czech Republic: Děčín",
		'CZ-802' => "Czech Republic: Frýdek Místek",
		'CZ-611' => "Czech Republic: Havlíčkův Brod",
		'CZ-625' => "Czech Republic: Hodonín",
		'CZ-521' => "Czech Republic: Hradec Králové",
		'CZ-512' => "Czech Republic: Jablonec nad Nisou",
		'CZ-711' => "Czech Republic: Jeseník",
		'CZ-612' => "Czech Republic: Jihlava",
		'CZ-JM'  => "Czech Republic: Jihomoravský kraj",
		'CZ-JC'  => "Czech Republic: Jihočeský kraj",
		'CZ-313' => "Czech Republic: Jindřichův Hradec",
		'CZ-522' => "Czech Republic: Jičín",
		'CZ-KA'  => "Czech Republic: Karlovarský kraj",
		'CZ-412' => "Czech Republic: Karlovy Vary",
		'CZ-803' => "Czech Republic: Karviná",
		'CZ-203' => "Czech Republic: Kladno",
		'CZ-322' => "Czech Republic: Klatovy",
		'CZ-204' => "Czech Republic: Kolín",
		'CZ-721' => "Czech Republic: Kromĕříž",
		'CZ-KR'  => "Czech Republic: Královéhradecký kraj",
		'CZ-205' => "Czech Republic: Kutná Hora",
		'CZ-513' => "Czech Republic: Liberec",
		'CZ-LI'  => "Czech Republic: Liberecký kraj",
		'CZ-423' => "Czech Republic: Litoměřice",
		'CZ-424' => "Czech Republic: Louny",
		'CZ-207' => "Czech Republic: Mladá Boleslav",
		'CZ-MO'  => "Czech Republic: Moravskoslezský kraj",
		'CZ-425' => "Czech Republic: Most",
		'CZ-206' => "Czech Republic: Mělník",
		'CZ-804' => "Czech Republic: Nový Jičín",
		'CZ-208' => "Czech Republic: Nymburk",
		'CZ-523' => "Czech Republic: Náchod",
		'CZ-712' => "Czech Republic: Olomouc",
		'CZ-OL'  => "Czech Republic: Olomoucký kraj",
		'CZ-805' => "Czech Republic: Opava",
		'CZ-806' => "Czech Republic: Ostrava město",
		'CZ-532' => "Czech Republic: Pardubice",
		'CZ-PA'  => "Czech Republic: Pardubický kraj",
		'CZ-613' => "Czech Republic: Pelhřimov",
		'CZ-324' => "Czech Republic: Plzeň jih",
		'CZ-323' => "Czech Republic: Plzeň město",
		'CZ-325' => "Czech Republic: Plzeň sever",
		'CZ-PL'  => "Czech Republic: Plzeňský kraj",
		'CZ-315' => "Czech Republic: Prachatice",
		'CZ-101' => "Czech Republic: Praha 1",
		'CZ-10A' => "Czech Republic: Praha 10",
		'CZ-10B' => "Czech Republic: Praha 11",
		'CZ-10C' => "Czech Republic: Praha 12",
		'CZ-10D' => "Czech Republic: Praha 13",
		'CZ-10E' => "Czech Republic: Praha 14",
		'CZ-10F' => "Czech Republic: Praha 15",
		'CZ-102' => "Czech Republic: Praha 2",
		'CZ-103' => "Czech Republic: Praha 3",
		'CZ-104' => "Czech Republic: Praha 4",
		'CZ-105' => "Czech Republic: Praha 5",
		'CZ-106' => "Czech Republic: Praha 6",
		'CZ-107' => "Czech Republic: Praha 7",
		'CZ-108' => "Czech Republic: Praha 8",
		'CZ-109' => "Czech Republic: Praha 9",
		'CZ-209' => "Czech Republic: Praha východ",
		'CZ-20A' => "Czech Republic: Praha západ",
		'CZ-PR'  => "Czech Republic: Praha, hlavní město",
		'CZ-713' => "Czech Republic: Prostĕjov",
		'CZ-314' => "Czech Republic: Písek",
		'CZ-714' => "Czech Republic: Přerov",
		'CZ-20B' => "Czech Republic: Příbram",
		'CZ-20C' => "Czech Republic: Rakovník",
		'CZ-326' => "Czech Republic: Rokycany",
		'CZ-524' => "Czech Republic: Rychnov nad Kněžnou",
		'CZ-514' => "Czech Republic: Semily",
		'CZ-413' => "Czech Republic: Sokolov",
		'CZ-316' => "Czech Republic: Strakonice",
		'CZ-ST'  => "Czech Republic: Středočeský kraj",
		'CZ-533' => "Czech Republic: Svitavy",
		'CZ-327' => "Czech Republic: Tachov",
		'CZ-426' => "Czech Republic: Teplice",
		'CZ-525' => "Czech Republic: Trutnov",
		'CZ-317' => "Czech Republic: Tábor",
		'CZ-614' => "Czech Republic: Třebíč",
		'CZ-722' => "Czech Republic: Uherské Hradištĕ",
		'CZ-723' => "Czech Republic: Vsetín",
		'CZ-VY'  => "Czech Republic: Vysočina",
		'CZ-626' => "Czech Republic: Vyškov",
		'CZ-724' => "Czech Republic: Zlín",
		'CZ-ZL'  => "Czech Republic: Zlínský kraj",
		'CZ-627' => "Czech Republic: Znojmo",
		'CZ-US'  => "Czech Republic: Ústecký kraj",
		'CZ-427' => "Czech Republic: Ústí nad Labem",
		'CZ-534' => "Czech Republic: Ústí nad Orlicí",
		'CZ-511' => "Czech Republic: Česká Lípa",
		'CZ-311' => "Czech Republic: České Budějovice",
		'CZ-312' => "Czech Republic: Český Krumlov",
		'CZ-715' => "Czech Republic: Šumperk",
		'CZ-615' => "Czech Republic: Žd’ár nad Sázavou",

		'--DK'  => "", '-DK' => "Denmark",
		'DK-84' => "Denmark: Hovedstaden",
		'DK-82' => "Denmark: Midtjylland",
		'DK-81' => "Denmark: Nordjylland",
		'DK-85' => "Denmark: Sjælland",
		'DK-83' => "Denmark: Syddanmark",

		'--EG'   => "", '-EG' => "Egypt",
		'EG-DK'  => "Egypt: Ad Daqahlīyah",
		'EG-BA'  => "Egypt: Al Bahr al Ahmar",
		'EG-BH'  => "Egypt: Al Buhayrah",
		'EG-FYM' => "Egypt: Al Fayyūm",
		'EG-GH'  => "Egypt: Al Gharbīyah",
		'EG-ALX' => "Egypt: Al Iskandarīyah",
		'EG-IS'  => "Egypt: Al Ismā`īlīyah",
		'EG-GZ'  => "Egypt: Al Jīzah",
		'EG-MN'  => "Egypt: Al Minyā",
		'EG-MNF' => "Egypt: Al Minūfīyah",
		'EG-KB'  => "Egypt: Al Qalyūbīyah",
		'EG-C'   => "Egypt: Al Qāhirah",
		'EG-WAD' => "Egypt: Al Wādī al Jadīd",
		'EG-SUZ' => "Egypt: As Suways",
		'EG-SU'  => "Egypt: As Sādis min Uktūbar",
		'EG-SHR' => "Egypt: Ash Sharqīyah",
		'EG-ASN' => "Egypt: Aswān",
		'EG-AST' => "Egypt: Asyūt",
		'EG-BNS' => "Egypt: Banī Suwayf",
		'EG-PTS' => "Egypt: Būr Sa`īd",
		'EG-DT'  => "Egypt: Dumyāt",
		'EG-JS'  => "Egypt: Janūb Sīnā'",
		'EG-KFS' => "Egypt: Kafr ash Shaykh",
		'EG-MT'  => "Egypt: Matrūh",
		'EG-KN'  => "Egypt: Qinā",
		'EG-SIN' => "Egypt: Shamal Sīnā'",
		'EG-SHG' => "Egypt: Sūhāj",
		'EG-HU'  => "Egypt: Ḩulwān",

		'--FR'  => "", '-FR' => "France",
		'FR-01' => "France: Ain",
		'FR-02' => "France: Aisne",
		'FR-03' => "France: Allier",
		'FR-06' => "France: Alpes-Maritimes",
		'FR-04' => "France: Alpes-de-Haute-Provence",
		'FR-A'  => "France: Alsace",
		'FR-B'  => "France: Aquitaine",
		'FR-08' => "France: Ardennes",
		'FR-07' => "France: Ardèche",
		'FR-09' => "France: Ariège",
		'FR-10' => "France: Aube",
		'FR-11' => "France: Aude",
		'FR-C'  => "France: Auvergne",
		'FR-12' => "France: Aveyron",
		'FR-67' => "France: Bas-Rhin",
		'FR-P'  => "France: Basse-Normandie",
		'FR-13' => "France: Bouches-du-Rhône",
		'FR-D'  => "France: Bourgogne",
		'FR-E'  => "France: Bretagne",
		'FR-14' => "France: Calvados",
		'FR-15' => "France: Cantal",
		'FR-F'  => "France: Centre",
		'FR-G'  => "France: Champagne-Ardenne",
		'FR-16' => "France: Charente",
		'FR-17' => "France: Charente-Maritime",
		'FR-18' => "France: Cher",
		'FR-CP' => "France: Clipperton",
		'FR-19' => "France: Corrèze",
		'FR-H'  => "France: Corse",
		'FR-2A' => "France: Corse-du-Sud",
		'FR-23' => "France: Creuse",
		'FR-21' => "France: Côte-d'Or",
		'FR-22' => "France: Côtes-d'Armor",
		'FR-79' => "France: Deux-Sèvres",
		'FR-24' => "France: Dordogne",
		'FR-25' => "France: Doubs",
		'FR-26' => "France: Drôme",
		'FR-91' => "France: Essonne",
		'FR-27' => "France: Eure",
		'FR-28' => "France: Eure-et-Loir",
		'FR-29' => "France: Finistère",
		'FR-I'  => "France: Franche-Comté",
		'FR-30' => "France: Gard",
		'FR-32' => "France: Gers",
		'FR-33' => "France: Gironde",
		'FR-GP' => "France: Guadeloupe",
		'FR-GF' => "France: Guyane",
		'FR-68' => "France: Haut-Rhin",
		'FR-2B' => "France: Haute-Corse",
		'FR-31' => "France: Haute-Garonne",
		'FR-43' => "France: Haute-Loire",
		'FR-52' => "France: Haute-Marne",
		'FR-Q'  => "France: Haute-Normandie",
		'FR-74' => "France: Haute-Savoie",
		'FR-70' => "France: Haute-Saône",
		'FR-87' => "France: Haute-Vienne",
		'FR-05' => "France: Hautes-Alpes",
		'FR-65' => "France: Hautes-Pyrénées",
		'FR-92' => "France: Hauts-de-Seine",
		'FR-34' => "France: Hérault",
		'FR-35' => "France: Ille-et-Vilaine",
		'FR-36' => "France: Indre",
		'FR-37' => "France: Indre-et-Loire",
		'FR-38' => "France: Isère",
		'FR-39' => "France: Jura",
		'FR-40' => "France: Landes",
		'FR-K'  => "France: Languedoc-Roussillon",
		'FR-L'  => "France: Limousin",
		'FR-41' => "France: Loir-et-Cher",
		'FR-42' => "France: Loire",
		'FR-44' => "France: Loire-Atlantique",
		'FR-45' => "France: Loiret",
		'FR-M'  => "France: Lorraine",
		'FR-46' => "France: Lot",
		'FR-47' => "France: Lot-et-Garonne",
		'FR-48' => "France: Lozère",
		'FR-49' => "France: Maine-et-Loire",
		'FR-50' => "France: Manche",
		'FR-51' => "France: Marne",
		'FR-MQ' => "France: Martinique",
		'FR-53' => "France: Mayenne",
		'FR-YT' => "France: Mayotte",
		'FR-54' => "France: Meurthe-et-Moselle",
		'FR-55' => "France: Meuse",
		'FR-N'  => "France: Midi-Pyrénées",
		'FR-56' => "France: Morbihan",
		'FR-57' => "France: Moselle",
		'FR-58' => "France: Nièvre",
		'FR-59' => "France: Nord",
		'FR-O'  => "France: Nord - Pas-de-Calais",
		'FR-NC' => "France: Nouvelle-Calédonie",
		'FR-60' => "France: Oise",
		'FR-61' => "France: Orne",
		'FR-75' => "France: Paris",
		'FR-62' => "France: Pas-de-Calais",
		'FR-R'  => "France: Pays de la Loire",
		'FR-S'  => "France: Picardie",
		'FR-T'  => "France: Poitou-Charentes",
		'FR-PF' => "France: Polynésie française",
		'FR-U'  => "France: Provence-Alpes-Côte d'Azur",
		'FR-63' => "France: Puy-de-Dôme",
		'FR-64' => "France: Pyrénées-Atlantiques",
		'FR-66' => "France: Pyrénées-Orientales",
		'FR-69' => "France: Rhône",
		'FR-V'  => "France: Rhône-Alpes",
		'FR-RE' => "France: Réunion",
		'FR-BL' => "France: Saint-Barthélemy",
		'FR-MF' => "France: Saint-Martin",
		'FR-PM' => "France: Saint-Pierre-et-Miquelon",
		'FR-72' => "France: Sarthe",
		'FR-73' => "France: Savoie",
		'FR-71' => "France: Saône-et-Loire",
		'FR-76' => "France: Seine-Maritime",
		'FR-93' => "France: Seine-Saint-Denis",
		'FR-77' => "France: Seine-et-Marne",
		'FR-80' => "France: Somme",
		'FR-81' => "France: Tarn",
		'FR-82' => "France: Tarn-et-Garonne",
		'FR-TF' => "France: Terres australes françaises",
		'FR-90' => "France: Territoire de Belfort",
		'FR-95' => "France: Val d'Oise",
		'FR-94' => "France: Val-de-Marne",
		'FR-83' => "France: Var",
		'FR-84' => "France: Vaucluse",
		'FR-85' => "France: Vendée",
		'FR-86' => "France: Vienne",
		'FR-88' => "France: Vosges",
		'FR-WF' => "France: Wallis-et-Futuna",
		'FR-89' => "France: Yonne",
		'FR-78' => "France: Yvelines",
		'FR-J'  => "France: Île-de-France",

		'--DE'  => "", '-DE' => "Germany",
		'DE-BW' => "Germany: Baden-Württemberg",
		'DE-BY' => "Germany: Bayern",
		'DE-BE' => "Germany: Berlin",
		'DE-BB' => "Germany: Brandenburg",
		'DE-HB' => "Germany: Bremen",
		'DE-HH' => "Germany: Hamburg",
		'DE-HE' => "Germany: Hessen",
		'DE-MV' => "Germany: Mecklenburg-Vorpommern",
		'DE-NI' => "Germany: Niedersachsen",
		'DE-NW' => "Germany: Nordrhein-Westfalen",
		'DE-RP' => "Germany: Rheinland-Pfalz",
		'DE-SL' => "Germany: Saarland",
		'DE-SN' => "Germany: Sachsen",
		'DE-ST' => "Germany: Sachsen-Anhalt",
		'DE-SH' => "Germany: Schleswig-Holstein",
		'DE-TH' => "Germany: Thüringen",

		'--GR'  => "", '-GR' => "Greece",
		'GR-13' => "Greece: Achaïa",
		'GR-69' => "Greece: Agio Oros",
		'GR-01' => "Greece: Aitolia kai Akarnania",
		'GR-A'  => "Greece: Anatoliki Makedonia kai Thraki",
		'GR-11' => "Greece: Argolida",
		'GR-12' => "Greece: Arkadia",
		'GR-31' => "Greece: Arta",
		'GR-A1' => "Greece: Attiki",
		'GR-64' => "Greece: Chalkidiki",
		'GR-94' => "Greece: Chania",
		'GR-85' => "Greece: Chios",
		'GR-81' => "Greece: Dodekanisos",
		'GR-52' => "Greece: Drama",
		'GR-G'  => "Greece: Dytiki Ellada",
		'GR-C'  => "Greece: Dytiki Makedonia",
		'GR-71' => "Greece: Evros",
		'GR-05' => "Greece: Evrytania",
		'GR-04' => "Greece: Evvoias",
		'GR-63' => "Greece: Florina",
		'GR-07' => "Greece: Fokida",
		'GR-06' => "Greece: Fthiotida",
		'GR-51' => "Greece: Grevena",
		'GR-14' => "Greece: Ileia",
		'GR-53' => "Greece: Imathia",
		'GR-33' => "Greece: Ioannina",
		'GR-F'  => "Greece: Ionia Nisia",
		'GR-D'  => "Greece: Ipeiros",
		'GR-91' => "Greece: Irakleio",
		'GR-41' => "Greece: Karditsa",
		'GR-56' => "Greece: Kastoria",
		'GR-55' => "Greece: Kavala",
		'GR-23' => "Greece: Kefallonia",
		'GR-B'  => "Greece: Kentriki Makedonia",
		'GR-22' => "Greece: Kerkyra",
		'GR-57' => "Greece: Kilkis",
		'GR-15' => "Greece: Korinthia",
		'GR-58' => "Greece: Kozani",
		'GR-M'  => "Greece: Kriti",
		'GR-82' => "Greece: Kyklades",
		'GR-16' => "Greece: Lakonia",
		'GR-42' => "Greece: Larisa",
		'GR-92' => "Greece: Lasithi",
		'GR-24' => "Greece: Lefkada",
		'GR-83' => "Greece: Lesvos",
		'GR-43' => "Greece: Magnisia",
		'GR-17' => "Greece: Messinia",
		'GR-L'  => "Greece: Notio Aigaio",
		'GR-59' => "Greece: Pella",
		'GR-J'  => "Greece: Peloponnisos",
		'GR-61' => "Greece: Pieria",
		'GR-34' => "Greece: Preveza",
		'GR-93' => "Greece: Rethymno",
		'GR-73' => "Greece: Rodopi",
		'GR-84' => "Greece: Samos",
		'GR-62' => "Greece: Serres",
		'GR-H'  => "Greece: Sterea Ellada",
		'GR-32' => "Greece: Thesprotia",
		'GR-E'  => "Greece: Thessalia",
		'GR-54' => "Greece: Thessaloniki",
		'GR-44' => "Greece: Trikala",
		'GR-03' => "Greece: Voiotia",
		'GR-K'  => "Greece: Voreio Aigaio",
		'GR-72' => "Greece: Xanthi",
		'GR-21' => "Greece: Zakynthos",

		'--HU'  => "", '-HU' => "Hungary",
		'HU-BA' => "Hungary: Baranya",
		'HU-BZ' => "Hungary: Borsod-Abaúj-Zemplén",
		'HU-BU' => "Hungary: Budapest",
		'HU-BK' => "Hungary: Bács-Kiskun",
		'HU-BE' => "Hungary: Békés",
		'HU-BC' => "Hungary: Békéscsaba",
		'HU-CS' => "Hungary: Csongrád",
		'HU-DE' => "Hungary: Debrecen",
		'HU-DU' => "Hungary: Dunaújváros",
		'HU-EG' => "Hungary: Eger",
		'HU-FE' => "Hungary: Fejér",
		'HU-GY' => "Hungary: Győr",
		'HU-GS' => "Hungary: Győr-Moson-Sopron",
		'HU-HB' => "Hungary: Hajdú-Bihar",
		'HU-HE' => "Hungary: Heves",
		'HU-HV' => "Hungary: Hódmezővásárhely",
		'HU-JN' => "Hungary: Jász-Nagykun-Szolnok",
		'HU-KV' => "Hungary: Kaposvár",
		'HU-KM' => "Hungary: Kecskemét",
		'HU-KE' => "Hungary: Komárom-Esztergom",
		'HU-MI' => "Hungary: Miskolc",
		'HU-NK' => "Hungary: Nagykanizsa",
		'HU-NY' => "Hungary: Nyíregyháza",
		'HU-NO' => "Hungary: Nógrád",
		'HU-PE' => "Hungary: Pest",
		'HU-PS' => "Hungary: Pécs",
		'HU-ST' => "Hungary: Salgótarján",
		'HU-SO' => "Hungary: Somogy",
		'HU-SN' => "Hungary: Sopron",
		'HU-SZ' => "Hungary: Szabolcs-Szatmár-Bereg",
		'HU-SD' => "Hungary: Szeged",
		'HU-SS' => "Hungary: Szekszárd",
		'HU-SK' => "Hungary: Szolnok",
		'HU-SH' => "Hungary: Szombathely",
		'HU-SF' => "Hungary: Székesfehérvár",
		'HU-TB' => "Hungary: Tatabánya",
		'HU-TO' => "Hungary: Tolna",
		'HU-VA' => "Hungary: Vas",
		'HU-VM' => "Hungary: Veszprém",
		'HU-VE' => "Hungary: Veszprém (county)",
		'HU-ZA' => "Hungary: Zala",
		'HU-ZE' => "Hungary: Zalaegerszeg",
		'HU-ER' => "Hungary: Érd",

		'--IS' => "", '-IS' => "Iceland",
		'IS-7' => "Iceland: Austurland",
		'IS-1' => "Iceland: Höfuðborgarsvæðið",
		'IS-6' => "Iceland: Norðurland eystra",
		'IS-5' => "Iceland: Norðurland vestra",
		'IS-0' => "Iceland: Reykjavík",
		'IS-8' => "Iceland: Suðurland",
		'IS-2' => "Iceland: Suðurnes",
		'IS-4' => "Iceland: Vestfirðir",
		'IS-3' => "Iceland: Vesturland",

		'--IN'  => "", '-IN' => "India",
		'IN-AN' => "India: Andaman and Nicobar Islands",
		'IN-AP' => "India: Andhra Pradesh",
		'IN-AR' => "India: Arunāchal Pradesh",
		'IN-AS' => "India: Assam",
		'IN-BR' => "India: Bihār",
		'IN-CH' => "India: Chandīgarh",
		'IN-CT' => "India: Chhattīsgarh",
		'IN-DD' => "India: Damān and Diu",
		'IN-DL' => "India: Delhi",
		'IN-DN' => "India: Dādra and Nagar Haveli",
		'IN-GA' => "India: Goa",
		'IN-GJ' => "India: Gujarāt",
		'IN-HR' => "India: Haryāna",
		'IN-HP' => "India: Himāchal Pradesh",
		'IN-JK' => "India: Jammu and Kashmīr",
		'IN-JH' => "India: Jharkhand",
		'IN-KA' => "India: Karnātaka",
		'IN-KL' => "India: Kerala",
		'IN-LD' => "India: Lakshadweep",
		'IN-MP' => "India: Madhya Pradesh",
		'IN-MH' => "India: Mahārāshtra",
		'IN-MN' => "India: Manipur",
		'IN-ML' => "India: Meghālaya",
		'IN-MZ' => "India: Mizoram",
		'IN-NL' => "India: Nāgāland",
		'IN-OR' => "India: Orissa",
		'IN-PY' => "India: Pondicherry",
		'IN-PB' => "India: Punjab",
		'IN-RJ' => "India: Rājasthān",
		'IN-SK' => "India: Sikkim",
		'IN-TN' => "India: Tamil Nādu",
		'IN-TR' => "India: Tripura",
		'IN-UP' => "India: Uttar Pradesh",
		'IN-UL' => "India: Uttaranchal",
		'IN-WB' => "India: West Bengal",

		'--ID'  => "", '-ID' => "Indonesia",
		'ID-AC' => "Indonesia: Aceh",
		'ID-BA' => "Indonesia: Bali",
		'ID-BB' => "Indonesia: Bangka Belitung",
		'ID-BT' => "Indonesia: Banten",
		'ID-BE' => "Indonesia: Bengkulu",
		'ID-GO' => "Indonesia: Gorontalo",
		'ID-JK' => "Indonesia: Jakarta Raya",
		'ID-JA' => "Indonesia: Jambi",
		'ID-JW' => "Indonesia: Jawa",
		'ID-JB' => "Indonesia: Jawa Barat",
		'ID-JT' => "Indonesia: Jawa Tengah",
		'ID-JI' => "Indonesia: Jawa Timur",
		'ID-KA' => "Indonesia: Kalimantan",
		'ID-KB' => "Indonesia: Kalimantan Barat",
		'ID-KS' => "Indonesia: Kalimantan Selatan",
		'ID-KT' => "Indonesia: Kalimantan Tengah",
		'ID-KI' => "Indonesia: Kalimantan Timur",
		'ID-KR' => "Indonesia: Kepulauan Riau",
		'ID-LA' => "Indonesia: Lampung",
		'ID-MA' => "Indonesia: Maluku",
		'ID-MU' => "Indonesia: Maluku Utara",
		'ID-NU' => "Indonesia: Nusa Tenggara",
		'ID-NB' => "Indonesia: Nusa Tenggara Barat",
		'ID-NT' => "Indonesia: Nusa Tenggara Timur",
		'ID-PA' => "Indonesia: Papua",
		'ID-PB' => "Indonesia: Papua Barat",
		'ID-RI' => "Indonesia: Riau",
		'ID-SL' => "Indonesia: Sulawesi",
		'ID-SR' => "Indonesia: Sulawesi Barat",
		'ID-SN' => "Indonesia: Sulawesi Selatan",
		'ID-ST' => "Indonesia: Sulawesi Tengah",
		'ID-SG' => "Indonesia: Sulawesi Tenggara",
		'ID-SA' => "Indonesia: Sulawesi Utara",
		'ID-SM' => "Indonesia: Sumatera",
		'ID-SU' => "Indonesia: Sumatera Utara",
		'ID-SB' => "Indonesia: Sumatra Barat",
		'ID-SS' => "Indonesia: Sumatra Selatan",
		'ID-YO' => "Indonesia: Yogyakarta",

		'--IE'  => "", '-IE' => "Ireland",
		'IE-CW' => "Ireland: Carlow",
		'IE-CN' => "Ireland: Cavan",
		'IE-CE' => "Ireland: Clare",
		'IE-C'  => "Ireland: Connacht",
		'IE-C'  => "Ireland: Cork",
		'IE-DL' => "Ireland: Donegal",
		'IE-D'  => "Ireland: Dublin",
		'IE-G'  => "Ireland: Galway",
		'IE-KY' => "Ireland: Kerry",
		'IE-KE' => "Ireland: Kildare",
		'IE-KK' => "Ireland: Kilkenny",
		'IE-LS' => "Ireland: Laois",
		'IE-L'  => "Ireland: Leinster",
		'IE-LM' => "Ireland: Leitrim",
		'IE-LK' => "Ireland: Limerick",
		'IE-LD' => "Ireland: Longford",
		'IE-LH' => "Ireland: Louth",
		'IE-MO' => "Ireland: Mayo",
		'IE-MH' => "Ireland: Meath",
		'IE-MN' => "Ireland: Monaghan",
		'IE-M'  => "Ireland: Munster",
		'IE-OY' => "Ireland: Offaly",
		'IE-RN' => "Ireland: Roscommon",
		'IE-SO' => "Ireland: Sligo",
		'IE-TA' => "Ireland: Tipperary",
		'IE-U'  => "Ireland: Ulster",
		'IE-WD' => "Ireland: Waterford",
		'IE-WH' => "Ireland: Westmeath",
		'IE-WX' => "Ireland: Wexford",
		'IE-WW' => "Ireland: Wicklow",

		'--IL'  => "", '-IL' => "Israel",
		'IL-D'  => "Israel: HaDarom",
		'IL-M'  => "Israel: HaMerkaz",
		'IL-Z'  => "Israel: HaZafon",
		'IL-HA' => "Israel: Hefa",
		'IL-TA' => "Israel: Tel-Aviv",
		'IL-JM' => "Israel: Yerushalayim Al Quds",

		'--IT'  => "", '-IT' => "Italy",
		'IT-65' => "Italy: Abruzzo",
		'IT-AG' => "Italy: Agrigento",
		'IT-AL' => "Italy: Alessandria",
		'IT-AN' => "Italy: Ancona",
		'IT-AO' => "Italy: Aosta",
		'IT-AR' => "Italy: Arezzo",
		'IT-AP' => "Italy: Ascoli Piceno",
		'IT-AT' => "Italy: Asti",
		'IT-AV' => "Italy: Avellino",
		'IT-BA' => "Italy: Bari",
		'IT-BT' => "Italy: Barletta-Andria-Trani",
		'IT-77' => "Italy: Basilicata",
		'IT-BL' => "Italy: Belluno",
		'IT-BN' => "Italy: Benevento",
		'IT-BG' => "Italy: Bergamo",
		'IT-BI' => "Italy: Biella",
		'IT-BO' => "Italy: Bologna",
		'IT-BZ' => "Italy: Bolzano",
		'IT-BS' => "Italy: Brescia",
		'IT-BR' => "Italy: Brindisi",
		'IT-CA' => "Italy: Cagliari",
		'IT-78' => "Italy: Calabria",
		'IT-CL' => "Italy: Caltanissetta",
		'IT-72' => "Italy: Campania",
		'IT-CB' => "Italy: Campobasso",
		'IT-CI' => "Italy: Carbonia-Iglesias",
		'IT-CE' => "Italy: Caserta",
		'IT-CT' => "Italy: Catania",
		'IT-CZ' => "Italy: Catanzaro",
		'IT-CH' => "Italy: Chieti",
		'IT-CO' => "Italy: Como",
		'IT-CS' => "Italy: Cosenza",
		'IT-CR' => "Italy: Cremona",
		'IT-KR' => "Italy: Crotone",
		'IT-CN' => "Italy: Cuneo",
		'IT-45' => "Italy: Emilia-Romagna",
		'IT-EN' => "Italy: Enna",
		'IT-FM' => "Italy: Fermo",
		'IT-FE' => "Italy: Ferrara",
		'IT-FI' => "Italy: Firenze",
		'IT-FG' => "Italy: Foggia",
		'IT-FC' => "Italy: Forlì-Cesena",
		'IT-36' => "Italy: Friuli-Venezia Giulia",
		'IT-FR' => "Italy: Frosinone",
		'IT-GE' => "Italy: Genova",
		'IT-GO' => "Italy: Gorizia",
		'IT-GR' => "Italy: Grosseto",
		'IT-IM' => "Italy: Imperia",
		'IT-IS' => "Italy: Isernia",
		'IT-AQ' => "Italy: L'Aquila",
		'IT-SP' => "Italy: La Spezia",
		'IT-LT' => "Italy: Latina",
		'IT-62' => "Italy: Lazio",
		'IT-LE' => "Italy: Lecce",
		'IT-LC' => "Italy: Lecco",
		'IT-42' => "Italy: Liguria",
		'IT-LI' => "Italy: Livorno",
		'IT-LO' => "Italy: Lodi",
		'IT-25' => "Italy: Lombardia",
		'IT-LU' => "Italy: Lucca",
		'IT-MC' => "Italy: Macerata",
		'IT-MN' => "Italy: Mantova",
		'IT-57' => "Italy: Marche",
		'IT-MS' => "Italy: Massa-Carrara",
		'IT-MT' => "Italy: Matera",
		'IT-VS' => "Italy: Medio Campidano",
		'IT-ME' => "Italy: Messina",
		'IT-MI' => "Italy: Milano",
		'IT-MO' => "Italy: Modena",
		'IT-67' => "Italy: Molise",
		'IT-MB' => "Italy: Monza e Brianza",
		'IT-NA' => "Italy: Napoli",
		'IT-NO' => "Italy: Novara",
		'IT-NU' => "Italy: Nuoro",
		'IT-OG' => "Italy: Ogliastra",
		'IT-OT' => "Italy: Olbia-Tempio",
		'IT-OR' => "Italy: Oristano",
		'IT-PD' => "Italy: Padova",
		'IT-PA' => "Italy: Palermo",
		'IT-PR' => "Italy: Parma",
		'IT-PV' => "Italy: Pavia",
		'IT-PG' => "Italy: Perugia",
		'IT-PU' => "Italy: Pesaro e Urbino",
		'IT-PE' => "Italy: Pescara",
		'IT-PC' => "Italy: Piacenza",
		'IT-21' => "Italy: Piemonte",
		'IT-PI' => "Italy: Pisa",
		'IT-PT' => "Italy: Pistoia",
		'IT-PN' => "Italy: Pordenone",
		'IT-PZ' => "Italy: Potenza",
		'IT-PO' => "Italy: Prato",
		'IT-75' => "Italy: Puglia",
		'IT-RG' => "Italy: Ragusa",
		'IT-RA' => "Italy: Ravenna",
		'IT-RC' => "Italy: Reggio Calabria",
		'IT-RE' => "Italy: Reggio Emilia",
		'IT-RI' => "Italy: Rieti",
		'IT-RN' => "Italy: Rimini",
		'IT-RM' => "Italy: Roma",
		'IT-RO' => "Italy: Rovigo",
		'IT-SA' => "Italy: Salerno",
		'IT-88' => "Italy: Sardegna",
		'IT-SS' => "Italy: Sassari",
		'IT-SV' => "Italy: Savona",
		'IT-82' => "Italy: Sicilia",
		'IT-SI' => "Italy: Siena",
		'IT-SR' => "Italy: Siracusa",
		'IT-SO' => "Italy: Sondrio",
		'IT-TA' => "Italy: Taranto",
		'IT-TE' => "Italy: Teramo",
		'IT-TR' => "Italy: Terni",
		'IT-TO' => "Italy: Torino",
		'IT-52' => "Italy: Toscana",
		'IT-TP' => "Italy: Trapani",
		'IT-32' => "Italy: Trentino-Alto Adige",
		'IT-TN' => "Italy: Trento",
		'IT-TV' => "Italy: Treviso",
		'IT-TS' => "Italy: Trieste",
		'IT-UD' => "Italy: Udine",
		'IT-55' => "Italy: Umbria",
		'IT-23' => "Italy: Valle d'Aosta",
		'IT-VA' => "Italy: Varese",
		'IT-34' => "Italy: Veneto",
		'IT-VE' => "Italy: Venezia",
		'IT-VB' => "Italy: Verbano-Cusio-Ossola",
		'IT-VC' => "Italy: Vercelli",
		'IT-VR' => "Italy: Verona",
		'IT-VV' => "Italy: Vibo Valentia",
		'IT-VI' => "Italy: Vicenza",
		'IT-VT' => "Italy: Viterbo",

		'--JP'  => "", '-JP' => "Japan",
		'JP-23' => "Japan: Aichi",
		'JP-05' => "Japan: Akita",
		'JP-02' => "Japan: Aomori",
		'JP-12' => "Japan: Chiba",
		'JP-38' => "Japan: Ehime",
		'JP-18' => "Japan: Fukui",
		'JP-40' => "Japan: Fukuoka",
		'JP-07' => "Japan: Fukushima",
		'JP-21' => "Japan: Gifu",
		'JP-10' => "Japan: Gunma",
		'JP-34' => "Japan: Hiroshima",
		'JP-01' => "Japan: Hokkaido",
		'JP-28' => "Japan: Hyogo",
		'JP-08' => "Japan: Ibaraki",
		'JP-17' => "Japan: Ishikawa",
		'JP-03' => "Japan: Iwate",
		'JP-37' => "Japan: Kagawa",
		'JP-46' => "Japan: Kagoshima",
		'JP-14' => "Japan: Kanagawa",
		'JP-39' => "Japan: Kochi",
		'JP-43' => "Japan: Kumamoto",
		'JP-26' => "Japan: Kyoto",
		'JP-24' => "Japan: Mie",
		'JP-04' => "Japan: Miyagi",
		'JP-45' => "Japan: Miyazaki",
		'JP-20' => "Japan: Nagano",
		'JP-42' => "Japan: Nagasaki",
		'JP-29' => "Japan: Nara",
		'JP-15' => "Japan: Niigata",
		'JP-44' => "Japan: Oita",
		'JP-33' => "Japan: Okayama",
		'JP-47' => "Japan: Okinawa",
		'JP-27' => "Japan: Osaka",
		'JP-41' => "Japan: Saga",
		'JP-11' => "Japan: Saitama",
		'JP-25' => "Japan: Shiga",
		'JP-32' => "Japan: Shimane",
		'JP-22' => "Japan: Shizuoka",
		'JP-09' => "Japan: Tochigi",
		'JP-36' => "Japan: Tokushima",
		'JP-13' => "Japan: Tokyo",
		'JP-31' => "Japan: Tottori",
		'JP-16' => "Japan: Toyama",
		'JP-30' => "Japan: Wakayama",
		'JP-06' => "Japan: Yamagata",
		'JP-35' => "Japan: Yamaguchi",
		'JP-19' => "Japan: Yamanashi",

		'--MX'   => "", '-MX' => "Mexico",
		'MX-AGU' => "Mexico: Aguascalientes",
		'MX-BCN' => "Mexico: Baja California",
		'MX-BCS' => "Mexico: Baja California Sur",
		'MX-CAM' => "Mexico: Campeche",
		'MX-CHP' => "Mexico: Chiapas",
		'MX-CHH' => "Mexico: Chihuahua",
		'MX-COA' => "Mexico: Coahuila",
		'MX-COL' => "Mexico: Colima",
		'MX-DIF' => "Mexico: Distrito Federal (Mexico City)",
		'MX-DUR' => "Mexico: Durango",
		'MX-GUA' => "Mexico: Guanajuato",
		'MX-GRO' => "Mexico: Guerrero",
		'MX-HID' => "Mexico: Hidalgo",
		'MX-JAL' => "Mexico: Jalisco",
		'MX-MIC' => "Mexico: Michoacán",
		'MX-MOR' => "Mexico: Morelos",
		'MX-MEX' => "Mexico: México",
		'MX-NAY' => "Mexico: Nayarit",
		'MX-NLE' => "Mexico: Nuevo León",
		'MX-OAX' => "Mexico: Oaxaca",
		'MX-PUE' => "Mexico: Puebla",
		'MX-QUE' => "Mexico: Querétaro",
		'MX-ROO' => "Mexico: Quintana Roo",
		'MX-SLP' => "Mexico: San Luis Potosí",
		'MX-SIN' => "Mexico: Sinaloa",
		'MX-SON' => "Mexico: Sonora",
		'MX-TAB' => "Mexico: Tabasco",
		'MX-TAM' => "Mexico: Tamaulipas",
		'MX-TLA' => "Mexico: Tlaxcala",
		'MX-VER' => "Mexico: Veracruz",
		'MX-YUC' => "Mexico: Yucatán",
		'MX-ZAC' => "Mexico: Zacatecas",

		'--MA'   => "", '-MA' => "Morocco",
		'MA-AGD' => "Morocco: Agadir-Ida-Outanane",
		'MA-HAO' => "Morocco: Al Haouz",
		'MA-HOC' => "Morocco: Al Hoceïma",
		'MA-AOU' => "Morocco: Aousserd",
		'MA-ASZ' => "Morocco: Assa-Zag",
		'MA-AZI' => "Morocco: Azilal",
		'MA-BES' => "Morocco: Ben Slimane",
		'MA-BEM' => "Morocco: Beni Mellal",
		'MA-BER' => "Morocco: Berkane",
		'MA-BOD' => "Morocco: Boujdour (EH)",
		'MA-BOM' => "Morocco: Boulemane",
		'MA-CAS' => "Morocco: Casablanca [Dar el Beïda]",
		'MA-09'  => "Morocco: Chaouia-Ouardigha",
		'MA-CHE' => "Morocco: Chefchaouen",
		'MA-CHI' => "Morocco: Chichaoua",
		'MA-CHT' => "Morocco: Chtouka-Ait Baha",
		'MA-10'  => "Morocco: Doukhala-Abda",
		'MA-HAJ' => "Morocco: El Hajeb",
		'MA-JDI' => "Morocco: El Jadida",
		'MA-ERR' => "Morocco: Errachidia",
		'MA-ESM' => "Morocco: Es Smara (EH)",
		'MA-ESI' => "Morocco: Essaouira",
		'MA-FAH' => "Morocco: Fahs-Beni Makada",
		'MA-FIG' => "Morocco: Figuig",
		'MA-05'  => "Morocco: Fès-Boulemane",
		'MA-FES' => "Morocco: Fès-Dar-Dbibegh",
		'MA-02'  => "Morocco: Gharb-Chrarda-Beni Hssen",
		'MA-08'  => "Morocco: Grand Casablanca",
		'MA-GUE' => "Morocco: Guelmim",
		'MA-14'  => "Morocco: Guelmim-Es Smara",
		'MA-IFR' => "Morocco: Ifrane",
		'MA-INE' => "Morocco: Inezgane-Ait Melloul",
		'MA-JRA' => "Morocco: Jrada",
		'MA-KES' => "Morocco: Kelaat es Sraghna",
		'MA-KHE' => "Morocco: Khemisaet",
		'MA-KHN' => "Morocco: Khenifra",
		'MA-KHO' => "Morocco: Khouribga",
		'MA-KEN' => "Morocco: Kénitra",
		'MA-04'  => "Morocco: L'Oriental",
		'MA-LAR' => "Morocco: Larache",
		'MA-LAA' => "Morocco: Laâyoune (EH)",
		'MA-15'  => "Morocco: Laâyoune-Boujdour-Sakia el Hamra",
		'MA-MMD' => "Morocco: Marrakech-Medina",
		'MA-MMN' => "Morocco: Marrakech-Menara",
		'MA-11'  => "Morocco: Marrakech-Tensift-Al Haouz",
		'MA-MEK' => "Morocco: Meknès",
		'MA-06'  => "Morocco: Meknès-Tafilalet",
		'MA-MOH' => "Morocco: Mohammadia",
		'MA-MOU' => "Morocco: Moulay Yacoub",
		'MA-MED' => "Morocco: Médiouna",
		'MA-NAD' => "Morocco: Nador",
		'MA-NOU' => "Morocco: Nouaceur",
		'MA-OUA' => "Morocco: Ouarzazate",
		'MA-OUD' => "Morocco: Oued ed Dahab (EH)",
		'MA-16'  => "Morocco: Oued ed Dahab-Lagouira",
		'MA-OUJ' => "Morocco: Oujda-Angad",
		'MA-RAB' => "Morocco: Rabat",
		'MA-07'  => "Morocco: Rabat-Salé-Zemmour-Zaer",
		'MA-SAF' => "Morocco: Safi",
		'MA-SAL' => "Morocco: Salé",
		'MA-SEF' => "Morocco: Sefrou",
		'MA-SET' => "Morocco: Settat",
		'MA-SYB' => "Morocco: Sidi Youssef Ben Ali",
		'MA-SIK' => "Morocco: Sidl Kacem",
		'MA-SKH' => "Morocco: Skhirate-Témara",
		'MA-13'  => "Morocco: Sous-Massa-Draa",
		'MA-12'  => "Morocco: Tadla-Azilal",
		'MA-TNT' => "Morocco: Tan-Tan",
		'MA-TNG' => "Morocco: Tanger-Assilah",
		'MA-01'  => "Morocco: Tanger-Tétouan",
		'MA-TAO' => "Morocco: Taounate",
		'MA-TAI' => "Morocco: Taourirt",
		'MA-TAR' => "Morocco: Taroudant",
		'MA-TAT' => "Morocco: Tata",
		'MA-TAZ' => "Morocco: Taza",
		'MA-03'  => "Morocco: Taza-Al Hoceima-Taounate",
		'MA-TIZ' => "Morocco: Tiznit",
		'MA-TET' => "Morocco: Tétouan",
		'MA-ZAG' => "Morocco: Zagora",

		'--NL'  => "", '-NL' => "Netherlands",
		'NL-DR' => "Netherlands: Drenthe",
		'NL-FL' => "Netherlands: Flevoland",
		'NL-FR' => "Netherlands: Friesland",
		'NL-GE' => "Netherlands: Gelderland",
		'NL-GR' => "Netherlands: Groningen",
		'NL-LI' => "Netherlands: Limburg",
		'NL-NB' => "Netherlands: Noord-Brabant",
		'NL-NH' => "Netherlands: Noord-Holland",
		'NL-OV' => "Netherlands: Overijssel",
		'NL-UT' => "Netherlands: Utrecht",
		'NL-ZE' => "Netherlands: Zeeland",
		'NL-ZH' => "Netherlands: Zuid-Holland",

		'--NG'  => "", '-NG' => "Nigeria",
		'NG-AB' => "Nigeria: Abia",
		'NG-FC' => "Nigeria: Abuja Capital Territory",
		'NG-AD' => "Nigeria: Adamawa",
		'NG-AK' => "Nigeria: Akwa Ibom",
		'NG-AN' => "Nigeria: Anambra",
		'NG-BA' => "Nigeria: Bauchi",
		'NG-BY' => "Nigeria: Bayelsa",
		'NG-BE' => "Nigeria: Benue",
		'NG-BO' => "Nigeria: Borno",
		'NG-CR' => "Nigeria: Cross River",
		'NG-DE' => "Nigeria: Delta",
		'NG-EB' => "Nigeria: Ebonyi",
		'NG-ED' => "Nigeria: Edo",
		'NG-EK' => "Nigeria: Ekiti",
		'NG-EN' => "Nigeria: Enugu",
		'NG-GO' => "Nigeria: Gombe",
		'NG-IM' => "Nigeria: Imo",
		'NG-JI' => "Nigeria: Jigawa",
		'NG-KD' => "Nigeria: Kaduna",
		'NG-KN' => "Nigeria: Kano",
		'NG-KT' => "Nigeria: Katsina",
		'NG-KE' => "Nigeria: Kebbi",
		'NG-KO' => "Nigeria: Kogi",
		'NG-KW' => "Nigeria: Kwara",
		'NG-LA' => "Nigeria: Lagos",
		'NG-NA' => "Nigeria: Nassarawa",
		'NG-NI' => "Nigeria: Niger, Níger",
		'NG-OG' => "Nigeria: Ogun",
		'NG-ON' => "Nigeria: Ondo",
		'NG-OS' => "Nigeria: Osun",
		'NG-OY' => "Nigeria: Oyo",
		'NG-PL' => "Nigeria: Plateau",
		'NG-RI' => "Nigeria: Rivers",
		'NG-SO' => "Nigeria: Sokoto",
		'NG-TA' => "Nigeria: Taraba",
		'NG-YO' => "Nigeria: Yobe",
		'NG-ZA' => "Nigeria: Zamfara",

		'--NO'  => "", '-NO' => "Norway",
		'NO-02' => "Norway: Akershus",
		'NO-09' => "Norway: Aust-Agder",
		'NO-06' => "Norway: Buskerud",
		'NO-20' => "Norway: Finnmark",
		'NO-04' => "Norway: Hedmark",
		'NO-12' => "Norway: Hordaland",
		'NO-22' => "Norway: Jan Mayen",
		'NO-15' => "Norway: Møre og Romsdal",
		'NO-17' => "Norway: Nord-Trøndelag",
		'NO-18' => "Norway: Nordland",
		'NO-05' => "Norway: Oppland",
		'NO-03' => "Norway: Oslo",
		'NO-11' => "Norway: Rogaland",
		'NO-14' => "Norway: Sogn og Fjordane",
		'NO-21' => "Norway: Svalbard",
		'NO-16' => "Norway: Sør-Trøndelag",
		'NO-08' => "Norway: Telemark",
		'NO-19' => "Norway: Troms",
		'NO-10' => "Norway: Vest-Agder",
		'NO-07' => "Norway: Vestfold",
		'NO-01' => "Norway: Østfold",

		'--PH'   => "", '-PH' => "Philippines",
		'PH-ABR' => "Philippines: Abra",
		'PH-AGN' => "Philippines: Agusan del Norte",
		'PH-AGS' => "Philippines: Agusan del Sur",
		'PH-AKL' => "Philippines: Aklan",
		'PH-ALB' => "Philippines: Albay",
		'PH-ANT' => "Philippines: Antique",
		'PH-APA' => "Philippines: Apayao",
		'PH-AUR' => "Philippines: Aurora",
		'PH-14'  => "Philippines: Autonomous Region in Muslim Mindanao (ARMM)",
		'PH-BAS' => "Philippines: Basilan",
		'PH-BTN' => "Philippines: Batanes",
		'PH-BTG' => "Philippines: Batangas",
		'PH-BAN' => "Philippines: Batasn",
		'PH-BEN' => "Philippines: Benguet",
		'PH-05'  => "Philippines: Bicol (Region V)",
		'PH-BIL' => "Philippines: Biliran",
		'PH-BOH' => "Philippines: Bohol",
		'PH-BUK' => "Philippines: Bukidnon",
		'PH-BUL' => "Philippines: Bulacan",
		'PH-40'  => "Philippines: CALABARZON (Region IV-A)",
		'PH-CAG' => "Philippines: Cagayan",
		'PH-02'  => "Philippines: Cagayan Valley (Region II)",
		'PH-CAN' => "Philippines: Camarines Norte",
		'PH-CAS' => "Philippines: Camarines Sur",
		'PH-CAM' => "Philippines: Camiguin",
		'PH-CAP' => "Philippines: Capiz",
		'PH-13'  => "Philippines: Caraga (Region XIII)",
		'PH-CAT' => "Philippines: Catanduanes",
		'PH-CAV' => "Philippines: Cavite",
		'PH-CEB' => "Philippines: Cebu",
		'PH-03'  => "Philippines: Central Luzon (Region III)",
		'PH-07'  => "Philippines: Central Visayas (Region VII)",
		'PH-COM' => "Philippines: Compostela Valley",
		'PH-15'  => "Philippines: Cordillera Administrative Region (CAR)",
		'PH-11'  => "Philippines: Davao (Region XI)",
		'PH-DAO' => "Philippines: Davao Oriental",
		'PH-DAV' => "Philippines: Davao del Norte",
		'PH-DAS' => "Philippines: Davao del Sur",
		'PH-DIN' => "Philippines: Dinagat Islands",
		'PH-EAS' => "Philippines: Eastern Samar",
		'PH-08'  => "Philippines: Eastern Visayas (Region VIII)",
		'PH-GUI' => "Philippines: Guimaras",
		'PH-IFU' => "Philippines: Ifugao",
		'PH-01'  => "Philippines: Ilocos (Region I)",
		'PH-ILN' => "Philippines: Ilocos Norte",
		'PH-ILS' => "Philippines: Ilocos Sur",
		'PH-ILI' => "Philippines: Iloilo",
		'PH-ISA' => "Philippines: Isabela",
		'PH-KAL' => "Philippines: Kalinga-Apayso",
		'PH-LUN' => "Philippines: La Union",
		'PH-LAG' => "Philippines: Laguna",
		'PH-LAN' => "Philippines: Lanao del Norte",
		'PH-LAS' => "Philippines: Lanao del Sur",
		'PH-LEY' => "Philippines: Leyte",
		'PH-41'  => "Philippines: MIMAROPA (Region IV-B)",
		'PH-MAG' => "Philippines: Maguindanao",
		'PH-MAD' => "Philippines: Marinduque",
		'PH-MAS' => "Philippines: Masbate",
		'PH-MDC' => "Philippines: Mindoro Occidental",
		'PH-MDR' => "Philippines: Mindoro Oriental",
		'PH-MSC' => "Philippines: Misamis Occidental",
		'PH-MSR' => "Philippines: Misamis Oriental",
		'PH-MOU' => "Philippines: Mountain Province",
		'PH-00'  => "Philippines: National Capital Region",
		'PH-NEC' => "Philippines: Negroe Occidental",
		'PH-NER' => "Philippines: Negros Oriental",
		'PH-NCO' => "Philippines: North Cotabato",
		'PH-10'  => "Philippines: Northern Mindanao (Region X)",
		'PH-NSA' => "Philippines: Northern Samar",
		'PH-NUE' => "Philippines: Nueva Ecija",
		'PH-NUV' => "Philippines: Nueva Vizcaya",
		'PH-PLW' => "Philippines: Palawan",
		'PH-PAM' => "Philippines: Pampanga",
		'PH-PAN' => "Philippines: Pangasinan",
		'PH-QUE' => "Philippines: Quezon",
		'PH-QUI' => "Philippines: Quirino",
		'PH-RIZ' => "Philippines: Rizal",
		'PH-ROM' => "Philippines: Romblon",
		'PH-SAR' => "Philippines: Sarangani",
		'PH-SIG' => "Philippines: Siquijor",
		'PH-12'  => "Philippines: Soccsksargen (Region XII)",
		'PH-SOR' => "Philippines: Sorsogon",
		'PH-SCO' => "Philippines: South Cotabato",
		'PH-SLE' => "Philippines: Southern Leyte",
		'PH-SUK' => "Philippines: Sultan Kudarat",
		'PH-SLU' => "Philippines: Sulu",
		'PH-SUN' => "Philippines: Surigao del Norte",
		'PH-SUR' => "Philippines: Surigao del Sur",
		'PH-TAR' => "Philippines: Tarlac",
		'PH-TAW' => "Philippines: Tawi-Tawi",
		'PH-WSA' => "Philippines: Western Samar",
		'PH-06'  => "Philippines: Western Visayas (Region VI)",
		'PH-ZMB' => "Philippines: Zambales",
		'PH-09'  => "Philippines: Zamboanga Peninsula (Region IX)",
		'PH-ZSI' => "Philippines: Zamboanga Sibugay",
		'PH-ZAN' => "Philippines: Zamboanga del Norte",
		'PH-ZAS' => "Philippines: Zamboanga del Sur",

		'--PL'  => "", '-PL' => "Poland",
		'PL-DS' => "Poland: Dolnośląskie",
		'PL-KP' => "Poland: Kujawsko-pomorskie",
		'PL-LU' => "Poland: Lubelskie",
		'PL-LB' => "Poland: Lubuskie",
		'PL-MZ' => "Poland: Mazowieckie",
		'PL-MA' => "Poland: Małopolskie",
		'PL-OP' => "Poland: Opolskie",
		'PL-PK' => "Poland: Podkarpackie",
		'PL-PD' => "Poland: Podlaskie",
		'PL-PM' => "Poland: Pomorskie",
		'PL-WN' => "Poland: Warmińsko-mazurskie",
		'PL-WP' => "Poland: Wielkopolskie",
		'PL-ZP' => "Poland: Zachodniopomorskie",
		'PL-LD' => "Poland: Łódzkie",
		'PL-SL' => "Poland: Śląskie",
		'PL-SK' => "Poland: Świętokrzyskie",

		'--PT'  => "", '-PT' => "Portugal",
		'PT-01' => "Portugal: Aveiro",
		'PT-02' => "Portugal: Beja",
		'PT-03' => "Portugal: Braga",
		'PT-04' => "Portugal: Bragança",
		'PT-05' => "Portugal: Castelo Branco",
		'PT-06' => "Portugal: Coimbra",
		'PT-08' => "Portugal: Faro",
		'PT-09' => "Portugal: Guarda",
		'PT-10' => "Portugal: Leiria",
		'PT-11' => "Portugal: Lisboa",
		'PT-12' => "Portugal: Portalegre",
		'PT-13' => "Portugal: Porto",
		'PT-30' => "Portugal: Região Autónoma da Madeira",
		'PT-20' => "Portugal: Região Autónoma dos Açores",
		'PT-14' => "Portugal: Santarém",
		'PT-15' => "Portugal: Setúbal",
		'PT-16' => "Portugal: Viana do Castelo",
		'PT-17' => "Portugal: Vila Real",
		'PT-18' => "Portugal: Viseu",
		'PT-07' => "Portugal: Évora",

		'--RO'  => "", '-RO' => "Romania",
		'RO-AB' => "Romania: Alba",
		'RO-AR' => "Romania: Arad",
		'RO-AG' => "Romania: Argeș",
		'RO-BC' => "Romania: Bacău",
		'RO-BH' => "Romania: Bihor",
		'RO-BN' => "Romania: Bistrița-Năsăud",
		'RO-BT' => "Romania: Botoșani",
		'RO-BV' => "Romania: Brașov",
		'RO-BR' => "Romania: Brăila",
		'RO-B'  => "Romania: București",
		'RO-BZ' => "Romania: Buzău",
		'RO-CS' => "Romania: Caraș-Severin",
		'RO-CJ' => "Romania: Cluj",
		'RO-CT' => "Romania: Constanța",
		'RO-CV' => "Romania: Covasna",
		'RO-CL' => "Romania: Călărași",
		'RO-DJ' => "Romania: Dolj",
		'RO-DB' => "Romania: Dâmbovița",
		'RO-GL' => "Romania: Galați",
		'RO-GR' => "Romania: Giurgiu",
		'RO-GJ' => "Romania: Gorj",
		'RO-HR' => "Romania: Harghita",
		'RO-HD' => "Romania: Hunedoara",
		'RO-IL' => "Romania: Ialomița",
		'RO-IS' => "Romania: Iași",
		'RO-IF' => "Romania: Ilfov",
		'RO-MM' => "Romania: Maramureș",
		'RO-MH' => "Romania: Mehedinți",
		'RO-MS' => "Romania: Mureș",
		'RO-NT' => "Romania: Neamț",
		'RO-OT' => "Romania: Olt",
		'RO-PH' => "Romania: Prahova",
		'RO-SM' => "Romania: Satu Mare",
		'RO-SB' => "Romania: Sibiu",
		'RO-SV' => "Romania: Suceava",
		'RO-SJ' => "Romania: Sălaj",
		'RO-TR' => "Romania: Teleorman",
		'RO-TM' => "Romania: Timiș",
		'RO-TL' => "Romania: Tulcea",
		'RO-VS' => "Romania: Vaslui",
		'RO-VN' => "Romania: Vrancea",
		'RO-VL' => "Romania: Vâlcea",

		'--RU'   => "", '-RU' => "Russian Federation",
		'RU-AD'  => "Russian Federation: Adygeya, Respublika",
		'RU-AL'  => "Russian Federation: Altay, Respublika",
		'RU-ALT' => "Russian Federation: Altayskiy kray",
		'RU-AMU' => "Russian Federation: Amurskaya oblast'",
		'RU-ARK' => "Russian Federation: Arkhangel'skaya oblast'",
		'RU-AST' => "Russian Federation: Astrakhanskaya oblast'",
		'RU-BA'  => "Russian Federation: Bashkortostan, Respublika",
		'RU-BEL' => "Russian Federation: Belgorodskaya oblast'",
		'RU-BRY' => "Russian Federation: Bryanskaya oblast'",
		'RU-BU'  => "Russian Federation: Buryatiya, Respublika",
		'RU-CE'  => "Russian Federation: Chechenskaya Respublika",
		'RU-CHE' => "Russian Federation: Chelyabinskaya oblast'",
		'RU-CHU' => "Russian Federation: Chukotskiy avtonomnyy okrug",
		'RU-CU'  => "Russian Federation: Chuvashskaya Respublika",
		'RU-DA'  => "Russian Federation: Dagestan, Respublika",
		'RU-IRK' => "Russian Federation: Irkutiskaya oblast'",
		'RU-IVA' => "Russian Federation: Ivanovskaya oblast'",
		'RU-KB'  => "Russian Federation: Kabardino-Balkarskaya Respublika",
		'RU-KGD' => "Russian Federation: Kaliningradskaya oblast'",
		'RU-KL'  => "Russian Federation: Kalmykiya, Respublika",
		'RU-KLU' => "Russian Federation: Kaluzhskaya oblast'",
		'RU-KAM' => "Russian Federation: Kamchatskiy kray",
		'RU-KC'  => "Russian Federation: Karachayevo-Cherkesskaya Respublika",
		'RU-KR'  => "Russian Federation: Kareliya, Respublika",
		'RU-KEM' => "Russian Federation: Kemerovskaya oblast'",
		'RU-KHA' => "Russian Federation: Khabarovskiy kray",
		'RU-KK'  => "Russian Federation: Khakasiya, Respublika",
		'RU-KHM' => "Russian Federation: Khanty-Mansiysky avtonomnyy okrug-Yugra",
		'RU-KIR' => "Russian Federation: Kirovskaya oblast'",
		'RU-KO'  => "Russian Federation: Komi, Respublika",
		'RU-KOS' => "Russian Federation: Kostromskaya oblast'",
		'RU-KDA' => "Russian Federation: Krasnodarskiy kray",
		'RU-KYA' => "Russian Federation: Krasnoyarskiy kray",
		'RU-KGN' => "Russian Federation: Kurganskaya oblast'",
		'RU-KRS' => "Russian Federation: Kurskaya oblast'",
		'RU-LEN' => "Russian Federation: Leningradskaya oblast'",
		'RU-LIP' => "Russian Federation: Lipetskaya oblast'",
		'RU-MAG' => "Russian Federation: Magadanskaya oblast'",
		'RU-ME'  => "Russian Federation: Mariy El, Respublika",
		'RU-MO'  => "Russian Federation: Mordoviya, Respublika",
		'RU-MOS' => "Russian Federation: Moskovskaya oblast'",
		'RU-MOW' => "Russian Federation: Moskva",
		'RU-MUR' => "Russian Federation: Murmanskaya oblast'",
		'RU-NEN' => "Russian Federation: Nenetskiy avtonomnyy okrug",
		'RU-NIZ' => "Russian Federation: Nizhegorodskaya oblast'",
		'RU-NGR' => "Russian Federation: Novgorodskaya oblast'",
		'RU-NVS' => "Russian Federation: Novosibirskaya oblast'",
		'RU-OMS' => "Russian Federation: Omskaya oblast'",
		'RU-ORE' => "Russian Federation: Orenburgskaya oblast'",
		'RU-ORL' => "Russian Federation: Orlovskaya oblast'",
		'RU-PNZ' => "Russian Federation: Penzenskaya oblast'",
		'RU-PER' => "Russian Federation: Permskiy kray",
		'RU-PRI' => "Russian Federation: Primorskiy kray",
		'RU-PSK' => "Russian Federation: Pskovskaya oblast'",
		'RU-IN'  => "Russian Federation: Respublika Ingushetiya",
		'RU-ROS' => "Russian Federation: Rostovskaya oblast'",
		'RU-RYA' => "Russian Federation: Ryazanskaya oblast'",
		'RU-SA'  => "Russian Federation: Sakha, Respublika [Yakutiya]",
		'RU-SAK' => "Russian Federation: Sakhalinskaya oblast'",
		'RU-SAM' => "Russian Federation: Samaraskaya oblast'",
		'RU-SPE' => "Russian Federation: Sankt-Peterburg",
		'RU-SAR' => "Russian Federation: Saratovskaya oblast'",
		'RU-SE'  => "Russian Federation: Severnaya Osetiya-Alaniya, Respublika",
		'RU-SMO' => "Russian Federation: Smolenskaya oblast'",
		'RU-STA' => "Russian Federation: Stavropol'skiy kray",
		'RU-SVE' => "Russian Federation: Sverdlovskaya oblast'",
		'RU-TAM' => "Russian Federation: Tambovskaya oblast'",
		'RU-TA'  => "Russian Federation: Tatarstan, Respublika",
		'RU-TOM' => "Russian Federation: Tomskaya oblast'",
		'RU-TUL' => "Russian Federation: Tul'skaya oblast'",
		'RU-TVE' => "Russian Federation: Tverskaya oblast'",
		'RU-TYU' => "Russian Federation: Tyumenskaya oblast'",
		'RU-TY'  => "Russian Federation: Tyva, Respublika [Tuva]",
		'RU-UD'  => "Russian Federation: Udmurtskaya Respublika",
		'RU-ULY' => "Russian Federation: Ul'yanovskaya oblast'",
		'RU-VLA' => "Russian Federation: Vladimirskaya oblast'",
		'RU-VGG' => "Russian Federation: Volgogradskaya oblast'",
		'RU-VLG' => "Russian Federation: Vologodskaya oblast'",
		'RU-VOR' => "Russian Federation: Voronezhskaya oblast'",
		'RU-YAN' => "Russian Federation: Yamalo-Nenetskiy avtonomnyy okrug",
		'RU-YAR' => "Russian Federation: Yaroslavskaya oblast'",
		'RU-YEV' => "Russian Federation: Yevreyskaya avtonomnaya oblast'",
		'RU-ZAB' => "Russian Federation: Zabajkal'skij kraj",

		'--SK'  => "", '-SK' => "Slovakia",
		'SK-BC' => "Slovakia: Banskobystrický kraj",
		'SK-BL' => "Slovakia: Bratislavský kraj",
		'SK-KI' => "Slovakia: Košický kraj",
		'SK-NI' => "Slovakia: Nitriansky kraj",
		'SK-PV' => "Slovakia: Prešovský kraj",
		'SK-TC' => "Slovakia: Trenčiansky kraj",
		'SK-TA' => "Slovakia: Trnavský kraj",
		'SK-ZI' => "Slovakia: Žilinský kraj",

		'--SI'   => "", '-SI' => "Slovenia",
		'SI-001' => "Slovenia: Ajdovščina",
		'SI-195' => "Slovenia: Apače",
		'SI-002' => "Slovenia: Beltinci",
		'SI-148' => "Slovenia: Benedikt",
		'SI-149' => "Slovenia: Bistrica ob Sotli",
		'SI-003' => "Slovenia: Bled",
		'SI-150' => "Slovenia: Bloke",
		'SI-004' => "Slovenia: Bohinj",
		'SI-005' => "Slovenia: Borovnica",
		'SI-006' => "Slovenia: Bovec",
		'SI-151' => "Slovenia: Braslovče",
		'SI-007' => "Slovenia: Brda",
		'SI-008' => "Slovenia: Brezovica",
		'SI-009' => "Slovenia: Brežice",
		'SI-152' => "Slovenia: Cankova",
		'SI-011' => "Slovenia: Celje",
		'SI-012' => "Slovenia: Cerklje na Gorenjskem",
		'SI-013' => "Slovenia: Cerknica",
		'SI-014' => "Slovenia: Cerkno",
		'SI-153' => "Slovenia: Cerkvenjak",
		'SI-196' => "Slovenia: Cirkulane",
		'SI-018' => "Slovenia: Destrnik",
		'SI-019' => "Slovenia: Divača",
		'SI-154' => "Slovenia: Dobje",
		'SI-020' => "Slovenia: Dobrepolje",
		'SI-155' => "Slovenia: Dobrna",
		'SI-021' => "Slovenia: Dobrova-Polhov Gradec",
		'SI-156' => "Slovenia: Dobrovnik/Dobronak",
		'SI-022' => "Slovenia: Dol pri Ljubljani",
		'SI-157' => "Slovenia: Dolenjske Toplice",
		'SI-023' => "Slovenia: Domžale",
		'SI-024' => "Slovenia: Dornava",
		'SI-025' => "Slovenia: Dravograd",
		'SI-026' => "Slovenia: Duplek",
		'SI-027' => "Slovenia: Gorenja vas-Poljane",
		'SI-028' => "Slovenia: Gorišnica",
		'SI-207' => "Slovenia: Gorje",
		'SI-029' => "Slovenia: Gornja Radgona",
		'SI-030' => "Slovenia: Gornji Grad",
		'SI-031' => "Slovenia: Gornji Petrovci",
		'SI-158' => "Slovenia: Grad",
		'SI-032' => "Slovenia: Grosuplje",
		'SI-159' => "Slovenia: Hajdina",
		'SI-161' => "Slovenia: Hodoš/Hodos",
		'SI-162' => "Slovenia: Horjul",
		'SI-160' => "Slovenia: Hoče-Slivnica",
		'SI-034' => "Slovenia: Hrastnik",
		'SI-035' => "Slovenia: Hrpelje-Kozina",
		'SI-036' => "Slovenia: Idrija",
		'SI-037' => "Slovenia: Ig",
		'SI-038' => "Slovenia: Ilirska Bistrica",
		'SI-039' => "Slovenia: Ivančna Gorica",
		'SI-040' => "Slovenia: Izola/Isola",
		'SI-041' => "Slovenia: Jesenice",
		'SI-163' => "Slovenia: Jezersko",
		'SI-042' => "Slovenia: Juršinci",
		'SI-043' => "Slovenia: Kamnik",
		'SI-044' => "Slovenia: Kanal",
		'SI-045' => "Slovenia: Kidričevo",
		'SI-046' => "Slovenia: Kobarid",
		'SI-047' => "Slovenia: Kobilje",
		'SI-049' => "Slovenia: Komen",
		'SI-164' => "Slovenia: Komenda",
		'SI-050' => "Slovenia: Koper/Capodistria",
		'SI-197' => "Slovenia: Kosanjevica na Krki",
		'SI-165' => "Slovenia: Kostel",
		'SI-051' => "Slovenia: Kozje",
		'SI-048' => "Slovenia: Kočevje",
		'SI-052' => "Slovenia: Kranj",
		'SI-053' => "Slovenia: Kranjska Gora",
		'SI-166' => "Slovenia: Križevci",
		'SI-054' => "Slovenia: Krško",
		'SI-055' => "Slovenia: Kungota",
		'SI-056' => "Slovenia: Kuzma",
		'SI-057' => "Slovenia: Laško",
		'SI-058' => "Slovenia: Lenart",
		'SI-059' => "Slovenia: Lendava/Lendva",
		'SI-060' => "Slovenia: Litija",
		'SI-061' => "Slovenia: Ljubljana",
		'SI-062' => "Slovenia: Ljubno",
		'SI-063' => "Slovenia: Ljutomer",
		'SI-208' => "Slovenia: Log-Dragomer",
		'SI-064' => "Slovenia: Logatec",
		'SI-167' => "Slovenia: Lovrenc na Pohorju",
		'SI-065' => "Slovenia: Loška dolina",
		'SI-066' => "Slovenia: Loški Potok",
		'SI-068' => "Slovenia: Lukovica",
		'SI-067' => "Slovenia: Luče",
		'SI-069' => "Slovenia: Majšperk",
		'SI-198' => "Slovenia: Makole",
		'SI-070' => "Slovenia: Maribor",
		'SI-168' => "Slovenia: Markovci",
		'SI-071' => "Slovenia: Medvode",
		'SI-072' => "Slovenia: Mengeš",
		'SI-073' => "Slovenia: Metlika",
		'SI-074' => "Slovenia: Mežica",
		'SI-169' => "Slovenia: Miklavž na Dravskem polju",
		'SI-075' => "Slovenia: Miren-Kostanjevica",
		'SI-170' => "Slovenia: Mirna Peč",
		'SI-076' => "Slovenia: Mislinja",
		'SI-199' => "Slovenia: Mokronog-Trebelno",
		'SI-078' => "Slovenia: Moravske Toplice",
		'SI-077' => "Slovenia: Moravče",
		'SI-079' => "Slovenia: Mozirje",
		'SI-080' => "Slovenia: Murska Sobota",
		'SI-081' => "Slovenia: Muta",
		'SI-082' => "Slovenia: Naklo",
		'SI-083' => "Slovenia: Nazarje",
		'SI-084' => "Slovenia: Nova Gorica",
		'SI-085' => "Slovenia: Novo mesto",
		'SI-086' => "Slovenia: Odranci",
		'SI-171' => "Slovenia: Oplotnica",
		'SI-087' => "Slovenia: Ormož",
		'SI-088' => "Slovenia: Osilnica",
		'SI-089' => "Slovenia: Pesnica",
		'SI-090' => "Slovenia: Piran/Pirano",
		'SI-091' => "Slovenia: Pivka",
		'SI-172' => "Slovenia: Podlehnik",
		'SI-093' => "Slovenia: Podvelka",
		'SI-092' => "Slovenia: Podčetrtek",
		'SI-200' => "Slovenia: Poljčane",
		'SI-173' => "Slovenia: Polzela",
		'SI-094' => "Slovenia: Postojna",
		'SI-174' => "Slovenia: Prebold",
		'SI-095' => "Slovenia: Preddvor",
		'SI-175' => "Slovenia: Prevalje",
		'SI-096' => "Slovenia: Ptuj",
		'SI-097' => "Slovenia: Puconci",
		'SI-100' => "Slovenia: Radenci",
		'SI-099' => "Slovenia: Radeče",
		'SI-101' => "Slovenia: Radlje ob Dravi",
		'SI-102' => "Slovenia: Radovljica",
		'SI-103' => "Slovenia: Ravne na Koroškem",
		'SI-176' => "Slovenia: Razkrižje",
		'SI-098' => "Slovenia: Rače-Fram",
		'SI-201' => "Slovenia: Renče-Vogrsko",
		'SI-209' => "Slovenia: Rečica ob Savinji",
		'SI-104' => "Slovenia: Ribnica",
		'SI-177' => "Slovenia: Ribnica na Pohorju",
		'SI-107' => "Slovenia: Rogatec",
		'SI-106' => "Slovenia: Rogaška Slatina",
		'SI-105' => "Slovenia: Rogašovci",
		'SI-108' => "Slovenia: Ruše",
		'SI-178' => "Slovenia: Selnica ob Dravi",
		'SI-109' => "Slovenia: Semič",
		'SI-110' => "Slovenia: Sevnica",
		'SI-111' => "Slovenia: Sežana",
		'SI-112' => "Slovenia: Slovenj Gradec",
		'SI-113' => "Slovenia: Slovenska Bistrica",
		'SI-114' => "Slovenia: Slovenske Konjice",
		'SI-179' => "Slovenia: Sodražica",
		'SI-180' => "Slovenia: Solčava",
		'SI-202' => "Slovenia: Središče ob Dravi",
		'SI-115' => "Slovenia: Starče",
		'SI-203' => "Slovenia: Straža",
		'SI-181' => "Slovenia: Sveta Ana",
		'SI-182' => "Slovenia: Sveta Andraž v Slovenskih Goricah",
		'SI-204' => "Slovenia: Sveta Trojica v Slovenskih Goricah",
		'SI-116' => "Slovenia: Sveti Jurij",
		'SI-210' => "Slovenia: Sveti Jurij v Slovenskih Goricah",
		'SI-205' => "Slovenia: Sveti Tomaž",
		'SI-184' => "Slovenia: Tabor",
		'SI-010' => "Slovenia: Tišina",
		'SI-128' => "Slovenia: Tolmin",
		'SI-129' => "Slovenia: Trbovlje",
		'SI-130' => "Slovenia: Trebnje",
		'SI-185' => "Slovenia: Trnovska vas",
		'SI-186' => "Slovenia: Trzin",
		'SI-131' => "Slovenia: Tržič",
		'SI-132' => "Slovenia: Turnišče",
		'SI-133' => "Slovenia: Velenje",
		'SI-187' => "Slovenia: Velika Polana",
		'SI-134' => "Slovenia: Velike Lašče",
		'SI-188' => "Slovenia: Veržej",
		'SI-135' => "Slovenia: Videm",
		'SI-136' => "Slovenia: Vipava",
		'SI-137' => "Slovenia: Vitanje",
		'SI-138' => "Slovenia: Vodice",
		'SI-139' => "Slovenia: Vojnik",
		'SI-189' => "Slovenia: Vransko",
		'SI-140' => "Slovenia: Vrhnika",
		'SI-141' => "Slovenia: Vuzenica",
		'SI-142' => "Slovenia: Zagorje ob Savi",
		'SI-143' => "Slovenia: Zavrč",
		'SI-144' => "Slovenia: Zreče",
		'SI-015' => "Slovenia: Črenšovci",
		'SI-016' => "Slovenia: Črna na Koroškem",
		'SI-017' => "Slovenia: Črnomelj",
		'SI-033' => "Slovenia: Šalovci",
		'SI-183' => "Slovenia: Šempeter-Vrtojba",
		'SI-118' => "Slovenia: Šentilj",
		'SI-119' => "Slovenia: Šentjernej",
		'SI-120' => "Slovenia: Šentjur",
		'SI-211' => "Slovenia: Šentrupert",
		'SI-117' => "Slovenia: Šenčur",
		'SI-121' => "Slovenia: Škocjan",
		'SI-122' => "Slovenia: Škofja Loka",
		'SI-123' => "Slovenia: Škofljica",
		'SI-124' => "Slovenia: Šmarje pri Jelšah",
		'SI-206' => "Slovenia: Šmarjeske Topliče",
		'SI-125' => "Slovenia: Šmartno ob Paki",
		'SI-194' => "Slovenia: Šmartno pri Litiji",
		'SI-126' => "Slovenia: Šoštanj",
		'SI-127' => "Slovenia: Štore",
		'SI-190' => "Slovenia: Žalec",
		'SI-146' => "Slovenia: Železniki",
		'SI-191' => "Slovenia: Žetale",
		'SI-147' => "Slovenia: Žiri",
		'SI-192' => "Slovenia: Žirovnica",
		'SI-193' => "Slovenia: Žužemberk",

		'--ZA'  => "", '-ZA' => "South Africa",
		'ZA-EC' => "South Africa: Eastern Cape",
		'ZA-FS' => "South Africa: Free State",
		'ZA-GT' => "South Africa: Gauteng",
		'ZA-NL' => "South Africa: Kwazulu-Natal",
		'ZA-LP' => "South Africa: Limpopo",
		'ZA-MP' => "South Africa: Mpumalanga",
		'ZA-NW' => "South Africa: North-West (South Africa)",
		'ZA-NC' => "South Africa: Northern Cape",
		'ZA-WC' => "South Africa: Western Cape",

		'--ES'  => "", '-ES' => "Spain",
		'ES-C'  => "Spain: A Coruña",
		'ES-AB' => "Spain: Albacete",
		'ES-A'  => "Spain: Alicante",
		'ES-AL' => "Spain: Almería",
		'ES-AN' => "Spain: Andalucía",
		'ES-AR' => "Spain: Aragón",
		'ES-O'  => "Spain: Asturias",
		'ES-AS' => "Spain: Asturias, Principado de",
		'ES-BA' => "Spain: Badajoz",
		'ES-PM' => "Spain: Balears",
		'ES-B'  => "Spain: Barcelona",
		'ES-BU' => "Spain: Burgos",
		'ES-CN' => "Spain: Canarias",
		'ES-S'  => "Spain: Cantabria",
		'ES-CS' => "Spain: Castellón",
		'ES-CL' => "Spain: Castilla y León",
		'ES-CM' => "Spain: Castilla-La Mancha",
		'ES-CT' => "Spain: Catalunya",
		'ES-CE' => "Spain: Ceuta",
		'ES-CR' => "Spain: Ciudad Real",
		'ES-CU' => "Spain: Cuenca",
		'ES-CC' => "Spain: Cáceres",
		'ES-CA' => "Spain: Cádiz",
		'ES-CO' => "Spain: Córdoba",
		'ES-EX' => "Spain: Extremadura",
		'ES-GA' => "Spain: Galicia",
		'ES-GI' => "Spain: Girona",
		'ES-GR' => "Spain: Granada",
		'ES-GU' => "Spain: Guadalajara",
		'ES-SS' => "Spain: Guipúzcoa / Gipuzkoa",
		'ES-H'  => "Spain: Huelva",
		'ES-HU' => "Spain: Huesca",
		'ES-IB' => "Spain: Illes Balears",
		'ES-J'  => "Spain: Jaén",
		'ES-LO' => "Spain: La Rioja",
		'ES-GC' => "Spain: Las Palmas",
		'ES-LE' => "Spain: León",
		'ES-L'  => "Spain: Lleida",
		'ES-LU' => "Spain: Lugo",
		'ES-M'  => "Spain: Madrid",
		'ES-MD' => "Spain: Madrid, Comunidad de",
		'ES-ML' => "Spain: Melilla",
		'ES-MU' => "Spain: Murcia",
		'ES-MC' => "Spain: Murcia, Región de",
		'ES-MA' => "Spain: Málaga",
		'ES-NA' => "Spain: Navarra / Nafarroa",
		'ES-NC' => "Spain: Navarra, Comunidad Foral de / Nafarroako Foru Komunitatea",
		'ES-OR' => "Spain: Ourense",
		'ES-P'  => "Spain: Palencia",
		'ES-PV' => "Spain: País Vasco / Euskal Herria",
		'ES-PO' => "Spain: Pontevedra",
		'ES-SA' => "Spain: Salamanca",
		'ES-TF' => "Spain: Santa Cruz de Tenerife",
		'ES-SG' => "Spain: Segovia",
		'ES-SE' => "Spain: Sevilla",
		'ES-SO' => "Spain: Soria",
		'ES-T'  => "Spain: Tarragona",
		'ES-TE' => "Spain: Teruel",
		'ES-TO' => "Spain: Toledo",
		'ES-V'  => "Spain: Valencia / València",
		'ES-VC' => "Spain: Valenciana, Comunidad / Valenciana, Comunitat",
		'ES-VA' => "Spain: Valladolid",
		'ES-BI' => "Spain: Vizcayaa / Bizkaia",
		'ES-ZA' => "Spain: Zamora",
		'ES-Z'  => "Spain: Zaragoza",
		'ES-VI' => "Spain: Álava",
		'ES-AV' => "Spain: Ávila",

		'--SE'  => "", '-SE' => "Sweden",
		'SE-K'  => "Sweden: Blekinge län",
		'SE-W'  => "Sweden: Dalarnas län",
		'SE-I'  => "Sweden: Gotlands län",
		'SE-X'  => "Sweden: Gävleborgs län",
		'SE-N'  => "Sweden: Hallands län",
		'SE-Z'  => "Sweden: Jämtlande län",
		'SE-F'  => "Sweden: Jönköpings län",
		'SE-H'  => "Sweden: Kalmar län",
		'SE-G'  => "Sweden: Kronobergs län",
		'SE-BD' => "Sweden: Norrbottens län",
		'SE-M'  => "Sweden: Skåne län",
		'SE-AB' => "Sweden: Stockholms län",
		'SE-D'  => "Sweden: Södermanlands län",
		'SE-C'  => "Sweden: Uppsala län",
		'SE-S'  => "Sweden: Värmlands län",
		'SE-AC' => "Sweden: Västerbottens län",
		'SE-Y'  => "Sweden: Västernorrlands län",
		'SE-U'  => "Sweden: Västmanlands län",
		'SE-O'  => "Sweden: Västra Götalands län",
		'SE-T'  => "Sweden: Örebro län",
		'SE-E'  => "Sweden: Östergötlands län",

		'--CH'  => "", '-CH' => "Switzerland",
		'CH-AG' => "Switzerland: Aargau",
		'CH-AR' => "Switzerland: Appenzell Ausserrhoden",
		'CH-AI' => "Switzerland: Appenzell Innerrhoden",
		'CH-BL' => "Switzerland: Basel-Landschaft",
		'CH-BS' => "Switzerland: Basel-Stadt",
		'CH-BE' => "Switzerland: Bern",
		'CH-FR' => "Switzerland: Fribourg",
		'CH-GE' => "Switzerland: Genève",
		'CH-GL' => "Switzerland: Glarus",
		'CH-GR' => "Switzerland: Graubünden",
		'CH-JU' => "Switzerland: Jura",
		'CH-LU' => "Switzerland: Luzern",
		'CH-NE' => "Switzerland: Neuchâtel",
		'CH-NW' => "Switzerland: Nidwalden",
		'CH-OW' => "Switzerland: Obwalden",
		'CH-SG' => "Switzerland: Sankt Gallen",
		'CH-SH' => "Switzerland: Schaffhausen",
		'CH-SZ' => "Switzerland: Schwyz",
		'CH-SO' => "Switzerland: Solothurn",
		'CH-TG' => "Switzerland: Thurgau",
		'CH-TI' => "Switzerland: Ticino",
		'CH-UR' => "Switzerland: Uri",
		'CH-VS' => "Switzerland: Valais",
		'CH-VD' => "Switzerland: Vaud",
		'CH-ZG' => "Switzerland: Zug",
		'CH-ZH' => "Switzerland: Zürich",

		'--TW'   => "", '-TW' => "Taiwan",
		'TW-CHA' => "Taiwan: Changhua",
		'TW-CYI' => "Taiwan: Chiay City",
		'TW-CYQ' => "Taiwan: Chiayi",
		'TW-HSQ' => "Taiwan: Hsinchu",
		'TW-HSZ' => "Taiwan: Hsinchui City",
		'TW-HUA' => "Taiwan: Hualien",
		'TW-ILA' => "Taiwan: Ilan",
		'TW-KHQ' => "Taiwan: Kaohsiung",
		'TW-KHH' => "Taiwan: Kaohsiung City",
		'TW-KEE' => "Taiwan: Keelung City",
		'TW-MIA' => "Taiwan: Miaoli",
		'TW-NAN' => "Taiwan: Nantou",
		'TW-PEN' => "Taiwan: Penghu",
		'TW-PIF' => "Taiwan: Pingtung",
		'TW-TXQ' => "Taiwan: Taichung",
		'TW-TXG' => "Taiwan: Taichung City",
		'TW-TNQ' => "Taiwan: Tainan",
		'TW-TNN' => "Taiwan: Tainan City",
		'TW-TPQ' => "Taiwan: Taipei",
		'TW-TPE' => "Taiwan: Taipei City",
		'TW-TTT' => "Taiwan: Taitung",
		'TW-TAO' => "Taiwan: Taoyuan",
		'TW-YUN' => "Taiwan: Yunlin",

		'--TH'  => "", '-TH' => "Thailand",
		'TH-37' => "Thailand: Amnat Charoen",
		'TH-15' => "Thailand: Ang Thong",
		'TH-31' => "Thailand: Buri Ram",
		'TH-24' => "Thailand: Chachoengsao",
		'TH-18' => "Thailand: Chai Nat",
		'TH-36' => "Thailand: Chaiyaphum",
		'TH-22' => "Thailand: Chanthaburi",
		'TH-50' => "Thailand: Chiang Mai",
		'TH-57' => "Thailand: Chiang Rai",
		'TH-20' => "Thailand: Chon Buri",
		'TH-86' => "Thailand: Chumphon",
		'TH-46' => "Thailand: Kalasin",
		'TH-62' => "Thailand: Kamphaeng Phet",
		'TH-71' => "Thailand: Kanchanaburi",
		'TH-40' => "Thailand: Khon Kaen",
		'TH-81' => "Thailand: Krabi",
		'TH-10' => "Thailand: Krung Thep Maha Nakhon Bangkok",
		'TH-52' => "Thailand: Lampang",
		'TH-51' => "Thailand: Lamphun",
		'TH-42' => "Thailand: Loei",
		'TH-16' => "Thailand: Lop Buri",
		'TH-58' => "Thailand: Mae Hong Son",
		'TH-44' => "Thailand: Maha Sarakham",
		'TH-49' => "Thailand: Mukdahan",
		'TH-26' => "Thailand: Nakhon Nayok",
		'TH-73' => "Thailand: Nakhon Pathom",
		'TH-48' => "Thailand: Nakhon Phanom",
		'TH-30' => "Thailand: Nakhon Ratchasima",
		'TH-60' => "Thailand: Nakhon Sawan",
		'TH-80' => "Thailand: Nakhon Si Thammarat",
		'TH-55' => "Thailand: Nan",
		'TH-96' => "Thailand: Narathiwat",
		'TH-39' => "Thailand: Nong Bua Lam Phu",
		'TH-43' => "Thailand: Nong Khai",
		'TH-12' => "Thailand: Nonthaburi",
		'TH-13' => "Thailand: Pathum Thani",
		'TH-94' => "Thailand: Pattani",
		'TH-82' => "Thailand: Phangnga",
		'TH-93' => "Thailand: Phatthalung",
		'TH-S'  => "Thailand: Phatthaya",
		'TH-56' => "Thailand: Phayao",
		'TH-67' => "Thailand: Phetchabun",
		'TH-76' => "Thailand: Phetchaburi",
		'TH-66' => "Thailand: Phichit",
		'TH-65' => "Thailand: Phitsanulok",
		'TH-14' => "Thailand: Phra Nakhon Si Ayutthaya",
		'TH-54' => "Thailand: Phrae",
		'TH-83' => "Thailand: Phuket",
		'TH-25' => "Thailand: Prachin Buri",
		'TH-77' => "Thailand: Prachuap Khiri Khan",
		'TH-85' => "Thailand: Ranong",
		'TH-70' => "Thailand: Ratchaburi",
		'TH-21' => "Thailand: Rayong",
		'TH-45' => "Thailand: Roi Et",
		'TH-27' => "Thailand: Sa Kaeo",
		'TH-47' => "Thailand: Sakon Nakhon",
		'TH-11' => "Thailand: Samut Prakan",
		'TH-74' => "Thailand: Samut Sakhon",
		'TH-75' => "Thailand: Samut Songkhram",
		'TH-19' => "Thailand: Saraburi",
		'TH-91' => "Thailand: Satun",
		'TH-33' => "Thailand: Si Sa Ket",
		'TH-17' => "Thailand: Sing Buri",
		'TH-90' => "Thailand: Songkhla",
		'TH-64' => "Thailand: Sukhothai",
		'TH-72' => "Thailand: Suphan Buri",
		'TH-84' => "Thailand: Surat Thani",
		'TH-32' => "Thailand: Surin",
		'TH-63' => "Thailand: Tak",
		'TH-92' => "Thailand: Trang",
		'TH-23' => "Thailand: Trat",
		'TH-34' => "Thailand: Ubon Ratchathani",
		'TH-41' => "Thailand: Udon Thani",
		'TH-61' => "Thailand: Uthai Thani",
		'TH-53' => "Thailand: Uttaradit",
		'TH-95' => "Thailand: Yala",
		'TH-35' => "Thailand: Yasothon",

		'--TR'  => "", '-TR' => "Turkey",
		'TR-01' => "Turkey: Adana",
		'TR-02' => "Turkey: Adıyaman",
		'TR-03' => "Turkey: Afyon",
		'TR-68' => "Turkey: Aksaray",
		'TR-05' => "Turkey: Amasya",
		'TR-06' => "Turkey: Ankara",
		'TR-07' => "Turkey: Antalya",
		'TR-75' => "Turkey: Ardahan",
		'TR-08' => "Turkey: Artvin",
		'TR-09' => "Turkey: Aydın",
		'TR-04' => "Turkey: Ağrı",
		'TR-10' => "Turkey: Balıkesir",
		'TR-74' => "Turkey: Bartın",
		'TR-72' => "Turkey: Batman",
		'TR-69' => "Turkey: Bayburt",
		'TR-11' => "Turkey: Bilecik",
		'TR-12' => "Turkey: Bingöl",
		'TR-13' => "Turkey: Bitlis",
		'TR-14' => "Turkey: Bolu",
		'TR-15' => "Turkey: Burdur",
		'TR-16' => "Turkey: Bursa",
		'TR-20' => "Turkey: Denizli",
		'TR-21' => "Turkey: Diyarbakır",
		'TR-81' => "Turkey: Düzce",
		'TR-22' => "Turkey: Edirne",
		'TR-23' => "Turkey: Elazığ",
		'TR-24' => "Turkey: Erzincan",
		'TR-25' => "Turkey: Erzurum",
		'TR-26' => "Turkey: Eskişehir",
		'TR-27' => "Turkey: Gaziantep",
		'TR-28' => "Turkey: Giresun",
		'TR-29' => "Turkey: Gümüşhane",
		'TR-30' => "Turkey: Hakkâri",
		'TR-31' => "Turkey: Hatay",
		'TR-32' => "Turkey: Isparta",
		'TR-76' => "Turkey: Iğdır",
		'TR-46' => "Turkey: Kahramanmaraş",
		'TR-78' => "Turkey: Karabük",
		'TR-70' => "Turkey: Karaman",
		'TR-36' => "Turkey: Kars",
		'TR-37' => "Turkey: Kastamonu",
		'TR-38' => "Turkey: Kayseri",
		'TR-79' => "Turkey: Kilis",
		'TR-41' => "Turkey: Kocaeli",
		'TR-42' => "Turkey: Konya",
		'TR-43' => "Turkey: Kütahya",
		'TR-39' => "Turkey: Kırklareli",
		'TR-71' => "Turkey: Kırıkkale",
		'TR-40' => "Turkey: Kırşehir",
		'TR-44' => "Turkey: Malatya",
		'TR-45' => "Turkey: Manisa",
		'TR-47' => "Turkey: Mardin",
		'TR-48' => "Turkey: Muğla",
		'TR-49' => "Turkey: Muş",
		'TR-50' => "Turkey: Nevşehir",
		'TR-51' => "Turkey: Niğde",
		'TR-52' => "Turkey: Ordu",
		'TR-80' => "Turkey: Osmaniye",
		'TR-53' => "Turkey: Rize",
		'TR-54' => "Turkey: Sakarya",
		'TR-55' => "Turkey: Samsun",
		'TR-56' => "Turkey: Siirt",
		'TR-57' => "Turkey: Sinop",
		'TR-58' => "Turkey: Sivas",
		'TR-59' => "Turkey: Tekirdağ",
		'TR-60' => "Turkey: Tokat",
		'TR-61' => "Turkey: Trabzon",
		'TR-62' => "Turkey: Tunceli",
		'TR-64' => "Turkey: Uşak",
		'TR-65' => "Turkey: Van",
		'TR-77' => "Turkey: Yalova",
		'TR-66' => "Turkey: Yozgat",
		'TR-67' => "Turkey: Zonguldak",
		'TR-17' => "Turkey: Çanakkale",
		'TR-18' => "Turkey: Çankırı",
		'TR-19' => "Turkey: Çorum",
		'TR-34' => "Turkey: İstanbul",
		'TR-35' => "Turkey: İzmir",
		'TR-33' => "Turkey: İçel",
		'TR-63' => "Turkey: Şanlıurfa",
		'TR-73' => "Turkey: Şırnak",

		'--UA'  => "", '-UA' => "Ukraine",
		'UA-71' => "Ukraine: Cherkas'ka Oblast'",
		'UA-74' => "Ukraine: Chernihivs'ka Oblast'",
		'UA-77' => "Ukraine: Chernivets'ka Oblast'",
		'UA-12' => "Ukraine: Dnipropetrovs'ka Oblast'",
		'UA-14' => "Ukraine: Donets'ka Oblast'",
		'UA-26' => "Ukraine: Ivano-Frankivs'ka Oblast'",
		'UA-63' => "Ukraine: Kharkivs'ka Oblast'",
		'UA-65' => "Ukraine: Khersons'ka Oblast'",
		'UA-68' => "Ukraine: Khmel'nyts'ka Oblast'",
		'UA-35' => "Ukraine: Kirovohrads'ka Oblast'",
		'UA-32' => "Ukraine: Kyïvs'ka Oblast'",
		'UA-30' => "Ukraine: Kyïvs'ka mis'ka rada",
		'UA-46' => "Ukraine: L'vivs'ka Oblast'",
		'UA-09' => "Ukraine: Luhans'ka Oblast'",
		'UA-48' => "Ukraine: Mykolaïvs'ka Oblast'",
		'UA-51' => "Ukraine: Odes'ka Oblast'",
		'UA-53' => "Ukraine: Poltavs'ka Oblast'",
		'UA-43' => "Ukraine: Respublika Krym",
		'UA-56' => "Ukraine: Rivnens'ka Oblast'",
		'UA-40' => "Ukraine: Sevastopol",
		'UA-59' => "Ukraine: Sums 'ka Oblast'",
		'UA-61' => "Ukraine: Ternopil's'ka Oblast'",
		'UA-05' => "Ukraine: Vinnyts'ka Oblast'",
		'UA-07' => "Ukraine: Volyns'ka Oblast'",
		'UA-21' => "Ukraine: Zakarpats'ka Oblast'",
		'UA-23' => "Ukraine: Zaporiz'ka Oblast'",
		'UA-18' => "Ukraine: Zhytomyrs'ka Oblast'",

		'--AE'  => "", '-AE' => "United Arab Emirates",
		'AE-AJ' => "United Arab Emirates: 'Ajmān",
		'AE-AZ' => "United Arab Emirates: Abū Ȥaby [Abu Dhabi]",
		'AE-FU' => "United Arab Emirates: Al Fujayrah",
		'AE-SH' => "United Arab Emirates: Ash Shāriqah",
		'AE-DU' => "United Arab Emirates: Dubayy",
		'AE-RK' => "United Arab Emirates: Ra’s al Khaymah",
		'AE-UQ' => "United Arab Emirates: Umm al Qaywayn",

		'--GB'   => "", '-GB' => "United Kingdom",
		'GB-ABE' => "United Kingdom: Aberdeen City",
		'GB-ABD' => "United Kingdom: Aberdeenshire",
		'GB-ANS' => "United Kingdom: Angus",
		'GB-ANT' => "United Kingdom: Antrim",
		'GB-ARD' => "United Kingdom: Ards",
		'GB-AGB' => "United Kingdom: Argyll and Bute",
		'GB-ARM' => "United Kingdom: Armagh",
		'GB-BLA' => "United Kingdom: Ballymena",
		'GB-BLY' => "United Kingdom: Ballymoney",
		'GB-BNB' => "United Kingdom: Banbridge",
		'GB-BDG' => "United Kingdom: Barking and Dagenham",
		'GB-BNE' => "United Kingdom: Barnet",
		'GB-BNS' => "United Kingdom: Barnsley",
		'GB-BAS' => "United Kingdom: Bath and North East Somerset",
		'GB-BDF' => "United Kingdom: Bedford",
		'GB-BFS' => "United Kingdom: Belfast",
		'GB-BEX' => "United Kingdom: Bexley",
		'GB-BIR' => "United Kingdom: Birmingham",
		'GB-BBD' => "United Kingdom: Blackburn with Darwen",
		'GB-BPL' => "United Kingdom: Blackpool",
		'GB-BGW' => "United Kingdom: Blaenau Gwent",
		'GB-BOL' => "United Kingdom: Bolton",
		'GB-BMH' => "United Kingdom: Bournemouth",
		'GB-BRC' => "United Kingdom: Bracknell Forest",
		'GB-BRD' => "United Kingdom: Bradford",
		'GB-BEN' => "United Kingdom: Brent",
		'GB-BGE' => "United Kingdom: Bridgend (Pen-y-bont ar Ogwr)",
		'GB-BNH' => "United Kingdom: Brighton and Hove",
		'GB-BST' => "United Kingdom: Bristol, City of",
		'GB-BRY' => "United Kingdom: Bromley",
		'GB-BKM' => "United Kingdom: Buckinghamshire",
		'GB-BUR' => "United Kingdom: Bury",
		'GB-CAY' => "United Kingdom: Caerphilly (Caerffili)",
		'GB-CLD' => "United Kingdom: Calderdale",
		'GB-CAM' => "United Kingdom: Cambridgeshire",
		'GB-CMD' => "United Kingdom: Camden",
		'GB-CRF' => "United Kingdom: Cardiff (Caerdydd)",
		'GB-CMN' => "United Kingdom: Carmarthenshire (Sir Gaerfyrddin)",
		'GB-CKF' => "United Kingdom: Carrickfergus",
		'GB-CSR' => "United Kingdom: Castlereagh",
		'GB-CBF' => "United Kingdom: Central Bedfordshire",
		'GB-CGN' => "United Kingdom: Ceredigion (Sir Ceredigion)",
		'GB-CHE' => "United Kingdom: Cheshire East",
		'GB-CHW' => "United Kingdom: Cheshire West and Chester",
		'GB-CLK' => "United Kingdom: Clackmannanshire",
		'GB-CLR' => "United Kingdom: Coleraine",
		'GB-CWY' => "United Kingdom: Conwy",
		'GB-CKT' => "United Kingdom: Cookstown",
		'GB-CON' => "United Kingdom: Cornwall",
		'GB-COV' => "United Kingdom: Coventry",
		'GB-CGV' => "United Kingdom: Craigavon",
		'GB-CRY' => "United Kingdom: Croydon",
		'GB-CMA' => "United Kingdom: Cumbria",
		'GB-DAL' => "United Kingdom: Darlington",
		'GB-DEN' => "United Kingdom: Denbighshire (Sir Ddinbych)",
		'GB-DER' => "United Kingdom: Derby",
		'GB-DBY' => "United Kingdom: Derbyshire",
		'GB-DRY' => "United Kingdom: Derry",
		'GB-DEV' => "United Kingdom: Devon",
		'GB-DNC' => "United Kingdom: Doncaster",
		'GB-DOR' => "United Kingdom: Dorset",
		'GB-DOW' => "United Kingdom: Down",
		'GB-DUD' => "United Kingdom: Dudley",
		'GB-DGY' => "United Kingdom: Dumfries and Galloway",
		'GB-DND' => "United Kingdom: Dundee City",
		'GB-DGN' => "United Kingdom: Dungannon",
		'GB-DUR' => "United Kingdom: Durham",
		'GB-EAL' => "United Kingdom: Ealing",
		'GB-EAY' => "United Kingdom: East Ayrshire",
		'GB-EDU' => "United Kingdom: East Dunbartonshire",
		'GB-ELN' => "United Kingdom: East Lothian",
		'GB-ERW' => "United Kingdom: East Renfrewshire",
		'GB-ERY' => "United Kingdom: East Riding of Yorkshire",
		'GB-ESX' => "United Kingdom: East Sussex",
		'GB-EDH' => "United Kingdom: Edinburgh, City of",
		'GB-ELS' => "United Kingdom: Eilean Siar",
		'GB-ENF' => "United Kingdom: Enfield",
		'GB-ENG' => "United Kingdom: England",
		'GB-EAW' => "United Kingdom: England and Wales",
		'GB-ESS' => "United Kingdom: Essex",
		'GB-FAL' => "United Kingdom: Falkirk",
		'GB-FER' => "United Kingdom: Fermanagh",
		'GB-FIF' => "United Kingdom: Fife",
		'GB-FLN' => "United Kingdom: Flintshire (Sir y Fflint)",
		'GB-GAT' => "United Kingdom: Gateshead",
		'GB-GLG' => "United Kingdom: Glasgow City",
		'GB-GLS' => "United Kingdom: Gloucestershire",
		'GB-GBN' => "United Kingdom: Great Britain",
		'GB-GRE' => "United Kingdom: Greenwich",
		'GB-GWN' => "United Kingdom: Gwynedd",
		'GB-HCK' => "United Kingdom: Hackney",
		'GB-HAL' => "United Kingdom: Halton",
		'GB-HMF' => "United Kingdom: Hammersmith and Fulham",
		'GB-HAM' => "United Kingdom: Hampshire",
		'GB-HRY' => "United Kingdom: Haringey",
		'GB-HRW' => "United Kingdom: Harrow",
		'GB-HPL' => "United Kingdom: Hartlepool",
		'GB-HAV' => "United Kingdom: Havering",
		'GB-HEF' => "United Kingdom: Herefordshire",
		'GB-HRT' => "United Kingdom: Hertfordshire",
		'GB-HLD' => "United Kingdom: Highland",
		'GB-HIL' => "United Kingdom: Hillingdon",
		'GB-HNS' => "United Kingdom: Hounslow",
		'GB-IVC' => "United Kingdom: Inverclyde",
		'GB-AGY' => "United Kingdom: Isle of Anglesey (Sir Ynys Môn)",
		'GB-IOW' => "United Kingdom: Isle of Wight",
		'GB-ISL' => "United Kingdom: Islington",
		'GB-KEC' => "United Kingdom: Kensington and Chelsea",
		'GB-KEN' => "United Kingdom: Kent",
		'GB-KHL' => "United Kingdom: Kingston upon Hull",
		'GB-KTT' => "United Kingdom: Kingston upon Thames",
		'GB-KIR' => "United Kingdom: Kirklees",
		'GB-KWL' => "United Kingdom: Knowsley",
		'GB-LBH' => "United Kingdom: Lambeth",
		'GB-LAN' => "United Kingdom: Lancashire",
		'GB-LRN' => "United Kingdom: Larne",
		'GB-LDS' => "United Kingdom: Leeds",
		'GB-LCE' => "United Kingdom: Leicester",
		'GB-LEC' => "United Kingdom: Leicestershire",
		'GB-LEW' => "United Kingdom: Lewisham",
		'GB-LMV' => "United Kingdom: Limavady",
		'GB-LIN' => "United Kingdom: Lincolnshire",
		'GB-LSB' => "United Kingdom: Lisburn",
		'GB-LIV' => "United Kingdom: Liverpool",
		'GB-LND' => "United Kingdom: London, City of",
		'GB-LUT' => "United Kingdom: Luton",
		'GB-MFT' => "United Kingdom: Magherafelt",
		'GB-MAN' => "United Kingdom: Manchester",
		'GB-MDW' => "United Kingdom: Medway",
		'GB-MTY' => "United Kingdom: Merthyr Tydfil (Merthyr Tudful)",
		'GB-MRT' => "United Kingdom: Merton",
		'GB-MDB' => "United Kingdom: Middlesbrough",
		'GB-MLN' => "United Kingdom: Midlothian",
		'GB-MIK' => "United Kingdom: Milton Keynes",
		'GB-MON' => "United Kingdom: Monmouthshire (Sir Fynwy)",
		'GB-MRY' => "United Kingdom: Moray",
		'GB-MYL' => "United Kingdom: Moyle",
		'GB-NTL' => "United Kingdom: Neath Port Talbot (Castell-nedd Port Talbot)",
		'GB-NET' => "United Kingdom: Newcastle upon Tyne",
		'GB-NWM' => "United Kingdom: Newham",
		'GB-NWP' => "United Kingdom: Newport (Casnewydd)",
		'GB-NYM' => "United Kingdom: Newry and Mourne",
		'GB-NTA' => "United Kingdom: Newtownabbey",
		'GB-NFK' => "United Kingdom: Norfolk",
		'GB-NAY' => "United Kingdom: North Ayrshire",
		'GB-NDN' => "United Kingdom: North Down",
		'GB-NEL' => "United Kingdom: North East Lincolnshire",
		'GB-NLK' => "United Kingdom: North Lanarkshire",
		'GB-NLN' => "United Kingdom: North Lincolnshire",
		'GB-NSM' => "United Kingdom: North Somerset",
		'GB-NTY' => "United Kingdom: North Tyneside",
		'GB-NYK' => "United Kingdom: North Yorkshire",
		'GB-NTH' => "United Kingdom: Northamptonshire",
		'GB-NIR' => "United Kingdom: Northern Ireland",
		'GB-NBL' => "United Kingdom: Northumberland",
		'GB-NGM' => "United Kingdom: Nottingham",
		'GB-NTT' => "United Kingdom: Nottinghamshire",
		'GB-OLD' => "United Kingdom: Oldham",
		'GB-OMH' => "United Kingdom: Omagh",
		'GB-ORK' => "United Kingdom: Orkney Islands",
		'GB-OXF' => "United Kingdom: Oxfordshire",
		'GB-PEM' => "United Kingdom: Pembrokeshire (Sir Benfro)",
		'GB-PKN' => "United Kingdom: Perth and Kinross",
		'GB-PTE' => "United Kingdom: Peterborough",
		'GB-PLY' => "United Kingdom: Plymouth",
		'GB-POL' => "United Kingdom: Poole",
		'GB-POR' => "United Kingdom: Portsmouth",
		'GB-POW' => "United Kingdom: Powys",
		'GB-RDG' => "United Kingdom: Reading",
		'GB-RDB' => "United Kingdom: Redbridge",
		'GB-RCC' => "United Kingdom: Redcar and Cleveland",
		'GB-RFW' => "United Kingdom: Renfrewshire",
		'GB-RCT' => "United Kingdom: Rhondda, Cynon, Taff (Rhondda, Cynon, Taf)",
		'GB-RIC' => "United Kingdom: Richmond upon Thames",
		'GB-RCH' => "United Kingdom: Rochdale",
		'GB-ROT' => "United Kingdom: Rotherham",
		'GB-RUT' => "United Kingdom: Rutland",
		'GB-SLF' => "United Kingdom: Salford",
		'GB-SAW' => "United Kingdom: Sandwell",
		'GB-SCT' => "United Kingdom: Scotland",
		'GB-SCB' => "United Kingdom: Scottish Borders, The",
		'GB-SFT' => "United Kingdom: Sefton",
		'GB-SHF' => "United Kingdom: Sheffield",
		'GB-ZET' => "United Kingdom: Shetland Islands",
		'GB-SHR' => "United Kingdom: Shropshire",
		'GB-SLG' => "United Kingdom: Slough",
		'GB-SOL' => "United Kingdom: Solihull",
		'GB-SOM' => "United Kingdom: Somerset",
		'GB-SAY' => "United Kingdom: South Ayrshire",
		'GB-SGC' => "United Kingdom: South Gloucestershire",
		'GB-SLK' => "United Kingdom: South Lanarkshire",
		'GB-STY' => "United Kingdom: South Tyneside",
		'GB-STH' => "United Kingdom: Southampton",
		'GB-SOS' => "United Kingdom: Southend-on-Sea",
		'GB-SWK' => "United Kingdom: Southwark",
		'GB-SHN' => "United Kingdom: St. Helens",
		'GB-STS' => "United Kingdom: Staffordshire",
		'GB-STG' => "United Kingdom: Stirling",
		'GB-SKP' => "United Kingdom: Stockport",
		'GB-STT' => "United Kingdom: Stockton-on-Tees",
		'GB-STE' => "United Kingdom: Stoke-on-Trent",
		'GB-STB' => "United Kingdom: Strabane",
		'GB-SFK' => "United Kingdom: Suffolk",
		'GB-SND' => "United Kingdom: Sunderland",
		'GB-SRY' => "United Kingdom: Surrey",
		'GB-STN' => "United Kingdom: Sutton",
		'GB-SWA' => "United Kingdom: Swansea (Abertawe)",
		'GB-SWD' => "United Kingdom: Swindon",
		'GB-TAM' => "United Kingdom: Tameside",
		'GB-TFW' => "United Kingdom: Telford and Wrekin",
		'GB-THR' => "United Kingdom: Thurrock",
		'GB-TOB' => "United Kingdom: Torbay",
		'GB-TOF' => "United Kingdom: Torfaen (Tor-faen)",
		'GB-TWH' => "United Kingdom: Tower Hamlets",
		'GB-TRF' => "United Kingdom: Trafford",
		'GB-UKM' => "United Kingdom: United Kingdom",
		'GB-VGL' => "United Kingdom: Vale of Glamorgan, The (Bro Morgannwg)",
		'GB-WKF' => "United Kingdom: Wakefield",
		'GB-WLS' => "United Kingdom: Wales",
		'GB-WLL' => "United Kingdom: Walsall",
		'GB-WFT' => "United Kingdom: Waltham Forest",
		'GB-WND' => "United Kingdom: Wandsworth",
		'GB-WRT' => "United Kingdom: Warrington",
		'GB-WAR' => "United Kingdom: Warwickshire",
		'GB-WBK' => "United Kingdom: West Berkshire",
		'GB-WDU' => "United Kingdom: West Dunbartonshire",
		'GB-WLN' => "United Kingdom: West Lothian",
		'GB-WSX' => "United Kingdom: West Sussex",
		'GB-WSM' => "United Kingdom: Westminster",
		'GB-WGN' => "United Kingdom: Wigan",
		'GB-WNM' => "United Kingdom: Windsor and Maidenhead",
		'GB-WRL' => "United Kingdom: Wirral",
		'GB-WOK' => "United Kingdom: Wokingham",
		'GB-WLV' => "United Kingdom: Wolverhampton",
		'GB-WOR' => "United Kingdom: Worcestershire",
		'GB-WRX' => "United Kingdom: Wrexham (Wrecsam)",
		'GB-YOR' => "United Kingdom: York",

		'--US'  => "", '-US' => "United States",
		'US-AL' => "United States: Alabama",
		'US-AK' => "United States: Alaska",
		'US-AS' => "United States: American Samoa, Samoa Americana",
		'US-AZ' => "United States: Arizona",
		'US-AR' => "United States: Arkansas",
		'US-CA' => "United States: California",
		'US-CO' => "United States: Colorado",
		'US-CT' => "United States: Connecticut",
		'US-DE' => "United States: Delaware",
		'US-DC' => "United States: District of Columbia, Disricte de Columbia",
		'US-FL' => "United States: Florida",
		'US-GA' => "United States: Georgia, Geòrgia",
		'US-GU' => "United States: Guam",
		'US-HI' => "United States: Hawaii",
		'US-ID' => "United States: Idaho",
		'US-IL' => "United States: Illinois",
		'US-IN' => "United States: Indiana",
		'US-IA' => "United States: Iowa",
		'US-KS' => "United States: Kansas",
		'US-KY' => "United States: Kentucky",
		'US-LA' => "United States: Louisiana",
		'US-ME' => "United States: Maine",
		'US-MD' => "United States: Maryland",
		'US-MA' => "United States: Massachusetts",
		'US-MI' => "United States: Michigan",
		'US-MN' => "United States: Minnesota",
		'US-MS' => "United States: Mississippi",
		'US-MO' => "United States: Missouri",
		'US-MT' => "United States: Montana",
		'US-NE' => "United States: Nebraska",
		'US-NV' => "United States: Nevada",
		'US-NH' => "United States: New Hampshire",
		'US-NJ' => "United States: New Jersey",
		'US-NM' => "United States: New Mexico",
		'US-NY' => "United States: New York",
		'US-NC' => "United States: North Carolina",
		'US-ND' => "United States: North Dakota",
		'US-MP' => "United States: Northern Mariana Islands, Illes Marianes del Nord",
		'US-OH' => "United States: Ohio",
		'US-OK' => "United States: Oklahoma",
		'US-OR' => "United States: Oregon",
		'US-PA' => "United States: Pennsylvania",
		'US-PR' => "United States: Puerto Rico",
		'US-RI' => "United States: Rhode Island",
		'US-SC' => "United States: South Carolina",
		'US-SD' => "United States: South Dakota",
		'US-TN' => "United States: Tennessee",
		'US-TX' => "United States: Texas",
		'US-UM' => "United States: United States Minor Outlying Islands, Illes Perifèriques Menors dels EUA",
		'US-UT' => "United States: Utah",
		'US-VT' => "United States: Vermont",
		'US-VI' => "United States: Virgin Islands, Illes Verge",
		'US-VA' => "United States: Virginia",
		'US-WA' => "United States: Washington",
		'US-WV' => "United States: West Virginia",
		'US-WI' => "United States: Wisconsin",
		'US-WY' => "United States: Wyoming",

		'--VN'  => "", '-VN' => "Vietnam",
		'VN-44' => "Vietnam: An Giang",
		'VN-43' => "Vietnam: Bà Rịa - Vũng Tàu",
		'VN-57' => "Vietnam: Bình Dương",
		'VN-58' => "Vietnam: Bình Phước",
		'VN-40' => "Vietnam: Bình Thuận",
		'VN-31' => "Vietnam: Bình Định",
		'VN-55' => "Vietnam: Bạc Liêu",
		'VN-54' => "Vietnam: Bắc Giang",
		'VN-53' => "Vietnam: Bắc Kạn",
		'VN-56' => "Vietnam: Bắc Ninh",
		'VN-50' => "Vietnam: Bến Tre",
		'VN-04' => "Vietnam: Cao Bằng",
		'VN-59' => "Vietnam: Cà Mau",
		'VN-48' => "Vietnam: Cần Thơ",
		'VN-30' => "Vietnam: Gia Lai",
		'VN-14' => "Vietnam: Hoà Bình",
		'VN-03' => "Vietnam: Hà Giang",
		'VN-63' => "Vietnam: Hà Nam",
		'VN-64' => "Vietnam: Hà Nội, thủ đô",
		'VN-15' => "Vietnam: Hà Tây",
		'VN-23' => "Vietnam: Hà Tỉnh",
		'VN-66' => "Vietnam: Hưng Yên",
		'VN-61' => "Vietnam: Hải Duong",
		'VN-62' => "Vietnam: Hải Phòng, thành phố",
		'VN-73' => "Vietnam: Hậu Giang",
		'VN-65' => "Vietnam: Hồ Chí Minh, thành phố [Sài Gòn]",
		'VN-34' => "Vietnam: Khánh Hòa",
		'VN-47' => "Vietnam: Kiên Giang",
		'VN-28' => "Vietnam: Kon Tum",
		'VN-01' => "Vietnam: Lai Châu",
		'VN-41' => "Vietnam: Long An",
		'VN-02' => "Vietnam: Lào Cai",
		'VN-35' => "Vietnam: Lâm Đồng",
		'VN-09' => "Vietnam: Lạng Sơn",
		'VN-67' => "Vietnam: Nam Định",
		'VN-22' => "Vietnam: Nghệ An",
		'VN-18' => "Vietnam: Ninh Bình",
		'VN-36' => "Vietnam: Ninh Thuận",
		'VN-68' => "Vietnam: Phú Thọ",
		'VN-32' => "Vietnam: Phú Yên",
		'VN-24' => "Vietnam: Quảng Bình",
		'VN-27' => "Vietnam: Quảng Nam",
		'VN-29' => "Vietnam: Quảng Ngãi",
		'VN-13' => "Vietnam: Quảng Ninh",
		'VN-25' => "Vietnam: Quảng Trị",
		'VN-52' => "Vietnam: Sóc Trăng",
		'VN-05' => "Vietnam: Sơn La",
		'VN-21' => "Vietnam: Thanh Hóa",
		'VN-20' => "Vietnam: Thái Bình",
		'VN-69' => "Vietnam: Thái Nguyên",
		'VN-26' => "Vietnam: Thừa Thiên-Huế",
		'VN-46' => "Vietnam: Tiền Giang",
		'VN-51' => "Vietnam: Trà Vinh",
		'VN-07' => "Vietnam: Tuyên Quang",
		'VN-37' => "Vietnam: Tây Ninh",
		'VN-49' => "Vietnam: Vĩnh Long",
		'VN-70' => "Vietnam: Vĩnh Phúc",
		'VN-06' => "Vietnam: Yên Bái",
		'VN-71' => "Vietnam: Điện Biên",
		'VN-60' => "Vietnam: Đà Nẵng, thành phố",
		'VN-33' => "Vietnam: Đắc Lắk",
		'VN-72' => "Vietnam: Đắk Nông",
		'VN-39' => "Vietnam: Đồng Nai",
		'VN-45' => "Vietnam: Đồng Tháp",
	];

}
regularlabs/fields/virtuemart.php000064400000007125152177723700013250 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_VirtueMart extends \RegularLabs\Library\FieldGroup
{
	public $type     = 'VirtueMart';
	public $language = null;

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories', 'products']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__virtuemart_categories AS c')
			->where('c.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear()
			->select('c.virtuemart_category_id as id, cc.category_parent_id AS parent_id, l.category_name AS title, c.published')
			->from('#__virtuemart_categories_' . $this->getActiveLanguage() . ' AS l')
			->join('', '#__virtuemart_categories AS c using (virtuemart_category_id)')
			->join('LEFT', '#__virtuemart_category_categories AS cc ON l.virtuemart_category_id = cc.category_child_id')
			->where('c.published > -1')
			->group('c.virtuemart_category_id')
			->order('c.ordering, l.category_name');
		$this->db->setQuery($query);
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items);
	}

	function getProducts()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__virtuemart_products AS p')
			->where('p.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('p.virtuemart_product_id as id, l.product_name AS name, p.product_sku as sku, cl.category_name AS cat, p.published')
			->join('LEFT', '#__virtuemart_products_' . $this->getActiveLanguage() . ' AS l ON l.virtuemart_product_id = p.virtuemart_product_id')
			->join('LEFT', '#__virtuemart_product_categories AS x ON x.virtuemart_product_id = p.virtuemart_product_id')
			->join('LEFT', '#__virtuemart_categories AS c ON c.virtuemart_category_id = x.virtuemart_category_id')
			->join('LEFT', '#__virtuemart_categories_' . $this->getActiveLanguage() . ' AS cl ON cl.virtuemart_category_id = c.virtuemart_category_id')
			->group('p.virtuemart_product_id')
			->order('l.product_name, p.product_sku');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['sku', 'cat', 'id']);
	}

	private function getActiveLanguage()
	{
		if (isset($this->language))
		{
			return $this->language;
		}

		$this->language = 'en_gb';

		if ( ! class_exists('VmConfig'))
		{
			require_once JPATH_ROOT . '/administrator/components/com_virtuemart/helpers/config.php';
		}

		if ( ! class_exists('VmConfig'))
		{
			return $this->language;
		}

		VmConfig::loadConfig();

		if ( ! empty(VmConfig::$vmlang))
		{
			$this->language = str_replace('-', '_', strtolower(VmConfig::$vmlang));

			return $this->language;
		}

		$active_languages = VmConfig::get('active_languages', []);

		if ( ! isset($active_languages[0]))
		{
			return $this->language;
		}

		$this->language = str_replace('-', '_', strtolower($active_languages[0]));

		return $this->language;
	}
}
regularlabs/fields/assignmentselection.php000064400000007256152177723700015131 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

/**
 * @deprecated  2018-10-30  Use ConditionSelection instead
 */
class JFormFieldRL_AssignmentSelection extends \RegularLabs\Library\Field
{
	public $type = 'AssignmentSelection';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		require_once __DIR__ . '/toggler.php';
		$toggler = new RLFieldToggler;

		$this->value     = (int) $this->value;
		$label           = $this->get('label');
		$param_name      = $this->get('name');
		$use_main_toggle = $this->get('use_main_toggle', 1);
		$showclose       = $this->get('showclose', 0);

		$html = [];

		if ( ! $label)
		{
			if ($use_main_toggle)
			{
				$html[] = $toggler->getInput(['div' => 1]);
			}

			$html[] = $toggler->getInput(['div' => 1]);

			return '</div>' . implode('', $html);
		}

		$label = RL_String::html_entity_decoder(JText::_($label));

		$html[] = '</div>';
		if ($use_main_toggle)
		{
			$html[] = $toggler->getInput(['div' => 1, 'param' => 'show_assignments|' . $param_name, 'value' => '1|1,2']);
		}

		$class = 'well well-small rl_well';
		if ($this->value === 1)
		{
			$class .= ' alert-success';
		}
		else if ($this->value === 2)
		{
			$class .= ' alert-error';
		}
		$html[] = '<div class="' . $class . '">';
		if ($showclose && JFactory::getUser()->authorise('core.admin'))
		{
			$html[] = '<button type="button" class="close rl_remove_assignment">&times;</button>';
		}

		$html[] = '<div class="control-group">';

		$html[] = '<div class="control-label">';
		$html[] = '<label><h4 class="rl_assignmentselection-header">' . $label . '</h4></label>';
		$html[] = '</div>';

		$html[] = '<div class="controls">';
		$html[] = '<fieldset id="' . $this->id . '"  class="radio btn-group">';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 0)"';
		$html[]  = '<input type="radio" id="' . $this->id . '0" name="' . $this->name . '" value="0"' . (( ! $this->value) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-ignore" for="' . $this->id . '0">' . JText::_('RL_IGNORE') . '</label>';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 1)"';
		$html[]  = '<input type="radio" id="' . $this->id . '1" name="' . $this->name . '" value="1"' . (($this->value === 1) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-include" for="' . $this->id . '1">' . JText::_('RL_INCLUDE') . '</label>';

		$onclick = ' onclick="RegularLabsForm.setToggleTitleClass(this, 2)"';
		$onclick .= ' onload="RegularLabsForm.setToggleTitleClass(this, ' . $this->value . ', 7)"';
		$html[]  = '<input type="radio" id="' . $this->id . '2" name="' . $this->name . '" value="2"' . (($this->value === 2) ? ' checked="checked"' : '') . $onclick . '>';
		$html[]  = '<label class="rl_btn-exclude" for="' . $this->id . '2">' . JText::_('RL_EXCLUDE') . '</label>';

		$html[] = '</fieldset>';
		$html[] = '</div>';

		$html[] = '</div>';
		$html[] = '<div class="clearfix"> </div>';

		$html[] = $toggler->getInput(['div' => 1, 'param' => $param_name, 'value' => '1,2']);
		$html[] = '<div><div>';

		return '</div>' . implode('', $html);
	}
}
regularlabs/fields/color.php000064400000003441152177723700012161 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Form\FormField as JFormField;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\RegEx as RL_RegEx;

jimport('joomla.form.formfield');

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Color extends JFormField
{
	public $type = 'Color';

	protected function getInput()
	{
		if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
		{
			return null;
		}

		$field = new RLFieldColor;

		return $field->getInput($this->name, $this->id, $this->value, $this->element->attributes());
	}
}

class RLFieldColor
{
	function getInput($name, $id, $value, $params)
	{
		$this->name   = $name;
		$this->id     = $id;
		$this->value  = $value;
		$this->params = $params;

		$class    = trim('rl_color minicolors ' . $this->get('class'));
		$disabled = $this->get('disabled') ? ' disabled="disabled"' : '';

		RL_Document::script('regularlabs/color.min.js');
		RL_Document::stylesheet('regularlabs/color.min.css');

		$this->value = strtolower(RL_RegEx::replace('[^a-z0-9]', '', $this->value));

		return '<input type="text" name="' . $this->name . '" id="' . $this->id . '" class="' . $class . '" value="' . $this->value . '"' . $disabled . '>';
	}

	private function get($val, $default = '')
	{
		return (isset($this->params[$val]) && (string) $this->params[$val] != '') ? (string) $this->params[$val] : $default;
	}
}
regularlabs/fields/simplecategories.php000064400000005732152177723700014407 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\ShowOn as RL_ShowOn;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_SimpleCategories extends \RegularLabs\Library\Field
{
	public $type = 'SimpleCategories';

	protected function getInput()
	{
		$size = (int) $this->get('size');
		$attr = $this->get('onchange') ? ' onchange="' . $this->get('onchange') . '"' : '';

		$categories = $this->getOptions();
		$options    = parent::getOptions();

		if ($this->get('show_none', 1))
		{
			$options[] = JHtml::_('select.option', '', '- ' . JText::_('JNONE') . ' -');
		}

		if ($this->get('show_new', 1))
		{
			$options[] = JHtml::_('select.option', '-1', '- ' . JText::_('RL_NEW_CATEGORY') . ' -');
		}

		$options = array_merge($options, $categories);

		if ( ! $this->get('show_new', 1))
		{
			return JHtml::_('select.genericlist',
				$options,
				$this->name,
				trim($attr),
				'value',
				'text',
				$this->value,
				$this->id
			);
		}

		JHtml::_('jquery.framework');
		RL_Document::script('regularlabs/simplecategories.min.js');

		$selectlist = $this->selectListSimple(
			$options,
			$this->getName($this->fieldname . '_select'),
			$this->value,
			$this->getId('', $this->fieldname . '_select'),
			$size,
			false
		);

		$html = [];

		$html[] = '<div class="rl_simplecategory">';

		$html[] = '<div class="rl_simplecategory_select">' . $selectlist . '</div>';

		$html[] = RL_ShowOn::show(
			'<div class="rl_simplecategory_new">'
			. '<input type="text" id="' . $this->id . '_new" value="" placeholder="' . JText::_('RL_NEW_CATEGORY_ENTER') . '">'
			. '</div>',
			$this->fieldname . '_select:-1', $this->formControl
		);

		$html[] = '<input type="hidden" class="rl_simplecategory_value" id="' . $this->id . '" name="' . $this->name . '" value="' . $this->value . '" />';

		$html[] = '</div>';

		return implode('', $html);
	}

	protected function getOptions()
	{
		$table = $this->get('table');

		if ( ! $table)
		{
			return [];
		}

		// Get the user groups from the database.
		$query = $this->db->getQuery(true)
			->select([
				$this->db->quoteName('category', 'value'),
				$this->db->quoteName('category', 'text'),
			])
			->from($this->db->quoteName('#__' . $table))
			->where($this->db->quoteName('category') . ' != ' . $this->db->quote(''))
			->group($this->db->quoteName('category'))
			->order($this->db->quoteName('category') . ' ASC');
		$this->db->setQuery($query);

		return $this->db->loadObjectList();
	}
}
regularlabs/fields/editor.php000064400000002042152177723700012325 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Editor extends \RegularLabs\Library\Field
{
	public $type = 'Editor';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$width  = $this->get('width', '100%');
		$height = $this->get('height', 400);

		$this->value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		// Get an editor object.
		$editor = JFactory::getEditor();
		$html   = $editor->display($this->name, $this->value, $width, $height, true, $this->id);

		return '</div><div>' . $html;
	}
}
regularlabs/fields/multiselect.php000064400000002413152177723700013373 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_MultiSelect extends \RegularLabs\Library\Field
{
	public $type = 'MultiSelect';

	protected function getInput()
	{
		$this->params = $this->element->attributes();

		if ( ! is_array($this->value))
		{
			$this->value = explode(',', $this->value);
		}

		foreach ($this->element->children() as $item)
		{
			$item_value    = (string) $item['value'];
			$item_name     = JText::_(trim((string) $item));
			$item_disabled = (int) $item['disabled'];
			$options[]     = JHtml::_('select.option', $item_value, $item_name, 'value', 'text', $item_disabled);
		}

		$size = (int) $this->get('size');

		return $this->selectList($options, $this->name, $this->value, $this->id, $size, true);
	}
}
regularlabs/fields/block.php000064400000003335152177723700012137 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Block extends \RegularLabs\Library\Field
{
	public $type = 'Block';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$title       = $this->get('label');
		$description = $this->get('description');
		$class       = $this->get('class');
		$showclose   = $this->get('showclose', 0);
		$nowell      = $this->get('nowell', 0);

		$start = $this->get('start', 0);
		$end   = $this->get('end', 0);

		$html = [];

		if ($start || ! $end)
		{
			$html[] = '</div>';

			if (strpos($class, 'alert') !== false)
			{
				$class = 'alert ' . $class;
			}
			else if ( ! $nowell)
			{
				$class = 'well well-small ' . $class;
			}

			$html[] = '<div class="' . $class . '">';

			if ($showclose && JFactory::getUser()->authorise('core.admin'))
			{
				$html[] = '<button type="button" class="close rl_remove_assignment">&times;</button>';
			}

			if ($title)
			{
				$html[] = '<h4>' . $this->prepareText($title) . '</h4>';
			}

			if ($description)
			{
				$html[] = '<div>' . $this->prepareText($description) . '</div>';
			}

			$html[] = '<div><div>';
		}

		if ( ! $start && ! $end)
		{
			$html[] = '</div>';
		}

		return '</div>' . implode('', $html);
	}
}
regularlabs/fields/password.php000064400000003637152177723700012714 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

require_once JPATH_LIBRARIES . '/joomla/form/fields/password.php';

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Password extends JFormFieldPassword
{
	public $type = 'Password';

	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$this->element = $element;

		$element['label']                = $this->prepareText($element['label']);
		$element['description']          = $this->prepareText($element['description']);
		$element['translateDescription'] = false;

		return parent::setup($element, $value, $group);
	}

	private function prepareText($string = '')
	{
		$string = trim($string);

		if ($string == '')
		{
			return '';
		}

		// variables
		$var1 = JText::_($this->get('var1'));
		$var2 = JText::_($this->get('var2'));
		$var3 = JText::_($this->get('var3'));
		$var4 = JText::_($this->get('var4'));
		$var5 = JText::_($this->get('var5'));

		$string = JText::sprintf(JText::_($string), $var1, $var2, $var3, $var4, $var5);
		$string = trim(RL_String::html_entity_decoder($string));
		$string = str_replace('&quot;', '"', $string);
		$string = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $string);

		return $string;
	}

	private function get($val, $default = '')
	{
		if ( ! isset($this->params[$val]) || (string) $this->params[$val] == '')
		{
			return $default;
		}

		return (string) $this->params[$val];
	}
}
regularlabs/fields/redshop.php000064400000006227152177723700012514 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\DB as RL_DB;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_RedShop extends \RegularLabs\Library\FieldGroup
{
	public $type = 'RedShop';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories' => 'category', 'products' => 'product']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__redshop_category AS c')
			->where('c.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$this->db->setQuery($this->getCategoriesQuery());
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items);
	}

	function getProducts()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__redshop_product AS p')
			->where('p.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$this->db->setQuery($this->getProductsQuery());
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['number', 'cat']);
	}

	private function getCategoriesQuery()
	{
		$query = $this->db->getQuery(true)
			->select('c.id, c.parent_id, c.name AS title, c.published')
			->from('#__redshop_category AS c')
			->where('c.published > -1');

		if (RL_DB::tableExists('redshop_category_xref'))
		{
			$query->clear('select')
				->select('c.category_id as id, x.category_parent_id AS parent_id, c.category_name AS title, c.published')
				->join('LEFT', '#__redshop_category_xref AS x ON x.category_child_id = c.category_id')
				->group('c.category_id')
				->order('c.ordering, c.category_name');

			return $query;
		}

		$query
			->group('c.id')
			->order('c.ordering, c.name');

		return $query;
	}

	private function getProductsQuery()
	{
		$query = $this->db->getQuery(true)
			->select('p.product_id as id, p.product_name AS name, p.product_number as number, c.name AS cat, p.published')
			->from('#__redshop_product AS p')
			->where('p.published > -1')
			->join('LEFT', '#__redshop_product_category_xref AS x ON x.product_id = p.product_id')
			->group('p.product_id')
			->order('p.product_name, p.product_number');

		if (RL_DB::tableExists('redshop_category_xref'))
		{
			$query->clear('select')
				->select('p.product_id as id, p.product_name AS name, p.product_number as number, c.category_name AS cat, p.published')
				->join('LEFT', '#__redshop_category AS c ON c.category_id = x.category_id');

			return $query;
		}

		$query
			->join('LEFT', '#__redshop_category AS c ON c.id = x.category_id');

		return $query;
	}
}
regularlabs/fields/grouplevel.php000064400000004434152177723700013232 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_GroupLevel extends \RegularLabs\Library\Field
{
	public $type = 'GroupLevel';

	protected function getInput()
	{
		$size      = (int) $this->get('size');
		$multiple  = $this->get('multiple');
		$show_all  = $this->get('show_all');
		$use_names = $this->get('use_names');

		return $this->selectListAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple', 'show_all', 'use_names')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getOptions(
			(bool) $attributes->get('show_all'),
			(bool) $attributes->get('use_names')
		);

		return $this->selectList($options, $name, $value, $id, $size, $multiple);
	}

	protected function getOptions($show_all = false, $use_names = false)
	{
		$options = $this->getUserGroups($use_names);

		if ($show_all)
		{
			$option          = (object) [];
			$option->value   = -1;
			$option->text    = '- ' . JText::_('JALL') . ' -';
			$option->disable = '';
			array_unshift($options, $option);
		}

		return $options;
	}

	protected function getUserGroups($use_names = false)
	{
		$value = $use_names ? 'a.title' : 'a.id';

		$query = $this->db->getQuery(true)
			->select($value . ' as value, a.title as text, a.parent_id AS parent')
			->from('#__usergroups AS a')
			->select('COUNT(DISTINCT b.id) AS level')
			->join('LEFT', '#__usergroups AS b ON a.lft > b.lft AND a.rgt < b.rgt')
			->group('a.id')
			->order('a.lft ASC');
		$this->db->setQuery($query);

		return $this->db->loadObjectList();
	}
}
regularlabs/fields/ajax.php000064400000004307152177723700011770 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Ajax extends \RegularLabs\Library\Field
{
	public $type = 'Ajax';

	protected function getInput()
	{
		RL_Document::loadMainDependencies();

		$loading = "jQuery(\"#" . $this->id . "\").find(\"span\").attr(\"class\", \"icon-refresh icon-spin\");";
		$success = "jQuery(\"#" . $this->id . "\").find(\"span\").attr(\"class\", \"icon-ok\");"
			. "if(data){jQuery(\"#message_" . $this->id . "\").addClass(\"alert alert-success alert-noclose alert-inline\").html(data);}";
		$error   = "jQuery(\"#" . $this->id . "\").find(\"span\").attr(\"class\", \"icon-warning\");"
			. "if(data){jQuery(\"#message_" . $this->id . "\").addClass(\"alert alert-danger alert-noclose alert-inline\").html(data);}";

		$script = "function loadAjax" . $this->id . "() {"
			. $loading
			. "jQuery(\"#message_" . $this->id . "\").attr(\"class\", \"\").html(\"\");"
			. "RegularLabsScripts.loadajax("
			. "'" . addslashes($this->get('url')) . "',"
			. "'var data = data.trim();"
			. "if(data == \"\" || data.substring(0,1) == \"+\") {"
			. "data = data.replace(/^\\\\+/, \\'\\');"
			. $success
			. "} else {"
			. $error
			. "}',"
			. "'" . $error . "'"
			. ");"
			. "}";
		JFactory::getDocument()->addScriptDeclaration($script);

		return
			'<button id="' . $this->id . '" class="' . $this->get('class', 'btn') . '" title="' . JText::_($this->get('description')) . '" onclick="loadAjax' . $this->id . '();return false;">'
			. '<span class="' . $this->get('icon', '') . '"></span> '
			. JText::_($this->get('text', $this->get('label')))
			. '</button>'
			. '<div id="message_' . $this->id . '"></div>';
	}
}
regularlabs/fields/toggler.php000064400000006077152177723700012516 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Form\FormField as JFormField;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

/**
 * @deprecated  2018-10-30  Use ShowOn instead
 */

/**
 * To use this, make a start xml param tag with the param and value set
 * And an end xml param tag without the param and value set
 * Everything between those tags will be included in the slide
 *
 * Available extra parameters:
 * param            The name of the reference parameter
 * value            a comma separated list of value on which to show the framework
 */
class JFormFieldRL_Toggler extends JFormField
{
	public $type = 'Toggler';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
		{
			return null;
		}

		$field = new RLFieldToggler;

		return $field->getInput($this->element->attributes());
	}
}

class RLFieldToggler
{
	function getInput($params)
	{
		$this->params = $params;

		$option = JFactory::getApplication()->input->get('option');

		// do not place toggler stuff on JoomFish pages
		if ($option == 'com_joomfish')
		{
			return '';
		}

		$param  = $this->get('param');
		$value  = $this->get('value');
		$nofx   = $this->get('nofx');
		$method = $this->get('method');
		$div    = $this->get('div', 0);

		RL_Document::script('regularlabs/toggler.min.js');

		$param = RL_RegEx::replace('^\s*(.*?)\s*$', '\1', $param);
		$param = RL_RegEx::replace('\s*\|\s*', '|', $param);

		$html = [];
		if ( ! $param)
		{
			return '</div>';
		}

		$param      = RL_RegEx::replace('[^a-z0-9-\.\|\@]', '_', $param);
		$param      = str_replace('@', '_', $param);
		$set_groups = explode('|', $param);
		$set_values = explode('|', $value);
		$ids        = [];
		foreach ($set_groups as $i => $group)
		{
			$count = $i;
			if ($count >= count($set_values))
			{
				$count = 0;
			}
			$value = explode(',', $set_values[$count]);
			foreach ($value as $val)
			{
				$ids[] = $group . '.' . $val;
			}
		}

		if ( ! $div)
		{
			$html[] = '</div></div>';
		}

		$html[] = '<div id="' . rand(1000000, 9999999) . '___' . implode('___', $ids) . '" class="rl_toggler';
		if ($nofx)
		{
			$html[] = ' rl_toggler_nofx';
		}
		if ($method == 'and')
		{
			$html[] = ' rl_toggler_and';
		}
		$html[] = '">';

		if ( ! $div)
		{
			$html[] = '<div><div>';
		}

		return implode('', $html);
	}

	private function get($val, $default = '')
	{
		if ( ! isset($this->params[$val]) || (string) $this->params[$val] == '')
		{
			return $default;
		}

		return (string) $this->params[$val];
	}
}
regularlabs/fields/slide.php000064400000005340152177723700012143 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Slide extends \RegularLabs\Library\Field
{
	public $type = 'Slide';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$this->params = $this->element->attributes();

		RL_Document::stylesheet('regularlabs/style.min.css');

		$label       = RL_String::html_entity_decoder(JText::_($this->get('label')));
		$description = $this->prepareText($this->get('description'));
		$lang_file   = $this->get('language_file');

		$html = '</td></tr></table></div></div>';
		$html .= '<div class="panel"><h3 class="jpane-toggler title" id="advanced-page"><span>';
		$html .= $label;
		$html .= '</span></h3>';
		$html .= '<div class="jpane-slider content"><table width="100%" class="paramlist admintable" cellspacing="1"><tr><td colspan="2" class="paramlist_value">';

		if ($lang_file)
		{
			jimport('joomla.filesystem.file');

			// Include extra language file
			$lang = str_replace('_', '-', JFactory::getLanguage()->getTag());

			$inc       = '';
			$lang_path = 'language/' . $lang . '/' . $lang . '.' . $lang_file . '.inc.php';
			if (JFile::exists(JPATH_ADMINISTRATOR . '/' . $lang_path))
			{
				$inc = JPATH_ADMINISTRATOR . '/' . $lang_path;
			}
			else if (JFile::exists(JPATH_SITE . '/' . $lang_path))
			{
				$inc = JPATH_SITE . '/' . $lang_path;
			}
			if ( ! $inc && $lang != 'en-GB')
			{
				$lang      = 'en-GB';
				$lang_path = 'language/' . $lang . '/' . $lang . '.' . $lang_file . '.inc.php';
				if (JFile::exists(JPATH_ADMINISTRATOR . '/' . $lang_path))
				{
					$inc = JPATH_ADMINISTRATOR . '/' . $lang_path;
				}
				else if (JFile::exists(JPATH_SITE . '/' . $lang_path))
				{
					$inc = JPATH_SITE . '/' . $lang_path;
				}
			}
			if ($inc)
			{
				include $inc;
			}
		}

		if ($description)
		{
			if ($description[0] != '<')
			{
				$description = '<p>' . $description . '</p>';
			}
			$class = 'rl_panel rl_panel_description';
			$html  .= '<div class="' . $class . '"><div class="rl_block rl_title">';
			$html  .= $description;
			$html  .= '<div style="clear: both;"></div></div></div>';
		}

		return $html;
	}
}
regularlabs/fields/icons.php000064400000006025152177723700012157 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Icons extends \RegularLabs\Library\Field
{
	public $type = 'Icons';

	protected function getInput()
	{
		$value = $this->value;
		if ( ! is_array($value))
		{
			$value = explode(',', $value);
		}

		$classes = [
			'reglab icon-contenttemplater',
			'home',
			'user',
			'locked',
			'comments',
			'comments-2',
			'out',
			'plus',
			'pencil',
			'pencil-2',
			'file',
			'file-add',
			'file-remove',
			'copy',
			'folder',
			'folder-2',
			'picture',
			'pictures',
			'list-view',
			'power-cord',
			'cube',
			'puzzle',
			'flag',
			'tools',
			'cogs',
			'cog',
			'equalizer',
			'wrench',
			'brush',
			'eye',
			'star',
			'calendar',
			'calendar-2',
			'help',
			'support',
			'warning',
			'checkmark',
			'mail',
			'mail-2',
			'drawer',
			'drawer-2',
			'box-add',
			'box-remove',
			'search',
			'filter',
			'camera',
			'play',
			'music',
			'grid-view',
			'grid-view-2',
			'menu',
			'thumbs-up',
			'thumbs-down',
			'plus-2',
			'minus-2',
			'key',
			'quote',
			'quote-2',
			'database',
			'location',
			'zoom-in',
			'zoom-out',
			'health',
			'wand',
			'refresh',
			'vcard',
			'clock',
			'compass',
			'address',
			'feed',
			'flag-2',
			'pin',
			'lamp',
			'chart',
			'bars',
			'pie',
			'dashboard',
			'lightning',
			'move',
			'printer',
			'color-palette',
			'camera-2',
			'cart',
			'basket',
			'broadcast',
			'screen',
			'tablet',
			'mobile',
			'users',
			'briefcase',
			'download',
			'upload',
			'bookmark',
			'out-2',
		];

		$html = [];

		if ($this->get('show_none'))
		{
			$checked = (in_array('0', $value) ? ' checked="checked"' : '');
			$html[]  = '<fieldset>';
			$html[]  = '<input type="radio" id="' . $this->id . '0" name="' . $this->name . '"' . ' value="0"' . $checked . '>';
			$html[]  = '<label for="' . $this->id . '0">' . JText::_('RL_NO_ICON') . '</label>';
			$html[]  = '</fieldset>';
		}

		foreach ($classes as $i => $class)
		{
			$id      = str_replace(' ', '_', $this->id . $class);
			$checked = (in_array($class, $value) ? ' checked="checked"' : '');

			$html[] = '<fieldset class="pull-left">';
			$html[] = '<input type="radio" id="' . $id . '" name="' . $this->name . '"'
				. ' value="' . htmlspecialchars($class, ENT_COMPAT, 'UTF-8') . '"' . $checked . '>';
			$html[] = '<label for="' . $id . '" class="btn btn-small"><span class="icon-' . $class . '"></span></label>';
			$html[] = '</fieldset>';
		}

		return '<div id="' . $this->id . '" class="btn-group radio rl_icon_group">' . implode('', $html) . '</div>';
	}
}
regularlabs/fields/customfieldvalue.php000064400000002544152177723700014421 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_CustomFieldValue extends \RegularLabs\Library\Field
{
	public $type = 'CustomFieldValue';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$label       = $this->get('label') ? $this->get('label') : '';
		$size        = $this->get('size') ? 'style="width:' . $this->get('size') . 'px"' : '';
		$class       = 'class="' . ($this->get('class') ? $this->get('class') : 'text_area') . '"';
		$this->value = htmlspecialchars(RL_String::html_entity_decoder($this->value), ENT_QUOTES);

		return
			'</div></div></div>'
			. '<input type="text" name="' . $this->name . '" id="' . $this->id . '" value="' . $this->value
			. '" placeholder="' . JText::_($label) . '" title="' . JText::_($label) . '" ' . $class . ' ' . $size . '>';
	}
}
regularlabs/fields/tags.php000064400000005062152177723700012002 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Tags extends \RegularLabs\Library\Field
{
	public $type = 'Tags';

	protected function getInput()
	{
		$size        = (int) $this->get('size');
		$simple      = (int) $this->get('simple');
		$show_ignore = $this->get('show_ignore');
		$use_names   = $this->get('use_names');

		if ($show_ignore && in_array('-1', $this->value))
		{
			$this->value = ['-1'];
		}

		return $this->selectListAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'simple', 'show_ignore', 'use_names'),
			$simple
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name   = $attributes->get('name', $this->type);
		$id     = $attributes->get('id', strtolower($name));
		$value  = $attributes->get('value', []);
		$size   = $attributes->get('size');
		$simple = $attributes->get('simple');

		$options = $this->getOptions(
			(bool) $attributes->get('show_all'),
			(bool) $attributes->get('use_names')
		);

		return $this->selectList($options, $name, $value, $id, $size, true, $simple);
	}

	protected function getOptions($show_ignore = false, $use_names = false, $value = [])
	{
		// assemble items to the array
		$options = [];

		if ($show_ignore)
		{
			$options[] = JHtml::_('select.option', '-1', '- ' . JText::_('RL_IGNORE') . ' -');
			$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true);
		}

		$options = array_merge($options, $this->getTags($use_names));

		return $options;
	}

	protected function getTags($use_names)
	{
		$value = $use_names ? 'a.title' : 'a.id';

		$query = $this->db->getQuery(true)
			->select($value . ' as value, a.title as text, a.parent_id AS parent')
			->from('#__tags AS a')
			->select('COUNT(DISTINCT b.id) - 1 AS level')
			->join('LEFT', '#__tags AS b ON a.lft > b.lft AND a.rgt < b.rgt')
			->where('a.alias <> ' . $this->db->quote('root'))
			->where('a.published IN (0,1)')
			->group('a.id')
			->order('a.lft ASC');
		$this->db->setQuery($query);

		return $this->db->loadObjectList();
	}
}
regularlabs/fields/hikashop.php000064400000005110152177723700012644 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_HikaShop extends \RegularLabs\Library\FieldGroup
{
	public $type = 'HikaShop';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories' => 'category', 'products' => 'product']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__hikashop_category')
			->where('category_published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear()
			->select('c.category_id')
			->from('#__hikashop_category AS c')
			->where('c.category_type = ' . $this->db->quote('root'));
		$this->db->setQuery($query);
		$root = (int) $this->db->loadResult();

		$query->clear()
			->select('c.category_id as id, c.category_parent_id AS parent_id, c.category_name AS title, c.category_published as published')
			->from('#__hikashop_category AS c')
			->where('c.category_type = ' . $this->db->quote('product'))
			->where('c.category_published > -1')
			->order('c.category_ordering, c.category_name');
		$this->db->setQuery($query);
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items, $root);
	}

	function getProducts()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__hikashop_product AS p')
			->where('p.product_published = 1')
			->where('p.product_type = ' . $this->db->quote('main'));
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('p.product_id as id, p.product_name AS name, p.product_published AS published, c.category_name AS cat')
			->join('LEFT', '#__hikashop_product_category AS x ON x.product_id = p.product_id')
			->join('INNER', '#__hikashop_category AS c ON c.category_id = x.category_id')
			->group('p.product_id')
			->order('p.product_id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['cat', 'id']);
	}
}
regularlabs/fields/templates.php000064400000006152152177723700013043 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Templates extends \RegularLabs\Library\Field
{
	public $type = 'Templates';

	protected function getInput()
	{
		// fix old '::' separator and change it to '--'
		$value = json_encode($this->value);
		$value = str_replace('::', '--', $value);
		$value = (array) json_decode($value, true);

		$size     = (int) $this->get('size');
		$multiple = $this->get('multiple');

		return $this->selectListAjax(
			$this->type, $this->name, $value, $this->id,
			compact('size', 'multiple')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getOptions();

		return $this->selectList($options, $name, $value, $id, $size, $multiple);
	}

	protected function getOptions()
	{
		$options = [];

		$templates = $this->getTemplates();

		foreach ($templates as $styles)
		{
			$level = 0;
			foreach ($styles as $style)
			{
				$style->level = $level;
				$options[]    = $style;

				if (count($styles) <= 2)
				{
					break;
				}

				$level = 1;
			}
		}

		return $options;
	}

	protected function getTemplates()
	{
		$groups = [];
		$lang   = JFactory::getLanguage();

		// Get the database object and a new query object.
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('s.id, s.title, e.name as name, s.template')
			->from('#__template_styles as s')
			->where('s.client_id = 0')
			->join('LEFT', '#__extensions as e on e.element=s.template')
			->where('e.enabled=1')
			->where($db->quoteName('e.type') . '=' . $db->quote('template'))
			->order('s.template')
			->order('s.title');

		// Set the query and load the styles.
		$db->setQuery($query);
		$styles = $db->loadObjectList();

		// Build the grouped list array.
		if ($styles)
		{
			foreach ($styles as $style)
			{
				$template = $style->template;
				$lang->load('tpl_' . $template . '.sys', JPATH_SITE)
				|| $lang->load('tpl_' . $template . '.sys', JPATH_SITE . '/templates/' . $template);
				$name = JText::_($style->name);

				// Initialize the group if necessary.
				if ( ! isset($groups[$template]))
				{
					$groups[$template]   = [];
					$groups[$template][] = JHtml::_('select.option', $template, $name);
				}

				$groups[$template][] = JHtml::_('select.option', $template . '--' . $style->id, $style->title);
			}
		}

		return $groups;
	}
}
regularlabs/fields/list.php000064400000004043152177723700012015 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;

if ( ! class_exists('JFormFieldList'))
{
	require_once JPATH_LIBRARIES . '/joomla/form/fields/list.php';
}

class JFormFieldRL_List extends JFormFieldList
{
	protected $type = 'List';

	protected function getInput()
	{
		$html = [];
		$attr = '';

		// Initialize some field attributes.
		$attr .= ! empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= $this->size ? ' style="width:' . $this->size . 'px"' : '';
		$attr .= $this->multiple ? ' multiple' : '';
		$attr .= $this->required ? ' required aria-required="true"' : '';
		$attr .= $this->autofocus ? ' autofocus' : '';

		// To avoid user's confusion, readonly="true" should imply disabled="true".
		if ((string) $this->readonly == '1' || (string) $this->readonly == 'true' || (string) $this->disabled == '1' || (string) $this->disabled == 'true')
		{
			$attr .= ' disabled="disabled"';
		}

		// Initialize JavaScript field attributes.
		$attr .= $this->onchange ? ' onchange="' . $this->onchange . '"' : '';

		// Get the field options.
		$options = (array) $this->getOptions();

		if ((string) $this->readonly == '1' || (string) $this->readonly == 'true')
		{
			// Create a read-only list (no name) with a hidden input to store the value.
			$html[] = JHtml::_('select.genericlist', $options, '', trim($attr), 'value', 'text', $this->value, $this->id);
			$html[] = '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') . '">';
		}
		else
		{
			// Create a regular list.
			$html[] = JHtml::_('select.genericlist', $options, $this->name, trim($attr), 'value', 'text', $this->value, $this->id);
		}

		return implode($html);
	}
}
regularlabs/fields/agents.php000064400000016304152177723700012326 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Agents extends \RegularLabs\Library\Field
{
	public $type = 'Agents';

	protected function getInput()
	{
		if ( ! is_array($this->value))
		{
			$this->value = explode(',', $this->value);
		}

		$size  = (int) $this->get('size');
		$group = $this->get('group', 'os');

		return $this->selectListSimpleAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'group')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name  = $attributes->get('name', $this->type);
		$id    = $attributes->get('id', strtolower($name));
		$value = $attributes->get('value', []);
		$size  = $attributes->get('size');

		$options = $this->getAgents(
			$attributes->get('group')
		);

		return $this->selectListSimple($options, $name, $value, $id, $size, true);
	}

	function getAgents($group = 'os')
	{
		$agents = [];
		switch ($group)
		{
			/* OS */
			case 'os':
				$agents[] = ['Windows (' . JText::_('JALL') . ')', 'Windows'];
				$agents[] = ['Windows 10', 'Windows nt 10.0'];
				$agents[] = ['Windows 8', 'Windows nt 6.2'];
				$agents[] = ['Windows 7', 'Windows nt 6.1'];
				$agents[] = ['Windows Vista', 'Windows nt 6.0'];
				$agents[] = ['Windows Server 2003', 'Windows nt 5.2'];
				$agents[] = ['Windows XP', 'Windows nt 5.1'];
				$agents[] = ['Windows 2000 sp1', 'Windows nt 5.01'];
				$agents[] = ['Windows 2000', 'Windows nt 5.0'];
				$agents[] = ['Windows NT 4.0', 'Windows nt 4.0'];
				$agents[] = ['Windows Me', 'Win 9x 4.9'];
				$agents[] = ['Windows 98', 'Windows 98'];
				$agents[] = ['Windows 95', 'Windows 95'];
				$agents[] = ['Windows CE', 'Windows ce'];
				$agents[] = ['Mac OS (' . JText::_('JALL') . ')', '#(Mac OS|Mac_PowerPC|Macintosh)#'];
				$agents[] = ['Mac OSX (' . JText::_('JALL') . ')', 'Mac OS X'];
				$agents[] = ['Mac OSX El Capitan', 'Mac OS X 10.11'];
				$agents[] = ['Mac OSX Yosemite', 'Mac OS X 10.10'];
				$agents[] = ['Mac OSX Mavericks', 'Mac OS X 10.9'];
				$agents[] = ['Mac OSX Mountain Lion', 'Mac OS X 10.8'];
				$agents[] = ['Mac OSX Lion', 'Mac OS X 10.7'];
				$agents[] = ['Mac OSX Snow Leopard', 'Mac OS X 10.6'];
				$agents[] = ['Mac OSX Leopard', 'Mac OS X 10.5'];
				$agents[] = ['Mac OSX Tiger', 'Mac OS X 10.4'];
				$agents[] = ['Mac OSX Panther', 'Mac OS X 10.3'];
				$agents[] = ['Mac OSX Jaguar', 'Mac OS X 10.2'];
				$agents[] = ['Mac OSX Puma', 'Mac OS X 10.1'];
				$agents[] = ['Mac OSX Cheetah', 'Mac OS X 10.0'];
				$agents[] = ['Mac OS (classic)', '#(Mac_PowerPC|Macintosh)#'];
				$agents[] = ['Linux', '#(Linux|X11)#'];
				$agents[] = ['Open BSD', 'OpenBSD'];
				$agents[] = ['Sun OS', 'SunOS'];
				$agents[] = ['QNX', 'QNX'];
				$agents[] = ['BeOS', 'BeOS'];
				$agents[] = ['OS/2', 'OS/2'];
				break;

			/* Browsers */
			case 'browsers':
				if ($this->get('simple') && $this->get('simple') !== 'false')
				{
					$agents[] = ['Chrome', 'Chrome'];
					$agents[] = ['Firefox', 'Firefox'];
					$agents[] = ['Edge', 'Edge'];
					$agents[] = ['Internet Explorer', 'MSIE'];
					$agents[] = ['Opera', 'Opera'];
					$agents[] = ['Safari', 'Safari'];
					break;
				}

				$agents[] = ['Chrome (' . JText::_('JALL') . ')', 'Chrome'];
				$agents[] = ['Chrome 61-70', '#Chrome/(6[1-9]|70)\.#'];
				$agents[] = ['Chrome 51-60', '#Chrome/(5[1-9]|60)\.#'];
				$agents[] = ['Chrome 41-50', '#Chrome/(4[1-9]|50)\.#'];
				$agents[] = ['Chrome 31-40', '#Chrome/(3[1-9]|40)\.#'];
				$agents[] = ['Chrome 21-30', '#Chrome/(2[1-9]|30)\.#'];
				$agents[] = ['Chrome 11-20', '#Chrome/(1[1-9]|20)\.#'];
				$agents[] = ['Chrome 1-10', '#Chrome/([1-9]|10)\.#'];
				$agents[] = ['Firefox (' . JText::_('JALL') . ')', 'Firefox'];
				$agents[] = ['Firefox 61-70', '#Firefox/(6[1-9]|70)\.#'];
				$agents[] = ['Firefox 51-60', '#Firefox/(5[1-9]|60)\.#'];
				$agents[] = ['Firefox 41-50', '#Firefox/(4[1-9]|50)\.#'];
				$agents[] = ['Firefox 31-40', '#Firefox/(3[1-9]|40)\.#'];
				$agents[] = ['Firefox 21-30', '#Firefox/(2[1-9]|30)\.#'];
				$agents[] = ['Firefox 11-20', '#Firefox/(1[1-9]|20)\.#'];
				$agents[] = ['Firefox 1-10', '#Firefox/([1-9]|10)\.#'];
				$agents[] = ['Internet Explorer (' . JText::_('JALL') . ')', 'MSIE'];
				$agents[] = ['Internet Explorer Edge', 'MSIE Edge']; // missing MSIE is added to agent string in assignments/agents.php
				$agents[] = ['Edge 15', 'Edge/15'];
				$agents[] = ['Edge 14', 'Edge/14'];
				$agents[] = ['Edge 13', 'Edge/13'];
				$agents[] = ['Edge 12', 'Edge/12'];
				$agents[] = ['Internet Explorer 11', 'MSIE 11']; // missing MSIE is added to agent string in assignments/agents.php
				$agents[] = ['Internet Explorer 10.6', 'MSIE 10.6'];
				$agents[] = ['Internet Explorer 10.0', 'MSIE 10.0'];
				$agents[] = ['Internet Explorer 10', 'MSIE 10.'];
				$agents[] = ['Internet Explorer 9', 'MSIE 9.'];
				$agents[] = ['Internet Explorer 8', 'MSIE 8.'];
				$agents[] = ['Internet Explorer 7', 'MSIE 7.'];
				$agents[] = ['Internet Explorer 1-6', '#MSIE [1-6]\.#'];
				$agents[] = ['Opera (' . JText::_('JALL') . ')', 'Opera'];
				$agents[] = ['Opera 51-60', '#Opera/(5[1-9]|60)\.#'];
				$agents[] = ['Opera 41-50', '#Opera/(4[1-9]|50)\.#'];
				$agents[] = ['Opera 31-40', '#Opera/(3[1-9]|40)\.#'];
				$agents[] = ['Opera 21-30', '#Opera/(2[1-9]|30)\.#'];
				$agents[] = ['Opera 11-20', '#Opera/(1[1-9]|20)\.#'];
				$agents[] = ['Opera 1-10', '#Opera/([1-9]|10)\.#'];
				$agents[] = ['Safari (' . JText::_('JALL') . ')', 'Safari'];
				$agents[] = ['Safari 11', '#Version/11\..*Safari/#'];
				$agents[] = ['Safari 10', '#Version/10\..*Safari/#'];
				$agents[] = ['Safari 9', '#Version/9\..*Safari/#'];
				$agents[] = ['Safari 8', '#Version/8\..*Safari/#'];
				$agents[] = ['Safari 7', '#Version/7\..*Safari/#'];
				$agents[] = ['Safari 6', '#Version/6\..*Safari/#'];
				$agents[] = ['Safari 5', '#Version/5\..*Safari/#'];
				$agents[] = ['Safari 4', '#Version/4\..*Safari/#'];
				$agents[] = ['Safari 1-3', '#Version/[1-3]\..*Safari/#'];
				break;

			/* Mobile browsers */
			case 'mobile':
				$agents[] = [JText::_('JALL'), 'mobile'];
				$agents[] = ['Android', 'Android'];
				$agents[] = ['Android Chrome', '#Android.*Chrome#'];
				$agents[] = ['Blackberry', 'Blackberry'];
				$agents[] = ['IE Mobile', 'IEMobile'];
				$agents[] = ['iPad', 'iPad'];
				$agents[] = ['iPhone', 'iPhone'];
				$agents[] = ['iPod Touch', 'iPod'];
				$agents[] = ['NetFront', 'NetFront'];
				$agents[] = ['Nokia', 'NokiaBrowser'];
				$agents[] = ['Opera Mini', 'Opera Mini'];
				$agents[] = ['Opera Mobile', 'Opera Mobi'];
				$agents[] = ['UC Browser', 'UC Browser'];
				break;
		}

		$options = [];
		foreach ($agents as $agent)
		{
			$option    = JHtml::_('select.option', $agent[1], $agent[0]);
			$options[] = $option;
		}

		return $options;
	}
}
regularlabs/fields/loadlanguage.php000064400000002034152177723700013463 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\Language as RL_Language;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_LoadLanguage extends \RegularLabs\Library\Field
{
	public $type = 'LoadLanguage';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$extension = $this->get('extension');
		$admin     = $this->get('admin', 1);

		self::loadLanguage($extension, $admin);

		return '';
	}

	function loadLanguage($extension, $admin = 1)
	{
		if ( ! $extension)
		{
			return;
		}

		RL_Language::load($extension, $admin ? JPATH_ADMINISTRATOR : JPATH_SITE);
	}
}
regularlabs/fields/textareaplus.php000064400000006433152177723700013570 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Date\Date as JDate;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_TextAreaPlus extends \RegularLabs\Library\Field
{
	public $type = 'TextAreaPlus';

	protected function getLabel()
	{
		$resize                = $this->get('resize', 0);
		$show_insert_date_name = $this->get('show_insert_date_name', 0);

		$label = RL_String::html_entity_decoder(JText::_($this->get('label')));

		$attribs = 'id="' . $this->id . '-lbl" for="' . $this->id . '"';

		if ($this->description)
		{
			$attribs .= ' class="hasPopover" title="' . $label . '"'
				. ' data-content="' . JText::_($this->description) . '"';
		}

		$html = '<label ' . $attribs . '>' . $label;

		if ($show_insert_date_name)
		{
			$date_name = JDate::getInstance()->format('[Y-m-d]') . ' ' . JFactory::getUser()->name . ' : ';
			$onclick   = "RegularLabsForm.prependTextarea('" . $this->id . "', '" . addslashes($date_name) . "', '---');";

			$html .= '<br><span role="button" class="btn btn-mini rl_insert_date" onclick="' . $onclick . '">'
				. JText::_('RL_INSERT_DATE_NAME')
				. '</span>';
		}

		if ($resize)
		{
			$html .= '<br><span role="button" class="rl_resize_textarea rl_maximize"'
				. ' data-id="' . $this->id . '"  data-min="' . $this->get('height', 80) . '" data-max="' . $resize . '">'
				. '<span class="rl_resize_textarea_maximize">'
				. '[ + ]'
				. '</span>'
				. '<span class="rl_resize_textarea_minimize">'
				. '[ - ]'
				. '</span>'
				. '</span>';
		}

		$html .= '</label>';

		return $html;
	}

	protected function getInput()
	{
		$width  = $this->get('width', 600);
		$height = $this->get('height', 80);
		$class  = ' class="' . trim('rl_textarea ' . $this->get('class')) . '"';
		$type   = $this->get('texttype');
		$hint   = $this->get('hint');

		if (is_array($this->value))
		{
			$this->value = trim(implode("\n", $this->value));
		}

		if ($type == 'html')
		{
			// Convert <br> tags so they are not visible when editing
			$this->value = str_replace('<br>', "\n", $this->value);
		}
		else if ($type == 'regex')
		{
			// Protects the special characters
			$this->value = str_replace('[:REGEX_ENTER:]', '\n', $this->value);
		}

		if ($this->get('translate') && $this->get('translate') !== 'false')
		{
			$this->value = JText::_($this->value);
			$hint        = JText::_($hint);
		}

		$this->value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');

		$hint = $hint ? ' placeholder="' . $hint . '"' : '';

		return
			'<textarea name="' . $this->name . '" cols="' . (round($width / 7.5)) . '" rows="' . (round($height / 15)) . '"'
			. ' style="width:' . (($width == '600') ? '100%' : $width . 'px') . ';height:' . $height . 'px"'
			. ' id="' . $this->id . '"' . $class . $hint . '>' . $this->value . '</textarea>';
	}
}
regularlabs/fields/radioimages.php000064400000005177152177723700013337 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_RadioImages extends \RegularLabs\Library\Field
{
	public $type = 'RadioImages';

	protected function getInput()
	{
		$this->params = $this->element->attributes();

		jimport('joomla.filesystem.folder');
		jimport('joomla.filesystem.file');

		// path to images directory
		$path     = JPATH_ROOT . '/' . $this->get('directory');
		$filter   = $this->get('filter');
		$exclude  = $this->get('exclude');
		$stripExt = $this->get('stripext');
		$files    = JFolder::files($path, $filter);
		$rowcount = $this->get('rowcount');

		$options = [];

		if ( ! $this->get('hide_none'))
		{
			$options[] = JHtml::_('select.option', '-1', JText::_('Do not use') . '<br>');
		}

		if ( ! $this->get('hide_default'))
		{
			$options[] = JHtml::_('select.option', '', JText::_('Use default') . '<br>');
		}

		if (is_array($files))
		{
			$count = 0;
			foreach ($files as $file)
			{
				if ($exclude)
				{
					if (RL_RegEx::match(chr(1) . $exclude . chr(1), $file))
					{
						continue;
					}
				}
				$count++;
				if ($stripExt)
				{
					$file = JFile::stripExt($file);
				}
				$image = '<img src="../' . $this->get('directory') . '/' . $file . '" style="padding-right: 10px;" title="' . $file . '" alt="' . $file . '">';
				if ($rowcount && $count >= $rowcount)
				{
					$image .= '<br>';
					$count = 0;
				}
				$options[] = JHtml::_('select.option', $file, $image);
			}
		}

		$list = JHtml::_('select.radiolist', $options, '' . $this->name . '', '', 'value', 'text', $this->value, $this->id);

		$list = '<div style="float:left;">' . str_replace('<input type="radio"', '</div><div style="float:left;margin:2px 0;"><input type="radio" style="float:left;"', $list) . '</div>';
		$list = str_replace(['<label', '</label>'], ['<span style="float: left;"', '</span>'], $list);
		$list = RL_RegEx::replace('</span>(\s*)</div>', '</span></div>\1', $list);
		$list = str_replace('<br></span></div>', '<br></span></div><div style="clear:both;"></div>', $list);

		$list = '<div style="clear:both;"></div>' . $list;

		return $list;
	}
}
regularlabs/fields/akeebasubs.php000064400000002245152177723700013151 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_AkeebaSubs extends \RegularLabs\Library\FieldGroup
{
	public $type          = 'AkeebaSubs';
	public $default_group = 'Levels';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['levels']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getLevels()
	{
		$query = $this->db->getQuery(true)
			->select('l.akeebasubs_level_id as id, l.title AS name, l.enabled as published')
			->from('#__akeebasubs_levels AS l')
			->where('l.enabled > -1')
			->order('l.title, l.akeebasubs_level_id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['id']);
	}
}
regularlabs/fields/header.php000064400000006420152177723700012273 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Application\ApplicationHelper as JApplicationHelper;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Header extends \RegularLabs\Library\Field
{
	public $type = 'Header';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$title       = $this->get('label');
		$description = $this->get('description');
		$xml         = $this->get('xml');
		$url         = $this->get('url');

		if ($description)
		{
			$description = RL_String::html_entity_decoder(trim(JText::_($description)));
		}

		if ($title)
		{
			$title = JText::_($title);
		}

		if ($description)
		{
			// Replace inline monospace style with rl_code classname
			$description = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $description);

			// 'Break' plugin style tags
			$description = str_replace(['{', '['], ['<span>{</span>', '<span>[</span>'], $description);

			// Wrap in paragraph (if not already starting with an html tag)
			if ($description[0] != '<')
			{
				$description = '<p>' . $description . '</p>';
			}
		}

		if ( ! $xml && $this->form->getValue('element'))
		{
			if ($this->form->getValue('folder'))
			{
				$xml = 'plugins/' . $this->form->getValue('folder') . '/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
			}
			else
			{
				$xml = 'administrator/modules/' . $this->form->getValue('element') . '/' . $this->form->getValue('element') . '.xml';
			}
		}

		if ($xml)
		{
			$xml     = JApplicationHelper::parseXMLInstallFile(JPATH_SITE . '/' . $xml);
			$version = 0;
			if ($xml && isset($xml['version']))
			{
				$version = $xml['version'];
			}
			if ($version)
			{
				if (strpos($version, 'PRO') !== false)
				{
					$version = str_replace('PRO', '', $version);
					$version .= ' <small style="color:green">[PRO]</small>';
				}
				else if (strpos($version, 'FREE') !== false)
				{
					$version = str_replace('FREE', '', $version);
					$version .= ' <small style="color:green">[FREE]</small>';
				}
				if ($title)
				{
					$title .= ' v';
				}
				else
				{
					$title = JText::_('Version') . ' ';
				}
				$title .= $version;
			}
		}
		$html = [];

		if ($title)
		{
			if ($url)
			{
				$title = '<a href="' . $url . '" target="_blank" title="'
					. RL_RegEx::replace('<[^>]*>', '', $title) . '">' . $title . '</a>';
			}
			$html[] = '<h4>' . RL_String::html_entity_decoder($title) . '</h4>';
		}

		if ($description)
		{
			$html[] = $description;
		}

		if ($url)
		{
			$html[] = '<p><a href="' . $url . '" class="btn btn-default" target="_blank" title="' . JText::_('RL_MORE_INFO') . '">' . JText::_('RL_MORE_INFO') . ' >></a></p>';
		}

		return '</div><div>' . implode('', $html);
	}
}
regularlabs/fields/form2content.php000064400000002172152177723700013463 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Form2Content extends \RegularLabs\Library\FieldGroup
{
	public $type          = 'Form2Content';
	public $default_group = 'Projects';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['projects' => 'project'], '', 'f2c'))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getProjects()
	{
		$query = $this->db->getQuery(true)
			->select('t.id, t.title as name')
			->from('#__f2c_project AS t')
			->where('t.published = 1')
			->order('t.title, t.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list);
	}
}
regularlabs/fields/accesslevel.php000064400000004230152177723700013331 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_AccessLevel extends \RegularLabs\Library\Field
{
	public $type = 'AccessLevel';

	protected function getInput()
	{
		$size      = (int) $this->get('size');
		$multiple  = $this->get('multiple');
		$show_all  = $this->get('show_all');
		$use_names = $this->get('use_names');

		return $this->selectListAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('size', 'multiple', 'show_all', 'use_names')
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');

		$options = $this->getOptions(
			(bool) $attributes->get('show_all'),
			(bool) $attributes->get('use_names')
		);

		return $this->selectList($options, $name, $value, $id, $size, $multiple);
	}

	protected function getOptions($show_all = false, $use_names = false)
	{
		$options = $this->getAccessLevels($use_names);

		if ($show_all)
		{
			$option          = (object) [];
			$option->value   = -1;
			$option->text    = '- ' . JText::_('JALL') . ' -';
			$option->disable = '';
			array_unshift($options, $option);
		}

		return $options;
	}

	protected function getAccessLevels($use_names = false)
	{
		$value = $use_names ? 'a.title' : 'a.id';

		$query = $this->db->getQuery(true)
			->select($value . ' as value, a.title as text')
			->from('#__viewlevels AS a')
			->group('a.id')
			->order('a.ordering ASC');
		$this->db->setQuery($query);

		return $this->db->loadObjectList();
	}
}
regularlabs/fields/datetime.php000064400000002450152177723700012636 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Date as RL_Date;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_DateTime extends \RegularLabs\Library\Field
{
	public $type = 'DateTime';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$label  = $this->get('label');
		$format = $this->get('format');

		$date = JFactory::getDate();

		$tz = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));
		$date->setTimeZone($tz);

		if ($format)
		{
			if (strpos($format, '%') !== false)
			{
				$format = RL_Date::strftimeToDateFormat($format);
			}
			$html = $date->format($format, true);
		}
		else
		{
			$html = $date->format('', true);
		}

		if ($label)
		{
			$html = JText::sprintf($label, $html);
		}

		return '</div><div>' . $html;
	}
}
regularlabs/fields/colorpicker.php000064400000005601152177723700013357 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         18.10.22077
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2018 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Form\FormField as JFormField;
use RegularLabs\Library\Document as RL_Document;

jimport('joomla.form.formfield');

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_ColorPicker extends JFormField
{
	public $type = 'ColorPicker';

	protected function getInput()
	{
		if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
		{
			return null;
		}

		$field = new RLFieldColorPicker;

		return $field->getInput($this->name, $this->id, $this->value, $this->element->attributes());
	}
}

class RLFieldColorPicker
{
	function getInput($name, $id, $value, $params)
	{
		$this->name   = $name;
		$this->id     = $id;
		$this->value  = $value;
		$this->params = $params;
		$action       = '';

		if ($this->get('inlist', 0) && $this->get('action'))
		{
			$this->name = $name . $id;
			$this->id   = $name . $id;
			$action     = ' onchange="' . $this->get('action') . '"';
		}

		RL_Document::script('regularlabs/colorpicker.min.js');
		RL_Document::stylesheet('regularlabs/colorpicker.min.css');

		$class = ' class="' . trim('nncolorpicker chzn-done ' . $this->get('class')) . '"';

		$color = strtolower($this->value);
		if ( ! $color || in_array($color, ['none', 'transparent']))
		{
			$color = 'none';
		}
		else if ($color[0] != '#')
		{
			$color = '#' . $color;
		}

		$colors = $this->get('colors');
		if (empty($colors))
		{
			$colors = [
				'none',
				'#049cdb',
				'#46a546',
				'#9d261d',
				'#ffc40d',
				'#f89406',
				'#c3325f',
				'#7a43b6',
				'#ffffff',
				'#999999',
				'#555555',
				'#000000',
			];
		}
		else
		{
			$colors = explode(',', $colors);
		}

		$split = (int) $this->get('split');
		if ( ! $split)
		{
			$count = count($colors);
			if ($count % 5 == 0)
			{
				$split = 5;
			}
			else if ($count % 4 == 0)
			{
				$split = 4;
			}
		}
		$split = $split ? $split : 3;

		$html   = [];
		$html[] = '<select ' . $action . ' name="' . $this->name . '" id="' . $this->id . '"'
			. $class . ' style="visibility:hidden;width:22px;height:1px">';

		foreach ($colors as $i => $c)
		{
			$html[] = '<option' . ($c == $color ? ' selected="selected"' : '') . '>' . $c . '</option>';
			if (($i + 1) % $split == 0)
			{
				$html[] = '<option>-</option>';
			}
		}
		$html[] = '</select>';

		return implode('', $html);
	}

	private function get($val, $default = '')
	{
		if ( ! isset($this->params[$val]) || (string) $this->params[$val] == '')
		{
			return $default;
		}

		return (string) $this->params[$val];
	}
}
regularlabs/fields/zoo.php000064400000007226152177723700011657 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form as RL_Form;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Zoo extends \RegularLabs\Library\FieldGroup
{
	public $type = 'Zoo';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['applications' => 'application', 'categories' => 'category', 'items' => 'item']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__zoo_category AS c')
			->where('c.published > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$options = [];
		if ($this->get('show_ignore'))
		{
			if (in_array('-1', $this->value))
			{
				$this->value = ['-1'];
			}
			$options[] = JHtml::_('select.option', '-1', '- ' . JText::_('RL_IGNORE') . ' -');
			$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true);
		}

		$query->clear()
			->select('a.id, a.name')
			->from('#__zoo_application AS a')
			->order('a.name, a.id');
		$this->db->setQuery($query);
		$apps = $this->db->loadObjectList();

		foreach ($apps as $i => $app)
		{
			$query->clear()
				->select('c.id, c.parent AS parent_id, c.name AS title, c.published')
				->from('#__zoo_category AS c')
				->where('c.application_id = ' . (int) $app->id)
				->where('c.published > -1')
				->order('c.ordering, c.name');
			$this->db->setQuery($query);
			$items = $this->db->loadObjectList();

			if ($i)
			{
				$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true);
			}

			// establish the hierarchy of the menu
			// TODO: use node model
			$children = [];

			if ($items)
			{
				// first pass - collect children
				foreach ($items as $v)
				{
					$pt   = $v->parent_id;
					$list = @$children[$pt] ? $children[$pt] : [];
					array_push($list, $v);
					$children[$pt] = $list;
				}
			}

			// second pass - get an indent list of the items
			$list = JHtml::_('menu.treerecurse', 0, '', [], $children, 9999, 0, 0);

			// assemble items to the array
			$options[] = JHtml::_('select.option', 'app' . $app->id, '[' . $app->name . ']');
			foreach ($list as $item)
			{
				$item->treename = '  ' . str_replace('&#160;&#160;- ', '  ', $item->treename);
				$item->treename = RL_Form::prepareSelectItem($item->treename, $item->published);
				$option         = JHtml::_('select.option', $item->id, $item->treename);
				$option->level  = 1;
				$options[]      = $option;
			}
		}

		return $options;
	}

	function getItems()
	{
		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__zoo_item AS i')
			->where('i.state > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$query->clear('select')
			->select('i.id, i.name, a.name as cat, i.state as published')
			->join('LEFT', '#__zoo_application AS a ON a.id = i.application_id')
			->group('i.id')
			->order('i.name, i.priority, i.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['cat', 'id']);
	}
}
regularlabs/fields/key.php000064400000005560152177723700011637 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Key extends \RegularLabs\Library\Field
{
	public $type = 'Key';

	protected function getInput()
	{
		$action = $this->get('action', 'Joomla.submitbutton(\'config.save.component.apply\')');

		$key = trim($this->value);

		if ( ! $key)
		{
			return '<div id="' . $this->id . '_field" class="btn-wrapper input-append clearfix">'
				. '<input type="text" class="rl_codefield" name="' . $this->name . '" id="' . $this->id . '" autocomplete="off" value="">'
				. '<button href="#" class="btn btn-success" title="' . JText::_('JAPPLY') . '" onclick="' . $action . '">'
				. '<span class="icon-checkmark"></span>'
				. '</button>'
				. '</div>';
		}

		$cloak_length = max(0, strlen($key) - 4);
		$key          = str_repeat('*', $cloak_length) . substr($this->value, $cloak_length);

		$show = 'jQuery(\'#' . $this->id . '\').attr(\'name\', \'' . $this->name . '\');'
			. 'jQuery(\'#' . $this->id . '_hidden\').attr(\'name\', \'\');'
			. 'jQuery(\'#' . $this->id . '_button\').hide();'
			. 'jQuery(\'#' . $this->id . '_field\').show();';

		$hide = 'jQuery(\'#' . $this->id . '\').attr(\'name\', \'\');'
			. 'jQuery(\'#' . $this->id . '_hidden\').attr(\'name\', \'' . $this->name . '\');'
			. 'jQuery(\'#' . $this->id . '_field\').hide();'
			. 'jQuery(\'#' . $this->id . '_button\').show();';

		return
			'<div class="rl_keycode pull-left">' . $key . '</div>'

			. '<div id="' . $this->id . '_button" class="pull-left">'
			. '<button class="btn btn-default btn-small" onclick="' . $show . ';return false;">'
			. '<span class="icon-edit"></span> '
			. JText::_('JACTION_EDIT')
			. '</button>'
			. '</div>'

			. '<div class="clearfix"></div>'

			. '<div id="' . $this->id . '_field" class="btn-wrapper input-append clearfix" style="display:none;">'
			. '<input type="text" class="rl_codefield" name="" id="' . $this->id . '" autocomplete="off" value="">'
			. '<button href="#" class="btn btn-success btn" title="' . JText::_('JAPPLY') . '" onclick="' . $action . '">'
			. '<span class="icon-checkmark"></span>'
			. '</button>'
			. '<button href="#" class="btn btn-danger btn" title="' . JText::_('JCANCEL') . '" onclick="' . $hide . ';return false;">'
			. '<span class="icon-cancel-2"></span>'
			. '</button>'
			. '</div>'

			. '<input type="hidden" name="' . $this->name . '" id="' . $this->id . '_hidden" value="' . $this->value . '">';
	}
}

regularlabs/fields/text.php000064400000003731152177723700012031 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\StringHelper as RL_String;

require_once JPATH_LIBRARIES . '/joomla/form/fields/text.php';

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Text extends JFormFieldText
{
	public $type = 'Text';

	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$this->element = $element;

		$element['label']                = $this->prepareText($element['label']);
		$element['description']          = $this->prepareText($element['description']);
		$element['hint']                 = $this->prepareText($element['hint']);
		$element['translateDescription'] = false;

		return parent::setup($element, $value, $group);
	}

	private function prepareText($string = '')
	{
		$string = trim($string);

		if ($string == '')
		{
			return '';
		}

		// variables
		$var1 = JText::_($this->get('var1'));
		$var2 = JText::_($this->get('var2'));
		$var3 = JText::_($this->get('var3'));
		$var4 = JText::_($this->get('var4'));
		$var5 = JText::_($this->get('var5'));

		$string = JText::sprintf(JText::_($string), $var1, $var2, $var3, $var4, $var5);
		$string = trim(RL_String::html_entity_decoder($string));
		$string = str_replace('&quot;', '"', $string);
		$string = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $string);

		return $string;
	}

	private function get($val, $default = '')
	{
		if ( ! isset($this->params[$val]) || (string) $this->params[$val] == '')
		{
			return $default;
		}

		return (string) $this->params[$val];
	}
}
regularlabs/fields/k2.php000064400000006151152177723700011360 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

// If controller.php exists, assume this is K2 v3
defined('RL_K2_VERSION') or define('RL_K2_VERSION', JFile::exists(JPATH_ADMINISTRATOR . '/components/com_k2/controller.php') ? 3 : 2);

class JFormFieldRL_K2 extends \RegularLabs\Library\FieldGroup
{
	public $type = 'K2';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['categories', 'items', 'tags']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getCategories()
	{
		$state_field = RL_K2_VERSION == 3 ? 'state' : 'published';

		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__k2_categories AS c')
			->where('c.' . $state_field . ' > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$parent_field   = RL_K2_VERSION == 3 ? 'parent_id' : 'parent';
		$title_field    = RL_K2_VERSION == 3 ? 'title' : 'name';
		$ordering_field = RL_K2_VERSION == 3 ? 'lft' : 'ordering';

		$query->clear('select')
			->select('c.id, c.' . $parent_field . ' AS parent_id, c.' . $title_field . ' AS title, c.' . $state_field . ' AS published');
		if ( ! $this->get('getcategories', 1))
		{
			$query->where('c.' . $parent_field . ' = 0');
		}
		$query->order('c.' . $ordering_field . ', c.' . $title_field);
		$this->db->setQuery($query);
		$items = $this->db->loadObjectList();

		return $this->getOptionsTreeByList($items);
	}

	function getTags()
	{
		$state_field = RL_K2_VERSION == 3 ? 'state' : 'published';

		$query = $this->db->getQuery(true)
			->select('t.name as id, t.name as name')
			->from('#__k2_tags AS t')
			->where('t.' . $state_field . ' = 1')
			->where('t.name != ' . $this->db->quote(''))
			->group('t.name')
			->order('t.name');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list);
	}

	function getItems()
	{
		$state_field = RL_K2_VERSION == 3 ? 'state' : 'published';

		$query = $this->db->getQuery(true)
			->select('COUNT(*)')
			->from('#__k2_items AS i')
			->where('i.' . $state_field . ' > -1');
		$this->db->setQuery($query);
		$total = $this->db->loadResult();

		if ($total > $this->max_list_count)
		{
			return -1;
		}

		$cat_title_field = RL_K2_VERSION == 3 ? 'title' : 'name';

		$query->clear('select')
			->select('i.id, i.title as name, c.' . $cat_title_field . ' as cat, i.' . $state_field . ' as published')
			->join('LEFT', '#__k2_categories AS c ON c.id = i.catid')
			->group('i.id')
			->order('i.title, i.ordering, i.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list, ['cat', 'id']);
	}
}
regularlabs/fields/note.php000064400000003741152177723700012013 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Note extends \RegularLabs\Library\Field
{
	public $type = 'Note';

	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$this->element = $element;

		$element['label']                = $this->prepareText($element['label']);
		$element['description']          = $this->prepareText($element['description']);
		$element['translateDescription'] = false;

		return parent::setup($element, $value, $group);
	}

	protected function getLabel()
	{
		if (empty($this->element['label']) && empty($this->element['description']))
		{
			return '';
		}

		$title       = $this->element['label'] ? (string) $this->element['label'] : ($this->element['title'] ? (string) $this->element['title'] : '');
		$heading     = $this->element['heading'] ? (string) $this->element['heading'] : 'h4';
		$description = (string) $this->element['description'];
		$class       = ! empty($this->class) ? ' class="' . $this->class . '"' : '';
		$close       = (string) $this->element['close'];

		$html = [];

		if ($close)
		{
			$close  = $close == 'true' ? 'alert' : $close;
			$html[] = '<button type="button" class="close" data-dismiss="' . $close . '">&times;</button>';
		}

		$html[] = ! empty($title) ? '<' . $heading . '>' . JText::_($title) . '</' . $heading . '>' : '';
		$html[] = ! empty($description) ? JText::_($description) : '';

		return '</div><div ' . $class . '>' . implode('', $html);
	}

	protected function getInput()
	{
		return '';
	}
}
regularlabs/fields/plaintext.php000064400000002360152177723700013052 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_PlainText extends \RegularLabs\Library\Field
{
	public $type = 'PlainText';

	protected function getLabel()
	{
		$label   = $this->prepareText($this->get('label'));
		$tooltip = $this->prepareText($this->get('description'));

		if ( ! $label && ! $tooltip)
		{
			return '';
		}

		if ( ! $label)
		{
			return '<div>' . $tooltip . '</div>';
		}

		if ( ! $tooltip)
		{
			return '<div>' . $label . '</div>';
		}

		return '<label class="hasPopover" title="' . $label . '" data-content="' . htmlentities($tooltip) . '">'
			. $label . '</label>';
	}

	protected function getInput()
	{
		$text = $this->prepareText($this->value);

		if ( ! $text)
		{
			return '';
		}

		return '<fieldset class="rl_plaintext">' . $text . '</fieldset>';
	}
}
regularlabs/fields/modules.php000064400000010544152177723700012515 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\Form as RL_Form;
use RegularLabs\Library\RegEx as RL_RegEx;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_Modules extends \RegularLabs\Library\Field
{
	public $type = 'Modules';

	protected function getInput()
	{
		JHtml::_('behavior.modal', 'a.modal');

		$size = $this->get('size') ? 'style="width:' . $this->get('size') . 'px"' : '';

		$multiple  = $this->get('multiple');
		$showtype  = $this->get('showtype');
		$showid    = $this->get('showid');
		$showinput = $this->get('showinput');

		// load the list of modules
		$query = $this->db->getQuery(true)
			->select('m.id, m.title, m.position, m.module, m.published, m.language')
			->from('#__modules AS m')
			->where('m.client_id = 0')
			->where('m.published > -2')
			->order('m.position, m.title, m.ordering, m.id');
		$this->db->setQuery($query);
		$modules = $this->db->loadObjectList();

		// assemble menu items to the array
		$options = [];

		$p = 0;
		foreach ($modules as $item)
		{
			if ($p !== $item->position)
			{
				$pos = $item->position;
				if ($pos == '')
				{
					$pos = ':: ' . JText::_('JNONE') . ' ::';
				}
				$options[] = JHtml::_('select.option', '-', '[ ' . $pos . ' ]', 'value', 'text', true);
			}
			$p = $item->position;

			$item->title = $item->title;
			if ($showtype)
			{
				$item->title .= ' [' . $item->module . ']';
			}
			if ($showinput || $showid)
			{
				$item->title .= ' [' . $item->id . ']';
			}
			if ($item->language && $item->language != '*')
			{
				$item->title .= ' (' . $item->language . ')';
			}
			$item->title = RL_Form::prepareSelectItem($item->title, $item->published);

			$options[] = JHtml::_('select.option', $item->id, $item->title);
		}

		if ($showinput)
		{
			array_unshift($options, JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', true));
			array_unshift($options, JHtml::_('select.option', '-', '- ' . JText::_('Select Item') . ' -'));

			if ($multiple)
			{
				$onchange = 'if ( this.value ) { if ( ' . $this->id . '.value ) { ' . $this->id . '.value+=\',\'; } ' . $this->id . '.value+=this.value; } this.value=\'\';';
			}
			else
			{
				$onchange = 'if ( this.value ) { ' . $this->id . '.value=this.value;' . $this->id . '_text.value=this.options[this.selectedIndex].innerHTML.replace( /^((&|&amp;|&#160;)nbsp;|-)*/gm, \'\' ).trim(); } this.value=\'\';';
			}
			$attribs = 'class="inputbox" onchange="' . $onchange . '"';

			$html = '<table cellpadding="0" cellspacing="0"><tr><td style="padding: 0px;">' . "\n";
			if ( ! $multiple)
			{
				$val_name = $this->value;
				if ($this->value)
				{
					foreach ($modules as $item)
					{
						if ($item->id == $this->value)
						{
							$val_name = $item->title;
							if ($showtype)
							{
								$val_name .= ' [' . $item->module . ']';
							}
							$val_name .= ' [' . $this->value . ']';
							break;
						}
					}
				}
				$html .= '<input type="text" id="' . $this->id . '_text" value="' . $val_name . '" class="inputbox" ' . $size . ' disabled="disabled">';
				$html .= '<input type="hidden" name="' . $this->name . '" id="' . $this->id . '" value="' . $this->value . '">';
			}
			else
			{
				$html .= '<input type="text" name="' . $this->name . '" id="' . $this->id . '" value="' . $this->value . '" class="inputbox" ' . $size . '>';
			}
			$html .= '</td><td style="padding: 0px;"padding-left: 5px;>' . "\n";
			$html .= JHtml::_('select.genericlist', $options, '', $attribs, 'value', 'text', '', '');
			$html .= '</td></tr></table>' . "\n";
		}
		else
		{
			$attr = $size;
			$attr .= $multiple ? ' multiple="multiple"' : '';
			$attr .= ' class="input-xxlarge"';

			$html = JHtml::_('select.genericlist', $options, $this->name, trim($attr), 'value', 'text', $this->value, $this->id);
			$html = '<div class="input-maximize">' . $html . '</div>';
		}

		return RL_RegEx::replace('>\[\[\:(.*?)\:\]\]', ' style="\1">', $html);
	}
}
regularlabs/fields/filelist.php000064400000005371152177723700012662 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use RegularLabs\Library\RegEx as RL_RegEx;

jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.file');

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

require_once JPATH_LIBRARIES . '/joomla/form/fields/list.php';

class JFormFieldRL_FileList extends JFormFieldList
{
	public  $type   = 'FileList';
	private $params = null;

	protected function getInput()
	{
		return parent::getInput();
	}

	protected function getOptions()
	{
		$options = [];

		$path = $this->get('folder');

		if ( ! is_dir($path))
		{
			$path = JPATH_ROOT . '/' . $path;
		}

		// Prepend some default options based on field attributes.
		if ( ! $this->get('hidenone', 0))
		{
			$options[] = JHtml::_('select.option', '-1', JText::alt('JOPTION_DO_NOT_USE',
				RL_RegEx::replace('[^a-z0-9_\-]', '_', $this->fieldname)));
		}

		if ( ! $this->get('hidedefault', 0))
		{
			$options[] = JHtml::_('select.option', '', JText::alt('JOPTION_USE_DEFAULT',
				RL_RegEx::replace('[^a-z0-9_\-]', '_', $this->fieldname)));
		}

		// Get a list of files in the search path with the given filter.
		$files = JFolder::files($path, $this->get('filter'));

		// Build the options list from the list of files.
		if (is_array($files))
		{
			foreach ($files as $file)
			{
				// Check to see if the file is in the exclude mask.
				if ($this->get('exclude'))
				{
					if (RL_RegEx::match(chr(1) . $this->get('exclude') . chr(1), $file))
					{
						continue;
					}
				}

				// If the extension is to be stripped, do it.
				if ($this->get('stripext', 1))
				{
					$file = JFile::stripExt($file);
				}

				$label = $file;
				if ($this->get('language_prefix'))
				{
					$label = JText::_($this->get('language_prefix') . strtoupper($label));
				}

				$options[] = JHtml::_('select.option', $file, $label);
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}

	private function get($val, $default = '')
	{
		if (isset($this->element[$val]))
		{
			return (string) $this->element[$val] != '' ? (string) $this->element[$val] : $default;
		}

		if (isset($this->params[$val]))
		{
			return (string) $this->params[$val] != '' ? (string) $this->params[$val] : $default;
		}

		return $default;
	}
}
regularlabs/fields/regions.txt000064400000453605152177723700012554 0ustar00// Region codes taken from https://documentation.snoobi.com/region-codes

'--AF' => '','-AF' => 'Afghanistan',
'AF-01' => 'Afghanistan: Badakhshan',
'AF-02' => 'Afghanistan: Badghis',
'AF-03' => 'Afghanistan: Baghlan',
'AF-30' => 'Afghanistan: Balkh',
'AF-05' => 'Afghanistan: Bamian',
'AF-06' => 'Afghanistan: Farah',
'AF-07' => 'Afghanistan: Faryab',
'AF-08' => 'Afghanistan: Ghazni',
'AF-09' => 'Afghanistan: Ghowr',
'AF-10' => 'Afghanistan: Helmand',
'AF-11' => 'Afghanistan: Herat',
'AF-31' => 'Afghanistan: Jowzjan',
'AF-13' => 'Afghanistan: Kabol',
'AF-23' => 'Afghanistan: Kandahar',
'AF-14' => 'Afghanistan: Kapisa',
'AF-37' => 'Afghanistan: Khowst',
'AF-15' => 'Afghanistan: Konar',
'AF-34' => 'Afghanistan: Konar',
'AF-24' => 'Afghanistan: Kondoz',
'AF-16' => 'Afghanistan: Laghman',
'AF-35' => 'Afghanistan: Laghman',
'AF-17' => 'Afghanistan: Lowgar',
'AF-18' => 'Afghanistan: Nangarhar',
'AF-19' => 'Afghanistan: Nimruz',
'AF-38' => 'Afghanistan: Nurestan',
'AF-20' => 'Afghanistan: Oruzgan',
'AF-21' => 'Afghanistan: Paktia',
'AF-36' => 'Afghanistan: Paktia',
'AF-29' => 'Afghanistan: Paktika',
'AF-22' => 'Afghanistan: Parvan',
'AF-32' => 'Afghanistan: Samangan',
'AF-33' => 'Afghanistan: Sar-e Pol',
'AF-26' => 'Afghanistan: Takhar',
'AF-27' => 'Afghanistan: Vardak',
'AF-28' => 'Afghanistan: Zabol',

'--AL' => '','-AL' => 'Albania',
'AL-40' => 'Albania: Berat',
'AL-41' => 'Albania: Diber',
'AL-42' => 'Albania: Durres',
'AL-43' => 'Albania: Elbasan',
'AL-44' => 'Albania: Fier',
'AL-45' => 'Albania: Gjirokaster',
'AL-46' => 'Albania: Korce',
'AL-47' => 'Albania: Kukes',
'AL-48' => 'Albania: Lezhe',
'AL-49' => 'Albania: Shkoder',
'AL-50' => 'Albania: Tirane',
'AL-51' => 'Albania: Vlore',

'--DZ' => '','-DZ' => 'Algeria',
'DZ-34' => 'Algeria: Adrar',
'DZ-35' => 'Algeria: Ain Defla',
'DZ-36' => 'Algeria: Ain Temouchent',
'DZ-01' => 'Algeria: Alger',
'DZ-37' => 'Algeria: Annaba',
'DZ-03' => 'Algeria: Batna',
'DZ-38' => 'Algeria: Bechar',
'DZ-18' => 'Algeria: Bejaia',
'DZ-19' => 'Algeria: Biskra',
'DZ-20' => 'Algeria: Blida',
'DZ-39' => 'Algeria: Bordj Bou Arreridj',
'DZ-21' => 'Algeria: Bouira',
'DZ-40' => 'Algeria: Boumerdes',
'DZ-41' => 'Algeria: Chlef',
'DZ-04' => 'Algeria: Constantine',
'DZ-22' => 'Algeria: Djelfa',
'DZ-42' => 'Algeria: El Bayadh',
'DZ-43' => 'Algeria: El Oued',
'DZ-44' => 'Algeria: El Tarf',
'DZ-45' => 'Algeria: Ghardaia',
'DZ-23' => 'Algeria: Guelma',
'DZ-46' => 'Algeria: Illizi',
'DZ-24' => 'Algeria: Jijel',
'DZ-47' => 'Algeria: Khenchela',
'DZ-25' => 'Algeria: Laghouat',
'DZ-26' => 'Algeria: Mascara',
'DZ-06' => 'Algeria: Medea',
'DZ-48' => 'Algeria: Mila',
'DZ-07' => 'Algeria: Mostaganem',
'DZ-27' => 'Algeria: M\'sila',
'DZ-49' => 'Algeria: Naama',
'DZ-09' => 'Algeria: Oran',
'DZ-50' => 'Algeria: Ouargla',
'DZ-29' => 'Algeria: Oum el Bouaghi',
'DZ-51' => 'Algeria: Relizane',
'DZ-10' => 'Algeria: Saida',
'DZ-12' => 'Algeria: Setif',
'DZ-30' => 'Algeria: Sidi Bel Abbes',
'DZ-31' => 'Algeria: Skikda',
'DZ-52' => 'Algeria: Souk Ahras',
'DZ-53' => 'Algeria: Tamanghasset',
'DZ-33' => 'Algeria: Tebessa',
'DZ-13' => 'Algeria: Tiaret',
'DZ-54' => 'Algeria: Tindouf',
'DZ-55' => 'Algeria: Tipaza',
'DZ-56' => 'Algeria: Tissemsilt',
'DZ-14' => 'Algeria: Tizi Ouzou',
'DZ-15' => 'Algeria: Tlemcen',

'--AD' => '','-AD' => 'Andorra',
'AD-07' => 'Andorra: Andorra la Vella',
'AD-02' => 'Andorra: Canillo',
'AD-03' => 'Andorra: Encamp',
'AD-08' => 'Andorra: Escaldes-Engordany',
'AD-04' => 'Andorra: La Massana',
'AD-05' => 'Andorra: Ordino',
'AD-06' => 'Andorra: Sant Julia de Loria',

'--AO' => '','-AO' => 'Angola',
'AO-19' => 'Angola: Bengo',
'AO-01' => 'Angola: Benguela',
'AO-02' => 'Angola: Bie',
'AO-03' => 'Angola: Cabinda',
'AO-04' => 'Angola: Cuando Cubango',
'AO-05' => 'Angola: Cuanza Norte',
'AO-06' => 'Angola: Cuanza Sul',
'AO-07' => 'Angola: Cunene',
'AO-08' => 'Angola: Huambo',
'AO-09' => 'Angola: Huila',
'AO-20' => 'Angola: Luanda',
'AO-17' => 'Angola: Lunda Norte',
'AO-18' => 'Angola: Lunda Sul',
'AO-12' => 'Angola: Malanje',
'AO-14' => 'Angola: Moxico',
'AO-15' => 'Angola: Uige',
'AO-16' => 'Angola: Zaire',

'--AG' => '','-AG' => 'Antigua and Barbuda',
'AG-01' => 'Antigua and Barbuda: Barbuda',
'AG-03' => 'Antigua and Barbuda: Saint George',
'AG-04' => 'Antigua and Barbuda: Saint John',
'AG-05' => 'Antigua and Barbuda: Saint Mary',
'AG-06' => 'Antigua and Barbuda: Saint Paul',
'AG-07' => 'Antigua and Barbuda: Saint Peter',
'AG-08' => 'Antigua and Barbuda: Saint Philip',

'--AR' => '','-AR' => 'Argentina',
'AR-01' => 'Argentina: Buenos Aires',
'AR-02' => 'Argentina: Catamarca',
'AR-03' => 'Argentina: Chaco',
'AR-04' => 'Argentina: Chubut',
'AR-05' => 'Argentina: Cordoba',
'AR-06' => 'Argentina: Corrientes',
'AR-07' => 'Argentina: Distrito Federal',
'AR-08' => 'Argentina: Entre Rios',
'AR-09' => 'Argentina: Formosa',
'AR-10' => 'Argentina: Jujuy',
'AR-11' => 'Argentina: La Pampa',
'AR-12' => 'Argentina: La Rioja',
'AR-13' => 'Argentina: Mendoza',
'AR-14' => 'Argentina: Misiones',
'AR-15' => 'Argentina: Neuquen',
'AR-16' => 'Argentina: Rio Negro',
'AR-17' => 'Argentina: Salta',
'AR-18' => 'Argentina: San Juan',
'AR-19' => 'Argentina: San Luis',
'AR-20' => 'Argentina: Santa Cruz',
'AR-21' => 'Argentina: Santa Fe',
'AR-22' => 'Argentina: Santiago del Estero',
'AR-23' => 'Argentina: Tierra del Fuego',
'AR-24' => 'Argentina: Tucuman',

'--AM' => '','-AM' => 'Armenia',
'AM-01' => 'Armenia: Aragatsotn',
'AM-02' => 'Armenia: Ararat',
'AM-03' => 'Armenia: Armavir',
'AM-04' => 'Armenia: Geghark\'unik\'',
'AM-05' => 'Armenia: Kotayk\'',
'AM-06' => 'Armenia: Lorri',
'AM-07' => 'Armenia: Shirak',
'AM-08' => 'Armenia: Syunik\'',
'AM-09' => 'Armenia: Tavush',
'AM-10' => 'Armenia: Vayots\' Dzor',
'AM-11' => 'Armenia: Yerevan',

'--AU' => '','-AU' => 'Australia',
'AU-01' => 'Australia: Australian Capital Territory',
'AU-02' => 'Australia: New South Wales',
'AU-03' => 'Australia: Northern Territory',
'AU-04' => 'Australia: Queensland',
'AU-05' => 'Australia: South Australia',
'AU-06' => 'Australia: Tasmania',
'AU-07' => 'Australia: Victoria',
'AU-08' => 'Australia: Western Australia',

'--AT' => '','-AT' => 'Austria',
'AT-01' => 'Austria: Burgenland',
'AT-02' => 'Austria: Karnten',
'AT-03' => 'Austria: Niederosterreich',
'AT-04' => 'Austria: Oberosterreich',
'AT-05' => 'Austria: Salzburg',
'AT-06' => 'Austria: Steiermark',
'AT-07' => 'Austria: Tirol',
'AT-08' => 'Austria: Vorarlberg',
'AT-09' => 'Austria: Wien',

'--AZ' => '','-AZ' => 'Azerbaijan',
'AZ-01' => 'Azerbaijan: Abseron',
'AZ-02' => 'Azerbaijan: Agcabadi',
'AZ-03' => 'Azerbaijan: Agdam',
'AZ-04' => 'Azerbaijan: Agdas',
'AZ-05' => 'Azerbaijan: Agstafa',
'AZ-06' => 'Azerbaijan: Agsu',
'AZ-07' => 'Azerbaijan: Ali Bayramli',
'AZ-08' => 'Azerbaijan: Astara',
'AZ-09' => 'Azerbaijan: Baki',
'AZ-10' => 'Azerbaijan: Balakan',
'AZ-11' => 'Azerbaijan: Barda',
'AZ-12' => 'Azerbaijan: Beylaqan',
'AZ-13' => 'Azerbaijan: Bilasuvar',
'AZ-14' => 'Azerbaijan: Cabrayil',
'AZ-15' => 'Azerbaijan: Calilabad',
'AZ-16' => 'Azerbaijan: Daskasan',
'AZ-17' => 'Azerbaijan: Davaci',
'AZ-18' => 'Azerbaijan: Fuzuli',
'AZ-19' => 'Azerbaijan: Gadabay',
'AZ-20' => 'Azerbaijan: Ganca',
'AZ-21' => 'Azerbaijan: Goranboy',
'AZ-22' => 'Azerbaijan: Goycay',
'AZ-23' => 'Azerbaijan: Haciqabul',
'AZ-24' => 'Azerbaijan: Imisli',
'AZ-25' => 'Azerbaijan: Ismayilli',
'AZ-26' => 'Azerbaijan: Kalbacar',
'AZ-27' => 'Azerbaijan: Kurdamir',
'AZ-28' => 'Azerbaijan: Lacin',
'AZ-29' => 'Azerbaijan: Lankaran',
'AZ-30' => 'Azerbaijan: Lankaran',
'AZ-31' => 'Azerbaijan: Lerik',
'AZ-32' => 'Azerbaijan: Masalli',
'AZ-33' => 'Azerbaijan: Mingacevir',
'AZ-34' => 'Azerbaijan: Naftalan',
'AZ-35' => 'Azerbaijan: Naxcivan',
'AZ-36' => 'Azerbaijan: Neftcala',
'AZ-37' => 'Azerbaijan: Oguz',
'AZ-38' => 'Azerbaijan: Qabala',
'AZ-39' => 'Azerbaijan: Qax',
'AZ-40' => 'Azerbaijan: Qazax',
'AZ-41' => 'Azerbaijan: Qobustan',
'AZ-42' => 'Azerbaijan: Quba',
'AZ-43' => 'Azerbaijan: Qubadli',
'AZ-44' => 'Azerbaijan: Qusar',
'AZ-45' => 'Azerbaijan: Saatli',
'AZ-46' => 'Azerbaijan: Sabirabad',
'AZ-47' => 'Azerbaijan: Saki',
'AZ-48' => 'Azerbaijan: Saki',
'AZ-49' => 'Azerbaijan: Salyan',
'AZ-50' => 'Azerbaijan: Samaxi',
'AZ-51' => 'Azerbaijan: Samkir',
'AZ-52' => 'Azerbaijan: Samux',
'AZ-53' => 'Azerbaijan: Siyazan',
'AZ-54' => 'Azerbaijan: Sumqayit',
'AZ-55' => 'Azerbaijan: Susa',
'AZ-56' => 'Azerbaijan: Susa',
'AZ-57' => 'Azerbaijan: Tartar',
'AZ-58' => 'Azerbaijan: Tovuz',
'AZ-59' => 'Azerbaijan: Ucar',
'AZ-60' => 'Azerbaijan: Xacmaz',
'AZ-61' => 'Azerbaijan: Xankandi',
'AZ-62' => 'Azerbaijan: Xanlar',
'AZ-63' => 'Azerbaijan: Xizi',
'AZ-64' => 'Azerbaijan: Xocali',
'AZ-65' => 'Azerbaijan: Xocavand',
'AZ-66' => 'Azerbaijan: Yardimli',
'AZ-67' => 'Azerbaijan: Yevlax',
'AZ-68' => 'Azerbaijan: Yevlax',
'AZ-69' => 'Azerbaijan: Zangilan',
'AZ-70' => 'Azerbaijan: Zaqatala',
'AZ-71' => 'Azerbaijan: Zardab',

'--BS' => '','-BS' => 'Bahamas',
'BS-24' => 'Bahamas: Acklins and Crooked Islands',
'BS-05' => 'Bahamas: Bimini',
'BS-06' => 'Bahamas: Cat Island',
'BS-10' => 'Bahamas: Exuma',
'BS-25' => 'Bahamas: Freeport',
'BS-26' => 'Bahamas: Fresh Creek',
'BS-27' => 'Bahamas: Governor\'s Harbour',
'BS-28' => 'Bahamas: Green Turtle Cay',
'BS-22' => 'Bahamas: Harbour Island',
'BS-29' => 'Bahamas: High Rock',
'BS-13' => 'Bahamas: Inagua',
'BS-30' => 'Bahamas: Kemps Bay',
'BS-15' => 'Bahamas: Long Island',
'BS-31' => 'Bahamas: Marsh Harbour',
'BS-16' => 'Bahamas: Mayaguana',
'BS-23' => 'Bahamas: New Providence',
'BS-32' => 'Bahamas: Nichollstown and Berry Islands',
'BS-18' => 'Bahamas: Ragged Island',
'BS-33' => 'Bahamas: Rock Sound',
'BS-35' => 'Bahamas: San Salvador and Rum Cay',
'BS-34' => 'Bahamas: Sandy Point',

'--BH' => '','-BH' => 'Bahrain',
'BH-01' => 'Bahrain: Al Hadd',
'BH-02' => 'Bahrain: Al Manamah',
'BH-08' => 'Bahrain: Al Mintaqah al Gharbiyah',
'BH-11' => 'Bahrain: Al Mintaqah al Wusta',
'BH-10' => 'Bahrain: Al Mintaqah ash Shamaliyah',
'BH-03' => 'Bahrain: Al Muharraq',
'BH-13' => 'Bahrain: Ar Rifa',
'BH-05' => 'Bahrain: Jidd Hafs',
'BH-14' => 'Bahrain: Madinat Hamad',
'BH-12' => 'Bahrain: Madinat',
'BH-09' => 'Bahrain: Mintaqat Juzur Hawar',
'BH-06' => 'Bahrain: Sitrah',

'--BD' => '','-BD' => 'Bangladesh',
'BD-22' => 'Bangladesh: Bagerhat',
'BD-04' => 'Bangladesh: Bandarban',
'BD-25' => 'Bangladesh: Barguna',
'BD-01' => 'Bangladesh: Barisal',
'BD-23' => 'Bangladesh: Bhola',
'BD-24' => 'Bangladesh: Bogra',
'BD-26' => 'Bangladesh: Brahmanbaria',
'BD-27' => 'Bangladesh: Chandpur',
'BD-28' => 'Bangladesh: Chapai Nawabganj',
'BD-29' => 'Bangladesh: Chattagram',
'BD-30' => 'Bangladesh: Chuadanga',
'BD-05' => 'Bangladesh: Comilla',
'BD-31' => 'Bangladesh: Cox\'s Bazar',
'BD-32' => 'Bangladesh: Dhaka',
'BD-33' => 'Bangladesh: Dinajpur',
'BD-34' => 'Bangladesh: Faridpur',
'BD-35' => 'Bangladesh: Feni',
'BD-36' => 'Bangladesh: Gaibandha',
'BD-37' => 'Bangladesh: Gazipur',
'BD-38' => 'Bangladesh: Gopalganj',
'BD-39' => 'Bangladesh: Habiganj',
'BD-40' => 'Bangladesh: Jaipurhat',
'BD-41' => 'Bangladesh: Jamalpur',
'BD-42' => 'Bangladesh: Jessore',
'BD-43' => 'Bangladesh: Jhalakati',
'BD-44' => 'Bangladesh: Jhenaidah',
'BD-45' => 'Bangladesh: Khagrachari',
'BD-46' => 'Bangladesh: Khulna',
'BD-47' => 'Bangladesh: Kishorganj',
'BD-48' => 'Bangladesh: Kurigram',
'BD-49' => 'Bangladesh: Kushtia',
'BD-50' => 'Bangladesh: Laksmipur',
'BD-51' => 'Bangladesh: Lalmonirhat',
'BD-52' => 'Bangladesh: Madaripur',
'BD-53' => 'Bangladesh: Magura',
'BD-54' => 'Bangladesh: Manikganj',
'BD-55' => 'Bangladesh: Meherpur',
'BD-56' => 'Bangladesh: Moulavibazar',
'BD-57' => 'Bangladesh: Munshiganj',
'BD-12' => 'Bangladesh: Mymensingh',
'BD-58' => 'Bangladesh: Naogaon',
'BD-59' => 'Bangladesh: Narail',
'BD-60' => 'Bangladesh: Narayanganj',
'BD-61' => 'Bangladesh: Narsingdi',
'BD-62' => 'Bangladesh: Nator',
'BD-63' => 'Bangladesh: Netrakona',
'BD-64' => 'Bangladesh: Nilphamari',
'BD-13' => 'Bangladesh: Noakhali',
'BD-65' => 'Bangladesh: Pabna',
'BD-66' => 'Bangladesh: Panchagar',
'BD-67' => 'Bangladesh: Parbattya Chattagram',
'BD-15' => 'Bangladesh: Patuakhali',
'BD-68' => 'Bangladesh: Pirojpur',
'BD-69' => 'Bangladesh: Rajbari',
'BD-70' => 'Bangladesh: Rajshahi',
'BD-71' => 'Bangladesh: Rangpur',
'BD-72' => 'Bangladesh: Satkhira',
'BD-73' => 'Bangladesh: Shariyatpur',
'BD-74' => 'Bangladesh: Sherpur',
'BD-75' => 'Bangladesh: Sirajganj',
'BD-76' => 'Bangladesh: Sunamganj',
'BD-77' => 'Bangladesh: Sylhet',
'BD-78' => 'Bangladesh: Tangail',
'BD-79' => 'Bangladesh: Thakurgaon',

'--BB' => '','-BB' => 'Barbados',
'BB-01' => 'Barbados: Christ Church',
'BB-02' => 'Barbados: Saint Andrew',
'BB-03' => 'Barbados: Saint George',
'BB-04' => 'Barbados: Saint James',
'BB-05' => 'Barbados: Saint John',
'BB-06' => 'Barbados: Saint Joseph',
'BB-07' => 'Barbados: Saint Lucy',
'BB-08' => 'Barbados: Saint Michael',
'BB-09' => 'Barbados: Saint Peter',
'BB-10' => 'Barbados: Saint Philip',
'BB-11' => 'Barbados: Saint Thomas',

'--BY' => '','-BY' => 'Belarus',
'BY-01' => 'Belarus: Brestskaya Voblasts\'',
'BY-02' => 'Belarus: Homyel\'skaya Voblasts\'',
'BY-03' => 'Belarus: Hrodzyenskaya Voblasts\'',
'BY-06' => 'Belarus: Mahilyowskaya Voblasts\'',
'BY-04' => 'Belarus: Minsk',
'BY-05' => 'Belarus: Minskaya Voblasts\'',
'BY-07' => 'Belarus: Vitsyebskaya Voblasts\'',

'--BE' => '','-BE' => 'Belgium',
'BE-01' => 'Belgium: Antwerpen',
'BE-10' => 'Belgium: Brabant Wallon',
'BE-02' => 'Belgium: Brabant',
'BE-11' => 'Belgium: Brussels Hoofdstedelijk Gewest',
'BE-03' => 'Belgium: Hainaut',
'BE-04' => 'Belgium: Liege',
'BE-05' => 'Belgium: Limburg',
'BE-06' => 'Belgium: Luxembourg',
'BE-07' => 'Belgium: Namur',
'BE-08' => 'Belgium: Oost-Vlaanderen',
'BE-12' => 'Belgium: Vlaams-Brabant',
'BE-09' => 'Belgium: West-Vlaanderen',

'--BZ' => '','-BZ' => 'Belize',
'BZ-01' => 'Belize: Belize',
'BZ-02' => 'Belize: Cayo',
'BZ-03' => 'Belize: Corozal',
'BZ-04' => 'Belize: Orange Walk',
'BZ-05' => 'Belize: Stann Creek',
'BZ-06' => 'Belize: Toledo',

'--BJ' => '','-BJ' => 'Benin',
'BJ-01' => 'Benin: Atakora',
'BJ-02' => 'Benin: Atlantique',
'BJ-03' => 'Benin: Borgou',
'BJ-04' => 'Benin: Mono',
'BJ-05' => 'Benin: Oueme',
'BJ-06' => 'Benin: Zou',

'--BM' => '','-BM' => 'Bermuda',
'BM-01' => 'Bermuda: Devonshire',
'BM-02' => 'Bermuda: Hamilton',
'BM-03' => 'Bermuda: Hamilton',
'BM-04' => 'Bermuda: Paget',
'BM-05' => 'Bermuda: Pembroke',
'BM-06' => 'Bermuda: Saint George',
'BM-07' => 'Bermuda: Saint George\'s',
'BM-08' => 'Bermuda: Sandys',
'BM-09' => 'Bermuda: Smiths',
'BM-10' => 'Bermuda: Southampton',
'BM-11' => 'Bermuda: Warwick',

'--BT' => '','-BT' => 'Bhutan',
'BT-05' => 'Bhutan: Bumthang',
'BT-06' => 'Bhutan: Chhukha',
'BT-07' => 'Bhutan: Chirang',
'BT-08' => 'Bhutan: Daga',
'BT-09' => 'Bhutan: Geylegphug',
'BT-10' => 'Bhutan: Ha',
'BT-11' => 'Bhutan: Lhuntshi',
'BT-12' => 'Bhutan: Mongar',
'BT-13' => 'Bhutan: Paro',
'BT-14' => 'Bhutan: Pemagatsel',
'BT-15' => 'Bhutan: Punakha',
'BT-16' => 'Bhutan: Samchi',
'BT-17' => 'Bhutan: Samdrup',
'BT-18' => 'Bhutan: Shemgang',
'BT-19' => 'Bhutan: Tashigang',
'BT-20' => 'Bhutan: Thimphu',
'BT-21' => 'Bhutan: Tongsa',
'BT-22' => 'Bhutan: Wangdi Phodrang',

'--BO' => '','-BO' => 'Bolivia',
'BO-01' => 'Bolivia: Chuquisaca',
'BO-02' => 'Bolivia: Cochabamba',
'BO-03' => 'Bolivia: El Beni',
'BO-04' => 'Bolivia: La Paz',
'BO-05' => 'Bolivia: Oruro',
'BO-06' => 'Bolivia: Pando',
'BO-07' => 'Bolivia: Potosi',
'BO-08' => 'Bolivia: Santa Cruz',
'BO-09' => 'Bolivia: Tarija',

'--BA' => '','-BA' => 'Bosnia and Herzegovina',
'BA-01' => 'Bosnia and Herzegovina: Federation of Bosnia and Herzegovina',
'BA-02' => 'Bosnia and Herzegovina: Republika Srpska',

'--BW' => '','-BW' => 'Botswana',
'BW-01' => 'Botswana: Central',
'BW-02' => 'Botswana: Chobe',
'BW-03' => 'Botswana: Ghanzi',
'BW-04' => 'Botswana: Kgalagadi',
'BW-05' => 'Botswana: Kgatleng',
'BW-06' => 'Botswana: Kweneng',
'BW-07' => 'Botswana: Ngamiland',
'BW-08' => 'Botswana: North-East',
'BW-09' => 'Botswana: South-East',
'BW-10' => 'Botswana: Southern',

'--BR' => '','-BR' => 'Brazil',
'BR-01' => 'Brazil: Acre',
'BR-02' => 'Brazil: Alagoas',
'BR-03' => 'Brazil: Amapa',
'BR-04' => 'Brazil: Amazonas',
'BR-05' => 'Brazil: Bahia',
'BR-06' => 'Brazil: Ceara',
'BR-07' => 'Brazil: Distrito Federal',
'BR-08' => 'Brazil: Espirito Santo',
'BR-29' => 'Brazil: Goias',
'BR-13' => 'Brazil: Maranhao',
'BR-11' => 'Brazil: Mato Grosso do Sul',
'BR-14' => 'Brazil: Mato Grosso',
'BR-15' => 'Brazil: Minas Gerais',
'BR-16' => 'Brazil: Para',
'BR-17' => 'Brazil: Paraiba',
'BR-18' => 'Brazil: Parana',
'BR-30' => 'Brazil: Pernambuco',
'BR-20' => 'Brazil: Piaui',
'BR-21' => 'Brazil: Rio de Janeiro',
'BR-22' => 'Brazil: Rio Grande do Norte',
'BR-23' => 'Brazil: Rio Grande do Sul',
'BR-24' => 'Brazil: Rondonia',
'BR-25' => 'Brazil: Roraima',
'BR-26' => 'Brazil: Santa Catarina',
'BR-27' => 'Brazil: Sao Paulo',
'BR-28' => 'Brazil: Sergipe',
'BR-31' => 'Brazil: Tocantins',

'--BN' => '','-BN' => 'Brunei Darussalam',
'BN-07' => 'Brunei Darussalam: Alibori',
'BN-08' => 'Brunei Darussalam: Belait',
'BN-09' => 'Brunei Darussalam: Brunei and Muara',
'BN-11' => 'Brunei Darussalam: Collines',
'BN-13' => 'Brunei Darussalam: Donga',
'BN-12' => 'Brunei Darussalam: Kouffo',
'BN-14' => 'Brunei Darussalam: Littoral',
'BN-16' => 'Brunei Darussalam: Oueme',
'BN-17' => 'Brunei Darussalam: Plateau',
'BN-10' => 'Brunei Darussalam: Temburong',
'BN-15' => 'Brunei Darussalam: Tutong',
'BN-18' => 'Brunei Darussalam: Zou',

'--BG' => '','-BG' => 'Bulgaria',
'BG-38' => 'Bulgaria: Blagoevgrad',
'BG-39' => 'Bulgaria: Burgas',
'BG-40' => 'Bulgaria: Dobrich',
'BG-41' => 'Bulgaria: Gabrovo',
'BG-42' => 'Bulgaria: Grad Sofiya',
'BG-43' => 'Bulgaria: Khaskovo',
'BG-44' => 'Bulgaria: Kurdzhali',
'BG-45' => 'Bulgaria: Kyustendil',
'BG-46' => 'Bulgaria: Lovech',
'BG-33' => 'Bulgaria: Mikhaylovgrad',
'BG-47' => 'Bulgaria: Montana',
'BG-48' => 'Bulgaria: Pazardzhik',
'BG-49' => 'Bulgaria: Pernik',
'BG-50' => 'Bulgaria: Pleven',
'BG-51' => 'Bulgaria: Plovdiv',
'BG-52' => 'Bulgaria: Razgrad',
'BG-53' => 'Bulgaria: Ruse',
'BG-54' => 'Bulgaria: Shumen',
'BG-55' => 'Bulgaria: Silistra',
'BG-56' => 'Bulgaria: Sliven',
'BG-57' => 'Bulgaria: Smolyan',
'BG-58' => 'Bulgaria: Sofiya',
'BG-59' => 'Bulgaria: Stara Zagora',
'BG-60' => 'Bulgaria: Turgovishte',
'BG-61' => 'Bulgaria: Varna',
'BG-62' => 'Bulgaria: Veliko Turnovo',
'BG-63' => 'Bulgaria: Vidin',
'BG-64' => 'Bulgaria: Vratsa',
'BG-65' => 'Bulgaria: Yambol',

'--BF' => '','-BF' => 'Burkina Faso',
'BF-45' => 'Burkina Faso: Bale',
'BF-15' => 'Burkina Faso: Bam',
'BF-46' => 'Burkina Faso: Banwa',
'BF-47' => 'Burkina Faso: Bazega',
'BF-48' => 'Burkina Faso: Bougouriba',
'BF-49' => 'Burkina Faso: Boulgou',
'BF-19' => 'Burkina Faso: Boulkiemde',
'BF-20' => 'Burkina Faso: Ganzourgou',
'BF-21' => 'Burkina Faso: Gnagna',
'BF-50' => 'Burkina Faso: Gourma',
'BF-51' => 'Burkina Faso: Houet',
'BF-52' => 'Burkina Faso: Ioba',
'BF-53' => 'Burkina Faso: Kadiogo',
'BF-54' => 'Burkina Faso: Kenedougou',
'BF-55' => 'Burkina Faso: Komoe',
'BF-56' => 'Burkina Faso: Komondjari',
'BF-57' => 'Burkina Faso: Kompienga',
'BF-58' => 'Burkina Faso: Kossi',
'BF-59' => 'Burkina Faso: Koulpelogo',
'BF-28' => 'Burkina Faso: Kouritenga',
'BF-60' => 'Burkina Faso: Kourweogo',
'BF-61' => 'Burkina Faso: Leraba',
'BF-62' => 'Burkina Faso: Loroum',
'BF-63' => 'Burkina Faso: Mouhoun',
'BF-64' => 'Burkina Faso: Namentenga',
'BF-65' => 'Burkina Faso: Naouri',
'BF-66' => 'Burkina Faso: Nayala',
'BF-67' => 'Burkina Faso: Noumbiel',
'BF-68' => 'Burkina Faso: Oubritenga',
'BF-33' => 'Burkina Faso: Oudalan',
'BF-34' => 'Burkina Faso: Passore',
'BF-69' => 'Burkina Faso: Poni',
'BF-36' => 'Burkina Faso: Sanguie',
'BF-70' => 'Burkina Faso: Sanmatenga',
'BF-71' => 'Burkina Faso: Seno',
'BF-72' => 'Burkina Faso: Sissili',
'BF-40' => 'Burkina Faso: Soum',
'BF-73' => 'Burkina Faso: Sourou',
'BF-42' => 'Burkina Faso: Tapoa',
'BF-74' => 'Burkina Faso: Tuy',
'BF-75' => 'Burkina Faso: Yagha',
'BF-76' => 'Burkina Faso: Yatenga',
'BF-77' => 'Burkina Faso: Ziro',
'BF-78' => 'Burkina Faso: Zondoma',
'BF-44' => 'Burkina Faso: Zoundweogo',

'--BI' => '','-BI' => 'Burundi',
'BI-09' => 'Burundi: Bubanza',
'BI-02' => 'Burundi: Bujumbura',
'BI-10' => 'Burundi: Bururi',
'BI-11' => 'Burundi: Cankuzo',
'BI-12' => 'Burundi: Cibitoke',
'BI-13' => 'Burundi: Gitega',
'BI-14' => 'Burundi: Karuzi',
'BI-15' => 'Burundi: Kayanza',
'BI-16' => 'Burundi: Kirundo',
'BI-17' => 'Burundi: Makamba',
'BI-22' => 'Burundi: Muramvya',
'BI-18' => 'Burundi: Muyinga',
'BI-23' => 'Burundi: Mwaro',
'BI-19' => 'Burundi: Ngozi',
'BI-20' => 'Burundi: Rutana',
'BI-21' => 'Burundi: Ruyigi',

'--KH' => '','-KH' => 'Cambodia',
'KH-29' => 'Cambodia: Batdambang',
'KH-02' => 'Cambodia: Kampong Cham',
'KH-03' => 'Cambodia: Kampong Chhnang',
'KH-04' => 'Cambodia: Kampong Spoe',
'KH-05' => 'Cambodia: Kampong Thum',
'KH-06' => 'Cambodia: Kampot',
'KH-07' => 'Cambodia: Kandal',
'KH-08' => 'Cambodia: Kaoh Kong',
'KH-09' => 'Cambodia: Kracheh',
'KH-10' => 'Cambodia: Mondol Kiri',
'KH-30' => 'Cambodia: Pailin',
'KH-11' => 'Cambodia: Phnum Penh',
'KH-12' => 'Cambodia: Pouthisat',
'KH-13' => 'Cambodia: Preah Vihear',
'KH-14' => 'Cambodia: Prey Veng',
'KH-15' => 'Cambodia: Rotanokiri',
'KH-16' => 'Cambodia: Siemreab-Otdar Meanchey',
'KH-17' => 'Cambodia: Stoeng Treng',
'KH-18' => 'Cambodia: Svay Rieng',
'KH-19' => 'Cambodia: Takev',

'--CM' => '','-CM' => 'Cameroon',
'CM-10' => 'Cameroon: Adamaoua',
'CM-11' => 'Cameroon: Centre',
'CM-04' => 'Cameroon: Est',
'CM-12' => 'Cameroon: Extreme-Nord',
'CM-05' => 'Cameroon: Littoral',
'CM-13' => 'Cameroon: Nord',
'CM-07' => 'Cameroon: Nord-Ouest',
'CM-08' => 'Cameroon: Ouest',
'CM-14' => 'Cameroon: Sud',
'CM-09' => 'Cameroon: Sud-Ouest',

'--CA' => '','-CA' => 'Canada',
'CA-AB' => 'Canada: Alberta',
'CA-BC' => 'Canada: British Columbia',
'CA-MB' => 'Canada: Manitoba',
'CA-NB' => 'Canada: New Brunswick',
'CA-NL' => 'Canada: Newfoundland',
'CA-NT' => 'Canada: Northwest Territories',
'CA-NS' => 'Canada: Nova Scotia',
'CA-NU' => 'Canada: Nunavut',
'CA-ON' => 'Canada: Ontario',
'CA-PE' => 'Canada: Prince Edward Island',
'CA-QC' => 'Canada: Quebec',
'CA-SK' => 'Canada: Saskatchewan',
'CA-YT' => 'Canada: Yukon Territory',

'--CV' => '','-CV' => 'Cape Verde',
'CV-01' => 'Cape Verde: Boa Vista',
'CV-02' => 'Cape Verde: Brava',
'CV-04' => 'Cape Verde: Maio',
'CV-13' => 'Cape Verde: Mosteiros',
'CV-05' => 'Cape Verde: Paul',
'CV-14' => 'Cape Verde: Praia',
'CV-07' => 'Cape Verde: Ribeira Grande',
'CV-08' => 'Cape Verde: Sal',
'CV-15' => 'Cape Verde: Santa Catarina',
'CV-16' => 'Cape Verde: Santa Cruz',
'CV-17' => 'Cape Verde: Sao Domingos',
'CV-18' => 'Cape Verde: Sao Filipe',
'CV-19' => 'Cape Verde: Sao Miguel',
'CV-10' => 'Cape Verde: Sao Nicolau',
'CV-11' => 'Cape Verde: Sao Vicente',
'CV-20' => 'Cape Verde: Tarrafal',

'--KY' => '','-KY' => 'Cayman Islands',
'KY-01' => 'Cayman Islands: Creek',
'KY-02' => 'Cayman Islands: Eastern',
'KY-03' => 'Cayman Islands: Midland',
'KY-04' => 'Cayman Islands: South Town',
'KY-05' => 'Cayman Islands: Spot Bay',
'KY-06' => 'Cayman Islands: Stake Bay',
'KY-07' => 'Cayman Islands: West End',
'KY-08' => 'Cayman Islands: Western',

'--CF' => '','-CF' => 'Central African Republic',
'CF-01' => 'Central African Republic: Bamingui-Bangoran',
'CF-18' => 'Central African Republic: Bangui',
'CF-02' => 'Central African Republic: Basse-Kotto',
'CF-03' => 'Central African Republic: Haute-Kotto',
'CF-05' => 'Central African Republic: Haut-Mbomou',
'CF-06' => 'Central African Republic: Kemo',
'CF-07' => 'Central African Republic: Lobaye',
'CF-04' => 'Central African Republic: Mambere-Kadei',
'CF-08' => 'Central African Republic: Mbomou',
'CF-15' => 'Central African Republic: Nana-Grebizi',
'CF-09' => 'Central African Republic: Nana-Mambere',
'CF-17' => 'Central African Republic: Ombella-Mpoko',
'CF-11' => 'Central African Republic: Ouaka',
'CF-12' => 'Central African Republic: Ouham',
'CF-13' => 'Central African Republic: Ouham-Pende',
'CF-16' => 'Central African Republic: Sangha-Mbaere',
'CF-14' => 'Central African Republic: Vakaga',

'--TD' => '','-TD' => 'Chad',
'TD-01' => 'Chad: Batha',
'TD-02' => 'Chad: Biltine',
'TD-03' => 'Chad: Borkou-Ennedi-Tibesti',
'TD-04' => 'Chad: Chari-Baguirmi',
'TD-05' => 'Chad: Guera',
'TD-06' => 'Chad: Kanem',
'TD-07' => 'Chad: Lac',
'TD-08' => 'Chad: Logone Occidental',
'TD-09' => 'Chad: Logone Oriental',
'TD-10' => 'Chad: Mayo-Kebbi',
'TD-11' => 'Chad: Moyen-Chari',
'TD-12' => 'Chad: Ouaddai',
'TD-13' => 'Chad: Salamat',
'TD-14' => 'Chad: Tandjile',

'--CL' => '','-CL' => 'Chile',
'CL-02' => 'Chile: Aisen del General Carlos Ibanez del Campo',
'CL-03' => 'Chile: Antofagasta',
'CL-04' => 'Chile: Araucania',
'CL-05' => 'Chile: Atacama',
'CL-06' => 'Chile: Bio-Bio',
'CL-07' => 'Chile: Coquimbo',
'CL-08' => 'Chile: Libertador General Bernardo O\'Higgins',
'CL-09' => 'Chile: Los Lagos',
'CL-10' => 'Chile: Magallanes y de la Antartica Chilena',
'CL-11' => 'Chile: Maule',
'CL-12' => 'Chile: Region Metropolitana',
'CL-13' => 'Chile: Tarapaca',
'CL-01' => 'Chile: Valparaiso',

'--CN' => '','-CN' => 'China',
'CN-01' => 'China: Anhui',
'CN-22' => 'China: Beijing',
'CN-33' => 'China: Chongqing',
'CN-07' => 'China: Fujian',
'CN-15' => 'China: Gansu',
'CN-30' => 'China: Guangdong',
'CN-16' => 'China: Guangxi',
'CN-18' => 'China: Guizhou',
'CN-31' => 'China: Hainan',
'CN-10' => 'China: Hebei',
'CN-08' => 'China: Heilongjiang',
'CN-09' => 'China: Henan',
'CN-12' => 'China: Hubei',
'CN-11' => 'China: Hunan',
'CN-04' => 'China: Jiangsu',
'CN-03' => 'China: Jiangxi',
'CN-05' => 'China: Jilin',
'CN-19' => 'China: Liaoning',
'CN-20' => 'China: Nei Mongol',
'CN-21' => 'China: Ningxia',
'CN-06' => 'China: Qinghai',
'CN-26' => 'China: Shaanxi',
'CN-25' => 'China: Shandong',
'CN-23' => 'China: Shanghai',
'CN-24' => 'China: Shanxi',
'CN-32' => 'China: Sichuan',
'CN-28' => 'China: Tianjin',
'CN-13' => 'China: Xinjiang',
'CN-14' => 'China: Xizang',
'CN-29' => 'China: Yunnan',
'CN-02' => 'China: Zhejiang',

'--CO' => '','-CO' => 'Colombia',
'CO-01' => 'Colombia: Amazonas',
'CO-02' => 'Colombia: Antioquia',
'CO-03' => 'Colombia: Arauca',
'CO-04' => 'Colombia: Atlantico',
'CO-35' => 'Colombia: Bolivar',
'CO-36' => 'Colombia: Boyaca',
'CO-37' => 'Colombia: Caldas',
'CO-08' => 'Colombia: Caqueta',
'CO-32' => 'Colombia: Casanare',
'CO-09' => 'Colombia: Cauca',
'CO-10' => 'Colombia: Cesar',
'CO-11' => 'Colombia: Choco',
'CO-12' => 'Colombia: Cordoba',
'CO-33' => 'Colombia: Cundinamarca',
'CO-34' => 'Colombia: Distrito Especial',
'CO-15' => 'Colombia: Guainia',
'CO-14' => 'Colombia: Guaviare',
'CO-16' => 'Colombia: Huila',
'CO-17' => 'Colombia: La Guajira',
'CO-38' => 'Colombia: Magdalena',
'CO-19' => 'Colombia: Meta',
'CO-20' => 'Colombia: Narino',
'CO-21' => 'Colombia: Norte de Santander',
'CO-22' => 'Colombia: Putumayo',
'CO-23' => 'Colombia: Quindio',
'CO-24' => 'Colombia: Risaralda',
'CO-25' => 'Colombia: San Andres y Providencia',
'CO-26' => 'Colombia: Santander',
'CO-27' => 'Colombia: Sucre',
'CO-28' => 'Colombia: Tolima',
'CO-29' => 'Colombia: Valle del Cauca',
'CO-30' => 'Colombia: Vaupes',
'CO-31' => 'Colombia: Vichada',

'--KM' => '','-KM' => 'Comoros',
'KM-01' => 'Comoros: Anjouan',
'KM-02' => 'Comoros: Grande Comore',
'KM-03' => 'Comoros: Moheli',

'--CD' => '','-CD' => 'Congo',
'CD-01' => 'Congo: Bandundu',
'CD-08' => 'Congo: Bas-Congo',

'--CG' => '','-CG' => 'Congo',
'CG-01' => 'Congo: Bouenza',
'CG-12' => 'Congo: Brazzamark',
'CG-03' => 'Congo: Cuvette',

'--CD' => '','-CD' => 'Congo',
'CD-02' => 'Congo: Equateur',
'CD-03' => 'Congo: Kasai-Occidental',
'CD-04' => 'Congo: Kasai-Oriental',
'CD-05' => 'Congo: Katanga',
'CD-06' => 'Congo: Kinshasa',
'CD-07' => 'Congo: Kivu',

'--CG' => '','-CG' => 'Congo',
'CG-04' => 'Congo: Kouilou',
'CG-05' => 'Congo: Lekoumou',
'CG-06' => 'Congo: Likouala',

'--CD' => '','-CD' => 'Congo',
'CD-10' => 'Congo: Maniema',

'--CG' => '','-CG' => 'Congo',
'CG-07' => 'Congo: Niari',

'--CD' => '','-CD' => 'Congo',
'CD-11' => 'Congo: Nord-Kivu',
'CD-09' => 'Congo: Orientale',

'--CG' => '','-CG' => 'Congo',
'CG-08' => 'Congo: Plateaux',
'CG-11' => 'Congo: Pool',
'CG-10' => 'Congo: Sangha',

'--CD' => '','-CD' => 'Congo',
'CD-12' => 'Congo: Sud-Kivu',

'--CR' => '','-CR' => 'Costa Rica',
'CR-01' => 'Costa Rica: Alajuela',
'CR-02' => 'Costa Rica: Cartago',
'CR-03' => 'Costa Rica: Guanacaste',
'CR-04' => 'Costa Rica: Heredia',
'CR-06' => 'Costa Rica: Limon',
'CR-07' => 'Costa Rica: Puntarenas',
'CR-08' => 'Costa Rica: San Jose',

'--CI' => '','-CI' => 'Cote D'Ivoire',
'CI-01' => 'Cote D\'Ivoire: Abengourou',
'CI-61' => 'Cote D\'Ivoire: Abidjan',
'CI-62' => 'Cote D\'Ivoire: Aboisso',
'CI-63' => 'Cote D\'Ivoire: Adiake',
'CI-05' => 'Cote D\'Ivoire: Adzope',
'CI-06' => 'Cote D\'Ivoire: Agbomark',
'CI-64' => 'Cote D\'Ivoire: Alepe',
'CI-36' => 'Cote D\'Ivoire: Bangolo',
'CI-37' => 'Cote D\'Ivoire: Beoumi',
'CI-07' => 'Cote D\'Ivoire: Biankouma',
'CI-65' => 'Cote D\'Ivoire: Bocanda',
'CI-38' => 'Cote D\'Ivoire: Bondoukou',
'CI-27' => 'Cote D\'Ivoire: Bongouanou',
'CI-39' => 'Cote D\'Ivoire: Bouafle',
'CI-40' => 'Cote D\'Ivoire: Bouake',
'CI-11' => 'Cote D\'Ivoire: Bouna',
'CI-12' => 'Cote D\'Ivoire: Boundiali',
'CI-03' => 'Cote D\'Ivoire: Dabakala',
'CI-66' => 'Cote D\'Ivoire: Dabou',
'CI-41' => 'Cote D\'Ivoire: Daloa',
'CI-14' => 'Cote D\'Ivoire: Danane',
'CI-42' => 'Cote D\'Ivoire: Daoukro',
'CI-67' => 'Cote D\'Ivoire: Dimbokro',
'CI-16' => 'Cote D\'Ivoire: Divo',
'CI-44' => 'Cote D\'Ivoire: Duekoue',
'CI-17' => 'Cote D\'Ivoire: Ferkessedougou',
'CI-18' => 'Cote D\'Ivoire: Gagnoa',
'CI-68' => 'Cote D\'Ivoire: Grand-Bassam',
'CI-45' => 'Cote D\'Ivoire: Grand-Lahou',
'CI-69' => 'Cote D\'Ivoire: Guiglo',
'CI-28' => 'Cote D\'Ivoire: Issia',
'CI-70' => 'Cote D\'Ivoire: Jacquemark',
'CI-20' => 'Cote D\'Ivoire: Katiola',
'CI-21' => 'Cote D\'Ivoire: Korhogo',
'CI-29' => 'Cote D\'Ivoire: Lakota',
'CI-47' => 'Cote D\'Ivoire: Man',
'CI-30' => 'Cote D\'Ivoire: Mankono',
'CI-48' => 'Cote D\'Ivoire: Mbahiakro',
'CI-23' => 'Cote D\'Ivoire: Odienne',
'CI-31' => 'Cote D\'Ivoire: Oume',
'CI-49' => 'Cote D\'Ivoire: Sakassou',
'CI-50' => 'Cote D\'Ivoire: San Pedro',
'CI-51' => 'Cote D\'Ivoire: Sassandra',
'CI-25' => 'Cote D\'Ivoire: Seguela',
'CI-52' => 'Cote D\'Ivoire: Sinfra',
'CI-32' => 'Cote D\'Ivoire: Soubre',
'CI-53' => 'Cote D\'Ivoire: Tabou',
'CI-54' => 'Cote D\'Ivoire: Tanda',
'CI-55' => 'Cote D\'Ivoire: Tiassale',
'CI-71' => 'Cote D\'Ivoire: Tiebissou',
'CI-33' => 'Cote D\'Ivoire: Tingrela',
'CI-26' => 'Cote D\'Ivoire: Touba',
'CI-72' => 'Cote D\'Ivoire: Toulepleu',
'CI-56' => 'Cote D\'Ivoire: Toumodi',
'CI-57' => 'Cote D\'Ivoire: Vavoua',
'CI-73' => 'Cote D\'Ivoire: Yamoussoukro',
'CI-34' => 'Cote D\'Ivoire: Zuenoula',

'--HR' => '','-HR' => 'Croatia',
'HR-01' => 'Croatia: Bjelovarsko-Bilogorska',
'HR-02' => 'Croatia: Brodsko-Posavska',
'HR-03' => 'Croatia: Dubrovacko-Neretvanska',
'HR-21' => 'Croatia: Grad Zagreb',
'HR-04' => 'Croatia: Istarska',
'HR-05' => 'Croatia: Karlovacka',
'HR-06' => 'Croatia: Koprivnicko-Krizevacka',
'HR-07' => 'Croatia: Krapinsko-Zagorska',
'HR-08' => 'Croatia: Licko-Senjska',
'HR-09' => 'Croatia: Medimurska',
'HR-10' => 'Croatia: Osjecko-Baranjska',
'HR-11' => 'Croatia: Pozesko-Slavonska',
'HR-12' => 'Croatia: Primorsko-Goranska',
'HR-13' => 'Croatia: Sibensko-Kninska',
'HR-14' => 'Croatia: Sisacko-Moslavacka',
'HR-15' => 'Croatia: Splitsko-Dalmatinska',
'HR-16' => 'Croatia: Varazdinska',
'HR-17' => 'Croatia: Viroviticko-Podravska',
'HR-18' => 'Croatia: Vukovarsko-Srijemska',
'HR-19' => 'Croatia: Zadarska',
'HR-20' => 'Croatia: Zagrebacka',

'--CU' => '','-CU' => 'Cuba',
'CU-05' => 'Cuba: Camaguey',
'CU-07' => 'Cuba: Ciego de Avila',
'CU-08' => 'Cuba: Cienfuegos',
'CU-02' => 'Cuba: Ciudad de la Habana',
'CU-09' => 'Cuba: Granma',
'CU-10' => 'Cuba: Guantanamo',
'CU-12' => 'Cuba: Holguin',
'CU-04' => 'Cuba: Isla de la Juventud',
'CU-11' => 'Cuba: La Habana',
'CU-13' => 'Cuba: Las Tunas',
'CU-03' => 'Cuba: Matanzas',
'CU-01' => 'Cuba: Pinar del Rio',
'CU-14' => 'Cuba: Sancti Spiritus',
'CU-15' => 'Cuba: Santiago de Cuba',
'CU-16' => 'Cuba: Villa Clara',

'--CY' => '','-CY' => 'Cyprus',
'CY-01' => 'Cyprus: Famagusta',
'CY-02' => 'Cyprus: Kyrenia',
'CY-03' => 'Cyprus: Larnaca',
'CY-05' => 'Cyprus: Limassol',
'CY-04' => 'Cyprus: Nicosia',
'CY-06' => 'Cyprus: Paphos',

'--CZ' => '','-CZ' => 'Czech Republic',
'CZ-03' => 'Czech Republic: Blansko',
'CZ-04' => 'Czech Republic: Breclav',
'CZ-52' => 'Czech Republic: Hlavni Mesto Praha',
'CZ-20' => 'Czech Republic: Hradec Kralove',
'CZ-21' => 'Czech Republic: Jablonec nad Nisou',
'CZ-23' => 'Czech Republic: Jiein',
'CZ-24' => 'Czech Republic: Jihlava',
'CZ-79' => 'Czech Republic: Jihocesky Kraj',
'CZ-78' => 'Czech Republic: Jihomoravsky Kraj',
'CZ-81' => 'Czech Republic: Karlovarsky Kraj',
'CZ-30' => 'Czech Republic: Kolin',
'CZ-82' => 'Czech Republic: Kralovehradecky Kraj',
'CZ-33' => 'Czech Republic: Liberec',
'CZ-83' => 'Czech Republic: Liberecky Kraj',
'CZ-36' => 'Czech Republic: Melnik',
'CZ-37' => 'Czech Republic: Mlada Boleslav',
'CZ-85' => 'Czech Republic: Moravskoslezsky Kraj',
'CZ-39' => 'Czech Republic: Nachod',
'CZ-41' => 'Czech Republic: Nymburk',
'CZ-84' => 'Czech Republic: Olomoucky Kraj',
'CZ-45' => 'Czech Republic: Pardubice',
'CZ-86' => 'Czech Republic: Pardubicky Kraj',
'CZ-87' => 'Czech Republic: Plzensky Kraj',
'CZ-61' => 'Czech Republic: Semily',
'CZ-88' => 'Czech Republic: Stredocesky Kraj',
'CZ-70' => 'Czech Republic: Trutnov',
'CZ-89' => 'Czech Republic: Ustecky Kraj',
'CZ-80' => 'Czech Republic: Vysocina',
'CZ-90' => 'Czech Republic: Zlinsky Kraj',

'--DK' => '','-DK' => 'Denmark',
'DK-01' => 'Denmark: Arhus',
'DK-02' => 'Denmark: Bornholm',
'DK-03' => 'Denmark: Frederiksborg',
'DK-04' => 'Denmark: Fyn',
'DK-05' => 'Denmark: Kobenhavn',
'DK-07' => 'Denmark: Nordjylland',
'DK-08' => 'Denmark: Ribe',
'DK-09' => 'Denmark: Ringkobing',
'DK-10' => 'Denmark: Roskilde',
'DK-11' => 'Denmark: Sonderjylland',
'DK-06' => 'Denmark: Staden Kobenhavn',
'DK-12' => 'Denmark: Storstrom',
'DK-13' => 'Denmark: Vejle',
'DK-14' => 'Denmark: Vestsjalland',
'DK-15' => 'Denmark: Viborg',

'--DJ' => '','-DJ' => 'Djibouti',
'DJ-02' => 'Djibouti: Dikhil',
'DJ-03' => 'Djibouti: Djibouti',
'DJ-04' => 'Djibouti: Obock',
'DJ-05' => 'Djibouti: Tadjoura',

'--DM' => '','-DM' => 'Dominica',
'DM-02' => 'Dominica: Saint Andrew',
'DM-03' => 'Dominica: Saint David',
'DM-04' => 'Dominica: Saint George',
'DM-05' => 'Dominica: Saint John',
'DM-06' => 'Dominica: Saint Joseph',
'DM-07' => 'Dominica: Saint Luke',
'DM-08' => 'Dominica: Saint Mark',
'DM-09' => 'Dominica: Saint Patrick',
'DM-10' => 'Dominica: Saint Paul',
'DM-11' => 'Dominica: Saint Peter',

'--DO' => '','-DO' => 'Dominican Republic',
'DO-01' => 'Dominican Republic: Azua',
'DO-02' => 'Dominican Republic: Baoruco',
'DO-03' => 'Dominican Republic: Barahona',
'DO-04' => 'Dominican Republic: Dajabon',
'DO-05' => 'Dominican Republic: Distrito Nacional',
'DO-06' => 'Dominican Republic: Duarte',
'DO-28' => 'Dominican Republic: El Seibo',
'DO-11' => 'Dominican Republic: Elias Pina',
'DO-08' => 'Dominican Republic: Espaillat',
'DO-29' => 'Dominican Republic: Hato Mayor',
'DO-09' => 'Dominican Republic: Independencia',
'DO-10' => 'Dominican Republic: La Altagracia',
'DO-12' => 'Dominican Republic: La Romana',
'DO-30' => 'Dominican Republic: La Vega',
'DO-14' => 'Dominican Republic: Maria Trinidad Sanchez',
'DO-31' => 'Dominican Republic: Monsenor Nouel',
'DO-15' => 'Dominican Republic: Monte Cristi',
'DO-32' => 'Dominican Republic: Monte Plata',
'DO-16' => 'Dominican Republic: Pedernales',
'DO-17' => 'Dominican Republic: Peravia',
'DO-18' => 'Dominican Republic: Puerto Plata',
'DO-19' => 'Dominican Republic: Salcedo',
'DO-20' => 'Dominican Republic: Samana',
'DO-33' => 'Dominican Republic: San Cristobal',
'DO-23' => 'Dominican Republic: San Juan',
'DO-24' => 'Dominican Republic: San Pedro De Macoris',
'DO-21' => 'Dominican Republic: Sanchez Ramirez',
'DO-26' => 'Dominican Republic: Santiago Rodriguez',
'DO-25' => 'Dominican Republic: Santiago',
'DO-27' => 'Dominican Republic: Valverde',

'--EC' => '','-EC' => 'Ecuador',
'EC-02' => 'Ecuador: Azuay',
'EC-03' => 'Ecuador: Bolivar',
'EC-04' => 'Ecuador: Canar',
'EC-05' => 'Ecuador: Carchi',
'EC-06' => 'Ecuador: Chimborazo',
'EC-07' => 'Ecuador: Cotopaxi',
'EC-08' => 'Ecuador: El Oro',
'EC-09' => 'Ecuador: Esmeraldas',
'EC-01' => 'Ecuador: Galapagos',
'EC-10' => 'Ecuador: Guayas',
'EC-11' => 'Ecuador: Imbabura',
'EC-12' => 'Ecuador: Loja',
'EC-13' => 'Ecuador: Los Rios',
'EC-14' => 'Ecuador: Manabi',
'EC-15' => 'Ecuador: Morona-Santiago',
'EC-23' => 'Ecuador: Napo',
'EC-24' => 'Ecuador: Orellana',
'EC-17' => 'Ecuador: Pastaza',
'EC-18' => 'Ecuador: Pichincha',
'EC-22' => 'Ecuador: Sucumbios',
'EC-19' => 'Ecuador: Tungurahua',
'EC-20' => 'Ecuador: Zamora-Chinchipe',

'--EG' => '','-EG' => 'Egypt',
'EG-01' => 'Egypt: Ad Daqahliyah',
'EG-02' => 'Egypt: Al Bahr al Ahmar',
'EG-03' => 'Egypt: Al Buhayrah',
'EG-04' => 'Egypt: Al Fayyum',
'EG-05' => 'Egypt: Al Gharbiyah',
'EG-06' => 'Egypt: Al Iskandariyah',
'EG-07' => 'Egypt: Al Isma\'iliyah',
'EG-08' => 'Egypt: Al Jizah',
'EG-09' => 'Egypt: Al Minufiyah',
'EG-10' => 'Egypt: Al Minya',
'EG-11' => 'Egypt: Al Qahirah',
'EG-12' => 'Egypt: Al Qalyubiyah',
'EG-13' => 'Egypt: Al Wadi al Jadid',
'EG-15' => 'Egypt: As Suways',
'EG-14' => 'Egypt: Ash Sharqiyah',
'EG-16' => 'Egypt: Aswan',
'EG-17' => 'Egypt: Asyut',
'EG-18' => 'Egypt: Bani Suwayf',
'EG-19' => 'Egypt: Bur Sa\'id',
'EG-20' => 'Egypt: Dumyat',
'EG-26' => 'Egypt: Janub Sina\'',
'EG-21' => 'Egypt: Kafr ash Shaykh',
'EG-22' => 'Egypt: Matruh',
'EG-23' => 'Egypt: Qina',
'EG-27' => 'Egypt: Shamal Sina\'',
'EG-24' => 'Egypt: Suhaj',

'--SV' => '','-SV' => 'El Salvador',
'SV-01' => 'El Salvador: Ahuachapan',
'SV-02' => 'El Salvador: Cabanas',
'SV-03' => 'El Salvador: Chalatenango',
'SV-04' => 'El Salvador: Cuscatlan',
'SV-05' => 'El Salvador: La Libertad',
'SV-06' => 'El Salvador: La Paz',
'SV-07' => 'El Salvador: La Union',
'SV-08' => 'El Salvador: Morazan',
'SV-09' => 'El Salvador: San Miguel',
'SV-10' => 'El Salvador: San Salvador',
'SV-12' => 'El Salvador: San Vicente',
'SV-11' => 'El Salvador: Santa Ana',
'SV-13' => 'El Salvador: Sonsonate',
'SV-14' => 'El Salvador: Usulutan',

'--GQ' => '','-GQ' => 'Equatorial Guinea',
'GQ-03' => 'Equatorial Guinea: Annobon',
'GQ-04' => 'Equatorial Guinea: Bioko Norte',
'GQ-05' => 'Equatorial Guinea: Bioko Sur',
'GQ-06' => 'Equatorial Guinea: Centro Sur',
'GQ-07' => 'Equatorial Guinea: Kie-Ntem',
'GQ-08' => 'Equatorial Guinea: Litoral',
'GQ-09' => 'Equatorial Guinea: Wele-Nzas',

'--EE' => '','-EE' => 'Estonia',
'EE-01' => 'Estonia: Harjumaa',
'EE-02' => 'Estonia: Hiiumaa',
'EE-03' => 'Estonia: Ida-Virumaa',
'EE-04' => 'Estonia: Jarvamaa',
'EE-05' => 'Estonia: Jogevamaa',
'EE-06' => 'Estonia: Kohtla-Jarve',
'EE-07' => 'Estonia: Laanemaa',
'EE-08' => 'Estonia: Laane-Virumaa',
'EE-09' => 'Estonia: Narva',
'EE-10' => 'Estonia: Parnu',
'EE-11' => 'Estonia: Parnumaa',
'EE-12' => 'Estonia: Polvamaa',
'EE-13' => 'Estonia: Raplamaa',
'EE-14' => 'Estonia: Saaremaa',
'EE-15' => 'Estonia: Sillamae',
'EE-16' => 'Estonia: Tallinn',
'EE-17' => 'Estonia: Tartu',
'EE-18' => 'Estonia: Tartumaa',
'EE-19' => 'Estonia: Valgamaa',
'EE-20' => 'Estonia: Viljandimaa',
'EE-21' => 'Estonia: Vorumaa',

'--ET' => '','-ET' => 'Ethiopia',
'ET-10' => 'Ethiopia: Addis Abeba',
'ET-44' => 'Ethiopia: Adis Abeba',
'ET-14' => 'Ethiopia: Afar',
'ET-45' => 'Ethiopia: Afar',
'ET-46' => 'Ethiopia: Amara',
'ET-02' => 'Ethiopia: Amhara',
'ET-13' => 'Ethiopia: Benishangul',
'ET-47' => 'Ethiopia: Binshangul Gumuz',
'ET-48' => 'Ethiopia: Dire Dawa',
'ET-49' => 'Ethiopia: Gambela Hizboch',
'ET-08' => 'Ethiopia: Gambella',
'ET-50' => 'Ethiopia: Hareri Hizb',
'ET-51' => 'Ethiopia: Oromiya',
'ET-07' => 'Ethiopia: Somali',
'ET-11' => 'Ethiopia: Southern',
'ET-52' => 'Ethiopia: Sumale',
'ET-12' => 'Ethiopia: Tigray',
'ET-53' => 'Ethiopia: Tigray',
'ET-54' => 'Ethiopia: YeDebub Biheroch Bihereseboch na Hizboch',

'--FJ' => '','-FJ' => 'Fiji',
'FJ-01' => 'Fiji: Central',
'FJ-02' => 'Fiji: Eastern',
'FJ-03' => 'Fiji: Northern',
'FJ-04' => 'Fiji: Rotuma',
'FJ-05' => 'Fiji: Western',

'--FI' => '','-FI' => 'Finland',
'FI-01' => 'Finland: Åland',
'FI-14' => 'Finland: Eastern Finland',
'FI-06' => 'Finland: Lapland',
'FI-08' => 'Finland: Oulu',
'FI-13' => 'Finland: Southern Finland',
'FI-15' => 'Finland: Western Finland',

'--FR' => '','-FR' => 'France',
'FR-C1' => 'France: Alsace',
'FR-97' => 'France: Aquitaine',
'FR-98' => 'France: Auvergne',
'FR-99' => 'France: Basse-Normandie',
'FR-A1' => 'France: Bourgogne',
'FR-A2' => 'France: Bretagne',
'FR-A3' => 'France: Centre',
'FR-A4' => 'France: Champagne-Ardenne',
'FR-A5' => 'France: Corse',
'FR-A6' => 'France: Franche-Comte',
'FR-A7' => 'France: Haute-Normandie',
'FR-A8' => 'France: Ile-de-France',
'FR-A9' => 'France: Languedoc-Roussillon',
'FR-B1' => 'France: Limousin',
'FR-B2' => 'France: Lorraine',
'FR-B3' => 'France: Midi-Pyrenees',
'FR-B4' => 'France: Nord-Pas-de-Calais',
'FR-B5' => 'France: Pays de la Loire',
'FR-B6' => 'France: Picardie',
'FR-B7' => 'France: Poitou-Charentes',
'FR-B8' => 'France: Provence-Alpes-Cote d\'Azur',
'FR-B9' => 'France: Rhone-Alpes',

'--GA' => '','-GA' => 'Gabon',
'GA-01' => 'Gabon: Estuaire',
'GA-02' => 'Gabon: Haut-Ogooue',
'GA-03' => 'Gabon: Moyen-Ogooue',
'GA-04' => 'Gabon: Ngounie',
'GA-05' => 'Gabon: Nyanga',
'GA-06' => 'Gabon: Ogooue-Ivindo',
'GA-07' => 'Gabon: Ogooue-Lolo',
'GA-08' => 'Gabon: Ogooue-Maritime',
'GA-09' => 'Gabon: Woleu-Ntem',

'--GM' => '','-GM' => 'Gambia',
'GM-01' => 'Gambia: Banjul',
'GM-02' => 'Gambia: Lower River',
'GM-03' => 'Gambia: MacCarthy Island',
'GM-07' => 'Gambia: North Bank',
'GM-04' => 'Gambia: Upper River',
'GM-05' => 'Gambia: Western',

'--GE' => '','-GE' => 'Georgia',
'GE-01' => 'Georgia: Abashis Raioni',
'GE-02' => 'Georgia: Abkhazia',
'GE-03' => 'Georgia: Adigenis Raioni',
'GE-04' => 'Georgia: Ajaria',
'GE-05' => 'Georgia: Akhalgoris Raioni',
'GE-06' => 'Georgia: Akhalk\'alak\'is Raioni',
'GE-07' => 'Georgia: Akhalts\'ikhis Raioni',
'GE-08' => 'Georgia: Akhmetis Raioni',
'GE-09' => 'Georgia: Ambrolauris Raioni',
'GE-10' => 'Georgia: Aspindzis Raioni',
'GE-11' => 'Georgia: Baghdat\'is Raioni',
'GE-12' => 'Georgia: Bolnisis Raioni',
'GE-13' => 'Georgia: Borjomis Raioni',
'GE-14' => 'Georgia: Chiat\'ura',
'GE-15' => 'Georgia: Ch\'khorotsqus Raioni',
'GE-16' => 'Georgia: Ch\'okhatauris Raioni',
'GE-17' => 'Georgia: Dedop\'listsqaros Raioni',
'GE-18' => 'Georgia: Dmanisis Raioni',
'GE-19' => 'Georgia: Dushet\'is Raioni',
'GE-20' => 'Georgia: Gardabanis Raioni',
'GE-21' => 'Georgia: Gori',
'GE-22' => 'Georgia: Goris Raioni',
'GE-23' => 'Georgia: Gurjaanis Raioni',
'GE-24' => 'Georgia: Javis Raioni',
'GE-25' => 'Georgia: K\'arelis Raioni',
'GE-26' => 'Georgia: Kaspis Raioni',
'GE-27' => 'Georgia: Kharagaulis Raioni',
'GE-28' => 'Georgia: Khashuris Raioni',
'GE-29' => 'Georgia: Khobis Raioni',
'GE-30' => 'Georgia: Khonis Raioni',
'GE-31' => 'Georgia: K\'ut\'aisi',
'GE-32' => 'Georgia: Lagodekhis Raioni',
'GE-33' => 'Georgia: Lanch\'khut\'is Raioni',
'GE-34' => 'Georgia: Lentekhis Raioni',
'GE-35' => 'Georgia: Marneulis Raioni',
'GE-36' => 'Georgia: Martvilis Raioni',
'GE-37' => 'Georgia: Mestiis Raioni',
'GE-38' => 'Georgia: Mts\'khet\'is Raioni',
'GE-39' => 'Georgia: Ninotsmindis Raioni',
'GE-40' => 'Georgia: Onis Raioni',
'GE-41' => 'Georgia: Ozurget\'is Raioni',
'GE-42' => 'Georgia: P\'ot\'i',
'GE-43' => 'Georgia: Qazbegis Raioni',
'GE-44' => 'Georgia: Qvarlis Raioni',
'GE-45' => 'Georgia: Rust\'avi',
'GE-46' => 'Georgia: Sach\'kheris Raioni',
'GE-47' => 'Georgia: Sagarejos Raioni',
'GE-48' => 'Georgia: Samtrediis Raioni',
'GE-49' => 'Georgia: Senakis Raioni',
'GE-50' => 'Georgia: Sighnaghis Raioni',
'GE-51' => 'Georgia: T\'bilisi',
'GE-52' => 'Georgia: T\'elavis Raioni',
'GE-53' => 'Georgia: T\'erjolis Raioni',
'GE-54' => 'Georgia: T\'et\'ritsqaros Raioni',
'GE-55' => 'Georgia: T\'ianet\'is Raioni',
'GE-56' => 'Georgia: Tqibuli',
'GE-57' => 'Georgia: Ts\'ageris Raioni',
'GE-58' => 'Georgia: Tsalenjikhis Raioni',
'GE-59' => 'Georgia: Tsalkis Raioni',
'GE-60' => 'Georgia: Tsqaltubo',
'GE-61' => 'Georgia: Vanis Raioni',
'GE-62' => 'Georgia: Zestap\'onis Raioni',
'GE-63' => 'Georgia: Zugdidi',
'GE-64' => 'Georgia: Zugdidis Raioni',

'--DE' => '','-DE' => 'Germany',
'DE-01' => 'Germany: Baden-Württemberg',
'DE-02' => 'Germany: Bayern',
'DE-16' => 'Germany: Berlin',
'DE-11' => 'Germany: Brandenburg',
'DE-03' => 'Germany: Bremen',
'DE-04' => 'Germany: Hamburg',
'DE-05' => 'Germany: Hessen',
'DE-12' => 'Germany: Mecklenburg-Vorpommern',
'DE-06' => 'Germany: Niedersachsen',
'DE-07' => 'Germany: Nordrhein-Westfalen',
'DE-08' => 'Germany: Rheinland-Pfalz',
'DE-09' => 'Germany: Saarland',
'DE-13' => 'Germany: Sachsen',
'DE-14' => 'Germany: Sachsen-Anhalt',
'DE-10' => 'Germany: Schleswig-Holstein',
'DE-15' => 'Germany: Thuringen',

'--GH' => '','-GH' => 'Ghana',
'GH-02' => 'Ghana: Ashanti',
'GH-03' => 'Ghana: Brong-Ahafo',
'GH-04' => 'Ghana: Central',
'GH-05' => 'Ghana: Eastern',
'GH-01' => 'Ghana: Greater Accra',
'GH-06' => 'Ghana: Northern',
'GH-10' => 'Ghana: Upper East',
'GH-11' => 'Ghana: Upper West',
'GH-08' => 'Ghana: Volta',
'GH-09' => 'Ghana: Western',

'--GR' => '','-GR' => 'Greece',
'GR-31' => 'Greece: Aitolia kai Akarnania',
'GR-38' => 'Greece: Akhaia',
'GR-36' => 'Greece: Argolis',
'GR-41' => 'Greece: Arkadhia',
'GR-20' => 'Greece: Arta',
'GR-35' => 'Greece: Attiki',
'GR-47' => 'Greece: Dhodhekanisos',
'GR-04' => 'Greece: Drama',
'GR-30' => 'Greece: Evritania',
'GR-01' => 'Greece: Evros',
'GR-34' => 'Greece: Evvoia',
'GR-08' => 'Greece: Florina',
'GR-32' => 'Greece: Fokis',
'GR-29' => 'Greece: Fthiotis',
'GR-10' => 'Greece: Grevena',
'GR-39' => 'Greece: Ilia',
'GR-12' => 'Greece: Imathia',
'GR-17' => 'Greece: Ioannina',
'GR-45' => 'Greece: Iraklion',
'GR-23' => 'Greece: Kardhitsa',
'GR-09' => 'Greece: Kastoria',
'GR-14' => 'Greece: Kavala',
'GR-27' => 'Greece: Kefallinia',
'GR-25' => 'Greece: Kerkira',
'GR-15' => 'Greece: Khalkidhiki',
'GR-43' => 'Greece: Khania',
'GR-50' => 'Greece: Khios',
'GR-49' => 'Greece: Kikladhes',
'GR-06' => 'Greece: Kilkis',
'GR-37' => 'Greece: Korinthia',
'GR-11' => 'Greece: Kozani',
'GR-42' => 'Greece: Lakonia',
'GR-21' => 'Greece: Larisa',
'GR-46' => 'Greece: Lasithi',
'GR-51' => 'Greece: Lesvos',
'GR-26' => 'Greece: Levkas',
'GR-24' => 'Greece: Magnisia',
'GR-40' => 'Greece: Messinia',
'GR-07' => 'Greece: Pella',
'GR-16' => 'Greece: Pieria',
'GR-19' => 'Greece: Preveza',
'GR-44' => 'Greece: Rethimni',
'GR-02' => 'Greece: Rodhopi',
'GR-48' => 'Greece: Samos',
'GR-05' => 'Greece: Serrai',
'GR-18' => 'Greece: Thesprotia',
'GR-13' => 'Greece: Thessaloniki',
'GR-22' => 'Greece: Trikala',
'GR-33' => 'Greece: Voiotia',
'GR-03' => 'Greece: Xanthi',
'GR-28' => 'Greece: Zakinthos',

'--GL' => '','-GL' => 'Greenland',
'GL-01' => 'Greenland: Nordgronland',
'GL-02' => 'Greenland: Ostgronland',
'GL-03' => 'Greenland: Vestgronland',

'--GD' => '','-GD' => 'Grenada',
'GD-01' => 'Grenada: Saint Andrew',
'GD-02' => 'Grenada: Saint David',
'GD-03' => 'Grenada: Saint George',
'GD-04' => 'Grenada: Saint John',
'GD-05' => 'Grenada: Saint Mark',
'GD-06' => 'Grenada: Saint Patrick',

'--GT' => '','-GT' => 'Guatemala',
'GT-01' => 'Guatemala: Alta Verapaz',
'GT-02' => 'Guatemala: Baja Verapaz',
'GT-03' => 'Guatemala: Chimaltenango',
'GT-04' => 'Guatemala: Chiquimula',
'GT-05' => 'Guatemala: El Progreso',
'GT-06' => 'Guatemala: Escuintla',
'GT-07' => 'Guatemala: Guatemala',
'GT-08' => 'Guatemala: Huehuetenango',
'GT-09' => 'Guatemala: Izabal',
'GT-10' => 'Guatemala: Jalapa',
'GT-11' => 'Guatemala: Jutiapa',
'GT-12' => 'Guatemala: Peten',
'GT-13' => 'Guatemala: Quetzaltenango',
'GT-14' => 'Guatemala: Quiche',
'GT-15' => 'Guatemala: Retalhuleu',
'GT-16' => 'Guatemala: Sacatepequez',
'GT-17' => 'Guatemala: San Marcos',
'GT-18' => 'Guatemala: Santa Rosa',
'GT-19' => 'Guatemala: Solola',
'GT-20' => 'Guatemala: Suchitepequez',
'GT-21' => 'Guatemala: Totonicapan',
'GT-22' => 'Guatemala: Zacapa',

'--GN' => '','-GN' => 'Guinea',
'GN-01' => 'Guinea: Beyla',
'GN-02' => 'Guinea: Boffa',
'GN-03' => 'Guinea: Boke',
'GN-04' => 'Guinea: Conakry',
'GN-30' => 'Guinea: Coyah',
'GN-05' => 'Guinea: Dabola',
'GN-06' => 'Guinea: Dalaba',
'GN-07' => 'Guinea: Dinguiraye',
'GN-31' => 'Guinea: Dubreka',
'GN-09' => 'Guinea: Faranah',
'GN-10' => 'Guinea: Forecariah',
'GN-11' => 'Guinea: Fria',
'GN-12' => 'Guinea: Gaoual',
'GN-13' => 'Guinea: Gueckedou',
'GN-32' => 'Guinea: Kankan',
'GN-15' => 'Guinea: Kerouane',
'GN-16' => 'Guinea: Kindia',
'GN-17' => 'Guinea: Kissidougou',
'GN-33' => 'Guinea: Koubia',
'GN-18' => 'Guinea: Koundara',
'GN-19' => 'Guinea: Kouroussa',
'GN-34' => 'Guinea: Labe',
'GN-35' => 'Guinea: Lelouma',
'GN-36' => 'Guinea: Lola',
'GN-21' => 'Guinea: Macenta',
'GN-22' => 'Guinea: Mali',
'GN-23' => 'Guinea: Mamou',
'GN-37' => 'Guinea: Mandiana',
'GN-38' => 'Guinea: Nzerekore',
'GN-25' => 'Guinea: Pita',
'GN-39' => 'Guinea: Siguiri',
'GN-27' => 'Guinea: Telimele',
'GN-28' => 'Guinea: Tougue',
'GN-29' => 'Guinea: Yomou',

'--GW' => '','-GW' => 'Guinea-Bissau',
'GW-01' => 'Guinea-Bissau: Bafata',
'GW-12' => 'Guinea-Bissau: Biombo',
'GW-11' => 'Guinea-Bissau: Bissau',
'GW-05' => 'Guinea-Bissau: Bolama',
'GW-06' => 'Guinea-Bissau: Cacheu',
'GW-10' => 'Guinea-Bissau: Gabu',
'GW-04' => 'Guinea-Bissau: Oio',
'GW-02' => 'Guinea-Bissau: Quinara',
'GW-07' => 'Guinea-Bissau: Tombali',

'--GY' => '','-GY' => 'Guyana',
'GY-10' => 'Guyana: Barima-Waini',
'GY-11' => 'Guyana: Cuyuni-Mazaruni',
'GY-12' => 'Guyana: Demerara-Mahaica',
'GY-13' => 'Guyana: East Berbice-Corentyne',
'GY-14' => 'Guyana: Essequibo Islands-West Demerara',
'GY-15' => 'Guyana: Mahaica-Berbice',
'GY-16' => 'Guyana: Pomeroon-Supenaam',
'GY-17' => 'Guyana: Potaro-Siparuni',
'GY-18' => 'Guyana: Upper Demerara-Berbice',
'GY-19' => 'Guyana: Upper Takutu-Upper Essequibo',

'--HT' => '','-HT' => 'Haiti',
'HT-06' => 'Haiti: Artibonite',
'HT-07' => 'Haiti: Centre',
'HT-08' => 'Haiti: Grand\' Anse',
'HT-09' => 'Haiti: Nord',
'HT-10' => 'Haiti: Nord-Est',
'HT-03' => 'Haiti: Nord-Ouest',
'HT-11' => 'Haiti: Ouest',
'HT-12' => 'Haiti: Sud',
'HT-13' => 'Haiti: Sud-Est',

'--HN' => '','-HN' => 'Honduras',
'HN-01' => 'Honduras: Atlantida',
'HN-02' => 'Honduras: Choluteca',
'HN-03' => 'Honduras: Colon',
'HN-04' => 'Honduras: Comayagua',
'HN-05' => 'Honduras: Copan',
'HN-06' => 'Honduras: Cortes',
'HN-07' => 'Honduras: El Paraiso',
'HN-08' => 'Honduras: Francisco Morazan',
'HN-09' => 'Honduras: Gracias a Dios',
'HN-10' => 'Honduras: Intibuca',
'HN-11' => 'Honduras: Islas de la Bahia',
'HN-12' => 'Honduras: La Paz',
'HN-13' => 'Honduras: Lempira',
'HN-14' => 'Honduras: Ocotepeque',
'HN-15' => 'Honduras: Olancho',
'HN-16' => 'Honduras: Santa Barbara',
'HN-17' => 'Honduras: Valle',
'HN-18' => 'Honduras: Yoro',

'--HU' => '','-HU' => 'Hungary',
'HU-01' => 'Hungary: Bacs-Kiskun',
'HU-02' => 'Hungary: Baranya',
'HU-03' => 'Hungary: Bekes',
'HU-26' => 'Hungary: Bekescsaba',
'HU-04' => 'Hungary: Borsod-Abauj-Zemplen',
'HU-05' => 'Hungary: Budapest',
'HU-06' => 'Hungary: Csongrad',
'HU-07' => 'Hungary: Debrecen',
'HU-27' => 'Hungary: Dunaujvaros',
'HU-28' => 'Hungary: Eger',
'HU-08' => 'Hungary: Fejer',
'HU-25' => 'Hungary: Gyor',
'HU-09' => 'Hungary: Gyor-Moson-Sopron',
'HU-10' => 'Hungary: Hajdu-Bihar',
'HU-11' => 'Hungary: Heves',
'HU-29' => 'Hungary: Hodmezovasarhely',
'HU-20' => 'Hungary: Jasz-Nagykun-Szolnok',
'HU-30' => 'Hungary: Kaposvar',
'HU-31' => 'Hungary: Kecskemet',
'HU-12' => 'Hungary: Komarom-Esztergom',
'HU-13' => 'Hungary: Miskolc',
'HU-32' => 'Hungary: Nagykanizsa',
'HU-14' => 'Hungary: Nograd',
'HU-33' => 'Hungary: Nyiregyhaza',
'HU-15' => 'Hungary: Pecs',
'HU-16' => 'Hungary: Pest',
'HU-17' => 'Hungary: Somogy',
'HU-34' => 'Hungary: Sopron',
'HU-18' => 'Hungary: Szabolcs-Szatmar-Bereg',
'HU-19' => 'Hungary: Szeged',
'HU-35' => 'Hungary: Szekesfehervar',
'HU-36' => 'Hungary: Szolnok',
'HU-37' => 'Hungary: Szombathely',
'HU-38' => 'Hungary: Tatabanya',
'HU-21' => 'Hungary: Tolna',
'HU-22' => 'Hungary: Vas',
'HU-23' => 'Hungary: Veszprem',
'HU-39' => 'Hungary: Veszprem',
'HU-24' => 'Hungary: Zala',
'HU-40' => 'Hungary: Zalaegerszeg',

'--IS' => '','-IS' => 'Iceland',
'IS-01' => 'Iceland: Akranes',
'IS-02' => 'Iceland: Akureyri',
'IS-03' => 'Iceland: Arnessysla',
'IS-04' => 'Iceland: Austur-Bardastrandarsysla',
'IS-05' => 'Iceland: Austur-Hunavatnssysla',
'IS-06' => 'Iceland: Austur-Skaftafellssysla',
'IS-07' => 'Iceland: Borgarfjardarsysla',
'IS-08' => 'Iceland: Dalasysla',
'IS-09' => 'Iceland: Eyjafjardarsysla',
'IS-10' => 'Iceland: Gullbringusysla',
'IS-11' => 'Iceland: Hafnarfjordur',
'IS-12' => 'Iceland: Husavik',
'IS-13' => 'Iceland: Isafjordur',
'IS-14' => 'Iceland: Keflavik',
'IS-15' => 'Iceland: Kjosarsysla',
'IS-16' => 'Iceland: Kopavogur',
'IS-17' => 'Iceland: Myrasysla',
'IS-18' => 'Iceland: Neskaupstadur',
'IS-19' => 'Iceland: Nordur-Isafjardarsysla',
'IS-20' => 'Iceland: Nordur-Mulasysla',
'IS-21' => 'Iceland: Nordur-Tingeyjarsysla',
'IS-22' => 'Iceland: Olafsfjordur',
'IS-23' => 'Iceland: Rangarvallasysla',
'IS-24' => 'Iceland: Reykjavik',
'IS-25' => 'Iceland: Saudarkrokur',
'IS-26' => 'Iceland: Seydisfjordur',
'IS-27' => 'Iceland: Siglufjordur',
'IS-28' => 'Iceland: Skagafjardarsysla',
'IS-29' => 'Iceland: Snafellsnes- og Hnappadalssysla',
'IS-30' => 'Iceland: Strandasysla',
'IS-31' => 'Iceland: Sudur-Mulasysla',
'IS-32' => 'Iceland: Sudur-Tingeyjarsysla',
'IS-33' => 'Iceland: Vestmannaeyjar',
'IS-34' => 'Iceland: Vestur-Bardastrandarsysla',
'IS-35' => 'Iceland: Vestur-Hunavatnssysla',
'IS-36' => 'Iceland: Vestur-Isafjardarsysla',
'IS-37' => 'Iceland: Vestur-Skaftafellssysla',

'--IN' => '','-IN' => 'India',
'IN-01' => 'India: Andaman and Nicobar Islands',
'IN-02' => 'India: Andhra Pradesh',
'IN-30' => 'India: Arunachal Pradesh',
'IN-03' => 'India: Assam',
'IN-34' => 'India: Bihar',
'IN-05' => 'India: Chandigarh',
'IN-37' => 'India: Chhattisgarh',
'IN-06' => 'India: Dadra and Nagar Haveli',
'IN-32' => 'India: Daman and Diu',
'IN-07' => 'India: Delhi',
'IN-33' => 'India: Goa',
'IN-09' => 'India: Gujarat',
'IN-10' => 'India: Haryana',
'IN-11' => 'India: Himachal Pradesh',
'IN-12' => 'India: Jammu and Kashmir',
'IN-38' => 'India: Jharkhand',
'IN-19' => 'India: Karnataka',
'IN-13' => 'India: Kerala',
'IN-14' => 'India: Lakshadweep',
'IN-35' => 'India: Madhya Pradesh',
'IN-16' => 'India: Maharashtra',
'IN-17' => 'India: Manipur',
'IN-18' => 'India: Meghalaya',
'IN-31' => 'India: Mizoram',
'IN-20' => 'India: Nagaland',
'IN-21' => 'India: Orissa',
'IN-22' => 'India: Pondicherry',
'IN-23' => 'India: Punjab',
'IN-24' => 'India: Rajasthan',
'IN-29' => 'India: Sikkim',
'IN-25' => 'India: Tamil Nadu',
'IN-26' => 'India: Tripura',
'IN-36' => 'India: Uttar Pradesh',
'IN-39' => 'India: Uttaranchal',
'IN-28' => 'India: West Bengal',

'--ID' => '','-ID' => 'Indonesia',
'ID-01' => 'Indonesia: Aceh',
'ID-02' => 'Indonesia: Bali',
'ID-33' => 'Indonesia: Banten',
'ID-03' => 'Indonesia: Bengkulu',
'ID-34' => 'Indonesia: Gorontalo',
'ID-04' => 'Indonesia: Jakarta Raya',
'ID-05' => 'Indonesia: Jambi',
'ID-30' => 'Indonesia: Jawa Barat',
'ID-07' => 'Indonesia: Jawa Tengah',
'ID-08' => 'Indonesia: Jawa Timur',
'ID-11' => 'Indonesia: Kalimantan Barat',
'ID-12' => 'Indonesia: Kalimantan Selatan',
'ID-13' => 'Indonesia: Kalimantan Tengah',
'ID-14' => 'Indonesia: Kalimantan Timur',
'ID-35' => 'Indonesia: Kepulauan Bangka Belitung',
'ID-15' => 'Indonesia: Lampung',
'ID-29' => 'Indonesia: Maluku Utara',
'ID-28' => 'Indonesia: Maluku',
'ID-17' => 'Indonesia: Nusa Tenggara Barat',
'ID-18' => 'Indonesia: Nusa Tenggara Timur',
'ID-09' => 'Indonesia: Papua',
'ID-19' => 'Indonesia: Riau',
'ID-20' => 'Indonesia: Sulawesi Selatan',
'ID-21' => 'Indonesia: Sulawesi Tengah',
'ID-22' => 'Indonesia: Sulawesi Tenggara',
'ID-31' => 'Indonesia: Sulawesi Utara',
'ID-24' => 'Indonesia: Sumatera Barat',
'ID-25' => 'Indonesia: Sumatera Selatan',
'ID-32' => 'Indonesia: Sumatera Selatan',
'ID-26' => 'Indonesia: Sumatera Utara',
'ID-10' => 'Indonesia: Yogyakarta',

'--IR' => '','-IR' => 'Iran',
'IR-32' => 'Iran: Ardabil',
'IR-01' => 'Iran: Azarbayjan-e Bakhtari',
'IR-02' => 'Iran: Azarbayjan-e Khavari',
'IR-13' => 'Iran: Bakhtaran',
'IR-22' => 'Iran: Bushehr',
'IR-03' => 'Iran: Chahar Mahall va Bakhtiari',
'IR-28' => 'Iran: Esfahan',
'IR-07' => 'Iran: Fars',
'IR-08' => 'Iran: Gilan',
'IR-37' => 'Iran: Golestan',
'IR-09' => 'Iran: Hamadan',
'IR-11' => 'Iran: Hormozgan',
'IR-10' => 'Iran: Ilam',
'IR-29' => 'Iran: Kerman',
'IR-30' => 'Iran: Khorasan',
'IR-15' => 'Iran: Khuzestan',
'IR-05' => 'Iran: Kohkiluyeh va Buyer Ahmadi',
'IR-16' => 'Iran: Kordestan',
'IR-23' => 'Iran: Lorestan',
'IR-34' => 'Iran: Markazi',
'IR-35' => 'Iran: Mazandaran',
'IR-38' => 'Iran: Qazvin',
'IR-39' => 'Iran: Qom',
'IR-25' => 'Iran: Semnan',
'IR-04' => 'Iran: Sistan va Baluchestan',
'IR-26' => 'Iran: Tehran',
'IR-31' => 'Iran: Yazd',
'IR-36' => 'Iran: Zanjan',

'--IQ' => '','-IQ' => 'Iraq',
'IQ-01' => 'Iraq: Al Anbar',
'IQ-02' => 'Iraq: Al Basrah',
'IQ-03' => 'Iraq: Al Muthanna',
'IQ-04' => 'Iraq: Al Qadisiyah',
'IQ-17' => 'Iraq: An Najaf',
'IQ-11' => 'Iraq: Arbil',
'IQ-05' => 'Iraq: As Sulaymaniyah',
'IQ-13' => 'Iraq: At Ta\'mim',
'IQ-06' => 'Iraq: Babil',
'IQ-07' => 'Iraq: Baghdad',
'IQ-08' => 'Iraq: Dahuk',
'IQ-09' => 'Iraq: Dhi Qar',
'IQ-10' => 'Iraq: Diyala',
'IQ-12' => 'Iraq: Karbala\'',
'IQ-14' => 'Iraq: Maysan',
'IQ-15' => 'Iraq: Ninawa',
'IQ-18' => 'Iraq: Salah ad Din',
'IQ-16' => 'Iraq: Wasit',

'--IE' => '','-IE' => 'Ireland',
'IE-01' => 'Ireland: Carlow',
'IE-02' => 'Ireland: Cavan',
'IE-03' => 'Ireland: Clare',
'IE-04' => 'Ireland: Cork',
'IE-06' => 'Ireland: Donegal',
'IE-07' => 'Ireland: Dublin',
'IE-10' => 'Ireland: Galway',
'IE-11' => 'Ireland: Kerry',
'IE-12' => 'Ireland: Kildare',
'IE-13' => 'Ireland: Kilkenny',
'IE-15' => 'Ireland: Laois',
'IE-14' => 'Ireland: Leitrim',
'IE-16' => 'Ireland: Limerick',
'IE-18' => 'Ireland: Longford',
'IE-19' => 'Ireland: Louth',
'IE-20' => 'Ireland: Mayo',
'IE-21' => 'Ireland: Meath',
'IE-22' => 'Ireland: Monaghan',
'IE-23' => 'Ireland: Offaly',
'IE-24' => 'Ireland: Roscommon',
'IE-25' => 'Ireland: Sligo',
'IE-26' => 'Ireland: Tipperary',
'IE-27' => 'Ireland: Waterford',
'IE-29' => 'Ireland: Westmeath',
'IE-30' => 'Ireland: Wexford',
'IE-31' => 'Ireland: Wicklow',

'--IL' => '','-IL' => 'Israel',
'IL-01' => 'Israel: HaDarom',
'IL-02' => 'Israel: HaMerkaz',
'IL-03' => 'Israel: HaZafon',
'IL-04' => 'Israel: Hefa',
'IL-05' => 'Israel: Tel Aviv',
'IL-06' => 'Israel: Yerushalayim',

'--IT' => '','-IT' => 'Italy',
'IT-01' => 'Italy: Abruzzi',
'IT-02' => 'Italy: Basilicata',
'IT-03' => 'Italy: Calabria',
'IT-04' => 'Italy: Campania',
'IT-05' => 'Italy: Emilia-Romagna',
'IT-06' => 'Italy: Friuli-Venezia Giulia',
'IT-07' => 'Italy: Lazio',
'IT-08' => 'Italy: Liguria',
'IT-09' => 'Italy: Lombardia',
'IT-10' => 'Italy: Marche',
'IT-11' => 'Italy: Molise',
'IT-12' => 'Italy: Piemonte',
'IT-13' => 'Italy: Puglia',
'IT-14' => 'Italy: Sardegna',
'IT-15' => 'Italy: Sicilia',
'IT-16' => 'Italy: Toscana',
'IT-17' => 'Italy: Trentino-Alto Adige',
'IT-18' => 'Italy: Umbria',
'IT-19' => 'Italy: Valle d\'Aosta',
'IT-20' => 'Italy: Veneto',

'--JM' => '','-JM' => 'Jamaica',
'JM-01' => 'Jamaica: Clarendon',
'JM-02' => 'Jamaica: Hanover',
'JM-17' => 'Jamaica: Kingston',
'JM-04' => 'Jamaica: Manchester',
'JM-07' => 'Jamaica: Portland',
'JM-08' => 'Jamaica: Saint Andrew',
'JM-09' => 'Jamaica: Saint Ann',
'JM-10' => 'Jamaica: Saint Catherine',
'JM-11' => 'Jamaica: Saint Elizabeth',
'JM-12' => 'Jamaica: Saint James',
'JM-13' => 'Jamaica: Saint Mary',
'JM-14' => 'Jamaica: Saint Thomas',
'JM-15' => 'Jamaica: Trelawny',
'JM-16' => 'Jamaica: Westmoreland',

'--JP' => '','-JP' => 'Japan',
'JP-01' => 'Japan: Aichi',
'JP-02' => 'Japan: Akita',
'JP-03' => 'Japan: Aomori',
'JP-04' => 'Japan: Chiba',
'JP-05' => 'Japan: Ehime',
'JP-06' => 'Japan: Fukui',
'JP-07' => 'Japan: Fukuoka',
'JP-08' => 'Japan: Fukushima',
'JP-09' => 'Japan: Gifu',
'JP-10' => 'Japan: Gumma',
'JP-11' => 'Japan: Hiroshima',
'JP-12' => 'Japan: Hokkaido',
'JP-13' => 'Japan: Hyogo',
'JP-14' => 'Japan: Ibaraki',
'JP-15' => 'Japan: Ishikawa',
'JP-16' => 'Japan: Iwate',
'JP-17' => 'Japan: Kagawa',
'JP-18' => 'Japan: Kagoshima',
'JP-19' => 'Japan: Kanagawa',
'JP-20' => 'Japan: Kochi',
'JP-21' => 'Japan: Kumamoto',
'JP-22' => 'Japan: Kyoto',
'JP-23' => 'Japan: Mie',
'JP-24' => 'Japan: Miyagi',
'JP-25' => 'Japan: Miyazaki',
'JP-26' => 'Japan: Nagano',
'JP-27' => 'Japan: Nagasaki',
'JP-28' => 'Japan: Nara',
'JP-29' => 'Japan: Niigata',
'JP-30' => 'Japan: Oita',
'JP-31' => 'Japan: Okayama',
'JP-47' => 'Japan: Okinawa',
'JP-32' => 'Japan: Osaka',
'JP-33' => 'Japan: Saga',
'JP-34' => 'Japan: Saitama',
'JP-35' => 'Japan: Shiga',
'JP-36' => 'Japan: Shimane',
'JP-37' => 'Japan: Shizuoka',
'JP-38' => 'Japan: Tochigi',
'JP-39' => 'Japan: Tokushima',
'JP-40' => 'Japan: Tokyo',
'JP-41' => 'Japan: Tottori',
'JP-42' => 'Japan: Toyama',
'JP-43' => 'Japan: Wakayama',
'JP-44' => 'Japan: Yamagata',
'JP-45' => 'Japan: Yamaguchi',
'JP-46' => 'Japan: Yamanashi',

'--JO' => '','-JO' => 'Jordan',
'JO-02' => 'Jordan: Al Balqa\'',
'JO-09' => 'Jordan: Al Karak',
'JO-10' => 'Jordan: Al Mafraq',
'JO-16' => 'Jordan: Amman',
'JO-12' => 'Jordan: At Tafilah',
'JO-13' => 'Jordan: Az Zarqa',
'JO-14' => 'Jordan: Irbid',
'JO-07' => 'Jordan: Ma',

'--KZ' => '','-KZ' => 'Kazakhstan',
'KZ-02' => 'Kazakhstan: Almaty City',
'KZ-01' => 'Kazakhstan: Almaty',
'KZ-03' => 'Kazakhstan: Aqmola',
'KZ-04' => 'Kazakhstan: Aqt?be',
'KZ-05' => 'Kazakhstan: Astana',
'KZ-06' => 'Kazakhstan: Atyrau',
'KZ-08' => 'Kazakhstan: Bayqonyr',
'KZ-15' => 'Kazakhstan: East Kazakhstan',
'KZ-09' => 'Kazakhstan: Mangghystau',
'KZ-16' => 'Kazakhstan: North Kazakhstan',
'KZ-11' => 'Kazakhstan: Pavlodar',
'KZ-12' => 'Kazakhstan: Qaraghandy',
'KZ-13' => 'Kazakhstan: Qostanay',
'KZ-14' => 'Kazakhstan: Qyzylorda',
'KZ-10' => 'Kazakhstan: South Kazakhstan',
'KZ-07' => 'Kazakhstan: West Kazakhstan',
'KZ-17' => 'Kazakhstan: Zhambyl',

'--KE' => '','-KE' => 'Kenya',
'KE-01' => 'Kenya: Central',
'KE-02' => 'Kenya: Coast',
'KE-03' => 'Kenya: Eastern',
'KE-05' => 'Kenya: Nairobi Area',
'KE-06' => 'Kenya: North-Eastern',
'KE-07' => 'Kenya: Nyanza',
'KE-08' => 'Kenya: Rift Valley',
'KE-09' => 'Kenya: Western',

'--KI' => '','-KI' => 'Kiribati',
'KI-01' => 'Kiribati: Gilbert Islands',
'KI-02' => 'Kiribati: Line Islands',
'KI-03' => 'Kiribati: Phoenix Islands',

'--KW' => '','-KW' => 'Kuwait',
'KW-01' => 'Kuwait: Al Ahmadi',
'KW-05' => 'Kuwait: Al Jahra',
'KW-02' => 'Kuwait: Al Kuwayt',
'KW-03' => 'Kuwait: Hawalli',

'--KG' => '','-KG' => 'Kyrgyzstan',
'KG-09' => 'Kyrgyzstan: Batken',
'KG-01' => 'Kyrgyzstan: Bishkek',
'KG-02' => 'Kyrgyzstan: Chuy',
'KG-03' => 'Kyrgyzstan: Jalal-Abad',
'KG-04' => 'Kyrgyzstan: Naryn',
'KG-08' => 'Kyrgyzstan: Osh',
'KG-06' => 'Kyrgyzstan: Talas',
'KG-07' => 'Kyrgyzstan: Ysyk-Kol',

'--LA' => '','-LA' => 'Lao',
'LA-01' => 'Lao: Attapu',
'LA-02' => 'Lao: Champasak',
'LA-03' => 'Lao: Houaphan',
'LA-04' => 'Lao: Khammouan',
'LA-05' => 'Lao: Louang Namtha',
'LA-17' => 'Lao: Louangphrabang',
'LA-07' => 'Lao: Oudomxai',
'LA-08' => 'Lao: Phongsali',
'LA-09' => 'Lao: Saravan',
'LA-10' => 'Lao: Savannakhet',
'LA-11' => 'Lao: Vientiane',
'LA-13' => 'Lao: Xaignabouri',
'LA-14' => 'Lao: Xiangkhoang',

'--LV' => '','-LV' => 'Latvia',
'LV-01' => 'Latvia: Aizkraukles',
'LV-02' => 'Latvia: Aluksnes',
'LV-03' => 'Latvia: Balvu',
'LV-04' => 'Latvia: Bauskas',
'LV-05' => 'Latvia: Césu',
'LV-06' => 'Latvia: Daugavpils',
'LV-07' => 'Latvia: Daugavpils',
'LV-08' => 'Latvia: Dobeles',
'LV-09' => 'Latvia: Gulbenes',
'LV-10' => 'Latvia: Jékabpils',
'LV-11' => 'Latvia: Jelgava',
'LV-12' => 'Latvia: Jelgavas',
'LV-13' => 'Latvia: Jurmala',
'LV-14' => 'Latvia: Kráslavas',
'LV-15' => 'Latvia: Kuldigas',
'LV-16' => 'Latvia: Liepája',
'LV-17' => 'Latvia: Liepájas',
'LV-18' => 'Latvia: Limbazu',
'LV-19' => 'Latvia: Ludzas',
'LV-20' => 'Latvia: Madonas',
'LV-21' => 'Latvia: Ogres',
'LV-22' => 'Latvia: Preilu',
'LV-23' => 'Latvia: Rézekne',
'LV-24' => 'Latvia: Rézeknes',
'LV-25' => 'Latvia: Riga',
'LV-26' => 'Latvia: Rigas',
'LV-27' => 'Latvia: Saldus',
'LV-28' => 'Latvia: Talsu',
'LV-29' => 'Latvia: Tukuma',
'LV-30' => 'Latvia: Valkas',
'LV-31' => 'Latvia: Valmieras',
'LV-32' => 'Latvia: Ventspils',
'LV-33' => 'Latvia: Ventspils',

'--LB' => '','-LB' => 'Lebanon',
'LB-01' => 'Lebanon: Beqaa',
'LB-04' => 'Lebanon: Beyrouth',
'LB-03' => 'Lebanon: Liban-Nord',
'LB-06' => 'Lebanon: Liban-Sud',
'LB-05' => 'Lebanon: Mont-Liban',
'LB-07' => 'Lebanon: Nabatiye',

'--LS' => '','-LS' => 'Lesotho',
'LS-10' => 'Lesotho: Berea',
'LS-11' => 'Lesotho: Butha-Buthe',
'LS-12' => 'Lesotho: Leribe',
'LS-13' => 'Lesotho: Mafeteng',
'LS-14' => 'Lesotho: Maseru',
'LS-15' => 'Lesotho: Mohales Hoek',
'LS-16' => 'Lesotho: Mokhotlong',
'LS-17' => 'Lesotho: Qachas Nek',
'LS-18' => 'Lesotho: Quthing',
'LS-19' => 'Lesotho: Thaba-Tseka',

'--LR' => '','-LR' => 'Liberia',
'LR-01' => 'Liberia: Bong',
'LR-11' => 'Liberia: Grand Bassa',
'LR-04' => 'Liberia: Grand Cape Mount',
'LR-02' => 'Liberia: Grand Jide',
'LR-05' => 'Liberia: Lofa',
'LR-06' => 'Liberia: Maryland',
'LR-07' => 'Liberia: Monrovia',
'LR-14' => 'Liberia: Montserrado',
'LR-09' => 'Liberia: Nimba',
'LR-10' => 'Liberia: Sino',

'--LY' => '','-LY' => 'Libyan Arab Jamahiriya',
'LY-47' => 'Libyan Arab Jamahiriya: Ajdabiya',
'LY-48' => 'Libyan Arab Jamahiriya: Al Fatih',
'LY-49' => 'Libyan Arab Jamahiriya: Al Jabal al Akhdar',
'LY-05' => 'Libyan Arab Jamahiriya: Al Jufrah',
'LY-50' => 'Libyan Arab Jamahiriya: Al Khums',
'LY-08' => 'Libyan Arab Jamahiriya: Al Kufrah',
'LY-03' => 'Libyan Arab Jamahiriya: Al',
'LY-51' => 'Libyan Arab Jamahiriya: An Nuqat al Khams',
'LY-13' => 'Libyan Arab Jamahiriya: Ash Shati\'',
'LY-52' => 'Libyan Arab Jamahiriya: Awbari',
'LY-53' => 'Libyan Arab Jamahiriya: Az Zawiyah',
'LY-54' => 'Libyan Arab Jamahiriya: Banghazi',
'LY-55' => 'Libyan Arab Jamahiriya: Darnah',
'LY-56' => 'Libyan Arab Jamahiriya: Ghadamis',
'LY-57' => 'Libyan Arab Jamahiriya: Gharyan',
'LY-58' => 'Libyan Arab Jamahiriya: Misratah',
'LY-30' => 'Libyan Arab Jamahiriya: Murzuq',
'LY-34' => 'Libyan Arab Jamahiriya: Sabha',
'LY-59' => 'Libyan Arab Jamahiriya: Sawfajjin',
'LY-60' => 'Libyan Arab Jamahiriya: Surt',
'LY-61' => 'Libyan Arab Jamahiriya: Tarabulus',
'LY-41' => 'Libyan Arab Jamahiriya: Tarhunah',
'LY-42' => 'Libyan Arab Jamahiriya: Tubruq',
'LY-62' => 'Libyan Arab Jamahiriya: Yafran',
'LY-45' => 'Libyan Arab Jamahiriya: Zlitan',

'--LI' => '','-LI' => 'Liechtenstein',
'LI-01' => 'Liechtenstein: Balzers',
'LI-02' => 'Liechtenstein: Eschen',
'LI-03' => 'Liechtenstein: Gamprin',
'LI-04' => 'Liechtenstein: Mauren',
'LI-05' => 'Liechtenstein: Planken',
'LI-06' => 'Liechtenstein: Ruggell',
'LI-07' => 'Liechtenstein: Schaan',
'LI-08' => 'Liechtenstein: Schellenberg',
'LI-09' => 'Liechtenstein: Triesen',
'LI-10' => 'Liechtenstein: Triesenberg',
'LI-11' => 'Liechtenstein: Vaduz',

'--LT' => '','-LT' => 'Lithuania',
'LT-56' => 'Lithuania: Alytaus Apskritis',
'LT-57' => 'Lithuania: Kauno Apskritis',
'LT-58' => 'Lithuania: Klaipedos Apskritis',
'LT-59' => 'Lithuania: Marijampoles Apskritis',
'LT-60' => 'Lithuania: Panevezio Apskritis',
'LT-61' => 'Lithuania: Siauliu Apskritis',
'LT-62' => 'Lithuania: Taurages Apskritis',
'LT-63' => 'Lithuania: Telsiu Apskritis',
'LT-64' => 'Lithuania: Utenos Apskritis',
'LT-65' => 'Lithuania: Vilniaus Apskritis',

'--LU' => '','-LU' => 'Luxembourg',
'LU-01' => 'Luxembourg: Diekirch',
'LU-02' => 'Luxembourg: Grevenmacher',
'LU-03' => 'Luxembourg: Luxembourg',

'--MO' => '','-MO' => 'Macau',
'MO-01' => 'Macau: Ilhas',
'MO-02' => 'Macau: Macau',

'--MK' => '','-MK' => 'Macedonia',
'MK-01' => 'Macedonia: Aracinovo',
'MK-02' => 'Macedonia: Bac',
'MK-03' => 'Macedonia: Belcista',
'MK-04' => 'Macedonia: Berovo',
'MK-05' => 'Macedonia: Bistrica',
'MK-06' => 'Macedonia: Bitola',
'MK-07' => 'Macedonia: Blatec',
'MK-08' => 'Macedonia: Bogdanci',
'MK-09' => 'Macedonia: Bogomila',
'MK-10' => 'Macedonia: Bogovinje',
'MK-11' => 'Macedonia: Bosilovo',
'MK-12' => 'Macedonia: Brvenica',
'MK-13' => 'Macedonia: Cair',
'MK-14' => 'Macedonia: Capari',
'MK-15' => 'Macedonia: Caska',
'MK-16' => 'Macedonia: Cegrane',
'MK-18' => 'Macedonia: Centar Zupa',
'MK-17' => 'Macedonia: Centar',
'MK-19' => 'Macedonia: Cesinovo',
'MK-20' => 'Macedonia: Cucer-Sandevo',
'MK-21' => 'Macedonia: Debar',
'MK-22' => 'Macedonia: Delcevo',
'MK-23' => 'Macedonia: Delogozdi',
'MK-24' => 'Macedonia: Demir Hisar',
'MK-25' => 'Macedonia: Demir Kapija',
'MK-26' => 'Macedonia: Dobrusevo',
'MK-27' => 'Macedonia: Dolna Banjica',
'MK-28' => 'Macedonia: Dolneni',
'MK-29' => 'Macedonia: Dorce Petrov',
'MK-30' => 'Macedonia: Drugovo',
'MK-31' => 'Macedonia: Dzepciste',
'MK-32' => 'Macedonia: Gazi Baba',
'MK-33' => 'Macedonia: Gevgelija',
'MK-34' => 'Macedonia: Gostivar',
'MK-35' => 'Macedonia: Gradsko',
'MK-36' => 'Macedonia: Ilinden',
'MK-37' => 'Macedonia: Izvor',
'MK-38' => 'Macedonia: Jegunovce',
'MK-39' => 'Macedonia: Kamenjane',
'MK-40' => 'Macedonia: Karbinci',
'MK-41' => 'Macedonia: Karpos',
'MK-42' => 'Macedonia: Kavadarci',
'MK-43' => 'Macedonia: Kicevo',
'MK-44' => 'Macedonia: Kisela Voda',
'MK-45' => 'Macedonia: Klecevce',
'MK-46' => 'Macedonia: Kocani',
'MK-47' => 'Macedonia: Konce',
'MK-48' => 'Macedonia: Kondovo',
'MK-49' => 'Macedonia: Konopiste',
'MK-50' => 'Macedonia: Kosel',
'MK-51' => 'Macedonia: Kratovo',
'MK-52' => 'Macedonia: Kriva Palanka',
'MK-53' => 'Macedonia: Krivogastani',
'MK-54' => 'Macedonia: Krusevo',
'MK-55' => 'Macedonia: Kuklis',
'MK-56' => 'Macedonia: Kukurecani',
'MK-57' => 'Macedonia: Kumanovo',
'MK-58' => 'Macedonia: Labunista',
'MK-59' => 'Macedonia: Lipkovo',
'MK-60' => 'Macedonia: Lozovo',
'MK-61' => 'Macedonia: Lukovo',
'MK-62' => 'Macedonia: Makedonska Kamenica',
'MK-63' => 'Macedonia: Makedonski Brod',
'MK-64' => 'Macedonia: Mavrovi Anovi',
'MK-65' => 'Macedonia: Meseista',
'MK-66' => 'Macedonia: Miravci',
'MK-67' => 'Macedonia: Mogila',
'MK-68' => 'Macedonia: Murtino',
'MK-69' => 'Macedonia: Negotino',
'MK-70' => 'Macedonia: Negotino-Polosko',
'MK-71' => 'Macedonia: Novaci',
'MK-72' => 'Macedonia: Novo Selo',
'MK-73' => 'Macedonia: Oblesevo',
'MK-74' => 'Macedonia: Ohrid',
'MK-75' => 'Macedonia: Orasac',
'MK-76' => 'Macedonia: Orizari',
'MK-77' => 'Macedonia: Oslomej',
'MK-78' => 'Macedonia: Pehcevo',
'MK-79' => 'Macedonia: Petrovec',
'MK-80' => 'Macedonia: Plasnica',
'MK-81' => 'Macedonia: Podares',
'MK-82' => 'Macedonia: Prilep',
'MK-83' => 'Macedonia: Probistip',
'MK-84' => 'Macedonia: Radovis',
'MK-85' => 'Macedonia: Rankovce',
'MK-86' => 'Macedonia: Resen',
'MK-87' => 'Macedonia: Rosoman',
'MK-88' => 'Macedonia: Rostusa',
'MK-89' => 'Macedonia: Samokov',
'MK-90' => 'Macedonia: Saraj',
'MK-91' => 'Macedonia: Sipkovica',
'MK-92' => 'Macedonia: Sopiste',
'MK-93' => 'Macedonia: Sopotnica',
'MK-94' => 'Macedonia: Srbinovo',
'MK-96' => 'Macedonia: Star Dojran',
'MK-95' => 'Macedonia: Staravina',
'MK-97' => 'Macedonia: Staro Nagoricane',
'MK-98' => 'Macedonia: Stip',
'MK-99' => 'Macedonia: Struga',
'MK-A1' => 'Macedonia: Strumica',
'MK-A2' => 'Macedonia: Studenicani',
'MK-A3' => 'Macedonia: Suto Orizari',
'MK-A4' => 'Macedonia: Sveti Nikole',
'MK-A5' => 'Macedonia: Tearce',
'MK-A6' => 'Macedonia: Tetovo',
'MK-A7' => 'Macedonia: Topolcani',
'MK-A8' => 'Macedonia: Valandovo',
'MK-A9' => 'Macedonia: Vasilevo',
'MK-B1' => 'Macedonia: Veles',
'MK-B2' => 'Macedonia: Velesta',
'MK-B3' => 'Macedonia: Vevcani',
'MK-B4' => 'Macedonia: Vinica',
'MK-B5' => 'Macedonia: Vitoliste',
'MK-B6' => 'Macedonia: Vranestica',
'MK-B7' => 'Macedonia: Vrapciste',
'MK-B8' => 'Macedonia: Vratnica',
'MK-B9' => 'Macedonia: Vrutok',
'MK-C1' => 'Macedonia: Zajas',
'MK-C2' => 'Macedonia: Zelenikovo',
'MK-C3' => 'Macedonia: Zelino',
'MK-C4' => 'Macedonia: Zitose',
'MK-C5' => 'Macedonia: Zletovo',
'MK-C6' => 'Macedonia: Zrnovci',

'--MG' => '','-MG' => 'Madagascar',
'MG-05' => 'Madagascar: Antananarivo',
'MG-01' => 'Madagascar: Antsiranana',
'MG-02' => 'Madagascar: Fianarantsoa',
'MG-03' => 'Madagascar: Mahajanga',
'MG-04' => 'Madagascar: Toamasina',
'MG-06' => 'Madagascar: Toliara',

'--MW' => '','-MW' => 'Malawi',
'MW-26' => 'Malawi: Balaka',
'MW-24' => 'Malawi: Blantyre',
'MW-02' => 'Malawi: Chikwawa',
'MW-03' => 'Malawi: Chiradzulu',
'MW-04' => 'Malawi: Chitipa',
'MW-06' => 'Malawi: Dedza',
'MW-07' => 'Malawi: Dowa',
'MW-08' => 'Malawi: Karonga',
'MW-09' => 'Malawi: Kasungu',
'MW-27' => 'Malawi: Likoma',
'MW-11' => 'Malawi: Lilongwe',
'MW-28' => 'Malawi: Machinga',
'MW-12' => 'Malawi: Mangochi',
'MW-13' => 'Malawi: Mchinji',
'MW-29' => 'Malawi: Mulanje',
'MW-25' => 'Malawi: Mwanza',
'MW-15' => 'Malawi: Mzimba',
'MW-17' => 'Malawi: Nkhata Bay',
'MW-18' => 'Malawi: Nkhotakota',
'MW-19' => 'Malawi: Nsanje',
'MW-16' => 'Malawi: Ntcheu',
'MW-20' => 'Malawi: Ntchisi',
'MW-30' => 'Malawi: Phalombe',
'MW-21' => 'Malawi: Rumphi',
'MW-22' => 'Malawi: Salima',
'MW-05' => 'Malawi: Thyolo',
'MW-23' => 'Malawi: Zomba',

'--MY' => '','-MY' => 'Malaysia',
'MY-01' => 'Malaysia: Johor',
'MY-02' => 'Malaysia: Kedah',
'MY-03' => 'Malaysia: Kelantan',
'MY-15' => 'Malaysia: Labuan',
'MY-04' => 'Malaysia: Melaka',
'MY-05' => 'Malaysia: Negeri Sembilan',
'MY-06' => 'Malaysia: Pahang',
'MY-07' => 'Malaysia: Perak',
'MY-08' => 'Malaysia: Perlis',
'MY-09' => 'Malaysia: Pulau Pinang',
'MY-16' => 'Malaysia: Sabah',
'MY-11' => 'Malaysia: Sarawak',
'MY-12' => 'Malaysia: Selangor',
'MY-13' => 'Malaysia: Terengganu',
'MY-14' => 'Malaysia: Wilayah Persekutuan',

'--MV' => '','-MV' => 'Maldives',
'MV-02' => 'Maldives: Aliff',
'MV-20' => 'Maldives: Baa',
'MV-17' => 'Maldives: Daalu',
'MV-14' => 'Maldives: Faafu',
'MV-27' => 'Maldives: Gaafu Aliff',
'MV-28' => 'Maldives: Gaafu Daalu',
'MV-07' => 'Maldives: Haa Aliff',
'MV-23' => 'Maldives: Haa Daalu',
'MV-26' => 'Maldives: Kaafu',
'MV-05' => 'Maldives: Laamu',
'MV-03' => 'Maldives: Laviyani',
'MV-12' => 'Maldives: Meemu',
'MV-29' => 'Maldives: Naviyani',
'MV-25' => 'Maldives: Noonu',
'MV-13' => 'Maldives: Raa',
'MV-01' => 'Maldives: Seenu',
'MV-24' => 'Maldives: Shaviyani',
'MV-08' => 'Maldives: Thaa',
'MV-04' => 'Maldives: Waavu',

'--ML' => '','-ML' => 'Mali',
'ML-01' => 'Mali: Bamako',
'ML-09' => 'Mali: Gao',
'ML-03' => 'Mali: Kayes',
'ML-10' => 'Mali: Kidal',
'ML-07' => 'Mali: Koulikoro',
'ML-04' => 'Mali: Mopti',
'ML-05' => 'Mali: Segou',
'ML-06' => 'Mali: Sikasso',
'ML-08' => 'Mali: Tombouctou',

'--MR' => '','-MR' => 'Mauritania',
'MR-07' => 'Mauritania: Adrar',
'MR-03' => 'Mauritania: Assaba',
'MR-05' => 'Mauritania: Brakna',
'MR-08' => 'Mauritania: Dakhlet Nouadhibou',
'MR-04' => 'Mauritania: Gorgol',
'MR-10' => 'Mauritania: Guidimaka',
'MR-01' => 'Mauritania: Hodh Ech Chargui',
'MR-02' => 'Mauritania: Hodh El Gharbi',
'MR-12' => 'Mauritania: Inchiri',
'MR-09' => 'Mauritania: Tagant',
'MR-11' => 'Mauritania: Tiris Zemmour',
'MR-06' => 'Mauritania: Trarza',

'--MU' => '','-MU' => 'Mauritius',
'MU-21' => 'Mauritius: Agalega Islands',
'MU-12' => 'Mauritius: Black River',
'MU-22' => 'Mauritius: Cargados Carajos',
'MU-13' => 'Mauritius: Flacq',
'MU-14' => 'Mauritius: Grand Port',
'MU-15' => 'Mauritius: Moka',
'MU-16' => 'Mauritius: Pamplemousses',
'MU-17' => 'Mauritius: Plaines Wilhems',
'MU-18' => 'Mauritius: Port Louis',
'MU-19' => 'Mauritius: Riviere du Rempart',
'MU-23' => 'Mauritius: Rodrigues',
'MU-20' => 'Mauritius: Savanne',

'--MX' => '','-MX' => 'Mexico',
'MX-01' => 'Mexico: Aguascalientes',
'MX-03' => 'Mexico: Baja California Sur',
'MX-02' => 'Mexico: Baja California',
'MX-04' => 'Mexico: Campeche',
'MX-05' => 'Mexico: Chiapas',
'MX-06' => 'Mexico: Chihuahua',
'MX-07' => 'Mexico: Coahuila de Zaragoza',
'MX-08' => 'Mexico: Colima',
'MX-09' => 'Mexico: Distrito Federal',
'MX-10' => 'Mexico: Durango',
'MX-11' => 'Mexico: Guanajuato',
'MX-12' => 'Mexico: Guerrero',
'MX-13' => 'Mexico: Hidalgo',
'MX-14' => 'Mexico: Jalisco',
'MX-15' => 'Mexico: Mexico',
'MX-16' => 'Mexico: Michoacan de Ocampo',
'MX-17' => 'Mexico: Morelos',
'MX-18' => 'Mexico: Nayarit',
'MX-19' => 'Mexico: Nuevo Leon',
'MX-20' => 'Mexico: Oaxaca',
'MX-21' => 'Mexico: Puebla',
'MX-22' => 'Mexico: Queretaro de Arteaga',
'MX-23' => 'Mexico: Quintana Roo',
'MX-24' => 'Mexico: San Luis Potosi',
'MX-25' => 'Mexico: Sinaloa',
'MX-26' => 'Mexico: Sonora',
'MX-27' => 'Mexico: Tabasco',
'MX-28' => 'Mexico: Tamaulipas',
'MX-29' => 'Mexico: Tlaxcala',
'MX-30' => 'Mexico: Veracruz-Llave',
'MX-31' => 'Mexico: Yucatan',
'MX-32' => 'Mexico: Zacatecas',

'--FM' => '','-FM' => 'Micronesia',
'FM-03' => 'Micronesia: Chuuk',
'FM-01' => 'Micronesia: Kosrae',
'FM-02' => 'Micronesia: Pohnpei',
'FM-04' => 'Micronesia: Yap',

'--MD' => '','-MD' => 'Moldova',
'MD-46' => 'Moldova: Balti',
'MD-47' => 'Moldova: Cahul',
'MD-48' => 'Moldova: Chisinau',
'MD-50' => 'Moldova: Edinet',
'MD-51' => 'Moldova: Gagauzia',
'MD-52' => 'Moldova: Lapusna',
'MD-53' => 'Moldova: Orhei',
'MD-54' => 'Moldova: Soroca',
'MD-49' => 'Moldova: Stinga Nistrului',
'MD-55' => 'Moldova: Tighina',
'MD-56' => 'Moldova: Ungheni',

'--MC' => '','-MC' => 'Monaco',
'MC-01' => 'Monaco: La Condamine',
'MC-02' => 'Monaco: Monaco',
'MC-03' => 'Monaco: Monte-Carlo',

'--MN' => '','-MN' => 'Mongolia',
'MN-01' => 'Mongolia: Arhangay',
'MN-02' => 'Mongolia: Bayanhongor',
'MN-03' => 'Mongolia: Bayan-Olgiy',
'MN-21' => 'Mongolia: Bulgan',
'MN-23' => 'Mongolia: Darhan Uul',
'MN-05' => 'Mongolia: Darhan',
'MN-06' => 'Mongolia: Dornod',
'MN-07' => 'Mongolia: Dornogovi',
'MN-08' => 'Mongolia: Dundgovi',
'MN-09' => 'Mongolia: Dzavhan',
'MN-22' => 'Mongolia: Erdenet',
'MN-10' => 'Mongolia: Govi-Altay',
'MN-24' => 'Mongolia: Govi-Sumber',
'MN-11' => 'Mongolia: Hentiy',
'MN-12' => 'Mongolia: Hovd',
'MN-13' => 'Mongolia: Hovsgol',
'MN-14' => 'Mongolia: Omnogovi',
'MN-25' => 'Mongolia: Orhon',
'MN-15' => 'Mongolia: Ovorhangay',
'MN-16' => 'Mongolia: Selenge',
'MN-17' => 'Mongolia: Suhbaatar',
'MN-18' => 'Mongolia: Tov',
'MN-20' => 'Mongolia: Ulaanbaatar',
'MN-19' => 'Mongolia: Uvs',

'--MS' => '','-MS' => 'Montserrat',
'MS-01' => 'Montserrat: Saint Anthony',
'MS-02' => 'Montserrat: Saint Georges',
'MS-03' => 'Montserrat: Saint Peter',

'--MA' => '','-MA' => 'Morocco',
'MA-01' => 'Morocco: Agadir',
'MA-02' => 'Morocco: Al Hoceima',
'MA-03' => 'Morocco: Azilal',
'MA-04' => 'Morocco: Ben Slimane',
'MA-05' => 'Morocco: Beni Mellal',
'MA-06' => 'Morocco: Boulemane',
'MA-07' => 'Morocco: Casablanca',
'MA-08' => 'Morocco: Chaouen',
'MA-09' => 'Morocco: El Jadida',
'MA-10' => 'Morocco: El Kelaa des Srarhna',
'MA-11' => 'Morocco: Er Rachidia',
'MA-12' => 'Morocco: Essaouira',
'MA-13' => 'Morocco: Fes',
'MA-14' => 'Morocco: Figuig',
'MA-33' => 'Morocco: Guelmim',
'MA-34' => 'Morocco: Ifrane',
'MA-15' => 'Morocco: Kenitra',
'MA-16' => 'Morocco: Khemisset',
'MA-17' => 'Morocco: Khenifra',
'MA-18' => 'Morocco: Khouribga',
'MA-35' => 'Morocco: Laayoune',
'MA-41' => 'Morocco: Larache',
'MA-19' => 'Morocco: Marrakech',
'MA-20' => 'Morocco: Meknes',
'MA-21' => 'Morocco: Nador',
'MA-22' => 'Morocco: Ouarzazate',
'MA-23' => 'Morocco: Oujda',
'MA-24' => 'Morocco: Rabat-Sale',
'MA-25' => 'Morocco: Safi',
'MA-26' => 'Morocco: Settat',
'MA-38' => 'Morocco: Sidi Kacem',
'MA-27' => 'Morocco: Tanger',
'MA-36' => 'Morocco: Tan-Tan',
'MA-37' => 'Morocco: Taounate',
'MA-39' => 'Morocco: Taroudannt',
'MA-29' => 'Morocco: Tata',
'MA-30' => 'Morocco: Taza',
'MA-40' => 'Morocco: Tetouan',
'MA-32' => 'Morocco: Tiznit',

'--MZ' => '','-MZ' => 'Mozambique',
'MZ-01' => 'Mozambique: Cabo Delgado',
'MZ-02' => 'Mozambique: Gaza',
'MZ-03' => 'Mozambique: Inhambane',
'MZ-10' => 'Mozambique: Manica',
'MZ-04' => 'Mozambique: Maputo',
'MZ-06' => 'Mozambique: Nampula',
'MZ-07' => 'Mozambique: Niassa',
'MZ-05' => 'Mozambique: Sofala',
'MZ-08' => 'Mozambique: Tete',
'MZ-09' => 'Mozambique: Zambezia',

'--MM' => '','-MM' => 'Myanmar',
'MM-02' => 'Myanmar: Chin State',
'MM-03' => 'Myanmar: Irrawaddy',
'MM-04' => 'Myanmar: Kachin State',
'MM-05' => 'Myanmar: Karan State',
'MM-06' => 'Myanmar: Kayah State',
'MM-07' => 'Myanmar: Magwe',
'MM-08' => 'Myanmar: Mandalay',
'MM-13' => 'Myanmar: Mon State',
'MM-09' => 'Myanmar: Pegu',
'MM-01' => 'Myanmar: Rakhine State',
'MM-14' => 'Myanmar: Rangoon',
'MM-10' => 'Myanmar: Sagaing',
'MM-11' => 'Myanmar: Shan State',
'MM-12' => 'Myanmar: Tenasserim',
'MM-17' => 'Myanmar: Yangon',

'--NA' => '','-NA' => 'Namibia',
'NA-01' => 'Namibia: Bethanien',
'NA-03' => 'Namibia: Boesmanland',
'NA-02' => 'Namibia: Caprivi Oos',
'NA-28' => 'Namibia: Caprivi',
'NA-22' => 'Namibia: Damaraland',
'NA-29' => 'Namibia: Erongo',
'NA-04' => 'Namibia: Gobabis',
'NA-05' => 'Namibia: Grootfontein',
'NA-30' => 'Namibia: Hardap',
'NA-23' => 'Namibia: Hereroland Oos',
'NA-24' => 'Namibia: Hereroland Wes',
'NA-06' => 'Namibia: Kaokoland',
'NA-31' => 'Namibia: Karas',
'NA-20' => 'Namibia: Karasburg',
'NA-07' => 'Namibia: Karibib',
'NA-25' => 'Namibia: Kavango',
'NA-08' => 'Namibia: Keetmanshoop',
'NA-32' => 'Namibia: Kunene',
'NA-09' => 'Namibia: Luderitz',
'NA-10' => 'Namibia: Maltahohe',
'NA-26' => 'Namibia: Mariental',
'NA-27' => 'Namibia: Namaland',
'NA-33' => 'Namibia: Ohangwena',
'NA-11' => 'Namibia: Okahandja',
'NA-34' => 'Namibia: Okavango',
'NA-35' => 'Namibia: Omaheke',
'NA-12' => 'Namibia: Omaruru',
'NA-36' => 'Namibia: Omusati',
'NA-37' => 'Namibia: Oshana',
'NA-38' => 'Namibia: Oshikoto',
'NA-13' => 'Namibia: Otjiwarongo',
'NA-39' => 'Namibia: Otjozondjupa',
'NA-14' => 'Namibia: Outjo',
'NA-15' => 'Namibia: Owambo',
'NA-16' => 'Namibia: Rehoboth',
'NA-17' => 'Namibia: Swakopmund',
'NA-18' => 'Namibia: Tsumeb',
'NA-21' => 'Namibia: Windhoek',

'--NR' => '','-NR' => 'Nauru',
'NR-01' => 'Nauru: Aiwo',
'NR-02' => 'Nauru: Anabar',
'NR-03' => 'Nauru: Anetan',
'NR-04' => 'Nauru: Anibare',
'NR-05' => 'Nauru: Baiti',
'NR-06' => 'Nauru: Boe',
'NR-07' => 'Nauru: Buada',
'NR-08' => 'Nauru: Denigomodu',
'NR-09' => 'Nauru: Ewa',
'NR-10' => 'Nauru: Ijuw',
'NR-11' => 'Nauru: Meneng',
'NR-12' => 'Nauru: Nibok',
'NR-13' => 'Nauru: Uaboe',
'NR-14' => 'Nauru: Yaren',

'--NP' => '','-NP' => 'Nepal',
'NP-01' => 'Nepal: Bagmati',
'NP-02' => 'Nepal: Bheri',
'NP-03' => 'Nepal: Dhawalagiri',
'NP-04' => 'Nepal: Gandaki',
'NP-05' => 'Nepal: Janakpur',
'NP-06' => 'Nepal: Karnali',
'NP-07' => 'Nepal: Kosi',
'NP-08' => 'Nepal: Lumbini',
'NP-09' => 'Nepal: Mahakali',
'NP-10' => 'Nepal: Mechi',
'NP-11' => 'Nepal: Narayani',
'NP-12' => 'Nepal: Rapti',
'NP-13' => 'Nepal: Sagarmatha',
'NP-14' => 'Nepal: Seti',

'--NL' => '','-NL' => 'Netherlands',
'NL-01' => 'Netherlands: Drenthe',
'NL-16' => 'Netherlands: Flevoland',
'NL-02' => 'Netherlands: Friesland',
'NL-03' => 'Netherlands: Gelderland',
'NL-04' => 'Netherlands: Groningen',
'NL-05' => 'Netherlands: Limburg',
'NL-06' => 'Netherlands: Noord-Brabant',
'NL-07' => 'Netherlands: Noord-Holland',
'NL-15' => 'Netherlands: Overijssel',
'NL-09' => 'Netherlands: Utrecht',
'NL-10' => 'Netherlands: Zeeland',
'NL-11' => 'Netherlands: Zuid-Holland',

'--NZ' => '','-NZ' => 'New Zealand',
'NZ-01' => 'New Zealand: Akaroa',
'NZ-03' => 'New Zealand: Amuri',
'NZ-04' => 'New Zealand: Ashburton',
'NZ-07' => 'New Zealand: Bay of Islands',
'NZ-08' => 'New Zealand: Bruce',
'NZ-09' => 'New Zealand: Buller',
'NZ-10' => 'New Zealand: Chatham Islands',
'NZ-11' => 'New Zealand: Cheviot',
'NZ-12' => 'New Zealand: Clifton',
'NZ-13' => 'New Zealand: Clutha',
'NZ-14' => 'New Zealand: Cook',
'NZ-16' => 'New Zealand: Dannevirke',
'NZ-17' => 'New Zealand: Egmont',
'NZ-18' => 'New Zealand: Eketahuna',
'NZ-19' => 'New Zealand: Ellesmere',
'NZ-20' => 'New Zealand: Eltham',
'NZ-21' => 'New Zealand: Eyre',
'NZ-22' => 'New Zealand: Featherston',
'NZ-24' => 'New Zealand: Franklin',
'NZ-26' => 'New Zealand: Golden Bay',
'NZ-27' => 'New Zealand: Great Barrier Island',
'NZ-28' => 'New Zealand: Grey',
'NZ-29' => 'New Zealand: Hauraki Plains',
'NZ-30' => 'New Zealand: Hawera',
'NZ-31' => 'New Zealand: Hawke\'s Bay',
'NZ-32' => 'New Zealand: Heathcote',
'NZ-D9' => 'New Zealand: Hikurangi',
'NZ-33' => 'New Zealand: Hobson',
'NZ-34' => 'New Zealand: Hokianga',
'NZ-35' => 'New Zealand: Horowhenua',
'NZ-D4' => 'New Zealand: Hurunui',
'NZ-36' => 'New Zealand: Hutt',
'NZ-37' => 'New Zealand: Inangahua',
'NZ-38' => 'New Zealand: Inglewood',
'NZ-39' => 'New Zealand: Kaikoura',
'NZ-40' => 'New Zealand: Kairanga',
'NZ-41' => 'New Zealand: Kiwitea',
'NZ-43' => 'New Zealand: Lake',
'NZ-45' => 'New Zealand: Mackenzie',
'NZ-46' => 'New Zealand: Malvern',
'NZ-E1' => 'New Zealand: Manaia',
'NZ-47' => 'New Zealand: Manawatu',
'NZ-48' => 'New Zealand: Mangonui',
'NZ-49' => 'New Zealand: Maniototo',
'NZ-50' => 'New Zealand: Marlborough',
'NZ-51' => 'New Zealand: Masterton',
'NZ-52' => 'New Zealand: Matamata',
'NZ-53' => 'New Zealand: Mount Herbert',
'NZ-54' => 'New Zealand: Ohinemuri',
'NZ-55' => 'New Zealand: Opotiki',
'NZ-56' => 'New Zealand: Oroua',
'NZ-57' => 'New Zealand: Otamatea',
'NZ-58' => 'New Zealand: Otorohanga',
'NZ-59' => 'New Zealand: Oxford',
'NZ-60' => 'New Zealand: Pahiatua',
'NZ-61' => 'New Zealand: Paparua',
'NZ-63' => 'New Zealand: Patea',
'NZ-65' => 'New Zealand: Piako',
'NZ-66' => 'New Zealand: Pohangina',
'NZ-67' => 'New Zealand: Raglan',
'NZ-68' => 'New Zealand: Rangiora',
'NZ-69' => 'New Zealand: Rangitikei',
'NZ-70' => 'New Zealand: Rodney',
'NZ-71' => 'New Zealand: Rotorua',
'NZ-E2' => 'New Zealand: Runanga',
'NZ-E3' => 'New Zealand: Saint Kilda',
'NZ-D5' => 'New Zealand: Silverpeaks',
'NZ-72' => 'New Zealand: Southland',
'NZ-73' => 'New Zealand: Stewart Island',
'NZ-74' => 'New Zealand: Stratford',
'NZ-D6' => 'New Zealand: Strathallan',
'NZ-76' => 'New Zealand: Taranaki',
'NZ-77' => 'New Zealand: Taumarunui',
'NZ-78' => 'New Zealand: Taupo',
'NZ-79' => 'New Zealand: Tauranga',
'NZ-E4' => 'New Zealand: Thames-Coromandel',
'NZ-81' => 'New Zealand: Tuapeka',
'NZ-82' => 'New Zealand: Vincent',
'NZ-83' => 'New Zealand: Waiapu',
'NZ-D8' => 'New Zealand: Waiheke',
'NZ-84' => 'New Zealand: Waihemo',
'NZ-85' => 'New Zealand: Waikato',
'NZ-86' => 'New Zealand: Waikohu',
'NZ-88' => 'New Zealand: Waimairi',
'NZ-89' => 'New Zealand: Waimarino',
'NZ-91' => 'New Zealand: Waimate West',
'NZ-90' => 'New Zealand: Waimate',
'NZ-92' => 'New Zealand: Waimea',
'NZ-93' => 'New Zealand: Waipa',
'NZ-95' => 'New Zealand: Waipawa',
'NZ-96' => 'New Zealand: Waipukurau',
'NZ-97' => 'New Zealand: Wairarapa South',
'NZ-98' => 'New Zealand: Wairewa',
'NZ-99' => 'New Zealand: Wairoa',
'NZ-A4' => 'New Zealand: Waitaki',
'NZ-A6' => 'New Zealand: Waitomo',
'NZ-A8' => 'New Zealand: Waitotara',
'NZ-E6' => 'New Zealand: Wallace',
'NZ-B2' => 'New Zealand: Wanganui',
'NZ-E5' => 'New Zealand: Waverley',
'NZ-B3' => 'New Zealand: Westland',
'NZ-B4' => 'New Zealand: Whakatane',
'NZ-A1' => 'New Zealand: Whangarei',
'NZ-A2' => 'New Zealand: Whangaroa',
'NZ-A3' => 'New Zealand: Woodmark',

'--NI' => '','-NI' => 'Nicaragua',
'NI-01' => 'Nicaragua: Boaco',
'NI-02' => 'Nicaragua: Carazo',
'NI-03' => 'Nicaragua: Chinandega',
'NI-04' => 'Nicaragua: Chontales',
'NI-05' => 'Nicaragua: Esteli',
'NI-06' => 'Nicaragua: Granada',
'NI-07' => 'Nicaragua: Jinotega',
'NI-08' => 'Nicaragua: Leon',
'NI-09' => 'Nicaragua: Madriz',
'NI-10' => 'Nicaragua: Managua',
'NI-11' => 'Nicaragua: Masaya',
'NI-12' => 'Nicaragua: Matagalpa',
'NI-13' => 'Nicaragua: Nueva Segovia',
'NI-14' => 'Nicaragua: Rio San Juan',
'NI-15' => 'Nicaragua: Rivas',
'NI-16' => 'Nicaragua: Zelaya',

'--NE' => '','-NE' => 'Niger',
'NE-01' => 'Niger: Agadez',
'NE-02' => 'Niger: Diffa',
'NE-03' => 'Niger: Dosso',
'NE-04' => 'Niger: Maradi',
'NE-05' => 'Niger: Niamey',
'NE-06' => 'Niger: Tahoua',
'NE-07' => 'Niger: Zinder',

'--NG' => '','-NG' => 'Nigeria',
'NG-45' => 'Nigeria: Abia',
'NG-11' => 'Nigeria: Abuja Capital Territory',
'NG-35' => 'Nigeria: Adamawa',
'NG-21' => 'Nigeria: Akwa Ibom',
'NG-25' => 'Nigeria: Anambra',
'NG-46' => 'Nigeria: Bauchi',
'NG-52' => 'Nigeria: Bayelsa',
'NG-26' => 'Nigeria: Benue',
'NG-27' => 'Nigeria: Borno',
'NG-22' => 'Nigeria: Cross River',
'NG-36' => 'Nigeria: Delta',
'NG-53' => 'Nigeria: Ebonyi',
'NG-37' => 'Nigeria: Edo',
'NG-54' => 'Nigeria: Ekiti',
'NG-47' => 'Nigeria: Enugu',
'NG-55' => 'Nigeria: Gombe',
'NG-28' => 'Nigeria: Imo',
'NG-39' => 'Nigeria: Jigawa',
'NG-23' => 'Nigeria: Kaduna',
'NG-29' => 'Nigeria: Kano',
'NG-24' => 'Nigeria: Katsina',
'NG-40' => 'Nigeria: Kebbi',
'NG-41' => 'Nigeria: Kogi',
'NG-30' => 'Nigeria: Kwara',
'NG-05' => 'Nigeria: Lagos',
'NG-56' => 'Nigeria: Nassarawa',
'NG-31' => 'Nigeria: Niger',
'NG-16' => 'Nigeria: Ogun',
'NG-48' => 'Nigeria: Ondo',
'NG-42' => 'Nigeria: Osun',
'NG-32' => 'Nigeria: Oyo',
'NG-49' => 'Nigeria: Plateau',
'NG-50' => 'Nigeria: Rivers',
'NG-51' => 'Nigeria: Sokoto',
'NG-43' => 'Nigeria: Taraba',
'NG-44' => 'Nigeria: Yobe',
'NG-57' => 'Nigeria: Zamfara',

'--KP' => '','-KP' => 'North Korea',
'KP-01' => 'North Korea: Chagang-do',
'KP-17' => 'North Korea: Hamgyong-bukto',
'KP-03' => 'North Korea: Hamgyong-namdo',
'KP-07' => 'North Korea: Hwanghae-bukto',
'KP-06' => 'North Korea: Hwanghae-namdo',
'KP-08' => 'North Korea: Kaesong-si',
'KP-09' => 'North Korea: Kangwon-do',
'KP-18' => 'North Korea: Najin Sonbong-si',
'KP-14' => 'North Korea: Namp\'o-si',
'KP-11' => 'North Korea: P\'yongan-bukto',
'KP-15' => 'North Korea: P\'yongan-namdo',
'KP-12' => 'North Korea: P\'yongyang-si',
'KP-13' => 'North Korea: Yanggang-do',

'--NO' => '','-NO' => 'Norway',
'NO-01' => 'Norway: Akershus',
'NO-02' => 'Norway: Aust-Agder',
'NO-04' => 'Norway: Buskerud',
'NO-05' => 'Norway: Finnmark',
'NO-06' => 'Norway: Hedmark',
'NO-07' => 'Norway: Hordaland',
'NO-08' => 'Norway: More og Romsdal',
'NO-09' => 'Norway: Nordland',
'NO-10' => 'Norway: Nord-Trondelag',
'NO-11' => 'Norway: Oppland',
'NO-12' => 'Norway: Oslo',
'NO-13' => 'Norway: Ostfold',
'NO-14' => 'Norway: Rogaland',
'NO-15' => 'Norway: Sogn og Fjordane',
'NO-16' => 'Norway: Sor-Trondelag',
'NO-17' => 'Norway: Telemark',
'NO-18' => 'Norway: Troms',
'NO-19' => 'Norway: Vest-Agder',
'NO-20' => 'Norway: Vestfold',

'--OM' => '','-OM' => 'Oman',
'OM-01' => 'Oman: Ad Dakhiliyah',
'OM-02' => 'Oman: Al Batinah',
'OM-03' => 'Oman: Al Wusta',
'OM-04' => 'Oman: Ash Sharqiyah',
'OM-05' => 'Oman: Az Zahirah',
'OM-06' => 'Oman: Masqat',
'OM-07' => 'Oman: Musandam',
'OM-08' => 'Oman: Zufar',

'--PK' => '','-PK' => 'Pakistan',
'PK-06' => 'Pakistan: Azad Kashmir',
'PK-02' => 'Pakistan: Balochistan',
'PK-01' => 'Pakistan: Federally Administered Tribal Areas',
'PK-08' => 'Pakistan: Islamabad',
'PK-07' => 'Pakistan: Northern Areas',
'PK-03' => 'Pakistan: North-West Frontier',
'PK-04' => 'Pakistan: Punjab',
'PK-05' => 'Pakistan: Sindh',

'--PA' => '','-PA' => 'Panama',
'PA-01' => 'Panama: Bocas del Toro',
'PA-02' => 'Panama: Chiriqui',
'PA-03' => 'Panama: Cocle',
'PA-04' => 'Panama: Colon',
'PA-05' => 'Panama: Darien',
'PA-06' => 'Panama: Herrera',
'PA-07' => 'Panama: Los Santos',
'PA-08' => 'Panama: Panama',
'PA-09' => 'Panama: San Blas',
'PA-10' => 'Panama: Veraguas',

'--PG' => '','-PG' => 'Papua New Guinea',
'PG-01' => 'Papua New Guinea: Central',
'PG-08' => 'Papua New Guinea: Chimbu',
'PG-10' => 'Papua New Guinea: East New Britain',
'PG-11' => 'Papua New Guinea: East Sepik',
'PG-09' => 'Papua New Guinea: Eastern Highlands',
'PG-19' => 'Papua New Guinea: Enga',
'PG-02' => 'Papua New Guinea: Gulf',
'PG-12' => 'Papua New Guinea: Madang',
'PG-13' => 'Papua New Guinea: Manus',
'PG-03' => 'Papua New Guinea: Milne Bay',
'PG-14' => 'Papua New Guinea: Morobe',
'PG-20' => 'Papua New Guinea: National Capital',
'PG-15' => 'Papua New Guinea: New Ireland',
'PG-07' => 'Papua New Guinea: North Solomons',
'PG-04' => 'Papua New Guinea: Northern',
'PG-18' => 'Papua New Guinea: Sandaun',
'PG-05' => 'Papua New Guinea: Southern Highlands',
'PG-17' => 'Papua New Guinea: West New Britain',
'PG-16' => 'Papua New Guinea: Western Highlands',
'PG-06' => 'Papua New Guinea: Western',

'--PY' => '','-PY' => 'Paraguay',
'PY-23' => 'Paraguay: Alto Paraguay',
'PY-01' => 'Paraguay: Alto Parana',
'PY-02' => 'Paraguay: Amambay',
'PY-03' => 'Paraguay: Boqueron',
'PY-04' => 'Paraguay: Caaguazu',
'PY-05' => 'Paraguay: Caazapa',
'PY-19' => 'Paraguay: Canindeyu',
'PY-06' => 'Paraguay: Central',
'PY-20' => 'Paraguay: Chaco',
'PY-07' => 'Paraguay: Concepcion',
'PY-08' => 'Paraguay: Cordillera',
'PY-10' => 'Paraguay: Guaira',
'PY-11' => 'Paraguay: Itapua',
'PY-12' => 'Paraguay: Misiones',
'PY-13' => 'Paraguay: Neembucu',
'PY-21' => 'Paraguay: Nueva Asuncion',
'PY-15' => 'Paraguay: Paraguari',
'PY-16' => 'Paraguay: Presidente Hayes',
'PY-17' => 'Paraguay: San Pedro',

'--PE' => '','-PE' => 'Peru',
'PE-01' => 'Peru: Amazonas',
'PE-02' => 'Peru: Ancash',
'PE-03' => 'Peru: Apurimac',
'PE-04' => 'Peru: Arequipa',
'PE-05' => 'Peru: Ayacucho',
'PE-06' => 'Peru: Cajamarca',
'PE-07' => 'Peru: Callao',
'PE-08' => 'Peru: Cusco',
'PE-09' => 'Peru: Huancavelica',
'PE-10' => 'Peru: Huanuco',
'PE-11' => 'Peru: Ica',
'PE-12' => 'Peru: Junin',
'PE-13' => 'Peru: La Libertad',
'PE-14' => 'Peru: Lambayeque',
'PE-15' => 'Peru: Lima',
'PE-16' => 'Peru: Loreto',
'PE-17' => 'Peru: Madre de Dios',
'PE-18' => 'Peru: Moquegua',
'PE-19' => 'Peru: Pasco',
'PE-20' => 'Peru: Piura',
'PE-21' => 'Peru: Puno',
'PE-22' => 'Peru: San Martin',
'PE-23' => 'Peru: Tacna',
'PE-24' => 'Peru: Tumbes',
'PE-25' => 'Peru: Ucayali',

'--PH' => '','-PH' => 'Philippines',
'PH-01' => 'Philippines: Abra',
'PH-02' => 'Philippines: Agusan del Norte',
'PH-03' => 'Philippines: Agusan del Sur',
'PH-04' => 'Philippines: Aklan',
'PH-05' => 'Philippines: Albay',
'PH-A1' => 'Philippines: Angeles',
'PH-06' => 'Philippines: Antique',
'PH-G8' => 'Philippines: Aurora',
'PH-A2' => 'Philippines: Bacolod',
'PH-A3' => 'Philippines: Bago',
'PH-A4' => 'Philippines: Baguio',
'PH-A5' => 'Philippines: Bais',
'PH-A6' => 'Philippines: Basilan City',
'PH-22' => 'Philippines: Basilan',
'PH-07' => 'Philippines: Bataan',
'PH-08' => 'Philippines: Batanes',
'PH-A7' => 'Philippines: Batangas City',
'PH-09' => 'Philippines: Batangas',
'PH-10' => 'Philippines: Benguet',
'PH-11' => 'Philippines: Bohol',
'PH-12' => 'Philippines: Bukidnon',
'PH-13' => 'Philippines: Bulacan',
'PH-A8' => 'Philippines: Butuan',
'PH-A9' => 'Philippines: Cabanatuan',
'PH-B1' => 'Philippines: Cadiz',
'PH-B2' => 'Philippines: Cagayan de Oro',
'PH-14' => 'Philippines: Cagayan',
'PH-B3' => 'Philippines: Calbayog',
'PH-B4' => 'Philippines: Caloocan',
'PH-15' => 'Philippines: Camarines Norte',
'PH-16' => 'Philippines: Camarines Sur',
'PH-17' => 'Philippines: Camiguin',
'PH-B5' => 'Philippines: Canlaon',
'PH-18' => 'Philippines: Capiz',
'PH-19' => 'Philippines: Catanduanes',
'PH-B6' => 'Philippines: Cavite City',
'PH-20' => 'Philippines: Cavite',
'PH-B7' => 'Philippines: Cebu City',
'PH-21' => 'Philippines: Cebu',
'PH-B8' => 'Philippines: Cotabato',
'PH-B9' => 'Philippines: Dagupan',
'PH-C1' => 'Philippines: Danao',
'PH-C2' => 'Philippines: Dapitan',
'PH-C3' => 'Philippines: Davao City',
'PH-25' => 'Philippines: Davao del Sur',
'PH-26' => 'Philippines: Davao Oriental',
'PH-24' => 'Philippines: Davao',
'PH-C4' => 'Philippines: Dipolog',
'PH-C5' => 'Philippines: Dumaguete',
'PH-23' => 'Philippines: Eastern Samar',
'PH-C6' => 'Philippines: General Santos',
'PH-C7' => 'Philippines: Gingoog',
'PH-27' => 'Philippines: Ifugao',
'PH-C8' => 'Philippines: Iligan',
'PH-28' => 'Philippines: Ilocos Norte',
'PH-29' => 'Philippines: Ilocos Sur',
'PH-C9' => 'Philippines: Iloilo City',
'PH-30' => 'Philippines: Iloilo',
'PH-D1' => 'Philippines: Iriga',
'PH-31' => 'Philippines: Isabela',
'PH-32' => 'Philippines: Kalinga-Apayao',
'PH-D2' => 'Philippines: La Carlota',
'PH-36' => 'Philippines: La Union',
'PH-33' => 'Philippines: Laguna',
'PH-34' => 'Philippines: Lanao del Norte',
'PH-35' => 'Philippines: Lanao del Sur',
'PH-D3' => 'Philippines: Laoag',
'PH-D4' => 'Philippines: Lapu-Lapu',
'PH-D5' => 'Philippines: Legaspi',
'PH-37' => 'Philippines: Leyte',
'PH-D6' => 'Philippines: Lipa',
'PH-D7' => 'Philippines: Lucena',
'PH-56' => 'Philippines: Maguindanao',
'PH-D8' => 'Philippines: Mandaue',
'PH-D9' => 'Philippines: Manila',
'PH-E1' => 'Philippines: Marawi',
'PH-38' => 'Philippines: Marinduque',
'PH-39' => 'Philippines: Masbate',
'PH-40' => 'Philippines: Mindoro Occidental',
'PH-41' => 'Philippines: Mindoro Oriental',
'PH-42' => 'Philippines: Misamis Occidental',
'PH-43' => 'Philippines: Misamis Oriental',
'PH-44' => 'Philippines: Mountain',
'PH-E2' => 'Philippines: Naga',
'PH-H3' => 'Philippines: Negros Occidental',
'PH-46' => 'Philippines: Negros Oriental',
'PH-57' => 'Philippines: North Cotabato',
'PH-67' => 'Philippines: Northern Samar',
'PH-47' => 'Philippines: Nueva Ecija',
'PH-48' => 'Philippines: Nueva Vizcaya',
'PH-E3' => 'Philippines: Olongapo',
'PH-E4' => 'Philippines: Ormoc',
'PH-E5' => 'Philippines: Oroquieta',
'PH-E6' => 'Philippines: Ozamis',
'PH-E7' => 'Philippines: Pagadian',
'PH-49' => 'Philippines: Palawan',
'PH-E8' => 'Philippines: Palayan',
'PH-50' => 'Philippines: Pampanga',
'PH-51' => 'Philippines: Pangasinan',
'PH-E9' => 'Philippines: Pasay',
'PH-F1' => 'Philippines: Puerto Princesa',
'PH-F2' => 'Philippines: Quezon City',
'PH-H2' => 'Philippines: Quezon',
'PH-68' => 'Philippines: Quirino',
'PH-53' => 'Philippines: Rizal',
'PH-54' => 'Philippines: Romblon',
'PH-F3' => 'Philippines: Roxas',
'PH-55' => 'Philippines: Samar',
'PH-F4' => 'Philippines: San Carlos',
'PH-F5' => 'Philippines: San Carlos',
'PH-F6' => 'Philippines: San Jose',
'PH-F7' => 'Philippines: San Pablo',
'PH-F8' => 'Philippines: Silay',
'PH-69' => 'Philippines: Siquijor',
'PH-58' => 'Philippines: Sorsogon',
'PH-70' => 'Philippines: South Cotabato',
'PH-59' => 'Philippines: Southern Leyte',
'PH-71' => 'Philippines: Sultan Kudarat',
'PH-60' => 'Philippines: Sulu',
'PH-61' => 'Philippines: Surigao del Norte',
'PH-62' => 'Philippines: Surigao del Sur',
'PH-F9' => 'Philippines: Surigao',
'PH-G1' => 'Philippines: Tacloban',
'PH-G2' => 'Philippines: Tagaytay',
'PH-G3' => 'Philippines: Tagbilaran',
'PH-G4' => 'Philippines: Tangub',
'PH-63' => 'Philippines: Tarlac',
'PH-72' => 'Philippines: Tawitawi',
'PH-G5' => 'Philippines: Toledo',
'PH-G6' => 'Philippines: Trece Martires',
'PH-64' => 'Philippines: Zambales',
'PH-65' => 'Philippines: Zamboanga del Norte',
'PH-66' => 'Philippines: Zamboanga del Sur',
'PH-G7' => 'Philippines: Zamboanga',

'--PL' => '','-PL' => 'Poland',
'PL-23' => 'Poland: Biala Podlaska',
'PL-24' => 'Poland: Bialystok',
'PL-25' => 'Poland: Bielsko',
'PL-26' => 'Poland: Bydgoszcz',
'PL-27' => 'Poland: Chelm',
'PL-28' => 'Poland: Ciechanow',
'PL-29' => 'Poland: Czestochowa',
'PL-72' => 'Poland: Dolnoslaskie',
'PL-30' => 'Poland: Elblag',
'PL-31' => 'Poland: Gdansk',
'PL-32' => 'Poland: Gorzow',
'PL-33' => 'Poland: Jelenia Gora',
'PL-34' => 'Poland: Kalisz',
'PL-35' => 'Poland: Katowice',
'PL-36' => 'Poland: Kielce',
'PL-37' => 'Poland: Konin',
'PL-38' => 'Poland: Koszalin',
'PL-39' => 'Poland: Krakow',
'PL-40' => 'Poland: Krosno',
'PL-73' => 'Poland: Kujawsko-Pomorskie',
'PL-41' => 'Poland: Legnica',
'PL-42' => 'Poland: Leszno',
'PL-43' => 'Poland: Lodz',
'PL-74' => 'Poland: Lodzkie',
'PL-44' => 'Poland: Lomza',
'PL-75' => 'Poland: Lubelskie',
'PL-45' => 'Poland: Lublin',
'PL-76' => 'Poland: Lubuskie',
'PL-77' => 'Poland: Malopolskie',
'PL-78' => 'Poland: Mazowieckie',
'PL-46' => 'Poland: Nowy Sacz',
'PL-47' => 'Poland: Olsztyn',
'PL-48' => 'Poland: Opole',
'PL-79' => 'Poland: Opolskie',
'PL-49' => 'Poland: Ostroleka',
'PL-50' => 'Poland: Pila',
'PL-51' => 'Poland: Piotrkow',
'PL-52' => 'Poland: Plock',
'PL-80' => 'Poland: Podkarpackie',
'PL-81' => 'Poland: Podlaskie',
'PL-82' => 'Poland: Pomorskie',
'PL-53' => 'Poland: Poznan',
'PL-54' => 'Poland: Przemysl',
'PL-55' => 'Poland: Radom',
'PL-56' => 'Poland: Rzeszow',
'PL-57' => 'Poland: Siedlce',
'PL-58' => 'Poland: Sieradz',
'PL-59' => 'Poland: Skierniewice',
'PL-83' => 'Poland: Slaskie',
'PL-60' => 'Poland: Slupsk',
'PL-61' => 'Poland: Suwalki',
'PL-84' => 'Poland: Swietokrzyskie',
'PL-62' => 'Poland: Szczecin',
'PL-63' => 'Poland: Tarnobrzeg',
'PL-64' => 'Poland: Tarnow',
'PL-65' => 'Poland: Torun',
'PL-66' => 'Poland: Walbrzych',
'PL-85' => 'Poland: Warminsko-Mazurskie',
'PL-67' => 'Poland: Warszawa',
'PL-86' => 'Poland: Wielkopolskie',
'PL-68' => 'Poland: Wloclawek',
'PL-69' => 'Poland: Wroclaw',
'PL-87' => 'Poland: Zachodniopomorskie',
'PL-70' => 'Poland: Zamosc',
'PL-71' => 'Poland: Zielona Gora',

'--PT' => '','-PT' => 'Portugal',
'PT-02' => 'Portugal: Aveiro',
'PT-23' => 'Portugal: Azores',
'PT-03' => 'Portugal: Beja',
'PT-04' => 'Portugal: Braga',
'PT-05' => 'Portugal: Braganca',
'PT-06' => 'Portugal: Castelo Branco',
'PT-07' => 'Portugal: Coimbra',
'PT-08' => 'Portugal: Evora',
'PT-09' => 'Portugal: Faro',
'PT-11' => 'Portugal: Guarda',
'PT-13' => 'Portugal: Leiria',
'PT-14' => 'Portugal: Lisboa',
'PT-10' => 'Portugal: Madeira',
'PT-16' => 'Portugal: Portalegre',
'PT-17' => 'Portugal: Porto',
'PT-18' => 'Portugal: Santarem',
'PT-19' => 'Portugal: Setubal',
'PT-20' => 'Portugal: Viana do Castelo',
'PT-21' => 'Portugal: Vila Real',
'PT-22' => 'Portugal: Viseu',

'--QA' => '','-QA' => 'Qatar',
'QA-01' => 'Qatar: Ad Dawhah',
'QA-02' => 'Qatar: Al Ghuwariyah',
'QA-03' => 'Qatar: Al Jumaliyah',
'QA-04' => 'Qatar: Al Khawr',
'QA-10' => 'Qatar: Al Wakrah',
'QA-06' => 'Qatar: Ar Rayyan',
'QA-11' => 'Qatar: Jariyan al Batnah',
'QA-08' => 'Qatar: Madinat ach Shamal',
'QA-12' => 'Qatar: Umm Sa\'id',
'QA-09' => 'Qatar: Umm Salal',

'--RO' => '','-RO' => 'Romania',
'RO-01' => 'Romania: Alba',
'RO-02' => 'Romania: Arad',
'RO-03' => 'Romania: Arges',
'RO-04' => 'Romania: Bacau',
'RO-05' => 'Romania: Bihor',
'RO-06' => 'Romania: Bistrita-Nasaud',
'RO-07' => 'Romania: Botosani',
'RO-08' => 'Romania: Braila',
'RO-09' => 'Romania: Brasov',
'RO-10' => 'Romania: Bucuresti',
'RO-11' => 'Romania: Buzau',
'RO-41' => 'Romania: Calarasi',
'RO-12' => 'Romania: Caras-Severin',
'RO-13' => 'Romania: Cluj',
'RO-14' => 'Romania: Constanta',
'RO-15' => 'Romania: Covasna',
'RO-16' => 'Romania: Dambovita',
'RO-17' => 'Romania: Dolj',
'RO-18' => 'Romania: Galati',
'RO-42' => 'Romania: Giurgiu',
'RO-19' => 'Romania: Gorj',
'RO-20' => 'Romania: Harghita',
'RO-21' => 'Romania: Hunedoara',
'RO-22' => 'Romania: Ialomita',
'RO-23' => 'Romania: Iasi',
'RO-43' => 'Romania: Ilfov',
'RO-25' => 'Romania: Maramures',
'RO-26' => 'Romania: Mehedinti',
'RO-27' => 'Romania: Mures',
'RO-28' => 'Romania: Neamt',
'RO-29' => 'Romania: Olt',
'RO-30' => 'Romania: Prahova',
'RO-31' => 'Romania: Salaj',
'RO-32' => 'Romania: Satu Mare',
'RO-33' => 'Romania: Sibiu',
'RO-34' => 'Romania: Suceava',
'RO-35' => 'Romania: Teleorman',
'RO-36' => 'Romania: Timis',
'RO-37' => 'Romania: Tulcea',
'RO-39' => 'Romania: Valcea',
'RO-38' => 'Romania: Vaslui',
'RO-40' => 'Romania: Vrancea',

'--RU' => '','-RU' => 'Russian Federation',
'RU-01' => 'Russian Federation: Adygeya',
'RU-02' => 'Russian Federation: Aginsky Buryatsky AO',
'RU-04' => 'Russian Federation: Altaisky krai',
'RU-05' => 'Russian Federation: Amur',
'RU-06' => 'Russian Federation: Arkhangel\'sk',
'RU-07' => 'Russian Federation: Astrakhan\'',
'RU-08' => 'Russian Federation: Bashkortostan',
'RU-09' => 'Russian Federation: Belgorod',
'RU-10' => 'Russian Federation: Bryansk',
'RU-11' => 'Russian Federation: Buryat',
'RU-12' => 'Russian Federation: Chechnya',
'RU-13' => 'Russian Federation: Chelyabinsk',
'RU-14' => 'Russian Federation: Chita',
'RU-15' => 'Russian Federation: Chukot',
'RU-16' => 'Russian Federation: Chuvashia',
'RU-17' => 'Russian Federation: Dagestan',
'RU-18' => 'Russian Federation: Evenk',
'RU-03' => 'Russian Federation: Gorno-Altay',
'RU-19' => 'Russian Federation: Ingush',
'RU-20' => 'Russian Federation: Irkutsk',
'RU-21' => 'Russian Federation: Ivanovo',
'RU-22' => 'Russian Federation: Kabardin-Balkar',
'RU-23' => 'Russian Federation: Kaliningrad',
'RU-24' => 'Russian Federation: Kalmyk',
'RU-25' => 'Russian Federation: Kaluga',
'RU-26' => 'Russian Federation: Kamchatka',
'RU-27' => 'Russian Federation: Karachay-Cherkess',
'RU-28' => 'Russian Federation: Karelia',
'RU-29' => 'Russian Federation: Kemerovo',
'RU-30' => 'Russian Federation: Khabarovsk',
'RU-31' => 'Russian Federation: Khakass',
'RU-32' => 'Russian Federation: Khanty-Mansiy',
'RU-33' => 'Russian Federation: Kirov',
'RU-34' => 'Russian Federation: Komi',
'RU-35' => 'Russian Federation: Komi-Permyak',
'RU-36' => 'Russian Federation: Koryak',
'RU-37' => 'Russian Federation: Kostroma',
'RU-38' => 'Russian Federation: Krasnodar',
'RU-39' => 'Russian Federation: Krasnoyarsk',
'RU-40' => 'Russian Federation: Kurgan',
'RU-41' => 'Russian Federation: Kursk',
'RU-42' => 'Russian Federation: Leningrad',
'RU-43' => 'Russian Federation: Lipetsk',
'RU-44' => 'Russian Federation: Magadan',
'RU-45' => 'Russian Federation: Mariy-El',
'RU-46' => 'Russian Federation: Mordovia',
'RU-48' => 'Russian Federation: Moscow City',
'RU-47' => 'Russian Federation: Moskva',
'RU-49' => 'Russian Federation: Murmansk',
'RU-50' => 'Russian Federation: Nenets',
'RU-51' => 'Russian Federation: Nizhegorod',
'RU-68' => 'Russian Federation: North Ossetia',
'RU-52' => 'Russian Federation: Novgorod',
'RU-53' => 'Russian Federation: Novosibirsk',
'RU-54' => 'Russian Federation: Omsk',
'RU-56' => 'Russian Federation: Orel',
'RU-55' => 'Russian Federation: Orenburg',
'RU-57' => 'Russian Federation: Penza',
'RU-58' => 'Russian Federation: Perm\'',
'RU-59' => 'Russian Federation: Primor\'ye',
'RU-60' => 'Russian Federation: Pskov',
'RU-61' => 'Russian Federation: Rostov',
'RU-62' => 'Russian Federation: Ryazan\'',
'RU-66' => 'Russian Federation: Saint Petersburg City',
'RU-63' => 'Russian Federation: Sakha',
'RU-64' => 'Russian Federation: Sakhalin',
'RU-65' => 'Russian Federation: Samara',
'RU-67' => 'Russian Federation: Saratov',
'RU-69' => 'Russian Federation: Smolensk',
'RU-70' => 'Russian Federation: Stavropol\'',
'RU-71' => 'Russian Federation: Sverdlovsk',
'RU-72' => 'Russian Federation: Tambovskaya oblast',
'RU-73' => 'Russian Federation: Tatarstan',
'RU-74' => 'Russian Federation: Taymyr',
'RU-75' => 'Russian Federation: Tomsk',
'RU-76' => 'Russian Federation: Tula',
'RU-79' => 'Russian Federation: Tuva',
'RU-77' => 'Russian Federation: Tver\'',
'RU-78' => 'Russian Federation: Tyumen\'',
'RU-80' => 'Russian Federation: Udmurt',
'RU-81' => 'Russian Federation: Ul\'yanovsk',
'RU-82' => 'Russian Federation: Ust-Orda Buryat',
'RU-83' => 'Russian Federation: Vladimir',
'RU-84' => 'Russian Federation: Volgograd',
'RU-85' => 'Russian Federation: Vologda',
'RU-86' => 'Russian Federation: Voronezh',
'RU-87' => 'Russian Federation: Yamal-Nenets',
'RU-88' => 'Russian Federation: Yaroslavl\'',
'RU-89' => 'Russian Federation: Yevrey',

'--RW' => '','-RW' => 'Rwanda',
'RW-01' => 'Rwanda: Butare',
'RW-02' => 'Rwanda: Byumba',
'RW-03' => 'Rwanda: Cyangugu',
'RW-04' => 'Rwanda: Gikongoro',
'RW-05' => 'Rwanda: Gisenyi',
'RW-06' => 'Rwanda: Gitarama',
'RW-07' => 'Rwanda: Kibungo',
'RW-08' => 'Rwanda: Kibuye',
'RW-09' => 'Rwanda: Kigali',
'RW-10' => 'Rwanda: Ruhengeri',

'--SH' => '','-SH' => 'Saint Helena',
'SH-01' => 'Saint Helena: Ascension',
'SH-02' => 'Saint Helena: Saint Helena',
'SH-03' => 'Saint Helena: Tristan da Cunha',

'--KN' => '','-KN' => 'Saint Kitts and Nevis',
'KN-01' => 'Saint Kitts and Nevis: Christ Church Nichola Town',
'KN-02' => 'Saint Kitts and Nevis: Saint Anne Sandy Point',
'KN-03' => 'Saint Kitts and Nevis: Saint George Basseterre',
'KN-04' => 'Saint Kitts and Nevis: Saint George Gingerland',
'KN-05' => 'Saint Kitts and Nevis: Saint James Windward',
'KN-06' => 'Saint Kitts and Nevis: Saint John Capisterre',
'KN-07' => 'Saint Kitts and Nevis: Saint John Figtree',
'KN-08' => 'Saint Kitts and Nevis: Saint Mary Cayon',
'KN-09' => 'Saint Kitts and Nevis: Saint Paul Capisterre',
'KN-10' => 'Saint Kitts and Nevis: Saint Paul Charlestown',
'KN-11' => 'Saint Kitts and Nevis: Saint Peter Basseterre',
'KN-12' => 'Saint Kitts and Nevis: Saint Thomas Lowland',
'KN-13' => 'Saint Kitts and Nevis: Saint Thomas Middle Island',
'KN-15' => 'Saint Kitts and Nevis: Trinity Palmetto Point',

'--LC' => '','-LC' => 'Saint Lucia',
'LC-01' => 'Saint Lucia: Anse-la-Raye',
'LC-03' => 'Saint Lucia: Castries',
'LC-04' => 'Saint Lucia: Choiseul',
'LC-02' => 'Saint Lucia: Dauphin',
'LC-05' => 'Saint Lucia: Dennery',
'LC-06' => 'Saint Lucia: Gros-Islet',
'LC-07' => 'Saint Lucia: Laborie',
'LC-08' => 'Saint Lucia: Micoud',
'LC-11' => 'Saint Lucia: Praslin',
'LC-09' => 'Saint Lucia: Soufriere',
'LC-10' => 'Saint Lucia: Vieux-Fort',

'--VC' => '','-VC' => 'Saint Vincent and the Grenadines',
'VC-01' => 'Saint Vincent and the Grenadines: Charlotte',
'VC-06' => 'Saint Vincent and the Grenadines: Grenadines',
'VC-02' => 'Saint Vincent and the Grenadines: Saint Andrew',
'VC-03' => 'Saint Vincent and the Grenadines: Saint David',
'VC-04' => 'Saint Vincent and the Grenadines: Saint George',
'VC-05' => 'Saint Vincent and the Grenadines: Saint Patrick',

'--WS' => '','-WS' => 'Samoa',
'WS-02' => 'Samoa: Aiga-i-le-Tai',
'WS-03' => 'Samoa: Atua',
'WS-04' => 'Samoa: Fa',
'WS-05' => 'Samoa: Gaga',
'WS-07' => 'Samoa: Gagaifomauga',
'WS-08' => 'Samoa: Palauli',
'WS-09' => 'Samoa: Satupa',
'WS-10' => 'Samoa: Tuamasaga',
'WS-06' => 'Samoa: Va',
'WS-11' => 'Samoa: Vaisigano',

'--SM' => '','-SM' => 'San Marino',
'SM-01' => 'San Marino: Acquaviva',
'SM-06' => 'San Marino: Borgo Maggiore',
'SM-02' => 'San Marino: Chiesanuova',
'SM-03' => 'San Marino: Domagnano',
'SM-04' => 'San Marino: Faetano',
'SM-05' => 'San Marino: Fiorentino',
'SM-08' => 'San Marino: Monte Giardino',
'SM-07' => 'San Marino: San Marino',
'SM-09' => 'San Marino: Serravalle',

'--ST' => '','-ST' => 'Sao Tome and Principe',
'ST-01' => 'Sao Tome and Principe: Principe',
'ST-02' => 'Sao Tome and Principe: Sao Tome',

'--SA' => '','-SA' => 'Saudi Arabia',
'SA-02' => 'Saudi Arabia: Al Bahah',
'SA-15' => 'Saudi Arabia: Al Hudud ash Shamaliyah',
'SA-03' => 'Saudi Arabia: Al Jawf',
'SA-20' => 'Saudi Arabia: Al Jawf',
'SA-05' => 'Saudi Arabia: Al Madinah',
'SA-08' => 'Saudi Arabia: Al Qasim',
'SA-09' => 'Saudi Arabia: Al Qurayyat',
'SA-10' => 'Saudi Arabia: Ar Riyad',
'SA-06' => 'Saudi Arabia: Ash Sharqiyah',
'SA-13' => 'Saudi Arabia: Ha\'il',
'SA-17' => 'Saudi Arabia: Jizan',
'SA-14' => 'Saudi Arabia: Makkah',
'SA-16' => 'Saudi Arabia: Najran',
'SA-19' => 'Saudi Arabia: Tabuk',

'--SN' => '','-SN' => 'Senegal',
'SN-01' => 'Senegal: Dakar',
'SN-03' => 'Senegal: Diourbel',
'SN-09' => 'Senegal: Fatick',
'SN-10' => 'Senegal: Kaolack',
'SN-11' => 'Senegal: Kolda',
'SN-08' => 'Senegal: Louga',
'SN-04' => 'Senegal: Saint-Louis',
'SN-05' => 'Senegal: Tambacounda',
'SN-07' => 'Senegal: Thies',
'SN-12' => 'Senegal: Ziguinchor',

'--RS' => '','-RS' => 'Serbia',
'RS-01' => 'Serbia: Kosovo',
'RS-02' => 'Serbia: Vojvodina',

'--SC' => '','-SC' => 'Seychelles',
'SC-01' => 'Seychelles: Anse aux Pins',
'SC-02' => 'Seychelles: Anse Boileau',
'SC-03' => 'Seychelles: Anse Etoile',
'SC-04' => 'Seychelles: Anse Louis',
'SC-05' => 'Seychelles: Anse Royale',
'SC-06' => 'Seychelles: Baie Lazare',
'SC-07' => 'Seychelles: Baie Sainte Anne',
'SC-08' => 'Seychelles: Beau Vallon',
'SC-09' => 'Seychelles: Bel Air',
'SC-10' => 'Seychelles: Bel Ombre',
'SC-11' => 'Seychelles: Cascade',
'SC-12' => 'Seychelles: Glacis',
'SC-13' => 'Seychelles: Grand\' Anse',
'SC-14' => 'Seychelles: Grand\' Anse',
'SC-15' => 'Seychelles: La Digue',
'SC-16' => 'Seychelles: La Riviere Anglaise',
'SC-17' => 'Seychelles: Mont Buxton',
'SC-18' => 'Seychelles: Mont Fleuri',
'SC-19' => 'Seychelles: Plaisance',
'SC-20' => 'Seychelles: Pointe La Rue',
'SC-21' => 'Seychelles: Port Glaud',
'SC-22' => 'Seychelles: Saint Louis',
'SC-23' => 'Seychelles: Takamaka',

'--SL' => '','-SL' => 'Sierra Leone',
'SL-01' => 'Sierra Leone: Eastern',
'SL-02' => 'Sierra Leone: Northern',
'SL-03' => 'Sierra Leone: Southern',
'SL-04' => 'Sierra Leone: Western Area',

'--SK' => '','-SK' => 'Slovakia',
'SK-01' => 'Slovakia: Banska Bystrica',
'SK-02' => 'Slovakia: Bratislava',
'SK-03' => 'Slovakia: Kosice',
'SK-04' => 'Slovakia: Nitra',
'SK-05' => 'Slovakia: Presov',
'SK-06' => 'Slovakia: Trencin',
'SK-07' => 'Slovakia: Trnava',
'SK-08' => 'Slovakia: Zilina',

'--SI' => '','-SI' => 'Slovenia',
'SI-01' => 'Slovenia: Ajdovscina',
'SI-02' => 'Slovenia: Beltinci',
'SI-03' => 'Slovenia: Bled',
'SI-04' => 'Slovenia: Bohinj',
'SI-05' => 'Slovenia: Borovnica',
'SI-06' => 'Slovenia: Bovec',
'SI-07' => 'Slovenia: Brda',
'SI-08' => 'Slovenia: Brezice',
'SI-09' => 'Slovenia: Brezovica',
'SI-11' => 'Slovenia: Celje',
'SI-12' => 'Slovenia: Cerklje na Gorenjskem',
'SI-13' => 'Slovenia: Cerknica',
'SI-14' => 'Slovenia: Cerkno',
'SI-15' => 'Slovenia: Crensovci',
'SI-16' => 'Slovenia: Crna na Koroskem',
'SI-17' => 'Slovenia: Crnomelj',
'SI-19' => 'Slovenia: Divaca',
'SI-20' => 'Slovenia: Dobrepolje',
'SI-G4' => 'Slovenia: Dobrova-Horjul-Polhov Gradec',
'SI-22' => 'Slovenia: Dol pri Ljubljani',
'SI-G7' => 'Slovenia: Domzale',
'SI-24' => 'Slovenia: Dornava',
'SI-25' => 'Slovenia: Dravograd',
'SI-26' => 'Slovenia: Duplek',
'SI-27' => 'Slovenia: Gorenja Vas-Poljane',
'SI-28' => 'Slovenia: Gorisnica',
'SI-29' => 'Slovenia: Gornja Radgona',
'SI-30' => 'Slovenia: Gornji Grad',
'SI-31' => 'Slovenia: Gornji Petrovci',
'SI-32' => 'Slovenia: Grosuplje',
'SI-34' => 'Slovenia: Hrastnik',
'SI-35' => 'Slovenia: Hrpelje-Kozina',
'SI-36' => 'Slovenia: Idrija',
'SI-37' => 'Slovenia: Ig',
'SI-38' => 'Slovenia: Ilirska Bistrica',
'SI-39' => 'Slovenia: Ivancna Gorica',
'SI-40' => 'Slovenia: Izola-Isola',
'SI-H4' => 'Slovenia: Jesenice',
'SI-42' => 'Slovenia: Jursinci',
'SI-H6' => 'Slovenia: Kamnik',
'SI-44' => 'Slovenia: Kanal',
'SI-45' => 'Slovenia: Kidricevo',
'SI-46' => 'Slovenia: Kobarid',
'SI-47' => 'Slovenia: Kobilje',
'SI-H7' => 'Slovenia: Kocevje',
'SI-49' => 'Slovenia: Komen',
'SI-50' => 'Slovenia: Koper-Capodistria',
'SI-51' => 'Slovenia: Kozje',
'SI-52' => 'Slovenia: Kranj',
'SI-53' => 'Slovenia: Kranjska Gora',
'SI-54' => 'Slovenia: Krsko',
'SI-55' => 'Slovenia: Kungota',
'SI-I2' => 'Slovenia: Kuzma',
'SI-57' => 'Slovenia: Lasko',
'SI-I3' => 'Slovenia: Lenart',
'SI-I5' => 'Slovenia: Litija',
'SI-61' => 'Slovenia: Ljubljana',
'SI-62' => 'Slovenia: Ljubno',
'SI-I6' => 'Slovenia: Ljutomer',
'SI-64' => 'Slovenia: Logatec',
'SI-I7' => 'Slovenia: Loska Dolina',
'SI-66' => 'Slovenia: Loski Potok',
'SI-I9' => 'Slovenia: Luce',
'SI-68' => 'Slovenia: Lukovica',
'SI-J1' => 'Slovenia: Majsperk',
'SI-J2' => 'Slovenia: Maribor',
'SI-71' => 'Slovenia: Medvode',
'SI-72' => 'Slovenia: Menges',
'SI-73' => 'Slovenia: Metlika',
'SI-74' => 'Slovenia: Mezica',
'SI-J5' => 'Slovenia: Miren-Kostanjevica',
'SI-76' => 'Slovenia: Mislinja',
'SI-77' => 'Slovenia: Moravce',
'SI-78' => 'Slovenia: Moravske Toplice',
'SI-79' => 'Slovenia: Mozirje',
'SI-80' => 'Slovenia: Murska Sobota',
'SI-81' => 'Slovenia: Muta',
'SI-82' => 'Slovenia: Naklo',
'SI-83' => 'Slovenia: Nazarje',
'SI-84' => 'Slovenia: Nova Gorica',
'SI-J7' => 'Slovenia: Novo Mesto',
'SI-86' => 'Slovenia: Odranci',
'SI-87' => 'Slovenia: Ormoz',
'SI-88' => 'Slovenia: Osilnica',
'SI-89' => 'Slovenia: Pesnica',
'SI-J9' => 'Slovenia: Piran',
'SI-91' => 'Slovenia: Pivka',
'SI-92' => 'Slovenia: Podcetrtek',
'SI-94' => 'Slovenia: Postojna',
'SI-K5' => 'Slovenia: Preddvor',
'SI-K7' => 'Slovenia: Ptuj',
'SI-97' => 'Slovenia: Puconci',
'SI-98' => 'Slovenia: Racam',
'SI-99' => 'Slovenia: Radece',
'SI-A1' => 'Slovenia: Radenci',
'SI-A2' => 'Slovenia: Radlje ob Dravi',
'SI-A3' => 'Slovenia: Radovljica',
'SI-L1' => 'Slovenia: Ribnica',
'SI-A7' => 'Slovenia: Rogaska Slatina',
'SI-A6' => 'Slovenia: Rogasovci',
'SI-A8' => 'Slovenia: Rogatec',
'SI-L3' => 'Slovenia: Ruse',
'SI-B1' => 'Slovenia: Semic',
'SI-B2' => 'Slovenia: Sencur',
'SI-B3' => 'Slovenia: Sentilj',
'SI-B4' => 'Slovenia: Sentjernej',
'SI-L7' => 'Slovenia: Sentjur pri Celju',
'SI-B6' => 'Slovenia: Sevnica',
'SI-B7' => 'Slovenia: Sezana',
'SI-B8' => 'Slovenia: Skocjan',
'SI-B9' => 'Slovenia: Skofja Loka',
'SI-C1' => 'Slovenia: Skofljica',
'SI-C2' => 'Slovenia: Slovenj Gradec',
'SI-L8' => 'Slovenia: Slovenska Bistrica',
'SI-C4' => 'Slovenia: Slovenske Konjice',
'SI-C5' => 'Slovenia: Smarje pri Jelsah',
'SI-C6' => 'Slovenia: Smartno ob Paki',
'SI-C7' => 'Slovenia: Sostanj',
'SI-C8' => 'Slovenia: Starse',
'SI-C9' => 'Slovenia: Store',
'SI-D1' => 'Slovenia: Sveti Jurij',
'SI-D2' => 'Slovenia: Tolmin',
'SI-D3' => 'Slovenia: Trbovlje',
'SI-D4' => 'Slovenia: Trebnje',
'SI-D5' => 'Slovenia: Trzic',
'SI-D6' => 'Slovenia: Turnisce',
'SI-D7' => 'Slovenia: Velenje',
'SI-D8' => 'Slovenia: Velike Lasce',
'SI-N2' => 'Slovenia: Videm',
'SI-E1' => 'Slovenia: Vipava',
'SI-E2' => 'Slovenia: Vitanje',
'SI-E3' => 'Slovenia: Vodice',
'SI-N3' => 'Slovenia: Vojnik',
'SI-E5' => 'Slovenia: Vrhnika',
'SI-E6' => 'Slovenia: Vuzenica',
'SI-E7' => 'Slovenia: Zagorje ob Savi',
'SI-N5' => 'Slovenia: Zalec',
'SI-E9' => 'Slovenia: Zavrc',
'SI-F1' => 'Slovenia: Zelezniki',
'SI-F2' => 'Slovenia: Ziri',
'SI-F3' => 'Slovenia: Zrece',

'--SB' => '','-SB' => 'Solomon Islands',
'SB-05' => 'Solomon Islands: Central',
'SB-06' => 'Solomon Islands: Guadalcanal',
'SB-07' => 'Solomon Islands: Isabel',
'SB-08' => 'Solomon Islands: Makira',
'SB-03' => 'Solomon Islands: Malaita',
'SB-09' => 'Solomon Islands: Temotu',
'SB-04' => 'Solomon Islands: Western',

'--SO' => '','-SO' => 'Somalia',
'SO-01' => 'Somalia: Bakool',
'SO-02' => 'Somalia: Banaadir',
'SO-03' => 'Somalia: Bari',
'SO-04' => 'Somalia: Bay',
'SO-05' => 'Somalia: Galguduud',
'SO-06' => 'Somalia: Gedo',
'SO-07' => 'Somalia: Hiiraan',
'SO-08' => 'Somalia: Jubbada Dhexe',
'SO-09' => 'Somalia: Jubbada Hoose',
'SO-10' => 'Somalia: Mudug',
'SO-11' => 'Somalia: Nugaal',
'SO-12' => 'Somalia: Sanaag',
'SO-13' => 'Somalia: Shabeellaha Dhexe',
'SO-14' => 'Somalia: Shabeellaha Hoose',
'SO-15' => 'Somalia: Togdheer',
'SO-16' => 'Somalia: Woqooyi Galbeed',

'--ZA' => '','-ZA' => 'South Africa',
'ZA-05' => 'South Africa: Eastern Cape',
'ZA-03' => 'South Africa: Free State',
'ZA-06' => 'South Africa: Gauteng',
'ZA-02' => 'South Africa: KwaZulu-Natal',
'ZA-09' => 'South Africa: Limpopo',
'ZA-07' => 'South Africa: Mpumalanga',
'ZA-08' => 'South Africa: Northern Cape',
'ZA-10' => 'South Africa: North-West',
'ZA-11' => 'South Africa: Western Cape',

'--KR' => '','-KR' => 'South Korea',
'KR-01' => 'South Korea: Cheju-do',
'KR-03' => 'South Korea: Cholla-bukto',
'KR-16' => 'South Korea: Cholla-namdo',
'KR-05' => 'South Korea: Ch\'ungch\'ong-bukto',
'KR-17' => 'South Korea: Ch\'ungch\'ong-namdo',
'KR-12' => 'South Korea: Inch\'on-jikhalsi',
'KR-06' => 'South Korea: Kangwon-do',
'KR-18' => 'South Korea: Kwangju-jikhalsi',
'KR-13' => 'South Korea: Kyonggi-do',
'KR-14' => 'South Korea: Kyongsang-bukto',
'KR-20' => 'South Korea: Kyongsang-namdo',
'KR-10' => 'South Korea: Pusan-jikhalsi',
'KR-11' => 'South Korea: Seoul-t\'ukpyolsi',
'KR-15' => 'South Korea: Taegu-jikhalsi',
'KR-19' => 'South Korea: Taejon-jikhalsi',
'KR-21' => 'South Korea: Ulsan-gwangyoksi',

'--ES' => '','-ES' => 'Spain',
'ES-51' => 'Spain: Andalucia',
'ES-52' => 'Spain: Aragon',
'ES-34' => 'Spain: Asturias',
'ES-53' => 'Spain: Canarias',
'ES-39' => 'Spain: Cantabria',
'ES-55' => 'Spain: Castilla y Leon',
'ES-54' => 'Spain: Castilla-La Mancha',
'ES-56' => 'Spain: Catalonia',
'ES-60' => 'Spain: Comunidad Valenciana',
'ES-57' => 'Spain: Extremadura',
'ES-58' => 'Spain: Galicia',
'ES-07' => 'Spain: Islas Baleares',
'ES-27' => 'Spain: La Rioja',
'ES-29' => 'Spain: Madrid',
'ES-31' => 'Spain: Murcia',
'ES-32' => 'Spain: Navarra',
'ES-59' => 'Spain: Pais Vasco',

'--LK' => '','-LK' => 'Sri Lanka',
'LK-01' => 'Sri Lanka: Amparai',
'LK-02' => 'Sri Lanka: Anuradhapura',
'LK-03' => 'Sri Lanka: Badulla',
'LK-04' => 'Sri Lanka: Batticaloa',
'LK-23' => 'Sri Lanka: Colombo',
'LK-06' => 'Sri Lanka: Galle',
'LK-24' => 'Sri Lanka: Gampaha',
'LK-07' => 'Sri Lanka: Hambantota',
'LK-25' => 'Sri Lanka: Jaffna',
'LK-09' => 'Sri Lanka: Kalutara',
'LK-10' => 'Sri Lanka: Kandy',
'LK-11' => 'Sri Lanka: Kegalla',
'LK-12' => 'Sri Lanka: Kurunegala',
'LK-26' => 'Sri Lanka: Mannar',
'LK-14' => 'Sri Lanka: Matale',
'LK-15' => 'Sri Lanka: Matara',
'LK-16' => 'Sri Lanka: Moneragala',
'LK-27' => 'Sri Lanka: Mullaittivu',
'LK-17' => 'Sri Lanka: Nuwara Eliya',
'LK-18' => 'Sri Lanka: Polonnaruwa',
'LK-19' => 'Sri Lanka: Puttalam',
'LK-20' => 'Sri Lanka: Ratnapura',
'LK-21' => 'Sri Lanka: Trincomalee',
'LK-28' => 'Sri Lanka: Vavuniya',

'--SD' => '','-SD' => 'Sudan',
'SD-28' => 'Sudan: Al Istiwa\'iyah',
'SD-29' => 'Sudan: Al Khartum',
'SD-27' => 'Sudan: Al Wusta',
'SD-30' => 'Sudan: Ash Shamaliyah',
'SD-31' => 'Sudan: Ash Sharqiyah',
'SD-32' => 'Sudan: Bahr al Ghazal',
'SD-33' => 'Sudan: Darfur',
'SD-34' => 'Sudan: Kurdufan',

'--SR' => '','-SR' => 'Suriname',
'SR-10' => 'Suriname: Brokopondo',
'SR-11' => 'Suriname: Commewijne',
'SR-12' => 'Suriname: Coronie',
'SR-13' => 'Suriname: Marowijne',
'SR-14' => 'Suriname: Nickerie',
'SR-15' => 'Suriname: Para',
'SR-16' => 'Suriname: Paramaribo',
'SR-17' => 'Suriname: Saramacca',
'SR-18' => 'Suriname: Sipaliwini',
'SR-19' => 'Suriname: Wanica',

'--SZ' => '','-SZ' => 'Swaziland',
'SZ-01' => 'Swaziland: Hhohho',
'SZ-02' => 'Swaziland: Lubombo',
'SZ-03' => 'Swaziland: Manzini',
'SZ-05' => 'Swaziland: Praslin',
'SZ-04' => 'Swaziland: Shiselweni',

'--SE' => '','-SE' => 'Sweden',
'SE-01' => 'Sweden: Alvsborgs Lan',
'SE-02' => 'Sweden: Blekinge Lan',
'SE-10' => 'Sweden: Dalarnas Lan',
'SE-03' => 'Sweden: Gavleborgs Lan',
'SE-04' => 'Sweden: Goteborgs och Bohus Lan',
'SE-05' => 'Sweden: Gotlands Lan',
'SE-06' => 'Sweden: Hallands Lan',
'SE-07' => 'Sweden: Jamtlands Lan',
'SE-08' => 'Sweden: Jonkopings Lan',
'SE-09' => 'Sweden: Kalmar Lan',
'SE-11' => 'Sweden: Kristianstads Lan',
'SE-12' => 'Sweden: Kronobergs Lan',
'SE-13' => 'Sweden: Malmohus Lan',
'SE-14' => 'Sweden: Norrbottens Lan',
'SE-15' => 'Sweden: Orebro Lan',
'SE-16' => 'Sweden: Ostergotlands Lan',
'SE-27' => 'Sweden: Skane Lan',
'SE-17' => 'Sweden: Skaraborgs Lan',
'SE-18' => 'Sweden: Sodermanlands Lan',
'SE-26' => 'Sweden: Stockholms Lan',
'SE-21' => 'Sweden: Uppsala Lan',
'SE-22' => 'Sweden: Varmlands Lan',
'SE-23' => 'Sweden: Vasterbottens Lan',
'SE-24' => 'Sweden: Vasternorrlands Lan',
'SE-25' => 'Sweden: Vastmanlands Lan',
'SE-28' => 'Sweden: Vastra Gotaland',

'--CH' => '','-CH' => 'Switzerland',
'CH-01' => 'Switzerland: Aargau',
'CH-02' => 'Switzerland: Ausser-Rhoden',
'CH-03' => 'Switzerland: Basel-Landschaft',
'CH-04' => 'Switzerland: Basel-Stadt',
'CH-05' => 'Switzerland: Bern',
'CH-06' => 'Switzerland: Fribourg',
'CH-07' => 'Switzerland: Geneve',
'CH-08' => 'Switzerland: Glarus',
'CH-09' => 'Switzerland: Graubunden',
'CH-10' => 'Switzerland: Inner-Rhoden',
'CH-26' => 'Switzerland: Jura',
'CH-11' => 'Switzerland: Luzern',
'CH-12' => 'Switzerland: Neuchatel',
'CH-13' => 'Switzerland: Nidwalden',
'CH-14' => 'Switzerland: Obwalden',
'CH-15' => 'Switzerland: Sankt Gallen',
'CH-16' => 'Switzerland: Schaffhausen',
'CH-17' => 'Switzerland: Schwyz',
'CH-18' => 'Switzerland: Solothurn',
'CH-19' => 'Switzerland: Thurgau',
'CH-20' => 'Switzerland: Ticino',
'CH-21' => 'Switzerland: Uri',
'CH-22' => 'Switzerland: Valais',
'CH-23' => 'Switzerland: Vaud',
'CH-24' => 'Switzerland: Zug',
'CH-25' => 'Switzerland: Zurich',

'--SY' => '','-SY' => 'Syrian Arab Republic',
'SY-01' => 'Syrian Arab Republic: Al Hasakah',
'SY-02' => 'Syrian Arab Republic: Al Ladhiqiyah',
'SY-03' => 'Syrian Arab Republic: Al Qunaytirah',
'SY-04' => 'Syrian Arab Republic: Ar Raqqah',
'SY-05' => 'Syrian Arab Republic: As Suwayda\'',
'SY-06' => 'Syrian Arab Republic: Dar',
'SY-07' => 'Syrian Arab Republic: Dayr az Zawr',
'SY-13' => 'Syrian Arab Republic: Dimashq',
'SY-09' => 'Syrian Arab Republic: Halab',
'SY-10' => 'Syrian Arab Republic: Hamah',
'SY-11' => 'Syrian Arab Republic: Hims',
'SY-12' => 'Syrian Arab Republic: Idlib',
'SY-08' => 'Syrian Arab Republic: Rif Dimashq',
'SY-14' => 'Syrian Arab Republic: Tartus',

'--TW' => '','-TW' => 'Taiwan',
'TW-01' => 'Taiwan: Fu-chien',
'TW-02' => 'Taiwan: Kao-hsiung',
'TW-03' => 'Taiwan: T\'ai-pei',
'TW-04' => 'Taiwan: T\'ai-wan',

'--TJ' => '','-TJ' => 'Tajikistan',
'TJ-02' => 'Tajikistan: Khatlon',
'TJ-01' => 'Tajikistan: Kuhistoni Badakhshon',
'TJ-03' => 'Tajikistan: Sughd',

'--TZ' => '','-TZ' => 'Tanwzania',
'TZ-01' => 'Tanwzania: Arusha',
'TZ-23' => 'Tanwzania: Dar es Salaam',
'TZ-03' => 'Tanwzania: Dodoma',
'TZ-04' => 'Tanwzania: Iringa',
'TZ-19' => 'Tanwzania: Kagera',
'TZ-05' => 'Tanwzania: Kigoma',
'TZ-06' => 'Tanwzania: Kilimanjaro',
'TZ-07' => 'Tanwzania: Lindi',
'TZ-08' => 'Tanwzania: Mara',
'TZ-09' => 'Tanwzania: Mbeya',
'TZ-10' => 'Tanwzania: Morogoro',
'TZ-11' => 'Tanwzania: Mtwara',
'TZ-12' => 'Tanwzania: Mwanza',
'TZ-13' => 'Tanwzania: Pemba North',
'TZ-20' => 'Tanwzania: Pemba South',
'TZ-02' => 'Tanwzania: Pwani',
'TZ-24' => 'Tanwzania: Rukwa',
'TZ-14' => 'Tanwzania: Ruvuma',
'TZ-15' => 'Tanwzania: Shinyanga',
'TZ-16' => 'Tanwzania: Singida',
'TZ-17' => 'Tanwzania: Tabora',
'TZ-18' => 'Tanwzania: Tanga',
'TZ-21' => 'Tanwzania: Zanzibar Central',
'TZ-22' => 'Tanwzania: Zanzibar North',
'TZ-25' => 'Tanwzania: Zanzibar Urban',

'--TH' => '','-TH' => 'Thailand',
'TH-35' => 'Thailand: Ang Thong',
'TH-28' => 'Thailand: Buriram',
'TH-44' => 'Thailand: Chachoengsao',
'TH-32' => 'Thailand: Chai Nat',
'TH-26' => 'Thailand: Chaiyaphum',
'TH-48' => 'Thailand: Chanthaburi',
'TH-02' => 'Thailand: Chiang Mai',
'TH-03' => 'Thailand: Chiang Rai',
'TH-46' => 'Thailand: Chon Buri',
'TH-58' => 'Thailand: Chumphon',
'TH-23' => 'Thailand: Kalasin',
'TH-11' => 'Thailand: Kamphaeng Phet',
'TH-50' => 'Thailand: Kanchanaburi',
'TH-22' => 'Thailand: Khon Kaen',
'TH-63' => 'Thailand: Krabi',
'TH-40' => 'Thailand: Krung Thep',
'TH-06' => 'Thailand: Lampang',
'TH-05' => 'Thailand: Lamphun',
'TH-18' => 'Thailand: Loei',
'TH-34' => 'Thailand: Lop Buri',
'TH-01' => 'Thailand: Mae Hong Son',
'TH-24' => 'Thailand: Maha Sarakham',
'TH-78' => 'Thailand: Mukdahan',
'TH-43' => 'Thailand: Nakhon Nayok',
'TH-53' => 'Thailand: Nakhon Pathom',
'TH-21' => 'Thailand: Nakhon Phanom',
'TH-27' => 'Thailand: Nakhon Ratchasima',
'TH-16' => 'Thailand: Nakhon Sawan',
'TH-64' => 'Thailand: Nakhon Si Thammarat',
'TH-04' => 'Thailand: Nan',
'TH-31' => 'Thailand: Narathiwat',
'TH-17' => 'Thailand: Nong Khai',
'TH-38' => 'Thailand: Nonthaburi',
'TH-39' => 'Thailand: Pathum Thani',
'TH-69' => 'Thailand: Pattani',
'TH-61' => 'Thailand: Phangnga',
'TH-66' => 'Thailand: Phatthalung',
'TH-41' => 'Thailand: Phayao',
'TH-14' => 'Thailand: Phetchabun',
'TH-56' => 'Thailand: Phetchaburi',
'TH-13' => 'Thailand: Phichit',
'TH-12' => 'Thailand: Phitsanulok',
'TH-36' => 'Thailand: Phra Nakhon Si Ayutthaya',
'TH-07' => 'Thailand: Phrae',
'TH-62' => 'Thailand: Phuket',
'TH-45' => 'Thailand: Prachin Buri',
'TH-57' => 'Thailand: Prachuap Khiri Khan',
'TH-59' => 'Thailand: Ranong',
'TH-52' => 'Thailand: Ratchaburi',
'TH-47' => 'Thailand: Rayong',
'TH-25' => 'Thailand: Roi Et',
'TH-20' => 'Thailand: Sakon Nakhon',
'TH-42' => 'Thailand: Samut Prakan',
'TH-55' => 'Thailand: Samut Sakhon',
'TH-54' => 'Thailand: Samut Songkhram',
'TH-37' => 'Thailand: Saraburi',
'TH-67' => 'Thailand: Satun',
'TH-33' => 'Thailand: Sing Buri',
'TH-30' => 'Thailand: Sisaket',
'TH-68' => 'Thailand: Songkhla',
'TH-09' => 'Thailand: Sukhothai',
'TH-51' => 'Thailand: Suphan Buri',
'TH-60' => 'Thailand: Surat Thani',
'TH-29' => 'Thailand: Surin',
'TH-08' => 'Thailand: Tak',
'TH-65' => 'Thailand: Trang',
'TH-49' => 'Thailand: Trat',
'TH-75' => 'Thailand: Ubon Ratchathani',
'TH-76' => 'Thailand: Udon Thani',
'TH-15' => 'Thailand: Uthai Thani',
'TH-10' => 'Thailand: Uttaradit',
'TH-70' => 'Thailand: Yala',
'TH-72' => 'Thailand: Yasothon',

'--TG' => '','-TG' => 'Togo',
'TG-01' => 'Togo: Amlame',
'TG-02' => 'Togo: Aneho',
'TG-03' => 'Togo: Atakpame',
'TG-15' => 'Togo: Badou',
'TG-04' => 'Togo: Bafilo',
'TG-05' => 'Togo: Bassar',
'TG-06' => 'Togo: Dapaong',
'TG-07' => 'Togo: Kante',
'TG-08' => 'Togo: Klouto',
'TG-14' => 'Togo: Kpagouda',
'TG-09' => 'Togo: Lama-Kara',
'TG-10' => 'Togo: Lome',
'TG-11' => 'Togo: Mango',
'TG-12' => 'Togo: Niamtougou',
'TG-13' => 'Togo: Notse',
'TG-16' => 'Togo: Sotouboua',
'TG-17' => 'Togo: Tabligbo',
'TG-19' => 'Togo: Tchamba',
'TG-20' => 'Togo: Tchaoudjo',
'TG-18' => 'Togo: Tsevie',
'TG-21' => 'Togo: Vogan',

'--TO' => '','-TO' => 'Tonga',
'TO-01' => 'Tonga: Ha',
'TO-02' => 'Tonga: Tongatapu',
'TO-03' => 'Tonga: Vava',

'--TT' => '','-TT' => 'Trinidad and Tobago',
'TT-01' => 'Trinidad and Tobago: Arima',
'TT-02' => 'Trinidad and Tobago: Caroni',
'TT-03' => 'Trinidad and Tobago: Mayaro',
'TT-04' => 'Trinidad and Tobago: Nariva',
'TT-05' => 'Trinidad and Tobago: Port-of-Spain',
'TT-06' => 'Trinidad and Tobago: Saint Andrew',
'TT-07' => 'Trinidad and Tobago: Saint David',
'TT-08' => 'Trinidad and Tobago: Saint George',
'TT-09' => 'Trinidad and Tobago: Saint Patrick',
'TT-10' => 'Trinidad and Tobago: San Fernando',
'TT-11' => 'Trinidad and Tobago: Tobago',
'TT-12' => 'Trinidad and Tobago: Victoria',

'--TN' => '','-TN' => 'Tunisia',
'TN-15' => 'Tunisia: Al Mahdiyah',
'TN-16' => 'Tunisia: Al Munastir',
'TN-02' => 'Tunisia: Al Qasrayn',
'TN-03' => 'Tunisia: Al Qayrawan',
'TN-38' => 'Tunisia: Ariana',
'TN-17' => 'Tunisia: Bajah',
'TN-18' => 'Tunisia: Banzart',
'TN-27' => 'Tunisia: Bin',
'TN-06' => 'Tunisia: Jundubah',
'TN-14' => 'Tunisia: Kef',
'TN-28' => 'Tunisia: Madanin',
'TN-39' => 'Tunisia: Manouba',
'TN-19' => 'Tunisia: Nabul',
'TN-29' => 'Tunisia: Qabis',
'TN-10' => 'Tunisia: Qafsah',
'TN-31' => 'Tunisia: Qibili',
'TN-32' => 'Tunisia: Safaqis',
'TN-33' => 'Tunisia: Sidi Bu Zayd',
'TN-22' => 'Tunisia: Silyanah',
'TN-23' => 'Tunisia: Susah',
'TN-34' => 'Tunisia: Tatawin',
'TN-35' => 'Tunisia: Tawzar',
'TN-36' => 'Tunisia: Tunis',
'TN-37' => 'Tunisia: Zaghwan',

'--TR' => '','-TR' => 'Turkey',
'TR-81' => 'Turkey: Adana',
'TR-02' => 'Turkey: Adiyaman',
'TR-03' => 'Turkey: Afyon',
'TR-04' => 'Turkey: Agri',
'TR-75' => 'Turkey: Aksaray',
'TR-05' => 'Turkey: Amasya',
'TR-68' => 'Turkey: Ankara',
'TR-07' => 'Turkey: Antalya',
'TR-86' => 'Turkey: Ardahan',
'TR-08' => 'Turkey: Artvin',
'TR-09' => 'Turkey: Aydin',
'TR-10' => 'Turkey: Balikesir',
'TR-87' => 'Turkey: Bartin',
'TR-76' => 'Turkey: Batman',
'TR-77' => 'Turkey: Bayburt',
'TR-11' => 'Turkey: Bilecik',
'TR-12' => 'Turkey: Bingol',
'TR-13' => 'Turkey: Bitlis',
'TR-14' => 'Turkey: Bolu',
'TR-15' => 'Turkey: Burdur',
'TR-16' => 'Turkey: Bursa',
'TR-17' => 'Turkey: Canakkale',
'TR-82' => 'Turkey: Cankiri',
'TR-19' => 'Turkey: Corum',
'TR-20' => 'Turkey: Denizli',
'TR-21' => 'Turkey: Diyarbakir',
'TR-93' => 'Turkey: Duzce',
'TR-22' => 'Turkey: Edirne',
'TR-23' => 'Turkey: Elazig',
'TR-24' => 'Turkey: Erzincan',
'TR-25' => 'Turkey: Erzurum',
'TR-26' => 'Turkey: Eskisehir',
'TR-83' => 'Turkey: Gaziantep',
'TR-28' => 'Turkey: Giresun',
'TR-69' => 'Turkey: Gumushane',
'TR-70' => 'Turkey: Hakkari',
'TR-31' => 'Turkey: Hatay',
'TR-32' => 'Turkey: Icel',
'TR-88' => 'Turkey: Igdir',
'TR-33' => 'Turkey: Isparta',
'TR-34' => 'Turkey: Istanbul',
'TR-35' => 'Turkey: Izmir',
'TR-46' => 'Turkey: Kahramanmaras',
'TR-89' => 'Turkey: Karabuk',
'TR-78' => 'Turkey: Karaman',
'TR-84' => 'Turkey: Kars',
'TR-37' => 'Turkey: Kastamonu',
'TR-38' => 'Turkey: Kayseri',
'TR-90' => 'Turkey: Kilis',
'TR-79' => 'Turkey: Kirikkale',
'TR-39' => 'Turkey: Kirklareli',
'TR-40' => 'Turkey: Kirsehir',
'TR-41' => 'Turkey: Kocaeli',
'TR-71' => 'Turkey: Konya',
'TR-43' => 'Turkey: Kutahya',
'TR-44' => 'Turkey: Malatya',
'TR-45' => 'Turkey: Manisa',
'TR-72' => 'Turkey: Mardin',
'TR-48' => 'Turkey: Mugla',
'TR-49' => 'Turkey: Mus',
'TR-50' => 'Turkey: Nevsehir',
'TR-73' => 'Turkey: Nigde',
'TR-52' => 'Turkey: Ordu',
'TR-91' => 'Turkey: Osmaniye',
'TR-53' => 'Turkey: Rize',
'TR-54' => 'Turkey: Sakarya',
'TR-55' => 'Turkey: Samsun',
'TR-63' => 'Turkey: Sanliurfa',
'TR-74' => 'Turkey: Siirt',
'TR-57' => 'Turkey: Sinop',
'TR-80' => 'Turkey: Sirnak',
'TR-58' => 'Turkey: Sivas',
'TR-59' => 'Turkey: Tekirdag',
'TR-60' => 'Turkey: Tokat',
'TR-61' => 'Turkey: Trabzon',
'TR-62' => 'Turkey: Tunceli',
'TR-64' => 'Turkey: Usak',
'TR-65' => 'Turkey: Van',
'TR-92' => 'Turkey: Yalova',
'TR-66' => 'Turkey: Yozgat',
'TR-85' => 'Turkey: Zonguldak',

'--TM' => '','-TM' => 'Turkmenistan',
'TM-01' => 'Turkmenistan: Ahal',
'TM-02' => 'Turkmenistan: Balkan',
'TM-03' => 'Turkmenistan: Dashoguz',
'TM-04' => 'Turkmenistan: Lebap',
'TM-05' => 'Turkmenistan: Mary',

'--UG' => '','-UG' => 'Uganda',
'UG-65' => 'Uganda: Adjumani',
'UG-77' => 'Uganda: Arua',
'UG-66' => 'Uganda: Bugiri',
'UG-67' => 'Uganda: Busia',
'UG-05' => 'Uganda: Busoga',
'UG-18' => 'Uganda: Central',
'UG-20' => 'Uganda: Eastern',
'UG-78' => 'Uganda: Iganga',
'UG-79' => 'Uganda: Kabarole',
'UG-80' => 'Uganda: Kaberamaido',
'UG-37' => 'Uganda: Kampala',
'UG-81' => 'Uganda: Kamwenge',
'UG-82' => 'Uganda: Kanungu',
'UG-08' => 'Uganda: Karamoja',
'UG-69' => 'Uganda: Katakwi',
'UG-83' => 'Uganda: Kayunga',
'UG-84' => 'Uganda: Kitgum',
'UG-85' => 'Uganda: Kyenjojo',
'UG-86' => 'Uganda: Mayuge',
'UG-87' => 'Uganda: Mbale',
'UG-88' => 'Uganda: Moroto',
'UG-89' => 'Uganda: Mpigi',
'UG-90' => 'Uganda: Mukono',
'UG-91' => 'Uganda: Nakapiripirit',
'UG-73' => 'Uganda: Nakasongola',
'UG-21' => 'Uganda: Nile',
'UG-22' => 'Uganda: North Buganda',
'UG-23' => 'Uganda: Northern',
'UG-92' => 'Uganda: Pader',
'UG-93' => 'Uganda: Rukungiri',
'UG-74' => 'Uganda: Sembabule',
'UG-94' => 'Uganda: Sironko',
'UG-95' => 'Uganda: Soroti',
'UG-12' => 'Uganda: South Buganda',
'UG-24' => 'Uganda: Southern',
'UG-96' => 'Uganda: Wakiso',
'UG-25' => 'Uganda: Western',
'UG-97' => 'Uganda: Yumbe',

'--UA' => '','-UA' => 'Ukraine',
'UA-01' => 'Ukraine: Cherkas\'ka Oblast\'',
'UA-02' => 'Ukraine: Chernihivs\'ka Oblast\'',
'UA-03' => 'Ukraine: Chernivets\'ka Oblast\'',
'UA-04' => 'Ukraine: Dnipropetrovs\'ka Oblast\'',
'UA-05' => 'Ukraine: Donets\'ka Oblast\'',
'UA-06' => 'Ukraine: Ivano-Frankivs\'ka Oblast\'',
'UA-07' => 'Ukraine: Kharkivs\'ka Oblast\'',
'UA-08' => 'Ukraine: Khersons\'ka Oblast\'',
'UA-09' => 'Ukraine: Khmel\'nyts\'ka Oblast\'',
'UA-10' => 'Ukraine: Kirovohrads\'ka Oblast\'',
'UA-11' => 'Ukraine: Krym',
'UA-12' => 'Ukraine: Kyyiv',
'UA-13' => 'Ukraine: Kyyivs\'ka Oblast\'',
'UA-14' => 'Ukraine: Luhans\'ka Oblast\'',
'UA-15' => 'Ukraine: L\'vivs\'ka Oblast\'',
'UA-16' => 'Ukraine: Mykolayivs\'ka Oblast\'',
'UA-17' => 'Ukraine: Odes\'ka Oblast\'',
'UA-18' => 'Ukraine: Poltavs\'ka Oblast\'',
'UA-19' => 'Ukraine: Rivnens\'ka Oblast\'',
'UA-20' => 'Ukraine: Sevastopol\'',
'UA-21' => 'Ukraine: Sums\'ka Oblast\'',
'UA-22' => 'Ukraine: Ternopil\'s\'ka Oblast\'',
'UA-23' => 'Ukraine: Vinnyts\'ka Oblast\'',
'UA-24' => 'Ukraine: Volyns\'ka Oblast\'',
'UA-25' => 'Ukraine: Zakarpats\'ka Oblast\'',
'UA-26' => 'Ukraine: Zaporiz\'ka Oblast\'',
'UA-27' => 'Ukraine: Zhytomyrs\'ka Oblast\'',

'--AE' => '','-AE' => 'United Arab Emirates',
'AE-01' => 'United Arab Emirates: Abu Dhabi',
'AE-02' => 'United Arab Emirates: Ajman',
'AE-03' => 'United Arab Emirates: Dubai',
'AE-04' => 'United Arab Emirates: Fujairah',
'AE-05' => 'United Arab Emirates: Ras Al Khaimah',
'AE-06' => 'United Arab Emirates: Sharjah',
'AE-07' => 'United Arab Emirates: Umm Al Quwain',

'--GB' => '','-GB' => 'United Kingdom',
'GB-T5' => 'United Kingdom: Aberdeen City',
'GB-T6' => 'United Kingdom: Aberdeenshire',
'GB-T7' => 'United Kingdom: Angus',
'GB-Q6' => 'United Kingdom: Antrim',
'GB-Q7' => 'United Kingdom: Ards',
'GB-T8' => 'United Kingdom: Argyll and Bute',
'GB-Q8' => 'United Kingdom: Armagh',
'GB-01' => 'United Kingdom: Avon',
'GB-Q9' => 'United Kingdom: Ballymena',
'GB-R1' => 'United Kingdom: Ballymoney',
'GB-R2' => 'United Kingdom: Banbridge',
'GB-A1' => 'United Kingdom: Barking and Dagenham',
'GB-A2' => 'United Kingdom: Barnet',
'GB-A3' => 'United Kingdom: Barnsley',
'GB-A4' => 'United Kingdom: Bath and North East Somerset',
'GB-A5' => 'United Kingdom: Bedfordshire',
'GB-R3' => 'United Kingdom: Belfast',
'GB-03' => 'United Kingdom: Berkshire',
'GB-A6' => 'United Kingdom: Bexley',
'GB-A7' => 'United Kingdom: Birmingham',
'GB-A8' => 'United Kingdom: Blackburn with Darwen',
'GB-A9' => 'United Kingdom: Blackpool',
'GB-X2' => 'United Kingdom: Blaenau Gwent',
'GB-B1' => 'United Kingdom: Bolton',
'GB-B2' => 'United Kingdom: Bournemouth',
'GB-B3' => 'United Kingdom: Bracknell Forest',
'GB-B4' => 'United Kingdom: Bradford',
'GB-B5' => 'United Kingdom: Brent',
'GB-X3' => 'United Kingdom: Bridgend',
'GB-B6' => 'United Kingdom: Brighton and Hove',
'GB-B7' => 'United Kingdom: Bristol',
'GB-B8' => 'United Kingdom: Bromley',
'GB-B9' => 'United Kingdom: Buckinghamshire',
'GB-C1' => 'United Kingdom: Bury',
'GB-X4' => 'United Kingdom: Caerphilly',
'GB-C2' => 'United Kingdom: Calderdale',
'GB-C3' => 'United Kingdom: Cambridgeshire',
'GB-C4' => 'United Kingdom: Camden',
'GB-X5' => 'United Kingdom: Cardiff',
'GB-X7' => 'United Kingdom: Carmarthenshire',
'GB-R4' => 'United Kingdom: Carrickfergus',
'GB-R5' => 'United Kingdom: Castlereagh',
'GB-79' => 'United Kingdom: Central',
'GB-X6' => 'United Kingdom: Ceredigion',
'GB-C5' => 'United Kingdom: Cheshire',
'GB-U1' => 'United Kingdom: Clackmannanshire',
'GB-07' => 'United Kingdom: Cleveland',
'GB-90' => 'United Kingdom: Clwyd',
'GB-R6' => 'United Kingdom: Coleraine',
'GB-X8' => 'United Kingdom: Conwy',
'GB-R7' => 'United Kingdom: Cookstown',
'GB-C6' => 'United Kingdom: Cornwall',
'GB-C7' => 'United Kingdom: Coventry',
'GB-R8' => 'United Kingdom: Craigavon',
'GB-C8' => 'United Kingdom: Croydon',
'GB-C9' => 'United Kingdom: Cumbria',
'GB-D1' => 'United Kingdom: Darlington',
'GB-X9' => 'United Kingdom: Denbighshire',
'GB-D2' => 'United Kingdom: Derby',
'GB-D3' => 'United Kingdom: Derbyshire',
'GB-S6' => 'United Kingdom: Derry',
'GB-D4' => 'United Kingdom: Devon',
'GB-D5' => 'United Kingdom: Doncaster',
'GB-D6' => 'United Kingdom: Dorset',
'GB-R9' => 'United Kingdom: Down',
'GB-D7' => 'United Kingdom: Dudley',
'GB-U2' => 'United Kingdom: Dumfries and Galloway',
'GB-U3' => 'United Kingdom: Dundee City',
'GB-S1' => 'United Kingdom: Dungannon',
'GB-D8' => 'United Kingdom: Durham',
'GB-91' => 'United Kingdom: Dyfed',
'GB-D9' => 'United Kingdom: Ealing',
'GB-U4' => 'United Kingdom: East Ayrshire',
'GB-U5' => 'United Kingdom: East Dunbartonshire',
'GB-U6' => 'United Kingdom: East Lothian',
'GB-U7' => 'United Kingdom: East Renfrewshire',
'GB-E1' => 'United Kingdom: East Riding of Yorkshire',
'GB-E2' => 'United Kingdom: East Sussex',
'GB-U8' => 'United Kingdom: Edinburgh',
'GB-W8' => 'United Kingdom: Eilean Siar',
'GB-E3' => 'United Kingdom: Enfield',
'GB-E4' => 'United Kingdom: Essex',
'GB-U9' => 'United Kingdom: Falkirk',
'GB-S2' => 'United Kingdom: Fermanagh',
'GB-V1' => 'United Kingdom: Fife',
'GB-Y1' => 'United Kingdom: Flintshire',
'GB-E5' => 'United Kingdom: Gateshead',
'GB-V2' => 'United Kingdom: Glasgow City',
'GB-E6' => 'United Kingdom: Gloucestershire',
'GB-82' => 'United Kingdom: Grampian',
'GB-17' => 'United Kingdom: Greater London',
'GB-18' => 'United Kingdom: Greater Manchester',
'GB-E7' => 'United Kingdom: Greenwich',
'GB-92' => 'United Kingdom: Gwent',
'GB-Y2' => 'United Kingdom: Gwynedd',
'GB-E8' => 'United Kingdom: Hackney',
'GB-E9' => 'United Kingdom: Halton',
'GB-F1' => 'United Kingdom: Hammersmith and Fulham',
'GB-F2' => 'United Kingdom: Hampshire',
'GB-F3' => 'United Kingdom: Haringey',
'GB-F4' => 'United Kingdom: Harrow',
'GB-F5' => 'United Kingdom: Hartlepool',
'GB-F6' => 'United Kingdom: Havering',
'GB-20' => 'United Kingdom: Hereford and Worcester',
'GB-F7' => 'United Kingdom: Herefordshire',
'GB-F8' => 'United Kingdom: Hertford',
'GB-V3' => 'United Kingdom: Highland',
'GB-F9' => 'United Kingdom: Hillingdon',
'GB-G1' => 'United Kingdom: Hounslow',
'GB-22' => 'United Kingdom: Humberside',
'GB-V4' => 'United Kingdom: Inverclyde',
'GB-X1' => 'United Kingdom: Isle of Anglesey',
'GB-G2' => 'United Kingdom: Isle of Wight',
'GB-G3' => 'United Kingdom: Islington',
'GB-G4' => 'United Kingdom: Kensington and Chelsea',
'GB-G5' => 'United Kingdom: Kent',
'GB-G6' => 'United Kingdom: Kingston upon Hull',
'GB-G7' => 'United Kingdom: Kingston upon Thames',
'GB-G8' => 'United Kingdom: Kirklees',
'GB-G9' => 'United Kingdom: Knowsley',
'GB-H1' => 'United Kingdom: Lambeth',
'GB-H2' => 'United Kingdom: Lancashire',
'GB-S3' => 'United Kingdom: Larne',
'GB-H3' => 'United Kingdom: Leeds',
'GB-H4' => 'United Kingdom: Leicester',
'GB-H5' => 'United Kingdom: Leicestershire',
'GB-H6' => 'United Kingdom: Lewisham',
'GB-S4' => 'United Kingdom: Limavady',
'GB-H7' => 'United Kingdom: Lincolnshire',
'GB-S5' => 'United Kingdom: Lisburn',
'GB-H8' => 'United Kingdom: Liverpool',
'GB-H9' => 'United Kingdom: London',
'GB-84' => 'United Kingdom: Lothian',
'GB-I1' => 'United Kingdom: Luton',
'GB-S7' => 'United Kingdom: Magherafelt',
'GB-I2' => 'United Kingdom: Manchester',
'GB-I3' => 'United Kingdom: Medway',
'GB-28' => 'United Kingdom: Merseyside',
'GB-Y3' => 'United Kingdom: Merthyr Tydfil',
'GB-I4' => 'United Kingdom: Merton',
'GB-94' => 'United Kingdom: Mid Glamorgan',
'GB-I5' => 'United Kingdom: Middlesbrough',
'GB-V5' => 'United Kingdom: Midlothian',
'GB-I6' => 'United Kingdom: Milton Keynes',
'GB-Y4' => 'United Kingdom: Monmouthshire',
'GB-V6' => 'United Kingdom: Moray',
'GB-S8' => 'United Kingdom: Moyle',
'GB-Y5' => 'United Kingdom: Neath Port Talbot',
'GB-I7' => 'United Kingdom: Newcastle upon Tyne',
'GB-I8' => 'United Kingdom: Newham',
'GB-Y6' => 'United Kingdom: Newport',
'GB-S9' => 'United Kingdom: Newry and Mourne',
'GB-T1' => 'United Kingdom: Newtownabbey',
'GB-I9' => 'United Kingdom: Norfolk',
'GB-V7' => 'United Kingdom: North Ayrshire',
'GB-T2' => 'United Kingdom: North Down',
'GB-J2' => 'United Kingdom: North East Lincolnshire',
'GB-V8' => 'United Kingdom: North Lanarkshire',
'GB-J3' => 'United Kingdom: North Lincolnshire',
'GB-J4' => 'United Kingdom: North Somerset',
'GB-J5' => 'United Kingdom: North Tyneside',
'GB-J7' => 'United Kingdom: North Yorkshire',
'GB-J1' => 'United Kingdom: Northamptonshire',
'GB-J6' => 'United Kingdom: Northumberland',
'GB-J8' => 'United Kingdom: Nottingham',
'GB-J9' => 'United Kingdom: Nottinghamshire',
'GB-K1' => 'United Kingdom: Oldham',
'GB-T3' => 'United Kingdom: Omagh',
'GB-V9' => 'United Kingdom: Orkney',
'GB-K2' => 'United Kingdom: Oxfordshire',
'GB-Y7' => 'United Kingdom: Pembrokeshire',
'GB-W1' => 'United Kingdom: Perth and Kinross',
'GB-K3' => 'United Kingdom: Peterborough',
'GB-K4' => 'United Kingdom: Plymouth',
'GB-K5' => 'United Kingdom: Poole',
'GB-K6' => 'United Kingdom: Portsmouth',
'GB-Y8' => 'United Kingdom: Powys',
'GB-K7' => 'United Kingdom: Reading',
'GB-K8' => 'United Kingdom: Redbridge',
'GB-K9' => 'United Kingdom: Redcar and Cleveland',
'GB-W2' => 'United Kingdom: Renfrewshire',
'GB-Y9' => 'United Kingdom: Rhondda Cynon Taff',
'GB-L1' => 'United Kingdom: Richmond upon Thames',
'GB-L2' => 'United Kingdom: Rochdale',
'GB-L3' => 'United Kingdom: Rotherham',
'GB-L4' => 'United Kingdom: Rutland',
'GB-L5' => 'United Kingdom: Salford',
'GB-L7' => 'United Kingdom: Sandwell',
'GB-T9' => 'United Kingdom: Scottish Borders',
'GB-L8' => 'United Kingdom: Sefton',
'GB-L9' => 'United Kingdom: Sheffield',
'GB-W3' => 'United Kingdom: Shetland Islands',
'GB-L6' => 'United Kingdom: Shropshire',
'GB-M1' => 'United Kingdom: Slough',
'GB-M2' => 'United Kingdom: Solihull',
'GB-M3' => 'United Kingdom: Somerset',
'GB-W4' => 'United Kingdom: South Ayrshire',
'GB-96' => 'United Kingdom: South Glamorgan',
'GB-M6' => 'United Kingdom: South Gloucestershire',
'GB-W5' => 'United Kingdom: South Lanarkshire',
'GB-M7' => 'United Kingdom: South Tyneside',
'GB-37' => 'United Kingdom: South Yorkshire',
'GB-M4' => 'United Kingdom: Southampton',
'GB-M5' => 'United Kingdom: Southend-on-Sea',
'GB-M8' => 'United Kingdom: Southwark',
'GB-N1' => 'United Kingdom: St. Helens',
'GB-M9' => 'United Kingdom: Staffordshire',
'GB-W6' => 'United Kingdom: Stirling',
'GB-N2' => 'United Kingdom: Stockport',
'GB-N3' => 'United Kingdom: Stockton-on-Tees',
'GB-N4' => 'United Kingdom: Stoke-on-Trent',
'GB-T4' => 'United Kingdom: Strabane',
'GB-87' => 'United Kingdom: Strathclyde',
'GB-N5' => 'United Kingdom: Suffolk',
'GB-N6' => 'United Kingdom: Sunderland',
'GB-N7' => 'United Kingdom: Surrey',
'GB-N8' => 'United Kingdom: Sutton',
'GB-Z1' => 'United Kingdom: Swansea',
'GB-N9' => 'United Kingdom: Swindon',
'GB-O1' => 'United Kingdom: Tameside',
'GB-88' => 'United Kingdom: Tayside',
'GB-O2' => 'United Kingdom: Telford and Wrekin',
'GB-O3' => 'United Kingdom: Thurrock',
'GB-O4' => 'United Kingdom: Torbay',
'GB-Z2' => 'United Kingdom: Torfaen',
'GB-O5' => 'United Kingdom: Tower Hamlets',
'GB-O6' => 'United Kingdom: Trafford',
'GB-41' => 'United Kingdom: Tyne and Wear',
'GB-Z3' => 'United Kingdom: Vale of Glamorgan',
'GB-O7' => 'United Kingdom: Wakefield',
'GB-O8' => 'United Kingdom: Walsall',
'GB-O9' => 'United Kingdom: Waltham Forest',
'GB-P1' => 'United Kingdom: Wandsworth',
'GB-P2' => 'United Kingdom: Warrington',
'GB-P3' => 'United Kingdom: Warwickshire',
'GB-P4' => 'United Kingdom: West Berkshire',
'GB-W7' => 'United Kingdom: West Dunbartonshire',
'GB-97' => 'United Kingdom: West Glamorgan',
'GB-W9' => 'United Kingdom: West Lothian',
'GB-43' => 'United Kingdom: West Midlands',
'GB-P6' => 'United Kingdom: West Sussex',
'GB-45' => 'United Kingdom: West Yorkshire',
'GB-P5' => 'United Kingdom: Westminster',
'GB-P7' => 'United Kingdom: Wigan',
'GB-P8' => 'United Kingdom: Wiltshire',
'GB-P9' => 'United Kingdom: Windsor and Maidenhead',
'GB-Q1' => 'United Kingdom: Wirral',
'GB-Q2' => 'United Kingdom: Wokingham',
'GB-Q3' => 'United Kingdom: Wolverhampton',
'GB-Q4' => 'United Kingdom: Worcestershire',
'GB-Z4' => 'United Kingdom: Wrexham',
'GB-Q5' => 'United Kingdom: York',

'--US' => '','-US' => 'United States',
'US-AL' => 'United States: Alabama',
'US-AK' => 'United States: Alaska',
'US-AS' => 'United States: American Samoa',
'US-AZ' => 'United States: Arizona',
'US-AR' => 'United States: Arkansas',
'US-AA' => 'United States: Armed Forces Americas',
'US-AE' => 'United States: Armed Forces Europe',
'US-AP' => 'United States: Armed Forces Pacific',
'US-CA' => 'United States: California',
'US-CO' => 'United States: Colorado',
'US-CT' => 'United States: Connecticut',
'US-DE' => 'United States: Delaware',
'US-DC' => 'United States: District of Columbia',
'US-FM' => 'United States: Federated States of Micronesia',
'US-FL' => 'United States: Florida',
'US-GA' => 'United States: Georgia',
'US-GU' => 'United States: Guam',
'US-HI' => 'United States: Hawaii',
'US-ID' => 'United States: Idaho',
'US-IL' => 'United States: Illinois',
'US-IN' => 'United States: Indiana',
'US-IA' => 'United States: Iowa',
'US-KS' => 'United States: Kansas',
'US-KY' => 'United States: Kentucky',
'US-LA' => 'United States: Louisiana',
'US-ME' => 'United States: Maine',
'US-MH' => 'United States: Marshall Islands',
'US-MD' => 'United States: Maryland',
'US-MA' => 'United States: Massachusetts',
'US-MI' => 'United States: Michigan',
'US-MN' => 'United States: Minnesota',
'US-MS' => 'United States: Mississippi',
'US-MO' => 'United States: Missouri',
'US-MT' => 'United States: Montana',
'US-NE' => 'United States: Nebraska',
'US-NV' => 'United States: Nevada',
'US-NH' => 'United States: New Hampshire',
'US-NJ' => 'United States: New Jersey',
'US-NM' => 'United States: New Mexico',
'US-NY' => 'United States: New York',
'US-NC' => 'United States: North Carolina',
'US-ND' => 'United States: North Dakota',
'US-MP' => 'United States: Northern Mariana Islands',
'US-OH' => 'United States: Ohio',
'US-OK' => 'United States: Oklahoma',
'US-OR' => 'United States: Oregon',
'US-PW' => 'United States: Palau',
'US-PA' => 'United States: Pennsylvania',
'US-PR' => 'United States: Puerto Rico',
'US-RI' => 'United States: Rhode Island',
'US-SC' => 'United States: South Carolina',
'US-SD' => 'United States: South Dakota',
'US-TN' => 'United States: Tennessee',
'US-TX' => 'United States: Texas',
'US-UT' => 'United States: Utah',
'US-VT' => 'United States: Vermont',
'US-VI' => 'United States: Virgin Islands',
'US-VA' => 'United States: Virginia',
'US-WA' => 'United States: Washington',
'US-WV' => 'United States: West Virginia',
'US-WI' => 'United States: Wisconsin',
'US-WY' => 'United States: Wyoming',

'--UY' => '','-UY' => 'Uruguay',
'UY-01' => 'Uruguay: Artigas',
'UY-02' => 'Uruguay: Canelones',
'UY-03' => 'Uruguay: Cerro Largo',
'UY-04' => 'Uruguay: Colonia',
'UY-05' => 'Uruguay: Durazno',
'UY-06' => 'Uruguay: Flores',
'UY-07' => 'Uruguay: Florida',
'UY-08' => 'Uruguay: Lavalleja',
'UY-09' => 'Uruguay: Maldonado',
'UY-10' => 'Uruguay: Montevideo',
'UY-11' => 'Uruguay: Paysandu',
'UY-12' => 'Uruguay: Rio Negro',
'UY-13' => 'Uruguay: Rivera',
'UY-14' => 'Uruguay: Rocha',
'UY-15' => 'Uruguay: Salto',
'UY-16' => 'Uruguay: San Jose',
'UY-17' => 'Uruguay: Soriano',
'UY-18' => 'Uruguay: Tacuarembo',
'UY-19' => 'Uruguay: Treinta y Tres',

'--UZ' => '','-UZ' => 'Uzbekistan',
'UZ-01' => 'Uzbekistan: Andijon',
'UZ-02' => 'Uzbekistan: Bukhoro',
'UZ-03' => 'Uzbekistan: Farghona',
'UZ-04' => 'Uzbekistan: Jizzakh',
'UZ-05' => 'Uzbekistan: Khorazm',
'UZ-06' => 'Uzbekistan: Namangan',
'UZ-07' => 'Uzbekistan: Nawoiy',
'UZ-08' => 'Uzbekistan: Qashqadaryo',
'UZ-09' => 'Uzbekistan: Qoraqalpoghiston',
'UZ-10' => 'Uzbekistan: Samarqand',
'UZ-11' => 'Uzbekistan: Sirdaryo',
'UZ-12' => 'Uzbekistan: Surkhondaryo',
'UZ-13' => 'Uzbekistan: Toshkent',
'UZ-14' => 'Uzbekistan: Toshkent',

'--VU' => '','-VU' => 'Vanuatu',
'VU-05' => 'Vanuatu: Ambrym',
'VU-06' => 'Vanuatu: Aoba',
'VU-08' => 'Vanuatu: Efate',
'VU-09' => 'Vanuatu: Epi',
'VU-10' => 'Vanuatu: Malakula',
'VU-16' => 'Vanuatu: Malampa',
'VU-11' => 'Vanuatu: Paama',
'VU-17' => 'Vanuatu: Penama',
'VU-12' => 'Vanuatu: Pentecote',
'VU-13' => 'Vanuatu: Sanma',
'VU-18' => 'Vanuatu: Shefa',
'VU-14' => 'Vanuatu: Shepherd',
'VU-15' => 'Vanuatu: Tafea',
'VU-07' => 'Vanuatu: Torba',

'--VE' => '','-VE' => 'Venezuela',
'VE-01' => 'Venezuela: Amazonas',
'VE-02' => 'Venezuela: Anzoategui',
'VE-03' => 'Venezuela: Apure',
'VE-04' => 'Venezuela: Aragua',
'VE-05' => 'Venezuela: Barinas',
'VE-06' => 'Venezuela: Bolivar',
'VE-07' => 'Venezuela: Carabobo',
'VE-08' => 'Venezuela: Cojedes',
'VE-09' => 'Venezuela: Delta Amacuro',
'VE-24' => 'Venezuela: Dependencias Federales',
'VE-25' => 'Venezuela: Distrito Federal',
'VE-11' => 'Venezuela: Falcon',
'VE-12' => 'Venezuela: Guarico',
'VE-13' => 'Venezuela: Lara',
'VE-14' => 'Venezuela: Merida',
'VE-15' => 'Venezuela: Miranda',
'VE-16' => 'Venezuela: Monagas',
'VE-17' => 'Venezuela: Nueva Esparta',
'VE-18' => 'Venezuela: Portuguesa',
'VE-19' => 'Venezuela: Sucre',
'VE-20' => 'Venezuela: Tachira',
'VE-21' => 'Venezuela: Trujillo',
'VE-26' => 'Venezuela: Vargas',
'VE-22' => 'Venezuela: Yaracuy',
'VE-23' => 'Venezuela: Zulia',

'--VN' => '','-VN' => 'Vietnam',
'VN-43' => 'Vietnam: An Giang',
'VN-53' => 'Vietnam: Ba Ria-Vung Tau',
'VN-02' => 'Vietnam: Bac Thai',
'VN-03' => 'Vietnam: Ben Tre',
'VN-54' => 'Vietnam: Binh Dinh',
'VN-55' => 'Vietnam: Binh Thuan',
'VN-56' => 'Vietnam: Can Tho',
'VN-05' => 'Vietnam: Cao Bang',
'VN-44' => 'Vietnam: Dac Lac',
'VN-45' => 'Vietnam: Dong Nai',
'VN-20' => 'Vietnam: Dong Nam Bo',
'VN-46' => 'Vietnam: Dong Thap',
'VN-57' => 'Vietnam: Gia Lai',
'VN-11' => 'Vietnam: Ha Bac',
'VN-58' => 'Vietnam: Ha Giang',
'VN-51' => 'Vietnam: Ha Noi',
'VN-59' => 'Vietnam: Ha Tay',
'VN-60' => 'Vietnam: Ha Tinh',
'VN-12' => 'Vietnam: Hai Hung',
'VN-13' => 'Vietnam: Hai Phong',
'VN-52' => 'Vietnam: Ho Chi Minh',
'VN-61' => 'Vietnam: Hoa Binh',
'VN-62' => 'Vietnam: Khanh Hoa',
'VN-47' => 'Vietnam: Kien Giang',
'VN-63' => 'Vietnam: Kon Tum',
'VN-22' => 'Vietnam: Lai Chau',
'VN-23' => 'Vietnam: Lam Dong',
'VN-39' => 'Vietnam: Lang Son',
'VN-64' => 'Vietnam: Lao Cai',
'VN-24' => 'Vietnam: Long An',
'VN-48' => 'Vietnam: Minh Hai',
'VN-65' => 'Vietnam: Nam Ha',
'VN-66' => 'Vietnam: Nghe An',
'VN-67' => 'Vietnam: Ninh Binh',
'VN-68' => 'Vietnam: Ninh Thuan',
'VN-69' => 'Vietnam: Phu Yen',
'VN-70' => 'Vietnam: Quang Binh',
'VN-29' => 'Vietnam: Quang Nam-Da Nang',
'VN-71' => 'Vietnam: Quang Ngai',
'VN-30' => 'Vietnam: Quang Ninh',
'VN-72' => 'Vietnam: Quang Tri',
'VN-73' => 'Vietnam: Soc Trang',
'VN-32' => 'Vietnam: Son La',
'VN-49' => 'Vietnam: Song Be',
'VN-33' => 'Vietnam: Tay Ninh',
'VN-35' => 'Vietnam: Thai Binh',
'VN-34' => 'Vietnam: Thanh Hoa',
'VN-74' => 'Vietnam: Thua Thien',
'VN-37' => 'Vietnam: Tien Giang',
'VN-75' => 'Vietnam: Tra Vinh',
'VN-76' => 'Vietnam: Tuyen Quang',
'VN-77' => 'Vietnam: Vinh Long',
'VN-50' => 'Vietnam: Vinh Phu',
'VN-78' => 'Vietnam: Yen Bai',

'--YE' => '','-YE' => 'Yemen',
'YE-01' => 'Yemen: Abyan',
'YE-20' => 'Yemen: Al Bayda\'',
'YE-08' => 'Yemen: Al Hudaydah',
'YE-21' => 'Yemen: Al Jawf',
'YE-03' => 'Yemen: Al Mahrah',
'YE-10' => 'Yemen: Al Mahwit',
'YE-11' => 'Yemen: Dhamar',
'YE-04' => 'Yemen: Hadramawt',
'YE-22' => 'Yemen: Hajjah',
'YE-23' => 'Yemen: Ibb',
'YE-24' => 'Yemen: Lahij',
'YE-14' => 'Yemen: Ma\'rib',
'YE-15' => 'Yemen: Sa',
'YE-16' => 'Yemen: San',
'YE-05' => 'Yemen: Shabwah',
'YE-25' => 'Yemen: Ta',

'--ZM' => '','-ZM' => 'Zambia',
'ZM-02' => 'Zambia: Central',
'ZM-08' => 'Zambia: Copperbelt',
'ZM-03' => 'Zambia: Eastern',
'ZM-04' => 'Zambia: Luapula',
'ZM-09' => 'Zambia: Lusaka',
'ZM-05' => 'Zambia: Northern',
'ZM-06' => 'Zambia: North-Western',
'ZM-07' => 'Zambia: Southern',
'ZM-01' => 'Zambia: Western',

'--ZW' => '','-ZW' => 'Zimbabwe',
'ZW-09' => 'Zimbabwe: Bulawayo',
'ZW-10' => 'Zimbabwe: Harare',
'ZW-01' => 'Zimbabwe: Manicaland',
'ZW-03' => 'Zimbabwe: Mashonaland Central',
'ZW-04' => 'Zimbabwe: Mashonaland East',
'ZW-05' => 'Zimbabwe: Mashonaland West',
'ZW-08' => 'Zimbabwe: Masvingo',
'ZW-06' => 'Zimbabwe: Matabeleland North',
'ZW-07' => 'Zimbabwe: Matabeleland South',
'ZW-02' => 'Zimbabwe: Midlands\'

regularlabs/fields/flexicontent.php000064400000002721152177723700013545 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_FlexiContent extends \RegularLabs\Library\FieldGroup
{
	public $type          = 'FlexiContent';
	public $default_group = 'Tags';

	protected function getInput()
	{
		if ($error = $this->missingFilesOrTables(['tags', 'types']))
		{
			return $error;
		}

		return $this->getSelectList();
	}

	function getTags()
	{
		$query = $this->db->getQuery(true)
			->select('t.name as id, t.name')
			->from('#__flexicontent_tags AS t')
			->where('t.published = 1')
			->where('t.name != ' . $this->db->quote(''))
			->group('t.name')
			->order('t.name');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list);
	}

	function getTypes()
	{
		$query = $this->db->getQuery(true)
			->select('t.id, t.name')
			->from('#__flexicontent_types AS t')
			->where('t.published = 1')
			->order('t.name, t.id');
		$this->db->setQuery($query);
		$list = $this->db->loadObjectList();

		return $this->getOptionsByList($list);
	}
}
regularlabs/fields/onlypro.php000064400000004356152177723700012553 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\Extension as RL_Extension;

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_OnlyPro extends \RegularLabs\Library\Field
{
	public $type = 'OnlyPro';

	protected function getLabel()
	{
		$label   = $this->prepareText($this->get('label'));
		$tooltip = $this->prepareText($this->get('description'));

		if ( ! $label && ! $tooltip)
		{
			return '</div><div>' . $this->getText();
		}

		if ( ! $label)
		{
			return $tooltip;
		}

		if ( ! $tooltip)
		{
			return $label;
		}

		return '<label class="hasPopover" title="' . $label . '" data-content="' . htmlentities($tooltip) . '">'
			. $label
			. '</label>';
	}

	protected function getInput()
	{
		$label   = $this->prepareText($this->get('label'));
		$tooltip = $this->prepareText($this->get('description'));

		if ( ! $label && ! $tooltip)
		{
			return '';
		}

		return $this->getText();
	}

	protected function getText()
	{
		$text = JText::_('RL_ONLY_AVAILABLE_IN_PRO');
		$text = '<em>' . $text . '</em>';

		$extension = $this->getExtensionName();

		$alias = RL_Extension::getAliasByName($extension);

		if ($alias)
		{
			$text = '<a href="https://www.regularlabs.com/extensions/' . $extension . '/features" target="_blank">'
				. $text
				. '</a>';
		}

		$class = $this->get('class');
		$class = $class ? ' class="' . $class . '"' : '';

		return '<div' . $class . '>' . $text . '</div>';
	}

	protected function getExtensionName()
	{
		if ($extension = $this->form->getValue('element'))
		{
			return $extension;
		}

		if ($extension = JFactory::getApplication()->input->get('component'))
		{
			return str_replace('com_', '', $extension);
		}

		if ($extension = JFactory::getApplication()->input->get('folder'))
		{
			$extension = explode('.', $extension);

			return array_pop($extension);
		}

		return false;
	}
}
regularlabs/fields/header_library.php000064400000003060152177723700014014 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;

require_once __DIR__ . '/header.php';

class JFormFieldRL_Header_Library extends JFormFieldRL_Header
{
	protected function getInput()
	{
		$extensions = [
			'Add to Menu',
			'Advanced Module Manager',
			'Advanced Template Manager',
			'Articles Anywhere',
			'Articles Field',
			'Better Preview',
			'Better Trash',
			'Cache Cleaner',
			'CDN for Joomla!',
			'Components Anywhere',
			'Conditional Content',
			'Content Templater',
			'DB Replacer',
			'Dummy Content',
			'Email Protector',
			'GeoIP',
			'IP Login',
			'Modals',
			'Modules Anywhere',
			'Quick Index',
			'Regular Labs Extension Manager',
			'ReReplacer',
			'Simple User Notes',
			'Sliders',
			'Snippets',
			'Sourcerer',
			'Tabs',
			'Tooltips',
			'What? Nothing!',
		];

		$list = '<ul><li>' . implode('</li><li>', $extensions) . '</li></ul>';

		$attributes = $this->element->attributes();

		$warning = '';
		if (isset($attributes['warning']))
		{
			$warning = '<div class="alert alert-danger">' . JText::_($attributes['warning']) . '</div>';
		}

		$this->element->attributes()['description'] = JText::sprintf($attributes['description'], $warning, $list);

		return parent::getInput();
	}
}
regularlabs/fields/isinstalled.php000064400000001777152177723700013370 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\Extension as RL_Extension;

jimport('joomla.form.formfield');

if ( ! is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	return;
}

require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';

class JFormFieldRL_IsInstalled extends \RegularLabs\Library\Field
{
	public $type = 'IsInstalled';

	protected function getLabel()
	{
		return '';
	}

	protected function getInput()
	{
		$is_installed = RL_Extension::isInstalled($this->get('extension'), $this->get('extension_type'), $this->get('folder'));

		return '<input type="hidden" name="' . $this->name . '" id="' . $this->id . '" value="' . (int) $is_installed . '">';
	}
}
regularlabs/regularlabs.xml000064400000002017152177723700012107 0ustar00<?xml version="1.0" encoding="utf-8"?>
<extension version="3.9" type="library" method="upgrade">
	<name>Regular Labs Library</name>
	<libraryname>regularlabs</libraryname>
	<description></description>
	<version>19.7.21312</version>
	<creationDate>July 2019</creationDate>
	<author>Regular Labs (Peter van Westen)</author>
	<authorEmail>info@regularlabs.com</authorEmail>
	<authorUrl>https://www.regularlabs.com</authorUrl>
	<copyright>Copyright © 2018 Regular Labs - All Rights Reserved</copyright>
	<license>http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL</license>

	<scriptfile>script.install.php</scriptfile>

	<files>
		<folder>vendor</folder>
		<folder>src</folder>
		<file>autoload.php</file>
		<file>regularlabs.xml</file>
		<folder>fields</folder>
		<folder>helpers</folder>
		<filename>script.install.helper.php</filename>
	</files>

	<media folder="media" destination="regularlabs">
		<folder>css</folder>
		<folder>fonts</folder>
		<folder>images</folder>
		<folder>js</folder>
		<folder>less</folder>
	</media>
</extension>
regularlabs/script.install.php000064400000001511152177723700012542 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

if ( ! class_exists('RegularLabsInstallerScript'))
{
	require_once __DIR__ . '/script.install.helper.php';

	class RegularLabsInstallerScript extends RegularLabsInstallerScriptHelper
	{
		public $name           = 'Regular Labs Library';
		public $alias          = 'regularlabs';
		public $extension_type = 'library';

		public function onBeforeInstall($route)
		{
			if ( ! $this->isNewer())
			{
				$this->softbreak = true;

				return false;
			}

			return true;
		}
	}
}
regularlabs/src/Conditions.php000064400000044175152177723700012506 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

jimport('joomla.filesystem.file');

/**
 * Class Conditions
 * @package RegularLabs\Library
 */
class Conditions
{
	static $installed_extensions = null;
	static $params               = null;

	public static function pass($conditions, $matching_method = 'all', $article = null, $module = null)
	{
		if (empty($conditions))
		{
			return true;
		}

		$article_id      = isset($article->id) ? $article->id : '';
		$module_id       = isset($module->id) ? $module->id : '';
		$matching_method = in_array($matching_method, ['any', 'or']) ? 'any' : 'all';
		$cache_id        = 'pass_' . $article_id . '_' . $module_id . '_' . $matching_method . '_' . json_encode($conditions);

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$pass = (bool) ($matching_method == 'all');

		foreach (self::getTypes() as $type)
		{
			// Break if not passed and matching method is ALL
			// Or if passed and matching method is ANY
			if (
				( ! $pass && $matching_method == 'all')
				|| ($pass && $matching_method == 'any')
			)
			{
				break;
			}

			if ( ! isset($conditions[$type]))
			{
				continue;
			}

			$pass = self::passByType($conditions[$type], $type, $article, $module);
		}

		return Cache::set(
			$cache_id,
			$pass
		);
	}

	public static function hasConditions($conditions)
	{
		if (empty($conditions))
		{
			return false;
		}

		foreach (self::getTypes() as $type)
		{
			if (isset($conditions[$type]) && isset($conditions[$type]->include_type) && $conditions[$type]->include_type)
			{
				return true;
			}
		}

		return false;
	}

	public static function getConditionsFromParams(&$params)
	{
		$cache_id = 'getConditionsFromParams_' . json_encode($params);

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		self::renameParamKeys($params);

		$types = [];

		foreach (self::getTypes() as $id => $type)
		{
			if (empty($params->conditions[$id]))
			{
				continue;
			}

			$types[$type] = (object) [
				'include_type' => $params->conditions[$id],
				'selection'    => [],
				'params'       => (object) [],
			];

			if (isset($params->conditions[$id . '_selection']))
			{
				$types[$type]->selection = self::getSelection($params->conditions[$id . '_selection'], $type);
			}

			self::addParams($types[$type], $type, $id, $params);
		}

		return Cache::set(
			$cache_id,
			$types
		);
	}

	public static function getConditionsFromTagAttributes(&$attributes, $only_types = [])
	{
		$conditions = [];

		PluginTag::replaceKeyAliases($attributes, self::getTypeAliases(), true);
		$types = self::getTypes($only_types);

		if (empty($types))
		{
			return $conditions;
		}

		$type_params = [];

		foreach ($attributes as $type_param => $value)
		{
			if (strpos($type_param, '_') === false)
			{
				continue;
			}

			list($type, $param) = explode('_', $type_param, 2);

			$condition_type = self::getType($type, $only_types);

			if ( ! $condition_type)
			{
				continue;
			}

			$type_params[$type_param] = $value;
			unset($attributes->{$type_param});
		}

		foreach ($attributes as $type => $value)
		{
			if (empty($value))
			{
				continue;
			}

			$condition_type = self::getType($type, $only_types);

			if ( ! $condition_type)
			{
				continue;
			}

			$value = html_entity_decode($value);

			$params             = self::getDefaultParamsByType($condition_type, $type);
			$params->conditions = $type_params;

			$reverse = false;

			$selection = self::getSelectionFromTagAttribute($condition_type, $value, $params, $reverse);

			$condition = (object) [
				'include_type' => $reverse ? 2 : 1,
				'selection'    => $selection,
				'params'       => (object) [],
			];

			self::addParams($condition, $condition_type, $type, $params);

			$conditions[$condition_type] = $condition;
		}

		return $conditions;
	}

	private static function initParametersByType(&$params, $type = '')
	{
		$params->class_name = str_replace('.', '', $type);

		$params->include_type = self::getConditionState($params->include_type);
	}

	private static function passByType($condition, $type, $article = null, $module = null)
	{
		$article_id   = isset($article->id) ? $article->id : '';
		$module_id    = isset($module->id) ? $module->id : '';
		$cache_prefix = 'passByType_' . $type . '_' . $article_id . '_' . $module_id;
		$cache_id     = $cache_prefix . '_' . json_encode($condition);

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		self::initParametersByType($condition, $type);

		$cache_id = $cache_prefix . '_' . json_encode($condition);

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$pass = false;

		switch ($condition->include_type)
		{
			case 'all':
				$pass = true;
				break;

			case 'none':
				$pass = false;
				break;

			default:
				if ( ! file_exists(__DIR__ . '/Condition/' . $condition->class_name . '.php'))
				{
					break;
				}

				$className = '\\RegularLabs\\Library\\Condition\\' . $condition->class_name;

				$class = new $className($condition, $article, $module);

				$class->beforePass();

				$pass = $class->pass();

				break;
		}

		return Cache::set(
			$cache_id,
			$pass
		);
	}

	private static function getConditionState($include_type)
	{
		switch ($include_type . '')
		{
			case 1:
			case 'include':
				return 'include';

			case 2:
			case 'exclude':
				return 'exclude';

			case 3:
			case -1:
			case 'none':
				return 'none';

			default:
				return 'all';
		}
	}

	private static function makeArray($array = '', $delimiter = ',', $trim = true)
	{
		if (empty($array))
		{
			return [];
		}

		$cache_id = 'makeArray_' . json_encode($array) . '_' . $delimiter . '_' . $trim;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$array = self::mixedDataToArray($array, $delimiter);

		if (empty($array))
		{
			return $array;
		}

		if ( ! $trim)
		{
			return $array;
		}

		foreach ($array as $k => $v)
		{
			if ( ! is_string($v))
			{
				continue;
			}

			$array[$k] = trim($v);
		}

		return Cache::set(
			$cache_id,
			$array
		);
	}

	private static function mixedDataToArray($array = '', $delimiter = ',')
	{
		if ( ! is_array($array))
		{
			return explode($delimiter, $array);
		}

		if (empty($array))
		{
			return $array;
		}

		if (isset($array[0]) && is_array($array[0]))
		{
			return $array[0];
		}

		if (count($array) === 1 && strpos($array[0], $delimiter) !== false)
		{
			return explode($delimiter, $array[0]);
		}

		return $array;
	}

	private static function renameParamKeys(&$params)
	{
		$params->conditions = isset($params->conditions) ? $params->conditions : [];

		foreach ($params as $key => $value)
		{
			if (strpos($key, 'condition_') === false && strpos($key, 'assignto_') === false)
			{
				continue;
			}

			$new_key                      = substr($key, strpos($key, '_') + 1);
			$params->conditions[$new_key] = $value;

			unset($params->{$key});
		}
	}

	private static function getSelection($selection, $type = '')
	{
		if (in_array($type, self::getNotArrayTextAreaTypes()))
		{
			return $selection;
		}

		$delimiter = in_array($type, self::getTextAreaTypes()) ? "\n" : ',';

		return self::makeArray($selection, $delimiter);
	}

	private static function getSelectionFromTagAttribute($type, $value, &$params, &$reverse)
	{
		if ($type == 'Date.Date')
		{
			$value = str_replace('from', '', $value);
			$dates = explode(' - ', str_replace('to', ' - ', $value));

			$params->ignore_time_zone = true;

			if ( ! empty($dates[0]))
			{
				$params->publish_up = date('Y-m-d H:i:s', strtotime($dates[0]));
			}

			if ( ! empty($dates[1]))
			{
				$params->publish_down = date('Y-m-d H:i:s', strtotime($dates[1]));
			}

			return [];
		}

		if ($type == 'Date.Time')
		{
			$value = str_replace('from', '', $value);
			$dates = explode(' - ', str_replace('to', ' - ', $value));

			$params->publish_up   = $dates[0];
			$params->publish_down = isset($dates[1]) ? $dates[1] : $dates[0];

			return [];
		}

		if (in_array($type, self::getTextAreaTypes()))
		{
			$value = Html::convertWysiwygToPlainText($value);
		}

		if (strpos($value, '!NOT!') === 0)
		{
			$reverse = true;
			$value   = substr($value, 5);
		}

		if ( ! in_array($type, self::getNotArrayTextAreaTypes()))
		{
			$value = str_replace('[[:COMMA:]]', ',', str_replace(',', '[[:SPLIT:]]', str_replace('\\,', '[[:COMMA:]]', $value)));
			$value = explode('[[:SPLIT:]]', $value);
		}

		return $value;
	}

	private static function getDefaultParamsByType($condition_type, $type)
	{
		switch ($condition_type)
		{
			case 'Content.Category':
				return (object) [
					'assignto_' . $type . '_inc' => [
						'inc_cats',
						'inc_arts',
					],
				];

			case 'Easyblog.Category':
			case 'K2.Category':
			case 'Zoo.Category':
			case 'Hikashop.Category':
			case 'Mijoshop.Category':
			case 'Redshop.Category':
			case 'Virtuemart.Category':
				return (object) [
					'assignto_' . $type . '_inc' => [
						'inc_cats',
						'inc_items',
					],
				];

			default:
				return (object) [];
		}
	}

	private static function addParams(&$object, $type, $id, &$params)
	{
		$bool_params  = [];
		$array_params = [];
		$includes     = [];

		switch ($type)
		{
			case 'Menu':
				$bool_params = ['inc_children', 'inc_noitemid'];
				break;

			case 'Date.Date':
				$bool_params = ['publish_up', 'publish_down', 'recurring', 'ignore_time_zone'];
				break;

			case 'Date.Season':
				$bool_params = ['hemisphere'];
				break;

			case 'Date.Time':
				$bool_params = ['publish_up', 'publish_down'];
				break;

			case 'User.Grouplevel':
				$bool_params = ['inc_children'];
				break;

			case 'Url':
				if (is_array($object->selection))
				{
					$object->selection = implode("\n", $object->selection);
				}
				if (isset($params->conditions['urls_selection_sef']))
				{
					$object->selection .= "\n" . $params->conditions['urls_selection_sef'];
				}
				$object->selection     = trim(str_replace("\r", '', $object->selection));
				$object->selection     = explode("\n", $object->selection);
				$object->params->regex = isset($params->conditions['urls_regex']) ? $params->conditions['urls_regex'] : false;
				break;

			case 'Agent.Browser':
				if ( ! empty($params->conditions['mobile_selection']))
				{
					$object->selection = array_merge(self::makeArray($object->selection), self::makeArray($params->conditions['mobile_selection']));
				}
				if ( ! empty($params->conditions['searchbots_selection']))
				{
					$object->selection = array_merge($object->selection, self::makeArray($params->conditions['searchbots_selection']));
				}
				break;

			case 'Tag':
				$bool_params = ['inc_children'];
				break;

			case 'Content.Category':
				$bool_params = ['inc_children'];
				$includes    = ['cats' => 'categories', 'arts' => 'articles', 'others'];
				break;

			case 'Easyblog.Category':
			case 'K2.Category':
			case 'Hikashop.Category':
			case 'Mijoshop.Category':
			case 'Redshop.Category':
			case 'Virtuemart.Category':
				$bool_params = ['inc_children'];
				$includes    = ['cats' => 'categories', 'items'];
				break;

			case 'Zoo.Category':
				$bool_params = ['inc_children'];
				$includes    = ['apps', 'cats' => 'categories', 'items'];
				break;

			case 'Easyblog.Tag':
			case 'Flexicontent.Tag':
			case 'K2.Tag':
				$includes = ['tags', 'items'];
				break;

			case 'Content.Article':
				$bool_params = ['content_keywords', 'keywords' => 'meta_keywords', 'authors'];
				break;

			case 'K2.Item':
				$bool_params = ['content_keywords', 'meta_keywords', 'authors'];
				break;

			case 'Easyblog.Item':
				$bool_params = ['content_keywords', 'authors'];
				break;

			case 'Zoo.Item':
				$bool_params = ['authors'];
				break;
		}

		if (in_array($type, self::getMatchAllTypes()))
		{
			$bool_params[] = 'match_all';

			if (count($object->selection) == 1 && strpos($object->selection[0], '+') !== false)
			{
				$object->selection = ArrayHelper::toArray($object->selection[0], '+');
				$params->match_all = true;
			}
		}

		if (empty($bool_params) && empty($array_params) && empty($includes))
		{
			return;
		}

		self::addParamsByType($object, $id, $params, $bool_params, $array_params, $includes);
	}

	private static function addParamsByType(&$object, $id, $params, $bool_params = [], $array_params = [], $includes = [])
	{
		foreach ($bool_params as $key => $param)
		{
			$key                      = is_numeric($key) ? $param : $key;
			$object->params->{$param} = self::getTypeParamValue($id, $params, $key);
		}

		foreach ($array_params as $key => $param)
		{
			$key                      = is_numeric($key) ? $param : $key;
			$object->params->{$param} = self::getTypeParamValue($id, $params, $key, true);
		}

		if (empty($includes))
		{
			return;
		}

		$incs = self::getTypeParamValue($id, $params, 'inc', true);

		foreach ($includes as $key => $param)
		{
			$key                               = is_numeric($key) ? $param : $key;
			$object->params->{'inc_' . $param} = in_array('inc_' . $key, $incs) ? 1 : 0;
		}

		unset($object->params->inc);
	}

	private static function getTypeParamValue($id, $params, $key, $is_array = false)
	{
		if (isset($params->conditions) && isset($params->conditions[$id . '_' . $key]))
		{
			return $params->conditions[$id . '_' . $key];
		}

		if (isset($params->{'assignto_' . $id . '_' . $key}))
		{
			return $params->{'assignto_' . $id . '_' . $key};
		}

		if (isset($params->{$key}))
		{
			return $params->{$key};
		}

		if ($is_array)
		{
			return [];
		}

		return 0;
	}

	private static function getTypes($only_types = [])
	{
		$types = [
			'menuitems'             => 'Menu',
			'homepage'              => 'Homepage',
			'date'                  => 'Date.Date',
			'seasons'               => 'Date.Season',
			'months'                => 'Date.Month',
			'days'                  => 'Date.Day',
			'time'                  => 'Date.Time',
			'accesslevels'          => 'User.Accesslevel',
			'usergrouplevels'       => 'User.Grouplevel',
			'users'                 => 'User.User',
			'languages'             => 'Language',
			'ips'                   => 'Ip',
			'geocontinents'         => 'Geo.Continent',
			'geocountries'          => 'Geo.Country',
			'georegions'            => 'Geo.Region',
			'geopostalcodes'        => 'Geo.Postalcode',
			'templates'             => 'Template',
			'urls'                  => 'Url',
			'devices'               => 'Agent.Device',
			'os'                    => 'Agent.Os',
			'browsers'              => 'Agent.Browser',
			'components'            => 'Component',
			'tags'                  => 'Tag',
			'contentpagetypes'      => 'Content.Pagetype',
			'cats'                  => 'Content.Category',
			'articles'              => 'Content.Article',
			'easyblogpagetypes'     => 'Easyblog.Pagetype',
			'easyblogcats'          => 'Easyblog.Category',
			'easyblogtags'          => 'Easyblog.Tag',
			'easyblogitems'         => 'Easyblog.Item',
			'flexicontentpagetypes' => 'Flexicontent.Pagetype',
			'flexicontenttags'      => 'Flexicontent.Tag',
			'flexicontenttypes'     => 'Flexicontent.Type',
			'form2contentprojects'  => 'Form2content.Project',
			'k2pagetypes'           => 'K2.Pagetype',
			'k2cats'                => 'K2.Category',
			'k2tags'                => 'K2.Tag',
			'k2items'               => 'K2.Item',
			'zoopagetypes'          => 'Zoo.Pagetype',
			'zoocats'               => 'Zoo.Category',
			'zooitems'              => 'Zoo.Item',
			'akeebasubspagetypes'   => 'Akeebasubs.Pagetype',
			'akeebasubslevels'      => 'Akeebasubs.Level',
			'hikashoppagetypes'     => 'Hikashop.Pagetype',
			'hikashopcats'          => 'Hikashop.Category',
			'hikashopproducts'      => 'Hikashop.Product',
			'mijoshoppagetypes'     => 'Mijoshop.Pagetype',
			'mijoshopcats'          => 'Mijoshop.Category',
			'mijoshopproducts'      => 'Mijoshop.Product',
			'redshoppagetypes'      => 'Redshop.Pagetype',
			'redshopcats'           => 'Redshop.Category',
			'redshopproducts'       => 'Redshop.Product',
			'virtuemartpagetypes'   => 'Virtuemart.Pagetype',
			'virtuemartcats'        => 'Virtuemart.Category',
			'virtuemartproducts'    => 'Virtuemart.Product',
			'cookieconfirm'         => 'Cookieconfirm',
			'php'                   => 'Php',
		];

		if (empty($only_types))
		{
			return $types;
		}

		return array_intersect_key($types, array_flip($only_types));
	}

	private static function getType(&$type, $only_types = [])
	{
		$types = self::getTypes($only_types);

		if (isset($types[$type]))
		{
			return $types[$type];
		}

		// Make it plural
		$type = rtrim($type, 's') . 's';

		if (isset($types[$type]))
		{
			return $types[$type];
		}

		// Replace incorrect plural endings
		$type = str_replace('ys', 'ies', $type);

		if (isset($types[$type]))
		{
			return $types[$type];
		}

		return false;
	}

	private static function getTypeAliases()
	{
		return [
			'matching_method'  => ['method'],
			'menuitems'        => ['menu'],
			'homepage'         => ['home'],
			'date'             => ['daterange'],
			'seasons'          => [''],
			'months'           => [''],
			'days'             => [''],
			'time'             => [''],
			'accesslevels'     => ['access'],
			'usergrouplevels'  => ['usergroups', 'groups'],
			'users'            => [''],
			'languages'        => ['langs'],
			'ips'              => ['ipaddress', 'ipaddresses'],
			'geocontinents'    => ['continents'],
			'geocountries'     => ['countries'],
			'georegions'       => ['regions'],
			'geopostalcodes'   => ['postalcodes', 'postcodes'],
			'templates'        => [''],
			'urls'             => [''],
			'devices'          => [''],
			'os'               => [''],
			'browsers'         => [''],
			'components'       => [''],
			'tags'             => [''],
			'contentpagetypes' => ['pagetypes'],
			'cats'             => ['categories', 'category'],
			'articles'         => [''],
			'php'              => [''],
		];
	}

	private static function getTextAreaTypes()
	{
		return [
			'Ip',
			'Url',
			'Php',
		];
	}

	private static function getNotArrayTextAreaTypes()
	{
		return [
			'Php',
		];
	}

	public static function getMatchAllTypes()
	{
		return [
			'User.Grouplevel',
			'Tag',
		];
	}
}
regularlabs/src/FieldGroup.php000064400000006035152177723700012426 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;
use Joomla\Registry\Registry;

class FieldGroup
	extends Field
{
	public $type          = 'Field';
	public $default_group = 'Categories';

	protected function getInput()
	{
		$this->params = $this->element->attributes();

		return $this->getSelectList();
	}

	public function getGroup()
	{
		$this->params = $this->element->attributes();

		return $this->get('group', $this->default_group ?: $this->type);
	}

	public function getOptions($group = false)
	{
		$group = $group ?: $this->getGroup();
		$id    = $this->type . '_' . $group;

		if ( ! isset($data[$id]))
		{
			$data[$id] = $this->{'get' . $group}();
		}

		return $data[$id];
	}

	public function getSelectList($group = '')
	{
		if ( ! is_array($this->value))
		{
			$this->value = explode(',', $this->value);
		}

		$size     = (int) $this->get('size');
		$multiple = $this->get('multiple');

		$group = $group ?: $this->getGroup();

		$simple = $this->get('simple', ! in_array($group, ['categories']));

		return $this->selectListAjax(
			$this->type, $this->name, $this->value, $this->id,
			compact('group', 'size', 'multiple', 'simple'),
			$simple
		);
	}

	function getAjaxRaw(Registry $attributes)
	{
		$name     = $attributes->get('name', $this->type);
		$id       = $attributes->get('id', strtolower($name));
		$value    = $attributes->get('value', []);
		$size     = $attributes->get('size');
		$multiple = $attributes->get('multiple');
		$simple   = $attributes->get('simple');

		$options = $this->getOptions(
			$attributes->get('group')
		);

		return $this->selectList($options, $name, $value, $id, $size, $multiple, $simple);
	}

	public function missingFilesOrTables($tables = ['categories', 'items'], $component = '', $table_prefix = '')
	{
		$component = $component ?: $this->type;

		if ( ! Extension::isInstalled($component))
		{
			return '<fieldset class="alert alert-danger">' . JText::_('ERROR') . ': ' . JText::sprintf('RL_FILES_NOT_FOUND', JText::_('RL_' . strtoupper($component))) . '</fieldset>';
		}

		$group = $this->getGroup();

		if ( ! in_array($group, $tables) && ! in_array($group, array_keys($tables)))
		{
			// no need to check database table for this group
			return false;
		}

		$table_list = $this->db->getTableList();

		$table = isset($tables[$group]) ? $tables[$group] : $group;
		$table = $this->db->getPrefix() . strtolower($table_prefix ?: $component) . '_' . $table;

		if (in_array($table, $table_list))
		{
			// database table exists, so no error
			return false;
		}

		return '<fieldset class="alert alert-danger">' . JText::_('ERROR') . ': ' . JText::sprintf('RL_TABLE_NOT_FOUND', JText::_('RL_' . strtoupper($component))) . '</fieldset>';
	}
}
regularlabs/src/Log.php000064400000006062152177723700011107 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use JLoader;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;

/**
 * Class Log
 * @package RegularLabs\Library
 */
class Log
{
	public static function add($message, $languageKey, $context)
	{
		$user = JFactory::getUser();

		$message['userid']      = $user->id;
		$message['username']    = $user->username;
		$message['accountlink'] = 'index.php?option=com_users&task=user.edit&id=' . $user->id;

		JLoader::register('ActionlogsHelper', JPATH_ADMINISTRATOR . '/components/com_actionlogs/helpers/actionlogs.php');
		JLoader::register('ActionlogsModelActionlog', JPATH_ADMINISTRATOR . '/components/com_actionlogs/models/actionlog.php');

		$model = JModel::getInstance('Actionlog', 'ActionlogsModel');
		$model->addLog([$message], $languageKey, $context, $user->id);
	}

	public static function save($message, $context, $isNew)
	{
		$languageKey       = $isNew ? 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED' : 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
		$message['action'] = $isNew ? 'add' : 'update';

		self::add($message, $languageKey, $context);
	}

	public static function delete($message, $context)
	{
		$languageKey       = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED';
		$message['action'] = 'deleted';

		self::add($message, $languageKey, $context);
	}

	public static function changeState($message, $context, $value)
	{
		switch ($value)
		{
			case 0:
				$languageKey       = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UNPUBLISHED';
				$message['action'] = 'unpublish';
				break;
			case 1:
				$languageKey       = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_PUBLISHED';
				$message['action'] = 'publish';
				break;
			case 2:
				$languageKey       = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ARCHIVED';
				$message['action'] = 'archive';
				break;
			case -2:
				$languageKey       = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_TRASHED';
				$message['action'] = 'trash';
				break;
			default:
				return;
		}

		self::add($message, $languageKey, $context);
	}

	public static function install($message, $context, $type = 'component')
	{
		$languageKey = 'PLG_ACTIONLOG_JOOMLA_' . strtoupper($type) . '_INSTALLED';
		if ( ! JFactory::getApplication()->getLanguage()->hasKey($languageKey))
		{
			$languageKey = 'PLG_ACTIONLOG_JOOMLA_EXTENSION_INSTALLED';
		}

		$message['action'] = 'install';
		$message['type']   = 'PLG_ACTIONLOG_JOOMLA_TYPE_' . strtoupper($type);

		self::add($message, $languageKey, $context);
	}

	public static function uninstall($message, $context, $type = 'component')
	{
		$languageKey = 'PLG_ACTIONLOG_JOOMLA_EXTENSION_UNINSTALLED';

		$message['action'] = 'uninstall';
		$message['type']   = 'PLG_ACTIONLOG_JOOMLA_TYPE_' . strtoupper($type);

		self::add($message, $languageKey, $context);
	}
}
regularlabs/src/Parameters.php000064400000016552152177723700012476 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Filesystem\File as JFile;
use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;

jimport('joomla.filesystem.file');

/**
 * Class Parameters
 * @package RegularLabs\Library
 */
class Parameters
{
	public static $instance = null;

	/**
	 * @return static instance
	 */
	public static function getInstance()
	{
		if (is_null(self::$instance))
		{
			self::$instance = new static;
		}

		return self::$instance;
	}

	/**
	 * Get a usable parameter object based on the Joomla Registry object
	 * The object will have all the available parameters with their value (default value if none is set)
	 *
	 * @param \Registry $params
	 * @param string    $path
	 * @param string    $default
	 *
	 * @return object
	 */
	public function getParams($params, $path = '', $default = '', $use_cache = true)
	{
		$cache_id = 'getParams_' . json_encode($params) . '_' . $path . '_' . $default;

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$xml = $this->loadXML($path, $default);

		if (empty($params))
		{
			return Cache::set(
				$cache_id,
				(object) $xml
			);
		}

		if ( ! is_object($params))
		{
			$params = json_decode($params);
			if (is_null($xml))
			{
				$xml = (object) [];
			}
		}
		elseif (method_exists($params, 'toObject'))
		{
			$params = $params->toObject();
		}

		if ( ! $params)
		{
			return Cache::set(
				$cache_id,
				(object) $xml
			);
		}

		if (empty($xml))
		{
			return Cache::set(
				$cache_id,
				$params
			);
		}

		foreach ($xml as $key => $val)
		{
			if (isset($params->{$key}) && $params->{$key} != '')
			{
				continue;
			}

			$params->{$key} = $val;
		}

		return Cache::set(
			$cache_id,
			$params
		);
	}

	/**
	 * Get a usable parameter object for the component
	 *
	 * @param string    $name
	 * @param \Registry $params
	 *
	 * @return object
	 */
	public function getComponentParams($name, $params = null, $use_cache = true)
	{
		$name = 'com_' . RegEx::replace('^com_', '', $name);

		$cache_id = 'getComponentParams_' . $name . '_' . json_encode($params);

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if (empty($params) && JComponentHelper::isInstalled($name))
		{
			$params = JComponentHelper::getParams($name);
		}

		return Cache::set(
			$cache_id,
			$this->getParams($params, JPATH_ADMINISTRATOR . '/components/' . $name . '/config.xml')
		);
	}

	/**
	 * Get a usable parameter object for the module
	 *
	 * @param string    $name
	 * @param int       $admin
	 * @param \Registry $params
	 *
	 * @return object
	 */
	public function getModuleParams($name, $admin = true, $params = '', $use_cache = true)
	{
		$name = 'mod_' . RegEx::replace('^mod_', '', $name);

		$cache_id = 'getModuleParams_' . $name . '_' . json_encode($params);

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if (empty($params))
		{
			$params = null;
		}

		return Cache::set(
			$cache_id,
			$this->getParams($params, ($admin ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $name . '/' . $name . '.xml')
		);
	}

	/**
	 * Get a usable parameter object for the plugin
	 *
	 * @param string    $name
	 * @param string    $type
	 * @param \Registry $params
	 *
	 * @return object
	 */
	public function getPluginParams($name, $type = 'system', $params = '', $use_cache = true)
	{
		$cache_id = 'getPluginParams_' . $name . '_' . $type . '_' . json_encode($params);

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if (empty($params))
		{
			$plugin = JPluginHelper::getPlugin($type, $name);
			$params = (is_object($plugin) && isset($plugin->params)) ? $plugin->params : null;
		}

		return Cache::set(
			$cache_id,
			$this->getParams($params, JPATH_PLUGINS . '/' . $type . '/' . $name . '/' . $name . '.xml')
		);
	}

	/**
	 * Returns an object based on the data in a given xml array
	 *
	 * @param $xml
	 *
	 * @return bool|mixed
	 */
	public function getObjectFromXml(&$xml, $use_cache = true)
	{
		$cache_id = 'getObjectFromXml_' . json_encode($xml);

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if ( ! is_array($xml))
		{
			$xml = [$xml];
		}

		$object = $this->getObjectFromXmlNode($xml);

		return Cache::set(
			$cache_id,
			$object
		);
	}

	/**
	 * Returns an array based on the data in a given xml file
	 *
	 * @param string $path
	 * @param string $default
	 *
	 * @return array
	 */
	private function loadXML($path, $default = '', $use_cache = true)
	{
		$cache_id = 'loadXML_' . $path . '_' . $default;

		if ($use_cache && Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if ( ! $path
			|| ! file_exists($path)
			|| ! $file = JFile::read($path)
		)
		{
			return Cache::set(
				$cache_id,
				[]
			);
		}

		$xml = [];

		$xml_parser = xml_parser_create();
		xml_parse_into_struct($xml_parser, $file, $fields);
		xml_parser_free($xml_parser);

		$default = $default ? strtoupper($default) : 'DEFAULT';
		foreach ($fields as $field)
		{
			if ($field['tag'] != 'FIELD'
				|| ! isset($field['attributes'])
				|| ! isset($field['attributes']['NAME'])
				|| $field['attributes']['NAME'] == ''
				|| $field['attributes']['NAME'][0] == '@'
				|| ! isset($field['attributes']['TYPE'])
				|| $field['attributes']['TYPE'] == 'spacer'
			)
			{
				continue;
			}

			if (isset($field['attributes'][$default]))
			{
				$field['attributes']['DEFAULT'] = $field['attributes'][$default];
			}

			if (!isset($field['attributes']['DEFAULT']))
			{
				$field['attributes']['DEFAULT'] = '';
			}

			if ($field['attributes']['TYPE'] == 'textarea')
			{
				$field['attributes']['DEFAULT'] = str_replace('<br>', "\n", $field['attributes']['DEFAULT']);
			}

			$xml[$field['attributes']['NAME']] = $field['attributes']['DEFAULT'];
		}

		return Cache::set(
			$cache_id,
			$xml
		);
	}

	/**
	 * Returns the main attributes key from an xml object
	 *
	 * @param $xml
	 *
	 * @return mixed
	 */
	private function getKeyFromXML($xml)
	{
		if ( ! empty($xml->_attributes) && isset($xml->_attributes['name']))
		{
			return $xml->_attributes['name'];
		}

		return $xml->_name;
	}

	/**
	 * Returns the value from an xml object / node
	 *
	 * @param $xml
	 *
	 * @return object
	 */
	private function getValFromXML($xml)
	{
		if ( ! empty($xml->_attributes) && isset($xml->_attributes['value']))
		{
			return $xml->_attributes['value'];
		}

		if (empty($xml->_children))
		{
			return $xml->_data;
		}

		return $this->getObjectFromXmlNode($xml->_children);
	}

	/**
	 * Create an object from the given xml node
	 *
	 * @param $xml
	 *
	 * @return object
	 */
	private function getObjectFromXmlNode($xml)
	{
		$object = (object) [];

		foreach ($xml as $child)
		{
			$key   = $this->getKeyFromXML($child);
			$value = $this->getValFromXML($child);

			if ( ! isset($object->{$key}))
			{
				$object->{$key} = $value;
				continue;
			}

			if ( ! is_array($object->{$key}))
			{
				$object->{$key} = [$object->{$key}];
			}

			$object->{$key}[] = $value;
		}

		return $object;
	}
}
regularlabs/src/PluginTag.php000064400000043644152177723700012267 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class PluginTag
 * @package RegularLabs\Library
 */
class PluginTag
{
	/**
	 * @var array
	 */
	static $protected_characters = [
		'=' => '[[:EQUAL:]]',
		'"' => '[[:QUOTE:]]',
		',' => '[[:COMMA:]]',
		'|' => '[[:BAR:]]',
		':' => '[[:COLON:]]',
	];

	/**
	 * Cleans the given tag word
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function clean($string = '')
	{
		return RegEx::replace('[^a-z0-9-_]', '', $string);
	}

	/**
	 * Get the attributes from  plugin style string
	 *
	 * @param string $string
	 * @param string $main_key
	 * @param array  $known_boolean_keys
	 * @param array  $keep_escaped_chars
	 *
	 * @return object
	 */
	public static function getAttributesFromString($string = '', $main_key = 'title', $known_boolean_keys = [], $keep_escaped_chars = [','])
	{
		if (empty($string))
		{
			return (object) [];
		}

		// Replace html entity quotes to normal quotes
		$string = str_replace('&quot;', '"', $string);

		self::protectSpecialChars($string);

		// replace weird whitespace
		$string = str_replace(chr(194) . chr(160), ' ', $string);

		// Replace html entity spaces between attributes to normal spaces
		$string = RegEx::replace('((?:^|")\s*)&nbsp;(\s*(?:[a-z]|$))', '\1 \2', $string);

		// Only one value, so return simple key/value object
		if (strpos($string, '|') == false && ! RegEx::match('=\s*"', $string))
		{
			self::unprotectSpecialChars($string, $keep_escaped_chars);

			return (object) [$main_key => $string];
		}

		// No foo="bar" syntax found, so assume old syntax
		if ( ! RegEx::match('=\s*"', $string))
		{
			self::unprotectSpecialChars($string, $keep_escaped_chars);

			$attributes = self::getAttributesFromStringOld($string, [$main_key]);
			self::convertOldSyntax($attributes, $known_boolean_keys);

			return $attributes;
		}

		// Cannot find right syntax, so return simple key/value object
		if ( ! RegEx::matchAll('(?:^|\s)(?<key>[a-z0-9-_]+)\s*(?<not>\!?)=\s*"(?<value>.*?)"', $string, $matches))
		{
			self::unprotectSpecialChars($string, $keep_escaped_chars);

			return (object) [$main_key => $string];
		}

		$tag = (object) [];

		foreach ($matches as $match)
		{
			$tag->{$match['key']} = self::getAttributeValueFromMatch($match, $known_boolean_keys, $keep_escaped_chars);
		}

		return $tag;
	}

	/**
	 * Get the value from a found attribute match
	 *
	 * @param array $match
	 * @param array $known_boolean_keys
	 * @param array $keep_escaped_chars
	 *
	 * @return bool|int|string
	 */
	private static function getAttributeValueFromMatch($match, $known_boolean_keys = [], $keep_escaped_chars = [','])
	{
		$value = $match['value'];

		self::unprotectSpecialChars($value, $keep_escaped_chars);

		if (is_numeric($value)
			&& (
				in_array($match['key'], $known_boolean_keys)
				|| in_array(strtolower($match['key']), $known_boolean_keys)
			)
		)
		{
			$value = $value ? 'true' : 'false';
		}

		// Convert numeric values to ints/floats
		if (is_numeric($value))
		{
			$value = $value + 0;
		}

		// Convert boolean values to actual booleans
		if ($value === 'true' || $value === true)
		{
			return $match['not'] ? false : true;
		}

		if ($value === 'false' || $value === false)
		{
			return $match['not'] ? true : false;
		}

		return $match['not'] ? '!NOT!' . $value : $value;
	}

	/**
	 * Replace special characters in the string with the protected versions
	 *
	 * @param string $string
	 */
	public static function protectSpecialChars(&$string)
	{
		$unescaped_chars = array_keys(self::$protected_characters);
		array_walk($unescaped_chars, function (&$char) {
			$char = '\\' . $char;
		});

		// replace escaped characters with special markup
		$string = str_replace(
			$unescaped_chars,
			array_values(self::$protected_characters),
			$string
		);

		if ( ! RegEx::matchAll(
			'(<.*?>|{.*?}|\[.*?\])',
			$string,
			$tags,
			null,
			PREG_PATTERN_ORDER
		)
		)
		{
			return;
		}

		foreach ($tags[0] as $tag)
		{
			// replace unescaped characters with special markup
			$protected = str_replace(
				['=', '"'],
				[self::$protected_characters['='], self::$protected_characters['"']],
				$tag
			);

			$string = str_replace($tag, $protected, $string);
		}
	}

	/**
	 * Replace protected characters in the string with the original special versions
	 *
	 * @param string $string
	 * @param array  $keep_escaped_chars
	 */
	public static function unprotectSpecialChars(&$string, $keep_escaped_chars = [])
	{
		$unescaped_chars = array_keys(self::$protected_characters);

		if ( ! empty($keep_escaped_chars))
		{
			array_walk($unescaped_chars, function (&$char, $key, $keep_escaped_chars) {
				if (is_array($keep_escaped_chars) && ! in_array($char, $keep_escaped_chars))
				{
					return;
				}
				$char = '\\' . $char;
			}, $keep_escaped_chars);
		}

		// replace special markup with unescaped characters
		$string = str_replace(
			array_values(self::$protected_characters),
			$unescaped_chars,
			$string
		);
	}

	/**
	 * Only used for old syntaxes
	 *
	 * @param string $string
	 * @param array  $keys
	 * @param string $separator
	 * @param string $equal
	 * @param int    $limit
	 *
	 * @return object
	 */
	public static function getAttributesFromStringOld($string = '', $keys = ['title'], $separator = '|', $equal = '=', $limit = 0)
	{
		$temp_separator = '[[SEPARATOR]]';
		$temp_equal     = '[[EQUAL]]';
		$tag_start      = '[[TAG]]';
		$tag_end        = '[[/TAG]]';

		// replace separators and equal signs with special markup
		$string = str_replace([$separator, $equal], [$temp_separator, $temp_equal], $string);
		// replace protected separators and equal signs back to original
		$string = str_replace(['\\' . $temp_separator, '\\' . $temp_equal], [$separator, $equal], $string);

		// protect all html tags
		RegEx::matchAll('</?[a-z][^>]*>', $string, $tags);

		if ( ! empty($tags))
		{
			foreach ($tags as $tag)
			{
				$string = str_replace(
					$tag[0],
					$tag_start . base64_encode(str_replace([$temp_separator, $temp_equal], [$separator, $equal], $tag[0])) . $tag_end,
					$string
				);
			}
		}

		// split string into array
		$attribs = $limit
			? explode($temp_separator, $string, (int) $limit)
			: explode($temp_separator, $string);

		$attributes = (object) [
			'params' => [],
		];

		// loop through splits
		foreach ($attribs as $i => $keyval)
		{
			// spit part into key and val by equal sign
			$keyval = explode($temp_equal, $keyval, 2);
			if (isset($keyval[1]))
			{
				$keyval[1] = str_replace([$temp_separator, $temp_equal], [$separator, $equal], $keyval[1]);
			}

			// unprotect tags in key and val
			foreach ($keyval as $key => $value)
			{
				RegEx::matchAll(RegEx::quote($tag_start) . '(.*?)' . RegEx::quote($tag_end), $value, $tags);

				if (empty($tags))
				{
					continue;
				}

				foreach ($tags as $tag)
				{
					$value = str_replace($tag[0], base64_decode($tag[1]), $value);
				}

				$keyval[trim($key)] = $value;
			}

			if (isset($keys[$i]))
			{
				$key = trim($keys[$i]);
				// if value is in the keys array add as defined in keys array
				// ignore equal sign
				$value = implode($equal, $keyval);

				if (substr($value, 0, strlen($key) + 1) == $key . '=')
				{
					$value = substr($value, strlen($key) + 1);
				}

				$attributes->{$key} = $value;
				unset($keys[$i]);

				continue;
			}

			// else add as defined in the string
			if (isset($keyval[1]))
			{
				$value = $keyval[1];

				$value = trim($value, '"');

				if ($value === 'true' || $value === true)
				{
					$value = true;
				}

				if ($value === 'false' || $value === false)
				{
					$value = false;
				}

				$attributes->{$keyval[0]} = $value;
				continue;
			}

			$attributes->params[] = implode($equal, $keyval);
		}

		return $attributes;
	}

	/**
	 * Replace keys aliases with the main key names in an object
	 *
	 * @param object $attributes
	 * @param array  $key_aliases
	 * @param bool   $handle_plurals
	 */
	public static function replaceKeyAliases(&$attributes, $key_aliases = [], $handle_plurals = false)
	{
		foreach ($key_aliases as $key => $aliases)
		{
			if (self::replaceKeyAlias($attributes, $key, $key, $handle_plurals))
			{
				continue;
			}

			foreach ($aliases as $alias)
			{
				if ( ! isset($attributes->{$alias}))
				{
					continue;
				}

				if (self::replaceKeyAlias($attributes, $key, $alias, $handle_plurals))
				{
					break;
				}
			}
		}
	}

	/**
	 * Replace specific key alias with the main key name in an object
	 *
	 * @param object $attributes
	 * @param string $key
	 * @param string $alias
	 * @param bool   $handle_plurals
	 *
	 * @return bool
	 */
	private static function replaceKeyAlias(&$attributes, $key, $alias, $handle_plurals = false)
	{
		if ($handle_plurals)
		{
			if (self::replaceKeyAlias($attributes, $key, $alias . 's'))
			{
				return true;
			}

			if (substr($alias, -1) == 's' && self::replaceKeyAlias($attributes, $key, substr($alias, 0, -1)))
			{
				return true;
			}
		}

		if (isset($attributes->{$key}))
		{
			return true;
		}

		if ( ! isset($attributes->{$alias}))
		{
			return false;
		}

		$attributes->{$key} = $attributes->{$alias};
		unset($attributes->{$alias});

		return true;
	}

	/**
	 * Convert an object using the old param style to the new syntax
	 *
	 * @param object $attributes
	 * @param array  $known_boolean_keys
	 * @param string $extra_key
	 */
	public static function convertOldSyntax(&$attributes, $known_boolean_keys = [], $extra_key = 'class')
	{
		$extra = isset($attributes->class) ? [$attributes->class] : [];

		foreach ($attributes->params as $i => $param)
		{
			if ( ! $param)
			{
				continue;
			}

			if (in_array($param, $known_boolean_keys))
			{
				$attributes->{$param} = true;
				continue;
			}

			if (strpos($param, '=') == false)
			{
				$extra[] = $param;
				continue;
			}

			list($key, $val) = explode('=', $param, 2);

			$attributes->{$key} = $val;
		}

		$attributes->{$extra_key} = trim(implode(' ', $extra));

		unset($attributes->params);
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Different types of spaces
	 *
	 * @param string $modifier
	 *
	 * @return string
	 */
	public static function getRegexSpaces($modifier = '+')
	{
		return '(?:\s|&nbsp;|&\#160;)' . $modifier;
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Plugin type tags inside others
	 *
	 * @return string
	 */
	public static function getRegexInsideTag($start_character = '{', $end_character = '}')
	{
		$s = RegEx::quote($start_character);
		$e = RegEx::quote($end_character);

		return '(?:[^' . $s . $e . ']*' . $s . '[^' . $e . ']*' . $s . ')*.*?';
	}

	/**
	 * Return the Regular Expressions string to match:
	 * html before plugin tag
	 *
	 * @param string $group_id
	 *
	 * @return string
	 */
	public static function getRegexLeadingHtml($group_id = '')
	{
		$group          = 'leading_block_element';
		$html_tag_group = 'html_tag';

		if ($group_id)
		{
			$group          .= '_' . $group_id;
			$html_tag_group .= '_' . $group_id;
		}

		$block_elements = Html::getBlockElements(['div']);
		$block_element  = '(?<' . $group . '>' . implode('|', $block_elements) . ')';

		$other_html = '[^<]*(<(?<' . $html_tag_group . '>[a-z][a-z0-9_-]*)[\s>]([^<]*</(?P=' . $html_tag_group . ')>)?[^<]*)*';

		// Grab starting block element tag and any html after it (that is not the same block element starting/ending tag).
		return '(?:'
			. '<' . $block_element . '(?: [^>]*)?>'
			. $other_html
			. ')?';
	}

	/**
	 * Return the Regular Expressions string to match:
	 * html after plugin tag
	 *
	 * @param string $group_id
	 *
	 * @return string
	 */
	public static function getRegexTrailingHtml($group_id = '')
	{
		$group = 'leading_block_element';

		if ($group_id)
		{
			$group .= '_' . $group_id;
		}

		// If the grouped name is found, then grab all content till ending html tag is found. Otherwise grab nothing.
		return '(?(<' . $group . '>)'
			. '(?:.*?</(?P=' . $group . ')>)?'
			. ')';
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Opening html tags
	 *
	 * @param array $block_elements
	 * @param array $inline_elements
	 * @param array $excluded_block_elements
	 *
	 * @return string
	 */
	public static function getRegexSurroundingTagsPre($block_elements = [], $inline_elements = ['span'], $excluded_block_elements = [])
	{
		$block_elements = ! empty($block_elements) ? $block_elements : Html::getBlockElements($excluded_block_elements);

		$regex = '(?:<(?:' . implode('|', $block_elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*)?';

		if ( ! empty($inline_elements))
		{
			$regex .= '(?:<(?:' . implode('|', $inline_elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*){0,3}';
		}

		return $regex;
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Closing html tags
	 *
	 * @param array $block_elements
	 * @param array $inline_elements
	 * @param array $excluded_block_elements
	 *
	 * @return string
	 */
	public static function getRegexSurroundingTagsPost($block_elements = [], $inline_elements = ['span'], $excluded_block_elements = [])
	{
		$block_elements = ! empty($block_elements) ? $block_elements : Html::getBlockElements($excluded_block_elements);

		$regex = '';

		if ( ! empty($inline_elements))
		{
			$regex .= '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $inline_elements) . ')>){0,3}';
		}

		$regex .= '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $block_elements) . ')>)?';

		return $regex;
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Leading html tag
	 *
	 * @param array $elements
	 *
	 * @return string
	 */
	public static function getRegexSurroundingTagPre($elements = [])
	{
		$elements = ! empty($elements) ? $elements : array_merge(Html::getBlockElements(), ['span']);

		return '(?:<(?:' . implode('|', $elements) . ')(?: [^>]*)?>\s*(?:<br ?/?>\s*)*)?';
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Trailing html tag
	 *
	 * @param array $elements
	 *
	 * @return string
	 */
	public static function getRegexSurroundingTagPost($elements = [])
	{
		$elements = ! empty($elements) ? $elements : array_merge(Html::getBlockElements(), ['span']);

		return '(?:(?:\s*<br ?/?>)*\s*<\/(?:' . implode('|', $elements) . ')>)?';
	}

	/**
	 * Return the Regular Expressions string to match:
	 * Plugin style tags
	 *
	 * @param array $tags
	 * @param bool  $include_no_attributes
	 * @param bool  $include_ending
	 * @param array $required_attributes
	 *
	 * @return string
	 */
	public static function getRegexTags($tags, $include_no_attributes = true, $include_ending = true, $required_attributes = [])
	{
		$tags = ArrayHelper::toArray($tags);
		$tags = count($tags) > 1 ? '(?:' . implode('|', $tags) . ')' : $tags[0];

		$value      = '(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|[a-z0-9-_]+))?';
		$attributes = '(?:\s+[a-z0-9-_]+' . $value . ')+';

		$required_attributes = ArrayHelper::toArray($required_attributes);
		if ( ! empty($required_attributes))
		{
			$attributes = '(?:' . $attributes . ')?' . '(?:\s+' . implode('|', $required_attributes) . ')' . $value . '(?:' . $attributes . ')?';
		}

		if ($include_no_attributes)
		{
			$attributes = '\s*(?:' . $attributes . ')?';
		}

		if ( ! $include_ending)
		{
			return '<' . $tags . $attributes . '\s*/?>';
		}

		return '<(?:\/' . $tags . '|' . $tags . $attributes . '\s*/?)\s*/?>';
	}

	/**
	 * Extract the plugin style div tags with the possible attributes. like:
	 * {div width:100|float:left}...{/div}
	 *
	 * @param string $start_tag
	 * @param string $end_tag
	 * @param string $tag_start
	 * @param string $tag_end
	 *
	 * @return array
	 */
	public static function getDivTags($start_tag = '', $end_tag = '', $tag_start = '{', $tag_end = '}')
	{
		$tag_start = RegEx::quote($tag_start);
		$tag_end   = RegEx::quote($tag_end);

		$start_div = ['pre' => '', 'tag' => '', 'post' => ''];
		$end_div   = ['pre' => '', 'tag' => '', 'post' => ''];

		if ( ! empty($start_tag)
			&& RegEx::match(
				'^(?<pre>.*?)(?<tag>' . $tag_start . 'div(?: .*?)?' . $tag_end . ')(?<post>.*)$',
				$start_tag,
				$match
			)
		)
		{
			$start_div = $match;
		}

		if ( ! empty($end_tag)
			&& RegEx::match(
				'^(?<pre>.*?)(?<tag>' . $tag_start . '/div' . $tag_end . ')(?<post>.*)$',
				$end_tag,
				$match
			)
		)
		{
			$end_div = $match;
		}

		if (empty($start_div['tag']) || empty($end_div['tag']))
		{
			return [$start_div, $end_div];
		}

		$attribs = trim(RegEx::replace($tag_start . 'div(.*)' . $tag_end, '\1', $start_div['tag']));

		$start_div['tag'] = '<div>';
		$end_div['tag']   = '</div>';

		if (empty($attribs))
		{
			return [$start_div, $end_div];
		}

		$attribs = self::getDivAttributes($attribs);

		$style = [];

		if (isset($attribs->width))
		{
			if (is_numeric($attribs->width))
			{
				$attribs->width .= 'px';
			}
			$style[] = 'width:' . $attribs->width;
		}

		if (isset($attribs->height))
		{
			if (is_numeric($attribs->height))
			{
				$attribs->height .= 'px';
			}
			$style[] = 'height:' . $attribs->height;
		}

		if (isset($attribs->align))
		{
			$style[] = 'float:' . $attribs->align;
		}

		if ( ! isset($attribs->align) && isset($attribs->float))
		{
			$style[] = 'float:' . $attribs->float;
		}

		$attribs = isset($attribs->class) ? 'class="' . $attribs->class . '"' : '';

		if ( ! empty($style))
		{
			$attribs .= ' style="' . implode(';', $style) . ';"';
		}

		$start_div['tag'] = trim('<div ' . trim($attribs)) . '>';

		return [$start_div, $end_div];
	}

	/**
	 * Get the attributes from a plugin style div tag
	 *
	 * @param string $string
	 *
	 * @return object
	 */
	private static function getDivAttributes($string)
	{
		if (strpos($string, '="') !== false)
		{
			return self::getAttributesFromString($string);
		}

		$parts      = explode('|', $string);
		$attributes = (object) [];

		foreach ($parts as $e)
		{
			if (strpos($e, ':') === false)
			{
				continue;
			}

			list($key, $val) = explode(':', $e, 2);
			$attributes->{$key} = $val;
		}

		return $attributes;
	}
}
regularlabs/src/HtmlTag.php000064400000007173152177723700011732 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class HtmlTag
 * @package RegularLabs\Library
 */
class HtmlTag
{
	/**
	 * Combine 2 opening html tags into one
	 *
	 * @param string $tag1
	 * @param string $tag2
	 *
	 * @return string
	 */
	public static function combine($tag1, $tag2)
	{
		// Return if tags are the same
		if ($tag1 == $tag2)
		{
			return $tag1;
		}

		if ( ! RegEx::match('<([a-z][a-z0-9]*)', $tag1, $tag_type))
		{
			return $tag2;
		}

		$tag_type = $tag_type[1];

		if ( ! $attribs = self::combineAttributes($tag1, $tag2))
		{
			return '<' . $tag_type . '>';
		}

		return '<' . $tag_type . ' ' . $attribs . '>';
	}

	/**
	 * Extract attribute value from a html tag string by given attribute key
	 *
	 * @param string $key
	 * @param string $string
	 *
	 * @return string
	 */
	public static function getAttributeValue($key, $string)
	{
		if (empty($key) || empty($string))
		{
			return '';
		}

		RegEx::match(RegEx::quote($key) . '="([^"]*)"', $string, $match);

		if (empty($match))
		{
			return '';
		}

		return $match[1];
	}

	/**
	 * Extract all attributes from a html tag string
	 *
	 * @param string $string
	 *
	 * @return array
	 */
	public static function getAttributes($string)
	{
		if (empty($string))
		{
			return [];
		}

		RegEx::matchAll('([a-z0-9-_]+)="([^"]*)"', $string, $matches);

		if (empty($matches))
		{
			return [];
		}

		$attribs = [];

		foreach ($matches as $match)
		{
			$attribs[$match[1]] = $match[2];
		}

		return $attribs;
	}

	/**
	 * Combine attribute values from 2 given html tag strings (or arrays of attributes)
	 * And return as a sting of attributes
	 *
	 * @param string /array $string1
	 * @param string /array $string2
	 *
	 * @return string
	 */
	public static function combineAttributes($string1, $string2, $flatten = true)
	{
		$attribsutes1 = is_array($string1) ? $string1 : self::getAttributes($string1);
		$attribsutes2 = is_array($string2) ? $string2 : self::getAttributes($string2);

		$duplicate_attributes = array_intersect_key($attribsutes1, $attribsutes2);

		// Fill $attributes with the unique ids
		$attributes = array_diff_key($attribsutes1, $attribsutes2) + array_diff_key($attribsutes2, $attribsutes1);

		// List of attrubute types that can only contain one value
		$single_value_attributes = ['id', 'href'];

		// Add/combine the duplicate ids
		foreach ($duplicate_attributes as $key => $val)
		{
			if (in_array($key, $single_value_attributes))
			{
				$attributes[$key] = $attribsutes2[$key];
				continue;
			}
			// Combine strings, but remove duplicates
			// "aaa bbb" + "aaa ccc" = "aaa bbb ccc"

			// use a ';' as a concatenated for javascript values (keys beginning with 'on')
			// Otherwise use a space (like for classes)
			$glue = substr($key, 0, 2) == 'on' ? ';' : ' ';

			$attributes[$key] = implode($glue, array_merge(explode($glue, $attribsutes1[$key]), explode($glue, $attribsutes2[$key])));
		}

		return $flatten ? self::flattenAttributes($attributes) : $attributes;
	}

	/**
	 * Convert array of attributes to a html style string
	 *
	 * @param array $attributes
	 *
	 * @return string
	 */
	public static function flattenAttributes($attributes)
	{
		foreach ($attributes as $key => &$val)
		{
			$val = str_replace('"', '&quot;',  $val);
			$val = $key . '="' . $val . '"';
		}

		return implode(' ', (array) $attributes);
	}
}
regularlabs/src/MobileDetect.php000064400000223037152177723700012731 0ustar00<?php
/**
 * Mobile Detect Library
 * Motto: "Every business should have a mobile detection script to detect mobile readers"
 *
 * Mobile_Detect is a lightweight PHP class for detecting mobile devices (including tablets).
 * It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.
 *
 * Homepage: http://mobiledetect.net
 * GitHub: https://github.com/serbanghita/Mobile-Detect
 * README: https://github.com/serbanghita/Mobile-Detect/blob/master/README.md
 * CONTRIBUTING: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/CONTRIBUTING.md
 * KNOWN LIMITATIONS: https://github.com/serbanghita/Mobile-Detect/blob/master/docs/KNOWN_LIMITATIONS.md
 * EXAMPLES: https://github.com/serbanghita/Mobile-Detect/wiki/Code-examples
 *
 * @license https://github.com/serbanghita/Mobile-Detect/blob/master/LICENSE.txt MIT License
 * @author  Serban Ghita <serbanghita@gmail.com>
 * @author  Nick Ilyin <nick.ilyin@gmail.com>
 * Original author: Victor Stanciu <vic.stanciu@gmail.com>
 *
 * @version 2.8.32
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use BadMethodCallException;

class MobileDetect
{
	/**
	 * Mobile detection type.
	 *
	 * @deprecated since version 2.6.9
	 */
	const DETECTION_TYPE_MOBILE = 'mobile';

	/**
	 * Extended detection type.
	 *
	 * @deprecated since version 2.6.9
	 */
	const DETECTION_TYPE_EXTENDED = 'extended';

	/**
	 * A frequently used regular expression to extract version #s.
	 *
	 * @deprecated since version 2.6.9
	 */
	const VER = '([\w._\+]+)';

	/**
	 * Top-level device.
	 */
	const MOBILE_GRADE_A = 'A';

	/**
	 * Mid-level device.
	 */
	const MOBILE_GRADE_B = 'B';

	/**
	 * Low-level device.
	 */
	const MOBILE_GRADE_C = 'C';

	/**
	 * Stores the version number of the current release.
	 */
	const VERSION = '2.8.33';

	/**
	 * A type for the version() method indicating a string return value.
	 */
	const VERSION_TYPE_STRING = 'text';

	/**
	 * A type for the version() method indicating a float return value.
	 */
	const VERSION_TYPE_FLOAT = 'float';

	/**
	 * A cache for resolved matches
	 * @var array
	 */
	protected $cache = [];

	/**
	 * The User-Agent HTTP header is stored in here.
	 * @var string
	 */
	protected $userAgent = null;

	/**
	 * HTTP headers in the PHP-flavor. So HTTP_USER_AGENT and SERVER_SOFTWARE.
	 * @var array
	 */
	protected $httpHeaders = [];

	/**
	 * CloudFront headers. E.g. CloudFront-Is-Desktop-Viewer, CloudFront-Is-Mobile-Viewer & CloudFront-Is-Tablet-Viewer.
	 * @var array
	 */
	protected $cloudfrontHeaders = [];

	/**
	 * The matching Regex.
	 * This is good for debug.
	 * @var string
	 */
	protected $matchingRegex = null;

	/**
	 * The matches extracted from the regex expression.
	 * This is good for debug.
	 *
	 * @var string
	 */
	protected $matchesArray = null;

	/**
	 * The detection type, using self::DETECTION_TYPE_MOBILE or self::DETECTION_TYPE_EXTENDED.
	 *
	 * @deprecated since version 2.6.9
	 *
	 * @var string
	 */
	protected $detectionType = self::DETECTION_TYPE_MOBILE;

	/**
	 * HTTP headers that trigger the 'isMobile' detection
	 * to be true.
	 *
	 * @var array
	 */
	protected static $mobileHeaders = [

		'HTTP_ACCEPT'                  => [
			'matches' => [
				// Opera Mini; @reference: http://dev.opera.com/articles/view/opera-binary-markup-language/
				'application/x-obml2d',
				// BlackBerry devices.
				'application/vnd.rim.html',
				'text/vnd.wap.wml',
				'application/vnd.wap.xhtml+xml',
			],
		],
		'HTTP_X_WAP_PROFILE'           => null,
		'HTTP_X_WAP_CLIENTID'          => null,
		'HTTP_WAP_CONNECTION'          => null,
		'HTTP_PROFILE'                 => null,
		// Reported by Opera on Nokia devices (eg. C3).
		'HTTP_X_OPERAMINI_PHONE_UA'    => null,
		'HTTP_X_NOKIA_GATEWAY_ID'      => null,
		'HTTP_X_ORANGE_ID'             => null,
		'HTTP_X_VODAFONE_3GPDPCONTEXT' => null,
		'HTTP_X_HUAWEI_USERID'         => null,
		// Reported by Windows Smartphones.
		'HTTP_UA_OS'                   => null,
		// Reported by Verizon, Vodafone proxy system.
		'HTTP_X_MOBILE_GATEWAY'        => null,
		// Seen this on HTC Sensation. SensationXE_Beats_Z715e.
		'HTTP_X_ATT_DEVICEID'          => null,
		// Seen this on a HTC.
		'HTTP_UA_CPU'                  => ['matches' => ['ARM']],
	];

	/**
	 * List of mobile devices (phones).
	 *
	 * @var array
	 */
	protected static $phoneDevices = [
		'iPhone'       => '\biPhone\b|\biPod\b', // |\biTunes
		'BlackBerry'   => 'BlackBerry|\bBB10\b|rim[0-9]+',
		'HTC'          => 'HTC|HTC.*(Sensation|Evo|Vision|Explorer|6800|8100|8900|A7272|S510e|C110e|Legend|Desire|T8282)|APX515CKT|Qtek9090|APA9292KT|HD_mini|Sensation.*Z710e|PG86100|Z715e|Desire.*(A8181|HD)|ADR6200|ADR6400L|ADR6425|001HT|Inspire 4G|Android.*\bEVO\b|T-Mobile G1|Z520m|Android [0-9.]+; Pixel',
		'Nexus'        => 'Nexus One|Nexus S|Galaxy.*Nexus|Android.*Nexus.*Mobile|Nexus 4|Nexus 5|Nexus 6',
		// @todo: Is 'Dell Streak' a tablet or a phone? ;)
		'Dell'         => 'Dell[;]? (Streak|Aero|Venue|Venue Pro|Flash|Smoke|Mini 3iX)|XCD28|XCD35|\b001DL\b|\b101DL\b|\bGS01\b',
		'Motorola'     => 'Motorola|DROIDX|DROID BIONIC|\bDroid\b.*Build|Android.*Xoom|HRI39|MOT-|A1260|A1680|A555|A853|A855|A953|A955|A956|Motorola.*ELECTRIFY|Motorola.*i1|i867|i940|MB200|MB300|MB501|MB502|MB508|MB511|MB520|MB525|MB526|MB611|MB612|MB632|MB810|MB855|MB860|MB861|MB865|MB870|ME501|ME502|ME511|ME525|ME600|ME632|ME722|ME811|ME860|ME863|ME865|MT620|MT710|MT716|MT720|MT810|MT870|MT917|Motorola.*TITANIUM|WX435|WX445|XT300|XT301|XT311|XT316|XT317|XT319|XT320|XT390|XT502|XT530|XT531|XT532|XT535|XT603|XT610|XT611|XT615|XT681|XT701|XT702|XT711|XT720|XT800|XT806|XT860|XT862|XT875|XT882|XT883|XT894|XT901|XT907|XT909|XT910|XT912|XT928|XT926|XT915|XT919|XT925|XT1021|\bMoto E\b|XT1068|XT1092|XT1052',
		'Samsung'      => '\bSamsung\b|SM-G950F|SM-G955F|SM-G9250|GT-19300|SGH-I337|BGT-S5230|GT-B2100|GT-B2700|GT-B2710|GT-B3210|GT-B3310|GT-B3410|GT-B3730|GT-B3740|GT-B5510|GT-B5512|GT-B5722|GT-B6520|GT-B7300|GT-B7320|GT-B7330|GT-B7350|GT-B7510|GT-B7722|GT-B7800|GT-C3010|GT-C3011|GT-C3060|GT-C3200|GT-C3212|GT-C3212I|GT-C3262|GT-C3222|GT-C3300|GT-C3300K|GT-C3303|GT-C3303K|GT-C3310|GT-C3322|GT-C3330|GT-C3350|GT-C3500|GT-C3510|GT-C3530|GT-C3630|GT-C3780|GT-C5010|GT-C5212|GT-C6620|GT-C6625|GT-C6712|GT-E1050|GT-E1070|GT-E1075|GT-E1080|GT-E1081|GT-E1085|GT-E1087|GT-E1100|GT-E1107|GT-E1110|GT-E1120|GT-E1125|GT-E1130|GT-E1160|GT-E1170|GT-E1175|GT-E1180|GT-E1182|GT-E1200|GT-E1210|GT-E1225|GT-E1230|GT-E1390|GT-E2100|GT-E2120|GT-E2121|GT-E2152|GT-E2220|GT-E2222|GT-E2230|GT-E2232|GT-E2250|GT-E2370|GT-E2550|GT-E2652|GT-E3210|GT-E3213|GT-I5500|GT-I5503|GT-I5700|GT-I5800|GT-I5801|GT-I6410|GT-I6420|GT-I7110|GT-I7410|GT-I7500|GT-I8000|GT-I8150|GT-I8160|GT-I8190|GT-I8320|GT-I8330|GT-I8350|GT-I8530|GT-I8700|GT-I8703|GT-I8910|GT-I9000|GT-I9001|GT-I9003|GT-I9010|GT-I9020|GT-I9023|GT-I9070|GT-I9082|GT-I9100|GT-I9103|GT-I9220|GT-I9250|GT-I9300|GT-I9305|GT-I9500|GT-I9505|GT-M3510|GT-M5650|GT-M7500|GT-M7600|GT-M7603|GT-M8800|GT-M8910|GT-N7000|GT-S3110|GT-S3310|GT-S3350|GT-S3353|GT-S3370|GT-S3650|GT-S3653|GT-S3770|GT-S3850|GT-S5210|GT-S5220|GT-S5229|GT-S5230|GT-S5233|GT-S5250|GT-S5253|GT-S5260|GT-S5263|GT-S5270|GT-S5300|GT-S5330|GT-S5350|GT-S5360|GT-S5363|GT-S5369|GT-S5380|GT-S5380D|GT-S5560|GT-S5570|GT-S5600|GT-S5603|GT-S5610|GT-S5620|GT-S5660|GT-S5670|GT-S5690|GT-S5750|GT-S5780|GT-S5830|GT-S5839|GT-S6102|GT-S6500|GT-S7070|GT-S7200|GT-S7220|GT-S7230|GT-S7233|GT-S7250|GT-S7500|GT-S7530|GT-S7550|GT-S7562|GT-S7710|GT-S8000|GT-S8003|GT-S8500|GT-S8530|GT-S8600|SCH-A310|SCH-A530|SCH-A570|SCH-A610|SCH-A630|SCH-A650|SCH-A790|SCH-A795|SCH-A850|SCH-A870|SCH-A890|SCH-A930|SCH-A950|SCH-A970|SCH-A990|SCH-I100|SCH-I110|SCH-I400|SCH-I405|SCH-I500|SCH-I510|SCH-I515|SCH-I600|SCH-I730|SCH-I760|SCH-I770|SCH-I830|SCH-I910|SCH-I920|SCH-I959|SCH-LC11|SCH-N150|SCH-N300|SCH-R100|SCH-R300|SCH-R351|SCH-R400|SCH-R410|SCH-T300|SCH-U310|SCH-U320|SCH-U350|SCH-U360|SCH-U365|SCH-U370|SCH-U380|SCH-U410|SCH-U430|SCH-U450|SCH-U460|SCH-U470|SCH-U490|SCH-U540|SCH-U550|SCH-U620|SCH-U640|SCH-U650|SCH-U660|SCH-U700|SCH-U740|SCH-U750|SCH-U810|SCH-U820|SCH-U900|SCH-U940|SCH-U960|SCS-26UC|SGH-A107|SGH-A117|SGH-A127|SGH-A137|SGH-A157|SGH-A167|SGH-A177|SGH-A187|SGH-A197|SGH-A227|SGH-A237|SGH-A257|SGH-A437|SGH-A517|SGH-A597|SGH-A637|SGH-A657|SGH-A667|SGH-A687|SGH-A697|SGH-A707|SGH-A717|SGH-A727|SGH-A737|SGH-A747|SGH-A767|SGH-A777|SGH-A797|SGH-A817|SGH-A827|SGH-A837|SGH-A847|SGH-A867|SGH-A877|SGH-A887|SGH-A897|SGH-A927|SGH-B100|SGH-B130|SGH-B200|SGH-B220|SGH-C100|SGH-C110|SGH-C120|SGH-C130|SGH-C140|SGH-C160|SGH-C170|SGH-C180|SGH-C200|SGH-C207|SGH-C210|SGH-C225|SGH-C230|SGH-C417|SGH-C450|SGH-D307|SGH-D347|SGH-D357|SGH-D407|SGH-D415|SGH-D780|SGH-D807|SGH-D980|SGH-E105|SGH-E200|SGH-E315|SGH-E316|SGH-E317|SGH-E335|SGH-E590|SGH-E635|SGH-E715|SGH-E890|SGH-F300|SGH-F480|SGH-I200|SGH-I300|SGH-I320|SGH-I550|SGH-I577|SGH-I600|SGH-I607|SGH-I617|SGH-I627|SGH-I637|SGH-I677|SGH-I700|SGH-I717|SGH-I727|SGH-i747M|SGH-I777|SGH-I780|SGH-I827|SGH-I847|SGH-I857|SGH-I896|SGH-I897|SGH-I900|SGH-I907|SGH-I917|SGH-I927|SGH-I937|SGH-I997|SGH-J150|SGH-J200|SGH-L170|SGH-L700|SGH-M110|SGH-M150|SGH-M200|SGH-N105|SGH-N500|SGH-N600|SGH-N620|SGH-N625|SGH-N700|SGH-N710|SGH-P107|SGH-P207|SGH-P300|SGH-P310|SGH-P520|SGH-P735|SGH-P777|SGH-Q105|SGH-R210|SGH-R220|SGH-R225|SGH-S105|SGH-S307|SGH-T109|SGH-T119|SGH-T139|SGH-T209|SGH-T219|SGH-T229|SGH-T239|SGH-T249|SGH-T259|SGH-T309|SGH-T319|SGH-T329|SGH-T339|SGH-T349|SGH-T359|SGH-T369|SGH-T379|SGH-T409|SGH-T429|SGH-T439|SGH-T459|SGH-T469|SGH-T479|SGH-T499|SGH-T509|SGH-T519|SGH-T539|SGH-T559|SGH-T589|SGH-T609|SGH-T619|SGH-T629|SGH-T639|SGH-T659|SGH-T669|SGH-T679|SGH-T709|SGH-T719|SGH-T729|SGH-T739|SGH-T746|SGH-T749|SGH-T759|SGH-T769|SGH-T809|SGH-T819|SGH-T839|SGH-T919|SGH-T929|SGH-T939|SGH-T959|SGH-T989|SGH-U100|SGH-U200|SGH-U800|SGH-V205|SGH-V206|SGH-X100|SGH-X105|SGH-X120|SGH-X140|SGH-X426|SGH-X427|SGH-X475|SGH-X495|SGH-X497|SGH-X507|SGH-X600|SGH-X610|SGH-X620|SGH-X630|SGH-X700|SGH-X820|SGH-X890|SGH-Z130|SGH-Z150|SGH-Z170|SGH-ZX10|SGH-ZX20|SHW-M110|SPH-A120|SPH-A400|SPH-A420|SPH-A460|SPH-A500|SPH-A560|SPH-A600|SPH-A620|SPH-A660|SPH-A700|SPH-A740|SPH-A760|SPH-A790|SPH-A800|SPH-A820|SPH-A840|SPH-A880|SPH-A900|SPH-A940|SPH-A960|SPH-D600|SPH-D700|SPH-D710|SPH-D720|SPH-I300|SPH-I325|SPH-I330|SPH-I350|SPH-I500|SPH-I600|SPH-I700|SPH-L700|SPH-M100|SPH-M220|SPH-M240|SPH-M300|SPH-M305|SPH-M320|SPH-M330|SPH-M350|SPH-M360|SPH-M370|SPH-M380|SPH-M510|SPH-M540|SPH-M550|SPH-M560|SPH-M570|SPH-M580|SPH-M610|SPH-M620|SPH-M630|SPH-M800|SPH-M810|SPH-M850|SPH-M900|SPH-M910|SPH-M920|SPH-M930|SPH-N100|SPH-N200|SPH-N240|SPH-N300|SPH-N400|SPH-Z400|SWC-E100|SCH-i909|GT-N7100|GT-N7105|SCH-I535|SM-N900A|SGH-I317|SGH-T999L|GT-S5360B|GT-I8262|GT-S6802|GT-S6312|GT-S6310|GT-S5312|GT-S5310|GT-I9105|GT-I8510|GT-S6790N|SM-G7105|SM-N9005|GT-S5301|GT-I9295|GT-I9195|SM-C101|GT-S7392|GT-S7560|GT-B7610|GT-I5510|GT-S7582|GT-S7530E|GT-I8750|SM-G9006V|SM-G9008V|SM-G9009D|SM-G900A|SM-G900D|SM-G900F|SM-G900H|SM-G900I|SM-G900J|SM-G900K|SM-G900L|SM-G900M|SM-G900P|SM-G900R4|SM-G900S|SM-G900T|SM-G900V|SM-G900W8|SHV-E160K|SCH-P709|SCH-P729|SM-T2558|GT-I9205|SM-G9350|SM-J120F|SM-G920F|SM-G920V|SM-G930F|SM-N910C|SM-A310F|GT-I9190|SM-J500FN|SM-G903F|SM-J330F',
		'LG'           => '\bLG\b;|LG[- ]?(C800|C900|E400|E610|E900|E-900|F160|F180K|F180L|F180S|730|855|L160|LS740|LS840|LS970|LU6200|MS690|MS695|MS770|MS840|MS870|MS910|P500|P700|P705|VM696|AS680|AS695|AX840|C729|E970|GS505|272|C395|E739BK|E960|L55C|L75C|LS696|LS860|P769BK|P350|P500|P509|P870|UN272|US730|VS840|VS950|LN272|LN510|LS670|LS855|LW690|MN270|MN510|P509|P769|P930|UN200|UN270|UN510|UN610|US670|US740|US760|UX265|UX840|VN271|VN530|VS660|VS700|VS740|VS750|VS910|VS920|VS930|VX9200|VX11000|AX840A|LW770|P506|P925|P999|E612|D955|D802|MS323|M257)',
		'Sony'         => 'SonyST|SonyLT|SonyEricsson|SonyEricssonLT15iv|LT18i|E10i|LT28h|LT26w|SonyEricssonMT27i|C5303|C6902|C6903|C6906|C6943|D2533',
		'Asus'         => 'Asus.*Galaxy|PadFone.*Mobile',
		'NokiaLumia'   => 'Lumia [0-9]{3,4}',
		// http://www.micromaxinfo.com/mobiles/smartphones
		// Added because the codes might conflict with Acer Tablets.
		'Micromax'     => 'Micromax.*\b(A210|A92|A88|A72|A111|A110Q|A115|A116|A110|A90S|A26|A51|A35|A54|A25|A27|A89|A68|A65|A57|A90)\b',
		// @todo Complete the regex.
		'Palm'         => 'PalmSource|Palm', // avantgo|blazer|elaine|hiptop|plucker|xiino ;
		'Vertu'        => 'Vertu|Vertu.*Ltd|Vertu.*Ascent|Vertu.*Ayxta|Vertu.*Constellation(F|Quest)?|Vertu.*Monika|Vertu.*Signature', // Just for fun ;)
		// http://www.pantech.co.kr/en/prod/prodList.do?gbrand=VEGA (PANTECH)
		// Most of the VEGA devices are legacy. PANTECH seem to be newer devices based on Android.
		'Pantech'      => 'PANTECH|IM-A850S|IM-A840S|IM-A830L|IM-A830K|IM-A830S|IM-A820L|IM-A810K|IM-A810S|IM-A800S|IM-T100K|IM-A725L|IM-A780L|IM-A775C|IM-A770K|IM-A760S|IM-A750K|IM-A740S|IM-A730S|IM-A720L|IM-A710K|IM-A690L|IM-A690S|IM-A650S|IM-A630K|IM-A600S|VEGA PTL21|PT003|P8010|ADR910L|P6030|P6020|P9070|P4100|P9060|P5000|CDM8992|TXT8045|ADR8995|IS11PT|P2030|P6010|P8000|PT002|IS06|CDM8999|P9050|PT001|TXT8040|P2020|P9020|P2000|P7040|P7000|C790',
		// http://www.fly-phone.com/devices/smartphones/ ; Included only smartphones.
		'Fly'          => 'IQ230|IQ444|IQ450|IQ440|IQ442|IQ441|IQ245|IQ256|IQ236|IQ255|IQ235|IQ245|IQ275|IQ240|IQ285|IQ280|IQ270|IQ260|IQ250',
		// http://fr.wikomobile.com
		'Wiko'         => 'KITE 4G|HIGHWAY|GETAWAY|STAIRWAY|DARKSIDE|DARKFULL|DARKNIGHT|DARKMOON|SLIDE|WAX 4G|RAINBOW|BLOOM|SUNSET|GOA(?!nna)|LENNY|BARRY|IGGY|OZZY|CINK FIVE|CINK PEAX|CINK PEAX 2|CINK SLIM|CINK SLIM 2|CINK +|CINK KING|CINK PEAX|CINK SLIM|SUBLIM',
		'iMobile'      => 'i-mobile (IQ|i-STYLE|idea|ZAA|Hitz)',
		// Added simvalley mobile just for fun. They have some interesting devices.
		// http://www.simvalley.fr/telephonie---gps-_22_telephonie-mobile_telephones_.html
		'SimValley'    => '\b(SP-80|XT-930|SX-340|XT-930|SX-310|SP-360|SP60|SPT-800|SP-120|SPT-800|SP-140|SPX-5|SPX-8|SP-100|SPX-8|SPX-12)\b',
		// Wolfgang - a brand that is sold by Aldi supermarkets.
		// http://www.wolfgangmobile.com/
		'Wolfgang'     => 'AT-B24D|AT-AS50HD|AT-AS40W|AT-AS55HD|AT-AS45q2|AT-B26D|AT-AS50Q',
		'Alcatel'      => 'Alcatel',
		'Nintendo'     => 'Nintendo (3DS|Switch)',
		// http://en.wikipedia.org/wiki/Amoi
		'Amoi'         => 'Amoi',
		// http://en.wikipedia.org/wiki/INQ
		'INQ'          => 'INQ',
		'OnePlus'      => 'ONEPLUS',
		// @Tapatalk is a mobile app; http://support.tapatalk.com/threads/smf-2-0-2-os-and-browser-detection-plugin-and-tapatalk.15565/#post-79039
		'GenericPhone' => 'Tapatalk|PDA;|SAGEM|\bmmp\b|pocket|\bpsp\b|symbian|Smartphone|smartfon|treo|up.browser|up.link|vodafone|\bwap\b|nokia|Series40|Series60|S60|SonyEricsson|N900|MAUI.*WAP.*Browser',
	];

	/**
	 * List of tablet devices.
	 *
	 * @var array
	 */
	protected static $tabletDevices = [
		// @todo: check for mobile friendly emails topic.
		'iPad'              => 'iPad|iPad.*Mobile',
		// Removed |^.*Android.*Nexus(?!(?:Mobile).)*$
		// @see #442
		// @todo Merge NexusTablet into GoogleTablet.
		'NexusTablet'       => 'Android.*Nexus[\s]+(7|9|10)',
		// https://en.wikipedia.org/wiki/Pixel_C
		'GoogleTablet'      => 'Android.*Pixel C',
		'SamsungTablet'     => 'SAMSUNG.*Tablet|Galaxy.*Tab|SC-01C|GT-P1000|GT-P1003|GT-P1010|GT-P3105|GT-P6210|GT-P6800|GT-P6810|GT-P7100|GT-P7300|GT-P7310|GT-P7500|GT-P7510|SCH-I800|SCH-I815|SCH-I905|SGH-I957|SGH-I987|SGH-T849|SGH-T859|SGH-T869|SPH-P100|GT-P3100|GT-P3108|GT-P3110|GT-P5100|GT-P5110|GT-P6200|GT-P7320|GT-P7511|GT-N8000|GT-P8510|SGH-I497|SPH-P500|SGH-T779|SCH-I705|SCH-I915|GT-N8013|GT-P3113|GT-P5113|GT-P8110|GT-N8010|GT-N8005|GT-N8020|GT-P1013|GT-P6201|GT-P7501|GT-N5100|GT-N5105|GT-N5110|SHV-E140K|SHV-E140L|SHV-E140S|SHV-E150S|SHV-E230K|SHV-E230L|SHV-E230S|SHW-M180K|SHW-M180L|SHW-M180S|SHW-M180W|SHW-M300W|SHW-M305W|SHW-M380K|SHW-M380S|SHW-M380W|SHW-M430W|SHW-M480K|SHW-M480S|SHW-M480W|SHW-M485W|SHW-M486W|SHW-M500W|GT-I9228|SCH-P739|SCH-I925|GT-I9200|GT-P5200|GT-P5210|GT-P5210X|SM-T311|SM-T310|SM-T310X|SM-T210|SM-T210R|SM-T211|SM-P600|SM-P601|SM-P605|SM-P900|SM-P901|SM-T217|SM-T217A|SM-T217S|SM-P6000|SM-T3100|SGH-I467|XE500|SM-T110|GT-P5220|GT-I9200X|GT-N5110X|GT-N5120|SM-P905|SM-T111|SM-T2105|SM-T315|SM-T320|SM-T320X|SM-T321|SM-T520|SM-T525|SM-T530NU|SM-T230NU|SM-T330NU|SM-T900|XE500T1C|SM-P605V|SM-P905V|SM-T337V|SM-T537V|SM-T707V|SM-T807V|SM-P600X|SM-P900X|SM-T210X|SM-T230|SM-T230X|SM-T325|GT-P7503|SM-T531|SM-T330|SM-T530|SM-T705|SM-T705C|SM-T535|SM-T331|SM-T800|SM-T700|SM-T537|SM-T807|SM-P907A|SM-T337A|SM-T537A|SM-T707A|SM-T807A|SM-T237|SM-T807P|SM-P607T|SM-T217T|SM-T337T|SM-T807T|SM-T116NQ|SM-T116BU|SM-P550|SM-T350|SM-T550|SM-T9000|SM-P9000|SM-T705Y|SM-T805|GT-P3113|SM-T710|SM-T810|SM-T815|SM-T360|SM-T533|SM-T113|SM-T335|SM-T715|SM-T560|SM-T670|SM-T677|SM-T377|SM-T567|SM-T357T|SM-T555|SM-T561|SM-T713|SM-T719|SM-T813|SM-T819|SM-T580|SM-T355Y?|SM-T280|SM-T817A|SM-T820|SM-W700|SM-P580|SM-T587|SM-P350|SM-P555M|SM-P355M|SM-T113NU|SM-T815Y|SM-T585|SM-T285|SM-T825|SM-W708|SM-T835',
		// SCH-P709|SCH-P729|SM-T2558|GT-I9205 - Samsung Mega - treat them like a regular phone.
		// http://docs.aws.amazon.com/silk/latest/developerguide/user-agent.html
		'Kindle'            => 'Kindle|Silk.*Accelerated|Android.*\b(KFOT|KFTT|KFJWI|KFJWA|KFOTE|KFSOWI|KFTHWI|KFTHWA|KFAPWI|KFAPWA|WFJWAE|KFSAWA|KFSAWI|KFASWI|KFARWI|KFFOWI|KFGIWI|KFMEWI)\b|Android.*Silk/[0-9.]+ like Chrome/[0-9.]+ (?!Mobile)',
		// Only the Surface tablets with Windows RT are considered mobile.
		// http://msdn.microsoft.com/en-us/library/ie/hh920767(v=vs.85).aspx
		'SurfaceTablet'     => 'Windows NT [0-9.]+; ARM;.*(Tablet|ARMBJS)',
		// http://shopping1.hp.com/is-bin/INTERSHOP.enfinity/WFS/WW-USSMBPublicStore-Site/en_US/-/USD/ViewStandardCatalog-Browse?CatalogCategoryID=JfIQ7EN5lqMAAAEyDcJUDwMT
		'HPTablet'          => 'HP Slate (7|8|10)|HP ElitePad 900|hp-tablet|EliteBook.*Touch|HP 8|Slate 21|HP SlateBook 10',
		// Watch out for PadFone, see #132.
		// http://www.asus.com/de/Tablets_Mobile/Memo_Pad_Products/
		'AsusTablet'        => '^.*PadFone((?!Mobile).)*$|Transformer|TF101|TF101G|TF300T|TF300TG|TF300TL|TF700T|TF700KL|TF701T|TF810C|ME171|ME301T|ME302C|ME371MG|ME370T|ME372MG|ME172V|ME173X|ME400C|Slider SL101|\bK00F\b|\bK00C\b|\bK00E\b|\bK00L\b|TX201LA|ME176C|ME102A|\bM80TA\b|ME372CL|ME560CG|ME372CG|ME302KL| K010 | K011 | K017 | K01E |ME572C|ME103K|ME170C|ME171C|\bME70C\b|ME581C|ME581CL|ME8510C|ME181C|P01Y|PO1MA|P01Z|\bP027\b|\bP024\b|\bP00C\b',
		'BlackBerryTablet'  => 'PlayBook|RIM Tablet',
		'HTCtablet'         => 'HTC_Flyer_P512|HTC Flyer|HTC Jetstream|HTC-P715a|HTC EVO View 4G|PG41200|PG09410',
		'MotorolaTablet'    => 'xoom|sholest|MZ615|MZ605|MZ505|MZ601|MZ602|MZ603|MZ604|MZ606|MZ607|MZ608|MZ609|MZ615|MZ616|MZ617',
		'NookTablet'        => 'Android.*Nook|NookColor|nook browser|BNRV200|BNRV200A|BNTV250|BNTV250A|BNTV400|BNTV600|LogicPD Zoom2',
		// http://www.acer.ro/ac/ro/RO/content/drivers
		// http://www.packardbell.co.uk/pb/en/GB/content/download (Packard Bell is part of Acer)
		// http://us.acer.com/ac/en/US/content/group/tablets
		// http://www.acer.de/ac/de/DE/content/models/tablets/
		// Can conflict with Micromax and Motorola phones codes.
		'AcerTablet'        => 'Android.*; \b(A100|A101|A110|A200|A210|A211|A500|A501|A510|A511|A700|A701|W500|W500P|W501|W501P|W510|W511|W700|G100|G100W|B1-A71|B1-710|B1-711|A1-810|A1-811|A1-830)\b|W3-810|\bA3-A10\b|\bA3-A11\b|\bA3-A20\b|\bA3-A30',
		// http://eu.computers.toshiba-europe.com/innovation/family/Tablets/1098744/banner_id/tablet_footerlink/
		// http://us.toshiba.com/tablets/tablet-finder
		// http://www.toshiba.co.jp/regza/tablet/
		'ToshibaTablet'     => 'Android.*(AT100|AT105|AT200|AT205|AT270|AT275|AT300|AT305|AT1S5|AT500|AT570|AT700|AT830)|TOSHIBA.*FOLIO',
		// http://www.nttdocomo.co.jp/english/service/developer/smart_phone/technical_info/spec/index.html
		// http://www.lg.com/us/tablets
		'LGTablet'          => '\bL-06C|LG-V909|LG-V900|LG-V700|LG-V510|LG-V500|LG-V410|LG-V400|LG-VK810\b',
		'FujitsuTablet'     => 'Android.*\b(F-01D|F-02F|F-05E|F-10D|M532|Q572)\b',
		// Prestigio Tablets http://www.prestigio.com/support
		'PrestigioTablet'   => 'PMP3170B|PMP3270B|PMP3470B|PMP7170B|PMP3370B|PMP3570C|PMP5870C|PMP3670B|PMP5570C|PMP5770D|PMP3970B|PMP3870C|PMP5580C|PMP5880D|PMP5780D|PMP5588C|PMP7280C|PMP7280C3G|PMP7280|PMP7880D|PMP5597D|PMP5597|PMP7100D|PER3464|PER3274|PER3574|PER3884|PER5274|PER5474|PMP5097CPRO|PMP5097|PMP7380D|PMP5297C|PMP5297C_QUAD|PMP812E|PMP812E3G|PMP812F|PMP810E|PMP880TD|PMT3017|PMT3037|PMT3047|PMT3057|PMT7008|PMT5887|PMT5001|PMT5002',
		// http://support.lenovo.com/en_GB/downloads/default.page?#
		'LenovoTablet'      => 'Lenovo TAB|Idea(Tab|Pad)( A1|A10| K1|)|ThinkPad([ ]+)?Tablet|YT3-850M|YT3-X90L|YT3-X90F|YT3-X90X|Lenovo.*(S2109|S2110|S5000|S6000|K3011|A3000|A3500|A1000|A2107|A2109|A1107|A5500|A7600|B6000|B8000|B8080)(-|)(FL|F|HV|H|)|TB-X103F|TB-X304F|TB-X304L|TB-8703F|Tab2A7-10F|TB2-X30L',
		// http://www.dell.com/support/home/us/en/04/Products/tab_mob/tablets
		'DellTablet'        => 'Venue 11|Venue 8|Venue 7|Dell Streak 10|Dell Streak 7',
		// http://www.yarvik.com/en/matrix/tablets/
		'YarvikTablet'      => 'Android.*\b(TAB210|TAB211|TAB224|TAB250|TAB260|TAB264|TAB310|TAB360|TAB364|TAB410|TAB411|TAB420|TAB424|TAB450|TAB460|TAB461|TAB464|TAB465|TAB467|TAB468|TAB07-100|TAB07-101|TAB07-150|TAB07-151|TAB07-152|TAB07-200|TAB07-201-3G|TAB07-210|TAB07-211|TAB07-212|TAB07-214|TAB07-220|TAB07-400|TAB07-485|TAB08-150|TAB08-200|TAB08-201-3G|TAB08-201-30|TAB09-100|TAB09-211|TAB09-410|TAB10-150|TAB10-201|TAB10-211|TAB10-400|TAB10-410|TAB13-201|TAB274EUK|TAB275EUK|TAB374EUK|TAB462EUK|TAB474EUK|TAB9-200)\b',
		'MedionTablet'      => 'Android.*\bOYO\b|LIFE.*(P9212|P9514|P9516|S9512)|LIFETAB',
		'ArnovaTablet'      => '97G4|AN10G2|AN7bG3|AN7fG3|AN8G3|AN8cG3|AN7G3|AN9G3|AN7dG3|AN7dG3ST|AN7dG3ChildPad|AN10bG3|AN10bG3DT|AN9G2',
		// http://www.intenso.de/kategorie_en.php?kategorie=33
		// @todo: http://www.nbhkdz.com/read/b8e64202f92a2df129126bff.html - investigate
		'IntensoTablet'     => 'INM8002KP|INM1010FP|INM805ND|Intenso Tab|TAB1004',
		// IRU.ru Tablets http://www.iru.ru/catalog/soho/planetable/
		'IRUTablet'         => 'M702pro',
		'MegafonTablet'     => 'MegaFon V9|\bZTE V9\b|Android.*\bMT7A\b',
		// http://www.e-boda.ro/tablete-pc.html
		'EbodaTablet'       => 'E-Boda (Supreme|Impresspeed|Izzycomm|Essential)',
		// http://www.allview.ro/produse/droseries/lista-tablete-pc/
		'AllViewTablet'     => 'Allview.*(Viva|Alldro|City|Speed|All TV|Frenzy|Quasar|Shine|TX1|AX1|AX2)',
		// http://wiki.archosfans.com/index.php?title=Main_Page
		// @note Rewrite the regex format after we add more UAs.
		'ArchosTablet'      => '\b(101G9|80G9|A101IT)\b|Qilive 97R|Archos5|\bARCHOS (70|79|80|90|97|101|FAMILYPAD|)(b|c|)(G10| Cobalt| TITANIUM(HD|)| Xenon| Neon|XSK| 2| XS 2| PLATINUM| CARBON|GAMEPAD)\b',
		// http://www.ainol.com/plugin.php?identifier=ainol&module=product
		'AinolTablet'       => 'NOVO7|NOVO8|NOVO10|Novo7Aurora|Novo7Basic|NOVO7PALADIN|novo9-Spark',
		'NokiaLumiaTablet'  => 'Lumia 2520',
		// @todo: inspect http://esupport.sony.com/US/p/select-system.pl?DIRECTOR=DRIVER
		// Readers http://www.atsuhiro-me.net/ebook/sony-reader/sony-reader-web-browser
		// http://www.sony.jp/support/tablet/
		'SonyTablet'        => 'Sony.*Tablet|Xperia Tablet|Sony Tablet S|SO-03E|SGPT12|SGPT13|SGPT114|SGPT121|SGPT122|SGPT123|SGPT111|SGPT112|SGPT113|SGPT131|SGPT132|SGPT133|SGPT211|SGPT212|SGPT213|SGP311|SGP312|SGP321|EBRD1101|EBRD1102|EBRD1201|SGP351|SGP341|SGP511|SGP512|SGP521|SGP541|SGP551|SGP621|SGP641|SGP612|SOT31|SGP771|SGP611|SGP612|SGP712',
		// http://www.support.philips.com/support/catalog/worldproducts.jsp?userLanguage=en&userCountry=cn&categoryid=3G_LTE_TABLET_SU_CN_CARE&title=3G%20tablets%20/%20LTE%20range&_dyncharset=UTF-8
		'PhilipsTablet'     => '\b(PI2010|PI3000|PI3100|PI3105|PI3110|PI3205|PI3210|PI3900|PI4010|PI7000|PI7100)\b',
		// db + http://www.cube-tablet.com/buy-products.html
		'CubeTablet'        => 'Android.*(K8GT|U9GT|U10GT|U16GT|U17GT|U18GT|U19GT|U20GT|U23GT|U30GT)|CUBE U8GT',
		// http://www.cobyusa.com/?p=pcat&pcat_id=3001
		'CobyTablet'        => 'MID1042|MID1045|MID1125|MID1126|MID7012|MID7014|MID7015|MID7034|MID7035|MID7036|MID7042|MID7048|MID7127|MID8042|MID8048|MID8127|MID9042|MID9740|MID9742|MID7022|MID7010',
		// http://www.match.net.cn/products.asp
		'MIDTablet'         => 'M9701|M9000|M9100|M806|M1052|M806|T703|MID701|MID713|MID710|MID727|MID760|MID830|MID728|MID933|MID125|MID810|MID732|MID120|MID930|MID800|MID731|MID900|MID100|MID820|MID735|MID980|MID130|MID833|MID737|MID960|MID135|MID860|MID736|MID140|MID930|MID835|MID733|MID4X10',
		// http://www.msi.com/support
		// @todo Research the Windows Tablets.
		'MSITablet'         => 'MSI \b(Primo 73K|Primo 73L|Primo 81L|Primo 77|Primo 93|Primo 75|Primo 76|Primo 73|Primo 81|Primo 91|Primo 90|Enjoy 71|Enjoy 7|Enjoy 10)\b',
		// @todo http://www.kyoceramobile.com/support/drivers/
		//    'KyoceraTablet' => null,
		// @todo http://intexuae.com/index.php/category/mobile-devices/tablets-products/
		//    'IntextTablet' => null,
		// http://pdadb.net/index.php?m=pdalist&list=SMiT (NoName Chinese Tablets)
		// http://www.imp3.net/14/show.php?itemid=20454
		'SMiTTablet'        => 'Android.*(\bMID\b|MID-560|MTV-T1200|MTV-PND531|MTV-P1101|MTV-PND530)',
		// http://www.rock-chips.com/index.php?do=prod&pid=2
		'RockChipTablet'    => 'Android.*(RK2818|RK2808A|RK2918|RK3066)|RK2738|RK2808A',
		// http://www.fly-phone.com/devices/tablets/ ; http://www.fly-phone.com/service/
		'FlyTablet'         => 'IQ310|Fly Vision',
		// http://www.bqreaders.com/gb/tablets-prices-sale.html
		'bqTablet'          => 'Android.*(bq)?.*(Elcano|Curie|Edison|Maxwell|Kepler|Pascal|Tesla|Hypatia|Platon|Newton|Livingstone|Cervantes|Avant|Aquaris ([E|M]10|M8))|Maxwell.*Lite|Maxwell.*Plus',
		// http://www.huaweidevice.com/worldwide/productFamily.do?method=index&directoryId=5011&treeId=3290
		// http://www.huaweidevice.com/worldwide/downloadCenter.do?method=index&directoryId=3372&treeId=0&tb=1&type=software (including legacy tablets)
		'HuaweiTablet'      => 'MediaPad|MediaPad 7 Youth|IDEOS S7|S7-201c|S7-202u|S7-101|S7-103|S7-104|S7-105|S7-106|S7-201|S7-Slim|M2-A01L|BAH-L09|BAH-W09',
		// Nec or Medias Tab
		'NecTablet'         => '\bN-06D|\bN-08D',
		// Pantech Tablets: http://www.pantechusa.com/phones/
		'PantechTablet'     => 'Pantech.*P4100',
		// Broncho Tablets: http://www.broncho.cn/ (hard to find)
		'BronchoTablet'     => 'Broncho.*(N701|N708|N802|a710)',
		// http://versusuk.com/support.html
		'VersusTablet'      => 'TOUCHPAD.*[78910]|\bTOUCHTAB\b',
		// http://www.zync.in/index.php/our-products/tablet-phablets
		'ZyncTablet'        => 'z1000|Z99 2G|z99|z930|z999|z990|z909|Z919|z900',
		// http://www.positivoinformatica.com.br/www/pessoal/tablet-ypy/
		'PositivoTablet'    => 'TB07STA|TB10STA|TB07FTA|TB10FTA',
		// https://www.nabitablet.com/
		'NabiTablet'        => 'Android.*\bNabi',
		'KoboTablet'        => 'Kobo Touch|\bK080\b|\bVox\b Build|\bArc\b Build',
		// French Danew Tablets http://www.danew.com/produits-tablette.php
		'DanewTablet'       => 'DSlide.*\b(700|701R|702|703R|704|802|970|971|972|973|974|1010|1012)\b',
		// Texet Tablets and Readers http://www.texet.ru/tablet/
		'TexetTablet'       => 'NaviPad|TB-772A|TM-7045|TM-7055|TM-9750|TM-7016|TM-7024|TM-7026|TM-7041|TM-7043|TM-7047|TM-8041|TM-9741|TM-9747|TM-9748|TM-9751|TM-7022|TM-7021|TM-7020|TM-7011|TM-7010|TM-7023|TM-7025|TM-7037W|TM-7038W|TM-7027W|TM-9720|TM-9725|TM-9737W|TM-1020|TM-9738W|TM-9740|TM-9743W|TB-807A|TB-771A|TB-727A|TB-725A|TB-719A|TB-823A|TB-805A|TB-723A|TB-715A|TB-707A|TB-705A|TB-709A|TB-711A|TB-890HD|TB-880HD|TB-790HD|TB-780HD|TB-770HD|TB-721HD|TB-710HD|TB-434HD|TB-860HD|TB-840HD|TB-760HD|TB-750HD|TB-740HD|TB-730HD|TB-722HD|TB-720HD|TB-700HD|TB-500HD|TB-470HD|TB-431HD|TB-430HD|TB-506|TB-504|TB-446|TB-436|TB-416|TB-146SE|TB-126SE',
		// Avoid detecting 'PLAYSTATION 3' as mobile.
		'PlaystationTablet' => 'Playstation.*(Portable|Vita)',
		// http://www.trekstor.de/surftabs.html
		'TrekstorTablet'    => 'ST10416-1|VT10416-1|ST70408-1|ST702xx-1|ST702xx-2|ST80208|ST97216|ST70104-2|VT10416-2|ST10216-2A|SurfTab',
		// http://www.pyleaudio.com/Products.aspx?%2fproducts%2fPersonal-Electronics%2fTablets
		'PyleAudioTablet'   => '\b(PTBL10CEU|PTBL10C|PTBL72BC|PTBL72BCEU|PTBL7CEU|PTBL7C|PTBL92BC|PTBL92BCEU|PTBL9CEU|PTBL9CUK|PTBL9C)\b',
		// http://www.advandigital.com/index.php?link=content-product&jns=JP001
		// because of the short codenames we have to include whitespaces to reduce the possible conflicts.
		'AdvanTablet'       => 'Android.* \b(E3A|T3X|T5C|T5B|T3E|T3C|T3B|T1J|T1F|T2A|T1H|T1i|E1C|T1-E|T5-A|T4|E1-B|T2Ci|T1-B|T1-D|O1-A|E1-A|T1-A|T3A|T4i)\b ',
		// http://www.danytech.com/category/tablet-pc
		'DanyTechTablet'    => 'Genius Tab G3|Genius Tab S2|Genius Tab Q3|Genius Tab G4|Genius Tab Q4|Genius Tab G-II|Genius TAB GII|Genius TAB GIII|Genius Tab S1',
		// http://www.galapad.net/product.html
		'GalapadTablet'     => 'Android.*\bG1\b(?!\))',
		// http://www.micromaxinfo.com/tablet/funbook
		'MicromaxTablet'    => 'Funbook|Micromax.*\b(P250|P560|P360|P362|P600|P300|P350|P500|P275)\b',
		// http://www.karbonnmobiles.com/products_tablet.php
		'KarbonnTablet'     => 'Android.*\b(A39|A37|A34|ST8|ST10|ST7|Smart Tab3|Smart Tab2)\b',
		// http://www.myallfine.com/Products.asp
		'AllFineTablet'     => 'Fine7 Genius|Fine7 Shine|Fine7 Air|Fine8 Style|Fine9 More|Fine10 Joy|Fine11 Wide',
		// http://www.proscanvideo.com/products-search.asp?itemClass=TABLET&itemnmbr=
		'PROSCANTablet'     => '\b(PEM63|PLT1023G|PLT1041|PLT1044|PLT1044G|PLT1091|PLT4311|PLT4311PL|PLT4315|PLT7030|PLT7033|PLT7033D|PLT7035|PLT7035D|PLT7044K|PLT7045K|PLT7045KB|PLT7071KG|PLT7072|PLT7223G|PLT7225G|PLT7777G|PLT7810K|PLT7849G|PLT7851G|PLT7852G|PLT8015|PLT8031|PLT8034|PLT8036|PLT8080K|PLT8082|PLT8088|PLT8223G|PLT8234G|PLT8235G|PLT8816K|PLT9011|PLT9045K|PLT9233G|PLT9735|PLT9760G|PLT9770G)\b',
		// http://www.yonesnav.com/products/products.php
		'YONESTablet'       => 'BQ1078|BC1003|BC1077|RK9702|BC9730|BC9001|IT9001|BC7008|BC7010|BC708|BC728|BC7012|BC7030|BC7027|BC7026',
		// http://www.cjshowroom.com/eproducts.aspx?classcode=004001001
		// China manufacturer makes tablets for different small brands (eg. http://www.zeepad.net/index.html)
		'ChangJiaTablet'    => 'TPC7102|TPC7103|TPC7105|TPC7106|TPC7107|TPC7201|TPC7203|TPC7205|TPC7210|TPC7708|TPC7709|TPC7712|TPC7110|TPC8101|TPC8103|TPC8105|TPC8106|TPC8203|TPC8205|TPC8503|TPC9106|TPC9701|TPC97101|TPC97103|TPC97105|TPC97106|TPC97111|TPC97113|TPC97203|TPC97603|TPC97809|TPC97205|TPC10101|TPC10103|TPC10106|TPC10111|TPC10203|TPC10205|TPC10503',
		// http://www.gloryunion.cn/products.asp
		// http://www.allwinnertech.com/en/apply/mobile.html
		// http://www.ptcl.com.pk/pd_content.php?pd_id=284 (EVOTAB)
		// @todo: Softwiner tablets?
		// aka. Cute or Cool tablets. Not sure yet, must research to avoid collisions.
		'GUTablet'          => 'TX-A1301|TX-M9002|Q702|kf026',
		// A12R|D75A|D77|D79|R83|A95|A106C|R15|A75|A76|D71|D72|R71|R73|R77|D82|R85|D92|A97|D92|R91|A10F|A77F|W71F|A78F|W78F|W81F|A97F|W91F|W97F|R16G|C72|C73E|K72|K73|R96G
		// http://www.pointofview-online.com/showroom.php?shop_mode=product_listing&category_id=118
		'PointOfViewTablet' => 'TAB-P506|TAB-navi-7-3G-M|TAB-P517|TAB-P-527|TAB-P701|TAB-P703|TAB-P721|TAB-P731N|TAB-P741|TAB-P825|TAB-P905|TAB-P925|TAB-PR945|TAB-PL1015|TAB-P1025|TAB-PI1045|TAB-P1325|TAB-PROTAB[0-9]+|TAB-PROTAB25|TAB-PROTAB26|TAB-PROTAB27|TAB-PROTAB26XL|TAB-PROTAB2-IPS9|TAB-PROTAB30-IPS9|TAB-PROTAB25XXL|TAB-PROTAB26-IPS10|TAB-PROTAB30-IPS10',
		// http://www.overmax.pl/pl/katalog-produktow,p8/tablety,c14/
		// @todo: add more tests.
		'OvermaxTablet'     => 'OV-(SteelCore|NewBase|Basecore|Baseone|Exellen|Quattor|EduTab|Solution|ACTION|BasicTab|TeddyTab|MagicTab|Stream|TB-08|TB-09)|Qualcore 1027',
		// http://hclmetablet.com/India/index.php
		'HCLTablet'         => 'HCL.*Tablet|Connect-3G-2.0|Connect-2G-2.0|ME Tablet U1|ME Tablet U2|ME Tablet G1|ME Tablet X1|ME Tablet Y2|ME Tablet Sync',
		// http://www.edigital.hu/Tablet_es_e-book_olvaso/Tablet-c18385.html
		'DPSTablet'         => 'DPS Dream 9|DPS Dual 7',
		// http://www.visture.com/index.asp
		'VistureTablet'     => 'V97 HD|i75 3G|Visture V4( HD)?|Visture V5( HD)?|Visture V10',
		// http://www.mijncresta.nl/tablet
		'CrestaTablet'      => 'CTP(-)?810|CTP(-)?818|CTP(-)?828|CTP(-)?838|CTP(-)?888|CTP(-)?978|CTP(-)?980|CTP(-)?987|CTP(-)?988|CTP(-)?989',
		// MediaTek - http://www.mediatek.com/_en/01_products/02_proSys.php?cata_sn=1&cata1_sn=1&cata2_sn=309
		'MediatekTablet'    => '\bMT8125|MT8389|MT8135|MT8377\b',
		// Concorde tab
		'ConcordeTablet'    => 'Concorde([ ]+)?Tab|ConCorde ReadMan',
		// GoClever Tablets - http://www.goclever.com/uk/products,c1/tablet,c5/
		'GoCleverTablet'    => 'GOCLEVER TAB|A7GOCLEVER|M1042|M7841|M742|R1042BK|R1041|TAB A975|TAB A7842|TAB A741|TAB A741L|TAB M723G|TAB M721|TAB A1021|TAB I921|TAB R721|TAB I720|TAB T76|TAB R70|TAB R76.2|TAB R106|TAB R83.2|TAB M813G|TAB I721|GCTA722|TAB I70|TAB I71|TAB S73|TAB R73|TAB R74|TAB R93|TAB R75|TAB R76.1|TAB A73|TAB A93|TAB A93.2|TAB T72|TAB R83|TAB R974|TAB R973|TAB A101|TAB A103|TAB A104|TAB A104.2|R105BK|M713G|A972BK|TAB A971|TAB R974.2|TAB R104|TAB R83.3|TAB A1042',
		// Modecom Tablets - http://www.modecom.eu/tablets/portal/
		'ModecomTablet'     => 'FreeTAB 9000|FreeTAB 7.4|FreeTAB 7004|FreeTAB 7800|FreeTAB 2096|FreeTAB 7.5|FreeTAB 1014|FreeTAB 1001 |FreeTAB 8001|FreeTAB 9706|FreeTAB 9702|FreeTAB 7003|FreeTAB 7002|FreeTAB 1002|FreeTAB 7801|FreeTAB 1331|FreeTAB 1004|FreeTAB 8002|FreeTAB 8014|FreeTAB 9704|FreeTAB 1003',
		// Vonino Tablets - http://www.vonino.eu/tablets
		'VoninoTablet'      => '\b(Argus[ _]?S|Diamond[ _]?79HD|Emerald[ _]?78E|Luna[ _]?70C|Onyx[ _]?S|Onyx[ _]?Z|Orin[ _]?HD|Orin[ _]?S|Otis[ _]?S|SpeedStar[ _]?S|Magnet[ _]?M9|Primus[ _]?94[ _]?3G|Primus[ _]?94HD|Primus[ _]?QS|Android.*\bQ8\b|Sirius[ _]?EVO[ _]?QS|Sirius[ _]?QS|Spirit[ _]?S)\b',
		// ECS Tablets - http://www.ecs.com.tw/ECSWebSite/Product/Product_Tablet_List.aspx?CategoryID=14&MenuID=107&childid=M_107&LanID=0
		'ECSTablet'         => 'V07OT2|TM105A|S10OT1|TR10CS1',
		// Storex Tablets - http://storex.fr/espace_client/support.html
		// @note: no need to add all the tablet codes since they are guided by the first regex.
		'StorexTablet'      => 'eZee[_\']?(Tab|Go)[0-9]+|TabLC7|Looney Tunes Tab',
		// Generic Vodafone tablets.
		'VodafoneTablet'    => 'SmartTab([ ]+)?[0-9]+|SmartTabII10|SmartTabII7|VF-1497',
		// French tablets - Essentiel B http://www.boulanger.fr/tablette_tactile_e-book/tablette_tactile_essentiel_b/cl_68908.htm?multiChoiceToDelete=brand&mc_brand=essentielb
		// Aka: http://www.essentielb.fr/
		'EssentielBTablet'  => 'Smart[ \']?TAB[ ]+?[0-9]+|Family[ \']?TAB2',
		// Ross & Moor - http://ross-moor.ru/
		'RossMoorTablet'    => 'RM-790|RM-997|RMD-878G|RMD-974R|RMT-705A|RMT-701|RME-601|RMT-501|RMT-711',
		// i-mobile http://product.i-mobilephone.com/Mobile_Device
		'iMobileTablet'     => 'i-mobile i-note',
		// http://www.tolino.de/de/vergleichen/
		'TolinoTablet'      => 'tolino tab [0-9.]+|tolino shine',
		// AudioSonic - a Kmart brand
		// http://www.kmart.com.au/webapp/wcs/stores/servlet/Search?langId=-1&storeId=10701&catalogId=10001&categoryId=193001&pageSize=72&currentPage=1&searchCategory=193001%2b4294965664&sortBy=p_MaxPrice%7c1
		'AudioSonicTablet'  => '\bC-22Q|T7-QC|T-17B|T-17P\b',
		// AMPE Tablets - http://www.ampe.com.my/product-category/tablets/
		// @todo: add them gradually to avoid conflicts.
		'AMPETablet'        => 'Android.* A78 ',
		// Skk Mobile - http://skkmobile.com.ph/product_tablets.php
		'SkkTablet'         => 'Android.* (SKYPAD|PHOENIX|CYCLOPS)',
		// Tecno Mobile (only tablet) - http://www.tecno-mobile.com/index.php/product?filterby=smart&list_order=all&page=1
		'TecnoTablet'       => 'TECNO P9|TECNO DP8D',
		// JXD (consoles & tablets) - http://jxd.hk/products.asp?selectclassid=009008&clsid=3
		'JXDTablet'         => 'Android.* \b(F3000|A3300|JXD5000|JXD3000|JXD2000|JXD300B|JXD300|S5800|S7800|S602b|S5110b|S7300|S5300|S602|S603|S5100|S5110|S601|S7100a|P3000F|P3000s|P101|P200s|P1000m|P200m|P9100|P1000s|S6600b|S908|P1000|P300|S18|S6600|S9100)\b',
		// i-Joy tablets - http://www.i-joy.es/en/cat/products/tablets/
		'iJoyTablet'        => 'Tablet (Spirit 7|Essentia|Galatea|Fusion|Onix 7|Landa|Titan|Scooby|Deox|Stella|Themis|Argon|Unique 7|Sygnus|Hexen|Finity 7|Cream|Cream X2|Jade|Neon 7|Neron 7|Kandy|Scape|Saphyr 7|Rebel|Biox|Rebel|Rebel 8GB|Myst|Draco 7|Myst|Tab7-004|Myst|Tadeo Jones|Tablet Boing|Arrow|Draco Dual Cam|Aurix|Mint|Amity|Revolution|Finity 9|Neon 9|T9w|Amity 4GB Dual Cam|Stone 4GB|Stone 8GB|Andromeda|Silken|X2|Andromeda II|Halley|Flame|Saphyr 9,7|Touch 8|Planet|Triton|Unique 10|Hexen 10|Memphis 4GB|Memphis 8GB|Onix 10)',
		// http://www.intracon.eu/tablet
		'FX2Tablet'         => 'FX2 PAD7|FX2 PAD10',
		// http://www.xoro.de/produkte/
		// @note: Might be the same brand with 'Simply tablets'
		'XoroTablet'        => 'KidsPAD 701|PAD[ ]?712|PAD[ ]?714|PAD[ ]?716|PAD[ ]?717|PAD[ ]?718|PAD[ ]?720|PAD[ ]?721|PAD[ ]?722|PAD[ ]?790|PAD[ ]?792|PAD[ ]?900|PAD[ ]?9715D|PAD[ ]?9716DR|PAD[ ]?9718DR|PAD[ ]?9719QR|PAD[ ]?9720QR|TelePAD1030|Telepad1032|TelePAD730|TelePAD731|TelePAD732|TelePAD735Q|TelePAD830|TelePAD9730|TelePAD795|MegaPAD 1331|MegaPAD 1851|MegaPAD 2151',
		// http://www1.viewsonic.com/products/computing/tablets/
		'ViewsonicTablet'   => 'ViewPad 10pi|ViewPad 10e|ViewPad 10s|ViewPad E72|ViewPad7|ViewPad E100|ViewPad 7e|ViewSonic VB733|VB100a',
		// https://www.verizonwireless.com/tablets/verizon/
		'VerizonTablet'     => 'QTAQZ3|QTAIR7|QTAQTZ3|QTASUN1|QTASUN2|QTAXIA1',
		// http://www.odys.de/web/internet-tablet_en.html
		'OdysTablet'        => 'LOOX|XENO10|ODYS[ -](Space|EVO|Xpress|NOON)|\bXELIO\b|Xelio10Pro|XELIO7PHONETAB|XELIO10EXTREME|XELIOPT2|NEO_QUAD10',
		// http://www.captiva-power.de/products.html#tablets-en
		'CaptivaTablet'     => 'CAPTIVA PAD',
		// IconBIT - http://www.iconbit.com/products/tablets/
		'IconbitTablet'     => 'NetTAB|NT-3702|NT-3702S|NT-3702S|NT-3603P|NT-3603P|NT-0704S|NT-0704S|NT-3805C|NT-3805C|NT-0806C|NT-0806C|NT-0909T|NT-0909T|NT-0907S|NT-0907S|NT-0902S|NT-0902S',
		// http://www.teclast.com/topic.php?channelID=70&topicID=140&pid=63
		'TeclastTablet'     => 'T98 4G|\bP80\b|\bX90HD\b|X98 Air|X98 Air 3G|\bX89\b|P80 3G|\bX80h\b|P98 Air|\bX89HD\b|P98 3G|\bP90HD\b|P89 3G|X98 3G|\bP70h\b|P79HD 3G|G18d 3G|\bP79HD\b|\bP89s\b|\bA88\b|\bP10HD\b|\bP19HD\b|G18 3G|\bP78HD\b|\bA78\b|\bP75\b|G17s 3G|G17h 3G|\bP85t\b|\bP90\b|\bP11\b|\bP98t\b|\bP98HD\b|\bG18d\b|\bP85s\b|\bP11HD\b|\bP88s\b|\bA80HD\b|\bA80se\b|\bA10h\b|\bP89\b|\bP78s\b|\bG18\b|\bP85\b|\bA70h\b|\bA70\b|\bG17\b|\bP18\b|\bA80s\b|\bA11s\b|\bP88HD\b|\bA80h\b|\bP76s\b|\bP76h\b|\bP98\b|\bA10HD\b|\bP78\b|\bP88\b|\bA11\b|\bA10t\b|\bP76a\b|\bP76t\b|\bP76e\b|\bP85HD\b|\bP85a\b|\bP86\b|\bP75HD\b|\bP76v\b|\bA12\b|\bP75a\b|\bA15\b|\bP76Ti\b|\bP81HD\b|\bA10\b|\bT760VE\b|\bT720HD\b|\bP76\b|\bP73\b|\bP71\b|\bP72\b|\bT720SE\b|\bC520Ti\b|\bT760\b|\bT720VE\b|T720-3GE|T720-WiFi',
		// Onda - http://www.onda-tablet.com/buy-android-onda.html?dir=desc&limit=all&order=price
		'OndaTablet'        => '\b(V975i|Vi30|VX530|V701|Vi60|V701s|Vi50|V801s|V719|Vx610w|VX610W|V819i|Vi10|VX580W|Vi10|V711s|V813|V811|V820w|V820|Vi20|V711|VI30W|V712|V891w|V972|V819w|V820w|Vi60|V820w|V711|V813s|V801|V819|V975s|V801|V819|V819|V818|V811|V712|V975m|V101w|V961w|V812|V818|V971|V971s|V919|V989|V116w|V102w|V973|Vi40)\b[\s]+|V10 \b4G\b',
		'JaytechTablet'     => 'TPC-PA762',
		'BlaupunktTablet'   => 'Endeavour 800NG|Endeavour 1010',
		// http://www.digma.ru/support/download/
		// @todo: Ebooks also (if requested)
		'DigmaTablet'       => '\b(iDx10|iDx9|iDx8|iDx7|iDxD7|iDxD8|iDsQ8|iDsQ7|iDsQ8|iDsD10|iDnD7|3TS804H|iDsQ11|iDj7|iDs10)\b',
		// http://www.evolioshop.com/ro/tablete-pc.html
		// http://www.evolio.ro/support/downloads_static.html?cat=2
		// @todo: Research some more
		'EvolioTablet'      => 'ARIA_Mini_wifi|Aria[ _]Mini|Evolio X10|Evolio X7|Evolio X8|\bEvotab\b|\bNeura\b',
		// @todo http://www.lavamobiles.com/tablets-data-cards
		'LavaTablet'        => 'QPAD E704|\bIvoryS\b|E-TAB IVORY|\bE-TAB\b',
		// http://www.breezetablet.com/
		'AocTablet'         => 'MW0811|MW0812|MW0922|MTK8382|MW1031|MW0831|MW0821|MW0931|MW0712',
		// http://www.mpmaneurope.com/en/products/internet-tablets-14/android-tablets-14/
		'MpmanTablet'       => 'MP11 OCTA|MP10 OCTA|MPQC1114|MPQC1004|MPQC994|MPQC974|MPQC973|MPQC804|MPQC784|MPQC780|\bMPG7\b|MPDCG75|MPDCG71|MPDC1006|MP101DC|MPDC9000|MPDC905|MPDC706HD|MPDC706|MPDC705|MPDC110|MPDC100|MPDC99|MPDC97|MPDC88|MPDC8|MPDC77|MP709|MID701|MID711|MID170|MPDC703|MPQC1010',
		// https://www.celkonmobiles.com/?_a=categoryphones&sid=2
		'CelkonTablet'      => 'CT695|CT888|CT[\s]?910|CT7 Tab|CT9 Tab|CT3 Tab|CT2 Tab|CT1 Tab|C820|C720|\bCT-1\b',
		// http://www.wolderelectronics.com/productos/manuales-y-guias-rapidas/categoria-2-miTab
		'WolderTablet'      => 'miTab \b(DIAMOND|SPACE|BROOKLYN|NEO|FLY|MANHATTAN|FUNK|EVOLUTION|SKY|GOCAR|IRON|GENIUS|POP|MINT|EPSILON|BROADWAY|JUMP|HOP|LEGEND|NEW AGE|LINE|ADVANCE|FEEL|FOLLOW|LIKE|LINK|LIVE|THINK|FREEDOM|CHICAGO|CLEVELAND|BALTIMORE-GH|IOWA|BOSTON|SEATTLE|PHOENIX|DALLAS|IN 101|MasterChef)\b',
		'MediacomTablet'    => 'M-MPI10C3G|M-SP10EG|M-SP10EGP|M-SP10HXAH|M-SP7HXAH|M-SP10HXBH|M-SP8HXAH|M-SP8MXA',
		// http://www.mi.com/en
		'MiTablet'          => '\bMI PAD\b|\bHM NOTE 1W\b',
		// http://www.nbru.cn/index.html
		'NibiruTablet'      => 'Nibiru M1|Nibiru Jupiter One',
		// http://navroad.com/products/produkty/tablety/
		// http://navroad.com/products/produkty/tablety/
		'NexoTablet'        => 'NEXO NOVA|NEXO 10|NEXO AVIO|NEXO FREE|NEXO GO|NEXO EVO|NEXO 3G|NEXO SMART|NEXO KIDDO|NEXO MOBI',
		// http://leader-online.com/new_site/product-category/tablets/
		// http://www.leader-online.net.au/List/Tablet
		'LeaderTablet'      => 'TBLT10Q|TBLT10I|TBL-10WDKB|TBL-10WDKBO2013|TBL-W230V2|TBL-W450|TBL-W500|SV572|TBLT7I|TBA-AC7-8G|TBLT79|TBL-8W16|TBL-10W32|TBL-10WKB|TBL-W100',
		// http://www.datawind.com/ubislate/
		'UbislateTablet'    => 'UbiSlate[\s]?7C',
		// http://www.pocketbook-int.com/ru/support
		'PocketBookTablet'  => 'Pocketbook',
		// http://www.kocaso.com/product_tablet.html
		'KocasoTablet'      => '\b(TB-1207)\b',
		// http://global.hisense.com/product/asia/tablet/Sero7/201412/t20141215_91832.htm
		'HisenseTablet'     => '\b(F5281|E2371)\b',
		// http://www.tesco.com/direct/hudl/
		'Hudl'              => 'Hudl HT7S3|Hudl 2',
		// http://www.telstra.com.au/home-phone/thub-2/
		'TelstraTablet'     => 'T-Hub2',
		'GenericTablet'     => 'Android.*\b97D\b|Tablet(?!.*PC)|BNTV250A|MID-WCDMA|LogicPD Zoom2|\bA7EB\b|CatNova8|A1_07|CT704|CT1002|\bM721\b|rk30sdk|\bEVOTAB\b|M758A|ET904|ALUMIUM10|Smartfren Tab|Endeavour 1010|Tablet-PC-4|Tagi Tab|\bM6pro\b|CT1020W|arc 10HD|\bTP750\b|\bQTAQZ3\b|WVT101|TM1088|KT107',
	];

	/**
	 * List of mobile Operating Systems.
	 *
	 * @var array
	 */
	protected static $operatingSystems = [
		'AndroidOS'       => 'Android',
		'BlackBerryOS'    => 'blackberry|\bBB10\b|rim tablet os',
		'PalmOS'          => 'PalmOS|avantgo|blazer|elaine|hiptop|palm|plucker|xiino',
		'SymbianOS'       => 'Symbian|SymbOS|Series60|Series40|SYB-[0-9]+|\bS60\b',
		// @reference: http://en.wikipedia.org/wiki/Windows_Mobile
		'WindowsMobileOS' => 'Windows CE.*(PPC|Smartphone|Mobile|[0-9]{3}x[0-9]{3})|Window Mobile|Windows Phone [0-9.]+|WCE;',
		// @reference: http://en.wikipedia.org/wiki/Windows_Phone
		// http://wifeng.cn/?r=blog&a=view&id=106
		// http://nicksnettravels.builttoroam.com/post/2011/01/10/Bogus-Windows-Phone-7-User-Agent-String.aspx
		// http://msdn.microsoft.com/library/ms537503.aspx
		// https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
		'WindowsPhoneOS'  => 'Windows Phone 10.0|Windows Phone 8.1|Windows Phone 8.0|Windows Phone OS|XBLWP7|ZuneWP7|Windows NT 6.[23]; ARM;',
		'iOS'             => '\biPhone.*Mobile|\biPod|\biPad|AppleCoreMedia',
		// http://en.wikipedia.org/wiki/MeeGo
		// @todo: research MeeGo in UAs
		'MeeGoOS'         => 'MeeGo',
		// http://en.wikipedia.org/wiki/Maemo
		// @todo: research Maemo in UAs
		'MaemoOS'         => 'Maemo',
		'JavaOS'          => 'J2ME/|\bMIDP\b|\bCLDC\b', // '|Java/' produces bug #135
		'webOS'           => 'webOS|hpwOS',
		'badaOS'          => '\bBada\b',
		'BREWOS'          => 'BREW',
	];

	/**
	 * List of mobile User Agents.
	 *
	 * IMPORTANT: This is a list of only mobile browsers.
	 * Mobile Detect 2.x supports only mobile browsers,
	 * it was never designed to detect all browsers.
	 * The change will come in 2017 in the 3.x release for PHP7.
	 *
	 * @var array
	 */
	protected static $browsers = [
		//'Vivaldi'         => 'Vivaldi',
		// @reference: https://developers.google.com/chrome/mobile/docs/user-agent
		'Chrome'         => '\bCrMo\b|CriOS|Android.*Chrome/[.0-9]* (Mobile)?',
		'Dolfin'         => '\bDolfin\b',
		'Opera'          => 'Opera.*Mini|Opera.*Mobi|Android.*Opera|Mobile.*OPR/[0-9.]+$|Coast/[0-9.]+',
		'Skyfire'        => 'Skyfire',
		'Edge'           => 'Mobile Safari/[.0-9]* Edge',
		'IE'             => 'IEMobile|MSIEMobile', // |Trident/[.0-9]+
		'Firefox'        => 'fennec|firefox.*maemo|(Mobile|Tablet).*Firefox|Firefox.*Mobile|FxiOS',
		'Bolt'           => 'bolt',
		'TeaShark'       => 'teashark',
		'Blazer'         => 'Blazer',
		// @reference: http://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/OptimizingforSafarioniPhone/OptimizingforSafarioniPhone.html#//apple_ref/doc/uid/TP40006517-SW3
		'Safari'         => 'Version.*Mobile.*Safari|Safari.*Mobile|MobileSafari',
		// http://en.wikipedia.org/wiki/Midori_(web_browser)
		//'Midori'          => 'midori',
		//'Tizen'           => 'Tizen',
		'WeChat'         => '\bMicroMessenger\b',
		'UCBrowser'      => 'UC.*Browser|UCWEB',
		'baiduboxapp'    => 'baiduboxapp',
		'baidubrowser'   => 'baidubrowser',
		// https://github.com/serbanghita/Mobile-Detect/issues/7
		'DiigoBrowser'   => 'DiigoBrowser',
		// http://www.puffinbrowser.com/index.php
		'Puffin'         => 'Puffin',
		// http://mercury-browser.com/index.html
		'Mercury'        => '\bMercury\b',
		// http://en.wikipedia.org/wiki/Obigo_Browser
		'ObigoBrowser'   => 'Obigo',
		// http://en.wikipedia.org/wiki/NetFront
		'NetFront'       => 'NF-Browser',
		// @reference: http://en.wikipedia.org/wiki/Minimo
		// http://en.wikipedia.org/wiki/Vision_Mobile_Browser
		'GenericBrowser' => 'NokiaBrowser|OviBrowser|OneBrowser|TwonkyBeamBrowser|SEMC.*Browser|FlyFlow|Minimo|NetFront|Novarra-Vision|MQQBrowser|MicroMessenger',
		// @reference: https://en.wikipedia.org/wiki/Pale_Moon_(web_browser)
		'PaleMoon'       => 'Android.*PaleMoon|Mobile.*PaleMoon',
	];

	/**
	 * Utilities.
	 *
	 * @var array
	 */
	protected static $utilities = [
		// Experimental. When a mobile device wants to switch to 'Desktop Mode'.
		// http://scottcate.com/technology/windows-phone-8-ie10-desktop-or-mobile/
		// https://github.com/serbanghita/Mobile-Detect/issues/57#issuecomment-15024011
		// https://developers.facebook.com/docs/sharing/best-practices
		'Bot'         => 'Googlebot|facebookexternalhit|AdsBot-Google|Google Keyword Suggestion|Facebot|YandexBot|YandexMobileBot|bingbot|ia_archiver|AhrefsBot|Ezooms|GSLFbot|WBSearchBot|Twitterbot|TweetmemeBot|Twikle|PaperLiBot|Wotbox|UnwindFetchor|Exabot|MJ12bot|YandexImages|TurnitinBot|Pingdom',
		'MobileBot'   => 'Googlebot-Mobile|AdsBot-Google-Mobile|YahooSeeker/M1A1-R2D2',
		'DesktopMode' => 'WPDesktop',
		'TV'          => 'SonyDTV|HbbTV', // experimental
		'WebKit'      => '(webkit)[ /]([\w.]+)',
		// @todo: Include JXD consoles.
		'Console'     => '\b(Nintendo|Nintendo WiiU|Nintendo 3DS|Nintendo Switch|PLAYSTATION|Xbox)\b',
		'Watch'       => 'SM-V700',
	];

	/**
	 * All possible HTTP headers that represent the
	 * User-Agent string.
	 *
	 * @var array
	 */
	protected static $uaHttpHeaders = [
		// The default User-Agent string.
		'HTTP_USER_AGENT',
		// Header can occur on devices using Opera Mini.
		'HTTP_X_OPERAMINI_PHONE_UA',
		// Vodafone specific header: http://www.seoprinciple.com/mobile-web-community-still-angry-at-vodafone/24/
		'HTTP_X_DEVICE_USER_AGENT',
		'HTTP_X_ORIGINAL_USER_AGENT',
		'HTTP_X_SKYFIRE_PHONE',
		'HTTP_X_BOLT_PHONE_UA',
		'HTTP_DEVICE_STOCK_UA',
		'HTTP_X_UCBROWSER_DEVICE_UA',
	];

	/**
	 * The individual segments that could exist in a User-Agent string. VER refers to the regular
	 * expression defined in the constant self::VER.
	 *
	 * @var array
	 */
	protected static $properties = [

		// Build
		'Mobile'           => 'Mobile/[VER]',
		'Build'            => 'Build/[VER]',
		'Version'          => 'Version/[VER]',
		'VendorID'         => 'VendorID/[VER]',

		// Devices
		'iPad'             => 'iPad.*CPU[a-z ]+[VER]',
		'iPhone'           => 'iPhone.*CPU[a-z ]+[VER]',
		'iPod'             => 'iPod.*CPU[a-z ]+[VER]',
		//'BlackBerry'    => array('BlackBerry[VER]', 'BlackBerry [VER];'),
		'Kindle'           => 'Kindle/[VER]',

		// Browser
		'Chrome'           => ['Chrome/[VER]', 'CriOS/[VER]', 'CrMo/[VER]'],
		'Coast'            => ['Coast/[VER]'],
		'Dolfin'           => 'Dolfin/[VER]',
		// @reference: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
		'Firefox'          => ['Firefox/[VER]', 'FxiOS/[VER]'],
		'Fennec'           => 'Fennec/[VER]',
		// http://msdn.microsoft.com/en-us/library/ms537503(v=vs.85).aspx
		// https://msdn.microsoft.com/en-us/library/ie/hh869301(v=vs.85).aspx
		'Edge'             => 'Edge/[VER]',
		'IE'               => ['IEMobile/[VER];', 'IEMobile [VER]', 'MSIE [VER];', 'Trident/[0-9.]+;.*rv:[VER]'],
		// http://en.wikipedia.org/wiki/NetFront
		'NetFront'         => 'NetFront/[VER]',
		'NokiaBrowser'     => 'NokiaBrowser/[VER]',
		'Opera'            => [' OPR/[VER]', 'Opera Mini/[VER]', 'Version/[VER]'],
		'Opera Mini'       => 'Opera Mini/[VER]',
		'Opera Mobi'       => 'Version/[VER]',
		'UCBrowser'        => ['UCWEB[VER]', 'UC.*Browser/[VER]'],
		'MQQBrowser'       => 'MQQBrowser/[VER]',
		'MicroMessenger'   => 'MicroMessenger/[VER]',
		'baiduboxapp'      => 'baiduboxapp/[VER]',
		'baidubrowser'     => 'baidubrowser/[VER]',
		'SamsungBrowser'   => 'SamsungBrowser/[VER]',
		'Iron'             => 'Iron/[VER]',
		// @note: Safari 7534.48.3 is actually Version 5.1.
		// @note: On BlackBerry the Version is overwriten by the OS.
		'Safari'           => ['Version/[VER]', 'Safari/[VER]'],
		'Skyfire'          => 'Skyfire/[VER]',
		'Tizen'            => 'Tizen/[VER]',
		'Webkit'           => 'webkit[ /][VER]',
		'PaleMoon'         => 'PaleMoon/[VER]',

		// Engine
		'Gecko'            => 'Gecko/[VER]',
		'Trident'          => 'Trident/[VER]',
		'Presto'           => 'Presto/[VER]',
		'Goanna'           => 'Goanna/[VER]',

		// OS
		'iOS'              => ' \bi?OS\b [VER][ ;]{1}',
		'Android'          => 'Android [VER]',
		'BlackBerry'       => ['BlackBerry[\w]+/[VER]', 'BlackBerry.*Version/[VER]', 'Version/[VER]'],
		'BREW'             => 'BREW [VER]',
		'Java'             => 'Java/[VER]',
		// @reference: http://windowsteamblog.com/windows_phone/b/wpdev/archive/2011/08/29/introducing-the-ie9-on-windows-phone-mango-user-agent-string.aspx
		// @reference: http://en.wikipedia.org/wiki/Windows_NT#Releases
		'Windows Phone OS' => ['Windows Phone OS [VER]', 'Windows Phone [VER]'],
		'Windows Phone'    => 'Windows Phone [VER]',
		'Windows CE'       => 'Windows CE/[VER]',
		// http://social.msdn.microsoft.com/Forums/en-US/windowsdeveloperpreviewgeneral/thread/6be392da-4d2f-41b4-8354-8dcee20c85cd
		'Windows NT'       => 'Windows NT [VER]',
		'Symbian'          => ['SymbianOS/[VER]', 'Symbian/[VER]'],
		'webOS'            => ['webOS/[VER]', 'hpwOS/[VER];'],
	];

	/**
	 * Construct an instance of this class.
	 *
	 * @param array  $headers   Specify the headers as injection. Should be PHP _SERVER flavored.
	 *                          If left empty, will use the global _SERVER['HTTP_*'] vars instead.
	 * @param string $userAgent Inject the User-Agent header. If null, will use HTTP_USER_AGENT
	 *                          from the $headers array instead.
	 */
	public function __construct(
		array $headers = null,
		$userAgent = null
	)
	{
		$this->setHttpHeaders($headers);
		$this->setUserAgent($userAgent);
	}

	/**
	 * Get the current script version.
	 * This is useful for the demo.php file,
	 * so people can check on what version they are testing
	 * for mobile devices.
	 *
	 * @return string The version number in semantic version format.
	 */
	public static function getScriptVersion()
	{
		return self::VERSION;
	}

	/**
	 * Set the HTTP Headers. Must be PHP-flavored. This method will reset existing headers.
	 *
	 * @param array $httpHeaders The headers to set. If null, then using PHP's _SERVER to extract
	 *                           the headers. The default null is left for backwards compatibility.
	 */
	public function setHttpHeaders($httpHeaders = null)
	{
		// use global _SERVER if $httpHeaders aren't defined
		if ( ! is_array($httpHeaders) || ! count($httpHeaders))
		{
			$httpHeaders = $_SERVER;
		}

		// clear existing headers
		$this->httpHeaders = [];

		// Only save HTTP headers. In PHP land, that means only _SERVER vars that
		// start with HTTP_.
		foreach ($httpHeaders as $key => $value)
		{
			if (substr($key, 0, 5) === 'HTTP_')
			{
				$this->httpHeaders[$key] = $value;
			}
		}

		// In case we're dealing with CloudFront, we need to know.
		$this->setCfHeaders($httpHeaders);
	}

	/**
	 * Retrieves the HTTP headers.
	 *
	 * @return array
	 */
	public function getHttpHeaders()
	{
		return $this->httpHeaders;
	}

	/**
	 * Retrieves a particular header. If it doesn't exist, no exception/error is caused.
	 * Simply null is returned.
	 *
	 * @param string $header The name of the header to retrieve. Can be HTTP compliant such as
	 *                       "User-Agent" or "X-Device-User-Agent" or can be php-esque with the
	 *                       all-caps, HTTP_ prefixed, underscore seperated awesomeness.
	 *
	 * @return string|null The value of the header.
	 */
	public function getHttpHeader($header)
	{
		// are we using PHP-flavored headers?
		if (strpos($header, '_') === false)
		{
			$header = str_replace('-', '_', $header);
			$header = strtoupper($header);
		}

		// test the alternate, too
		$altHeader = 'HTTP_' . $header;

		//Test both the regular and the HTTP_ prefix
		if (isset($this->httpHeaders[$header]))
		{
			return $this->httpHeaders[$header];
		}
		elseif (isset($this->httpHeaders[$altHeader]))
		{
			return $this->httpHeaders[$altHeader];
		}

		return null;
	}

	public function getMobileHeaders()
	{
		return self::$mobileHeaders;
	}

	/**
	 * Get all possible HTTP headers that
	 * can contain the User-Agent string.
	 *
	 * @return array List of HTTP headers.
	 */
	public function getUaHttpHeaders()
	{
		return self::$uaHttpHeaders;
	}

	/**
	 * Set CloudFront headers
	 * http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/header-caching.html#header-caching-web-device
	 *
	 * @param array $cfHeaders List of HTTP headers
	 *
	 * @return  boolean If there were CloudFront headers to be set
	 */
	public function setCfHeaders($cfHeaders = null)
	{
		// use global _SERVER if $cfHeaders aren't defined
		if ( ! is_array($cfHeaders) || ! count($cfHeaders))
		{
			$cfHeaders = $_SERVER;
		}

		// clear existing headers
		$this->cloudfrontHeaders = [];

		// Only save CLOUDFRONT headers. In PHP land, that means only _SERVER vars that
		// start with cloudfront-.
		$response = false;
		foreach ($cfHeaders as $key => $value)
		{
			if (substr(strtolower($key), 0, 16) === 'http_cloudfront_')
			{
				$this->cloudfrontHeaders[strtoupper($key)] = $value;
				$response                                  = true;
			}
		}

		return $response;
	}

	/**
	 * Retrieves the cloudfront headers.
	 *
	 * @return array
	 */
	public function getCfHeaders()
	{
		return $this->cloudfrontHeaders;
	}

	/**
	 * @param string $userAgent
	 *
	 * @return string
	 */
	private function prepareUserAgent($userAgent)
	{
		$userAgent = trim($userAgent);
		$userAgent = substr($userAgent, 0, 500);

		return $userAgent;
	}

	/**
	 * Set the User-Agent to be used.
	 *
	 * @param string $userAgent The user agent string to set.
	 *
	 * @return string|null
	 */
	public function setUserAgent($userAgent = null)
	{
		// Invalidate cache due to #375
		$this->cache = [];

		if (false === empty($userAgent))
		{
			return $this->userAgent = $this->prepareUserAgent($userAgent);
		}
		else
		{
			$this->userAgent = null;
			foreach ($this->getUaHttpHeaders() as $altHeader)
			{
				if (false === empty($this->httpHeaders[$altHeader]))
				{ // @todo: should use getHttpHeader(), but it would be slow. (Serban)
					$this->userAgent .= $this->httpHeaders[$altHeader] . " ";
				}
			}

			if ( ! empty($this->userAgent))
			{
				return $this->userAgent = $this->prepareUserAgent($this->userAgent);
			}
		}

		if (count($this->getCfHeaders()) > 0)
		{
			return $this->userAgent = 'Amazon CloudFront';
		}

		return $this->userAgent = null;
	}

	/**
	 * Retrieve the User-Agent.
	 *
	 * @return string|null The user agent if it's set.
	 */
	public function getUserAgent()
	{
		return $this->userAgent;
	}

	/**
	 * Set the detection type. Must be one of self::DETECTION_TYPE_MOBILE or
	 * self::DETECTION_TYPE_EXTENDED. Otherwise, nothing is set.
	 *
	 * @deprecated since version 2.6.9
	 *
	 * @param string $type The type. Must be a self::DETECTION_TYPE_* constant. The default
	 *                     parameter is null which will default to self::DETECTION_TYPE_MOBILE.
	 */
	public function setDetectionType($type = null)
	{
		if ($type === null)
		{
			$type = self::DETECTION_TYPE_MOBILE;
		}

		if ($type !== self::DETECTION_TYPE_MOBILE && $type !== self::DETECTION_TYPE_EXTENDED)
		{
			return;
		}

		$this->detectionType = $type;
	}

	public function getMatchingRegex()
	{
		return $this->matchingRegex;
	}

	public function getMatchesArray()
	{
		return $this->matchesArray;
	}

	/**
	 * Retrieve the list of known phone devices.
	 *
	 * @return array List of phone devices.
	 */
	public static function getPhoneDevices()
	{
		return self::$phoneDevices;
	}

	/**
	 * Retrieve the list of known tablet devices.
	 *
	 * @return array List of tablet devices.
	 */
	public static function getTabletDevices()
	{
		return self::$tabletDevices;
	}

	/**
	 * Alias for getBrowsers() method.
	 *
	 * @return array List of user agents.
	 */
	public static function getUserAgents()
	{
		return self::getBrowsers();
	}

	/**
	 * Retrieve the list of known browsers. Specifically, the user agents.
	 *
	 * @return array List of browsers / user agents.
	 */
	public static function getBrowsers()
	{
		return self::$browsers;
	}

	/**
	 * Retrieve the list of known utilities.
	 *
	 * @return array List of utilities.
	 */
	public static function getUtilities()
	{
		return self::$utilities;
	}

	/**
	 * Method gets the mobile detection rules. This method is used for the magic methods $detect->is*().
	 *
	 * @deprecated since version 2.6.9
	 *
	 * @return array All the rules (but not extended).
	 */
	public static function getMobileDetectionRules()
	{
		static $rules;

		if ( ! $rules)
		{
			$rules = array_merge(
				self::$phoneDevices,
				self::$tabletDevices,
				self::$operatingSystems,
				self::$browsers
			);
		}

		return $rules;
	}

	/**
	 * Method gets the mobile detection rules + utilities.
	 * The reason this is separate is because utilities rules
	 * don't necessary imply mobile. This method is used inside
	 * the new $detect->is('stuff') method.
	 *
	 * @deprecated since version 2.6.9
	 *
	 * @return array All the rules + extended.
	 */
	public function getMobileDetectionRulesExtended()
	{
		static $rules;

		if ( ! $rules)
		{
			// Merge all rules together.
			$rules = array_merge(
				self::$phoneDevices,
				self::$tabletDevices,
				self::$operatingSystems,
				self::$browsers,
				self::$utilities
			);
		}

		return $rules;
	}

	/**
	 * Retrieve the current set of rules.
	 *
	 * @deprecated since version 2.6.9
	 *
	 * @return array
	 */
	public function getRules()
	{
		if ($this->detectionType == self::DETECTION_TYPE_EXTENDED)
		{
			return self::getMobileDetectionRulesExtended();
		}
		else
		{
			return self::getMobileDetectionRules();
		}
	}

	/**
	 * Retrieve the list of mobile operating systems.
	 *
	 * @return array The list of mobile operating systems.
	 */
	public static function getOperatingSystems()
	{
		return self::$operatingSystems;
	}

	/**
	 * Check the HTTP headers for signs of mobile.
	 * This is the fastest mobile check possible; it's used
	 * inside isMobile() method.
	 *
	 * @return bool
	 */
	public function checkHttpHeadersForMobile()
	{

		foreach ($this->getMobileHeaders() as $mobileHeader => $matchType)
		{
			if (isset($this->httpHeaders[$mobileHeader]))
			{
				if (is_array($matchType['matches']))
				{
					foreach ($matchType['matches'] as $_match)
					{
						if (strpos($this->httpHeaders[$mobileHeader], $_match) !== false)
						{
							return true;
						}
					}

					return false;
				}
				else
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Magic overloading method.
	 *
	 * @method boolean is[...]()
	 * @param  string $name
	 * @param  array  $arguments
	 *
	 * @return mixed
	 * @throws BadMethodCallException when the method doesn't exist and doesn't start with 'is'
	 */
	public function __call($name, $arguments)
	{
		// make sure the name starts with 'is', otherwise
		if (substr($name, 0, 2) !== 'is')
		{
			throw new BadMethodCallException("No such method exists: $name");
		}

		$this->setDetectionType(self::DETECTION_TYPE_MOBILE);

		$key = substr($name, 2);

		return $this->matchUAAgainstKey($key);
	}

	/**
	 * Find a detection rule that matches the current User-agent.
	 *
	 * @param  null $userAgent deprecated
	 *
	 * @return boolean
	 */
	protected function matchDetectionRulesAgainstUA($userAgent = null)
	{
		// Begin general search.
		foreach ($this->getRules() as $_regex)
		{
			if (empty($_regex))
			{
				continue;
			}

			if ($this->match($_regex, $userAgent))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Search for a certain key in the rules array.
	 * If the key is found then try to match the corresponding
	 * regex against the User-Agent.
	 *
	 * @param string $key
	 *
	 * @return boolean
	 */
	protected function matchUAAgainstKey($key)
	{
		// Make the keys lowercase so we can match: isIphone(), isiPhone(), isiphone(), etc.
		$key = strtolower($key);
		if (false === isset($this->cache[$key]))
		{

			// change the keys to lower case
			$_rules = array_change_key_case($this->getRules());

			if (false === empty($_rules[$key]))
			{
				$this->cache[$key] = $this->match($_rules[$key]);
			}

			if (false === isset($this->cache[$key]))
			{
				$this->cache[$key] = false;
			}
		}

		return $this->cache[$key];
	}

	/**
	 * Check if the device is mobile.
	 * Returns true if any type of mobile device detected, including special ones
	 *
	 * @param  null $userAgent   deprecated
	 * @param  null $httpHeaders deprecated
	 *
	 * @return bool
	 */
	public function isMobile($userAgent = null, $httpHeaders = null)
	{

		if ($httpHeaders)
		{
			$this->setHttpHeaders($httpHeaders);
		}

		if ($userAgent)
		{
			$this->setUserAgent($userAgent);
		}

		// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
		if ($this->getUserAgent() === 'Amazon CloudFront')
		{
			$cfHeaders = $this->getCfHeaders();
			if (array_key_exists('HTTP_CLOUDFRONT_IS_MOBILE_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_MOBILE_VIEWER'] === 'true')
			{
				return true;
			}
		}

		$this->setDetectionType(self::DETECTION_TYPE_MOBILE);

		if ($this->checkHttpHeadersForMobile())
		{
			return true;
		}
		else
		{
			return $this->matchDetectionRulesAgainstUA();
		}
	}

	/**
	 * Check if the device is a tablet.
	 * Return true if any type of tablet device is detected.
	 *
	 * @param  string $userAgent   deprecated
	 * @param  array  $httpHeaders deprecated
	 *
	 * @return bool
	 */
	public function isTablet($userAgent = null, $httpHeaders = null)
	{
		// Check specifically for cloudfront headers if the useragent === 'Amazon CloudFront'
		if ($this->getUserAgent() === 'Amazon CloudFront')
		{
			$cfHeaders = $this->getCfHeaders();
			if (array_key_exists('HTTP_CLOUDFRONT_IS_TABLET_VIEWER', $cfHeaders) && $cfHeaders['HTTP_CLOUDFRONT_IS_TABLET_VIEWER'] === 'true')
			{
				return true;
			}
		}

		$this->setDetectionType(self::DETECTION_TYPE_MOBILE);

		foreach (self::$tabletDevices as $_regex)
		{
			if ($this->match($_regex, $userAgent))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * This method checks for a certain property in the
	 * userAgent.
	 * @todo: The httpHeaders part is not yet used.
	 *
	 * @param  string $key
	 * @param  string $userAgent   deprecated
	 * @param  string $httpHeaders deprecated
	 *
	 * @return bool|int|null
	 */
	public function is($key, $userAgent = null, $httpHeaders = null)
	{
		// Set the UA and HTTP headers only if needed (eg. batch mode).
		if ($httpHeaders)
		{
			$this->setHttpHeaders($httpHeaders);
		}

		if ($userAgent)
		{
			$this->setUserAgent($userAgent);
		}

		$this->setDetectionType(self::DETECTION_TYPE_EXTENDED);

		return $this->matchUAAgainstKey($key);
	}

	/**
	 * Some detection rules are relative (not standard),
	 * because of the diversity of devices, vendors and
	 * their conventions in representing the User-Agent or
	 * the HTTP headers.
	 *
	 * This method will be used to check custom regexes against
	 * the User-Agent string.
	 *
	 * @param         $regex
	 * @param  string $userAgent
	 *
	 * @return bool
	 *
	 * @todo: search in the HTTP headers too.
	 */
	public function match($regex, $userAgent = null)
	{
		$match = (bool) preg_match(sprintf('#%s#is', $regex), (false === empty($userAgent) ? $userAgent : $this->userAgent), $matches);
		// If positive match is found, store the results for debug.
		if ($match)
		{
			$this->matchingRegex = $regex;
			$this->matchesArray  = $matches;
		}

		return $match;
	}

	/**
	 * Get the properties array.
	 *
	 * @return array
	 */
	public static function getProperties()
	{
		return self::$properties;
	}

	/**
	 * Prepare the version number.
	 *
	 * @todo Remove the error supression from str_replace() call.
	 *
	 * @param string $ver The string version, like "2.6.21.2152";
	 *
	 * @return float
	 */
	public function prepareVersionNo($ver)
	{
		$ver    = str_replace(['_', ' ', '/'], '.', $ver);
		$arrVer = explode('.', $ver, 2);

		if (isset($arrVer[1]))
		{
			$arrVer[1] = @str_replace('.', '', $arrVer[1]); // @todo: treat strings versions.
		}

		return (float) implode('.', $arrVer);
	}

	/**
	 * Check the version of the given property in the User-Agent.
	 * Will return a float number. (eg. 2_0 will return 2.0, 4.3.1 will return 4.31)
	 *
	 * @param string $propertyName The name of the property. See self::getProperties() array
	 *                             keys for all possible properties.
	 * @param string $type         Either self::VERSION_TYPE_STRING to get a string value or
	 *                             self::VERSION_TYPE_FLOAT indicating a float value. This parameter
	 *                             is optional and defaults to self::VERSION_TYPE_STRING. Passing an
	 *                             invalid parameter will default to the this type as well.
	 *
	 * @return string|float The version of the property we are trying to extract.
	 */
	public function version($propertyName, $type = self::VERSION_TYPE_STRING)
	{
		if (empty($propertyName))
		{
			return false;
		}

		// set the $type to the default if we don't recognize the type
		if ($type !== self::VERSION_TYPE_STRING && $type !== self::VERSION_TYPE_FLOAT)
		{
			$type = self::VERSION_TYPE_STRING;
		}

		$properties = self::getProperties();

		// Check if the property exists in the properties array.
		if (true === isset($properties[$propertyName]))
		{

			// Prepare the pattern to be matched.
			// Make sure we always deal with an array (string is converted).
			$properties[$propertyName] = (array) $properties[$propertyName];

			foreach ($properties[$propertyName] as $propertyMatchString)
			{

				$propertyPattern = str_replace('[VER]', self::VER, $propertyMatchString);

				// Identify and extract the version.
				preg_match(sprintf('#%s#is', $propertyPattern), $this->userAgent, $match);

				if (false === empty($match[1]))
				{
					$version = ($type == self::VERSION_TYPE_FLOAT ? $this->prepareVersionNo($match[1]) : $match[1]);

					return $version;
				}
			}
		}

		return false;
	}

	/**
	 * Retrieve the mobile grading, using self::MOBILE_GRADE_* constants.
	 *
	 * @return string One of the self::MOBILE_GRADE_* constants.
	 */
	public function mobileGrade()
	{
		$isMobile = $this->isMobile();

		if (
			// Apple iOS 4-7.0 – Tested on the original iPad (4.3 / 5.0), iPad 2 (4.3 / 5.1 / 6.1), iPad 3 (5.1 / 6.0), iPad Mini (6.1), iPad Retina (7.0), iPhone 3GS (4.3), iPhone 4 (4.3 / 5.1), iPhone 4S (5.1 / 6.0), iPhone 5 (6.0), and iPhone 5S (7.0)
			$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) >= 4.3 ||
			$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) >= 4.3 ||
			$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) >= 4.3 ||

			// Android 2.1-2.3 - Tested on the HTC Incredible (2.2), original Droid (2.2), HTC Aria (2.1), Google Nexus S (2.3). Functional on 1.5 & 1.6 but performance may be sluggish, tested on Google G1 (1.5)
			// Android 3.1 (Honeycomb)  - Tested on the Samsung Galaxy Tab 10.1 and Motorola XOOM
			// Android 4.0 (ICS)  - Tested on a Galaxy Nexus. Note: transition performance can be poor on upgraded devices
			// Android 4.1 (Jelly Bean)  - Tested on a Galaxy Nexus and Galaxy 7
			($this->version('Android', self::VERSION_TYPE_FLOAT) > 2.1 && $this->is('Webkit')) ||

			// Windows Phone 7.5-8 - Tested on the HTC Surround (7.5), HTC Trophy (7.5), LG-E900 (7.5), Nokia 800 (7.8), HTC Mazaa (7.8), Nokia Lumia 520 (8), Nokia Lumia 920 (8), HTC 8x (8)
			$this->version('Windows Phone OS', self::VERSION_TYPE_FLOAT) >= 7.5 ||

			// Tested on the Torch 9800 (6) and Style 9670 (6), BlackBerry® Torch 9810 (7), BlackBerry Z10 (10)
			$this->is('BlackBerry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 6.0 ||
			// Blackberry Playbook (1.0-2.0) - Tested on PlayBook
			$this->match('Playbook.*Tablet') ||

			// Palm WebOS (1.4-3.0) - Tested on the Palm Pixi (1.4), Pre (1.4), Pre 2 (2.0), HP TouchPad (3.0)
			($this->version('webOS', self::VERSION_TYPE_FLOAT) >= 1.4 && $this->match('Palm|Pre|Pixi')) ||
			// Palm WebOS 3.0  - Tested on HP TouchPad
			$this->match('hp.*TouchPad') ||

			// Firefox Mobile 18 - Tested on Android 2.3 and 4.1 devices
			($this->is('Firefox') && $this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 18) ||

			// Chrome for Android - Tested on Android 4.0, 4.1 device
			($this->is('Chrome') && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 4.0) ||

			// Skyfire 4.1 - Tested on Android 2.3 device
			($this->is('Skyfire') && $this->version('Skyfire', self::VERSION_TYPE_FLOAT) >= 4.1 && $this->is('AndroidOS') && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3) ||

			// Opera Mobile 11.5-12: Tested on Android 2.3
			($this->is('Opera') && $this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11.5 && $this->is('AndroidOS')) ||

			// Meego 1.2 - Tested on Nokia 950 and N9
			$this->is('MeeGoOS') ||

			// Tizen (pre-release) - Tested on early hardware
			$this->is('Tizen') ||

			// Samsung Bada 2.0 - Tested on a Samsung Wave 3, Dolphin browser
			// @todo: more tests here!
			$this->is('Dolfin') && $this->version('Bada', self::VERSION_TYPE_FLOAT) >= 2.0 ||

			// UC Browser - Tested on Android 2.3 device
			(($this->is('UC Browser') || $this->is('Dolfin')) && $this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3) ||

			// Kindle 3 and Fire  - Tested on the built-in WebKit browser for each
			($this->match('Kindle Fire') ||
				$this->is('Kindle') && $this->version('Kindle', self::VERSION_TYPE_FLOAT) >= 3.0) ||

			// Nook Color 1.4.1 - Tested on original Nook Color, not Nook Tablet
			$this->is('AndroidOS') && $this->is('NookTablet') ||

			// Chrome Desktop 16-24 - Tested on OS X 10.7 and Windows 7
			$this->version('Chrome', self::VERSION_TYPE_FLOAT) >= 16 && ! $isMobile ||

			// Safari Desktop 5-6 - Tested on OS X 10.7 and Windows 7
			$this->version('Safari', self::VERSION_TYPE_FLOAT) >= 5.0 && ! $isMobile ||

			// Firefox Desktop 10-18 - Tested on OS X 10.7 and Windows 7
			$this->version('Firefox', self::VERSION_TYPE_FLOAT) >= 10.0 && ! $isMobile ||

			// Internet Explorer 7-9 - Tested on Windows XP, Vista and 7
			$this->version('IE', self::VERSION_TYPE_FLOAT) >= 7.0 && ! $isMobile ||

			// Opera Desktop 10-12 - Tested on OS X 10.7 and Windows 7
			$this->version('Opera', self::VERSION_TYPE_FLOAT) >= 10 && ! $isMobile
		)
		{
			return self::MOBILE_GRADE_A;
		}

		if (
			$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) < 4.3 ||
			$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) < 4.3 ||
			$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) < 4.3 ||

			// Blackberry 5.0: Tested on the Storm 2 9550, Bold 9770
			$this->is('Blackberry') && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) >= 5 && $this->version('BlackBerry', self::VERSION_TYPE_FLOAT) < 6 ||

			//Opera Mini (5.0-6.5) - Tested on iOS 3.2/4.3 and Android 2.3
			($this->version('Opera Mini', self::VERSION_TYPE_FLOAT) >= 5.0 && $this->version('Opera Mini', self::VERSION_TYPE_FLOAT) <= 7.0 &&
				($this->version('Android', self::VERSION_TYPE_FLOAT) >= 2.3 || $this->is('iOS'))) ||

			// Nokia Symbian^3 - Tested on Nokia N8 (Symbian^3), C7 (Symbian^3), also works on N97 (Symbian^1)
			$this->match('NokiaN8|NokiaC7|N97.*Series60|Symbian/3') ||

			// @todo: report this (tested on Nokia N71)
			$this->version('Opera Mobi', self::VERSION_TYPE_FLOAT) >= 11 && $this->is('SymbianOS')
		)
		{
			return self::MOBILE_GRADE_B;
		}

		if (
			// Blackberry 4.x - Tested on the Curve 8330
			$this->version('BlackBerry', self::VERSION_TYPE_FLOAT) <= 5.0 ||
			// Windows Mobile - Tested on the HTC Leo (WinMo 5.2)
			$this->match('MSIEMobile|Windows CE.*Mobile') || $this->version('Windows Mobile', self::VERSION_TYPE_FLOAT) <= 5.2 ||

			// Tested on original iPhone (3.1), iPhone 3 (3.2)
			$this->is('iOS') && $this->version('iPad', self::VERSION_TYPE_FLOAT) <= 3.2 ||
			$this->is('iOS') && $this->version('iPhone', self::VERSION_TYPE_FLOAT) <= 3.2 ||
			$this->is('iOS') && $this->version('iPod', self::VERSION_TYPE_FLOAT) <= 3.2 ||

			// Internet Explorer 7 and older - Tested on Windows XP
			$this->version('IE', self::VERSION_TYPE_FLOAT) <= 7.0 && ! $isMobile
		)
		{
			return self::MOBILE_GRADE_C;
		}

		// All older smartphone platforms and featurephones - Any device that doesn't support media queries
		// will receive the basic, C grade experience.
		return self::MOBILE_GRADE_C;
	}
}
regularlabs/src/License.php000064400000003252152177723700011746 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Language\Text as JText;

/**
 * Class Language
 * @package RegularLabs\Library
 */
class License
{
	/**
	 * Render the license message for Free versions
	 *
	 * @param string $name
	 * @param bool   $check_pro
	 *
	 * @return string
	 */
	public static function getMessage($name, $check_pro = false)
	{
		if ( ! $name)
		{
			return '';
		}

		$alias = Extension::getAliasByName($name);
		$name  = Extension::getNameByAlias($name);

		if ($check_pro && self::isPro($alias))
		{
			return '';
		}

		Document::loadMainDependencies();

		return
			'<div class="alert alert-default rl_licence">'
			. JText::sprintf('RL_IS_FREE_VERSION', $name)
			. '<br>'
			. JText::_('RL_FOR_MORE_GO_PRO')
			. '<br>'
			. '<a href="https://www.regularlabs.com/purchase?ext=' . $alias . '" target="_blank" class="btn btn-small btn-primary">'
			. ' <span class="icon-basket"></span>'
			. StringHelper::html_entity_decoder(JText::_('RL_GO_PRO'))
			. '</a>'
			. '</div>';
	}

	/**
	 * Check if the installed version of the extension is a Pro version
	 *
	 * @param string $element_name
	 *
	 * @return bool
	 */
	private static function isPro($element_name)
	{
		if ( ! $version = Extension::getXMLValue('version', $element_name))
		{
			return false;
		}

		return (stripos($version, 'PRO') !== false);
	}
}
regularlabs/src/DB.php000064400000010222152177723700010644 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class DB
 * @package RegularLabs\Library
 */
class DB
{
	static $tables = [];

	/**
	 * Check if a table exists in the database
	 *
	 * @param string $table
	 *
	 * @return bool
	 */
	public static function tableExists($table)
	{
		if (isset(self::$tables[$table]))
		{
			return self::$tables[$table];
		}

		$db = JFactory::getDbo();

		if (strpos($table, '#__') === 0)
		{
			$table = $db->getPrefix() . substr($table, 3);
		}

		if (strpos($table, $db->getPrefix()) !== 0)
		{
			$table = $db->getPrefix() . $table;
		}

		$query = 'SHOW TABLES LIKE ' . $db->quote($table);
		$db->setQuery($query);
		$result = $db->loadResult();

		self::$tables[$table] = ! empty($result);

		return self::$tables[$table];
	}

	/**
	 * Concatenate conditions using AND or OR
	 *
	 * @param string $glue
	 * @param array  $conditions
	 *
	 * @return string
	 */
	public static function combine($conditions = [], $glue = 'OR')
	{
		if (empty($conditions))
		{
			return '';
		}

		if ( ! is_array($conditions))
		{
			return (string) $conditions;
		}

		if (count($conditions) < 2)
		{
			return $conditions[0];
		}

		$glue = strtoupper($glue) == 'AND' ? 'AND' : 'OR';

		return '(' . implode(' ' . $glue . ' ', $conditions) . ')';
	}

	/**
	 * Create an IN statement
	 * Reverts to a simple equals statement if array just has 1 value
	 *
	 * @param string|array $value
	 *
	 * @return string
	 */
	public static function in($value, $handle_now = false)
	{
		if (empty($value) && ! is_array($value))
		{
			return ' = 0';
		}

		$operator = self::getOperator($value);
		$value    = self::prepareValue($value, $handle_now);

		if ( ! is_array($value))
		{
			return ' ' . $operator . ' ' . $value;
		}

		if (count($value) == 1)
		{
			return ' ' . $operator . ' ' . reset($value);
		}

		$operator = $operator == '!=' ? 'NOT IN' : 'IN';

		$values = empty($value) ? "''" : implode(',', $value);

		return ' ' . $operator . ' (' . $values . ')';
	}

	public static function prepareValue($value, $handle_now = false)
	{
		$dates = ['now', 'now()', 'date()', 'jfactory::getdate()'];

		if ($handle_now && ! is_array($value) && in_array(strtolower($value), $dates))
		{
			return 'NOW()';
		}

		if (is_numeric($value))
		{
			return $value;
		}

		return JFactory::getDbo()->quote($value);
	}

	public static function getOperator(&$value, $default = '=')
	{
		if (empty($value))
		{
			return $default;
		}

		if (is_array($value))
		{
			$operator = self::getOperatorFromValue($value[0], $default);

			// remove operators from other array values
			foreach ($value as &$val)
			{
				$val = self::removeOperator($val);
			}

			return $operator;
		}

		$operator = self::getOperatorFromValue($value, $default);

		$value = self::removeOperator($value);

		return $operator;
	}

	public static function removeOperator($string)
	{
		$regex = '^' . RegEx::quote(self::getOperators(), 'operator');

		return RegEx::replace($regex, '', $string);
	}

	public static function getOperators()
	{
		return ['!NOT!', '!=', '!', '<>', '<=', '<', '>=', '>', '=', '=='];
	}

	public static function getOperatorFromValue($value, $default = '=')
	{
		$regex = '^' . RegEx::quote(self::getOperators(), 'operator');

		if ( ! RegEx::match($regex, $value, $parts))
		{
			return $default;
		}

		$operator = $parts['operator'];

		switch ($operator)
		{
			case '!':
			case '!NOT!':
				$operator = '!=';
				break;

			case '==':
				$operator = '=';
				break;
		}

		return $operator;
	}

	/**
	 * Create an LIKE statement
	 *
	 * @param string $value
	 *
	 * @return string
	 */
	public static function like($value)
	{
		$operator = self::getOperator($value);
		$value    = str_replace('*', '%', self::prepareValue($value));

		$operator = $operator == '!=' ? 'NOT LIKE' : 'LIKE';

		return ' ' . $operator . ' ' . $value;
	}
}
regularlabs/src/Article.php000064400000016624152177723700011756 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\Registry\Registry;

jimport('joomla.filesystem.file');

/**
 * Class Article
 * @package RegularLabs\Library
 */
class Article
{
	static $articles = [];

	/**
	 * Method to get article data.
	 *
	 * @param   integer|string $id The id, alias or title of the article.
	 *
	 * @return  object|boolean Menu item data object on success, boolean false
	 */
	public static function get($id = null, $get_unpublished = false)
	{
		$id = ! empty($id) ? $id : (int) self::getId();

		if (isset(self::$articles[$id]))
		{
			return self::$articles[$id];
		}

		$db   = JFactory::getDbo();
		$user = JFactory::getUser();

		$query = $db->getQuery(true)
			->select(
				[
					'a.id', 'a.asset_id', 'a.title', 'a.alias', 'a.introtext', 'a.fulltext',
					'a.state', 'a.catid', 'a.created', 'a.created_by', 'a.created_by_alias',
					// Use created if modified is 0
					'CASE WHEN a.modified = ' . $db->quote($db->getNullDate()) . ' THEN a.created ELSE a.modified END as modified',
					'a.modified_by', 'a.checked_out', 'a.checked_out_time', 'a.publish_up', 'a.publish_down',
					'a.images', 'a.urls', 'a.attribs', 'a.version', 'a.ordering',
					'a.metakey', 'a.metadesc', 'a.access', 'a.hits', 'a.metadata', 'a.featured', 'a.language', 'a.xreference',
				]
			)
			->from($db->quoteName('#__content', 'a'));

		if ( ! is_numeric($id))
		{
			$query->where('(' .
				$db->quoteName('a.title') . ' = ' . $db->quote($id)
				. ' OR ' .
				$db->quoteName('a.alias') . ' = ' . $db->quote($id)
				. ')');
		}
		else
		{
			$query->where($db->quoteName('a.id') . ' = ' . (int) $id);
		}

		// Join on category table.
		$query->select([
			$db->quoteName('c.title', 'category_title'),
			$db->quoteName('c.alias', 'category_alias'),
			$db->quoteName('c.access', 'category_access'),
		])
			->innerJoin($db->quoteName('#__categories', 'c') . ' ON ' . $db->quoteName('c.id') . ' = ' . $db->quoteName('a.catid'))
			->where($db->quoteName('c.published') . ' > 0');

		// Join on user table.
		$query->select($db->quoteName('u.name', 'author'))
			->join('LEFT', $db->quoteName('#__users', 'u') . ' ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('a.created_by'));

		// Join over the categories to get parent category titles
		$query->select([
			$db->quoteName('parent.title', 'parent_title'),
			$db->quoteName('parent.id', 'parent_id'),
			$db->quoteName('parent.path', 'parent_route'),
			$db->quoteName('parent.alias', 'parent_alias'),
		])
			->join('LEFT', $db->quoteName('#__categories', 'parent') . ' ON ' . $db->quoteName('parent.id') . ' = ' . $db->quoteName('c.parent_id'));

		// Join on voting table
		$query->select([
			'ROUND(v.rating_sum / v.rating_count, 0) AS rating',
			$db->quoteName('v.rating_count', 'rating_count'),
		])
			->join('LEFT', $db->quoteName('#__content_rating', 'v') . ' ON ' . $db->quoteName('v.content_id') . ' = ' . $db->quoteName('a.id'));

		if ( ! $get_unpublished
			&& ( ! $user->authorise('core.edit.state', 'com_content'))
			&& ( ! $user->authorise('core.edit', 'com_content'))
		)
		{
			// Filter by start and end dates.
			$nullDate = $db->quote($db->getNullDate());
			$date     = JFactory::getDate();

			$nowDate = $db->quote($date->toSql());

			$query->where($db->quoteName('a.state') . ' = 1')
				->where('(' . $db->quoteName('a.publish_up') . ' = ' . $nullDate . ' OR ' . $db->quoteName('a.publish_up') . ' <= ' . $nowDate . ')')
				->where('(' . $db->quoteName('a.publish_down') . ' = ' . $nullDate . ' OR ' . $db->quoteName('a.publish_down') . ' >= ' . $nowDate . ')');
		}

		$db->setQuery($query);

		$data = $db->loadObject();

		if (empty($data))
		{
			return false;
		}

		// Convert parameter fields to objects.
		$data->params   = new Registry($data->attribs);
		$data->metadata = new Registry($data->metadata);

		self::$articles[$id] = $data;

		return self::$articles[$id];
	}

	/**
	 * Gets the current article id based on url data
	 */
	public static function getId()
	{
		$input = JFactory::getApplication()->input;

		$id = $input->getInt('id');

		if ( ! $id
			|| ! (
				($input->get('option') == 'com_content' && $input->get('view') == 'article')
				|| ($input->get('option') == 'com_flexicontent' && $input->get('view') == 'item')
			)
		)
		{
			return false;
		}

		return $id;
	}

	/**
	 * Passes the different article parts through the given plugin method
	 *
	 * @param object $article
	 * @param string $context
	 * @param object $helper
	 * @param string $method
	 * @param array  $params
	 * @param array  $ignore
	 */
	public static function process(&$article, &$context, &$helper, $method, $params = [], $ignore = [])
	{
		self::processText('title', $article, $helper, $method, $params, $ignore);
		self::processText('created_by_alias', $article, $helper, $method, $params, $ignore);
		self::processText('description', $article, $helper, $method, $params, $ignore);

		// Don't replace in text fields in the category list view, as they won't get used anyway
		if (Document::isCategoryList($context))
		{
			return;
		}

		// prevent fulltext from being messed with, when it is a json encoded string (Yootheme Pro templates do this for some weird f-ing reason)
		if ( ! empty($article->fulltext) && substr($article->fulltext, 0, 6) == '<!-- {')
		{
			self::processText('text', $article, $helper, $method, $params, $ignore);

			return;
		}

		$has_text                  = isset($article->text);
		$has_article_texts         = isset($article->introtext) && isset($article->fulltext);
		$text_same_as_article_text = false;

		if ($has_text && $has_article_texts)
		{
			$check_text               = RegEx::replace('\s', '', $article->text);
			$check_introtext_fulltext = RegEx::replace('\s', '', $article->introtext . ' ' . $article->fulltext);

			$text_same_as_article_text = $check_text == $check_introtext_fulltext;
		}

		if ($has_article_texts && ! $has_text)
		{
			self::processText('introtext', $article, $helper, $method, $params, $ignore);
			self::processText('fulltext', $article, $helper, $method, $params, $ignore);

			return;
		}

		if ($has_article_texts && $text_same_as_article_text)
		{
			$splitter = '͞';
			if (strpos($article->introtext, $splitter) !== false
				|| strpos($article->fulltext, $splitter) !== false)
			{
				$splitter = 'Ͽ';
			}

			$article->text = $article->introtext . $splitter . $article->fulltext;

			self::processText('text', $article, $helper, $method, $params, $ignore);

			list($article->introtext, $article->fulltext) = explode($splitter, $article->text, 2);

			$article->text = str_replace($splitter, ' ', $article->text);

			return;
		}

		self::processText('text', $article, $helper, $method, $params, $ignore);
		self::processText('introtext', $article, $helper, $method, $params, $ignore);
		self::processText('fulltext', $article, $helper, $method, $params, $ignore);
	}

	private static function processText($type = '', &$article, &$helper, $method, $params = [], $ignore = [])
	{
		if (empty($article->{$type}))
		{
			return;
		}

		if (in_array($type, $ignore))
		{
			return;
		}

		call_user_func_array([$helper, $method], array_merge([&$article->{$type}], $params));
	}
}
regularlabs/src/StringHelper.php000064400000013435152177723700012776 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\String\Normalise;

/**
 * Class StringHelper
 * @package RegularLabs\Library
 */
class StringHelper
	extends \Joomla\String\StringHelper
{
	/**
	 * Decode html entities in string or array of strings
	 *
	 * @param string $data
	 * @param int    $quote_style
	 * @param string $encoding
	 *
	 * @return array|string
	 */
	public static function html_entity_decoder($data, $quote_style = ENT_QUOTES, $encoding = 'UTF-8')
	{
		if (is_array($data))
		{
			array_walk($data, function (&$part, $key, $quote_style, $encoding) {
				$part = self::html_entity_decoder($part, $quote_style, $encoding);
			}, $quote_style, $encoding);

			return $data;
		}

		if ( ! is_string($data))
		{
			return $data;
		}

		return html_entity_decode($data, $quote_style | ENT_HTML5, $encoding);
	}

	/**
	 * Replace the given replace string once in the main string
	 *
	 * @param string $search
	 * @param string $replace
	 * @param string $string
	 *
	 * @return string
	 */
	public static function replaceOnce($search, $replace, $string)
	{
		if (empty($search) || empty($string))
		{
			return $string;
		}

		$pos = strpos($string, $search);

		if ($pos === false)
		{
			return $string;
		}

		return substr_replace($string, $replace, $pos, strlen($search));
	}

	/**
	 * Check if any of the needles are found in any of the haystacks
	 *
	 * @param $haystacks
	 * @param $needles
	 *
	 * @return bool
	 */
	public static function contains($haystacks, $needles)
	{
		$haystacks = (array) $haystacks;
		$needles   = (array) $needles;

		foreach ($haystacks as $haystack)
		{
			foreach ($needles as $needle)
			{
				if (strpos($haystack, $needle) !== false)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Check if string is alphanumerical
	 *
	 * @param string $string
	 *
	 * @return bool
	 */
	public static function is_alphanumeric($string)
	{
		if (function_exists('ctype_alnum'))
		{
			return (bool) ctype_alnum($string);
		}

		return (bool) RegEx::match('^[a-z0-9]+$', $string);
	}

	/**
	 * Check if string is a valid key / alias (alphanumeric with optional _ or - chars)
	 *
	 * @param string $string
	 *
	 * @return bool
	 */
	public static function is_key($string)
	{
		return RegEx::match('^[a-z][a-z0-9-_]*$', trim($string));
	}

	/**
	 * Split a long string into parts (array)
	 *
	 * @param string $string
	 * @param array  $delimiters     Array of strings to split the string on
	 * @param int    $max_length     Maximum length of each part
	 * @param bool   $maximize_parts If true, the different parts will be made as large as possible (combining consecutive short string elements)
	 *
	 * @return array
	 */
	public static function split($string, $delimiters = [], $max_length = 10000, $maximize_parts = true)
	{
		// String is too short to split
		if (strlen($string) < $max_length)
		{
			return [$string];
		}

		// No delimiters given or found
		if (empty($delimiters) || ! self::contains($string, $delimiters))
		{
			return [$string];
		}

		// preg_quote all delimiters
		$array = preg_split('#' . RegEx::quote($delimiters) . '#s', $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);

		if ( ! $maximize_parts)
		{
			return $array;
		}

		$new_array = [];
		foreach ($array as $part)
		{
			// First element, add to new array
			if ( ! count($new_array))
			{
				$new_array[] = $part;
				continue;
			}

			$last_part = end($new_array);
			$last_key  = key($new_array);

			// If last and current parts are longer than max_length, then simply add as new value
			if (strlen($last_part) + strlen($part) > $max_length)
			{
				$new_array[] = $part;
				continue;
			}

			// Concatenate part to previous part
			$new_array[$last_key] .= $part;
		}

		return $new_array;
	}

	/**
	 * Check whether string is a UTF-8 encoded string
	 *
	 * @param string $string
	 *
	 * @return bool
	 */
	public static function detectUTF8($string = '')
	{
		// Try to check the string via the mb_check_encoding function
		if (function_exists('mb_check_encoding'))
		{
			return mb_check_encoding($string, 'UTF-8');
		}

		// Otherwise: Try to check the string via the iconv function
		if (function_exists('iconv'))
		{
			$converted = iconv('UTF-8', 'UTF-8//IGNORE', $string);

			return (md5($converted) == md5($string));
		}

		// As last fallback, check if the preg_match finds anything using the unicode flag
		return preg_match('#.#u', $string);
	}

	/**
	 * Converts a string to a UTF-8 encoded string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function convertToUtf8(&$string = '')
	{
		if (self::detectUTF8($string))
		{
			// Already UTF-8, so skip
			return $string;
		}

		if ( ! function_exists('iconv'))
		{
			// Still need to find a stable fallback
			return $string;
		}

		$utf8_string = @iconv('UTF8', 'UTF-8//IGNORE', $string);

		if (empty($utf8_string))
		{
			return $string;
		}

		return $utf8_string;
	}

	/**
	 * Converts a camelcased string to a underscore separated string
	 * eg: FooBar => foo_bar
	 *
	 * @param string $string
	 * @param bool   $tolowercase
	 *
	 * @return string
	 */
	public static function camelToUnderscore($string = '', $tolowercase = true)
	{
		$string = Normalise::toUnderscoreSeparated(Normalise::fromCamelCase($string));

		if ( ! $tolowercase)
		{
			return $string;
		}

		return strtolower($string);
	}

	/**
	 * Removes html tags from string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function removeHtml($string)
	{
		return Html::removeHtmlTags($string);
	}
}
regularlabs/src/Uri.php000064400000005016152177723700011123 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Uri\Uri as JUri;

/**
 * Class Uri
 * @package RegularLabs\Library
 */
class Uri
{
	/**
	 * Returns the full uri and optionally adds/replaces the hash
	 *
	 * @param string $hash
	 *
	 * @return string
	 */
	public static function get($hash = '')
	{
		$url = JUri::getInstance()->toString();

		if ($hash == '')
		{
			return $url;
		}

		return self::appendHash($url, $hash);
	}

	/**
	 * Appends the given hash to the url or replaces it if there is already one
	 *
	 * @param string $url
	 * @param string $hash
	 *
	 * @return string
	 */
	private static function appendHash($url = '', $hash = '')
	{
		if (empty($hash))
		{
			return $url;
		}

		if (strpos($url, '#') !== false)
		{
			$url = substr($url, 0, strpos($url, '#'));
		}

		return $url . '#' . $hash;
	}

	public static function isExternal($url)
	{
		if (strpos($url, '://') === false)
		{
			return false;
		}

		// hostname: give preference to SERVER_NAME, because this includes subdomains
		$hostname = ($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];

		return ! (strpos(RegEx::replace('^.*?://', '', $url), $hostname) === 0);
	}

	public static function route($url)
	{
		return JRoute::_(JUri::root(true) . '/' . $url);
	}

	public static function encode($string)
	{
		return urlencode(base64_encode(gzdeflate($string)));
	}

	public static function decode($string)
	{
		return gzinflate(base64_decode(urldecode($string)));
	}

	public static function createCompressedAttributes($string)
	{
		$parameters = [];

		$compressed   = base64_encode(gzdeflate($string));
		$chunk_length = ceil(strlen($compressed) / 10);
		$chunks       = str_split($compressed, $chunk_length);

		foreach ($chunks as $i => $chunk)
		{
			$parameters[] = 'rlatt_' . $i . '=' . urlencode($chunk);
		}

		return implode('&', $parameters);
	}

	public static function getCompressedAttributes()
	{
		$input = JFactory::getApplication()->input;

		$compressed = '';

		for ($i = 0; $i < 10; $i++)
		{
			$compressed .= $input->getString('rlatt_' . $i, '');
		}

		return gzinflate(base64_decode($compressed));
	}
}
regularlabs/src/EditorButtonHelper.php000064400000004314152177723700014146 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Object\CMSObject as JObject;

/**
 * Class EditorButtonHelper
 * @package RegularLabs\Library
 */
class EditorButtonHelper
{
	var $_name  = '';
	var $params = null;

	public function __construct($name, &$params)
	{
		$this->_name  = $name;
		$this->params = $params;

		Language::load('plg_editors-xtd_' . $name);

		JHtml::_('jquery.framework');

		Document::script('regularlabs/script.min.js');
		Document::style('regularlabs/style.min.css');
	}

	public function getButtonText()
	{
		$text_ini = strtoupper(str_replace(' ', '_', $this->params->button_text));
		$text     = JText::_($text_ini);

		if ($text == $text_ini)
		{
			$text = JText::_($this->params->button_text);
		}

		return trim($text);
	}

	public function getIcon($icon = '')
	{
		$icon = $icon ?: $this->_name;

		return 'reglab icon-' . $icon;
	}

	public function renderPopupButton($editor_name, $width = 0, $height = 0)
	{
		$button = new JObject;

		$button->modal   = true;
		$button->class   = 'btn';
		$button->link    = $this->getPopupLink($editor_name);
		$button->text    = $this->getButtonText();
		$button->name    = $this->getIcon();
		$button->options = $this->getPopupOptions($width, $height);

		return $button;
	}

	public function getPopupLink($editor_name)
	{
		return 'index.php?rl_qp=1'
			. '&folder=plugins.editors-xtd.' . $this->_name
			. '&file=popup.php'
			. '&name=' . $editor_name;
	}

	public function getPopupOptions($width = 0, $height = 0)
	{
		$width  = $width ?: 1600;
		$height = $height ?: 1200;

		$width  = 'Math.min(window.getSize().x-100, ' . $width . ')';
		$height = 'Math.min(window.getSize().y-100, ' . $height . ')';

		return '{'
			. 'handler: \'iframe\','
			. 'size: {'
			. 'x:' . $width . ','
			. 'y:' . $height
			. '}'
			. '}';
	}
}
regularlabs/src/EditorButtonPopup.php000064400000004046152177723700014034 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Exception;
use Joomla\CMS\Language\Text as JText;
use ReflectionClass;

/**
 * Class EditorButtonPopup
 * @package RegularLabs\Library
 */
class EditorButtonPopup
{
	var $extension         = '';
	var $params            = null;
	var $require_core_auth = true;

	public function __construct($extension)
	{
		$this->extension = $extension;
		$this->params    = Parameters::getInstance()->getPluginParams($extension);
	}

	public function render()
	{
		if ( ! Extension::isAuthorised($this->require_core_auth))
		{
			throw new Exception(JText::_("ALERTNOTAUTH"));
		}

		if ( ! Extension::isEnabledInArea($this->params))
		{
			throw new Exception(JText::_("ALERTNOTAUTH"));
		}

		$this->loadLibraryLanguages();
		$this->loadLibraryScriptsStyles();

		$this->loadLanguages();

		Document::style('regularlabs/popup.min.css');

		$this->loadScripts();
		$this->loadStyles();

		echo $this->renderTemplate();
	}

	public function loadLanguages()
	{
		Language::load('plg_editors-xtd_' . $this->extension);
		Language::load('plg_system_' . $this->extension);
	}

	public function loadScripts()
	{
	}

	public function loadStyles()
	{
	}

	private function loadLibraryLanguages()
	{
		Language::load('plg_system_regularlabs');
	}

	private function loadLibraryScriptsStyles()
	{
		Document::loadPopupDependencies();
	}

	private function renderTemplate()
	{
		ob_start();
		include $this->getDir() . '/popup.tmpl.php';
		$html = ob_get_contents();
		ob_end_clean();

		return $html;
	}

	private function getDir()
	{
		// use static::class instead of get_class($this) after php 5.4 support is dropped
		$rc = new ReflectionClass(get_class($this));

		return dirname($rc->getFileName());
	}
}
regularlabs/src/Language.php000064400000002022152177723700012101 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Language
 * @package RegularLabs\Library
 */
class Language
{
	/**
	 * Load the language of the given extension
	 *
	 * @param string $extension
	 * @param string $basePath
	 * @param bool   $reload
	 *
	 * @return bool
	 */
	public static function load($extension = 'plg_system_regularlabs', $basePath = '', $reload = false)
	{
		if ($basePath && JFactory::getLanguage()->load($extension, $basePath, null, $reload))
		{
			return true;
		}

		$basePath = Extension::getPath($extension, $basePath, 'language');

		return JFactory::getLanguage()->load($extension, $basePath, null, $reload);
	}
}
regularlabs/src/Alias.php000064400000006014152177723700011414 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Application\ApplicationHelper as JApplicationHelper;
use Joomla\CMS\Factory as JFactory;

/**
 * Class Alias
 * @package RegularLabs\Library
 */
class Alias
{
	/**
	 * Creates an alias from a string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function get($string = '', $unicode = false)
	{
		if (empty($string))
		{
			return '';
		}

		$string = StringHelper::removeHtml($string);

		if ($unicode || JFactory::getConfig()->get('unicodeslugs') == 1)
		{
			return self::stringURLUnicodeSlug($string);
		}

		// Remove < > html entities
		$string = str_replace(['&lt;', '&gt;'], '', $string);

		// Convert html entities
		$string = StringHelper::html_entity_decoder($string);

		return JApplicationHelper::stringURLSafe($string);
	}

	/**
	 * Creates a unicode alias from a string
	 * Based on stringURLUnicodeSlug method from the unicode slug plugin by infograf768
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private static function stringURLUnicodeSlug($string = '')
	{
		if (empty($string))
		{
			return '';
		}

		// Remove < > html entities
		$string = str_replace(['&lt;', '&gt;'], '', $string);

		// Convert html entities
		$string = StringHelper::html_entity_decoder($string);

		// Convert to lowercase
		$string = StringHelper::strtolower($string);

		// remove html tags
		$string = RegEx::replace('</?[a-z][^>]*>', '', $string);
		// remove comments tags
		$string = RegEx::replace('<\!--.*?-->', '', $string);

		// Replace weird whitespace characters like (Â) with spaces
		//$string = str_replace(array(chr(160), chr(194)), ' ', $string);
		$string = str_replace("\xC2\xA0", ' ', $string);
		$string = str_replace("\xE2\x80\xA8", ' ', $string); // ascii only

		// Replace double byte whitespaces by single byte (East Asian languages)
		$string = str_replace("\xE3\x80\x80", ' ', $string);

		// Remove any '-' from the string as they will be used as concatenator.
		// Would be great to let the spaces in but only Firefox is friendly with this
		$string = str_replace('-', ' ', $string);

		// Replace forbidden characters by whitespaces
		$string = RegEx::replace('[' . RegEx::quote(',:#$*"@+=;&.%()[]{}/\'\\|') . ']', "\x20", $string);

		// Delete all characters that should not take up any space, like: ?
		$string = RegEx::replace('[' . RegEx::quote('?!¿¡') . ']', '', $string);

		// Trim white spaces at beginning and end of alias and make lowercase
		$string = trim($string);

		// Remove any duplicate whitespace and replace whitespaces by hyphens
		$string = RegEx::replace('\x20+', '-', $string);

		// Remove leading and trailing hyphens
		$string = trim($string, '-');

		return $string;
	}
}
regularlabs/src/File.php000064400000027154152177723700011252 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Filesystem\Path as JPath;
use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Client\ClientHelper as JClientHelper;
use Joomla\CMS\Client\FtpClient as JFtpClient;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Log\Log as JLog;
use Joomla\CMS\Uri\Uri as JUri;

/**
 * Class File
 * @package RegularLabs\Library
 */
class File
{
	/**
	 * Find a matching media file in the different possible extension media folders for given type
	 *
	 * @param string $type (css/js/...)
	 * @param string $file
	 *
	 * @return bool|string
	 */
	public static function getMediaFile($type, $file)
	{
		// If http is present in filename
		if (strpos($file, 'http') === 0 || strpos($file, '//') === 0)
		{
			return $file;
		}

		$files = [];

		// Detect debug mode
		if (JFactory::getConfig()->get('debug') || JFactory::getApplication()->input->get('debug'))
		{
			$files[] = str_replace(['.min.', '-min.'], '.', $file);
		}

		$files[] = $file;

		/*
		 * Loop on 1 or 2 files and break on first find.
		 * Add the content of the MD5SUM file located in the same folder to url to ensure cache browser refresh
		 * This MD5SUM file must represent the signature of the folder content
		 */
		foreach ($files as $check_file)
		{
			$file_found = self::findMediaFileByFile($check_file, $type);

			if ( ! $file_found)
			{
				continue;
			}

			return $file_found;
		}

		return false;
	}

	/**
	 * Find a matching media file in the different possible extension media folders for given type
	 *
	 * @param string $file
	 * @param string $type (css/js/...)
	 *
	 * @return bool|string
	 */
	private static function findMediaFileByFile($file, $type)
	{
		$template = JFactory::getApplication()->getTemplate();

		// If the file is in the template folder
		$file_found = self::getFileUrl('/templates/' . $template . '/' . $type . '/' . $file);
		if ($file_found)
		{
			return $file_found;
		}

		// Try to deal with system files in the media folder
		if (strpos($file, '/') === false)
		{
			$file_found = self::getFileUrl('/media/system/' . $type . '/' . $file);

			if ( ! $file_found)
			{
				return false;
			}

			return $file_found;
		}

		$paths = [];

		// If the file contains any /: it can be in a media extension subfolder
		// Divide the file extracting the extension as the first part before /
		list($extension, $file) = explode('/', $file, 2);

		$paths[] = '/media/' . $extension . '/' . $type;
		$paths[] = '/templates/' . $template . '/' . $type . '/system';
		$paths[] = '/media/system/' . $type;

		foreach ($paths as $path)
		{
			$file_found = self::getFileUrl($path . '/' . $file);

			if ( ! $file_found)
			{
				continue;
			}

			return $file_found;
		}

		return false;
	}

	/**
	 * Get the url for the file
	 *
	 * @param string $path
	 *
	 * @return bool|string
	 */
	private static function getFileUrl($path)
	{
		if ( ! file_exists(JPATH_ROOT . $path))
		{
			return false;
		}

		return JUri::root(true) . $path;
	}

	/**
	 * Delete a file or array of files
	 *
	 * @param   mixed   $file               The file name or an array of file names
	 * @param   boolean $show_messages      Whether or not to show error messages
	 * @param   int     $min_age_in_minutes Minimum last modified age in minutes
	 *
	 * @return  boolean  True on success
	 *
	 * @since   11.1
	 */
	public static function delete($file, $show_messages = false, $min_age_in_minutes = 0)
	{
		$FTPOptions = JClientHelper::getCredentials('ftp');
		$pathObject = new JPath;

		$files = is_array($file) ? $file : [$file];

		if ($FTPOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);
		}

		foreach ($files as $file)
		{
			$file = $pathObject->clean($file);

			if ( ! is_file($file))
			{
				continue;
			}

			if ($min_age_in_minutes && floor((time() - filemtime($file)) / 60) < $min_age_in_minutes)
			{
				continue;
			}

			// Try making the file writable first. If it's read-only, it can't be deleted
			// on Windows, even if the parent folder is writable
			@chmod($file, 0777);

			if ($FTPOptions['enabled'] == 1)
			{
				$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');

				if ( ! $ftp->delete($file))
				{
					// FTP connector throws an error
					return false;
				}
			}

			// Try the unlink twice in case something was blocking it on first try
			if ( ! @unlink($file) && ! @unlink($file))
			{
				$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), JLog::WARNING, 'jerror');

				return false;
			}
		}

		return true;
	}

	/**
	 * Delete a folder.
	 *
	 * @param   string  $path               The path to the folder to delete.
	 * @param   boolean $show_messages      Whether or not to show error messages
	 * @param   int     $min_age_in_minutes Minimum last modified age in minutes
	 *
	 * @return  boolean  True on success.
	 */
	public static function deleteFolder($path, $show_messages = false, $min_age_in_minutes = 0)
	{
		@set_time_limit(ini_get('max_execution_time'));
		$pathObject = new JPath;

		if ( ! $path)
		{
			$show_messages && JLog::add(__METHOD__ . ': ' . JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), JLog::WARNING, 'jerror');

			return false;
		}

		// Check to make sure the path valid and clean
		$path = $pathObject->clean($path);

		if ( ! is_dir($path))
		{
			$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), JLog::WARNING, 'jerror');

			return false;
		}

		// Remove all the files in folder if they exist; disable all filtering
		$files = JFolder::files($path, '.', false, true, [], []);

		if ( ! empty($files))
		{
			if (self::delete($files, $show_messages, $min_age_in_minutes) !== true)
			{
				// JFile::delete throws an error
				return false;
			}
		}

		// Remove sub-folders of folder; disable all filtering
		$folders = JFolder::folders($path, '.', false, true, [], []);

		foreach ($folders as $folder)
		{
			if (is_link($folder))
			{
				// Don't descend into linked directories, just delete the link.

				if (self::delete($folder, $show_messages, $min_age_in_minutes) !== true)
				{
					return false;
				}

				continue;
			}

			if ( ! self::deleteFolder($folder, $show_messages, $min_age_in_minutes))
			{
				return false;
			}
		}

		// Skip if folder is not empty yet
		if ( ! empty(JFolder::files($path, '.', false, true, [], []))
			|| ! empty(JFolder::folders($path, '.', false, true, [], [])))
		{
			return true;
		}

		if (@rmdir($path))
		{
			return true;
		}

		$FTPOptions = JClientHelper::getCredentials('ftp');

		if ($FTPOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = JFtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], [], $FTPOptions['user'], $FTPOptions['pass']);

			// Translate path and delete
			$path = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');

			// FTP connector throws an error
			return $ftp->delete($path);
		}

		if ( ! @rmdir($path))
		{
			$show_messages && JLog::add(JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), JLog::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	public static function trimFolder($folder)
	{
		return trim(str_replace(['\\', '//'], '/', $folder), '/');
	}

	public static function isInternal($url)
	{
		return ! self::isExternal($url);
	}

	public static function isExternal($url)
	{
		if (strpos($url, '://') === false)
		{
			return false;
		}

		// hostname: give preference to SERVER_NAME, because this includes subdomains
		$hostname = ($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];

		return ! (strpos(RegEx::replace('^.*?://', '', $url), $hostname) === 0);
	}


	// some/url/to/a/file.ext
	// > some/url/to/a
	public static function getDirName($url)
	{
		return rtrim(dirname($url), '/');
	}

	// some/url/to/a/file.ext
	// > file.ext
	public static function getBaseName($url, $lowercase = false)
	{
		$basename = ltrim(basename($url), '/');

		$parts = explode('?', $basename);

		$basename = $parts[0];

		if ($lowercase)
		{
			$basename = strtolower($basename);
		}

		return $basename;
	}

	// some/url/to/a/file.ext
	// > file
	public static function getFileName($url, $lowercase = false)
	{
		$info = pathinfo($url);

		$filename = isset($info['filename']) ? $info['filename'] : $url;

		if ($lowercase)
		{
			$filename = strtolower($filename);
		}

		return $filename;
	}

	// some/url/to/a/file.ext
	// > ext
	public static function getExtension($url)
	{
		$info = pathinfo($url);

		if ( ! isset($info['extension']))
		{
			return '';
		}

		$ext = explode('?', $info['extension']);

		return strtolower($ext[0]);
	}

	public static function isImage($url)
	{
		return self::isMedia($url, self::getFileTypes('images'));
	}

	public static function isVideo($url)
	{
		return self::isMedia($url, self::getFileTypes('videos'));
	}

	public static function isExternalVideo($url)
	{
		return (strpos($url, 'youtu.be') !== false
			|| strpos($url, 'youtube.com') !== false
			|| strpos($url, 'vimeo.com') !== false
		);
	}

	public static function isDocument($url)
	{
		return self::isMedia($url, self::getFileTypes('documents'));
	}

	public static function isMedia($url, $filetypes = [])
	{
		$filetype = self::getExtension($url);

		if ( ! $filetype)
		{
			return false;
		}

		if ( ! is_array($filetypes))
		{
			$filetypes = [$filetypes];
		}

		if (count($filetypes) == 1 && strpos($filetypes[0], ',') !== false)
		{
			$filetypes = ArrayHelper::toArray($filetypes[0]);
		}

		$filetypes = ! empty($filetypes) ? $filetypes : self::getFileTypes();

		return in_array($filetype, $filetypes);
	}

	public static function getFileTypes($type = 'images')
	{
		switch ($type)
		{
			case 'image':
			case 'images':
				return [
					'bmp',
					'flif',
					'gif',
					'jpe',
					'jpeg',
					'jpg',
					'png',
					'tiff',
					'eps',
				];

			case 'audio':
				return [
					'aif',
					'aiff',
					'mp3',
					'wav',
				];

			case 'video':
			case 'videos':
				return [
					'3g2',
					'3gp',
					'avi',
					'divx',
					'f4v',
					'flv',
					'm4v',
					'mov',
					'mp4',
					'mpe',
					'mpeg',
					'mpg',
					'ogv',
					'swf',
					'webm',
					'wmv',
				];

			case 'document':
			case 'documents':
				return [
					'doc',
					'docm',
					'docx',
					'dotm',
					'dotx',
					'odb',
					'odc',
					'odf',
					'odg',
					'odi',
					'odm',
					'odp',
					'ods',
					'odt',
					'onepkg',
					'onetmp',
					'onetoc',
					'onetoc2',
					'otg',
					'oth',
					'otp',
					'ots',
					'ott',
					'oxt',
					'pdf',
					'potm',
					'potx',
					'ppam',
					'pps',
					'ppsm',
					'ppsx',
					'ppt',
					'pptm',
					'pptx',
					'rtf',
					'sldm',
					'sldx',
					'thmx',
					'xla',
					'xlam',
					'xlc',
					'xld',
					'xll',
					'xlm',
					'xls',
					'xlsb',
					'xlsm',
					'xlsx',
					'xlt',
					'xltm',
					'xltx',
					'xlw',
				];

			case 'other':
			case 'others':
				return [
					'css',
					'csv',
					'js',
					'json',
					'tar',
					'txt',
					'xml',
					'zip',
				];

			default:
			case 'all':
				return array_merge(
					self::getFileTypes('images'),
					self::getFileTypes('audio'),
					self::getFileTypes('videos'),
					self::getFileTypes('documents'),
					self::getFileTypes('other')
				);
		}
	}
}
regularlabs/src/ConditionContent.php000064400000005115152177723700013645 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class ConditionContent
 * @package RegularLabs\Library
 */
trait ConditionContent
{
	public function passContentId()
	{
		if (empty($this->selection))
		{
			return null;
		}

		return in_array($this->request->id, $this->selection);
	}

	public function passContentKeyword($fields = ['title', 'introtext', 'fulltext'], $text = '')
	{
		if (empty($this->params->content_keywords))
		{
			return null;
		}

		if ( ! $text)
		{
			$item = $this->getItem($fields);

			foreach ($fields as $field)
			{
				if ( ! isset($item->{$field}))
				{
					return false;
				}

				$text = trim($text . ' ' . $item->{$field});
			}
		}

		if (empty($text))
		{
			return false;
		}

		$this->params->content_keywords = $this->makeArray($this->params->content_keywords);

		foreach ($this->params->content_keywords as $keyword)
		{
			if ( ! RegEx::match('\b' . RegEx::quote($keyword) . '\b', $text))
			{
				continue;
			}

			return true;
		}

		return false;
	}

	public function passMetaKeyword($field = 'metakey', $keywords = '')
	{
		if (empty($this->params->meta_keywords))
		{
			return null;
		}

		if ( ! $keywords)
		{
			$item = $this->getItem($field);

			if ( ! isset($item->metakey) || empty($item->metakey))
			{
				return false;
			}

			$keywords = $item->metakey;
		}

		if (empty($keywords))
		{
			return false;
		}

		if (is_string($keywords))
		{
			$keywords = str_replace(' ', ',', $keywords);
		}

		$keywords = $this->makeArray($keywords);

		$this->params->meta_keywords = $this->makeArray($this->params->meta_keywords);

		foreach ($this->params->meta_keywords as $keyword)
		{
			if ( ! $keyword || ! in_array(trim($keyword), $keywords))
			{
				continue;
			}

			return true;
		}

		return false;
	}

	public function passAuthor($field = 'created_by', $author = '')
	{
		if (empty($this->params->authors))
		{
			return null;
		}

		if ( ! $author)
		{
			$item = $this->getItem($field);

			if ( ! isset($item->{$field}))
			{
				return false;
			}

			$author = $item->{$field};
		}

		if (empty($author))
		{
			return false;
		}

		$this->params->authors = $this->makeArray($this->params->authors);

		return in_array($author, $this->params->authors);
	}

	abstract public function getItem($fields = []);
}
regularlabs/src/Html.php000064400000046705152177723700011302 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use DOMDocument;

/**
 * Class Html
 * @package RegularLabs\Library
 */
class Html
{
	/**
	 * Convert content saved in a WYSIWYG editor to plain text (like removing html tags)
	 *
	 * @param $string
	 *
	 * @return string
	 */
	public static function convertWysiwygToPlainText($string)
	{
		// replace chr style enters with normal enters
		$string = str_replace([chr(194) . chr(160), '&#160;', '&nbsp;'], ' ', $string);

		// replace linebreak tags with normal linebreaks (paragraphs, enters, etc).
		$enter_tags = ['p', 'br'];
		$regex      = '</?((' . implode(')|(', $enter_tags) . '))+[^>]*?>\n?';
		$string     = RegEx::replace($regex, " \n", $string);

		// replace indent characters with spaces
		$string = RegEx::replace('<img [^>]*/sourcerer/images/tab\.png[^>]*>', '    ', $string);

		// strip all other tags
		$regex  = '<(/?\w+((\s+\w+(\s*=\s*(?:".*?"|\'.*?\'|[^\'">\s]+))?)+\s*|\s*)/?)>';
		$string = RegEx::replace($regex, '', $string);

		// reset htmlentities
		$string = StringHelper::html_entity_decoder($string);

		// convert protected html entities &_...; -> &...;
		$string = RegEx::replace('&_([a-z0-9\#]+?);', '&\1;', $string);

		return $string;
	}

	/**
	 * Extract the <body>...</body> part from an entire html output string
	 *
	 * @param string $html
	 *
	 * @return array
	 */
	public static function getBody($html, $include_body_tag = true)
	{
		if (strpos($html, '<body') === false || strpos($html, '</body>') === false)
		{
			return ['', $html, ''];
		}

		// Force string to UTF-8
		$html = StringHelper::convertToUtf8($html);

		$html_split = explode('<body', $html, 2);
		$pre        = $html_split[0];
		$body       = '<body' . $html_split[1];
		$body_split = explode('</body>', $body);
		$post       = array_pop($body_split);
		$body       = implode('</body>', $body_split) . '</body>';

		if ( ! $include_body_tag)
		{
			RegEx::match('^(<body[^>]*>\s*)(.*?)(\s*</body>)$', $body, $match);

			$pre  = $pre . $match[1];
			$body = $match[2];
			$post = $match[3] . $post;
		}

		return [$pre, $body, $post];
	}

	/**
	 * Search the string for the start and end searches and split the string in a pre, body and post part
	 * This is used to be able to do replacements on the body part, which will be lighter than doing it on the entire string
	 *
	 * @param string $string
	 * @param array  $start_searches
	 * @param array  $end_searches
	 * @param int    $start_offset
	 * @param null   $end_offset
	 *
	 * @return array
	 */
	public static function getContentContainingSearches($string, $start_searches = [], $end_searches = [], $start_offset = 1000, $end_offset = null)
	{
		// String is too short to split and search through
		if (strlen($string) < 2000)
		{
			return ['', $string, ''];
		}

		$end_offset = is_null($end_offset) ? $start_offset : $end_offset;

		$found       = false;
		$start_split = strlen($string);

		foreach ($start_searches as $search)
		{
			$pos = strpos($string, $search);

			if ($pos === false)
			{
				continue;
			}

			$start_split = min($start_split, $pos);
			$found       = true;
		}

		// No searches are found
		if ( ! $found)
		{
			return [$string, '', ''];
		}

		// String is too short to split
		if (strlen($string) < ($start_offset + $end_offset + 1000))
		{
			return ['', $string, ''];
		}

		$start_split = max($start_split - $start_offset, 0);

		$pre    = substr($string, 0, $start_split);
		$string = substr($string, $start_split);

		self::fixBrokenTagsByPreString($pre, $string);

		if (empty($end_searches))
		{
			$end_searches = $start_searches;
		}

		$end_split = 0;
		$found     = false;

		foreach ($end_searches as $search)
		{
			$pos = strrpos($string, $search);

			if ($pos === false)
			{
				continue;
			}

			$end_split = max($end_split, $pos + strlen($search));
			$found     = true;
		}

		// No end split is found, so don't split remainder
		if ( ! $found)
		{
			return [$pre, $string, ''];
		}

		$end_split = min($end_split + $end_offset, strlen($string));

		$post   = substr($string, $end_split);
		$string = substr($string, 0, $end_split);

		self::fixBrokenTagsByPostString($post, $string);

		return [$pre, $string, $post];
	}

	/**
	 * Check if string contains block elements
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function containsBlockElements($string)
	{
		return RegEx::match('</?(' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>', $string);
	}

	/**
	 * Fix broken/invalid html syntax in a string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function fix($string)
	{
		if ( ! self::containsBlockElements($string))
		{
			return $string;
		}

		// Convert utf8 characters to html entities
		if (function_exists('mb_convert_encoding'))
		{
			$string = mb_convert_encoding($string, 'html-entities', 'utf-8');
		}

		$string = self::protectSpecialCode($string);

		$string = self::convertDivsInsideInlineElementsToSpans($string);
		$string = self::removeParagraphsAroundBlockElements($string);
		$string = self::removeInlineElementsAroundBlockElements($string);
		$string = self::fixParagraphsAroundParagraphElements($string);

		$string = class_exists('DOMDocument')
			? self::fixUsingDOMDocument($string)
			: self::fixUsingCustomFixer($string);

		$string = self::unprotectSpecialCode($string);

		// Convert html entities back to utf8 characters
		if (function_exists('mb_convert_encoding'))
		{
			// Make sure &lt; and &gt; don't get converted
			$string = str_replace(['&lt;', '&gt;'], ['&amp;lt;', '&amp;gt;'], $string);

			$string = mb_convert_encoding($string, 'utf-8', 'html-entities');
		}

		$string = self::removeParagraphsAroundComments($string);

		return $string;
	}

	/**
	 * Fix broken/invalid html syntax in an array of strings
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function fixArray($array)
	{
		$splitter = ':|:';

		$string = self::fix(implode($splitter, $array));

		$parts = self::removeEmptyTags(explode($splitter, $string));

		// use original keys but new values
		return array_combine(array_keys($array), $parts);
	}

	/**
	 * Removes empty tags which span concatenating parts in the array
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function removeEmptyTags($array)
	{
		$splitter = ':|:';
		$comments = '(?:\s*<\!--[^>]*-->\s*)*';

		$string = implode($splitter, $array);

		Protect::protectHtmlCommentTags($string);

		$string = RegEx::replace(
			'<([a-z][a-z0-9]*)(?: [^>]*)?>\s*(' . $comments . RegEx::quote($splitter) . $comments . ')\s*</\1>',
			'\2',
			$string
		);

		Protect::unprotect($string);

		return explode($splitter, $string);
	}

	/**
	 * Fix broken/invalid html syntax in a string using php DOMDocument functionality
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	private static function fixUsingDOMDocument($string)
	{
		$doc = new DOMDocument;

		$doc->substituteEntities = false;

		list($pre, $body, $post) = Html::getBody($string, false);

		// Add temporary surrounding div
		$body = '<div>' . $body . '</div>';

		@$doc->loadHTML($body);
		$body = $doc->saveHTML();

		// Remove html document structures
		$body = RegEx::replace('^<[^>]*>(.*?)<html>.*?(?:<head>(.*)</head>.*?)?<body>(.*)</body>.*?$', '\1\2\3', $body);

		// Remove temporary surrounding div
		$body = RegEx::replace('^\s*<div>(.*)</div>\s*$', '\1', $body);

		// Remove leading/trailing empty paragraph
		$body = RegEx::replace('(^\s*<div>\s*</div>|<div>\s*</div>\s*$)', '', $body);

		// Remove leading/trailing empty paragraph
		$body = RegEx::replace('(^\s*<p(?: [^>]*)?>\s*</p>|<p(?: [^>]*)?>\s*</p>\s*$)', '', $body);

		return $pre . $body . $post;
	}

	/**
	 * Fix broken/invalid html syntax in a string using custom code as an alternative to php DOMDocument functionality
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private static function fixUsingCustomFixer($string)
	{
		$block_regex = '<(' . implode('|', self::getBlockElementsNoDiv()) . ')[\s>]';

		$string = RegEx::replace('(' . $block_regex . ')', '[:SPLIT-BLOCK:]\1', $string);
		$parts  = explode('[:SPLIT-BLOCK:]', $string);

		foreach ($parts as $i => &$part)
		{
			if ( ! RegEx::match('^' . $block_regex, $part, $type))
			{
				continue;
			}

			$type = strtolower($type[1]);

			// remove endings of other block elements
			$part = RegEx::replace('</(?:' . implode('|', self::getBlockElementsNoDiv($type)) . ')>', '', $part);

			if (strpos($part, '</' . $type . '>') !== false)
			{
				continue;
			}

			// Add ending tag once
			$part = RegEx::replaceOnce('(\s*)$', '</' . $type . '>\1', $part);

			// Remove empty block tags
			$part = RegEx::replace('^<' . $type . '(?: [^>]*)?>\s*</' . $type . '>', '', $part);
		}

		return implode('', $parts);
	}

	/**
	 * Removes complete html tag pairs from the concatenated parts
	 *
	 * @param array $parts
	 * @param array $elements
	 *
	 * @return array
	 */
	public static function cleanSurroundingTags($parts, $elements = ['p', 'span'])
	{
		$breaks = '(?:(?:<br ?/?>|<\!--[^>]*-->|:\|:)\s*)*';
		$keys   = array_keys($parts);

		$string = implode(':|:', $parts);
		Protect::protectHtmlCommentTags($string);

		// Remove empty tags
		$regex = '<(' . implode('|', $elements) . ')(?: [^>]*)?>\s*(' . $breaks . ')<\/\1>\s*';

		while (RegEx::match($regex, $string, $match))
		{
			$string = str_replace($match[0], $match[2], $string);
		}

		// Remove paragraphs around block elements
		$block_elements = [
			'p', 'div',
			'table', 'tr', 'td', 'thead', 'tfoot',
			'h[1-6]',
		];
		$block_elements = '(' . implode('|', $block_elements) . ')';

		$regex = '(<p(?: [^>]*)?>)(\s*' . $breaks . ')(<' . $block_elements . '(?: [^>]*)?>)';

		while (RegEx::match($regex, $string, $match))
		{
			if ($match[4] == 'p')
			{
				$match[3] = $match[1] . $match[3];
				self::combinePTags($match[3]);
			}

			$string = str_replace($match[0], $match[2] . $match[3], $string);
		}

		$regex = '(</' . $block_elements . '>\s*' . $breaks . ')</p>';

		while (RegEx::match($regex, $string, $match))
		{
			$string = str_replace($match[0], $match[1], $string);
		}

		Protect::unprotect($string);
		$parts = explode(':|:', $string);

		$new_tags = [];

		foreach ($parts as $key => $val)
		{
			$key            = isset($keys[$key]) ? $keys[$key] : $key;
			$new_tags[$key] = $val;
		}

		return $new_tags;
	}

	/**
	 * Remove <p> tags around block elements
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	private static function removeParagraphsAroundBlockElements($string)
	{
		if (strpos($string, '</p>') == false)
		{
			return $string;
		}

		Protect::protectHtmlCommentTags($string);

		$string = RegEx::replace(
			'<p(?: [^>]*)?>\s*'
			. '((?:<\!--[^>]*-->\s*)*</?(?:' . implode('|', self::getBlockElements()) . ')' . '(?: [^>]*)?>)',
			'\1',
			$string
		);

		$string = RegEx::replace(
			'(</?(?:' . implode('|', self::getBlockElements()) . ')' . '(?: [^>]*)?>(?:\s*<\!--[^>]*-->)*)'
			. '(?:\s*</p>)',
			'\1',
			$string
		);

		Protect::unprotect($string);

		return $string;
	}

	/**
	 * Remove <p> tags around comments
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	private static function removeParagraphsAroundComments($string)
	{
		if (strpos($string, '</p>') == false)
		{
			return $string;
		}

		Protect::protectHtmlCommentTags($string);

		$string = RegEx::replace(
			'(?:<p(?: [^>]*)?>\s*)'
			. '(<\!--[^>]*-->)'
			. '(?:\s*</p>)',
			'\1',
			$string
		);

		Protect::unprotect($string);

		return $string;
	}

	/**
	 * Fix <p> tags around other <p> elements
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	private static function fixParagraphsAroundParagraphElements($string)
	{
		if (strpos($string, '</p>') == false)
		{
			return $string;
		}

		$parts  = explode('</p>', $string);
		$ending = '</p>' . array_pop($parts);

		foreach ($parts as &$part)
		{
			if (strpos($part, '<p>') === false && strpos($part, '<p ') === false)
			{
				$part = '<p>' . $part;
				continue;
			}

			$part = RegEx::replace(
				'(<p(?: [^>]*)?>.*?)(<p(?: [^>]*)?>)',
				'\1</p>\2',
				$part
			);
		}

		return implode('</p>', $parts) . $ending;
	}

	/*
	 * Remove empty tags
	 *
	 * @param string $string
	 * @param array $elements
	 *
	 * @return mixed
	 */
	public static function removeEmptyTagPairs($string, $elements = ['p', 'span'])
	{
		$breaks = '(?:(?:<br ?/?>|<\!--[^>]*-->)\s*)*';

		$regex = '<(' . implode('|', $elements) . ')(?: [^>]*)?>\s*(' . $breaks . ')<\/\1>\s*';

		Protect::protectHtmlCommentTags($string);
		while (RegEx::match($regex, $string, $match))
		{
			$string = str_replace($match[0], $match[2], $string);
		}

		Protect::unprotect($string);

		return $string;
	}

	/**
	 * Convert <div> tags inside inline elements to <span> tags
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	private static function convertDivsInsideInlineElementsToSpans($string)
	{
		if (strpos($string, '</div>') == false)
		{
			return $string;
		}

		// Ignore block elements inside anchors
		$regex = '<(' . implode('|', self::getInlineElementsNoAnchor()) . ')(?: [^>]*)?>.*?</\1>';
		RegEx::matchAll($regex, $string, $matches, '', PREG_PATTERN_ORDER);

		if (empty($matches))
		{
			return $string;
		}

		$matches      = array_unique($matches[0]);
		$searches     = [];
		$replacements = [];

		foreach ($matches as $match)
		{
			if (strpos($match, '</div>') === false)
			{
				continue;
			}

			$searches[]     = $match;
			$replacements[] = str_replace(
				['<div>', '<div ', '</div>'],
				['<span>', '<span ', '</span>'],
				$match
			);
		}

		if (empty($searches))
		{
			return $string;
		}

		return str_replace($searches, $replacements, $string);
	}

	/**
	 * Combine duplicate <p> tags
	 * input: <p class="aaa" a="1"><!-- ... --><p class="bbb" b="2">
	 * output: <p class="aaa bbb" a="1" b="2"><!-- ... -->
	 *
	 * @param $string
	 */
	public static function combinePTags(&$string)
	{
		if (empty($string))
		{
			return;
		}

		$p_start_tag   = '<p(?: [^>]*)?>';
		$optional_tags = '\s*(?:<\!--[^>]*-->|&nbsp;|&\#160;)*\s*';

		Protect::protectHtmlCommentTags($string);

		RegEx::matchAll('(' . $p_start_tag . ')(' . $optional_tags . ')(' . $p_start_tag . ')', $string, $tags);

		if (empty($tags))
		{

			Protect::unprotect($string);

			return;
		}

		foreach ($tags as $tag)
		{
			$string = str_replace($tag[0], $tag[2] . HtmlTag::combine($tag[1], $tag[3]), $string);
		}

		Protect::unprotect($string);
	}

	/**
	 * Remove inline elements around block elements
	 *
	 * @param string $string
	 *
	 * @return mixed
	 */
	public static function removeInlineElementsAroundBlockElements($string)
	{
		$string = RegEx::replace(
			'(?:<(?:' . implode('|', self::getInlineElementsNoAnchor()) . ')(?: [^>]*)?>\s*)'
			. '(</?(?:' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>)',
			'\1',
			$string
		);

		$string = RegEx::replace(
			'(</?(?:' . implode('|', self::getBlockElements()) . ')(?: [^>]*)?>)'
			. '(?:\s*</(?:' . implode('|', self::getInlineElementsNoAnchor()) . ')>)',
			'\1',
			$string
		);

		return $string;
	}

	/**
	 * Return an array of block element names, optionally without any of the names given $exclude
	 *
	 * @param array $exclude
	 *
	 * @return array
	 */
	public static function getBlockElements($exclude = [])
	{
		if ( ! is_array($exclude))
		{
			$exclude = [$exclude];
		}

		$elements = [
			'div', 'p', 'pre',
			'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
		];

		$elements = array_diff($elements, $exclude);

		$elements = implode(',', $elements);
		$elements = str_replace('h1,h2,h3,h4,h5,h6', 'h[1-6]', $elements);
		$elements = explode(',', $elements);

		return $elements;
	}

	/**
	 * Return an array of inline element names, optionally without any of the names given $exclude
	 *
	 * @param array $exclude
	 *
	 * @return array
	 */
	public static function getInlineElements($exclude = [])
	{
		if ( ! is_array($exclude))
		{
			$exclude = [$exclude];
		}

		$elements = [
			'span', 'code', 'a',
			'strong', 'b', 'em', 'i', 'u', 'big', 'small', 'font',
			'sup', 'sub',
		];

		return array_diff($elements, $exclude);
	}

	/**
	 * Return an array of block element names, without divs and any of the names given $exclude
	 *
	 * @param array $exclude
	 *
	 * @return array
	 */
	public static function getBlockElementsNoDiv($exclude = [])
	{
		return array_diff(self::getBlockElements($exclude), ['div']);
	}

	/**
	 * Return an array of block element names, without anchors (a) and any of the names given $exclude
	 *
	 * @param array $exclude
	 *
	 * @return array
	 */
	public static function getInlineElementsNoAnchor($exclude = [])
	{
		return array_diff(self::getInlineElements($exclude), ['a']);
	}

	/**
	 * Protect plugin style tags and php
	 *
	 * @param $string
	 *
	 * @return mixed
	 */
	private static function protectSpecialCode($string)
	{
		// Protect PHP code
		Protect::protectByRegex($string, '(<|&lt;)\?php\s.*?\?(>|&gt;)');

		// Protect {...} tags
		Protect::protectByRegex($string, '\{[a-z0-9].*?\}');

		// Protect [...] tags
		Protect::protectByRegex($string, '\[[a-z0-9].*?\]');

		// Protect scripts
		Protect::protectByRegex($string, '<script[^>]*>.*?</script>');

		// Protect css
		Protect::protectByRegex($string, '<style[^>]*>.*?</style>');

		Protect::convertProtectionToHtmlSafe($string);

		return $string;
	}

	/**
	 * Unprotect protected tags
	 *
	 * @param $string
	 *
	 * @return mixed
	 */
	private static function unprotectSpecialCode($string)
	{
		Protect::unprotectHtmlSafe($string);

		return $string;
	}

	/**
	 * Prevents broken html tags at the end of $pre (other half at beginning of $string)
	 * It will move the broken part to the beginning of $string to complete it
	 *
	 * @param $pre
	 * @param $string
	 */
	private static function fixBrokenTagsByPreString(&$pre, &$string)
	{
		if ( ! RegEx::match('<(\![^>]*|/?[a-z][^>]*(="[^"]*)?)$', $pre, $match))
		{
			return;
		}

		$pre    = substr($pre, 0, strlen($pre) - strlen($match[0]));
		$string = $match[0] . $string;
	}

	/**
	 * Prevents broken html tags at the beginning of $pre (other half at end of $string)
	 * It will move the broken part to the end of $string to complete it
	 *
	 * @param $post
	 * @param $string
	 */
	private static function fixBrokenTagsByPostString(&$post, &$string)
	{
		if ( ! RegEx::match('<(\![^>]*|/?[a-z][^>]*(="[^"]*)?)$', $string, $match))
		{
			return;
		}

		if ( ! RegEx::match('^[^>]*>', $post, $match))
		{
			return;
		}

		$post = substr($post, strlen($match[0]));

		$string .= $match[0];
	}

	/**
	 * Removes html tags from string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function removeHtmlTags($string)
	{
		// remove pagenavcounter
		$string = RegEx::replace('<div class="pagenavcounter">.*?</div>', ' ', $string);
		// remove pagenavbar
		$string = RegEx::replace('<div class="pagenavbar">(<div>.*?</div>)*</div>', ' ', $string);
		// remove inline scripts
		$string = RegEx::replace('<script[^a-z0-9].*?</script>', '', $string);
		$string = RegEx::replace('<noscript[^a-z0-9].*?</noscript>', '', $string);
		// remove inline styles
		$string = RegEx::replace('<style[^a-z0-9].*?</style>', '', $string);
		// remove inline html tags
		$string = RegEx::replace(
			'</?(' . implode('|', self::getInlineElements()) . ')( [^>]*)?>',
			'',
			$string
		);
		// replace other tags with a space
		$string = RegEx::replace('</?[a-z].*?>', ' ', $string);
		// remove double whitespace
		$string = trim(RegEx::replace('(\s)[ ]+', '\1', $string));

		return $string;
	}
}
regularlabs/src/RegEx.php000064400000012670152177723700011402 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class RegEx
 * @package RegularLabs\Library
 */
class RegEx
{
	/**
	 * Perform a regular expression search and replace
	 *
	 * @param string $pattern
	 * @param string $replacement
	 * @param string $string
	 * @param string $options
	 * @param int    $limit
	 * @param int    $count
	 *
	 * @return string
	 */
	public static function replace($pattern, $replacement, $string, $options = null, $limit = -1, &$count = null)
	{
		if ( ! is_string($pattern) || $pattern == '' || ! is_string($string) || $string == '')
		{
			return $string;
		}

		$pattern = self::preparePattern($pattern, $options, $string);

		return preg_replace($pattern, $replacement, $string, $limit, $count);
	}

	/**
	 * Perform a regular expression search and replace once
	 *
	 * @param string $pattern
	 * @param string $replacement
	 * @param string $string
	 * @param string $options
	 *
	 * @return string
	 */
	public static function replaceOnce($pattern, $replacement, $string, $options = null)
	{
		return self::replace($pattern, $replacement, $string, $options, 1);
	}

	/**
	 * Perform a regular expression match
	 *
	 * @param string $pattern
	 * @param string $string
	 * @param null   $matches
	 * @param string $options
	 * @param int    $flags
	 *
	 * @return int
	 */
	public static function match($pattern, $string, &$matches = null, $options = null, $flags = 0)
	{
		if ( ! is_string($pattern) || $pattern == '' || ! is_string($string) || $string == '')
		{
			return false;
		}

		$pattern = self::preparePattern($pattern, $options, $string);

		return preg_match($pattern, $string, $matches, $flags);
	}

	/**
	 * Perform a global regular expression match
	 *
	 * @param string $pattern
	 * @param string $string
	 * @param null   $matches
	 * @param string $options
	 * @param int    $flags
	 *
	 * @return int
	 */
	public static function matchAll($pattern, $string, &$matches = null, $options = null, $flags = PREG_SET_ORDER)
	{
		if ( ! is_string($pattern) || $pattern == '' || ! is_string($string) || $string == '')
		{
			$matches = [];

			return false;
		}

		$pattern = self::preparePattern($pattern, $options, $string);

		return preg_match_all($pattern, $string, $matches, $flags);
	}

	/**
	 * preg_quote the given string or array of strings
	 *
	 * @param string|array $data
	 * @param string       $name
	 * @param string       $delimiter
	 *
	 * @return string
	 */
	public static function quote($data, $name = '', $delimiter = '#', $capture = true)
	{
		if (is_array($data))
		{
			$array = self::quoteArray($data, $delimiter);

			$prefix = '?!';
			if ($capture)
			{
				$prefix = $name ? '?<' . $name . '>' : '';
			}

			return '(' . $prefix . implode('|', $array) . ')';
		}

		if ( ! empty($name))
		{
			return '(?<' . $name . '>' . preg_quote($data, $delimiter) . ')';
		}

		return preg_quote($data, $delimiter);
	}

	/**
	 * reverse preg_quote the given string
	 *
	 * @param string $string
	 * @param string $delimiter
	 *
	 * @return string
	 */
	public static function unquote($string, $delimiter = '#')
	{
		return strtr($string, [
			'\\' . $delimiter => $delimiter,
			'\\.'             => '.',
			'\\\\'            => '\\',
			'\\+'             => '+',
			'\\*'             => '*',
			'\\?'             => '?',
			'\\['             => '[',
			'\\^'             => '^',
			'\\]'             => ']',
			'\\$'             => '$',
			'\\('             => '(',
			'\\)'             => ')',
			'\\{'             => '{',
			'\\}'             => '}',
			'\\='             => '=',
			'\\!'             => '!',
			'\\<'             => '<',
			'\\>'             => '>',
			'\\|'             => '|',
			'\\:'             => ':',
			'\\-'             => '-',
		]);
	}

	/**
	 * preg_quote the given array of strings
	 *
	 * @param array  $array
	 * @param string $delimiter
	 *
	 * @return array
	 */
	public static function quoteArray($array = [], $delimiter = '#')
	{
		array_walk($array, function (&$part, $key, $delimiter) {
			$part = self::quote($part, '', $delimiter);
		}, $delimiter);

		return $array;
	}

	/**
	 * Make a string a valid regular expression pattern
	 *
	 * @param string $pattern
	 * @param string $options
	 * @param string $string
	 *
	 * @return string
	 */
	public static function preparePattern($pattern, $options = null, $string = '')
	{
		if (is_array($pattern))
		{
			return self::preparePatternArray($pattern, $options, $string);
		}

		if (substr($pattern, 0, 1) != '#')
		{
			$pattern = '#' . $pattern . '#';
		}

		$options = ! is_null($options) ? $options : 'si';

		if (substr($pattern, -1, 1) == '#')
		{
			$pattern .= $options;
		}

		if (StringHelper::detectUTF8($string))
		{
			// use utf-8
			return $pattern . 'u';
		}

		return $pattern;
	}

	/**
	 * Make an array of strings valid regular expression patterns
	 *
	 * @param array  $pattern
	 * @param string $options
	 * @param string $string
	 *
	 * @return array
	 */
	private static function preparePatternArray($pattern, $options = null, $string = '')
	{
		array_walk($pattern, function (&$subpattern, $key, $string) {
			$subpattern = self::preparePattern($subpattern, $options = null, $string);
		}, $string);

		return $pattern;
	}
}
regularlabs/src/Date.php000064400000007512152177723700011244 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use DateTimeZone;
use Joomla\CMS\Factory as JFactory;

class Date
{
	/**
	 * Convert string to a correct date format ('00-00-00 00:00:00' or '00-00-00') or null
	 *
	 * @param string $date
	 *
	 * @return null|string
	 */
	public static function fix($date)
	{
		if ( ! $date)
		{
			return null;
		}

		$date = trim($date);

		// Check if date has correct syntax: 00-00-00 00:00:00
		// If so, the date format is correct
		if ( ! RegEx::match('^[0-9]+-[0-9]+-[0-9]+( [0-9][0-9]:[0-9][0-9]:[0-9][0-9])?$', $date))
		{
			return $date;
		}

		// Check if date has syntax: 00-00-00 00:00
		// If so, it is missing the seconds, so add :00 (seconds)
		if (RegEx::match('^[0-9]+-[0-9]+-[0-9]+ [0-9][0-9]:[0-9][0-9]$', $date))
		{
			return $date . ':00';
		}

		// Check if date has a prepending date syntax: 00-00-00
		// If so, it is missing a correct time time, so add 00:00:00 (hours, minutes, seconds)
		if (RegEx::match('^([0-9]+-[0-9]+-[0-9]+)$', $date, $match))
		{
			return $match[1] . ' 00:00:00';
		}

		// Date format is not correct, so return null
		return null;
	}

	/**
	 * Applies offset to a date
	 *
	 * @param string $date
	 * @param string $timezone
	 */
	public static function applyTimezone(&$date, $timezone = '')
	{
		if ($date <= 0)
		{
			$date = 0;

			return;
		}

		$timezone = $timezone ?: JFactory::getUser()->getParam('timezone', JFactory::getConfig()->get('offset'));

		$date = JFactory::getDate($date, $timezone);
		$date->setTimezone(new DateTimeZone('UTC'));

		$date = $date->format('Y-m-d H:i:s', true, false);
	}

	/**
	 * Convert string with 'date' format to 'strftime' format
	 *
	 * @param string $format
	 *
	 * @return string
	 */
	public static function strftimeToDateFormat($format)
	{
		if (strpos($format, '%') === false)
		{
			return $format;
		}

		return strtr((string) $format, self::getStrftimeToDateFormats());
	}

	/**
	 * Convert string with 'date' format to 'strftime' format
	 *
	 * @param string $format
	 *
	 * @return string
	 */
	public static function dateToStrftimeFormat($format)
	{
		return strtr((string) $format, self::getDateToStrftimeFormats());
	}

	private static function getStrftimeToDateFormats()
	{
		return [
			// Day
			'%d'  => 'd',
			'%a'  => 'D',
			'%#d' => 'j',
			'%A'  => 'l',
			'%u'  => 'N',
			'%w'  => 'w',
			'%j'  => 'z',
			// Week
			'%V'  => 'W',
			// Month
			'%B'  => 'F',
			'%m'  => 'm',
			'%b'  => 'M',
			// Year
			'%G'  => 'o',
			'%Y'  => 'Y',
			'%y'  => 'y',
			// Time
			'%P'  => 'a',
			'%p'  => 'A',
			'%l'  => 'g',
			'%I'  => 'h',
			'%H'  => 'H',
			'%M'  => 'i',
			'%S'  => 's',
			// Timezone
			'%z'  => 'O',
			'%Z'  => 'T',
			// Full Date / Time
			'%s'  => 'U',
		];
	}

	private static function getDateToStrftimeFormats()
	{
		return [
			// Day - no strf eq : S
			'd'  => '%d',
			'D'  => '%a',
			'jS' => '%#d[TH]',
			'j'  => '%#d',
			'l'  => '%A',
			'N'  => '%u',
			'w'  => '%w',
			'z'  => '%j',
			// Week - no date eq : %U, %W
			'W'  => '%V',
			// Month - no strf eq : n, t
			'F'  => '%B',
			'm'  => '%m',
			'M'  => '%b',
			// Year - no strf eq : L; no date eq : %C, %g
			'o'  => '%G',
			'Y'  => '%Y',
			'y'  => '%y',
			// Time - no strf eq : B, G, u; no date eq : %r, %R, %T, %X
			'a'  => '%P',
			'A'  => '%p',
			'g'  => '%l',
			'h'  => '%I',
			'H'  => '%H',
			'i'  => '%M',
			's'  => '%S',
			// Timezone - no strf eq : e, I, P, Z
			'O'  => '%z',
			'T'  => '%Z',
			// Full Date / Time - no strf eq : c, r; no date eq : %c, %D, %F, %x
			'U'  => '%s',
		];
	}
}
regularlabs/src/ArrayHelper.php000064400000007525152177723700012611 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class ArrayHelper
 * @package RegularLabs\Library
 */
class ArrayHelper
{
	/**
	 * Convert data (string or object) to an array
	 *
	 * @param mixed  $data
	 * @param string $separator
	 * @param bool   $unique
	 *
	 * @return array
	 */
	public static function toArray($data, $separator = ',', $unique = false, $trim = true)
	{
		if (is_array($data))
		{
			return $data;
		}

		if (is_object($data))
		{
			return (array) $data;
		}

		if ($data == '')
		{
			return [];
		}

		if ($separator == '')
		{
			return [$data];
		}

		// explode on separator, but keep escaped separators
		$splitter = uniqid('RL_SPLIT');
		$data     = str_replace($separator, $splitter, $data);
		$data     = str_replace('\\' . $splitter, $separator, $data);
		$array    = explode($splitter, $data);

		if ($trim)
		{
			$array = self::trim($array);
		}

		if ($unique)
		{
			$array = array_unique($array);
		}

		return $array;
	}

	/**
	 * Join array elements with a string
	 *
	 * @param string $glue
	 * @param array  $pieces
	 *
	 * @return array
	 */
	public static function implode($pieces, $glue = '')
	{
		if ( ! is_array($pieces))
		{
			$pieces = self::toArray($pieces, $glue);
		}

		return implode($glue, $pieces);
	}

	/**
	 * Clean array by trimming values and removing empty/false values
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function clean($array)
	{
		if ( ! is_array($array))
		{
			return $array;
		}

		$array = self::trim($array);
		$array = self::unique($array);
		$array = self::removeEmpty($array);

		return $array;
	}

	/**
	 * Removes empty values from the array
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function removeEmpty($array)
	{
		if ( ! is_array($array))
		{
			return $array;
		}

		foreach ($array as $key => &$value)
		{
			if ($key && ! is_numeric($key))
			{
				continue;
			}

			if ($value !== '')
			{
				continue;
			}

			unset($array[$key]);
		}

		return $array;
	}

	/**
	 * Removes duplicate values from the array
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function unique($array)
	{
		if ( ! is_array($array))
		{
			return $array;
		}

		$values = [];

		foreach ($array as $key => $value)
		{
			if ( ! is_numeric($key))
			{
				continue;
			}

			if ( ! in_array($value, $values))
			{
				$values[] = $value;
				continue;
			}

			unset($array[$key]);
		}

		return $array;
	}

	/**
	 * Clean array by trimming values
	 *
	 * @param array $array
	 *
	 * @return array
	 */
	public static function trim($array)
	{
		if ( ! is_array($array))
		{
			return $array;
		}

		foreach ($array as &$value)
		{
			if ( ! is_string($value))
			{
				continue;
			}

			$value = trim($value);
		}

		return $array;
	}

	/**
	 * Check if any of the given values is found in the array
	 *
	 * @param array $values
	 * @param array $array
	 *
	 * @return boolean
	 */
	public static function find($values, $array)
	{
		if ( ! is_array($array) || empty($array))
		{
			return false;
		}

		$values = self::toArray($values);

		foreach ($values as $value)
		{
			if (in_array($value, $array))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Sorts the array by keys based on the values of another array
	 *
	 * @param array $array
	 * @param array $order
	 *
	 * @return array
	 */
	public static function sortByOtherArray($array, $order)
	{
		uksort($array, function ($key1, $key2) use ($order) {
			return (array_search($key1, $order) > array_search($key2, $order));
		});

		return $array;
	}
}
regularlabs/src/ShowOn.php000064400000002501152177723700011575 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

use Joomla\CMS\Form\FormHelper as JFormHelper;
use RegularLabs\Library\Document as RL_Document;

defined('_JEXEC') or die;

/**
 * Class ShowOn
 * @package RegularLabs\Library
 */
class ShowOn
{
	public static function open($condition = '', $formControl = '', $group = '', $class = '')
	{
		if ( ! $condition)
		{
			return self::close();
		}

		RL_Document::loadFormDependencies();

		$json = json_encode(JFormHelper::parseShowOnConditions($condition, $formControl, $group));

		$class = $class ? ' class="' . $class . '"' : '';

		return '<div data-showon=\'' . $json . '\' style="display: none;"' . $class . '>';
	}

	public static function close()
	{
		return '</div>';
	}

	public static function show($string = '', $condition = '', $formControl = '', $group = '', $animate = true, $class = '')
	{
		if ( ! $condition || ! $string)
		{
			return $string;
		}

		return self::open($condition, $formControl, $group, $animate, $class)
			. $string
			. self::close();
	}
}
regularlabs/src/Api/ConditionInterface.php000064400000001040152177723700014635 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Api;

defined('_JEXEC') or die;

/**
 * Interface ConditionConditionInterface
 * @package RegularLabs\Library\Api
 */
interface ConditionInterface
{
	public function pass();
}
regularlabs/src/EditorButton.php000064400000001003152177723700012776 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * @deprecated  2018-11-14  Use EditorButtonPlugin instead
 */
class EditorButton
	extends EditorButtonPlugin
{
}
regularlabs/src/Condition/UserUser.php000064400000001173152177723700014067 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class UserUser
 * @package RegularLabs\Library\Condition
 */
class UserUser
	extends User
{
	public function pass()
	{
		return $this->passSimple(JFactory::getUser()->get('id'));
	}
}
regularlabs/src/Condition/VirtuemartCategory.php000064400000005421152177723700016152 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\RegEx;

/**
 * Class VirtuemartCategory
 * @package RegularLabs\Library\Condition
 */
class VirtuemartCategory
	extends Virtuemart
{
	public function pass()
	{
		if ($this->request->option != 'com_virtuemart')
		{
			return $this->_(false);
		}

		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		$pass = (($this->params->inc_categories && in_array($this->request->view, ['categories', 'category']))
			|| ($this->params->inc_items && $this->request->view == 'productdetails')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = [];
		if ($this->request->view == 'productdetails' && $this->request->item_id)
		{
			$query = $this->db->getQuery(true)
				->select('x.virtuemart_category_id')
				->from('#__virtuemart_product_categories AS x')
				->where('x.virtuemart_product_id = ' . (int) $this->request->item_id);
			$this->db->setQuery($query);
			$cats = $this->db->loadColumn();
		}
		else if ($this->request->category_id)
		{
			$cats = $this->request->category_id;
			if ( ! is_numeric($cats))
			{
				$query = $this->db->getQuery(true)
					->select('config')
					->from('#__virtuemart_configs')
					->where('virtuemart_config_id = 1');
				$this->db->setQuery($query);
				$config = $this->db->loadResult();
				$lang   = substr($config, strpos($config, 'vmlang='));
				$lang   = substr($lang, 0, strpos($lang, '|'));
				if (RegEx::match('"([^"]*_[^"]*)"', $lang, $lang))
				{
					$lang = $lang[1];
				}
				else
				{
					$lang = 'en_gb';
				}

				$query = $this->db->getQuery(true)
					->select('l.virtuemart_category_id')
					->from('#__virtuemart_categories_' . $lang . ' AS l')
					->where('l.slug = ' . $this->db->quote($cats));
				$this->db->setQuery($query);
				$cats = $this->db->loadResult();
			}
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'virtuemart_category_categories', 'category_parent_id', 'category_child_id');
	}
}
regularlabs/src/Condition/EasyblogItem.php000064400000002055152177723700014676 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class EasyblogItem
 * @package RegularLabs\Library\Condition
 */
class EasyblogItem
	extends Easyblog
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_easyblog' || $this->request->view != 'entry')
		{
			return $this->_(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentId'))
		{
			return $this->_(false);
		}

		// Pass Content Keywords
		if ( ! $this->passItemByType($pass, 'ContentKeyword'))
		{
			return $this->_(false);
		}

		// Pass Author
		if ( ! $this->passItemByType($pass, 'Author'))
		{
			return $this->_(false);
		}

		return $this->_($pass);
	}
}
regularlabs/src/Condition/Zoo.php000064400000002635152177723700013065 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Zoo
 * @package RegularLabs\Library\Condition
 */
abstract class Zoo
	extends \RegularLabs\Library\Condition
{
	use \RegularLabs\Library\ConditionContent;

	public function initRequest(&$request)
	{
		$request->view = $request->task ?: $request->view;

		switch ($request->view)
		{
			case 'item':
				$request->idname = 'item_id';
				break;
			case 'category':
				$request->idname = 'category_id';
				break;
		}

		if ( ! isset($request->idname))
		{
			$request->idname = '';
		}

		switch ($request->idname)
		{
			case 'item_id':
				$request->view = 'item';
				break;
			case 'category_id':
				$request->view = 'category';
				break;
		}

		$request->id = JFactory::getApplication()->input->getInt($request->idname, 0);
	}

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__zoo_item')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

}
regularlabs/src/Condition/GeoCountry.php000064400000001311152177723700014402 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class GeoCountry
 * @package RegularLabs\Library\Condition
 */
class GeoCountry
	extends Geo
{
	public function pass()
	{
		if ( ! $this->getGeo() || empty($this->geo->countryCode))
		{
			return $this->_(false);
		}

		return $this->passSimple([$this->geo->country, $this->geo->countryCode]);
	}
}
regularlabs/src/Condition/DateDate.php000064400000004012152177723700013760 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use RegularLabs\Library\Date as RL_Date;

/**
 * Class DateDate
 * @package RegularLabs\Library\Condition
 */
class DateDate
	extends Date
{
	public function pass()
	{
		if ( ! $this->params->publish_up && ! $this->params->publish_down)
		{
			// no date range set
			return ($this->include_type == 'include');
		}

		RL_Date::fix($this->params->publish_up);
		RL_Date::fix($this->params->publish_down);

		$now  = $this->getNow();
		$up   = $this->getDate($this->params->publish_up);
		$down = $this->getDate($this->params->publish_down);

		if (isset($this->params->recurring) && $this->params->recurring)
		{
			if ( ! (int) $this->params->publish_up || ! (int) $this->params->publish_down)
			{
				// no date range set
				return ($this->include_type == 'include');
			}

			$up   = strtotime(date('Y') . $up->format('-m-d H:i:s', true));
			$down = strtotime(date('Y') . $down->format('-m-d H:i:s', true));

			// pass:
			// 1) now is between up and down
			// 2) up is later in year than down and:
			// 2a) now is after up
			// 2b) now is before down
			if (
				($up < $now && $down > $now)
				|| ($up > $down
					&& (
						$up < $now
						|| $down > $now
					)
				)
			)
			{
				return ($this->include_type == 'include');
			}

			// outside date range
			return $this->_(false);
		}

		if (
			(
				(int) $this->params->publish_up
				&& strtotime($up->format('Y-m-d H:i:s', true)) > $now
			)
			|| (
				(int) $this->params->publish_down
				&& strtotime($down->format('Y-m-d H:i:s', true)) < $now
			)
		)
		{
			// outside date range
			return $this->_(false);
		}

		// pass
		return ($this->include_type == 'include');
	}
}
regularlabs/src/Condition/K2Pagetype.php000064400000001463152177723700014267 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class K2Pagetype
 * @package RegularLabs\Library\Condition
 */
class K2Pagetype
	extends K2
{
	public function pass()
	{
		// K2 messes with the task in the request, so we have to reset the task
		$this->request->task = JFactory::getApplication()->input->get('task');

		return $this->passByPageType('com_k2', $this->selection, $this->include_type, false, true);
	}
}
regularlabs/src/Condition/ContentCategory.php000064400000007710152177723700015425 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use ContentsubmitModelArticle;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Table\Table as JTable;

/**
 * Class ContentCategory
 * @package RegularLabs\Library\Condition
 */
class ContentCategory
	extends Content
{
	public function pass()
	{
		// components that use the com_content secs/cats
		$components = ['com_content', 'com_flexicontent', 'com_contentsubmit'];
		if ( ! in_array($this->request->option, $components))
		{
			return $this->_(false);
		}

		if (empty($this->selection))
		{
			return $this->_(false);
		}

		$is_content  = in_array($this->request->option, ['com_content', 'com_flexicontent']);
		$is_category = in_array($this->request->view, ['category']);
		$is_item     = in_array($this->request->view, ['', 'article', 'item', 'form']);

		if (
			$this->request->option != 'com_contentsubmit'
			&& ! ($this->params->inc_categories && $is_content && $is_category)
			&& ! ($this->params->inc_articles && $is_content && $is_item)
			&& ! ($this->params->inc_others && ! ($is_content && ($is_category || $is_item)))
		)
		{
			return $this->_(false);
		}

		if ($this->request->option == 'com_contentsubmit')
		{
			// Content Submit
			$contentsubmit_params = new ContentsubmitModelArticle;
			if (in_array($contentsubmit_params->_id, $this->selection))
			{
				return $this->_(true);
			}

			return $this->_(false);
		}

		$pass = false;
		if (
			$this->params->inc_others
			&& ! ($is_content && ($is_category || $is_item))
			&& $this->article
		)
		{
			if ( ! isset($this->article->id) && isset($this->article->slug))
			{
				$this->article->id = (int) $this->article->slug;
			}

			if ( ! isset($this->article->catid) && isset($this->article->catslug))
			{
				$this->article->catid = (int) $this->article->catslug;
			}

			$this->request->id   = $this->article->id;
			$this->request->view = 'article';
		}

		$catids = $this->getCategoryIds($is_category);

		foreach ($catids as $catid)
		{
			if ( ! $catid)
			{
				continue;
			}

			$pass = in_array($catid, $this->selection);

			if ($pass && $this->params->inc_children == 2)
			{
				$pass = false;
				continue;
			}

			if ( ! $pass && $this->params->inc_children)
			{
				$parent_ids = $this->getCatParentIds($catid);
				$parent_ids = array_diff($parent_ids, [1]);
				foreach ($parent_ids as $id)
				{
					if (in_array($id, $this->selection))
					{
						$pass = true;
						break;
					}
				}

				unset($parent_ids);
			}
		}

		return $this->_($pass);
	}

	private function getCategoryIds($is_category = false)
	{
		if ($is_category)
		{
			return (array) $this->request->id;
		}

		if ( ! $this->article && $this->request->id)
		{
			$this->article = JTable::getInstance('content');
			$this->article->load($this->request->id);
		}

		if ($this->article && isset($this->article->catid))
		{
			return (array) $this->article->catid;
		}

		$app = JFactory::getApplication();

		$catid = $app->getUserState('com_content.edit.article.data.catid');
		if ( ! $catid)
		{
			$catid = $app->getUserState('com_content.articles.filter.category_id');
		}
		if ( ! $catid)
		{
			$catid = JFactory::getApplication()->input->getInt('catid');
		}

		$menuparams = $this->getMenuItemParams($this->request->Itemid);

		if ($this->request->view == 'featured')
		{
			$menuparams = $this->getMenuItemParams($this->request->Itemid);

			return isset($menuparams->featured_categories) ? (array) $menuparams->featured_categories : (array) $catid;
		}

		return isset($menuparams->catid) ? (array) $menuparams->catid : (array) $catid;
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'categories');
	}
}
regularlabs/src/Condition/AgentBrowser.php000064400000001415152177723700014713 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class AgentBrowser
 * @package RegularLabs\Library\Condition
 */
class AgentBrowser
	extends Agent
{
	public function pass()
	{
		if (empty($this->selection))
		{
			return $this->_(false);
		}

		foreach ($this->selection as $browser)
		{
			if ( ! $this->passBrowser($browser))
			{
				continue;
			}

			return $this->_(true);
		}

		return $this->_(false);
	}
}
regularlabs/src/Condition/Ip.php000064400000006473152177723700012672 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Ip
 * @package RegularLabs\Library\Condition
 */
class Ip
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		if (is_array($this->selection))
		{
			$this->selection = implode(',', $this->selection);
		}

		$this->selection = explode(',', str_replace([' ', "\r", "\n"], ['', '', ','], $this->selection));

		$pass = $this->checkIPList();

		return $this->_($pass);
	}

	private function checkIPList()
	{
		foreach ($this->selection as $range)
		{
			// Check next range if this one doesn't match
			if ( ! $this->checkIP($range))
			{
				continue;
			}

			// Match found, so return true!
			return true;
		}

		// No matches found, so return false
		return false;
	}

	private function checkIP($range)
	{
		if (empty($range))
		{
			return false;
		}

		if (strpos($range, '-') !== false)
		{
			// Selection is an IP range
			return $this->checkIPRange($range);
		}

		// Selection is a single IP (part)
		return $this->checkIPPart($range);
	}

	private function checkIPRange($range)
	{
		$ip = $this->getIP();

		// Return if no IP address can be found (shouldn't happen, but who knows)
		if (empty($ip))
		{
			return false;
		}

		// check if IP is between or equal to the from and to IP range
		list($min, $max) = explode('-', trim($range), 2);

		// Return false if IP is smaller than the range start
		if ($ip < trim($min))
		{
			return false;
		}

		$max = $this->fillMaxRange($max, $min);

		// Return false if IP is larger than the range end
		if ($ip > trim($max))
		{
			return false;
		}

		return true;
	}

	/* Fill the max range by prefixing it with the missing parts from the min range
	 * So 101.102.103.104-201.202 becomes:
	 * max: 101.102.201.202
	 */
	private function fillMaxRange($max, $min)
	{
		$max_parts = explode('.', $max);

		if (count($max_parts) == 4)
		{
			return $max;
		}

		$min_parts = explode('.', $min);

		$prefix = array_slice($min_parts, 0, count($min_parts) - count($max_parts));

		return implode('.', $prefix) . '.' . implode('.', $max_parts);
	}

	private function checkIPPart($range)
	{
		$ip = $this->getIP();

		// Return if no IP address can be found (shouldn't happen, but who knows)
		if (empty($ip))
		{
			return false;
		}

		$ip_parts    = explode('.', $ip);
		$range_parts = explode('.', trim($range));

		// Trim the IP to the part length of the range
		$ip = implode('.', array_slice($ip_parts, 0, count($range_parts)));

		// Return false if ip does not match the range
		if ($range != $ip)
		{
			return false;
		}

		return true;
	}

	private function getIP()
	{
		if ( ! empty($_SERVER['HTTP_X_REAL_IP']) && $this->isValidIp($_SERVER['HTTP_X_REAL_IP']))
		{
			return $_SERVER['HTTP_X_REAL_IP'];
		}

		if ( ! empty($_SERVER['HTTP_CLIENT_IP']) && $this->isValidIp($_SERVER['HTTP_CLIENT_IP']))
		{
			$_SERVER['HTTP_CLIENT_IP'];
		}

		return $_SERVER['REMOTE_ADDR'];
	}

	private function isValidIp($string)
	{
		return preg_match('#^([0-9]{1,3}\.){3}[0-9]{1,3}$#', $string);
	}
}
regularlabs/src/Condition/EasyblogPagetype.php000064400000001205152177723700015552 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class EasyblogPagetype
 * @package RegularLabs\Library\Condition
 */
class EasyblogPagetype
	extends Easyblog
{
	public function pass()
	{
		return $this->passByPageType('com_easyblog', $this->selection, $this->include_type);
	}
}
regularlabs/src/Condition/Mijoshop.php000064400000002535152177723700014105 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use MijoShop as MijoShopClass;

/**
 * Class Mijoshop
 * @package RegularLabs\Library\Condition
 */
abstract class Mijoshop
	extends \RegularLabs\Library\Condition
{
	public function initRequest(&$request)
	{
		$input = JFactory::getApplication()->input;

		$category_id = $input->getCmd('path', 0);

		if (strpos($category_id, '_'))
		{
			$category_id = end(explode('_', $category_id));
		}

		$request->item_id     = $input->getInt('product_id', 0);
		$request->category_id = $category_id;
		$request->id          = $request->item_id ?: $request->category_id;

		$view = $input->getCmd('view', '');

		if (empty($view))
		{
			$mijoshop = JPATH_ROOT . '/components/com_mijoshop/mijoshop/mijoshop.php';

			if ( ! file_exists($mijoshop))
			{
				return;
			}

			require_once $mijoshop;

			$route = $input->getString('route', '');
			$view  = MijoShopClass::get('router')->getView($route);
		}

		$request->view = $view;
	}
}
regularlabs/src/Condition/RedshopProduct.php000064400000001352152177723700015256 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class RedshopProduct
 * @package RegularLabs\Library\Condition
 */
class RedshopProduct
	extends Redshop
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_redshop' || $this->request->view != 'product')
		{
			return $this->_(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/src/Condition/EasyblogKeyword.php000064400000001114152177723700015417 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class EasyblogKeyword
 * @package RegularLabs\Library\Condition
 */
class EasyblogKeyword
	extends Easyblog
{
	public function pass()
	{
		parent::passContentKeyword();
	}
}
regularlabs/src/Condition/RedshopPagetype.php000064400000001207152177723700015413 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class RedshopPagetype
 * @package RegularLabs\Library\Condition
 */
class RedshopPagetype
	extends Redshop
{
	public function pass()
	{
		return $this->passByPageType('com_redshop', $this->selection, $this->include_type, true);
	}
}
regularlabs/src/Condition/User.php000064400000001027152177723700013226 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class User
 * @package RegularLabs\Library\Condition
 */
abstract class User
	extends \RegularLabs\Library\Condition
{
}
regularlabs/src/Condition/Form2contentProject.php000064400000001726152177723700016225 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Form2contentProject
 * @package RegularLabs\Library\Condition
 */
class Form2contentProject
	extends Form2content
{
	public function pass()
	{
		if ($this->request->option != 'com_content' && $this->request->view == 'article')
		{
			return $this->_(false);
		}

		$query = $this->db->getQuery(true)
			->select('c.projectid')
			->from('#__f2c_form AS c')
			->where('c.reference_id = ' . (int) $this->request->id);
		$this->db->setQuery($query);
		$type = $this->db->loadResult();

		$types = $this->makeArray($type);

		return $this->passSimple($types);
	}
}
regularlabs/src/Condition/K2Tag.php000064400000002603152177723700013221 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class K2Tag
 * @package RegularLabs\Library\Condition
 */
class K2Tag
	extends K2
{
	public function pass()
	{
		if ($this->request->option != 'com_k2')
		{
			return $this->_(false);
		}

		$tag  = trim(JFactory::getApplication()->input->getString('tag', ''));
		$pass = (
			($this->params->inc_tags && $tag != '')
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		if ($this->params->inc_tags && $tag != '')
		{
			$tags = [trim(JFactory::getApplication()->input->getString('tag', ''))];

			return $this->passSimple($tags, true);
		}

		$query = $this->db->getQuery(true)
			->select('t.name')
			->from('#__k2_tags_xref AS x')
			->join('LEFT', '#__k2_tags AS t ON t.id = x.tagID')
			->where('x.itemID = ' . (int) $this->request->id)
			->where('t.published = 1');
		$this->db->setQuery($query);
		$tags = $this->db->loadColumn();

		return $this->passSimple($tags, true);
	}
}
regularlabs/src/Condition/Hikashop.php000064400000002045152177723700014057 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Hikashop
 * @package RegularLabs\Library\Condition
 */
abstract class Hikashop
	extends \RegularLabs\Library\Condition
{
	public function beforePass()
	{
		$input = JFactory::getApplication()->input;

		// Reset $this->request because HikaShop messes with the view after stuff is loaded!
		$this->request->option = $input->get('option', $this->request->option);
		$this->request->view   = $input->get('view', $input->get('ctrl', $this->request->view));
		$this->request->id     = $input->getInt('id', $this->request->id);
		$this->request->Itemid = $input->getInt('Itemid', $this->request->Itemid);
	}
}
regularlabs/src/Condition/DateDay.php000064400000001215152177723700013622 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class DateDay
 * @package RegularLabs\Library\Condition
 */
class DateDay
	extends Date
{
	public function pass()
	{
		$day = $this->date->format('N', true); // 1 (for Monday) though 7 (for Sunday )

		return $this->passSimple($day);
	}
}
regularlabs/src/Condition/EasyblogCategory.php000064400000003513152177723700015555 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class EasyblogCategory
 * @package RegularLabs\Library\Condition
 */
class EasyblogCategory
	extends Easyblog
{
	public function pass()
	{
		if ($this->request->option != 'com_easyblog')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_categories && $this->request->view == 'categories')
			|| ($this->params->inc_items && $this->request->view == 'entry')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = $this->makeArray($this->getCategories());

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		switch ($this->request->view)
		{
			case 'entry' :
				return $this->getCategoryIDFromItem();
				break;

			case 'categories' :
				return $this->request->id;
				break;

			default:
				return '';
		}
	}

	private function getCategoryIDFromItem()
	{
		$query = $this->db->getQuery(true)
			->select('i.category_id')
			->from('#__easyblog_post AS i')
			->where('i.id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadResult();
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'easyblog_category', 'parent_id');
	}
}
regularlabs/src/Condition/Cookieconfirm.php000064400000001404152177723700015076 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use PlgSystemCookieconfirmCore;

/**
 * Class Cookieconfirm
 * @package RegularLabs\Library\Condition
 */
class Cookieconfirm
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		require_once JPATH_PLUGINS . '/system/cookieconfirm/core.php';
		$pass = PlgSystemCookieconfirmCore::getInstance()->isCookiesAllowed();

		return $this->_($pass);
	}
}
regularlabs/src/Condition/ContentPagetype.php000064400000001602152177723700015420 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class ContentPagetype
 * @package RegularLabs\Library\Condition
 */
class ContentPagetype
	extends Content
{
	public function pass()
	{
		$components = ['com_content', 'com_contentsubmit'];

		if ( ! in_array($this->request->option, $components))
		{
			return $this->_(false);
		}
		if ($this->request->view == 'category' && $this->request->layout == 'blog')
		{
			$view = 'categoryblog';
		}
		else
		{
			$view = $this->request->view;
		}

		return $this->passSimple($view);
	}
}
regularlabs/src/Condition/Agent.php000064400000005354152177723700013355 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use RegularLabs\Library\MobileDetect;
use RegularLabs\Library\RegEx;

/**
 * Class Agent
 * @package RegularLabs\Library\Condition
 */
abstract class Agent
	extends \RegularLabs\Library\Condition
{
	var $agent     = null;
	var $device    = null;
	var $is_mobile = false;

	/**
	 * isPhone
	 */
	public function isPhone()
	{
		return $this->isMobile();
	}

	/**
	 * isMobile
	 */
	public function isMobile()
	{
		return $this->getDevice() == 'mobile';
	}

	/**
	 * isTablet
	 */
	public function isTablet()
	{
		return $this->getDevice() == 'tablet';
	}

	/**
	 * isDesktop
	 */
	public function isDesktop()
	{
		return $this->getDevice() == 'desktop';
	}

	/**
	 * passBrowser
	 */
	public function passBrowser($browser = '')
	{
		if ( ! $browser)
		{
			return false;
		}

		if ($browser == 'mobile')
		{
			return $this->isMobile();
		}

		// also check for _ instead of .
		$browser = RegEx::replace('\\\.([^\]])', '[\._]\1', $browser);
		$browser = str_replace('\.]', '\._]', $browser);

		return RegEx::match($browser, $this->getAgent(), $match, 'i');
	}

	/**
	 * setDevice
	 */
	private function getDevice()
	{
		if ( ! is_null($this->device))
		{
			return $this->device;
		}

		$detect = new MobileDetect;

		$this->is_mobile = $detect->isMobile();

		switch (true)
		{
			case($detect->isTablet()):
				$this->device = 'tablet';
				break;

			case ($detect->isMobile()):
				$this->device = 'mobile';
				break;

			default:
				$this->device = 'desktop';
		}

		return $this->device;
	}

	/**
	 * getAgent
	 */
	private function getAgent()
	{
		if ( ! is_null($this->agent))
		{
			return $this->agent;
		}

		$detect = new MobileDetect;
		$agent  = $detect->getUserAgent();

		switch (true)
		{
			case (stripos($agent, 'Trident') !== false):
				// Add MSIE to IE11 and others missing it
				$agent = RegEx::replace('(Trident/[0-9\.]+;.*rv[: ]([0-9\.]+))', '\1 MSIE \2', $agent);
				break;

			case (stripos($agent, 'Chrome') !== false):
				// Remove Safari from Chrome
				$agent = RegEx::replace('(Chrome/.*)Safari/[0-9\.]*', '\1', $agent);
				// Add MSIE to IE Edge and remove Chrome from IE Edge
				$agent = RegEx::replace('Chrome/.*(Edge/[0-9])', 'MSIE \1', $agent);
				break;

			case (stripos($agent, 'Opera') !== false):
				$agent = RegEx::replace('(Opera/.*)Version/', '\1Opera/', $agent);
				break;
		}

		$this->agent = $agent;

		return $this->agent;
	}
}
regularlabs/src/Condition/ZooPagetype.php000064400000001161152177723700014555 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class ZooPagetype
 * @package RegularLabs\Library\Condition
 */
class ZooPagetype
	extends Zoo
{
	public function pass()
	{
		return $this->passByPageType('com_zoo', $this->selection, $this->include_type);
	}
}
regularlabs/src/Condition/AgentDevice.php000064400000001411152177723700014463 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class AgentDevice
 * @package RegularLabs\Library\Condition
 */
class AgentDevice
	extends Agent
{
	public function pass()
	{
		$pass = (in_array('mobile', $this->selection) && $this->isMobile())
			|| (in_array('tablet', $this->selection) && $this->isTablet())
			|| (in_array('desktop', $this->selection) && $this->isDesktop());

		return $this->_($pass);
	}
}
regularlabs/src/Condition/Tag.php000064400000006073152177723700013031 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Tag
 * @package RegularLabs\Library\Condition
 */
class Tag
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		if (! $this->request->id)
		{
			return $this->_(false);
		}

		if (in_array($this->request->option, ['com_content', 'com_flexicontent']))
		{
			return $this->passTagsContent();
		}

		if ($this->request->option != 'com_tags'
			|| $this->request->view != 'tag'
		)
		{
			return $this->_(false);
		}

		return $this->passTag($this->request->id);
	}

	private function passTagsContent()
	{
		$is_item     = in_array($this->request->view, ['', 'article', 'item']);
		$is_category = in_array($this->request->view, ['category']);

		switch (true)
		{
			case ($is_item):
				$prefix = 'com_content.article';
				break;

			case ($is_category):
				$prefix = 'com_content.category';
				break;

			default:
				return $this->_(false);
		}

		// Load the tags.
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('t.id'))
			->select($this->db->quoteName('t.title'))
			->from('#__tags AS t')
			->join(
				'INNER', '#__contentitem_tag_map AS m'
				. ' ON m.tag_id = t.id'
				. ' AND m.type_alias = ' . $this->db->quote($prefix)
				. ' AND m.content_item_id = ' . (int) $this->request->id
			);
		$this->db->setQuery($query);
		$tags = $this->db->loadObjectList();

		if (empty($tags))
		{
			return $this->_(false);
		}

		return $this->_($this->passTagList($tags));
	}

	private function passTagList($tags)
	{
		if ($this->params->match_all)
		{
			return $this->passTagListMatchAll($tags);
		}

		foreach ($tags as $tag)
		{
			if ( ! $this->passTag($tag->id) && ! $this->passTag($tag->title))
			{
				continue;
			}

			return true;
		}

		return false;
	}

	private function passTag($tag)
	{
		$pass = in_array($tag, $this->selection);

		if ($pass)
		{
			// If passed, return false if assigned to only children
			// Else return true
			return ($this->params->inc_children != 2);
		}

		if ( ! $this->params->inc_children)
		{
			return false;
		}

		// Return true if a parent id is present in the selection
		return array_intersect(
			$this->getTagsParentIds($tag),
			$this->selection
		);
	}

	private function getTagsParentIds($id = 0)
	{
		$parentids = $this->getParentIds($id, 'tags');
		// Remove the root tag
		$parentids = array_diff($parentids, [1]);

		return $parentids;
	}

	private function passTagListMatchAll($tags)
	{
		foreach ($this->selection as $id)
		{
			if ( ! $this->passTagMatchAll($id, $tags))
			{
				return false;
			}
		}

		return true;
	}

	private function passTagMatchAll($id, $tags)
	{

		foreach ($tags as $tag)
		{
			if ($tag->id == $id || $tag->title == $id)
			{
				return true;
			}
		}

		return false;
	}
}
regularlabs/src/Condition/Menu.php000064400000004436152177723700013223 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\Document as RL_Document;

/**
 * Class Menu
 * @package RegularLabs\Library\Condition
 */
class Menu
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		// return if no Itemid or selection is set
		if ( ! $this->request->Itemid || empty($this->selection))
		{
			return $this->_($this->params->inc_noitemid);
		}

		// return true if menu is in selection
		if (in_array($this->request->Itemid, $this->selection))
		{
			return $this->_(($this->params->inc_children != 2));
		}

		$menutype = 'type.' . self::getMenuType();

		// return true if menu type is in selection
		if (in_array($menutype, $this->selection))
		{
			return $this->_(true);
		}

		if ( ! $this->params->inc_children)
		{
			return $this->_(false);
		}

		$parent_ids = $this->getMenuParentIds($this->request->Itemid);
		$parent_ids = array_diff($parent_ids, [1]);
		foreach ($parent_ids as $id)
		{
			if ( ! in_array($id, $this->selection))
			{
				continue;
			}

			return $this->_(true);
		}

		return $this->_(false);
	}

	private function getMenuParentIds($id = 0)
	{
		return $this->getParentIds($id, 'menu');
	}

	private function getMenuType()
	{
		if (isset($this->request->menutype))
		{
			return $this->request->menutype;
		}

		if (empty($this->request->Itemid))
		{
			$this->request->menutype = '';

			return $this->request->menutype;
		}

		if (RL_Document::isClient('site'))
		{
			$menu = JFactory::getApplication()->getMenu()->getItem((int) $this->request->Itemid);

			$this->request->menutype = isset($menu->menutype) ? $menu->menutype : '';

			return $this->request->menutype;
		}

		$query = $this->db->getQuery(true)
			->select('m.menutype')
			->from('#__menu AS m')
			->where('m.id = ' . (int) $this->request->Itemid);
		$this->db->setQuery($query);
		$this->request->menutype = $this->db->loadResult();

		return $this->request->menutype;
	}
}
regularlabs/src/Condition/AgentOs.php000064400000001033152177723700013645 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class AgentOs
 * @package RegularLabs\Library\Condition
 */
class AgentOs
	extends AgentBrowser
{
	// Same as AgentBrowser
}
regularlabs/src/Condition/K2.php000064400000001752152177723700012571 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

// If controller.php exists, assume this is K2 v3
defined('RL_K2_VERSION') or define('RL_K2_VERSION', file_exists(JPATH_ADMINISTRATOR . '/components/com_k2/controller.php') ? 3 : 2);

/**
 * Class K2
 * @package RegularLabs\Library\Condition
 */
abstract class K2
	extends \RegularLabs\Library\Condition
{
	use \RegularLabs\Library\ConditionContent;

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__k2_items')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}
}
regularlabs/src/Condition/AkeebasubsLevel.php000064400000001360152177723700015345 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class AkeebasubsLevel
 * @package RegularLabs\Library\Condition
 */
class AkeebasubsLevel
	extends Akeebasubs
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_akeebasubs' || $this->request->view != 'level')
		{
			return $this->_(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/src/Condition/Php.php000064400000007737152177723700013055 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Filesystem\File as JFile;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;
use RegularLabs\Library\RegEx;

/**
 * Class Php
 * @package RegularLabs\Library\Condition
 */
class Php
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		if ( ! is_array($this->selection))
		{
			$this->selection = [$this->selection];
		}

		$pass = false;
		foreach ($this->selection as $php)
		{
			// replace \n with newline and other fix stuff
			$php = str_replace('\|', '|', $php);
			$php = RegEx::replace('(?<!\\\)\\\n', "\n", $php);
			$php = trim(str_replace('[:REGEX_ENTER:]', '\n', $php));

			if ($php == '')
			{
				$pass = true;
				break;
			}

			ob_start();
			$pass = (bool) $this->execute($php, $this->article, $this->module);
			ob_end_clean();

			if ($pass)
			{
				break;
			}
		}

		return $this->_($pass);
	}

	private function getArticleById($id = 0)
	{
		if ( ! $id)
		{
			return null;
		}

		if ( ! class_exists('ContentModelArticle'))
		{
			require_once JPATH_SITE . '/components/com_content/models/article.php';
		}

		$model = JModel::getInstance('article', 'contentModel');

		if ( ! method_exists($model, 'getItem'))
		{
			return null;
		}

		return $model->getItem($this->request->id);
	}

	public function execute($string = '', $article = null, $module = null)
	{
		if ( ! $function_name = $this->getFunctionName($string))
		{
			// Something went wrong!
			return true;
		}

		return $this->runFunction($function_name, $string, $article, $module);
	}

	private function runFunction($function_name = 'rl_function', $string = '', $article = null, $module = null)
	{
		if ( ! $article && strpos($string, '$article') !== false)
		{
			if ($this->request->option == 'com_content' && $this->request->view == 'article')
			{
				$article = $this->getArticleById($this->request->id);
			}
		}

		return $function_name($article, $module);
	}

	private function getFunctionName($string = '')
	{
		$function_name = 'regularlabs_php_' . md5($string);

		if (function_exists($function_name))
		{
			return $function_name;
		}

		$this->createFunctionInMemory($function_name, $string);

		if ( ! function_exists($function_name))
		{
			// Something went wrong!
			return false;
		}

		return $function_name;
	}

	private function createFunctionInMemory($function_name = 'rl_function', $string = '')
	{
		$contents = $this->generateFileContents($function_name, $string);

		$folder    = JFactory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp');
		$temp_file = $folder . '/' . $function_name;

		// Write file
		JFile::write($temp_file, $contents);

		// Include file
		include_once $temp_file;

		// Delete file
		if ( ! defined('JDEBUG') || ! JDEBUG)
		{
			@chmod($temp_file, 0777);
			@unlink($temp_file);
		}
	}

	private function generateFileContents($function_name = 'rl_function', $string = '')
	{
		$init_variables = $this->getVarInits();

		$contents = [
			'<?php',
			'defined(\'_JEXEC\') or die;',
			'function ' . $function_name . '($article, $module){',
			implode("\n", $init_variables),
			$string,
			';return true;',
			';}',
		];

		$contents = implode("\n", $contents);

		// Remove Zero Width spaces / (non-)joiners
		$contents = str_replace(
			[
				"\xE2\x80\x8B",
				"\xE2\x80\x8C",
				"\xE2\x80\x8D",
			],
			'',
			$contents
		);

		return $contents;
	}

	private function getVarInits()
	{
		return [
			'$app = $mainframe = JFactory::getApplication();',
			'$document = $doc = JFactory::getDocument();',
			'$database = $db = JFactory::getDbo();',
			'$user = JFactory::getUser();',
			'$Itemid = $app->input->getInt(\'Itemid\');',
		];
	}
}
regularlabs/src/Condition/RedshopCategory.php000064400000003353152177723700015416 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class RedshopCategory
 * @package RegularLabs\Library\Condition
 */
class RedshopCategory
	extends Redshop
{
	public function pass()
	{
		if ($this->request->option != 'com_redshop')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = [];
		if ($this->request->category_id)
		{
			$cats = $this->request->category_id;
		}
		else if ($this->request->item_id)
		{
			$query = $this->db->getQuery(true)
				->select('x.category_id')
				->from('#__redshop_product_category_xref AS x')
				->where('x.product_id = ' . (int) $this->request->item_id);
			$this->db->setQuery($query);
			$cats = $this->db->loadColumn();
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'redshop_category_xref', 'category_parent_id', 'category_child_id');
	}
}
regularlabs/src/Condition/Language.php000064400000001236152177723700014035 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Language
 * @package RegularLabs\Library\Condition
 */
class Language
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		return $this->passSimple(JFactory::getLanguage()->getTag(), true);
	}
}
regularlabs/src/Condition/MijoshopCategory.php000064400000003433152177723700015601 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class MijoshopCategory
 * @package RegularLabs\Library\Condition
 */
class MijoshopCategory
	extends Mijoshop
{
	public function pass()
	{
		if ($this->request->option != 'com_mijoshop')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = $this->getCats();

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCats()
	{
		if ($this->request->category_id)
		{
			return $this->makeArray($this->request->category_id);
		}

		if ( ! $this->request->item_id)
		{
			return [];
		}

		$query = $this->db->getQuery(true)
			->select('c.category_id')
			->from('#__mijoshop_product_to_category AS c')
			->where('c.product_id = ' . (int) $this->request->id);
		$this->db->setQuery($query);
		$cats = $this->db->loadColumn();

		return $this->makeArray($cats);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'mijoshop_category', 'parent_id', 'category_id');
	}
}
regularlabs/src/Condition/Flexicontent.php000064400000001047152177723700014754 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Flexicontent
 * @package RegularLabs\Library\Condition
 */
abstract class Flexicontent
	extends \RegularLabs\Library\Condition
{
}
regularlabs/src/Condition/ZooCategory.php000064400000007411152177723700014560 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class ZooCategory
 * @package RegularLabs\Library\Condition
 */
class ZooCategory
	extends Zoo
{
	public function pass()
	{
		if ($this->request->option != 'com_zoo')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_apps && $this->request->view == 'frontpage')
			|| ($this->params->inc_categories && $this->request->view == 'category')
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = $this->getCategories();

		if ($cats === false)
		{
			return $this->_(false);
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		if ($this->article && isset($this->article->catid))
		{
			return [$this->article->catid];
		}

		$menuparams = $this->getMenuItemParams($this->request->Itemid);

		switch ($this->request->view)
		{
			case 'frontpage':
				if ($this->request->id)
				{
					return [$this->request->id];
				}

				if ( ! isset($menuparams->application))
				{
					return [];
				}

				return ['app' . $menuparams->application];

			case 'category':
				$cats = [];

				if ($this->request->id)
				{
					$cats[] = $this->request->id;
				}
				else if (isset($menuparams->category))
				{
					$cats[] = $menuparams->category;
				}

				if (empty($cats[0]))
				{
					return [];
				}

				$query = $this->db->getQuery(true)
					->select('c.application_id')
					->from('#__zoo_category AS c')
					->where('c.id = ' . (int) $cats[0]);
				$this->db->setQuery($query);
				$cats[] = 'app' . $this->db->loadResult();

				return $cats;

			case 'item':
				$id = $this->request->id;

				if ( ! $id && isset($menuparams->item_id))
				{
					$id = $menuparams->item_id;
				}

				if ( ! $id)
				{
					return [];
				}

				$query = $this->db->getQuery(true)
					->select('c.category_id')
					->from('#__zoo_category_item AS c')
					->where('c.item_id = ' . (int) $id)
					->where('c.category_id != 0');
				$this->db->setQuery($query);
				$cats = $this->db->loadColumn();

				$query = $this->db->getQuery(true)
					->select('i.application_id')
					->from('#__zoo_item AS i')
					->where('i.id = ' . (int) $id);
				$this->db->setQuery($query);
				$cats[] = 'app' . $this->db->loadResult();

				return $cats;

			default:
				return false;
		}
	}

	private function getCatParentIds($id = 0)
	{
		$parent_ids = [];

		if ( ! $id)
		{
			return $parent_ids;
		}

		while ($id)
		{
			if (substr($id, 0, 3) == 'app')
			{
				$parent_ids[] = $id;
				break;
			}

			$query = $this->db->getQuery(true)
				->select('c.parent')
				->from('#__zoo_category AS c')
				->where('c.id = ' . (int) $id);
			$this->db->setQuery($query);
			$pid = $this->db->loadResult();

			if ( ! $pid)
			{
				$query = $this->db->getQuery(true)
					->select('c.application_id')
					->from('#__zoo_category AS c')
					->where('c.id = ' . (int) $id);
				$this->db->setQuery($query);
				$app = $this->db->loadResult();

				if ($app)
				{
					$parent_ids[] = 'app' . $app;
				}

				break;
			}

			$parent_ids[] = $pid;

			$id = $pid;
		}

		return $parent_ids;
	}
}
regularlabs/src/Condition/DateTime.php000064400000002340152177723700014003 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class DateTime
 * @package RegularLabs\Library\Condition
 */
class DateTime
	extends Date
{
	public function pass()
	{
		$now  = $this->getNow();
		$up   = strtotime($this->date->format('Y-m-d ', true) . $this->params->publish_up);
		$down = strtotime($this->date->format('Y-m-d ', true) . $this->params->publish_down);

		if ($up > $down)
		{
			// publish up is after publish down (spans midnight)
			// current time should be:
			// - after publish up
			// - OR before publish down
			if ($now >= $up || $now < $down)
			{
				return $this->_(true);
			}

			return $this->_(false);
		}

		// publish down is after publish up (simple time span)
		// current time should be:
		// - after publish up
		// - AND before publish down
		if ($now >= $up && $now < $down)
		{
			return $this->_(true);
		}

		return $this->_(false);
	}
}
regularlabs/src/Condition/HikashopCategory.php000064400000004412152177723700015555 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class HikashopCategory
 * @package RegularLabs\Library\Condition
 */
class HikashopCategory
	extends Hikashop
{
	public function pass()
	{
		if ($this->request->option != 'com_hikashop')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category' || $this->request->layout == 'listing')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = $this->getCategories();

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		switch (true)
		{
			case (($this->request->view == 'category' || $this->request->layout == 'listing') && $this->request->id):
				return [$this->request->id];

			case ($this->request->view == 'category' || $this->request->layout == 'listing'):
				include_once JPATH_ADMINISTRATOR . '/components/com_hikashop/helpers/helper.php';
				$menuClass = hikashop_get('class.menus');
				$menuData  = $menuClass->get($this->request->Itemid);

				return $this->makeArray($menuData->hikashop_params['selectparentlisting']);

			case ($this->request->id):
				$query = $this->db->getQuery(true)
					->select('c.category_id')
					->from('#__hikashop_product_category AS c')
					->where('c.product_id = ' . (int) $this->request->id);
				$this->db->setQuery($query);
				$cats = $this->db->loadColumn();

				return $this->makeArray($cats);

			default:
				return [];
		}
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'hikashop_category', 'category_parent_id', 'category_id');
	}
}
regularlabs/src/Condition/VirtuemartProduct.php000064400000001647152177723700016023 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class VirtuemartProduct
 * @package RegularLabs\Library\Condition
 */
class VirtuemartProduct
	extends Virtuemart
{
	public function pass()
	{
		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		if ( ! $this->request->id || $this->request->option != 'com_virtuemart' || $this->request->view != 'productdetails')
		{
			return $this->_(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/src/Condition/Content.php000064400000002073152177723700013724 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;

/**
 * Class Content
 * @package RegularLabs\Library\Condition
 */
abstract class Content
	extends \RegularLabs\Library\Condition
{
	use \RegularLabs\Library\ConditionContent;

	public function getItem($fields = [])
	{
		if ($this->article)
		{
			return $this->article;
		}

		if ( ! class_exists('ContentModelArticle'))
		{
			require_once JPATH_SITE . '/components/com_content/models/article.php';
		}

		$model = JModel::getInstance('article', 'contentModel');

		if ( ! method_exists($model, 'getItem'))
		{
			return null;
		}

		$this->article = $model->getItem($this->request->id);

		return $this->article;
	}
}
regularlabs/src/Condition/Virtuemart.php000064400000002122152177723700014447 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Virtuemart
 * @package RegularLabs\Library\Condition
 */
abstract class Virtuemart
	extends \RegularLabs\Library\Condition
{
	public function initRequest(&$request)
	{
		$virtuemart_product_id  = JFactory::getApplication()->input->get('virtuemart_product_id', [], 'array');
		$virtuemart_category_id = JFactory::getApplication()->input->get('virtuemart_category_id', [], 'array');

		$request->item_id     = isset($virtuemart_product_id[0]) ? $virtuemart_product_id[0] : null;
		$request->category_id = isset($virtuemart_category_id[0]) ? $virtuemart_category_id[0] : null;
		$request->id          = $request->item_id ?: $request->category_id;
	}
}
regularlabs/src/Condition/FlexicontentPagetype.php000064400000001225152177723700016451 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class FlexicontentPagetype
 * @package RegularLabs\Library\Condition
 */
class FlexicontentPagetype
	extends Flexicontent
{
	public function pass()
	{
		return $this->passByPageType('com_flexicontent', $this->selection, $this->include_type);
	}
}
regularlabs/src/Condition/DateMonth.php000064400000001240152177723700014170 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class DateMonth
 * @package RegularLabs\Library\Condition
 */
class DateMonth
	extends Date
{
	public function pass()
	{
		$month = $this->date->format('m', true); // 01 (for January) through 12 (for December)

		return $this->passSimple((int) $month);
	}
}
regularlabs/src/Condition/Component.php000064400000001166152177723700014256 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Component
 * @package RegularLabs\Library\Condition
 */
class Component
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		return $this->passSimple(strtolower($this->request->option));
	}
}
regularlabs/src/Condition/FlexicontentType.php000064400000002051152177723700015612 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class FlexicontentType
 * @package RegularLabs\Library\Condition
 */
class FlexicontentType
	extends Flexicontent
{
	public function pass()
	{
		if ($this->request->option != 'com_flexicontent')
		{
			return $this->_(false);
		}

		$pass = in_array($this->request->view, ['item', 'items']);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$query = $this->db->getQuery(true)
			->select('x.type_id')
			->from('#__flexicontent_items_ext AS x')
			->where('x.item_id = ' . (int) $this->request->id);
		$this->db->setQuery($query);
		$type = $this->db->loadResult();

		$types = $this->makeArray($type);

		return $this->passSimple($types);
	}
}
regularlabs/src/Condition/Homepage.php000064400000011415152177723700014037 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\LanguageHelper as JLanguageHelper;
use Joomla\CMS\Uri\Uri as JUri;
use RegularLabs\Library\RegEx;
use RegularLabs\Library\StringHelper;

/**
 * Class HomePage
 * @package RegularLabs\Library\Condition
 */
class HomePage
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		$home = JFactory::getApplication()->getMenu('site')->getDefault(JFactory::getLanguage()->getTag());

		// return if option or other set values do not match the homepage menu item values
		if ($this->request->option)
		{
			// check if option is different to home menu
			if ( ! $home || ! isset($home->query['option']) || $home->query['option'] != $this->request->option)
			{
				return $this->_(false);
			}

			if ( ! $this->request->option)
			{
				// set the view/task/layout in the menu item to empty if not set
				$home->query['view']   = isset($home->query['view']) ? $home->query['view'] : '';
				$home->query['task']   = isset($home->query['task']) ? $home->query['task'] : '';
				$home->query['layout'] = isset($home->query['layout']) ? $home->query['layout'] : '';
			}

			// check set values against home menu query items
			foreach ($home->query as $k => $v)
			{
				if ((isset($this->request->{$k}) && $this->request->{$k} != $v)
					|| (
						( ! isset($this->request->{$k}) || in_array($v, ['virtuemart', 'mijoshop']))
						&& JFactory::getApplication()->input->get($k) != $v
					)
				)
				{
					return $this->_(false);
				}
			}

			// check post values against home menu params
			foreach ($home->params->toObject() as $k => $v)
			{
				if (($v && isset($_POST[$k]) && $_POST[$k] != $v)
					|| ( ! $v && isset($_POST[$k]) && $_POST[$k])
				)
				{
					return $this->_(false);
				}
			}
		}

		$pass = $this->checkPass($home);

		if ( ! $pass)
		{
			$pass = $this->checkPass($home, true);
		}

		return $this->_($pass);
	}

	private function checkPass(&$home, $addlang = false)
	{
		$uri = JUri::getInstance();

		if ($addlang)
		{
			$sef = $uri->getVar('lang');
			if (empty($sef))
			{
				$langs = array_keys(JLanguageHelper::getLanguages('sef'));
				$path  = StringHelper::substr(
					$uri->toString(['scheme', 'user', 'pass', 'host', 'port', 'path']),
					StringHelper::strlen($uri->base())
				);
				$path  = RegEx::replace('^index\.php/?', '', $path);
				$parts = explode('/', $path);
				$part  = reset($parts);
				if (in_array($part, $langs))
				{
					$sef = $part;
				}
			}

			if (empty($sef))
			{
				return false;
			}
		}

		$query = $uri->toString(['query']);
		if (strpos($query, 'option=') === false && strpos($query, 'Itemid=') === false)
		{
			$url = $uri->toString(['host', 'path']);
		}
		else
		{
			$url = $uri->toString(['host', 'path', 'query']);
		}

		// remove the www.
		$url = RegEx::replace('^www\.', '', $url);
		// replace ampersand chars
		$url = str_replace('&amp;', '&', $url);
		// remove any language vars
		$url = RegEx::replace('((\?)lang=[a-z-_]*(&|$)|&lang=[a-z-_]*)', '\2', $url);
		// remove trailing nonsense
		$url = trim(RegEx::replace('/?\??&?$', '', $url));
		// remove the index.php/
		$url = RegEx::replace('/index\.php(/|$)', '/', $url);
		// remove trailing /
		$url = trim(RegEx::replace('/$', '', $url));

		$root = JUri::root();

		// remove the http(s)
		$root = RegEx::replace('^.*?://', '', $root);
		// remove the www.
		$root = RegEx::replace('^www\.', '', $root);
		//remove the port
		$root = RegEx::replace(':[0-9]+', '', $root);
		// so also passes on urls with trailing /, ?, &, /?, etc...
		$root = RegEx::replace('(Itemid=[0-9]*).*^', '\1', $root);
		// remove trailing /
		$root = trim(RegEx::replace('/$', '', $root));

		if ($addlang)
		{
			$root .= '/' . $sef;
		}

		/* Pass urls:
		 * [root]
		 */
		$regex = '^' . $root . '$';

		if (RegEx::match($regex, $url))
		{
			return true;
		}

		/* Pass urls:
		 * [root]?Itemid=[menu-id]
		 * [root]/?Itemid=[menu-id]
		 * [root]/index.php?Itemid=[menu-id]
		 * [root]/[menu-alias]
		 * [root]/[menu-alias]?Itemid=[menu-id]
		 * [root]/index.php?[menu-alias]
		 * [root]/index.php?[menu-alias]?Itemid=[menu-id]
		 * [root]/[menu-link]
		 * [root]/[menu-link]&Itemid=[menu-id]
		 */
		$regex = '^' . $root
			. '(/('
			. 'index\.php'
			. '|'
			. '(index\.php\?)?' . RegEx::quote($home->alias)
			. '|'
			. RegEx::quote($home->link)
			. ')?)?'
			. '(/?[\?&]Itemid=' . (int) $home->id . ')?'
			. '$';

		return RegEx::match($regex, $url);
	}
}
regularlabs/src/Condition/K2Category.php000064400000004476152177723700014275 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class K2Category
 * @package RegularLabs\Library\Condition
 */
class K2Category
	extends K2
{
	public function pass()
	{
		if ($this->request->option != 'com_k2')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_categories
				&& (($this->request->view == 'itemlist' && $this->request->task == 'category')
					|| $this->request->view == 'latest'
				)
			)
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		$cats = $this->makeArray($this->getCategories());
		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->_(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		switch ($this->request->view)
		{
			case 'item' :
				return $this->getCategoryIDFromItem();
				break;

			case 'itemlist' :
				return $this->getCategoryID();
				break;

			default:
				return '';
		}
	}

	private function getCategoryID()
	{
		return $this->request->id ?: JFactory::getApplication()->getUserStateFromRequest('com_k2itemsfilter_category', 'catid', 0, 'int');
	}

	private function getCategoryIDFromItem()
	{
		if ($this->article && isset($this->article->catid))
		{
			return $this->article->catid;
		}

		if ( ! $this->request->id)
		{
			return $this->getCategoryID();
		}

		$query = $this->db->getQuery(true)
			->select('i.catid')
			->from('#__k2_items AS i')
			->where('i.id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadResult();
	}

	private function getCatParentIds($id = 0)
	{
		$parent_field = RL_K2_VERSION == 3 ? 'parent_id' : 'parent';

		return $this->getParentIds($id, 'k2_categories', $parent_field);
	}
}
regularlabs/src/Condition/Date.php000064400000002377152177723700013176 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use DateTimeZone;
use Joomla\CMS\Factory as JFactory;

/**
 * Class Date
 * @package RegularLabs\Library\Condition
 */
abstract class Date
	extends \RegularLabs\Library\Condition
{
	var $timezone = null;
	var $dates    = [];

	public function getNow()
	{
		return strtotime($this->date->format('Y-m-d H:i:s', true));
	}

	public function getDate($date = '')
	{
		$id = 'date_' . $date;

		if (isset($this->dates[$id]))
		{
			return $this->dates[$id];
		}

		$this->dates[$id] = JFactory::getDate($date);

		if (empty($this->params->ignore_time_zone))
		{
			$this->dates[$id]->setTimeZone($this->getTimeZone());
		}

		return $this->dates[$id];
	}

	private function getTimeZone()
	{
		if ( ! is_null($this->timezone))
		{
			return $this->timezone;
		}

		$this->timezone = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));

		return $this->timezone;
	}
}
regularlabs/src/Condition/HikashopPagetype.php000064400000001634152177723700015561 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class HikashopPagetype
 * @package RegularLabs\Library\Condition
 */
class HikashopPagetype
	extends Hikashop
{
	public function pass()
	{
		if ($this->request->option != 'com_hikashop')
		{
			return $this->_(false);
		}

		$type = $this->request->view;
		if (
			($type == 'product' && in_array($this->request->layout, ['contact', 'show']))
			|| ($type == 'user' && in_array($this->request->layout, ['cpanel']))
		)
		{
			$type .= '_' . $this->request->layout;
		}

		return $this->passSimple($type);
	}
}
regularlabs/src/Condition/GeoPostalcode.php000064400000001440152177723700015037 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class GeoPostalcode
 * @package RegularLabs\Library\Condition
 */
class GeoPostalcode
	extends Geo
{
	public function pass()
	{
		if ( ! $this->getGeo() || empty($this->geo->postalCode))
		{
			return $this->_(false);
		}

		// replace dashes with dots: 730-0011 => 730.0011
		$postalcode = str_replace('-', '.', $this->geo->postalCode);

		return $this->passInRange($postalcode);
	}
}
regularlabs/src/Condition/Template.php000064400000003612152177723700014065 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Template
 * @package RegularLabs\Library\Condition
 */
class Template
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		$template = $this->getTemplate();

		// Put template name and name + style id into array
		// The '::' separator was used in pre Joomla 3.3
		$template = [$template->template, $template->template . '--' . $template->id, $template->template . '::' . $template->id];

		return $this->passSimple($template, true);
	}

	public function getTemplate()
	{
		$template = JFactory::getApplication()->getTemplate(true);

		if (isset($template->id))
		{
			return $template;
		}

		$params = json_encode($template->params);

		// Find template style id based on params, as the template style id is not always stored in the getTemplate
		$query = $this->db->getQuery(true)
			->select('id')
			->from('#__template_styles AS s')
			->where('s.client_id = 0')
			->where('s.template = ' . $this->db->quote($template->template))
			->where('s.params = ' . $this->db->quote($params));
		$this->db->setQuery($query, 0, 1);
		$template->id = $this->db->loadResult('id');

		if ($template->id)
		{
			return $template;
		}

		// No template style id is found, so just grab the first result based on the template name
		$query->clear('where')
			->where('s.client_id = 0')
			->where('s.template = ' . $this->db->quote($template->template));
		$this->db->setQuery($query, 0, 1);
		$template->id = $this->db->loadResult('id');

		return $template;
	}
}
regularlabs/src/Condition/ZooItem.php000064400000001710152177723700013675 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class ZooItem
 * @package RegularLabs\Library\Condition
 */
class ZooItem
	extends Zoo
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_zoo')
		{
			return $this->_(false);
		}

		if ($this->request->view != 'item')
		{
			return $this->_(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentId'))
		{
			return $this->_(false);
		}

		// Pass Author
		if ( ! $this->passItemByType($pass, 'Author'))
		{
			return $this->_(false);
		}

		return $this->_($pass);
	}
}
regularlabs/src/Condition/Url.php000064400000003213152177723700013051 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Uri\Uri as JUri;
use RegularLabs\Library\RegEx;
use RegularLabs\Library\StringHelper;

/**
 * Class Url
 * @package RegularLabs\Library\Condition
 */
class Url
	extends \RegularLabs\Library\Condition
{
	public function pass()
	{
		$regex = isset($this->params->regex) ? $this->params->regex : false;

		if ( ! is_array($this->selection))
		{
			$this->selection = explode("\n", $this->selection);
		}

		if (count($this->selection) == 1)
		{
			$this->selection = explode("\n", $this->selection[0]);
		}

		$url = JUri::getInstance();
		$url = $url->toString();

		$urls = [
			StringHelper::html_entity_decoder(urldecode($url)),
			urldecode($url),
			StringHelper::html_entity_decoder($url),
			$url,
		];
		$urls = array_unique($urls);

		$pass = false;
		foreach ($urls as $url)
		{
			foreach ($this->selection as $s)
			{
				$s = trim($s);
				if ($s == '')
				{
					continue;
				}

				if ($regex)
				{
					$url_part = str_replace(['#', '&amp;'], ['\#', '(&amp;|&)'], $s);
					if (@RegEx::match($url_part, $url))
					{
						$pass = true;
						break;
					}

					continue;
				}

				if (strpos($url, $s) !== false)
				{
					$pass = true;
					break;
				}
			}

			if ($pass)
			{
				break;
			}
		}

		return $this->_($pass);
	}
}
regularlabs/src/Condition/UserGrouplevel.php000064400000005616152177723700015303 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\DB as RL_DB;

/**
 * Class UserGrouplevel
 * @package RegularLabs\Library\Condition
 */
class UserGrouplevel
	extends User
{
	public function pass()
	{
		$user = JFactory::getUser();

		if ( ! empty($user->groups))
		{
			$groups = array_values($user->groups);
		}
		else
		{
			$groups = $user->getAuthorisedGroups();
		}

		if ( ! $this->params->match_all && $this->params->inc_children)
		{
			$this->setUserGroupChildrenIds();
		}

		$this->selection = $this->convertUsergroupNamesToIds($this->selection);

		if ($this->params->match_all)
		{
			return $this->passMatchAll($groups);
		}

		return $this->passSimple($groups);
	}

	private function passMatchAll($groups)
	{
		$pass = ! array_diff($this->selection, $groups) && ! array_diff($groups, $this->selection);

		return $this->_($pass);
	}

	private function convertUsergroupNamesToIds($selection)
	{
		$names = [];

		foreach ($selection as $i => $group)
		{
			if (is_numeric($group))
			{
				continue;
			}

			unset($selection[$i]);

			$names[] = strtolower(str_replace(' ', '', $group));
		}

		if (empty($names))
		{
			return $selection;
		}

		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from('#__usergroups')
			->where('LOWER(REPLACE(' . $db->quoteName('title') . ', " ", ""))'
				. RL_DB::in($names));
		$db->setQuery($query);

		$group_ids = $db->loadColumn();

		return array_unique(array_merge($selection, $group_ids));
	}

	private function setUserGroupChildrenIds()
	{
		$children = $this->getUserGroupChildrenIds($this->selection);

		if ($this->params->inc_children == 2)
		{
			$this->selection = $children;

			return;
		}

		$this->selection = array_merge($this->selection, $children);
	}

	private function getUserGroupChildrenIds($groups)
	{
		$children = [];

		$db = JFactory::getDbo();

		foreach ($groups as $group)
		{
			$query = $db->getQuery(true)
				->select($db->quoteName('id'))
				->from($db->quoteName('#__usergroups'))
				->where($db->quoteName('parent_id') . ' = ' . (int) $group);
			$db->setQuery($query);

			$group_children = $db->loadColumn();

			if (empty($group_children))
			{
				continue;
			}

			$children = array_merge($children, $group_children);

			$group_grand_children = $this->getUserGroupChildrenIds($group_children);

			if (empty($group_grand_children))
			{
				continue;
			}

			$children = array_merge($children, $group_grand_children);
		}

		$children = array_unique($children);

		return $children;
	}
}
regularlabs/src/Condition/MijoshopProduct.php000064400000001356152177723700015446 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class MijoshopProduct
 * @package RegularLabs\Library\Condition
 */
class MijoshopProduct
	extends Mijoshop
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_mijoshop' || $this->request->view != 'product')
		{
			return $this->_(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/src/Condition/GeoContinent.php000064400000001323152177723700014703 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class GeoContinent
 * @package RegularLabs\Library\Condition
 */
class GeoContinent
	extends Geo
{
	public function pass()
	{
		if ( ! $this->getGeo() || empty($this->geo->continentCode))
		{
			return $this->_(false);
		}

		return $this->passSimple([$this->geo->continent, $this->geo->continentCode]);
	}
}
regularlabs/src/Condition/UserAccesslevel.php000064400000002702152177723700015401 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Library\DB as RL_DB;

/**
 * Class UserAccesslevel
 * @package RegularLabs\Library\Condition
 */
class UserAccesslevel
	extends User
{
	public function pass()
	{
		$user = JFactory::getUser();

		$levels = $user->getAuthorisedViewLevels();

		$this->selection = $this->convertAccessLevelNamesToIds($this->selection);

		return $this->passSimple($levels);
	}

	private function convertAccessLevelNamesToIds($selection)
	{
		$names = [];

		foreach ($selection as $i => $level)
		{
			if (is_numeric($level))
			{
				continue;
			}

			unset($selection[$i]);

			$names[] = strtolower(str_replace(' ', '', $level));
		}

		if (empty($names))
		{
			return $selection;
		}

		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from('#__viewlevels')
			->where('LOWER(REPLACE(' . $db->quoteName('title') . ', " ", ""))'
				. RL_DB::in($names));
		$db->setQuery($query);

		$level_ids = $db->loadColumn();

		return array_unique(array_merge($selection, $level_ids));
	}
}
regularlabs/src/Condition/AkeebasubsPagetype.php000064400000001215152177723700016053 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class AkeebasubsPagetype
 * @package RegularLabs\Library\Condition
 */
class AkeebasubsPagetype
	extends Akeebasubs
{
	public function pass()
	{
		return $this->passByPageType('com_akeebasubs', $this->selection, $this->include_type);
	}
}
regularlabs/src/Condition/K2Item.php000064400000002204152177723700013401 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class K2Item
 * @package RegularLabs\Library\Condition
 */
class K2Item
	extends K2
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_k2' || $this->request->view != 'item')
		{
			return $this->_(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentId'))
		{
			return $this->_(false);
		}

		// Pass Content Keyword
		if ( ! $this->passItemByType($pass, 'ContentKeyword'))
		{
			return $this->_(false);
		}

		// Pass Meta Keyword
		if ( ! $this->passItemByType($pass, 'MetaKeyword'))
		{
			return $this->_(false);
		}

		// Pass Author
		if ( ! $this->passItemByType($pass, 'Author'))
		{
			return $this->_(false);
		}

		return $this->_($pass);
	}
}
regularlabs/src/Condition/GeoRegion.php000064400000002143152177723700014166 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class GeoRegion
 * @package RegularLabs\Library\Condition
 */
class GeoRegion
	extends Geo
{
	public function pass()
	{
		if ( ! $this->getGeo() || empty($this->geo->countryCode) || empty($this->geo->regionCodes))
		{
			return $this->_(false);
		}

		$country = $this->geo->countryCode;
		$regions = $this->geo->regionCodes;

		array_walk($regions, function (&$region, $key, $country) {

			$region = $this->getCountryRegionCode($region, $country);
		}, $country);

		return $this->passSimple($regions);
	}

	private function getCountryRegionCode(&$region, $country)
	{
		switch ($country . '-' . $region)
		{
			case 'MX-CMX':
				return 'MX-DIF';

			default:
				return $country . '-' . $region;
		}
	}
}
regularlabs/src/Condition/EasyblogTag.php000064400000003020152177723700014504 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class EasyblogTag
 * @package RegularLabs\Library\Condition
 */
class EasyblogTag
	extends Easyblog
{
	public function pass()
	{
		if ($this->request->option != 'com_easyblog')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_tags && $this->request->layout == 'tag')
			|| ($this->params->inc_items && $this->request->view == 'entry')
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		if ($this->params->inc_tags && $this->request->layout == 'tag')
		{
			$query = $this->db->getQuery(true)
				->select('t.alias')
				->from('#__easyblog_tag AS t')
				->where('t.id = ' . (int) $this->request->id)
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tags = $this->db->loadColumn();

			return $this->passSimple($tags, true);
		}

		$query = $this->db->getQuery(true)
			->select('t.alias')
			->from('#__easyblog_post_tag AS x')
			->join('LEFT', '#__easyblog_tag AS t ON t.id = x.tag_id')
			->where('x.post_id = ' . (int) $this->request->id)
			->where('t.published = 1');
		$this->db->setQuery($query);
		$tags = $this->db->loadColumn();

		return $this->passSimple($tags, true);
	}
}
regularlabs/src/Condition/MijoshopPagetype.php000064400000001213152177723700015574 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class MijoshopPagetype
 * @package RegularLabs\Library\Condition
 */
class MijoshopPagetype
	extends Mijoshop
{
	public function pass()
	{
		return $this->passByPageType('com_mijoshop', $this->selection, $this->include_type, true);
	}
}
regularlabs/src/Condition/VirtuemartPagetype.php000064400000001475152177723700016160 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class VirtuemartPagetype
 * @package RegularLabs\Library\Condition
 */
class VirtuemartPagetype
	extends Virtuemart
{
	public function pass()
	{
		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		return $this->passByPageType('com_virtuemart', $this->selection, $this->include_type, true);
	}
}
regularlabs/src/Condition/Form2content.php000064400000001047152177723700014672 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Form2content
 * @package RegularLabs\Library\Condition
 */
abstract class Form2content
	extends \RegularLabs\Library\Condition
{
}
regularlabs/src/Condition/Redshop.php000064400000001524152177723700013716 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Redshop
 * @package RegularLabs\Library\Condition
 */
abstract class Redshop
	extends \RegularLabs\Library\Condition
{
	public function initRequest(&$request)
	{
		$request->item_id     = JFactory::getApplication()->input->getInt('pid', 0);
		$request->category_id = JFactory::getApplication()->input->getInt('cid', 0);
		$request->id          = $request->item_id ?: $request->category_id;
	}
}
regularlabs/src/Condition/ContentArticle.php000064400000002413152177723700015226 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class ContentArticle
 * @package RegularLabs\Library\Condition
 */
class ContentArticle
	extends Content
{
	public function pass()
	{
		if ( ! $this->request->id
			|| ! (($this->request->option == 'com_content' && $this->request->view == 'article')
				|| ($this->request->option == 'com_flexicontent' && $this->request->view == 'item')
			)
		)
		{
			return $this->_(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentId'))
		{
			return $this->_(false);
		}

		// Pass Content Keywords
		if ( ! $this->passItemByType($pass, 'ContentKeyword'))
		{
			return $this->_(false);
		}

		// Pass Meta Keywords
		if ( ! $this->passItemByType($pass, 'MetaKeyword'))
		{
			return $this->_(false);
		}

		// Pass Author
		if ( ! $this->passItemByType($pass, 'Author'))
		{
			return $this->_(false);
		}

		return $this->_($pass);
	}
}
regularlabs/src/Condition/Geo.php000064400000002440152177723700013022 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Log\Log as JLog;

/**
 * Class Geo
 * @package RegularLabs\Library\Condition
 */
abstract class Geo
	extends \RegularLabs\Library\Condition
{
	var $geo = null;

	public function getGeo($ip = '')
	{
		if ($this->geo !== null)
		{
			return $this->geo;
		}


		$geo = $this->getGeoObject($ip);

		if (empty($geo))
		{
			return false;
		}

		$this->geo = $geo->get();

		if (JDEBUG)
		{
			JLog::addLogger(['text_file' => 'regularlabs_geoip.log.php'], JLog::ALL, ['regularlabs_geoip']);
			JLog::add(json_encode($this->geo), JLog::DEBUG, 'regularlabs_geoip');
		}

		return $this->geo;
	}

	private function getGeoObject($ip)
	{
		if ( ! file_exists(JPATH_LIBRARIES . '/geoip/geoip.php'))
		{
			return false;
		}

		require_once JPATH_LIBRARIES . '/geoip/geoip.php';

		if ( ! class_exists('RegularLabs_GeoIp'))
		{
			return new \GeoIp($ip);
		}

		return new \RegularLabs_GeoIp($ip);
	}
}
regularlabs/src/Condition/HikashopProduct.php000064400000001356152177723700015424 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class HikashopProduct
 * @package RegularLabs\Library\Condition
 */
class HikashopProduct
	extends Hikashop
{
	public function pass()
	{
		if ( ! $this->request->id || $this->request->option != 'com_hikashop' || $this->request->view != 'product')
		{
			return $this->_(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/src/Condition/FlexicontentTag.php000064400000003214152177723700015406 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class FlexicontentTag
 * @package RegularLabs\Library\Condition
 */
class FlexicontentTag
	extends Flexicontent
{
	public function pass()
	{
		if ($this->request->option != 'com_flexicontent')
		{
			return $this->_(false);
		}

		$pass = (
			($this->params->inc_tags && $this->request->view == 'tags')
			|| ($this->params->inc_items && in_array($this->request->view, ['item', 'items']))
		);

		if ( ! $pass)
		{
			return $this->_(false);
		}

		if ($this->params->inc_tags && $this->request->view == 'tags')
		{
			$query = $this->db->getQuery(true)
				->select('t.name')
				->from('#__flexicontent_tags AS t')
				->where('t.id = ' . (int) trim(JFactory::getApplication()->input->getInt('id', 0)))
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tag  = $this->db->loadResult();
			$tags = [$tag];
		}
		else
		{
			$query = $this->db->getQuery(true)
				->select('t.name')
				->from('#__flexicontent_tags_item_relations AS x')
				->join('LEFT', '#__flexicontent_tags AS t ON t.id = x.tid')
				->where('x.itemid = ' . (int) $this->request->id)
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tags = $this->db->loadColumn();
		}

		return $this->passSimple($tags, true);
	}
}
regularlabs/src/Condition/Easyblog.php000064400000001503152177723700014054 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class Easyblog
 * @package RegularLabs\Library\Condition
 */
abstract class Easyblog
	extends \RegularLabs\Library\Condition
{
	use \RegularLabs\Library\ConditionContent;

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__easyblog_post')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}
}
regularlabs/src/Condition/DateSeason.php000064400000005255152177723700014345 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

/**
 * Class DateSeason
 * @package RegularLabs\Library\Condition
 */
class DateSeason
	extends Date
{
	public function pass()
	{
		$season = self::getSeason($this->date, $this->params->hemisphere);

		return $this->passSimple($season);
	}

	private function getSeason(&$d, $hemisphere = 'northern')
	{
		// Set $date to today
		$date = strtotime($d->format('Y-m-d H:i:s', true));

		// Get year of date specified
		$date_year = $d->format('Y', true); // Four digit representation for the year

		// Specify the season names
		$season_names = ['winter', 'spring', 'summer', 'fall'];

		// Declare season date ranges
		switch (strtolower($hemisphere))
		{
			case 'southern':
				if (
					$date < strtotime($date_year . '-03-21')
					|| $date >= strtotime($date_year . '-12-21')
				)
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-09-23'))
				{
					return $season_names[1]; // Must be in Spring
				}

				if ($date >= strtotime($date_year . '-06-21'))
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-03-21'))
				{
					return $season_names[3]; // Must be in Fall
				}
				break;
			case 'australia':
				if (
					$date < strtotime($date_year . '-03-01')
					|| $date >= strtotime($date_year . '-12-01')
				)
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-09-01'))
				{
					return $season_names[1]; // Must be in Spring
				}

				if ($date >= strtotime($date_year . '-06-01'))
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-03-01'))
				{
					return $season_names[3]; // Must be in Fall
				}
				break;
			default: // northern
				if (
					$date < strtotime($date_year . '-03-21')
					|| $date >= strtotime($date_year . '-12-21')
				)
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-09-23'))
				{
					return $season_names[3]; // Must be in Fall
				}

				if ($date >= strtotime($date_year . '-06-21'))
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-03-21'))
				{
					return $season_names[1]; // Must be in Spring
				}
				break;
		}

		return 0;
	}
}
regularlabs/src/Condition/Akeebasubs.php000064400000002074152177723700014360 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library\Condition;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Akeebasubs
 * @package RegularLabs\Library\Condition
 */
abstract class Akeebasubs
	extends \RegularLabs\Library\Condition
{
	var $agent  = null;
	var $device = null;

	public function initRequest(&$request)
	{
		if ($request->id || $request->view != 'level')
		{
			return;
		}

		$slug = JFactory::getApplication()->input->getString('slug', '');

		if ( ! $slug)
		{
			return;
		}

		$query = $this->db->getQuery(true)
			->select('l.akeebasubs_level_id')
			->from('#__akeebasubs_levels AS l')
			->where('l.slug = ' . $this->db->quote($slug));
		$this->db->setQuery($query);
		$request->id = $this->db->loadResult();
	}
}
regularlabs/src/Cache.php000064400000003530152177723700011366 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

/**
 * Class Cache
 * @package RegularLabs\Library
 */
class Cache
{
	static $group = 'regularlabs';
	static $cache = [];

	// Is the cached object in the cache memory?
	public static function has($id)
	{
		return isset(self::$cache[md5($id)]);
	}

	// Get the cached object from the cache memory
	public static function get($id)
	{
		$hash = md5($id);

		if ( ! isset(self::$cache[$hash]))
		{
			return false;
		}

		return is_object(self::$cache[$hash]) ? clone self::$cache[$hash] : self::$cache[$hash];
	}

	// Save the cached object to the cache memory
	public static function set($id, $data)
	{
		self::$cache[md5($id)] = $data;

		return $data;
	}

	// Get the cached object from the Joomla cache
	public static function read($id)
	{
		$hash = md5($id);

		if (isset(self::$cache[$hash]))
		{
			return self::$cache[$hash];
		}

		$cache = JFactory::getCache(self::$group, 'output');

		return $cache->get($hash);
	}

	// Save the cached object to the Joomla cache
	public static function write($id, $data, $time_to_life_in_minutes = 0, $force_caching = true)
	{
		$hash = md5($id);

		self::$cache[$hash] = $data;

		$cache = JFactory::getCache(self::$group, 'output');

		if ($time_to_life_in_minutes)
		{
			// convert ttl to minutes
			$cache->setLifeTime($time_to_life_in_minutes * 60);
		}

		if ($force_caching)
		{
			$cache->setCaching(true);
		}

		$cache->store($data, $hash);

		self::set($hash, $data);

		return $data;
	}
}
regularlabs/src/Condition.php000064400000021667152177723700012324 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use DateTimeZone;
use Joomla\CMS\Factory as JFactory;

/**
 * Class Condition
 * @package RegularLabs\Library
 */
abstract class Condition
	implements \RegularLabs\Library\Api\ConditionInterface
{
	public $request      = null;
	public $date         = null;
	public $db           = null;
	public $selection    = null;
	public $params       = null;
	public $include_type = null;
	public $article      = null;
	public $module       = null;

	public function __construct($condition = [], $article = null, $module = null)
	{
		$tz         = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));
		$this->date = JFactory::getDate()->setTimeZone($tz);

		$this->request = self::getRequest();

		$this->db = JFactory::getDbo();

		$this->selection    = isset($condition->selection) ? $condition->selection : [];
		$this->params       = isset($condition->params) ? $condition->params : [];
		$this->include_type = isset($condition->include_type) ? $condition->include_type : 'none';

		$this->article = $article;
		$this->module  = $module;
	}

	public function init()
	{
	}

	public function initRequest(&$request)
	{
	}

	public function beforePass()
	{
	}

	private function getRequest()
	{
		$app   = JFactory::getApplication();
		$input = $app->input;

		$id = $input->get('id', $input->get('a_id', [0], 'array'), 'array');

		$request = (object) [
			'idname' => 'id',
			'option' => $input->get('option'),
			'view'   => $input->get('view'),
			'task'   => $input->get('task'),
			'layout' => $input->getString('layout'),
			'Itemid' => $this->getItemId(),
			'id'     => (int) $id[0],
		];

		switch ($request->option)
		{
			case 'com_categories':
				$extension       = $input->getCmd('extension');
				$request->option = $extension ? $extension : 'com_content';
				$request->view   = 'category';
				break;

			case 'com_breezingforms':
				if ($request->view == 'article')
				{
					$request->option = 'com_content';
				}
				break;
		}

		$this->initRequest($request);

		if ( ! $request->id)
		{
			$cid         = $input->get('cid', [0], 'array');
			$request->id = (int) $cid[0];
		}

		// if no id is found, check if menuitem exists to get view and id
		if (Document::isClient('site')
			&& ( ! $request->option || ! $request->id)
		)
		{
			$menuItem = empty($request->Itemid)
				? $app->getMenu('site')->getActive()
				: $app->getMenu('site')->getItem($request->Itemid);

			if ($menuItem)
			{
				if ( ! $request->option)
				{
					$request->option = (empty($menuItem->query['option'])) ? null : $menuItem->query['option'];
				}

				$request->view = (empty($menuItem->query['view'])) ? null : $menuItem->query['view'];
				$request->task = (empty($menuItem->query['task'])) ? null : $menuItem->query['task'];

				if ( ! $request->id)
				{
					$request->id = (empty($menuItem->query[$request->idname])) ? $menuItem->params->get($request->idname) : $menuItem->query[$request->idname];
				}
			}

			unset($menuItem);
		}

		return $request;
	}

	public function _($pass = true, $include_type = null)
	{
		$include_type = $include_type ?: $this->include_type;

		return $pass ? ($include_type == 'include') : ($include_type == 'exclude');
	}

	public function passSimple($values = '', $caseinsensitive = false, $include_type = null, $selection = null)
	{
		$values       = $this->makeArray($values);
		$include_type = $include_type ?: $this->include_type;
		$selection    = $selection ?: $this->selection;

		$pass = false;
		foreach ($values as $value)
		{
			if ($caseinsensitive)
			{
				if (in_array(strtolower($value), array_map('strtolower', $selection)))
				{
					$pass = true;
					break;
				}

				continue;
			}

			if (in_array($value, $selection))
			{
				$pass = true;
				break;
			}
		}

		return $this->_($pass, $include_type);
	}

	public function passInRange($value = '', $include_type = null, $selection = null)
	{
		$include_type = $include_type ?: $this->include_type;

		if (empty($value))
		{
			return $this->_(false, $include_type);
		}

		$selections = $this->makeArray($selection ?: $this->selection);

		$pass = false;
		foreach ($selections as $selection)
		{
			if (empty($selection))
			{
				continue;
			}

			if (strpos($selection, '-') === false)
			{
				if ((int) $value == (int) $selection)
				{
					$pass = true;
					break;
				}

				continue;
			}

			list($min, $max) = explode('-', $selection, 2);

			if ((int) $value >= (int) $min && (int) $value <= (int) $max)
			{
				$pass = true;
				break;
			}
		}

		return $this->_($pass, $include_type);
	}

	public function passItemByType(&$pass, $type = '', $data = null)
	{
		$pass_type = ! empty($data) ? $this->{'pass' . $type}($data) : $this->{'pass' . $type}();

		if ($pass_type == null)
		{
			return true;
		}

		$pass = $pass_type;

		return $pass;
	}

	public function passByPageType($option, $selection = [], $include_type = 'all', $add_view = false, $get_task = false, $get_layout = true)
	{
		if ($this->request->option != $option)
		{
			return $this->_(false, $include_type);
		}

		if ($get_task && $this->request->task && $this->request->task != $this->request->view && $this->request->task != 'default')
		{
			$pagetype = ($add_view ? $this->request->view . '_' : '') . $this->request->task;

			return $this->passSimple($pagetype, $selection, $include_type);
		}

		if ($get_layout && $this->request->layout && $this->request->layout != $this->request->view && $this->request->layout != 'default')
		{
			$pagetype = ($add_view ? $this->request->view . '_' : '') . $this->request->layout;

			return $this->passSimple($pagetype, $selection, $include_type);
		}

		return $this->passSimple($this->request->view, $selection, $include_type);
	}

	public function getMenuItemParams($id = 0)
	{
		$cache_id = 'getMenuItemParams_' . $id;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$query = $this->db->getQuery(true)
			->select('m.params')
			->from('#__menu AS m')
			->where('m.id = ' . (int) $id);
		$this->db->setQuery($query);
		$params = $this->db->loadResult();

		$parameters = Parameters::getInstance();

		return Cache::set(
			$cache_id,
			$parameters->getParams($params)
		);
	}

	public function getParentIds($id = 0, $table = 'menu', $parent = 'parent_id', $child = 'id')
	{
		if ( ! $id)
		{
			return [];
		}

		$cache_id = 'getParentIds_' . $id . '_' . $table . '_' . $parent . '_' . $child;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$parent_ids = [];

		while ($id)
		{
			$query = $this->db->getQuery(true)
				->select('t.' . $parent)
				->from('#__' . $table . ' as t')
				->where('t.' . $child . ' = ' . (int) $id);
			$this->db->setQuery($query);
			$id = $this->db->loadResult();

			// Break if no parent is found or parent already found before for some reason
			if ( ! $id || in_array($id, $parent_ids))
			{
				break;
			}

			$parent_ids[] = $id;
		}

		return Cache::set(
			$cache_id,
			$parent_ids
		);
	}

	public function makeArray($array = '', $delimiter = ',', $trim = false)
	{
		if (empty($array))
		{
			return [];
		}

		$cache_id = 'makeArray_' . json_encode($array) . '_' . $delimiter . '_' . $trim;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$array = $this->mixedDataToArray($array, $delimiter);

		if (empty($array))
		{
			return $array;
		}

		if ( ! $trim)
		{
			return $array;
		}

		foreach ($array as $k => $v)
		{
			if ( ! is_string($v))
			{
				continue;
			}

			$array[$k] = trim($v);
		}

		return Cache::set(
			$cache_id,
			$array
		);
	}

	private function mixedDataToArray($array = '', $onlycommas = false)
	{
		if ( ! is_array($array))
		{
			$delimiter = ($onlycommas || strpos($array, '|') === false) ? ',' : '|';

			return explode($delimiter, $array);
		}

		if (empty($array))
		{
			return $array;
		}

		if (isset($array[0]) && is_array($array[0]))
		{
			return $array[0];
		}

		if (count($array) === 1 && strpos($array[0], ',') !== false)
		{
			return explode(',', $array[0]);
		}

		return $array;
	}

	private function getItemId()
	{
		$app = JFactory::getApplication();

		if ($id = $app->input->getInt('Itemid', 0))
		{
			return $id;
		}

		$menu = $this->getActiveMenu();

		return isset($menu->id) ? $menu->id : 0;
	}

	private function getActiveMenu()
	{
		$menu = JFactory::getApplication()->getMenu()->getActive();

		if (empty($menu->id))
		{
			return false;
		}

		return $this->getMenuById($menu->id);
	}

	private function getMenuById($id = 0)
	{
		$menu = JFactory::getApplication()->getMenu()->getItem($id);

		if (empty($menu->id))
		{
			return false;
		}

		if ($menu->type == 'alias')
		{
			$params = $menu->getParams();

			return $this->getMenuById($params->get('aliasoptions'));
		}

		return $menu;
	}
}
regularlabs/src/Document.php000064400000027314152177723700012147 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;

/**
 * Class Document
 * @package RegularLabs\Library
 */
class Document
{
	/**
	 * Check if page is an admin page
	 *
	 * @param bool $exclude_login
	 *
	 * @return bool
	 */
	public static function isAdmin($exclude_login = false)
	{
		$cache_id = __FUNCTION__ . '_' . $exclude_login;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$input = JFactory::getApplication()->input;

		return Cache::set($cache_id,
			(
				self::isClient('administrator')
				&& ( ! $exclude_login || ! JFactory::getUser()->get('guest'))
				&& $input->get('task') != 'preview'
				&& ! (
					$input->get('option') == 'com_finder'
					&& $input->get('format') == 'json'
				)
			)
		);
	}

	/**
	 * Check if page is an edit page
	 *
	 * @return bool
	 */
	public static function isClient($identifier)
	{
		$identifier = $identifier == 'admin' ? 'administrator' : $identifier;

		$cache_id = __FUNCTION__ . '_' . $identifier;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		return Cache::set($cache_id, JFactory::getApplication()->isClient($identifier));
	}

	/**
	 * Check if page is an edit page
	 *
	 * @return bool
	 */
	public static function isEditPage()
	{
		$cache_id = __FUNCTION__;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$input = JFactory::getApplication()->input;

		$option = $input->get('option');

		// always return false for these components
		if (in_array($option, ['com_rsevents', 'com_rseventspro']))
		{
			return Cache::set($cache_id, false);
		}

		$task = $input->get('task');

		if (strpos($task, '.') !== false)
		{
			$task = explode('.', $task);
			$task = array_pop($task);
		}

		$view = $input->get('view');

		if (strpos($view, '.') !== false)
		{
			$view = explode('.', $view);
			$view = array_pop($view);
		}

		return Cache::set($cache_id,
			(
				in_array($option, ['com_contentsubmit', 'com_cckjseblod'])
				|| ($option == 'com_comprofiler' && in_array($task, ['', 'userdetails']))
				|| in_array($task, ['edit', 'form', 'submission'])
				|| in_array($view, ['edit', 'form'])
				|| in_array($input->get('do'), ['edit', 'form'])
				|| in_array($input->get('layout'), ['edit', 'form', 'write'])
				|| self::isAdmin()
			)
		);
	}

	/**
	 * Checks if current page is a html page
	 *
	 * @return bool
	 */
	public static function isHtml()
	{
		$cache_id = __FUNCTION__;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		return Cache::set($cache_id,
			(JFactory::getDocument()->getType() == 'html')
		);
	}

	/**
	 * Checks if current page is a feed
	 *
	 * @return bool
	 */
	public static function isFeed()
	{
		$cache_id = __FUNCTION__;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$input = JFactory::getApplication()->input;

		return Cache::set($cache_id,
			(
				JFactory::getDocument()->getType() == 'feed'
				|| $input->getWord('format') == 'feed'
				|| $input->getWord('format') == 'xml'
				|| $input->getWord('type') == 'rss'
				|| $input->getWord('type') == 'atom'
			)
		);
	}

	/**
	 * Checks if current page is a pdf
	 *
	 * @return bool
	 */
	public static function isPDF()
	{
		$cache_id = __FUNCTION__;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$input = JFactory::getApplication()->input;

		return Cache::set($cache_id,
			(
				JFactory::getDocument()->getType() == 'pdf'
				|| $input->getWord('format') == 'pdf'
				|| $input->getWord('cAction') == 'pdf'
			)
		);
	}

	/**
	 * Checks if current page is a https (ssl) page
	 *
	 * @return bool
	 */
	public static function isHttps()
	{
		$cache_id = __FUNCTION__;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		return Cache::set($cache_id,
			(
				( ! empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
				|| (isset($_SERVER['SSL_PROTOCOL']))
				|| (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)
				|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
			)
		);
	}

	/**
	 * Checks if context/page is a category list
	 *
	 * @param string $context
	 *
	 * @return bool
	 */
	public static function isCategoryList($context)
	{
		$cache_id = __FUNCTION__ . '_' . $context;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$app   = JFactory::getApplication();
		$input = $app->input;

		// Return false if it is not a category page
		if ($context != 'com_content.category' || $input->get('view') != 'category')
		{
			return Cache::set($cache_id, false);
		}

		// Return false if layout is set and it is not a list layout
		if ($input->get('layout') && $input->get('layout') != 'list')
		{
			return Cache::set($cache_id, false);
		}

		// Return false if default layout is set to blog
		if ($app->getParams()->get('category_layout') == '_:blog')
		{
			return Cache::set($cache_id, false);
		}

		// Return true if it IS a list layout
		return Cache::set($cache_id, true);
	}

	/**
	 * Adds a script file to the page (with optional versioning)
	 *
	 * @param string $file
	 * @param string $version
	 */
	public static function script($file, $version = '')
	{
		if ( ! $url = File::getMediaFile('js', $file))
		{
			return;
		}

		JHtml::_('jquery.framework');

		if (strpos($file, 'regularlabs/') !== false)
		{
			JHtml::_('behavior.core');
			JHtml::_('script', 'jui/cms.js', ['version' => 'auto', 'relative' => true]);
			$version = '19.7.21312';
		}

		if ( ! empty($version))
		{
			$url .= '?v=' . $version;
		}

		JFactory::getDocument()->addScript($url);
	}

	/**
	 * Adds a stylesheet file to the page(with optional versioning)
	 *
	 * @param string $file
	 * @param string $version
	 */
	public static function style($file, $version = '')
	{
		if (strpos($file, 'regularlabs/') === 0)
		{
			$version = '19.7.21312';
		}

		if ( ! $file = File::getMediaFile('css', $file))
		{
			return;
		}

		if ( ! empty($version))
		{
			$file .= '?v=' . $version;
		}

		JFactory::getDocument()->addStylesheet($file);
	}

	/**
	 * Alias of \RegularLabs\Library\Document::style()
	 *
	 * @param string $file
	 * @param string $version
	 */
	public static function stylesheet($file, $version = '')
	{
		self::style($file, $version);
	}

	/**
	 * Adds extension options to the page
	 *
	 * @param array  $options
	 * @param string $name
	 */
	public static function scriptOptions($options = [], $name = '')
	{
		$key = 'rl_' . Extension::getAliasByName($name);
		JHtml::_('behavior.core');

		JFactory::getDocument()->addScriptOptions($key, $options);
	}

	/**
	 * Loads the required scripts and styles used in forms
	 */
	public static function loadMainDependencies()
	{
		JHtml::_('jquery.framework');

		self::script('regularlabs/script.min.js');
		self::style('regularlabs/style.min.css');
	}

	/**
	 * Loads the required scripts and styles used in forms
	 */
	public static function loadFormDependencies()
	{
		JHtml::_('jquery.framework');
		JHtml::_('behavior.tooltip');
		JHtml::_('behavior.formvalidator');
		JHtml::_('behavior.combobox');
		JHtml::_('behavior.keepalive');
		JHtml::_('behavior.tabstate');

		JHtml::_('formbehavior.chosen', '#jform_position', null, ['disable_search_threshold' => 0]);
		JHtml::_('formbehavior.chosen', '.multipleCategories', null, ['placeholder_text_multiple' => JText::_('JOPTION_SELECT_CATEGORY')]);
		JHtml::_('formbehavior.chosen', '.multipleTags', null, ['placeholder_text_multiple' => JText::_('JOPTION_SELECT_TAG')]);
		JHtml::_('formbehavior.chosen', 'select');

		self::script('regularlabs/form.min.js');
		self::style('regularlabs/form.min.css');
	}

	/**
	 * Loads the required scripts and styles used in forms
	 */
	public static function loadEditorButtonDependencies()
	{
		self::loadMainDependencies();

		JHtml::_('bootstrap.popover');
	}

	public static function loadPopupDependencies()
	{
		self::loadMainDependencies();
		self::loadFormDependencies();

		self::style('regularlabs/popup.min.css');
	}

	/**
	 * Adds a javascript declaration to the page
	 *
	 * @param string $content
	 * @param string $name
	 * @param bool   $minify
	 * @param string $type
	 */
	public static function scriptDeclaration($content = '', $name = '', $minify = true, $type = 'text/javascript')
	{
		if ($minify)
		{
			$content = self::minify($content);
		}

		if ( ! empty($name))
		{
			$content = Protect::wrapScriptDeclaration($content, $name, $minify);
		}

		JFactory::getDocument()->addScriptDeclaration($content, $type);
	}

	/**
	 * Adds a stylesheet declaration to the page
	 *
	 * @param string $content
	 * @param string $name
	 * @param bool   $minify
	 * @param string $type
	 */
	public static function styleDeclaration($content = '', $name = '', $minify = true, $type = 'text/css')
	{
		if ($minify)
		{
			$content = self::minify($content);
		}

		if ( ! empty($name))
		{
			$content = Protect::wrapStyleDeclaration($content, $name, $minify);
		}

		JFactory::getDocument()->addStyleDeclaration($content, $type);
	}

	/**
	 * Remove style/css blocks from html string
	 *
	 * @param string $string
	 * @param string $name
	 * @param string $alias
	 */
	public static function removeScriptsStyles(&$string, $name, $alias = '')
	{
		list($start, $end) = Protect::getInlineCommentTags($name, null, true);
		$alias = $alias ?: Extension::getAliasByName($name);

		$string = RegEx::replace('((?:;\s*)?)(;?)' . $start . '.*?' . $end . '\s*', '\1', $string);
		$string = RegEx::replace('\s*<link [^>]*href="[^"]*/(' . $alias . '/css|css/' . $alias . ')/[^"]*\.css[^"]*"[^>]*( /)?>', '', $string);
		$string = RegEx::replace('\s*<script [^>]*src="[^"]*/(' . $alias . '/js|js/' . $alias . ')/[^"]*\.js[^"]*"[^>]*></script>', '', $string);
	}

	/**
	 * Remove joomla script options
	 *
	 * @param string $string
	 * @param string $name
	 * @param string $alias
	 */
	public static function removeScriptsOptions(&$string, $name, $alias = '')
	{
		RegEx::match(
			'(<script type="application/json" class="joomla-script-options new">)(.*?)(</script>)',
			$string,
			$match
		);

		if (empty($match))
		{
			return;
		}

		$alias = $alias ?: Extension::getAliasByName($name);

		$scripts = json_decode($match[2]);

		if ( ! isset($scripts->{'rl_' . $alias}))
		{
			return;
		}

		unset($scripts->{'rl_' . $alias});

		$string = str_replace(
			$match[0],
			$match[1] . json_encode($scripts) . $match[3],
			$string
		);
	}

	/**
	 * Returns the document buffer
	 *
	 * @return null|string
	 */
	public static function getBuffer()
	{
		$buffer = JFactory::getDocument()->getBuffer('component');

		if (empty($buffer) || ! is_string($buffer))
		{
			return null;
		}

		$buffer = trim($buffer);

		if (empty($buffer))
		{
			return null;
		}

		return $buffer;
	}

	/**
	 * Set the document buffer
	 *
	 * @param string $buffer
	 */
	public static function setBuffer($buffer = '')
	{
		JFactory::getDocument()->setBuffer($buffer, 'component');
	}

	/**
	 * Minify the given string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function minify($string)
	{
		// place new lines around string to make regex searching easier
		$string = "\n" . $string . "\n";

		// Remove comment lines
		$string = RegEx::replace('\n\s*//.*?\n', '', $string);
		// Remove comment blocks
		$string = RegEx::replace('/\*.*?\*/', '', $string);
		// Remove enters
		$string = RegEx::replace('\n\s*', ' ', $string);

		// Remove surrounding whitespace
		$string = trim($string);

		return $string;
	}
}
regularlabs/src/EditorButtonPlugin.php000064400000007207152177723700014171 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Object\CMSObject as JObject;
use Joomla\CMS\Plugin\CMSPlugin as JPlugin;
use ReflectionClass;

/**
 * Class EditorButtonPlugin
 * @package RegularLabs\Library
 */
class EditorButtonPlugin
	extends JPlugin
{
	private $_init   = false;
	private $_helper = null;

	var $main_type            = 'plugin'; // The type of extension that holds the parameters
	var $check_installed      = null; // The types of extensions that need to be checked (will default to main_type)
	var $require_core_auth    = true; // Whether or not the core content create/edit permissions are required
	var $folder               = null; // The path to the original caller file
	var $enable_on_acymailing = false; // Whether or not to enable the editor button on AcyMailing

	/**
	 * Display the button
	 *
	 * @param string $editor_name
	 *
	 * @return JObject|null A button object
	 */
	function onDisplay($editor_name)
	{
		if ( ! $this->getHelper())
		{
			return null;
		}

		return $this->_helper->render($editor_name, $this->_subject);
	}

	/*
	 * Below methods are general functions used in most of the Regular Labs extensions
	 * The reason these are not placed in the Regular Labs Library files is that they also
	 * need to be used when the Regular Labs Library is not installed
	 */

	/**
	 * Create the helper object
	 *
	 * @return object|null The plugins helper object
	 */
	private function getHelper()
	{
		// Already initialized, so return
		if ($this->_init)
		{
			return $this->_helper;
		}

		$this->_init = true;

		if ( ! Extension::isFrameworkEnabled())
		{
			return null;
		}

		if ( ! Extension::isAuthorised($this->require_core_auth))
		{
			return null;
		}

		if ( ! $this->isInstalled())
		{
			return null;
		}

		if ( ! $this->enable_on_acymailing && JFactory::getApplication()->input->get('option') == 'com_acymailing')
		{
			return null;
		}

		$params = $this->getParams();

		if ( ! Extension::isEnabledInComponent($params))
		{
			return null;
		}

		if ( ! Extension::isEnabledInArea($params))
		{
			return null;
		}

		if ( ! $this->extraChecks($params))
		{
			return null;
		}

		require_once $this->getDir() . '/helper.php';
		$class_name    = 'PlgButton' . ucfirst($this->_name) . 'Helper';
		$this->_helper = new $class_name($this->_name, $params);

		return $this->_helper;
	}

	public function extraChecks($params)
	{
		return true;
	}

	private function getDir()
	{
		// use static::class instead of get_class($this) after php 5.4 support is dropped
		$rc = new ReflectionClass(get_class($this));

		return dirname($rc->getFileName());
	}

	private function getParams()
	{
		switch ($this->main_type)
		{
			case 'component':
				if ( ! Protect::isComponentInstalled($this->_name))
				{
					return null;
				}

				// Load component parameters
				return Parameters::getInstance()->getComponentParams($this->_name);

			case 'plugin':
			default:
				if ( ! Protect::isSystemPluginInstalled($this->_name))
				{
					return null;
				}

				// Load plugin parameters
				return Parameters::getInstance()->getPluginParams($this->_name);
		}
	}

	private function isInstalled()
	{
		$extensions = ! is_null($this->check_installed)
			? $this->check_installed
			: [$this->main_type];

		return Extension::areInstalled($this->_name, $extensions);
	}
}
regularlabs/src/Protect.php000064400000065505152177723700012015 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Access\Access as JAccess;
use Joomla\CMS\Factory as JFactory;

jimport('joomla.filesystem.file');

/**
 * Class Protect
 * @package RegularLabs\Library
 */
class Protect
{
	static $protect_start        = '<!-- ___RL_PROTECTED___';
	static $protect_end          = '___RL_PROTECTED___ -->';
	static $protect_tags_start   = '<!-- ___RL_PROTECTED_TAGS___';
	static $protect_tags_end     = '___RL_PROTECTED_TAGS___ -->';
	static $html_safe_start      = '___RL_PROTECTED___';
	static $html_safe_end        = '___/RL_PROTECTED___';
	static $html_safe_tags_start = '___RL_PROTECTED_TAGS___';
	static $html_safe_tags_end   = '___/RL_PROTECTED_TAGS___';
	static $sourcerer_tag        = null;
	static $sourcerer_characters = '{.}';

	/**
	 * Check if page should be protected for given extension
	 *
	 * @param string $extension_alias
	 *
	 * @return bool
	 */
	public static function isDisabledByUrl($extension_alias = '')
	{
		// return if disabled via url
		if (($extension_alias && JFactory::getApplication()->input->get('disable_' . $extension_alias)))
		{
			return true;
		}
	}

	/**
	 * Check if page should be protected for given extension
	 *
	 * @param bool  $hastags
	 * @param array $restricted_formats
	 *
	 * @return bool
	 */
	public static function isRestrictedPage($hastags = false, $restricted_formats = [])
	{
		$cache_id = 'isRestrictedPage_' . $hastags . '_' . json_encode($restricted_formats);

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		$input = JFactory::getApplication()->input;

		// return if current page is in protected formats
		// return if current page is an image
		// return if current page is an installation page
		// return if current page is Regular Labs QuickPage
		// return if current page is a JoomFish or Josetta page
		$is_restricted = (
			in_array($input->get('format'), $restricted_formats)
			|| in_array($input->get('view'), ['image', 'img'])
			|| in_array($input->get('type'), ['image', 'img'])
			|| in_array($input->get('task'), ['install.install', 'install.ajax_upload'])
			|| ($hastags
				&& (
					$input->getInt('rl_qp', 0)
					|| in_array($input->get('option'), ['com_joomfishplus', 'com_josetta'])
				)
			)
			|| (Document::isClient('administrator')
				&& in_array($input->get('option'), ['com_jdownloads'])
			)
		);

		return Cache::set(
			$cache_id,
			$is_restricted
		);
	}

	/**
	 * @deprecated Use isDisabledByUrl() and isRestrictedPage()
	 */
	public static function isProtectedPage($extension_alias = '', $hastags = false, $exclude_formats = [])
	{
		if (self::isDisabledByUrl($extension_alias))
		{
			return true;
		}

		return self::isRestrictedPage($hastags, $exclude_formats);
	}

	/**
	 * Check if the page is a restricted component
	 *
	 * @param array  $restricted_components
	 * @param string $area
	 *
	 * @return bool
	 */
	public static function isRestrictedComponent($restricted_components, $area = 'component')
	{
		if ($area != 'component' && ! ($area == 'article' && JFactory::getApplication()->input->get('option') == 'com_content'))
		{
			return false;
		}

		$restricted_components =
			is_array($restricted_components)
				? $restricted_components
				: explode(',', str_replace('|', ',', $restricted_components));

		if (in_array(JFactory::getApplication()->input->get('option'), $restricted_components))
		{
			return true;
		}

		if (JFactory::getApplication()->input->get('option') == 'com_acymailing'
			&& ! in_array(JFactory::getApplication()->input->get('ctrl'), ['user', 'archive'])
		)
		{
			return true;
		}

		return false;
	}

	/**
	 * Check if the component is installed
	 *
	 * @param string $extension_alias
	 *
	 * @return bool
	 */
	public static function isComponentInstalled($extension_alias)
	{
		return file_exists(JPATH_ADMINISTRATOR . '/components/com_' . $extension_alias . '/' . $extension_alias . '.php');
	}

	/**
	 * Check if the component is installed
	 *
	 * @param string $extension_alias
	 *
	 * @return bool
	 */
	public static function isSystemPluginInstalled($extension_alias)
	{
		return file_exists(JPATH_PLUGINS . '/system/' . $extension_alias . '/' . $extension_alias . '.php');
	}

	/**
	 * Return the Regular Expressions string to match:
	 * The edit form
	 *
	 * @param int $regex_format
	 *
	 * @return string
	 */
	public static function getFormRegex()
	{
		return '(<form\s[^>]*('
			. '(id|name)="(adminForm|postform|submissionForm|default_action_user|seblod_form|spEntryForm)"'
			. '|action="[^"]*option=com_myjspace&(amp;)?view=see"'
			. '))';
	}

	/**
	 * Protect all text based form fields
	 *
	 * @param string $string
	 * @param array  $search_strings
	 */
	public static function protectFields(&$string, $search_strings = [])
	{
		// No specified strings tags found in the string
		if ( ! self::containsStringsToProtect($string, $search_strings))
		{
			return;
		}

		$parts = StringHelper::split($string, ['</label>', '</select>']);

		foreach ($parts as &$part)
		{
			if ( ! self::containsStringsToProtect($part, $search_strings))
			{
				continue;
			}

			self::protectFieldsPart($part);
		}

		$string = implode('', $parts);
	}

	/**
	 * Check if the string contains certain substrings to protect
	 *
	 * @param string $string
	 * @param array  $search_strings
	 *
	 * @return bool
	 */
	private static function containsStringsToProtect($string, $search_strings = [])
	{
		if (
			empty($string)
			|| (
				strpos($string, '<input') === false
				&& strpos($string, '<textarea') === false
				&& strpos($string, '<select') === false
			)
		)
		{
			return false;
		}

		// No specified strings tags found in the string
		if ( ! empty($search_strings) && ! StringHelper::contains($string, $search_strings))
		{
			return false;
		}

		return true;
	}

	/**
	 * Protect the fields in the string
	 *
	 * @param string $string
	 */
	private static function protectFieldsPart(&$string)
	{
		self::protectFieldsTextAreas($string);
		self::protectFieldsInputFields($string);
	}

	/**
	 * Protect the textarea fields in the string
	 *
	 * @param string $string
	 */
	private static function protectFieldsTextAreas(&$string)
	{
		if (strpos($string, '<textarea') === false)
		{
			return;
		}

		// Only replace non-empty textareas
		// Todo: maybe also prevent empty textareas but with a non-empty placeholder attribute

		// Temporarily replace empty textareas
		$temp_tag = '___TEMP_TEXTAREA___';
		$string   = RegEx::replace(
			'<textarea((?:\s[^>]*)?)>(\s*)</textarea>',
			'<' . $temp_tag . '\1>\2</' . $temp_tag . '>',
			$string
		);

		self::protectByRegex(
			$string,
			'(?:'
			. '<textarea.*?</textarea>'
			. '\s*)+'
		);

		// Replace back the temporarily replaced empty textareas
		$string = str_replace($temp_tag, 'textarea', $string);
	}

	/**
	 * Protect the input fields in the string
	 *
	 * @param string $string
	 */
	private static function protectFieldsInputFields(&$string)
	{
		if (strpos($string, '<input') === false)
		{
			return;
		}

		$type_values = '(?:text|email|hidden)';
		// must be of certain type
		$param_type = '\s+type\s*=\s*(?:"' . $type_values . '"|\'' . $type_values . '\'])';
		// must have a non-empty value or placeholder attribute
		$param_value = '\s+(?:value|placeholder)\s*=\s*(?:"[^"]+"|\'[^\']+\'])';
		// Regex to match any other parameter
		$params = '(?:\s+[a-z][a-z0-9-_]*(?:\s*=\s*(?:"[^"]*"|\'[^\']*\'|[0-9]+))?)*';

		self::protectByRegex(
			$string,
			'(?:(?:'
			. '<input' . $params . $param_type . $params . $param_value . $params . '\s*/?>'
			. '|<input' . $params . $param_value . $params . $param_type . $params . '\s*/?>'
			. ')\s*)+'
		);
	}

	/**
	 * Protect the script tags
	 *
	 * @param string $string
	 */
	public static function protectScripts(&$string)
	{
		if (strpos($string, '</script>') === false)
		{
			return;
		}

		self::protectByRegex(
			$string,
			'<script[\s>].*?</script>'
		);
	}

	/**
	 * Protect all html tags with some type of attributes/content
	 *
	 * @param string $string
	 */
	public static function protectHtmlTags(&$string)
	{
		// protect comment tags
		self::protectHtmlCommentTags($string);

		// protect html tags
		self::protectByRegex($string, '<[a-z][^>]*(?:="[^"]*"|=\'[^\']*\')+[^>]*>');
	}

	/**
	 * Protect all html comment tags
	 *
	 * @param string $string
	 */
	public static function protectHtmlCommentTags(&$string)
	{
		// protect comment tags
		self::protectByRegex($string, '<\!--.*?-->');
	}

	/**
	 * Protect text by given regex
	 *
	 * @param string $string
	 * @param string $regex
	 */
	public static function protectByRegex(&$string, $regex)
	{
		RegEx::matchAll($regex, $string, $matches, null, PREG_PATTERN_ORDER);

		if (empty($matches))
		{
			return;
		}

		$matches      = array_unique($matches[0]);
		$replacements = [];

		foreach ($matches as $match)
		{
			$replacements[] = self::protectString($match);
		}

		$string = str_replace($matches, $replacements, $string);
	}

	/**
	 * Protect given plugin style tags
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param bool   $include_closing_tags
	 */
	public static function protectTags(&$string, $tags = [], $include_closing_tags = true)
	{
		list($tags, $protected) = self::prepareTags($tags, $include_closing_tags);

		$string = str_replace($tags, $protected, $string);
	}

	/**
	 * Replace any protected tags to original
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param bool   $include_closing_tags
	 */
	public static function unprotectTags(&$string, $tags = [], $include_closing_tags = true)
	{
		list($tags, $protected) = self::prepareTags($tags, $include_closing_tags);

		$string = str_replace($protected, $tags, $string);
	}

	/**
	 * Protect array of strings
	 *
	 * @param string $string
	 * @param array  $unprotected
	 * @param array  $protected
	 */
	public static function protectInString(&$string, $unprotected = [], $protected = [])
	{
		$protected = empty($protected) ? self::protectArray($unprotected) : $protected;

		$string = str_replace($unprotected, $protected, $string);
	}

	/**
	 * Replace any protected tags to original
	 *
	 * @param string $string
	 * @param array  $unprotected
	 * @param array  $protected
	 */
	public static function unprotectInString(&$string, $unprotected = [], $protected = [])
	{
		$protected = empty($protected) ? self::protectArray($unprotected) : $protected;

		$string = str_replace($protected, $unprotected, $string);
	}

	/**
	 * Return the sourcerer tag name and characters
	 *
	 * @return array
	 */
	public static function getSourcererTag()
	{
		if ( ! is_null(self::$sourcerer_tag))
		{
			return [self::$sourcerer_tag, self::$sourcerer_characters];
		}

		$parameters = Parameters::getInstance()->getPluginParams('sourcerer');

		self::$sourcerer_tag        = isset($parameters->syntax_word) ? $parameters->syntax_word : '';
		self::$sourcerer_characters = isset($parameters->tag_characters) ? $parameters->tag_characters : '{.}';

		return [self::$sourcerer_tag, self::$sourcerer_characters];
	}

	/**
	 * Protect all Sourcerer blocks
	 *
	 * @param string $string
	 */
	public static function protectSourcerer(&$string)
	{
		list($tag, $characters) = self::getSourcererTag();

		if (empty($tag))
		{
			return;
		}

		list($start, $end) = explode('.', $characters);

		if (strpos($string, $start . '/' . $tag . $end) === false)
		{
			return;
		}

		$regex = RegEx::quote($start . $tag)
			. '[\s\}].*?'
			. RegEx::quote($start . '/' . $tag . $end);

		RegEx::matchAll($regex, $string, $matches, null, PREG_PATTERN_ORDER);

		if (empty($matches))
		{
			return;
		}

		$matches = array_unique($matches[0]);

		foreach ($matches as $match)
		{
			$string = str_replace($match, self::protectString($match), $string);
		}
	}

	/**
	 * Protect complete AdminForm
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param bool   $include_closing_tags
	 */
	public static function protectForm(&$string, $tags = [], $include_closing_tags = true)
	{
		if ( ! Document::isEditPage())
		{
			return;
		}

		list($tags, $protected_tags) = self::prepareTags($tags, $include_closing_tags);

		$string = RegEx::replace(self::getFormRegex(), '<!-- TMP_START_EDITOR -->\1', $string);
		$string = explode('<!-- TMP_START_EDITOR -->', $string);

		foreach ($string as $i => &$string_part)
		{
			if (empty($string_part) || ! fmod($i, 2))
			{
				continue;
			}

			self::protectFormPart($string_part, $tags, $protected_tags);
		}

		$string = implode('', $string);
	}

	/**
	 * Protect part of the AdminForm
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param array  $protected_tags
	 */
	private static function protectFormPart(&$string, $tags = [], $protected_tags = [])
	{
		if (strpos($string, '</form>') === false)
		{
			return;
		}

		// Protect entire form
		if (empty($tags))
		{
			$form_parts    = explode('</form>', $string, 2);
			$form_parts[0] = self::protectString($form_parts[0] . '</form>');
			$string        = implode('', $form_parts);

			return;
		}

		$regex_tags = RegEx::quote($tags);

		if ( ! RegEx::match($regex_tags, $string))
		{
			return;
		}

		$form_parts = explode('</form>', $string, 2);
		// protect tags only inside form fields
		RegEx::matchAll(
			'(?:<textarea[^>]*>.*?<\/textarea>|<input[^>]*>)',
			$form_parts[0],
			$matches,
			null,
			PREG_PATTERN_ORDER
		);

		if (empty($matches))
		{
			return;
		}

		$matches = array_unique($matches[0]);

		foreach ($matches as $match)
		{
			$field         = str_replace($tags, $protected_tags, $match);
			$form_parts[0] = str_replace($match, $field, $form_parts[0]);
		}

		$string = implode('</form>', $form_parts);
	}

	/**
	 * Replace any protected text to original
	 *
	 * @param string|array $string
	 */
	public static function unprotect(&$string)
	{
		if (is_array($string))
		{
			foreach ($string as &$part)
			{
				self::unprotect($part);
			}

			return;
		}

		self::unprotectByDelimiters(
			$string,
			[self::$protect_tags_start, self::$protect_tags_end]
		);

		self::unprotectByDelimiters(
			$string,
			[self::$protect_start, self::$protect_end]
		);

		if (StringHelper::contains($string, [self::$protect_tags_start, self::$protect_tags_end, self::$protect_start, self::$protect_end]))
		{
			self::unprotect($string);
		}
	}

	/**
	 * @param string $string
	 * @param array  $delimiters
	 */
	private static function unprotectByDelimiters(&$string, $delimiters)
	{
		if ( ! StringHelper::contains($string, $delimiters))
		{
			return;
		}

		$regex = RegEx::preparePattern(RegEx::quote($delimiters), 's', $string);

		$parts = preg_split($regex, $string);

		foreach ($parts as $i => &$part)
		{
			if ($i % 2 == 0)
			{
				continue;
			}

			$part = base64_decode($part);
		}

		$string = implode('', $parts);
	}

	/**
	 * Replace any protected text to original
	 *
	 * @param string $string
	 */
	public static function convertProtectionToHtmlSafe(&$string)
	{
		$string = str_replace(
			[
				self::$protect_start,
				self::$protect_end,
				self::$protect_tags_start,
				self::$protect_tags_end,
			],
			[
				self::$html_safe_start,
				self::$html_safe_end,
				self::$html_safe_tags_start,
				self::$html_safe_tags_end,
			],
			$string
		);
	}

	/**
	 * Replace any protected text to original
	 *
	 * @param string $string
	 */
	public static function unprotectHtmlSafe(&$string)
	{
		$string = str_replace(
			[
				self::$html_safe_start,
				self::$html_safe_end,
				self::$html_safe_tags_start,
				self::$html_safe_tags_end,
			],
			[
				self::$protect_start,
				self::$protect_end,
				self::$protect_tags_start,
				self::$protect_tags_end,
			],
			$string
		);

		self::unprotect($string);
	}

	/**
	 * Prepare the tags and protected tags array
	 *
	 * @param array $tags
	 * @param bool  $include_closing_tags
	 *
	 * @return bool|mixed
	 */
	private static function prepareTags($tags, $include_closing_tags = true)
	{
		if ( ! is_array($tags))
		{
			$tags = [$tags];
		}

		$cache_id = 'prepareTags_' . json_encode($tags) . '_' . $include_closing_tags;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		foreach ($tags as $i => $tag)
		{
			if (StringHelper::is_alphanumeric($tag[0]))
			{
				$tag = '{' . $tag;
			}

			$tags[$i] = $tag;

			if ($include_closing_tags)
			{
				$tags[] = RegEx::replace('^([^a-z0-9]+)', '\1/', $tag);
			}
		}

		return Cache::set(
			$cache_id,
			[$tags, self::protectArray($tags, 1)]
		);
	}

	/**
	 * Encode string
	 *
	 * @param string $string
	 * @param int    $is_tag
	 *
	 * @return string
	 */
	public static function protectString($string, $is_tag = false)
	{
		if ($is_tag)
		{
			return self::$protect_tags_start . base64_encode($string) . self::$protect_tags_end;
		}

		return self::$protect_start . base64_encode($string) . self::$protect_end;
	}

	/**
	 * Decode string
	 *
	 * @param string $string
	 * @param int    $is_tag
	 *
	 * @return string
	 */
	public static function unprotectString($string, $is_tag = false)
	{
		if ($is_tag)
		{
			return self::$protect_tags_start . base64_decode($string) . self::$protect_tags_end;
		}

		return self::$protect_start . base64_decode($string) . self::$protect_end;
	}

	/**
	 * Encode tag string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public static function protectTag($string)
	{
		return self::protectString($string, 1);
	}

	/**
	 * Encode array of strings
	 *
	 * @param array $array
	 * @param int   $is_tag
	 *
	 * @return mixed
	 */
	public static function protectArray($array, $is_tag = false)
	{
		foreach ($array as &$string)
		{
			$string = self::protectString($string, $is_tag);
		}

		return $array;
	}

	/**
	 * Decode array of strings
	 *
	 * @param array $array
	 * @param int   $is_tag
	 *
	 * @return mixed
	 */
	public static function unprotectArray($array, $is_tag = false)
	{
		foreach ($array as &$string)
		{
			$string = self::unprotectString($string, $is_tag);
		}

		return $array;
	}

	/**
	 * Replace any protected tags to original
	 *
	 * @param string $string
	 * @param array  $tags
	 */
	public static function unprotectForm(&$string, $tags = [])
	{
		// Protect entire form
		if (empty($tags))
		{
			self::unprotect($string);

			return;
		}

		self::unprotectTags($string, $tags);
	}

	/**
	 * Wrap string in comment tags
	 *
	 * @param string $name
	 * @param string $comment
	 *
	 * @return string
	 */
	public static function wrapInCommentTags($name, $string)
	{
		list($start, $end) = self::getCommentTags($name);

		return $start . $string . $end;
	}

	/**
	 * Get the html comment tags
	 *
	 * @param string $name
	 *
	 * @return array
	 */
	public static function getCommentTags($name = '')
	{
		return [self::getCommentStartTag($name), self::getCommentEndTag($name)];
	}

	/**
	 * Get the html start comment tags
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	public static function getCommentStartTag($name = '')
	{
		return '<!-- START: ' . $name . ' -->';
	}

	/**
	 * Get the html end comment tags
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	public static function getCommentEndTag($name = '')
	{
		return '<!-- END: ' . $name . ' -->';
	}

	/**
	 * Create a html comment from given comment string
	 *
	 * @param string $name
	 * @param string $comment
	 *
	 * @return string
	 */
	public static function getMessageCommentTag($name, $comment)
	{
		list($start, $end) = self::getMessageCommentTags($name);

		return $start . $comment . $end;
	}

	/**
	 * Get the start and end parts for the html message comment tag
	 *
	 * @param string $name
	 *
	 * @return array
	 */
	public static function getMessageCommentTags($name = '')
	{
		return ['<!--  ' . $name . ' Message: ', ' -->'];
	}

	/**
	 * Get the start and end parts for the inline comment tags for scripts/styles
	 *
	 * @param string $name
	 * @param string $type
	 *
	 * @return array
	 */
	public static function getInlineCommentTags($name = '', $type = '', $regex = false)
	{
		if ($regex)
		{
			$type = 'TYPE_PLACEHOLDER';
		}

		$start = '/* START: ' . $name . ' ' . $type . ' */';
		$end   = '/* END: ' . $name . ' ' . $type . ' */';

		if ($regex)
		{
			$start = str_replace($type, '[a-z]*', RegEx::quote($start));
			$end   = str_replace($type, '[a-z]*', RegEx::quote($end));
		}

		return [$start, $end];
	}

	/**
	 * Wraps a style or javascript declaration with comment tags
	 *
	 * @param string $content
	 * @param string $name
	 * @param string $type
	 * @param bool   $minify
	 */
	public static function wrapDeclaration($content = '', $name = '', $type = 'styles', $minify = true)
	{
		if (empty($name))
		{
			return $content;
		}

		list($start, $end) = self::getInlineCommentTags($name, $type);

		$spacer = $minify ? ' ' : "\n";

		return $start . $spacer . $content . $spacer . $end;
	}

	/**
	 * Wraps a javascript declaration with comment tags
	 *
	 * @param string $content
	 * @param string $name
	 * @param bool   $minify
	 */
	public static function wrapScriptDeclaration($content = '', $name = '', $minify = true)
	{
		return self::wrapDeclaration($content, $name, 'scripts', $minify);
	}

	/**
	 * Wraps a stylesheet declaration with comment tags
	 *
	 * @param string $content
	 * @param string $name
	 * @param bool   $minify
	 */
	public static function wrapStyleDeclaration($content = '', $name = '', $minify = true)
	{
		return self::wrapDeclaration($content, $name, 'styles', $minify);
	}

	/**
	 * Remove area comments in html
	 *
	 * @param string $string
	 * @param string $prefix
	 */
	public static function removeAreaTags(&$string, $prefix = '')
	{
		$string = RegEx::replace('<!-- (START|END): ' . $prefix . '_[A-Z]+ -->', '', $string, 's');
	}

	/**
	 * Remove comments in html
	 *
	 * @param string $string
	 * @param string $name
	 */
	public static function removeCommentTags(&$string, $name = '')
	{
		list($start, $end) = self::getCommentTags($name);

		$string = str_replace(
			[
				$start, $end,
				htmlentities($start), htmlentities($end),
				urlencode($start), urlencode($end),
			], '', $string
		);

		list($start, $end) = self::getMessageCommentTags($name);

		$string = RegEx::replace(
			RegEx::quote($start) . '.*?' . RegEx::quote($end),
			'',
			$string
		);
	}

	/**
	 * Remove inline comments in scrips and styles
	 *
	 * @param string $string
	 * @param string $name
	 */
	public static function removeInlineComments(&$string, $name)
	{
		list($start, $end) = Protect::getInlineCommentTags($name, null, true);
		$string = RegEx::replace('(' . $start . '|' . $end . ')', "\n", $string);
	}

	/**
	 * Remove left over plugin tags
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param string $character_start
	 * @param string $character_end
	 * @param bool   $keep_content
	 */
	public static function removePluginTags(&$string, $tags, $character_start = '{', $character_end = '{', $keep_content = true)
	{
		$character_start = RegEx::quote($character_start);
		$character_end   = RegEx::quote($character_end);

		foreach ($tags as $tag)
		{
			if ( ! is_array($tag))
			{
				$tag = [$tag, $tag];
			}

			if (count($tag) < 2)
			{
				$tag = [$tag[0], $tag[0]];
			}

			$regex = $character_start . RegEx::quote($tag[0]) . '(?:\s.*?)?' . $character_end
				. '(.*?)'
				. $character_start . '/' . RegEx::quote($tag[1]) . $character_end;

			$replace = $keep_content ? '\1' : '';

			$string = RegEx::replace($regex, $replace, $string);
		}
	}

	/**
	 * Remove tags from title tags
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param bool   $include_closing_tags
	 * @param array  $html_tags
	 */
	public static function removeFromHtmlTagContent(&$string, $tags, $include_closing_tags = true, $html_tags = ['title'])
	{
		list($tags, $protected) = self::prepareTags($tags, $include_closing_tags);

		if ( ! is_array($html_tags))
		{
			$html_tags = [$html_tags];
		}

		RegEx::matchAll('(<(' . implode('|', $html_tags) . ')(?:\s[^>]*?)>)(.*?)(</\2>)', $string, $matches);

		if (empty($matches))
		{
			return;
		}

		foreach ($matches as $match)
		{
			$content = $match[3];
			foreach ($tags as $tag)
			{
				$content = RegEx::replace(RegEx::quote($tag) . '.*?\}', '', $content);
			}
			$string = str_replace($match[0], $match[1] . $content . $match[4], $string);
		}
	}

	/**
	 * Remove tags from tag attributes
	 *
	 * @param string $string
	 * @param array  $tags
	 * @param string $attributes
	 * @param bool   $include_closing_tags
	 */
	public static function removeFromHtmlTagAttributes(&$string, $tags, $attributes = 'ALL', $include_closing_tags = true)
	{
		list($tags, $protected) = self::prepareTags($tags, $include_closing_tags);

		if ($attributes == 'ALL')
		{
			$attributes = ['[a-z][a-z0-9-_]*'];
		}

		if ( ! is_array($attributes))
		{
			$attributes = [$attributes];
		}

		RegEx::matchAll(
			'\s(?:' . implode('|', $attributes) . ')\s*=\s*".*?"',
			$string,
			$matches,
			null,
			PREG_PATTERN_ORDER
		);

		if (empty($matches) || empty($matches[0]))
		{
			return;
		}

		$matches = array_unique($matches[0]);

		// preg_quote all tags
		$tags_regex = RegEx::quote($tags) . '.*?\}';

		foreach ($matches as $match)
		{
			if ( ! StringHelper::contains($match, $tags))
			{
				continue;
			}

			$title = $match;

			$title = RegEx::replace($tags_regex, '', $title);

			$string = StringHelper::replaceOnce($match, $title, $string);
		}
	}

	/**
	 * Check if article passes security levels
	 *
	 * @param object $article
	 * @param array  $securtiy_levels
	 *
	 * @return bool|int
	 */
	public static function articlePassesSecurity(&$article, $securtiy_levels = [])
	{
		if ( ! isset($article->created_by))
		{
			return true;
		}

		if (empty($securtiy_levels))
		{
			return true;
		}

		if (is_string($securtiy_levels))
		{
			$securtiy_levels = [$securtiy_levels];
		}

		if (
			! is_array($securtiy_levels)
			|| in_array('-1', $securtiy_levels)
		)
		{
			return true;
		}

		// Lookup group level of creator
		$user_groups = new JAccess;
		$user_groups = $user_groups->getGroupsByUser($article->created_by);

		// Return true if any of the security levels are found in the users groups
		return count(array_intersect($user_groups, $securtiy_levels));
	}

	/**
	 * Replace in protect array
	 *
	 * @param array  $array
	 * @param string $search
	 * @param string $replacement
	 */
	public static function replaceInArray(&$array, $search, $replacement)
	{
		foreach ($array as $key => &$string)
		{
			// only do something if string is not empty
			// or on uneven count = not yet protected
			if (trim($string) == '' || fmod($key, 2))
			{
				continue;
			}

			$array[$key] = str_replace($search, $replacement, $string);
		}
	}

	/**
	 * Replace in protect array using Regular Expressions
	 *
	 * @param array  $array
	 * @param string $search
	 * @param string $replacement
	 */
	public static function pregReplaceInArray(&$array, $search, $replacement)
	{
		foreach ($array as $key => &$string)
		{
			// only do something if string is not empty
			// or on uneven count = not yet protected
			if (trim($string) == '' || fmod($key, 2))
			{
				continue;
			}

			$array[$key] = RegEx::replace($search, $replacement, $string);
		}
	}
}
regularlabs/src/Version.php000064400000017717152177723700012024 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Router\Route as JRoute;
use Joomla\CMS\Session\Session as JSession;
use Joomla\CMS\Uri\Uri as JUri;

jimport('joomla.filesystem.file');

/**
 * Class Version
 * @package RegularLabs\Library
 */
class Version
{
	/**
	 * Get the version of the given extension
	 *
	 * @param        $alias
	 * @param string $type
	 * @param string $folder
	 *
	 * @return string
	 */
	public static function get($alias, $type = 'component', $folder = 'system')
	{
		return trim(Extension::getXmlValue('version', $alias, $type, $folder));
	}

	/**
	 * Get the version of the given plugin
	 *
	 * @param        $alias
	 * @param string $folder
	 *
	 * @return string
	 */
	public static function getPluginVersion($alias, $folder = 'system')
	{
		return self::get($alias, 'plugin', $folder);
	}

	/**
	 * Get the version of the given component
	 *
	 * @param $alias
	 *
	 * @return string
	 */
	public static function getComponentVersion($alias)
	{
		return self::get($alias, 'component');
	}

	/**
	 * Get the version of the given module
	 *
	 * @param $alias
	 *
	 * @return string
	 */
	public static function getModuleVersion($alias)
	{
		return self::get($alias, 'module');
	}

	/**
	 * Get the version message
	 *
	 * @param $alias
	 *
	 * @return string
	 */
	public static function getMessage($alias)
	{
		if ( ! $alias)
		{
			return '';
		}

		$name  = Extension::getNameByAlias($alias);
		$alias = Extension::getAliasByName($alias);

		if ( ! $version = self::get($alias))
		{
			return '';
		}

		Document::loadMainDependencies();

		$url    = 'download.regularlabs.com/extensions.xml?j=3&e=' . $alias;
		$script = "
			jQuery(document).ready(function() {
				RegularLabsScripts.loadajax(
					'" . $url . "',
					'RegularLabsScripts.displayVersion( data, \"" . $alias . "\", \"" . str_replace(['FREE', 'PRO'], '', $version) . "\" )',
					'RegularLabsScripts.displayVersion( \"\" )',
					null, null, null, (60 * 60)
				);
			});
		";
		JFactory::getDocument()->addScriptDeclaration($script);

		return '<div class="alert alert-success" style="display:none;" id="regularlabs_version_' . $alias . '">' . self::getMessageText($alias, $name, $version) . '</div>';
	}

	/**
	 * Get the full footer
	 *
	 * @param     $name
	 * @param int $copyright
	 *
	 * @return string
	 */
	public static function getFooter($name, $copyright = true)
	{
		Document::loadMainDependencies();

		$html = [];

		$html[] = '<div class="rl_footer_extension">' . self::getFooterName($name) . '</div>';

		if ($copyright)
		{
			$html[] = '<div class="rl_footer_review">' . self::getFooterReview($name) . '</div>';
			$html[] = '<div class="rl_footer_logo">' . self::getFooterLogo() . '</div>';
			$html[] = '<div class="rl_footer_copyright">' . self::getFooterCopyright() . '</div>';
		}

		return '<div class="rl_footer">' . implode('', $html) . '</div>';
	}

	/**
	 * Get the version message text
	 *
	 * @param $alias
	 * @param $name
	 * @param $version
	 *
	 * @return array|string
	 */
	private static function getMessageText($alias, $name, $version)
	{
		list($url, $onclick) = self::getUpdateLink($alias, $version);

		$href    = $onclick ? '' : 'href="' . $url . '" target="_blank" ';
		$onclick = $onclick ? 'onclick="' . $onclick . '" ' : '';

		$is_pro  = strpos($version, 'PRO') !== false;
		$version = str_replace(['FREE', 'PRO'], ['', ' <small>[PRO]</small>'], $version);

		$msg = '<div class="text-center">'
			. '<span class="ghosted">'
			. JText::sprintf('RL_NEW_VERSION_OF_AVAILABLE', JText::_($name))
			. '</span>'
			. '<br>'
			. '<a ' . $href . $onclick . ' class="btn btn-large btn-success">'
			. '<span class="icon-upload"></span> '
			. StringHelper::html_entity_decoder(JText::sprintf('RL_UPDATE_TO', '<span id="regularlabs_newversionnumber_' . $alias . '"></span>'))
			. '</a>';

		if ( ! $is_pro)
		{
			$msg .= ' <a href="https://www.regularlabs.com/purchase?ext=' . $alias . '" target="_blank" class="btn btn-large btn-primary">'
				. '<span class="icon-basket"></span> '
				. JText::_('RL_GO_PRO')
				. '</a>';
		}

		$msg .= '<br>'
			. '<span class="ghosted">'
			. '[ <a href="https://www.regularlabs.com/' . $alias . '/changelog" target="_blank">'
			. JText::_('RL_CHANGELOG')
			. '</a> ]'
			. '<br>'
			. JText::sprintf('RL_CURRENT_VERSION', $version)
			. '</span>'
			. '</div>';

		return StringHelper::html_entity_decoder($msg);
	}

	/**
	 * Get the url and onclick function for the update link
	 *
	 * @param $alias
	 * @param $version
	 *
	 * @return array
	 */
	private static function getUpdateLink($alias, $version)
	{
		$is_pro = strpos($version, 'PRO') !== false;

		if (
			! file_exists(JPATH_ADMINISTRATOR . '/components/com_regularlabsmanager/regularlabsmanager.xml')
			|| ! JComponentHelper::isInstalled('com_regularlabsmanager')
			|| ! JComponentHelper::isEnabled('com_regularlabsmanager')
		)
		{
			$url = $is_pro
				? 'https://www.regularlabs.com/' . $alias . '/features'
				: JRoute::_('index.php?option=com_installer&view=update');

			return [$url, ''];
		}

		$config = JComponentHelper::getParams('com_regularlabsmanager');

		$key = trim($config->get('key'));

		if ($is_pro && ! $key)
		{
			return ['index.php?option=com_regularlabsmanager', ''];
		}

		jimport('joomla.filesystem.file');

		Document::loadMainDependencies();
		JHtml::_('behavior.modal');

		JFactory::getDocument()->addScriptDeclaration(
			"
			var RLEM_TIMEOUT = " . (int) $config->get('timeout', 5) . ";
			var RLEM_TOKEN = '" . JSession::getFormToken() . "';
		"
		);
		Document::script('regularlabsmanager/script.min.js', '19.7.21312');

		$url = 'https://download.regularlabs.com?ext=' . $alias . '&j=3';

		if ($is_pro)
		{
			$url .= '&k=' . strtolower(substr($key, 0, 8) . md5(substr($key, 8)));
		}

		return ['', 'RegularLabsManager.openModal(\'update\', [\'' . $alias . '\'], [\'' . $url . '\'], true);'];
	}

	/**
	 * Get the extension name and version for the footer
	 *
	 * @param $name
	 *
	 * @return string
	 */
	private static function getFooterName($name)
	{
		$name = JText::_($name);

		if ( ! $version = self::get($name))
		{
			return $name;
		}

		if (strpos($version, 'PRO') !== false)
		{
			return $name . ' v' . str_replace('PRO', '', $version) . ' <small>[PRO]</small>';
		}

		if (strpos($version, 'FREE') !== false)
		{
			return $name . ' v' . str_replace('FREE', '', $version) . ' <small>[FREE]</small>';
		}

		return $name . ' v' . $version;
	}

	/**
	 * Get the review text for the footer
	 *
	 * @param $name
	 *
	 * @return string
	 */
	private static function getFooterReview($name)
	{
		$alias = Extension::getAliasByName($name);

		$jed_url = 'http://regl.io/jed-' . $alias . '#reviews';

		return StringHelper::html_entity_decoder(
			JText::sprintf(
				'RL_JED_REVIEW',
				'<a href="' . $jed_url . '" target="_blank">',
				'</a>'
				. ' <a href="' . $jed_url . '" target="_blank" class="stars">'
				. str_repeat('<span class="icon-star"></span>', 5)
				. '</a>'
			)
		);
	}

	/**
	 * Get the Regular Labs logo for the footer
	 *
	 * @return string
	 */
	private static function getFooterLogo()
	{
		return JText::sprintf(
			'RL_POWERED_BY',
			'<a href="https://www.regularlabs.com" target="_blank">'
			. '<img src="' . JUri::root() . 'media/regularlabs/images/logo.png" width="135" height="24" alt="Regular Labs">'
			. '</a>'
		);
	}

	/**
	 * Get the copyright text for the footer
	 *
	 * @return string
	 */
	private static function getFooterCopyright()
	{
		return JText::_('RL_COPYRIGHT') . ' &copy; ' . date('Y') . ' Regular Labs - ' . JText::_('RL_ALL_RIGHTS_RESERVED');
	}
}
regularlabs/src/Title.php000064400000004670152177723700011452 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * Class Title
 * @package RegularLabs\Library
 */
class Title
{
	/**
	 * Cleans the string to make it usable as a title
	 *
	 * @param string $string
	 * @param bool   $strip_tags
	 * @param bool   $strip_spaces
	 *
	 * @return string
	 */
	public static function clean($string = '', $strip_tags = false, $strip_spaces = true)
	{
		if (empty($string))
		{
			return '';
		}

		// remove comment tags
		$string = RegEx::replace('<\!--.*?-->', '', $string);

		// replace weird whitespace
		$string = str_replace(chr(194) . chr(160), ' ', $string);

		if ($strip_tags)
		{
			// remove svgs
			$string = RegEx::replace('<svg.*?</svg>', '', $string);
			// remove html tags
			$string = RegEx::replace('</?[a-z][^>]*>', '', $string);
			// remove comments tags
			$string = RegEx::replace('<\!--.*?-->', '', $string);
		}

		if ($strip_spaces)
		{
			// Replace html spaces
			$string = str_replace(['&nbsp;', '&#160;'], ' ', $string);

			// Remove duplicate whitespace
			$string = RegEx::replace('[ \n\r\t]+', ' ', $string);
		}

		return trim($string);
	}

	/**
	 * Creates an array of different syntaxes of titles to match against a url variable
	 *
	 * @param array $titles
	 *
	 * @return array
	 */
	public static function getUrlMatches($titles = [])
	{
		$matches = [];
		foreach ($titles as $title)
		{
			$matches[] = $title;
			$matches[] = StringHelper::strtolower($title);
		}

		$matches = array_unique($matches);

		foreach ($matches as $title)
		{
			$matches[] = htmlspecialchars(StringHelper::html_entity_decoder($title));
		}

		$matches = array_unique($matches);

		foreach ($matches as $title)
		{
			$matches[] = urlencode($title);
			$matches[] = utf8_decode($title);
			$matches[] = str_replace(' ', '', $title);
			$matches[] = trim(RegEx::replace('[^a-z0-9]', '', $title));
			$matches[] = trim(RegEx::replace('[^a-z]', '', $title));
		}

		$matches = array_unique($matches);

		foreach ($matches as $i => $title)
		{
			$matches[$i] = trim(str_replace('?', '', $title));
		}

		$matches = array_diff(array_unique($matches), ['', '-']);

		return $matches;
	}
}
regularlabs/src/Image.php000064400000017511152177723700011411 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Image\Image as JImage;
use Joomla\CMS\Uri\Uri as JUri;

class Image
{
//	public static function getSet($source, $width, $height, $folder = 'resized', $resize = true, $quality = 'medium', $possible_suffix = '')
//	{
//		$paths = self::getPaths($source, $width, $height, $folder, $resize, $quality, $possible_suffix);
//
//		return (object) [
//			'original'     => (object) [
//				'url'    => $paths->image,
//				'width'  => self::getWidth($paths->original),
//				'height' => self::getHeight($paths->original),
//			],
//			'resized' => (object) [
//				'url'    => $paths->resized,
//				'width'  => self::getWidth($paths->resized),
//				'height' => self::getHeight($paths->resized),
//			],
//		];
//	}

	public static function getUrls($source, $width, $height, $folder = 'resized', $resize = true, $quality = 'medium', $possible_suffix = '')
	{
		if ($image = self::isResized($source, $folder, $possible_suffix))
		{
			$source = $image;
		}

		$original = $source;
		$resized  = self::getResize($source, $width, $height, $folder, $resize, $quality);

		return (object) compact('original', 'resized');
	}

	public static function getResize($source, $width, $height, $folder = 'resized', $resize = true, $quality = 'medium')
	{
		$destination_folder = File::getDirName($source) . '/' . $folder;

		$override = File::getDirName($source) . '/' . $folder . '/' . File::getBaseName($source);

		if (file_exists(JPATH_SITE . '/' . $override))
		{
			$source = $override;
		}

		if ( ! self::setNewDimensions($source, $width, $height))
		{
			return $source;
		}

		if ( ! $width && ! $height)
		{
			return $source;
		}

		$destination = self::getNewPath(
			$source,
			$width,
			$height,
			$destination_folder
		);

		if ( ! file_exists(JPATH_SITE . '/' . $destination) && $resize)
		{
			// Create new resized image
			$destination = self::resize(
				$source,
				$width,
				$height,
				$destination_folder,
				$quality
			);
		}

		if ( ! file_exists(JPATH_SITE . '/' . $destination))
		{
			return $source;
		}

		return $destination;
	}

	public static function isResized($file, $folder = 'resized', $possible_suffix = '')
	{
		if (File::isExternal($file))
		{
			return false;
		}

		if ( ! file_exists($file))
		{
			return false;
		}

		if ($main_image = self::isResizedWithFolder($file, $folder))
		{
			return $main_image;
		}

		if ($possible_suffix && $main_image = self::isResizedWithSuffix($file, $possible_suffix))
		{
			return $main_image;
		}

		return false;
	}

	public static function isResizedWithSuffix($file, $suffix = '_t')
	{
		// Remove the suffix from the file
		// image_t.jpg => image.jpg
		$main_file = RegEx::replace(
			RegEx::quote($suffix) . '(\.[^.]+)$',
			'\1',
			$file
		);

		// Nothing removed, so not a resized image
		if ($main_file == $file)
		{
			return false;
		}

		if ( ! file_exists(JPATH_SITE . '/' . utf8_decode($main_file)))
		{
			return false;
		}

		return $main_file;
	}

	private static function isResizedWithFolder($file, $resize_folder = 'resized')
	{
		$folder             = File::getDirName($file);
		$file               = File::getBaseName($file);
		$parent_folder_name = File::getBaseName($folder);
		$parent_folder      = File::getDirName($folder);

		// Image is not inside the resize folder
		if ($parent_folder_name != $resize_folder)
		{
			return false;
		}

		// Check if image with same name exists in parent folder
		if (file_exists(JPATH_SITE . '/' . $parent_folder . '/' . utf8_decode($file)))
		{
			return $parent_folder . '/' . $file;
		}

		// Remove any dimensions from the file
		// image_300x200.jpg => image.jpg
		$file = RegEx::replace(
			'_[0-9]+x[0-9]*(\.[^.]+)$',
			'\1',
			$file
		);

		// Check again if image with same name (but without dimensions) exists in parent folder
		if (file_exists(JPATH_SITE . '/' . $parent_folder . '/' . utf8_decode($file)))
		{
			return $parent_folder . '/' . $file;
		}

		return false;
	}

	public static function resize($source, &$width, &$height, $destination_folder = '', $quality = 'medium', $overwrite = false)
	{
		if (File::isExternal($source))
		{
			return $source;
		}

		$clean_source = ltrim(str_replace(JUri::root(), '', $source), '/');
		$source_path  = JPATH_SITE . '/' . $clean_source;

		$destination_folder = ltrim($destination_folder ?: File::getDirName($clean_source));

		if ( ! file_exists($source_path))
		{
			return false;
		}

		if ( ! self::setNewDimensions($source, $width, $height))
		{
			return $source;
		}

		if ( ! $width && ! $height)
		{
			return $source;
		}

		$image = new JImage($source_path);

		$destination      = self::getNewPath($source, $width, $height, $destination_folder);
		$destination_path = JPATH_SITE . '/' . $destination;

		if (file_exists($destination_path) && ! $overwrite)
		{
			return $destination;
		}

		JFolder::create(JPATH_SITE . '/' . $destination_folder);

		$info = JImage::getImageFileProperties($source_path);

		$options = ['quality' => self::getQuality($info->type, $quality)];

		$image->cropResize($width, $height, false)
			->toFile($destination_path, $info->type, $options);

		$image->destroy();

		return $destination;
	}

	public static function setNewDimensions($source, &$width, &$height)
	{
		if ( ! $width && ! $height)
		{
			return false;
		}

		if (File::isExternal($source))
		{
			return false;
		}

		$clean_source = ltrim(str_replace(JUri::root(), '', $source), '/');
		$source_path  = JPATH_SITE . '/' . $clean_source;

		if ( ! file_exists($source_path))
		{
			return false;
		}

		$image = new JImage($source_path);

		$original_width  = $image->getWidth();
		$original_height = $image->getHeight();

		$width  = $width ?: round($original_width / $original_height * $height);
		$height = $height ?: round($original_height / $original_width * $width);

		$image->destroy();

		if ($width == $original_width && $height == $original_height)
		{
			return false;
		}

		return true;
	}

	public static function getNewPath($source, $width, $height, $destination_folder = '')
	{
		$clean_source = self::cleanPath($source);

		$source_parts = pathinfo($clean_source);

		$destination_folder = ltrim($destination_folder ?: File::getDirName($clean_source));
		$destination_file   = File::getFileName($clean_source) . '_' . $width . 'x' . $height . '.' . $source_parts['extension'];

		JFolder::create(JPATH_SITE . '/' . $destination_folder);

		return ltrim($destination_folder . '/' . $destination_file);
	}

	public static function cleanPath($source)
	{
		return ltrim(str_replace(JUri::root(), '', $source), '/');
	}

	public static function getWidth($source)
	{
		$dimensions = self::getDimensions($source);

		return $dimensions->width;
	}

	public static function getHeight($source)
	{
		$dimensions = self::getDimensions($source);

		return $dimensions->height;
	}

	public static function getDimensions($source)
	{
		if (File::isExternal($source))
		{
			return (object) [
				'width'  => 0,
				'height' => 0,
			];
		}

		$image = new JImage(JPATH_SITE . '/' . $source);

		return (object) [
			'width'  => $image->getWidth(),
			'height' => $image->getHeight(),
		];
	}

	public static function getQuality($type, $quality = 'medium')
	{
		switch ($type)
		{
			case IMAGETYPE_JPEG:
				return min(max(self::getJpgQuality($quality), 0), 100);

			case IMAGETYPE_PNG:
				return 9;

			default:
				return '';
		}
	}

	public static function getJpgQuality($quality = 'medium')
	{
		switch ($quality)
		{
			case 'low':
				return 50;

			case 'high':
				return 90;

			case 'medium':
			default:
				return 70;
		}
	}

}
regularlabs/src/Http.php000064400000007351152177723700011307 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Http\HttpFactory as JHttpFactory;
use Joomla\Registry\Registry;
use RuntimeException;

/**
 * Class Http
 * @package RegularLabs\Library
 */
class Http
{
	/**
	 * Get the contents of the given internal url
	 *
	 * @param string $url
	 * @param int    $timeout
	 *
	 * @return string
	 */
	public static function get($url, $timeout = 20)
	{
		if (Uri::isExternal($url))
		{
			return '';
		}

		return self::getFromUrl($url, $timeout);
	}

	/**
	 * Get the contents of the given url
	 *
	 * @param string $url
	 * @param int    $timeout
	 *
	 * @return string
	 */
	public static function getFromUrl($url, $timeout = 20)
	{
		$cache_id = 'getUrl_' . $url;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if (JFactory::getApplication()->input->getInt('cache', 0)
			&& $content = Cache::read($cache_id)
		)
		{
			return $content;
		}

		$content = self::getContents($url, $timeout);

		if (empty($content))
		{
			return '';
		}

		if ($ttl = JFactory::getApplication()->input->getInt('cache', 0))
		{
			return Cache::write($cache_id, $content, $ttl > 1 ? $ttl : 0);
		}

		return Cache::set($cache_id, $content);
	}

	/**
	 * Get the contents of the given external url from the Regular Labs server
	 *
	 * @param string $url
	 * @param int    $timeout
	 *
	 * @return string
	 */
	public static function getFromServer($url, $timeout = 20)
	{
		$cache_id = 'getByUrl_' . $url;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		// only allow url calls from administrator
		if ( ! Document::isClient('administrator'))
		{
			die;
		}

		// only allow when logged in
		$user = JFactory::getUser();
		if ( ! $user->id)
		{
			die;
		}

		if (substr($url, 0, 4) != 'http')
		{
			$url = 'http://' . $url;
		}

		// only allow url calls to regularlabs.com domain
		if ( ! (RegEx::match('^https?://([^/]+\.)?regularlabs\.com/', $url)))
		{
			die;
		}

		// only allow url calls to certain files
		if (
			strpos($url, 'download.regularlabs.com/extensions.php') === false
			&& strpos($url, 'download.regularlabs.com/extensions.json') === false
			&& strpos($url, 'download.regularlabs.com/extensions.xml') === false
		)
		{
			die;
		}

		$content = self::getContents($url, $timeout);

		if (empty($content))
		{
			return '';
		}

		$format = (strpos($url, '.json') !== false || strpos($url, 'format=json') !== false)
			? 'application/json'
			: 'text/xml';

		header("Pragma: public");
		header("Expires: 0");
		header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
		header("Cache-Control: public");
		header("Content-type: " . $format);

		if ($ttl = JFactory::getApplication()->input->getInt('cache', 0))
		{
			return Cache::write($cache_id, $content, $ttl > 1 ? $ttl : 0);
		}

		return Cache::set($cache_id, $content);
	}

	/**
	 * Load the contents of the given url
	 *
	 * @param string $url
	 * @param int    $timeout
	 *
	 * @return string
	 */
	private static function getContents($url, $timeout = 20)
	{
		try
		{
			// Adding a valid user agent string, otherwise some feed-servers returning an error
			$options = new Registry([
				'userAgent' => 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0',
			]);

			$content = JHttpFactory::getHttp($options)->get($url, null, $timeout)->body;
		}
		catch (RuntimeException $e)
		{
			return '';
		}

		return $content;
	}

}
regularlabs/src/Database.php000064400000000720152177723700012065 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

/**
 * @depecated Use DB instead
 */
class Database extends DB
{
}
regularlabs/src/Extension.php000064400000025527152177723700012351 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Filesystem\Folder as JFolder;
use Joomla\CMS\Component\ComponentHelper as JComponentHelper;
use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Helper\ModuleHelper as JModuleHelper;
use Joomla\CMS\Installer\Installer as JInstaller;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');

/**
 * Class Extension
 * @package RegularLabs\Library
 */
class Extension
{
	/**
	 * Get the full path to the extension folder
	 *
	 * @param string $extension
	 * @param string $basePath
	 * @param string $check_folder
	 *
	 * @return string
	 */
	public static function getPath($extension = 'plg_system_regularlabs', $basePath = JPATH_ADMINISTRATOR, $check_folder = '')
	{
		$basePath = $basePath ?: JPATH_SITE;

		if ( ! in_array($basePath, [JPATH_ADMINISTRATOR, JPATH_SITE]))
		{
			return $basePath;
		}

		$extension = str_replace('.sys', '', $extension);

		switch (true)
		{
			case (strpos($extension, 'mod_') === 0):
				$path = 'modules/' . $extension;
				break;

			case (strpos($extension, 'plg_') === 0):
				list($prefix, $folder, $name) = explode('_', $extension, 3);
				$path = 'plugins/' . $folder . '/' . $name;
				break;

			case (strpos($extension, 'com_') === 0):
			default:
				$path = 'components/' . $extension;
				break;
		}

		$check_folder = $check_folder ? '/' . $check_folder : '';

		if (is_dir($basePath . '/' . $path . $check_folder))
		{
			return $basePath . '/' . $path;
		}

		if (is_dir(JPATH_ADMINISTRATOR . '/' . $path . $check_folder))
		{
			return JPATH_ADMINISTRATOR . '/' . $path;
		}

		if (is_dir(JPATH_SITE . '/' . $path . $check_folder))
		{
			return JPATH_SITE . '/' . $path;
		}

		return $basePath;
	}

	/**
	 * Check if all extension types of a given extension are installed
	 *
	 * @param string $extension
	 * @param array  $types
	 *
	 * @return bool
	 */
	public static function areInstalled($extension, $types = ['plugin'])
	{
		foreach ($types as $type)
		{
			$folder = 'system';

			if (is_array($type))
			{
				list($type, $folder) = $type;
			}

			if ( ! self::isInstalled($extension, $type, $folder))
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Check if the given extension is installed
	 *
	 * @param string $extension
	 * @param string $type
	 * @param string $folder
	 *
	 * @return bool
	 */
	public static function isInstalled($extension, $type = 'component', $folder = 'system')
	{
		$extension = strtolower($extension);

		switch ($type)
		{
			case 'component':
				if (file_exists(JPATH_ADMINISTRATOR . '/components/com_' . $extension . '/' . $extension . '.php')
					|| file_exists(JPATH_ADMINISTRATOR . '/components/com_' . $extension . '/admin.' . $extension . '.php')
					|| file_exists(JPATH_SITE . '/components/com_' . $extension . '/' . $extension . '.php')
				)
				{
					if ($extension == 'cookieconfirm' && file_exists(JPATH_ADMINISTRATOR . '/components/com_cookieconfirm/version.php'))
					{
						// Only Cookie Confirm 2.0.0.rc1 and above is supported, because
						// previous versions don't have isCookiesAllowed()
						require_once JPATH_ADMINISTRATOR . '/components/com_cookieconfirm/version.php';

						if (version_compare(COOKIECONFIRM_VERSION, '2.2.0.rc1', '<'))
						{
							return false;
						}
					}

					return true;
				}
				break;

			case 'plugin':
				return file_exists(JPATH_PLUGINS . '/' . $folder . '/' . $extension . '/' . $extension . '.php');

			case 'module':
				return (file_exists(JPATH_ADMINISTRATOR . '/modules/mod_' . $extension . '/' . $extension . '.php')
					|| file_exists(JPATH_ADMINISTRATOR . '/modules/mod_' . $extension . '/mod_' . $extension . '.php')
					|| file_exists(JPATH_SITE . '/modules/mod_' . $extension . '/' . $extension . '.php')
					|| file_exists(JPATH_SITE . '/modules/mod_' . $extension . '/mod_' . $extension . '.php')
				);

			case 'library':
				return JFolder::exists(JPATH_LIBRARIES . '/' . $extension);
		}

		return false;
	}

	/**
	 * Check if the Regular Labs Library is enabled
	 *
	 * @return bool
	 */
	public static function isEnabled($extension, $type = 'component', $folder = 'system')
	{
		$extension = strtolower($extension);

		if ( ! self::isInstalled($extension, $type, $folder))
		{
			return false;
		}

		switch ($type)
		{
			case 'component':
				return JComponentHelper::isEnabled($extension);

			case 'plugin':
				return JPluginHelper::isEnabled($folder, $extension);

			case 'module':
				return JModuleHelper::isEnabled($extension);
		}

		return false;
	}

	/**
	 * Check if the Regular Labs Library is enabled
	 *
	 * @return bool
	 */
	public static function isFrameworkEnabled()
	{
		return JPluginHelper::isEnabled('system', 'regularlabs');
	}

	/**
	 * Return an alias and element name based on the given extension name
	 *
	 * @param string $name
	 *
	 * @return array
	 */
	public static function getAliasAndElement(&$name)
	{
		$name    = self::getNameByAlias($name);
		$alias   = self::getAliasByName($name);
		$element = self::getElementByAlias($alias);

		return [$alias, $element];
	}

	/**
	 * Return the name based on the given extension alias
	 *
	 * @param string $alias
	 *
	 * @return string
	 */
	public static function getNameByAlias($alias)
	{
		// Alias is a language string
		if (strpos($alias, ' ') === false && strtoupper($alias) == $alias)
		{
			return JText::_($alias);
		}

		// Alias has a space and/or capitals, so is already a name
		if (strpos($alias, ' ') !== false || $alias !== strtolower($alias))
		{
			return $alias;
		}

		return JText::_(self::getXMLValue('name', $alias));
	}

	/**
	 * Return an alias based on the given extension name
	 *
	 * @param string $name
	 *
	 * @return string
	 */
	public static function getAliasByName($name)
	{
		$alias = RegEx::replace('[^a-z0-9]', '', strtolower($name));

		switch ($alias)
		{
			case 'advancedmodules':
				return 'advancedmodulemanager';

			case 'advancedtemplates':
				return 'advancedtemplatemanager';

			case 'nonumbermanager':
				return 'nonumberextensionmanager';

			case 'what-nothing':
				return 'whatnothing';
		}

		return $alias;
	}

	/**
	 * Return an element name based on the given extension alias
	 *
	 * @param string $alias
	 *
	 * @return string
	 */
	public static function getElementByAlias($alias)
	{
		$alias = self::getAliasByName($alias);

		switch ($alias)
		{
			case 'advancedmodulemanager':
				return 'advancedmodules';

			case 'advancedtemplatemanager':
				return 'advancedtemplates';

			case 'nonumberextensionmanager':
				return 'nonumbermanager';
		}

		return $alias;
	}

	/**
	 * Return a value from an extensions main xml file based on the given key
	 *
	 * @param string $key
	 * @param string $alias
	 * @param string $type
	 * @param string $folder
	 *
	 * @return string
	 */
	public static function getXMLValue($key, $alias, $type = '', $folder = '')
	{
		if ( ! $xml = self::getXML($alias, $type, $folder))
		{
			return '';
		}

		if ( ! isset($xml[$key]))
		{
			return '';
		}

		return isset($xml[$key]) ? $xml[$key] : '';
	}

	/**
	 * Return an extensions main xml array
	 *
	 * @param string $alias
	 * @param string $type
	 * @param string $folder
	 *
	 * @return array|bool
	 */
	public static function getXML($alias, $type = '', $folder = '')
	{
		if ( ! $file = self::getXMLFile($alias, $type, $folder))
		{
			return false;
		}

		return JInstaller::parseXMLInstallFile($file);
	}

	/**
	 * Return an extensions main xml file name (including path)
	 *
	 * @param string $alias
	 * @param string $type
	 * @param string $folder
	 *
	 * @return string
	 */
	public static function getXMLFile($alias, $type = '', $folder = '')
	{
		$element = self::getElementByAlias($alias);

		$files = [];

		// Components
		if (empty($type) || $type == 'component')
		{
			$files[] = JPATH_ADMINISTRATOR . '/components/com_' . $element . '/' . $element . '.xml';
			$files[] = JPATH_SITE . '/components/com_' . $element . '/' . $element . '.xml';
			$files[] = JPATH_ADMINISTRATOR . '/components/com_' . $element . '/com_' . $element . '.xml';
			$files[] = JPATH_SITE . '/components/com_' . $element . '/com_' . $element . '.xml';
		}

		// Plugins
		if (empty($type) || $type == 'plugin')
		{
			if ( ! empty($folder))
			{
				$files[] = JPATH_PLUGINS . '/' . $folder . '/' . $element . '/' . $element . '.xml';
				$files[] = JPATH_PLUGINS . '/' . $folder . '/' . $element . '.xml';
			}

			// System Plugins
			$files[] = JPATH_PLUGINS . '/system/' . $element . '/' . $element . '.xml';
			$files[] = JPATH_PLUGINS . '/system/' . $element . '.xml';

			// Editor Button Plugins
			$files[] = JPATH_PLUGINS . '/editors-xtd/' . $element . '/' . $element . '.xml';
			$files[] = JPATH_PLUGINS . '/editors-xtd/' . $element . '.xml';
		}

		// Modules
		if (empty($type) || $type == 'module')
		{
			$files[] = JPATH_ADMINISTRATOR . '/modules/mod_' . $element . '/' . $element . '.xml';
			$files[] = JPATH_SITE . '/modules/mod_' . $element . '/' . $element . '.xml';
			$files[] = JPATH_ADMINISTRATOR . '/modules/mod_' . $element . '/mod_' . $element . '.xml';
			$files[] = JPATH_SITE . '/modules/mod_' . $element . '/mod_' . $element . '.xml';
		}

		foreach ($files as $file)
		{
			if ( ! file_exists($file))
			{
				continue;
			}

			return $file;
		}

		return '';
	}

	public static function isAuthorised($require_core_auth = true)
	{
		$user = JFactory::getUser();

		if ($user->get('guest'))
		{
			return false;
		}

		if ( ! $require_core_auth)
		{
			return true;
		}

		if (
			! $user->authorise('core.edit', 'com_content')
			&& ! $user->authorise('core.edit.own', 'com_content')
			&& ! $user->authorise('core.create', 'com_content')
		)
		{
			return false;
		}

		return true;
	}

	public static function isEnabledInArea($params)
	{
		if ( ! isset($params->enable_frontend))
		{
			return true;
		}

		// Only allow in frontend
		if ($params->enable_frontend == 2 && Document::isClient('administrator'))
		{
			return false;
		}

		// Do not allow in frontend
		if ( ! $params->enable_frontend && Document::isClient('site'))
		{
			return false;
		}

		return true;
	}

	public static function isEnabledInComponent($params)
	{
		if ( ! isset($params->disabled_components))
		{
			return true;
		}

		return ! Protect::isRestrictedComponent($params->disabled_components);
	}

	public static function getById($id)
	{
		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName(['extension_id', 'manifest_cache']))
			->from($db->quoteName('#__extensions'))
			->where($db->quoteName('extension_id') . ' = ' . (int) $id);
		$db->setQuery($query);

		return $db->loadObject();
	}
}
regularlabs/src/Form.php000064400000034410152177723700011267 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;

class Form
{
	/**
	 * Render a full select list
	 *
	 * @param array  $options
	 * @param string $name
	 * @param string $value
	 * @param string $id
	 * @param int    $size
	 * @param bool   $multiple
	 * @param bool   $simple
	 *
	 * @return string
	 */
	public static function selectList(&$options, $name, $value, $id, $size = 0, $multiple = false, $simple = false)
	{
		if (empty($options))
		{
			return '<fieldset class="radio">' . JText::_('RL_NO_ITEMS_FOUND') . '</fieldset>';
		}

		if ( ! $multiple)
		{
			$simple = true;
		}

		$parameters = Parameters::getInstance();
		$params     = $parameters->getPluginParams('regularlabs');

		if ( ! is_array($value))
		{
			$value = explode(',', $value);
		}

		if (count($value) === 1 && strpos($value[0], ',') !== false)
		{
			$value = explode(',', $value[0]);
		}

		$count = 0;
		if ($options != -1)
		{
			foreach ($options as $option)
			{
				$count++;
				if (isset($option->links))
				{
					$count += count($option->links);
				}
				if ($count > $params->max_list_count)
				{
					break;
				}
			}
		}

		if ($options == -1 || $count > $params->max_list_count)
		{
			if (is_array($value))
			{
				$value = implode(',', $value);
			}
			if ( ! $value)
			{
				$input = '<textarea name="' . $name . '" id="' . $id . '" cols="40" rows="5">' . $value . '</textarea>';
			}
			else
			{
				$input = '<input type="text" name="' . $name . '" id="' . $id . '" value="' . $value . '" size="60">';
			}

			$plugin = JPluginHelper::getPlugin('system', 'regularlabs');

			$url = ! empty($plugin->id)
				? 'index.php?option=com_plugins&task=plugin.edit&extension_id=' . $plugin->id
				: 'index.php?option=com_plugins&filter_folder=&filter_search=Regular%20Labs%20Library';

			$label   = JText::_('RL_ITEM_IDS');
			$text    = JText::_('RL_MAX_LIST_COUNT_INCREASE');
			$tooltip = JText::_('RL_MAX_LIST_COUNT_INCREASE_DESC,' . $params->max_list_count . ',RL_MAX_LIST_COUNT');
			$link    = '<a href="' . $url . '" target="_blank" id="' . $id . '_msg"'
				. ' class="hasPopover" title="' . $text . '" data-content="' . htmlentities($tooltip) . '">'
				. '<span class="icon icon-cog"></span>'
				. $text
				. '</a>';

			$script = 'jQuery("#' . $id . '_msg").popover({"html": true,"trigger": "hover focus","container": "body"})';

			return '<fieldset class="radio">'
				. '<label for="' . $id . '">' . $label . ':</label>'
				. $input
				. '<br><small>' . $link . '</small>'
				. '</fieldset>'
				. '<script>' . $script . '</script>';
		}

		if ($simple)
		{
			$first_level = isset($options[0]->level) ? $options[0]->level : 0;
			foreach ($options as &$option)
			{
				if ( ! isset($option->level))
				{
					continue;
				}
				$repeat       = ($option->level - $first_level > 0) ? $option->level - $first_level : 0;
				$option->text = str_repeat(' - ', $repeat) . $option->text;
			}
		}

		if ( ! $multiple)
		{
			$html = JHtml::_('select.genericlist', $options, $name, 'class="inputbox"', 'value', 'text', $value, $id);

			return self::handlePreparedStyles($html);
		}

		$size = (int) $size ?: 300;

		if ($simple)
		{
			$attr = 'style="width: ' . $size . 'px" multiple="multiple"';

			if (substr($name, -2) !== '[]')
			{
				$name .= '[]';
			}

			$html = JHtml::_('select.genericlist', $options, $name, trim($attr), 'value', 'text', $value, $id);

			return self::handlePreparedStyles($html);
		}

		Language::load('com_modules', JPATH_ADMINISTRATOR);

		Document::script('regularlabs/multiselect.min.js');
		Document::stylesheet('regularlabs/multiselect.min.css');

		$html = [];

		$html[] = '<div class="well well-small rl_multiselect" id="' . $id . '">';
		$html[] = '
			<div class="form-inline rl_multiselect-controls">
				<span class="small">' . JText::_('JSELECT') . ':
					<a class="rl_multiselect-checkall" href="javascript:;">' . JText::_('JALL') . '</a>,
					<a class="rl_multiselect-uncheckall" href="javascript:;">' . JText::_('JNONE') . '</a>,
					<a class="rl_multiselect-toggleall" href="javascript:;">' . JText::_('RL_TOGGLE') . '</a>
				</span>
				<span class="width-20">|</span>
				<span class="small">' . JText::_('RL_EXPAND') . ':
					<a class="rl_multiselect-expandall" href="javascript:;">' . JText::_('JALL') . '</a>,
					<a class="rl_multiselect-collapseall" href="javascript:;">' . JText::_('JNONE') . '</a>
				</span>
				<span class="width-20">|</span>
				<span class="small">' . JText::_('JSHOW') . ':
					<a class="rl_multiselect-showall" href="javascript:;">' . JText::_('JALL') . '</a>,
					<a class="rl_multiselect-showselected" href="javascript:;">' . JText::_('RL_SELECTED') . '</a>
				</span>
				<span class="rl_multiselect-maxmin">
				<span class="width-20">|</span>
				<span class="small">
					<a class="rl_multiselect-maximize" href="javascript:;">' . JText::_('RL_MAXIMIZE') . '</a>
					<a class="rl_multiselect-minimize" style="display:none;" href="javascript:;">' . JText::_('RL_MINIMIZE') . '</a>
				</span>
				</span>
				<input type="text" name="rl_multiselect-filter" class="rl_multiselect-filter input-medium search-query pull-right" size="16"
					autocomplete="off" placeholder="' . JText::_('JSEARCH_FILTER') . '" aria-invalid="false" tabindex="-1">
			</div>

			<div class="clearfix"></div>

			<hr class="hr-condensed">';

		$o = [];
		foreach ($options as $option)
		{
			$option->level = isset($option->level) ? $option->level : 0;
			$o[]           = $option;
			if (isset($option->links))
			{
				foreach ($option->links as $link)
				{
					$link->level = $option->level + (isset($link->level) ? $link->level : 1);
					$o[]         = $link;
				}
			}
		}

		$html[]    = '<ul class="rl_multiselect-ul" style="max-height:300px;min-width:' . $size . 'px;overflow-x: hidden;">';
		$prevlevel = 0;

		foreach ($o as $i => $option)
		{
			if ($prevlevel < $option->level)
			{
				// correct wrong level indentations
				$option->level = $prevlevel + 1;

				$html[] = '<ul class="rl_multiselect-sub">';
			}
			else if ($prevlevel > $option->level)
			{
				$html[] = str_repeat('</li></ul>', $prevlevel - $option->level);
			}
			else if ($i)
			{
				$html[] = '</li>';
			}

			$labelclass = trim('pull-left ' . (isset($option->labelclass) ? $option->labelclass : ''));

			$html[] = '<li>';

			$item = '<div class="' . trim('rl_multiselect-item pull-left ' . (isset($option->class) ? $option->class : '')) . '">';
			if (isset($option->title))
			{
				$labelclass .= ' nav-header';
			}

			if (isset($option->title) && ( ! isset($option->value) || ! $option->value))
			{
				$item .= '<label class="' . $labelclass . '">' . $option->title . '</label>';
			}
			else
			{
				$selected = in_array($option->value, $value) ? ' checked="checked"' : '';
				$disabled = (isset($option->disable) && $option->disable) ? ' disabled="disabled"' : '';

				$item .= '<input type="checkbox" class="pull-left" name="' . $name . '" id="' . $id . $option->value . '" value="' . $option->value . '"' . $selected . $disabled . '>
					<label for="' . $id . $option->value . '" class="' . $labelclass . '">' . $option->text . '</label>';
			}
			$item   .= '</div>';
			$html[] = $item;

			if ( ! isset($o[$i + 1]) && $option->level > 0)
			{
				$html[] = str_repeat('</li></ul>', (int) $option->level);
			}
			$prevlevel = $option->level;
		}
		$html[] = '</ul>';
		$html[] = '
			<div style="display:none;" class="rl_multiselect-menu-block">
				<div class="pull-left nav-hover rl_multiselect-menu">
					<div class="btn-group">
						<a href="#" data-toggle="dropdown" class="dropdown-toggle btn btn-micro">
							<span class="caret"></span>
						</a>
						<ul class="dropdown-menu">
							<li class="nav-header">' . JText::_('COM_MODULES_SUBITEMS') . '</li>
							<li class="divider"></li>
							<li class=""><a class="checkall" href="javascript:;"><span class="icon-checkbox"></span> ' . JText::_('JSELECT') . '</a>
							</li>
							<li><a class="uncheckall" href="javascript:;"><span class="icon-checkbox-unchecked"></span> ' . JText::_('COM_MODULES_DESELECT') . '</a>
							</li>
							<div class="rl_multiselect-menu-expand">
								<li class="divider"></li>
								<li><a class="expandall" href="javascript:;"><span class="icon-plus"></span> ' . JText::_('RL_EXPAND') . '</a></li>
								<li><a class="collapseall" href="javascript:;"><span class="icon-minus"></span> ' . JText::_('RL_COLLAPSE') . '</a></li>
							</div>
						</ul>
					</div>
				</div>
			</div>';
		$html[] = '</div>';

		$html = implode('', $html);

		return self::handlePreparedStyles($html);
	}

	/**
	 * Render a simple select list
	 *
	 * @param array  $options
	 * @param        $string $name
	 * @param string $value
	 * @param string $id
	 * @param int    $size
	 * @param bool   $multiple
	 *
	 * @return string
	 */
	public static function selectListSimple(&$options, $name, $value, $id, $size = 0, $multiple = false)
	{
		return self::selectlist($options, $name, $value, $id, $size, $multiple, true);
	}

	/**
	 * Render a select list loaded via Ajax
	 *
	 * @param string $field
	 * @param string $name
	 * @param string $value
	 * @param string $id
	 * @param array  $attributes
	 * @param bool   $simple
	 *
	 * @return string
	 */
	public static function selectListAjax($field, $name, $value, $id, $attributes = [], $simple = false)
	{
		JHtml::_('jquery.framework');

		$script = self::getAddToLoadAjaxListScript($field, $name, $value, $id, $attributes, $simple);

		if (is_array($value))
		{
			$value = implode(',', $value);
		}

		Document::script('regularlabs/script.min.js');
		Document::stylesheet('regularlabs/style.min.css');

		$input = '<textarea name="' . $name . '" id="' . $id . '" cols="40" rows="5">' . $value . '</textarea>'
			. '<div id="' . $id . '_spinner" class="rl_spinner"></div>';

		return $input . $script;
	}

	public static function getAddToLoadAjaxListScript($field, $name, $value, $id, $attributes = [], $simple = false)
	{
		$attributes['field'] = $field;
		$attributes['name']  = $name;
		$attributes['value'] = $value;
		$attributes['id']    = $id;

		$url = 'index.php?option=com_ajax&plugin=regularlabs&format=raw'
			. '&' . Uri::createCompressedAttributes(json_encode($attributes));

		$remove_spinner = "$('#" . $id . "_spinner').remove();";
		$replace_field  = "$('#" . $id . "').replaceWith(data);";

		$error   = $remove_spinner;
		$success = "if(data)\{" . $replace_field . "\}" . $remove_spinner;

//			$success .= "console.log('#" . $id . "');";
//			$success .= "console.log(data);";

		if ($simple)
		{
			$success .= "if(data.indexOf('</select>') > -1)\{$('#" . $id . "').chosen();\}";
		}
		else
		{
			Document::script('regularlabs/multiselect.min.js');
			Document::stylesheet('regularlabs/multiselect.min.css');

			$success .= "if(data.indexOf('rl_multiselect') > -1)\{RegularLabsMultiSelect.init($('#" . $id . "'));\}";
		}

		$script = "jQuery(document).ready(function() {"
			. "RegularLabsScripts.addToLoadAjaxList("
			. "'" . addslashes($url) . "',"
			. "'" . addslashes($success) . "',"
			. "'" . addslashes($error) . "'"
			. ")"
			. "});";

		return '<script>' . $script . '</script>';
	}

	/**
	 * Render a simple select list loaded via Ajax
	 *
	 * @param string $field
	 * @param string $name
	 * @param string $value
	 * @param string $id
	 * @param array  $attributes
	 *
	 * @return string
	 */
	public static function selectListSimpleAjax($field, $name, $value, $id, $attributes = [])
	{
		return self::selectListAjax($field, $name, $value, $id, $attributes, true);
	}

	/**
	 * Prepare the string for a select form field item
	 *
	 * @param string $string
	 * @param int    $published
	 * @param string $type
	 * @param int    $remove_first
	 *
	 * @return string
	 */
	public static function prepareSelectItem($string, $published = 1, $type = '', $remove_first = 0)
	{
		if (empty($string))
		{
			return '';
		}

		$string = str_replace(['&nbsp;', '&#160;'], ' ', $string);
		$string = RegEx::replace('- ', '  ', $string);

		for ($i = 0; $remove_first > $i; $i++)
		{
			$string = RegEx::replace('^  ', '', $string, '');
		}

		if (RegEx::match('^( *)(.*)$', $string, $match, ''))
		{
			list($string, $pre, $name) = $match;

			$pre = str_replace('  ', ' ·  ', $pre);
			$pre = RegEx::replace('(( ·  )*) ·  ', '\1 »  ', $pre);
			$pre = str_replace('  ', ' &nbsp; ', $pre);

			$string = $pre . $name;
		}

		switch (true)
		{
			case ($type == 'separator'):
				$string = '[[:font-weight:normal;font-style:italic;color:grey;:]]' . $string;
				break;

			case ($published == -2):
				$string = '[[:font-style:italic;color:grey;:]]' . $string . ' [' . JText::_('JTRASHED') . ']';
				break;

			case ($published == 0):
				$string = '[[:font-style:italic;color:grey;:]]' . $string . ' [' . JText::_('JUNPUBLISHED') . ']';
				break;

			case ($published == 2):
				$string = '[[:font-style:italic;:]]' . $string . ' [' . JText::_('JARCHIVED') . ']';
				break;
		}

		return $string;
	}

	/**
	 * Replace style placeholders with actual style attributes
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private static function handlePreparedStyles($string)
	{
		// No placeholders found
		if (strpos($string, '[[:') === false)
		{
			return $string;
		}

		// Doing following replacement in 3 steps to prevent the Regular Expressions engine from exploding

		// Replace style tags right after the html tags
		$string = RegEx::replace(
			'>\s*\[\[\:(.*?)\:\]\]',
			' style="\1">',
			$string
		);

		// No more placeholders found
		if (strpos($string, '[[:') === false)
		{
			return $string;
		}

		// Replace style tags prepended with a minus and any amount of whitespace: '- '
		$string = RegEx::replace(
			'>((?:-\s*)+)\[\[\:(.*?)\:\]\]',
			' style="\2">\1',
			$string
		);

		// No more placeholders found
		if (strpos($string, '[[:') === false)
		{
			return $string;
		}

		// Replace style tags prepended with whitespace, a minus and any amount of whitespace: ' - '
		$string = RegEx::replace(
			'>((?:\s+-\s*)+)\[\[\:(.*?)\:\]\]',
			' style="\2">\1',
			$string
		);

		return $string;
	}
}
regularlabs/src/Xml.php000064400000002767152177723700011136 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use SimpleXMLElement;

jimport('joomla.filesystem.file');

/**
 * Class File
 * @package RegularLabs\Library
 */
class Xml
{
	/**
	 * Get an object filled with data from an xml file
	 *
	 * @param string $url
	 * @param string $root
	 *
	 * @return object
	 */
	public static function toObject($url, $root = '')
	{
		$cache_id = 'xmlToObject_' . $url . '_' . $root;

		if (Cache::has($cache_id))
		{
			return Cache::get($cache_id);
		}

		if (file_exists($url))
		{
			$xml = @new SimpleXMLElement($url, LIBXML_NONET | LIBXML_NOCDATA, 1);
		}
		else
		{
			$xml = simplexml_load_string($url, "SimpleXMLElement", LIBXML_NONET | LIBXML_NOCDATA);
		}

		if ( ! @count($xml))
		{
			return Cache::set(
				$cache_id,
				(object) []
			);
		}

		if ($root)
		{
			if ( ! isset($xml->{$root}))
			{
				return Cache::set(
					$cache_id,
					(object) []
				);
			}

			$xml = $xml->{$root};
		}

		$json = json_encode($xml);
		$xml  = json_decode($json);
		if (is_null($xml))
		{
			$xml = (object) [];
		}

		if ($root && isset($xml->{$root}))
		{
			$xml = $xml->{$root};
		}

		return Cache::set(
			$cache_id,
			$xml
		);
	}
}
regularlabs/src/Field.php000064400000017760152177723700011420 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Form\Form as JForm;
use Joomla\CMS\HTML\HTMLHelper as JHtml;
use Joomla\CMS\Language\Text as JText;

/**
 * Class Field
 * @package RegularLabs\Library
 */
class Field
	extends \JFormField
{
	/**
	 * @var string
	 */
	public $type = 'Field';
	/**
	 * @var \JDatabaseDriver|null
	 */
	public $db = null;
	/**
	 * @var int
	 */
	public $max_list_count = 0;
	/**
	 * @var null
	 */
	public $params = null;

	/**
	 * @param JForm $form
	 */
	public function __construct($form = null)
	{
		parent::__construct($form);

		$this->db = JFactory::getDbo();

		$params = Parameters::getInstance()->getPluginParams('regularlabs');

		$this->max_list_count = $params->max_list_count;

		Document::loadFormDependencies();
		Document::stylesheet('regularlabs/style.min.css');
	}

	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$this->params = $element->attributes();

		return parent::setup($element, $value, $group);
	}

	/**
	 * Return the field input markup
	 * Return empty by default
	 *
	 * @return string
	 */
	protected function getInput()
	{
		return '';
	}

	/**
	 * Return the field options (array)
	 * Overrules the Joomla core functionality
	 *
	 * @return array
	 */
	protected function getOptions()
	{
		// This only returns 1 option!!!
		if (empty($this->element->option))
		{
			return [];
		}

		$option = $this->element->option;

		$fieldname = RegEx::replace('[^a-z0-9_\-]', '_', $this->fieldname);
		$value     = (string) $option['value'];
		$text      = trim((string) $option) ? trim((string) $option) : $value;

		return [
			[
				'value' => $value,
				'text'  => '- ' . JText::alt($text, $fieldname) . ' -',
			],
		];
	}

	public static function selectList(&$options, $name, $value, $id, $size = 0, $multiple = false, $simple = false)
	{
		return Form::selectlist($options, $name, $value, $id, $size, $multiple, $simple);
	}

	public static function selectListSimple(&$options, $name, $value, $id, $size = 0, $multiple = false)
	{
		return Form::selectListSimple($options, $name, $value, $id, $size, $multiple);
	}

	public static function selectListAjax($field, $name, $value, $id, $attributes = [], $simple = false)
	{
		return Form::selectListAjax($field, $name, $value, $id, $attributes, $simple);
	}

	public static function selectListSimpleAjax($field, $name, $value, $id, $attributes = [])
	{
		return Form::selectListSimpleAjax($field, $name, $value, $id, $attributes);
	}

	/**
	 * Get a value from the field params
	 *
	 * @param string $key
	 * @param string $default
	 *
	 * @return bool|string
	 */
	public function get($key, $default = '')
	{
		$value = $default;

		if (isset($this->params[$key]) && (string) $this->params[$key] != '')
		{
			$value = (string) $this->params[$key];
		}

		if ($value === 'true')
		{
			return true;
		}

		if ($value === 'false')
		{
			return false;
		}

		return $value;
	}

	/**
	 * Return a array of options using the custom prepare methods
	 *
	 * @param array $list
	 * @param array $extras
	 * @param int   $levelOffset
	 *
	 * @return array
	 */
	function getOptionsByList($list, $extras = [], $levelOffset = 0)
	{
		$options = [];
		foreach ($list as $id => $item)
		{
			$options[$id] = $this->getOptionByListItem($item, $extras, $levelOffset);
		}

		return $options;
	}

	/**
	 * Return a list option using the custom prepare methods
	 *
	 * @param object $item
	 * @param array  $extras
	 * @param int    $levelOffset
	 *
	 * @return mixed
	 */
	function getOptionByListItem($item, $extras = [], $levelOffset = 0)
	{
		$name = trim($item->name);

		foreach ($extras as $key => $extra)
		{
			if (empty($item->{$extra}))
			{
				continue;
			}

			if ($extra == 'language' && $item->{$extra} == '*')
			{
				continue;
			}

			if (in_array($extra, ['id', 'alias']) && $item->{$extra} == $item->name)
			{
				continue;
			}

			$name .= ' [' . $item->{$extra} . ']';
		}

		$name = Form::prepareSelectItem($name, isset($item->published) ? $item->published : 1);

		$option = JHtml::_('select.option', $item->id, $name, 'value', 'text', 0);

		if (isset($item->level))
		{
			$option->level = $item->level + $levelOffset;
		}

		return $option;
	}

	/**
	 * Return a recursive options list using the custom prepare methods
	 *
	 * @param array $items
	 * @param int   $root
	 *
	 * @return array
	 */
	function getOptionsTreeByList($items = [], $root = 0)
	{
		// establish the hierarchy of the menu
		// TODO: use node model
		$children = [];

		if ( ! empty($items))
		{
			// first pass - collect children
			foreach ($items as $v)
			{
				$pt   = $v->parent_id;
				$list = @$children[$pt] ? $children[$pt] : [];
				array_push($list, $v);
				$children[$pt] = $list;
			}
		}

		// second pass - get an indent list of the items
		$list = JHtml::_('menu.treerecurse', $root, '', [], $children, 9999, 0, 0);

		// assemble items to the array
		$options = [];
		if ($this->get('show_ignore'))
		{
			if (in_array('-1', $this->value))
			{
				$this->value = ['-1'];
			}
			$options[] = JHtml::_('select.option', '-1', '- ' . JText::_('RL_IGNORE') . ' -', 'value', 'text', 0);
			$options[] = JHtml::_('select.option', '-', '&nbsp;', 'value', 'text', 1);
		}

		foreach ($list as $item)
		{
			$item->treename = Form::prepareSelectItem($item->treename, isset($item->published) ? $item->published : 1, '', 1);

			$options[] = JHtml::_('select.option', $item->id, $item->treename, 'value', 'text', 0);
		}

		return $options;
	}

	/**
	 * Prepare the option string, handling language strings
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	public function prepareText($string = '')
	{
		$string = trim($string);

		if ($string == '')
		{
			return '';
		}

		switch (true)
		{
			// Old fields using var attributes
			case (JText::_($this->get('var1'))):
				$string = $this->sprintf_old($string);
				break;

			// Normal language string
			default:
				$string = JText::_($string);
		}

		return $this->fixLanguageStringSyntax($string);
	}

	/**
	 * Fix some syntax/encoding issues in option text strings
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private function fixLanguageStringSyntax($string = '')
	{
		$string = str_replace('[:COMMA:]', ',', $string);
		$string = trim(StringHelper::html_entity_decoder($string));
		$string = str_replace('&quot;', '"', $string);
		$string = str_replace('span style="font-family:monospace;"', 'span class="rl_code"', $string);

		return $string;
	}

	/**
	 * Replace language strings in a string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private function sprintf($string = '')
	{
		$string = trim($string);

		if (strpos($string, ',') === false)
		{
			return $string;
		}

		$string_parts = explode(',', $string);
		$first_part   = array_shift($string_parts);

		if ($first_part === strtoupper($first_part))
		{
			$first_part = JText::_($first_part);
		}

		$first_part = RegEx::replace('\[\[%([0-9]+):[^\]]*\]\]', '%\1$s', $first_part);

		array_walk($string_parts, '\RegularLabs\Library\Field::jText');

		return vsprintf($first_part, $string_parts);
	}

	/**
	 * Passes along to the JText method.
	 * This is used for the array_walk in the sprintf method above.
	 *
	 * @param $string
	 */
	public function jText(&$string)
	{
		$string = JText::_($string);
	}

	/**
	 * Replace language strings in an old syntax string
	 *
	 * @param string $string
	 *
	 * @return string
	 */
	private function sprintf_old($string = '')
	{
		// variables
		$var1 = JText::_($this->get('var1'));
		$var2 = JText::_($this->get('var2'));
		$var3 = JText::_($this->get('var3'));
		$var4 = JText::_($this->get('var4'));
		$var5 = JText::_($this->get('var5'));

		return JText::sprintf(JText::_(trim($string)), $var1, $var2, $var3, $var4, $var5);
	}
}
regularlabs/src/ActionLogPlugin.php000064400000014360152177723700013424 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Library;

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\Text as JText;
use Joomla\CMS\Plugin\CMSPlugin as JPlugin;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Extension as RL_Extension;
use RegularLabs\Library\Log as RL_Log;
use RegularLabs\Library\Parameters as RL_Parameters;

/**
 * Class ActionLogPlugin
 * @package RegularLabs\Library
 */
class ActionLogPlugin
	extends JPlugin
{
	public $name   = '';
	public $alias  = '';
	public $option = '';
	public $items  = [];
	public $table  = null;
	public $events = [];

	static $ids = [];

	public function __construct(&$subject, array $config = [])
	{
		parent::__construct($subject, $config);

		Language::load('plg_actionlog_' . $this->alias);

		$config = RL_Parameters::getInstance()->getComponentParams($this->alias);

		$enable_actionlog = isset($config->enable_actionlog) ? $config->enable_actionlog : true;
		$this->events     = $enable_actionlog ? ['*'] : [];

		if ($enable_actionlog && ! empty($config->actionlog_events))
		{
			$this->events = RL_Array::toArray($config->actionlog_events);
		}

		$this->name   = JText::_($this->name);
		$this->option = $this->option ?: 'com_' . $this->alias;
	}

	public function onContentAfterSave($context, $table, $isNew)
	{
		if (strpos($context, $this->option) === false)
		{
			return;
		}

		$event = $isNew ? 'create' : 'update';

		if ( ! RL_Array::find(['*', $event], $this->events))
		{
			return;
		}

		$item = $this->getItem($context);

		$title    = isset($table->title) ? $table->title : (isset($table->name) ? $table->name : $table->id);
		$item_url = str_replace('{id}', $table->id, $item->url);

		$message = [
			'type'     => $item->title,
			'id'       => $table->id,
			'title'    => $title,
			'itemlink' => $item_url,
		];

		RL_Log::save($message, $context, $isNew);
	}

	public function onContentAfterDelete($context, $table)
	{
		if (strpos($context, $this->option) === false)
		{
			return;
		}

		if ( ! RL_Array::find(['*', 'delete'], $this->events))
		{
			return;
		}

		$item = $this->getItem($context);

		$title = isset($table->title) ? $table->title : (isset($table->name) ? $table->name : $table->id);

		$message = [
			'type'  => $item->title,
			'id'    => $table->id,
			'title' => $title,
		];

		RL_Log::delete($message, $context);
	}

	public function onContentChangeState($context, $ids, $value)
	{
		if (strpos($context, $this->option) === false)
		{
			return;
		}

		if ( ! RL_Array::find(['*', 'change_state'], $this->events))
		{
			return;
		}

		$item = $this->getItem($context);

		if ( ! $this->table)
		{
			if ( ! is_file($item->file))
			{
				return;
			}

			require_once $item->file;

			$this->table = (new $item->model)->getTable();
		}

		foreach ($ids as $id)
		{
			$this->table->load($id);

			$title    = isset($this->table->title) ? $this->table->title : (isset($this->table->name) ? $this->table->name : $this->table->id);
			$itemlink = str_replace('{id}', $this->table->id, $item->url);

			$message = [
				'type'     => $item->title,
				'id'       => $id,
				'title'    => $title,
				'itemlink' => $itemlink,
			];

			RL_Log::changeState($message, $context, $value);
		}
	}

	public function onExtensionAfterSave($context, $table, $isNew)
	{
		self::onContentAfterSave($context, $table, $isNew);
	}

	public function onExtensionAfterDelete($context, $table)
	{
		self::onContentAfterDelete($context, $table);
	}

	public function onExtensionAfterInstall($installer, $eid)
	{
		// Prevent duplicate logs
		if (in_array('install_' . $eid, self::$ids))
		{
			return;
		}

		$context = JFactory::getApplication()->input->get('option');

		if (strpos($context, $this->option) === false)
		{
			return;
		}

		if ( ! RL_Array::find(['*', 'install'], $this->events))
		{
			return;
		}

		$extension = RL_Extension::getById($eid);

		if (empty($extension->manifest_cache))
		{
			return;
		}

		$manifest = json_decode($extension->manifest_cache);

		if (empty($manifest->name))
		{
			return;
		}

		self::$ids[] = 'install_' . $eid;

		$message = [
			'id'             => $eid,
			'extension_name' => JText::_($manifest->name),
		];

		RL_Log::install($message, 'com_regularlabsmanager', $manifest->type);
	}

	public function onExtensionAfterUninstall($installer, $eid, $result)
	{
		// Prevent duplicate logs
		if (in_array('uninstall_' . $eid, self::$ids))
		{
			return;
		}

		$context = JFactory::getApplication()->input->get('option');

		if (strpos($context, $this->option) === false)
		{
			return;
		}

		if ( ! RL_Array::find(['*', 'uninstall'], $this->events))
		{
			return;
		}

		if ($result === false)
		{
			return;
		}

		$manifest = $installer->get('manifest');

		if ($manifest === null)
		{
			return;
		}

		self::$ids[] = 'uninstall_' . $eid;

		$message = [
			'id'             => $eid,
			'extension_name' => JText::_($manifest->name),
		];

		RL_Log::uninstall($message, 'com_regularlabsmanager', $manifest->attributes()->type);
	}

	private function getItem($context)
	{
		$item = $this->getItemData($context);

		$item->title = isset($item->title)
			? JText::_($item->title)
			: $this->type . ' ' . JText::_('RL_ITEM');

		if ( ! isset($item->file))
		{
			$item->file = JPATH_ADMINISTRATOR . '/components/' . $this->option . '/models/' . $item->type . '.php';
		}

		if ( ! isset($item->model))
		{
			$item->model = $this->alias . 'Model' . ucfirst($item->type);
		}

		if ( ! isset($item->url))
		{
			$item->url = 'index.php?option=' . $this->option . '&view=' . $item->type . '&layout=edit&id={id}';
		}

		return $item;
	}

	private function getItemData($context)
	{
		$default = (object) [
			'type' => 'item',
		];

		$type = key($this->items) ?: 'item';

		if (strpos($context, '.') !== false)
		{
			$parts = explode('.', $context);
			$type  = $parts[1];
		}

		if ( ! isset($this->items[$type]))
		{
			return $default;
		}

		$item = $this->items[$type];

		if ( ! isset($item->type))
		{
			$item->type = $type;
		}

		return $item;
	}
}
regularlabs/helpers/parameters.php000064400000001236152177723700013402 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Parameters as RL_Parameters;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLParameters
{
	public static function getInstance()
	{
		return RL_Parameters::getInstance();
	}
}
regularlabs/helpers/search.php000064400000013561152177723700012510 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/**
 * BASE ON JOOMLA CORE FILE:
 * /components/com_search/models/search.php
 */

/**
 * @package     Joomla.Site
 * @subpackage  com_search
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;
use Joomla\CMS\Pagination\Pagination as JPagination;
use Joomla\CMS\Plugin\PluginHelper as JPluginHelper;

/**
 * Search Component Search Model
 *
 * @since  1.5
 */
class SearchModelSearch extends JModel
{
	/**
	 * Search data array
	 *
	 * @var array
	 */
	protected $_data = null;

	/**
	 * Search total
	 *
	 * @var integer
	 */
	protected $_total = null;

	/**
	 * Search areas
	 *
	 * @var integer
	 */
	protected $_areas = null;

	/**
	 * Pagination object
	 *
	 * @var object
	 */
	protected $_pagination = null;

	/**
	 * Constructor
	 *
	 * @since 1.5
	 */
	public function __construct()
	{
		parent::__construct();

		// Get configuration
		$app    = JFactory::getApplication();
		$config = JFactory::getConfig();

		// Get the pagination request variables
		$this->setState('limit', $app->getUserStateFromRequest('com_search.limit', 'limit', $config->get('list_limit'), 'uint'));
		$this->setState('limitstart', $app->input->get('limitstart', 0, 'uint'));

		// Get parameters.
		$params = $app->getParams();

		if ($params->get('searchphrase') == 1)
		{
			$searchphrase = 'any';
		}
		elseif ($params->get('searchphrase') == 2)
		{
			$searchphrase = 'exact';
		}
		else
		{
			$searchphrase = 'all';
		}

		// Set the search parameters
		$keyword  = urldecode($app->input->getString('searchword'));
		$match    = $app->input->get('searchphrase', $searchphrase, 'word');
		$ordering = $app->input->get('ordering', $params->get('ordering', 'newest'), 'word');
		$this->setSearch($keyword, $match, $ordering);

		// Set the search areas
		$areas = $app->input->get('areas', null, 'array');
		$this->setAreas($areas);
	}

	/**
	 * Method to set the search parameters
	 *
	 * @param   string $keyword  string search string
	 * @param   string $match    matching option, exact|any|all
	 * @param   string $ordering option, newest|oldest|popular|alpha|category
	 *
	 * @return  void
	 *
	 * @access    public
	 */
	public function setSearch($keyword, $match = 'all', $ordering = 'newest')
	{
		if (isset($keyword))
		{
			$this->setState('origkeyword', $keyword);

			if ($match !== 'exact')
			{
				$keyword = preg_replace('#\xE3\x80\x80#s', ' ', $keyword);
			}

			$this->setState('keyword', $keyword);
		}

		if (isset($match))
		{
			$this->setState('match', $match);
		}

		if (isset($ordering))
		{
			$this->setState('ordering', $ordering);
		}
	}

	/**
	 * Method to get weblink item data for the category
	 *
	 * @access public
	 * @return array
	 */
	public function getData()
	{
		// Lets load the content if it doesn't already exist
		if (empty($this->_data))
		{
			$areas = $this->getAreas();

			JPluginHelper::importPlugin('search');
			$dispatcher = JEventDispatcher::getInstance();
			$results    = $dispatcher->trigger('onContentSearch', [
					$this->getState('keyword'),
					$this->getState('match'),
					$this->getState('ordering'),
					$areas['active'],
				]
			);

			$rows = [];

			foreach ($results as $result)
			{
				$rows = array_merge((array) $rows, (array) $result);
			}

			$this->_total = count($rows);

			if ($this->getState('limit') > 0)
			{
				$this->_data = array_splice($rows, $this->getState('limitstart'), $this->getState('limit'));
			}
			else
			{
				$this->_data = $rows;
			}

			/* >>> ADDED: Run content plugins over results */
			$params = JFactory::getApplication()->getParams('com_content');
			$params->set('rl_search', 1);
			foreach ($this->_data as $item)
			{
				if (empty($item->text))
				{
					continue;
				}

				$dispatcher->trigger('onContentPrepare', ['com_search.search.article', &$item, &$params, 0]);

				if (empty($item->title))
				{
					continue;
				}

				// strip html tags from title
				$item->title = strip_tags($item->title);
			}
			/* <<< */
		}

		return $this->_data;
	}

	/**
	 * Method to get the total number of weblink items for the category
	 *
	 * @access  public
	 *
	 * @return  integer
	 */
	public function getTotal()
	{
		return $this->_total;
	}

	/**
	 * Method to set the search areas
	 *
	 * @param   array $active areas
	 * @param   array $search areas
	 *
	 * @return  void
	 *
	 * @access  public
	 */
	public function setAreas($active = [], $search = [])
	{
		$this->_areas['active'] = $active;
		$this->_areas['search'] = $search;
	}

	/**
	 * Method to get a pagination object of the weblink items for the category
	 *
	 * @access public
	 * @return  integer
	 */
	public function getPagination()
	{
		// Lets load the content if it doesn't already exist
		if (empty($this->_pagination))
		{
			$this->_pagination = new JPagination($this->getTotal(), $this->getState('limitstart'), $this->getState('limit'));
		}

		return $this->_pagination;
	}

	/**
	 * Method to get the search areas
	 *
	 * @return int
	 *
	 * @since 1.5
	 */
	public function getAreas()
	{
		// Load the Category data
		if (empty($this->_areas['search']))
		{
			$areas = [];

			JPluginHelper::importPlugin('search');
			$dispatcher  = JEventDispatcher::getInstance();
			$searchareas = $dispatcher->trigger('onContentSearchAreas');

			foreach ($searchareas as $area)
			{
				if (is_array($area))
				{
					$areas = array_merge($areas, $area);
				}
			}

			$this->_areas['search'] = $areas;
		}

		return $this->_areas;
	}
}
regularlabs/helpers/licenses.php000064400000001354152177723700013045 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\License as RL_License;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLLicenses
{
	public static function render($name, $check_pro = false)
	{
		return ! class_exists('RegularLabs\Library\License') ? '' : RL_License::getMessage($name, $check_pro);
	}
}
regularlabs/helpers/field.php000064400000001070152177723700012316 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLFormField
	extends \RegularLabs\Library\Field
{
}
regularlabs/helpers/versions.php000064400000002413152177723700013105 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Version as RL_Version;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLVersions
{
	public static function getXMLVersion($alias, $urlformat = false, $type = 'component', $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Version') ? '' : RL_Version::get($alias, $type, $folder);
	}

	public static function getPluginXMLVersion($alias, $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Version') ? '' : RL_Version::getPluginVersion($alias, $folder);
	}

	public static function render($alias)
	{
		return ! class_exists('RegularLabs\Library\Version') ? '' : RL_Version::getMessage($alias);
	}

	public static function getFooter($name, $copyright = 1)
	{
		return ! class_exists('RegularLabs\Library\Version') ? '' : RL_Version::getFooter($name, $copyright);
	}
}
regularlabs/helpers/assignments/users.php000064400000007123152177723700014734 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsUsers extends RLAssignment
{
	public function passAccessLevels()
	{
		$user = JFactory::getUser();

		$levels = $user->getAuthorisedViewLevels();

		$this->selection = $this->convertAccessLevelNamesToIds($this->selection);

		return $this->passSimple($levels);
	}

	public function passUserGroupLevels()
	{
		$user = JFactory::getUser();

		if ( ! empty($user->groups))
		{
			$groups = array_values($user->groups);
		}
		else
		{
			$groups = $user->getAuthorisedGroups();
		}

		if ($this->params->inc_children)
		{
			$this->setUserGroupChildrenIds();
		}

		$this->selection = $this->convertUsergroupNamesToIds($this->selection);

		return $this->passSimple($groups);
	}

	public function passUsers()
	{
		return $this->passSimple(JFactory::getUser()->get('id'));
	}

	private function convertAccessLevelNamesToIds($selection)
	{
		$names = [];

		foreach ($selection as $i => $level)
		{
			if (is_numeric($level))
			{
				continue;
			}

			unset($selection[$i]);

			$names[] = strtolower(str_replace(' ', '', $level));
		}

		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from('#__viewlevels')
			->where('LOWER(REPLACE(' . $db->quoteName('title') . ', " ", "")) IN (\'' . implode('\',\'', $names) . '\')');
		$db->setQuery($query);

		$level_ids = $db->loadColumn();

		return array_unique(array_merge($selection, $level_ids));
	}

	private function convertUsergroupNamesToIds($selection)
	{
		$names = [];

		foreach ($selection as $i => $group)
		{
			if (is_numeric($group))
			{
				continue;
			}

			unset($selection[$i]);

			$names[] = strtolower(str_replace(' ', '', $group));
		}

		$db = JFactory::getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from('#__usergroups')
			->where('LOWER(REPLACE(' . $db->quoteName('title') . ', " ", "")) IN (\'' . implode('\',\'', $names) . '\')');
		$db->setQuery($query);

		$group_ids = $db->loadColumn();

		return array_unique(array_merge($selection, $group_ids));
	}

	private function setUserGroupChildrenIds()
	{
		$children = $this->getUserGroupChildrenIds($this->selection);

		if ($this->params->inc_children == 2)
		{
			$this->selection = $children;

			return;
		}

		$this->selection = array_merge($this->selection, $children);
	}

	private function getUserGroupChildrenIds($groups)
	{
		$children = [];

		$db = JFactory::getDbo();

		foreach ($groups as $group)
		{
			$query = $db->getQuery(true)
				->select($db->quoteName('id'))
				->from($db->quoteName('#__usergroups'))
				->where($db->quoteName('parent_id') . ' = ' . (int) $group);
			$db->setQuery($query);

			$group_children = $db->loadColumn();

			if (empty($group_children))
			{
				continue;
			}

			$children = array_merge($children, $group_children);

			$group_grand_children = $this->getUserGroupChildrenIds($group_children);

			if (empty($group_grand_children))
			{
				continue;
			}

			$children = array_merge($children, $group_grand_children);
		}

		$children = array_unique($children);

		return $children;
	}
}
regularlabs/helpers/assignments/mijoshop.php000064400000005770152177723700015431 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsMijoShop extends RLAssignment
{
	public function init()
	{
		$input = JFactory::getApplication()->input;

		$category_id = $input->getCmd('path', 0);
		if (strpos($category_id, '_'))
		{
			$category_id = end(explode('_', $category_id));
		}

		$this->request->item_id     = $input->getInt('product_id', 0);
		$this->request->category_id = $category_id;
		$this->request->id          = ($this->request->item_id) ? $this->request->item_id : $this->request->category_id;

		$view = $input->getCmd('view', '');
		if (empty($view))
		{
			$mijoshop = JPATH_ROOT . '/components/com_mijoshop/mijoshop/mijoshop.php';
			if ( ! file_exists($mijoshop))
			{
				return;
			}

			require_once($mijoshop);

			$route = $input->getString('route', '');
			$view  = MijoShop::get('router')->getView($route);
		}

		$this->request->view = $view;
	}

	public function passPageTypes()
	{
		return $this->passByPageTypes('com_mijoshop', $this->selection, $this->assignment, true);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_mijoshop')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = [];
		if ($this->request->category_id)
		{
			$cats = $this->request->category_id;
		}
		else if ($this->request->item_id)
		{
			$query = $this->db->getQuery(true)
				->select('c.category_id')
				->from('#__mijoshop_product_to_category AS c')
				->where('c.product_id = ' . (int) $this->request->id);
			$this->db->setQuery($query);
			$cats = $this->db->loadColumn();
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	public function passProducts()
	{
		if ( ! $this->request->id || $this->request->option != 'com_mijoshop' || $this->request->view != 'product')
		{
			return $this->pass(false);
		}

		return $this->passSimple($this->request->id);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'mijoshop_category', 'parent_id', 'category_id');
	}
}
regularlabs/helpers/assignments/content.php000064400000013030152177723700015237 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;
use Joomla\CMS\Table\Table as JTable;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsContent extends RLAssignment
{
	public function passPageTypes()
	{
		$components = ['com_content', 'com_contentsubmit'];
		if ( ! in_array($this->request->option, $components))
		{
			return $this->pass(false);
		}
		if ($this->request->view == 'category' && $this->request->layout == 'blog')
		{
			$view = 'categoryblog';
		}
		else
		{
			$view = $this->request->view;
		}

		return $this->passSimple($view);
	}

	public function passCategories()
	{
		// components that use the com_content secs/cats
		$components = ['com_content', 'com_flexicontent', 'com_contentsubmit'];
		if ( ! in_array($this->request->option, $components))
		{
			return $this->pass(false);
		}

		if (empty($this->selection))
		{
			return $this->pass(false);
		}

		$is_content  = in_array($this->request->option, ['com_content', 'com_flexicontent']);
		$is_category = in_array($this->request->view, ['category']);
		$is_item     = in_array($this->request->view, ['', 'article', 'item', 'form']);

		if (
			$this->request->option != 'com_contentsubmit'
			&& ! ($this->params->inc_categories && $is_content && $is_category)
			&& ! ($this->params->inc_articles && $is_content && $is_item)
			&& ! ($this->params->inc_others && ! ($is_content && ($is_category || $is_item)))
		)
		{
			return $this->pass(false);
		}

		if ($this->request->option == 'com_contentsubmit')
		{
			// Content Submit
			$contentsubmit_params = new ContentsubmitModelArticle;
			if (in_array($contentsubmit_params->_id, $this->selection))
			{
				return $this->pass(true);
			}

			return $this->pass(false);
		}

		$pass = false;
		if (
			$this->params->inc_others
			&& ! ($is_content && ($is_category || $is_item))
			&& $this->article
		)
		{
			if ( ! isset($this->article->id) && isset($this->article->slug))
			{
				$this->article->id = (int) $this->article->slug;
			}

			if ( ! isset($this->article->catid) && isset($this->article->catslug))
			{
				$this->article->catid = (int) $this->article->catslug;
			}

			$this->request->id   = $this->article->id;
			$this->request->view = 'article';
		}

		$catids = $this->getCategoryIds($is_category);

		foreach ($catids as $catid)
		{
			if ( ! $catid)
			{
				continue;
			}

			$pass = in_array($catid, $this->selection);

			if ($pass && $this->params->inc_children == 2)
			{
				$pass = false;
				continue;
			}

			if ( ! $pass && $this->params->inc_children)
			{
				$parent_ids = $this->getCatParentIds($catid);
				$parent_ids = array_diff($parent_ids, [1]);
				foreach ($parent_ids as $id)
				{
					if (in_array($id, $this->selection))
					{
						$pass = true;
						break;
					}
				}

				unset($parent_ids);
			}
		}

		return $this->pass($pass);
	}

	private function getCategoryIds($is_category = false)
	{
		if ($is_category)
		{
			return (array) $this->request->id;
		}

		if ( ! $this->article && $this->request->id)
		{
			$this->article = JTable::getInstance('content');
			$this->article->load($this->request->id);
		}

		if ($this->article && $this->article->catid)
		{
			return (array) $this->article->catid;
		}

		$catid      = JFactory::getApplication()->input->getInt('catid', JFactory::getApplication()->getUserState('com_content.articles.filter.category_id'));
		$menuparams = $this->getMenuItemParams($this->request->Itemid);

		if ($this->request->view == 'featured')
		{
			$menuparams = $this->getMenuItemParams($this->request->Itemid);

			return isset($menuparams->featured_categories) ? (array) $menuparams->featured_categories : (array) $catid;
		}

		return isset($menuparams->catid) ? (array) $menuparams->catid : (array) $catid;
	}

	public function passArticles()
	{
		if ( ! $this->request->id
			|| ! (($this->request->option == 'com_content' && $this->request->view == 'article')
				|| ($this->request->option == 'com_flexicontent' && $this->request->view == 'item')
			)
		)
		{
			return $this->pass(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentIds'))
		{
			return $this->pass(false);
		}

		// Pass Content Keywords
		if ( ! $this->passItemByType($pass, 'ContentKeywords'))
		{
			return $this->pass(false);
		}

		// Pass Meta Keywords
		if ( ! $this->passItemByType($pass, 'MetaKeywords'))
		{
			return $this->pass(false);
		}

		// Pass Authors
		if ( ! $this->passItemByType($pass, 'Authors'))
		{
			return $this->pass(false);
		}

		return $this->pass($pass);
	}

	public function getItem($fields = [])
	{
		if ($this->article)
		{
			return $this->article;
		}

		if ( ! class_exists('ContentModelArticle'))
		{
			require_once JPATH_SITE . '/components/com_content/models/article.php';
		}

		$model = JModel::getInstance('article', 'contentModel');

		if ( ! method_exists($model, 'getItem'))
		{
			return null;
		}

		$this->article = $model->getItem($this->request->id);

		return $this->article;
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'categories');
	}
}
regularlabs/helpers/assignments/languages.php000064400000001371152177723700015540 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsLanguages extends RLAssignment
{
	public function passLanguages()
	{
		return $this->passSimple(JFactory::getLanguage()->getTag(), true);
	}
}
regularlabs/helpers/assignments/components.php000064400000001321152177723700015752 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsComponents extends RLAssignment
{
	public function passComponents()
	{
		return $this->passSimple(strtolower($this->request->option));
	}
}
regularlabs/helpers/assignments/easyblog.php000064400000010007152177723700015373 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsEasyBlog extends RLAssignment
{
	public function passPageTypes()
	{
		return $this->passByPageTypes('com_easyblog', $this->selection, $this->assignment);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_easyblog')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_categories && $this->request->view == 'categories')
			|| ($this->params->inc_items && $this->request->view == 'entry')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = $this->makeArray($this->getCategories());

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		switch ($this->request->view)
		{
			case 'entry' :
				return $this->getCategoryIDFromItem();
				break;

			case 'categories' :
				return $this->request->id;
				break;

			default:
				return '';
		}
	}

	private function getCategoryIDFromItem()
	{
		$query = $this->db->getQuery(true)
			->select('i.category_id')
			->from('#__easyblog_post AS i')
			->where('i.id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadResult();
	}

	public function passTags()
	{
		if ($this->request->option != 'com_easyblog')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_tags && $this->request->layout == 'tag')
			|| ($this->params->inc_items && $this->request->view == 'entry')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		if ($this->params->inc_tags && $this->request->layout == 'tag')
		{
			$query = $this->db->getQuery(true)
				->select('t.alias')
				->from('#__easyblog_tag AS t')
				->where('t.id = ' . (int) $this->request->id)
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tags = $this->db->loadColumn();

			return $this->passSimple($tags, true);
		}

		$query = $this->db->getQuery(true)
			->select('t.alias')
			->from('#__easyblog_post_tag AS x')
			->join('LEFT', '#__easyblog_tag AS t ON t.id = x.tag_id')
			->where('x.post_id = ' . (int) $this->request->id)
			->where('t.published = 1');
		$this->db->setQuery($query);
		$tags = $this->db->loadColumn();

		return $this->passSimple($tags, true);
	}

	public function passItems()
	{
		if ( ! $this->request->id || $this->request->option != 'com_easyblog' || $this->request->view != 'entry')
		{
			return $this->pass(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentIds'))
		{
			return $this->pass(false);
		}

		// Pass Content Keywords
		if ( ! $this->passItemByType($pass, 'ContentKeywords'))
		{
			return $this->pass(false);
		}

		// Pass Authors
		if ( ! $this->passItemByType($pass, 'Authors'))
		{
			return $this->pass(false);
		}

		return $this->pass($pass);
	}

	public function passContentKeywords($fields = ['title', 'intro', 'content'], $text = '')
	{
		parent::passContentKeywords($fields);
	}

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__easyblog_post')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'easyblog_category', 'parent_id');
	}
}
regularlabs/helpers/assignments/homepage.php000064400000011606152177723700015361 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\Language\LanguageHelper as JLanguageHelper;
use Joomla\CMS\Uri\Uri as JUri;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/text.php';
require_once dirname(__DIR__) . '/string.php';
require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsHomePage extends RLAssignment
{
	public function passHomePage()
	{
		$home = JFactory::getApplication()->getMenu('site')->getDefault(JFactory::getLanguage()->getTag());

		// return if option or other set values do not match the homepage menu item values
		if ($this->request->option)
		{
			// check if option is different to home menu
			if ( ! $home || ! isset($home->query['option']) || $home->query['option'] != $this->request->option)
			{
				return $this->pass(false);
			}

			if ( ! $this->request->option)
			{
				// set the view/task/layout in the menu item to empty if not set
				$home->query['view']   = isset($home->query['view']) ? $home->query['view'] : '';
				$home->query['task']   = isset($home->query['task']) ? $home->query['task'] : '';
				$home->query['layout'] = isset($home->query['layout']) ? $home->query['layout'] : '';
			}

			// check set values against home menu query items
			foreach ($home->query as $k => $v)
			{
				if ((isset($this->request->{$k}) && $this->request->{$k} != $v)
					|| (
						( ! isset($this->request->{$k}) || in_array($v, ['virtuemart', 'mijoshop']))
						&& JFactory::getApplication()->input->get($k) != $v
					)
				)
				{
					return $this->pass(false);
				}
			}

			// check post values against home menu params
			foreach ($home->params->toObject() as $k => $v)
			{
				if (($v && isset($_POST[$k]) && $_POST[$k] != $v)
					|| ( ! $v && isset($_POST[$k]) && $_POST[$k])
				)
				{
					return $this->pass(false);
				}
			}
		}

		$pass = $this->checkPass($home);

		if ( ! $pass)
		{
			$pass = $this->checkPass($home, 1);
		}

		return $this->pass($pass);
	}

	private function checkPass(&$home, $addlang = 0)
	{
		$uri = JUri::getInstance();

		if ($addlang)
		{
			$sef = $uri->getVar('lang');
			if (empty($sef))
			{
				$langs = array_keys(JLanguageHelper::getLanguages('sef'));
				$path  = RLString::substr(
					$uri->toString(['scheme', 'user', 'pass', 'host', 'port', 'path']),
					RLString::strlen($uri->base())
				);
				$path  = preg_replace('#^index\.php/?#', '', $path);
				$parts = explode('/', $path);
				$part  = reset($parts);
				if (in_array($part, $langs))
				{
					$sef = $part;
				}
			}

			if (empty($sef))
			{
				return false;
			}
		}

		$query = $uri->toString(['query']);
		if (strpos($query, 'option=') === false && strpos($query, 'Itemid=') === false)
		{
			$url = $uri->toString(['host', 'path']);
		}
		else
		{
			$url = $uri->toString(['host', 'path', 'query']);
		}

		// remove the www.
		$url = preg_replace('#^www\.#', '', $url);
		// replace ampersand chars
		$url = str_replace('&amp;', '&', $url);
		// remove any language vars
		$url = preg_replace('#((\?)lang=[a-z-_]*(&|$)|&lang=[a-z-_]*)#', '\2', $url);
		// remove trailing nonsense
		$url = trim(preg_replace('#/?\??&?$#', '', $url));
		// remove the index.php/
		$url = preg_replace('#/index\.php(/|$)#', '/', $url);
		// remove trailing /
		$url = trim(preg_replace('#/$#', '', $url));

		$root = JUri::root();

		// remove the http(s)
		$root = preg_replace('#^.*?://#', '', $root);
		// remove the www.
		$root = preg_replace('#^www\.#', '', $root);
		//remove the port
		$root = preg_replace('#:[0-9]+#', '', $root);
		// so also passes on urls with trailing /, ?, &, /?, etc...
		$root = preg_replace('#(Itemid=[0-9]*).*^#', '\1', $root);
		// remove trailing /
		$root = trim(preg_replace('#/$#', '', $root));

		if ($addlang)
		{
			$root .= '/' . $sef;
		}

		/* Pass urls:
		 * [root]
		 */
		$regex = '#^' . $root . '$#i';

		if (preg_match($regex, $url))
		{
			return true;
		}

		/* Pass urls:
		 * [root]?Itemid=[menu-id]
		 * [root]/?Itemid=[menu-id]
		 * [root]/index.php?Itemid=[menu-id]
		 * [root]/[menu-alias]
		 * [root]/[menu-alias]?Itemid=[menu-id]
		 * [root]/index.php?[menu-alias]
		 * [root]/index.php?[menu-alias]?Itemid=[menu-id]
		 * [root]/[menu-link]
		 * [root]/[menu-link]&Itemid=[menu-id]
		 */
		$regex = '#^' . $root
			. '(/('
			. 'index\.php'
			. '|'
			. '(index\.php\?)?' . RLText::pregQuote($home->alias)
			. '|'
			. RLText::pregQuote($home->link)
			. ')?)?'
			. '(/?[\?&]Itemid=' . (int) $home->id . ')?'
			. '$#i';

		return preg_match($regex, $url);
	}
}
regularlabs/helpers/assignments/urls.php000064400000003401152177723700014553 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Uri\Uri as JUri;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';
require_once dirname(__DIR__) . '/text.php';

class RLAssignmentsURLs extends RLAssignment
{
	public function passURLs()
	{
		$regex = isset($this->params->regex) ? $this->params->regex : 0;

		if ( ! is_array($this->selection))
		{
			$this->selection = explode("\n", $this->selection);
		}

		if (count($this->selection) == 1)
		{
			$this->selection = explode("\n", $this->selection[0]);
		}

		$url = JUri::getInstance();
		$url = $url->toString();

		$urls = [
			RLText::html_entity_decoder(urldecode($url)),
			urldecode($url),
			RLText::html_entity_decoder($url),
			$url,
		];
		$urls = array_unique($urls);

		$pass = false;
		foreach ($urls as $url)
		{
			foreach ($this->selection as $s)
			{
				$s = trim($s);
				if ($s == '')
				{
					continue;
				}

				if ($regex)
				{
					$url_part = str_replace(['#', '&amp;'], ['\#', '(&amp;|&)'], $s);
					$s        = '#' . $url_part . '#si';
					if (@preg_match($s . 'u', $url) || @preg_match($s, $url))
					{
						$pass = true;
						break;
					}

					continue;
				}

				if (strpos($url, $s) !== false)
				{
					$pass = true;
					break;
				}
			}

			if ($pass)
			{
				break;
			}
		}

		return $this->pass($pass);
	}
}
regularlabs/helpers/assignments/geo.php000064400000005054152177723700014346 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Log\Log as JLog;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsGeo extends RLAssignment
{
	var $geo = null;

	/**
	 * passContinents
	 */
	public function passContinents()
	{
		if ( ! $this->getGeo() || empty($this->geo->continentCode))
		{
			return $this->pass(false);
		}

		return $this->passSimple([$this->geo->continent, $this->geo->continentCode]);
	}

	/**
	 * passCountries
	 */
	public function passCountries()
	{
		$this->getGeo();

		if ( ! $this->getGeo() || empty($this->geo->countryCode))
		{
			return $this->pass(false);
		}

		return $this->passSimple([$this->geo->country, $this->geo->countryCode]);
	}

	/**
	 * passRegions
	 */
	public function passRegions()
	{
		if ( ! $this->getGeo() || empty($this->geo->countryCode) || empty($this->geo->regionCodes))
		{
			return $this->pass(false);
		}

		$regions = $this->geo->regionCodes;
		array_walk($regions, function (&$value) {
			$value = $this->geo->countryCode . '-' . $value;
		});

		return $this->passSimple($regions);
	}

	/**
	 * passPostalcodes
	 */
	public function passPostalcodes()
	{
		if ( ! $this->getGeo() || empty($this->geo->postalCode))
		{
			return $this->pass(false);
		}

		// replace dashes with dots: 730-0011 => 730.0011
		$postalcode = str_replace('-', '.', $this->geo->postalCode);

		return $this->passInRange($postalcode);
	}

	public function getGeo($ip = '')
	{
		if ($this->geo !== null)
		{
			return $this->geo;
		}

		$geo = $this->getGeoObject($ip);

		if (empty($geo))
		{
			return false;
		}

		$this->geo = $geo->get();

		if (JDEBUG)
		{
			JLog::addLogger(['text_file' => 'regularlabs_geoip.log.php'], JLog::ALL, ['regularlabs_geoip']);
			JLog::add(json_encode($this->geo), JLog::DEBUG, 'regularlabs_geoip');
		}

		return $this->geo;
	}

	private function getGeoObject($ip)
	{
		if ( ! file_exists(JPATH_LIBRARIES . '/geoip/geoip.php'))
		{
			return false;
		}

		require_once JPATH_LIBRARIES . '/geoip/geoip.php';

		if ( ! class_exists('RegularLabs_GeoIp'))
		{
			return new GeoIp($ip);
		}

		return new RegularLabs_GeoIp($ip);
	}
}
regularlabs/helpers/assignments/virtuemart.php000064400000010021152177723700015764 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsVirtueMart extends RLAssignment
{
	public function init()
	{
		$virtuemart_product_id  = JFactory::getApplication()->input->get('virtuemart_product_id', [], 'array');
		$virtuemart_category_id = JFactory::getApplication()->input->get('virtuemart_category_id', [], 'array');

		$this->request->item_id     = isset($virtuemart_product_id[0]) ? $virtuemart_product_id[0] : null;
		$this->request->category_id = isset($virtuemart_category_id[0]) ? $virtuemart_category_id[0] : null;
		$this->request->id          = ($this->request->item_id) ? $this->request->item_id : $this->request->category_id;
	}

	public function passPageTypes()
	{
		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		return $this->passByPageTypes('com_virtuemart', $this->selection, $this->assignment, true);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_virtuemart')
		{
			return $this->pass(false);
		}

		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		$pass = (($this->params->inc_categories && in_array($this->request->view, ['categories', 'category']))
			|| ($this->params->inc_items && $this->request->view == 'productdetails')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = [];
		if ($this->request->view == 'productdetails' && $this->request->item_id)
		{
			$query = $this->db->getQuery(true)
				->select('x.virtuemart_category_id')
				->from('#__virtuemart_product_categories AS x')
				->where('x.virtuemart_product_id = ' . (int) $this->request->item_id);
			$this->db->setQuery($query);
			$cats = $this->db->loadColumn();
		}
		else if ($this->request->category_id)
		{
			$cats = $this->request->category_id;
			if ( ! is_numeric($cats))
			{
				$query = $this->db->getQuery(true)
					->select('config')
					->from('#__virtuemart_configs')
					->where('virtuemart_config_id = 1');
				$this->db->setQuery($query);
				$config = $this->db->loadResult();
				$lang   = substr($config, strpos($config, 'vmlang='));
				$lang   = substr($lang, 0, strpos($lang, '|'));
				if (preg_match('#"([^"]*_[^"]*)"#', $lang, $lang))
				{
					$lang = $lang[1];
				}
				else
				{
					$lang = 'en_gb';
				}

				$query = $this->db->getQuery(true)
					->select('l.virtuemart_category_id')
					->from('#__virtuemart_categories_' . $lang . ' AS l')
					->where('l.slug = ' . $this->db->quote($cats));
				$this->db->setQuery($query);
				$cats = $this->db->loadResult();
			}
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	public function passProducts()
	{
		// Because VM sucks, we have to get the view again
		$this->request->view = JFactory::getApplication()->input->getString('view');

		if ( ! $this->request->id || $this->request->option != 'com_virtuemart' || $this->request->view != 'productdetails')
		{
			return $this->pass(false);
		}

		return $this->passSimple($this->request->id);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'virtuemart_category_categories', 'category_parent_id', 'category_child_id');
	}
}
regularlabs/helpers/assignments/menu.php000064400000004547152177723700014546 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsMenu extends RLAssignment
{
	public function passMenu()
	{
		// return if no Itemid or selection is set
		if ( ! $this->request->Itemid || empty($this->selection))
		{
			return $this->pass($this->params->inc_noitemid);
		}

		$menutype = 'type.' . self::getMenuType();

		// return true if menu type is in selection
		if (in_array($menutype, $this->selection))
		{
			return $this->pass(true);
		}

		// return true if menu is in selection
		if (in_array($this->request->Itemid, $this->selection))
		{
			return $this->pass(($this->params->inc_children != 2));
		}

		if ( ! $this->params->inc_children)
		{
			return $this->pass(false);
		}

		$parent_ids = $this->getMenuParentIds($this->request->Itemid);
		$parent_ids = array_diff($parent_ids, [1]);
		foreach ($parent_ids as $id)
		{
			if ( ! in_array($id, $this->selection))
			{
				continue;
			}

			return $this->pass(true);
		}

		return $this->pass(false);
	}

	private function getMenuParentIds($id = 0)
	{
		return $this->getParentIds($id, 'menu');
	}

	private function getMenuType()
	{
		if (isset($this->request->menutype))
		{
			return $this->request->menutype;
		}

		if (empty($this->request->Itemid))
		{
			$this->request->menutype = '';

			return $this->request->menutype;
		}

		if (JFactory::getApplication()->isClient('site'))
		{
			$menu = JFactory::getApplication()->getMenu()->getItem((int) $this->request->Itemid);

			$this->request->menutype = isset($menu->menutype) ? $menu->menutype : '';

			return $this->request->menutype;
		}

		$query = $this->db->getQuery(true)
			->select('m.menutype')
			->from('#__menu AS m')
			->where('m.id = ' . (int) $this->request->Itemid);
		$this->db->setQuery($query);
		$this->request->menutype = $this->db->loadResult();

		return $this->request->menutype;
	}
}
regularlabs/helpers/assignments/redshop.php000064400000005046152177723700015241 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsRedShop extends RLAssignment
{
	public function init()
	{
		$this->request->item_id     = JFactory::getApplication()->input->getInt('pid', 0);
		$this->request->category_id = JFactory::getApplication()->input->getInt('cid', 0);
		$this->request->id          = ($this->request->item_id) ? $this->request->item_id : $this->request->category_id;
	}

	public function passPageTypes()
	{
		return $this->passByPageTypes('com_redshop', $this->selection, $this->assignment, true);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_redshop')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = [];
		if ($this->request->category_id)
		{
			$cats = $this->request->category_id;
		}
		else if ($this->request->item_id)
		{
			$query = $this->db->getQuery(true)
				->select('x.category_id')
				->from('#__redshop_product_category_xref AS x')
				->where('x.product_id = ' . (int) $this->request->item_id);
			$this->db->setQuery($query);
			$cats = $this->db->loadColumn();
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	public function passProducts()
	{
		if ( ! $this->request->id || $this->request->option != 'com_redshop' || $this->request->view != 'product')
		{
			return $this->pass(false);
		}

		return $this->passSimple($this->request->id);
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'redshop_category_xref', 'category_parent_id', 'category_child_id');
	}
}
regularlabs/helpers/assignments/tags.php000064400000005173152177723700014534 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsTags extends RLAssignment
{
	public function passTags()
	{
		if (in_array($this->request->option, ['com_content', 'com_flexicontent']))
		{
			return $this->passTagsContent();
		}

		if ($this->request->option != 'com_tags'
			|| $this->request->view != 'tag'
			|| ! $this->request->id
		)
		{
			return $this->pass(false);
		}

		return $this->passTag($this->request->id);
	}

	private function passTagsContent()
	{
		$is_item     = in_array($this->request->view, ['', 'article', 'item']);
		$is_category = in_array($this->request->view, ['category']);

		switch (true)
		{
			case ($is_item):
				$prefix = 'com_content.article';
				break;

			case ($is_category):
				$prefix = 'com_content.category';
				break;

			default:
				return $this->pass(false);
		}

		// Load the tags.
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('t.id'))
			->select($this->db->quoteName('t.title'))
			->from('#__tags AS t')
			->join(
				'INNER', '#__contentitem_tag_map AS m'
				. ' ON m.tag_id = t.id'
				. ' AND m.type_alias = ' . $this->db->quote($prefix)
				. ' AND m.content_item_id IN ( ' . $this->request->id . ')'
			);
		$this->db->setQuery($query);
		$tags = $this->db->loadObjectList();

		if (empty($tags))
		{
			return $this->pass(false);
		}

		foreach ($tags as $tag)
		{
			if ( ! $this->passTag($tag->id) && ! $this->passTag($tag->title))
			{
				continue;
			}

			return $this->pass(true);
		}

		return $this->pass(false);
	}

	private function passTag($tag)
	{
		$pass = in_array($tag, $this->selection);

		if ($pass)
		{
			// If passed, return false if assigned to only children
			// Else return true
			return ($this->params->inc_children != 2);
		}

		if ( ! $this->params->inc_children)
		{
			return false;
		}

		// Return true if a parent id is present in the selection
		return array_intersect(
			$this->getTagsParentIds($tag),
			$this->selection
		);
	}

	private function getTagsParentIds($id = 0)
	{
		$parentids = $this->getParentIds($id, 'tags');
		// Remove the root tag
		$parentids = array_diff($parentids, [1]);

		return $parentids;
	}
}
regularlabs/helpers/assignments/hikashop.php000064400000005775152177723700015414 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsHikaShop extends RLAssignment
{
	public function passPageTypes()
	{
		if ($this->request->option != 'com_hikashop')
		{
			return $this->pass(false);
		}

		$type = $this->request->view;
		if (
			($type == 'product' && in_array($this->request->layout, ['contact', 'show']))
			|| ($type == 'user' && in_array($this->request->layout, ['cpanel']))
		)
		{
			$type .= '_' . $this->request->layout;
		}

		return $this->passSimple($type);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_hikashop')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_categories
				&& ($this->request->view == 'category' || $this->request->layout == 'listing')
			)
			|| ($this->params->inc_items && $this->request->view == 'product')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = $this->getCategories();

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	public function passProducts()
	{
		if ( ! $this->request->id || $this->request->option != 'com_hikashop' || $this->request->view != 'product')
		{
			return $this->pass(false);
		}

		return $this->passSimple($this->request->id);
	}

	private function getCategories()
	{
		switch (true)
		{
			case (($this->request->view == 'category' || $this->request->layout == 'listing') && $this->request->id):
				return [$this->request->id];

			case ($this->request->view == 'category' || $this->request->layout == 'listing'):
				include_once JPATH_ADMINISTRATOR . '/components/com_hikashop/helpers/helper.php';
				$menuClass = hikashop_get('class.menus');
				$menuData  = $menuClass->get($this->request->Itemid);

				return $this->makeArray($menuData->hikashop_params['selectparentlisting']);

			case ($this->request->id):
				$query = $this->db->getQuery(true)
					->select('c.category_id')
					->from('#__hikashop_product_category AS c')
					->where('c.product_id = ' . (int) $this->request->id);
				$this->db->setQuery($query);
				$cats = $this->db->loadColumn();

				return $this->makeArray($cats);

			default:
				return [];
		}
	}

	private function getCatParentIds($id = 0)
	{
		return $this->getParentIds($id, 'hikashop_category', 'category_parent_id', 'category_id');
	}
}
regularlabs/helpers/assignments/templates.php000064400000003745152177723700015577 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsTemplates extends RLAssignment
{
	public function passTemplates()
	{
		$template = $this->getTemplate();

		// Put template name and name + style id into array
		// The '::' separator was used in pre Joomla 3.3
		$template = [$template->template, $template->template . '--' . $template->id, $template->template . '::' . $template->id];

		return $this->passSimple($template, true);
	}

	public function getTemplate()
	{
		$template = JFactory::getApplication()->getTemplate(true);

		if (isset($template->id))
		{
			return $template;
		}

		$params = json_encode($template->params);

		// Find template style id based on params, as the template style id is not always stored in the getTemplate
		$query = $this->db->getQuery(true)
			->select('id')
			->from('#__template_styles as s')
			->where('s.client_id = 0')
			->where('s.template = ' . $this->db->quote($template->template))
			->where('s.params = ' . $this->db->quote($params));
		$this->db->setQuery($query, 0, 1);
		$template->id = $this->db->loadResult('id');

		if ($template->id)
		{
			return $template;
		}

		// No template style id is found, so just grab the first result based on the template name
		$query->clear('where')
			->where('s.client_id = 0')
			->where('s.template = ' . $this->db->quote($template->template));
		$this->db->setQuery($query, 0, 1);
		$template->id = $this->db->loadResult('id');

		return $template;
	}
}
regularlabs/helpers/assignments/agents.php000064400000007077152177723700015064 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';
require_once dirname(__DIR__) . '/text.php';
require_once dirname(__DIR__) . '/mobile_detect.php';

class RLAssignmentsAgents extends RLAssignment
{
	var $agent  = null;
	var $device = null;

	/**
	 * passBrowsers
	 */
	public function passBrowsers()
	{
		if (empty($this->selection))
		{
			return $this->pass(false);
		}

		foreach ($this->selection as $browser)
		{
			if ( ! $this->passBrowser($browser))
			{
				continue;
			}

			return $this->pass(true);
		}

		return $this->pass(false);
	}

	/**
	 * passOS
	 */
	public function passOS()
	{
		return self::passBrowsers();
	}

	/**
	 * passDevices
	 */
	public function passDevices()
	{
		$pass = (in_array('mobile', $this->selection) && $this->isMobile())
			|| (in_array('tablet', $this->selection) && $this->isTablet())
			|| (in_array('desktop', $this->selection) && $this->isDesktop());

		return $this->pass($pass);
	}

	/**
	 * isPhone
	 */
	public function isPhone()
	{
		return $this->isMobile();
	}

	/**
	 * isMobile
	 */
	public function isMobile()
	{
		return $this->getDevice() == 'mobile';
	}

	/**
	 * isTablet
	 */
	public function isTablet()
	{
		return $this->getDevice() == 'tablet';
	}

	/**
	 * isDesktop
	 */
	public function isDesktop()
	{
		return $this->getDevice() == 'desktop';
	}

	/**
	 * setDevice
	 */
	private function getDevice()
	{
		if ( ! is_null($this->device))
		{
			return $this->device;
		}

		$detect = new RLMobile_Detect;

		$this->is_mobile = $detect->isMobile();

		switch (true)
		{
			case($detect->isTablet()):
				$this->device = 'tablet';
				break;

			case ($detect->isMobile()):
				$this->device = 'mobile';
				break;

			default:
				$this->device = 'desktop';
		}

		return $this->device;
	}

	/**
	 * getAgent
	 */
	private function getAgent()
	{
		if ( ! is_null($this->agent))
		{
			return $this->agent;
		}

		$detect = new RLMobile_Detect;
		$agent  = $detect->getUserAgent();

		switch (true)
		{
			case (stripos($agent, 'Trident') !== false):
				// Add MSIE to IE11
				$agent = preg_replace('#(Trident/[0-9\.]+; rv:([0-9\.]+))#is', '\1 MSIE \2', $agent);
				break;

			case (stripos($agent, 'Chrome') !== false):
				// Remove Safari from Chrome
				$agent = preg_replace('#(Chrome/.*)Safari/[0-9\.]*#is', '\1', $agent);
				// Add MSIE to IE Edge and remove Chrome from IE Edge
				$agent = preg_replace('#Chrome/.*(Edge/[0-9])#is', 'MSIE \1', $agent);
				break;

			case (stripos($agent, 'Opera') !== false):
				$agent = preg_replace('#(Opera/.*)Version/#is', '\1Opera/', $agent);
				break;
		}

		$this->agent = $agent;

		return $this->agent;
	}

	/**
	 * passBrowser
	 */
	private function passBrowser($browser = '')
	{
		if ( ! $browser)
		{
			return false;
		}

		if ($browser == 'mobile')
		{
			return $this->isMobile();
		}

		if ( ! (strpos($browser, '#') === 0))
		{
			$browser = '#' . RLText::pregQuote($browser) . '#';
		}

		// also check for _ instead of .
		$browser = preg_replace('#\\\.([^\]])#', '[\._]\1', $browser);
		$browser = str_replace('\.]', '\._]', $browser);

		return preg_match($browser . 'i', $this->getAgent());
	}
}
regularlabs/helpers/assignments/ips.php000064400000005742152177723700014373 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsIPs extends RLAssignment
{
	public function passIPs()
	{
		if (is_array($this->selection))
		{
			$this->selection = implode(',', $this->selection);
		}

		$this->selection = explode(',', str_replace([' ', "\r", "\n"], ['', '', ','], $this->selection));

		$pass = $this->checkIPList();

		return $this->pass($pass);
	}

	private function checkIPList()
	{
		foreach ($this->selection as $range)
		{
			// Check next range if this one doesn't match
			if ( ! $this->checkIP($range))
			{
				continue;
			}

			// Match found, so return true!
			return true;
		}

		// No matches found, so return false
		return false;
	}

	private function checkIP($range)
	{
		if (empty($range))
		{
			return false;
		}

		if (strpos($range, '-') !== false)
		{
			// Selection is an IP range
			return $this->checkIPRange($range);
		}

		// Selection is a single IP (part)
		return $this->checkIPPart($range);
	}

	private function checkIPRange($range)
	{
		$ip = $_SERVER['REMOTE_ADDR'];

		// Return if no IP address can be found (shouldn't happen, but who knows)
		if (empty($ip))
		{
			return false;
		}

		// check if IP is between or equal to the from and to IP range
		list($min, $max) = explode('-', trim($range), 2);

		// Return false if IP is smaller than the range start
		if ($ip < trim($min))
		{
			return false;
		}

		$max = $this->fillMaxRange($max, $min);

		// Return false if IP is larger than the range end
		if ($ip > trim($max))
		{
			return false;
		}

		return true;
	}

	/* Fill the max range by prefixing it with the missing parts from the min range
	 * So 101.102.103.104-201.202 becomes:
	 * max: 101.102.201.202
	 */
	private function fillMaxRange($max, $min)
	{
		$max_parts = explode('.', $max);

		if (count() == 4)
		{
			return $max;
		}

		$min_parts = explode('.', $min);

		$prefix = array_slice($min_parts, 0, count($min_parts) - count($max_parts));

		return implode('.', $prefix) . '.' . implode('.', $max_parts);
	}

	private function checkIPPart($range)
	{
		$ip = $_SERVER['REMOTE_ADDR'];

		// Return if no IP address can be found (shouldn't happen, but who knows)
		if (empty($ip))
		{
			return false;
		}

		$ip_parts    = explode('.', $ip);
		$range_parts = explode('.', trim($range));

		// Trim the IP to the part length of the range
		$ip = implode('.', array_slice($ip_parts, 0, count($range_parts)));

		// Return false if ip does not match the range
		if ($range != $ip)
		{
			return false;
		}

		return true;
	}
}
regularlabs/helpers/assignments/akeebasubs.php000064400000002606152177723700015701 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLAssignmentsAkeebaSubs extends RLAssignment
{
	public function init()
	{
		if ( ! $this->request->id && $this->request->view == 'level')
		{
			$slug = JFactory::getApplication()->input->getString('slug', '');
			if ($slug)
			{
				$query = $this->db->getQuery(true)
					->select('l.akeebasubs_level_id')
					->from('#__akeebasubs_levels AS l')
					->where('l.slug = ' . $this->db->quote($slug));
				$this->db->setQuery($query);
				$this->request->id = $this->db->loadResult();
			}
		}
	}

	public function passPageTypes()
	{
		return $this->passByPageTypes('com_akeebasubs', $this->selection, $this->assignment);
	}

	public function passLevels()
	{
		if ( ! $this->request->id || $this->request->option != 'com_akeebasubs' || $this->request->view != 'level')
		{
			return $this->pass(false);
		}

		return $this->passSimple($this->request->id);
	}
}
regularlabs/helpers/assignments/form2content.php000064400000002062152177723700016210 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsForm2Content extends RLAssignment
{
	public function passProjects()
	{
		if ($this->request->option != 'com_content' && $this->request->view == 'article')
		{
			return $this->pass(false);
		}

		$query = $this->db->getQuery(true)
			->select('c.projectid')
			->from('#__f2c_form AS c')
			->where('c.reference_id = ' . (int) $this->request->id);
		$this->db->setQuery($query);
		$type = $this->db->loadResult();

		$types = $this->makeArray($type);

		return $this->passSimple($types);
	}
}
regularlabs/helpers/assignments/datetime.php000064400000014107152177723700015367 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsDateTime extends RLAssignment
{
	var $timezone = null;
	var $dates    = [];

	public function passDate()
	{
		if ( ! $this->params->publish_up && ! $this->params->publish_down)
		{
			// no date range set
			return ($this->assignment == 'include');
		}

		require_once dirname(__DIR__) . '/text.php';

		RLText::fixDate($this->params->publish_up);
		RLText::fixDate($this->params->publish_down);

		$now  = $this->getNow();
		$up   = $this->getDate($this->params->publish_up);
		$down = $this->getDate($this->params->publish_down);

		if (isset($this->params->recurring) && $this->params->recurring)
		{
			if ( ! (int) $this->params->publish_up || ! (int) $this->params->publish_down)
			{
				// no date range set
				return ($this->assignment == 'include');
			}

			$up   = strtotime(date('Y') . $up->format('-m-d H:i:s', true));
			$down = strtotime(date('Y') . $down->format('-m-d H:i:s', true));

			// pass:
			// 1) now is between up and down
			// 2) up is later in year than down and:
			// 2a) now is after up
			// 2b) now is before down
			if (
				($up < $now && $down > $now)
				|| ($up > $down
					&& (
						$up < $now
						|| $down > $now
					)
				)
			)
			{
				return ($this->assignment == 'include');
			}

			// outside date range
			return $this->pass(false);
		}

		if (
			(
				(int) $this->params->publish_up
				&& strtotime($up->format('Y-m-d H:i:s', true)) > $now
			)
			|| (
				(int) $this->params->publish_down
				&& strtotime($down->format('Y-m-d H:i:s', true)) < $now
			)
		)
		{
			// outside date range
			return $this->pass(false);
		}

		// pass
		return ($this->assignment == 'include');
	}

	public function passSeasons()
	{
		$season = self::getSeason($this->date, $this->params->hemisphere);

		return $this->passSimple($season);
	}

	public function passMonths()
	{
		$month = $this->date->format('m', true); // 01 (for January) through 12 (for December)

		return $this->passSimple((int) $month);
	}

	public function passDays()
	{
		$day = $this->date->format('N', true); // 1 (for Monday) though 7 (for Sunday )

		return $this->passSimple($day);
	}

	public function passTime()
	{
		$now  = $this->getNow();
		$up   = strtotime($this->date->format('Y-m-d ', true) . $this->params->publish_up);
		$down = strtotime($this->date->format('Y-m-d ', true) . $this->params->publish_down);

		if ($up > $down)
		{
			// publish up is after publish down (spans midnight)
			// current time should be:
			// - after publish up
			// - OR before publish down
			if ($now >= $up || $now < $down)
			{
				return $this->pass(true);
			}

			return $this->pass(false);
		}

		// publish down is after publish up (simple time span)
		// current time should be:
		// - after publish up
		// - AND before publish down
		if ($now >= $up && $now < $down)
		{
			return $this->pass(true);
		}

		return $this->pass(false);
	}

	private function getSeason(&$d, $hemisphere = 'northern')
	{
		// Set $date to today
		$date = strtotime($d->format('Y-m-d H:i:s', true));

		// Get year of date specified
		$date_year = $d->format('Y', true); // Four digit representation for the year

		// Specify the season names
		$season_names = ['winter', 'spring', 'summer', 'fall'];

		// Declare season date ranges
		switch (strtolower($hemisphere))
		{
			case 'southern':
				if (
					$date < strtotime($date_year . '-03-21')
					|| $date >= strtotime($date_year . '-12-21')
				)
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-09-23'))
				{
					return $season_names[1]; // Must be in Spring
				}

				if ($date >= strtotime($date_year . '-06-21'))
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-03-21'))
				{
					return $season_names[3]; // Must be in Fall
				}
				break;
			case 'australia':
				if (
					$date < strtotime($date_year . '-03-01')
					|| $date >= strtotime($date_year . '-12-01')
				)
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-09-01'))
				{
					return $season_names[1]; // Must be in Spring
				}

				if ($date >= strtotime($date_year . '-06-01'))
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-03-01'))
				{
					return $season_names[3]; // Must be in Fall
				}
				break;
			default: // northern
				if (
					$date < strtotime($date_year . '-03-21')
					|| $date >= strtotime($date_year . '-12-21')
				)
				{
					return $season_names[0]; // Must be in Winter
				}

				if ($date >= strtotime($date_year . '-09-23'))
				{
					return $season_names[3]; // Must be in Fall
				}

				if ($date >= strtotime($date_year . '-06-21'))
				{
					return $season_names[2]; // Must be in Summer
				}

				if ($date >= strtotime($date_year . '-03-21'))
				{
					return $season_names[1]; // Must be in Spring
				}
				break;
		}

		return 0;
	}

	private function getNow()
	{
		return strtotime($this->date->format('Y-m-d H:i:s', true));
	}

	private function getDate($date = '')
	{
		$id = 'date_' . $date;

		if (isset($this->dates[$id]))
		{
			return $this->dates[$id];
		}

		$this->dates[$id] = JFactory::getDate($date);

		if (empty($this->params->ignore_time_zone))
		{
			$this->dates[$id]->setTimeZone($this->getTimeZone());
		}

		return $this->dates[$id];
	}

	private function getTimeZone()
	{
		if ( ! is_null($this->timezone))
		{
			return $this->timezone;
		}

		$this->timezone = new DateTimeZone(JFactory::getApplication()->getCfg('offset'));

		return $this->timezone;
	}
}
regularlabs/helpers/assignments/zoo.php000064400000013015152177723700014377 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsZoo extends RLAssignment
{
	public function init()
	{
		if ( ! $this->request->view)
		{
			$this->request->view = $this->request->task;
		}

		switch ($this->request->view)
		{
			case 'item':
				$this->request->idname = 'item_id';
				break;
			case 'category':
				$this->request->idname = 'category_id';
				break;
		}

		$this->request->id = JFactory::getApplication()->input->getInt($this->request->idname, 0);
	}

	public function initAssignment($assignment, $article = 0)
	{
		parent::initAssignment($assignment, $article);

		if ($this->request->option != 'com_zoo' && ! isset($this->request->idname))
		{
			return;
		}

		switch ($this->request->idname)
		{
			case 'item_id':
				$this->request->view = 'item';
				break;
			case 'category_id':
				$this->request->view = 'category';
				break;
		}
	}

	public function passPageTypes()
	{
		return $this->passByPageTypes('com_zoo', $this->selection, $this->assignment);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_zoo')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_apps && $this->request->view == 'frontpage')
			|| ($this->params->inc_categories && $this->request->view == 'category')
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = $this->getCategories();

		if ($cats === false)
		{
			return $this->pass(false);
		}

		$cats = $this->makeArray($cats);

		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}

		if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		if ($this->article && isset($this->article->catid))
		{
			return [$this->article->catid];
		}

		$menuparams = $this->getMenuItemParams($this->request->Itemid);

		switch ($this->request->view)
		{
			case 'frontpage':
				if ($this->request->id)
				{
					return [$this->request->id];
				}

				if ( ! isset($menuparams->application))
				{
					return [];
				}

				return ['app' . $menuparams->application];

			case 'category':
				$cats = [];

				if ($this->request->id)
				{
					$cats[] = $this->request->id;
				}
				else if (isset($menuparams->category))
				{
					$cats[] = $menuparams->category;
				}

				if (empty($cats[0]))
				{
					return [];
				}

				$query = $this->db->getQuery(true)
					->select('c.application_id')
					->from('#__zoo_category AS c')
					->where('c.id = ' . (int) $cats[0]);
				$this->db->setQuery($query);
				$cats[] = 'app' . $this->db->loadResult();

				return $cats;

			case 'item':
				$id = $this->request->id;

				if ( ! $id && isset($menuparams->item_id))
				{
					$id = $menuparams->item_id;
				}

				if ( ! $id)
				{
					return [];
				}

				$query = $this->db->getQuery(true)
					->select('c.category_id')
					->from('#__zoo_category_item AS c')
					->where('c.item_id = ' . (int) $id)
					->where('c.category_id != 0');
				$this->db->setQuery($query);
				$cats = $this->db->loadColumn();

				$query = $this->db->getQuery(true)
					->select('i.application_id')
					->from('#__zoo_item AS i')
					->where('i.id = ' . (int) $id);
				$this->db->setQuery($query);
				$cats[] = 'app' . $this->db->loadResult();

				return $cats;

			default:
				return false;
		}
	}

	public function passItems()
	{
		if ( ! $this->request->id || $this->request->option != 'com_zoo')
		{
			return $this->pass(false);
		}

		if ($this->request->view != 'item')
		{
			return $this->pass(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentIds'))
		{
			return $this->pass(false);
		}

		// Pass Authors
		if ( ! $this->passItemByType($pass, 'Authors'))
		{
			return $this->pass(false);
		}

		return $this->pass($pass);
	}

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__zoo_item')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

	private function getCatParentIds($id = 0)
	{
		$parent_ids = [];

		if ( ! $id)
		{
			return $parent_ids;
		}

		while ($id)
		{
			if (substr($id, 0, 3) == 'app')
			{
				$parent_ids[] = $id;
				break;
			}

			$query = $this->db->getQuery(true)
				->select('c.parent')
				->from('#__zoo_category AS c')
				->where('c.id = ' . (int) $id);
			$this->db->setQuery($query);
			$pid = $this->db->loadResult();

			if ( ! $pid)
			{
				$query = $this->db->getQuery(true)
					->select('c.application_id')
					->from('#__zoo_category AS c')
					->where('c.id = ' . (int) $id);
				$this->db->setQuery($query);
				$app = $this->db->loadResult();

				if ($app)
				{
					$parent_ids[] = 'app' . $app;
				}

				break;
			}

			$parent_ids[] = $pid;

			$id = $pid;
		}

		return $parent_ids;
	}
}
regularlabs/helpers/assignments/k2.php000064400000010700152177723700014102 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

// If controller.php exists, assume this is K2 v3
defined('RL_K2_VERSION') or define('RL_K2_VERSION', JFile::exists(JPATH_ADMINISTRATOR . '/components/com_k2/controller.php') ? 3 : 2);

class RLAssignmentsK2 extends RLAssignment
{
	public function passPageTypes()
	{
		return $this->passByPageTypes('com_k2', $this->selection, $this->assignment, false, true);
	}

	public function passCategories()
	{
		if ($this->request->option != 'com_k2')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_categories
				&& (($this->request->view == 'itemlist' && $this->request->task == 'category')
					|| $this->request->view == 'latest'
				)
			)
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$cats = $this->makeArray($this->getCategories());
		$pass = $this->passSimple($cats, 'include');

		if ($pass && $this->params->inc_children == 2)
		{
			return $this->pass(false);
		}
		else if ( ! $pass && $this->params->inc_children)
		{
			foreach ($cats as $cat)
			{
				$cats = array_merge($cats, $this->getCatParentIds($cat));
			}
		}

		return $this->passSimple($cats);
	}

	private function getCategories()
	{
		switch ($this->request->view)
		{
			case 'item' :
				return $this->getCategoryIDFromItem();
				break;

			case 'itemlist' :
				return $this->getCategoryID();
				break;

			default:
				return '';
		}
	}

	private function getCategoryID()
	{
		return $this->request->id ?: JFactory::getApplication()->getUserStateFromRequest('com_k2itemsfilter_category', 'catid', 0, 'int');
	}

	private function getCategoryIDFromItem()
	{
		if ($this->article && isset($this->article->catid))
		{
			return $this->article->catid;
		}

		$query = $this->db->getQuery(true)
			->select('i.catid')
			->from('#__k2_items AS i')
			->where('i.id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadResult();
	}

	public function passTags()
	{
		if ($this->request->option != 'com_k2')
		{
			return $this->pass(false);
		}

		$tag  = trim(JFactory::getApplication()->input->getString('tag', ''));
		$pass = (
			($this->params->inc_tags && $tag != '')
			|| ($this->params->inc_items && $this->request->view == 'item')
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		if ($this->params->inc_tags && $tag != '')
		{
			$tags = [trim(JFactory::getApplication()->input->getString('tag', ''))];

			return $this->passSimple($tags, true);
		}

		$query = $this->db->getQuery(true)
			->select('t.name')
			->from('#__k2_tags_xref AS x')
			->join('LEFT', '#__k2_tags AS t ON t.id = x.tagID')
			->where('x.itemID = ' . (int) $this->request->id)
			->where('t.published = 1');
		$this->db->setQuery($query);
		$tags = $this->db->loadColumn();

		return $this->passSimple($tags, true);
	}

	public function passItems()
	{
		if ( ! $this->request->id || $this->request->option != 'com_k2' || $this->request->view != 'item')
		{
			return $this->pass(false);
		}

		$pass = false;

		// Pass Article Id
		if ( ! $this->passItemByType($pass, 'ContentIds'))
		{
			return $this->pass(false);
		}

		// Pass Content Keywords
		if ( ! $this->passItemByType($pass, 'ContentKeywords'))
		{
			return $this->pass(false);
		}

		// Pass Meta Keywords
		if ( ! $this->passItemByType($pass, 'MetaKeywords'))
		{
			return $this->pass(false);
		}

		// Pass Authors
		if ( ! $this->passItemByType($pass, 'Authors'))
		{
			return $this->pass(false);
		}

		return $this->pass($pass);
	}

	public function getItem($fields = [])
	{
		$query = $this->db->getQuery(true)
			->select($fields)
			->from('#__k2_items')
			->where('id = ' . (int) $this->request->id);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

	private function getCatParentIds($id = 0)
	{
		$parent_field = RL_K2_VERSION == 3 ? 'parent_id' : 'parent';

		return $this->getParentIds($id, 'k2_categories', $parent_field);
	}
}
regularlabs/helpers/assignments/cookieconfirm.php000064400000001477152177723700016430 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsCookieConfirm extends RLAssignment
{
	public function passCookieConfirm()
	{
		require_once JPATH_PLUGINS . '/system/cookieconfirm/core.php';
		$pass = PlgSystemCookieconfirmCore::getInstance()->isCookiesAllowed();

		return $this->pass($pass);
	}
}
regularlabs/helpers/assignments/php.php000064400000005445152177723700014367 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;
use Joomla\CMS\MVC\Model\BaseDatabaseModel as JModel;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsPHP extends RLAssignment
{
	public function passPHP()
	{
		$article = $this->article;

		if ( ! is_array($this->selection))
		{
			$this->selection = [$this->selection];
		}

		$pass = false;
		foreach ($this->selection as $php)
		{
			// replace \n with newline and other fix stuff
			$php = str_replace('\|', '|', $php);
			$php = preg_replace('#(?<!\\\)\\\n#', "\n", $php);
			$php = trim(str_replace('[:REGEX_ENTER:]', '\n', $php));

			if ($php == '')
			{
				$pass = true;
				break;
			}

			if ( ! $article && strpos($php, '$article') !== false)
			{
				$article = null;
				if ($this->request->option == 'com_content' && $this->request->view == 'article')
				{
					$article = $this->getArticleById($this->request->id);
				}
			}
			if ( ! isset($Itemid))
			{
				$Itemid = JFactory::getApplication()->input->getInt('Itemid', 0);
			}
			if ( ! isset($mainframe))
			{
				$mainframe = JFactory::getApplication();
			}
			if ( ! isset($app))
			{
				$app = JFactory::getApplication();
			}
			if ( ! isset($document))
			{
				$document = JFactory::getDocument();
			}
			if ( ! isset($doc))
			{
				$doc = JFactory::getDocument();
			}
			if ( ! isset($database))
			{
				$database = JFactory::getDbo();
			}
			if ( ! isset($db))
			{
				$db = JFactory::getDbo();
			}
			if ( ! isset($user))
			{
				$user = JFactory::getUser();
			}
			$php .= ';return true;';

			$temp_PHP_func = create_function('&$article, &$Itemid, &$mainframe, &$app, &$document, &$doc, &$database, &$db, &$user', $php);

			// evaluate the script
			ob_start();
			$pass = (bool) $temp_PHP_func($article, $Itemid, $mainframe, $app, $document, $doc, $database, $db, $user);
			unset($temp_PHP_func);
			ob_end_clean();

			if ($pass)
			{
				break;
			}
		}

		return $this->pass($pass);
	}

	private function getArticleById($id = 0)
	{
		if ( ! $id)
		{
			return null;
		}

		if ( ! class_exists('ContentModelArticle'))
		{
			require_once JPATH_SITE . '/components/com_content/models/article.php';
		}

		$model = JModel::getInstance('article', 'contentModel');

		if ( ! method_exists($model, 'getItem'))
		{
			return null;
		}

		return $model->getItem($this->request->id);
	}
}
regularlabs/helpers/assignments/flexicontent.php000064400000004622152177723700016276 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use Joomla\CMS\Factory as JFactory;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

require_once dirname(__DIR__) . '/assignment.php';

class RLAssignmentsFlexiContent extends RLAssignment
{
	public function passPageTypes()
	{
		return $this->passByPageTypes('com_flexicontent', $this->selection, $this->assignment);
	}

	public function passTags()
	{
		if ($this->request->option != 'com_flexicontent')
		{
			return $this->pass(false);
		}

		$pass = (
			($this->params->inc_tags && $this->request->view == 'tags')
			|| ($this->params->inc_items && in_array($this->request->view, ['item', 'items']))
		);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		if ($this->params->inc_tags && $this->request->view == 'tags')
		{
			$query = $this->db->getQuery(true)
				->select('t.name')
				->from('#__flexicontent_tags AS t')
				->where('t.id = ' . (int) trim(JFactory::getApplication()->input->getInt('id', 0)))
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tag  = $this->db->loadResult();
			$tags = [$tag];
		}
		else
		{
			$query = $this->db->getQuery(true)
				->select('t.name')
				->from('#__flexicontent_tags_item_relations AS x')
				->join('LEFT', '#__flexicontent_tags AS t ON t.id = x.tid')
				->where('x.itemid = ' . (int) $this->request->id)
				->where('t.published = 1');
			$this->db->setQuery($query);
			$tags = $this->db->loadColumn();
		}

		return $this->passSimple($tags, true);
	}

	public function passTypes()
	{
		if ($this->request->option != 'com_flexicontent')
		{
			return $this->pass(false);
		}

		$pass = in_array($this->request->view, ['item', 'items']);

		if ( ! $pass)
		{
			return $this->pass(false);
		}

		$query = $this->db->getQuery(true)
			->select('x.type_id')
			->from('#__flexicontent_items_ext AS x')
			->where('x.item_id = ' . (int) $this->request->id);
		$this->db->setQuery($query);
		$type = $this->db->loadResult();

		$types = $this->makeArray($type);

		return $this->passSimple($types);
	}
}
regularlabs/helpers/string.php000064400000000716152177723700012547 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

jimport('joomla.string.string');

abstract class RLString extends JString
{
}
regularlabs/helpers/htmlfix.php000064400000001205152177723700012706 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Html as RL_Html;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLHtmlFix
{
	public static function _($string)
	{
		return RL_Html::fix($string);
	}
}
regularlabs/helpers/mobile_detect.php000064400000001232152177723700014032 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLMobile_Detect extends \RegularLabs\Library\MobileDetect
{
	public function isMac()
	{
		return $this->match('(Mac OS|Mac_PowerPC|Macintosh)');
	}
}
regularlabs/helpers/protect.php000064400000014604152177723700012722 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Protect as RL_Protect;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLProtect
{
	public static function isProtectedPage($extension_alias = '', $hastags = false, $exclude_formats = ['pdf'])
	{
		if ( ! class_exists('RegularLabs\Library\Protect'))
		{
			return true;
		}

		if (RL_Protect::isDisabledByUrl($extension_alias))
		{
			return true;
		}

		return class_exists('RegularLabs\Library\Protect') && RL_Protect::isRestrictedPage($hastags, $exclude_formats);
	}

	public static function isAdmin($block_login = false)
	{
		return class_exists('RegularLabs\Library\Document') && RL_Document::isAdmin($block_login);
	}

	public static function isEditPage()
	{
		return class_exists('RegularLabs\Library\Document') && RL_Document::isEditPage();
	}

	public static function isRestrictedComponent($restricted_components, $area = 'component')
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::isRestrictedComponent($restricted_components, $area);
	}

	public static function isComponentInstalled($extension_alias)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::isComponentInstalled($extension_alias);
	}

	public static function isSystemPluginInstalled($extension_alias)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::isSystemPluginInstalled($extension_alias);
	}

	public static function getFormRegex($regex_format = false)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::getFormRegex($regex_format);
	}

	public static function protectFields(&$string, $search_strings = [])
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectFields($string, $search_strings);
	}

	public static function protectScripts(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectScripts($string);
	}

	public static function protectHtmlTags(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectHtmlTags($string);
	}

	public static function protectByRegex(&$string, $regex)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectByRegex($string, $regex);
	}

	public static function protectTags(&$string, $tags = [], $include_closing_tags = true)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectTags($string, $tags, $include_closing_tags);
	}

	public static function unprotectTags(&$string, $tags = [], $include_closing_tags = true)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectTags($string, $tags, $include_closing_tags);
	}

	public static function protectInString(&$string, $unprotected = [], $protected = [])
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectInString($string, $unprotected, $protected);
	}

	public static function unprotectInString(&$string, $unprotected = [], $protected = [])
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectInString($string, $unprotected, $protected);
	}

	public static function protectSourcerer(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectSourcerer($string);
	}

	public static function protectForm(&$string, $tags = [], $include_closing_tags = true)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::protectForm($string, $tags, $include_closing_tags);
	}

	public static function unprotect(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotect($string);
	}

	public static function convertProtectionToHtmlSafe(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::convertProtectionToHtmlSafe($string);
	}

	public static function unprotectHtmlSafe(&$string)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectHtmlSafe($string);
	}

	public static function protectString($string, $is_tag = false)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::protectString($string, $is_tag);
	}

	public static function unprotectString($string, $is_tag = false)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectString($string, $is_tag);
	}

	public static function protectTag($string)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::protectTag($string);
	}

	public static function protectArray($array, $is_tag = false)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::protectArray($array, $is_tag);
	}

	public static function unprotectArray($array, $is_tag = false)
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectArray($array, $is_tag);
	}

	public static function unprotectForm(&$string, $tags = [])
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::unprotectForm($string, $tags);
	}

	public static function removeInlineComments(&$string, $name)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::removeInlineComments($string, $name);
	}

	public static function removePluginTags(&$string, $tags, $character_start = '{', $character_end = '{', $keep_content = true)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::removePluginTags($string, $tags, $character_start, $character_end, $keep_content);
	}

	public static function removeFromHtmlTagContent(&$string, $tags, $include_closing_tags = true, $html_tags = ['title'])
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::removeFromHtmlTagContent($string, $tags, $include_closing_tags, $html_tags);
	}

	public static function removeFromHtmlTagAttributes(&$string, $tags, $attributes = 'ALL', $include_closing_tags = true)
	{
		class_exists('RegularLabs\Library\Protect') && RL_Protect::removeFromHtmlTagAttributes($string, $tags, $attributes, $include_closing_tags);
	}

	public static function articlePassesSecurity(&$article, $securtiy_levels = [])
	{
		return class_exists('RegularLabs\Library\Protect') && RL_Protect::articlePassesSecurity($article, $securtiy_levels);
	}

	public static function isJoomla3()
	{
		return true;
	}
}
regularlabs/helpers/helper.php000064400000003406152177723700012517 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

defined('_JEXEC') or die;

use RegularLabs\Library\Article as RL_Article;
use RegularLabs\Library\Cache as RL_Cache;
use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Parameters as RL_Parameters;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLHelper
{
	public static function getPluginHelper($plugin, $params = null)
	{
		if ( ! class_exists('RegularLabs\Library\Cache'))
		{
			return null;
		}

		$hash = md5('getPluginHelper_' . $plugin->get('_type') . '_' . $plugin->get('_name') . '_' . json_encode($params));

		if (RL_Cache::has($hash))
		{
			return RL_Cache::get($hash);
		}

		if ( ! $params)
		{
			$params = RL_Parameters::getInstance()->getPluginParams($plugin->get('_name'));
		}

		$file = JPATH_PLUGINS . '/' . $plugin->get('_type') . '/' . $plugin->get('_name') . '/helper.php';

		if ( ! is_file($file))
		{
			return null;
		}

		require_once $file;
		$class = get_class($plugin) . 'Helper';

		return RL_Cache::set(
			$hash,
			new $class($params)
		);
	}

	public static function processArticle(&$article, &$context, &$helper, $method, $params = [])
	{
		class_exists('RegularLabs\Library\Article') && RL_Article::process($article, $context, $helper, $method, $params);
	}

	public static function isCategoryList($context)
	{
		return class_exists('RegularLabs\Library\Document') && RL_Document::isCategoryList($context);
	}
}
regularlabs/helpers/html.php000064400000001656152177723700012211 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Form as RL_Form;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLHtml
{
	static function selectlist(&$options, $name, $value, $id, $size = 0, $multiple = 0, $simple = 0)
	{
		return RL_Form::selectList($options, $name, $value, $id, $size, $multiple, $simple);
	}

	static function selectlistsimple(&$options, $name, $value, $id, $size = 0, $multiple = 0)
	{
		return RL_Form::selectListSimple($options, $name, $value, $id, $size, $multiple);
	}
}
regularlabs/helpers/tags.php000064400000012022152177723700012170 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Html as RL_Html;
use RegularLabs\Library\PluginTag as RL_PluginTag;
use RegularLabs\Library\RegEx as RL_RegEx;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLTags
{
	static $protected_characters = [
		'=' => '[[:EQUAL:]]',
		'"' => '[[:QUOTE:]]',
		',' => '[[:COMMA:]]',
		'|' => '[[:BAR:]]',
		':' => '[[:COLON:]]',
	];

	public static function getValuesFromString($string = '', $main_key = 'title', $known_boolean_keys = [], $keep_escaped = [','])
	{
		return RL_PluginTag::getAttributesFromString($string, $main_key, $known_boolean_keys, $keep_escaped);
	}

	public static function protectSpecialChars(&$string)
	{
		RL_PluginTag::protectSpecialChars($string);
	}

	public static function unprotectSpecialChars(&$string, $keep_escaped_chars = [])
	{
		RL_PluginTag::unprotectSpecialChars($string, $keep_escaped_chars);
	}

	public static function replaceKeyAliases(&$values, $key_aliases = [], $handle_plurals = false)
	{
		RL_PluginTag::replaceKeyAliases($values, $key_aliases, $handle_plurals);
	}

	public static function convertOldSyntax(&$values, $known_boolean_keys = [], $extra_key = 'class')
	{
		RL_PluginTag::convertOldSyntax($values, $known_boolean_keys, $extra_key);
	}

	public static function getRegexSpaces($modifier = '+')
	{
		return RL_PluginTag::getRegexSpaces($modifier);
	}

	public static function getRegexInsideTag()
	{
		return RL_PluginTag::getRegexInsideTag();
	}

	public static function getRegexSurroundingTagPre($elements = ['p', 'span'])
	{
		return RL_PluginTag::getRegexSurroundingTagPre($elements);
	}

	public static function getRegexSurroundingTagPost($elements = ['p', 'span'])
	{
		return RL_PluginTag::getRegexSurroundingTagPost($elements);
	}

	public static function getRegexTags($tags, $include_no_attributes = true, $include_ending = true, $required_attributes = [])
	{
		return RL_PluginTag::getRegexTags($tags, $include_no_attributes, $include_ending, $required_attributes);
	}

	public static function fixBrokenHtmlTags($string)
	{
		return RL_Html::fix($string);
	}

	public static function cleanSurroundingTags($tags, $elements = ['p', 'span'])
	{
		return RL_Html::cleanSurroundingTags($tags, $elements);
	}

	public static function fixSurroundingTags($tags)
	{
		return RL_Html::fixArray($tags);
	}

	public static function removeEmptyHtmlTagPairs($string, $elements = ['p', 'span'])
	{
		return RL_Html::removeEmptyTagPairs($string, $elements);
	}

	public static function getDivTags($start_tag = '', $end_tag = '', $tag_start = '{', $tag_end = '}')
	{
		$tag_start = RL_RegEx::unquote($tag_start);
		$tag_end   = RL_RegEx::unquote($tag_end);

		return RL_PluginTag::getDivTags($start_tag, $end_tag, $tag_start, $tag_end);
	}

	public static function getTagValues($string = '', $keys = ['title'], $separator = '|', $equal = '=', $limit = 0)
	{
		return RL_PluginTag::getAttributesFromStringOld($string, $keys, $separator, $equal, $limit);
	}

	/* @Deprecated */

	public static function setSurroundingTags($pre, $post, $tags = 0)
	{
		if ($tags == 0)
		{
			// tags that have a matching ending tag
			$tags = [
				'div', 'p', 'span', 'pre', 'a',
				'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
				'strong', 'b', 'em', 'i', 'u', 'big', 'small', 'font',
				// html 5 stuff
				'header', 'nav', 'section', 'article', 'aside', 'footer',
				'figure', 'figcaption', 'details', 'summary', 'mark', 'time',
			];
		}

		$a = explode('<', $pre);
		$b = explode('</', $post);

		if (count($b) < 2 || count($a) < 2)
		{
			return [trim($pre), trim($post)];
		}

		$a      = array_reverse($a);
		$a_pre  = array_pop($a);
		$b_pre  = array_shift($b);
		$a_tags = $a;

		foreach ($a_tags as $i => $a_tag)
		{
			$a[$i]      = '<' . trim($a_tag);
			$a_tags[$i] = RL_RegEx::replace('^([a-z0-9]+).*$', '\1', trim($a_tag));
		}

		$b_tags = $b;

		foreach ($b_tags as $i => $b_tag)
		{
			$b[$i]      = '</' . trim($b_tag);
			$b_tags[$i] = RL_RegEx::replace('^([a-z0-9]+).*$', '\1', trim($b_tag));
		}

		foreach ($b_tags as $i => $b_tag)
		{
			if (empty($b_tag) || ! in_array($b_tag, $tags))
			{
				continue;
			}

			foreach ($a_tags as $j => $a_tag)
			{
				if ($b_tag != $a_tag)
				{
					continue;
				}

				$a_tags[$i] = '';
				$b[$i]      = trim(RL_RegEx::replace('^</' . $b_tag . '.*?>', '', $b[$i]));
				$a[$j]      = trim(RL_RegEx::replace('^<' . $a_tag . '.*?>', '', $a[$j]));
				break;
			}
		}

		foreach ($a_tags as $i => $tag)
		{
			if (empty($tag) || ! in_array($tag, $tags))
			{
				continue;
			}

			array_unshift($b, trim($a[$i]));
			$a[$i] = '';
		}

		$a = array_reverse($a);
		list($pre, $post) = [implode('', $a), implode('', $b)];

		return [trim($pre), trim($post)];
	}
}
regularlabs/helpers/assignments.php000064400000002132152177723700013566 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Conditions as RL_Conditions;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLAssignmentsHelper
{
	function passAll($assignments, $matching_method = 'all', $item = 0)
	{
		return RL_Conditions::pass($assignments, $matching_method, $item);
	}

	public function getAssignmentsFromParams(&$params)
	{
		return RL_Conditions::getConditionsFromParams($params);
	}

	public function getAssignmentsFromTagAttributes(&$params, $types = [])
	{
		return RL_Conditions::getConditionsFromTagAttributes($params, $types);
	}

	public function hasAssignments(&$assignments)
	{
		return RL_Conditions::hasConditions($assignments);
	}
}
regularlabs/helpers/cache.php000064400000001734152177723700012305 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Cache as RL_Cache;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLCache
{
	static $cache = [];

	public static function has($id)
	{
		return RL_Cache::has($id);
	}

	public static function get($id)
	{
		return RL_Cache::get($id);
	}

	public static function set($id, $data)
	{
		return RL_Cache::set($id, $data);
	}

	public static function read($id)
	{
		return RL_Cache::read($id);
	}

	public static function write($id, $data, $ttl = 0)
	{
		return RL_Cache::write($id, $data, $ttl);
	}
}
regularlabs/helpers/text.php000064400000011417152177723700012225 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Alias as RL_Alias;
use RegularLabs\Library\ArrayHelper as RL_Array;
use RegularLabs\Library\Date as RL_Date;
use RegularLabs\Library\Form as RL_Form;
use RegularLabs\Library\Html as RL_Html;
use RegularLabs\Library\HtmlTag as RL_HtmlTag;
use RegularLabs\Library\PluginTag as RL_PluginTag;
use RegularLabs\Library\RegEx as RL_RegEx;
use RegularLabs\Library\StringHelper as RL_String;
use RegularLabs\Library\Title as RL_Title;
use RegularLabs\Library\Uri as RL_Uri;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLText
{
	/* Date functions */

	public static function fixDate(&$date)
	{
		$date = RL_Date::fix($date);
	}

	public static function fixDateOffset(&$date)
	{
		RL_Date::applyTimezone($date);
	}

	public static function dateToDateFormat($dateFormat)
	{
		return RL_Date::strftimeToDateFormat($dateFormat);
	}

	public static function dateToStrftimeFormat($dateFormat)
	{
		return RL_Date::dateToStrftimeFormat($dateFormat);
	}

	/* String functions */

	public static function html_entity_decoder($string, $quote_style = ENT_QUOTES, $charset = 'UTF-8')
	{
		return RL_String::html_entity_decoder($string, $quote_style, $charset);
	}

	public static function stringContains($haystacks, $needles)
	{
		return RL_String::contains($haystacks, $needles);
	}

	public static function is_alphanumeric($string)
	{
		return RL_String::is_alphanumeric($string);
	}

	public static function splitString($string, $delimiters = [], $max_length = 10000, $maximize_parts = true)
	{
		return RL_String::split($string, $delimiters, $max_length, $maximize_parts);
	}

	public static function strReplaceOnce($search, $replace, $string)
	{
		return RL_String::replaceOnce($search, $replace, $string);
	}

	/* Array functions */

	public static function toArray($data, $separator = '')
	{
		return RL_Array::toArray($data, $separator);
	}

	public static function createArray($data, $separator = ',')
	{
		return RL_Array::toArray($data, $separator, true);
	}

	/* RegEx functions */

	public static function regexReplace($pattern, $replacement, $string)
	{
		return RL_RegEx::replace($pattern, $replacement, $string);
	}

	public static function pregQuote($string = '', $delimiter = '#')
	{
		return RL_RegEx::quote($string, $delimiter);
	}

	public static function pregQuoteArray($array = [], $delimiter = '#')
	{
		return RL_RegEx::quoteArray($array, $delimiter);
	}

	/* Title functions */

	public static function cleanTitle($string, $strip_tags = false, $strip_spaces = true)
	{
		return RL_Title::clean($string, $strip_tags, $strip_spaces);
	}

	public static function createUrlMatches($titles = [])
	{
		return RL_Title::getUrlMatches($titles);
	}

	/* Alias functions */

	public static function createAlias($string)
	{
		return RL_Alias::get($string);
	}

	/* Uri functions */

	public static function getURI($hash = '')
	{
		return RL_Uri::get($hash);
	}

	/* Plugin Tag functions */

	public static function getTagRegex($tags, $include_no_attributes = true, $include_ending = true, $required_attributes = [])
	{
		return RL_PluginTag::getRegexTags($tags, $include_no_attributes, $include_ending, $required_attributes);
	}

	/* HTML functions */
	public static function getBody($html)
	{
		return RL_Html::getBody($html);
	}

	public static function getContentContainingSearches($string, $start_searches = [], $end_searches = [], $start_offset = 1000, $end_offset = null)
	{
		return RL_Html::getContentContainingSearches($string, $start_searches, $end_searches, $start_offset, $end_offset);
	}

	public static function convertWysiwygToPlainText($string)
	{
		return RL_Html::convertWysiwygToPlainText($string);
	}

	public static function combinePTags(&$string)
	{
		RL_Html::combinePTags($string);
	}

	/* HTML Tag functions */

	public static function combineTags($tag1, $tag2)
	{
		return RL_HtmlTag::combine($tag1, $tag2);
	}

	public static function getAttribute($key, $string)
	{
		return RL_HtmlTag::getAttributeValue($key, $string);
	}

	public static function getAttributes($string)
	{
		return RL_HtmlTag::getAttributes($string);
	}

	public static function combineAttributes($string1, $string2)
	{
		return RL_HtmlTag::combineAttributes($string1, $string2);
	}

	/* Form functions */

	public static function prepareSelectItem($string, $published = 1, $type = '', $remove_first = 0)
	{
		return RL_Form::prepareSelectItem($string, $published, $type, $remove_first);
	}
}
regularlabs/helpers/assignment.php000064400000002561152177723700013411 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLAssignment
	extends \RegularLabs\Library\Condition
{
	function pass($pass = true, $include_type = null)
	{
		return $this->_($pass, $include_type);
	}

	public function passByPageTypes($option, $selection = [], $assignment = 'all', $add_view = false, $get_task = false, $get_layout = true)
	{
		return $this->passByPageType($option, $selection, $assignment, $add_view, $get_task, $get_layout);
	}

	public function passContentIds()
	{
		return $this->passContentId();
	}

	public function passContentKeywords($fields = ['title', 'introtext', 'fulltext'], $text = '')
	{
		return $this->passContentKeyword($fields, $text);
	}

	public function passMetaKeywords($field = 'metakey', $keywords = '')
	{
		return $this->passMetaKeyword($field, $keywords);
	}

	public function passAuthors($field = 'created_by', $author = '')
	{
		return $this->passAuthors($field, $author);
	}
}
regularlabs/helpers/functions.php000064400000010566152177723700013255 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

use RegularLabs\Library\Document as RL_Document;
use RegularLabs\Library\Extension as RL_Extension;
use RegularLabs\Library\File as RL_File;
use RegularLabs\Library\Http as RL_Http;
use RegularLabs\Library\Language as RL_Language;
use RegularLabs\Library\Xml as RL_Xml;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

/**
 * Framework Functions
 */
class RLFunctions
{
	public static function getContents($url, $timeout = 20)
	{
		return ! class_exists('RegularLabs\Library\Http') ? '' : RL_Http::get($url, $timeout);
	}

	public static function getByUrl($url, $timeout = 20)
	{
		return ! class_exists('RegularLabs\Library\Http') ? '' : RL_Http::getFromServer($url, $timeout);
	}

	public static function isFeed()
	{
		return class_exists('RegularLabs\Library\Document') && RL_Document::isFeed();
	}

	public static function script($file, $version = '')
	{
		class_exists('RegularLabs\Library\Document') && RL_Document::script($file, $version);
	}

	public static function stylesheet($file, $version = '')
	{
		class_exists('RegularLabs\Library\Document') && RL_Document::stylesheet($file, $version);
	}

	public static function addScriptVersion($url)
	{
		jimport('joomla.filesystem.file');

		$version = '';

		if (JFile::exists(JPATH_SITE . $url))
		{
			$version = filemtime(JPATH_SITE . $url);
		}

		self::script($url, $version);
	}

	public static function addStyleSheetVersion($url)
	{
		jimport('joomla.filesystem.file');

		$version = '';

		if (JFile::exists(JPATH_SITE . $url))
		{
			$version = filemtime(JPATH_SITE . $url);
		}

		self::stylesheet($url, $version);
	}

	protected static function getFileByFolder($folder, $file)
	{
		return ! class_exists('RegularLabs\Library\File') ? '' : RL_File::getMediaFile($folder, $file);
	}

	public static function getComponentBuffer()
	{
		return ! class_exists('RegularLabs\Library\Document') ? '' : RL_Document::getBuffer();
	}

	public static function getAliasAndElement(&$name)
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getAliasAndElement($name);
	}

	public static function getNameByAlias($alias)
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getNameByAlias($alias);
	}

	public static function getAliasByName($name)
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getAliasByName($name);
	}

	public static function getElementByAlias($alias)
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getElementByAlias($alias);
	}

	public static function getXMLValue($key, $alias, $type = 'component', $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getXMLValue($key, $alias, $type, $folder);
	}

	public static function getXML($alias, $type = 'component', $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getXML($alias, $type, $folder);
	}

	public static function getXMLFile($alias, $type = 'component', $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getXMLFile($alias, $type, $folder);
	}

	public static function extensionInstalled($extension, $type = 'component', $folder = 'system')
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::isInstalled($extension, $type, $folder);
	}

	public static function getExtensionPath($extension = 'plg_system_regularlabs', $basePath = JPATH_ADMINISTRATOR, $check_folder = '')
	{
		return ! class_exists('RegularLabs\Library\Extension') ? '' : RL_Extension::getPath($extension, $basePath, $check_folder);
	}

	public static function loadLanguage($extension = 'plg_system_regularlabs', $basePath = '', $reload = false)
	{
		return class_exists('RegularLabs\Library\Language') && RL_Language::load($extension, $basePath, $reload);
	}

	public static function xmlToObject($url, $root = '')
	{
		return ! class_exists('RegularLabs\Library\Xml') ? '' : RL_Xml::toObject($url, $root);
	}
}
regularlabs/helpers/groupfield.php000064400000001102152177723700013367 0ustar00<?php
/**
 * @package         Regular Labs Library
 * @version         19.7.21312
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://www.regularlabs.com
 * @copyright       Copyright © 2019 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

/* @DEPRECATED */

defined('_JEXEC') or die;

if (is_file(JPATH_LIBRARIES . '/regularlabs/autoload.php'))
{
	require_once JPATH_LIBRARIES . '/regularlabs/autoload.php';
}

class RLFormGroupField
	extends \RegularLabs\Library\FieldGroup
{
}
regularlabs/vendor/autoload.php000064400000000262152177723700012700 0ustar00<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInitf9099d81d2e2cf4863a68cf73354cfc1::getLoader();
regularlabs/vendor/composer/LICENSE000064400000002063152177723700013214 0ustar00
Copyright (c) 2016 Nils Adermann, Jordi Boggiano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

regularlabs/vendor/composer/autoload_static.php000064400000001316152177723700016077 0ustar00<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInitf9099d81d2e2cf4863a68cf73354cfc1
{
	public static $prefixLengthsPsr4 = [
		'R' =>
			[
				'RegularLabs\\Library\\' => 20,
			],
	];

	public static $prefixDirsPsr4 = [
		'RegularLabs\\Library\\' =>
			[
				0 => __DIR__ . '/../..' . '/src',
			],
	];

	public static function getInitializer(ClassLoader $loader)
	{
		return \Closure::bind(function () use ($loader) {
			$loader->prefixLengthsPsr4 = ComposerStaticInitf9099d81d2e2cf4863a68cf73354cfc1::$prefixLengthsPsr4;
			$loader->prefixDirsPsr4    = ComposerStaticInitf9099d81d2e2cf4863a68cf73354cfc1::$prefixDirsPsr4;
		}, null, ClassLoader::class);
	}
}
regularlabs/vendor/composer/autoload_real.php000064400000002761152177723700015540 0ustar00<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInitf9099d81d2e2cf4863a68cf73354cfc1
{
	private static $loader;

	public static function loadClassLoader($class)
	{
		if ('Composer\Autoload\ClassLoader' === $class)
		{
			require __DIR__ . '/ClassLoader.php';
		}
	}

	public static function getLoader()
	{
		if (null !== self::$loader)
		{
			return self::$loader;
		}

		spl_autoload_register(['ComposerAutoloaderInitf9099d81d2e2cf4863a68cf73354cfc1', 'loadClassLoader'], true, true);
		self::$loader = $loader = new \Composer\Autoload\ClassLoader;
		spl_autoload_unregister(['ComposerAutoloaderInitf9099d81d2e2cf4863a68cf73354cfc1', 'loadClassLoader']);

		$useStaticLoader = PHP_VERSION_ID >= 50600 && ! defined('HHVM_VERSION') && ( ! function_exists('zend_loader_file_encoded') || ! zend_loader_file_encoded());
		if ($useStaticLoader)
		{
			require_once __DIR__ . '/autoload_static.php';

			call_user_func(\Composer\Autoload\ComposerStaticInitf9099d81d2e2cf4863a68cf73354cfc1::getInitializer($loader));
		}
		else
		{
			$map = require __DIR__ . '/autoload_namespaces.php';
			foreach ($map as $namespace => $path)
			{
				$loader->set($namespace, $path);
			}

			$map = require __DIR__ . '/autoload_psr4.php';
			foreach ($map as $namespace => $path)
			{
				$loader->setPsr4($namespace, $path);
			}

			$classMap = require __DIR__ . '/autoload_classmap.php';
			if ($classMap)
			{
				$loader->addClassMap($classMap);
			}
		}

		$loader->register(true);

		return $loader;
	}
}
regularlabs/vendor/composer/autoload_namespaces.php000064400000000221152177723700016721 0ustar00<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir   = dirname($vendorDir);

return [];
regularlabs/vendor/composer/ClassLoader.php000064400000026064152177723700015123 0ustar00<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
	// PSR-4
	private $prefixLengthsPsr4 = [];
	private $prefixDirsPsr4    = [];
	private $fallbackDirsPsr4  = [];

	// PSR-0
	private $prefixesPsr0     = [];
	private $fallbackDirsPsr0 = [];

	private $useIncludePath        = false;
	private $classMap              = [];
	private $classMapAuthoritative = false;
	private $missingClasses        = [];
	private $apcuPrefix;

	public function getPrefixes()
	{
		if ( ! empty($this->prefixesPsr0))
		{
			return call_user_func_array('array_merge', $this->prefixesPsr0);
		}

		return [];
	}

	public function getPrefixesPsr4()
	{
		return $this->prefixDirsPsr4;
	}

	public function getFallbackDirs()
	{
		return $this->fallbackDirsPsr0;
	}

	public function getFallbackDirsPsr4()
	{
		return $this->fallbackDirsPsr4;
	}

	public function getClassMap()
	{
		return $this->classMap;
	}

	/**
	 * @param array $classMap Class to filename map
	 */
	public function addClassMap(array $classMap)
	{
		if ($this->classMap)
		{
			$this->classMap = array_merge($this->classMap, $classMap);
		}
		else
		{
			$this->classMap = $classMap;
		}
	}

	/**
	 * Registers a set of PSR-0 directories for a given prefix, either
	 * appending or prepending to the ones previously set for this prefix.
	 *
	 * @param string       $prefix  The prefix
	 * @param array|string $paths   The PSR-0 root directories
	 * @param bool         $prepend Whether to prepend the directories
	 */
	public function add($prefix, $paths, $prepend = false)
	{
		if ( ! $prefix)
		{
			if ($prepend)
			{
				$this->fallbackDirsPsr0 = array_merge(
					(array) $paths,
					$this->fallbackDirsPsr0
				);
			}
			else
			{
				$this->fallbackDirsPsr0 = array_merge(
					$this->fallbackDirsPsr0,
					(array) $paths
				);
			}

			return;
		}

		$first = $prefix[0];
		if ( ! isset($this->prefixesPsr0[$first][$prefix]))
		{
			$this->prefixesPsr0[$first][$prefix] = (array) $paths;

			return;
		}
		if ($prepend)
		{
			$this->prefixesPsr0[$first][$prefix] = array_merge(
				(array) $paths,
				$this->prefixesPsr0[$first][$prefix]
			);
		}
		else
		{
			$this->prefixesPsr0[$first][$prefix] = array_merge(
				$this->prefixesPsr0[$first][$prefix],
				(array) $paths
			);
		}
	}

	/**
	 * Registers a set of PSR-4 directories for a given namespace, either
	 * appending or prepending to the ones previously set for this namespace.
	 *
	 * @param string       $prefix  The prefix/namespace, with trailing '\\'
	 * @param array|string $paths   The PSR-4 base directories
	 * @param bool         $prepend Whether to prepend the directories
	 *
	 * @throws \InvalidArgumentException
	 */
	public function addPsr4($prefix, $paths, $prepend = false)
	{
		if ( ! $prefix)
		{
			// Register directories for the root namespace.
			if ($prepend)
			{
				$this->fallbackDirsPsr4 = array_merge(
					(array) $paths,
					$this->fallbackDirsPsr4
				);
			}
			else
			{
				$this->fallbackDirsPsr4 = array_merge(
					$this->fallbackDirsPsr4,
					(array) $paths
				);
			}
		}
		elseif ( ! isset($this->prefixDirsPsr4[$prefix]))
		{
			// Register directories for a new namespace.
			$length = strlen($prefix);
			if ('\\' !== $prefix[$length - 1])
			{
				throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
			}
			$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
			$this->prefixDirsPsr4[$prefix]                = (array) $paths;
		}
		elseif ($prepend)
		{
			// Prepend directories for an already registered namespace.
			$this->prefixDirsPsr4[$prefix] = array_merge(
				(array) $paths,
				$this->prefixDirsPsr4[$prefix]
			);
		}
		else
		{
			// Append directories for an already registered namespace.
			$this->prefixDirsPsr4[$prefix] = array_merge(
				$this->prefixDirsPsr4[$prefix],
				(array) $paths
			);
		}
	}

	/**
	 * Registers a set of PSR-0 directories for a given prefix,
	 * replacing any others previously set for this prefix.
	 *
	 * @param string       $prefix The prefix
	 * @param array|string $paths  The PSR-0 base directories
	 */
	public function set($prefix, $paths)
	{
		if ( ! $prefix)
		{
			$this->fallbackDirsPsr0 = (array) $paths;
		}
		else
		{
			$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
		}
	}

	/**
	 * Registers a set of PSR-4 directories for a given namespace,
	 * replacing any others previously set for this namespace.
	 *
	 * @param string       $prefix The prefix/namespace, with trailing '\\'
	 * @param array|string $paths  The PSR-4 base directories
	 *
	 * @throws \InvalidArgumentException
	 */
	public function setPsr4($prefix, $paths)
	{
		if ( ! $prefix)
		{
			$this->fallbackDirsPsr4 = (array) $paths;
		}
		else
		{
			$length = strlen($prefix);
			if ('\\' !== $prefix[$length - 1])
			{
				throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
			}
			$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
			$this->prefixDirsPsr4[$prefix]                = (array) $paths;
		}
	}

	/**
	 * Turns on searching the include path for class files.
	 *
	 * @param bool $useIncludePath
	 */
	public function setUseIncludePath($useIncludePath)
	{
		$this->useIncludePath = $useIncludePath;
	}

	/**
	 * Can be used to check if the autoloader uses the include path to check
	 * for classes.
	 *
	 * @return bool
	 */
	public function getUseIncludePath()
	{
		return $this->useIncludePath;
	}

	/**
	 * Turns off searching the prefix and fallback directories for classes
	 * that have not been registered with the class map.
	 *
	 * @param bool $classMapAuthoritative
	 */
	public function setClassMapAuthoritative($classMapAuthoritative)
	{
		$this->classMapAuthoritative = $classMapAuthoritative;
	}

	/**
	 * Should class lookup fail if not found in the current class map?
	 *
	 * @return bool
	 */
	public function isClassMapAuthoritative()
	{
		return $this->classMapAuthoritative;
	}

	/**
	 * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
	 *
	 * @param string|null $apcuPrefix
	 */
	public function setApcuPrefix($apcuPrefix)
	{
		$this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null;
	}

	/**
	 * The APCu prefix in use, or null if APCu caching is not enabled.
	 *
	 * @return string|null
	 */
	public function getApcuPrefix()
	{
		return $this->apcuPrefix;
	}

	/**
	 * Registers this instance as an autoloader.
	 *
	 * @param bool $prepend Whether to prepend the autoloader or not
	 */
	public function register($prepend = false)
	{
		spl_autoload_register([$this, 'loadClass'], true, $prepend);
	}

	/**
	 * Unregisters this instance as an autoloader.
	 */
	public function unregister()
	{
		spl_autoload_unregister([$this, 'loadClass']);
	}

	/**
	 * Loads the given class or interface.
	 *
	 * @param  string $class The name of the class
	 *
	 * @return bool|null True if loaded, null otherwise
	 */
	public function loadClass($class)
	{
		if ($file = $this->findFile($class))
		{
			includeFile($file);

			return true;
		}
	}

	/**
	 * Finds the path to the file where the class is defined.
	 *
	 * @param string $class The name of the class
	 *
	 * @return string|false The path if found, false otherwise
	 */
	public function findFile($class)
	{
		// class map lookup
		if (isset($this->classMap[$class]))
		{
			return $this->classMap[$class];
		}
		if ($this->classMapAuthoritative || isset($this->missingClasses[$class]))
		{
			return false;
		}
		if (null !== $this->apcuPrefix)
		{
			$file = apcu_fetch($this->apcuPrefix . $class, $hit);
			if ($hit)
			{
				return $file;
			}
		}

		$file = $this->findFileWithExtension($class, '.php');

		// Search for Hack files if we are running on HHVM
		if (false === $file && defined('HHVM_VERSION'))
		{
			$file = $this->findFileWithExtension($class, '.hh');
		}

		if (null !== $this->apcuPrefix)
		{
			apcu_add($this->apcuPrefix . $class, $file);
		}

		if (false === $file)
		{
			// Remember that this class does not exist.
			$this->missingClasses[$class] = true;
		}

		return $file;
	}

	private function findFileWithExtension($class, $ext)
	{
		// PSR-4 lookup
		$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

		$first = $class[0];
		if (isset($this->prefixLengthsPsr4[$first]))
		{
			foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length)
			{
				if (0 === strpos($class, $prefix))
				{
					foreach ($this->prefixDirsPsr4[$prefix] as $dir)
					{
						if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length)))
						{
							return $file;
						}
					}
				}
			}
		}

		// PSR-4 fallback dirs
		foreach ($this->fallbackDirsPsr4 as $dir)
		{
			if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4))
			{
				return $file;
			}
		}

		// PSR-0 lookup
		if (false !== $pos = strrpos($class, '\\'))
		{
			// namespaced class name
			$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
				. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
		}
		else
		{
			// PEAR-like class name
			$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
		}

		if (isset($this->prefixesPsr0[$first]))
		{
			foreach ($this->prefixesPsr0[$first] as $prefix => $dirs)
			{
				if (0 === strpos($class, $prefix))
				{
					foreach ($dirs as $dir)
					{
						if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0))
						{
							return $file;
						}
					}
				}
			}
		}

		// PSR-0 fallback dirs
		foreach ($this->fallbackDirsPsr0 as $dir)
		{
			if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0))
			{
				return $file;
			}
		}

		// PSR-0 include paths.
		if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0))
		{
			return $file;
		}

		return false;
	}
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
	include $file;
}
regularlabs/vendor/composer/autoload_classmap.php000064400000000217152177723700016412 0ustar00<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir   = dirname($vendorDir);

return [];
regularlabs/vendor/composer/installed.json000064400000000003152177723700015051 0ustar00[]
regularlabs/vendor/composer/autoload_psr4.php000064400000000276152177723700015504 0ustar00<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir   = dirname($vendorDir);

return [
	'RegularLabs\\Library\\' => [$baseDir . '/src'],
];
src/Installer/InstallerScript.php000064400000021534152177723700013143 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('_JEXEC') or die;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');

/**
 * Base install script for use by extensions providing helper methods for common behaviours.
 *
 * @since  3.6
 */
class InstallerScript
{
	/**
	 * The version number of the extension.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $release;

	/**
	 * The table the parameters are stored in.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $paramTable;

	/**
	 * The extension name. This should be set in the installer script.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $extension;

	/**
	 * A list of files to be deleted
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $deleteFiles = array();

	/**
	 * A list of folders to be deleted
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $deleteFolders = array();

	/**
	 * A list of CLI script files to be copied to the cli directory
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $cliScriptFiles = array();

	/**
	 * Minimum PHP version required to install the extension
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $minimumPhp;

	/**
	 * Minimum Joomla! version required to install the extension
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $minimumJoomla;

	/**
	 * Allow downgrades of your extension
	 *
	 * Use at your own risk as if there is a change in functionality people may wish to downgrade.
	 *
	 * @var    boolean
	 * @since  3.6
	 */
	protected $allowDowngrades = false;

	/**
	 * Function called before extension installation/update/removal procedure commences
	 *
	 * @param   string            $type    The type of change (install, update or discover_install, not uninstall)
	 * @param   InstallerAdapter  $parent  The class calling this method
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.6
	 */
	public function preflight($type, $parent)
	{
		// Check for the minimum PHP version before continuing
		if (!empty($this->minimumPhp) && version_compare(PHP_VERSION, $this->minimumPhp, '<'))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_PHP', $this->minimumPhp), \JLog::WARNING, 'jerror');

			return false;
		}

		// Check for the minimum Joomla version before continuing
		if (!empty($this->minimumJoomla) && version_compare(JVERSION, $this->minimumJoomla, '<'))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_MINIMUM_JOOMLA', $this->minimumJoomla), \JLog::WARNING, 'jerror');

			return false;
		}

		// Extension manifest file version
		$this->extension = $parent->getName();
		$this->release   = $parent->get('manifest')->version;
		$extensionType   = substr($this->extension, 0, 3);

		// Modules parameters are located in the module table - else in the extension table
		if ($extensionType === 'mod')
		{
			$this->paramTable = '#__modules';
		}
		else
		{
			$this->paramTable = '#__extensions';
		}

		// Abort if the extension being installed is not newer than the currently installed version
		if (!$this->allowDowngrades && strtolower($type) === 'update')
		{
			$manifest = $this->getItemArray('manifest_cache', '#__extensions', 'element', \JFactory::getDbo()->quote($this->extension));
			$oldRelease = $manifest['version'];

			if (version_compare($this->release, $oldRelease, '<'))
			{
				\JFactory::getApplication()->enqueueMessage(\JText::sprintf('JLIB_INSTALLER_INCORRECT_SEQUENCE', $oldRelease, $this->release), 'error');

				return false;
			}
		}

		return true;
	}

	/**
	 * Gets each instance of a module in the #__modules table
	 *
	 * @param   boolean  $isModule  True if the extension is a module as this can have multiple instances
	 *
	 * @return  array  An array of ID's of the extension
	 *
	 * @since   3.6
	 */
	public function getInstances($isModule)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);

		// Select the item(s) and retrieve the id
		$query->select($db->quoteName('id'));

		if ($isModule)
		{
			$query->from($db->quoteName('#__modules'))
				->where($db->quoteName('module') . ' = ' . $db->quote($this->extension));
		}
		else
		{
			$query->from($db->quoteName('#__extensions'))
				->where($db->quoteName('element') . ' = ' . $db->quote($this->extension));
		}

		// Set the query and obtain an array of id's
		return $db->setQuery($query)->loadColumn();
	}

	/**
	 * Gets parameter value in the extensions row of the extension table
	 *
	 * @param   string   $name  The name of the parameter to be retrieved
	 * @param   integer  $id    The id of the item in the Param Table
	 *
	 * @return  string  The parameter desired
	 *
	 * @since   3.6
	 */
	public function getParam($name, $id = 0)
	{
		if (!is_int($id) || $id == 0)
		{
			// Return false if there is no item given
			return false;
		}

		$params = $this->getItemArray('params', $this->paramTable, 'id', $id);

		return $params[$name];
	}

	/**
	 * Sets parameter values in the extensions row of the extension table. Note that the
	 * this must be called separately for deleting and editing. Note if edit is called as a
	 * type then if the param doesn't exist it will be created
	 *
	 * @param   array    $param_array  The array of parameters to be added/edited/removed
	 * @param   string   $type         The type of change to be made to the param (edit/remove)
	 * @param   integer  $id           The id of the item in the relevant table
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.6
	 */
	public function setParams($param_array = null, $type = 'edit', $id = 0)
	{
		if (!is_int($id) || $id == 0)
		{
			// Return false if there is no valid item given
			return false;
		}

		$params = $this->getItemArray('params', $this->paramTable, 'id', $id);

		if ($param_array)
		{
			foreach ($param_array as $name => $value)
			{
				if ($type === 'edit')
				{
					// Add or edit the new variable(s) to the existing params
					if (is_array($value))
					{
						// Convert an array into a json encoded string
						$params[(string) $name] = array_values($value);
					}
					else
					{
						$params[(string) $name] = (string) $value;
					}
				}
				elseif ($type === 'remove')
				{
					// Unset the parameter from the array
					unset($params[(string) $name]);
				}
			}
		}

		// Store the combined new and existing values back as a JSON string
		$paramsString = json_encode($params);

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->update($db->quoteName($this->paramTable))
			->set('params = ' . $db->quote($paramsString))
			->where('id = ' . $id);

		// Update table
		$db->setQuery($query)->execute();

		return true;
	}

	/**
	 * Builds a standard select query to produce better DRY code in this script.
	 * This should produce a single unique cell which is json encoded - it will then
	 * return an associated array with this data in.
	 *
	 * @param   string  $element     The element to get from the query
	 * @param   string  $table       The table to search for the data in
	 * @param   string  $column      The column of the database to search from
	 * @param   mixed   $identifier  The integer id or the already quoted string
	 *
	 * @return  array  Associated array containing data from the cell
	 *
	 * @since   3.6
	 */
	public function getItemArray($element, $table, $column, $identifier)
	{
		// Get the DB and query objects
		$db = \JFactory::getDbo();

		// Build the query
		$query = $db->getQuery(true)
			->select($db->quoteName($element))
			->from($db->quoteName($table))
			->where($db->quoteName($column) . ' = ' . $identifier);
		$db->setQuery($query);

		// Load the single cell and json_decode data
		return json_decode($db->loadResult(), true);
	}

	/**
	 * Remove the files and folders in the given array from
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function removeFiles()
	{
		if (!empty($this->deleteFiles))
		{
			foreach ($this->deleteFiles as $file)
			{
				if (file_exists(JPATH_ROOT . $file) && !\JFile::delete(JPATH_ROOT . $file))
				{
					echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $file) . '<br />';
				}
			}
		}

		if (!empty($this->deleteFolders))
		{
			foreach ($this->deleteFolders as $folder)
			{
				if (\JFolder::exists(JPATH_ROOT . $folder) && !\JFolder::delete(JPATH_ROOT . $folder))
				{
					echo \JText::sprintf('JLIB_INSTALLER_ERROR_FILE_FOLDER', $folder) . '<br />';
				}
			}
		}
	}

	/**
	 * Moves the CLI scripts into the CLI folder in the CMS
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function moveCliFiles()
	{
		if (!empty($this->cliScriptFiles))
		{
			foreach ($this->cliScriptFiles as $file)
			{
				$name = basename($file);

				if (file_exists(JPATH_ROOT . $file) && !\JFile::move(JPATH_ROOT . $file, JPATH_ROOT . '/cli/' . $name))
				{
					echo \JText::sprintf('JLIB_INSTALLER_FILE_ERROR_MOVE', $name);
				}
			}
		}
	}
}
src/Installer/InstallerExtension.php000064400000005541152177723700013653 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;

/**
 * Extension object
 *
 * @since  3.1
 */
class InstallerExtension extends \JObject
{
	/**
	 * Filename of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $filename = '';

	/**
	 * Type of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $type = '';

	/**
	 * Unique Identifier for the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $id = '';

	/**
	 * The status of the extension
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	public $published = false;

	/**
	 * String representation of client. Valid for modules, templates and languages.
	 * Set by default to site.
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $client = 'site';

	/**
	 * The group name of the plugin. Not used for other known extension types (only plugins)
	 *
	 * @var string
	 * @since  3.1
	 */
	public $group = '';

	/**
	 * An object representation of the manifest file stored metadata
	 *
	 * @var object
	 * @since  3.1
	 */
	public $manifest_cache = null;

	/**
	 * An object representation of the extension params
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $params = null;

	/**
	 * Constructor
	 *
	 * @param   \SimpleXMLElement  $element  A SimpleXMLElement from which to load data from
	 *
	 * @since  3.1
	 */
	public function __construct(\SimpleXMLElement $element = null)
	{
		if ($element)
		{
			$this->type = (string) $element->attributes()->type;
			$this->id = (string) $element->attributes()->id;

			switch ($this->type)
			{
				case 'component':
					// By default a component doesn't have anything
					break;

				case 'module':
				case 'template':
				case 'language':
					$this->client = (string) $element->attributes()->client;
					$tmp_client_id = ApplicationHelper::getClientInfo($this->client, 1);

					if ($tmp_client_id == null)
					{
						\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_EXTENSION_INVALID_CLIENT_IDENTIFIER'), \JLog::WARNING, 'jerror');
					}
					else
					{
						$this->client_id = $tmp_client_id->id;
					}
					break;

				case 'plugin':
					$this->group = (string) $element->attributes()->group;
					break;

				default:
					// Catch all
					// Get and set client and group if we don't recognise the extension
					if ($element->attributes()->client)
					{
						$this->client_id = ApplicationHelper::getClientInfo($this->client, 1);
						$this->client_id = $this->client_id->id;
					}

					if ($element->attributes()->group)
					{
						$this->group = (string) $element->attributes()->group;
					}
					break;
			}

			$this->filename = (string) $element;
		}
	}
}
src/Installer/InstallerHelper.php000064400000023751152177723700013121 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\Archive\Archive;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Version;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');

/**
 * Installer helper class
 *
 * @since  3.1
 */
abstract class InstallerHelper
{
	/**
	 * Hash not validated identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_NOT_VALIDATED = 0;

	/**
	 * Hash validated identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_VALIDATED = 1;

	/**
	 * Hash not provided identifier.
	 *
	 * @var    integer
	 * @since  3.9.0
	 */
	const HASH_NOT_PROVIDED = 2;

	/**
	 * Downloads a package
	 *
	 * @param   string  $url     URL of file to download
	 * @param   mixed   $target  Download target filename or false to get the filename from the URL
	 *
	 * @return  string|boolean  Path to downloaded package or boolean false on failure
	 *
	 * @since   3.1
	 */
	public static function downloadPackage($url, $target = false)
	{
		// Capture PHP errors
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		// Set user agent
		$version = new Version;
		ini_set('user_agent', $version->getUserAgent('Installer'));

		// Load installer plugins, and allow URL and headers modification
		$headers = array();
		PluginHelper::importPlugin('installer');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onInstallerBeforePackageDownload', array(&$url, &$headers));

		try
		{
			$response = \JHttpFactory::getHttp()->get($url, $headers);
		}
		catch (\RuntimeException $exception)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $exception->getMessage()), \JLog::WARNING, 'jerror');

			return false;
		}

		// Convert keys of headers to lowercase, to accomodate for case variations
		$headers = array_change_key_case($response->headers);

		if (302 == $response->code && !empty($headers['location']))
		{
			return self::downloadPackage($headers['location']);
		}
		elseif (200 != $response->code)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_DOWNLOAD_SERVER_CONNECT', $response->code), \JLog::WARNING, 'jerror');

			return false;
		}

		// Parse the Content-Disposition header to get the file name
		if (!empty($headers['content-disposition'])
			&& preg_match("/\s*filename\s?=\s?(.*)/", $headers['content-disposition'], $parts))
		{
			$flds = explode(';', $parts[1]);
			$target = trim($flds[0], '"');
		}

		$tmpPath = \JFactory::getConfig()->get('tmp_path');

		// Set the target path if not given
		if (!$target)
		{
			$target = $tmpPath . '/' . self::getFilenameFromUrl($url);
		}
		else
		{
			$target = $tmpPath . '/' . basename($target);
		}

		// Write buffer to file
		\JFile::write($target, $response->body);

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Bump the max execution time because not using built in php zip libs are slow
		@set_time_limit(ini_get('max_execution_time'));

		// Return the name of the downloaded package
		return basename($target);
	}

	/**
	 * Unpacks a file and verifies it as a Joomla element package
	 * Supports .gz .tar .tar.gz and .zip
	 *
	 * @param   string   $p_filename         The uploaded package filename or install directory
	 * @param   boolean  $alwaysReturnArray  If should return false (and leave garbage behind) or return $retval['type']=false
	 *
	 * @return  array|boolean  Array on success or boolean false on failure
	 *
	 * @since   3.1
	 */
	public static function unpack($p_filename, $alwaysReturnArray = false)
	{
		// Path to the archive
		$archivename = $p_filename;

		// Temporary folder to extract the archive into
		$tmpdir = uniqid('install_');

		// Clean the paths to use for archive extraction
		$extractdir = \JPath::clean(dirname($p_filename) . '/' . $tmpdir);
		$archivename = \JPath::clean($archivename);

		// Do the unpacking of the archive
		try
		{
			$archive = new Archive(array('tmp_path' => \JFactory::getConfig()->get('tmp_path')));
			$extract = $archive->extract($archivename, $extractdir);
		}
		catch (\Exception $e)
		{
			if ($alwaysReturnArray)
			{
				return array(
					'extractdir'  => null,
					'packagefile' => $archivename,
					'type'        => false,
				);
			}

			return false;
		}

		if (!$extract)
		{
			if ($alwaysReturnArray)
			{
				return array(
					'extractdir'  => null,
					'packagefile' => $archivename,
					'type'        => false,
				);
			}

			return false;
		}

		/*
		 * Let's set the extraction directory and package file in the result array so we can
		 * cleanup everything properly later on.
		 */
		$retval['extractdir'] = $extractdir;
		$retval['packagefile'] = $archivename;

		/*
		 * Try to find the correct install directory.  In case the package is inside a
		 * subdirectory detect this and set the install directory to the correct path.
		 *
		 * List all the items in the installation directory.  If there is only one, and
		 * it is a folder, then we will set that folder to be the installation folder.
		 */
		$dirList = array_merge((array) \JFolder::files($extractdir, ''), (array) \JFolder::folders($extractdir, ''));

		if (count($dirList) === 1)
		{
			if (\JFolder::exists($extractdir . '/' . $dirList[0]))
			{
				$extractdir = \JPath::clean($extractdir . '/' . $dirList[0]);
			}
		}

		/*
		 * We have found the install directory so lets set it and then move on
		 * to detecting the extension type.
		 */
		$retval['dir'] = $extractdir;

		/*
		 * Get the extension type and return the directory/type array on success or
		 * false on fail.
		 */
		$retval['type'] = self::detectType($extractdir);

		if ($alwaysReturnArray || $retval['type'])
		{
			return $retval;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to detect the extension type from a package directory
	 *
	 * @param   string  $p_dir  Path to package directory
	 *
	 * @return  mixed  Extension type string or boolean false on fail
	 *
	 * @since   3.1
	 */
	public static function detectType($p_dir)
	{
		// Search the install dir for an XML file
		$files = \JFolder::files($p_dir, '\.xml$', 1, true);

		if (!$files || !count($files))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), \JLog::WARNING, 'jerror');

			return false;
		}

		foreach ($files as $file)
		{
			$xml = simplexml_load_file($file);

			if (!$xml)
			{
				continue;
			}

			if ($xml->getName() !== 'extension')
			{
				unset($xml);
				continue;
			}

			$type = (string) $xml->attributes()->type;

			// Free up memory
			unset($xml);

			return $type;
		}

		\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), \JLog::WARNING, 'jerror');

		// Free up memory.
		unset($xml);

		return false;
	}

	/**
	 * Gets a file name out of a url
	 *
	 * @param   string  $url  URL to get name from
	 *
	 * @return  string  Clean version of the filename or a unique id
	 *
	 * @since   3.1
	 */
	public static function getFilenameFromUrl($url)
	{
		$default = uniqid();

		if (!is_string($url) || strpos($url, '/') === false)
		{
			return $default;
		}

		// Get last part of the url (after the last slash).
		$parts    = explode('/', $url);
		$filename = array_pop($parts);

		// Replace special characters with underscores.
		$filename = preg_replace('/[^a-z0-9\_\-\.]/i', '_', $filename);

		// Replace multiple underscores with just one.
		$filename = preg_replace('/__+/', '_', trim($filename, '_'));

		// Return the cleaned filename or, if it is empty, a unique id.
		return $filename ?: $default;
	}

	/**
	 * Clean up temporary uploaded package and unpacked extension
	 *
	 * @param   string  $package    Path to the uploaded package file
	 * @param   string  $resultdir  Path to the unpacked extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public static function cleanupInstall($package, $resultdir)
	{
		$config = \JFactory::getConfig();

		// Does the unpacked extension directory exist?
		if ($resultdir && is_dir($resultdir))
		{
			\JFolder::delete($resultdir);
		}

		// Is the package file a valid file?
		if (is_file($package))
		{
			\JFile::delete($package);
		}
		elseif (is_file(\JPath::clean($config->get('tmp_path') . '/' . $package)))
		{
			// It might also be just a base filename
			\JFile::delete(\JPath::clean($config->get('tmp_path') . '/' . $package));
		}
	}

	/**
	 * Splits contents of a sql file into array of discreet queries.
	 * Queries need to be delimited with end of statement marker ';'
	 *
	 * @param   string  $query  The SQL statement.
	 *
	 * @return  array  Array of queries
	 *
	 * @since   3.1
	 * @deprecated  4.0  Use \JDatabaseDriver::splitSql() directly
	 * @codeCoverageIgnore
	 */
	public static function splitSql($query)
	{
		\JLog::add('JInstallerHelper::splitSql() is deprecated. Use JDatabaseDriver::splitSql() instead.', \JLog::WARNING, 'deprecated');
		$db = \JFactory::getDbo();

		return $db->splitSql($query);
	}

	/**
	 * Return the result of the checksum of a package with the SHA256/SHA384/SHA512 tags in the update server manifest
	 *
	 * @param   string   $packagefile   Location of the package to be installed
	 * @param   JUpdate  $updateObject  The Update Object
	 *
	 * @return  integer  one if the hashes match, zero if hashes doesn't match, two if hashes not found
	 *
	 * @since   3.9.0
	 */
	public static function isChecksumValid($packagefile, $updateObject)
	{
		$hashes     = array('sha256', 'sha384', 'sha512');
		$hashOnFile = false;

		foreach ($hashes as $hash)
		{
			if ($updateObject->get($hash, false))
			{
				$hashPackage = hash_file($hash, $packagefile);
				$hashRemote  = $updateObject->$hash->_data;
				$hashOnFile  = true;

				if ($hashPackage !== $hashRemote)
				{
					return self::HASH_NOT_VALIDATED;
				}
			}
		}

		if ($hashOnFile)
		{
			return self::HASH_VALIDATED;
		}

		return self::HASH_NOT_PROVIDED;
	}
}
src/Installer/InstallerAdapter.php000064400000060725152177723700013264 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;

\JLoader::import('joomla.base.adapterinstance');

/**
 * Abstract adapter for the installer.
 *
 * @method         Installer  getParent()  Retrieves the parent object.
 * @property-read  Installer  $parent      Parent object
 *
 * @since  3.4
 * @note   As of 4.0, this class will no longer extend from JAdapterInstance
 */
abstract class InstallerAdapter extends \JAdapterInstance
{
	/**
	 * ID for the currently installed extension if present
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $currentExtensionId = null;

	/**
	 * The unique identifier for the extension (e.g. mod_login)
	 *
	 * @var    string
	 * @since  3.4
	 * */
	protected $element = null;

	/**
	 * Extension object.
	 *
	 * @var    Extension
	 * @since  3.4
	 * */
	protected $extension = null;

	/**
	 * Messages rendered by custom scripts
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $extensionMessage = '';

	/**
	 * Copy of the XML manifest file.
	 *
	 * Making this object public allows extensions to customize the manifest in custom scripts.
	 *
	 * @var    string
	 * @since  3.4
	 */
	public $manifest = null;

	/**
	 * A path to the PHP file that the scriptfile declaration in the manifest refers to.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $manifest_script = null;

	/**
	 * Name of the extension
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $name = null;

	/**
	 * Install function routing
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $route = 'install';

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = true;

	/**
	 * The type of adapter in use
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $type;

	/**
	 * Constructor
	 *
	 * @param   Installer         $parent   Parent object
	 * @param   \JDatabaseDriver  $db       Database object
	 * @param   array             $options  Configuration Options
	 *
	 * @since   3.4
	 */
	public function __construct(Installer $parent, \JDatabaseDriver $db, array $options = array())
	{
		parent::__construct($parent, $db, $options);

		// Get a generic TableExtension instance for use if not already loaded
		if (!($this->extension instanceof TableInterface))
		{
			$this->extension = Table::getInstance('extension');
		}

		// Sanity check, make sure the type is set by taking the adapter name from the class name
		if (!$this->type)
		{
			// This assumes the adapter short class name in its namespace is `<foo>Adapter`, replace this logic in subclasses if needed
			$reflection = new \ReflectionClass(get_called_class());
			$this->type = str_replace('Adapter', '', $reflection->getShortName());
		}

		// Extension type is stored as lowercase in the database
		$this->type = strtolower($this->type);
	}

	/**
	 * Check if a package extension allows its child extensions to be uninstalled individually
	 *
	 * @param   integer  $packageId  The extension ID of the package to check
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 * @note    This method defaults to true to emulate the behavior of 3.6 and earlier which did not support this lookup
	 */
	protected function canUninstallPackageChild($packageId)
	{
		$package = Table::getInstance('extension');

		// If we can't load this package ID, we have a corrupt database
		if (!$package->load((int) $packageId))
		{
			return true;
		}

		$manifestFile = JPATH_MANIFESTS . '/packages/' . $package->element . '.xml';

		$xml = $this->parent->isManifest($manifestFile);

		// If the manifest doesn't exist, we've got some major issues
		if (!$xml)
		{
			return true;
		}

		$manifest = new PackageManifest($manifestFile);

		return $manifest->blockChildUninstall === false;
	}

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array('element' => $this->element, 'type' => $this->type)
			);

			// If it does exist, load it
			if ($this->currentExtensionId)
			{
				$this->extension->load(array('element' => $this->element, 'type' => $this->type));
			}
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to check if the extension is present in the filesystem, flags the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		if (file_exists($this->parent->getPath('extension_root')) && (!$this->parent->isOverwrite() || $this->parent->isUpgrade()))
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update function available or update tag detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')))
			{
				// Force this one
				$this->parent->setOverwrite(true);
				$this->parent->setUpgrade(true);

				if ($this->currentExtensionId)
				{
					// If there is a matching extension mark this as an update
					$this->setRoute('update');
				}
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find an update tag so lets call it safe
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->type,
						$this->parent->getPath('extension_root')
					)
				);
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	abstract protected function copyBaseFiles();

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// If the extension directory does not exist, lets create it
		$created = false;

		if (!file_exists($this->parent->getPath('extension_root')))
		{
			if (!$created = \JFolder::create($this->parent->getPath('extension_root')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->parent->getPath('extension_root')
					)
				);
			}
		}

		/*
		 * Since we created the extension directory and will want to remove it if
		 * we have to roll back the installation, let's add it to the
		 * installation step stack
		 */

		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' => $this->parent->getPath('extension_root'),
				)
			);
		}
	}

	/**
	 * Generic discover_install method for extensions
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 */
	public function discover_install()
	{
		// Get the extension's description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->message = \JText::_($description);
		}
		else
		{
			$this->parent->message = '';
		}

		// Set the extension's name and element
		$this->name    = $this->getName();
		$this->element = $this->getElement();

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Extension Precheck and Setup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Setup the install paths and perform other prechecks as necessary
		try
		{
			$this->setupInstallPaths();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 * ---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('preflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 * ---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->storeExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Run the custom install method
		try
		{
			$this->triggerManifestScript('install');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->finaliseInstall();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// And now we run the postflight
		try
		{
			$this->triggerManifestScript('postflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		return $this->extension->extension_id;
	}

	/**
	 * Method to handle database transactions for the installer
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function doDatabaseTransactions()
	{
		$route = $this->route === 'discover_install' ? 'install' : $this->route;

		// Let's run the install queries for the component
		if (isset($this->getManifest()->{$route}->sql))
		{
			$result = $this->parent->parseSQLFiles($this->getManifest()->{$route}->sql);

			if ($result === false)
			{
				// Only rollback if installing
				if ($route === 'install')
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_SQL_ERROR',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
							$this->parent->getDbo()->stderr(true)
						)
					);
				}

				return false;
			}

			// If installing with success and there is an uninstall script, add an installer rollback step to rollback if needed
			if ($route === 'install' && isset($this->getManifest()->uninstall->sql))
			{
				$this->parent->pushStep(array('type' => 'query', 'script' => $this->getManifest()->uninstall->sql));
			}
		}

		return true;
	}

	/**
	 * Load language files
	 *
	 * @param   string  $extension  The name of the extension
	 * @param   string  $source     Path to the extension
	 * @param   string  $base       Base path for the extension language
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function doLoadLanguage($extension, $source, $base = JPATH_ADMINISTRATOR)
	{
		$lang = \JFactory::getLanguage();
		$lang->load($extension . '.sys', $source, null, false, true) || $lang->load($extension . '.sys', $base, null, false, true);
	}

	/**
	 * Checks if the adapter supports discover_install
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public function getDiscoverInstallSupported()
	{
		return $this->supportsDiscoverInstall;
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Ensure the element is a string
			$element = (string) $this->getManifest()->element;
		}

		if (!$element)
		{
			$element = $this->getName();
		}

		// Filter the name for illegal characters
		return strtolower(\JFilterInput::getInstance()->clean($element, 'cmd'));
	}

	/**
	 * Get the manifest object.
	 *
	 * @return  \SimpleXMLElement  Manifest object
	 *
	 * @since   3.4
	 */
	public function getManifest()
	{
		return $this->manifest;
	}

	/**
	 * Get the filtered component name from the manifest
	 *
	 * @return  string  The filtered name
	 *
	 * @since   3.4
	 */
	public function getName()
	{
		// Ensure the name is a string
		$name = (string) $this->getManifest()->name;

		// Filter the name for illegal characters
		$name = \JFilterInput::getInstance()->clean($name, 'string');

		return $name;
	}

	/**
	 * Get the install route being followed
	 *
	 * @return  string  The install route
	 *
	 * @since   3.4
	 */
	public function getRoute()
	{
		return $this->route;
	}

	/**
	 * Get the class name for the install adapter script.
	 *
	 * @return  string  The class name.
	 *
	 * @since   3.4
	 */
	protected function getScriptClassName()
	{
		// Support element names like 'en-GB'
		$className = \JFilterInput::getInstance()->clean($this->element, 'cmd') . 'InstallerScript';

		// Cannot have - in class names
		$className = str_replace('-', '', $className);

		return $className;
	}

	/**
	 * Generic install method for extensions
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on failure
	 *
	 * @since   3.4
	 */
	public function install()
	{
		// Get the extension's description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->message = \JText::_($description);
		}
		else
		{
			$this->parent->message = '';
		}

		// Set the extension's name and element
		$this->name    = $this->getName();
		$this->element = $this->getElement();

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Extension Precheck and Setup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Setup the install paths and perform other prechecks as necessary
		try
		{
			$this->setupInstallPaths();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Check to see if an extension by the same name is already installed.
		try
		{
			$this->checkExistingExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Check if the extension is present in the filesystem
		try
		{
			$this->checkExtensionInFilesystem();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// If we are on the update route, run any custom setup routines
		if ($this->route === 'update')
		{
			try
			{
				$this->setupUpdates();
			}
			catch (\RuntimeException $e)
			{
				// Install failed, roll back changes
				$this->parent->abort($e->getMessage());

				return false;
			}
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 * ---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('preflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Filesystem Processing Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// If the extension directory does not exist, lets create it
		try
		{
			$this->createExtensionRoot();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Copy all necessary files
		try
		{
			$this->copyBaseFiles();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Parse optional tags
		$this->parseOptionalTags();

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 * ---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->storeExtension();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// Run the custom method based on the route
		try
		{
			$this->triggerManifestScript($this->route);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		/*
		 * ---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		try
		{
			$this->finaliseInstall();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		// And now we run the postflight
		try
		{
			$this->triggerManifestScript('postflight');
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort($e->getMessage());

			return false;
		}

		return $this->extension->extension_id;
	}

	/**
	 * Method to parse the queries specified in the `<sql>` tags
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function parseQueries()
	{
		// Let's run the queries for the extension
		if (in_array($this->route, array('install', 'discover_install', 'uninstall')))
		{
			// This method may throw an exception, but it is caught by the parent caller
			if (!$this->doDatabaseTransactions())
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_SQL_ERROR',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->db->stderr(true)
					)
				);
			}

			// Set the schema version to be the latest update version
			if ($this->getManifest()->update)
			{
				$this->parent->setSchemaVersion($this->getManifest()->update->schemas, $this->extension->extension_id);
			}
		}
		elseif ($this->route === 'update')
		{
			if ($this->getManifest()->update)
			{
				$result = $this->parent->parseSchemaUpdates($this->getManifest()->update->schemas, $this->extension->extension_id);

				if ($result === false)
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_SQL_ERROR',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
							$this->db->stderr(true)
						)
					);
				}
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function parseOptionalTags()
	{
		// Some extensions may not have optional tags
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		// Adapters may not support discover install or may have overridden the default task and aren't using this
	}

	/**
	 * Set the manifest object.
	 *
	 * @param   object  $manifest  The manifest object
	 *
	 * @return  InstallerAdapter  Instance of this class to support chaining
	 *
	 * @since   3.4
	 */
	public function setManifest($manifest)
	{
		$this->manifest = $manifest;

		return $this;
	}

	/**
	 * Set the install route being followed
	 *
	 * @param   string  $route  The install route being followed
	 *
	 * @return  InstallerAdapter  Instance of this class to support chaining
	 *
	 * @since   3.4
	 */
	public function setRoute($route)
	{
		$this->route = $route;

		return $this;
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	abstract protected function setupInstallPaths();

	/**
	 * Setup the manifest script file for those adapters that use it.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupScriptfile()
	{
		// If there is a manifest class file, lets load it; we'll copy it later (don't have dest yet)
		$manifestScript = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;

			$classname = $this->getScriptClassName();

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->manifest_script = $manifestScript;
			}
		}
	}

	/**
	 * Method to setup the update routine for the adapter
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupUpdates()
	{
		// Some extensions may not have custom setup routines for updates
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	abstract protected function storeExtension();

	/**
	 * Executes a custom install script method
	 *
	 * @param   string  $method  The install method to execute
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function triggerManifestScript($method)
	{
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method))
		{
			switch ($method)
			{
				// The preflight and postflight take the route as a param
				case 'preflight' :
				case 'postflight' :
					if ($this->parent->manifestClass->$method($this->route, $this) === false)
					{
						if ($method !== 'postflight')
						{
							// Clean and close the output buffer
							ob_end_clean();

							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}
					break;

				// The install, uninstall, and update methods only pass this object as a param
				case 'install' :
				case 'uninstall' :
				case 'update' :
					if ($this->parent->manifestClass->$method($this) === false)
					{
						if ($method !== 'uninstall')
						{
							// Clean and close the output buffer
							ob_end_clean();

							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}
					break;
			}
		}

		// Append to the message object
		$this->extensionMessage .= ob_get_clean();

		// If in postflight or uninstall, set the message for display
		if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '')
		{
			$this->parent->set('extension_message', $this->extensionMessage);
		}

		return true;
	}

	/**
	 * Generic update method for extensions
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on failure
	 *
	 * @since   3.4
	 */
	public function update()
	{
		// Set the overwrite setting
		$this->parent->setOverwrite(true);
		$this->parent->setUpgrade(true);

		// And make sure the route is set correctly
		$this->setRoute('update');

		// Now jump into the install method to run the update
		return $this->install();
	}
}
src/Installer/Manifest.php000064400000004275152177723700011572 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

\JLoader::import('joomla.filesystem.file');

/**
 * Joomla! Package Manifest File
 *
 * @since  3.1
 */
abstract class Manifest
{
	/**
	 * Path to the manifest file
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $manifest_file = '';

	/**
	 * Name of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $name = '';

	/**
	 * Version of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $version = '';

	/**
	 * Description of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $description = '';

	/**
	 * Packager of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packager = '';

	/**
	 * Packager's URL of the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packagerurl = '';

	/**
	 * Update site for the extension
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $update = '';

	/**
	 * List of files in the extension
	 *
	 * @var    array
	 * @since  3.1
	 */
	public $filelist = array();

	/**
	 * Constructor
	 *
	 * @param   string  $xmlpath  Path to XML manifest file.
	 *
	 * @since   3.1
	 */
	public function __construct($xmlpath = '')
	{
		if ($xmlpath !== '')
		{
			$this->loadManifestFromXml($xmlpath);
		}
	}

	/**
	 * Load a manifest from a file
	 *
	 * @param   string  $xmlfile  Path to file to load
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function loadManifestFromXml($xmlfile)
	{
		$this->manifest_file = basename($xmlfile, '.xml');

		$xml = simplexml_load_file($xmlfile);

		if (!$xml)
		{
			$this->_errors[] = \JText::sprintf('JLIB_INSTALLER_ERROR_LOAD_XML', $xmlfile);

			return false;
		}
		else
		{
			$this->loadManifestFromData($xml);

			return true;
		}
	}

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	abstract protected function loadManifestFromData(\SimpleXmlElement $xml);
}
src/Installer/Manifest/LibraryManifest.php000064400000004157152177723700014664 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Manifest;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Manifest;

/**
 * Joomla! Library Manifest File
 *
 * @since  3.1
 */
class LibraryManifest extends Manifest
{
	/**
	 * File system name of the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $libraryname = '';

	/**
	 * Creation Date of the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $creationDate = '';

	/**
	 * Copyright notice for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $copyright = '';

	/**
	 * License for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $license = '';

	/**
	 * Author for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $author = '';

	/**
	 * Author email for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $authoremail = '';

	/**
	 * Author URL for the library
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $authorurl = '';

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function loadManifestFromData(\SimpleXMLElement $xml)
	{
		$this->name         = (string) $xml->name;
		$this->libraryname  = (string) $xml->libraryname;
		$this->version      = (string) $xml->version;
		$this->description  = (string) $xml->description;
		$this->creationdate = (string) $xml->creationDate;
		$this->author       = (string) $xml->author;
		$this->authoremail  = (string) $xml->authorEmail;
		$this->authorurl    = (string) $xml->authorUrl;
		$this->packager     = (string) $xml->packager;
		$this->packagerurl  = (string) $xml->packagerurl;
		$this->update       = (string) $xml->update;

		if (isset($xml->files) && isset($xml->files->file) && count($xml->files->file))
		{
			foreach ($xml->files->file as $file)
			{
				$this->filelist[] = (string) $file;
			}
		}
	}
}
src/Installer/Manifest/PackageManifest.php000064400000004715152177723700014613 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Manifest;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\InstallerExtension;
use Joomla\CMS\Installer\Manifest;

/**
 * Joomla! Package Manifest File
 *
 * @since  3.1
 */
class PackageManifest extends Manifest
{
	/**
	 * Unique name of the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $packagename = '';

	/**
	 * Website for the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $url = '';

	/**
	 * Scriptfile for the package
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $scriptfile = '';

	/**
	 * Flag if the package blocks individual child extensions from being uninstalled
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	public $blockChildUninstall = false;

	/**
	 * Apply manifest data from a \SimpleXMLElement to the object.
	 *
	 * @param   \SimpleXMLElement  $xml  Data to load
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function loadManifestFromData(\SimpleXMLElement $xml)
	{
		$this->name        = (string) $xml->name;
		$this->packagename = (string) $xml->packagename;
		$this->update      = (string) $xml->update;
		$this->authorurl   = (string) $xml->authorUrl;
		$this->author      = (string) $xml->author;
		$this->authoremail = (string) $xml->authorEmail;
		$this->description = (string) $xml->description;
		$this->packager    = (string) $xml->packager;
		$this->packagerurl = (string) $xml->packagerurl;
		$this->scriptfile  = (string) $xml->scriptfile;
		$this->version     = (string) $xml->version;

		if (isset($xml->blockChildUninstall))
		{
			$value = (string) $xml->blockChildUninstall;

			if ($value === '1' || $value === 'true')
			{
				$this->blockChildUninstall = true;
			}
		}

		if (isset($xml->files->file) && count($xml->files->file))
		{
			foreach ($xml->files->file as $file)
			{
				// NOTE: JInstallerExtension doesn't expect a string.
				// DO NOT CAST $file
				$this->filelist[] = new InstallerExtension($file);
			}
		}

		// Handle cases where package contains folders
		if (isset($xml->files->folder) && count($xml->files->folder))
		{
			foreach ($xml->files->folder as $folder)
			{
				// NOTE: JInstallerExtension doesn't expect a string.
				// DO NOT CAST $folder
				$this->filelist[] = new InstallerExtension($folder);
			}
		}
	}
}
src/Installer/Adapter/FileAdapter.php000064400000040165152177723700013562 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.folder');

/**
 * File installer
 *
 * @since  3.1
 */
class FileAdapter extends InstallerAdapter
{
	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = false;

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Populate File and Folder List to copy
		$this->populateFilesAndFolderList();

		// Now that we have folder list, lets start creating them
		foreach ($this->folderList as $folder)
		{
			if (!\JFolder::exists($folder))
			{
				if (!$created = \JFolder::create($folder))
				{
					throw new \RuntimeException(
						\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $folder)
					);
				}

				// Since we created a directory and will want to remove it if we have to roll back.
				// The installation due to some errors, let's add it to the installation step stack.
				if ($created)
				{
					$this->parent->pushStep(array('type' => 'folder', 'path' => $folder));
				}
			}
		}

		// Now that we have file list, let's start copying them
		$this->parent->copyFiles($this->fileList);
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		$update = Table::getInstance('update');

		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		$manifest = array();
		$manifest['src'] = $this->parent->getPath('manifest');
		$manifest['dest'] = JPATH_MANIFESTS . '/files/' . basename($this->parent->getPath('manifest'));

		if (!$this->parent->copyFiles(array($manifest), true))
		{
			// Install failed, rollback changes
			throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_FILE_INSTALL_COPY_SETUP'));
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			// First, we have to create a folder for the script if one isn't present
			if (!file_exists($this->parent->getPath('extension_root')))
			{
				\JFolder::create($this->parent->getPath('extension_root'));
			}

			$path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
						)
					);
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			$manifestPath = \JPath::clean($this->parent->getPath('manifest'));
			$element = preg_replace('/\.xml/', '', basename($manifestPath));
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path on which to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path)
	{
		$extension = 'files_' . strtolower(str_replace('files_', '', $this->getElement()));

		$this->doLoadLanguage($extension, $path, JPATH_SITE);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseLanguages($this->getManifest()->languages);
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupInstallPaths()
	{
		// Set the file root path
		if ($this->name === 'files_joomla')
		{
			// If we are updating the Joomla core, set the root path to the root of Joomla
			$this->parent->setPath('extension_root', JPATH_ROOT);
		}
		else
		{
			$this->parent->setPath('extension_root', JPATH_MANIFESTS . '/files/' . $this->element);
		}
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		if ($this->currentExtensionId)
		{
			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);

			// Update name
			$this->extension->name = $this->name;

			// Update manifest
			$this->extension->manifest_cache = $this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}
		}
		else
		{
			// Add an entry to the extension table with a whole heap of defaults
			$this->extension->name = $this->name;
			$this->extension->type = 'file';
			$this->extension->element = $this->element;

			// There is no folder for files so leave it blank
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 0;
			$this->extension->client_id = 0;
			$this->extension->params = '';
			$this->extension->system_data = '';
			$this->extension->manifest_cache = $this->parent->generateManifestCache();
			$this->extension->custom_data = '';

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a module item, we add it to the installation step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(array('type' => 'extension', 'extension_id' => $this->extension->extension_id));
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $id  The id of the file to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$row = Table::getInstance('extension');

		if (!$row->load($id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_ENTRY'), \JLog::WARNING, 'jerror');

			return false;
		}

		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_WARNCOREFILE'), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$retval = true;
		$manifestFile = JPATH_MANIFESTS . '/files/' . $row->element . '.xml';

		// Because files may not have their own folders we cannot use the standard method of finding an installation manifest
		if (file_exists($manifestFile))
		{
			// Set the files root path
			$this->parent->setPath('extension_root', JPATH_MANIFESTS . '/files/' . $row->element);

			$xml = simplexml_load_file($manifestFile);

			// If we cannot load the XML file return null
			if (!$xml)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_LOAD_MANIFEST'), \JLog::WARNING, 'jerror');

				return false;
			}

			// Check for a valid XML root tag.
			if ($xml->getName() !== 'extension')
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_MANIFEST'), \JLog::WARNING, 'jerror');

				return false;
			}

			$this->setManifest($xml);

			// If there is a manifest class file, let's load it
			$this->scriptElement = $this->getManifest()->scriptfile;
			$manifestScript = (string) $this->getManifest()->scriptfile;

			if ($manifestScript)
			{
				$manifestScriptFile = $this->parent->getPath('extension_root') . '/' . $manifestScript;

				// Set the class name
				$classname = $row->element . 'InstallerScript';

				\JLoader::register($classname, $manifestScriptFile);

				if (class_exists($classname))
				{
					// Create a new instance
					$this->parent->manifestClass = new $classname($this);

					// And set this so we can copy it later
					$this->set('manifest_script', $manifestScript);
				}
			}

			ob_start();
			ob_implicit_flush(false);

			// Run uninstall if possible
			if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'uninstall'))
			{
				$this->parent->manifestClass->uninstall($this);
			}

			$msg = ob_get_contents();
			ob_end_clean();

			if ($msg != '')
			{
				$this->parent->set('extension_message', $msg);
			}

			$db = \JFactory::getDbo();

			// Let's run the uninstall queries for the extension
			$result = $this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);

			if ($result === false)
			{
				// Install failed, rollback changes
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_SQL_ERROR', $db->stderr(true)), \JLog::WARNING, 'jerror');
				$retval = false;
			}

			// Remove the schema version
			$query = $db->getQuery(true)
				->delete('#__schemas')
				->where('extension_id = ' . $row->extension_id);
			$db->setQuery($query);
			$db->execute();

			// Loop through all elements and get list of files and folders
			foreach ($xml->fileset->files as $eFiles)
			{
				$target = (string) $eFiles->attributes()->target;

				// Create folder path
				if (empty($target))
				{
					$targetFolder = JPATH_ROOT;
				}
				else
				{
					$targetFolder = JPATH_ROOT . '/' . $target;
				}

				$folderList = array();

				// Check if all children exists
				if (count($eFiles->children()) > 0)
				{
					// Loop through all filenames elements
					foreach ($eFiles->children() as $eFileName)
					{
						if ($eFileName->getName() === 'folder')
						{
							$folderList[] = $targetFolder . '/' . $eFileName;
						}
						else
						{
							$fileName = $targetFolder . '/' . $eFileName;
							\JFile::delete($fileName);
						}
					}
				}

				// Delete any folders that don't have any content in them.
				foreach ($folderList as $folder)
				{
					$files = \JFolder::files($folder);

					if ($files !== false && !count($files))
					{
						\JFolder::delete($folder);
					}
				}
			}

			\JFile::delete($manifestFile);

			// Lastly, remove the extension_root
			$folder = $this->parent->getPath('extension_root');

			if (\JFolder::exists($folder))
			{
				\JFolder::delete($folder);
			}
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_FILE_UNINSTALL_INVALID_NOTFOUND_MANIFEST'), \JLog::WARNING, 'jerror');

			// Delete the row because its broken
			$row->delete();

			return false;
		}

		$this->parent->removeFiles($xml->languages);

		$row->delete();

		return $retval;
	}

	/**
	 * Function used to check if extension is already installed
	 *
	 * @param   string  $extension  The element name of the extension to install
	 *
	 * @return  boolean  True if extension exists
	 *
	 * @since   3.1
	 */
	protected function extensionExistsInSystem($extension = null)
	{
		// Get a database connector object
		$db = $this->parent->getDbo();

		$query = $db->getQuery(true)
			->select($db->quoteName('extension_id'))
			->from($db->quoteName('#__extensions'))
			->where($db->quoteName('type') . ' = ' . $db->quote('file'))
			->where($db->quoteName('element') . ' = ' . $db->quote($extension));
		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', $db->stderr(true)));

			return false;
		}

		$id = $db->loadResult();

		if (empty($id))
		{
			return false;
		}

		return true;
	}

	/**
	 * Function used to populate files and folder list
	 *
	 * @return  boolean  none
	 *
	 * @since   3.1
	 */
	protected function populateFilesAndFolderList()
	{
		// Initialise variable
		$this->folderList = array();
		$this->fileList = array();

		// Set root folder names
		$packagePath = $this->parent->getPath('source');
		$jRootPath = \JPath::clean(JPATH_ROOT);

		// Loop through all elements and get list of files and folders
		foreach ($this->getManifest()->fileset->files as $eFiles)
		{
			// Check if the element is files element
			$folder = (string) $eFiles->attributes()->folder;
			$target = (string) $eFiles->attributes()->target;

			// Split folder names into array to get folder names. This will help in creating folders
			$arrList = preg_split("#/|\\/#", $target);

			$folderName = $jRootPath;

			foreach ($arrList as $dir)
			{
				if (empty($dir))
				{
					continue;
				}

				$folderName .= '/' . $dir;

				// Check if folder exists, if not then add to the array for folder creation
				if (!\JFolder::exists($folderName))
				{
					$this->folderList[] = $folderName;
				}
			}

			// Create folder path
			$sourceFolder = empty($folder) ? $packagePath : $packagePath . '/' . $folder;
			$targetFolder = empty($target) ? $jRootPath : $jRootPath . '/' . $target;

			// Check if source folder exists
			if (!\JFolder::exists($sourceFolder))
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_FAIL_SOURCE_DIRECTORY', $sourceFolder), \JLog::WARNING, 'jerror');

				// If installation fails, rollback
				$this->parent->abort();

				return false;
			}

			// Check if all children exists
			if (count($eFiles->children()))
			{
				// Loop through all filenames elements
				foreach ($eFiles->children() as $eFileName)
				{
					$path['src'] = $sourceFolder . '/' . $eFileName;
					$path['dest'] = $targetFolder . '/' . $eFileName;
					$path['type'] = 'file';

					if ($eFileName->getName() === 'folder')
					{
						$folderName         = $targetFolder . '/' . $eFileName;
						$this->folderList[] = $folderName;
						$path['type']       = 'folder';
					}

					$this->fileList[] = $path;
				}
			}
			else
			{
				$files = \JFolder::files($sourceFolder);

				foreach ($files as $file)
				{
					$path['src'] = $sourceFolder . '/' . $file;
					$path['dest'] = $targetFolder . '/' . $file;

					$this->fileList[] = $path;
				}
			}
		}
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$manifestPath = JPATH_MANIFESTS . '/files/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Adapter/ModuleAdapter.php000064400000050421152177723700014124 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\Utilities\ArrayHelper;

\JLoader::import('joomla.filesystem.folder');

/**
 * Module installer
 *
 * @since  3.1
 */
class ModuleAdapter extends InstallerAdapter
{
	/**
	 * The install client ID
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $clientId;

	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array(
					'element'   => $this->element,
					'type'      => $this->type,
					'client_id' => $this->clientId,
				)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all necessary files
		if ($this->parent->parseFiles($this->getManifest()->files, -1) === false)
		{
			throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_COPY_FILES'));
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_MANIFEST'));
				}
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid    = $update->find(
			array(
				'element'   => $this->element,
				'type'      => 'module',
				'client_id' => $this->clientId,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_MOD_INSTALL_COPY_SETUP'));
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			if (count($this->getManifest()->files->children()))
			{
				foreach ($this->getManifest()->files->children() as $file)
				{
					if ((string) $file->attributes()->module)
					{
						$element = strtolower((string) $file->attributes()->module);

						break;
					}
				}
			}
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where we find language files
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');
		$client = $this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $client . '/modules/' . $this->parent->extension->element);
		}

		$this->setManifest($this->parent->getManifest());

		if ($this->getManifest()->files)
		{
			$extension = $this->getElement();

			if ($extension)
			{
				$source = $path ?: ($this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $extension;
				$folder = (string) $this->getManifest()->files->attributes()->folder;

				if ($folder && file_exists($path . '/' . $folder))
				{
					$source = $path . '/' . $folder;
				}

				$client = (string) $this->getManifest()->attributes()->client;
				$this->doLoadLanguage($extension, $source, constant('JPATH_' . strtoupper($client)));
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media, $this->clientId);
		$this->parent->parseLanguages($this->getManifest()->languages, $this->clientId);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Get the target application
		$cname = (string) $this->getManifest()->attributes()->client;

		if ($cname)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_UNKNOWN_CLIENT',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$client->name
					)
				);
			}

			$basePath = $client->path;
			$this->clientId = $client->id;
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$basePath = JPATH_SITE;
			$this->clientId = 0;
		}

		// Set the installation path
		if (empty($this->element))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		$this->parent->setPath('extension_root', $basePath . '/modules/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_MOD_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a module already installed with the same name?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_INSTALL_ALLREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);

			// Update name
			$this->extension->name = $this->name;

			// Update manifest
			$this->extension->manifest_cache = $this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}
		}
		else
		{
			$this->extension->name    = $this->name;
			$this->extension->type    = 'module';
			$this->extension->element = $this->element;

			// There is no folder for modules
			$this->extension->folder    = '';
			$this->extension->enabled   = 1;
			$this->extension->protected = 0;
			$this->extension->access    = $this->clientId == 1 ? 2 : 0;
			$this->extension->client_id = $this->clientId;
			$this->extension->params    = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data    = '';
			$this->extension->system_data    = '';
			$this->extension->manifest_cache = $this->parent->generateManifestCache();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_MOD_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a module item, we add it to the installation step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(
				array(
					'type' => 'extension',
					'extension_id' => $this->extension->extension_id,
				)
			);

			// Create unpublished module
			$name = preg_replace('#[\*?]#', '', \JText::_($this->name));

			/** @var \JTableModule $module */
			$module            = Table::getInstance('module');
			$module->title     = $name;
			$module->content   = '';
			$module->module    = $this->element;
			$module->access    = '1';
			$module->showtitle = '1';
			$module->params    = '';
			$module->client_id = $this->clientId;
			$module->language  = '*';

			$module->store();
		}
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$site_list = \JFolder::folders(JPATH_SITE . '/modules');
		$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR . '/modules');
		$site_info = ApplicationHelper::getClientInfo('site', true);
		$admin_info = ApplicationHelper::getClientInfo('administrator', true);

		foreach ($site_list as $module)
		{
			if (file_exists(JPATH_SITE . "/modules/$module/$module.xml"))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . "/modules/$module/$module.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'module');
				$extension->set('client_id', $site_info->id);
				$extension->set('element', $module);
				$extension->set('folder', '');
				$extension->set('name', $module);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = clone $extension;
			}
		}

		foreach ($admin_list as $module)
		{
			if (file_exists(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml"))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR . "/modules/$module/$module.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'module');
				$extension->set('client_id', $admin_info->id);
				$extension->set('element', $module);
				$extension->set('folder', '');
				$extension->set('name', $module);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = clone $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure.
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/modules/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the module to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$retval = true;
		$db     = $this->db;

		// First order of business will be to load the module object table from the database.
		// This should give us the necessary information to proceed.
		if (!$this->extension->load((int) $id) || $this->extension->element === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_ERRORUNKOWNEXTENSION'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Is the module we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($this->extension->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_WARNCOREMODULE', $this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the extension root path
		$element = $this->extension->element;
		$client  = ApplicationHelper::getClientInfo($this->extension->client_id);

		if ($client === false)
		{
			$this->parent->abort(
				\JText::sprintf(
					'JLIB_INSTALLER_ERROR_MOD_UNINSTALL_UNKNOWN_CLIENT',
					$this->extension->client_id
				)
			);

			return false;
		}

		$this->parent->setPath('extension_root', $client->path . '/modules/' . $element);

		$this->parent->setPath('source', $this->parent->getPath('extension_root'));

		// Get the module's manifest objecct
		// We do findManifest to avoid problem when uninstalling a list of extensions: getManifest cache its manifest file.
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		// Attempt to load the language file; might have uninstall strings
		$this->loadLanguage(($this->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/modules/' . $element);

		// If there is a manifest class file, let's load it
		$this->scriptElement = $this->getManifest()->scriptfile;
		$manifestScript      = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('extension_root') . '/' . $manifestScript;

			// Set the class name
			$classname = $element . 'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		try
		{
			$this->triggerManifestScript('uninstall');
		}
		catch (\RuntimeException $e)
		{
			// Ignore errors for now
		}

		if (!($this->getManifest() instanceof \SimpleXMLElement))
		{
			// Make sure we delete the folders
			\JFolder::delete($this->parent->getPath('extension_root'));
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_INVALID_NOTFOUND_MANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Let's run the uninstall queries for the module
		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			// Install failed, rollback changes
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');
			$retval = false;
		}

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' . $this->extension->extension_id);
		$db->setQuery($query);
		$db->execute();

		// Remove other files
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages, $this->extension->client_id);

		// Let's delete all the module copies for the type we are uninstalling
		$query->clear()
			->select($db->quoteName('id'))
			->from($db->quoteName('#__modules'))
			->where($db->quoteName('module') . ' = ' . $db->quote($this->extension->element))
			->where($db->quoteName('client_id') . ' = ' . (int) $this->extension->client_id);
		$db->setQuery($query);

		try
		{
			$modules = $db->loadColumn();
		}
		catch (\RuntimeException $e)
		{
			$modules = array();
		}

		// Do we have any module copies?
		if (count($modules))
		{
			// Ensure the list is sane
			$modules = ArrayHelper::toInteger($modules);
			$modID = implode(',', $modules);

			// Wipe out any items assigned to menus
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__modules_menu'))
				->where($db->quoteName('moduleid') . ' IN (' . $modID . ')');
			$db->setQuery($query);

			try
			{
				$db->execute();
			}
			catch (\RuntimeException $e)
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $db->stderr(true)), \JLog::WARNING, 'jerror');
				$retval = false;
			}

			// Wipe out any instances in the modules table
			/** @var \JTableModule $module */
			$module = Table::getInstance('Module');

			foreach ($modules as $modInstanceId)
			{
				$module->load($modInstanceId);

				if (!$module->delete())
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_MOD_UNINSTALL_EXCEPTION', $module->getError()), \JLog::WARNING, 'jerror');
					$retval = false;
				}
			}
		}

		// Now we will no longer need the module object, so let's delete it and free up memory
		$this->extension->delete($this->extension->extension_id);
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules'))
			->where($db->quoteName('module') . ' = ' . $db->quote($this->extension->element))
			->where($db->quoteName('client_id') . ' = ' . (int) $this->extension->client_id);
		$db->setQuery($query);

		try
		{
			// Clean up any other ones that might exist as well
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Ignore the error...
		}

		// Remove the installation folder
		if (!\JFolder::delete($this->parent->getPath('extension_root')))
		{
			// \JFolder should raise an error
			$retval = false;
		}

		return $retval;
	}

	/**
	 * Custom rollback method
	 * - Roll back the menu item
	 *
	 * @param   array  $arg  Installation step to rollback
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_menu($arg)
	{
		// Get database connector object
		$db = $this->parent->getDbo();

		// Remove the entry from the #__modules_menu table
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules_menu'))
			->where($db->quoteName('moduleid') . ' = ' . (int) $arg['id']);
		$db->setQuery($query);

		try
		{
			return $db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}
	}

	/**
	 * Custom rollback method
	 * - Roll back the module item
	 *
	 * @param   array  $arg  Installation step to rollback
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_module($arg)
	{
		// Get database connector object
		$db = $this->parent->getDbo();

		// Remove the entry from the #__modules table
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__modules'))
			->where($db->quoteName('id') . ' = ' . (int) $arg['id']);
		$db->setQuery($query);

		try
		{
			return $db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}
	}
}
src/Installer/Adapter/LanguageAdapter.php000064400000060003152177723700014417 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;

jimport('joomla.filesystem.folder');

/**
 * Language installer
 *
 * @since  3.1
 */
class LanguageAdapter extends InstallerAdapter
{
	/**
	 * Core language pack flag
	 *
	 * @var    boolean
	 * @since  3.0.0
	 */
	protected $core = false;

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupInstallPaths()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// TODO - Refactor adapter to use common code
	}

	/**
	 * Custom install method
	 *
	 * Note: This behaves badly due to hacks made in the middle of 1.5.x to add
	 * the ability to install multiple distinct packs in one install. The
	 * preferred method is to use a package to install multiple language packs.
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on failure
	 *
	 * @since   3.1
	 */
	public function install()
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent
				->setPath(
				'source',
				($this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE) . '/language/' . $this->parent->extension->element
			);
		}

		$this->setManifest($this->parent->getManifest());

		// Get the client application target
		if ($cname = (string) $this->getManifest()->attributes()->client)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === null)
			{
				$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', \JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE', $cname)));

				return false;
			}

			$basePath = $client->path;
			$clientId = $client->id;
			$element  = $this->getManifest()->files;

			return $this->_install($cname, $basePath, $clientId, $element);
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$cname    = 'site';
			$basePath = JPATH_SITE;
			$clientId = 0;
			$element  = $this->getManifest()->files;

			return $this->_install($cname, $basePath, $clientId, $element);
		}
	}

	/**
	 * Install function that is designed to handle individual clients
	 *
	 * @param   string   $cname     Cname @todo: not used
	 * @param   string   $basePath  The base name.
	 * @param   integer  $clientId  The client id.
	 * @param   object   &$element  The XML element.
	 *
	 * @return  boolean|integer  The extension ID on success, boolean false on failure
	 *
	 * @since   3.1
	 */
	protected function _install($cname, $basePath, $clientId, &$element)
	{
		$this->setManifest($this->parent->getManifest());

		// Get the language name
		// Set the extensions name
		$name = \JFilterInput::getInstance()->clean((string) $this->getManifest()->name, 'cmd');
		$this->set('name', $name);

		// Get the Language tag [ISO tag, eg. en-GB]
		$tag = (string) $this->getManifest()->tag;

		// Check if we found the tag - if we didn't, we may be trying to install from an older language package
		if (!$tag)
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', \JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));

			return false;
		}

		$this->set('tag', $tag);

		// Set the language installation path
		$this->parent->setPath('extension_site', $basePath . '/language/' . $tag);

		// Do we have a meta file in the file list?  In other words... is this a core language pack?
		if ($element && count($element->children()))
		{
			$files = $element->children();

			foreach ($files as $file)
			{
				if ((string) $file->attributes()->file === 'meta')
				{
					$this->core = true;
					break;
				}
			}
		}

		// If the language directory does not exist, let's create it
		$created = false;

		if (!file_exists($this->parent->getPath('extension_site')))
		{
			if (!$created = \JFolder::create($this->parent->getPath('extension_site')))
			{
				$this->parent
					->abort(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT',
						\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_FOLDER_FAILED', $this->parent->getPath('extension_site'))
					)
				);

				return false;
			}
		}
		else
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update tag detected
			if ($updateElement || $this->parent->isUpgrade())
			{
				// Transfer control to the update function
				return $this->update();
			}
			elseif (!$this->parent->isOverwrite())
			{
				// Overwrite is set
				// We didn't have overwrite set, find an update function or find an update tag so lets call it safe
				if (file_exists($this->parent->getPath('extension_site')))
				{
					// If the site exists say so.
					\JLog::add(
						\JText::sprintf('JLIB_INSTALLER_ABORT', \JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE', $this->parent->getPath('extension_site'))),
						\JLog::WARNING, 'jerror'
					);
				}
				else
				{
					// If the admin exists say so.
					\JLog::add(
						\JText::sprintf('JLIB_INSTALLER_ABORT',
							\JText::sprintf('JLIB_INSTALLER_ERROR_FOLDER_IN_USE', $this->parent->getPath('extension_administrator'))
						),
						\JLog::WARNING, 'jerror'
					);
				}

				return false;
			}
		}

		/*
		 * If we created the language directory we will want to remove it if we
		 * have to roll back the installation, so let's add it to the installation
		 * step stack
		 */
		if ($created)
		{
			$this->parent->pushStep(array('type' => 'folder', 'path' => $this->parent->getPath('extension_site')));
		}

		// Copy all the necessary files
		if ($this->parent->parseFiles($element) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media);

		// Copy all the necessary font files to the common pdf_fonts directory
		$this->parent->setPath('extension_site', $basePath . '/language/pdf_fonts');
		$overwrite = $this->parent->setOverwrite(true);

		if ($this->parent->parseFiles($this->getManifest()->fonts) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		$this->parent->setOverwrite($overwrite);

		// Get the language description
		$description = (string) $this->getManifest()->description;

		if ($description)
		{
			$this->parent->set('message', \JText::_($description));
		}
		else
		{
			$this->parent->set('message', '');
		}

		// Add an entry to the extension table with a whole heap of defaults
		$row = Table::getInstance('extension');
		$row->set('name', $this->get('name'));
		$row->set('type', 'language');
		$row->set('element', $this->get('tag'));

		// There is no folder for languages
		$row->set('folder', '');
		$row->set('enabled', 1);
		$row->set('protected', 0);
		$row->set('access', 0);
		$row->set('client_id', $clientId);
		$row->set('params', $this->parent->getParams());
		$row->set('manifest_cache', $this->parent->generateManifestCache());

		if (!$row->check() || !$row->store())
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', $row->getError()));

			return false;
		}

		// Create an unpublished content language.
		if ((int) $clientId === 0)
		{
			// Load the site language manifest.
			$siteLanguageManifest = LanguageHelper::parseXMLLanguageFile(JPATH_SITE . '/language/' . $this->tag . '/' . $this->tag . '.xml');

			// Set the content language title as the language metadata name.
			$contentLanguageTitle = $siteLanguageManifest['name'];

			// Set, as fallback, the content language native title to the language metadata name.
			$contentLanguageNativeTitle = $contentLanguageTitle;

			// If exist, load the native title from the language xml metadata.
			if (isset($siteLanguageManifest['nativeName']) && $siteLanguageManifest['nativeName'])
			{
				$contentLanguageNativeTitle = $siteLanguageManifest['nativeName'];
			}

			// Try to load a language string from the installation language var. Will be removed in 4.0.
			if ($contentLanguageNativeTitle === $contentLanguageTitle)
			{
				if (file_exists(JPATH_INSTALLATION . '/language/' . $this->tag . '/' . $this->tag . '.xml'))
				{
					$installationLanguage = new Language($this->tag);
					$installationLanguage->load('', JPATH_INSTALLATION);

					if ($installationLanguage->hasKey('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME'))
					{
						// Make sure it will not use the en-GB fallback.
						$defaultLanguage = new Language('en-GB');
						$defaultLanguage->load('', JPATH_INSTALLATION);

						$defaultLanguageNativeTitle      = $defaultLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
						$installationLanguageNativeTitle = $installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');

						if ($defaultLanguageNativeTitle !== $installationLanguageNativeTitle)
						{
							$contentLanguageNativeTitle = $installationLanguage->_('INSTL_DEFAULTLANGUAGE_NATIVE_LANGUAGE_NAME');
						}
					}
				}
			}

			// Prepare language data for store.
			$languageData = array(
				'lang_id'      => 0,
				'lang_code'    => $this->tag,
				'title'        => $contentLanguageTitle,
				'title_native' => $contentLanguageNativeTitle,
				'sef'          => $this->getSefString($this->tag),
				'image'        => strtolower(str_replace('-', '_', $this->tag)),
				'published'    => 0,
				'ordering'     => 0,
				'access'       => (int) \JFactory::getConfig()->get('access', 1),
				'description'  => '',
				'metakey'      => '',
				'metadesc'     => '',
				'sitename'     => '',
			);

			$tableLanguage = Table::getInstance('language');

			if (!$tableLanguage->bind($languageData) || !$tableLanguage->check() || !$tableLanguage->store() || !$tableLanguage->reorder())
			{
				\JLog::add(
					\JText::sprintf('JLIB_INSTALLER_WARNING_UNABLE_TO_INSTALL_CONTENT_LANGUAGE', $siteLanguageManifest['name'], $tableLanguage->getError()),
					\JLog::NOTICE,
					'jerror'
				);
			}
		}

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(array('element' => $this->get('tag'), 'type' => 'language', 'folder' => ''));

		if ($uid)
		{
			$update->delete($uid);
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		return $row->get('extension_id');
	}


	/**
	 * Gets a unique language SEF string.
	 *
	 * This function checks other existing language with the same code, if they exist provides a unique SEF name.
	 * For instance: en-GB, en-US and en-AU will share the same SEF code by default: www.mywebsite.com/en/
	 * To avoid this conflict, this function creates an specific SEF in case of existing conflict:
	 * For example: www.mywebsite.com/en-au/
	 *
	 * @param   string  $itemLanguageTag  Language Tag.
	 *
	 * @return  string
	 *
	 * @since   3.7.0
	 */
	protected function getSefString($itemLanguageTag)
	{
		$langs               = explode('-', $itemLanguageTag);
		$prefixToFind        = $langs[0];
		$numberPrefixesFound = 0;

		// Get the sef value of all current content languages.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->qn('sef'))
			->from($db->qn('#__languages'));
		$db->setQuery($query);

		$siteLanguages = $db->loadObjectList();

		foreach ($siteLanguages as $siteLang)
		{
			if ($siteLang->sef === $prefixToFind)
			{
				$numberPrefixesFound++;
			}
		}

		return $numberPrefixesFound === 0 ? $prefixToFind : strtolower($itemLanguageTag);
	}

	/**
	 * Custom update method
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @since   3.1
	 */
	public function update()
	{
		$xml = $this->parent->getManifest();

		$this->setManifest($xml);

		$cname = $xml->attributes()->client;

		// Attempt to map the client to a base path
		$client = ApplicationHelper::getClientInfo($cname, true);

		if ($client === null || (empty($cname) && $cname !== 0))
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', \JText::sprintf('JLIB_INSTALLER_ERROR_UNKNOWN_CLIENT_TYPE', $cname)));

			return false;
		}

		$basePath = $client->path;
		$clientId = $client->id;

		// Get the language name
		// Set the extensions name
		$name = (string) $this->getManifest()->name;
		$name = \JFilterInput::getInstance()->clean($name, 'cmd');
		$this->set('name', $name);

		// Get the Language tag [ISO tag, eg. en-GB]
		$tag = (string) $xml->tag;

		// Check if we found the tag - if we didn't, we may be trying to install from an older language package
		if (!$tag)
		{
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', \JText::_('JLIB_INSTALLER_ERROR_NO_LANGUAGE_TAG')));

			return false;
		}

		$this->set('tag', $tag);

		// Set the language installation path
		$this->parent->setPath('extension_site', $basePath . '/language/' . $tag);

		// Do we have a meta file in the file list?  In other words... is this a core language pack?
		if (count($xml->files->children()))
		{
			foreach ($xml->files->children() as $file)
			{
				if ((string) $file->attributes()->file === 'meta')
				{
					$this->core = true;
					break;
				}
			}
		}

		// Copy all the necessary files
		if ($this->parent->parseFiles($xml->files) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		// Parse optional tags
		$this->parent->parseMedia($xml->media);

		// Copy all the necessary font files to the common pdf_fonts directory
		$this->parent->setPath('extension_site', $basePath . '/language/pdf_fonts');
		$overwrite = $this->parent->setOverwrite(true);

		if ($this->parent->parseFiles($xml->fonts) === false)
		{
			// Install failed, rollback changes
			$this->parent->abort();

			return false;
		}

		$this->parent->setOverwrite($overwrite);

		// Get the language description and set it as message
		$this->parent->set('message', (string) $xml->description);

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Finalization and Cleanup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(array('element' => $this->get('tag'), 'type' => 'language', 'client_id' => $clientId));

		if ($uid)
		{
			$update->delete($uid);
		}

		// Update an entry to the extension table
		$row = Table::getInstance('extension');
		$eid = $row->find(array('element' => $this->get('tag'), 'type' => 'language', 'client_id' => $clientId));

		if ($eid)
		{
			$row->load($eid);
		}
		else
		{
			// Set the defaults

			// There is no folder for language
			$row->set('folder', '');
			$row->set('enabled', 1);
			$row->set('protected', 0);
			$row->set('access', 0);
			$row->set('client_id', $clientId);
			$row->set('params', $this->parent->getParams());
		}

		$row->set('name', $this->get('name'));
		$row->set('type', 'language');
		$row->set('element', $this->get('tag'));
		$row->set('manifest_cache', $this->parent->generateManifestCache());

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		if (!$row->check() || !$row->store())
		{
			// Install failed, roll back changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT', $row->getError()));

			return false;
		}

		return $row->get('extension_id');
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $eid  The tag of the language to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($eid)
	{
		// Load up the extension details
		$extension = Table::getInstance('extension');
		$extension->load($eid);

		// Grab a copy of the client details
		$client = ApplicationHelper::getClientInfo($extension->get('client_id'));

		// Check the element isn't blank to prevent nuking the languages directory...just in case
		$element = $extension->get('element');

		if (empty($element))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_ELEMENT_EMPTY'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Check that the language is not protected, Normally en-GB.
		$protected = $extension->get('protected');

		if ($protected == 1)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PROTECTED'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Verify that it's not the default language for that client
		$params = ComponentHelper::getParams('com_languages');

		if ($params->get($client->name) === $element)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DEFAULT'), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Construct the path from the client, the language and the extension element name
		$path = $client->path . '/language/' . $element;

		// Get the package manifest object and remove media
		$this->parent->setPath('source', $path);

		// We do findManifest to avoid problem when uninstalling a list of extension: getManifest cache its manifest file
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());
		$this->parent->removeFiles($this->getManifest()->media);

		// Check it exists
		if (!\JFolder::exists($path))
		{
			// If the folder doesn't exist lets just nuke the row as well and presume the user killed it for us
			$extension->delete();
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_PATH_EMPTY'), \JLog::WARNING, 'jerror');

			return false;
		}

		if (!\JFolder::delete($path))
		{
			// If deleting failed we'll leave the extension entry in tact just in case
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_UNINSTALL_DIRECTORY'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Remove the extension table entry
		$extension->delete();

		// Setting the language of users which have this language as the default language
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->from('#__users')
			->select('*');
		$db->setQuery($query);
		$users = $db->loadObjectList();

		if ($client->name === 'administrator')
		{
			$param_name = 'admin_language';
		}
		else
		{
			$param_name = 'language';
		}

		$count = 0;

		foreach ($users as $user)
		{
			$registry = new Registry($user->params);

			if ($registry->get($param_name) === $element)
			{
				$registry->set($param_name, '');
				$query->clear()
					->update('#__users')
					->set('params=' . $db->quote($registry))
					->where('id=' . (int) $user->id);
				$db->setQuery($query);
				$db->execute();
				$count++;
			}
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		if (!empty($count))
		{
			\JLog::add(\JText::plural('JLIB_INSTALLER_NOTICE_LANG_RESET_USERS', $count), \JLog::NOTICE, 'jerror');
		}

		// All done!
		return true;
	}

	/**
	 * Custom discover method
	 * Finds language files
	 *
	 * @return  boolean  True on success
	 *
	 * @since  3.1
	 */
	public function discover()
	{
		$results = array();
		$site_languages = \JFolder::folders(JPATH_SITE . '/language');
		$admin_languages = \JFolder::folders(JPATH_ADMINISTRATOR . '/language');

		foreach ($site_languages as $language)
		{
			if (file_exists(JPATH_SITE . '/language/' . $language . '/' . $language . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . '/language/' . $language . '/' . $language . '.xml');
				$extension = Table::getInstance('extension');
				$extension->set('type', 'language');
				$extension->set('client_id', 0);
				$extension->set('element', $language);
				$extension->set('folder', '');
				$extension->set('name', $language);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_languages as $language)
		{
			if (file_exists(JPATH_ADMINISTRATOR . '/language/' . $language . '/' . $language . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR . '/language/' . $language . '/' . $language . '.xml');
				$extension = Table::getInstance('extension');
				$extension->set('type', 'language');
				$extension->set('client_id', 1);
				$extension->set('element', $language);
				$extension->set('folder', '');
				$extension->set('name', $language);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Custom discover install method
	 * Basically updates the manifest cache and leaves everything alone
	 *
	 * @return  integer  The extension id
	 *
	 * @since   3.1
	 */
	public function discover_install()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$short_element = $this->parent->extension->element;
		$manifestPath = $client->path . '/language/' . $short_element . '/' . $short_element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->parent->setPath('source', $client->path . '/language/' . $short_element);
		$this->parent->setPath('extension_root', $this->parent->getPath('source'));
		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->state = 0;
		$this->parent->extension->name = $manifest_details['name'];
		$this->parent->extension->enabled = 1;

		// @todo remove code: $this->parent->extension->params = $this->parent->getParams();
		try
		{
			$this->parent->extension->check();
			$this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LANG_DISCOVER_STORE_DETAILS'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Clean installed languages cache.
		\JFactory::getCache()->clean('com_languages');

		return $this->parent->extension->get('extension_id');
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/language/' . $this->parent->extension->element . '/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_MOD_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Adapter/LibraryAdapter.php000064400000031723152177723700014307 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\Manifest\LibraryManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Library installer
 *
 * @since  3.1
 */
class LibraryAdapter extends InstallerAdapter
{
	/**
	 * Method to check if the extension is present in the filesystem, flags the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		if ($this->currentExtensionId)
		{
			// Already installed, can we upgrade?
			if ($this->parent->isOverwrite() || $this->parent->isUpgrade())
			{
				// We can upgrade, so uninstall the old one
				$installer = new Installer; // we don't want to compromise this instance!
				$installer->setPackageUninstall(true);
				$installer->uninstall('library', $this->currentExtensionId);

				// Clear the cached data
				$this->currentExtensionId = null;
				$this->extension = Table::getInstance('Extension', 'JTable', array('dbo' => $this->db));

				// From this point we'll consider this an update
				$this->setRoute('update');
			}
			else
			{
				// Abort the install, no upgrade possible
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_ALREADY_INSTALLED'));
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		if ($this->parent->parseFiles($this->getManifest()->files, -1) === false)
		{
			throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_COPY_FILES'));
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			$manifest = array();
			$manifest['src'] = $this->parent->getPath('manifest');
			$manifest['dest'] = JPATH_MANIFESTS . '/libraries/' . $this->element . '.xml';

			$destFolder = dirname($manifest['dest']);

			if (!is_dir($destFolder) && !@mkdir($destFolder))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
			}

			if (!$this->parent->copyFiles(array($manifest), true))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_COPY_SETUP'));
			}

			// If there is a manifest script, let's copy it.
			if ($this->manifest_script)
			{
				$path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script;
				$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

				if ($this->parent->isOverwrite() || !file_exists($path['dest']))
				{
					if (!$this->parent->copyFiles(array($path)))
					{
						// Install failed, rollback changes
						throw new \RuntimeException(
							\JText::sprintf(
								'JLIB_INSTALLER_ABORT_MANIFEST',
								\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
							)
						);
					}
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			$element  = (string) $this->getManifest()->libraryname;
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent->setPath('source', JPATH_PLATFORM . '/' . $this->getElement());
		}

		$extension = 'lib_' . str_replace('/', '_', $this->getElement());
		$librarypath = (string) $this->getManifest()->libraryname;
		$source = $path ?: JPATH_PLATFORM . '/' . $librarypath;

		$this->doLoadLanguage($extension, $source, JPATH_SITE);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseLanguages($this->getManifest()->languages);
		$this->parent->parseMedia($this->getManifest()->media);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$manifestPath = JPATH_MANIFESTS . '/libraries/' . $this->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$group = (string) $this->getManifest()->libraryname;

		if (!$group)
		{
			throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_LIB_INSTALL_NOFILE'));
		}

		$this->parent->setPath('extension_root', JPATH_PLATFORM . '/' . implode(DIRECTORY_SEPARATOR, explode('/', $group)));
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_LIB_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		$this->extension->name = $this->name;
		$this->extension->type = 'library';
		$this->extension->element = $this->element;

		// There is no folder for libraries
		$this->extension->folder = '';
		$this->extension->enabled = 1;
		$this->extension->protected = 0;
		$this->extension->access = 1;
		$this->extension->client_id = 0;
		$this->extension->params = $this->parent->getParams();

		// Custom data
		$this->extension->custom_data = '';
		$this->extension->system_data = '';

		// Update the manifest cache for the entry
		$this->extension->manifest_cache = $this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_LIB_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		// Since we have created a library item, we add it to the installation step stack
		// so that if we have to rollback the changes we can undo it.
		$this->parent->pushStep(array('type' => 'extension', 'id' => $this->extension->extension_id));
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   string  $id  The id of the library to uninstall.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$retval = true;

		// First order of business will be to load the module object table from the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id) || $row->element === '')
		{
			\JLog::add(\JText::_('ERRORUNKOWNEXTENSION'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Is the library we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_WARNCORELIBRARY'), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$manifestFile = JPATH_MANIFESTS . '/libraries/' . $row->element . '.xml';

		// Because libraries may not have their own folders we cannot use the standard method of finding an installation manifest
		if (file_exists($manifestFile))
		{
			$manifest = new LibraryManifest($manifestFile);

			// Set the library root path
			$this->parent->setPath('extension_root', JPATH_PLATFORM . '/' . $manifest->libraryname);

			$xml = simplexml_load_file($manifestFile);

			// If we cannot load the XML file return null
			if (!$xml)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_LOAD_MANIFEST'), \JLog::WARNING, 'jerror');

				return false;
			}

			// Check for a valid XML root tag.
			if ($xml->getName() !== 'extension')
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_MANIFEST'), \JLog::WARNING, 'jerror');

				return false;
			}

			$this->parent->removeFiles($xml->files, -1);
			\JFile::delete($manifestFile);
		}
		else
		{
			// Remove this row entry since its invalid
			$row->delete($row->extension_id);
			unset($row);
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_UNINSTALL_INVALID_NOTFOUND_MANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		// TODO: Change this so it walked up the path backwards so we clobber multiple empties
		// If the folder is empty, let's delete it
		if (\JFolder::exists($this->parent->getPath('extension_root')))
		{
			if (is_dir($this->parent->getPath('extension_root')))
			{
				$files = \JFolder::files($this->parent->getPath('extension_root'));

				if (!count($files))
				{
					\JFolder::delete($this->parent->getPath('extension_root'));
				}
			}
		}

		$this->parent->removeFiles($xml->media);
		$this->parent->removeFiles($xml->languages);

		$elementParts = explode('/', $row->element);

		// Delete empty vendor folders
		if (2 === count($elementParts))
		{
			@rmdir(JPATH_MANIFESTS . '/libraries/' . $elementParts[0]);
			@rmdir(JPATH_PLATFORM . '/' . $elementParts[0]);
		}

		$row->delete($row->extension_id);
		unset($row);

		return $retval;
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension  list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();


		$mainFolder = JPATH_MANIFESTS . '/libraries';
		$folder = new \RecursiveDirectoryIterator($mainFolder);
		$iterator = new \RegexIterator(
			new \RecursiveIteratorIterator($folder),
			'/\.xml$/i',
			\RecursiveRegexIterator::GET_MATCH
		);

		foreach ($iterator as $file => $pattern)
		{
			$element = str_replace(array($mainFolder . DIRECTORY_SEPARATOR, '.xml'), '', $file);
			$manifestCache = Installer::parseXMLInstallFile($file);

			$extension = Table::getInstance('extension');
			$extension->set('type', 'library');
			$extension->set('client_id', 0);
			$extension->set('element', $element);
			$extension->set('folder', '');
			$extension->set('name', $element);
			$extension->set('state', -1);
			$extension->set('manifest_cache', json_encode($manifestCache));
			$extension->set('params', '{}');
			$results[] = $extension;
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$manifestPath = JPATH_MANIFESTS . '/libraries/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_LIB_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Adapter/PackageAdapter.php000064400000047017152177723700014241 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Installer\Manifest\PackageManifest;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

/**
 * Package installer
 *
 * @since  3.1
 */
class PackageAdapter extends InstallerAdapter
{
	/**
	 * Flag if the internal event callback has been registered
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	private static $eventRegistered = false;

	/**
	 * An array of extension IDs for each installed extension
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	protected $installedIds = array();

	/**
	 * The results of each installed extensions
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $results = array();

	/**
	 * Flag if the adapter supports discover installs
	 *
	 * Adapters should override this and set to false if discover install is unsupported
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $supportsDiscoverInstall = false;

	/**
	 * Method to check if the extension is present in the filesystem, flags the route as update if so
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		// If the package manifest already exists, then we will assume that the package is already installed.
		if (file_exists(JPATH_MANIFESTS . '/packages/' . basename($this->parent->getPath('manifest'))))
		{
			// Look for an update function or update tag
			$updateElement = $this->manifest->update;

			// Upgrade manually set or update function available or update tag detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')))
			{
				// Force this one
				$this->parent->setOverwrite(true);
				$this->parent->setUpgrade(true);

				if ($this->currentExtensionId)
				{
					// If there is a matching extension mark this as an update
					$this->setRoute('update');
				}
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find an update tag so lets call it safe
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->type,
						$this->parent->getPath('extension_root')
					)
				);
			}
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		$folder = (string) $this->getManifest()->files->attributes()->folder;
		$source = $this->parent->getPath('source');

		if ($folder)
		{
			$source .= '/' . $folder;
		}

		// Install all necessary files
		if (!count($this->getManifest()->files->children()))
		{
			throw new \RuntimeException(
				\JText::sprintf('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		// Add a callback for the `onExtensionAfterInstall` event so we can receive the installed extension ID
		if (!self::$eventRegistered)
		{
			self::$eventRegistered = true;
			\JEventDispatcher::getInstance()->register('onExtensionAfterInstall', array($this, 'onExtensionAfterInstall'));
		}

		foreach ($this->getManifest()->files->children() as $child)
		{
			$file = $source . '/' . (string) $child;

			if (is_dir($file))
			{
				// If it's actually a directory then fill it up
				$package = array();
				$package['dir'] = $file;
				$package['type'] = InstallerHelper::detectType($file);
			}
			else
			{
				// If it's an archive
				$package = InstallerHelper::unpack($file);
			}

			$tmpInstaller  = new Installer;
			$installResult = $tmpInstaller->install($package['dir']);

			if (!$installResult)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PACK_INSTALL_ERROR_EXTENSION',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						basename($file)
					)
				);
			}

			$this->results[] = array(
				'name'   => (string) $tmpInstaller->manifest->name,
				'result' => $installResult,
			);
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		/*
		 * For packages, we only need the extension root if copying manifest files; this step will be handled
		 * at that point if necessary
		 */
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type' => $this->type,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Set the package ID for each of the installed extensions to track the relationship
		if (!empty($this->installedIds))
		{
			$db = $this->db;
			$query = $db->getQuery(true)
				->update('#__extensions')
				->set($db->quoteName('package_id') . ' = ' . (int) $this->extension->extension_id)
				->where($db->quoteName('extension_id') . ' IN (' . implode(', ', $this->installedIds) . ')');

			try
			{
				$db->setQuery($query)->execute();
			}
			catch (\JDatabaseExceptionExecuting $e)
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_SETTING_PACKAGE_ID'), \JLog::WARNING, 'jerror');
			}
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		$manifest = array();
		$manifest['src'] = $this->parent->getPath('manifest');
		$manifest['dest'] = JPATH_MANIFESTS . '/packages/' . basename($this->parent->getPath('manifest'));

		if (!$this->parent->copyFiles(array($manifest), true))
		{
			// Install failed, rollback changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_COPY_SETUP',
					\JText::_('JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_FILES')
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			// First, we have to create a folder for the script if one isn't present
			if (!file_exists($this->parent->getPath('extension_root')))
			{
				if (!\JFolder::create($this->parent->getPath('extension_root')))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_CREATE_DIRECTORY',
							\JText::_('JLIB_INSTALLER_' . $this->route),
							$this->parent->getPath('extension_root')
						)
					);
				}

				/*
				 * Since we created the extension directory and will want to remove it if
				 * we have to roll back the installation, let's add it to the
				 * installation step stack
				 */

				$this->parent->pushStep(
					array(
						'type' => 'folder',
						'path' => $this->parent->getPath('extension_root'),
					)
				);
			}

			$path['src'] = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_PACKAGE_INSTALL_MANIFEST'));
				}
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Ensure the element is a string
			$element = (string) $this->getManifest()->packagename;

			// Filter the name for illegal characters
			$element = 'pkg_' . \JFilterInput::getInstance()->clean($element, 'cmd');
		}

		return $element;
	}

	/**
	 * Load language from a path
	 *
	 * @param   string  $path  The path of the language.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path)
	{
		$this->doLoadLanguage($this->getElement(), $path);
	}

	/**
	 * Handler for the `onExtensionAfterInstall` event
	 *
	 * @param   Installer        $installer  Installer instance managing the extension's installation
	 * @param   integer|boolean  $eid        The extension ID of the installed extension on success, boolean false on install failure
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function onExtensionAfterInstall(Installer $installer, $eid)
	{
		if ($eid !== false)
		{
			$this->installedIds[] = $eid;
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseLanguages($this->getManifest()->languages);
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$packagepath = (string) $this->getManifest()->packagename;

		if (empty($packagepath))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_NO_PACK',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		$this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . $packagepath);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ALREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			$this->extension->load($this->currentExtensionId);
			$this->extension->name = $this->name;
		}
		else
		{
			$this->extension->name = $this->name;
			$this->extension->type = 'package';
			$this->extension->element = $this->element;

			// There is no folder for packages
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = 0;

			// Custom data
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
			$this->extension->params = $this->parent->getParams();
		}

		// Update the manifest cache for the entry
		$this->extension->manifest_cache = $this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PACK_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		// Since we have created a package item, we add it to the installation step stack
		// so that if we have to rollback the changes we can undo it.
		$this->parent->pushStep(array('type' => 'extension', 'id' => $this->extension->extension_id));
	}

	/**
	 * Executes a custom install script method
	 *
	 * @param   string  $method  The install method to execute
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.4
	 */
	protected function triggerManifestScript($method)
	{
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, $method))
		{
			switch ($method)
			{
				// The preflight method takes the route as a param
				case 'preflight':
					if ($this->parent->manifestClass->$method($this->route, $this) === false)
					{
						// The script failed, rollback changes
						throw new \RuntimeException(
							\JText::sprintf(
								'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
								\JText::_('JLIB_INSTALLER_' . $this->route)
							)
						);
					}

					break;

				// The postflight method takes the route and a results array as params
				case 'postflight':
					$this->parent->manifestClass->$method($this->route, $this, $this->results);

					break;

				// The install, uninstall, and update methods only pass this object as a param
				case 'install':
				case 'uninstall':
				case 'update':
					if ($this->parent->manifestClass->$method($this) === false)
					{
						if ($method !== 'uninstall')
						{
							// The script failed, rollback changes
							throw new \RuntimeException(
								\JText::sprintf(
									'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
									\JText::_('JLIB_INSTALLER_' . $this->route)
								)
							);
						}
					}

					break;
			}
		}

		// Append to the message object
		$this->extensionMessage .= ob_get_clean();

		// If in postflight or uninstall, set the message for display
		if (($method === 'uninstall' || $method === 'postflight') && $this->extensionMessage !== '')
		{
			$this->parent->set('extension_message', $this->extensionMessage);
		}

		return true;
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the package to uninstall.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$row = null;
		$retval = true;

		$row = Table::getInstance('extension');
		$row->load($id);

		if ($row->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_WARNCOREPACK'), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$manifestFile = JPATH_MANIFESTS . '/packages/' . $row->get('element') . '.xml';
		$manifest = new PackageManifest($manifestFile);

		// Set the package root path
		$this->parent->setPath('extension_root', JPATH_MANIFESTS . '/packages/' . $manifest->packagename);

		// Because packages may not have their own folders we cannot use the standard method of finding an installation manifest
		if (!file_exists($manifestFile))
		{
			// TODO: Fail?
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MISSINGMANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		$xml = simplexml_load_file($manifestFile);

		// If we cannot load the XML file return false
		if (!$xml)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_LOAD_MANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Check for a valid XML root tag.
		if ($xml->getName() !== 'extension')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_INVALID_MANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		// If there is a manifest class file, let's load it
		$manifestScript = (string) $manifest->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('extension_root') . '/' . $manifestScript;

			// Set the class name
			$classname = $row->element . 'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		ob_start();
		ob_implicit_flush(false);

		// Run uninstall if possible
		if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'uninstall'))
		{
			$this->parent->manifestClass->uninstall($this);
		}

		$msg = ob_get_contents();
		ob_end_clean();

		if ($msg != '')
		{
			$this->parent->set('extension_message', $msg);
		}

		$error = false;

		foreach ($manifest->filelist as $extension)
		{
			$tmpInstaller = new Installer;
			$tmpInstaller->setPackageUninstall(true);

			$id = $this->_getExtensionId($extension->type, $extension->id, $extension->client, $extension->group);
			$client = ApplicationHelper::getClientInfo($extension->client, true);

			if ($id)
			{
				if (!$tmpInstaller->uninstall($extension->type, $id, $client->id))
				{
					$error = true;
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_NOT_PROPER', basename($extension->filename)), \JLog::WARNING, 'jerror');
				}
			}
			else
			{
				\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_UNKNOWN_EXTENSION'), \JLog::WARNING, 'jerror');
			}
		}

		// Remove any language files
		$this->parent->removeFiles($xml->languages);

		// Clean up manifest file after we're done if there were no errors
		if (!$error)
		{
			\JFile::delete($manifestFile);
			$folder = $this->parent->getPath('extension_root');

			if (\JFolder::exists($folder))
			{
				\JFolder::delete($folder);
			}

			$row->delete();
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_UNINSTALL_MANIFEST_NOT_REMOVED'), \JLog::WARNING, 'jerror');
		}

		// Return the result up the line
		return $retval;
	}

	/**
	 * Gets the extension id.
	 *
	 * @param   string   $type    The extension type.
	 * @param   string   $id      The name of the extension (the element field).
	 * @param   integer  $client  The application id (0: Joomla CMS site; 1: Joomla CMS administrator).
	 * @param   string   $group   The extension group (mainly for plugins).
	 *
	 * @return  integer
	 *
	 * @since   3.1
	 */
	protected function _getExtensionId($type, $id, $client, $group)
	{
		$db = $this->parent->getDbo();

		$query = $db->getQuery(true)
			->select('extension_id')
			->from('#__extensions')
			->where('type = ' . $db->quote($type))
			->where('element = ' . $db->quote($id));

		switch ($type)
		{
			case 'plugin':
				// Plugins have a folder but not a client
				$query->where('folder = ' . $db->quote($group));
				break;

			case 'library':
			case 'package':
			case 'component':
				// Components, packages and libraries don't have a folder or client.
				// Included for completeness.
				break;

			case 'language':
			case 'module':
			case 'template':
				// Languages, modules and templates have a client but not a folder
				$client = ApplicationHelper::getClientInfo($client, true);
				$query->where('client_id = ' . (int) $client->id);
				break;
		}

		$db->setQuery($query);

		// Note: For templates, libraries and packages their unique name is their key.
		// This means they come out the same way they came in.

		return $db->loadResult();
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$manifestPath = JPATH_MANIFESTS . '/packages/' . $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PACK_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Adapter/ComponentAdapter.php000064400000113557152177723700014653 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Asset;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

jimport('joomla.filesystem.folder');

/**
 * Component installer
 *
 * @since  3.1
 */
class ComponentAdapter extends InstallerAdapter
{
	/**
	 * The list of current files fo the Joomla! CMS administrator that are installed and is read
	 * from the manifest on disk in the update area to handle doing a diff
	 * and deleting files that are in the old files list and not in the new
	 * files list.
	 *
	 * @var    array
	 * @since  3.1
	 * */
	protected $oldAdminFiles = null;

	/**
	 * The list of current files that are installed and is read
	 * from the manifest on disk in the update area to handle doing a diff
	 * and deleting files that are in the old files list and not in the new
	 * files list.
	 *
	 * @var    array
	 * @since  3.1
	 * */
	protected $oldFiles = null;

	/**
	 * A path to the PHP file that the scriptfile declaration in
	 * the manifest refers to.
	 *
	 * @var    string
	 * @since  3.1
	 * */
	protected $manifest_script = null;

	/**
	 * For legacy installations this is a path to the PHP file that the scriptfile declaration in the
	 * manifest refers to.
	 *
	 * @var    string
	 * @since  3.1
	 * */
	protected $install_script = null;

	/**
	 * Method to check if the extension is present in the filesystem
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExtensionInFilesystem()
	{
		/*
		 * If the component site or admin directory already exists, then we will assume that the component is already
		 * installed or another component is using that directory.
		 */
		if (file_exists($this->parent->getPath('extension_site')) || file_exists($this->parent->getPath('extension_administrator')))
		{
			// Look for an update function or update tag
			$updateElement = $this->getManifest()->update;

			// Upgrade manually set or update function available or update tag detected
			if ($updateElement || $this->parent->isUpgrade()
				|| ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'update')))
			{
				// If there is a matching extension mark this as an update
				$this->setRoute('update');
			}
			elseif (!$this->parent->isOverwrite())
			{
				// We didn't have overwrite set, find an update function or find an update tag so lets call it safe
				if (file_exists($this->parent->getPath('extension_site')))
				{
					// If the site exists say so.
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_SITE',
							$this->parent->getPath('extension_site')
						)
					);
				}

				// If the admin exists say so
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_INSTALL_DIR_ADMIN',
						$this->parent->getPath('extension_administrator')
					)
				);
			}
		}

		return false;
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy site files
		if ($this->getManifest()->files)
		{
			if ($this->route === 'update')
			{
				$result = $this->parent->parseFiles($this->getManifest()->files, 0, $this->oldFiles);
			}
			else
			{
				$result = $this->parent->parseFiles($this->getManifest()->files);
			}

			if ($result === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_FAIL_SITE_FILES',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// Copy admin files
		if ($this->getManifest()->administration->files)
		{
			if ($this->route === 'update')
			{
				$result = $this->parent->parseFiles($this->getManifest()->administration->files, 1, $this->oldAdminFiles);
			}
			else
			{
				$result = $this->parent->parseFiles($this->getManifest()->administration->files, 1);
			}

			if ($result === false)
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_FAIL_ADMIN_FILES',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_administrator') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_COMP_COPY_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
						)
					);
				}
			}
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// If the component directory does not exist, let's create it
		$created = false;

		if (!file_exists($this->parent->getPath('extension_site')))
		{
			if (!$created = \JFolder::create($this->parent->getPath('extension_site')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->parent->getPath('extension_site')
					)
				);
			}
		}

		/*
		 * Since we created the component directory and we will want to remove it if we have to roll back
		 * the installation, let's add it to the installation step stack
		 */
		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' => $this->parent->getPath('extension_site'),
				)
			);
		}

		// If the component admin directory does not exist, let's create it
		$created = false;

		if (!file_exists($this->parent->getPath('extension_administrator')))
		{
			if (!$created = \JFolder::create($this->parent->getPath('extension_administrator')))
			{
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ERROR_COMP_FAILED_TO_CREATE_DIRECTORY',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->parent->getPath('extension_site')
					)
				);
			}
		}

		/*
		 * Since we created the component admin directory and we will want to remove it if we have to roll
		 * back the installation, let's add it to the installation step stack
		 */
		if ($created)
		{
			$this->parent->pushStep(
				array(
					'type' => 'folder',
					'path' => $this->parent->getPath('extension_administrator'),
				)
			);
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		/** @var Update $update */
		$update = Table::getInstance('update');

		// Clobber any possible pending updates
		$uid = $update->find(
			array(
				'element'   => $this->element,
				'type'      => $this->extension->type,
				'client_id' => 1,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// We will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_COMP_COPY_SETUP',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
					)
				);
			}
		}

		// Time to build the admin menus
		if (!$this->_buildAdminMenus($this->extension->extension_id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_BUILDADMINMENUS_FAILED'), \JLog::WARNING, 'jerror');
		}

		// Make sure that menu items pointing to the component have correct component id assigned to them.
		// Prevents message "Component 'com_extension' does not exist." after uninstalling / re-installing component.
		if (!$this->_updateMenus($this->extension->extension_id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_COMP_UPDATESITEMENUS_FAILED'), \JLog::WARNING, 'jerror');
		}

		/** @var Asset $asset */
		$asset = Table::getInstance('Asset');

		// Check if an asset already exists for this extension and create it if not
		if (!$asset->loadByName($this->extension->element))
		{
			// Register the component container just under root in the assets table.
			$asset->name      = $this->extension->element;
			$asset->parent_id = 1;
			$asset->rules     = '{}';
			$asset->title     = $this->extension->name;
			$asset->setLocation(1, 'last-child');

			if (!$asset->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
						$this->extension->getError()
					)
				);
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		$element = parent::getElement($element);

		if (strpos($element, 'com_') !== 0)
		{
			$element = 'com_' . $element;
		}

		return $element;
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path language files are on.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');
		$client = $this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $client . '/components/' . $this->parent->extension->element);
		}

		$extension = $this->getElement();
		$source    = $path ?: $client . '/components/' . $extension;

		if ($this->getManifest()->administration->files)
		{
			$element = $this->getManifest()->administration->files;
		}
		elseif ($this->getManifest()->files)
		{
			$element = $this->getManifest()->files;
		}
		else
		{
			$element = null;
		}

		if ($element)
		{
			$folder = (string) $element->attributes()->folder;

			if ($folder && file_exists($path . '/' . $folder))
			{
				$source = $path . '/' . $folder;
			}
		}

		$this->doLoadLanguage($extension, $source);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags
		$this->parent->parseMedia($this->getManifest()->media);
		$this->parent->parseLanguages($this->getManifest()->languages);
		$this->parent->parseLanguages($this->getManifest()->administration->languages, 1);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	public function prepareDiscoverInstall()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$client = ApplicationHelper::getClientInfo($this->extension->client_id);
		$short_element = str_replace('com_', '', $this->extension->element);
		$manifestPath = $client->path . '/components/' . $this->extension->element . '/' . $short_element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->parent->setPath('source', $client->path . '/components/' . $this->extension->element);
		$this->parent->setPath('extension_root', $this->parent->getPath('source'));
		$this->setManifest($this->parent->getManifest());

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->extension->manifest_cache = json_encode($manifest_details);
		$this->extension->state = 0;
		$this->extension->name = $manifest_details['name'];
		$this->extension->enabled = 1;
		$this->extension->params = $this->parent->getParams();

		$stored = false;

		try
		{
			$this->extension->store();
			$stored = true;
		}
		catch (\RuntimeException $e)
		{
			// Try to delete existing failed records before retrying
			$db = $this->db;

			$query = $db->getQuery(true)
				->select($db->qn('extension_id'))
				->from($db->qn('#__extensions'))
				->where($db->qn('name') . ' = ' . $db->q($this->extension->name))
				->where($db->qn('type') . ' = ' . $db->q($this->extension->type))
				->where($db->qn('element') . ' = ' . $db->q($this->extension->element));

			$db->setQuery($query);

			$extension_ids = $db->loadColumn();

			if (!empty($extension_ids))
			{
				foreach ($extension_ids as $eid)
				{
					// Remove leftover admin menus for this extension ID
					$this->_removeAdminMenus($eid);

					// Remove the extension record itself
					/** @var Extension $extensionTable */
					$extensionTable = Table::getInstance('extension');
					$extensionTable->delete($eid);
				}
			}
		}

		if (!$stored)
		{
			try
			{
				$this->extension->store();
			}
			catch (\RuntimeException $e)
			{
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_DISCOVER_STORE_DETAILS'), $e->getCode(), $e);
			}
		}
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Set the installation target paths
		$this->parent->setPath('extension_site', \JPath::clean(JPATH_SITE . '/components/' . $this->element));
		$this->parent->setPath('extension_administrator', \JPath::clean(JPATH_ADMINISTRATOR . '/components/' . $this->element));

		// Copy the admin path as it's used as a common base
		$this->parent->setPath('extension_root', $this->parent->getPath('extension_administrator'));

		// Make sure that we have an admin element
		if (!$this->getManifest()->administration)
		{
			throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_COMP_INSTALL_ADMIN_ELEMENT'));
		}
	}

	/**
	 * Method to setup the update routine for the adapter
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function setupUpdates()
	{
		// Hunt for the original XML file
		$old_manifest = null;

		// Use a temporary instance due to side effects; start in the administrator first
		$tmpInstaller = new Installer;
		$tmpInstaller->setPath('source', $this->parent->getPath('extension_administrator'));

		if (!$tmpInstaller->findManifest())
		{
			// Then the site
			$tmpInstaller->setPath('source', $this->parent->getPath('extension_site'));

			if ($tmpInstaller->findManifest())
			{
				$old_manifest = $tmpInstaller->getManifest();
			}
		}
		else
		{
			$old_manifest = $tmpInstaller->getManifest();
		}

		if ($old_manifest)
		{
			$this->oldAdminFiles = $old_manifest->administration->files;
			$this->oldFiles = $old_manifest->files;
		}
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @param   bool  $deleteExisting  Should I try to delete existing records of the same component?
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension($deleteExisting = false)
	{
		// The extension is stored during prepareDiscoverInstall for discover installs
		if ($this->route === 'discover_install')
		{
			return;
		}

		// Add or update an entry to the extension table
		$this->extension->name    = $this->name;
		$this->extension->type    = 'component';
		$this->extension->element = $this->element;

		// If we are told to delete existing extension entries then do so.
		if ($deleteExisting)
		{
			$db = $this->parent->getDbo();

			$query = $db->getQuery(true)
				->select($db->qn('extension_id'))
				->from($db->qn('#__extensions'))
				->where($db->qn('name') . ' = ' . $db->q($this->extension->name))
				->where($db->qn('type') . ' = ' . $db->q($this->extension->type))
				->where($db->qn('element') . ' = ' . $db->q($this->extension->element));

			$db->setQuery($query);

			$extension_ids = $db->loadColumn();

			if (!empty($extension_ids))
			{
				foreach ($extension_ids as $eid)
				{
					// Remove leftover admin menus for this extension ID
					$this->_removeAdminMenus($eid);

					// Remove the extension record itself
					/** @var Extension $extensionTable */
					$extensionTable = Table::getInstance('extension');
					$extensionTable->delete($eid);
				}
			}
		}

		// If there is not already a row, generate a heap of defaults
		if (!$this->currentExtensionId)
		{
			$this->extension->folder    = '';
			$this->extension->enabled   = 1;
			$this->extension->protected = 0;
			$this->extension->access    = 0;
			$this->extension->client_id = 1;
			$this->extension->params    = $this->parent->getParams();
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
		}

		$this->extension->manifest_cache = $this->parent->generateManifestCache();

		$couldStore = $this->extension->store();

		if (!$couldStore && $deleteExisting)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK',
					$this->extension->getError()
				)
			);
		}

		if (!$couldStore && !$deleteExisting)
		{
			// Maybe we have a failed installation (e.g. timeout). Let's retry after deleting old records.
			$this->storeExtension(true);
		}
	}

	/**
	 * Custom uninstall method for components
	 *
	 * @param   integer  $id  The unique extension id of the component to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$db     = $this->db;
		$retval = true;

		// First order of business will be to load the component object table from the database.
		// This should give us the necessary information to proceed.
		if (!$this->extension->load((int) $id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORUNKOWNEXTENSION'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Is the component we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($this->extension->protected)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_WARNCORECOMPONENT'), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($this->extension->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($this->extension->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $this->extension->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the admin and site paths for the component
		$this->parent->setPath('extension_administrator', \JPath::clean(JPATH_ADMINISTRATOR . '/components/' . $this->extension->element));
		$this->parent->setPath('extension_site', \JPath::clean(JPATH_SITE . '/components/' . $this->extension->element));

		// Copy the admin path as it's used as a common base
		$this->parent->setPath('extension_root', $this->parent->getPath('extension_administrator'));

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Manifest Document Setup Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Find and load the XML install file for the component
		$this->parent->setPath('source', $this->parent->getPath('extension_administrator'));

		// Get the package manifest object
		// We do findManifest to avoid problem when uninstalling a list of extension: getManifest cache its manifest file
		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		if (!$this->getManifest())
		{
			// Make sure we delete the folders if no manifest exists
			\JFolder::delete($this->parent->getPath('extension_administrator'));
			\JFolder::delete($this->parent->getPath('extension_site'));

			// Remove the menu
			$this->_removeAdminMenus($this->extension->extension_id);

			// Raise a warning
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_ERRORREMOVEMANUALLY'), \JLog::WARNING, 'jerror');

			// Return
			return false;
		}

		// Set the extensions name
		$this->set('name', $this->getName());
		$this->set('element', $this->getElement());

		// Attempt to load the admin language file; might have uninstall strings
		$this->loadLanguage(JPATH_ADMINISTRATOR . '/components/' . $this->element);

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading and Uninstall
		 * ---------------------------------------------------------------------------------------------
		 */

		$this->setupScriptfile();

		try
		{
			$this->triggerManifestScript('uninstall');
		}
		catch (\RuntimeException $e)
		{
			// Ignore errors for now
		}

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Database Processing Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Let's run the uninstall queries for the component
		try
		{
			$this->parseQueries();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');

			$retval = false;
		}

		$this->_removeAdminMenus($this->extension->extension_id);

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Filesystem Processing Section
		 * ---------------------------------------------------------------------------------------------
		 */

		// Let's remove those language files and media in the JROOT/images/ folder that are
		// associated with the component we are uninstalling
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages);
		$this->parent->removeFiles($this->getManifest()->administration->languages, 1);

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' . $id);
		$db->setQuery($query);
		$db->execute();

		// Remove the component container in the assets table.
		$asset = Table::getInstance('Asset');

		if ($asset->loadByName($this->element))
		{
			$asset->delete();
		}

		// Remove categories for this component
		$query->clear()
			->delete('#__categories')
			->where('extension=' . $db->quote($this->element), 'OR')
			->where('extension LIKE ' . $db->quote($this->element . '.%'));
		$db->setQuery($query);
		$db->execute();

		// Rebuild the categories for correct lft/rgt
		$category = Table::getInstance('category');
		$category->rebuild();

		// Clobber any possible pending updates
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element'   => $this->extension->element,
				'type'      => 'component',
				'client_id' => 1,
				'folder'    => '',
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Now we need to delete the installation directories. This is the final step in uninstalling the component.
		if (trim($this->extension->element))
		{
			// Delete the component site directory
			if (is_dir($this->parent->getPath('extension_site')))
			{
				if (!\JFolder::delete($this->parent->getPath('extension_site')))
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_SITE'), \JLog::WARNING, 'jerror');
					$retval = false;
				}
			}

			// Delete the component admin directory
			if (is_dir($this->parent->getPath('extension_administrator')))
			{
				if (!\JFolder::delete($this->parent->getPath('extension_administrator')))
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_FAILED_REMOVE_DIRECTORY_ADMIN'), \JLog::WARNING, 'jerror');
					$retval = false;
				}
			}

			// Now we will no longer need the extension object, so let's delete it
			$this->extension->delete($this->extension->extension_id);

			return $retval;
		}
		else
		{
			// No component option defined... cannot delete what we don't know about
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_UNINSTALL_NO_OPTION'), \JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Method to build menu database entries for a component
	 *
	 * @param   int|null  $component_id  The component ID for which I'm building menus
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	protected function _buildAdminMenus($component_id = null)
	{
		$db     = $this->parent->getDbo();

		$option = $this->get('element');

		// If a component exists with this option in the table within the protected menutype 'main' then we don't need to add menus
		$query = $db->getQuery(true)
			->select('m.id, e.extension_id')
			->from('#__menu AS m')
			->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id')
			->where('m.parent_id = 1')
			->where('m.client_id = 1')
			->where('m.menutype = ' . $db->quote('main'))
			->where('e.element = ' . $db->quote($option));

		$db->setQuery($query);

		// In case of a failed installation (e.g. timeout error) we may have duplicate menu item and extension records.
		$componentrows = $db->loadObjectList();

		// Check if menu items exist
		if (!empty($componentrows))
		{
			// Don't do anything if overwrite has not been enabled
			if (!$this->parent->isOverwrite())
			{
				return true;
			}

			// Remove all menu items
			foreach ($componentrows as $componentrow)
			{
				// Remove existing menu items if overwrite has been enabled
				if ($option)
				{
					// If something goes wrong, there's no way to rollback TODO: Search for better solution
					$this->_removeAdminMenus($componentrow->extension_id);
				}
			}
		}

		// Only try to detect the component ID if it's not provided
		if (empty($component_id))
		{
			// Lets find the extension id
			$query->clear()
				->select('e.extension_id')
				->from('#__extensions AS e')
				->where('e.type = ' . $db->quote('component'))
				->where('e.element = ' . $db->quote($option));

			$db->setQuery($query);
			$component_id = $db->loadResult();
		}

		// Ok, now its time to handle the menus.  Start with the component root menu, then handle submenus.
		$menuElement = $this->getManifest()->administration->menu;

		// Just do not create the menu if $menuElement not exist
		if (!$menuElement)
		{
			return true;
		}

		// If the menu item is hidden do nothing more, just return
		if (in_array((string) $menuElement['hidden'], array('true', 'hidden')))
		{
			return true;
		}

		// Let's figure out what the menu item data should look like
		$data = array();

		if ($menuElement)
		{
			// I have a menu element, use this information
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string) trim($menuElement);
			$data['alias'] = (string) $menuElement;
			$data['type'] = 'component';
			$data['published'] = 1;
			$data['parent_id'] = 1;
			$data['component_id'] = $component_id;
			$data['img'] = ((string) $menuElement->attributes()->img) ?: 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';

			// Set the menu link
			$request = array();

			if ((string) $menuElement->attributes()->task)
			{
				$request[] = 'task=' . $menuElement->attributes()->task;
			}

			if ((string) $menuElement->attributes()->view)
			{
				$request[] = 'view=' . $menuElement->attributes()->view;
			}

			$qstring = count($request) ? '&' . implode('&', $request) : '';
			$data['link'] = 'index.php?option=' . $option . $qstring;
		}
		else
		{
			// No menu element was specified, Let's make a generic menu item
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = $option;
			$data['alias'] = $option;
			$data['link'] = 'index.php?option=' . $option;
			$data['type'] = 'component';
			$data['published'] = 1;
			$data['parent_id'] = 1;
			$data['component_id'] = $component_id;
			$data['img'] = 'class:component';
			$data['home'] = 0;
			$data['path'] = '';
			$data['params'] = '';
		}

		// Try to create the menu item in the database
		$parent_id = $this->_createAdminMenuItem($data, 1);

		if ($parent_id === false)
		{
			return false;
		}

		/*
		 * Process SubMenus
		 */

		if (!$this->getManifest()->administration->submenu)
		{
			// No submenu? We're done.
			return true;
		}

		foreach ($this->getManifest()->administration->submenu->menu as $child)
		{
			$data = array();
			$data['menutype'] = 'main';
			$data['client_id'] = 1;
			$data['title'] = (string) trim($child);
			$data['alias'] = (string) $child;
			$data['type'] = 'component';
			$data['published'] = 1;
			$data['parent_id'] = $parent_id;
			$data['component_id'] = $component_id;
			$data['img'] = ((string) $child->attributes()->img) ?: 'class:component';
			$data['home'] = 0;

			// Set the sub menu link
			if ((string) $child->attributes()->link)
			{
				$data['link'] = 'index.php?' . $child->attributes()->link;
			}
			else
			{
				$request = array();

				if ((string) $child->attributes()->act)
				{
					$request[] = 'act=' . $child->attributes()->act;
				}

				if ((string) $child->attributes()->task)
				{
					$request[] = 'task=' . $child->attributes()->task;
				}

				if ((string) $child->attributes()->controller)
				{
					$request[] = 'controller=' . $child->attributes()->controller;
				}

				if ((string) $child->attributes()->view)
				{
					$request[] = 'view=' . $child->attributes()->view;
				}

				if ((string) $child->attributes()->layout)
				{
					$request[] = 'layout=' . $child->attributes()->layout;
				}

				if ((string) $child->attributes()->sub)
				{
					$request[] = 'sub=' . $child->attributes()->sub;
				}

				$qstring = count($request) ? '&' . implode('&', $request) : '';
				$data['link'] = 'index.php?option=' . $option . $qstring;
			}

			$submenuId = $this->_createAdminMenuItem($data, $parent_id);

			if ($submenuId === false)
			{
				return false;
			}

			/*
			 * Since we have created a menu item, we add it to the installation step stack
			 * so that if we have to rollback the changes we can undo it.
			 */
			$this->parent->pushStep(array('type' => 'menu', 'id' => $component_id));
		}

		return true;
	}

	/**
	 * Method to remove admin menu references to a component
	 *
	 * @param   int  $id  The ID of the extension whose admin menus will be removed
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   3.1
	 */
	protected function _removeAdminMenus($id)
	{
		$db = $this->parent->getDbo();

		/** @var  \JTableMenu  $table */
		$table = Table::getInstance('menu');

		// Get the ids of the menu items
		$query = $db->getQuery(true)
			->select('id')
			->from('#__menu')
			->where($db->quoteName('client_id') . ' = 1')
			->where($db->quoteName('menutype') . ' = ' . $db->q('main'))
			->where($db->quoteName('component_id') . ' = ' . (int) $id);

		$db->setQuery($query);

		$ids = $db->loadColumn();

		$result = true;

		// Check for error
		if (!empty($ids))
		{
			// Iterate the items to delete each one.
			foreach ($ids as $menuid)
			{
				if (!$table->delete((int) $menuid, false))
				{
					$this->setError($table->getError());

					$result = false;
				}
			}

			// Rebuild the whole tree
			$table->rebuild();
		}

		return $result;
	}

	/**
	 * Method to update menu database entries for a component in case the component has been uninstalled before.
	 * NOTE: This will not update admin menus. Use _updateMenus() instead to update admin menus ase well.
	 *
	 * @param   int|null  $component_id  The component ID.
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.4.2
	 */
	protected function _updateSiteMenus($component_id = null)
	{
		return $this->_updateMenus($component_id, 0);
	}

	/**
	 * Method to update menu database entries for a component in case if the component has been uninstalled before.
	 *
	 * @param   int|null  $component_id  The component ID.
	 * @param   int       $clientId      The client id
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.7.0
	 */
	protected function _updateMenus($component_id, $clientId = null)
	{
		$db     = $this->parent->getDbo();
		$option = $this->get('element');

		// Update all menu items which contain 'index.php?option=com_extension' or 'index.php?option=com_extension&...'
		// to use the new component id.
		$query = $db->getQuery(true)
			->update('#__menu')
			->set('component_id = ' . $db->quote($component_id))
			->where('type = ' . $db->quote('component'))
			->where('('
				. 'link LIKE ' . $db->quote('index.php?option=' . $option) . ' OR '
				. 'link LIKE ' . $db->q($db->escape('index.php?option=' . $option . '&') . '%', false)
				. ')');

		if (isset($clientId))
		{
			$query->where('client_id = ' . (int) $clientId);
		}

		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			return false;
		}

		return true;
	}

	/**
	 * Custom rollback method
	 * - Roll back the component menu item
	 *
	 * @param   array  $step  Installation step to rollback.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	protected function _rollback_menu($step)
	{
		return $this->_removeAdminMenus($step['id']);
	}

	/**
	 * Discover unregistered extensions.
	 *
	 * @return  array  A list of extensions.
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$site_components = \JFolder::folders(JPATH_SITE . '/components');
		$admin_components = \JFolder::folders(JPATH_ADMINISTRATOR . '/components');

		foreach ($site_components as $component)
		{
			if (file_exists(JPATH_SITE . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(
					JPATH_SITE . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'
				);
				$extension = Table::getInstance('extension');
				$extension->set('type', 'component');
				$extension->set('client_id', 0);
				$extension->set('element', $component);
				$extension->set('folder', '');
				$extension->set('name', $component);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_components as $component)
		{
			if (file_exists(JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'))
			{
				$manifest_details = Installer::parseXMLInstallFile(
					JPATH_ADMINISTRATOR . '/components/' . $component . '/' . str_replace('com_', '', $component) . '.xml'
				);
				$extension = Table::getInstance('extension');
				$extension->set('type', 'component');
				$extension->set('client_id', 1);
				$extension->set('element', $component);
				$extension->set('folder', '');
				$extension->set('name', $component);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store this normally
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$short_element = str_replace('com_', '', $this->parent->extension->element);
		$manifestPath = $client->path . '/components/' . $this->parent->extension->element . '/' . $short_element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_COMP_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Creates the menu item in the database. If the item already exists it tries to remove it and create it afresh.
	 *
	 * @param   array    &$data     The menu item data to create
	 * @param   integer  $parentId  The parent menu item ID
	 *
	 * @return  boolean|integer  Menu item ID on success, false on failure
	 */
	protected function _createAdminMenuItem(array &$data, $parentId)
	{
		$db = $this->parent->getDbo();

		/** @var  \JTableMenu  $table */
		$table  = Table::getInstance('menu');

		try
		{
			$table->setLocation($parentId, 'last-child');
		}
		catch (\InvalidArgumentException $e)
		{
			\JLog::add($e->getMessage(), \JLog::WARNING, 'jerror');

			return false;
		}

		if (!$table->bind($data) || !$table->check() || !$table->store())
		{
			// The menu item already exists. Delete it and retry instead of throwing an error.
			$query = $db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype = ' . $db->q($data['menutype']))
				->where('client_id = 1')
				->where('link = ' . $db->q($data['link']))
				->where('type = ' . $db->q($data['type']))
				->where('parent_id = ' . $db->q($data['parent_id']))
				->where('home = ' . $db->q($data['home']));

			$db->setQuery($query);
			$menu_id = $db->loadResult();

			if (!$menu_id)
			{
				// Oops! Could not get the menu ID. Go back and rollback changes.
				\JError::raiseWarning(1, $table->getError());

				return false;
			}
			else
			{
				/** @var  \JTableMenu $temporaryTable */
				$temporaryTable = Table::getInstance('menu');
				$temporaryTable->delete($menu_id, true);
				$temporaryTable->load($parentId);
				$temporaryTable->rebuild($parentId, $temporaryTable->lft, $temporaryTable->level, $temporaryTable->path);

				// Retry creating the menu item
				$table->setLocation($parentId, 'last-child');

				if (!$table->bind($data) || !$table->check() || !$table->store())
				{
					// Install failed, warn user and rollback changes
					\JError::raiseWarning(1, $table->getError());

					return false;
				}
			}
		}

		return $table->id;
	}
}
src/Installer/Adapter/PluginAdapter.php000064400000046454152177723700014150 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Plugin installer
 *
 * @since  3.1
 */
class PluginAdapter extends InstallerAdapter
{
	/**
	 * `<scriptfile>` element of the extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $scriptElement = null;

	/**
	 * `<files>` element of the old extension manifest
	 *
	 * @var    object
	 * @since  3.1
	 */
	protected $oldFiles = null;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array('type' => $this->type, 'element' => $this->element, 'folder' => $this->group)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all necessary files
		if ($this->parent->parseFiles($this->getManifest()->files, -1, $this->oldFiles) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PLG_COPY_FILES',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					// Install failed, rollback changes
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_PLG_INSTALL_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . $this->route)
						)
					);
				}
			}
		}
	}

	/**
	 * Method to create the extension root path if necessary
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function createExtensionRoot()
	{
		// Run the common create code first
		parent::createExtensionRoot();

		// If we're updating at this point when there is always going to be an extension_root find the old XML files
		if ($this->route === 'update')
		{
			// Create a new installer because findManifest sets stuff; side effects!
			$tmpInstaller = new Installer;

			// Look in the extension root
			$tmpInstaller->setPath('source', $this->parent->getPath('extension_root'));

			if ($tmpInstaller->findManifest())
			{
				$old_manifest   = $tmpInstaller->getManifest();
				$this->oldFiles = $old_manifest->files;
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');
		$uid = $update->find(
			array(
				'element' => $this->element,
				'type'    => $this->type,
				'folder'  => $this->group,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_COPY_SETUP',
						\JText::_('JLIB_INSTALLER_' . $this->route)
					)
				);
			}
		}
	}

	/**
	 * Get the filtered extension element from the manifest
	 *
	 * @param   string  $element  Optional element name to be converted
	 *
	 * @return  string  The filtered element
	 *
	 * @since   3.4
	 */
	public function getElement($element = null)
	{
		if (!$element)
		{
			// Backward Compatibility
			// @todo Deprecate in future version
			if (count($this->getManifest()->files->children()))
			{
				$type = (string) $this->getManifest()->attributes()->type;

				foreach ($this->getManifest()->files->children() as $file)
				{
					if ((string) $file->attributes()->$type)
					{
						$element = (string) $file->attributes()->$type;

						break;
					}
				}
			}
		}

		return $element;
	}

	/**
	 * Get the class name for the install adapter script.
	 *
	 * @return  string  The class name.
	 *
	 * @since   3.4
	 */
	protected function getScriptClassName()
	{
		return 'Plg' . str_replace('-', '', $this->group) . $this->element . 'InstallerScript';
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source = $this->parent->getPath('source');

		if (!$source)
		{
			$this->parent->setPath(
				'source',
				JPATH_PLUGINS . '/' . $this->parent->extension->folder . '/' . $this->parent->extension->element
			);
		}

		$element = $this->getManifest()->files;

		if ($element)
		{
			$group = strtolower((string) $this->getManifest()->attributes()->group);
			$name = '';

			if (count($element->children()))
			{
				foreach ($element->children() as $file)
				{
					if ((string) $file->attributes()->plugin)
					{
						$name = strtolower((string) $file->attributes()->plugin);
						break;
					}
				}
			}

			if ($name)
			{
				$extension = "plg_${group}_${name}";
				$source = $path ?: JPATH_PLUGINS . "/$group/$name";
				$folder = (string) $element->attributes()->folder;

				if ($folder && file_exists("$path/$folder"))
				{
					$source = "$path/$folder";
				}

				$this->doLoadLanguage($extension, $source, JPATH_ADMINISTRATOR);
			}
		}
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		// Parse optional tags -- media and language files for plugins go in admin app
		$this->parent->parseMedia($this->getManifest()->media, 1);
		$this->parent->parseLanguages($this->getManifest()->languages, 1);
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client   = ApplicationHelper::getClientInfo($this->extension->client_id);
		$basePath = $client->path . '/plugins/' . $this->extension->folder;

		if (is_dir($basePath . '/' . $this->extension->element))
		{
			$manifestPath = $basePath . '/' . $this->extension->element . '/' . $this->extension->element . '.xml';
		}
		else
		{
			// @deprecated 4.0 - This path supports Joomla! 1.5 plugin folder layouts
			$manifestPath = $basePath . '/' . $this->extension->element . '.xml';
		}

		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		$this->group = (string) $this->getManifest()->attributes()->group;

		if (empty($this->element) && empty($this->group))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_PLG_INSTALL_NO_FILE',
					\JText::_('JLIB_INSTALLER_' . $this->route)
				)
			);
		}

		$this->parent->setPath('extension_root', JPATH_PLUGINS . '/' . $this->group . '/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 'editors' === $this->extension->folder ? 1 : 0;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_PLG_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a plugin with the same name already installed?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_ALLREADY_EXISTS',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->name
					)
				);
			}

			$this->extension->load($this->currentExtensionId);
			$this->extension->name = $this->name;
			$this->extension->manifest_cache = $this->parent->generateManifestCache();

			// Update the manifest cache and name
			$this->extension->store();
		}
		else
		{
			// Store in the extensions table (1.6)
			$this->extension->name = $this->name;
			$this->extension->type = 'plugin';
			$this->extension->ordering = 0;
			$this->extension->element = $this->element;
			$this->extension->folder = $this->group;
			$this->extension->enabled = 0;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = 0;
			$this->extension->params = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data = '';

			// System data
			$this->extension->system_data = '';
			$this->extension->manifest_cache = $this->parent->generateManifestCache();

			// Editor plugins are published by default
			if ($this->group === 'editors')
			{
				$this->extension->enabled = 1;
			}

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::sprintf(
						'JLIB_INSTALLER_ABORT_PLG_INSTALL_ROLLBACK',
						\JText::_('JLIB_INSTALLER_' . $this->route),
						$this->extension->getError()
					)
				);
			}

			// Since we have created a plugin item, we add it to the installation step stack
			// so that if we have to rollback the changes we can undo it.
			$this->parent->pushStep(array('type' => 'extension', 'id' => $this->extension->extension_id));
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The id of the plugin to uninstall
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		$this->route = 'uninstall';

		$row = null;
		$retval = true;
		$db = $this->parent->getDbo();

		// First order of business will be to load the plugin object table from the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id))
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_ERRORUNKOWNEXTENSION'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Is the plugin we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_WARNCOREPLUGIN', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the plugin folder so we can properly build the plugin path
		if (trim($row->folder) === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_UNINSTALL_FOLDER_FIELD_EMPTY'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Set the plugin root path
		$this->parent->setPath('extension_root', JPATH_PLUGINS . '/' . $row->folder . '/' . $row->element);

		$this->parent->setPath('source', $this->parent->getPath('extension_root'));

		$this->parent->findManifest();
		$this->setManifest($this->parent->getManifest());

		// Attempt to load the language file; might have uninstall strings
		$this->parent->setPath('source', JPATH_PLUGINS . '/' . $row->folder . '/' . $row->element);
		$this->loadLanguage(JPATH_PLUGINS . '/' . $row->folder . '/' . $row->element);

		/**
		 * ---------------------------------------------------------------------------------------------
		 * Installer Trigger Loading
		 * ---------------------------------------------------------------------------------------------
		 */

		// If there is a manifest class file, let's load it; we'll copy it later (don't have dest yet)
		$manifestScript = (string) $this->getManifest()->scriptfile;

		if ($manifestScript)
		{
			$manifestScriptFile = $this->parent->getPath('source') . '/' . $manifestScript;

			// If a dash is present in the folder, remove it
			$folderClass = str_replace('-', '', $row->folder);

			// Set the class name
			$classname = 'Plg' . $folderClass . $row->element . 'InstallerScript';

			\JLoader::register($classname, $manifestScriptFile);

			if (class_exists($classname))
			{
				// Create a new instance
				$this->parent->manifestClass = new $classname($this);

				// And set this so we can copy it later
				$this->set('manifest_script', $manifestScript);
			}
		}

		// Run preflight if possible (since we know we're not an update)
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'preflight'))
		{
			if ($this->parent->manifestClass->preflight($this->route, $this) === false)
			{
				// Preflight failed, rollback changes
				$this->parent->abort(\JText::_('JLIB_INSTALLER_ABORT_PLG_INSTALL_CUSTOM_INSTALL_FAILURE'));

				return false;
			}
		}

		// Create the $msg object and append messages from preflight
		$msg = ob_get_contents();
		ob_end_clean();

		// Let's run the queries for the plugin
		$utfresult = $this->parent->parseSQLFiles($this->getManifest()->uninstall->sql);

		if ($utfresult === false)
		{
			// Install failed, rollback changes
			$this->parent->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_PLG_UNINSTALL_SQL_ERROR', $db->stderr(true)));

			return false;
		}

		// Run the custom uninstall method if possible
		ob_start();
		ob_implicit_flush(false);

		if ($this->parent->manifestClass && method_exists($this->parent->manifestClass, 'uninstall'))
		{
			$this->parent->manifestClass->uninstall($this);
		}

		// Append messages
		$msg .= ob_get_contents();
		ob_end_clean();

		// Remove the plugin files
		$this->parent->removeFiles($this->getManifest()->files, -1);

		// Remove all media and languages as well
		$this->parent->removeFiles($this->getManifest()->media);
		$this->parent->removeFiles($this->getManifest()->languages, 1);

		// Remove the schema version
		$query = $db->getQuery(true)
			->delete('#__schemas')
			->where('extension_id = ' . $row->extension_id);
		$db->setQuery($query);
		$db->execute();

		// Now we will no longer need the plugin object, so let's delete it
		$row->delete($row->extension_id);
		unset($row);

		// Remove the plugin's folder
		\JFolder::delete($this->parent->getPath('extension_root'));

		if ($msg != '')
		{
			$this->parent->set('extension_message', $msg);
		}

		return $retval;
	}

	/**
	 * Custom discover method
	 *
	 * @return  array  Extension) list of extensions available
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$results = array();
		$folder_list = \JFolder::folders(JPATH_SITE . '/plugins');

		foreach ($folder_list as $folder)
		{
			$file_list = \JFolder::files(JPATH_SITE . '/plugins/' . $folder, '\.xml$');

			foreach ($file_list as $file)
			{
				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . '/plugins/' . $folder . '/' . $file);
				$file = \JFile::stripExt($file);

				// Ignore example plugins
				if ($file === 'example' || $manifest_details === false)
				{
					continue;
				}

				$element = empty($manifest_details['filename']) ? $file : $manifest_details['filename'];

				$extension = Table::getInstance('extension');
				$extension->set('type', 'plugin');
				$extension->set('client_id', 0);
				$extension->set('element', $element);
				$extension->set('folder', $folder);
				$extension->set('name', $manifest_details['name']);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}

			$folder_list = \JFolder::folders(JPATH_SITE . '/plugins/' . $folder);

			foreach ($folder_list as $plugin_folder)
			{
				$file_list = \JFolder::files(JPATH_SITE . '/plugins/' . $folder . '/' . $plugin_folder, '\.xml$');

				foreach ($file_list as $file)
				{
					$manifest_details = Installer::parseXMLInstallFile(
						JPATH_SITE . '/plugins/' . $folder . '/' . $plugin_folder . '/' . $file
					);
					$file = \JFile::stripExt($file);

					if ($file === 'example' || $manifest_details === false)
					{
						continue;
					}

					$element = empty($manifest_details['filename']) ? $file : $manifest_details['filename'];

					// Ignore example plugins
					$extension = Table::getInstance('extension');
					$extension->set('type', 'plugin');
					$extension->set('client_id', 0);
					$extension->set('element', $element);
					$extension->set('folder', $folder);
					$extension->set('name', $manifest_details['name']);
					$extension->set('state', -1);
					$extension->set('manifest_cache', json_encode($manifest_details));
					$extension->set('params', '{}');
					$results[] = $extension;
				}
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache.
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure.
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		/*
		 * Plugins use the extensions table as their primary store
		 * Similar to modules and templates, rather easy
		 * If it's not in the extensions table we just add it
		 */
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/plugins/' . $this->parent->extension->folder . '/' . $this->parent->extension->element . '/'
			. $this->parent->extension->element . '.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);

		$this->parent->extension->name = $manifest_details['name'];

		if ($this->parent->extension->store())
		{
			return true;
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_PLG_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Adapter/TemplateAdapter.php000064400000041532152177723700014455 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Installer\Installer;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\Update;

\JLoader::import('joomla.filesystem.folder');

/**
 * Template installer
 *
 * @since  3.1
 */
class TemplateAdapter extends InstallerAdapter
{
	/**
	 * The install client ID
	 *
	 * @var    integer
	 * @since  3.4
	 */
	protected $clientId;

	/**
	 * Method to check if the extension is already present in the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function checkExistingExtension()
	{
		try
		{
			$this->currentExtensionId = $this->extension->find(
				array(
					'element'   => $this->element,
					'type'      => $this->type,
					'client_id' => $this->clientId,
				)
			);
		}
		catch (\RuntimeException $e)
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . $this->route),
					$e->getMessage()
				),
				$e->getCode(),
				$e
			);
		}
	}

	/**
	 * Method to copy the extension's base files from the `<files>` tag(s) and the manifest file
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function copyBaseFiles()
	{
		// Copy all the necessary files
		if ($this->parent->parseFiles($this->getManifest()->files, -1) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'files'
				)
			);
		}

		if ($this->parent->parseFiles($this->getManifest()->images, -1) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'images'
				)
			);
		}

		if ($this->parent->parseFiles($this->getManifest()->css, -1) === false)
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_FILES',
					'css'
				)
			);
		}

		// If there is a manifest script, let's copy it.
		if ($this->manifest_script)
		{
			$path['src']  = $this->parent->getPath('source') . '/' . $this->manifest_script;
			$path['dest'] = $this->parent->getPath('extension_root') . '/' . $this->manifest_script;

			if ($this->parent->isOverwrite() || !file_exists($path['dest']))
			{
				if (!$this->parent->copyFiles(array($path)))
				{
					throw new \RuntimeException(
						\JText::sprintf(
							'JLIB_INSTALLER_ABORT_MANIFEST',
							\JText::_('JLIB_INSTALLER_' . strtoupper($this->getRoute()))
						)
					);
				}
			}
		}
	}

	/**
	 * Method to finalise the installation processing
	 *
	 * @return  void
	 *
	 * @since   3.1
	 * @throws  \RuntimeException
	 */
	protected function finaliseInstall()
	{
		// Clobber any possible pending updates
		/** @var Update $update */
		$update = Table::getInstance('update');

		$uid = $update->find(
			array(
				'element'   => $this->element,
				'type'      => $this->type,
				'client_id' => $this->clientId,
			)
		);

		if ($uid)
		{
			$update->delete($uid);
		}

		// Lastly, we will copy the manifest file to its appropriate place.
		if ($this->route !== 'discover_install')
		{
			if (!$this->parent->copyManifest(-1))
			{
				// Install failed, rollback changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_COPY_SETUP'));
			}
		}
	}

	/**
	 * Custom loadLanguage method
	 *
	 * @param   string  $path  The path where to find language files.
	 *
	 * @return  InstallerTemplate
	 *
	 * @since   3.1
	 */
	public function loadLanguage($path = null)
	{
		$source   = $this->parent->getPath('source');
		$basePath = $this->parent->extension->client_id ? JPATH_ADMINISTRATOR : JPATH_SITE;

		if (!$source)
		{
			$this->parent->setPath('source', $basePath . '/templates/' . $this->parent->extension->element);
		}

		$this->setManifest($this->parent->getManifest());

		$client = (string) $this->getManifest()->attributes()->client;

		// Load administrator language if not set.
		if (!$client)
		{
			$client = 'ADMINISTRATOR';
		}

		$base = constant('JPATH_' . strtoupper($client));
		$extension = 'tpl_' . $this->getName();
		$source    = $path ?: $base . '/templates/' . $this->getName();

		$this->doLoadLanguage($extension, $source, $base);
	}

	/**
	 * Method to parse optional tags in the manifest
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function parseOptionalTags()
	{
		$this->parent->parseMedia($this->getManifest()->media);
		$this->parent->parseLanguages($this->getManifest()->languages, $this->clientId);
	}

	/**
	 * Overloaded method to parse queries for template installations
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function parseQueries()
	{
		if (in_array($this->route, array('install', 'discover_install')))
		{
			$db    = $this->db;
			$lang  = \JFactory::getLanguage();
			$debug = $lang->setDebug(false);

			$columns = array(
				$db->quoteName('template'),
				$db->quoteName('client_id'),
				$db->quoteName('home'),
				$db->quoteName('title'),
				$db->quoteName('params'),
			);

			$values = array(
				$db->quote($this->extension->element), $this->extension->client_id, $db->quote(0),
				$db->quote(\JText::sprintf('JLIB_INSTALLER_DEFAULT_STYLE', \JText::_($this->extension->name))),
				$db->quote($this->extension->params),
			);

			$lang->setDebug($debug);

			// Insert record in #__template_styles
			$query = $db->getQuery(true);
			$query->insert($db->quoteName('#__template_styles'))
				->columns($columns)
				->values(implode(',', $values));

			// There is a chance this could fail but we don't care...
			$db->setQuery($query)->execute();
		}
	}

	/**
	 * Prepares the adapter for a discover_install task
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function prepareDiscoverInstall()
	{
		$client = ApplicationHelper::getClientInfo($this->extension->client_id);
		$manifestPath = $client->path . '/templates/' . $this->extension->element . '/templateDetails.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);
		$this->setManifest($this->parent->getManifest());
	}

	/**
	 * Method to do any prechecks and setup the install paths for the extension
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function setupInstallPaths()
	{
		// Get the client application target
		$cname = (string) $this->getManifest()->attributes()->client;

		if ($cname)
		{
			// Attempt to map the client to a base path
			$client = ApplicationHelper::getClientInfo($cname, true);

			if ($client === false)
			{
				throw new \RuntimeException(\JText::sprintf('JLIB_INSTALLER_ABORT_TPL_INSTALL_UNKNOWN_CLIENT', $cname));
			}

			$basePath = $client->path;
			$this->clientId = $client->id;
		}
		else
		{
			// No client attribute was found so we assume the site as the client
			$basePath = JPATH_SITE;
			$this->clientId = 0;
		}

		// Set the template root path
		if (empty($this->element))
		{
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_MOD_INSTALL_NOFILE',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route))
				)
			);
		}

		$this->parent->setPath('extension_root', $basePath . '/templates/' . $this->element);
	}

	/**
	 * Method to store the extension to the database
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	protected function storeExtension()
	{
		// Discover installs are stored a little differently
		if ($this->route === 'discover_install')
		{
			$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));

			$this->extension->manifest_cache = json_encode($manifest_details);
			$this->extension->state = 0;
			$this->extension->name = $manifest_details['name'];
			$this->extension->enabled = 1;
			$this->extension->params = $this->parent->getParams();

			if (!$this->extension->store())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(\JText::_('JLIB_INSTALLER_ERROR_TPL_DISCOVER_STORE_DETAILS'));
			}

			return;
		}

		// Was there a template already installed with the same name?
		if ($this->currentExtensionId)
		{
			if (!$this->parent->isOverwrite())
			{
				// Install failed, roll back changes
				throw new \RuntimeException(
					\JText::_('JLIB_INSTALLER_ABORT_TPL_INSTALL_ALREADY_INSTALLED')
				);
			}

			// Load the entry and update the manifest_cache
			$this->extension->load($this->currentExtensionId);
		}
		else
		{
			$this->extension->type = 'template';
			$this->extension->element = $this->element;

			// There is no folder for templates
			$this->extension->folder = '';
			$this->extension->enabled = 1;
			$this->extension->protected = 0;
			$this->extension->access = 1;
			$this->extension->client_id = $this->clientId;
			$this->extension->params = $this->parent->getParams();

			// Custom data
			$this->extension->custom_data = '';
			$this->extension->system_data = '';
		}

		// Name might change in an update
		$this->extension->name = $this->name;
		$this->extension->manifest_cache = $this->parent->generateManifestCache();

		if (!$this->extension->store())
		{
			// Install failed, roll back changes
			throw new \RuntimeException(
				\JText::sprintf(
					'JLIB_INSTALLER_ABORT_ROLLBACK',
					\JText::_('JLIB_INSTALLER_' . strtoupper($this->route)),
					$this->extension->getError()
				)
			);
		}
	}

	/**
	 * Custom uninstall method
	 *
	 * @param   integer  $id  The extension ID
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function uninstall($id)
	{
		// First order of business will be to load the template object table from the database.
		// This should give us the necessary information to proceed.
		$row = Table::getInstance('extension');

		if (!$row->load((int) $id) || $row->element === '')
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_ERRORUNKOWNEXTENSION'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Is the template we are trying to uninstall a core one?
		// Because that is not a good idea...
		if ($row->protected)
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_WARNCORETEMPLATE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Does this extension have a parent package?
		 * If so, check if the package disallows individual extensions being uninstalled if the package is not being uninstalled
		 */
		if ($row->package_id && !$this->parent->isPackageUninstall() && !$this->canUninstallPackageChild($row->package_id))
		{
			\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CANNOT_UNINSTALL_CHILD_OF_PACKAGE', $row->name), \JLog::WARNING, 'jerror');

			return false;
		}

		$name = $row->element;
		$clientId = $row->client_id;

		// For a template the id will be the template name which represents the subfolder of the templates folder that the template resides in.
		if (!$name)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_ID_EMPTY'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Deny remove default template
		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn('#__template_styles'))
			->where($db->qn('home') . ' = ' . $db->q('1'))
			->where($db->qn('template') . ' = ' . $db->q($name));
		$db->setQuery($query);

		if ($db->loadResult() != 0)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DEFAULT'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Get the template root path
		$client = ApplicationHelper::getClientInfo($clientId);

		if (!$client)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_CLIENT'), \JLog::WARNING, 'jerror');

			return false;
		}

		$this->parent->setPath('extension_root', $client->path . '/templates/' . strtolower($name));
		$this->parent->setPath('source', $this->parent->getPath('extension_root'));

		// We do findManifest to avoid problem when uninstalling a list of extensions: getManifest cache its manifest file
		$this->parent->findManifest();
		$manifest = $this->parent->getManifest();

		if (!($manifest instanceof \SimpleXMLElement))
		{
			// Kill the extension entry
			$row->delete($row->extension_id);
			unset($row);

			// Make sure we delete the folders
			\JFolder::delete($this->parent->getPath('extension_root'));
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_INVALID_NOTFOUND_MANIFEST'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Remove files
		$this->parent->removeFiles($manifest->media);
		$this->parent->removeFiles($manifest->languages, $clientId);

		// Delete the template directory
		if (\JFolder::exists($this->parent->getPath('extension_root')))
		{
			$retval = \JFolder::delete($this->parent->getPath('extension_root'));
		}
		else
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_UNINSTALL_TEMPLATE_DIRECTORY'), \JLog::WARNING, 'jerror');
			$retval = false;
		}

		// Set menu that assigned to the template back to default template
		$subQuery = $db->getQuery(true)
			->select('s.id')
			->from($db->qn('#__template_styles', 's'))
			->where($db->qn('s.template') . ' = ' . $db->q(strtolower($name)))
			->where($db->qn('s.client_id') . ' = ' . $clientId);
		$query->clear()
			->update($db->qn('#__menu'))
			->set($db->qn('template_style_id') . ' = 0')
			->where($db->qn('template_style_id') . ' IN (' . (string) $subQuery . ')');
		$db->setQuery($query);
		$db->execute();

		$query = $db->getQuery(true)
			->delete($db->quoteName('#__template_styles'))
			->where($db->quoteName('template') . ' = ' . $db->quote($name))
			->where($db->quoteName('client_id') . ' = ' . $clientId);
		$db->setQuery($query);
		$db->execute();

		$row->delete($row->extension_id);
		unset($row);

		return $retval;
	}

	/**
	 * Discover existing but uninstalled templates
	 *
	 * @return  array  Extension list
	 */
	public function discover()
	{
		$results = array();
		$site_list = \JFolder::folders(JPATH_SITE . '/templates');
		$admin_list = \JFolder::folders(JPATH_ADMINISTRATOR . '/templates');
		$site_info = ApplicationHelper::getClientInfo('site', true);
		$admin_info = ApplicationHelper::getClientInfo('administrator', true);

		foreach ($site_list as $template)
		{
			if (file_exists(JPATH_SITE . "/templates/$template/templateDetails.xml"))
			{
				if ($template === 'system')
				{
					// Ignore special system template
					continue;
				}

				$manifest_details = Installer::parseXMLInstallFile(JPATH_SITE . "/templates/$template/templateDetails.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'template');
				$extension->set('client_id', $site_info->id);
				$extension->set('element', $template);
				$extension->set('folder', '');
				$extension->set('name', $template);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		foreach ($admin_list as $template)
		{
			if (file_exists(JPATH_ADMINISTRATOR . "/templates/$template/templateDetails.xml"))
			{
				if ($template === 'system')
				{
					// Ignore special system template
					continue;
				}

				$manifest_details = Installer::parseXMLInstallFile(JPATH_ADMINISTRATOR . "/templates/$template/templateDetails.xml");
				$extension = Table::getInstance('extension');
				$extension->set('type', 'template');
				$extension->set('client_id', $admin_info->id);
				$extension->set('element', $template);
				$extension->set('folder', '');
				$extension->set('name', $template);
				$extension->set('state', -1);
				$extension->set('manifest_cache', json_encode($manifest_details));
				$extension->set('params', '{}');
				$results[] = $extension;
			}
		}

		return $results;
	}

	/**
	 * Refreshes the extension table cache
	 *
	 * @return  boolean  Result of operation, true if updated, false on failure
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache()
	{
		// Need to find to find where the XML file is since we don't store this normally.
		$client = ApplicationHelper::getClientInfo($this->parent->extension->client_id);
		$manifestPath = $client->path . '/templates/' . $this->parent->extension->element . '/templateDetails.xml';
		$this->parent->manifest = $this->parent->isManifest($manifestPath);
		$this->parent->setPath('manifest', $manifestPath);

		$manifest_details = Installer::parseXMLInstallFile($this->parent->getPath('manifest'));
		$this->parent->extension->manifest_cache = json_encode($manifest_details);
		$this->parent->extension->name = $manifest_details['name'];

		try
		{
			return $this->parent->extension->store();
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_TPL_REFRESH_MANIFEST_CACHE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}
}
src/Installer/Installer.php000064400000162213152177723700011756 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Installer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Extension;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');

/**
 * Joomla base installer class
 *
 * @since  3.1
 */
class Installer extends \JAdapter
{
	/**
	 * Array of paths needed by the installer
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $paths = array();

	/**
	 * True if package is an upgrade
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	protected $upgrade = null;

	/**
	 * The manifest trigger class
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $manifestClass = null;

	/**
	 * True if existing files can be overwritten
	 *
	 * @var    boolean
	 * @since  3.0.0
	 */
	protected $overwrite = false;

	/**
	 * Stack of installation steps
	 * - Used for installation rollback
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected $stepStack = array();

	/**
	 * Extension Table Entry
	 *
	 * @var    Extension
	 * @since  3.1
	 */
	public $extension = null;

	/**
	 * The output from the install/uninstall scripts
	 *
	 * @var    string
	 * @since  3.1
	 * */
	public $message = null;

	/**
	 * The installation manifest XML object
	 *
	 * @var    object
	 * @since  3.1
	 */
	public $manifest = null;

	/**
	 * The extension message that appears
	 *
	 * @var    string
	 * @since  3.1
	 */
	protected $extension_message = null;

	/**
	 * The redirect URL if this extension (can be null if no redirect)
	 *
	 * @var    string
	 * @since  3.1
	 */
	protected $redirect_url = null;

	/**
	 * Flag if the uninstall process was triggered by uninstalling a package
	 *
	 * @var    boolean
	 * @since  3.7.0
	 */
	protected $packageUninstall = false;

	/**
	 * Installer instance container.
	 *
	 * @var    Installer
	 * @since  3.1
	 * @deprecated  4.0
	 */
	protected static $instance;

	/**
	 * Installer instances container.
	 *
	 * @var    Installer[]
	 * @since  3.4
	 */
	protected static $instances;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   3.1
	 */
	public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder = 'Adapter')
	{
		parent::__construct($basepath, $classprefix, $adapterfolder);

		$this->extension = Table::getInstance('extension');
	}

	/**
	 * Returns the global Installer object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @return  Installer  An installer object
	 *
	 * @since   3.1
	 */
	public static function getInstance($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Installer\\Adapter', $adapterfolder = 'Adapter')
	{
		if (!isset(self::$instances[$basepath]))
		{
			self::$instances[$basepath] = new Installer($basepath, $classprefix, $adapterfolder);

			// For B/C, we load the first instance into the static $instance container, remove at 4.0
			if (!isset(self::$instance))
			{
				self::$instance = self::$instances[$basepath];
			}
		}

		return self::$instances[$basepath];
	}

	/**
	 * Get the allow overwrite switch
	 *
	 * @return  boolean  Allow overwrite switch
	 *
	 * @since   3.1
	 */
	public function isOverwrite()
	{
		return $this->overwrite;
	}

	/**
	 * Set the allow overwrite switch
	 *
	 * @param   boolean  $state  Overwrite switch state
	 *
	 * @return  boolean  True it state is set, false if it is not
	 *
	 * @since   3.1
	 */
	public function setOverwrite($state = false)
	{
		$tmp = $this->overwrite;

		if ($state)
		{
			$this->overwrite = true;
		}
		else
		{
			$this->overwrite = false;
		}

		return $tmp;
	}

	/**
	 * Get the redirect location
	 *
	 * @return  string  Redirect location (or null)
	 *
	 * @since   3.1
	 */
	public function getRedirectUrl()
	{
		return $this->redirect_url;
	}

	/**
	 * Set the redirect location
	 *
	 * @param   string  $newurl  New redirect location
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setRedirectUrl($newurl)
	{
		$this->redirect_url = $newurl;
	}

	/**
	 * Get whether this installer is uninstalling extensions which are part of a package
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function isPackageUninstall()
	{
		return $this->packageUninstall;
	}

	/**
	 * Set whether this installer is uninstalling extensions which are part of a package
	 *
	 * @param   boolean  $uninstall  True if a package triggered the uninstall, false otherwise
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function setPackageUninstall($uninstall)
	{
		$this->packageUninstall = $uninstall;
	}

	/**
	 * Get the upgrade switch
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function isUpgrade()
	{
		return $this->upgrade;
	}

	/**
	 * Set the upgrade switch
	 *
	 * @param   boolean  $state  Upgrade switch state
	 *
	 * @return  boolean  True if upgrade, false otherwise
	 *
	 * @since   3.1
	 */
	public function setUpgrade($state = false)
	{
		$tmp = $this->upgrade;

		if ($state)
		{
			$this->upgrade = true;
		}
		else
		{
			$this->upgrade = false;
		}

		return $tmp;
	}

	/**
	 * Get the installation manifest object
	 *
	 * @return  \SimpleXMLElement  Manifest object
	 *
	 * @since   3.1
	 */
	public function getManifest()
	{
		if (!is_object($this->manifest))
		{
			$this->findManifest();
		}

		return $this->manifest;
	}

	/**
	 * Get an installer path by name
	 *
	 * @param   string  $name     Path name
	 * @param   string  $default  Default value
	 *
	 * @return  string  Path
	 *
	 * @since   3.1
	 */
	public function getPath($name, $default = null)
	{
		return (!empty($this->paths[$name])) ? $this->paths[$name] : $default;
	}

	/**
	 * Sets an installer path by name
	 *
	 * @param   string  $name   Path name
	 * @param   string  $value  Path
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setPath($name, $value)
	{
		$this->paths[$name] = $value;
	}

	/**
	 * Pushes a step onto the installer stack for rolling back steps
	 *
	 * @param   array  $step  Installer step
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function pushStep($step)
	{
		$this->stepStack[] = $step;
	}

	/**
	 * Installation abort method
	 *
	 * @param   string  $msg   Abort message from the installer
	 * @param   string  $type  Package type if defined
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function abort($msg = null, $type = null)
	{
		$retval = true;
		$step = array_pop($this->stepStack);

		// Raise abort warning
		if ($msg)
		{
			\JLog::add($msg, \JLog::WARNING, 'jerror');
		}

		while ($step != null)
		{
			switch ($step['type'])
			{
				case 'file':
					// Remove the file
					$stepval = \JFile::delete($step['path']);
					break;

				case 'folder':
					// Remove the folder
					$stepval = \JFolder::delete($step['path']);
					break;

				case 'query':
					// Execute the query.
					$stepval = $this->parseSQLFiles($step['script']);
					break;

				case 'extension':
					// Get database connector object
					$db = $this->getDbo();
					$query = $db->getQuery(true);

					// Remove the entry from the #__extensions table
					$query->delete($db->quoteName('#__extensions'))
						->where($db->quoteName('extension_id') . ' = ' . (int) $step['id']);
					$db->setQuery($query);

					try
					{
						$db->execute();

						$stepval = true;
					}
					catch (\JDatabaseExceptionExecuting $e)
					{
						// The database API will have already logged the error it caught, we just need to alert the user to the issue
						\JLog::add(\JText::_('JLIB_INSTALLER_ABORT_ERROR_DELETING_EXTENSIONS_RECORD'), \JLog::WARNING, 'jerror');

						$stepval = false;
					}

					break;

				default:
					if ($type && is_object($this->_adapters[$type]))
					{
						// Build the name of the custom rollback method for the type
						$method = '_rollback_' . $step['type'];

						// Custom rollback method handler
						if (method_exists($this->_adapters[$type], $method))
						{
							$stepval = $this->_adapters[$type]->$method($step);
						}
					}
					else
					{
						// Set it to false
						$stepval = false;
					}
					break;
			}

			// Only set the return value if it is false
			if ($stepval === false)
			{
				$retval = false;
			}

			// Get the next step and continue
			$step = array_pop($this->stepStack);
		}

		return $retval;
	}

	// Adapter functions

	/**
	 * Package installation method
	 *
	 * @param   string  $path  Path to package source folder
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function install($path = null)
	{
		if ($path && \JFolder::exists($path))
		{
			$this->setPath('source', $path);
		}
		else
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOINSTALLPATH'));

			return false;
		}

		if (!$adapter = $this->setupInstall('install', true))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));

			return false;
		}

		if (!is_object($adapter))
		{
			return false;
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage($path);
		}

		// Fire the onExtensionBeforeInstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger(
			'onExtensionBeforeInstall',
			array(
				'method' => 'install',
				'type' => $this->manifest->attributes()->type,
				'manifest' => $this->manifest,
				'extension' => 0,
			)
		);

		// Run the install
		$result = $adapter->install();

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterInstall',
			array('installer' => clone $this, 'eid' => $result)
		);

		if ($result !== false)
		{
			// Refresh versionable assets cache
			\JFactory::getApplication()->flushAssets();

			return true;
		}

		return false;
	}

	/**
	 * Discovered package installation method
	 *
	 * @param   integer  $eid  Extension ID
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function discover_install($eid = null)
	{
		if (!$eid)
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_EXTENSIONNOTVALID'));

			return false;
		}

		if (!$this->extension->load($eid))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));

			return false;
		}

		if ($this->extension->state != -1)
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_ALREADYINSTALLED'));

			return false;
		}

		// Load the adapter(s) for the install manifest
		$type   = $this->extension->type;
		$params = array('extension' => $this->extension, 'route' => 'discover_install');

		$adapter = $this->getAdapter($type, $params);

		if (!is_object($adapter))
		{
			return false;
		}

		if (!method_exists($adapter, 'discover_install') || !$adapter->getDiscoverInstallSupported())
		{
			$this->abort(\JText::sprintf('JLIB_INSTALLER_ERROR_DISCOVER_INSTALL_UNSUPPORTED', $type));

			return false;
		}

		// The adapter needs to prepare itself
		if (method_exists($adapter, 'prepareDiscoverInstall'))
		{
			try
			{
				$adapter->prepareDiscoverInstall();
			}
			catch (\RuntimeException $e)
			{
				$this->abort($e->getMessage());

				return false;
			}
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage();
		}

		// Fire the onExtensionBeforeInstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger(
			'onExtensionBeforeInstall',
			array(
				'method' => 'discover_install',
				'type' => $this->extension->get('type'),
				'manifest' => null,
				'extension' => $this->extension->get('extension_id'),
			)
		);

		// Run the install
		$result = $adapter->discover_install();

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterInstall',
			array('installer' => clone $this, 'eid' => $result)
		);

		if ($result !== false)
		{
			// Refresh versionable assets cache
			\JFactory::getApplication()->flushAssets();

			return true;
		}

		return false;
	}

	/**
	 * Extension discover method
	 *
	 * Asks each adapter to find extensions
	 *
	 * @return  InstallerExtension[]
	 *
	 * @since   3.1
	 */
	public function discover()
	{
		$this->loadAllAdapters();
		$results = array();

		foreach ($this->_adapters as $adapter)
		{
			// Joomla! 1.5 installation adapter legacy support
			if (method_exists($adapter, 'discover'))
			{
				$tmp = $adapter->discover();

				// If its an array and has entries
				if (is_array($tmp) && count($tmp))
				{
					// Merge it into the system
					$results = array_merge($results, $tmp);
				}
			}
		}

		return $results;
	}

	/**
	 * Package update method
	 *
	 * @param   string  $path  Path to package source folder
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function update($path = null)
	{
		if ($path && \JFolder::exists($path))
		{
			$this->setPath('source', $path);
		}
		else
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_NOUPDATEPATH'));

			return false;
		}

		if (!$adapter = $this->setupInstall('update', true))
		{
			$this->abort(\JText::_('JLIB_INSTALLER_ABORT_DETECTMANIFEST'));

			return false;
		}

		if (!is_object($adapter))
		{
			return false;
		}

		// Add the languages from the package itself
		if (method_exists($adapter, 'loadLanguage'))
		{
			$adapter->loadLanguage($path);
		}

		// Fire the onExtensionBeforeUpdate event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onExtensionBeforeUpdate', array('type' => $this->manifest->attributes()->type, 'manifest' => $this->manifest));

		// Run the update
		$result = $adapter->update();

		// Fire the onExtensionAfterUpdate
		$dispatcher->trigger(
			'onExtensionAfterUpdate',
			array('installer' => clone $this, 'eid' => $result)
		);

		if ($result !== false)
		{
			return true;
		}

		return false;
	}

	/**
	 * Package uninstallation method
	 *
	 * @param   string   $type        Package type
	 * @param   mixed    $identifier  Package identifier for adapter
	 * @param   integer  $cid         Application ID; deprecated in 1.6
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.1
	 */
	public function uninstall($type, $identifier, $cid = 0)
	{
		$params = array('extension' => $this->extension, 'route' => 'uninstall');

		$adapter = $this->getAdapter($type, $params);

		if (!is_object($adapter))
		{
			return false;
		}

		// We don't load languages here, we get the extension adapter to work it out
		// Fire the onExtensionBeforeUninstall event.
		PluginHelper::importPlugin('extension');
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onExtensionBeforeUninstall', array('eid' => $identifier));

		// Run the uninstall
		$result = $adapter->uninstall($identifier);

		// Fire the onExtensionAfterInstall
		$dispatcher->trigger(
			'onExtensionAfterUninstall',
			array('installer' => clone $this, 'eid' => $identifier, 'result' => $result)
		);

		// Refresh versionable assets cache
		\JFactory::getApplication()->flushAssets();

		return $result;
	}

	/**
	 * Refreshes the manifest cache stored in #__extensions
	 *
	 * @param   integer  $eid  Extension ID
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function refreshManifestCache($eid)
	{
		if ($eid)
		{
			if (!$this->extension->load($eid))
			{
				$this->abort(\JText::_('JLIB_INSTALLER_ABORT_LOAD_DETAILS'));

				return false;
			}

			if ($this->extension->state == -1)
			{
				$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE', $this->extension->name));

				return false;
			}

			// Fetch the adapter
			$adapter = $this->getAdapter($this->extension->type);

			if (!is_object($adapter))
			{
				return false;
			}

			if (!method_exists($adapter, 'refreshManifestCache'))
			{
				$this->abort(\JText::sprintf('JLIB_INSTALLER_ABORT_METHODNOTSUPPORTED_TYPE', $this->extension->type));

				return false;
			}

			$result = $adapter->refreshManifestCache();

			if ($result !== false)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		$this->abort(\JText::_('JLIB_INSTALLER_ABORT_REFRESH_MANIFEST_CACHE_VALID'));

		return false;
	}

	// Utility functions

	/**
	 * Prepare for installation: this method sets the installation directory, finds
	 * and checks the installation file and verifies the installation type.
	 *
	 * @param   string   $route          The install route being followed
	 * @param   boolean  $returnAdapter  Flag to return the instantiated adapter
	 *
	 * @return  boolean|InstallerAdapter  InstallerAdapter object if explicitly requested otherwise boolean
	 *
	 * @since   3.1
	 */
	public function setupInstall($route = 'install', $returnAdapter = false)
	{
		// We need to find the installation manifest file
		if (!$this->findManifest())
		{
			return false;
		}

		// Load the adapter(s) for the install manifest
		$type   = (string) $this->manifest->attributes()->type;
		$params = array('route' => $route, 'manifest' => $this->getManifest());

		// Load the adapter
		$adapter = $this->getAdapter($type, $params);

		if ($returnAdapter)
		{
			return $adapter;
		}

		return true;
	}

	/**
	 * Backward compatible method to parse through a queries element of the
	 * installation manifest file and take appropriate action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 *
	 * @return  mixed  Number of queries processed or False on error
	 *
	 * @since   3.1
	 */
	public function parseQueries(\SimpleXMLElement $element)
	{
		// Get the database connector object
		$db = & $this->_db;

		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return zero files processed.
			return 0;
		}

		// Get the array of query nodes to process
		$queries = $element->children();

		if (count($queries) === 0)
		{
			// No queries to process
			return 0;
		}

		$update_count = 0;

		// Process each query in the $queries array (children of $tagName).
		foreach ($queries as $query)
		{
			$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

			try
			{
				$db->execute();
			}
			catch (\JDatabaseExceptionExecuting $e)
			{
				\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), \JLog::WARNING, 'jerror');

				return false;
			}

			$update_count++;
		}

		return $update_count;
	}

	/**
	 * Method to extract the name of a discreet installation sql file from the installation manifest file.
	 *
	 * @param   object  $element  The XML node to process
	 *
	 * @return  mixed  Number of queries processed or False on error
	 *
	 * @since   3.1
	 */
	public function parseSQLFiles($element)
	{
		if (!$element || !count($element->children()))
		{
			// The tag does not exist.
			return 0;
		}

		$db = & $this->_db;

		// TODO - At 4.0 we can change this to use `getServerType()` since SQL Server will not be supported
		$dbDriver = strtolower($db->name);

		if ($db->getServerType() === 'mysql')
		{
			$dbDriver = 'mysql';
		}
		elseif ($db->getServerType() === 'postgresql')
		{
			$dbDriver = 'postgresql';
		}

		$update_count = 0;

		// Get the name of the sql file to process
		foreach ($element->children() as $file)
		{
			$fCharset = strtolower($file->attributes()->charset) === 'utf8' ? 'utf8' : '';
			$fDriver  = strtolower($file->attributes()->driver);

			if ($fDriver === 'mysqli' || $fDriver === 'pdomysql')
			{
				$fDriver = 'mysql';
			}
			elseif ($fDriver === 'pgsql')
			{
				$fDriver = 'postgresql';
			}

			if ($fCharset === 'utf8' && $fDriver == $dbDriver)
			{
				$sqlfile = $this->getPath('extension_root') . '/' . trim($file);

				// Check that sql files exists before reading. Otherwise raise error for rollback
				if (!file_exists($sqlfile))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_FILENOTFOUND', $sqlfile), \JLog::WARNING, 'jerror');

					return false;
				}

				$buffer = file_get_contents($sqlfile);

				// Graceful exit and rollback if read not successful
				if ($buffer === false)
				{
					\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), \JLog::WARNING, 'jerror');

					return false;
				}

				// Create an array of queries from the sql file
				$queries = \JDatabaseDriver::splitSql($buffer);

				if (count($queries) === 0)
				{
					// No queries to process
					continue;
				}

				// Process each query in the $queries array (split out of sql file).
				foreach ($queries as $query)
				{
					$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

					try
					{
						$db->execute();
					}
					catch (\JDatabaseExceptionExecuting $e)
					{
						\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), \JLog::WARNING, 'jerror');

						return false;
					}

					$update_count++;
				}
			}
		}

		return $update_count;
	}

	/**
	 * Set the schema version for an extension by looking at its latest update
	 *
	 * @param   \SimpleXMLElement  $schema  Schema Tag
	 * @param   integer            $eid     Extension ID
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function setSchemaVersion(\SimpleXMLElement $schema, $eid)
	{
		if ($eid && $schema)
		{
			$db = \JFactory::getDbo();
			$schemapaths = $schema->children();

			if (!$schemapaths)
			{
				return;
			}

			if (count($schemapaths))
			{
				$dbDriver = strtolower($db->name);

				if ($db->getServerType() === 'mysql')
				{
					$dbDriver = 'mysql';
				}
				elseif ($db->getServerType() === 'postgresql')
				{
					$dbDriver = 'postgresql';
				}

				$schemapath = '';

				foreach ($schemapaths as $entry)
				{
					$attrs = $entry->attributes();

					if ($attrs['type'] == $dbDriver)
					{
						$schemapath = $entry;
						break;
					}
				}

				if ($schemapath !== '')
				{
					$files = str_replace('.sql', '', \JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$'));
					usort($files, 'version_compare');

					// Update the database
					$query = $db->getQuery(true)
						->delete('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);

					if ($db->execute())
					{
						$query->clear()
							->insert($db->quoteName('#__schemas'))
							->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')))
							->values($eid . ', ' . $db->quote(end($files)));
						$db->setQuery($query);
						$db->execute();
					}
				}
			}
		}
	}

	/**
	 * Method to process the updates for an item
	 *
	 * @param   \SimpleXMLElement  $schema  The XML node to process
	 * @param   integer            $eid     Extension Identifier
	 *
	 * @return  boolean           Result of the operations
	 *
	 * @since   3.1
	 */
	public function parseSchemaUpdates(\SimpleXMLElement $schema, $eid)
	{
		$update_count = 0;

		// Ensure we have an XML element and a valid extension id
		if ($eid && $schema)
		{
			$db = \JFactory::getDbo();
			$schemapaths = $schema->children();

			if (count($schemapaths))
			{
				// TODO - At 4.0 we can change this to use `getServerType()` since SQL Server will not be supported
				$dbDriver = strtolower($db->name);

				if ($db->getServerType() === 'mysql')
				{
					$dbDriver = 'mysql';
				}
				elseif ($db->getServerType() === 'postgresql')
				{
					$dbDriver = 'postgresql';
				}

				$schemapath = '';

				foreach ($schemapaths as $entry)
				{
					$attrs = $entry->attributes();

					// Assuming that the type is a mandatory attribute but if it is not mandatory then there should be a discussion for it.
					$uDriver = strtolower($attrs['type']);

					if ($uDriver === 'mysqli' || $uDriver === 'pdomysql')
					{
						$uDriver = 'mysql';
					}
					elseif ($uDriver === 'pgsql')
					{
						$uDriver = 'postgresql';
					}

					if ($uDriver == $dbDriver)
					{
						$schemapath = $entry;
						break;
					}
				}

				if ($schemapath !== '')
				{
					$files = \JFolder::files($this->getPath('extension_root') . '/' . $schemapath, '\.sql$');

					if (empty($files))
					{
						return $update_count;
					}

					$files = str_replace('.sql', '', $files);
					usort($files, 'version_compare');

					$query = $db->getQuery(true)
						->select('version_id')
						->from('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);
					$version = $db->loadResult();

					// No version - use initial version.
					if (!$version)
					{
						$version = '0.0.0';
					}

					foreach ($files as $file)
					{
						if (version_compare($file, $version) > 0)
						{
							$buffer = file_get_contents($this->getPath('extension_root') . '/' . $schemapath . '/' . $file . '.sql');

							// Graceful exit and rollback if read not successful
							if ($buffer === false)
							{
								\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_READBUFFER'), \JLog::WARNING, 'jerror');

								return false;
							}

							// Create an array of queries from the sql file
							$queries = \JDatabaseDriver::splitSql($buffer);

							if (count($queries) === 0)
							{
								// No queries to process
								continue;
							}

							// Process each query in the $queries array (split out of sql file).
							foreach ($queries as $query)
							{
								$db->setQuery($db->convertUtf8mb4QueryToUtf8($query));

								try
								{
									$db->execute();
								}
								catch (\JDatabaseExceptionExecuting $e)
								{
									\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_SQL_ERROR', $e->getMessage()), \JLog::WARNING, 'jerror');

									return false;
								}

								$queryString = (string) $query;
								$queryString = str_replace(array("\r", "\n"), array('', ' '), substr($queryString, 0, 80));
								\JLog::add(\JText::sprintf('JLIB_INSTALLER_UPDATE_LOG_QUERY', $file, $queryString), \JLog::INFO, 'Update');

								$update_count++;
							}
						}
					}

					// Update the database
					$query = $db->getQuery(true)
						->delete('#__schemas')
						->where('extension_id = ' . $eid);
					$db->setQuery($query);

					if ($db->execute())
					{
						$query->clear()
							->insert($db->quoteName('#__schemas'))
							->columns(array($db->quoteName('extension_id'), $db->quoteName('version_id')))
							->values($eid . ', ' . $db->quote(end($files)));
						$db->setQuery($query);
						$db->execute();
					}
				}
			}
		}

		return $update_count;
	}

	/**
	 * Method to parse through a files element of the installation manifest and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element   The XML node to process
	 * @param   integer            $cid       Application ID of application to install to
	 * @param   array              $oldFiles  List of old files (SimpleXMLElement's)
	 * @param   array              $oldMD5    List of old MD5 sums (indexed by filename with value as MD5)
	 *
	 * @return  boolean      True on success
	 *
	 * @since   3.1
	 */
	public function parseFiles(\SimpleXMLElement $element, $cid = 0, $oldFiles = null, $oldMD5 = null)
	{
		// Get the array of file nodes to process; we checked whether this had children above.
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children (hence no files to process) therefore we return zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		/*
		 * Here we set the folder we are going to remove the files from.
		 */
		if ($client)
		{
			$pathname = 'extension_' . $client->name;
			$destination = $this->getPath($pathname);
		}
		else
		{
			$pathname = 'extension_root';
			$destination = $this->getPath($pathname);
		}

		/*
		 * Here we set the folder we are going to copy the files from.
		 *
		 * Does the element have a folder attribute?
		 *
		 * If so this indicates that the files are in a subdirectory of the source
		 * folder and we should append the folder attribute to the source path when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source') . '/' . $folder))
		{
			$source = $this->getPath('source') . '/' . $folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Work out what files have been deleted
		if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement))
		{
			$oldEntries = $oldFiles->children();

			if (count($oldEntries))
			{
				$deletions = $this->findDeletedFiles($oldEntries, $element->children());

				foreach ($deletions['folders'] as $deleted_folder)
				{
					\JFolder::delete($destination . '/' . $deleted_folder);
				}

				foreach ($deletions['files'] as $deleted_file)
				{
					\JFile::delete($destination . '/' . $deleted_file);
				}
			}
		}

		$path = array();

		// Copy the MD5SUMS file if it exists
		if (file_exists($source . '/MD5SUMS'))
		{
			$path['src'] = $source . '/MD5SUMS';
			$path['dest'] = $destination . '/MD5SUMS';
			$path['type'] = 'file';
			$copyfiles[] = $path;
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			$path['src'] = $source . '/' . $file;
			$path['dest'] = $destination . '/' . $file;

			// Is this path a file or folder?
			$path['type'] = $file->getName() === 'folder' ? 'folder' : 'file';

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse through a languages element of the installation manifest and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 * @param   integer            $cid      Application ID of application to install to
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function parseLanguages(\SimpleXMLElement $element, $cid = 0)
	{
		// TODO: work out why the below line triggers 'node no longer exists' errors with files
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		// Here we set the folder we are going to copy the files to.
		// 'languages' Files are copied to JPATH_BASE/language/ folder

		$destination = $client->path . '/language';

		/*
		 * Here we set the folder we are going to copy the files from.
		 *
		 * Does the element have a folder attribute?
		 *
		 * If so this indicates that the files are in a subdirectory of the source
		 * folder and we should append the folder attribute to the source path when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source') . '/' . $folder))
		{
			$source = $this->getPath('source') . '/' . $folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			/*
			 * Language files go in a subfolder based on the language code, ie.
			 * <language tag="en-US">en-US.mycomponent.ini</language>
			 * would go in the en-US subdirectory of the language folder.
			 */

			// We will only install language files where a core language pack
			// already exists.

			if ((string) $file->attributes()->tag !== '')
			{
				$path['src'] = $source . '/' . $file;

				if ((string) $file->attributes()->client !== '')
				{
					// Override the client
					$langclient = ApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
					$path['dest'] = $langclient->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
				}
				else
				{
					// Use the default client
					$path['dest'] = $destination . '/' . $file->attributes()->tag . '/' . basename((string) $file);
				}

				// If the language folder is not present, then the core pack hasn't been installed... ignore
				if (!\JFolder::exists(dirname($path['dest'])))
				{
					continue;
				}
			}
			else
			{
				$path['src'] = $source . '/' . $file;
				$path['dest'] = $destination . '/' . $file;
			}

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse through a media element of the installation manifest and take appropriate
	 * action.
	 *
	 * @param   \SimpleXMLElement  $element  The XML node to process
	 * @param   integer            $cid      Application ID of application to install to
	 *
	 * @return  boolean     True on success
	 *
	 * @since   3.1
	 */
	public function parseMedia(\SimpleXMLElement $element, $cid = 0)
	{
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return zero files processed.
			return 0;
		}

		$copyfiles = array();

		// Here we set the folder we are going to copy the files to.
		// Default 'media' Files are copied to the JPATH_BASE/media folder

		$folder = ((string) $element->attributes()->destination) ? '/' . $element->attributes()->destination : null;
		$destination = \JPath::clean(JPATH_ROOT . '/media' . $folder);

		// Here we set the folder we are going to copy the files from.

		/*
		 * Does the element have a folder attribute?
		 * If so this indicates that the files are in a subdirectory of the source
		 * folder and we should append the folder attribute to the source path when
		 * copying files.
		 */

		$folder = (string) $element->attributes()->folder;

		if ($folder && file_exists($this->getPath('source') . '/' . $folder))
		{
			$source = $this->getPath('source') . '/' . $folder;
		}
		else
		{
			$source = $this->getPath('source');
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($element->children() as $file)
		{
			$path['src'] = $source . '/' . $file;
			$path['dest'] = $destination . '/' . $file;

			// Is this path a file or folder?
			$path['type'] = $file->getName() === 'folder' ? 'folder' : 'file';

			/*
			 * Before we can add a file to the copyfiles array we need to ensure
			 * that the folder we are copying our file to exits and if it doesn't,
			 * we need to create it.
			 */

			if (basename($path['dest']) !== $path['dest'])
			{
				$newdir = dirname($path['dest']);

				if (!\JFolder::create($newdir))
				{
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_CREATE_DIRECTORY', $newdir), \JLog::WARNING, 'jerror');

					return false;
				}
			}

			// Add the file to the copyfiles array
			$copyfiles[] = $path;
		}

		return $this->copyFiles($copyfiles);
	}

	/**
	 * Method to parse the parameters of an extension, build the JSON string for its default parameters, and return the JSON string.
	 *
	 * @return  string  JSON string of parameter values
	 *
	 * @since   3.1
	 * @note    This method must always return a JSON compliant string
	 */
	public function getParams()
	{
		// Validate that we have a fieldset to use
		if (!isset($this->manifest->config->fields->fieldset))
		{
			return '{}';
		}

		// Getting the fieldset tags
		$fieldsets = $this->manifest->config->fields->fieldset;

		// Creating the data collection variable:
		$ini = array();

		// Iterating through the fieldsets:
		foreach ($fieldsets as $fieldset)
		{
			if (!count($fieldset->children()))
			{
				// Either the tag does not exist or has no children therefore we return zero files processed.
				return '{}';
			}

			// Iterating through the fields and collecting the name/default values:
			foreach ($fieldset as $field)
			{
				// Check against the null value since otherwise default values like "0"
				// cause entire parameters to be skipped.

				if (($name = $field->attributes()->name) === null)
				{
					continue;
				}

				if (($value = $field->attributes()->default) === null)
				{
					continue;
				}

				$ini[(string) $name] = (string) $value;
			}
		}

		return json_encode($ini);
	}

	/**
	 * Copyfiles
	 *
	 * Copy files from source directory to the target directory
	 *
	 * @param   array    $files      Array with filenames
	 * @param   boolean  $overwrite  True if existing files can be replaced
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function copyFiles($files, $overwrite = null)
	{
		/*
		 * To allow for manual override on the overwriting flag, we check to see if
		 * the $overwrite flag was set and is a boolean value.  If not, use the object
		 * allowOverwrite flag.
		 */

		if ($overwrite === null || !is_bool($overwrite))
		{
			$overwrite = $this->overwrite;
		}

		/*
		 * $files must be an array of filenames.  Verify that it is an array with
		 * at least one file to copy.
		 */
		if (is_array($files) && count($files) > 0)
		{
			foreach ($files as $file)
			{
				// Get the source and destination paths
				$filesource = \JPath::clean($file['src']);
				$filedest = \JPath::clean($file['dest']);
				$filetype = array_key_exists('type', $file) ? $file['type'] : 'file';

				if (!file_exists($filesource))
				{
					/*
					 * The source file does not exist.  Nothing to copy so set an error
					 * and return false.
					 */
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_NO_FILE', $filesource), \JLog::WARNING, 'jerror');

					return false;
				}
				elseif (($exists = file_exists($filedest)) && !$overwrite)
				{
					// It's okay if the manifest already exists
					if ($this->getPath('manifest') === $filesource)
					{
						continue;
					}

					// The destination file already exists and the overwrite flag is false.
					// Set an error and return false.
					\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FILE_EXISTS', $filedest), \JLog::WARNING, 'jerror');

					return false;
				}
				else
				{
					// Copy the folder or file to the new location.
					if ($filetype === 'folder')
					{
						if (!\JFolder::copy($filesource, $filedest, null, $overwrite))
						{
							\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FOLDER', $filesource, $filedest), \JLog::WARNING, 'jerror');

							return false;
						}

						$step = array('type' => 'folder', 'path' => $filedest);
					}
					else
					{
						if (!\JFile::copy($filesource, $filedest, null))
						{
							\JLog::add(\JText::sprintf('JLIB_INSTALLER_ERROR_FAIL_COPY_FILE', $filesource, $filedest), \JLog::WARNING, 'jerror');

							// In 3.2, TinyMCE language handling changed.  Display a special notice in case an older language pack is installed.
							if (strpos($filedest, 'media/editors/tinymce/jscripts/tiny_mce/langs'))
							{
								\JLog::add(\JText::_('JLIB_INSTALLER_NOT_ERROR'), \JLog::WARNING, 'jerror');
							}

							return false;
						}

						$step = array('type' => 'file', 'path' => $filedest);
					}

					/*
					 * Since we copied a file/folder, we want to add it to the installation step stack so that
					 * in case we have to roll back the installation we can remove the files copied.
					 */
					if (!$exists)
					{
						$this->stepStack[] = $step;
					}
				}
			}
		}
		else
		{
			// The $files variable was either not an array or an empty array
			return false;
		}

		return count($files);
	}

	/**
	 * Method to parse through a files element of the installation manifest and remove
	 * the files that were installed
	 *
	 * @param   object   $element  The XML node to process
	 * @param   integer  $cid      Application ID of application to remove from
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 */
	public function removeFiles($element, $cid = 0)
	{
		if (!$element || !count($element->children()))
		{
			// Either the tag does not exist or has no children therefore we return zero files processed.
			return true;
		}

		$retval = true;

		// Get the client info if we're using a specific client
		if ($cid > -1)
		{
			$client = ApplicationHelper::getClientInfo($cid);
		}
		else
		{
			$client = null;
		}

		// Get the array of file nodes to process
		$files = $element->children();

		if (count($files) === 0)
		{
			// No files to process
			return true;
		}

		$folder = '';

		/*
		 * Here we set the folder we are going to remove the files from.  There are a few
		 * special cases that need to be considered for certain reserved tags.
		 */
		switch ($element->getName())
		{
			case 'media':
				if ((string) $element->attributes()->destination)
				{
					$folder = (string) $element->attributes()->destination;
				}
				else
				{
					$folder = '';
				}

				$source = $client->path . '/media/' . $folder;

				break;

			case 'languages':
				$lang_client = (string) $element->attributes()->client;

				if ($lang_client)
				{
					$client = ApplicationHelper::getClientInfo($lang_client, true);
					$source = $client->path . '/language';
				}
				else
				{
					if ($client)
					{
						$source = $client->path . '/language';
					}
					else
					{
						$source = '';
					}
				}

				break;

			default:
				if ($client)
				{
					$pathname = 'extension_' . $client->name;
					$source = $this->getPath($pathname);
				}
				else
				{
					$pathname = 'extension_root';
					$source = $this->getPath($pathname);
				}

				break;
		}

		// Process each file in the $files array (children of $tagName).
		foreach ($files as $file)
		{
			/*
			 * If the file is a language, we must handle it differently.  Language files
			 * go in a subdirectory based on the language code, ie.
			 * <language tag="en_US">en_US.mycomponent.ini</language>
			 * would go in the en_US subdirectory of the languages directory.
			 */

			if ($file->getName() === 'language' && (string) $file->attributes()->tag !== '')
			{
				if ($source)
				{
					$path = $source . '/' . $file->attributes()->tag . '/' . basename((string) $file);
				}
				else
				{
					$target_client = ApplicationHelper::getClientInfo((string) $file->attributes()->client, true);
					$path = $target_client->path . '/language/' . $file->attributes()->tag . '/' . basename((string) $file);
				}

				// If the language folder is not present, then the core pack hasn't been installed... ignore
				if (!\JFolder::exists(dirname($path)))
				{
					continue;
				}
			}
			else
			{
				$path = $source . '/' . $file;
			}

			// Actually delete the files/folders

			if (is_dir($path))
			{
				$val = \JFolder::delete($path);
			}
			else
			{
				$val = \JFile::delete($path);
			}

			if ($val === false)
			{
				\JLog::add('Failed to delete ' . $path, \JLog::WARNING, 'jerror');
				$retval = false;
			}
		}

		if (!empty($folder))
		{
			\JFolder::delete($source);
		}

		return $retval;
	}

	/**
	 * Copies the installation manifest file to the extension folder in the given client
	 *
	 * @param   integer  $cid  Where to copy the installfile [optional: defaults to 1 (admin)]
	 *
	 * @return  boolean  True on success, False on error
	 *
	 * @since   3.1
	 */
	public function copyManifest($cid = 1)
	{
		// Get the client info
		$client = ApplicationHelper::getClientInfo($cid);

		$path['src'] = $this->getPath('manifest');

		if ($client)
		{
			$pathname = 'extension_' . $client->name;
			$path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
		}
		else
		{
			$pathname = 'extension_root';
			$path['dest'] = $this->getPath($pathname) . '/' . basename($this->getPath('manifest'));
		}

		return $this->copyFiles(array($path), true);
	}

	/**
	 * Tries to find the package manifest file
	 *
	 * @return  boolean  True on success, False on error
	 *
	 * @since   3.1
	 */
	public function findManifest()
	{
		// Do nothing if folder does not exist for some reason
		if (!\JFolder::exists($this->getPath('source')))
		{
			return false;
		}

		// Main folder manifests (higher priority)
		$parentXmlfiles = \JFolder::files($this->getPath('source'), '.xml$', false, true);

		// Search for children manifests (lower priority)
		$allXmlFiles    = \JFolder::files($this->getPath('source'), '.xml$', 1, true);

		// Create an unique array of files ordered by priority
		$xmlfiles = array_unique(array_merge($parentXmlfiles, $allXmlFiles));

		// If at least one XML file exists
		if (!empty($xmlfiles))
		{
			foreach ($xmlfiles as $file)
			{
				// Is it a valid Joomla installation manifest file?
				$manifest = $this->isManifest($file);

				if ($manifest !== null)
				{
					// If the root method attribute is set to upgrade, allow file overwrite
					if ((string) $manifest->attributes()->method === 'upgrade')
					{
						$this->upgrade = true;
						$this->overwrite = true;
					}

					// If the overwrite option is set, allow file overwriting
					if ((string) $manifest->attributes()->overwrite === 'true')
					{
						$this->overwrite = true;
					}

					// Set the manifest object and path
					$this->manifest = $manifest;
					$this->setPath('manifest', $file);

					// Set the installation source path to that of the manifest file
					$this->setPath('source', dirname($file));

					return true;
				}
			}

			// None of the XML files found were valid install files
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDJOOMLAXMLSETUPFILE'), \JLog::WARNING, 'jerror');

			return false;
		}
		else
		{
			// No XML files were found in the install folder
			\JLog::add(\JText::_('JLIB_INSTALLER_ERROR_NOTFINDXMLSETUPFILE'), \JLog::WARNING, 'jerror');

			return false;
		}
	}

	/**
	 * Is the XML file a valid Joomla installation manifest file.
	 *
	 * @param   string  $file  An xmlfile path to check
	 *
	 * @return  \SimpleXMLElement|null  A \SimpleXMLElement, or null if the file failed to parse
	 *
	 * @since   3.1
	 */
	public function isManifest($file)
	{
		$xml = simplexml_load_file($file);

		// If we cannot load the XML file return null
		if (!$xml)
		{
			return;
		}

		// Check for a valid XML root tag.
		if ($xml->getName() !== 'extension')
		{
			return;
		}

		// Valid manifest file return the object
		return $xml;
	}

	/**
	 * Generates a manifest cache
	 *
	 * @return string serialised manifest data
	 *
	 * @since   3.1
	 */
	public function generateManifestCache()
	{
		return json_encode(self::parseXMLInstallFile($this->getPath('manifest')));
	}

	/**
	 * Cleans up discovered extensions if they're being installed some other way
	 *
	 * @param   string   $type     The type of extension (component, etc)
	 * @param   string   $element  Unique element identifier (e.g. com_content)
	 * @param   string   $folder   The folder of the extension (plugins; e.g. system)
	 * @param   integer  $client   The client application (administrator or site)
	 *
	 * @return  object    Result of query
	 *
	 * @since   3.1
	 */
	public function cleanDiscoveredExtension($type, $element, $folder = '', $client = 0)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__extensions'))
			->where('type = ' . $db->quote($type))
			->where('element = ' . $db->quote($element))
			->where('folder = ' . $db->quote($folder))
			->where('client_id = ' . (int) $client)
			->where('state = -1');
		$db->setQuery($query);

		return $db->execute();
	}

	/**
	 * Compares two "files" entries to find deleted files/folders
	 *
	 * @param   array  $old_files  An array of \SimpleXMLElement objects that are the old files
	 * @param   array  $new_files  An array of \SimpleXMLElement objects that are the new files
	 *
	 * @return  array  An array with the delete files and folders in findDeletedFiles[files] and findDeletedFiles[folders] respectively
	 *
	 * @since   3.1
	 */
	public function findDeletedFiles($old_files, $new_files)
	{
		// The magic find deleted files function!
		// The files that are new
		$files = array();

		// The folders that are new
		$folders = array();

		// The folders of the files that are new
		$containers = array();

		// A list of files to delete
		$files_deleted = array();

		// A list of folders to delete
		$folders_deleted = array();

		foreach ($new_files as $file)
		{
			switch ($file->getName())
			{
				case 'folder':
					// Add any folders to the list
					$folders[] = (string) $file; // add any folders to the list
					break;

				case 'file':
				default:
					// Add any files to the list
					$files[] = (string) $file;

					// Now handle the folder part of the file to ensure we get any containers
					// Break up the parts of the directory
					$container_parts = explode('/', dirname((string) $file));

					// Make sure this is clean and empty
					$container = '';

					foreach ($container_parts as $part)
					{
						// Iterate through each part
						// Add a slash if its not empty
						if (!empty($container))
						{
							$container .= '/';
						}

						// Aappend the folder part
						$container .= $part;

						if (!in_array($container, $containers))
						{
							// Add the container if it doesn't already exist
							$containers[] = $container;
						}
					}
					break;
			}
		}

		foreach ($old_files as $file)
		{
			switch ($file->getName())
			{
				case 'folder':
					if (!in_array((string) $file, $folders))
					{
						// See whether the folder exists in the new list
						if (!in_array((string) $file, $containers))
						{
							// Check if the folder exists as a container in the new list
							// If it's not in the new list or a container then delete it
							$folders_deleted[] = (string) $file;
						}
					}
					break;

				case 'file':
				default:
					if (!in_array((string) $file, $files))
					{
						// Look if the file exists in the new list
						if (!in_array(dirname((string) $file), $folders))
						{
							// Look if the file is now potentially in a folder
							$files_deleted[] = (string) $file; // not in a folder, doesn't exist, wipe it out!
						}
					}
					break;
			}
		}

		return array('files' => $files_deleted, 'folders' => $folders_deleted);
	}

	/**
	 * Loads an MD5SUMS file into an associative array
	 *
	 * @param   string  $filename  Filename to load
	 *
	 * @return  array  Associative array with filenames as the index and the MD5 as the value
	 *
	 * @since   3.1
	 */
	public function loadMD5Sum($filename)
	{
		if (!file_exists($filename))
		{
			// Bail if the file doesn't exist
			return false;
		}

		$data = file($filename, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
		$retval = array();

		foreach ($data as $row)
		{
			// Split up the data
			$results = explode('  ', $row);

			// Cull any potential prefix
			$results[1] = str_replace('./', '', $results[1]);

			// Throw into the array
			$retval[$results[1]] = $results[0];
		}

		return $retval;
	}

	/**
	 * Parse a XML install manifest file.
	 *
	 * XML Root tag should be 'install' except for languages which use meta file.
	 *
	 * @param   string  $path  Full path to XML file.
	 *
	 * @return  array  XML metadata.
	 *
	 * @since   3.0.0
	 */
	public static function parseXMLInstallFile($path)
	{
		// Check if xml file exists.
		if (!file_exists($path))
		{
			return false;
		}

		// Read the file to see if it's a valid component XML file
		$xml = simplexml_load_file($path);

		if (!$xml)
		{
			return false;
		}

		// Check for a valid XML root tag.

		// Extensions use 'extension' as the root tag.  Languages use 'metafile' instead

		$name = $xml->getName();

		if ($name !== 'extension' && $name !== 'metafile')
		{
			unset($xml);

			return false;
		}

		$data = array();

		$data['name'] = (string) $xml->name;

		// Check if we're a language. If so use metafile.
		$data['type'] = $xml->getName() === 'metafile' ? 'language' : (string) $xml->attributes()->type;

		$data['creationDate'] = ((string) $xml->creationDate) ?: \JText::_('JLIB_UNKNOWN');
		$data['author'] = ((string) $xml->author) ?: \JText::_('JLIB_UNKNOWN');

		$data['copyright'] = (string) $xml->copyright;
		$data['authorEmail'] = (string) $xml->authorEmail;
		$data['authorUrl'] = (string) $xml->authorUrl;
		$data['version'] = (string) $xml->version;
		$data['description'] = (string) $xml->description;
		$data['group'] = (string) $xml->group;

		if ($xml->files && count($xml->files->children()))
		{
			$filename = \JFile::getName($path);
			$data['filename'] = \JFile::stripExt($filename);

			foreach ($xml->files->children() as $oneFile)
			{
				if ((string) $oneFile->attributes()->plugin)
				{
					$data['filename'] = (string) $oneFile->attributes()->plugin;
					break;
				}
			}
		}

		return $data;
	}

	/**
	 * Fetches an adapter and adds it to the internal storage if an instance is not set
	 * while also ensuring its a valid adapter name
	 *
	 * @param   string  $name     Name of adapter to return
	 * @param   array   $options  Adapter options
	 *
	 * @return  InstallerAdapter
	 *
	 * @since       3.4
	 * @deprecated  4.0  The internal adapter cache will no longer be supported,
	 *                   use loadAdapter() to fetch an adapter instance
	 */
	public function getAdapter($name, $options = array())
	{
		$this->getAdapters($options);

		if (!$this->setAdapter($name, $this->_adapters[$name]))
		{
			return false;
		}

		return $this->_adapters[$name];
	}

	/**
	 * Gets a list of available install adapters.
	 *
	 * @param   array  $options  An array of options to inject into the adapter
	 * @param   array  $custom   Array of custom install adapters
	 *
	 * @return  array  An array of available install adapters.
	 *
	 * @since   3.4
	 * @note    As of 4.0, this method will only return the names of available adapters and will not
	 *          instantiate them and store to the $_adapters class var.
	 */
	public function getAdapters($options = array(), array $custom = array())
	{
		$files = new \DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder);

		// Process the core adapters
		foreach ($files as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() !== 'php')
			{
				continue;
			}

			// Derive the class name from the filename.
			$name  = str_ireplace('.php', '', trim($fileName));
			$name  = str_ireplace('adapter', '', trim($name));
			$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter';

			if (!class_exists($class))
			{
				// Not namespaced
				$class = $this->_classprefix . ucfirst($name);
			}

			// Core adapters should autoload based on classname, keep this fallback just in case
			if (!class_exists($class))
			{
				// Try to load the adapter object
				\JLoader::register($class, $this->_basepath . '/' . $this->_adapterfolder . '/' . $fileName);

				if (!class_exists($class))
				{
					// Skip to next one
					continue;
				}
			}

			$this->_adapters[strtolower($name)] = $this->loadAdapter($name, $options);
		}

		// Add any custom adapters if specified
		if (count($custom) >= 1)
		{
			foreach ($custom as $adapter)
			{
				// Setup the class name
				// TODO - Can we abstract this to not depend on the Joomla class namespace without PHP namespaces?
				$class = $this->_classprefix . ucfirst(trim($adapter));

				// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
				if (!class_exists($class))
				{
					continue;
				}

				$this->_adapters[$name] = $this->loadAdapter($name, $options);
			}
		}

		return $this->_adapters;
	}

	/**
	 * Method to load an adapter instance
	 *
	 * @param   string  $adapter  Adapter name
	 * @param   array   $options  Adapter options
	 *
	 * @return  InstallerAdapter
	 *
	 * @since   3.4
	 * @throws  \InvalidArgumentException
	 */
	public function loadAdapter($adapter, $options = array())
	{
		$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($adapter) . 'Adapter';

		if (!class_exists($class))
		{
			// Not namespaced
			$class = $this->_classprefix . ucfirst($adapter);
		}

		if (!class_exists($class))
		{
			// @deprecated 4.0 - The adapter should be autoloaded or manually included by the caller
			$path = $this->_basepath . '/' . $this->_adapterfolder . '/' . $adapter . '.php';

			// Try to load the adapter object
			if (!file_exists($path))
			{
				throw new \InvalidArgumentException(sprintf('The %s install adapter does not exist.', $adapter));
			}

			// Try once more to find the class
			\JLoader::register($class, $path);

			if (!class_exists($class))
			{
				throw new \InvalidArgumentException(sprintf('The %s install adapter does not exist.', $adapter));
			}
		}

		// Ensure the adapter type is part of the options array
		$options['type'] = $adapter;

		return new $class($this, $this->getDbo(), $options);
	}

	/**
	 * Loads all adapters.
	 *
	 * @param   array  $options  Adapter options
	 *
	 * @return  void
	 *
	 * @since       3.4
	 * @deprecated  4.0  Individual adapters should be instantiated as needed
	 * @note        This method is serving as a proxy of the legacy \JAdapter API into the preferred API
	 */
	public function loadAllAdapters($options = array())
	{
		$this->getAdapters($options);
	}
}
src/Feed/FeedPerson.php000064400000002264152177723700010760 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

/**
 * Feed Person class.
 *
 * @since  3.1.4
 */
class FeedPerson
{
	/**
	 * The email address of the person.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $email;

	/**
	 * The full name of the person.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $name;

	/**
	 * The type of person.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $type;

	/**
	 * The URI for the person.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $uri;

	/**
	 * Constructor.
	 *
	 * @param   string  $name   The full name of the person.
	 * @param   string  $email  The email address of the person.
	 * @param   string  $uri    The URI for the person.
	 * @param   string  $type   The type of person.
	 *
	 * @since   3.1.4
	 */
	public function __construct($name = null, $email = null, $uri = null, $type = null)
	{
		$this->name = $name;
		$this->email = $email;
		$this->uri = $uri;
		$this->type = $type;
	}
}
src/Feed/FeedFactory.php000064400000010171152177723700011115 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Http\HttpFactory;
use Joomla\Registry\Registry;

/**
 * Feed factory class.
 *
 * @since  3.1.4
 */
class FeedFactory
{
	/**
	 * @var    array  The list of registered parser classes for feeds.
	 * @since  3.1.4
	 */
	protected $parsers = array('rss' => 'Joomla\\CMS\\Feed\\Parser\\RssParser', 'feed' => 'Joomla\\CMS\\Feed\\Parser\\AtomParser');

	/**
	 * Method to load a URI into the feed reader for parsing.
	 *
	 * @param   string  $uri  The URI of the feed to load. Idn uris must be passed already converted to punycode.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public function getFeed($uri)
	{
		// Create the XMLReader object.
		$reader = new \XMLReader;

		// Open the URI within the stream reader.
		if (!@$reader->open($uri, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
		{
			// Retry with JHttpFactory that allow using CURL and Sockets as alternative method when available

			// Adding a valid user agent string, otherwise some feed-servers returning an error
			$options = new Registry;
			$options->set('userAgent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0');

			try
			{
				$response = HttpFactory::getHttp($options)->get($uri);
			}
			catch (RuntimeException $e)
			{
				throw new \RuntimeException('Unable to open the feed.', $e->getCode(), $e);
			}

			if ($response->code != 200)
			{
				throw new \RuntimeException('Unable to open the feed.');
			}

			// Set the value to the XMLReader parser
			if (!$reader->xml($response->body, null, LIBXML_NOERROR | LIBXML_ERR_NONE | LIBXML_NOWARNING))
			{
				throw new \RuntimeException('Unable to parse the feed.');
			}
		}

		try
		{
			// Skip ahead to the root node.
			while ($reader->read())
			{
				if ($reader->nodeType == \XMLReader::ELEMENT)
				{
					break;
				}
			}
		}
		catch (\Exception $e)
		{
			throw new \RuntimeException('Error reading feed.', $e->getCode(), $e);
		}

		// Setup the appropriate feed parser for the feed.
		$parser = $this->_fetchFeedParser($reader->name, $reader);

		return $parser->parse();
	}

	/**
	 * Method to register a FeedParser class for a given root tag name.
	 *
	 * @param   string   $tagName    The root tag name for which to register the parser class.
	 * @param   string   $className  The FeedParser class name to register for a root tag name.
	 * @param   boolean  $overwrite  True to overwrite the parser class if one is already registered.
	 *
	 * @return  FeedFactory
	 *
	 * @since   3.1.4
	 * @throws  \InvalidArgumentException
	 */
	public function registerParser($tagName, $className, $overwrite = false)
	{
		// Verify that the class exists.
		if (!class_exists($className))
		{
			throw new \InvalidArgumentException('The feed parser class ' . $className . ' does not exist.');
		}

		// Validate that the tag name is valid.
		if (!preg_match('/\A(?!XML)[a-z][\w0-9-]*/i', $tagName))
		{
			throw new \InvalidArgumentException('The tag name ' . $tagName . ' is not valid.');
		}

		// Register the given parser class for the tag name if nothing registered or the overwrite flag set.
		if (empty($this->parsers[$tagName]) || (bool) $overwrite)
		{
			$this->parsers[(string) $tagName] = (string) $className;
		}

		return $this;
	}

	/**
	 * Method to return a new JFeedParser object based on the registered parsers and a given type.
	 *
	 * @param   string      $type    The name of parser to return.
	 * @param   \XMLReader  $reader  The XMLReader instance for the feed.
	 *
	 * @return  FeedParser
	 *
	 * @since   3.1.4
	 * @throws  \LogicException
	 */
	private function _fetchFeedParser($type, \XMLReader $reader)
	{
		// Look for a registered parser for the feed type.
		if (empty($this->parsers[$type]))
		{
			throw new \LogicException('No registered feed parser for type ' . $type . '.');
		}

		return new $this->parsers[$type]($reader);
	}
}
src/Feed/FeedLink.php000064400000003675152177723700010416 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

/**
 * Feed Link class.
 *
 * @since  3.1.4
 */
class FeedLink
{
	/**
	 * The URI to the linked resource.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $uri;

	/**
	 * The relationship between the feed and the linked resource.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $relation;

	/**
	 * The resource type.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $type;

	/**
	 * The language of the resource found at the given URI.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $language;

	/**
	 * The title of the resource.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	public $title;

	/**
	 * The length of the resource in bytes.
	 *
	 * @var    integer
	 * @since  3.1.4
	 */
	public $length;

	/**
	 * Constructor.
	 *
	 * @param   string   $uri       The URI to the linked resource.
	 * @param   string   $relation  The relationship between the feed and the linked resource.
	 * @param   string   $type      The resource type.
	 * @param   string   $language  The language of the resource found at the given URI.
	 * @param   string   $title     The title of the resource.
	 * @param   integer  $length    The length of the resource in bytes.
	 *
	 * @since   3.1.4
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($uri = null, $relation = null, $type = null, $language = null, $title = null, $length = null)
	{
		$this->uri = $uri;
		$this->relation = $relation;
		$this->type = $type;
		$this->language = $language;
		$this->title = $title;

		// Validate the length input.
		if (isset($length) && !is_numeric($length))
		{
			throw new \InvalidArgumentException('Length must be numeric.');
		}

		$this->length = (int) $length;
	}
}
src/Feed/Parser/AtomParser.php000064400000014665152177723700012247 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed\Parser;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\FeedLink;
use Joomla\CMS\Feed\FeedParser;

/**
 * ATOM Feed Parser class.
 *
 * @link   http://www.atomenabled.org/developers/syndication/
 * @since  3.1.4
 */
class AtomParser extends FeedParser
{
	/**
	 * @var    string  The feed format version.
	 * @since  3.1.4
	 */
	protected $version;

	/**
	 * Method to handle the `<author>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleAuthor(Feed $feed, \SimpleXMLElement $el)
	{
		// Set the author information from the XML element.
		$feed->setAuthor((string) $el->name, (string) $el->email, (string) $el->uri);
	}

	/**
	 * Method to handle the `<contributor>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleContributor(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->addContributor((string) $el->name, (string) $el->email, (string) $el->uri);
	}

	/**
	 * Method to handle the `<generator>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->generator = (string) $el;
	}

	/**
	 * Method to handle the `<id>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleId(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->uri = (string) $el;
	}

	/**
	 * Method to handle the `<link>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleLink(Feed $feed, \SimpleXMLElement $el)
	{
		$link = new FeedLink;
		$link->uri      = (string) $el['href'];
		$link->language = (string) $el['hreflang'];
		$link->length   = (int) $el['length'];
		$link->relation = (string) $el['rel'];
		$link->title    = (string) $el['title'];
		$link->type     = (string) $el['type'];

		$feed->link = $link;
	}

	/**
	 * Method to handle the `<rights>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleRights(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->copyright = (string) $el;
	}

	/**
	 * Method to handle the `<subtitle>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleSubtitle(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->description = (string) $el;
	}

	/**
	 * Method to handle the `<title>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->title = (string) $el;
	}

	/**
	 * Method to handle the `<updated>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleUpdated(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->updatedDate = (string) $el;
	}

	/**
	 * Method to initialise the feed for parsing.  Here we detect the version and advance the stream
	 * reader so that it is ready to parse feed elements.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function initialise()
	{
		// Read the version attribute.
		$this->version = ($this->stream->getAttribute('version') == '0.3') ? '0.3' : '1.0';

		// We want to move forward to the first element after the root element.
		$this->moveToNextElement();
	}

	/**
	 * Method to handle a `<entry>` element for the feed.
	 *
	 * @param   FeedEntry          $entry  The FeedEntry object being built from the parsed feed entry.
	 * @param   \SimpleXMLElement  $el     The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
	{
		$entry->uri         = (string) $el->id;
		$entry->title       = (string) $el->title;
		$entry->updatedDate = (string) $el->updated;
		$entry->content     = (string) $el->summary;

		if (!$entry->content)
		{
			$entry->content = (string) $el->content;
		}

		if (filter_var($entry->uri, FILTER_VALIDATE_URL) === false && !is_null($el->link) && $el->link)
		{
			$link = $el->link;

			if (is_array($link))
			{
				$link = $this->bestLinkForUri($link);
			}

			$uri = (string) $link['href'];

			if ($uri)
			{
				$entry->uri = $uri;
			}
		}
	}

	/**
	 * If there is more than one <link> in the feed entry, find the most appropriate one and return it.
	 *
	 * @param   array  $links  Array of <link> elements from the feed entry.
	 *
	 * @return  \SimpleXMLElement
	 */
	private function bestLinkForUri(array $links)
	{
		$linkPrefs = array('', 'self', 'alternate');

		foreach ($linkPrefs as $pref)
		{
			foreach ($links as $link)
			{
				$rel = (string) $link['rel'];

				if ($rel === $pref)
				{
					return $link;
				}
			}
		}

		return array_shift($links);
	}
}
src/Feed/Parser/NamespaceParserInterface.php000064400000002362152177723700015053 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed\Parser;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;

/**
 * Feed Namespace interface.
 *
 * @since  3.1.4
 */
interface NamespaceParserInterface
{
	/**
	 * Method to handle an element for the feed given that a certain namespace is present.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeed(Feed $feed, \SimpleXMLElement $el);

	/**
	 * Method to handle the feed entry element for the feed given that a certain namespace is present.
	 *
	 * @param   FeedEntry          $entry  The FeedEntry object being built from the parsed feed entry.
	 * @param   \SimpleXMLElement  $el     The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeedEntry(FeedEntry $entry, \SimpleXMLElement $el);
}
src/Feed/Parser/Rss/ItunesRssParser.php000064400000002666152177723700014053 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed\Parser\Rss;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;

/**
 * RSS Feed Parser Namespace handler for iTunes.
 *
 * @link   https://itunespartner.apple.com/en/podcasts/overview
 * @since  3.1.4
 */
class ItunesRssParser implements NamespaceParserInterface
{
	/**
	 * Method to handle an element for the feed given that the itunes namespace is present.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeed(Feed $feed, \SimpleXMLElement $el)
	{
		return;
	}

	/**
	 * Method to handle the feed entry element for the feed given that the itunes namespace is present.
	 *
	 * @param   FeedEntry          $entry  The FeedEntry object being built from the parsed feed entry.
	 * @param   \SimpleXMLElement  $el     The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
	{
		return;
	}
}
src/Feed/Parser/Rss/MediaRssParser.php000064400000002643152177723700013616 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed\Parser\Rss;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\Parser\NamespaceParserInterface;

/**
 * RSS Feed Parser Namespace handler for MediaRSS.
 *
 * @link   http://video.search.yahoo.com/mrss
 * @since  3.1.4
 */
class MediaRssParser implements NamespaceParserInterface
{
	/**
	 * Method to handle an element for the feed given that the media namespace is present.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeed(Feed $feed, \SimpleXMLElement $el)
	{
		return;
	}

	/**
	 * Method to handle the feed entry element for the feed given that the media namespace is present.
	 *
	 * @param   FeedEntry          $entry  The FeedEntry object being built from the parsed feed entry.
	 * @param   \SimpleXMLElement  $el     The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function processElementForFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
	{
		return;
	}
}
src/Feed/Parser/RssParser.php000064400000025757152177723700012122 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed\Parser;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Feed;
use Joomla\CMS\Feed\FeedEntry;
use Joomla\CMS\Feed\FeedLink;
use Joomla\CMS\Feed\FeedParser;
use Joomla\CMS\Feed\FeedPerson;

/**
 * RSS Feed Parser class.
 *
 * @link   http://cyber.law.harvard.edu/rss/rss.html
 * @since  3.1.4
 */
class RssParser extends FeedParser
{
	/**
	 * @var    string  The feed element name for the entry elements.
	 * @since  3.1.4
	 */
	protected $entryElementName = 'item';

	/**
	 * @var    string  The feed format version.
	 * @since  3.1.4
	 */
	protected $version;

	/**
	 * Method to handle the `<category>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleCategory(Feed $feed, \SimpleXMLElement $el)
	{
		// Get the data from the element.
		$domain    = (string) $el['domain'];
		$category  = (string) $el;

		$feed->addCategory($category, $domain);
	}

	/**
	 * Method to handle the `<cloud>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleCloud(Feed $feed, \SimpleXMLElement $el)
	{
		$cloud = new \stdClass;
		$cloud->domain            = (string) $el['domain'];
		$cloud->port              = (string) $el['port'];
		$cloud->path              = (string) $el['path'];
		$cloud->protocol          = (string) $el['protocol'];
		$cloud->registerProcedure = (string) $el['registerProcedure'];

		$feed->cloud = $cloud;
	}

	/**
	 * Method to handle the `<copyright>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleCopyright(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->copyright = (string) $el;
	}

	/**
	 * Method to handle the `<description>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleDescription(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->description = (string) $el;
	}

	/**
	 * Method to handle the `<generator>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleGenerator(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->generator = (string) $el;
	}

	/**
	 * Method to handle the `<image>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleImage(Feed $feed, \SimpleXMLElement $el)
	{
		// Create a feed link object for the image.
		$image = new FeedLink(
			(string) $el->url,
			null,
			'logo',
			null,
			(string) $el->title
		);

		// Populate extra fields if they exist.
		$image->link         = (string) $el->link;
		$image->description  = (string) $el->description;
		$image->height       = (string) $el->height;
		$image->width        = (string) $el->width;

		$feed->image = $image;
	}

	/**
	 * Method to handle the `<language>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleLanguage(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->language = (string) $el;
	}

	/**
	 * Method to handle the `<lastBuildDate>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleLastBuildDate(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->updatedDate = (string) $el;
	}

	/**
	 * Method to handle the `<link>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleLink(Feed $feed, \SimpleXMLElement $el)
	{
		$link = new FeedLink;
		$link->uri = (string) $el['href'];
		$feed->link = $link;
	}

	/**
	 * Method to handle the `<managingEditor>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleManagingEditor(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->author = $this->processPerson((string) $el);
	}

	/**
	 * Method to handle the `<skipDays>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleSkipDays(Feed $feed, \SimpleXMLElement $el)
	{
		// Initialise the array.
		$days = array();

		// Add all of the day values from the feed to the array.
		foreach ($el->day as $day)
		{
			$days[] = (string) $day;
		}

		$feed->skipDays = $days;
	}

	/**
	 * Method to handle the `<skipHours>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleSkipHours(Feed $feed, \SimpleXMLElement $el)
	{
		// Initialise the array.
		$hours = array();

		// Add all of the day values from the feed to the array.
		foreach ($el->hour as $hour)
		{
			$hours[] = (int) $hour;
		}

		$feed->skipHours = $hours;
	}

	/**
	 * Method to handle the `<pubDate>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handlePubDate(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->publishedDate = (string) $el;
	}

	/**
	 * Method to handle the `<title>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleTitle(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->title = (string) $el;
	}

	/**
	 * Method to handle the `<ttl>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleTtl(Feed $feed, \SimpleXMLElement $el)
	{
		$feed->ttl = (integer) $el;
	}

	/**
	 * Method to handle the `<webmaster>` element for the feed.
	 *
	 * @param   Feed               $feed  The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el    The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function handleWebmaster(Feed $feed, \SimpleXMLElement $el)
	{
		// Get the tag contents and split it over the first space.
		$tmp = (string) $el;
		$tmp = explode(' ', $tmp, 2);

		// This is really cheap parsing.  Probably need to create a method to do this more robustly.
		$name = null;

		if (isset($tmp[1]))
		{
			$name = trim($tmp[1], ' ()');
		}

		$email = trim($tmp[0]);

		$feed->addContributor($name, $email, null, 'webmaster');
	}

	/**
	 * Method to initialise the feed for parsing.  Here we detect the version and advance the stream
	 * reader so that it is ready to parse feed elements.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function initialise()
	{
		// Read the version attribute.
		$this->version = $this->stream->getAttribute('version');

		// We want to move forward to the first element after the <channel> element.
		$this->moveToNextElement('channel');
		$this->moveToNextElement();
	}

	/**
	 * Method to handle a `<item>` element for the feed.
	 *
	 * @param   FeedEntry          $entry  The FeedEntry object being built from the parsed feed entry.
	 * @param   \SimpleXMLElement  $el     The current XML element object to handle.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function processFeedEntry(FeedEntry $entry, \SimpleXMLElement $el)
	{
		$entry->uri           = (string) $el->link;
		$entry->title         = (string) $el->title;
		$entry->publishedDate = (string) $el->pubDate;
		$entry->updatedDate   = (string) $el->pubDate;
		$entry->content       = (string) $el->description;
		$entry->guid          = (string) $el->guid;
		$entry->isPermaLink   = $entry->guid === '' || (string) $el->guid['isPermaLink'] === 'false' ? false : true;
		$entry->comments      = (string) $el->comments;

		// Add the feed entry author if available.
		$author = (string) $el->author;

		if (!empty($author))
		{
			$entry->author = $this->processPerson($author);
		}

		// Add any categories to the entry.
		foreach ($el->category as $category)
		{
			$entry->addCategory((string) $category, (string) $category['domain']);
		}

		// Add any enclosures to the entry.
		foreach ($el->enclosure as $enclosure)
		{
			$link = new FeedLink(
				(string) $enclosure['url'],
				null,
				(string) $enclosure['type'],
				null,
				null,
				(int) $enclosure['length']
			);

			$entry->addLink($link);
		}
	}

	/**
	 * Method to parse a string with person data and return a FeedPerson object.
	 *
	 * @param   string  $data  The string to parse for a person.
	 *
	 * @return  FeedPerson
	 *
	 * @since   3.1.4
	 */
	protected function processPerson($data)
	{
		// Create a new person object.
		$person = new FeedPerson;

		// This is really cheap parsing, but so far good enough. :)
		$data = explode(' ', $data, 2);

		if (isset($data[1]))
		{
			$person->name = trim($data[1], ' ()');
		}

		// Set the email for the person.
		$person->email = trim($data[0]);

		return $person;
	}
}
src/Feed/FeedEntry.php000064400000016111152177723700010607 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Date\Date;

/**
 * Class to encapsulate a feed entry for the Joomla Platform.
 *
 * @property  FeedPerson  $author         Person responsible for feed entry content.
 * @property  array       $categories     Categories to which the feed entry belongs.
 * @property  string      $content        The content of the feed entry.
 * @property  array       $contributors   People who contributed to the feed entry content.
 * @property  string      $copyright      Information about rights, e.g. copyrights, held in and over the feed entry.
 * @property  array       $links          Links associated with the feed entry.
 * @property  Date        $publishedDate  The publication date for the feed entry.
 * @property  Feed        $source         The feed from which the entry is sourced.
 * @property  string      $title          A human readable title for the feed entry.
 * @property  Date        $updatedDate    The last time the content of the feed entry changed.
 * @property  string      $uri            Universal, permanent identifier for the feed entry.
 *
 * @since  3.1.4
 */
class FeedEntry
{
	/**
	 * @var    array  The entry properties.
	 * @since  3.1.4
	 */
	protected $properties = array(
		'uri'  => '',
		'title' => '',
		'updatedDate' => '',
		'content' => '',
		'categories' => array(),
		'contributors' => array(),
		'links' => array(),
	);

	/**
	 * Magic method to return values for feed entry properties.
	 *
	 * @param   string  $name  The name of the property.
	 *
	 * @return  mixed
	 *
	 * @since   3.1.4
	 */
	public function __get($name)
	{
		return (isset($this->properties[$name])) ? $this->properties[$name] : null;
	}

	/**
	 * Magic method to set values for feed properties.
	 *
	 * @param   string  $name   The name of the property.
	 * @param   mixed   $value  The value to set for the property.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function __set($name, $value)
	{
		// Ensure that setting a date always sets a JDate instance.
		if ((($name == 'updatedDate') || ($name == 'publishedDate')) && !($value instanceof Date))
		{
			$value = new Date($value);
		}

		// Validate that any authors that are set are instances of JFeedPerson or null.
		if (($name == 'author') && (!($value instanceof FeedPerson) || ($value === null)))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
					get_class($this),
					gettype($value) === 'object' ? get_class($value) : gettype($value)
				)
			);
		}

		// Validate that any sources that are set are instances of JFeed or null.
		if (($name == 'source') && (!($value instanceof Feed) || ($value === null)))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'%1$s "source" must be an instance of Joomla\\CMS\\Feed\\Feed. %2$s given.',
					get_class($this),
					gettype($value) === 'object' ? get_class($value) : gettype($value)
				)
			);
		}

		// Disallow setting categories, contributors, or links directly.
		if (in_array($name, array('categories', 'contributors', 'links')))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'Cannot directly set %1$s property "%2$s".',
					get_class($this),
					$name
				)
			);
		}

		$this->properties[$name] = $value;
	}

	/**
	 * Method to add a category to the feed entry object.
	 *
	 * @param   string  $name  The name of the category to add.
	 * @param   string  $uri   The optional URI for the category to add.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function addCategory($name, $uri = '')
	{
		$this->properties['categories'][$name] = $uri;

		return $this;
	}

	/**
	 * Method to add a contributor to the feed entry object.
	 *
	 * @param   string  $name   The full name of the person to add.
	 * @param   string  $email  The email address of the person to add.
	 * @param   string  $uri    The optional URI for the person to add.
	 * @param   string  $type   The optional type of person to add.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function addContributor($name, $email, $uri = null, $type = null)
	{
		$contributor = new FeedPerson($name, $email, $uri, $type);

		// If the new contributor already exists then there is nothing to do, so just return.
		foreach ($this->properties['contributors'] as $c)
		{
			if ($c == $contributor)
			{
				return $this;
			}
		}

		// Add the new contributor.
		$this->properties['contributors'][] = $contributor;

		return $this;
	}

	/**
	 * Method to add a link to the feed entry object.
	 *
	 * @param   FeedLink  $link  The link object to add.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function addLink(FeedLink $link)
	{
		// If the new link already exists then there is nothing to do, so just return.
		foreach ($this->properties['links'] as $l)
		{
			if ($l == $link)
			{
				return $this;
			}
		}

		// Add the new link.
		$this->properties['links'][] = $link;

		return $this;
	}

	/**
	 * Method to remove a category from the feed entry object.
	 *
	 * @param   string  $name  The name of the category to remove.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function removeCategory($name)
	{
		unset($this->properties['categories'][$name]);

		return $this;
	}

	/**
	 * Method to remove a contributor from the feed entry object.
	 *
	 * @param   FeedPerson  $contributor  The person object to remove.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function removeContributor(FeedPerson $contributor)
	{
		// If the contributor exists remove it.
		foreach ($this->properties['contributors'] as $k => $c)
		{
			if ($c == $contributor)
			{
				unset($this->properties['contributors'][$k]);
				$this->properties['contributors'] = array_values($this->properties['contributors']);

				return $this;
			}
		}

		return $this;
	}

	/**
	 * Method to remove a link from the feed entry object.
	 *
	 * @param   FeedLink  $link  The link object to remove.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function removeLink(FeedLink $link)
	{
		// If the link exists remove it.
		foreach ($this->properties['links'] as $k => $l)
		{
			if ($l == $link)
			{
				unset($this->properties['links'][$k]);
				$this->properties['links'] = array_values($this->properties['links']);

				return $this;
			}
		}

		return $this;
	}

	/**
	 * Shortcut method to set the author for the feed entry object.
	 *
	 * @param   string  $name   The full name of the person to set.
	 * @param   string  $email  The email address of the person to set.
	 * @param   string  $uri    The optional URI for the person to set.
	 * @param   string  $type   The optional type of person to set.
	 *
	 * @return  FeedEntry
	 *
	 * @since   3.1.4
	 */
	public function setAuthor($name, $email, $uri = null, $type = null)
	{
		$author = new FeedPerson($name, $email, $uri, $type);

		$this->properties['author'] = $author;

		return $this;
	}
}
src/Feed/FeedParser.php000064400000014737152177723700010756 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Feed\Parser\NamespaceParserInterface;

/**
 * Feed Parser class.
 *
 * @since  3.1.4
 */
abstract class FeedParser
{
	/**
	 * The feed element name for the entry elements.
	 *
	 * @var    string
	 * @since  3.1.4
	 */
	protected $entryElementName = 'entry';

	/**
	 * Array of NamespaceParserInterface objects
	 *
	 * @var    array
	 * @since  3.1.4
	 */
	protected $namespaces = array();

	/**
	 * The XMLReader stream object for the feed.
	 *
	 * @var    \XMLReader
	 * @since  3.1.4
	 */
	protected $stream;

	/**
	 * Constructor.
	 *
	 * @param   \XMLReader  $stream  The XMLReader stream object for the feed.
	 *
	 * @since   3.1.4
	 */
	public function __construct(\XMLReader $stream)
	{
		$this->stream  = $stream;
	}

	/**
	 * Method to parse the feed into a JFeed object.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function parse()
	{
		$feed = new Feed;

		// Detect the feed version.
		$this->initialise();

		// Let's get this party started...
		do
		{
			// Expand the element for processing.
			$el = new \SimpleXMLElement($this->stream->readOuterXml());

			// Get the list of namespaces used within this element.
			$ns = $el->getNamespaces(true);

			// Get an array of available namespace objects for the element.
			$namespaces = array();

			foreach ($ns as $prefix => $uri)
			{
				// Ignore the empty namespace prefix.
				if (empty($prefix))
				{
					continue;
				}

				// Get the necessary namespace objects for the element.
				$namespace = $this->fetchNamespace($prefix);

				if ($namespace)
				{
					$namespaces[] = $namespace;
				}
			}

			// Process the element.
			$this->processElement($feed, $el, $namespaces);

			// Skip over this element's children since it has been processed.
			$this->moveToClosingElement();
		}

		while ($this->moveToNextElement());

		return $feed;
	}

	/**
	 * Method to register a namespace handler object.
	 *
	 * @param   string                    $prefix     The XML namespace prefix for which to register the namespace object.
	 * @param   NamespaceParserInterface  $namespace  The namespace object to register.
	 *
	 * @return  JFeed
	 *
	 * @since   3.1.4
	 */
	public function registerNamespace($prefix, NamespaceParserInterface $namespace)
	{
		$this->namespaces[$prefix] = $namespace;

		return $this;
	}

	/**
	 * Method to initialise the feed for parsing.  If child parsers need to detect versions or other
	 * such things this is where you'll want to implement that logic.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	abstract protected function initialise();

	/**
	 * Method to parse a specific feed element.
	 *
	 * @param   Feed               $feed        The Feed object being built from the parsed feed.
	 * @param   \SimpleXMLElement  $el          The current XML element object to handle.
	 * @param   array              $namespaces  The array of relevant namespace objects to process for the element.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	protected function processElement(Feed $feed, \SimpleXMLElement $el, array $namespaces)
	{
		// Build the internal method name.
		$method = 'handle' . ucfirst($el->getName());

		// If we are dealing with an item then it is feed entry time.
		if ($el->getName() == $this->entryElementName)
		{
			// Create a new feed entry for the item.
			$entry = new FeedEntry;

			// First call the internal method.
			$this->processFeedEntry($entry, $el);

			foreach ($namespaces as $namespace)
			{
				if ($namespace instanceof NamespaceParserInterface)
				{
					$namespace->processElementForFeedEntry($entry, $el);
				}
			}

			// Add the new entry to the feed.
			$feed->addEntry($entry);

			return;
		}

		// Otherwise we treat it like any other element.

		// First call the internal method.
		if (is_callable(array($this, $method)))
		{
			$this->$method($feed, $el);
		}

		foreach ($namespaces as $namespace)
		{
			if ($namespace instanceof NamespaceParserInterface)
			{
				$namespace->processElementForFeed($feed, $el);
			}
		}
	}

	/**
	 * Method to get a namespace object for a given namespace prefix.
	 *
	 * @param   string  $prefix  The XML prefix for which to fetch the namespace object.
	 *
	 * @return  mixed  NamespaceParserInterface or false if none exists.
	 *
	 * @since   3.1.4
	 */
	protected function fetchNamespace($prefix)
	{
		if (isset($this->namespaces[$prefix]))
		{
			return $this->namespaces[$prefix];
		}

		$className = get_class($this) . ucfirst($prefix);

		if (class_exists($className))
		{
			$this->namespaces[$prefix] = new $className;

			return $this->namespaces[$prefix];
		}

		return false;
	}

	/**
	 * Method to move the stream parser to the next XML element node.
	 *
	 * @param   string  $name  The name of the element for which to move the stream forward until is found.
	 *
	 * @return  boolean  True if the stream parser is on an XML element node.
	 *
	 * @since   3.1.4
	 */
	protected function moveToNextElement($name = null)
	{
		// Only keep looking until the end of the stream.
		while ($this->stream->read())
		{
			// As soon as we get to the next ELEMENT node we are done.
			if ($this->stream->nodeType == \XMLReader::ELEMENT)
			{
				// If we are looking for a specific name make sure we have it.
				if (isset($name) && ($this->stream->name != $name))
				{
					continue;
				}

				return true;
			}
		}

		return false;
	}

	/**
	 * Method to move the stream parser to the closing XML node of the current element.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  \RuntimeException  If the closing tag cannot be found.
	 */
	protected function moveToClosingElement()
	{
		// If we are on a self-closing tag then there is nothing to do.
		if ($this->stream->isEmptyElement)
		{
			return;
		}

		// Get the name and depth for the current node so that we can match the closing node.
		$name  = $this->stream->name;
		$depth = $this->stream->depth;

		// Only keep looking until the end of the stream.
		while ($this->stream->read())
		{
			// If we have an END_ELEMENT node with the same name and depth as the node we started with we have a bingo. :-)
			if (($this->stream->name == $name) && ($this->stream->depth == $depth) && ($this->stream->nodeType == \XMLReader::END_ELEMENT))
			{
				return;
			}
		}

		throw new \RuntimeException('Unable to find the closing XML node.');
	}
}
src/Feed/Feed.php000064400000021563152177723700007574 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Feed;

defined('JPATH_PLATFORM') or die();

use Joomla\CMS\Date\Date;

/**
 * Class to encapsulate a feed for the Joomla Platform.
 *
 * @property  FeedPerson  $author         Person responsible for feed content.
 * @property  array       $categories     Categories to which the feed belongs.
 * @property  array       $contributors   People who contributed to the feed content.
 * @property  string      $copyright      Information about rights, e.g. copyrights, held in and over the feed.
 * @property  string      $description    A phrase or sentence describing the feed.
 * @property  string      $generator      A string indicating the program used to generate the feed.
 * @property  string      $image          Specifies a GIF, JPEG or PNG image that should be displayed with the feed.
 * @property  Date        $publishedDate  The publication date for the feed content.
 * @property  string      $title          A human readable title for the feed.
 * @property  Date        $updatedDate    The last time the content of the feed changed.
 * @property  string      $uri            Universal, permanent identifier for the feed.
 *
 * @since  3.1.4
 */
class Feed implements \ArrayAccess, \Countable
{
	/**
	 * @var    array  The entry properties.
	 * @since  3.1.4
	 */
	protected $properties = array(
		'uri' => '',
		'title' => '',
		'updatedDate' => '',
		'description' => '',
		'categories' => array(),
		'contributors' => array(),
	);

	/**
	 * @var    array  The list of feed entry objects.
	 * @since  3.1.4
	 */
	protected $entries = array();

	/**
	 * Magic method to return values for feed properties.
	 *
	 * @param   string  $name  The name of the property.
	 *
	 * @return  mixed
	 *
	 * @since   3.1.4
	 */
	public function __get($name)
	{
		return isset($this->properties[$name]) ? $this->properties[$name] : null;
	}

	/**
	 * Magic method to set values for feed properties.
	 *
	 * @param   string  $name   The name of the property.
	 * @param   mixed   $value  The value to set for the property.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function __set($name, $value)
	{
		// Ensure that setting a date always sets a JDate instance.
		if ((($name == 'updatedDate') || ($name == 'publishedDate')) && !($value instanceof Date))
		{
			$value = new Date($value);
		}

		// Validate that any authors that are set are instances of JFeedPerson or null.
		if (($name == 'author') && (!($value instanceof FeedPerson) || ($value === null)))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'%1$s "author" must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
					get_class($this),
					gettype($value) === 'object' ? get_class($value) : gettype($value)
				)
			);
		}

		// Disallow setting categories or contributors directly.
		if (in_array($name, array('categories', 'contributors')))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'Cannot directly set %1$s property "%2$s".',
					get_class($this),
					$name
				)
			);
		}

		$this->properties[$name] = $value;
	}

	/**
	 * Method to add a category to the feed object.
	 *
	 * @param   string  $name  The name of the category to add.
	 * @param   string  $uri   The optional URI for the category to add.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function addCategory($name, $uri = '')
	{
		$this->properties['categories'][$name] = $uri;

		return $this;
	}

	/**
	 * Method to add a contributor to the feed object.
	 *
	 * @param   string  $name   The full name of the person to add.
	 * @param   string  $email  The email address of the person to add.
	 * @param   string  $uri    The optional URI for the person to add.
	 * @param   string  $type   The optional type of person to add.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function addContributor($name, $email, $uri = null, $type = null)
	{
		$contributor = new FeedPerson($name, $email, $uri, $type);

		// If the new contributor already exists then there is nothing to do, so just return.
		foreach ($this->properties['contributors'] as $c)
		{
			if ($c == $contributor)
			{
				return $this;
			}
		}

		// Add the new contributor.
		$this->properties['contributors'][] = $contributor;

		return $this;
	}

	/**
	 * Method to add an entry to the feed object.
	 *
	 * @param   FeedEntry  $entry  The entry object to add.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function addEntry(FeedEntry $entry)
	{
		// If the new entry already exists then there is nothing to do, so just return.
		foreach ($this->entries as $e)
		{
			if ($e == $entry)
			{
				return $this;
			}
		}

		// Add the new entry.
		$this->entries[] = $entry;

		return $this;
	}

	/**
	 * Returns a count of the number of entries in the feed.
	 *
	 * This method is here to implement the Countable interface.
	 * You can call it by doing count($feed) rather than $feed->count();
	 *
	 * @return  integer number of entries in the feed.
	 */
	public function count()
	{
		return count($this->entries);
	}

	/**
	 * Whether or not an offset exists.  This method is executed when using isset() or empty() on
	 * objects implementing ArrayAccess.
	 *
	 * @param   mixed  $offset  An offset to check for.
	 *
	 * @return  boolean
	 *
	 * @see     ArrayAccess::offsetExists()
	 * @since   3.1.4
	 */
	public function offsetExists($offset)
	{
		return isset($this->entries[$offset]);
	}

	/**
	 * Returns the value at specified offset.
	 *
	 * @param   mixed  $offset  The offset to retrieve.
	 *
	 * @return  mixed  The value at the offset.
	 *
	 * @see     ArrayAccess::offsetGet()
	 * @since   3.1.4
	 */
	public function offsetGet($offset)
	{
		return $this->entries[$offset];
	}

	/**
	 * Assigns a value to the specified offset.
	 *
	 * @param   mixed      $offset  The offset to assign the value to.
	 * @param   FeedEntry  $value   The JFeedEntry to set.
	 *
	 * @return  boolean
	 *
	 * @see     ArrayAccess::offsetSet()
	 * @since   3.1.4
	 * @throws  \InvalidArgumentException
	 */
	public function offsetSet($offset, $value)
	{
		if (!($value instanceof FeedEntry))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'%1$s entries must be an instance of Joomla\\CMS\\Feed\\FeedPerson. %2$s given.',
					get_class($this),
					gettype($value) === 'object' ? get_class($value) : gettype($value)
				)
			);
		}

		$this->entries[$offset] = $value;

		return true;
	}

	/**
	 * Unsets an offset.
	 *
	 * @param   mixed  $offset  The offset to unset.
	 *
	 * @return  void
	 *
	 * @see     ArrayAccess::offsetUnset()
	 * @since   3.1.4
	 */
	public function offsetUnset($offset)
	{
		unset($this->entries[$offset]);
	}

	/**
	 * Method to remove a category from the feed object.
	 *
	 * @param   string  $name  The name of the category to remove.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function removeCategory($name)
	{
		unset($this->properties['categories'][$name]);

		return $this;
	}

	/**
	 * Method to remove a contributor from the feed object.
	 *
	 * @param   FeedPerson  $contributor  The person object to remove.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function removeContributor(FeedPerson $contributor)
	{
		// If the contributor exists remove it.
		foreach ($this->properties['contributors'] as $k => $c)
		{
			if ($c == $contributor)
			{
				unset($this->properties['contributors'][$k]);
				$this->properties['contributors'] = array_values($this->properties['contributors']);

				return $this;
			}
		}

		return $this;
	}

	/**
	 * Method to remove an entry from the feed object.
	 *
	 * @param   FeedEntry  $entry  The entry object to remove.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function removeEntry(FeedEntry $entry)
	{
		// If the entry exists remove it.
		foreach ($this->entries as $k => $e)
		{
			if ($e == $entry)
			{
				unset($this->entries[$k]);
				$this->entries = array_values($this->entries);

				return $this;
			}
		}

		return $this;
	}

	/**
	 * Shortcut method to set the author for the feed object.
	 *
	 * @param   string  $name   The full name of the person to set.
	 * @param   string  $email  The email address of the person to set.
	 * @param   string  $uri    The optional URI for the person to set.
	 * @param   string  $type   The optional type of person to set.
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function setAuthor($name, $email, $uri = null, $type = null)
	{
		$author = new FeedPerson($name, $email, $uri, $type);

		$this->properties['author'] = $author;

		return $this;
	}

	/**
	 * Method to reverse the items if display is set to 'oldest first'
	 *
	 * @return  Feed
	 *
	 * @since   3.1.4
	 */
	public function reverseItems()
	{
		if (is_array($this->entries) && !empty($this->entries))
		{
			$this->entries = array_reverse($this->entries);
		}

		return $this;
	}
}
src/Environment/Browser.php000064400000052616152177723700012020 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Environment;

defined('JPATH_PLATFORM') or die;

/**
 * Browser class, provides capability information about the current web client.
 *
 * Browser identification is performed by examining the HTTP_USER_AGENT
 * environment variable provided by the web server.
 *
 * This class has many influences from the lib/Browser.php code in
 * version 3 of Horde by Chuck Hagenbuch and Jon Parise.
 *
 * @since  1.7.0
 */
class Browser
{
	/**
	 * @var    integer  Major version number
	 * @since  3.0.0
	 */
	protected $majorVersion = 0;

	/**
	 * @var    integer  Minor version number
	 * @since  3.0.0
	 */
	protected $minorVersion = 0;

	/**
	 * @var    string  Browser name.
	 * @since  3.0.0
	 */
	protected $browser = '';

	/**
	 * @var    string  Full user agent string.
	 * @since  3.0.0
	 */
	protected $agent = '';

	/**
	 * @var    string  Lower-case user agent string
	 * @since  3.0.0
	 */
	protected $lowerAgent = '';

	/**
	 * @var    string  HTTP_ACCEPT string.
	 * @since  3.0.0
	 */
	protected $accept = '';

	/**
	 * @var    array  Parsed HTTP_ACCEPT string
	 * @since  3.0.0
	 */
	protected $acceptParsed = array();

	/**
	 * @var    string  Platform the browser is running on
	 * @since  3.0.0
	 */
	protected $platform = '';

	/**
	 * @var    array  Known robots.
	 * @since  3.0.0
	 */
	protected $robots = array(
		'Googlebot\/',
		'Googlebot-Mobile',
		'Googlebot-Image',
		'Googlebot-News',
		'Googlebot-Video',
		'AdsBot-Google([^-]|$)',
		'AdsBot-Google-Mobile',
		'Feedfetcher-Google',
		'Mediapartners-Google',
		'Mediapartners \(Googlebot\)',
		'APIs-Google',
		'bingbot',
		'Slurp',
		'[wW]get',
		'curl',
		'LinkedInBot',
		'Python-urllib',
		'python-requests',
		'libwww',
		'httpunit',
		'nutch',
		'Go-http-client',
		'phpcrawl',
		'msnbot',
		'jyxobot',
		'FAST-WebCrawler',
		'FAST Enterprise Crawler',
		'BIGLOTRON',
		'Teoma',
		'convera',
		'seekbot',
		'Gigabot',
		'Gigablast',
		'exabot',
		'ia_archiver',
		'GingerCrawler',
		'webmon ',
		'HTTrack',
		'grub.org',
		'UsineNouvelleCrawler',
		'antibot',
		'netresearchserver',
		'speedy',
		'fluffy',
		'bibnum.bnf',
		'findlink',
		'msrbot',
		'panscient',
		'yacybot',
		'AISearchBot',
		'ips-agent',
		'tagoobot',
		'MJ12bot',
		'woriobot',
		'yanga',
		'buzzbot',
		'mlbot',
		'YandexBot',
		'yandex.com\/bots',
		'purebot',
		'Linguee Bot',
		'CyberPatrol',
		'voilabot',
		'Baiduspider',
		'citeseerxbot',
		'spbot',
		'twengabot',
		'postrank',
		'turnitinbot',
		'scribdbot',
		'page2rss',
		'sitebot',
		'linkdex',
		'Adidxbot',
		'blekkobot',
		'ezooms',
		'dotbot',
		'Mail.RU_Bot',
		'discobot',
		'heritrix',
		'findthatfile',
		'europarchive.org',
		'NerdByNature.Bot',
		'sistrix crawler',
		'Ahrefs(Bot|SiteAudit)',
		'fuelbot',
		'CrunchBot',
		'centurybot9',
		'IndeedBot',
		'mappydata',
		'woobot',
		'ZoominfoBot',
		'PrivacyAwareBot',
		'Multiviewbot',
		'SWIMGBot',
		'Grobbot',
		'eright',
		'Apercite',
		'semanticbot',
		'Aboundex',
		'domaincrawler',
		'wbsearchbot',
		'summify',
		'CCBot',
		'edisterbot',
		'seznambot',
		'ec2linkfinder',
		'gslfbot',
		'aiHitBot',
		'intelium_bot',
		'facebookexternalhit',
		'Yeti',
		'RetrevoPageAnalyzer',
		'lb-spider',
		'Sogou',
		'lssbot',
		'careerbot',
		'wotbox',
		'wocbot',
		'ichiro',
		'DuckDuckBot',
		'lssrocketcrawler',
		'drupact',
		'webcompanycrawler',
		'acoonbot',
		'openindexspider',
		'gnam gnam spider',
		'web-archive-net.com.bot',
		'backlinkcrawler',
		'coccoc',
		'integromedb',
		'content crawler spider',
		'toplistbot',
		'it2media-domain-crawler',
		'ip-web-crawler.com',
		'siteexplorer.info',
		'elisabot',
		'proximic',
		'changedetection',
		'arabot',
		'WeSEE:Search',
		'niki-bot',
		'CrystalSemanticsBot',
		'rogerbot',
		'360Spider',
		'psbot',
		'InterfaxScanBot',
		'CC Metadata Scaper',
		'g00g1e.net',
		'GrapeshotCrawler',
		'urlappendbot',
		'brainobot',
		'fr-crawler',
		'binlar',
		'SimpleCrawler',
		'Twitterbot',
		'cXensebot',
		'smtbot',
		'bnf.fr_bot',
		'A6-Indexer',
		'ADmantX',
		'Facebot',
		'OrangeBot\/',
		'memorybot',
		'AdvBot',
		'MegaIndex',
		'SemanticScholarBot',
		'ltx71',
		'nerdybot',
		'xovibot',
		'BUbiNG',
		'Qwantify',
		'archive.org_bot',
		'Applebot',
		'TweetmemeBot',
		'crawler4j',
		'findxbot',
		'S[eE][mM]rushBot',
		'yoozBot',
		'lipperhey',
		'Y!J',
		'Domain Re-Animator Bot',
		'AddThis',
		'Screaming Frog SEO Spider',
		'MetaURI',
		'Scrapy',
		'Livelap[bB]ot',
		'OpenHoseBot',
		'CapsuleChecker',
		'collection@infegy.com',
		'IstellaBot',
		'DeuSu\/',
		'betaBot',
		'Cliqzbot\/',
		'MojeekBot\/',
		'netEstate NE Crawler',
		'SafeSearch microdata crawler',
		'Gluten Free Crawler\/',
		'Sonic',
		'Sysomos',
		'Trove',
		'deadlinkchecker',
		'Slack-ImgProxy',
		'Embedly',
		'RankActiveLinkBot',
		'iskanie',
		'SafeDNSBot',
		'SkypeUriPreview',
		'Veoozbot',
		'Slackbot',
		'redditbot',
		'datagnionbot',
		'Google-Adwords-Instant',
		'adbeat_bot',
		'WhatsApp',
		'contxbot',
		'pinterest',
		'electricmonk',
		'GarlikCrawler',
		'BingPreview\/',
		'vebidoobot',
		'FemtosearchBot',
		'Yahoo Link Preview',
		'MetaJobBot',
		'DomainStatsBot',
		'mindUpBot',
		'Daum\/',
		'Jugendschutzprogramm-Crawler',
		'Xenu Link Sleuth',
		'Pcore-HTTP',
		'moatbot',
		'KosmioBot',
		'pingdom',
		'PhantomJS',
		'Gowikibot',
		'PiplBot',
		'Discordbot',
		'TelegramBot',
		'Jetslide',
		'newsharecounts',
		'James BOT',
		'Barkrowler',
		'TinEye',
		'SocialRankIOBot',
		'trendictionbot',
		'Ocarinabot',
		'epicbot',
		'Primalbot',
		'DuckDuckGo-Favicons-Bot',
		'GnowitNewsbot',
		'Leikibot',
		'LinkArchiver',
		'YaK\/',
		'PaperLiBot',
		'Digg Deeper',
		'dcrawl',
		'Snacktory',
		'AndersPinkBot',
		'Fyrebot',
		'EveryoneSocialBot',
		'Mediatoolkitbot',
		'Luminator-robots',
		'ExtLinksBot',
		'SurveyBot',
		'NING\/',
		'okhttp',
		'Nuzzel',
		'omgili',
		'PocketParser',
		'YisouSpider',
		'um-LN',
		'ToutiaoSpider',
		'MuckRack',
		'Jamie\'s Spider',
		'AHC\/',
		'NetcraftSurveyAgent',
		'Laserlikebot',
		'Apache-HttpClient',
		'AppEngine-Google',
		'Jetty',
		'Upflow',
		'Thinklab',
		'Traackr.com',
		'Twurly',
		'Mastodon',
		'http_get',
		'DnyzBot',
		'botify',
		'007ac9 Crawler',
		'BehloolBot',
		'BrandVerity',
		'check_http',
		'BDCbot',
		'ZumBot',
		'EZID',
		'ICC-Crawler',
		'ArchiveBot',
		'^LCC ',
		'filterdb.iss.net\/crawler',
		'BLP_bbot',
		'BomboraBot',
		'Buck\/',
		'Companybook-Crawler',
		'Genieo',
		'magpie-crawler',
		'MeltwaterNews',
		'Moreover',
		'newspaper\/',
		'ScoutJet',
		'(^| )sentry\/',
		'StorygizeBot',
		'UptimeRobot',
		'OutclicksBot',
		'seoscanners',
		'Hatena',
		'Google Web Preview',
		'MauiBot',
		'AlphaBot',
		'SBL-BOT',
		'IAS crawler',
		'adscanner',
		'Netvibes',
		'acapbot',
		'Baidu-YunGuanCe',
		'bitlybot',
		'blogmuraBot',
		'Bot.AraTurka.com',
		'bot-pge.chlooe.com',
		'BoxcarBot',
		'BTWebClient',
		'ContextAd Bot',
		'Digincore bot',
		'Disqus',
		'Feedly',
		'Fetch\/',
		'Fever',
		'Flamingo_SearchEngine',
		'FlipboardProxy',
		'g2reader-bot',
		'imrbot',
		'K7MLWCBot',
		'Kemvibot',
		'Landau-Media-Spider',
		'linkapediabot',
		'vkShare',
		'Siteimprove.com',
		'BLEXBot\/',
		'DareBoost',
		'ZuperlistBot\/',
		'Miniflux\/',
		'Feedspotbot\/',
		'Diffbot\/',
		'SEOkicks',
		'tracemyfile',
		'Nimbostratus-Bot',
		'zgrab',
		'PR-CY.RU',
		'AdsTxtCrawler',
		'Datafeedwatch',
		'Zabbix',
		'TangibleeBot',
		'google-xrawler',
		'axios',
		'Amazon CloudFront',
		'Pulsepoint',
	);

	/**
	 * @var    boolean  Is this a mobile browser?
	 * @since  3.0.0
	 */
	protected $mobile = false;

	/**
	 * List of viewable image MIME subtypes.
	 * This list of viewable images works for IE and Netscape/Mozilla.
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $images = array('jpeg', 'gif', 'png', 'pjpeg', 'x-png', 'bmp');

	/**
	 * @var    array  Browser instances container.
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Create a browser instance (constructor).
	 *
	 * @param   string  $userAgent  The browser string to parse.
	 * @param   string  $accept     The HTTP_ACCEPT settings to use.
	 *
	 * @since   1.7.0
	 */
	public function __construct($userAgent = null, $accept = null)
	{
		$this->match($userAgent, $accept);
	}

	/**
	 * Returns the global Browser object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $userAgent  The browser string to parse.
	 * @param   string  $accept     The HTTP_ACCEPT settings to use.
	 *
	 * @return  Browser  The Browser object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($userAgent = null, $accept = null)
	{
		$signature = serialize(array($userAgent, $accept));

		if (empty(self::$instances[$signature]))
		{
			self::$instances[$signature] = new Browser($userAgent, $accept);
		}

		return self::$instances[$signature];
	}

	/**
	 * Parses the user agent string and inititializes the object with
	 * all the known features and quirks for the given browser.
	 *
	 * @param   string  $userAgent  The browser string to parse.
	 * @param   string  $accept     The HTTP_ACCEPT settings to use.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function match($userAgent = null, $accept = null)
	{
		// Set our agent string.
		if (is_null($userAgent))
		{
			if (isset($_SERVER['HTTP_USER_AGENT']))
			{
				$this->agent = trim($_SERVER['HTTP_USER_AGENT']);
			}
		}
		else
		{
			$this->agent = $userAgent;
		}

		$this->lowerAgent = strtolower($this->agent);

		// Set our accept string.
		if (is_null($accept))
		{
			if (isset($_SERVER['HTTP_ACCEPT']))
			{
				$this->accept = strtolower(trim($_SERVER['HTTP_ACCEPT']));
			}
		}
		else
		{
			$this->accept = strtolower($accept);
		}

		if (!empty($this->agent))
		{
			$this->_setPlatform();

			/*
			 * Determine if mobile. Note: Some Handhelds have their screen resolution in the
			 * user agent string, which we can use to look for mobile agents.
			 */
			if (strpos($this->agent, 'MOT-') !== false
				|| strpos($this->lowerAgent, 'j-') !== false
				|| preg_match('/(mobileexplorer|openwave|opera mini|opera mobi|operamini|avantgo|wap|elaine)/i', $this->agent)
				|| preg_match('/(iPhone|iPod|iPad|Android|Mobile|Phone|BlackBerry|Xiino|Palmscape|palmsource)/i', $this->agent)
				|| preg_match('/(Nokia|Ericsson|docomo|digital paths|portalmmm|CriOS[\/ ]([0-9.]+))/i', $this->agent)
				|| preg_match('/(UP|UP.B|UP.L)/', $this->agent)
				|| preg_match('/; (120x160|240x280|240x320|320x320)\)/', $this->agent))
			{
				$this->mobile = true;
			}

			/*
			 * We have to check for Edge as the first browser, because Edge has something like:
			 * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393
			 */
			if (preg_match('|Edge\/([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('edge');

				if (strpos($version[1], '.') !== false)
				{
					list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
				}
				else
				{
					$this->majorVersion = $version[1];
					$this->minorVersion = 0;
				}
			}
			/*
			 * We have to check for Edge as the first browser, because Edge has something like:
			 * Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3738.0 Safari/537.36 Edg/75.0.107.0
			 */
			elseif (preg_match('|Edg\/([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('edg');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
			}
			elseif (preg_match('|Opera[\/ ]([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('opera');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);

				/*
				 * Due to changes in Opera UA, we need to check Version/xx.yy,
				 * but only if version is > 9.80. See: http://dev.opera.com/articles/view/opera-ua-string-changes/
				 */
				if ($this->majorVersion == 9 && $this->minorVersion >= 80)
				{
					$this->identifyBrowserVersion();
				}
			}

			// Opera 15+
			elseif (preg_match('/OPR[\/ ]([0-9.]+)/', $this->agent, $version))
			{
				$this->setBrowser('opera');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
			}
			elseif (preg_match('/Chrome[\/ ]([0-9.]+)/i', $this->agent, $version)
				|| preg_match('/CrMo[\/ ]([0-9.]+)/i', $this->agent, $version)
				|| preg_match('/CriOS[\/ ]([0-9.]+)/i', $this->agent, $version))
			{
				$this->setBrowser('chrome');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
			}
			elseif (strpos($this->lowerAgent, 'elaine/') !== false
				|| strpos($this->lowerAgent, 'palmsource') !== false
				|| strpos($this->lowerAgent, 'digital paths') !== false)
			{
				$this->setBrowser('palm');
			}
			elseif (preg_match('/MSIE ([0-9.]+)/i', $this->agent, $version)
				|| preg_match('/IE ([0-9.]+)/i', $this->agent, $version)
				|| preg_match('/Internet Explorer[\/ ]([0-9.]+)/i', $this->agent, $version)
				|| preg_match('/Trident\/.*rv:([0-9.]+)/i', $this->agent, $version))
			{
				$this->setBrowser('msie');

				// Special case for IE 11+
				if (strpos($version[0], 'Trident') !== false && strpos($version[0], 'rv:') !== false)
				{
					preg_match('|rv:([0-9.]+)|', $this->agent, $version);
				}

				if (strpos($version[1], '.') !== false)
				{
					list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
				}
				else
				{
					$this->majorVersion = $version[1];
					$this->minorVersion = 0;
				}
			}
			elseif (preg_match('|amaya\/([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('amaya');
				$this->majorVersion = $version[1];

				if (isset($version[2]))
				{
					$this->minorVersion = $version[2];
				}
			}
			elseif (preg_match('|ANTFresco\/([0-9]+)|', $this->agent, $version))
			{
				$this->setBrowser('fresco');
			}
			elseif (strpos($this->lowerAgent, 'avantgo') !== false)
			{
				$this->setBrowser('avantgo');
			}
			elseif (preg_match('|[Kk]onqueror\/([0-9]+)|', $this->agent, $version) || preg_match('|Safari/([0-9]+)\.?([0-9]+)?|', $this->agent, $version))
			{
				// Konqueror and Apple's Safari both use the KHTML rendering engine.
				$this->setBrowser('konqueror');
				$this->majorVersion = $version[1];

				if (isset($version[2]))
				{
					$this->minorVersion = $version[2];
				}

				if (strpos($this->agent, 'Safari') !== false && $this->majorVersion >= 60)
				{
					// Safari.
					$this->setBrowser('safari');
					$this->identifyBrowserVersion();
				}
			}
			elseif (preg_match('|Firefox\/([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('firefox');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
			}
			elseif (preg_match('|Lynx\/([0-9]+)|', $this->agent, $version))
			{
				$this->setBrowser('lynx');
			}
			elseif (preg_match('|Links \(([0-9]+)|', $this->agent, $version))
			{
				$this->setBrowser('links');
			}
			elseif (preg_match('|HotJava\/([0-9]+)|', $this->agent, $version))
			{
				$this->setBrowser('hotjava');
			}
			elseif (strpos($this->agent, 'UP/') !== false || strpos($this->agent, 'UP.B') !== false || strpos($this->agent, 'UP.L') !== false)
			{
				$this->setBrowser('up');
			}
			elseif (strpos($this->agent, 'Xiino/') !== false)
			{
				$this->setBrowser('xiino');
			}
			elseif (strpos($this->agent, 'Palmscape/') !== false)
			{
				$this->setBrowser('palmscape');
			}
			elseif (strpos($this->agent, 'Nokia') !== false)
			{
				$this->setBrowser('nokia');
			}
			elseif (strpos($this->agent, 'Ericsson') !== false)
			{
				$this->setBrowser('ericsson');
			}
			elseif (strpos($this->lowerAgent, 'wap') !== false)
			{
				$this->setBrowser('wap');
			}
			elseif (strpos($this->lowerAgent, 'docomo') !== false || strpos($this->lowerAgent, 'portalmmm') !== false)
			{
				$this->setBrowser('imode');
			}
			elseif (strpos($this->agent, 'BlackBerry') !== false)
			{
				$this->setBrowser('blackberry');
			}
			elseif (strpos($this->agent, 'MOT-') !== false)
			{
				$this->setBrowser('motorola');
			}
			elseif (strpos($this->lowerAgent, 'j-') !== false)
			{
				$this->setBrowser('mml');
			}
			elseif (preg_match('|Mozilla\/([0-9.]+)|', $this->agent, $version))
			{
				$this->setBrowser('mozilla');

				list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);
			}
		}
	}

	/**
	 * Match the platform of the browser.
	 *
	 * This is a pretty simplistic implementation, but it's intended
	 * to let us tell what line breaks to send, so it's good enough
	 * for its purpose.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _setPlatform()
	{
		if (strpos($this->lowerAgent, 'wind') !== false)
		{
			$this->platform = 'win';
		}
		elseif (strpos($this->lowerAgent, 'mac') !== false)
		{
			$this->platform = 'mac';
		}
		else
		{
			$this->platform = 'unix';
		}
	}

	/**
	 * Return the currently matched platform.
	 *
	 * @return  string  The user's platform.
	 *
	 * @since   1.7.0
	 */
	public function getPlatform()
	{
		return $this->platform;
	}

	/**
	 * Set browser version, not by engine version
	 * Fallback to use when no other method identify the engine version
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function identifyBrowserVersion()
	{
		if (preg_match('|Version[/ ]([0-9.]+)|', $this->agent, $version))
		{
			list($this->majorVersion, $this->minorVersion) = explode('.', $version[1]);

			return;
		}

		// Can't identify browser version
		$this->majorVersion = 0;
		$this->minorVersion = 0;
	}

	/**
	 * Sets the current browser.
	 *
	 * @param   string  $browser  The browser to set as current.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setBrowser($browser)
	{
		$this->browser = $browser;
	}

	/**
	 * Retrieve the current browser.
	 *
	 * @return  string  The current browser.
	 *
	 * @since   1.7.0
	 */
	public function getBrowser()
	{
		return $this->browser;
	}

	/**
	 * Retrieve the current browser's major version.
	 *
	 * @return  integer  The current browser's major version
	 *
	 * @since   1.7.0
	 */
	public function getMajor()
	{
		return $this->majorVersion;
	}

	/**
	 * Retrieve the current browser's minor version.
	 *
	 * @return  integer  The current browser's minor version.
	 *
	 * @since   1.7.0
	 */
	public function getMinor()
	{
		return $this->minorVersion;
	}

	/**
	 * Retrieve the current browser's version.
	 *
	 * @return  string  The current browser's version.
	 *
	 * @since   1.7.0
	 */
	public function getVersion()
	{
		return $this->majorVersion . '.' . $this->minorVersion;
	}

	/**
	 * Return the full browser agent string.
	 *
	 * @return  string  The browser agent string
	 *
	 * @since   1.7.0
	 */
	public function getAgentString()
	{
		return $this->agent;
	}

	/**
	 * Returns the server protocol in use on the current server.
	 *
	 * @return  string  The HTTP server protocol version.
	 *
	 * @since   1.7.0
	 */
	public function getHTTPProtocol()
	{
		if (isset($_SERVER['SERVER_PROTOCOL']))
		{
			if (($pos = strrpos($_SERVER['SERVER_PROTOCOL'], '/')))
			{
				return substr($_SERVER['SERVER_PROTOCOL'], $pos + 1);
			}
		}

		return;
	}

	/**
	 * Determines if a browser can display a given MIME type.
	 *
	 * Note that  image/jpeg and image/pjpeg *appear* to be the same
	 * entity, but Mozilla doesn't seem to want to accept the latter.
	 * For our purposes, we will treat them the same.
	 *
	 * @param   string  $mimetype  The MIME type to check.
	 *
	 * @return  boolean  True if the browser can display the MIME type.
	 *
	 * @since   1.7.0
	 */
	public function isViewable($mimetype)
	{
		$mimetype = strtolower($mimetype);
		list($type, $subtype) = explode('/', $mimetype);

		if (!empty($this->accept))
		{
			$wildcard_match = false;

			if (strpos($this->accept, $mimetype) !== false)
			{
				return true;
			}

			if (strpos($this->accept, '*/*') !== false)
			{
				$wildcard_match = true;

				if ($type != 'image')
				{
					return true;
				}
			}

			// Deal with Mozilla pjpeg/jpeg issue
			if ($this->isBrowser('mozilla') && ($mimetype == 'image/pjpeg') && (strpos($this->accept, 'image/jpeg') !== false))
			{
				return true;
			}

			if (!$wildcard_match)
			{
				return false;
			}
		}

		if ($type != 'image')
		{
			return false;
		}

		return in_array($subtype, $this->images);
	}

	/**
	 * Determine if the given browser is the same as the current.
	 *
	 * @param   string  $browser  The browser to check.
	 *
	 * @return  boolean  Is the given browser the same as the current?
	 *
	 * @since   1.7.0
	 */
	public function isBrowser($browser)
	{
		return $this->browser === $browser;
	}

	/**
	 * Determines if the browser is a robot or not.
	 *
	 * @return  boolean  True if browser is a known robot.
	 *
	 * @since   1.7.0
	 */
	public function isRobot()
	{
		foreach ($this->robots as $robot)
		{
			if (preg_match('/' . $robot . '/', $this->agent))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Determines if the browser is mobile version or not.
	 *
	 * @return boolean  True if browser is a known mobile version.
	 *
	 * @since   1.7.0
	 */
	public function isMobile()
	{
		return $this->mobile;
	}

	/**
	 * Determine if we are using a secure (SSL) connection.
	 *
	 * @return  boolean  True if using SSL, false if not.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use the isSSLConnection method on the application object.
	 */
	public function isSSLConnection()
	{
		\JLog::add(
			'Browser::isSSLConnection() is deprecated. Use the isSSLConnection method on the application object instead.',
			\JLog::WARNING,
			'deprecated'
		);

		return (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) || getenv('SSL_PROTOCOL_VERSION');
	}
}
src/Extension/ExtensionHelper.php000064400000025033152177723700013152 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Extension;

defined('JPATH_PLATFORM') or die;

/**
 * Extension Helper class.
 *
 * @since       3.7.4
 *
 * @deprecated  4.0  Replace class with a non static methods for better testing
 */
class ExtensionHelper
{
	/**
	 * Array of core extensions
	 * Each element is an array with elements "type", "element", "folder" and
	 * "client_id".
	 *
	 * @var    array
	 * @since  3.7.4
	 */
	protected static $coreExtensions = array(
		// Format: `type`, `element`, `folder`, `client_id`

		// Core component extensions
		array('component', 'com_actionlogs', '', 1),
		array('component', 'com_admin', '', 1),
		array('component', 'com_ajax', '', 1),
		array('component', 'com_associations', '', 1),
		array('component', 'com_banners', '', 1),
		array('component', 'com_cache', '', 1),
		array('component', 'com_categories', '', 1),
		array('component', 'com_checkin', '', 1),
		array('component', 'com_config', '', 1),
		array('component', 'com_contact', '', 1),
		array('component', 'com_content', '', 1),
		array('component', 'com_contenthistory', '', 1),
		array('component', 'com_cpanel', '', 1),
		array('component', 'com_fields', '', 1),
		array('component', 'com_finder', '', 1),
		array('component', 'com_installer', '', 1),
		array('component', 'com_joomlaupdate', '', 1),
		array('component', 'com_languages', '', 1),
		array('component', 'com_login', '', 1),
		array('component', 'com_mailto', '', 0),
		array('component', 'com_media', '', 1),
		array('component', 'com_menus', '', 1),
		array('component', 'com_messages', '', 1),
		array('component', 'com_modules', '', 1),
		array('component', 'com_newsfeeds', '', 1),
		array('component', 'com_plugins', '', 1),
		array('component', 'com_postinstall', '', 1),
		array('component', 'com_privacy', '', 1),
		array('component', 'com_redirect', '', 1),
		array('component', 'com_search', '', 1),
		array('component', 'com_tags', '', 1),
		array('component', 'com_templates', '', 1),
		array('component', 'com_users', '', 1),
		array('component', 'com_wrapper', '', 0),

		// Core file extensions
		array('file', 'joomla', '', 0),

		// Core language extensions - administrator
		array('language', 'en-GB', '', 1),

		// Core language extensions - site
		array('language', 'en-GB', '', 0),

		// Core library extensions
		array('library', 'fof', '', 0),
		array('library', 'idna_convert', '', 0),
		array('library', 'joomla', '', 0),
		array('library', 'phpass', '', 0),
		array('library', 'phputf8', '', 0),

		// Core module extensions - administrator
		array('module', 'mod_custom', '', 1),
		array('module', 'mod_feed', '', 1),
		array('module', 'mod_latest', '', 1),
		array('module', 'mod_latestactions', '', 1),
		array('module', 'mod_logged', '', 1),
		array('module', 'mod_login', '', 1),
		array('module', 'mod_menu', '', 1),
		array('module', 'mod_multilangstatus', '', 1),
		array('module', 'mod_popular', '', 1),
		array('module', 'mod_privacy_dashboard', '', 1),
		array('module', 'mod_quickicon', '', 1),
		array('module', 'mod_sampledata', '', 1),
		array('module', 'mod_stats_admin', '', 1),
		array('module', 'mod_status', '', 1),
		array('module', 'mod_submenu', '', 1),
		array('module', 'mod_title', '', 1),
		array('module', 'mod_toolbar', '', 1),
		array('module', 'mod_version', '', 1),

		// Core module extensions - site
		array('module', 'mod_articles_archive', '', 0),
		array('module', 'mod_articles_categories', '', 0),
		array('module', 'mod_articles_category', '', 0),
		array('module', 'mod_articles_latest', '', 0),
		array('module', 'mod_articles_news', '', 0),
		array('module', 'mod_articles_popular', '', 0),
		array('module', 'mod_banners', '', 0),
		array('module', 'mod_breadcrumbs', '', 0),
		array('module', 'mod_custom', '', 0),
		array('module', 'mod_feed', '', 0),
		array('module', 'mod_finder', '', 0),
		array('module', 'mod_footer', '', 0),
		array('module', 'mod_languages', '', 0),
		array('module', 'mod_login', '', 0),
		array('module', 'mod_menu', '', 0),
		array('module', 'mod_random_image', '', 0),
		array('module', 'mod_related_items', '', 0),
		array('module', 'mod_search', '', 0),
		array('module', 'mod_stats', '', 0),
		array('module', 'mod_syndicate', '', 0),
		array('module', 'mod_tags_popular', '', 0),
		array('module', 'mod_tags_similar', '', 0),
		array('module', 'mod_users_latest', '', 0),
		array('module', 'mod_whosonline', '', 0),
		array('module', 'mod_wrapper', '', 0),

		// Core package extensions
		array('package', 'pkg_en-GB', '', 0),

		// Core plugin extensions - actionlog
		array('plugin', 'joomla', 'actionlog', 0),

		// Core plugin extensions - authentication
		array('plugin', 'cookie', 'authentication', 0),
		array('plugin', 'gmail', 'authentication', 0),
		array('plugin', 'joomla', 'authentication', 0),
		array('plugin', 'ldap', 'authentication', 0),

		// Core plugin extensions - captcha
		array('plugin', 'recaptcha', 'captcha', 0),
		array('plugin', 'recaptcha_invisible', 'captcha', 0),

		// Core plugin extensions - content
		array('plugin', 'confirmconsent', 'content', 0),
		array('plugin', 'contact', 'content', 0),
		array('plugin', 'emailcloak', 'content', 0),
		array('plugin', 'fields', 'content', 0),
		array('plugin', 'finder', 'content', 0),
		array('plugin', 'joomla', 'content', 0),
		array('plugin', 'loadmodule', 'content', 0),
		array('plugin', 'pagebreak', 'content', 0),
		array('plugin', 'pagenavigation', 'content', 0),
		array('plugin', 'vote', 'content', 0),

		// Core plugin extensions - editors
		array('plugin', 'codemirror', 'editors', 0),
		array('plugin', 'none', 'editors', 0),
		array('plugin', 'tinymce', 'editors', 0),

		// Core plugin extensions - editors xtd
		array('plugin', 'article', 'editors-xtd', 0),
		array('plugin', 'contact', 'editors-xtd', 0),
		array('plugin', 'fields', 'editors-xtd', 0),
		array('plugin', 'image', 'editors-xtd', 0),
		array('plugin', 'menu', 'editors-xtd', 0),
		array('plugin', 'module', 'editors-xtd', 0),
		array('plugin', 'pagebreak', 'editors-xtd', 0),
		array('plugin', 'readmore', 'editors-xtd', 0),

		// Core plugin extensions - extension
		array('plugin', 'joomla', 'extension', 0),

		// Core plugin extensions - fields
		array('plugin', 'calendar', 'fields', 0),
		array('plugin', 'checkboxes', 'fields', 0),
		array('plugin', 'color', 'fields', 0),
		array('plugin', 'editor', 'fields', 0),
		array('plugin', 'imagelist', 'fields', 0),
		array('plugin', 'integer', 'fields', 0),
		array('plugin', 'list', 'fields', 0),
		array('plugin', 'media', 'fields', 0),
		array('plugin', 'radio', 'fields', 0),
		array('plugin', 'repeatable', 'fields', 0),
		array('plugin', 'sql', 'fields', 0),
		array('plugin', 'text', 'fields', 0),
		array('plugin', 'textarea', 'fields', 0),
		array('plugin', 'url', 'fields', 0),
		array('plugin', 'user', 'fields', 0),
		array('plugin', 'usergrouplist', 'fields', 0),

		// Core plugin extensions - finder
		array('plugin', 'categories', 'finder', 0),
		array('plugin', 'contacts', 'finder', 0),
		array('plugin', 'content', 'finder', 0),
		array('plugin', 'newsfeeds', 'finder', 0),
		array('plugin', 'tags', 'finder', 0),

		// Core plugin extensions - installer
		array('plugin', 'folderinstaller', 'installer', 0),
		array('plugin', 'packageinstaller', 'installer', 0),
		array('plugin', 'urlinstaller', 'installer', 0),

		// Core plugin extensions - privacy
		array('plugin', 'actionlogs', 'privacy', 0),
		array('plugin', 'consents', 'privacy', 0),
		array('plugin', 'contact', 'privacy', 0),
		array('plugin', 'content', 'privacy', 0),
		array('plugin', 'message', 'privacy', 0),
		array('plugin', 'user', 'privacy', 0),

		// Core plugin extensions - quick icon
		array('plugin', 'extensionupdate', 'quickicon', 0),
		array('plugin', 'joomlaupdate', 'quickicon', 0),
		array('plugin', 'phpversioncheck', 'quickicon', 0),
		array('plugin', 'privacycheck', 'quickicon', 0),

		// Core plugin extensions - sample data
		array('plugin', 'blog', 'sampledata', 0),

		// Core plugin extensions - search
		array('plugin', 'categories', 'search', 0),
		array('plugin', 'contacts', 'search', 0),
		array('plugin', 'content', 'search', 0),
		array('plugin', 'newsfeeds', 'search', 0),
		array('plugin', 'tags', 'search', 0),

		// Core plugin extensions - system
		array('plugin', 'actionlogs', 'system', 0),
		array('plugin', 'cache', 'system', 0),
		array('plugin', 'debug', 'system', 0),
		array('plugin', 'fields', 'system', 0),
		array('plugin', 'highlight', 'system', 0),
		array('plugin', 'languagecode', 'system', 0),
		array('plugin', 'languagefilter', 'system', 0),
		array('plugin', 'log', 'system', 0),
		array('plugin', 'logout', 'system', 0),
		array('plugin', 'logrotation', 'system', 0),
		array('plugin', 'p3p', 'system', 0),
		array('plugin', 'privacyconsent', 'system', 0),
		array('plugin', 'redirect', 'system', 0),
		array('plugin', 'remember', 'system', 0),
		array('plugin', 'sef', 'system', 0),
		array('plugin', 'sessiongc', 'system', 0),
		array('plugin', 'stats', 'system', 0),
		array('plugin', 'updatenotification', 'system', 0),

		// Core plugin extensions - two factor authentication
		array('plugin', 'totp', 'twofactorauth', 0),
		array('plugin', 'yubikey', 'twofactorauth', 0),

		// Core plugin extensions - user
		array('plugin', 'contactcreator', 'user', 0),
		array('plugin', 'joomla', 'user', 0),
		array('plugin', 'profile', 'user', 0),
		array('plugin', 'terms', 'user', 0),

		// Core template extensions - administrator
		array('template', 'hathor', '', 1),
		array('template', 'isis', '', 1),

		// Core template extensions - site
		array('template', 'beez3', '', 0),
		array('template', 'protostar', '', 0),
	);

	/**
	 * Gets the core extensions.
	 *
	 * @return  array  Array with core extensions.
	 *                 Each extension is an array with following format:
	 *                 `type`, `element`, `folder`, `client_id`.
	 *
	 * @since   3.7.4
	 */
	public static function getCoreExtensions()
	{
		return self::$coreExtensions;
	}

	/**
	 * Check if an extension is core or not
	 *
	 * @param   string   $type       The extension's type.
	 * @param   string   $element    The extension's element name.
	 * @param   integer  $client_id  The extension's client ID. Default 0.
	 * @param   string   $folder     The extension's folder. Default ''.
	 *
	 * @return  boolean  True if core, false if not.
	 *
	 * @since   3.7.4
	 */
	public static function checkIfCoreExtension($type, $element, $client_id = 0, $folder = '')
	{
		return in_array(array($type, $element, $folder, $client_id), self::$coreExtensions);
	}
}
src/Application/AdministratorApplication.php000064400000031371152177723700015333 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;

/**
 * Joomla! Administrator Application class
 *
 * @since  3.2
 */
class AdministratorApplication extends CMSApplication
{
	/**
	 * Class constructor.
	 *
	 * @param   Input                   $input   An optional argument to provide dependency injection for the application's
	 *                                           input object.  If the argument is a \JInput object that object will become
	 *                                           the application's input object, otherwise a default input object is created.
	 * @param   Registry                $config  An optional argument to provide dependency injection for the application's
	 *                                           config object.  If the argument is a Registry object that object will become
	 *                                           the application's config object, otherwise a default config object is created.
	 * @param   \JApplicationWebClient  $client  An optional argument to provide dependency injection for the application's
	 *                                           client object.  If the argument is a \JApplicationWebClient object that object will become
	 *                                           the application's client object, otherwise a default client object is created.
	 *
	 * @since   3.2
	 */
	public function __construct(Input $input = null, Registry $config = null, \JApplicationWebClient $client = null)
	{
		// Register the application name
		$this->_name = 'administrator';

		// Register the client ID
		$this->_clientId = 1;

		// Execute the parent constructor
		parent::__construct($input, $config, $client);

		// Set the root in the URI based on the application name
		\JUri::root(null, rtrim(dirname(\JUri::base(true)), '/\\'));
	}

	/**
	 * Dispatch the application
	 *
	 * @param   string  $component  The component which is being rendered.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function dispatch($component = null)
	{
		if ($component === null)
		{
			$component = \JAdministratorHelper::findOption();
		}

		// Load the document to the API
		$this->loadDocument();

		// Set up the params
		$document = \JFactory::getDocument();

		// Register the document object with \JFactory
		\JFactory::$document = $document;

		switch ($document->getType())
		{
			case 'html':
				$document->setMetaData('keywords', $this->get('MetaKeys'));

				// Get the template
				$template = $this->getTemplate(true);

				// Store the template and its params to the config
				$this->set('theme', $template->template);
				$this->set('themeParams', $template->params);

				break;

			default:
				break;
		}

		$document->setTitle($this->get('sitename') . ' - ' . \JText::_('JADMINISTRATION'));
		$document->setDescription($this->get('MetaDesc'));
		$document->setGenerator('Joomla! - Open Source Content Management');

		$contents = ComponentHelper::renderComponent($component);
		$document->setBuffer($contents, 'component');

		// Trigger the onAfterDispatch event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterDispatch');
	}

	/**
	 * Method to run the Web application routines.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function doExecute()
	{
		// Get the language from the (login) form or user state
		$login_lang = ($this->input->get('option') == 'com_login') ? $this->input->get('lang') : '';
		$options    = array('language' => $login_lang ?: $this->getUserState('application.lang'));

		// Initialise the application
		$this->initialiseApp($options);

		// Test for magic quotes
		if (get_magic_quotes_gpc())
		{
			$lang = $this->getLanguage();

			if ($lang->hasKey('JERROR_MAGIC_QUOTES'))
			{
				$this->enqueueMessage(\JText::_('JERROR_MAGIC_QUOTES'), 'error');
			}
			else
			{
				$this->enqueueMessage('Your host needs to disable magic_quotes_gpc to run this version of Joomla!', 'error');
			}
		}

		// Mark afterInitialise in the profiler.
		JDEBUG ? $this->profiler->mark('afterInitialise') : null;

		// Route the application
		$this->route();

		// Mark afterRoute in the profiler.
		JDEBUG ? $this->profiler->mark('afterRoute') : null;

		/*
		 * Check if the user is required to reset their password
		 *
		 * Before $this->route(); "option" and "view" can't be safely read using:
		 * $this->input->getCmd('option'); or $this->input->getCmd('view');
		 * ex: due of the sef urls
		 */
		$this->checkUserRequireReset('com_admin', 'profile', 'edit', 'com_admin/profile.save,com_admin/profile.apply,com_login/logout');

		// Dispatch the application
		$this->dispatch();

		// Mark afterDispatch in the profiler.
		JDEBUG ? $this->profiler->mark('afterDispatch') : null;
	}

	/**
	 * Return a reference to the \JRouter object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JRouter
	 *
	 * @since	3.2
	 */
	public static function getRouter($name = 'administrator', array $options = array())
	{
		return parent::getRouter($name, $options);
	}

	/**
	 * Gets the name of the current template.
	 *
	 * @param   boolean  $params  True to return the template parameters
	 *
	 * @return  string  The name of the template.
	 *
	 * @since   3.2
	 * @throws  \InvalidArgumentException
	 */
	public function getTemplate($params = false)
	{
		if (is_object($this->template))
		{
			if ($params)
			{
				return $this->template;
			}

			return $this->template->template;
		}

		$admin_style = \JFactory::getUser()->getParam('admin_style');

		// Load the template name from the database
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('template, s.params')
			->from('#__template_styles as s')
			->join('LEFT', '#__extensions as e ON e.type=' . $db->quote('template') . ' AND e.element=s.template AND e.client_id=s.client_id');

		if ($admin_style)
		{
			$query->where('s.client_id = 1 AND id = ' . (int) $admin_style . ' AND e.enabled = 1', 'OR');
		}

		$query->where('s.client_id = 1 AND home = ' . $db->quote('1'), 'OR')
			->order('home');
		$db->setQuery($query);
		$template = $db->loadObject();

		$template->template = \JFilterInput::getInstance()->clean($template->template, 'cmd');
		$template->params = new Registry($template->params);

		if (!file_exists(JPATH_THEMES . '/' . $template->template . '/index.php'))
		{
			$this->enqueueMessage(\JText::_('JERROR_ALERTNOTEMPLATE'), 'error');
			$template->params = new Registry;
			$template->template = 'isis';
		}

		// Cache the result
		$this->template = $template;

		if (!file_exists(JPATH_THEMES . '/' . $template->template . '/index.php'))
		{
			throw new \InvalidArgumentException(\JText::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $template->template));
		}

		if ($params)
		{
			return $template;
		}

		return $template->template;
	}

	/**
	 * Initialise the application.
	 *
	 * @param   array  $options  An optional associative array of configuration settings.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function initialiseApp($options = array())
	{
		$user = \JFactory::getUser();

		// If the user is a guest we populate it with the guest user group.
		if ($user->guest)
		{
			$guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
			$user->groups = array($guestUsergroup);
		}

		// If a language was specified it has priority, otherwise use user or default language settings
		if (empty($options['language']))
		{
			$lang = $user->getParam('admin_language');

			// Make sure that the user's language exists
			if ($lang && \JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
			else
			{
				$params = ComponentHelper::getParams('com_languages');
				$options['language'] = $params->get('administrator', $this->get('language', 'en-GB'));
			}
		}

		// One last check to make sure we have something
		if (!\JLanguageHelper::exists($options['language']))
		{
			$lang = $this->get('language', 'en-GB');

			if (\JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
			else
			{
				// As a last ditch fail to english
				$options['language'] = 'en-GB';
			}
		}

		// Finish initialisation
		parent::initialiseApp($options);
	}

	/**
	 * Login authentication function
	 *
	 * @param   array  $credentials  Array('username' => string, 'password' => string)
	 * @param   array  $options      Array('remember' => boolean)
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 */
	public function login($credentials, $options = array())
	{
		// The minimum group
		$options['group'] = 'Public Backend';

		// Make sure users are not auto-registered
		$options['autoregister'] = false;

		// Set the application login entry point
		if (!array_key_exists('entry_url', $options))
		{
			$options['entry_url'] = \JUri::base() . 'index.php?option=com_users&task=login';
		}

		// Set the access control action to check.
		$options['action'] = 'core.login.admin';

		$result = parent::login($credentials, $options);

		if (!($result instanceof \Exception))
		{
			$lang = $this->input->getCmd('lang');
			$lang = preg_replace('/[^A-Z-]/i', '', $lang);

			if ($lang)
			{
				$this->setUserState('application.lang', $lang);
			}

			static::purgeMessages();
		}

		return $result;
	}

	/**
	 * Purge the jos_messages table of old messages
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function purgeMessages()
	{
		$user = \JFactory::getUser();
		$userid = $user->get('id');

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('*')
			->from($db->quoteName('#__messages_cfg'))
			->where($db->quoteName('user_id') . ' = ' . (int) $userid, 'AND')
			->where($db->quoteName('cfg_name') . ' = ' . $db->quote('auto_purge'), 'AND');
		$db->setQuery($query);
		$config = $db->loadObject();

		// Check if auto_purge value set
		if (is_object($config) && $config->cfg_name === 'auto_purge')
		{
			$purge = $config->cfg_value;
		}
		else
		{
			// If no value set, default is 7 days
			$purge = 7;
		}

		// If purge value is not 0, then allow purging of old messages
		if ($purge > 0)
		{
			// Purge old messages at day set in message configuration
			$past = \JFactory::getDate(time() - $purge * 86400);
			$pastStamp = $past->toSql();

			$query->clear()
				->delete($db->quoteName('#__messages'))
				->where($db->quoteName('date_time') . ' < ' . $db->quote($pastStamp), 'AND')
				->where($db->quoteName('user_id_to') . ' = ' . (int) $userid, 'AND');
			$db->setQuery($query);
			$db->execute();
		}
	}

	/**
	 * Rendering is the process of pushing the document buffers into the template
	 * placeholders, retrieving data from the document and pushing it into
	 * the application response buffer.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function render()
	{
		// Get the \JInput object
		$input = $this->input;

		$component = $input->getCmd('option', 'com_login');
		$file      = $input->getCmd('tmpl', 'index');

		if ($component === 'com_login')
		{
			$file = 'login';
		}

		$this->set('themeFile', $file . '.php');

		// Safety check for when configuration.php root_user is in use.
		$rootUser = $this->get('root_user');

		if (property_exists('\JConfig', 'root_user'))
		{
			if (\JFactory::getUser()->get('username') === $rootUser || \JFactory::getUser()->id === (string) $rootUser)
			{
				$this->enqueueMessage(
					\JText::sprintf(
						'JWARNING_REMOVE_ROOT_USER',
						'index.php?option=com_config&task=config.removeroot&' . \JSession::getFormToken() . '=1'
					),
					'error'
				);
			}
			// Show this message to superusers too
			elseif (\JFactory::getUser()->authorise('core.admin'))
			{
				$this->enqueueMessage(
					\JText::sprintf(
						'JWARNING_REMOVE_ROOT_USER_ADMIN',
						$rootUser,
						'index.php?option=com_config&task=config.removeroot&' . \JSession::getFormToken() . '=1'
					),
					'error'
				);
			}
		}

		parent::render();
	}

	/**
	 * Route the application.
	 *
	 * Routing is the process of examining the request environment to determine which
	 * component should receive the request. The component optional parameters
	 * are then set in the request object to be processed when the application is being
	 * dispatched.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function route()
	{
		$uri = \JUri::getInstance();

		if ($this->get('force_ssl') >= 1 && strtolower($uri->getScheme()) !== 'https')
		{
			// Forward to https
			$uri->setScheme('https');
			$this->redirect((string) $uri, 301);
		}

		// Trigger the onAfterRoute event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterRoute');
	}
}
src/Application/ApplicationHelper.php000064400000014223152177723700013727 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;

/**
 * Application helper functions
 *
 * @since  1.5
 */
class ApplicationHelper
{
	/**
	 * Client information array
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected static $_clients = array();

	/**
	 * Return the name of the request component [main component]
	 *
	 * @param   string  $default  The default option
	 *
	 * @return  string  Option (e.g. com_something)
	 *
	 * @since   1.6
	 */
	public static function getComponentName($default = null)
	{
		static $option;

		if ($option)
		{
			return $option;
		}

		$input = \JFactory::getApplication()->input;
		$option = strtolower($input->get('option'));

		if (empty($option))
		{
			$option = $default;
		}

		$input->set('option', $option);

		return $option;
	}

	/**
	 * Provides a secure hash based on a seed
	 *
	 * @param   string  $seed  Seed string.
	 *
	 * @return  string  A secure hash
	 *
	 * @since   3.2
	 */
	public static function getHash($seed)
	{
		return md5(\JFactory::getConfig()->get('secret') . $seed);
	}

	/**
	 * This method transliterates a string into a URL
	 * safe string or returns a URL safe UTF-8 string
	 * based on the global configuration
	 *
	 * @param   string  $string    String to process
	 * @param   string  $language  Language to transliterate to if unicode slugs are disabled
	 *
	 * @return  string  Processed string
	 *
	 * @since   3.2
	 */
	public static function stringURLSafe($string, $language = '')
	{
		if (\JFactory::getConfig()->get('unicodeslugs') == 1)
		{
			$output = \JFilterOutput::stringURLUnicodeSlug($string);
		}
		else
		{
			if ($language === '*' || $language === '')
			{
				$languageParams = ComponentHelper::getParams('com_languages');
				$language = $languageParams->get('site');
			}

			$output = \JFilterOutput::stringURLSafe($string, $language);
		}

		return $output;
	}

	/**
	 * Gets information on a specific client id.  This method will be useful in
	 * future versions when we start mapping applications in the database.
	 *
	 * This method will return a client information array if called
	 * with no arguments which can be used to add custom application information.
	 *
	 * @param   integer  $id      A client identifier
	 * @param   boolean  $byName  If True, find the client by its name
	 *
	 * @return  mixed  Object describing the client or false if not known
	 *
	 * @since   1.5
	 */
	public static function getClientInfo($id = null, $byName = false)
	{
		// Only create the array if it is empty
		if (empty(self::$_clients))
		{
			$obj = new \stdClass;

			// Site Client
			$obj->id = 0;
			$obj->name = 'site';
			$obj->path = JPATH_SITE;
			self::$_clients[0] = clone $obj;

			// Administrator Client
			$obj->id = 1;
			$obj->name = 'administrator';
			$obj->path = JPATH_ADMINISTRATOR;
			self::$_clients[1] = clone $obj;

			// Installation Client
			$obj->id = 2;
			$obj->name = 'installation';
			$obj->path = JPATH_INSTALLATION;
			self::$_clients[2] = clone $obj;
		}

		// If no client id has been passed return the whole array
		if ($id === null)
		{
			return self::$_clients;
		}

		// Are we looking for client information by id or by name?
		if (!$byName)
		{
			if (isset(self::$_clients[$id]))
			{
				return self::$_clients[$id];
			}
		}
		else
		{
			foreach (self::$_clients as $client)
			{
				if ($client->name == strtolower($id))
				{
					return $client;
				}
			}
		}

		return;
	}

	/**
	 * Adds information for a client.
	 *
	 * @param   mixed  $client  A client identifier either an array or object
	 *
	 * @return  boolean  True if the information is added. False on error
	 *
	 * @since   1.6
	 */
	public static function addClientInfo($client)
	{
		if (is_array($client))
		{
			$client = (object) $client;
		}

		if (!is_object($client))
		{
			return false;
		}

		$info = self::getClientInfo();

		if (!isset($client->id))
		{
			$client->id = count($info);
		}

		self::$_clients[$client->id] = clone $client;

		return true;
	}

	/**
	 * Parse a XML install manifest file.
	 *
	 * XML Root tag should be 'install' except for languages which use meta file.
	 *
	 * @param   string  $path  Full path to XML file.
	 *
	 * @return  array  XML metadata.
	 *
	 * @since       1.5
	 * @deprecated  4.0 Use \JInstaller::parseXMLInstallFile instead.
	 */
	public static function parseXMLInstallFile($path)
	{
		\JLog::add('ApplicationHelper::parseXMLInstallFile is deprecated. Use \JInstaller::parseXMLInstallFile instead.', \JLog::WARNING, 'deprecated');

		return \JInstaller::parseXMLInstallFile($path);
	}

	/**
	 * Parse a XML language meta file.
	 *
	 * XML Root tag  for languages which is meta file.
	 *
	 * @param   string  $path  Full path to XML file.
	 *
	 * @return  array  XML metadata.
	 *
	 * @since       1.5
	 * @deprecated  4.0 Use \JInstaller::parseXMLInstallFile instead.
	 */
	public static function parseXMLLangMetaFile($path)
	{
		\JLog::add('ApplicationHelper::parseXMLLangMetaFile is deprecated. Use \JInstaller::parseXMLInstallFile instead.', \JLog::WARNING, 'deprecated');

		// Check if meta file exists.
		if (!file_exists($path))
		{
			return false;
		}

		// Read the file to see if it's a valid component XML file
		$xml = simplexml_load_file($path);

		if (!$xml)
		{
			return false;
		}

		/*
		 * Check for a valid XML root tag.
		 *
		 * Should be 'metafile'.
		 */
		if ($xml->getName() !== 'metafile')
		{
			unset($xml);

			return false;
		}

		$data = array();

		$data['name'] = (string) $xml->name;
		$data['type'] = $xml->attributes()->type;

		$data['creationDate'] = ((string) $xml->creationDate) ?: \JText::_('JLIB_UNKNOWN');
		$data['author'] = ((string) $xml->author) ?: \JText::_('JLIB_UNKNOWN');

		$data['copyright'] = (string) $xml->copyright;
		$data['authorEmail'] = (string) $xml->authorEmail;
		$data['authorUrl'] = (string) $xml->authorUrl;
		$data['version'] = (string) $xml->version;
		$data['description'] = (string) $xml->description;
		$data['group'] = (string) $xml->group;

		return $data;
	}
}
src/Application/DaemonApplication.php000064400000061030152177723700013711 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');

use Joomla\Registry\Registry;

/**
 * Class to turn CliApplication applications into daemons.  It requires CLI and PCNTL support built into PHP.
 *
 * @link   https://www.php.net/manual/en/book.pcntl.php
 * @link   https://www.php.net/manual/en/features.commandline.php
 * @since  1.7.0
 */
class DaemonApplication extends CliApplication
{
	/**
	 * @var    array  The available POSIX signals to be caught by default.
	 * @link   https://www.php.net/manual/pcntl.constants.php
	 * @since  1.7.0
	 */
	protected static $signals = array(
		'SIGHUP',
		'SIGINT',
		'SIGQUIT',
		'SIGILL',
		'SIGTRAP',
		'SIGABRT',
		'SIGIOT',
		'SIGBUS',
		'SIGFPE',
		'SIGUSR1',
		'SIGSEGV',
		'SIGUSR2',
		'SIGPIPE',
		'SIGALRM',
		'SIGTERM',
		'SIGSTKFLT',
		'SIGCLD',
		'SIGCHLD',
		'SIGCONT',
		'SIGTSTP',
		'SIGTTIN',
		'SIGTTOU',
		'SIGURG',
		'SIGXCPU',
		'SIGXFSZ',
		'SIGVTALRM',
		'SIGPROF',
		'SIGWINCH',
		'SIGPOLL',
		'SIGIO',
		'SIGPWR',
		'SIGSYS',
		'SIGBABY',
		'SIG_BLOCK',
		'SIG_UNBLOCK',
		'SIG_SETMASK',
	);

	/**
	 * @var    boolean  True if the daemon is in the process of exiting.
	 * @since  1.7.0
	 */
	protected $exiting = false;

	/**
	 * @var    integer  The parent process id.
	 * @since  3.0.0
	 */
	protected $parentId = 0;

	/**
	 * @var    integer  The process id of the daemon.
	 * @since  1.7.0
	 */
	protected $processId = 0;

	/**
	 * @var    boolean  True if the daemon is currently running.
	 * @since  1.7.0
	 */
	protected $running = false;

	/**
	 * Class constructor.
	 *
	 * @param   \JInputCli         $input       An optional argument to provide dependency injection for the application's
	 *                                         input object.  If the argument is a \JInputCli object that object will become
	 *                                         the application's input object, otherwise a default input object is created.
	 * @param   Registry           $config      An optional argument to provide dependency injection for the application's
	 *                                         config object.  If the argument is a Registry object that object will become
	 *                                         the application's config object, otherwise a default config object is created.
	 * @param   \JEventDispatcher  $dispatcher  An optional argument to provide dependency injection for the application's
	 *                                         event dispatcher.  If the argument is a \JEventDispatcher object that object will become
	 *                                         the application's event dispatcher, if it is null then the default event dispatcher
	 *                                         will be created based on the application's loadDispatcher() method.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function __construct(\JInputCli $input = null, Registry $config = null, \JEventDispatcher $dispatcher = null)
	{
		// Verify that the process control extension for PHP is available.
		if (!defined('SIGHUP'))
		{
			\JLog::add('The PCNTL extension for PHP is not available.', \JLog::ERROR);
			throw new \RuntimeException('The PCNTL extension for PHP is not available.');
		}

		// Verify that POSIX support for PHP is available.
		if (!function_exists('posix_getpid'))
		{
			\JLog::add('The POSIX extension for PHP is not available.', \JLog::ERROR);
			throw new \RuntimeException('The POSIX extension for PHP is not available.');
		}

		// Call the parent constructor.
		parent::__construct($input, $config, $dispatcher);

		// Set some system limits.
		@set_time_limit($this->config->get('max_execution_time', 0));

		if ($this->config->get('max_memory_limit') !== null)
		{
			ini_set('memory_limit', $this->config->get('max_memory_limit', '256M'));
		}

		// Flush content immediately.
		ob_implicit_flush();
	}

	/**
	 * Method to handle POSIX signals.
	 *
	 * @param   integer  $signal  The received POSIX signal.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @see     pcntl_signal()
	 * @throws  \RuntimeException
	 */
	public static function signal($signal)
	{
		// Log all signals sent to the daemon.
		\JLog::add('Received signal: ' . $signal, \JLog::DEBUG);

		// Let's make sure we have an application instance.
		if (!is_subclass_of(static::$instance, 'CliApplication'))
		{
			\JLog::add('Cannot find the application instance.', \JLog::EMERGENCY);
			throw new \RuntimeException('Cannot find the application instance.');
		}

		// Fire the onReceiveSignal event.
		static::$instance->triggerEvent('onReceiveSignal', array($signal));

		switch ($signal)
		{
			case SIGINT:
			case SIGTERM:
				// Handle shutdown tasks
				if (static::$instance->running && static::$instance->isActive())
				{
					static::$instance->shutdown();
				}
				else
				{
					static::$instance->close();
				}
				break;
			case SIGHUP:
				// Handle restart tasks
				if (static::$instance->running && static::$instance->isActive())
				{
					static::$instance->shutdown(true);
				}
				else
				{
					static::$instance->close();
				}
				break;
			case SIGCHLD:
				// A child process has died
				while (static::$instance->pcntlWait($signal, WNOHANG || WUNTRACED) > 0)
				{
					usleep(1000);
				}
				break;
			case SIGCLD:
				while (static::$instance->pcntlWait($signal, WNOHANG) > 0)
				{
					$signal = static::$instance->pcntlChildExitStatus($signal);
				}
				break;
			default:
				break;
		}
	}

	/**
	 * Check to see if the daemon is active.  This does not assume that $this daemon is active, but
	 * only if an instance of the application is active as a daemon.
	 *
	 * @return  boolean  True if daemon is active.
	 *
	 * @since   1.7.0
	 */
	public function isActive()
	{
		// Get the process id file location for the application.
		$pidFile = $this->config->get('application_pid_file');

		// If the process id file doesn't exist then the daemon is obviously not running.
		if (!is_file($pidFile))
		{
			return false;
		}

		// Read the contents of the process id file as an integer.
		$fp = fopen($pidFile, 'r');
		$pid = fread($fp, filesize($pidFile));
		$pid = (int) $pid;
		fclose($fp);

		// Check to make sure that the process id exists as a positive integer.
		if (!$pid)
		{
			return false;
		}

		// Check to make sure the process is active by pinging it and ensure it responds.
		if (!posix_kill($pid, 0))
		{
			// No response so remove the process id file and log the situation.
			@ unlink($pidFile);
			\JLog::add('The process found based on PID file was unresponsive.', \JLog::WARNING);

			return false;
		}

		return true;
	}

	/**
	 * Load an object or array into the application configuration object.
	 *
	 * @param   mixed  $data  Either an array or object to be loaded into the configuration object.
	 *
	 * @return  DaemonApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function loadConfiguration($data)
	{
		// Execute the parent load method.
		parent::loadConfiguration($data);

		/*
		 * Setup some application metadata options.  This is useful if we ever want to write out startup scripts
		 * or just have some sort of information available to share about things.
		 */

		// The application author name.  This string is used in generating startup scripts and has
		// a maximum of 50 characters.
		$tmp = (string) $this->config->get('author_name', 'Joomla Platform');
		$this->config->set('author_name', (strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp);

		// The application author email.  This string is used in generating startup scripts.
		$tmp = (string) $this->config->get('author_email', 'admin@joomla.org');
		$this->config->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL));

		// The application name.  This string is used in generating startup scripts.
		$tmp = (string) $this->config->get('application_name', 'DaemonApplication');
		$this->config->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp));

		// The application description.  This string is used in generating startup scripts.
		$tmp = (string) $this->config->get('application_description', 'A generic Joomla Platform application.');
		$this->config->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING));

		/*
		 * Setup the application path options.  This defines the default executable name, executable directory,
		 * and also the path to the daemon process id file.
		 */

		// The application executable daemon.  This string is used in generating startup scripts.
		$tmp = (string) $this->config->get('application_executable', basename($this->input->executable));
		$this->config->set('application_executable', $tmp);

		// The home directory of the daemon.
		$tmp = (string) $this->config->get('application_directory', dirname($this->input->executable));
		$this->config->set('application_directory', $tmp);

		// The pid file location.  This defaults to a path inside the /tmp directory.
		$name = $this->config->get('application_name');
		$tmp = (string) $this->config->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid'));
		$this->config->set('application_pid_file', $tmp);

		/*
		 * Setup the application identity options.  It is important to remember if the default of 0 is set for
		 * either UID or GID then changing that setting will not be attempted as there is no real way to "change"
		 * the identity of a process from some user to root.
		 */

		// The user id under which to run the daemon.
		$tmp = (int) $this->config->get('application_uid', 0);
		$options = array('options' => array('min_range' => 0, 'max_range' => 65000));
		$this->config->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options));

		// The group id under which to run the daemon.
		$tmp = (int) $this->config->get('application_gid', 0);
		$options = array('options' => array('min_range' => 0, 'max_range' => 65000));
		$this->config->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options));

		// Option to kill the daemon if it cannot switch to the chosen identity.
		$tmp = (bool) $this->config->get('application_require_identity', 1);
		$this->config->set('application_require_identity', $tmp);

		/*
		 * Setup the application runtime options.  By default our execution time limit is infinite obviously
		 * because a daemon should be constantly running unless told otherwise.  The default limit for memory
		 * usage is 256M, which admittedly is a little high, but remember it is a "limit" and PHP's memory
		 * management leaves a bit to be desired :-)
		 */

		// The maximum execution time of the application in seconds.  Zero is infinite.
		$tmp = $this->config->get('max_execution_time');

		if ($tmp !== null)
		{
			$this->config->set('max_execution_time', (int) $tmp);
		}

		// The maximum amount of memory the application can use.
		$tmp = $this->config->get('max_memory_limit', '256M');

		if ($tmp !== null)
		{
			$this->config->set('max_memory_limit', (string) $tmp);
		}

		return $this;
	}

	/**
	 * Execute the daemon.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function execute()
	{
		// Trigger the onBeforeExecute event.
		$this->triggerEvent('onBeforeExecute');

		// Enable basic garbage collection.
		gc_enable();

		\JLog::add('Starting ' . $this->name, \JLog::INFO);

		// Set off the process for becoming a daemon.
		if ($this->daemonize())
		{
			// Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor
			// incoming signals after each tick and call the relevant signal handler automatically.
			declare (ticks = 1);

			// Start the main execution loop.
			while (true)
			{
				// Perform basic garbage collection.
				$this->gc();

				// Don't completely overload the CPU.
				usleep(1000);

				// Execute the main application logic.
				$this->doExecute();
			}
		}
		// We were not able to daemonize the application so log the failure and die gracefully.
		else
		{
			\JLog::add('Starting ' . $this->name . ' failed', \JLog::INFO);
		}

		// Trigger the onAfterExecute event.
		$this->triggerEvent('onAfterExecute');
	}

	/**
	 * Restart daemon process.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function restart()
	{
		\JLog::add('Stopping ' . $this->name, \JLog::INFO);
		$this->shutdown(true);
	}

	/**
	 * Stop daemon process.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function stop()
	{
		\JLog::add('Stopping ' . $this->name, \JLog::INFO);
		$this->shutdown();
	}

	/**
	 * Method to change the identity of the daemon process and resources.
	 *
	 * @return  boolean  True if identity successfully changed
	 *
	 * @since   1.7.0
	 * @see     posix_setuid()
	 */
	protected function changeIdentity()
	{
		// Get the group and user ids to set for the daemon.
		$uid = (int) $this->config->get('application_uid', 0);
		$gid = (int) $this->config->get('application_gid', 0);

		// Get the application process id file path.
		$file = $this->config->get('application_pid_file');

		// Change the user id for the process id file if necessary.
		if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid)))
		{
			\JLog::add('Unable to change user ownership of the process id file.', \JLog::ERROR);

			return false;
		}

		// Change the group id for the process id file if necessary.
		if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid)))
		{
			\JLog::add('Unable to change group ownership of the process id file.', \JLog::ERROR);

			return false;
		}

		// Set the correct home directory for the process.
		if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir']))
		{
			system('export HOME="' . $info['dir'] . '"');
		}

		// Change the user id for the process necessary.
		if ($uid && (posix_getuid($file) != $uid) && (!@ posix_setuid($uid)))
		{
			\JLog::add('Unable to change user ownership of the proccess.', \JLog::ERROR);

			return false;
		}

		// Change the group id for the process necessary.
		if ($gid && (posix_getgid($file) != $gid) && (!@ posix_setgid($gid)))
		{
			\JLog::add('Unable to change group ownership of the proccess.', \JLog::ERROR);

			return false;
		}

		// Get the user and group information based on uid and gid.
		$user = posix_getpwuid($uid);
		$group = posix_getgrgid($gid);

		\JLog::add('Changed daemon identity to ' . $user['name'] . ':' . $group['name'], \JLog::INFO);

		return true;
	}

	/**
	 * Method to put the application into the background.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function daemonize()
	{
		// Is there already an active daemon running?
		if ($this->isActive())
		{
			\JLog::add($this->name . ' daemon is still running. Exiting the application.', \JLog::EMERGENCY);

			return false;
		}

		// Reset Process Information
		$this->safeMode = !!@ ini_get('safe_mode');
		$this->processId = 0;
		$this->running = false;

		// Detach process!
		try
		{
			// Check if we should run in the foreground.
			if (!$this->input->get('f'))
			{
				// Detach from the terminal.
				$this->detach();
			}
			else
			{
				// Setup running values.
				$this->exiting = false;
				$this->running = true;

				// Set the process id.
				$this->processId = (int) posix_getpid();
				$this->parentId = $this->processId;
			}
		}
		catch (\RuntimeException $e)
		{
			\JLog::add('Unable to fork.', \JLog::EMERGENCY);

			return false;
		}

		// Verify the process id is valid.
		if ($this->processId < 1)
		{
			\JLog::add('The process id is invalid; the fork failed.', \JLog::EMERGENCY);

			return false;
		}

		// Clear the umask.
		@ umask(0);

		// Write out the process id file for concurrency management.
		if (!$this->writeProcessIdFile())
		{
			\JLog::add('Unable to write the pid file at: ' . $this->config->get('application_pid_file'), \JLog::EMERGENCY);

			return false;
		}

		// Attempt to change the identity of user running the process.
		if (!$this->changeIdentity())
		{
			// If the identity change was required then we need to return false.
			if ($this->config->get('application_require_identity'))
			{
				\JLog::add('Unable to change process owner.', \JLog::CRITICAL);

				return false;
			}
			else
			{
				\JLog::add('Unable to change process owner.', \JLog::WARNING);
			}
		}

		// Setup the signal handlers for the daemon.
		if (!$this->setupSignalHandlers())
		{
			return false;
		}

		// Change the current working directory to the application working directory.
		@ chdir($this->config->get('application_directory'));

		return true;
	}

	/**
	 * This is truly where the magic happens.  This is where we fork the process and kill the parent
	 * process, which is essentially what turns the application into a daemon.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	protected function detach()
	{
		\JLog::add('Detaching the ' . $this->name . ' daemon.', \JLog::DEBUG);

		// Attempt to fork the process.
		$pid = $this->fork();

		// If the pid is positive then we successfully forked, and can close this application.
		if ($pid)
		{
			// Add the log entry for debugging purposes and exit gracefully.
			\JLog::add('Ending ' . $this->name . ' parent process', \JLog::DEBUG);
			$this->close();
		}
		// We are in the forked child process.
		else
		{
			// Setup some protected values.
			$this->exiting = false;
			$this->running = true;

			// Set the parent to self.
			$this->parentId = $this->processId;
		}
	}

	/**
	 * Method to fork the process.
	 *
	 * @return  integer  The child process id to the parent process, zero to the child process.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function fork()
	{
		// Attempt to fork the process.
		$pid = $this->pcntlFork();

		// If the fork failed, throw an exception.
		if ($pid === -1)
		{
			throw new \RuntimeException('The process could not be forked.');
		}
		// Update the process id for the child.
		elseif ($pid === 0)
		{
			$this->processId = (int) posix_getpid();
		}
		// Log the fork in the parent.
		else
		{
			// Log the fork.
			\JLog::add('Process forked ' . $pid, \JLog::DEBUG);
		}

		// Trigger the onFork event.
		$this->postFork();

		return $pid;
	}

	/**
	 * Method to perform basic garbage collection and memory management in the sense of clearing the
	 * stat cache.  We will probably call this method pretty regularly in our main loop.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function gc()
	{
		// Perform generic garbage collection.
		gc_collect_cycles();

		// Clear the stat cache so it doesn't blow up memory.
		clearstatcache();
	}

	/**
	 * Method to attach the DaemonApplication signal handler to the known signals.  Applications
	 * can override these handlers by using the pcntl_signal() function and attaching a different
	 * callback method.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @see     pcntl_signal()
	 */
	protected function setupSignalHandlers()
	{
		// We add the error suppression for the loop because on some platforms some constants are not defined.
		foreach (self::$signals as $signal)
		{
			// Ignore signals that are not defined.
			if (!defined($signal) || !is_int(constant($signal)) || (constant($signal) === 0))
			{
				// Define the signal to avoid notices.
				\JLog::add('Signal "' . $signal . '" not defined. Defining it as null.', \JLog::DEBUG);
				define($signal, null);

				// Don't listen for signal.
				continue;
			}

			// Attach the signal handler for the signal.
			if (!$this->pcntlSignal(constant($signal), array('DaemonApplication', 'signal')))
			{
				\JLog::add(sprintf('Unable to reroute signal handler: %s', $signal), \JLog::EMERGENCY);

				return false;
			}
		}

		return true;
	}

	/**
	 * Method to shut down the daemon and optionally restart it.
	 *
	 * @param   boolean  $restart  True to restart the daemon on exit.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function shutdown($restart = false)
	{
		// If we are already exiting, chill.
		if ($this->exiting)
		{
			return;
		}
		// If not, now we are.
		else
		{
			$this->exiting = true;
		}

		// If we aren't already daemonized then just kill the application.
		if (!$this->running && !$this->isActive())
		{
			\JLog::add('Process was not daemonized yet, just halting current process', \JLog::INFO);
			$this->close();
		}

		// Only read the pid for the parent file.
		if ($this->parentId == $this->processId)
		{
			// Read the contents of the process id file as an integer.
			$fp = fopen($this->config->get('application_pid_file'), 'r');
			$pid = fread($fp, filesize($this->config->get('application_pid_file')));
			$pid = (int) $pid;
			fclose($fp);

			// Remove the process id file.
			@ unlink($this->config->get('application_pid_file'));

			// If we are supposed to restart the daemon we need to execute the same command.
			if ($restart)
			{
				$this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &'));
			}
			// If we are not supposed to restart the daemon let's just kill -9.
			else
			{
				passthru('kill -9 ' . $pid);
				$this->close();
			}
		}
	}

	/**
	 * Method to write the process id file out to disk.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	protected function writeProcessIdFile()
	{
		// Verify the process id is valid.
		if ($this->processId < 1)
		{
			\JLog::add('The process id is invalid.', \JLog::EMERGENCY);

			return false;
		}

		// Get the application process id file path.
		$file = $this->config->get('application_pid_file');

		if (empty($file))
		{
			\JLog::add('The process id file path is empty.', \JLog::ERROR);

			return false;
		}

		// Make sure that the folder where we are writing the process id file exists.
		$folder = dirname($file);

		if (!is_dir($folder) && !\JFolder::create($folder))
		{
			\JLog::add('Unable to create directory: ' . $folder, \JLog::ERROR);

			return false;
		}

		// Write the process id file out to disk.
		if (!file_put_contents($file, $this->processId))
		{
			\JLog::add('Unable to write proccess id file: ' . $file, \JLog::ERROR);

			return false;
		}

		// Make sure the permissions for the proccess id file are accurate.
		if (!chmod($file, 0644))
		{
			\JLog::add('Unable to adjust permissions for the proccess id file: ' . $file, \JLog::ERROR);

			return false;
		}

		return true;
	}

	/**
	 * Method to handle post-fork triggering of the onFork event.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function postFork()
	{
		// Trigger the onFork event.
		$this->triggerEvent('onFork');
	}

	/**
	 * Method to return the exit code of a terminated child process.
	 *
	 * @param   integer  $status  The status parameter is the status parameter supplied to a successful call to pcntl_waitpid().
	 *
	 * @return  integer  The child process exit code.
	 *
	 * @see     pcntl_wexitstatus()
	 * @since   1.7.3
	 */
	protected function pcntlChildExitStatus($status)
	{
		return pcntl_wexitstatus($status);
	}

	/**
	 * Method to return the exit code of a terminated child process.
	 *
	 * @return  integer  On success, the PID of the child process is returned in the parent's thread
	 *                   of execution, and a 0 is returned in the child's thread of execution. On
	 *                   failure, a -1 will be returned in the parent's context, no child process
	 *                   will be created, and a PHP error is raised.
	 *
	 * @see     pcntl_fork()
	 * @since   1.7.3
	 */
	protected function pcntlFork()
	{
		return pcntl_fork();
	}

	/**
	 * Method to install a signal handler.
	 *
	 * @param   integer   $signal   The signal number.
	 * @param   callable  $handler  The signal handler which may be the name of a user created function,
	 *                              or method, or either of the two global constants SIG_IGN or SIG_DFL.
	 * @param   boolean   $restart  Specifies whether system call restarting should be used when this
	 *                              signal arrives.
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     pcntl_signal()
	 * @since   1.7.3
	 */
	protected function pcntlSignal($signal, $handler, $restart = true)
	{
		return pcntl_signal($signal, $handler, $restart);
	}

	/**
	 * Method to wait on or return the status of a forked child.
	 *
	 * @param   integer  &$status  Status information.
	 * @param   integer  $options  If wait3 is available on your system (mostly BSD-style systems),
	 *                             you can provide the optional options parameter.
	 *
	 * @return  integer  The process ID of the child which exited, -1 on error or zero if WNOHANG
	 *                   was provided as an option (on wait3-available systems) and no child was available.
	 *
	 * @see     pcntl_wait()
	 * @since   1.7.3
	 */
	protected function pcntlWait(&$status, $options = 0)
	{
		return pcntl_wait($status, $options);
	}
}
src/Application/SiteApplication.php000064400000052445152177723700013424 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;

/**
 * Joomla! Site Application class
 *
 * @since  3.2
 */
final class SiteApplication extends CMSApplication
{
	/**
	 * Option to filter by language
	 *
	 * @var    boolean
	 * @since  3.2
	 * @deprecated  4.0  Will be renamed $language_filter
	 */
	protected $_language_filter = false;

	/**
	 * Option to detect language by the browser
	 *
	 * @var    boolean
	 * @since  3.2
	 * @deprecated  4.0  Will be renamed $detect_browser
	 */
	protected $_detect_browser = false;

	/**
	 * Class constructor.
	 *
	 * @param   Input                   $input   An optional argument to provide dependency injection for the application's
	 *                                           input object.  If the argument is a \JInput object that object will become
	 *                                           the application's input object, otherwise a default input object is created.
	 * @param   Registry                $config  An optional argument to provide dependency injection for the application's
	 *                                           config object.  If the argument is a Registry object that object will become
	 *                                           the application's config object, otherwise a default config object is created.
	 * @param   \JApplicationWebClient  $client  An optional argument to provide dependency injection for the application's
	 *                                           client object.  If the argument is a \JApplicationWebClient object that object will become
	 *                                           the application's client object, otherwise a default client object is created.
	 *
	 * @since   3.2
	 */
	public function __construct(Input $input = null, Registry $config = null, \JApplicationWebClient $client = null)
	{
		// Register the application name
		$this->_name = 'site';

		// Register the client ID
		$this->_clientId = 0;

		// Execute the parent constructor
		parent::__construct($input, $config, $client);
	}

	/**
	 * Check if the user can access the application
	 *
	 * @param   integer  $itemid  The item ID to check authorisation for
	 *
	 * @return  void
	 *
	 * @since   3.2
	 *
	 * @throws  \Exception When you are not authorised to view the home page menu item
	 */
	protected function authorise($itemid)
	{
		$menus = $this->getMenu();
		$user = \JFactory::getUser();

		if (!$menus->authorise($itemid))
		{
			if ($user->get('id') == 0)
			{
				// Set the data
				$this->setUserState('users.login.form.data', array('return' => \JUri::getInstance()->toString()));

				$url = \JRoute::_('index.php?option=com_users&view=login', false);

				$this->enqueueMessage(\JText::_('JGLOBAL_YOU_MUST_LOGIN_FIRST'), 'error');
				$this->redirect($url);
			}
			else
			{
				// Get the home page menu item
				$home_item = $menus->getDefault($this->getLanguage()->getTag());

				// If we are already in the homepage raise an exception
				if ($menus->getActive()->id == $home_item->id)
				{
					throw new \Exception(\JText::_('JERROR_ALERTNOAUTHOR'), 403);
				}

				// Otherwise redirect to the homepage and show an error
				$this->enqueueMessage(\JText::_('JERROR_ALERTNOAUTHOR'), 'error');
				$this->redirect(\JRoute::_('index.php?Itemid=' . $home_item->id, false));
			}
		}
	}

	/**
	 * Dispatch the application
	 *
	 * @param   string  $component  The component which is being rendered.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function dispatch($component = null)
	{
		// Get the component if not set.
		if (!$component)
		{
			$component = $this->input->getCmd('option', null);
		}

		// Load the document to the API
		$this->loadDocument();

		// Set up the params
		$document = $this->getDocument();
		$router   = static::getRouter();
		$params   = $this->getParams();

		// Register the document object with \JFactory
		\JFactory::$document = $document;

		switch ($document->getType())
		{
			case 'html':
				// Get language
				$lang_code = $this->getLanguage()->getTag();
				$languages = \JLanguageHelper::getLanguages('lang_code');

				// Set metadata
				if (isset($languages[$lang_code]) && $languages[$lang_code]->metakey)
				{
					$document->setMetaData('keywords', $languages[$lang_code]->metakey);
				}
				else
				{
					$document->setMetaData('keywords', $this->get('MetaKeys'));
				}

				$document->setMetaData('rights', $this->get('MetaRights'));

				if ($router->getMode() == JROUTER_MODE_SEF)
				{
					$document->setBase(htmlspecialchars(\JUri::current()));
				}

				// Get the template
				$template = $this->getTemplate(true);

				// Store the template and its params to the config
				$this->set('theme', $template->template);
				$this->set('themeParams', $template->params);

				break;

			case 'feed':
				$document->setBase(htmlspecialchars(\JUri::current()));
				break;
		}

		$document->setTitle($params->get('page_title'));
		$document->setDescription($params->get('page_description'));

		// Add version number or not based on global configuration
		if ($this->get('MetaVersion', 0))
		{
			$document->setGenerator('Joomla! - Open Source Content Management - Version ' . JVERSION);
		}
		else
		{
			$document->setGenerator('Joomla! - Open Source Content Management');
		}

		$contents = ComponentHelper::renderComponent($component);
		$document->setBuffer($contents, 'component');

		// Trigger the onAfterDispatch event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterDispatch');
	}

	/**
	 * Method to run the Web application routines.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function doExecute()
	{
		// Initialise the application
		$this->initialiseApp();

		// Mark afterInitialise in the profiler.
		JDEBUG ? $this->profiler->mark('afterInitialise') : null;

		// Route the application
		$this->route();

		// Mark afterRoute in the profiler.
		JDEBUG ? $this->profiler->mark('afterRoute') : null;

		/*
		 * Check if the user is required to reset their password
		 *
		 * Before $this->route(); "option" and "view" can't be safely read using:
		 * $this->input->getCmd('option'); or $this->input->getCmd('view');
		 * ex: due of the sef urls
		 */
		$this->checkUserRequireReset('com_users', 'profile', 'edit', 'com_users/profile.save,com_users/profile.apply,com_users/user.logout');

		// Dispatch the application
		$this->dispatch();

		// Mark afterDispatch in the profiler.
		JDEBUG ? $this->profiler->mark('afterDispatch') : null;
	}

	/**
	 * Return the current state of the detect browser option.
	 *
	 * @return	boolean
	 *
	 * @since	3.2
	 */
	public function getDetectBrowser()
	{
		return $this->_detect_browser;
	}

	/**
	 * Return the current state of the language filter.
	 *
	 * @return	boolean
	 *
	 * @since	3.2
	 */
	public function getLanguageFilter()
	{
		return $this->_language_filter;
	}

	/**
	 * Return a reference to the \JMenu object.
	 *
	 * @param   string  $name     The name of the application/client.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JMenu  \JMenu object.
	 *
	 * @since   3.2
	 */
	public function getMenu($name = 'site', $options = array())
	{
		return parent::getMenu($name, $options);
	}

	/**
	 * Get the application parameters
	 *
	 * @param   string  $option  The component option
	 *
	 * @return  Registry  The parameters object
	 *
	 * @since   3.2
	 * @deprecated  4.0  Use getParams() instead
	 */
	public function getPageParameters($option = null)
	{
		return $this->getParams($option);
	}

	/**
	 * Get the application parameters
	 *
	 * @param   string  $option  The component option
	 *
	 * @return  Registry  The parameters object
	 *
	 * @since   3.2
	 */
	public function getParams($option = null)
	{
		static $params = array();

		$hash = '__default';

		if (!empty($option))
		{
			$hash = $option;
		}

		if (!isset($params[$hash]))
		{
			// Get component parameters
			if (!$option)
			{
				$option = $this->input->getCmd('option', null);
			}

			// Get new instance of component global parameters
			$params[$hash] = clone ComponentHelper::getParams($option);

			// Get menu parameters
			$menus = $this->getMenu();
			$menu  = $menus->getActive();

			// Get language
			$lang_code = $this->getLanguage()->getTag();
			$languages = \JLanguageHelper::getLanguages('lang_code');

			$title = $this->get('sitename');

			if (isset($languages[$lang_code]) && $languages[$lang_code]->metadesc)
			{
				$description = $languages[$lang_code]->metadesc;
			}
			else
			{
				$description = $this->get('MetaDesc');
			}

			$rights = $this->get('MetaRights');
			$robots = $this->get('robots');

			// Retrieve com_menu global settings
			$temp = clone ComponentHelper::getParams('com_menus');

			// Lets cascade the parameters if we have menu item parameters
			if (is_object($menu))
			{
				// Get show_page_heading from com_menu global settings
				$params[$hash]->def('show_page_heading', $temp->get('show_page_heading'));

				$params[$hash]->merge($menu->params);
				$title = $menu->title;
			}
			else
			{
				// Merge com_menu global settings
				$params[$hash]->merge($temp);

				// If supplied, use page title
				$title = $temp->get('page_title', $title);
			}

			$params[$hash]->def('page_title', $title);
			$params[$hash]->def('page_description', $description);
			$params[$hash]->def('page_rights', $rights);
			$params[$hash]->def('robots', $robots);
		}

		return $params[$hash];
	}

	/**
	 * Return a reference to the \JPathway object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JPathway  A \JPathway object
	 *
	 * @since   3.2
	 */
	public function getPathway($name = 'site', $options = array())
	{
		return parent::getPathway($name, $options);
	}

	/**
	 * Return a reference to the \JRouter object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return	\JRouter
	 *
	 * @since	3.2
	 */
	public static function getRouter($name = 'site', array $options = array())
	{
		return parent::getRouter($name, $options);
	}

	/**
	 * Gets the name of the current template.
	 *
	 * @param   boolean  $params  True to return the template parameters
	 *
	 * @return  string  The name of the template.
	 *
	 * @since   3.2
	 * @throws  \InvalidArgumentException
	 */
	public function getTemplate($params = false)
	{
		if (is_object($this->template))
		{
			if (!file_exists(JPATH_THEMES . '/' . $this->template->template . '/index.php'))
			{
				throw new \InvalidArgumentException(\JText::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $this->template->template));
			}

			if ($params)
			{
				return $this->template;
			}

			return $this->template->template;
		}

		// Get the id of the active menu item
		$menu = $this->getMenu();
		$item = $menu->getActive();

		if (!$item)
		{
			$item = $menu->getItem($this->input->getInt('Itemid', null));
		}

		$id = 0;

		if (is_object($item))
		{
			// Valid item retrieved
			$id = $item->template_style_id;
		}

		$tid = $this->input->getUint('templateStyle', 0);

		if (is_numeric($tid) && (int) $tid > 0)
		{
			$id = (int) $tid;
		}

		$cache = \JFactory::getCache('com_templates', '');

		if ($this->_language_filter)
		{
			$tag = $this->getLanguage()->getTag();
		}
		else
		{
			$tag = '';
		}

		$cacheId = 'templates0' . $tag;

		if ($cache->contains($cacheId))
		{
			$templates = $cache->get($cacheId);
		}
		else
		{
			// Load styles
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('id, home, template, s.params')
				->from('#__template_styles as s')
				->where('s.client_id = 0')
				->where('e.enabled = 1')
				->join('LEFT', '#__extensions as e ON e.element=s.template AND e.type=' . $db->quote('template') . ' AND e.client_id=s.client_id');

			$db->setQuery($query);
			$templates = $db->loadObjectList('id');

			foreach ($templates as &$template)
			{
				// Create home element
				if ($template->home == 1 && !isset($template_home) || $this->_language_filter && $template->home == $tag)
				{
					$template_home = clone $template;
				}

				$template->params = new Registry($template->params);
			}

			// Unset the $template reference to the last $templates[n] item cycled in the foreach above to avoid editing it later
			unset($template);

			// Add home element, after loop to avoid double execution
			if (isset($template_home))
			{
				$template_home->params = new Registry($template_home->params);
				$templates[0] = $template_home;
			}

			$cache->store($templates, $cacheId);
		}

		if (isset($templates[$id]))
		{
			$template = $templates[$id];
		}
		else
		{
			$template = $templates[0];
		}

		// Allows for overriding the active template from the request
		$template_override = $this->input->getCmd('template', '');

		// Only set template override if it is a valid template (= it exists and is enabled)
		if (!empty($template_override))
		{
			if (file_exists(JPATH_THEMES . '/' . $template_override . '/index.php'))
			{
				foreach ($templates as $tmpl)
				{
					if ($tmpl->template === $template_override)
					{
						$template = $tmpl;
						break;
					}
				}
			}
		}

		// Need to filter the default value as well
		$template->template = \JFilterInput::getInstance()->clean($template->template, 'cmd');

		// Fallback template
		if (!file_exists(JPATH_THEMES . '/' . $template->template . '/index.php'))
		{
			$this->enqueueMessage(\JText::_('JERROR_ALERTNOTEMPLATE'), 'error');

			// Try to find data for 'beez3' template
			$original_tmpl = $template->template;

			foreach ($templates as $tmpl)
			{
				if ($tmpl->template === 'beez3')
				{
					$template = $tmpl;
					break;
				}
			}

			// Check, the data were found and if template really exists
			if (!file_exists(JPATH_THEMES . '/' . $template->template . '/index.php'))
			{
				throw new \InvalidArgumentException(\JText::sprintf('JERROR_COULD_NOT_FIND_TEMPLATE', $original_tmpl));
			}
		}

		// Cache the result
		$this->template = $template;

		if ($params)
		{
			return $template;
		}

		return $template->template;
	}

	/**
	 * Initialise the application.
	 *
	 * @param   array  $options  An optional associative array of configuration settings.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function initialiseApp($options = array())
	{
		$user = \JFactory::getUser();

		// If the user is a guest we populate it with the guest user group.
		if ($user->guest)
		{
			$guestUsergroup = ComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
			$user->groups = array($guestUsergroup);
		}

		/*
		 * If a language was specified it has priority, otherwise use user or default language settings
		 * Check this only if the languagefilter plugin is enabled
		 *
		 * @TODO - Remove the hardcoded dependency to the languagefilter plugin
		 */
		if (\JPluginHelper::isEnabled('system', 'languagefilter'))
		{
			$plugin = \JPluginHelper::getPlugin('system', 'languagefilter');

			$pluginParams = new Registry($plugin->params);

			$this->setLanguageFilter(true);
			$this->setDetectBrowser($pluginParams->get('detect_browser', '1') == '1');
		}

		if (empty($options['language']))
		{
			// Detect the specified language
			$lang = $this->input->getString('language', null);

			// Make sure that the user's language exists
			if ($lang && \JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
		}

		if (empty($options['language']) && $this->getLanguageFilter())
		{
			// Detect cookie language
			$lang = $this->input->cookie->get(md5($this->get('secret') . 'language'), null, 'string');

			// Make sure that the user's language exists
			if ($lang && \JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
		}

		if (empty($options['language']))
		{
			// Detect user language
			$lang = $user->getParam('language');

			// Make sure that the user's language exists
			if ($lang && \JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
		}

		if (empty($options['language']) && $this->getDetectBrowser())
		{
			// Detect browser language
			$lang = \JLanguageHelper::detectLanguage();

			// Make sure that the user's language exists
			if ($lang && \JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
		}

		if (empty($options['language']))
		{
			// Detect default language
			$params = ComponentHelper::getParams('com_languages');
			$options['language'] = $params->get('site', $this->get('language', 'en-GB'));
		}

		// One last check to make sure we have something
		if (!\JLanguageHelper::exists($options['language']))
		{
			$lang = $this->config->get('language', 'en-GB');

			if (\JLanguageHelper::exists($lang))
			{
				$options['language'] = $lang;
			}
			else
			{
				// As a last ditch fail to english
				$options['language'] = 'en-GB';
			}
		}

		// Finish initialisation
		parent::initialiseApp($options);
	}

	/**
	 * Load the library language files for the application
	 *
	 * @return  void
	 *
	 * @since   3.6.3
	 */
	protected function loadLibraryLanguage()
	{
		/*
		 * Try the lib_joomla file in the current language (without allowing the loading of the file in the default language)
		 * Fallback to the default language if necessary
		 */
		$this->getLanguage()->load('lib_joomla', JPATH_SITE, null, false, true)
			|| $this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR, null, false, true);
	}

	/**
	 * Login authentication function
	 *
	 * @param   array  $credentials  Array('username' => string, 'password' => string)
	 * @param   array  $options      Array('remember' => boolean)
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 */
	public function login($credentials, $options = array())
	{
		// Set the application login entry point
		if (!array_key_exists('entry_url', $options))
		{
			$options['entry_url'] = \JUri::base() . 'index.php?option=com_users&task=user.login';
		}

		// Set the access control action to check.
		$options['action'] = 'core.login.site';

		return parent::login($credentials, $options);
	}

	/**
	 * Rendering is the process of pushing the document buffers into the template
	 * placeholders, retrieving data from the document and pushing it into
	 * the application response buffer.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function render()
	{
		switch ($this->document->getType())
		{
			case 'feed':
				// No special processing for feeds
				break;

			case 'html':
			default:
				$template = $this->getTemplate(true);
				$file     = $this->input->get('tmpl', 'index');

				if ($file === 'offline' && !$this->get('offline'))
				{
					$this->set('themeFile', 'index.php');
				}

				if ($this->get('offline') && !\JFactory::getUser()->authorise('core.login.offline'))
				{
					$this->setUserState('users.login.form.data', array('return' => \JUri::getInstance()->toString()));
					$this->set('themeFile', 'offline.php');
					$this->setHeader('Status', '503 Service Temporarily Unavailable', 'true');
				}

				if (!is_dir(JPATH_THEMES . '/' . $template->template) && !$this->get('offline'))
				{
					$this->set('themeFile', 'component.php');
				}

				// Ensure themeFile is set by now
				if ($this->get('themeFile') == '')
				{
					$this->set('themeFile', $file . '.php');
				}

				break;
		}

		parent::render();
	}

	/**
	 * Route the application.
	 *
	 * Routing is the process of examining the request environment to determine which
	 * component should receive the request. The component optional parameters
	 * are then set in the request object to be processed when the application is being
	 * dispatched.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function route()
	{
		// Execute the parent method
		parent::route();

		$Itemid = $this->input->getInt('Itemid', null);
		$this->authorise($Itemid);
	}

	/**
	 * Set the current state of the detect browser option.
	 *
	 * @param   boolean  $state  The new state of the detect browser option
	 *
	 * @return	boolean	 The previous state
	 *
	 * @since	3.2
	 */
	public function setDetectBrowser($state = false)
	{
		$old = $this->_detect_browser;
		$this->_detect_browser = $state;

		return $old;
	}

	/**
	 * Set the current state of the language filter.
	 *
	 * @param   boolean  $state  The new state of the language filter
	 *
	 * @return	boolean	 The previous state
	 *
	 * @since	3.2
	 */
	public function setLanguageFilter($state = false)
	{
		$old = $this->_language_filter;
		$this->_language_filter = $state;

		return $old;
	}

	/**
	 * Overrides the default template that would be used
	 *
	 * @param   string  $template     The template name
	 * @param   mixed   $styleParams  The template style parameters
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function setTemplate($template, $styleParams = null)
	{
		if (is_dir(JPATH_THEMES . '/' . $template))
		{
			$this->template = new \stdClass;
			$this->template->template = $template;

			if ($styleParams instanceof Registry)
			{
				$this->template->params = $styleParams;
			}
			else
			{
				$this->template->params = new Registry($styleParams);
			}

			// Store the template and its params to the config
			$this->set('theme', $this->template->template);
			$this->set('themeParams', $this->template->params);
		}
	}
}
src/Application/BaseApplication.php000064400000011203152177723700013355 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\Application\AbstractApplication;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;

/**
 * Joomla Platform Base Application Class
 *
 * @property-read  \JInput  $input  The application input object
 *
 * @since  3.0.0
 */
abstract class BaseApplication extends AbstractApplication
{
	/**
	 * The application dispatcher object.
	 *
	 * @var    \JEventDispatcher
	 * @since  3.0.0
	 */
	protected $dispatcher;

	/**
	 * The application identity object.
	 *
	 * @var    \JUser
	 * @since  3.0.0
	 */
	protected $identity;

	/**
	 * Class constructor.
	 *
	 * @param   Input     $input   An optional argument to provide dependency injection for the application's
	 *                             input object.  If the argument is a \JInput object that object will become
	 *                             the application's input object, otherwise a default input object is created.
	 * @param   Registry  $config  An optional argument to provide dependency injection for the application's
	 *                             config object.  If the argument is a Registry object that object will become
	 *                             the application's config object, otherwise a default config object is created.
	 *
	 * @since   3.0.0
	 */
	public function __construct(Input $input = null, Registry $config = null)
	{
		$this->input = $input instanceof Input ? $input : new Input;
		$this->config = $config instanceof Registry ? $config : new Registry;

		$this->initialise();
	}

	/**
	 * Get the application identity.
	 *
	 * @return  mixed  A \JUser object or null.
	 *
	 * @since   3.0.0
	 */
	public function getIdentity()
	{
		return $this->identity;
	}

	/**
	 * Registers a handler to a particular event group.
	 *
	 * @param   string    $event    The event name.
	 * @param   callable  $handler  The handler, a function or an instance of an event object.
	 *
	 * @return  BaseApplication  The application to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function registerEvent($event, $handler)
	{
		if ($this->dispatcher instanceof \JEventDispatcher)
		{
			$this->dispatcher->register($event, $handler);
		}

		return $this;
	}

	/**
	 * Calls all handlers associated with an event group.
	 *
	 * @param   string  $event  The event name.
	 * @param   array   $args   An array of arguments (optional).
	 *
	 * @return  array   An array of results from each function call, or null if no dispatcher is defined.
	 *
	 * @since   3.0.0
	 */
	public function triggerEvent($event, array $args = null)
	{
		if ($this->dispatcher instanceof \JEventDispatcher)
		{
			return $this->dispatcher->trigger($event, $args);
		}

		return;
	}

	/**
	 * Allows the application to load a custom or default dispatcher.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create event
	 * dispatchers, if required, based on more specific needs.
	 *
	 * @param   \JEventDispatcher  $dispatcher  An optional dispatcher object. If omitted, the factory dispatcher is created.
	 *
	 * @return  BaseApplication This method is chainable.
	 *
	 * @since   3.0.0
	 */
	public function loadDispatcher(\JEventDispatcher $dispatcher = null)
	{
		$this->dispatcher = ($dispatcher === null) ? \JEventDispatcher::getInstance() : $dispatcher;

		return $this;
	}

	/**
	 * Allows the application to load a custom or default identity.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create an identity,
	 * if required, based on more specific needs.
	 *
	 * @param   \JUser  $identity  An optional identity object. If omitted, the factory user is created.
	 *
	 * @return  BaseApplication This method is chainable.
	 *
	 * @since   3.0.0
	 */
	public function loadIdentity(\JUser $identity = null)
	{
		$this->identity = ($identity === null) ? \JFactory::getUser() : $identity;

		return $this;
	}

	/**
	 * Method to run the application routines.  Most likely you will want to instantiate a controller
	 * and execute it, or perform some sort of task directly.
	 *
	 * @return  void
	 *
	 * @since   3.4 (CMS)
	 * @deprecated  4.0  The default concrete implementation of doExecute() will be removed, subclasses will need to provide their own implementation.
	 */
	protected function doExecute()
	{
		return;
	}
}
src/Application/CliApplication.php000064400000016461152177723700013225 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\Application\Cli\CliOutput;
use Joomla\CMS\Input\Cli;
use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;

/**
 * Base class for a Joomla! command line application.
 *
 * @since  2.5.0
 * @note   As of 4.0 this class will be abstract
 */
class CliApplication extends BaseApplication
{
	/**
	 * @var    CliOutput  The output type.
	 * @since  3.3
	 */
	protected $output;

	/**
	 * @var    CliApplication  The application instance.
	 * @since  1.7.0
	 */
	protected static $instance;

	/**
	 * Class constructor.
	 *
	 * @param   Cli                $input       An optional argument to provide dependency injection for the application's
	 *                                          input object.  If the argument is a \JInputCli object that object will become
	 *                                          the application's input object, otherwise a default input object is created.
	 * @param   Registry           $config      An optional argument to provide dependency injection for the application's
	 *                                          config object.  If the argument is a Registry object that object will become
	 *                                          the application's config object, otherwise a default config object is created.
	 * @param   \JEventDispatcher  $dispatcher  An optional argument to provide dependency injection for the application's
	 *                                          event dispatcher.  If the argument is a \JEventDispatcher object that object will become
	 *                                          the application's event dispatcher, if it is null then the default event dispatcher
	 *                                          will be created based on the application's loadDispatcher() method.
	 *
	 * @see     BaseApplication::loadDispatcher()
	 * @since   1.7.0
	 */
	public function __construct(Cli $input = null, Registry $config = null, \JEventDispatcher $dispatcher = null)
	{
		// Close the application if we are not executed from the command line.
		if (!defined('STDOUT') || !defined('STDIN') || !isset($_SERVER['argv']))
		{
			$this->close();
		}

		// If an input object is given use it.
		if ($input instanceof Input)
		{
			$this->input = $input;
		}
		// Create the input based on the application logic.
		else
		{
			if (class_exists('\\Joomla\\CMS\\Input\\Cli'))
			{
				$this->input = new Cli;
			}
		}

		// If a config object is given use it.
		if ($config instanceof Registry)
		{
			$this->config = $config;
		}
		// Instantiate a new configuration object.
		else
		{
			$this->config = new Registry;
		}

		$this->loadDispatcher($dispatcher);

		// Load the configuration object.
		$this->loadConfiguration($this->fetchConfigurationData());

		// Set the execution datetime and timestamp;
		$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));
		$this->set('execution.timestamp', time());

		// Set the current directory.
		$this->set('cwd', getcwd());
	}

	/**
	 * Returns a reference to the global CliApplication object, only creating it if it doesn't already exist.
	 *
	 * This method must be invoked as: $cli = CliApplication::getInstance();
	 *
	 * @param   string  $name  The name (optional) of the JApplicationCli class to instantiate.
	 *
	 * @return  CliApplication
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($name = null)
	{
		// Only create the object if it doesn't exist.
		if (empty(self::$instance))
		{
			if (class_exists($name) && (is_subclass_of($name, '\\Joomla\\CMS\\Application\\CliApplication')))
			{
				self::$instance = new $name;
			}
			else
			{
				self::$instance = new CliApplication;
			}
		}

		return self::$instance;
	}

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function execute()
	{
		// Trigger the onBeforeExecute event.
		$this->triggerEvent('onBeforeExecute');

		// Perform application routines.
		$this->doExecute();

		// Trigger the onAfterExecute event.
		$this->triggerEvent('onAfterExecute');
	}

	/**
	 * Load an object or array into the application configuration object.
	 *
	 * @param   mixed  $data  Either an array or object to be loaded into the configuration object.
	 *
	 * @return  CliApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function loadConfiguration($data)
	{
		// Load the data into the configuration object.
		if (is_array($data))
		{
			$this->config->loadArray($data);
		}
		elseif (is_object($data))
		{
			$this->config->loadObject($data);
		}

		return $this;
	}

	/**
	 * Write a string to standard output.
	 *
	 * @param   string   $text  The text to display.
	 * @param   boolean  $nl    True (default) to append a new line at the end of the output string.
	 *
	 * @return  CliApplication  Instance of $this to allow chaining.
	 *
	 * @codeCoverageIgnore
	 * @since   1.7.0
	 */
	public function out($text = '', $nl = true)
	{
		$output = $this->getOutput();
		$output->out($text, $nl);

		return $this;
	}

	/**
	 * Get an output object.
	 *
	 * @return  CliOutput
	 *
	 * @since   3.3
	 */
	public function getOutput()
	{
		if (!$this->output)
		{
			// In 4.0, this will convert to throwing an exception and you will expected to
			// initialize this in the constructor. Until then set a default.
			$default = new \Joomla\Application\Cli\Output\Xml;
			$this->setOutput($default);
		}

		return $this->output;
	}

	/**
	 * Set an output object.
	 *
	 * @param   CliOutput  $output  CliOutput object
	 *
	 * @return  CliApplication  Instance of $this to allow chaining.
	 *
	 * @since   3.3
	 */
	public function setOutput(CliOutput $output)
	{
		$this->output = $output;

		return $this;
	}

	/**
	 * Get a value from standard input.
	 *
	 * @return  string  The input string from standard input.
	 *
	 * @codeCoverageIgnore
	 * @since   1.7.0
	 */
	public function in()
	{
		return rtrim(fread(STDIN, 8192), "\n");
	}

	/**
	 * Method to load a PHP configuration class file based on convention and return the instantiated data object.  You
	 * will extend this method in child classes to provide configuration data from whatever data source is relevant
	 * for your specific application.
	 *
	 * @param   string  $file   The path and filename of the configuration file. If not provided, configuration.php
	 *                          in JPATH_CONFIGURATION will be used.
	 * @param   string  $class  The class name to instantiate.
	 *
	 * @return  mixed   Either an array or object to be loaded into the configuration object.
	 *
	 * @since   1.7.0
	 */
	protected function fetchConfigurationData($file = '', $class = '\JConfig')
	{
		// Instantiate variables.
		$config = array();

		if (empty($file))
		{
			$file = JPATH_CONFIGURATION . '/configuration.php';

			// Applications can choose not to have any configuration data by not implementing this method and not having a config file.
			if (!file_exists($file))
			{
				$file = '';
			}
		}

		if (!empty($file))
		{
			\JLoader::register($class, $file);

			if (class_exists($class))
			{
				$config = new $class;
			}
			else
			{
				throw new \RuntimeException('Configuration class does not exist.');
			}
		}

		return $config;
	}
}
src/Application/WebApplication.php000064400000112222152177723700013223 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Input\Input;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;

/**
 * Base class for a Joomla! Web application.
 *
 * @since  2.5.0
 * @note   As of 4.0 this class will be abstract
 */
class WebApplication extends BaseApplication
{
	/**
	 * @var    string  Character encoding string.
	 * @since  1.7.3
	 */
	public $charSet = 'utf-8';

	/**
	 * @var    string  Response mime type.
	 * @since  1.7.3
	 */
	public $mimeType = 'text/html';

	/**
	 * @var    \JDate  The body modified date for response headers.
	 * @since  1.7.3
	 */
	public $modifiedDate;

	/**
	 * @var    \JApplicationWebClient  The application client object.
	 * @since  1.7.3
	 */
	public $client;

	/**
	 * @var    \JDocument  The application document object.
	 * @since  1.7.3
	 */
	protected $document;

	/**
	 * @var    \JLanguage  The application language object.
	 * @since  1.7.3
	 */
	protected $language;

	/**
	 * @var    \JSession  The application session object.
	 * @since  1.7.3
	 */
	protected $session;

	/**
	 * @var    object  The application response object.
	 * @since  1.7.3
	 */
	protected $response;

	/**
	 * @var    WebApplication  The application instance.
	 * @since  1.7.3
	 */
	protected static $instance;

	/**
	 * A map of integer HTTP 1.1 response codes to the full HTTP Status for the headers.
	 *
	 * @var    object
	 * @since  3.4
	 * @link   http://tools.ietf.org/pdf/rfc7231.pdf
	 */
	private $responseMap = array(
		100 => 'HTTP/1.1 100 Continue',
		101 => 'HTTP/1.1 101 Switching Protocols',
		102 => 'HTTP/1.1 102 Processing',
		200 => 'HTTP/1.1 200 OK',
		201 => 'HTTP/1.1 201 Created',
		202 => 'HTTP/1.1 202 Accepted',
		203 => 'HTTP/1.1 203 Non-Authoritative Information',
		204 => 'HTTP/1.1 204 No Content',
		205 => 'HTTP/1.1 205 Reset Content',
		206 => 'HTTP/1.1 206 Partial Content',
		207 => 'HTTP/1.1 207 Multi-Status',
		208 => 'HTTP/1.1 208 Already Reported',
		226 => 'HTTP/1.1 226 IM Used',
		300 => 'HTTP/1.1 300 Multiple Choices',
		301 => 'HTTP/1.1 301 Moved Permanently',
		302 => 'HTTP/1.1 302 Found',
		303 => 'HTTP/1.1 303 See other',
		304 => 'HTTP/1.1 304 Not Modified',
		305 => 'HTTP/1.1 305 Use Proxy',
		306 => 'HTTP/1.1 306 (Unused)',
		307 => 'HTTP/1.1 307 Temporary Redirect',
		308 => 'HTTP/1.1 308 Permanent Redirect',
		400 => 'HTTP/1.1 400 Bad Request',
		401 => 'HTTP/1.1 401 Unauthorized',
		402 => 'HTTP/1.1 402 Payment Required',
		403 => 'HTTP/1.1 403 Forbidden',
		404 => 'HTTP/1.1 404 Not Found',
		405 => 'HTTP/1.1 405 Method Not Allowed',
		406 => 'HTTP/1.1 406 Not Acceptable',
		407 => 'HTTP/1.1 407 Proxy Authentication Required',
		408 => 'HTTP/1.1 408 Request Timeout',
		409 => 'HTTP/1.1 409 Conflict',
		410 => 'HTTP/1.1 410 Gone',
		411 => 'HTTP/1.1 411 Length Required',
		412 => 'HTTP/1.1 412 Precondition Failed',
		413 => 'HTTP/1.1 413 Payload Too Large',
		414 => 'HTTP/1.1 414 URI Too Long',
		415 => 'HTTP/1.1 415 Unsupported Media Type',
		416 => 'HTTP/1.1 416 Range Not Satisfiable',
		417 => 'HTTP/1.1 417 Expectation Failed',
		418 => 'HTTP/1.1 418 I\'m a teapot',
		421 => 'HTTP/1.1 421 Misdirected Request',
		422 => 'HTTP/1.1 422 Unprocessable Entity',
		423 => 'HTTP/1.1 423 Locked',
		424 => 'HTTP/1.1 424 Failed Dependency',
		426 => 'HTTP/1.1 426 Upgrade Required',
		428 => 'HTTP/1.1 428 Precondition Required',
		429 => 'HTTP/1.1 429 Too Many Requests',
		431 => 'HTTP/1.1 431 Request Header Fields Too Large',
		451 => 'HTTP/1.1 451 Unavailable For Legal Reasons',
		500 => 'HTTP/1.1 500 Internal Server Error',
		501 => 'HTTP/1.1 501 Not Implemented',
		502 => 'HTTP/1.1 502 Bad Gateway',
		503 => 'HTTP/1.1 503 Service Unavailable',
		504 => 'HTTP/1.1 504 Gateway Timeout',
		505 => 'HTTP/1.1 505 HTTP Version Not Supported',
		506 => 'HTTP/1.1 506 Variant Also Negotiates',
		507 => 'HTTP/1.1 507 Insufficient Storage',
		508 => 'HTTP/1.1 508 Loop Detected',
		510 => 'HTTP/1.1 510 Not Extended',
		511 => 'HTTP/1.1 511 Network Authentication Required',
	);

	/**
	 * A map of HTTP Response headers which may only send a single value, all others
	 * are considered to allow multiple
	 *
	 * @var    object
	 * @since  3.5.2
	 * @link   https://tools.ietf.org/html/rfc7230
	 */
	private $singleValueResponseHeaders = array(
		'status', // This is not a valid header name, but the representation used by Joomla to identify the HTTP Response Code
		'content-length',
		'host',
		'content-type',
		'content-location',
		'date',
		'location',
		'retry-after',
		'server',
		'mime-version',
		'last-modified',
		'etag',
		'accept-ranges',
		'content-range',
		'age',
		'expires',
		'clear-site-data',
		'pragma',
		'strict-transport-security',
		'content-security-policy',
		'content-security-policy-report-only',
		'x-frame-options',
		'x-xss-protection',
		'x-content-type-options',
		'referrer-policy',
		'expect-ct',
		'feature-policy',
	);

	/**
	 * Class constructor.
	 *
	 * @param   Input                   $input   An optional argument to provide dependency injection for the application's
	 *                                           input object.  If the argument is a \JInput object that object will become
	 *                                           the application's input object, otherwise a default input object is created.
	 * @param   Registry                $config  An optional argument to provide dependency injection for the application's
	 *                                           config object.  If the argument is a Registry object that object will become
	 *                                           the application's config object, otherwise a default config object is created.
	 * @param   \JApplicationWebClient  $client  An optional argument to provide dependency injection for the application's
	 *                                           client object.  If the argument is a \JApplicationWebClient object that object will become
	 *                                           the application's client object, otherwise a default client object is created.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Input $input = null, Registry $config = null, \JApplicationWebClient $client = null)
	{
		// If an input object is given use it.
		if ($input instanceof Input)
		{
			$this->input = $input;
		}
		// Create the input based on the application logic.
		else
		{
			$this->input = new Input;
		}

		// If a config object is given use it.
		if ($config instanceof Registry)
		{
			$this->config = $config;
		}
		// Instantiate a new configuration object.
		else
		{
			$this->config = new Registry;
		}

		// If a client object is given use it.
		if ($client instanceof \JApplicationWebClient)
		{
			$this->client = $client;
		}
		// Instantiate a new web client object.
		else
		{
			$this->client = new \JApplicationWebClient;
		}

		// Load the configuration object.
		$this->loadConfiguration($this->fetchConfigurationData());

		// Set the execution datetime and timestamp;
		$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));
		$this->set('execution.timestamp', time());

		// Setup the response object.
		$this->response = new \stdClass;
		$this->response->cachable = false;
		$this->response->headers = array();
		$this->response->body = array();

		// Set the system URIs.
		$this->loadSystemUris();
	}

	/**
	 * Returns a reference to the global WebApplication object, only creating it if it doesn't already exist.
	 *
	 * This method must be invoked as: $web = WebApplication::getInstance();
	 *
	 * @param   string  $name  The name (optional) of the JApplicationWeb class to instantiate.
	 *
	 * @return  WebApplication
	 *
	 * @since   1.7.3
	 */
	public static function getInstance($name = null)
	{
		// Only create the object if it doesn't exist.
		if (empty(self::$instance))
		{
			if (class_exists($name) && (is_subclass_of($name, '\\Joomla\\CMS\\Application\\WebApplication')))
			{
				self::$instance = new $name;
			}
			else
			{
				self::$instance = new WebApplication;
			}
		}

		return self::$instance;
	}

	/**
	 * Initialise the application.
	 *
	 * @param   mixed  $session     An optional argument to provide dependency injection for the application's
	 *                              session object.  If the argument is a \JSession object that object will become
	 *                              the application's session object, if it is false then there will be no session
	 *                              object, and if it is null then the default session object will be created based
	 *                              on the application's loadSession() method.
	 * @param   mixed  $document    An optional argument to provide dependency injection for the application's
	 *                              document object.  If the argument is a \JDocument object that object will become
	 *                              the application's document object, if it is false then there will be no document
	 *                              object, and if it is null then the default document object will be created based
	 *                              on the application's loadDocument() method.
	 * @param   mixed  $language    An optional argument to provide dependency injection for the application's
	 *                              language object.  If the argument is a \JLanguage object that object will become
	 *                              the application's language object, if it is false then there will be no language
	 *                              object, and if it is null then the default language object will be created based
	 *                              on the application's loadLanguage() method.
	 * @param   mixed  $dispatcher  An optional argument to provide dependency injection for the application's
	 *                              event dispatcher.  If the argument is a \JEventDispatcher object that object will become
	 *                              the application's event dispatcher, if it is null then the default event dispatcher
	 *                              will be created based on the application's loadDispatcher() method.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @deprecated  4.0
	 * @see     WebApplication::loadSession()
	 * @see     WebApplication::loadDocument()
	 * @see     WebApplication::loadLanguage()
	 * @see     WebApplication::loadDispatcher()
	 * @since   1.7.3
	 */
	public function initialise($session = null, $document = null, $language = null, $dispatcher = null)
	{
		// Create the session based on the application logic.
		if ($session !== false)
		{
			$this->loadSession($session);
		}

		// Create the document based on the application logic.
		if ($document !== false)
		{
			$this->loadDocument($document);
		}

		// Create the language based on the application logic.
		if ($language !== false)
		{
			$this->loadLanguage($language);
		}

		$this->loadDispatcher($dispatcher);

		return $this;
	}

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function execute()
	{
		// Trigger the onBeforeExecute event.
		$this->triggerEvent('onBeforeExecute');

		// Perform application routines.
		$this->doExecute();

		// Trigger the onAfterExecute event.
		$this->triggerEvent('onAfterExecute');

		// If we have an application document object, render it.
		if ($this->document instanceof \JDocument)
		{
			// Trigger the onBeforeRender event.
			$this->triggerEvent('onBeforeRender');

			// Render the application output.
			$this->render();

			// Trigger the onAfterRender event.
			$this->triggerEvent('onAfterRender');
		}

		// If gzip compression is enabled in configuration and the server is compliant, compress the output.
		if ($this->get('gzip') && !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'))
		{
			$this->compress();
		}

		// Trigger the onBeforeRespond event.
		$this->triggerEvent('onBeforeRespond');

		// Send the application response.
		$this->respond();

		// Trigger the onAfterRespond event.
		$this->triggerEvent('onAfterRespond');
	}

	/**
	 * Rendering is the process of pushing the document buffers into the template
	 * placeholders, retrieving data from the document and pushing it into
	 * the application response buffer.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	protected function render()
	{
		// Setup the document options.
		$options = array(
			'template' => $this->get('theme'),
			'file' => $this->get('themeFile', 'index.php'),
			'params' => $this->get('themeParams'),
		);

		if ($this->get('themes.base'))
		{
			$options['directory'] = $this->get('themes.base');
		}
		// Fall back to constants.
		else
		{
			$options['directory'] = defined('JPATH_THEMES') ? JPATH_THEMES : (defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes';
		}

		// Parse the document.
		$this->document->parse($options);

		// Render the document.
		$data = $this->document->render($this->get('cache_enabled'), $options);

		// Set the application output data.
		$this->setBody($data);
	}

	/**
	 * Checks the accept encoding of the browser and compresses the data before
	 * sending it to the client if possible.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	protected function compress()
	{
		// Supported compression encodings.
		$supported = array(
			'x-gzip' => 'gz',
			'gzip' => 'gz',
			'deflate' => 'deflate',
		);

		// Get the supported encoding.
		$encodings = array_intersect($this->client->encodings, array_keys($supported));

		// If no supported encoding is detected do nothing and return.
		if (empty($encodings))
		{
			return;
		}

		// Verify that headers have not yet been sent, and that our connection is still alive.
		if ($this->checkHeadersSent() || !$this->checkConnectionAlive())
		{
			return;
		}

		// Iterate through the encodings and attempt to compress the data using any found supported encodings.
		foreach ($encodings as $encoding)
		{
			if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate'))
			{
				// Verify that the server supports gzip compression before we attempt to gzip encode the data.
				if (!extension_loaded('zlib') || ini_get('zlib.output_compression'))
				{
					continue;
				}

				// Attempt to gzip encode the data with an optimal level 4.
				$data = $this->getBody();
				$gzdata = gzencode($data, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE);

				// If there was a problem encoding the data just try the next encoding scheme.
				if ($gzdata === false)
				{
					continue;
				}

				// Set the encoding headers.
				$this->setHeader('Content-Encoding', $encoding);

				// Header will be removed at 4.0
				if ($this->get('MetaVersion'))
				{
					$this->setHeader('X-Content-Encoded-By', 'Joomla');
				}

				// Replace the output with the encoded data.
				$this->setBody($gzdata);

				// Compression complete, let's break out of the loop.
				break;
			}
		}
	}

	/**
	 * Method to send the application response to the client.  All headers will be sent prior to the main
	 * application output data.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	protected function respond()
	{
		// Send the content-type header.
		$this->setHeader('Content-Type', $this->mimeType . '; charset=' . $this->charSet);

		// If the response is set to uncachable, we need to set some appropriate headers so browsers don't cache the response.
		if (!$this->response->cachable)
		{
			// Expires in the past.
			$this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);

			// Always modified.
			$this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true);
			$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);

			// HTTP 1.0
			$this->setHeader('Pragma', 'no-cache');
		}
		else
		{
			// Expires.
			$this->setHeader('Expires', gmdate('D, d M Y H:i:s', time() + 900) . ' GMT');

			// Last modified.
			if ($this->modifiedDate instanceof \JDate)
			{
				$this->setHeader('Last-Modified', $this->modifiedDate->format('D, d M Y H:i:s'));
			}
		}

		$this->sendHeaders();

		echo $this->getBody();
	}

	/**
	 * Redirect to another URL.
	 *
	 * If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently"
	 * or "303 See Other" code in the header pointing to the new location. If the headers have already been
	 * sent this will be accomplished using a JavaScript statement.
	 *
	 * @param   string   $url     The URL to redirect to. Can only be http/https URL.
	 * @param   integer  $status  The HTTP 1.1 status code to be provided. 303 is assumed by default.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function redirect($url, $status = 303)
	{
		// Check for relative internal links.
		if (preg_match('#^index\.php#', $url))
		{
			// We changed this from "$this->get('uri.base.full') . $url" due to the inability to run the system tests with the original code
			$url = \JUri::base() . $url;
		}

		// Perform a basic sanity check to make sure we don't have any CRLF garbage.
		$url = preg_split("/[\r\n]/", $url);
		$url = $url[0];

		/*
		 * Here we need to check and see if the URL is relative or absolute.  Essentially, do we need to
		 * prepend the URL with our base URL for a proper redirect.  The rudimentary way we are looking
		 * at this is to simply check whether or not the URL string has a valid scheme or not.
		 */
		if (!preg_match('#^[a-z]+\://#i', $url))
		{
			// Get a \JUri instance for the requested URI.
			$uri = \JUri::getInstance($this->get('uri.request'));

			// Get a base URL to prepend from the requested URI.
			$prefix = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port'));

			// We just need the prefix since we have a path relative to the root.
			if ($url[0] == '/')
			{
				$url = $prefix . $url;
			}
			// It's relative to where we are now, so lets add that.
			else
			{
				$parts = explode('/', $uri->toString(array('path')));
				array_pop($parts);
				$path = implode('/', $parts) . '/';
				$url = $prefix . $path . $url;
			}
		}

		// If the headers have already been sent we need to send the redirect statement via JavaScript.
		if ($this->checkHeadersSent())
		{
			echo "<script>document.location.href=" . json_encode(str_replace("'", '&apos;', $url)) . ";</script>\n";
		}
		else
		{
			// We have to use a JavaScript redirect here because MSIE doesn't play nice with utf-8 URLs.
			if (($this->client->engine == \JApplicationWebClient::TRIDENT) && !StringHelper::is_ascii($url))
			{
				$html = '<html><head>';
				$html .= '<meta http-equiv="content-type" content="text/html; charset=' . $this->charSet . '" />';
				$html .= '<script>document.location.href=' . json_encode(str_replace("'", '&apos;', $url)) . ';</script>';
				$html .= '</head><body></body></html>';

				echo $html;
			}
			else
			{
				// Check if we have a boolean for the status variable for compatibility with old $move parameter
				// @deprecated 4.0
				if (is_bool($status))
				{
					$status = $status ? 301 : 303;
				}

				// Now check if we have an integer status code that maps to a valid redirect. If we don't then set a 303
				// @deprecated 4.0 From 4.0 if no valid status code is given an InvalidArgumentException will be thrown
				if (!is_int($status) || !$this->isRedirectState($status))
				{
					$status = 303;
				}

				// All other cases use the more efficient HTTP header for redirection.
				$this->setHeader('Status', $status, true);
				$this->setHeader('Location', $url, true);
			}
		}

		// Trigger the onBeforeRespond event.
		$this->triggerEvent('onBeforeRespond');

		// Set appropriate headers
		$this->respond();

		// Trigger the onAfterRespond event.
		$this->triggerEvent('onAfterRespond');

		//  Close the application after the redirect.
		$this->close();
	}

	/**
	 * Checks if a state is a redirect state
	 *
	 * @param   integer  $state  The HTTP 1.1 status code.
	 *
	 * @return  boolean
	 *
	 * @since   3.8.0
	 */
	protected function isRedirectState($state)
	{
		$state = (int) $state;

		return ($state > 299 && $state < 400);
	}

	/**
	 * Load an object or array into the application configuration object.
	 *
	 * @param   mixed  $data  Either an array or object to be loaded into the configuration object.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function loadConfiguration($data)
	{
		// Load the data into the configuration object.
		if (is_array($data))
		{
			$this->config->loadArray($data);
		}
		elseif (is_object($data))
		{
			$this->config->loadObject($data);
		}

		return $this;
	}

	/**
	 * Set/get cachable state for the response.  If $allow is set, sets the cachable state of the
	 * response.  Always returns the current state.
	 *
	 * @param   boolean  $allow  True to allow browser caching.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.3
	 */
	public function allowCache($allow = null)
	{
		if ($allow !== null)
		{
			$this->response->cachable = (bool) $allow;
		}

		return $this->response->cachable;
	}

	/**
	 * Method to set a response header.  If the replace flag is set then all headers
	 * with the given name will be replaced by the new one.  The headers are stored
	 * in an internal array to be sent when the site is sent to the browser.
	 *
	 * @param   string   $name     The name of the header to set.
	 * @param   string   $value    The value of the header to set.
	 * @param   boolean  $replace  True to replace any headers with the same name.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function setHeader($name, $value, $replace = false)
	{
		// Sanitize the input values.
		$name = (string) $name;
		$value = (string) $value;

		// Create an array of duplicate header names
		$keys = false;

		if ($this->response->headers)
		{
			$names = array();

			foreach ($this->response->headers as $key => $header)
			{
				$names[$key] = $header['name'];
			}

			// Find existing headers by name
			$keys = array_keys($names, $name);
		}

		// Remove if $replace is true and there are duplicate names
		if ($replace && $keys)
		{
			$this->response->headers = array_diff_key($this->response->headers, array_flip($keys));
		}

		/*
		 * If no keys found, safe to insert (!$keys)
		 * If ($keys && $replace) it's a replacement and previous have been deleted
		 * If ($keys && !in_array...) it's a multiple value header
		 */
		$single = in_array(strtolower($name), $this->singleValueResponseHeaders);

		if ($value && (!$keys || ($keys && ($replace || !$single))))
		{
			// Add the header to the internal array.
			$this->response->headers[] = array('name' => $name, 'value' => $value);
		}

		return $this;
	}

	/**
	 * Method to get the array of response headers to be sent when the response is sent
	 * to the client.
	 *
	 * @return  array	 *
	 *
	 * @since   1.7.3
	 */
	public function getHeaders()
	{
		return $this->response->headers;
	}

	/**
	 * Method to clear any set response headers.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function clearHeaders()
	{
		$this->response->headers = array();

		return $this;
	}

	/**
	 * Send the response headers.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function sendHeaders()
	{
		if (!$this->checkHeadersSent())
		{
			// Creating an array of headers, making arrays of headers with multiple values
			$val = array();

			foreach ($this->response->headers as $header)
			{
				if ('status' == strtolower($header['name']))
				{
					// 'status' headers indicate an HTTP status, and need to be handled slightly differently
					$status = $this->getHttpStatusValue($header['value']);
					$this->header($status, true, (int) $header['value']);
				}
				else
				{
					$val[$header['name']] = !isset($val[$header['name']]) ? $header['value'] : implode(', ', array($val[$header['name']], $header['value']));
					$this->header($header['name'] . ': ' . $val[$header['name']], true);
				}
			}
		}

		return $this;
	}

	/**
	 * Check if a given value can be successfully mapped to a valid http status value
	 *
	 * @param   string  $value  The given status as int or string
	 *
	 * @return  string
	 *
	 * @since   3.8.0
	 */
	protected function getHttpStatusValue($value)
	{
		$code = (int) $value;

		if (array_key_exists($code, $this->responseMap))
		{
			return $this->responseMap[$code];
		}

		return 'HTTP/1.1 ' . $code;
	}

	/**
	 * Set body content.  If body content already defined, this will replace it.
	 *
	 * @param   string  $content  The content to set as the response body.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function setBody($content)
	{
		$this->response->body = array((string) $content);

		return $this;
	}

	/**
	 * Prepend content to the body content
	 *
	 * @param   string  $content  The content to prepend to the response body.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function prependBody($content)
	{
		array_unshift($this->response->body, (string) $content);

		return $this;
	}

	/**
	 * Append content to the body content
	 *
	 * @param   string  $content  The content to append to the response body.
	 *
	 * @return  WebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function appendBody($content)
	{
		$this->response->body[] = (string) $content;

		return $this;
	}

	/**
	 * Return the body content
	 *
	 * @param   boolean  $asArray  True to return the body as an array of strings.
	 *
	 * @return  mixed  The response body either as an array or concatenated string.
	 *
	 * @since   1.7.3
	 */
	public function getBody($asArray = false)
	{
		return $asArray ? $this->response->body : implode((array) $this->response->body);
	}

	/**
	 * Method to get the application document object.
	 *
	 * @return  \JDocument  The document object
	 *
	 * @since   1.7.3
	 */
	public function getDocument()
	{
		return $this->document;
	}

	/**
	 * Method to get the application language object.
	 *
	 * @return  \JLanguage  The language object
	 *
	 * @since   1.7.3
	 */
	public function getLanguage()
	{
		return $this->language;
	}

	/**
	 * Method to get the application session object.
	 *
	 * @return  \JSession  The session object
	 *
	 * @since   1.7.3
	 */
	public function getSession()
	{
		return $this->session;
	}

	/**
	 * Method to check the current client connection status to ensure that it is alive.  We are
	 * wrapping this to isolate the connection_status() function from our code base for testing reasons.
	 *
	 * @return  boolean  True if the connection is valid and normal.
	 *
	 * @see     connection_status()
	 * @since   1.7.3
	 */
	protected function checkConnectionAlive()
	{
		return connection_status() === CONNECTION_NORMAL;
	}

	/**
	 * Method to check to see if headers have already been sent.  We are wrapping this to isolate the
	 * headers_sent() function from our code base for testing reasons.
	 *
	 * @return  boolean  True if the headers have already been sent.
	 *
	 * @see     headers_sent()
	 * @since   1.7.3
	 */
	protected function checkHeadersSent()
	{
		return headers_sent();
	}

	/**
	 * Method to detect the requested URI from server environment variables.
	 *
	 * @return  string  The requested URI
	 *
	 * @since   1.7.3
	 */
	protected function detectRequestUri()
	{
		// First we need to detect the URI scheme.
		if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
		{
			$scheme = 'https://';
		}
		else
		{
			$scheme = 'http://';
		}

		/*
		 * There are some differences in the way that Apache and IIS populate server environment variables.  To
		 * properly detect the requested URI we need to adjust our algorithm based on whether or not we are getting
		 * information from Apache or IIS.
		 */
		// Define variable to return
		$uri = '';

		// If PHP_SELF and REQUEST_URI are both populated then we will assume "Apache Mode".
		if (!empty($_SERVER['PHP_SELF']) && !empty($_SERVER['REQUEST_URI']))
		{
			// The URI is built from the HTTP_HOST and REQUEST_URI environment variables in an Apache environment.
			$uri = $scheme . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
		}
		// If not in "Apache Mode" we will assume that we are in an IIS environment and proceed.
		elseif (isset($_SERVER['HTTP_HOST']))
		{
			// IIS uses the SCRIPT_NAME variable instead of a REQUEST_URI variable... thanks, MS
			$uri = $scheme . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

			// If the QUERY_STRING variable exists append it to the URI string.
			if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']))
			{
				$uri .= '?' . $_SERVER['QUERY_STRING'];
			}
		}

		return trim($uri);
	}

	/**
	 * Method to load a PHP configuration class file based on convention and return the instantiated data object.  You
	 * will extend this method in child classes to provide configuration data from whatever data source is relevant
	 * for your specific application.
	 *
	 * @param   string  $file   The path and filename of the configuration file. If not provided, configuration.php
	 *                          in JPATH_CONFIGURATION will be used.
	 * @param   string  $class  The class name to instantiate.
	 *
	 * @return  mixed   Either an array or object to be loaded into the configuration object.
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	protected function fetchConfigurationData($file = '', $class = '\JConfig')
	{
		// Instantiate variables.
		$config = array();

		if (empty($file))
		{
			$file = JPATH_CONFIGURATION . '/configuration.php';

			// Applications can choose not to have any configuration data
			// by not implementing this method and not having a config file.
			if (!file_exists($file))
			{
				$file = '';
			}
		}

		if (!empty($file))
		{
			\JLoader::register($class, $file);

			if (class_exists($class))
			{
				$config = new $class;
			}
			else
			{
				throw new \RuntimeException('Configuration class does not exist.');
			}
		}

		return $config;
	}

	/**
	 * Flush the media version to refresh versionable assets
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function flushAssets()
	{
		$version = new \JVersion;
		$version->refreshMediaVersion();
	}

	/**
	 * Method to send a header to the client.  We are wrapping this to isolate the header() function
	 * from our code base for testing reasons.
	 *
	 * @param   string   $string   The header string.
	 * @param   boolean  $replace  The optional replace parameter indicates whether the header should
	 *                             replace a previous similar header, or add a second header of the same type.
	 * @param   integer  $code     Forces the HTTP response code to the specified value. Note that
	 *                             this parameter only has an effect if the string is not empty.
	 *
	 * @return  void
	 *
	 * @see     header()
	 * @since   1.7.3
	 */
	protected function header($string, $replace = true, $code = null)
	{
		$string = str_replace(chr(0), '', $string);
		header($string, $replace, $code);
	}

	/**
	 * Determine if we are using a secure (SSL) connection.
	 *
	 * @return  boolean  True if using SSL, false if not.
	 *
	 * @since   3.0.1
	 */
	public function isSSLConnection()
	{
		return (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on')) || getenv('SSL_PROTOCOL_VERSION');
	}

	/**
	 * Allows the application to load a custom or default document.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create a document,
	 * if required, based on more specific needs.
	 *
	 * @param   \JDocument  $document  An optional document object. If omitted, the factory document is created.
	 *
	 * @return  WebApplication This method is chainable.
	 *
	 * @since   1.7.3
	 */
	public function loadDocument(\JDocument $document = null)
	{
		$this->document = ($document === null) ? \JFactory::getDocument() : $document;

		return $this;
	}

	/**
	 * Allows the application to load a custom or default language.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create a language,
	 * if required, based on more specific needs.
	 *
	 * @param   \JLanguage  $language  An optional language object. If omitted, the factory language is created.
	 *
	 * @return  WebApplication This method is chainable.
	 *
	 * @since   1.7.3
	 */
	public function loadLanguage(\JLanguage $language = null)
	{
		$this->language = ($language === null) ? \JFactory::getLanguage() : $language;

		return $this;
	}

	/**
	 * Allows the application to load a custom or default session.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create a session,
	 * if required, based on more specific needs.
	 *
	 * @param   \JSession  $session  An optional session object. If omitted, the session is created.
	 *
	 * @return  WebApplication This method is chainable.
	 *
	 * @since   1.7.3
	 */
	public function loadSession(\JSession $session = null)
	{
		if ($session !== null)
		{
			$this->session = $session;

			return $this;
		}

		// Generate a session name.
		$name = md5($this->get('secret') . $this->get('session_name', get_class($this)));

		// Calculate the session lifetime.
		$lifetime = (($this->get('sess_lifetime')) ? $this->get('sess_lifetime') * 60 : 900);

		// Get the session handler from the configuration.
		$handler = $this->get('sess_handler', 'none');

		// Initialize the options for \JSession.
		$options = array(
			'name' => $name,
			'expire' => $lifetime,
			'force_ssl' => $this->get('force_ssl'),
		);

		$this->registerEvent('onAfterSessionStart', array($this, 'afterSessionStart'));

		// Instantiate the session object.
		$session = \JSession::getInstance($handler, $options);
		$session->initialise($this->input, $this->dispatcher);

		if ($session->getState() == 'expired')
		{
			$session->restart();
		}
		else
		{
			$session->start();
		}

		// Set the session object.
		$this->session = $session;

		return $this;
	}

	/**
	 * After the session has been started we need to populate it with some default values.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function afterSessionStart()
	{
		$session = \JFactory::getSession();

		if ($session->isNew())
		{
			$session->set('registry', new Registry);
			$session->set('user', new \JUser);
		}
	}

	/**
	 * Method to load the system URI strings for the application.
	 *
	 * @param   string  $requestUri  An optional request URI to use instead of detecting one from the
	 *                               server environment variables.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	protected function loadSystemUris($requestUri = null)
	{
		// Set the request URI.
		if (!empty($requestUri))
		{
			$this->set('uri.request', $requestUri);
		}
		else
		{
			$this->set('uri.request', $this->detectRequestUri());
		}

		// Check to see if an explicit base URI has been set.
		$siteUri = trim($this->get('site_uri'));

		if ($siteUri != '')
		{
			$uri = \JUri::getInstance($siteUri);
			$path = $uri->toString(array('path'));
		}
		// No explicit base URI was set so we need to detect it.
		else
		{
			// Start with the requested URI.
			$uri = \JUri::getInstance($this->get('uri.request'));

			// If we are working from a CGI SAPI with the 'cgi.fix_pathinfo' directive disabled we use PHP_SELF.
			if (strpos(php_sapi_name(), 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($_SERVER['REQUEST_URI']))
			{
				// We aren't expecting PATH_INFO within PHP_SELF so this should work.
				$path = dirname($_SERVER['PHP_SELF']);
			}
			// Pretty much everything else should be handled with SCRIPT_NAME.
			else
			{
				$path = dirname($_SERVER['SCRIPT_NAME']);
			}
		}

		$host = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port'));

		// Check if the path includes "index.php".
		if (strpos($path, 'index.php') !== false)
		{
			// Remove the index.php portion of the path.
			$path = substr_replace($path, '', strpos($path, 'index.php'), 9);
		}

		$path = rtrim($path, '/\\');

		// Set the base URI both as just a path and as the full URI.
		$this->set('uri.base.full', $host . $path . '/');
		$this->set('uri.base.host', $host);
		$this->set('uri.base.path', $path . '/');

		// Set the extended (non-base) part of the request URI as the route.
		if (stripos($this->get('uri.request'), $this->get('uri.base.full')) === 0)
		{
			$this->set('uri.route', substr_replace($this->get('uri.request'), '', 0, strlen($this->get('uri.base.full'))));
		}

		// Get an explicitly set media URI is present.
		$mediaURI = trim($this->get('media_uri'));

		if ($mediaURI)
		{
			if (strpos($mediaURI, '://') !== false)
			{
				$this->set('uri.media.full', $mediaURI);
				$this->set('uri.media.path', $mediaURI);
			}
			else
			{
				// Normalise slashes.
				$mediaURI = trim($mediaURI, '/\\');
				$mediaURI = !empty($mediaURI) ? '/' . $mediaURI . '/' : '/';
				$this->set('uri.media.full', $this->get('uri.base.host') . $mediaURI);
				$this->set('uri.media.path', $mediaURI);
			}
		}
		// No explicit media URI was set, build it dynamically from the base uri.
		else
		{
			$this->set('uri.media.full', $this->get('uri.base.full') . 'media/');
			$this->set('uri.media.path', $this->get('uri.base.path') . 'media/');
		}
	}
}
src/Application/CMSApplication.php000064400000075052152177723700013141 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Application;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Input\Input;
use Joomla\CMS\Session\MetadataManager;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;

/**
 * Joomla! CMS Application class
 *
 * @since  3.2
 */
class CMSApplication extends WebApplication
{
	/**
	 * Array of options for the \JDocument object
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $docOptions = array();

	/**
	 * Application instances container.
	 *
	 * @var    CMSApplication[]
	 * @since  3.2
	 */
	protected static $instances = array();

	/**
	 * The scope of the application.
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $scope = null;

	/**
	 * The client identifier.
	 *
	 * @var    integer
	 * @since  3.2
	 * @deprecated  4.0  Will be renamed $clientId
	 */
	protected $_clientId = null;

	/**
	 * The application message queue.
	 *
	 * @var    array
	 * @since  3.2
	 * @deprecated  4.0  Will be renamed $messageQueue
	 */
	protected $_messageQueue = array();

	/**
	 * The name of the application.
	 *
	 * @var    array
	 * @since  3.2
	 * @deprecated  4.0  Will be renamed $name
	 */
	protected $_name = null;

	/**
	 * The profiler instance
	 *
	 * @var    \JProfiler
	 * @since  3.2
	 */
	protected $profiler = null;

	/**
	 * Currently active template
	 *
	 * @var    object
	 * @since  3.2
	 */
	protected $template = null;

	/**
	 * Class constructor.
	 *
	 * @param   Input                   $input   An optional argument to provide dependency injection for the application's
	 *                                           input object.  If the argument is a \JInput object that object will become
	 *                                           the application's input object, otherwise a default input object is created.
	 * @param   Registry                $config  An optional argument to provide dependency injection for the application's
	 *                                           config object.  If the argument is a Registry object that object will become
	 *                                           the application's config object, otherwise a default config object is created.
	 * @param   \JApplicationWebClient  $client  An optional argument to provide dependency injection for the application's
	 *                                           client object.  If the argument is a \JApplicationWebClient object that object will become
	 *                                           the application's client object, otherwise a default client object is created.
	 *
	 * @since   3.2
	 */
	public function __construct(Input $input = null, Registry $config = null, \JApplicationWebClient $client = null)
	{
		parent::__construct($input, $config, $client);

		// Load and set the dispatcher
		$this->loadDispatcher();

		// If JDEBUG is defined, load the profiler instance
		if (defined('JDEBUG') && JDEBUG)
		{
			$this->profiler = \JProfiler::getInstance('Application');
		}

		// Enable sessions by default.
		if ($this->config->get('session') === null)
		{
			$this->config->set('session', true);
		}

		// Set the session default name.
		if ($this->config->get('session_name') === null)
		{
			$this->config->set('session_name', $this->getName());
		}

		// Create the session if a session name is passed.
		if ($this->config->get('session') !== false)
		{
			$this->loadSession();
		}
	}

	/**
	 * Checks the user session.
	 *
	 * If the session record doesn't exist, initialise it.
	 * If session is new, create session variables
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @throws  \RuntimeException
	 */
	public function checkSession()
	{
		$metadataManager = new MetadataManager($this, \JFactory::getDbo());
		$metadataManager->createRecordIfNonExisting(\JFactory::getSession(), \JFactory::getUser());
	}

	/**
	 * Enqueue a system message.
	 *
	 * @param   string  $msg   The message to enqueue.
	 * @param   string  $type  The message type. Default is message.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function enqueueMessage($msg, $type = 'message')
	{
		// Don't add empty messages.
		if (trim($msg) === '')
		{
			return;
		}

		// For empty queue, if messages exists in the session, enqueue them first.
		$messages = $this->getMessageQueue();

		$message = array('message' => $msg, 'type' => strtolower($type));

		if (!in_array($message, $this->_messageQueue))
		{
			// Enqueue the message.
			$this->_messageQueue[] = $message;
		}
	}

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function execute()
	{
		// Perform application routines.
		$this->doExecute();

		// If we have an application document object, render it.
		if ($this->document instanceof \JDocument)
		{
			// Render the application output.
			$this->render();
		}

		// If gzip compression is enabled in configuration and the server is compliant, compress the output.
		if ($this->get('gzip') && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler')
		{
			$this->compress();

			// Trigger the onAfterCompress event.
			$this->triggerEvent('onAfterCompress');
		}

		// Send the application response.
		$this->respond();

		// Trigger the onAfterRespond event.
		$this->triggerEvent('onAfterRespond');
	}

	/**
	 * Check if the user is required to reset their password.
	 *
	 * If the user is required to reset their password will be redirected to the page that manage the password reset.
	 *
	 * @param   string  $option  The option that manage the password reset
	 * @param   string  $view    The view that manage the password reset
	 * @param   string  $layout  The layout of the view that manage the password reset
	 * @param   string  $tasks   Permitted tasks
	 *
	 * @return  void
	 */
	protected function checkUserRequireReset($option, $view, $layout, $tasks)
	{
		if (\JFactory::getUser()->get('requireReset', 0))
		{
			$redirect = false;

			/*
			 * By default user profile edit page is used.
			 * That page allows you to change more than just the password and might not be the desired behavior.
			 * This allows a developer to override the page that manage the password reset.
			 * (can be configured using the file: configuration.php, or if extended, through the global configuration form)
			 */
			$name = $this->getName();

			if ($this->get($name . '_reset_password_override', 0))
			{
				$option = $this->get($name . '_reset_password_option', '');
				$view = $this->get($name . '_reset_password_view', '');
				$layout = $this->get($name . '_reset_password_layout', '');
				$tasks = $this->get($name . '_reset_password_tasks', '');
			}

			$task = $this->input->getCmd('task', '');

			// Check task or option/view/layout
			if (!empty($task))
			{
				$tasks = explode(',', $tasks);

				// Check full task version "option/task"
				if (array_search($this->input->getCmd('option', '') . '/' . $task, $tasks) === false)
				{
					// Check short task version, must be on the same option of the view
					if ($this->input->getCmd('option', '') !== $option || array_search($task, $tasks) === false)
					{
						// Not permitted task
						$redirect = true;
					}
				}
			}
			else
			{
				if ($this->input->getCmd('option', '') !== $option || $this->input->getCmd('view', '') !== $view
					|| $this->input->getCmd('layout', '') !== $layout)
				{
					// Requested a different option/view/layout
					$redirect = true;
				}
			}

			if ($redirect)
			{
				// Redirect to the profile edit page
				$this->enqueueMessage(\JText::_('JGLOBAL_PASSWORD_RESET_REQUIRED'), 'notice');
				$this->redirect(\JRoute::_('index.php?option=' . $option . '&view=' . $view . '&layout=' . $layout, false));
			}
		}
	}

	/**
	 * Gets a configuration value.
	 *
	 * @param   string  $varname  The name of the value to get.
	 * @param   string  $default  Default value to return
	 *
	 * @return  mixed  The user state.
	 *
	 * @since   3.2
	 * @deprecated  4.0  Use get() instead
	 */
	public function getCfg($varname, $default = null)
	{
		return $this->get($varname, $default);
	}

	/**
	 * Gets the client id of the current running application.
	 *
	 * @return  integer  A client identifier.
	 *
	 * @since   3.2
	 */
	public function getClientId()
	{
		return $this->_clientId;
	}

	/**
	 * Returns a reference to the global CMSApplication object, only creating it if it doesn't already exist.
	 *
	 * This method must be invoked as: $web = CMSApplication::getInstance();
	 *
	 * @param   string  $name  The name (optional) of the CMSApplication class to instantiate.
	 *
	 * @return  CMSApplication
	 *
	 * @since   3.2
	 * @throws  \RuntimeException
	 */
	public static function getInstance($name = null)
	{
		if (empty(static::$instances[$name]))
		{
			// Create a CMSApplication object.
			$classname = '\JApplication' . ucfirst($name);

			if (!class_exists($classname))
			{
				throw new \RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_APPLICATION_LOAD', $name), 500);
			}

			static::$instances[$name] = new $classname;
		}

		return static::$instances[$name];
	}

	/**
	 * Returns the application \JMenu object.
	 *
	 * @param   string  $name     The name of the application/client.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JMenu|null
	 *
	 * @since   3.2
	 */
	public function getMenu($name = null, $options = array())
	{
		if (!isset($name))
		{
			$name = $this->getName();
		}

		// Inject this application object into the \JMenu tree if one isn't already specified
		if (!isset($options['app']))
		{
			$options['app'] = $this;
		}

		try
		{
			$menu = \JMenu::getInstance($name, $options);
		}
		catch (\Exception $e)
		{
			return;
		}

		return $menu;
	}

	/**
	 * Get the system message queue.
	 *
	 * @param   boolean  $clear  Clear the messages currently attached to the application object
	 *
	 * @return  array  The system message queue.
	 *
	 * @since   3.2
	 */
	public function getMessageQueue($clear = false)
	{
		// For empty queue, if messages exists in the session, enqueue them.
		if (!$this->_messageQueue)
		{
			$session = \JFactory::getSession();
			$sessionQueue = $session->get('application.queue', array());

			if ($sessionQueue)
			{
				$this->_messageQueue = $sessionQueue;
				$session->set('application.queue', array());
			}
		}

		$messageQueue = $this->_messageQueue;

		if ($clear)
		{
			$this->_messageQueue = array();
		}

		return $messageQueue;
	}

	/**
	 * Gets the name of the current running application.
	 *
	 * @return  string  The name of the application.
	 *
	 * @since   3.2
	 */
	public function getName()
	{
		return $this->_name;
	}

	/**
	 * Returns the application \JPathway object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JPathway|null
	 *
	 * @since   3.2
	 */
	public function getPathway($name = null, $options = array())
	{
		if (!isset($name))
		{
			$name = $this->getName();
		}
		else
		{
			// Name should not be used
			$this->getLogger()->warning(
				'Name attribute is deprecated, in the future fetch the pathway '
				. 'through the respective application.',
				array('category' => 'deprecated')
			);
		}

		try
		{
			$pathway = \JPathway::getInstance($name, $options);
		}
		catch (\Exception $e)
		{
			return;
		}

		return $pathway;
	}

	/**
	 * Returns the application \JRouter object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  \JRouter|null
	 *
	 * @since   3.2
	 */
	public static function getRouter($name = null, array $options = array())
	{
		if (!isset($name))
		{
			$app = \JFactory::getApplication();
			$name = $app->getName();
		}

		$options['mode'] = \JFactory::getConfig()->get('sef');

		try
		{
			$router = \JRouter::getInstance($name, $options);
		}
		catch (\Exception $e)
		{
			return;
		}

		return $router;
	}

	/**
	 * Gets the name of the current template.
	 *
	 * @param   boolean  $params  An optional associative array of configuration settings
	 *
	 * @return  mixed  System is the fallback.
	 *
	 * @since   3.2
	 */
	public function getTemplate($params = false)
	{
		$template = new \stdClass;

		$template->template = 'system';
		$template->params   = new Registry;

		if ($params)
		{
			return $template;
		}

		return $template->template;
	}

	/**
	 * Gets a user state.
	 *
	 * @param   string  $key      The path of the state.
	 * @param   mixed   $default  Optional default value, returned if the internal value is null.
	 *
	 * @return  mixed  The user state or null.
	 *
	 * @since   3.2
	 */
	public function getUserState($key, $default = null)
	{
		$session = \JFactory::getSession();
		$registry = $session->get('registry');

		if ($registry !== null)
		{
			return $registry->get($key, $default);
		}

		return $default;
	}

	/**
	 * Gets the value of a user state variable.
	 *
	 * @param   string  $key      The key of the user state variable.
	 * @param   string  $request  The name of the variable passed in a request.
	 * @param   string  $default  The default value for the variable if not found. Optional.
	 * @param   string  $type     Filter for the variable, for valid values see {@link \JFilterInput::clean()}. Optional.
	 *
	 * @return  mixed  The request user state.
	 *
	 * @since   3.2
	 */
	public function getUserStateFromRequest($key, $request, $default = null, $type = 'none')
	{
		$cur_state = $this->getUserState($key, $default);
		$new_state = $this->input->get($request, null, $type);

		if ($new_state === null)
		{
			return $cur_state;
		}

		// Save the new value only if it was set in this request.
		$this->setUserState($key, $new_state);

		return $new_state;
	}

	/**
	 * Initialise the application.
	 *
	 * @param   array  $options  An optional associative array of configuration settings.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function initialiseApp($options = array())
	{
		// Set the configuration in the API.
		$this->config = \JFactory::getConfig();

		// Check that we were given a language in the array (since by default may be blank).
		if (isset($options['language']))
		{
			$this->set('language', $options['language']);
		}

		// Build our language object
		$lang = \JLanguage::getInstance($this->get('language'), $this->get('debug_lang'));

		// Load the language to the API
		$this->loadLanguage($lang);

		// Register the language object with \JFactory
		\JFactory::$language = $this->getLanguage();

		// Load the library language files
		$this->loadLibraryLanguage();

		// Set user specific editor.
		$user = \JFactory::getUser();
		$editor = $user->getParam('editor', $this->get('editor'));

		if (!\JPluginHelper::isEnabled('editors', $editor))
		{
			$editor = $this->get('editor');

			if (!\JPluginHelper::isEnabled('editors', $editor))
			{
				$editor = 'none';
			}
		}

		$this->set('editor', $editor);

		// Trigger the onAfterInitialise event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterInitialise');
	}

	/**
	 * Is admin interface?
	 *
	 * @return  boolean  True if this application is administrator.
	 *
	 * @since       3.2
	 * @deprecated  4.0 Use isClient('administrator') instead.
	 */
	public function isAdmin()
	{
		return $this->isClient('administrator');
	}

	/**
	 * Is site interface?
	 *
	 * @return  boolean  True if this application is site.
	 *
	 * @since       3.2
	 * @deprecated  4.0 Use isClient('site') instead.
	 */
	public function isSite()
	{
		return $this->isClient('site');
	}

	/**
	 * Checks if HTTPS is forced in the client configuration.
	 *
	 * @param   integer  $clientId  An optional client id (defaults to current application client).
	 *
	 * @return  boolean  True if is forced for the client, false otherwise.
	 *
	 * @since   3.7.3
	 */
	public function isHttpsForced($clientId = null)
	{
		$clientId = (int) ($clientId !== null ? $clientId : $this->getClientId());
		$forceSsl = (int) $this->get('force_ssl');

		if ($clientId === 0 && $forceSsl === 2)
		{
			return true;
		}

		if ($clientId === 1 && $forceSsl >= 1)
		{
			return true;
		}

		return false;
	}

	/**
	 * Check the client interface by name.
	 *
	 * @param   string  $identifier  String identifier for the application interface
	 *
	 * @return  boolean  True if this application is of the given type client interface.
	 *
	 * @since   3.7.0
	 */
	public function isClient($identifier)
	{
		return $this->getName() === $identifier;
	}

	/**
	 * Load the library language files for the application
	 *
	 * @return  void
	 *
	 * @since   3.6.3
	 */
	protected function loadLibraryLanguage()
	{
		$this->getLanguage()->load('lib_joomla', JPATH_ADMINISTRATOR);
	}

	/**
	 * Allows the application to load a custom or default session.
	 *
	 * The logic and options for creating this object are adequately generic for default cases
	 * but for many applications it will make sense to override this method and create a session,
	 * if required, based on more specific needs.
	 *
	 * @param   \JSession  $session  An optional session object. If omitted, the session is created.
	 *
	 * @return  CMSApplication  This method is chainable.
	 *
	 * @since   3.2
	 */
	public function loadSession(\JSession $session = null)
	{
		if ($session !== null)
		{
			$this->session = $session;

			return $this;
		}

		$this->registerEvent('onAfterSessionStart', array($this, 'afterSessionStart'));

		/*
		 * Note: The below code CANNOT change from instantiating a session via \JFactory until there is a proper dependency injection container supported
		 * by the application. The current default behaviours result in this method being called each time an application class is instantiated.
		 * https://github.com/joomla/joomla-cms/issues/12108 explains why things will crash and burn if you ever attempt to make this change
		 * without a proper dependency injection container.
		 */

		$session = \JFactory::getSession(
			array(
				'name'      => \JApplicationHelper::getHash($this->get('session_name', get_class($this))),
				'expire'    => $this->get('lifetime') ? $this->get('lifetime') * 60 : 900,
				'force_ssl' => $this->isHttpsForced(),
			)
		);

		$session->initialise($this->input, $this->dispatcher);

		// Get the session handler from the configuration.
		$handler = $this->get('session_handler', 'none');

		/*
		 * Check for extra session metadata when:
		 *
		 * 1) The database handler is in use and the session is new
		 * 2) The database handler is not in use and the time is an even numbered second or the session is new
		 */
		if (($handler !== 'database' && (time() % 2 || $session->isNew())) || ($handler === 'database' && $session->isNew()))
		{
			$this->checkSession();
		}

		// Set the session object.
		$this->session = $session;

		return $this;
	}

	/**
	 * Login authentication function.
	 *
	 * Username and encoded password are passed the onUserLogin event which
	 * is responsible for the user validation. A successful validation updates
	 * the current session record with the user's details.
	 *
	 * Username and encoded password are sent as credentials (along with other
	 * possibilities) to each observer (authentication plugin) for user
	 * validation.  Successful validation will update the current session with
	 * the user details.
	 *
	 * @param   array  $credentials  Array('username' => string, 'password' => string)
	 * @param   array  $options      Array('remember' => boolean)
	 *
	 * @return  boolean|\JException  True on success, false if failed or silent handling is configured, or a \JException object on authentication error.
	 *
	 * @since   3.2
	 */
	public function login($credentials, $options = array())
	{
		// Get the global \JAuthentication object.
		$authenticate = \JAuthentication::getInstance();
		$response = $authenticate->authenticate($credentials, $options);

		// Import the user plugin group.
		\JPluginHelper::importPlugin('user');

		if ($response->status === \JAuthentication::STATUS_SUCCESS)
		{
			/*
			 * Validate that the user should be able to login (different to being authenticated).
			 * This permits authentication plugins blocking the user.
			 */
			$authorisations = $authenticate->authorise($response, $options);
			$denied_states = \JAuthentication::STATUS_EXPIRED | \JAuthentication::STATUS_DENIED;

			foreach ($authorisations as $authorisation)
			{
				if ((int) $authorisation->status & $denied_states)
				{
					// Trigger onUserAuthorisationFailure Event.
					$this->triggerEvent('onUserAuthorisationFailure', array((array) $authorisation));

					// If silent is set, just return false.
					if (isset($options['silent']) && $options['silent'])
					{
						return false;
					}

					// Return the error.
					switch ($authorisation->status)
					{
						case \JAuthentication::STATUS_EXPIRED:
							return \JError::raiseWarning('102002', \JText::_('JLIB_LOGIN_EXPIRED'));

						case \JAuthentication::STATUS_DENIED:
							return \JError::raiseWarning('102003', \JText::_('JLIB_LOGIN_DENIED'));

						default:
							return \JError::raiseWarning('102004', \JText::_('JLIB_LOGIN_AUTHORISATION'));
					}
				}
			}

			// OK, the credentials are authenticated and user is authorised.  Let's fire the onLogin event.
			$results = $this->triggerEvent('onUserLogin', array((array) $response, $options));

			/*
			 * If any of the user plugins did not successfully complete the login routine
			 * then the whole method fails.
			 *
			 * Any errors raised should be done in the plugin as this provides the ability
			 * to provide much more information about why the routine may have failed.
			 */
			$user = \JFactory::getUser();

			if ($response->type === 'Cookie')
			{
				$user->set('cookieLogin', true);
			}

			if (in_array(false, $results, true) == false)
			{
				$options['user'] = $user;
				$options['responseType'] = $response->type;

				// The user is successfully logged in. Run the after login events
				$this->triggerEvent('onUserAfterLogin', array($options));

				return true;
			}
		}

		// Trigger onUserLoginFailure Event.
		$this->triggerEvent('onUserLoginFailure', array((array) $response));

		// If silent is set, just return false.
		if (isset($options['silent']) && $options['silent'])
		{
			return false;
		}

		// If status is success, any error will have been raised by the user plugin
		if ($response->status !== \JAuthentication::STATUS_SUCCESS)
		{
			$this->getLogger()->warning($response->error_message, array('category' => 'jerror'));
		}

		return false;
	}

	/**
	 * Logout authentication function.
	 *
	 * Passed the current user information to the onUserLogout event and reverts the current
	 * session record back to 'anonymous' parameters.
	 * If any of the authentication plugins did not successfully complete
	 * the logout routine then the whole method fails. Any errors raised
	 * should be done in the plugin as this provides the ability to give
	 * much more information about why the routine may have failed.
	 *
	 * @param   integer  $userid   The user to load - Can be an integer or string - If string, it is converted to ID automatically
	 * @param   array    $options  Array('clientid' => array of client id's)
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.2
	 */
	public function logout($userid = null, $options = array())
	{
		// Get a user object from the \JApplication.
		$user = \JFactory::getUser($userid);

		// Build the credentials array.
		$parameters['username'] = $user->get('username');
		$parameters['id'] = $user->get('id');

		// Set clientid in the options array if it hasn't been set already and shared sessions are not enabled.
		if (!$this->get('shared_session', '0') && !isset($options['clientid']))
		{
			$options['clientid'] = $this->getClientId();
		}

		// Import the user plugin group.
		\JPluginHelper::importPlugin('user');

		// OK, the credentials are built. Lets fire the onLogout event.
		$results = $this->triggerEvent('onUserLogout', array($parameters, $options));

		// Check if any of the plugins failed. If none did, success.
		if (!in_array(false, $results, true))
		{
			$options['username'] = $user->get('username');
			$this->triggerEvent('onUserAfterLogout', array($options));

			return true;
		}

		// Trigger onUserLoginFailure Event.
		$this->triggerEvent('onUserLogoutFailure', array($parameters));

		return false;
	}

	/**
	 * Redirect to another URL.
	 *
	 * If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently"
	 * or "303 See Other" code in the header pointing to the new location. If the headers have already been
	 * sent this will be accomplished using a JavaScript statement.
	 *
	 * @param   string   $url     The URL to redirect to. Can only be http/https URL
	 * @param   integer  $status  The HTTP 1.1 status code to be provided. 303 is assumed by default.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function redirect($url, $status = 303)
	{
		// Handle B/C by checking if a message was passed to the method, will be removed at 4.0
		if (func_num_args() > 1)
		{
			$args = func_get_args();

			/*
			 * Do some checks on the $args array, values below correspond to legacy redirect() method
			 *
			 * $args[0] = $url
			 * $args[1] = Message to enqueue
			 * $args[2] = Message type
			 * $args[3] = $status (previously moved)
			 */
			if (isset($args[1]) && !empty($args[1]) && (!is_bool($args[1]) && !is_int($args[1])))
			{
				$this->getLogger()->warning(
					'Passing a message and message type to ' . __METHOD__ . '() is deprecated. '
					. 'Please set your message via ' . __CLASS__ . '::enqueueMessage() prior to calling ' . __CLASS__
					. '::redirect().',
					array('category' => 'deprecated')
				);

				$message = $args[1];

				// Set the message type if present
				if (isset($args[2]) && !empty($args[2]))
				{
					$type = $args[2];
				}
				else
				{
					$type = 'message';
				}

				// Enqueue the message
				$this->enqueueMessage($message, $type);

				// Reset the $moved variable
				$status = isset($args[3]) ? (boolean) $args[3] : false;
			}
		}

		// Persist messages if they exist.
		if ($this->_messageQueue)
		{
			$session = \JFactory::getSession();
			$session->set('application.queue', $this->_messageQueue);
		}

		// Hand over processing to the parent now
		parent::redirect($url, $status);
	}

	/**
	 * Rendering is the process of pushing the document buffers into the template
	 * placeholders, retrieving data from the document and pushing it into
	 * the application response buffer.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function render()
	{
		// Setup the document options.
		$this->docOptions['template'] = $this->get('theme');
		$this->docOptions['file']     = $this->get('themeFile', 'index.php');
		$this->docOptions['params']   = $this->get('themeParams');

		if ($this->get('themes.base'))
		{
			$this->docOptions['directory'] = $this->get('themes.base');
		}
		// Fall back to constants.
		else
		{
			$this->docOptions['directory'] = defined('JPATH_THEMES') ? JPATH_THEMES : (defined('JPATH_BASE') ? JPATH_BASE : __DIR__) . '/themes';
		}

		// Parse the document.
		$this->document->parse($this->docOptions);

		// Trigger the onBeforeRender event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onBeforeRender');

		$caching = false;

		if ($this->isClient('site') && $this->get('caching') && $this->get('caching', 2) == 2 && !\JFactory::getUser()->get('id'))
		{
			$caching = true;
		}

		// Render the document.
		$data = $this->document->render($caching, $this->docOptions);

		// Set the application output data.
		$this->setBody($data);

		// Trigger the onAfterRender event.
		$this->triggerEvent('onAfterRender');

		// Mark afterRender in the profiler.
		JDEBUG ? $this->profiler->mark('afterRender') : null;
	}

	/**
	 * Route the application.
	 *
	 * Routing is the process of examining the request environment to determine which
	 * component should receive the request. The component optional parameters
	 * are then set in the request object to be processed when the application is being
	 * dispatched.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function route()
	{
		// Get the full request URI.
		$uri = clone \JUri::getInstance();

		$router = static::getRouter();
		$result = $router->parse($uri);

		$active = $this->getMenu()->getActive();

		if ($active !== null
			&& $active->type === 'alias'
			&& $active->params->get('alias_redirect')
			&& in_array($this->input->getMethod(), array('GET', 'HEAD'), true))
		{
			$item = $this->getMenu()->getItem($active->params->get('aliasoptions'));

			if ($item !== null)
			{
				$oldUri = clone \JUri::getInstance();

				if ($oldUri->getVar('Itemid') == $active->id)
				{
					$oldUri->setVar('Itemid', $item->id);
				}

				$base = \JUri::base(true);
				$oldPath = StringHelper::strtolower(substr($oldUri->getPath(), strlen($base) + 1));
				$activePathPrefix = StringHelper::strtolower($active->route);

				$position = strpos($oldPath, $activePathPrefix);

				if ($position !== false)
				{
					$oldUri->setPath($base . '/' . substr_replace($oldPath, $item->route, $position, strlen($activePathPrefix)));

					$this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);
					$this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true);
					$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);
					$this->setHeader('Pragma', 'no-cache');
					$this->sendHeaders();

					$this->redirect((string) $oldUri, 301);
				}
			}
		}

		foreach ($result as $key => $value)
		{
			$this->input->def($key, $value);
		}

		// Trigger the onAfterRoute event.
		\JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterRoute');
	}

	/**
	 * Sets the value of a user state variable.
	 *
	 * @param   string  $key    The path of the state.
	 * @param   mixed   $value  The value of the variable.
	 *
	 * @return  mixed  The previous state, if one existed.
	 *
	 * @since   3.2
	 */
	public function setUserState($key, $value)
	{
		$session = \JFactory::getSession();
		$registry = $session->get('registry');

		if ($registry !== null)
		{
			return $registry->set($key, $value);
		}

		return;
	}

	/**
	 * Sends all headers prior to returning the string
	 *
	 * @param   boolean  $compress  If true, compress the data
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function toString($compress = false)
	{
		// Don't compress something if the server is going to do it anyway. Waste of time.
		if ($compress && !ini_get('zlib.output_compression') && ini_get('output_handler') !== 'ob_gzhandler')
		{
			$this->compress();
		}

		if ($this->allowCache() === false)
		{
			$this->setHeader('Cache-Control', 'no-cache', false);

			// HTTP 1.0
			$this->setHeader('Pragma', 'no-cache');
		}

		$this->sendHeaders();

		return $this->getBody();
	}
}
src/MVC/Controller/AdminController.php000064400000021470152177723700013727 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Base class for a Joomla Administrator Controller
 *
 * Controller (controllers are where you put all the actual code) Provides basic
 * functionality, such as rendering views (aka displaying templates).
 *
 * @since  1.6
 */
class AdminController extends BaseController
{
	/**
	 * The URL option for the component.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $option;

	/**
	 * The prefix to use with controller messages.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $text_prefix;

	/**
	 * The URL view list variable.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $view_list;

	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     \JControllerLegacy
	 * @since   1.6
	 * @throws  \Exception
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		// Define standard task mappings.

		// Value = 0
		$this->registerTask('unpublish', 'publish');

		// Value = 2
		$this->registerTask('archive', 'publish');

		// Value = -2
		$this->registerTask('trash', 'publish');

		// Value = -3
		$this->registerTask('report', 'publish');
		$this->registerTask('orderup', 'reorder');
		$this->registerTask('orderdown', 'reorder');

		// Guess the option as com_NameOfController.
		if (empty($this->option))
		{
			$this->option = 'com_' . strtolower($this->getName());
		}

		// Guess the \JText message prefix. Defaults to the option.
		if (empty($this->text_prefix))
		{
			$this->text_prefix = strtoupper($this->option);
		}

		// Guess the list view as the suffix, eg: OptionControllerSuffix.
		if (empty($this->view_list))
		{
			$r = null;

			if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r))
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
			}

			$this->view_list = strtolower($r[2]);
		}
	}

	/**
	 * Removes an item.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function delete()
	{
		// Check for request forgeries
		$this->checkToken();

		// Get items to remove from the request.
		$cid = $this->input->get('cid', array(), 'array');

		if (!is_array($cid) || count($cid) < 1)
		{
			\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
		}
		else
		{
			// Get the model.
			$model = $this->getModel();

			// Make sure the item ids are integers
			$cid = ArrayHelper::toInteger($cid);

			// Remove the items.
			if ($model->delete($cid))
			{
				$this->setMessage(\JText::plural($this->text_prefix . '_N_ITEMS_DELETED', count($cid)));
			}
			else
			{
				$this->setMessage($model->getError(), 'error');
			}

			// Invoke the postDelete method to allow for the child class to access the model.
			$this->postDeleteHook($model, $cid);
		}

		$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));
	}

	/**
	 * Function that allows child controller access to model data
	 * after the item has been deleted.
	 *
	 * @param   \JModelLegacy  $model  The data model object.
	 * @param   integer        $id     The validated data.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function postDeleteHook(\JModelLegacy $model, $id = null)
	{
	}

	/**
	 * Method to publish a list of items
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function publish()
	{
		// Check for request forgeries
		$this->checkToken();

		// Get items to publish from the request.
		$cid = $this->input->get('cid', array(), 'array');
		$data = array('publish' => 1, 'unpublish' => 0, 'archive' => 2, 'trash' => -2, 'report' => -3);
		$task = $this->getTask();
		$value = ArrayHelper::getValue($data, $task, 0, 'int');

		if (empty($cid))
		{
			\JLog::add(\JText::_($this->text_prefix . '_NO_ITEM_SELECTED'), \JLog::WARNING, 'jerror');
		}
		else
		{
			// Get the model.
			$model = $this->getModel();

			// Make sure the item ids are integers
			$cid = ArrayHelper::toInteger($cid);

			// Publish the items.
			try
			{
				$model->publish($cid, $value);
				$errors = $model->getErrors();
				$ntext = null;

				if ($value === 1)
				{
					if ($errors)
					{
						\JFactory::getApplication()->enqueueMessage(\JText::plural($this->text_prefix . '_N_ITEMS_FAILED_PUBLISHING', count($cid)), 'error');
					}
					else
					{
						$ntext = $this->text_prefix . '_N_ITEMS_PUBLISHED';
					}
				}
				elseif ($value === 0)
				{
					$ntext = $this->text_prefix . '_N_ITEMS_UNPUBLISHED';
				}
				elseif ($value === 2)
				{
					$ntext = $this->text_prefix . '_N_ITEMS_ARCHIVED';
				}
				else
				{
					$ntext = $this->text_prefix . '_N_ITEMS_TRASHED';
				}

				if ($ntext !== null)
				{
					$this->setMessage(\JText::plural($ntext, count($cid)));
				}
			}
			catch (\Exception $e)
			{
				$this->setMessage($e->getMessage(), 'error');
			}
		}

		$extension = $this->input->get('extension');
		$extensionURL = $extension ? '&extension=' . $extension : '';
		$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list . $extensionURL, false));
	}

	/**
	 * Changes the order of one or more records.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.6
	 */
	public function reorder()
	{
		// Check for request forgeries.
		$this->checkToken();

		$ids = $this->input->post->get('cid', array(), 'array');
		$inc = $this->getTask() === 'orderup' ? -1 : 1;

		$model = $this->getModel();
		$return = $model->reorder($ids, $inc);

		if ($return === false)
		{
			// Reorder failed.
			$message = \JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED', $model->getError());
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');

			return false;
		}
		else
		{
			// Reorder succeeded.
			$message = \JText::_('JLIB_APPLICATION_SUCCESS_ITEM_REORDERED');
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message);

			return true;
		}
	}

	/**
	 * Method to save the submitted ordering values for records.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.6
	 */
	public function saveorder()
	{
		// Check for request forgeries.
		$this->checkToken();

		// Get the input
		$pks = $this->input->post->get('cid', array(), 'array');
		$order = $this->input->post->get('order', array(), 'array');

		// Sanitize the input
		$pks = ArrayHelper::toInteger($pks);
		$order = ArrayHelper::toInteger($order);

		// Get the model
		$model = $this->getModel();

		// Save the ordering
		$return = $model->saveorder($pks, $order);

		if ($return === false)
		{
			// Reorder failed
			$message = \JText::sprintf('JLIB_APPLICATION_ERROR_REORDER_FAILED', $model->getError());
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');

			return false;
		}
		else
		{
			// Reorder succeeded.
			$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_ORDERING_SAVED'));
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false));

			return true;
		}
	}

	/**
	 * Check in of one or more records.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.6
	 */
	public function checkin()
	{
		// Check for request forgeries.
		$this->checkToken();

		$ids = $this->input->post->get('cid', array(), 'array');

		$model = $this->getModel();
		$return = $model->checkin($ids);

		if ($return === false)
		{
			// Checkin failed.
			$message = \JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError());
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message, 'error');

			return false;
		}
		else
		{
			// Checkin succeeded.
			$message = \JText::plural($this->text_prefix . '_N_ITEMS_CHECKED_IN', count($ids));
			$this->setRedirect(\JRoute::_('index.php?option=' . $this->option . '&view=' . $this->view_list, false), $message);

			return true;
		}
	}

	/**
	 * Method to save the submitted ordering values for records via AJAX.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function saveOrderAjax()
	{
		// Get the input
		$pks = $this->input->post->get('cid', array(), 'array');
		$order = $this->input->post->get('order', array(), 'array');

		// Sanitize the input
		$pks = ArrayHelper::toInteger($pks);
		$order = ArrayHelper::toInteger($order);

		// Get the model
		$model = $this->getModel();

		// Save the ordering
		$return = $model->saveorder($pks, $order);

		if ($return)
		{
			echo '1';
		}

		// Close the application
		\JFactory::getApplication()->close();
	}
}
src/MVC/Controller/BaseController.php000064400000062674152177723700013564 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

defined('JPATH_PLATFORM') or die;

/**
 * Base class for a Joomla Controller
 *
 * Controller (Controllers are where you put all the actual code.) Provides basic
 * functionality, such as rendering views (aka displaying templates).
 *
 * @since  2.5.5
 */
class BaseController extends \JObject
{
	/**
	 * The base path of the controller
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $basePath;

	/**
	 * The default view for the display method.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $default_view;

	/**
	 * The mapped task that was performed.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $doTask;

	/**
	 * Redirect message.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $message;

	/**
	 * Redirect message type.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $messageType;

	/**
	 * Array of class methods
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $methods;

	/**
	 * The name of the controller
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $name;

	/**
	 * The prefix of the models
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $model_prefix;

	/**
	 * The set of search directories for resources (views).
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $paths;

	/**
	 * URL for redirection.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $redirect;

	/**
	 * Current or most recently performed task.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $task;

	/**
	 * Array of class methods to call for a given task.
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $taskMap;

	/**
	 * Hold a JInput object for easier access to the input variables.
	 *
	 * @var    \JInput
	 * @since  3.0
	 */
	protected $input;

	/**
	 * Instance container.
	 *
	 * @var    \JControllerLegacy
	 * @since  3.0
	 */
	protected static $instance;

	/**
	 * Instance container containing the views.
	 *
	 * @var    \JViewLegacy[]
	 * @since  3.4
	 */
	protected static $views;

	/**
	 * Adds to the stack of model paths in LIFO order.
	 *
	 * @param   mixed   $path    The directory (string), or list of directories (array) to add.
	 * @param   string  $prefix  A prefix for models
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function addModelPath($path, $prefix = '')
	{
		\JModelLegacy::addIncludePath($path, $prefix);
	}

	/**
	 * Create the filename for a resource.
	 *
	 * @param   string  $type   The resource type to create the filename for.
	 * @param   array   $parts  An associative array of filename information. Optional.
	 *
	 * @return  string  The filename.
	 *
	 * @since   3.0
	 */
	protected static function createFileName($type, $parts = array())
	{
		$filename = '';

		switch ($type)
		{
			case 'controller':
				if (!empty($parts['format']))
				{
					if ($parts['format'] === 'html')
					{
						$parts['format'] = '';
					}
					else
					{
						$parts['format'] = '.' . $parts['format'];
					}
				}
				else
				{
					$parts['format'] = '';
				}

				$filename = strtolower($parts['name'] . $parts['format'] . '.php');
				break;

			case 'view':
				if (!empty($parts['type']))
				{
					$parts['type'] = '.' . $parts['type'];
				}
				else
				{
					$parts['type'] = '';
				}

				$filename = strtolower($parts['name'] . '/view' . $parts['type'] . '.php');
				break;
		}

		return $filename;
	}

	/**
	 * Method to get a singleton controller instance.
	 *
	 * @param   string  $prefix  The prefix for the controller.
	 * @param   array   $config  An array of optional constructor options.
	 *
	 * @return  \JControllerLegacy
	 *
	 * @since   3.0
	 * @throws  \Exception if the controller cannot be loaded.
	 */
	public static function getInstance($prefix, $config = array())
	{
		if (is_object(self::$instance))
		{
			return self::$instance;
		}

		$input = \JFactory::getApplication()->input;

		// Get the environment configuration.
		$basePath = array_key_exists('base_path', $config) ? $config['base_path'] : JPATH_COMPONENT;
		$format   = $input->getWord('format');
		$command  = $input->get('task', 'display');

		// Check for array format.
		$filter = \JFilterInput::getInstance();

		if (is_array($command))
		{
			$command = $filter->clean(array_pop(array_keys($command)), 'cmd');
		}
		else
		{
			$command = $filter->clean($command, 'cmd');
		}

		// Check for a controller.task command.
		if (strpos($command, '.') !== false)
		{
			// Explode the controller.task command.
			list ($type, $task) = explode('.', $command);

			// Define the controller filename and path.
			$file = self::createFileName('controller', array('name' => $type, 'format' => $format));
			$path = $basePath . '/controllers/' . $file;
			$backuppath = $basePath . '/controller/' . $file;

			// Reset the task without the controller context.
			$input->set('task', $task);
		}
		else
		{
			// Base controller.
			$type = null;

			// Define the controller filename and path.
			$file       = self::createFileName('controller', array('name' => 'controller', 'format' => $format));
			$path       = $basePath . '/' . $file;
			$backupfile = self::createFileName('controller', array('name' => 'controller'));
			$backuppath = $basePath . '/' . $backupfile;
		}

		// Get the controller class name.
		$class = ucfirst($prefix) . 'Controller' . ucfirst($type);

		// Include the class if not present.
		if (!class_exists($class))
		{
			// If the controller file path exists, include it.
			if (file_exists($path))
			{
				require_once $path;
			}
			elseif (isset($backuppath) && file_exists($backuppath))
			{
				require_once $backuppath;
			}
			else
			{
				throw new \InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER', $type, $format));
			}
		}

		// Instantiate the class.
		if (!class_exists($class))
		{
			throw new \InvalidArgumentException(\JText::sprintf('JLIB_APPLICATION_ERROR_INVALID_CONTROLLER_CLASS', $class));
		}

		// Instantiate the class, store it to the static container, and return it
		return self::$instance = new $class($config);
	}

	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 * Recognized key values include 'name', 'default_task', 'model_path', and
	 * 'view_path' (this list is not meant to be comprehensive).
	 *
	 * @since   3.0
	 */
	public function __construct($config = array())
	{
		$this->methods = array();
		$this->message = null;
		$this->messageType = 'message';
		$this->paths = array();
		$this->redirect = null;
		$this->taskMap = array();

		if (defined('JDEBUG') && JDEBUG)
		{
			\JLog::addLogger(array('text_file' => 'jcontroller.log.php'), \JLog::ALL, array('controller'));
		}

		$this->input = \JFactory::getApplication()->input;

		// Determine the methods to exclude from the base class.
		$xMethods = get_class_methods('\JControllerLegacy');

		// Get the public methods in this class using reflection.
		$r = new \ReflectionClass($this);
		$rMethods = $r->getMethods(\ReflectionMethod::IS_PUBLIC);

		foreach ($rMethods as $rMethod)
		{
			$mName = $rMethod->getName();

			// Add default display method if not explicitly declared.
			if ($mName === 'display' || !in_array($mName, $xMethods))
			{
				$this->methods[] = strtolower($mName);

				// Auto register the methods as tasks.
				$this->taskMap[strtolower($mName)] = $mName;
			}
		}

		// Set the view name
		if (empty($this->name))
		{
			if (array_key_exists('name', $config))
			{
				$this->name = $config['name'];
			}
			else
			{
				$this->name = $this->getName();
			}
		}

		// Set a base path for use by the controller
		if (array_key_exists('base_path', $config))
		{
			$this->basePath = $config['base_path'];
		}
		else
		{
			$this->basePath = JPATH_COMPONENT;
		}

		// If the default task is set, register it as such
		if (array_key_exists('default_task', $config))
		{
			$this->registerDefaultTask($config['default_task']);
		}
		else
		{
			$this->registerDefaultTask('display');
		}

		// Set the models prefix
		if (empty($this->model_prefix))
		{
			if (array_key_exists('model_prefix', $config))
			{
				// User-defined prefix
				$this->model_prefix = $config['model_prefix'];
			}
			else
			{
				$this->model_prefix = ucfirst($this->name) . 'Model';
			}
		}

		// Set the default model search path
		if (array_key_exists('model_path', $config))
		{
			// User-defined dirs
			$this->addModelPath($config['model_path'], $this->model_prefix);
		}
		else
		{
			$this->addModelPath($this->basePath . '/models', $this->model_prefix);
		}

		// Set the default view search path
		if (array_key_exists('view_path', $config))
		{
			// User-defined dirs
			$this->setPath('view', $config['view_path']);
		}
		else
		{
			$this->setPath('view', $this->basePath . '/views');
		}

		// Set the default view.
		if (array_key_exists('default_view', $config))
		{
			$this->default_view = $config['default_view'];
		}
		elseif (empty($this->default_view))
		{
			$this->default_view = $this->getName();
		}
	}

	/**
	 * Adds to the search path for templates and resources.
	 *
	 * @param   string  $type  The path type (e.g. 'model', 'view').
	 * @param   mixed   $path  The directory string  or stream array to search.
	 *
	 * @return  \JControllerLegacy  A \JControllerLegacy object to support chaining.
	 *
	 * @since   3.0
	 */
	protected function addPath($type, $path)
	{
		if (!isset($this->paths[$type]))
		{
			$this->paths[$type] = array();
		}

		// Loop through the path directories
		foreach ((array) $path as $dir)
		{
			// No surrounding spaces allowed!
			$dir = rtrim(\JPath::check($dir), '/') . '/';

			// Add to the top of the search dirs
			array_unshift($this->paths[$type], $dir);
		}

		return $this;
	}

	/**
	 * Add one or more view paths to the controller's stack, in LIFO order.
	 *
	 * @param   mixed  $path  The directory (string) or list of directories (array) to add.
	 *
	 * @return  \JControllerLegacy  This object to support chaining.
	 *
	 * @since   3.0
	 */
	public function addViewPath($path)
	{
		return $this->addPath('view', $path);
	}

	/**
	 * Authorisation check
	 *
	 * @param   string  $task  The ACO Section Value to check access on.
	 *
	 * @return  boolean  True if authorised
	 *
	 * @since   3.0
	 * @deprecated  3.0  Use \JAccess instead.
	 */
	public function authorise($task)
	{
		\JLog::add(__METHOD__ . ' is deprecated. Use \JAccess instead.', \JLog::WARNING, 'deprecated');

		return true;
	}

	/**
	 * Method to check whether an ID is in the edit list.
	 *
	 * @param   string   $context  The context for the session storage.
	 * @param   integer  $id       The ID of the record to add to the edit list.
	 *
	 * @return  boolean  True if the ID is in the edit list.
	 *
	 * @since   3.0
	 */
	protected function checkEditId($context, $id)
	{
		if ($id)
		{
			$values = (array) \JFactory::getApplication()->getUserState($context . '.id');

			$result = in_array((int) $id, $values);

			if (defined('JDEBUG') && JDEBUG)
			{
				\JLog::add(
					sprintf(
						'Checking edit ID %s.%s: %d %s',
						$context,
						$id,
						(int) $result,
						str_replace("\n", ' ', print_r($values, 1))
					),
					\JLog::INFO,
					'controller'
				);
			}

			return $result;
		}

		// No id for a new item.
		return true;
	}

	/**
	 * Method to load and return a model object.
	 *
	 * @param   string  $name    The name of the model.
	 * @param   string  $prefix  Optional model prefix.
	 * @param   array   $config  Configuration array for the model. Optional.
	 *
	 * @return  \JModelLegacy|boolean   Model object on success; otherwise false on failure.
	 *
	 * @since   3.0
	 */
	protected function createModel($name, $prefix = '', $config = array())
	{
		// Clean the model name
		$modelName = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$classPrefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		return \JModelLegacy::getInstance($modelName, $classPrefix, $config);
	}

	/**
	 * Method to load and return a view object. This method first looks in the
	 * current template directory for a match and, failing that, uses a default
	 * set path to load the view class file.
	 *
	 * Note the "name, prefix, type" order of parameters, which differs from the
	 * "name, type, prefix" order used in related public methods.
	 *
	 * @param   string  $name    The name of the view.
	 * @param   string  $prefix  Optional prefix for the view class name.
	 * @param   string  $type    The type of view.
	 * @param   array   $config  Configuration array for the view. Optional.
	 *
	 * @return  \JViewLegacy|null  View object on success; null or error result on failure.
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	protected function createView($name, $prefix = '', $type = '', $config = array())
	{
		// Clean the view name
		$viewName = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$classPrefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);
		$viewType = preg_replace('/[^A-Z0-9_]/i', '', $type);

		// Build the view class name
		$viewClass = $classPrefix . $viewName;

		if (!class_exists($viewClass))
		{
			jimport('joomla.filesystem.path');
			$path = \JPath::find($this->paths['view'], $this->createFileName('view', array('name' => $viewName, 'type' => $viewType)));

			if (!$path)
			{
				return null;
			}

			require_once $path;

			if (!class_exists($viewClass))
			{
				throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_CLASS_NOT_FOUND', $viewClass, $path), 500);
			}
		}

		return new $viewClass($config);
	}

	/**
	 * Typical view method for MVC based architecture
	 *
	 * This function is provide as a default implementation, in most cases
	 * you will need to override it in your own controllers.
	 *
	 * @param   boolean  $cachable   If true, the view output will be cached
	 * @param   array    $urlparams  An array of safe URL parameters and their variable types, for valid values see {@link \JFilterInput::clean()}.
	 *
	 * @return  \JControllerLegacy  A \JControllerLegacy object to support chaining.
	 *
	 * @since   3.0
	 */
	public function display($cachable = false, $urlparams = array())
	{
		$document = \JFactory::getDocument();
		$viewType = $document->getType();
		$viewName = $this->input->get('view', $this->default_view);
		$viewLayout = $this->input->get('layout', 'default', 'string');

		$view = $this->getView($viewName, $viewType, '', array('base_path' => $this->basePath, 'layout' => $viewLayout));

		// Get/Create the model
		if ($model = $this->getModel($viewName))
		{
			// Push the model into the view (as default)
			$view->setModel($model, true);
		}

		$view->document = $document;

		// Display the view
		if ($cachable && $viewType !== 'feed' && \JFactory::getConfig()->get('caching') >= 1)
		{
			$option = $this->input->get('option');

			if (is_array($urlparams))
			{
				$app = \JFactory::getApplication();

				if (!empty($app->registeredurlparams))
				{
					$registeredurlparams = $app->registeredurlparams;
				}
				else
				{
					$registeredurlparams = new \stdClass;
				}

				foreach ($urlparams as $key => $value)
				{
					// Add your safe URL parameters with variable type as value {@see \JFilterInput::clean()}.
					$registeredurlparams->$key = $value;
				}

				$app->registeredurlparams = $registeredurlparams;
			}

			try
			{
				/** @var \JCacheControllerView $cache */
				$cache = \JFactory::getCache($option, 'view');
				$cache->get($view, 'display');
			}
			catch (\JCacheException $exception)
			{
				$view->display();
			}
		}
		else
		{
			$view->display();
		}

		return $this;
	}

	/**
	 * Execute a task by triggering a method in the derived class.
	 *
	 * @param   string  $task  The task to perform. If no matching task is found, the '__default' task is executed, if defined.
	 *
	 * @return  mixed   The value returned by the called method.
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function execute($task)
	{
		$this->task = $task;

		$task = strtolower($task);

		if (isset($this->taskMap[$task]))
		{
			$doTask = $this->taskMap[$task];
		}
		elseif (isset($this->taskMap['__default']))
		{
			$doTask = $this->taskMap['__default'];
		}
		else
		{
			throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_TASK_NOT_FOUND', $task), 404);
		}

		// Record the actual task being fired
		$this->doTask = $doTask;

		return $this->$doTask();
	}

	/**
	 * Method to get a model object, loading it if required.
	 *
	 * @param   string  $name    The model name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  \JModelLegacy|boolean  Model object on success; otherwise false on failure.
	 *
	 * @since   3.0
	 */
	public function getModel($name = '', $prefix = '', $config = array())
	{
		if (empty($name))
		{
			$name = $this->getName();
		}

		if (empty($prefix))
		{
			$prefix = $this->model_prefix;
		}

		if ($model = $this->createModel($name, $prefix, $config))
		{
			// Task is a reserved state
			$model->setState('task', $this->task);

			// Let's get the application object and set menu information if it's available
			$menu = \JFactory::getApplication()->getMenu();

			if (is_object($menu) && $item = $menu->getActive())
			{
				$params = $menu->getParams($item->id);

				// Set default state data
				$model->setState('parameters.menu', $params);
			}
		}

		return $model;
	}

	/**
	 * Method to get the controller name
	 *
	 * The dispatcher name is set by default parsed using the classname, or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @return  string  The name of the dispatcher
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$r = null;

			if (!preg_match('/(.*)Controller/i', get_class($this), $r))
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
			}

			$this->name = strtolower($r[1]);
		}

		return $this->name;
	}

	/**
	 * Get the last task that is being performed or was most recently performed.
	 *
	 * @return  string  The task that is being performed or was most recently performed.
	 *
	 * @since   3.0
	 */
	public function getTask()
	{
		return $this->task;
	}

	/**
	 * Gets the available tasks in the controller.
	 *
	 * @return  array  Array[i] of task names.
	 *
	 * @since   3.0
	 */
	public function getTasks()
	{
		return $this->methods;
	}

	/**
	 * Method to get a reference to the current view and load it if necessary.
	 *
	 * @param   string  $name    The view name. Optional, defaults to the controller name.
	 * @param   string  $type    The view type. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for view. Optional.
	 *
	 * @return  \JViewLegacy  Reference to the view or an error.
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function getView($name = '', $type = '', $prefix = '', $config = array())
	{
		// @note We use self so we only access stuff in this class rather than in all classes.
		if (!isset(self::$views))
		{
			self::$views = array();
		}

		if (empty($name))
		{
			$name = $this->getName();
		}

		if (empty($prefix))
		{
			$prefix = $this->getName() . 'View';
		}

		if (empty(self::$views[$name][$type][$prefix]))
		{
			if ($view = $this->createView($name, $prefix, $type, $config))
			{
				self::$views[$name][$type][$prefix] = & $view;
			}
			else
			{
				throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_VIEW_NOT_FOUND', $name, $type, $prefix), 404);
			}
		}

		return self::$views[$name][$type][$prefix];
	}

	/**
	 * Method to add a record ID to the edit list.
	 *
	 * @param   string   $context  The context for the session storage.
	 * @param   integer  $id       The ID of the record to add to the edit list.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function holdEditId($context, $id)
	{
		$app = \JFactory::getApplication();
		$values = (array) $app->getUserState($context . '.id');

		// Add the id to the list if non-zero.
		if (!empty($id))
		{
			$values[] = (int) $id;
			$values   = array_unique($values);
			$app->setUserState($context . '.id', $values);

			if (defined('JDEBUG') && JDEBUG)
			{
				\JLog::add(
					sprintf(
						'Holding edit ID %s.%s %s',
						$context,
						$id,
						str_replace("\n", ' ', print_r($values, 1))
					),
					\JLog::INFO,
					'controller'
				);
			}
		}
	}

	/**
	 * Redirects the browser or returns false if no redirect is set.
	 *
	 * @return  boolean  False if no redirect exists.
	 *
	 * @since   3.0
	 */
	public function redirect()
	{
		if ($this->redirect)
		{
			$app = \JFactory::getApplication();

			// Enqueue the redirect message
			$app->enqueueMessage($this->message, $this->messageType);

			// Execute the redirect
			$app->redirect($this->redirect);
		}

		return false;
	}

	/**
	 * Register the default task to perform if a mapping is not found.
	 *
	 * @param   string  $method  The name of the method in the derived class to perform if a named task is not found.
	 *
	 * @return  \JControllerLegacy  A \JControllerLegacy object to support chaining.
	 *
	 * @since   3.0
	 */
	public function registerDefaultTask($method)
	{
		$this->registerTask('__default', $method);

		return $this;
	}

	/**
	 * Register (map) a task to a method in the class.
	 *
	 * @param   string  $task    The task.
	 * @param   string  $method  The name of the method in the derived class to perform for this task.
	 *
	 * @return  \JControllerLegacy  A \JControllerLegacy object to support chaining.
	 *
	 * @since   3.0
	 */
	public function registerTask($task, $method)
	{
		if (in_array(strtolower($method), $this->methods))
		{
			$this->taskMap[strtolower($task)] = $method;
		}

		return $this;
	}

	/**
	 * Unregister (unmap) a task in the class.
	 *
	 * @param   string  $task  The task.
	 *
	 * @return  \JControllerLegacy  This object to support chaining.
	 *
	 * @since   3.0
	 */
	public function unregisterTask($task)
	{
		unset($this->taskMap[strtolower($task)]);

		return $this;
	}

	/**
	 * Method to check whether an ID is in the edit list.
	 *
	 * @param   string   $context  The context for the session storage.
	 * @param   integer  $id       The ID of the record to add to the edit list.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function releaseEditId($context, $id)
	{
		$app = \JFactory::getApplication();
		$values = (array) $app->getUserState($context . '.id');

		// Do a strict search of the edit list values.
		$index = array_search((int) $id, $values, true);

		if (is_int($index))
		{
			unset($values[$index]);
			$app->setUserState($context . '.id', $values);

			if (defined('JDEBUG') && JDEBUG)
			{
				\JLog::add(
					sprintf(
						'Releasing edit ID %s.%s %s',
						$context,
						$id,
						str_replace("\n", ' ', print_r($values, 1))
					),
					\JLog::INFO,
					'controller'
				);
			}
		}
	}

	/**
	 * Sets the internal message that is passed with a redirect
	 *
	 * @param   string  $text  Message to display on redirect.
	 * @param   string  $type  Message type. Optional, defaults to 'message'.
	 *
	 * @return  string  Previous message
	 *
	 * @since   3.0
	 */
	public function setMessage($text, $type = 'message')
	{
		$previous = $this->message;
		$this->message = $text;
		$this->messageType = $type;

		return $previous;
	}

	/**
	 * Sets an entire array of search paths for resources.
	 *
	 * @param   string  $type  The type of path to set, typically 'view' or 'model'.
	 * @param   string  $path  The new set of search paths. If null or false, resets to the current directory only.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function setPath($type, $path)
	{
		// Clear out the prior search dirs
		$this->paths[$type] = array();

		// Actually add the user-specified directories
		$this->addPath($type, $path);
	}

	/**
	 * Checks for a form token in the request.
	 *
	 * Use in conjunction with \JHtml::_('form.token') or \JSession::getFormToken.
	 *
	 * @param   string   $method    The request method in which to look for the token key.
	 * @param   boolean  $redirect  Whether to implicitly redirect user to the referrer page on failure or simply return false.
	 *
	 * @return  boolean  True if found and valid, otherwise return false or redirect to referrer page.
	 *
	 * @since   3.7.0
	 * @see     \JSession::checkToken()
	 */
	public function checkToken($method = 'post', $redirect = true)
	{
		$valid = \JSession::checkToken($method);

		if (!$valid && $redirect)
		{
			$referrer = $this->input->server->getString('HTTP_REFERER');

			if (!\JUri::isInternal($referrer))
			{
				$referrer = 'index.php';
			}

			$app = \JFactory::getApplication();
			$app->enqueueMessage(\JText::_('JINVALID_TOKEN_NOTICE'), 'warning');
			$app->redirect($referrer);
		}

		return $valid;
	}

	/**
	 * Set a URL for browser redirection.
	 *
	 * @param   string  $url   URL to redirect to.
	 * @param   string  $msg   Message to display on redirect. Optional, defaults to value set internally by controller, if any.
	 * @param   string  $type  Message type. Optional, defaults to 'message' or the type set by a previous call to setMessage.
	 *
	 * @return  \JControllerLegacy  This object to support chaining.
	 *
	 * @since   3.0
	 */
	public function setRedirect($url, $msg = null, $type = null)
	{
		$this->redirect = $url;

		if ($msg !== null)
		{
			// Controller may have set this directly
			$this->message = $msg;
		}

		// Ensure the type is not overwritten by a previous call to setMessage.
		if (empty($type))
		{
			if (empty($this->messageType))
			{
				$this->messageType = 'message';
			}
		}
		// If the type is explicitly set, set it.
		else
		{
			$this->messageType = $type;
		}

		return $this;
	}
}
src/MVC/Controller/FormController.php000064400000057646152177723700013620 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Controller;

defined('JPATH_PLATFORM') or die;

/**
 * Controller tailored to suit most form-based admin operations.
 *
 * @since  1.6
 * @todo   Add ability to set redirect manually to better cope with frontend usage.
 */
class FormController extends BaseController
{
	/**
	 * The context for storing internal data, e.g. record.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $context;

	/**
	 * The URL option for the component.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $option;

	/**
	 * The URL view item variable.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $view_item;

	/**
	 * The URL view list variable.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $view_list;

	/**
	 * The prefix to use with controller messages.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $text_prefix;

	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     \JControllerLegacy
	 * @since   1.6
	 * @throws  \Exception
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		// Guess the option as com_NameOfController
		if (empty($this->option))
		{
			$this->option = 'com_' . strtolower($this->getName());
		}

		// Guess the \JText message prefix. Defaults to the option.
		if (empty($this->text_prefix))
		{
			$this->text_prefix = strtoupper($this->option);
		}

		// Guess the context as the suffix, eg: OptionControllerContent.
		if (empty($this->context))
		{
			$r = null;

			if (!preg_match('/(.*)Controller(.*)/i', get_class($this), $r))
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_CONTROLLER_GET_NAME'), 500);
			}

			$this->context = strtolower($r[2]);
		}

		// Guess the item view as the context.
		if (empty($this->view_item))
		{
			$this->view_item = $this->context;
		}

		// Guess the list view as the plural of the item view.
		if (empty($this->view_list))
		{
			// @TODO Probably worth moving to an inflector class based on
			// http://kuwamoto.org/2007/12/17/improved-pluralizing-in-php-actionscript-and-ror/

			// Simple pluralisation based on public domain snippet by Paul Osman
			// For more complex types, just manually set the variable in your class.
			$plural = array(
				array('/(x|ch|ss|sh)$/i', "$1es"),
				array('/([^aeiouy]|qu)y$/i', "$1ies"),
				array('/([^aeiouy]|qu)ies$/i', "$1y"),
				array('/(bu)s$/i', "$1ses"),
				array('/s$/i', 's'),
				array('/$/', 's'),
			);

			// Check for matches using regular expressions
			foreach ($plural as $pattern)
			{
				if (preg_match($pattern[0], $this->view_item))
				{
					$this->view_list = preg_replace($pattern[0], $pattern[1], $this->view_item);
					break;
				}
			}
		}

		// Apply, Save & New, and Save As copy should be standard on forms.
		$this->registerTask('apply', 'save');
		$this->registerTask('save2new', 'save');
		$this->registerTask('save2copy', 'save');
	}

	/**
	 * Method to add a new record.
	 *
	 * @return  boolean  True if the record can be added, false if not.
	 *
	 * @since   1.6
	 */
	public function add()
	{
		$context = "$this->option.edit.$this->context";

		// Access check.
		if (!$this->allowAdd())
		{
			// Set the internal error and also the redirect error.
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CREATE_RECORD_NOT_PERMITTED'));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);

			return false;
		}

		// Clear the record edit information from the session.
		\JFactory::getApplication()->setUserState($context . '.data', null);

		// Redirect to the edit screen.
		$this->setRedirect(
			\JRoute::_(
				'index.php?option=' . $this->option . '&view=' . $this->view_item
				. $this->getRedirectToItemAppend(), false
			)
		);

		return true;
	}

	/**
	 * Method to check if you can add a new record.
	 *
	 * Extended classes can override this if necessary.
	 *
	 * @param   array  $data  An array of input data.
	 *
	 * @return  boolean
	 *
	 * @since   1.6
	 */
	protected function allowAdd($data = array())
	{
		$user = \JFactory::getUser();

		return $user->authorise('core.create', $this->option) || count($user->getAuthorisedCategories($this->option, 'core.create'));
	}

	/**
	 * Method to check if you can edit an existing record.
	 *
	 * Extended classes can override this if necessary.
	 *
	 * @param   array   $data  An array of input data.
	 * @param   string  $key   The name of the key for the primary key; default is id.
	 *
	 * @return  boolean
	 *
	 * @since   1.6
	 */
	protected function allowEdit($data = array(), $key = 'id')
	{
		return \JFactory::getUser()->authorise('core.edit', $this->option);
	}

	/**
	 * Method to check if you can save a new or existing record.
	 *
	 * Extended classes can override this if necessary.
	 *
	 * @param   array   $data  An array of input data.
	 * @param   string  $key   The name of the key for the primary key.
	 *
	 * @return  boolean
	 *
	 * @since   1.6
	 */
	protected function allowSave($data, $key = 'id')
	{
		$recordId = isset($data[$key]) ? $data[$key] : '0';

		if ($recordId)
		{
			return $this->allowEdit($data, $key);
		}
		else
		{
			return $this->allowAdd($data);
		}
	}

	/**
	 * Method to run batch operations.
	 *
	 * @param   \JModelLegacy  $model  The model of the component being processed.
	 *
	 * @return	boolean	 True if successful, false otherwise and internal error is set.
	 *
	 * @since	1.7
	 */
	public function batch($model)
	{
		$vars = $this->input->post->get('batch', array(), 'array');
		$cid  = $this->input->post->get('cid', array(), 'array');

		// Build an array of item contexts to check
		$contexts = array();

		$option = isset($this->extension) ? $this->extension : $this->option;

		foreach ($cid as $id)
		{
			// If we're coming from com_categories, we need to use extension vs. option
			$contexts[$id] = $option . '.' . $this->context . '.' . $id;
		}

		// Attempt to run the batch operation.
		if ($model->batch($vars, $cid, $contexts))
		{
			$this->setMessage(\JText::_('JLIB_APPLICATION_SUCCESS_BATCH'));

			return true;
		}
		else
		{
			$this->setMessage(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_FAILED', $model->getError()), 'warning');

			return false;
		}
	}

	/**
	 * Method to cancel an edit.
	 *
	 * @param   string  $key  The name of the primary key of the URL variable.
	 *
	 * @return  boolean  True if access level checks pass, false otherwise.
	 *
	 * @since   1.6
	 */
	public function cancel($key = null)
	{
		$this->checkToken();

		$model = $this->getModel();
		$table = $model->getTable();
		$context = "$this->option.edit.$this->context";

		if (empty($key))
		{
			$key = $table->getKeyName();
		}

		$recordId = $this->input->getInt($key);

		// Attempt to check-in the current record.
		if ($recordId && property_exists($table, 'checked_out') && $model->checkin($recordId) === false)
		{
			// Check-in failed, go back to the record and display a notice.
			$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $key), false
				)
			);

			return false;
		}

		// Clean the session data and redirect.
		$this->releaseEditId($context, $recordId);
		\JFactory::getApplication()->setUserState($context . '.data', null);

		$url = 'index.php?option=' . $this->option . '&view=' . $this->view_list
			. $this->getRedirectToListAppend();

		// Check if there is a return value
		$return = $this->input->get('return', null, 'base64');

		if (!is_null($return) && \JUri::isInternal(base64_decode($return)))
		{
			$url = base64_decode($return);
		}

		// Redirect to the list screen.
		$this->setRedirect(\JRoute::_($url, false));

		return true;
	}

	/**
	 * Method to edit an existing record.
	 *
	 * @param   string  $key     The name of the primary key of the URL variable.
	 * @param   string  $urlVar  The name of the URL variable if different from the primary key
	 *                           (sometimes required to avoid router collisions).
	 *
	 * @return  boolean  True if access level check and checkout passes, false otherwise.
	 *
	 * @since   1.6
	 */
	public function edit($key = null, $urlVar = null)
	{
		// Do not cache the response to this, its a redirect, and mod_expires and google chrome browser bugs cache it forever!
		\JFactory::getApplication()->allowCache(false);

		$model = $this->getModel();
		$table = $model->getTable();
		$cid   = $this->input->post->get('cid', array(), 'array');
		$context = "$this->option.edit.$this->context";

		// Determine the name of the primary key for the data.
		if (empty($key))
		{
			$key = $table->getKeyName();
		}

		// To avoid data collisions the urlVar may be different from the primary key.
		if (empty($urlVar))
		{
			$urlVar = $key;
		}

		// Get the previous record id (if any) and the current record id.
		$recordId = (int) (count($cid) ? $cid[0] : $this->input->getInt($urlVar));
		$checkin = property_exists($table, $table->getColumnAlias('checked_out'));

		// Access check.
		if (!$this->allowEdit(array($key => $recordId), $key))
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);

			return false;
		}

		// Attempt to check-out the new record for editing and redirect.
		if ($checkin && !$model->checkout($recordId))
		{
			// Check-out failed, display a notice but allow the user to see the record.
			$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKOUT_FAILED', $model->getError()));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $urlVar), false
				)
			);

			return false;
		}
		else
		{
			// Check-out succeeded, push the new record id into the session.
			$this->holdEditId($context, $recordId);
			\JFactory::getApplication()->setUserState($context . '.data', null);

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $urlVar), false
				)
			);

			return true;
		}
	}

	/**
	 * Method to get a model object, loading it if required.
	 *
	 * @param   string  $name    The model name. Optional.
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  \JModelLegacy  The model.
	 *
	 * @since   1.6
	 */
	public function getModel($name = '', $prefix = '', $config = array('ignore_request' => true))
	{
		if (empty($name))
		{
			$name = $this->context;
		}

		return parent::getModel($name, $prefix, $config);
	}

	/**
	 * Gets the URL arguments to append to an item redirect.
	 *
	 * @param   integer  $recordId  The primary key id for the item.
	 * @param   string   $urlVar    The name of the URL variable for the id.
	 *
	 * @return  string  The arguments to append to the redirect URL.
	 *
	 * @since   1.6
	 */
	protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id')
	{
		$append = '';

		// Setup redirect info.
		if ($tmpl = $this->input->get('tmpl', '', 'string'))
		{
			$append .= '&tmpl=' . $tmpl;
		}

		if ($layout = $this->input->get('layout', 'edit', 'string'))
		{
			$append .= '&layout=' . $layout;
		}

		if ($forcedLanguage = $this->input->get('forcedLanguage', '', 'cmd'))
		{
			$append .= '&forcedLanguage=' . $forcedLanguage;
		}

		if ($recordId)
		{
			$append .= '&' . $urlVar . '=' . $recordId;
		}

		$return = $this->input->get('return', null, 'base64');

		if ($return)
		{
			$append .= '&return=' . $return;
		}

		return $append;
	}

	/**
	 * Gets the URL arguments to append to a list redirect.
	 *
	 * @return  string  The arguments to append to the redirect URL.
	 *
	 * @since   1.6
	 */
	protected function getRedirectToListAppend()
	{
		$append = '';

		// Setup redirect info.
		if ($tmpl = $this->input->get('tmpl', '', 'string'))
		{
			$append .= '&tmpl=' . $tmpl;
		}

		if ($forcedLanguage = $this->input->get('forcedLanguage', '', 'cmd'))
		{
			$append .= '&forcedLanguage=' . $forcedLanguage;
		}

		return $append;
	}

	/**
	 * Function that allows child controller access to model data
	 * after the data has been saved.
	 *
	 * @param   \JModelLegacy  $model      The data model object.
	 * @param   array          $validData  The validated data.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	protected function postSaveHook(\JModelLegacy $model, $validData = array())
	{
	}

	/**
	 * Method to load a row from version history
	 *
	 * @return  mixed  True if the record can be added, an error object if not.
	 *
	 * @since   3.2
	 */
	public function loadhistory()
	{
		$model = $this->getModel();
		$table = $model->getTable();
		$historyId = $this->input->getInt('version_id', null);

		if (!$model->loadhistory($historyId, $table))
		{
			$this->setMessage($model->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);

			return false;
		}

		// Determine the name of the primary key for the data.
		if (empty($key))
		{
			$key = $table->getKeyName();
		}

		$recordId = $table->$key;

		// To avoid data collisions the urlVar may be different from the primary key.
		$urlVar = empty($this->urlVar) ? $key : $this->urlVar;

		// Access check.
		if (!$this->allowEdit(array($key => $recordId), $key))
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_EDIT_NOT_PERMITTED'));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);
			$table->checkin();

			return false;
		}

		$table->store();
		$this->setRedirect(
			\JRoute::_(
				'index.php?option=' . $this->option . '&view=' . $this->view_item
				. $this->getRedirectToItemAppend($recordId, $urlVar), false
			)
		);

		$this->setMessage(
			\JText::sprintf(
				'JLIB_APPLICATION_SUCCESS_LOAD_HISTORY', $model->getState('save_date'), $model->getState('version_note')
			)
		);

		// Invoke the postSave method to allow for the child class to access the model.
		$this->postSaveHook($model);

		return true;
	}

	/**
	 * Method to save a record.
	 *
	 * @param   string  $key     The name of the primary key of the URL variable.
	 * @param   string  $urlVar  The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
	 *
	 * @return  boolean  True if successful, false otherwise.
	 *
	 * @since   1.6
	 */
	public function save($key = null, $urlVar = null)
	{
		// Check for request forgeries.
		$this->checkToken();

		$app   = \JFactory::getApplication();
		$model = $this->getModel();
		$table = $model->getTable();
		$data  = $this->input->post->get('jform', array(), 'array');
		$checkin = property_exists($table, $table->getColumnAlias('checked_out'));
		$context = "$this->option.edit.$this->context";
		$task = $this->getTask();

		// Determine the name of the primary key for the data.
		if (empty($key))
		{
			$key = $table->getKeyName();
		}

		// To avoid data collisions the urlVar may be different from the primary key.
		if (empty($urlVar))
		{
			$urlVar = $key;
		}

		$recordId = $this->input->getInt($urlVar);

		// Populate the row id from the session.
		$data[$key] = $recordId;

		// The save2copy task needs to be handled slightly differently.
		if ($task === 'save2copy')
		{
			// Check-in the original row.
			if ($checkin && $model->checkin($data[$key]) === false)
			{
				// Check-in failed. Go back to the item and display a notice.
				$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
				$this->setMessage($this->getError(), 'error');

				$this->setRedirect(
					\JRoute::_(
						'index.php?option=' . $this->option . '&view=' . $this->view_item
						. $this->getRedirectToItemAppend($recordId, $urlVar), false
					)
				);

				return false;
			}

			// Reset the ID, the multilingual associations and then treat the request as for Apply.
			$data[$key] = 0;
			$data['associations'] = array();
			$task = 'apply';
		}

		// Access check.
		if (!$this->allowSave($data, $key))
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_SAVE_NOT_PERMITTED'));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);

			return false;
		}

		// Validate the posted data.
		// Sometimes the form needs some posted data, such as for plugins and modules.
		$form = $model->getForm($data, false);

		if (!$form)
		{
			$app->enqueueMessage($model->getError(), 'error');

			return false;
		}

		// Send an object which can be modified through the plugin event
		$objData = (object) $data;
		$app->triggerEvent(
			'onContentNormaliseRequestData',
			array($this->option . '.' . $this->context, $objData, $form)
		);
		$data = (array) $objData;

		// Test whether the data is valid.
		$validData = $model->validate($form, $data);

		// Check for validation errors.
		if ($validData === false)
		{
			// Get the validation messages.
			$errors = $model->getErrors();

			// Push up to three validation messages out to the user.
			for ($i = 0, $n = count($errors); $i < $n && $i < 3; $i++)
			{
				if ($errors[$i] instanceof \Exception)
				{
					$app->enqueueMessage($errors[$i]->getMessage(), 'warning');
				}
				else
				{
					$app->enqueueMessage($errors[$i], 'warning');
				}
			}

			// Save the data in the session.
			$app->setUserState($context . '.data', $data);

			// Redirect back to the edit screen.
			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $urlVar), false
				)
			);

			return false;
		}

		if (!isset($validData['tags']))
		{
			$validData['tags'] = null;
		}

		// Attempt to save the data.
		if (!$model->save($validData))
		{
			// Save the data in the session.
			$app->setUserState($context . '.data', $validData);

			// Redirect back to the edit screen.
			$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_SAVE_FAILED', $model->getError()));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $urlVar), false
				)
			);

			return false;
		}

		// Save succeeded, so check-in the record.
		if ($checkin && $model->checkin($validData[$key]) === false)
		{
			// Save the data in the session.
			$app->setUserState($context . '.data', $validData);

			// Check-in failed, so go back to the record and display a notice.
			$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_CHECKIN_FAILED', $model->getError()));
			$this->setMessage($this->getError(), 'error');

			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_item
					. $this->getRedirectToItemAppend($recordId, $urlVar), false
				)
			);

			return false;
		}

		$langKey = $this->text_prefix . ($recordId === 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS';
		$prefix  = \JFactory::getLanguage()->hasKey($langKey) ? $this->text_prefix : 'JLIB_APPLICATION';

		$this->setMessage(\JText::_($prefix . ($recordId === 0 && $app->isClient('site') ? '_SUBMIT' : '') . '_SAVE_SUCCESS'));

		// Redirect the user and adjust session state based on the chosen task.
		switch ($task)
		{
			case 'apply':
				// Set the record data in the session.
				$recordId = $model->getState($this->context . '.id');
				$this->holdEditId($context, $recordId);
				$app->setUserState($context . '.data', null);
				$model->checkout($recordId);

				// Redirect back to the edit screen.
				$this->setRedirect(
					\JRoute::_(
						'index.php?option=' . $this->option . '&view=' . $this->view_item
						. $this->getRedirectToItemAppend($recordId, $urlVar), false
					)
				);
				break;

			case 'save2new':
				// Clear the record id and data from the session.
				$this->releaseEditId($context, $recordId);
				$app->setUserState($context . '.data', null);

				// Redirect back to the edit screen.
				$this->setRedirect(
					\JRoute::_(
						'index.php?option=' . $this->option . '&view=' . $this->view_item
						. $this->getRedirectToItemAppend(null, $urlVar), false
					)
				);
				break;

			default:
				// Clear the record id and data from the session.
				$this->releaseEditId($context, $recordId);
				$app->setUserState($context . '.data', null);

				$url = 'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend();

				// Check if there is a return value
				$return = $this->input->get('return', null, 'base64');

				if (!is_null($return) && \JUri::isInternal(base64_decode($return)))
				{
					$url = base64_decode($return);
				}

				// Redirect to the list screen.
				$this->setRedirect(\JRoute::_($url, false));
				break;
		}

		// Invoke the postSave method to allow for the child class to access the model.
		$this->postSaveHook($model, $validData);

		return true;
	}

	/**
	 * Method to reload a record.
	 *
	 * @param   string  $key     The name of the primary key of the URL variable.
	 * @param   string  $urlVar  The name of the URL variable if different from the primary key (sometimes required to avoid router collisions).
	 *
	 * @return  void
	 *
	 * @since   3.7.4
	 */
	public function reload($key = null, $urlVar = null)
	{
		// Check for request forgeries.
		$this->checkToken();

		$app     = \JFactory::getApplication();
		$model   = $this->getModel();
		$data    = $this->input->post->get('jform', array(), 'array');

		// Determine the name of the primary key for the data.
		if (empty($key))
		{
			$key = $model->getTable()->getKeyName();
		}

		// To avoid data collisions the urlVar may be different from the primary key.
		if (empty($urlVar))
		{
			$urlVar = $key;
		}

		$recordId = $this->input->getInt($urlVar);

		// Populate the row id from the session.
		$data[$key] = $recordId;

		// Check if it is allowed to edit or create the data
		if (($recordId && !$this->allowEdit($data, $key)) || (!$recordId && !$this->allowAdd($data)))
		{
			$this->setRedirect(
				\JRoute::_(
					'index.php?option=' . $this->option . '&view=' . $this->view_list
					. $this->getRedirectToListAppend(), false
				)
			);
			$this->redirect();
		}

		// The redirect url
		$redirectUrl = \JRoute::_(
			'index.php?option=' . $this->option . '&view=' . $this->view_item .
			$this->getRedirectToItemAppend($recordId, $urlVar),
			false
		);

		// Validate the posted data.
		// Sometimes the form needs some posted data, such as for plugins and modules.
		$form = $model->getForm($data, false);

		if (!$form)
		{
			$app->enqueueMessage($model->getError(), 'error');

			$this->setRedirect($redirectUrl);
			$this->redirect();
		}

		// Save the data in the session.
		$app->setUserState($this->option . '.edit.' . $this->context . '.data', $form->filter($data));

		$this->setRedirect($redirectUrl);
		$this->redirect();
	}

	/**
	 * Load item to edit associations in com_associations
	 *
	 * @return  void
	 *
	 * @since   3.9.0
	 */
	public function editAssociations()
	{
		// Initialise variables.
		$app   = \JFactory::getApplication();
		$input = $app->input;
		$model = $this->getModel();

		$data = $input->get('jform', array(), 'array');
		$model->editAssociations($data);
	}
}
src/MVC/View/CategoryView.php000064400000017773152177723700012045 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\View;

defined('JPATH_PLATFORM') or die;

/**
 * Base HTML View class for the a Category list
 *
 * @since  3.2
 */
class CategoryView extends HtmlView
{
	/**
	 * State data
	 *
	 * @var    \Joomla\Registry\Registry
	 * @since  3.2
	 */
	protected $state;

	/**
	 * Category items data
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $items;

	/**
	 * The category model object for this category
	 *
	 * @var    \JModelCategory
	 * @since  3.2
	 */
	protected $category;

	/**
	 * The list of other categories for this extension.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $categories;

	/**
	 * Pagination object
	 *
	 * @var    \JPagination
	 * @since  3.2
	 */
	protected $pagination;

	/**
	 * Child objects
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $children;

	/**
	 * The name of the extension for the category
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $extension;

	/**
	 * The name of the view to link individual items to
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $viewName;

	/**
	 * Default title to use for page title
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $defaultPageTitle;

	/**
	 * Whether to run the standard Joomla plugin events.
	 * Off by default for b/c
	 *
	 * @var    bool
	 * @since  3.5
	 */
	protected $runPlugins = false;

	/**
	 * Method with common display elements used in category list displays
	 *
	 * @return  boolean|\JException|void  Boolean false or \JException instance on error, nothing otherwise
	 *
	 * @since   3.2
	 */
	public function commonCategoryDisplay()
	{
		$app    = \JFactory::getApplication();
		$user   = \JFactory::getUser();
		$params = $app->getParams();

		// Get some data from the models
		$model       = $this->getModel();
		$paramsModel = $model->getState('params');

		$paramsModel->set('check_access_rights', 0);
		$model->setState('params', $paramsModel);

		$state       = $this->get('State');
		$category    = $this->get('Category');
		$children    = $this->get('Children');
		$parent      = $this->get('Parent');

		if ($category == false)
		{
			return \JError::raiseError(404, \JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
		}

		if ($parent == false)
		{
			return \JError::raiseError(404, \JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
		}

		// Check whether category access level allows access.
		$groups = $user->getAuthorisedViewLevels();

		if (!in_array($category->access, $groups))
		{
			return \JError::raiseError(403, \JText::_('JERROR_ALERTNOAUTHOR'));
		}

		$items      = $this->get('Items');
		$pagination = $this->get('Pagination');

		// Check for errors.
		if (count($errors = $this->get('Errors')))
		{
			\JError::raiseError(500, implode("\n", $errors));

			return false;
		}

		// Setup the category parameters.
		$cparams          = $category->getParams();
		$category->params = clone $params;
		$category->params->merge($cparams);

		$children = array($category->id => $children);

		// Escape strings for HTML output
		$this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx'));

		if ($this->runPlugins)
		{
			\JPluginHelper::importPlugin('content');

			foreach ($items as $itemElement)
			{
				$itemElement = (object) $itemElement;
				$itemElement->event = new \stdClass;

				// For some plugins.
				!empty($itemElement->description) ? $itemElement->text = $itemElement->description : $itemElement->text = null;

				$dispatcher = \JEventDispatcher::getInstance();

				$dispatcher->trigger('onContentPrepare', array($this->extension . '.category', &$itemElement, &$itemElement->params, 0));

				$results = $dispatcher->trigger('onContentAfterTitle', array($this->extension . '.category', &$itemElement, &$itemElement->core_params, 0));
				$itemElement->event->afterDisplayTitle = trim(implode("\n", $results));

				$results = $dispatcher->trigger('onContentBeforeDisplay', array($this->extension . '.category', &$itemElement, &$itemElement->core_params, 0));
				$itemElement->event->beforeDisplayContent = trim(implode("\n", $results));

				$results = $dispatcher->trigger('onContentAfterDisplay', array($this->extension . '.category', &$itemElement, &$itemElement->core_params, 0));
				$itemElement->event->afterDisplayContent = trim(implode("\n", $results));

				if ($itemElement->text)
				{
					$itemElement->description = $itemElement->text;
				}
			}
		}

		$maxLevel         = $params->get('maxLevel', -1) < 0 ? PHP_INT_MAX : $params->get('maxLevel', PHP_INT_MAX);
		$this->maxLevel   = &$maxLevel;
		$this->state      = &$state;
		$this->items      = &$items;
		$this->category   = &$category;
		$this->children   = &$children;
		$this->params     = &$params;
		$this->parent     = &$parent;
		$this->pagination = &$pagination;
		$this->user       = &$user;

		// Check for layout override only if this is not the active menu item
		// If it is the active menu item, then the view and category id will match
		$active = $app->getMenu()->getActive();

		if ($active
			&& $active->component == $this->extension
			&& isset($active->query['view'], $active->query['id'])
			&& $active->query['view'] == 'category'
			&& $active->query['id'] == $this->category->id)
		{
			if (isset($active->query['layout']))
			{
				$this->setLayout($active->query['layout']);
			}
		}
		elseif ($layout = $category->params->get('category_layout'))
		{
			$this->setLayout($layout);
		}

		$this->category->tags = new \JHelperTags;
		$this->category->tags->getItemTags($this->extension . '.category', $this->category->id);
	}

	/**
	 * Execute and display a template script.
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  mixed  A string if successful, otherwise an Error object.
	 *
	 * @since   3.2
	 */
	public function display($tpl = null)
	{
		$this->prepareDocument();

		return parent::display($tpl);
	}

	/**
	 * Method to prepares the document
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function prepareDocument()
	{
		$app           = \JFactory::getApplication();
		$menus         = $app->getMenu();
		$this->pathway = $app->getPathway();
		$title         = null;

		// Because the application sets a default page title, we need to get it from the menu item itself
		$this->menu = $menus->getActive();

		if ($this->menu)
		{
			$this->params->def('page_heading', $this->params->get('page_title', $this->menu->title));
		}
		else
		{
			$this->params->def('page_heading', \JText::_($this->defaultPageTitle));
		}

		$title = $this->params->get('page_title', '');

		if (empty($title))
		{
			$title = $app->get('sitename');
		}
		elseif ($app->get('sitename_pagetitles', 0) == 1)
		{
			$title = \JText::sprintf('JPAGETITLE', $app->get('sitename'), $title);
		}
		elseif ($app->get('sitename_pagetitles', 0) == 2)
		{
			$title = \JText::sprintf('JPAGETITLE', $title, $app->get('sitename'));
		}

		$this->document->setTitle($title);

		if ($this->params->get('menu-meta_description'))
		{
			$this->document->setDescription($this->params->get('menu-meta_description'));
		}

		if ($this->params->get('menu-meta_keywords'))
		{
			$this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords'));
		}

		if ($this->params->get('robots'))
		{
			$this->document->setMetadata('robots', $this->params->get('robots'));
		}
	}

	/**
	 * Method to add an alternative feed link to a category layout.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function addFeed()
	{
		if ($this->params->get('show_feed_link', 1) == 1)
		{
			$link    = '&format=feed&limitstart=';
			$attribs = array('type' => 'application/rss+xml', 'title' => 'RSS 2.0');
			$this->document->addHeadLink(\JRoute::_($link . '&type=rss'), 'alternate', 'rel', $attribs);
			$attribs = array('type' => 'application/atom+xml', 'title' => 'Atom 1.0');
			$this->document->addHeadLink(\JRoute::_($link . '&type=atom'), 'alternate', 'rel', $attribs);
		}
	}
}
src/MVC/View/CategoriesView.php000064400000006447152177723700012351 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\View;

defined('JPATH_PLATFORM') or die;

/**
 * Categories view base class.
 *
 * @since  3.2
 */
class CategoriesView extends HtmlView
{
	/**
	 * State data
	 *
	 * @var    \Joomla\Registry\Registry
	 * @since  3.2
	 */
	protected $state;

	/**
	 * Category items data
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $items;

	/**
	 * Language key for default page heading
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $pageHeading;

	/**
	 * Execute and display a template script.
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  mixed  A string if successful, otherwise an Error object.
	 *
	 * @since   3.2
	 */
	public function display($tpl = null)
	{
		$state  = $this->get('State');
		$items  = $this->get('Items');
		$parent = $this->get('Parent');

		$app = \JFactory::getApplication();

		// Check for errors.
		if (count($errors = $this->get('Errors')))
		{
			$app->enqueueMessage($errors, 'error');

			return false;
		}

		if ($items === false)
		{
			$app->enqueueMessage(\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'), 'error');

			return false;
		}

		if ($parent == false)
		{
			$app->enqueueMessage(\JText::_('JGLOBAL_CATEGORY_NOT_FOUND'), 'error');

			return false;
		}

		$params = &$state->params;

		$items = array($parent->id => $items);

		// Escape strings for HTML output
		$this->pageclass_sfx = htmlspecialchars($params->get('pageclass_sfx'), ENT_COMPAT, 'UTF-8');

		$this->maxLevelcat = $params->get('maxLevelcat', -1) < 0 ? PHP_INT_MAX : $params->get('maxLevelcat', PHP_INT_MAX);
		$this->params      = &$params;
		$this->parent      = &$parent;
		$this->items       = &$items;

		$this->prepareDocument();

		return parent::display($tpl);
	}

	/**
	 * Prepares the document
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function prepareDocument()
	{
		$app   = \JFactory::getApplication();
		$menus = $app->getMenu();

		// Because the application sets a default page title, we need to get it from the menu item itself
		$menu = $menus->getActive();

		if ($menu)
		{
			$this->params->def('page_heading', $this->params->get('page_title', $menu->title));
		}
		else
		{
			$this->params->def('page_heading', \JText::_($this->pageHeading));
		}

		$title = $this->params->get('page_title', '');

		if (empty($title))
		{
			$title = $app->get('sitename');
		}
		elseif ($app->get('sitename_pagetitles', 0) == 1)
		{
			$title = \JText::sprintf('JPAGETITLE', $app->get('sitename'), $title);
		}
		elseif ($app->get('sitename_pagetitles', 0) == 2)
		{
			$title = \JText::sprintf('JPAGETITLE', $title, $app->get('sitename'));
		}

		$this->document->setTitle($title);

		if ($this->params->get('menu-meta_description'))
		{
			$this->document->setDescription($this->params->get('menu-meta_description'));
		}

		if ($this->params->get('menu-meta_keywords'))
		{
			$this->document->setMetadata('keywords', $this->params->get('menu-meta_keywords'));
		}

		if ($this->params->get('robots'))
		{
			$this->document->setMetadata('robots', $this->params->get('robots'));
		}
	}
}
src/MVC/View/CategoryFeedView.php000064400000007726152177723700012626 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\View;

defined('JPATH_PLATFORM') or die;

/**
 * Base feed View class for a category
 *
 * @since  3.2
 */
class CategoryFeedView extends HtmlView
{
	/**
	 * Execute and display a template script.
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  mixed  A string if successful, otherwise an Error object.
	 *
	 * @since   3.2
	 */
	public function display($tpl = null)
	{
		$app      = \JFactory::getApplication();
		$document = \JFactory::getDocument();

		$extension      = $app->input->getString('option');
		$contentType = $extension . '.' . $this->viewName;

		$ucmType = new \JUcmType;
		$ucmRow = $ucmType->getTypeByAlias($contentType);
		$ucmMapCommon = json_decode($ucmRow->field_mappings)->common;
		$createdField = null;
		$titleField = null;

		if (is_object($ucmMapCommon))
		{
			$createdField = $ucmMapCommon->core_created_time;
			$titleField = $ucmMapCommon->core_title;
		}
		elseif (is_array($ucmMapCommon))
		{
			$createdField = $ucmMapCommon[0]->core_created_time;
			$titleField = $ucmMapCommon[0]->core_title;
		}

		$document->link = \JRoute::_(\JHelperRoute::getCategoryRoute($app->input->getInt('id'), $language = 0, $extension));

		$app->input->set('limit', $app->get('feed_limit'));
		$siteEmail        = $app->get('mailfrom');
		$fromName         = $app->get('fromname');
		$feedEmail        = $app->get('feed_email', 'none');
		$document->editor = $fromName;

		if ($feedEmail !== 'none')
		{
			$document->editorEmail = $siteEmail;
		}

		// Get some data from the model
		$items    = $this->get('Items');
		$category = $this->get('Category');

		// Don't display feed if category id missing or non existent
		if ($category == false || $category->alias === 'root')
		{
			return \JError::raiseError(404, \JText::_('JGLOBAL_CATEGORY_NOT_FOUND'));
		}

		foreach ($items as $item)
		{
			$this->reconcileNames($item);

			// Strip html from feed item title
			if ($titleField)
			{
				$title = $this->escape($item->$titleField);
				$title = html_entity_decode($title, ENT_COMPAT, 'UTF-8');
			}
			else
			{
				$title = '';
			}

			// URL link to article
			$router = new \JHelperRoute;
			$link   = \JRoute::_($router->getRoute($item->id, $contentType, null, null, $item->catid));

			// Strip HTML from feed item description text.
			$description   = $item->description;
			$author        = $item->created_by_alias ?: $item->author;
			$categoryTitle = isset($item->category_title) ? $item->category_title : $category->title;

			if ($createdField)
			{
				$date = isset($item->$createdField) ? date('r', strtotime($item->$createdField)) : '';
			}
			else
			{
				$date = '';
			}

			// Load individual item creator class.
			$feeditem              = new \JFeedItem;
			$feeditem->title       = $title;
			$feeditem->link        = $link;
			$feeditem->description = $description;
			$feeditem->date        = $date;
			$feeditem->category    = $categoryTitle;
			$feeditem->author      = $author;

			// We don't have the author email so we have to use site in both cases.
			if ($feedEmail === 'site')
			{
				$feeditem->authorEmail = $siteEmail;
			}
			elseif ($feedEmail === 'author')
			{
				$feeditem->authorEmail = $item->author_email;
			}

			// Loads item information into RSS array
			$document->addItem($feeditem);
		}
	}

	/**
	 * Method to reconcile non standard names from components to usage in this class.
	 * Typically overriden in the component feed view class.
	 *
	 * @param   object  $item  The item for a feed, an element of the $items array.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function reconcileNames($item)
	{
		if (!property_exists($item, 'title') && property_exists($item, 'name'))
		{
			$item->title = $item->name;
		}
	}
}
src/MVC/View/HtmlView.php000064400000047357152177723700011175 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\View;

defined('JPATH_PLATFORM') or die;

/**
 * Base class for a Joomla View
 *
 * Class holding methods for displaying presentation data.
 *
 * @since  2.5.5
 */
class HtmlView extends \JObject
{
	/**
	 * The active document object
	 *
	 * @var    \JDocument
	 * @since  3.0
	 */
	public $document;

	/**
	 * The name of the view
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $_name = null;

	/**
	 * Registered models
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $_models = array();

	/**
	 * The base path of the view
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_basePath = null;

	/**
	 * The default model
	 *
	 * @var	   string
	 * @since  3.0
	 */
	protected $_defaultModel = null;

	/**
	 * Layout name
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_layout = 'default';

	/**
	 * Layout extension
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_layoutExt = 'php';

	/**
	 * Layout template
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_layoutTemplate = '_';

	/**
	 * The set of search directories for resources (templates)
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $_path = array('template' => array(), 'helper' => array());

	/**
	 * The name of the default template source file.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_template = null;

	/**
	 * The output of the template script.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_output = null;

	/**
	 * Callback for escaping.
	 *
	 * @var    string
	 * @since  3.0
	 * @deprecated  3.0
	 */
	protected $_escape = 'htmlspecialchars';

	/**
	 * Charset to use in escaping mechanisms; defaults to urf8 (UTF-8)
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $_charset = 'UTF-8';

	/**
	 * Constructor
	 *
	 * @param   array  $config  A named configuration array for object construction.
	 *                          name: the name (optional) of the view (defaults to the view class name suffix).
	 *                          charset: the character set to use for display
	 *                          escape: the name (optional) of the function to use for escaping strings
	 *                          base_path: the parent path (optional) of the views directory (defaults to the component folder)
	 *                          template_plath: the path (optional) of the layout directory (defaults to base_path + /views/ + view name
	 *                          helper_path: the path (optional) of the helper files (defaults to base_path + /helpers/)
	 *                          layout: the layout (optional) to use to display the view
	 *
	 * @since   3.0
	 */
	public function __construct($config = array())
	{
		// Set the view name
		if (empty($this->_name))
		{
			if (array_key_exists('name', $config))
			{
				$this->_name = $config['name'];
			}
			else
			{
				$this->_name = $this->getName();
			}
		}

		// Set the charset (used by the variable escaping functions)
		if (array_key_exists('charset', $config))
		{
			\JLog::add('Setting a custom charset for escaping is deprecated. Override \JViewLegacy::escape() instead.', \JLog::WARNING, 'deprecated');
			$this->_charset = $config['charset'];
		}

		// User-defined escaping callback
		if (array_key_exists('escape', $config))
		{
			$this->setEscape($config['escape']);
		}

		// Set a base path for use by the view
		if (array_key_exists('base_path', $config))
		{
			$this->_basePath = $config['base_path'];
		}
		else
		{
			$this->_basePath = JPATH_COMPONENT;
		}

		// Set the default template search path
		if (array_key_exists('template_path', $config))
		{
			// User-defined dirs
			$this->_setPath('template', $config['template_path']);
		}
		elseif (is_dir($this->_basePath . '/view'))
		{
			$this->_setPath('template', $this->_basePath . '/view/' . $this->getName() . '/tmpl');
		}
		else
		{
			$this->_setPath('template', $this->_basePath . '/views/' . $this->getName() . '/tmpl');
		}

		// Set the default helper search path
		if (array_key_exists('helper_path', $config))
		{
			// User-defined dirs
			$this->_setPath('helper', $config['helper_path']);
		}
		else
		{
			$this->_setPath('helper', $this->_basePath . '/helpers');
		}

		// Set the layout
		if (array_key_exists('layout', $config))
		{
			$this->setLayout($config['layout']);
		}
		else
		{
			$this->setLayout('default');
		}

		$this->baseurl = \JUri::base(true);
	}

	/**
	 * Execute and display a template script.
	 *
	 * @param   string  $tpl  The name of the template file to parse; automatically searches through the template paths.
	 *
	 * @return  mixed  A string if successful, otherwise an Error object.
	 *
	 * @see     \JViewLegacy::loadTemplate()
	 * @since   3.0
	 */
	public function display($tpl = null)
	{
		$result = $this->loadTemplate($tpl);

		if ($result instanceof \Exception)
		{
			return $result;
		}

		echo $result;
	}

	/**
	 * Assigns variables to the view script via differing strategies.
	 *
	 * This method is overloaded; you can assign all the properties of
	 * an object, an associative array, or a single value by name.
	 *
	 * You are not allowed to set variables that begin with an underscore;
	 * these are either private properties for \JView or private variables
	 * within the template script itself.
	 *
	 * <code>
	 * $view = new \Joomla\CMS\View\HtmlView;
	 *
	 * // Assign directly
	 * $view->var1 = 'something';
	 * $view->var2 = 'else';
	 *
	 * // Assign by name and value
	 * $view->assign('var1', 'something');
	 * $view->assign('var2', 'else');
	 *
	 * // Assign by assoc-array
	 * $ary = array('var1' => 'something', 'var2' => 'else');
	 * $view->assign($obj);
	 *
	 * // Assign by object
	 * $obj = new \stdClass;
	 * $obj->var1 = 'something';
	 * $obj->var2 = 'else';
	 * $view->assign($obj);
	 *
	 * </code>
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   3.0
	 * @deprecated  3.0 Use native PHP syntax.
	 */
	public function assign()
	{
		\JLog::add(__METHOD__ . ' is deprecated. Use native PHP syntax.', \JLog::WARNING, 'deprecated');

		// Get the arguments; there may be 1 or 2.
		$arg0 = @func_get_arg(0);
		$arg1 = @func_get_arg(1);

		// Assign by object
		if (is_object($arg0))
		{
			// Assign public properties
			foreach (get_object_vars($arg0) as $key => $val)
			{
				if (strpos($key, '_') !== 0)
				{
					$this->$key = $val;
				}
			}

			return true;
		}

		// Assign by associative array
		if (is_array($arg0))
		{
			foreach ($arg0 as $key => $val)
			{
				if (strpos($key, '_') !== 0)
				{
					$this->$key = $val;
				}
			}

			return true;
		}

		// Assign by string name and mixed value.

		// We use array_key_exists() instead of isset() because isset()
		// fails if the value is set to null.
		if (is_string($arg0) && strpos($arg0, '_') !== 0 && func_num_args() > 1)
		{
			$this->$arg0 = $arg1;

			return true;
		}

		// $arg0 was not object, array, or string.
		return false;
	}

	/**
	 * Assign variable for the view (by reference).
	 *
	 * You are not allowed to set variables that begin with an underscore;
	 * these are either private properties for \JView or private variables
	 * within the template script itself.
	 *
	 * <code>
	 * $view = new \JView;
	 *
	 * // Assign by name and value
	 * $view->assignRef('var1', $ref);
	 *
	 * // Assign directly
	 * $view->var1 = &$ref;
	 * </code>
	 *
	 * @param   string  $key   The name for the reference in the view.
	 * @param   mixed   &$val  The referenced variable.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   3.0
	 * @deprecated  3.0  Use native PHP syntax.
	 */
	public function assignRef($key, &$val)
	{
		\JLog::add(__METHOD__ . ' is deprecated. Use native PHP syntax.', \JLog::WARNING, 'deprecated');

		if (is_string($key) && strpos($key, '_') !== 0)
		{
			$this->$key = &$val;

			return true;
		}

		return false;
	}

	/**
	 * Escapes a value for output in a view script.
	 *
	 * If escaping mechanism is either htmlspecialchars or htmlentities, uses
	 * {@link $_encoding} setting.
	 *
	 * @param   mixed  $var  The output to escape.
	 *
	 * @return  mixed  The escaped value.
	 *
	 * @note the ENT_COMPAT flag will be replaced by ENT_QUOTES in Joomla 4.0 to also escape single quotes
	 *
	 * @since   3.0
	 */
	public function escape($var)
	{
		if (in_array($this->_escape, array('htmlspecialchars', 'htmlentities')))
		{
			return call_user_func($this->_escape, $var, ENT_COMPAT, $this->_charset);
		}

		return call_user_func($this->_escape, $var);
	}

	/**
	 * Method to get data from a registered model or a property of the view
	 *
	 * @param   string  $property  The name of the method to call on the model or the property to get
	 * @param   string  $default   The name of the model to reference or the default value [optional]
	 *
	 * @return  mixed  The return value of the method
	 *
	 * @since   3.0
	 */
	public function get($property, $default = null)
	{
		// If $model is null we use the default model
		if ($default === null)
		{
			$model = $this->_defaultModel;
		}
		else
		{
			$model = strtolower($default);
		}

		// First check to make sure the model requested exists
		if (isset($this->_models[$model]))
		{
			// Model exists, let's build the method name
			$method = 'get' . ucfirst($property);

			// Does the method exist?
			if (method_exists($this->_models[$model], $method))
			{
				// The method exists, let's call it and return what we get
				$result = $this->_models[$model]->$method();

				return $result;
			}
		}

		// Degrade to \JObject::get
		$result = parent::get($property, $default);

		return $result;
	}

	/**
	 * Method to get the model object
	 *
	 * @param   string  $name  The name of the model (optional)
	 *
	 * @return  mixed  \JModelLegacy object
	 *
	 * @since   3.0
	 */
	public function getModel($name = null)
	{
		if ($name === null)
		{
			$name = $this->_defaultModel;
		}

		return $this->_models[strtolower($name)];
	}

	/**
	 * Get the layout.
	 *
	 * @return  string  The layout name
	 *
	 * @since   3.0
	 */
	public function getLayout()
	{
		return $this->_layout;
	}

	/**
	 * Get the layout template.
	 *
	 * @return  string  The layout template name
	 *
	 * @since   3.0
	 */
	public function getLayoutTemplate()
	{
		return $this->_layoutTemplate;
	}

	/**
	 * Method to get the view name
	 *
	 * The model name by default parsed using the classname, or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @return  string  The name of the model
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function getName()
	{
		if (empty($this->_name))
		{
			$classname = get_class($this);
			$viewpos = strpos($classname, 'View');

			if ($viewpos === false)
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'), 500);
			}

			$this->_name = strtolower(substr($classname, $viewpos + 4));
		}

		return $this->_name;
	}

	/**
	 * Method to add a model to the view.  We support a multiple model single
	 * view system by which models are referenced by classname.  A caveat to the
	 * classname referencing is that any classname prepended by \JModel will be
	 * referenced by the name without \JModel, eg. \JModelCategory is just
	 * Category.
	 *
	 * @param   \JModelLegacy  $model    The model to add to the view.
	 * @param   boolean        $default  Is this the default model?
	 *
	 * @return  \JModelLegacy  The added model.
	 *
	 * @since   3.0
	 */
	public function setModel($model, $default = false)
	{
		$name = strtolower($model->getName());
		$this->_models[$name] = $model;

		if ($default)
		{
			$this->_defaultModel = $name;
		}

		return $model;
	}

	/**
	 * Sets the layout name to use
	 *
	 * @param   string  $layout  The layout name or a string in format <template>:<layout file>
	 *
	 * @return  string  Previous value.
	 *
	 * @since   3.0
	 */
	public function setLayout($layout)
	{
		$previous = $this->_layout;

		if (strpos($layout, ':') === false)
		{
			$this->_layout = $layout;
		}
		else
		{
			// Convert parameter to array based on :
			$temp = explode(':', $layout);
			$this->_layout = $temp[1];

			// Set layout template
			$this->_layoutTemplate = $temp[0];
		}

		return $previous;
	}

	/**
	 * Allows a different extension for the layout files to be used
	 *
	 * @param   string  $value  The extension.
	 *
	 * @return  string  Previous value
	 *
	 * @since   3.0
	 */
	public function setLayoutExt($value)
	{
		$previous = $this->_layoutExt;

		if ($value = preg_replace('#[^A-Za-z0-9]#', '', trim($value)))
		{
			$this->_layoutExt = $value;
		}

		return $previous;
	}

	/**
	 * Sets the _escape() callback.
	 *
	 * @param   mixed  $spec  The callback for _escape() to use.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 * @deprecated  3.0  Override \JViewLegacy::escape() instead.
	 */
	public function setEscape($spec)
	{
		\JLog::add(__METHOD__ . ' is deprecated. Override \JViewLegacy::escape() instead.', \JLog::WARNING, 'deprecated');

		$this->_escape = $spec;
	}

	/**
	 * Adds to the stack of view script paths in LIFO order.
	 *
	 * @param   mixed  $path  A directory path or an array of paths.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function addTemplatePath($path)
	{
		$this->_addPath('template', $path);
	}

	/**
	 * Adds to the stack of helper script paths in LIFO order.
	 *
	 * @param   mixed  $path  A directory path or an array of paths.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function addHelperPath($path)
	{
		$this->_addPath('helper', $path);
	}

	/**
	 * Load a template file -- first look in the templates folder for an override
	 *
	 * @param   string  $tpl  The name of the template source file; automatically searches the template paths and compiles as needed.
	 *
	 * @return  string  The output of the the template script.
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function loadTemplate($tpl = null)
	{
		// Clear prior output
		$this->_output = null;

		$template = \JFactory::getApplication()->getTemplate();
		$layout = $this->getLayout();
		$layoutTemplate = $this->getLayoutTemplate();

		// Create the template file name based on the layout
		$file = isset($tpl) ? $layout . '_' . $tpl : $layout;

		// Clean the file name
		$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $file);
		$tpl = isset($tpl) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $tpl) : $tpl;

		// Load the language file for the template
		$lang = \JFactory::getLanguage();
		$lang->load('tpl_' . $template, JPATH_BASE, null, false, true)
			|| $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", null, false, true);

		// Change the template folder if alternative layout is in different template
		if (isset($layoutTemplate) && $layoutTemplate !== '_' && $layoutTemplate != $template)
		{
			$this->_path['template'] = str_replace($template, $layoutTemplate, $this->_path['template']);
		}

		// Load the template script
		jimport('joomla.filesystem.path');
		$filetofind = $this->_createFileName('template', array('name' => $file));
		$this->_template = \JPath::find($this->_path['template'], $filetofind);

		// If alternate layout can't be found, fall back to default layout
		if ($this->_template == false)
		{
			$filetofind = $this->_createFileName('', array('name' => 'default' . (isset($tpl) ? '_' . $tpl : $tpl)));
			$this->_template = \JPath::find($this->_path['template'], $filetofind);
		}

		if ($this->_template != false)
		{
			// Unset so as not to introduce into template scope
			unset($tpl, $file);

			// Never allow a 'this' property
			if (isset($this->this))
			{
				unset($this->this);
			}

			// Start capturing output into a buffer
			ob_start();

			// Include the requested template filename in the local scope
			// (this will execute the view logic).
			include $this->_template;

			// Done with the requested template; get the buffer and
			// clear it.
			$this->_output = ob_get_contents();
			ob_end_clean();

			return $this->_output;
		}
		else
		{
			throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $file), 500);
		}
	}

	/**
	 * Load a helper file
	 *
	 * @param   string  $hlp  The name of the helper source file automatically searches the helper paths and compiles as needed.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function loadHelper($hlp = null)
	{
		// Clean the file name
		$file = preg_replace('/[^A-Z0-9_\.-]/i', '', $hlp);

		// Load the template script
		jimport('joomla.filesystem.path');
		$helper = \JPath::find($this->_path['helper'], $this->_createFileName('helper', array('name' => $file)));

		if ($helper != false)
		{
			// Include the requested template filename in the local scope
			include_once $helper;
		}
	}

	/**
	 * Sets an entire array of search paths for templates or resources.
	 *
	 * @param   string  $type  The type of path to set, typically 'template'.
	 * @param   mixed   $path  The new search path, or an array of search paths.  If null or false, resets to the current directory only.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function _setPath($type, $path)
	{
		$component = \JApplicationHelper::getComponentName();
		$app = \JFactory::getApplication();

		// Clear out the prior search dirs
		$this->_path[$type] = array();

		// Actually add the user-specified directories
		$this->_addPath($type, $path);

		// Always add the fallback directories as last resort
		switch (strtolower($type))
		{
			case 'template':
				// Set the alternative template search dir
				if (isset($app))
				{
					$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $component);
					$fallback = JPATH_THEMES . '/' . $app->getTemplate() . '/html/' . $component . '/' . $this->getName();
					$this->_addPath('template', $fallback);
				}
				break;
		}
	}

	/**
	 * Adds to the search path for templates and resources.
	 *
	 * @param   string  $type  The type of path to add.
	 * @param   mixed   $path  The directory or stream, or an array of either, to search.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function _addPath($type, $path)
	{
		jimport('joomla.filesystem.path');

		// Loop through the path directories
		foreach ((array) $path as $dir)
		{
			// Clean up the path
			$dir = \JPath::clean($dir);

			// Add trailing separators as needed
			if (substr($dir, -1) != DIRECTORY_SEPARATOR)
			{
				// Directory
				$dir .= DIRECTORY_SEPARATOR;
			}

			// Add to the top of the search dirs
			array_unshift($this->_path[$type], $dir);
		}
	}

	/**
	 * Create the filename for a resource
	 *
	 * @param   string  $type   The resource type to create the filename for
	 * @param   array   $parts  An associative array of filename information
	 *
	 * @return  string  The filename
	 *
	 * @since   3.0
	 */
	protected function _createFileName($type, $parts = array())
	{
		switch ($type)
		{
			case 'template':
				$filename = strtolower($parts['name']) . '.' . $this->_layoutExt;
				break;

			default:
				$filename = strtolower($parts['name']) . '.php';
				break;
		}

		return $filename;
	}

	/**
	 * Returns the form object
	 *
	 * @return  mixed  A \JForm object on success, false on failure
	 *
	 * @since   3.2
	 */
	public function getForm()
	{
		if (!is_object($this->form))
		{
			$this->form = $this->get('Form');
		}

		return $this->form;
	}

	/**
	 * Sets the document title according to Global Configuration options
	 *
	 * @param   string  $title  The page title
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function setDocumentTitle($title)
	{
		$app = \JFactory::getApplication();

		// Check for empty title and add site name if param is set
		if (empty($title))
		{
			$title = $app->get('sitename');
		}
		elseif ($app->get('sitename_pagetitles', 0) == 1)
		{
			$title = \JText::sprintf('JPAGETITLE', $app->get('sitename'), $title);
		}
		elseif ($app->get('sitename_pagetitles', 0) == 2)
		{
			$title = \JText::sprintf('JPAGETITLE', $title, $app->get('sitename'));
		}

		$this->document->setTitle($title);
	}
}
src/MVC/Model/ListModel.php000064400000045764152177723700011460 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Model class for handling lists of items.
 *
 * @since  1.6
 */
class ListModel extends BaseDatabaseModel
{
	/**
	 * Internal memory based cache array of data.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected $cache = array();

	/**
	 * Context string for the model type.  This is used to handle uniqueness
	 * when dealing with the getStoreId() method and caching data structures.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $context = null;

	/**
	 * Valid filter fields or ordering.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected $filter_fields = array();

	/**
	 * An internal cache for the last query used.
	 *
	 * @var    \JDatabaseQuery[]
	 * @since  1.6
	 */
	protected $query = array();

	/**
	 * Name of the filter form to load
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $filterFormName = null;

	/**
	 * Associated HTML form
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $htmlFormName = 'adminForm';

	/**
	 * A blacklist of filter variables to not merge into the model's state
	 *
	 * @var    array
	 * @since  3.4.5
	 */
	protected $filterBlacklist = array();

	/**
	 * A blacklist of list variables to not merge into the model's state
	 *
	 * @var    array
	 * @since  3.4.5
	 */
	protected $listBlacklist = array('select');

	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     \JModelLegacy
	 * @since   1.6
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		// Add the ordering filtering fields whitelist.
		if (isset($config['filter_fields']))
		{
			$this->filter_fields = $config['filter_fields'];
		}

		// Guess the context as Option.ModelName.
		if (empty($this->context))
		{
			$this->context = strtolower($this->option . '.' . $this->getName());
		}
	}

	/**
	 * Method to cache the last query constructed.
	 *
	 * This method ensures that the query is constructed only once for a given state of the model.
	 *
	 * @return  \JDatabaseQuery  A \JDatabaseQuery object
	 *
	 * @since   1.6
	 */
	protected function _getListQuery()
	{
		// Capture the last store id used.
		static $lastStoreId;

		// Compute the current store id.
		$currentStoreId = $this->getStoreId();

		// If the last store id is different from the current, refresh the query.
		if ($lastStoreId != $currentStoreId || empty($this->query))
		{
			$lastStoreId = $currentStoreId;
			$this->query = $this->getListQuery();
		}

		return $this->query;
	}

	/**
	 * Function to get the active filters
	 *
	 * @return  array  Associative array in the format: array('filter_published' => 0)
	 *
	 * @since   3.2
	 */
	public function getActiveFilters()
	{
		$activeFilters = array();

		if (!empty($this->filter_fields))
		{
			foreach ($this->filter_fields as $filter)
			{
				$filterName = 'filter.' . $filter;

				if (property_exists($this->state, $filterName) && (!empty($this->state->{$filterName}) || is_numeric($this->state->{$filterName})))
				{
					$activeFilters[$filter] = $this->state->get($filterName);
				}
			}
		}

		return $activeFilters;
	}

	/**
	 * Method to get an array of data items.
	 *
	 * @return  mixed  An array of data items on success, false on failure.
	 *
	 * @since   1.6
	 */
	public function getItems()
	{
		// Get a storage key.
		$store = $this->getStoreId();

		// Try to load the data from internal storage.
		if (isset($this->cache[$store]))
		{
			return $this->cache[$store];
		}

		try
		{
			// Load the list items and add the items to the internal cache.
			$this->cache[$store] = $this->_getList($this->_getListQuery(), $this->getStart(), $this->getState('list.limit'));
		}
		catch (\RuntimeException $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		return $this->cache[$store];
	}

	/**
	 * Method to get a \JDatabaseQuery object for retrieving the data set from a database.
	 *
	 * @return  \JDatabaseQuery  A \JDatabaseQuery object to retrieve the data set.
	 *
	 * @since   1.6
	 */
	protected function getListQuery()
	{
		return $this->getDbo()->getQuery(true);
	}

	/**
	 * Method to get a \JPagination object for the data set.
	 *
	 * @return  \JPagination  A \JPagination object for the data set.
	 *
	 * @since   1.6
	 */
	public function getPagination()
	{
		// Get a storage key.
		$store = $this->getStoreId('getPagination');

		// Try to load the data from internal storage.
		if (isset($this->cache[$store]))
		{
			return $this->cache[$store];
		}

		$limit = (int) $this->getState('list.limit') - (int) $this->getState('list.links');

		// Create the pagination object and add the object to the internal cache.
		$this->cache[$store] = new \JPagination($this->getTotal(), $this->getStart(), $limit);

		return $this->cache[$store];
	}

	/**
	 * Method to get a store id based on the model configuration state.
	 *
	 * This is necessary because the model is used by the component and
	 * different modules that might need different sets of data or different
	 * ordering requirements.
	 *
	 * @param   string  $id  An identifier string to generate the store id.
	 *
	 * @return  string  A store id.
	 *
	 * @since   1.6
	 */
	protected function getStoreId($id = '')
	{
		// Add the list state to the store id.
		$id .= ':' . $this->getState('list.start');
		$id .= ':' . $this->getState('list.limit');
		$id .= ':' . $this->getState('list.ordering');
		$id .= ':' . $this->getState('list.direction');

		return md5($this->context . ':' . $id);
	}

	/**
	 * Method to get the total number of items for the data set.
	 *
	 * @return  integer  The total number of items available in the data set.
	 *
	 * @since   1.6
	 */
	public function getTotal()
	{
		// Get a storage key.
		$store = $this->getStoreId('getTotal');

		// Try to load the data from internal storage.
		if (isset($this->cache[$store]))
		{
			return $this->cache[$store];
		}

		try
		{
			// Load the total and add the total to the internal cache.
			$this->cache[$store] = (int) $this->_getListCount($this->_getListQuery());
		}
		catch (\RuntimeException $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		return $this->cache[$store];
	}

	/**
	 * Method to get the starting number of items for the data set.
	 *
	 * @return  integer  The starting number of items available in the data set.
	 *
	 * @since   1.6
	 */
	public function getStart()
	{
		$store = $this->getStoreId('getstart');

		// Try to load the data from internal storage.
		if (isset($this->cache[$store]))
		{
			return $this->cache[$store];
		}

		$start = $this->getState('list.start');

		if ($start > 0)
		{
			$limit = $this->getState('list.limit');
			$total = $this->getTotal();

			if ($start > $total - $limit)
			{
				$start = max(0, (int) (ceil($total / $limit) - 1) * $limit);
			}
		}

		// Add the total to the internal cache.
		$this->cache[$store] = $start;

		return $this->cache[$store];
	}

	/**
	 * Get the filter form
	 *
	 * @param   array    $data      data
	 * @param   boolean  $loadData  load current data
	 *
	 * @return  \JForm|boolean  The \JForm object or false on error
	 *
	 * @since   3.2
	 */
	public function getFilterForm($data = array(), $loadData = true)
	{
		$form = null;

		// Try to locate the filter form automatically. Example: ContentModelArticles => "filter_articles"
		if (empty($this->filterFormName))
		{
			$classNameParts = explode('Model', get_called_class());

			if (count($classNameParts) == 2)
			{
				$this->filterFormName = 'filter_' . strtolower($classNameParts[1]);
			}
		}

		if (!empty($this->filterFormName))
		{
			// Get the form.
			$form = $this->loadForm($this->context . '.filter', $this->filterFormName, array('control' => '', 'load_data' => $loadData));
		}

		return $form;
	}

	/**
	 * Method to get a form object.
	 *
	 * @param   string          $name     The name of the form.
	 * @param   string          $source   The form source. Can be XML string if file flag is set to false.
	 * @param   array           $options  Optional array of options for the form creation.
	 * @param   boolean         $clear    Optional argument to force load a new form.
	 * @param   string|boolean  $xpath    An optional xpath to search for the fields.
	 *
	 * @return  \JForm|boolean  \JForm object on success, False on error.
	 *
	 * @see     \JForm
	 * @since   3.2
	 */
	protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
	{
		// Handle the optional arguments.
		$options['control'] = ArrayHelper::getValue((array) $options, 'control', false);

		// Create a signature hash.
		$hash = md5($source . serialize($options));

		// Check if we can use a previously loaded form.
		if (!$clear && isset($this->_forms[$hash]))
		{
			return $this->_forms[$hash];
		}

		// Get the form.
		\JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
		\JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');

		try
		{
			$form = \JForm::getInstance($name, $source, $options, false, $xpath);

			if (isset($options['load_data']) && $options['load_data'])
			{
				// Get the data for the form.
				$data = $this->loadFormData();
			}
			else
			{
				$data = array();
			}

			// Allow for additional modification of the form, and events to be triggered.
			// We pass the data because plugins may require it.
			$this->preprocessForm($form, $data);

			// Load the data into the form after the plugins have operated.
			$form->bind($data);
		}
		catch (\Exception $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		// Store the form for later.
		$this->_forms[$hash] = $form;

		return $form;
	}

	/**
	 * Method to get the data that should be injected in the form.
	 *
	 * @return	mixed	The data for the form.
	 *
	 * @since	3.2
	 */
	protected function loadFormData()
	{
		// Check the session for previously entered form data.
		$data = \JFactory::getApplication()->getUserState($this->context, new \stdClass);

		// Pre-fill the list options
		if (!property_exists($data, 'list'))
		{
			$data->list = array(
				'direction' => $this->getState('list.direction'),
				'limit'     => $this->getState('list.limit'),
				'ordering'  => $this->getState('list.ordering'),
				'start'     => $this->getState('list.start'),
			);
		}

		return $data;
	}

	/**
	 * Method to auto-populate the model state.
	 *
	 * This method should only be called once per instantiation and is designed
	 * to be called on the first call to the getState() method unless the model
	 * configuration flag to ignore the request is set.
	 *
	 * Note. Calling getState in this method will result in recursion.
	 *
	 * @param   string  $ordering   An optional ordering field.
	 * @param   string  $direction  An optional direction (asc|desc).
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	protected function populateState($ordering = null, $direction = null)
	{
		// If the context is set, assume that stateful lists are used.
		if ($this->context)
		{
			$app         = \JFactory::getApplication();
			$inputFilter = \JFilterInput::getInstance();

			// Receive & set filters
			if ($filters = $app->getUserStateFromRequest($this->context . '.filter', 'filter', array(), 'array'))
			{
				foreach ($filters as $name => $value)
				{
					// Exclude if blacklisted
					if (!in_array($name, $this->filterBlacklist))
					{
						$this->setState('filter.' . $name, $value);
					}
				}
			}

			$limit = 0;

			// Receive & set list options
			if ($list = $app->getUserStateFromRequest($this->context . '.list', 'list', array(), 'array'))
			{
				foreach ($list as $name => $value)
				{
					// Exclude if blacklisted
					if (!in_array($name, $this->listBlacklist))
					{
						// Extra validations
						switch ($name)
						{
							case 'fullordering':
								$orderingParts = explode(' ', $value);

								if (count($orderingParts) >= 2)
								{
									// Latest part will be considered the direction
									$fullDirection = end($orderingParts);

									if (in_array(strtoupper($fullDirection), array('ASC', 'DESC', '')))
									{
										$this->setState('list.direction', $fullDirection);
									}
									else
									{
										$this->setState('list.direction', $direction);

										// Fallback to the default value
										$value = $ordering . ' ' . $direction;
									}

									unset($orderingParts[count($orderingParts) - 1]);

									// The rest will be the ordering
									$fullOrdering = implode(' ', $orderingParts);

									if (in_array($fullOrdering, $this->filter_fields))
									{
										$this->setState('list.ordering', $fullOrdering);
									}
									else
									{
										$this->setState('list.ordering', $ordering);

										// Fallback to the default value
										$value = $ordering . ' ' . $direction;
									}
								}
								else
								{
									$this->setState('list.ordering', $ordering);
									$this->setState('list.direction', $direction);

									// Fallback to the default value
									$value = $ordering . ' ' . $direction;
								}
								break;

							case 'ordering':
								if (!in_array($value, $this->filter_fields))
								{
									$value = $ordering;
								}
								break;

							case 'direction':
								if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
								{
									$value = $direction;
								}
								break;

							case 'limit':
								$value = $inputFilter->clean($value, 'int');
								$limit = $value;
								break;

							case 'select':
								$explodedValue = explode(',', $value);

								foreach ($explodedValue as &$field)
								{
									$field = $inputFilter->clean($field, 'cmd');
								}

								$value = implode(',', $explodedValue);
								break;
						}

						$this->setState('list.' . $name, $value);
					}
				}
			}
			else
			// Keep B/C for components previous to jform forms for filters
			{
				// Pre-fill the limits
				$limit = $app->getUserStateFromRequest('global.list.limit', 'limit', $app->get('list_limit'), 'uint');
				$this->setState('list.limit', $limit);

				// Check if the ordering field is in the whitelist, otherwise use the incoming value.
				$value = $app->getUserStateFromRequest($this->context . '.ordercol', 'filter_order', $ordering);

				if (!in_array($value, $this->filter_fields))
				{
					$value = $ordering;
					$app->setUserState($this->context . '.ordercol', $value);
				}

				$this->setState('list.ordering', $value);

				// Check if the ordering direction is valid, otherwise use the incoming value.
				$value = $app->getUserStateFromRequest($this->context . '.orderdirn', 'filter_order_Dir', $direction);

				if (!in_array(strtoupper($value), array('ASC', 'DESC', '')))
				{
					$value = $direction;
					$app->setUserState($this->context . '.orderdirn', $value);
				}

				$this->setState('list.direction', $value);
			}

			// Support old ordering field
			$oldOrdering = $app->input->get('filter_order');

			if (!empty($oldOrdering) && in_array($oldOrdering, $this->filter_fields))
			{
				$this->setState('list.ordering', $oldOrdering);
			}

			// Support old direction field
			$oldDirection = $app->input->get('filter_order_Dir');

			if (!empty($oldDirection) && in_array(strtoupper($oldDirection), array('ASC', 'DESC', '')))
			{
				$this->setState('list.direction', $oldDirection);
			}

			$value = $app->getUserStateFromRequest($this->context . '.limitstart', 'limitstart', 0, 'int');
			$limitstart = ($limit != 0 ? (floor($value / $limit) * $limit) : 0);
			$this->setState('list.start', $limitstart);
		}
		else
		{
			$this->setState('list.start', 0);
			$this->setState('list.limit', 0);
		}
	}

	/**
	 * Method to allow derived classes to preprocess the form.
	 *
	 * @param   \JForm  $form   A \JForm object.
	 * @param   mixed   $data   The data expected for the form.
	 * @param   string  $group  The name of the plugin group to import (defaults to "content").
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @throws  \Exception if there is an error in the form event.
	 */
	protected function preprocessForm(\JForm $form, $data, $group = 'content')
	{
		// Import the appropriate plugin group.
		\JPluginHelper::importPlugin($group);

		// Get the dispatcher.
		$dispatcher = \JEventDispatcher::getInstance();

		// Trigger the form preparation event.
		$results = $dispatcher->trigger('onContentPrepareForm', array($form, $data));

		// Check for errors encountered while preparing the form.
		if (count($results) && in_array(false, $results, true))
		{
			// Get the last error.
			$error = $dispatcher->getError();

			if (!($error instanceof \Exception))
			{
				throw new \Exception($error);
			}
		}
	}

	/**
	 * Gets the value of a user state variable and sets it in the session
	 *
	 * This is the same as the method in \JApplication except that this also can optionally
	 * force you back to the first page when a filter has changed
	 *
	 * @param   string   $key        The key of the user state variable.
	 * @param   string   $request    The name of the variable passed in a request.
	 * @param   string   $default    The default value for the variable if not found. Optional.
	 * @param   string   $type       Filter for the variable, for valid values see {@link \JFilterInput::clean()}. Optional.
	 * @param   boolean  $resetPage  If true, the limitstart in request is set to zero
	 *
	 * @return  mixed  The request user state.
	 *
	 * @since   1.6
	 */
	public function getUserStateFromRequest($key, $request, $default = null, $type = 'none', $resetPage = true)
	{
		$app       = \JFactory::getApplication();
		$input     = $app->input;
		$old_state = $app->getUserState($key);
		$cur_state = $old_state !== null ? $old_state : $default;
		$new_state = $input->get($request, null, $type);

		// BC for Search Tools which uses different naming
		if ($new_state === null && strpos($request, 'filter_') === 0)
		{
			$name    = substr($request, 7);
			$filters = $app->input->get('filter', array(), 'array');

			if (isset($filters[$name]))
			{
				$new_state = $filters[$name];
			}
		}

		if ($cur_state != $new_state && $new_state !== null && $resetPage)
		{
			$input->set('limitstart', 0);
		}

		// Save the new value only if it is set in this request.
		if ($new_state !== null)
		{
			$app->setUserState($key, $new_state);
		}
		else
		{
			$new_state = $cur_state;
		}

		return $new_state;
	}

	/**
	 * Parse and transform the search string into a string fit for regex-ing arbitrary strings against
	 *
	 * @param   string  $search          The search string
	 * @param   string  $regexDelimiter  The regex delimiter to use for the quoting
	 *
	 * @return  string  Search string escaped for regex
	 *
	 * @since   3.4
	 */
	protected function refineSearchStringToRegex($search, $regexDelimiter = '/')
	{
		$searchArr = explode('|', trim($search, ' |'));

		foreach ($searchArr as $key => $searchString)
		{
			if (trim($searchString) === '')
			{
				unset($searchArr[$key]);
				continue;
			}

			$searchArr[$key] = str_replace(' ', '.*', preg_quote(trim($searchString), $regexDelimiter));
		}

		return implode('|', $searchArr);
	}
}
src/MVC/Model/AdminModel.php000064400000113731152177723700011563 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Language\LanguageHelper;

/**
 * Prototype admin model.
 *
 * @since  1.6
 */
abstract class AdminModel extends FormModel
{
	/**
	 * The type alias for this content type (for example, 'com_content.article').
	 *
	 * @var    string
	 * @since  3.8.6
	 */
	public $typeAlias;

	/**
	 * The prefix to use with controller messages.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $text_prefix = null;

	/**
	 * The event to trigger after deleting the data.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $event_after_delete = null;

	/**
	 * The event to trigger after saving the data.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $event_after_save = null;

	/**
	 * The event to trigger before deleting the data.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $event_before_delete = null;

	/**
	 * The event to trigger before saving the data.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $event_before_save = null;

	/**
	 * The event to trigger after changing the published state of the data.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $event_change_state = null;

	/**
	 * Batch copy/move command. If set to false,
	 * the batch copy/move command is not supported
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $batch_copymove = 'category_id';

	/**
	 * Allowed batch commands
	 *
	 * @var    array
	 * @since  3.4
	 */
	protected $batch_commands = array(
		'assetgroup_id' => 'batchAccess',
		'language_id' => 'batchLanguage',
		'tag' => 'batchTag',
	);

	/**
	 * The context used for the associations table
	 *
	 * @var     string
	 * @since   3.4.4
	 */
	protected $associationsContext = null;

	/**
	 * A flag to indicate if member variables for batch actions (and saveorder) have been initialized
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $batchSet = null;

	/**
	 * The user performing the actions (re-usable in batch methods & saveorder(), initialized via initBatch())
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $user = null;

	/**
	 * A JTable instance (of appropropriate type) to manage the DB records (re-usable in batch methods & saveorder(), initialized via initBatch())
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $table = null;

	/**
	 * The class name of the JTable instance managing the DB records (re-usable in batch methods & saveorder(), initialized via initBatch())
	 *
	 * @var     string
	 * @since   3.8.2
	 */
	protected $tableClassName = null;

	/**
	 * UCM Type corresponding to the current model class (re-usable in batch action methods, initialized via initBatch())
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $contentType = null;

	/**
	 * DB data of UCM Type corresponding to the current model class (re-usable in batch action methods, initialized via initBatch())
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $type = null;

	/**
	 * A tags Observer instance to handle assigned tags (re-usable in batch action methods, initialized via initBatch())
	 *
	 * @var     object
	 * @since   3.8.2
	 */
	protected $tagsObserver = null;


	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     \JModelLegacy
	 * @since   1.6
	 */
	public function __construct($config = array())
	{
		parent::__construct($config);

		if (isset($config['event_after_delete']))
		{
			$this->event_after_delete = $config['event_after_delete'];
		}
		elseif (empty($this->event_after_delete))
		{
			$this->event_after_delete = 'onContentAfterDelete';
		}

		if (isset($config['event_after_save']))
		{
			$this->event_after_save = $config['event_after_save'];
		}
		elseif (empty($this->event_after_save))
		{
			$this->event_after_save = 'onContentAfterSave';
		}

		if (isset($config['event_before_delete']))
		{
			$this->event_before_delete = $config['event_before_delete'];
		}
		elseif (empty($this->event_before_delete))
		{
			$this->event_before_delete = 'onContentBeforeDelete';
		}

		if (isset($config['event_before_save']))
		{
			$this->event_before_save = $config['event_before_save'];
		}
		elseif (empty($this->event_before_save))
		{
			$this->event_before_save = 'onContentBeforeSave';
		}

		if (isset($config['event_change_state']))
		{
			$this->event_change_state = $config['event_change_state'];
		}
		elseif (empty($this->event_change_state))
		{
			$this->event_change_state = 'onContentChangeState';
		}

		$config['events_map'] = isset($config['events_map']) ? $config['events_map'] : array();

		$this->events_map = array_merge(
			array(
				'delete'       => 'content',
				'save'         => 'content',
				'change_state' => 'content',
				'validate'     => 'content',
			), $config['events_map']
		);

		// Guess the \JText message prefix. Defaults to the option.
		if (isset($config['text_prefix']))
		{
			$this->text_prefix = strtoupper($config['text_prefix']);
		}
		elseif (empty($this->text_prefix))
		{
			$this->text_prefix = strtoupper($this->option);
		}
	}

	/**
	 * Method to perform batch operations on an item or a set of items.
	 *
	 * @param   array  $commands  An array of commands to perform.
	 * @param   array  $pks       An array of item ids.
	 * @param   array  $contexts  An array of item contexts.
	 *
	 * @return  boolean  Returns true on success, false on failure.
	 *
	 * @since   1.7
	 */
	public function batch($commands, $pks, $contexts)
	{
		// Sanitize ids.
		$pks = array_unique($pks);
		$pks = ArrayHelper::toInteger($pks);

		// Remove any values of zero.
		if (array_search(0, $pks, true))
		{
			unset($pks[array_search(0, $pks, true)]);
		}

		if (empty($pks))
		{
			$this->setError(\JText::_('JGLOBAL_NO_ITEM_SELECTED'));

			return false;
		}

		$done = false;

		// Initialize re-usable member properties
		$this->initBatch();

		if ($this->batch_copymove && !empty($commands[$this->batch_copymove]))
		{
			$cmd = ArrayHelper::getValue($commands, 'move_copy', 'c');

			if ($cmd === 'c')
			{
				$result = $this->batchCopy($commands[$this->batch_copymove], $pks, $contexts);

				if (is_array($result))
				{
					foreach ($result as $old => $new)
					{
						$contexts[$new] = $contexts[$old];
					}

					$pks = array_values($result);
				}
				else
				{
					return false;
				}
			}
			elseif ($cmd === 'm' && !$this->batchMove($commands[$this->batch_copymove], $pks, $contexts))
			{
				return false;
			}

			$done = true;
		}

		foreach ($this->batch_commands as $identifier => $command)
		{
			if (!empty($commands[$identifier]))
			{
				if (!$this->$command($commands[$identifier], $pks, $contexts))
				{
					return false;
				}

				$done = true;
			}
		}

		if (!$done)
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));

			return false;
		}

		// Clear the cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Batch access level changes for a group of rows.
	 *
	 * @param   integer  $value     The new value matching an Asset Group ID.
	 * @param   array    $pks       An array of row IDs.
	 * @param   array    $contexts  An array of item contexts.
	 *
	 * @return  boolean  True if successful, false otherwise and internal error is set.
	 *
	 * @since   1.7
	 */
	protected function batchAccess($value, $pks, $contexts)
	{
		// Initialize re-usable member properties, and re-usable local variables
		$this->initBatch();

		foreach ($pks as $pk)
		{
			if ($this->user->authorise('core.edit', $contexts[$pk]))
			{
				$this->table->reset();
				$this->table->load($pk);
				$this->table->access = (int) $value;

				if (!empty($this->type))
				{
					$this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
				}

				if (!$this->table->store())
				{
					$this->setError($this->table->getError());

					return false;
				}
			}
			else
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

				return false;
			}
		}

		// Clean the cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Batch copy items to a new category or current.
	 *
	 * @param   integer  $value     The new category.
	 * @param   array    $pks       An array of row IDs.
	 * @param   array    $contexts  An array of item contexts.
	 *
	 * @return  array|boolean  An array of new IDs on success, boolean false on failure.
	 *
	 * @since	1.7
	 */
	protected function batchCopy($value, $pks, $contexts)
	{
		// Initialize re-usable member properties, and re-usable local variables
		$this->initBatch();

		$categoryId = $value;

		if (!$this->checkCategoryId($categoryId))
		{
			return false;
		}

		$newIds = array();

		// Parent exists so let's proceed
		while (!empty($pks))
		{
			// Pop the first ID off the stack
			$pk = array_shift($pks);

			$this->table->reset();

			// Check that the row actually exists
			if (!$this->table->load($pk))
			{
				if ($error = $this->table->getError())
				{
					// Fatal error
					$this->setError($error);

					return false;
				}
				else
				{
					// Not fatal error
					$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
					continue;
				}
			}

			$this->generateTitle($categoryId, $this->table);

			// Reset the ID because we are making a copy
			$this->table->id = 0;

			// Unpublish because we are making a copy
			if (isset($this->table->published))
			{
				$this->table->published = 0;
			}
			elseif (isset($this->table->state))
			{
				$this->table->state = 0;
			}

			$hitsAlias = $this->table->getColumnAlias('hits');

			if (isset($this->table->$hitsAlias))
			{
				$this->table->$hitsAlias = 0;
			}

			// New category ID
			$this->table->catid = $categoryId;

			// TODO: Deal with ordering?
			// $this->table->ordering = 1;

			// Check the row.
			if (!$this->table->check())
			{
				$this->setError($this->table->getError());

				return false;
			}

			if (!empty($this->type))
			{
				$this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
			}

			// Store the row.
			if (!$this->table->store())
			{
				$this->setError($this->table->getError());

				return false;
			}

			// Get the new item ID
			$newId = $this->table->get('id');

			$this->cleanupPostBatchCopy($this->table, $newId, $pk);

			// Add the new ID to the array
			$newIds[$pk] = $newId;
		}

		// Clean the cache
		$this->cleanCache();

		return $newIds;
	}

	/**
	 * Function that can be overriden to do any data cleanup after batch copying data
	 *
	 * @param   \JTableInterface  $table  The table object containing the newly created item
	 * @param   integer           $newId  The id of the new item
	 * @param   integer           $oldId  The original item id
	 *
	 * @return  void
	 *
	 * @since  3.8.12
	 */
	protected function cleanupPostBatchCopy(\JTableInterface $table, $newId, $oldId)
	{
	}

	/**
	 * Batch language changes for a group of rows.
	 *
	 * @param   string  $value     The new value matching a language.
	 * @param   array   $pks       An array of row IDs.
	 * @param   array   $contexts  An array of item contexts.
	 *
	 * @return  boolean  True if successful, false otherwise and internal error is set.
	 *
	 * @since   2.5
	 */
	protected function batchLanguage($value, $pks, $contexts)
	{
		// Initialize re-usable member properties, and re-usable local variables
		$this->initBatch();

		foreach ($pks as $pk)
		{
			if ($this->user->authorise('core.edit', $contexts[$pk]))
			{
				$this->table->reset();
				$this->table->load($pk);
				$this->table->language = $value;

				if (!empty($this->type))
				{
					$this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
				}

				if (!$this->table->store())
				{
					$this->setError($this->table->getError());

					return false;
				}
			}
			else
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

				return false;
			}
		}

		// Clean the cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Batch move items to a new category
	 *
	 * @param   integer  $value     The new category ID.
	 * @param   array    $pks       An array of row IDs.
	 * @param   array    $contexts  An array of item contexts.
	 *
	 * @return  boolean  True if successful, false otherwise and internal error is set.
	 *
	 * @since	1.7
	 */
	protected function batchMove($value, $pks, $contexts)
	{
		// Initialize re-usable member properties, and re-usable local variables
		$this->initBatch();

		$categoryId = (int) $value;

		if (!$this->checkCategoryId($categoryId))
		{
			return false;
		}

		// Parent exists so we proceed
		foreach ($pks as $pk)
		{
			if (!$this->user->authorise('core.edit', $contexts[$pk]))
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

				return false;
			}

			// Check that the row actually exists
			if (!$this->table->load($pk))
			{
				if ($error = $this->table->getError())
				{
					// Fatal error
					$this->setError($error);

					return false;
				}
				else
				{
					// Not fatal error
					$this->setError(\JText::sprintf('JLIB_APPLICATION_ERROR_BATCH_MOVE_ROW_NOT_FOUND', $pk));
					continue;
				}
			}

			// Set the new category ID
			$this->table->catid = $categoryId;

			// Check the row.
			if (!$this->table->check())
			{
				$this->setError($this->table->getError());

				return false;
			}

			if (!empty($this->type))
			{
				$this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
			}

			// Store the row.
			if (!$this->table->store())
			{
				$this->setError($this->table->getError());

				return false;
			}
		}

		// Clean the cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Batch tag a list of item.
	 *
	 * @param   integer  $value     The value of the new tag.
	 * @param   array    $pks       An array of row IDs.
	 * @param   array    $contexts  An array of item contexts.
	 *
	 * @return  boolean  True if successful, false otherwise and internal error is set.
	 *
	 * @since   3.1
	 */
	protected function batchTag($value, $pks, $contexts)
	{
		// Initialize re-usable member properties, and re-usable local variables
		$this->initBatch();
		$tags = array($value);

		foreach ($pks as $pk)
		{
			if ($this->user->authorise('core.edit', $contexts[$pk]))
			{
				$this->table->reset();
				$this->table->load($pk);

				// Add new tags, keeping existing ones
				$result = $this->tagsObserver->setNewTags($tags, false);

				if (!$result)
				{
					$this->setError($this->table->getError());

					return false;
				}
			}
			else
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_EDIT'));

				return false;
			}
		}

		// Clean the cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Method to test whether a record can be deleted.
	 *
	 * @param   object  $record  A record object.
	 *
	 * @return  boolean  True if allowed to delete the record. Defaults to the permission for the component.
	 *
	 * @since   1.6
	 */
	protected function canDelete($record)
	{
		return \JFactory::getUser()->authorise('core.delete', $this->option);
	}

	/**
	 * Method to test whether a record can have its state changed.
	 *
	 * @param   object  $record  A record object.
	 *
	 * @return  boolean  True if allowed to change the state of the record. Defaults to the permission for the component.
	 *
	 * @since   1.6
	 */
	protected function canEditState($record)
	{
		return \JFactory::getUser()->authorise('core.edit.state', $this->option);
	}

	/**
	 * Method override to check-in a record or an array of record
	 *
	 * @param   mixed  $pks  The ID of the primary key or an array of IDs
	 *
	 * @return  integer|boolean  Boolean false if there is an error, otherwise the count of records checked in.
	 *
	 * @since   1.6
	 */
	public function checkin($pks = array())
	{
		$pks = (array) $pks;
		$table = $this->getTable();
		$count = 0;

		if (empty($pks))
		{
			$pks = array((int) $this->getState($this->getName() . '.id'));
		}

		$checkedOutField = $table->getColumnAlias('checked_out');

		// Check in all items.
		foreach ($pks as $pk)
		{
			if ($table->load($pk))
			{
				if ($table->{$checkedOutField} > 0)
				{
					if (!parent::checkin($pk))
					{
						return false;
					}

					$count++;
				}
			}
			else
			{
				$this->setError($table->getError());

				return false;
			}
		}

		return $count;
	}

	/**
	 * Method override to check-out a record.
	 *
	 * @param   integer  $pk  The ID of the primary key.
	 *
	 * @return  boolean  True if successful, false if an error occurs.
	 *
	 * @since   1.6
	 */
	public function checkout($pk = null)
	{
		$pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id');

		return parent::checkout($pk);
	}

	/**
	 * Method to delete one or more records.
	 *
	 * @param   array  &$pks  An array of record primary keys.
	 *
	 * @return  boolean  True if successful, false if an error occurs.
	 *
	 * @since   1.6
	 */
	public function delete(&$pks)
	{
		$dispatcher = \JEventDispatcher::getInstance();
		$pks = (array) $pks;
		$table = $this->getTable();

		// Include the plugins for the delete events.
		\JPluginHelper::importPlugin($this->events_map['delete']);

		// Iterate the items to delete each one.
		foreach ($pks as $i => $pk)
		{
			if ($table->load($pk))
			{
				if ($this->canDelete($table))
				{
					$context = $this->option . '.' . $this->name;

					// Trigger the before delete event.
					$result = $dispatcher->trigger($this->event_before_delete, array($context, $table));

					if (in_array(false, $result, true))
					{
						$this->setError($table->getError());

						return false;
					}

					// Multilanguage: if associated, delete the item in the _associations table
					if ($this->associationsContext && \JLanguageAssociations::isEnabled())
					{
						$db = $this->getDbo();
						$query = $db->getQuery(true)
							->select('COUNT(*) as count, ' . $db->quoteName('as1.key'))
							->from($db->quoteName('#__associations') . ' AS as1')
							->join('LEFT', $db->quoteName('#__associations') . ' AS as2 ON ' . $db->quoteName('as1.key') . ' =  ' . $db->quoteName('as2.key'))
							->where($db->quoteName('as1.context') . ' = ' . $db->quote($this->associationsContext))
							->where($db->quoteName('as1.id') . ' = ' . (int) $pk)
							->group($db->quoteName('as1.key'));

						$db->setQuery($query);
						$row = $db->loadAssoc();

						if (!empty($row['count']))
						{
							$query = $db->getQuery(true)
								->delete($db->quoteName('#__associations'))
								->where($db->quoteName('context') . ' = ' . $db->quote($this->associationsContext))
								->where($db->quoteName('key') . ' = ' . $db->quote($row['key']));

							if ($row['count'] > 2)
							{
								$query->where($db->quoteName('id') . ' = ' . (int) $pk);
							}

							$db->setQuery($query);
							$db->execute();
						}
					}

					if (!$table->delete($pk))
					{
						$this->setError($table->getError());

						return false;
					}

					// Trigger the after event.
					$dispatcher->trigger($this->event_after_delete, array($context, $table));
				}
				else
				{
					// Prune items that you can't change.
					unset($pks[$i]);
					$error = $this->getError();

					if ($error)
					{
						\JLog::add($error, \JLog::WARNING, 'jerror');

						return false;
					}
					else
					{
						\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_DELETE_NOT_PERMITTED'), \JLog::WARNING, 'jerror');

						return false;
					}
				}
			}
			else
			{
				$this->setError($table->getError());

				return false;
			}
		}

		// Clear the component's cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Method to change the title & alias.
	 *
	 * @param   integer  $category_id  The id of the category.
	 * @param   string   $alias        The alias.
	 * @param   string   $title        The title.
	 *
	 * @return	array  Contains the modified title and alias.
	 *
	 * @since	1.7
	 */
	protected function generateNewTitle($category_id, $alias, $title)
	{
		// Alter the title & alias
		$table      = $this->getTable();
		$aliasField = $table->getColumnAlias('alias');
		$catidField = $table->getColumnAlias('catid');
		$titleField = $table->getColumnAlias('title');

		while ($table->load(array($aliasField => $alias, $catidField => $category_id)))
		{
			if ($title === $table->$titleField)
			{
				$title = StringHelper::increment($title);
			}

			$alias = StringHelper::increment($alias, 'dash');
		}

		return array($title, $alias);
	}

	/**
	 * Method to get a single record.
	 *
	 * @param   integer  $pk  The id of the primary key.
	 *
	 * @return  \JObject|boolean  Object on success, false on failure.
	 *
	 * @since   1.6
	 */
	public function getItem($pk = null)
	{
		$pk = (!empty($pk)) ? $pk : (int) $this->getState($this->getName() . '.id');
		$table = $this->getTable();

		if ($pk > 0)
		{
			// Attempt to load the row.
			$return = $table->load($pk);

			// Check for a table object error.
			if ($return === false && $table->getError())
			{
				$this->setError($table->getError());

				return false;
			}
		}

		// Convert to the \JObject before adding other data.
		$properties = $table->getProperties(1);
		$item = ArrayHelper::toObject($properties, '\JObject');

		if (property_exists($item, 'params'))
		{
			$registry = new Registry($item->params);
			$item->params = $registry->toArray();
		}

		return $item;
	}

	/**
	 * A protected method to get a set of ordering conditions.
	 *
	 * @param   \JTable  $table  A \JTable object.
	 *
	 * @return  array  An array of conditions to add to ordering queries.
	 *
	 * @since   1.6
	 */
	protected function getReorderConditions($table)
	{
		return array();
	}

	/**
	 * Stock method to auto-populate the model state.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	protected function populateState()
	{
		$table = $this->getTable();
		$key = $table->getKeyName();

		// Get the pk of the record from the request.
		$pk = \JFactory::getApplication()->input->getInt($key);
		$this->setState($this->getName() . '.id', $pk);

		// Load the parameters.
		$value = \JComponentHelper::getParams($this->option);
		$this->setState('params', $value);
	}

	/**
	 * Prepare and sanitise the table data prior to saving.
	 *
	 * @param   \JTable  $table  A reference to a \JTable object.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	protected function prepareTable($table)
	{
		// Derived class will provide its own implementation if required.
	}

	/**
	 * Method to change the published state of one or more records.
	 *
	 * @param   array    &$pks   A list of the primary keys to change.
	 * @param   integer  $value  The value of the published state.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 */
	public function publish(&$pks, $value = 1)
	{
		$dispatcher = \JEventDispatcher::getInstance();
		$user = \JFactory::getUser();
		$table = $this->getTable();
		$pks = (array) $pks;

		// Include the plugins for the change of state event.
		\JPluginHelper::importPlugin($this->events_map['change_state']);

		// Access checks.
		foreach ($pks as $i => $pk)
		{
			$table->reset();

			if ($table->load($pk))
			{
				if (!$this->canEditState($table))
				{
					// Prune items that you can't change.
					unset($pks[$i]);

					\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror');

					return false;
				}

				// If the table is checked out by another user, drop it and report to the user trying to change its state.
				if (property_exists($table, 'checked_out') && $table->checked_out && ($table->checked_out != $user->id))
				{
					\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'), \JLog::WARNING, 'jerror');

					// Prune items that you can't change.
					unset($pks[$i]);

					return false;
				}

				/**
				 * Prune items that are already at the given state.  Note: Only models whose table correctly
				 * sets 'published' column alias (if different than published) will benefit from this
				 */
				$publishedColumnName = $table->getColumnAlias('published');

				if (property_exists($table, $publishedColumnName) && $table->get($publishedColumnName, $value) == $value)
				{
					unset($pks[$i]);

					continue;
				}
			}
		}

		// Check if there are items to change
		if (!count($pks))
		{
			return true;
		}

		// Attempt to change the state of the records.
		if (!$table->publish($pks, $value, $user->get('id')))
		{
			$this->setError($table->getError());

			return false;
		}

		$context = $this->option . '.' . $this->name;

		// Trigger the change state event.
		$result = $dispatcher->trigger($this->event_change_state, array($context, $pks, $value));

		if (in_array(false, $result, true))
		{
			$this->setError($table->getError());

			return false;
		}

		// Clear the component's cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Method to adjust the ordering of a row.
	 *
	 * Returns NULL if the user did not have edit
	 * privileges for any of the selected primary keys.
	 *
	 * @param   integer  $pks    The ID of the primary key to move.
	 * @param   integer  $delta  Increment, usually +1 or -1
	 *
	 * @return  boolean|null  False on failure or error, true on success, null if the $pk is empty (no items selected).
	 *
	 * @since   1.6
	 */
	public function reorder($pks, $delta = 0)
	{
		$table = $this->getTable();
		$pks = (array) $pks;
		$result = true;

		$allowed = true;

		foreach ($pks as $i => $pk)
		{
			$table->reset();

			if ($table->load($pk) && $this->checkout($pk))
			{
				// Access checks.
				if (!$this->canEditState($table))
				{
					// Prune items that you can't change.
					unset($pks[$i]);
					$this->checkin($pk);
					\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror');
					$allowed = false;
					continue;
				}

				$where = $this->getReorderConditions($table);

				if (!$table->move($delta, $where))
				{
					$this->setError($table->getError());
					unset($pks[$i]);
					$result = false;
				}

				$this->checkin($pk);
			}
			else
			{
				$this->setError($table->getError());
				unset($pks[$i]);
				$result = false;
			}
		}

		if ($allowed === false && empty($pks))
		{
			$result = null;
		}

		// Clear the component's cache
		if ($result == true)
		{
			$this->cleanCache();
		}

		return $result;
	}

	/**
	 * Method to save the form data.
	 *
	 * @param   array  $data  The form data.
	 *
	 * @return  boolean  True on success, False on error.
	 *
	 * @since   1.6
	 */
	public function save($data)
	{
		$dispatcher = \JEventDispatcher::getInstance();
		$table      = $this->getTable();
		$context    = $this->option . '.' . $this->name;

		if (!empty($data['tags']) && $data['tags'][0] != '')
		{
			$table->newTags = $data['tags'];
		}

		$key = $table->getKeyName();
		$pk = (!empty($data[$key])) ? $data[$key] : (int) $this->getState($this->getName() . '.id');
		$isNew = true;

		// Include the plugins for the save events.
		\JPluginHelper::importPlugin($this->events_map['save']);

		// Allow an exception to be thrown.
		try
		{
			// Load the row if saving an existing record.
			if ($pk > 0)
			{
				$table->load($pk);
				$isNew = false;
			}

			// Bind the data.
			if (!$table->bind($data))
			{
				$this->setError($table->getError());

				return false;
			}

			// Prepare the row for saving
			$this->prepareTable($table);

			// Check the data.
			if (!$table->check())
			{
				$this->setError($table->getError());

				return false;
			}

			// Trigger the before save event.
			$result = $dispatcher->trigger($this->event_before_save, array($context, $table, $isNew, $data));

			if (in_array(false, $result, true))
			{
				$this->setError($table->getError());

				return false;
			}

			// Store the data.
			if (!$table->store())
			{
				$this->setError($table->getError());

				return false;
			}

			// Clean the cache.
			$this->cleanCache();

			// Trigger the after save event.
			$dispatcher->trigger($this->event_after_save, array($context, $table, $isNew, $data));
		}
		catch (\Exception $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		if (isset($table->$key))
		{
			$this->setState($this->getName() . '.id', $table->$key);
		}

		$this->setState($this->getName() . '.new', $isNew);

		if ($this->associationsContext && \JLanguageAssociations::isEnabled() && !empty($data['associations']))
		{
			$associations = $data['associations'];

			// Unset any invalid associations
			$associations = ArrayHelper::toInteger($associations);

			// Unset any invalid associations
			foreach ($associations as $tag => $id)
			{
				if (!$id)
				{
					unset($associations[$tag]);
				}
			}

			// Show a warning if the item isn't assigned to a language but we have associations.
			if ($associations && $table->language === '*')
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::_(strtoupper($this->option) . '_ERROR_ALL_LANGUAGE_ASSOCIATED'),
					'warning'
				);
			}

			// Get associationskey for edited item
			$db    = $this->getDbo();
			$query = $db->getQuery(true)
				->select($db->qn('key'))
				->from($db->qn('#__associations'))
				->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext))
				->where($db->qn('id') . ' = ' . (int) $table->$key);
			$db->setQuery($query);
			$old_key = $db->loadResult();

			// Deleting old associations for the associated items
			$query = $db->getQuery(true)
				->delete($db->qn('#__associations'))
				->where($db->qn('context') . ' = ' . $db->quote($this->associationsContext));

			if ($associations)
			{
				$query->where('(' . $db->qn('id') . ' IN (' . implode(',', $associations) . ') OR '
					. $db->qn('key') . ' = ' . $db->q($old_key) . ')');
			}
			else
			{
				$query->where($db->qn('key') . ' = ' . $db->q($old_key));
			}

			$db->setQuery($query);
			$db->execute();

			// Adding self to the association
			if ($table->language !== '*')
			{
				$associations[$table->language] = (int) $table->$key;
			}

			if (count($associations) > 1)
			{
				// Adding new association for these items
				$key   = md5(json_encode($associations));
				$query = $db->getQuery(true)
					->insert('#__associations');

				foreach ($associations as $id)
				{
					$query->values(((int) $id) . ',' . $db->quote($this->associationsContext) . ',' . $db->quote($key));
				}

				$db->setQuery($query);
				$db->execute();
			}
		}

		return true;
	}

	/**
	 * Saves the manually set order of records.
	 *
	 * @param   array    $pks    An array of primary key ids.
	 * @param   integer  $order  +1 or -1
	 *
	 * @return  boolean|\JException  Boolean true on success, false on failure, or \JException if no items are selected
	 *
	 * @since   1.6
	 */
	public function saveorder($pks = array(), $order = null)
	{
		// Initialize re-usable member properties
		$this->initBatch();

		$conditions = array();

		if (empty($pks))
		{
			return \JError::raiseWarning(500, \JText::_($this->text_prefix . '_ERROR_NO_ITEMS_SELECTED'));
		}

		$orderingField = $this->table->getColumnAlias('ordering');

		// Update ordering values
		foreach ($pks as $i => $pk)
		{
			$this->table->load((int) $pk);

			// Access checks.
			if (!$this->canEditState($this->table))
			{
				// Prune items that you can't change.
				unset($pks[$i]);
				\JLog::add(\JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'), \JLog::WARNING, 'jerror');
			}
			elseif ($this->table->$orderingField != $order[$i])
			{
				$this->table->$orderingField = $order[$i];

				if ($this->type)
				{
					$this->createTagsHelper($this->tagsObserver, $this->type, $pk, $this->typeAlias, $this->table);
				}

				if (!$this->table->store())
				{
					$this->setError($this->table->getError());

					return false;
				}

				// Remember to reorder within position and client_id
				$condition = $this->getReorderConditions($this->table);
				$found = false;

				foreach ($conditions as $cond)
				{
					if ($cond[1] == $condition)
					{
						$found = true;
						break;
					}
				}

				if (!$found)
				{
					$key = $this->table->getKeyName();
					$conditions[] = array($this->table->$key, $condition);
				}
			}
		}

		// Execute reorder for each category.
		foreach ($conditions as $cond)
		{
			$this->table->load($cond[0]);
			$this->table->reorder($cond[1]);
		}

		// Clear the component's cache
		$this->cleanCache();

		return true;
	}

	/**
	 * Method to create a tags helper to ensure proper management of tags
	 *
	 * @param   \JTableObserverTags  $tagsObserver  The tags observer for this table
	 * @param   \JUcmType            $type          The type for the table being processed
	 * @param   integer              $pk            Primary key of the item bing processed
	 * @param   string               $typeAlias     The type alias for this table
	 * @param   \JTable              $table         The \JTable object
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function createTagsHelper($tagsObserver, $type, $pk, $typeAlias, $table)
	{
		if (!empty($tagsObserver) && !empty($type))
		{
			$table->tagsHelper = new \JHelperTags;
			$table->tagsHelper->typeAlias = $typeAlias;
			$table->tagsHelper->tags = explode(',', $table->tagsHelper->getTagIds($pk, $typeAlias));
		}
	}

	/**
	 * Method to check the validity of the category ID for batch copy and move
	 *
	 * @param   integer  $categoryId  The category ID to check
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	protected function checkCategoryId($categoryId)
	{
		// Check that the category exists
		if ($categoryId)
		{
			$categoryTable = \JTable::getInstance('Category');

			if (!$categoryTable->load($categoryId))
			{
				if ($error = $categoryTable->getError())
				{
					// Fatal error
					$this->setError($error);

					return false;
				}
				else
				{
					$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));

					return false;
				}
			}
		}

		if (empty($categoryId))
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_MOVE_CATEGORY_NOT_FOUND'));

			return false;
		}

		// Check that the user has create permission for the component
		$extension = \JFactory::getApplication()->input->get('option', '');
		$user = \JFactory::getUser();

		if (!$user->authorise('core.create', $extension . '.category.' . $categoryId))
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_BATCH_CANNOT_CREATE'));

			return false;
		}

		return true;
	}

	/**
	 * A method to preprocess generating a new title in order to allow tables with alternative names
	 * for alias and title to use the batch move and copy methods
	 *
	 * @param   integer  $categoryId  The target category id
	 * @param   \JTable  $table       The \JTable within which move or copy is taking place
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function generateTitle($categoryId, $table)
	{
		// Alter the title & alias
		$titleField         = $table->getColumnAlias('title');
		$aliasField         = $table->getColumnAlias('alias');
		$data               = $this->generateNewTitle($categoryId, $table->$aliasField, $table->$titleField);
		$table->$titleField = $data['0'];
		$table->$aliasField = $data['1'];
	}

	/**
	 * Method to initialize member variables used by batch methods and other methods like saveorder()
	 *
	 * @return  void
	 *
	 * @since   3.8.2
	 */
	public function initBatch()
	{
		if ($this->batchSet === null)
		{
			$this->batchSet = true;

			// Get current user
			$this->user = \JFactory::getUser();

			// Get table
			$this->table = $this->getTable();

			// Get table class name
			$tc = explode('\\', get_class($this->table));
			$this->tableClassName = end($tc);

			// Get UCM Type data
			$this->contentType = new \JUcmType;
			$this->type = $this->contentType->getTypeByTable($this->tableClassName)
				?: $this->contentType->getTypeByAlias($this->typeAlias);

			// Get tabs observer
			$this->tagsObserver = $this->table->getObserverOfClass('Joomla\CMS\Table\Observer\Tags');
		}
	}

	/**
	 * Method to load an item in com_associations.
	 *
	 * @param   array  $data  The form data.
	 *
	 * @return  boolean  True if successful, false otherwise.
	 *
	 * @since   3.9.0
	 */
	public function editAssociations($data)
	{
		// Save the item
		$this->save($data);

		$app = \JFactory::getApplication();
		$id  = $data['id'];

		// Deal with categories associations
		if ($this->text_prefix === 'COM_CATEGORIES')
		{
			$extension       = $app->input->get('extension', 'com_content');
			$this->typeAlias = $extension . '.category';
			$component       = strtolower($this->text_prefix);
			$view            = 'category';
		}
		else
		{
			$aliasArray = explode('.', $this->typeAlias);
			$component  = $aliasArray[0];
			$view       = $aliasArray[1];
			$extension  = '';
		}

		// Menu item redirect needs admin client
		$client = $component === 'com_menus' ? '&client_id=0' : '';

		if ($id == 0)
		{
			$app->enqueueMessage(\JText::_('JGLOBAL_ASSOCIATIONS_NEW_ITEM_WARNING'), 'error');
			$app->redirect(
				\JRoute::_('index.php?option=' . $component . '&view=' . $view . $client . '&layout=edit&id=' . $id . $extension, false)
			);

			return false;
		}

		if ($data['language'] === '*')
		{
			$app->enqueueMessage(\JText::_('JGLOBAL_ASSOC_NOT_POSSIBLE'), 'notice');
			$app->redirect(
				\JRoute::_('index.php?option=' . $component . '&view=' . $view . $client . '&layout=edit&id=' . $id . $extension, false)
			);

			return false;
		}

		$languages = LanguageHelper::getContentLanguages(array(0, 1));
		$target    = '';

		/* If the site contains only 2 languages and an association exists for the item
		   load directly the associated target item in the side by side view
		   otherwise select already the target language
		*/
		if (count($languages) === 2)
		{
			foreach ($languages as $language)
			{
				$lang_code[] = $language->lang_code;
			}

			$refLang    = array($data['language']);
			$targetLang = array_diff($lang_code, $refLang);
			$targetLang = implode(',', $targetLang);
			$targetId   = $data['associations'][$targetLang];

			if ($targetId)
			{
				$target = '&target=' . $targetLang . '%3A' . $targetId . '%3Aedit';
			}
			else
			{
				$target = '&target=' . $targetLang . '%3A0%3Aadd';
			}
		}

		$app->redirect(
			\JRoute::_(
				'index.php?option=com_associations&view=association&layout=edit&itemtype=' . $this->typeAlias
				. '&task=association.edit&id=' . $id . $target, false
			)
		);

		return true;
	}
}
src/MVC/Model/BaseDatabaseModel.php000064400000033534152177723700013034 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Base class for a database aware Joomla Model
 *
 * Acts as a Factory class for application specific objects and provides many supporting API functions.
 *
 * @since  2.5.5
 */
abstract class BaseDatabaseModel extends \JObject
{
	/**
	 * Indicates if the internal state has been set
	 *
	 * @var    boolean
	 * @since  3.0
	 */
	protected $__state_set = null;

	/**
	 * Database Connector
	 *
	 * @var    \JDatabaseDriver
	 * @since  3.0
	 */
	protected $_db;

	/**
	 * The model (base) name
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $name;

	/**
	 * The URL option for the component.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $option = null;

	/**
	 * A state object
	 *
	 * @var    \JObject
	 * @since  3.0
	 */
	protected $state;

	/**
	 * The event to trigger when cleaning cache.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $event_clean_cache = null;

	/**
	 * Add a directory where \JModelLegacy should search for models. You may
	 * either pass a string or an array of directories.
	 *
	 * @param   mixed   $path    A path or array[sting] of paths to search.
	 * @param   string  $prefix  A prefix for models.
	 *
	 * @return  array  An array with directory elements. If prefix is equal to '', all directories are returned.
	 *
	 * @since   3.0
	 */
	public static function addIncludePath($path = '', $prefix = '')
	{
		static $paths;

		if (!isset($paths))
		{
			$paths = array();
		}

		if (!isset($paths[$prefix]))
		{
			$paths[$prefix] = array();
		}

		if (!isset($paths['']))
		{
			$paths[''] = array();
		}

		if (!empty($path))
		{
			jimport('joomla.filesystem.path');

			foreach ((array) $path as $includePath)
			{
				if (!in_array($includePath, $paths[$prefix]))
				{
					array_unshift($paths[$prefix], \JPath::clean($includePath));
				}

				if (!in_array($includePath, $paths['']))
				{
					array_unshift($paths[''], \JPath::clean($includePath));
				}
			}
		}

		return $paths[$prefix];
	}

	/**
	 * Adds to the stack of model table paths in LIFO order.
	 *
	 * @param   mixed  $path  The directory as a string or directories as an array to add.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function addTablePath($path)
	{
		\JTable::addIncludePath($path);
	}

	/**
	 * Create the filename for a resource
	 *
	 * @param   string  $type   The resource type to create the filename for.
	 * @param   array   $parts  An associative array of filename information.
	 *
	 * @return  string  The filename
	 *
	 * @since   3.0
	 */
	protected static function _createFileName($type, $parts = array())
	{
		$filename = '';

		switch ($type)
		{
			case 'model':
				$filename = strtolower($parts['name']) . '.php';
				break;
		}

		return $filename;
	}

	/**
	 * Returns a Model object, always creating it
	 *
	 * @param   string  $type    The model type to instantiate
	 * @param   string  $prefix  Prefix for the model class name. Optional.
	 * @param   array   $config  Configuration array for model. Optional.
	 *
	 * @return  \JModelLegacy|boolean   A \JModelLegacy instance or false on failure
	 *
	 * @since   3.0
	 */
	public static function getInstance($type, $prefix = '', $config = array())
	{
		$type = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
		$modelClass = $prefix . ucfirst($type);

		if (!class_exists($modelClass))
		{
			jimport('joomla.filesystem.path');
			$path = \JPath::find(self::addIncludePath(null, $prefix), self::_createFileName('model', array('name' => $type)));

			if (!$path)
			{
				$path = \JPath::find(self::addIncludePath(null, ''), self::_createFileName('model', array('name' => $type)));
			}

			if (!$path)
			{
				return false;
			}

			require_once $path;

			if (!class_exists($modelClass))
			{
				\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_MODELCLASS_NOT_FOUND', $modelClass), \JLog::WARNING, 'jerror');

				return false;
			}
		}

		return new $modelClass($config);
	}

	/**
	 * Constructor
	 *
	 * @param   array  $config  An array of configuration options (name, state, dbo, table_path, ignore_request).
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function __construct($config = array())
	{
		// Guess the option from the class name (Option)Model(View).
		if (empty($this->option))
		{
			$r = null;

			if (!preg_match('/(.*)Model/i', get_class($this), $r))
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_MODEL_GET_NAME'), 500);
			}

			$this->option = 'com_' . strtolower($r[1]);
		}

		// Set the view name
		if (empty($this->name))
		{
			if (array_key_exists('name', $config))
			{
				$this->name = $config['name'];
			}
			else
			{
				$this->name = $this->getName();
			}
		}

		// Set the model state
		if (array_key_exists('state', $config))
		{
			$this->state = $config['state'];
		}
		else
		{
			$this->state = new \JObject;
		}

		// Set the model dbo
		if (array_key_exists('dbo', $config))
		{
			$this->_db = $config['dbo'];
		}
		else
		{
			$this->_db = \JFactory::getDbo();
		}

		// Set the default view search path
		if (array_key_exists('table_path', $config))
		{
			$this->addTablePath($config['table_path']);
		}
		// @codeCoverageIgnoreStart
		elseif (defined('JPATH_COMPONENT_ADMINISTRATOR'))
		{
			$this->addTablePath(JPATH_COMPONENT_ADMINISTRATOR . '/tables');
			$this->addTablePath(JPATH_COMPONENT_ADMINISTRATOR . '/table');
		}

		// @codeCoverageIgnoreEnd

		// Set the internal state marker - used to ignore setting state from the request
		if (!empty($config['ignore_request']))
		{
			$this->__state_set = true;
		}

		// Set the clean cache event
		if (isset($config['event_clean_cache']))
		{
			$this->event_clean_cache = $config['event_clean_cache'];
		}
		elseif (empty($this->event_clean_cache))
		{
			$this->event_clean_cache = 'onContentCleanCache';
		}
	}

	/**
	 * Gets an array of objects from the results of database query.
	 *
	 * @param   string   $query       The query.
	 * @param   integer  $limitstart  Offset.
	 * @param   integer  $limit       The number of records.
	 *
	 * @return  object[]  An array of results.
	 *
	 * @since   3.0
	 * @throws  \RuntimeException
	 */
	protected function _getList($query, $limitstart = 0, $limit = 0)
	{
		$this->getDbo()->setQuery($query, $limitstart, $limit);

		return $this->getDbo()->loadObjectList();
	}

	/**
	 * Returns a record count for the query.
	 *
	 * Note: Current implementation of this method assumes that getListQuery() returns a set of unique rows,
	 * thus it uses SELECT COUNT(*) to count the rows. In cases that getListQuery() uses DISTINCT
	 * then either this method must be overriden by a custom implementation at the derived Model Class
	 * or a GROUP BY clause should be used to make the set unique.
	 *
	 * @param   \JDatabaseQuery|string  $query  The query.
	 *
	 * @return  integer  Number of rows for query.
	 *
	 * @since   3.0
	 */
	protected function _getListCount($query)
	{
		// Use fast COUNT(*) on \JDatabaseQuery objects if there is no GROUP BY or HAVING clause:
		if ($query instanceof \JDatabaseQuery
			&& $query->type == 'select'
			&& $query->group === null
			&& $query->union === null
			&& $query->unionAll === null
			&& $query->having === null)
		{
			$query = clone $query;
			$query->clear('select')->clear('order')->clear('limit')->clear('offset')->select('COUNT(*)');

			$this->getDbo()->setQuery($query);

			return (int) $this->getDbo()->loadResult();
		}

		// Otherwise fall back to inefficient way of counting all results.

		// Remove the limit and offset part if it's a \JDatabaseQuery object
		if ($query instanceof \JDatabaseQuery)
		{
			$query = clone $query;
			$query->clear('limit')->clear('offset');
		}

		$this->getDbo()->setQuery($query);
		$this->getDbo()->execute();

		return (int) $this->getDbo()->getNumRows();
	}

	/**
	 * Method to load and return a model object.
	 *
	 * @param   string  $name    The name of the view
	 * @param   string  $prefix  The class prefix. Optional.
	 * @param   array   $config  Configuration settings to pass to \JTable::getInstance
	 *
	 * @return  \JTable|boolean  Table object or boolean false if failed
	 *
	 * @since   3.0
	 * @see     \JTable::getInstance()
	 */
	protected function _createTable($name, $prefix = 'Table', $config = array())
	{
		// Clean the model name
		$name = preg_replace('/[^A-Z0-9_]/i', '', $name);
		$prefix = preg_replace('/[^A-Z0-9_]/i', '', $prefix);

		// Make sure we are returning a DBO object
		if (!array_key_exists('dbo', $config))
		{
			$config['dbo'] = $this->getDbo();
		}

		return \JTable::getInstance($name, $prefix, $config);
	}

	/**
	 * Method to get the database driver object
	 *
	 * @return  \JDatabaseDriver
	 *
	 * @since   3.0
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to get the model name
	 *
	 * The model name. By default parsed using the classname or it can be set
	 * by passing a $config['name'] in the class constructor
	 *
	 * @return  string  The name of the model
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$r = null;

			if (!preg_match('/Model(.*)/i', get_class($this), $r))
			{
				throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_MODEL_GET_NAME'), 500);
			}

			$this->name = strtolower($r[1]);
		}

		return $this->name;
	}

	/**
	 * Method to get model state variables
	 *
	 * @param   string  $property  Optional parameter name
	 * @param   mixed   $default   Optional default value
	 *
	 * @return  mixed  The property where specified, the state object where omitted
	 *
	 * @since   3.0
	 */
	public function getState($property = null, $default = null)
	{
		if (!$this->__state_set)
		{
			// Protected method to auto-populate the model state.
			$this->populateState();

			// Set the model state set flag to true.
			$this->__state_set = true;
		}

		return $property === null ? $this->state : $this->state->get($property, $default);
	}

	/**
	 * Method to get a table object, load it if necessary.
	 *
	 * @param   string  $name     The table name. Optional.
	 * @param   string  $prefix   The class prefix. Optional.
	 * @param   array   $options  Configuration array for model. Optional.
	 *
	 * @return  \JTable  A \JTable object
	 *
	 * @since   3.0
	 * @throws  \Exception
	 */
	public function getTable($name = '', $prefix = 'Table', $options = array())
	{
		if (empty($name))
		{
			$name = $this->getName();
		}

		if ($table = $this->_createTable($name, $prefix, $options))
		{
			return $table;
		}

		throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_TABLE_NAME_NOT_SUPPORTED', $name), 0);
	}

	/**
	 * Method to load a row for editing from the version history table.
	 *
	 * @param   integer  $version_id  Key to the version history table.
	 * @param   \JTable  &$table      Content table object being loaded.
	 *
	 * @return  boolean  False on failure or error, true otherwise.
	 *
	 * @since   3.2
	 */
	public function loadHistory($version_id, \JTable &$table)
	{
		// Only attempt to check the row in if it exists, otherwise do an early exit.
		if (!$version_id)
		{
			return false;
		}

		// Get an instance of the row to checkout.
		$historyTable = \JTable::getInstance('Contenthistory');

		if (!$historyTable->load($version_id))
		{
			$this->setError($historyTable->getError());

			return false;
		}

		$rowArray = ArrayHelper::fromObject(json_decode($historyTable->version_data));
		$typeId   = \JTable::getInstance('Contenttype')->getTypeId($this->typeAlias);

		if ($historyTable->ucm_type_id != $typeId)
		{
			$this->setError(\JText::_('JLIB_APPLICATION_ERROR_HISTORY_ID_MISMATCH'));

			$key = $table->getKeyName();

			if (isset($rowArray[$key]))
			{
				$table->checkIn($rowArray[$key]);
			}

			return false;
		}

		$this->setState('save_date', $historyTable->save_date);
		$this->setState('version_note', $historyTable->version_note);

		return $table->bind($rowArray);
	}

	/**
	 * Method to auto-populate the model state.
	 *
	 * This method should only be called once per instantiation and is designed
	 * to be called on the first call to the getState() method unless the model
	 * configuration flag to ignore the request is set.
	 *
	 * @return  void
	 *
	 * @note    Calling getState in this method will result in recursion.
	 * @since   3.0
	 */
	protected function populateState()
	{
	}

	/**
	 * Method to set the database driver object
	 *
	 * @param   \JDatabaseDriver  $db  A \JDatabaseDriver based object
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function setDbo($db)
	{
		$this->_db = $db;
	}

	/**
	 * Method to set model state variables
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set or null.
	 *
	 * @return  mixed  The previous value of the property or null if not set.
	 *
	 * @since   3.0
	 */
	public function setState($property, $value = null)
	{
		return $this->state->set($property, $value);
	}

	/**
	 * Clean the cache
	 *
	 * @param   string   $group      The cache group
	 * @param   integer  $client_id  The ID of the client
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	protected function cleanCache($group = null, $client_id = 0)
	{
		$conf = \JFactory::getConfig();

		$options = array(
			'defaultgroup' => $group ?: (isset($this->option) ? $this->option : \JFactory::getApplication()->input->get('option')),
			'cachebase' => $client_id ? JPATH_ADMINISTRATOR . '/cache' : $conf->get('cache_path', JPATH_SITE . '/cache'),
			'result' => true,
		);

		try
		{
			/** @var \JCacheControllerCallback $cache */
			$cache = \JCache::getInstance('callback', $options);
			$cache->clean();
		}
		catch (\JCacheException $exception)
		{
			$options['result'] = false;
		}

		// Trigger the onContentCleanCache event.
		\JEventDispatcher::getInstance()->trigger($this->event_clean_cache, $options);
	}
}
src/MVC/Model/FormModel.php000064400000023106152177723700011432 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Prototype form model.
 *
 * @see    \JForm
 * @see    \JFormField
 * @see    \JFormRule
 * @since  1.6
 */
abstract class FormModel extends BaseDatabaseModel
{
	/**
	 * Array of form objects.
	 *
	 * @var    \JForm[]
	 * @since  1.6
	 */
	protected $_forms = array();

	/**
	 * Maps events to plugin groups.
	 *
	 * @var    array
	 * @since  3.6
	 */
	protected $events_map = null;

	/**
	 * Constructor.
	 *
	 * @param   array  $config  An optional associative array of configuration settings.
	 *
	 * @see     \JModelLegacy
	 * @since   3.6
	 */
	public function __construct($config = array())
	{
		$config['events_map'] = isset($config['events_map']) ? $config['events_map'] : array();

		$this->events_map = array_merge(
			array(
				'validate' => 'content',
			),
			$config['events_map']
		);

		parent::__construct($config);
	}

	/**
	 * Method to checkin a row.
	 *
	 * @param   integer  $pk  The numeric id of the primary key.
	 *
	 * @return  boolean  False on failure or error, true otherwise.
	 *
	 * @since   1.6
	 */
	public function checkin($pk = null)
	{
		// Only attempt to check the row in if it exists.
		if ($pk)
		{
			$user = \JFactory::getUser();

			// Get an instance of the row to checkin.
			$table = $this->getTable();

			if (!$table->load($pk))
			{
				$this->setError($table->getError());

				return false;
			}

			$checkedOutField = $table->getColumnAlias('checked_out');
			$checkedOutTimeField = $table->getColumnAlias('checked_out_time');

			// If there is no checked_out or checked_out_time field, just return true.
			if (!property_exists($table, $checkedOutField) || !property_exists($table, $checkedOutTimeField))
			{
				return true;
			}

			// Check if this is the user having previously checked out the row.
			if ($table->{$checkedOutField} > 0 && $table->{$checkedOutField} != $user->get('id') && !$user->authorise('core.admin', 'com_checkin'))
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CHECKIN_USER_MISMATCH'));

				return false;
			}

			// Attempt to check the row in.
			if (!$table->checkIn($pk))
			{
				$this->setError($table->getError());

				return false;
			}
		}

		return true;
	}

	/**
	 * Method to check-out a row for editing.
	 *
	 * @param   integer  $pk  The numeric id of the primary key.
	 *
	 * @return  boolean  False on failure or error, true otherwise.
	 *
	 * @since   1.6
	 */
	public function checkout($pk = null)
	{
		// Only attempt to check the row in if it exists.
		if ($pk)
		{
			// Get an instance of the row to checkout.
			$table = $this->getTable();

			if (!$table->load($pk))
			{
				$this->setError($table->getError());

				return false;
			}

			$checkedOutField = $table->getColumnAlias('checked_out');
			$checkedOutTimeField = $table->getColumnAlias('checked_out_time');

			// If there is no checked_out or checked_out_time field, just return true.
			if (!property_exists($table, $checkedOutField) || !property_exists($table, $checkedOutTimeField))
			{
				return true;
			}

			$user = \JFactory::getUser();

			// Check if this is the user having previously checked out the row.
			if ($table->{$checkedOutField} > 0 && $table->{$checkedOutField} != $user->get('id'))
			{
				$this->setError(\JText::_('JLIB_APPLICATION_ERROR_CHECKOUT_USER_MISMATCH'));

				return false;
			}

			// Attempt to check the row out.
			if (!$table->checkOut($user->get('id'), $pk))
			{
				$this->setError($table->getError());

				return false;
			}
		}

		return true;
	}

	/**
	 * Abstract method for getting the form from the model.
	 *
	 * @param   array    $data      Data for the form.
	 * @param   boolean  $loadData  True if the form is to load its own data (default case), false if not.
	 *
	 * @return  \JForm|boolean  A \JForm object on success, false on failure
	 *
	 * @since   1.6
	 */
	abstract public function getForm($data = array(), $loadData = true);

	/**
	 * Method to get a form object.
	 *
	 * @param   string   $name     The name of the form.
	 * @param   string   $source   The form source. Can be XML string if file flag is set to false.
	 * @param   array    $options  Optional array of options for the form creation.
	 * @param   boolean  $clear    Optional argument to force load a new form.
	 * @param   string   $xpath    An optional xpath to search for the fields.
	 *
	 * @return  \JForm|boolean  \JForm object on success, false on error.
	 *
	 * @see     \JForm
	 * @since   1.6
	 */
	protected function loadForm($name, $source = null, $options = array(), $clear = false, $xpath = false)
	{
		// Handle the optional arguments.
		$options['control'] = ArrayHelper::getValue((array) $options, 'control', false);

		// Create a signature hash. But make sure, that loading the data does not create a new instance
		$sigoptions = $options;

		if (isset($sigoptions['load_data']))
		{
			unset($sigoptions['load_data']);
		}

		$hash = md5($source . serialize($sigoptions));

		// Check if we can use a previously loaded form.
		if (!$clear && isset($this->_forms[$hash]))
		{
			return $this->_forms[$hash];
		}

		// Get the form.
		\JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
		\JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
		\JForm::addFormPath(JPATH_COMPONENT . '/model/form');
		\JForm::addFieldPath(JPATH_COMPONENT . '/model/field');

		try
		{
			$form = \JForm::getInstance($name, $source, $options, false, $xpath);

			if (isset($options['load_data']) && $options['load_data'])
			{
				// Get the data for the form.
				$data = $this->loadFormData();
			}
			else
			{
				$data = array();
			}

			// Allow for additional modification of the form, and events to be triggered.
			// We pass the data because plugins may require it.
			$this->preprocessForm($form, $data);

			// Load the data into the form after the plugins have operated.
			$form->bind($data);
		}
		catch (\Exception $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		// Store the form for later.
		$this->_forms[$hash] = $form;

		return $form;
	}

	/**
	 * Method to get the data that should be injected in the form.
	 *
	 * @return  array  The default data is an empty array.
	 *
	 * @since   1.6
	 */
	protected function loadFormData()
	{
		return array();
	}

	/**
	 * Method to allow derived classes to preprocess the data.
	 *
	 * @param   string  $context  The context identifier.
	 * @param   mixed   &$data    The data to be processed. It gets altered directly.
	 * @param   string  $group    The name of the plugin group to import (defaults to "content").
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	protected function preprocessData($context, &$data, $group = 'content')
	{
		// Get the dispatcher and load the users plugins.
		$dispatcher = \JEventDispatcher::getInstance();
		\JPluginHelper::importPlugin($group);

		// Trigger the data preparation event.
		$results = $dispatcher->trigger('onContentPrepareData', array($context, &$data));

		// Check for errors encountered while preparing the data.
		if (count($results) > 0 && in_array(false, $results, true))
		{
			$this->setError($dispatcher->getError());
		}
	}

	/**
	 * Method to allow derived classes to preprocess the form.
	 *
	 * @param   \JForm  $form   A \JForm object.
	 * @param   mixed   $data   The data expected for the form.
	 * @param   string  $group  The name of the plugin group to import (defaults to "content").
	 *
	 * @return  void
	 *
	 * @see     \JFormField
	 * @since   1.6
	 * @throws  \Exception if there is an error in the form event.
	 */
	protected function preprocessForm(\JForm $form, $data, $group = 'content')
	{
		// Import the appropriate plugin group.
		\JPluginHelper::importPlugin($group);

		// Get the dispatcher.
		$dispatcher = \JEventDispatcher::getInstance();

		// Trigger the form preparation event.
		$results = $dispatcher->trigger('onContentPrepareForm', array($form, $data));

		// Check for errors encountered while preparing the form.
		if (count($results) && in_array(false, $results, true))
		{
			// Get the last error.
			$error = $dispatcher->getError();

			if (!($error instanceof \Exception))
			{
				throw new \Exception($error);
			}
		}
	}

	/**
	 * Method to validate the form data.
	 *
	 * @param   \JForm  $form   The form to validate against.
	 * @param   array   $data   The data to validate.
	 * @param   string  $group  The name of the field group to validate.
	 *
	 * @return  array|boolean  Array of filtered data if valid, false otherwise.
	 *
	 * @see     \JFormRule
	 * @see     \JFilterInput
	 * @since   1.6
	 */
	public function validate($form, $data, $group = null)
	{
		// Include the plugins for the delete events.
		\JPluginHelper::importPlugin($this->events_map['validate']);

		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onUserBeforeDataValidation', array($form, &$data));

		// Filter and validate the form data.
		$data = $form->filter($data);
		$return = $form->validate($data, $group);

		// Check for an error.
		if ($return instanceof \Exception)
		{
			$this->setError($return->getMessage());

			return false;
		}

		// Check the validation results.
		if ($return === false)
		{
			// Get the validation messages from the form.
			foreach ($form->getErrors() as $message)
			{
				$this->setError($message);
			}

			return false;
		}

		// Tags B/C break at 3.1.2
		if (!isset($data['tags']) && isset($data['metadata']['tags']))
		{
			$data['tags'] = $data['metadata']['tags'];
		}

		return $data;
	}
}
src/MVC/Model/ItemModel.php000064400000002035152177723700011423 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\MVC\Model;

defined('JPATH_PLATFORM') or die;

/**
 * Prototype item model.
 *
 * @since  1.6
 */
abstract class ItemModel extends BaseDatabaseModel
{
	/**
	 * An item.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected $_item = null;

	/**
	 * Model context string.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_context = 'group.type';

	/**
	 * Method to get a store id based on model configuration state.
	 *
	 * This is necessary because the model is used by the component and
	 * different modules that might need different sets of data or different
	 * ordering requirements.
	 *
	 * @param   string  $id  A prefix for the store id.
	 *
	 * @return  string  A store id.
	 *
	 * @since   1.6
	 */
	protected function getStoreId($id = '')
	{
		// Compile the store id.
		return md5($id);
	}
}
src/Form/Field/ModuleorderField.php000064400000006401152177723700013233 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormField;
use Joomla\CMS\Session\Session;

/**
 * Module Order field.
 *
 * @since  1.6
 */
class ModuleorderField extends FormField
{
	/**
	 * The form field type.
	 *
	 * @var     string
	 * @since   1.6
	 */
	protected $type = 'ModuleOrder';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.6.3
	 */
	protected $layout = 'joomla.form.field.moduleorder';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.6.3
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'linked':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.6.3
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'linked':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.6.3
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->linked    = isset($this->element['linked']) ? (int) $this->element['linked'] : 'position';
		}

		return $return;
	}

	/**
	 * Method to get the field input markup for the moduleorder field.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since  3.6.3
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		$extraData = array(
			'ordering' => $this->form->getValue('ordering'),
			'clientId' => $this->form->getValue('client_id'),
			'name'     => $this->name,
			'token'    => Session::getFormToken() . '=1',
			'element'  => $this->form->getName() . '_' . $this->linked
		);

		return array_merge($data, $extraData);
	}
}
src/Form/Field/ChromestyleField.php000064400000013105152177723700013247 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('groupedlist');

/**
 * Chrome Styles field.
 *
 * @since  3.0
 */
class ChromestyleField extends \JFormFieldGroupedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.0
	 */
	public $type = 'ChromeStyle';

	/**
	 * The client ID.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $clientId;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'clientId':
				return $this->clientId;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to get the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'clientId':
				$this->clientId = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			// Get the client id.
			$clientId = $this->element['client_id'];

			if (!isset($clientId))
			{
				$clientName = $this->element['client'];

				if (isset($clientName))
				{
					$client = \JApplicationHelper::getClientInfo($clientName, true);
					$clientId = $client->id;
				}
			}

			if (!isset($clientId) && $this->form instanceof \JForm)
			{
				$clientId = $this->form->getValue('client_id');
			}

			$this->clientId = (int) $clientId;
		}

		return $result;
	}


	/**
	 * Method to get the list of template chrome style options
	 * grouped by template.
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   3.0
	 */
	protected function getGroups()
	{
		$groups = array();

		// Add Module Style Field
		$tmp = '---' . \JText::_('JLIB_FORM_VALUE_FROM_TEMPLATE') . '---';
		$groups[$tmp][] = \JHtml::_('select.option', '0', \JText::_('JLIB_FORM_VALUE_INHERITED'));

		$templateStyles = $this->getTemplateModuleStyles();

		// Create one new option object for each available style, grouped by templates
		foreach ($templateStyles as $template => $styles)
		{
			$template = ucfirst($template);
			$groups[$template] = array();

			foreach ($styles as $style)
			{
				$tmp = \JHtml::_('select.option', $template . '-' . $style, $style);
				$groups[$template][] = $tmp;
			}
		}

		reset($groups);

		return $groups;
	}

	/**
	 * Method to get the templates module styles.
	 *
	 * @return  array  The array of styles, grouped by templates.
	 *
	 * @since   3.0
	 */
	protected function getTemplateModuleStyles()
	{
		$moduleStyles = array();

		$templates = array($this->getSystemTemplate());
		$templates = array_merge($templates, $this->getTemplates());
		$path      = JPATH_ADMINISTRATOR;

		if ($this->clientId === 0)
		{
			$path = JPATH_SITE;
		}

		foreach ($templates as $template)
		{
			$modulesFilePath = $path . '/templates/' . $template->element . '/html/modules.php';

			// Is there modules.php for that template?
			if (file_exists($modulesFilePath))
			{
				$modulesFileData = file_get_contents($modulesFilePath);

				preg_match_all('/function[\s\t]*modChrome\_([a-z0-9\-\_]*)[\s\t]*\(/i', $modulesFileData, $styles);

				if (!array_key_exists($template->element, $moduleStyles))
				{
					$moduleStyles[$template->element] = array();
				}

				$moduleStyles[$template->element] = $styles[1];
			}
		}

		return $moduleStyles;
	}

	/**
	 * Method to get the system template as an object.
	 *
	 * @return  \stdClass  The object of system template.
	 *
	 * @since   3.0
	 */
	protected function getSystemTemplate()
	{
		$template = new \stdClass;
		$template->element = 'system';
		$template->name    = 'system';

		return $template;
	}

	/**
	 * Return a list of templates
	 *
	 * @return  array  List of templates
	 *
	 * @since   3.2.1
	 */
	protected function getTemplates()
	{
		$db = Factory::getDbo();

		// Get the database object and a new query object.
		$query = $db->getQuery(true);

		// Build the query.
		$query->select('element, name')
			->from('#__extensions')
			->where('client_id = ' . $this->clientId)
			->where('type = ' . $db->quote('template'))
			->where('enabled = 1');

		// Set the query and load the templates.
		$db->setQuery($query);

		return $db->loadObjectList('element');
	}
}
src/Form/Field/LimitboxField.php000064400000004363152177723700012546 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Field to load a list of posible item count limits
 *
 * @since  3.2
 */
class LimitboxField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $type = 'Limitbox';

	/**
	 * Cached array of the category items.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $options = array();

	/**
	 * Default options
	 *
	 * @var  array
	 */
	protected $defaultLimits = array(5, 10, 15, 20, 25, 30, 50, 100, 200, 500);

	/**
	 * Method to get the options to populate to populate list
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.2
	 */
	protected function getOptions()
	{
		// Accepted modifiers
		$hash = md5($this->element);

		if (!isset(static::$options[$hash]))
		{
			static::$options[$hash] = parent::getOptions();

			$options = array();
			$limits = $this->defaultLimits;

			// Limits manually specified
			if (isset($this->element['limits']))
			{
				$limits = explode(',', $this->element['limits']);
			}

			// User wants to add custom limits
			if (isset($this->element['append']))
			{
				$limits = array_unique(array_merge($limits, explode(',', $this->element['append'])));
			}

			// User wants to remove some default limits
			if (isset($this->element['remove']))
			{
				$limits = array_diff($limits, explode(',', $this->element['remove']));
			}

			// Order the options
			asort($limits);

			// Add an option to show all?
			$showAll = isset($this->element['showall']) ? (string) $this->element['showall'] === 'true' : true;

			if ($showAll)
			{
				$limits[] = 0;
			}

			if (!empty($limits))
			{
				foreach ($limits as $value)
				{
					$options[] = (object) array(
						'value' => $value,
						'text' => ($value != 0) ? \JText::_('J' . $value) : \JText::_('JALL'),
					);
				}

				static::$options[$hash] = array_merge(static::$options[$hash], $options);
			}
		}

		return static::$options[$hash];
	}
}
src/Form/Field/ContenthistoryField.php000064400000003626152177723700014014 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormField;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Table\Table;

/**
 * Field to select Content History from a modal list.
 *
 * @since  3.2
 */
class ContenthistoryField extends FormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $type = 'ContentHistory';

	/**
	 * Layout to render the label
	 *
	 * @var  string
	 */
	protected $layout = 'joomla.form.field.contenthistory';

	/**
	 * Get the data that is going to be passed to the layout
	 *
	 * @return  array
	 */
	public function getLayoutData()
	{
		// Get the basic field data
		$data = parent::getLayoutData();

		$typeId = Table::getInstance('Contenttype')->getTypeId($this->element['data-typeAlias']);
		$itemId = $this->form->getValue('id');
		$label  = \JText::_('JTOOLBAR_VERSIONS');

		$link   = 'index.php?option=com_contenthistory&amp;view=history&amp;layout=modal&amp;tmpl=component&amp;field='
			. $this->id . '&amp;item_id=' . $itemId . '&amp;type_id=' . $typeId . '&amp;type_alias='
			. $this->element['data-typeAlias'] . '&amp;' . Session::getFormToken() . '=1';

		$extraData = array(
				'type' => $typeId,
				'item' => $itemId,
				'label' => $label,
				'link' => $link,
		);

		return array_merge($data, $extraData);
	}

	/**
	 * Method to get the content history field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}
}
src/Form/Field/MenuitemField.php000064400000013637152177723700012546 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('groupedlist');

// Import the com_menus helper.
require_once realpath(JPATH_ADMINISTRATOR . '/components/com_menus/helpers/menus.php');

/**
 * Supports an HTML grouped select list of menu item grouped by menu
 *
 * @since  1.6
 */
class MenuitemField extends \JFormFieldGroupedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'MenuItem';

	/**
	 * The menu type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $menuType;

	/**
	 * The client id.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $clientId;

	/**
	 * The language.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $language;

	/**
	 * The published status.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $published;

	/**
	 * The disabled status.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $disable;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'menuType':
			case 'clientId':
			case 'language':
			case 'published':
			case 'disable':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'menuType':
				$this->menuType = (string) $value;
				break;

			case 'clientId':
				$this->clientId = (int) $value;
				break;

			case 'language':
			case 'published':
			case 'disable':
				$value = (string) $value;
				$this->$name = $value ? explode(',', $value) : array();
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			$this->menuType  = (string) $this->element['menu_type'];
			$this->clientId  = (int) $this->element['client_id'];
			$this->published = $this->element['published'] ? explode(',', (string) $this->element['published']) : array();
			$this->disable   = $this->element['disable'] ? explode(',', (string) $this->element['disable']) : array();
			$this->language  = $this->element['language'] ? explode(',', (string) $this->element['language']) : array();
		}

		return $result;
	}

	/**
	 * Method to get the field option groups.
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   1.6
	 */
	protected function getGroups()
	{
		$groups = array();

		$menuType = $this->menuType;

		// Get the menu items.
		$items = \MenusHelper::getMenuLinks($menuType, 0, 0, $this->published, $this->language, $this->clientId);

		// Build group for a specific menu type.
		if ($menuType)
		{
			// If the menutype is empty, group the items by menutype.
			$db    = Factory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName('title'))
				->from($db->quoteName('#__menu_types'))
				->where($db->quoteName('menutype') . ' = ' . $db->quote($menuType));
			$db->setQuery($query);

			try
			{
				$menuTitle = $db->loadResult();
			}
			catch (\RuntimeException $e)
			{
				$menuTitle = $menuType;
			}

			// Initialize the group.
			$groups[$menuTitle] = array();

			// Build the options array.
			foreach ($items as $link)
			{
				$levelPrefix = str_repeat('- ', max(0, $link->level - 1));

				// Displays language code if not set to All
				if ($link->language !== '*')
				{
					$lang = ' (' . $link->language . ')';
				}
				else
				{
					$lang = '';
				}

				$groups[$menuTitle][] = \JHtml::_('select.option',
								$link->value, $levelPrefix . $link->text . $lang,
								'value',
								'text',
								in_array($link->type, $this->disable)
							);
			}
		}
		// Build groups for all menu types.
		else
		{
			// Build the groups arrays.
			foreach ($items as $menu)
			{
				// Initialize the group.
				$groups[$menu->title] = array();

				// Build the options array.
				foreach ($menu->links as $link)
				{
					$levelPrefix = str_repeat('- ', max(0, $link->level - 1));

					// Displays language code if not set to All
					if ($link->language !== '*')
					{
						$lang = ' (' . $link->language . ')';
					}
					else
					{
						$lang = '';
					}

					$groups[$menu->title][] = \JHtml::_('select.option',
										$link->value, $levelPrefix . $link->text . $lang,
										'value',
										'text',
										in_array($link->type, $this->disable)
									);
				}
			}
		}

		// Merge any additional groups in the XML definition.
		$groups = array_merge(parent::getGroups(), $groups);

		return $groups;
	}
}
src/Form/Field/UsergrouplistField.php000064400000005335152177723700013646 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Helper\UserGroupsHelper;

FormHelper::loadFieldClass('list');

/**
 * Field to load a dropdown list of available user groups
 *
 * @since  3.2
 */
class UsergrouplistField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $type = 'UserGroupList';

	/**
	 * Cached array of the category items.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $options = array();

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		if (is_string($value) && strpos($value, ',') !== false)
		{
			$value = explode(',', $value);
		}

		return parent::setup($element, $value, $group);
	}

	/**
	 * Method to get the options to populate list
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.2
	 */
	protected function getOptions()
	{
		// Hash for caching
		$hash = md5($this->element);

		if (!isset(static::$options[$hash]))
		{
			static::$options[$hash] = parent::getOptions();

			$groups         = UserGroupsHelper::getInstance()->getAll();
			$checkSuperUser = (int) $this->getAttribute('checksuperusergroup', 0);
			$isSuperUser    = Factory::getUser()->authorise('core.admin');
			$options        = array();

			foreach ($groups as $group)
			{
				// Don't show super user groups to non super users.
				if ($checkSuperUser && !$isSuperUser && Access::checkGroup($group->id, 'core.admin'))
				{
					continue;
				}

				$options[] = (object) array(
					'text'  => str_repeat('- ', $group->level) . $group->title,
					'value' => $group->id,
					'level' => $group->level
				);
			}

			static::$options[$hash] = array_merge(static::$options[$hash], $options);
		}

		return static::$options[$hash];
	}
}
src/Form/Field/LastvisitdaterangeField.php000064400000002763152177723700014616 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Field to show a list of available date ranges to filter on last visit date.
 *
 * @since  3.6
 */
class LastvisitdaterangeField extends \JFormFieldPredefinedList
{
	/**
	 * Method to instantiate the form field object.
	 *
	 * @param   Form  $form  The form to attach to the form field object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($form = null)
	{
		parent::__construct($form);

		// Set the type
		$this->type = 'LastvisitDateRange';

		// Load the required language
		$lang = Factory::getLanguage();
		$lang->load('com_users', JPATH_ADMINISTRATOR);

		// Set the pre-defined options
		$this->predefinedOptions = array(
			'today'       => 'COM_USERS_OPTION_RANGE_TODAY',
			'past_week'   => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
			'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
			'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
			'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
			'past_year'   => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
			'post_year'   => 'COM_USERS_OPTION_RANGE_POST_YEAR',
			'never'       => 'COM_USERS_OPTION_RANGE_NEVER',
		);
	}
}
src/Form/Field/StatusField.php000064400000001461152177723700012236 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Form Field to load a list of states
 *
 * @since  3.2
 */
class StatusField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $type = 'Status';

	/**
	 * Available statuses
	 *
	 * @var  array
	 * @since  3.2
	 */
	protected $predefinedOptions = array(
		'-2' =>	'JTRASHED',
		'0'  => 'JUNPUBLISHED',
		'1'  => 'JPUBLISHED',
		'2'  => 'JARCHIVED',
		'*'  => 'JALL',
	);
}
src/Form/Field/UserstateField.php000064400000001402152177723700012725 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Field to load a list of available users statuses
 *
 * @since  3.2
 */
class UserstateField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $type = 'UserState';

	/**
	 * Available statuses
	 *
	 * @var  array
	 * @since  3.2
	 */
	protected $predefinedOptions = array(
		'0'  => 'JENABLED',
		'1'  => 'JDISABLED',
	);
}
src/Form/Field/ContenttypeField.php000064400000004432152177723700013270 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Content Type field.
 *
 * @since  3.1
 */
class ContenttypeField extends \JFormFieldList
{
	/**
	 * A flexible tag list that respects access controls
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $type = 'Contenttype';

	/**
	 * Method to get the field input for a list of content types.
	 *
	 * @return  string  The field input.
	 *
	 * @since   3.1
	 */
	protected function getInput()
	{
		if (!is_array($this->value))
		{
			if (is_object($this->value))
			{
				$this->value = $this->value->tags;
			}

			if (is_string($this->value))
			{
				$this->value = explode(',', $this->value);
			}
		}

		return parent::getInput();
	}

	/**
	 * Method to get a list of content types
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.1
	 */
	protected function getOptions()
	{
		$lang = Factory::getLanguage();
		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('a.type_id AS value, a.type_title AS text, a.type_alias AS alias')
			->from('#__content_types AS a')

			->order('a.type_title ASC');

		// Get the options.
		$db->setQuery($query);

		try
		{
			$options = $db->loadObjectList();
		}
		catch (\RuntimeException $e)
		{
			return array();
		}

		foreach ($options as $option)
		{
			// Make up the string from the component sys.ini file
			$parts = explode('.', $option->alias);
			$comp = array_shift($parts);

			// Make sure the component sys.ini is loaded
			$lang->load($comp . '.sys', JPATH_ADMINISTRATOR, null, false, true)
			|| $lang->load($comp . '.sys', JPATH_ADMINISTRATOR . '/components/' . $comp, null, false, true);

			$option->string = implode('_', $parts);
			$option->string = $comp . '_CONTENT_TYPE_' . $option->string;

			if ($lang->hasKey($option->string))
			{
				$option->text = \JText::_($option->string);
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
src/Form/Field/MediaField.php000064400000014222152177723700011771 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\FormField;

/**
 * Provides a modal media selector including upload mechanism
 *
 * @since  1.6
 */
class MediaField extends FormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $type = 'Media';

	/**
	 * The authorField.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $authorField;

	/**
	 * The asset.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $asset;

	/**
	 * The link.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $link;

	/**
	 * Modal width.
	 *
	 * @var    integer
	 * @since  3.4.5
	 */
	protected $width;

	/**
	 * Modal height.
	 *
	 * @var    integer
	 * @since  3.4.5
	 */
	protected $height;

	/**
	 * The authorField.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $preview;

	/**
	 * The preview.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $directory;

	/**
	 * The previewWidth.
	 *
	 * @var    int
	 * @since  3.2
	 */
	protected $previewWidth;

	/**
	 * The previewHeight.
	 *
	 * @var    int
	 * @since  3.2
	 */
	protected $previewHeight;

	/**
	 * Layout to render
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $layout = 'joomla.form.field.media';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'authorField':
			case 'asset':
			case 'link':
			case 'width':
			case 'height':
			case 'preview':
			case 'directory':
			case 'previewWidth':
			case 'previewHeight':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'authorField':
			case 'asset':
			case 'link':
			case 'width':
			case 'height':
			case 'preview':
			case 'directory':
				$this->$name = (string) $value;
				break;

			case 'previewWidth':
			case 'previewHeight':
				$this->$name = (int) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see 	FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			$assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';

			$this->authorField   = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
			$this->asset         = $this->form->getValue($assetField) ?: (string) $this->element['asset_id'];
			$this->link          = (string) $this->element['link'];
			$this->width  	     = isset($this->element['width']) ? (int) $this->element['width'] : 800;
			$this->height 	     = isset($this->element['height']) ? (int) $this->element['height'] : 500;
			$this->preview       = (string) $this->element['preview'];
			$this->directory     = (string) $this->element['directory'];
			$this->previewWidth  = isset($this->element['preview_width']) ? (int) $this->element['preview_width'] : 200;
			$this->previewHeight = isset($this->element['preview_height']) ? (int) $this->element['preview_height'] : 200;
		}

		return $result;
	}

	/**
	 * Method to get the field input markup for a media selector.
	 * Use attributes to identify specific created_by and asset_id fields
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Get the data that is going to be passed to the layout
	 *
	 * @return  array
	 */
	public function getLayoutData()
	{
		// Get the basic field data
		$data = parent::getLayoutData();

		$asset = $this->asset;

		if ($asset === '')
		{
			$asset = \JFactory::getApplication()->input->get('option');
		}

		if ($this->value && file_exists(JPATH_ROOT . '/' . $this->value))
		{
			$this->folder = explode('/', $this->value);
			$this->folder = array_diff_assoc($this->folder, explode('/', ComponentHelper::getParams('com_media')->get('image_path', 'images')));
			array_pop($this->folder);
			$this->folder = implode('/', $this->folder);
		}
		elseif (file_exists(JPATH_ROOT . '/' . ComponentHelper::getParams('com_media')->get('image_path', 'images') . '/' . $this->directory))
		{
			$this->folder = $this->directory;
		}
		else
		{
			$this->folder = '';
		}

		$extraData = array(
			'asset'         => $asset,
			'authorField'   => $this->authorField,
			'authorId'      => $this->form->getValue($this->authorField),
			'folder'        => $this->folder,
			'link'          => $this->link,
			'preview'       => $this->preview,
			'previewHeight' => $this->previewHeight,
			'previewWidth'  => $this->previewWidth,
		);

		return array_merge($data, $extraData);
	}
}
src/Form/Field/ModuletagField.php000064400000002046152177723700012674 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Module Tag field.
 *
 * @since  3.0
 */
class ModuletagField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $type = 'ModuleTag';

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.0
	 */
	protected function getOptions()
	{
		$options = array();
		$tags    = array('address', 'article', 'aside', 'details', 'div', 'footer', 'header', 'main', 'nav', 'section', 'summary');

		// Create one new option object for each tag
		foreach ($tags as $tag)
		{
			$tmp = \JHtml::_('select.option', $tag, $tag);
			$options[] = $tmp;
		}

		reset($options);

		return $options;
	}
}
src/Form/Field/PluginstatusField.php000064400000001353152177723700013455 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Plugin Status field.
 *
 * @since  3.5
 */
class PluginstatusField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $type = 'Plugin_Status';

	/**
	 * Available statuses
	 *
	 * @var  array
	 * @since  3.5
	 */
	protected $predefinedOptions = array(
		'0'  => 'JDISABLED',
		'1'  => 'JENABLED',
	);
}
src/Form/Field/OrderingField.php000064400000011275152177723700012530 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\UCM\UCMType;

/**
 * Ordering field.
 *
 * @since  3.2
 */
class OrderingField extends FormField
{
	/**
	 * The form field type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $type = 'Ordering';

	/**
	 * The form field content type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $contentType;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'contentType':
				return $this->contentType;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'contentType':
				$this->contentType = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			$this->contentType = (string) $this->element['content_type'];
		}

		return $result;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string	The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		$html = array();
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= $this->disabled ? ' disabled' : '';
		$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';

		// Initialize JavaScript field attributes.
		$attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';

		$itemId = (int) $this->getItemId();

		$query = $this->getQuery();

		// Create a read-only list (no name) with a hidden input to store the value.
		if ($this->readonly)
		{
			$html[] = \JHtml::_('list.ordering', '', $query, trim($attr), $this->value, $itemId ? 0 : 1);
			$html[] = '<input type="hidden" name="' . $this->name . '" value="' . $this->value . '"/>';
		}
		else
		{
			// Create a regular list.
			$html[] = \JHtml::_('list.ordering', $this->name, $query, trim($attr), $this->value, $itemId ? 0 : 1);
		}

		return implode($html);
	}

	/**
	 * Builds the query for the ordering list.
	 *
	 * @return  \JDatabaseQuery  The query for the ordering form field
	 *
	 * @since   3.2
	 */
	protected function getQuery()
	{
		$categoryId   = (int) $this->form->getValue('catid');
		$ucmType      = new UCMType;
		$ucmRow       = $ucmType->getType($ucmType->getTypeId($this->contentType));
		$ucmMapCommon = json_decode($ucmRow->field_mappings)->common;

		if (is_object($ucmMapCommon))
		{
			$ordering = $ucmMapCommon->core_ordering;
			$title    = $ucmMapCommon->core_title;
		}
		elseif (is_array($ucmMapCommon))
		{
			$ordering = $ucmMapCommon[0]->core_ordering;
			$title    = $ucmMapCommon[0]->core_title;
		}

		$db    = Factory::getDbo();
		$query = $db->getQuery(true);
		$query->select(array($db->quoteName($ordering, 'value'), $db->quoteName($title, 'text')))
			->from($db->quoteName(json_decode($ucmRow->table)->special->dbtable))
			->where($db->quoteName('catid') . ' = ' . (int) $categoryId)
			->order('ordering');

		return $query;
	}

	/**
	 * Retrieves the current Item's Id.
	 *
	 * @return  integer  The current item ID
	 *
	 * @since   3.2
	 */
	protected function getItemId()
	{
		return (int) $this->form->getValue('id');
	}
}
src/Form/Field/HeadertagField.php000064400000001775152177723700012647 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla! CMS.
 *
 * @since  3.0
 */
class HeadertagField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $type = 'HeaderTag';

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.0
	 */
	protected function getOptions()
	{
		$options = array();
		$tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div');

		// Create one new option object for each tag
		foreach ($tags as $tag)
		{
			$tmp = \JHtml::_('select.option', $tag, $tag);
			$options[] = $tmp;
		}

		reset($options);

		return $options;
	}
}
src/Form/Field/AuthorField.php000064400000003036152177723700012215 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Form Field to load a list of content authors
 *
 * @since  3.2
 */
class AuthorField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $type = 'Author';

	/**
	 * Cached array of the category items.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $options = array();

	/**
	 * Method to get the options to populate list
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.2
	 */
	protected function getOptions()
	{
		// Accepted modifiers
		$hash = md5($this->element);

		if (!isset(static::$options[$hash]))
		{
			static::$options[$hash] = parent::getOptions();

			$db = Factory::getDbo();

			// Construct the query
			$query = $db->getQuery(true)
				->select('u.id AS value, u.name AS text')
				->from('#__users AS u')
				->join('INNER', '#__content AS c ON c.created_by = u.id')
				->group('u.id, u.name')
				->order('u.name');

			// Setup the query
			$db->setQuery($query);

			// Return the result
			if ($options = $db->loadObjectList())
			{
				static::$options[$hash] = array_merge(static::$options[$hash], $options);
			}
		}

		return static::$options[$hash];
	}
}
src/Form/Field/UseractiveField.php000064400000002264152177723700013067 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Field to show a list of available user active statuses
 *
 * @since  3.2
 */
class UseractiveField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $type = 'UserActive';

	/**
	 * Available statuses
	 *
	 * @var  array
	 * @since  3.2
	 */
	protected $predefinedOptions = array(
		'0'  => 'COM_USERS_ACTIVATED',
		'1'  => 'COM_USERS_UNACTIVATED',
	);

	/**
	 * Method to instantiate the form field object.
	 *
	 * @param   Form  $form  The form to attach to the form field object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($form = null)
	{
		parent::__construct($form);

		// Load the required language
		$lang = Factory::getLanguage();
		$lang->load('com_users', JPATH_ADMINISTRATOR);
	}
}
src/Form/Field/MenuField.php000064400000005603152177723700011661 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

// Import the com_menus helper.
require_once realpath(JPATH_ADMINISTRATOR . '/components/com_menus/helpers/menus.php');

FormHelper::loadFieldClass('GroupedList');

/**
 * Supports an HTML select list of menus
 *
 * @since  1.6
 */
class MenuField extends \JFormFieldGroupedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'Menu';

	/**
	 * Method to get the field option groups.
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	protected function getGroups()
	{
		$clientId   = (string) $this->element['clientid'];
		$accessType = (string) $this->element['accesstype'];
		$showAll    = (string) $this->element['showAll'] == 'true';

		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select($db->qn(array('id', 'menutype', 'title', 'client_id'), array('id', 'value', 'text', 'client_id')))
			->from($db->quoteName('#__menu_types'))
			->order('client_id, title');

		if (strlen($clientId))
		{
			$query->where('client_id = ' . (int) $clientId);
		}

		$menus = $db->setQuery($query)->loadObjectList();

		if ($accessType)
		{
			$user = Factory::getUser();

			foreach ($menus as $key => $menu)
			{
				switch ($accessType)
				{
					case 'create':
					case 'manage':
						if (!$user->authorise('core.' . $accessType, 'com_menus.menu.' . (int) $menu->id))
						{
							unset($menus[$key]);
						}
						break;

					// Editing a menu item is a bit tricky, we have to check the current menutype for core.edit and all others for core.create
					case 'edit':
						$check = $this->value == $menu->value ? 'edit' : 'create';

						if (!$user->authorise('core.' . $check, 'com_menus.menu.' . (int) $menu->id))
						{
							unset($menus[$key]);
						}
						break;
				}
			}
		}

		$opts = array();

		// Protected menutypes can be shown if requested
		if ($clientId == 1 && $showAll)
		{
			$opts[] = (object) array(
				'value'     => 'main',
				'text'      => \JText::_('COM_MENUS_MENU_TYPE_PROTECTED_MAIN_LABEL'),
				'client_id' => 1,
			);
		}

		$options = array_merge($opts, $menus);
		$groups  = array();

		if (strlen($clientId))
		{
			$groups[0] = $options;
		}
		else
		{
			foreach ($options as $option)
			{
				// If client id is not specified we group the items.
				$label = ($option->client_id == 1 ? \JText::_('JADMINISTRATOR') : \JText::_('JSITE'));

				$groups[$label][] = $option;
			}
		}

		// Merge any additional options in the XML definition.
		return array_merge(parent::getGroups(), $groups);
	}
}
src/Form/Field/ContentlanguageField.php000064400000001655152177723700014076 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Provides a list of content languages
 *
 * @see    JFormFieldLanguage for a select list of application languages.
 * @since  1.6
 */
class ContentlanguageField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'ContentLanguage';

	/**
	 * Method to get the field options for content languages.
	 *
	 * @return  array  The options the field is going to show.
	 *
	 * @since   1.6
	 */
	protected function getOptions()
	{
		return array_merge(parent::getOptions(), \JHtml::_('contentlanguage.existing'));
	}
}
src/Form/Field/HelpsiteField.php000064400000003265152177723700012534 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Help\Help;

FormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a select list of help sites.
 *
 * @since       1.6
 * @deprecated  4.0 To be removed
 */
class HelpsiteField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'Helpsite';

	/**
	 * Method to get the help site field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.6
	 */
	protected function getOptions()
	{
		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), Help::createSiteList(JPATH_ADMINISTRATOR . '/help/helpsites.xml', $this->value));

		return $options;
	}

	/**
	 * Override to add refresh button
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		\JHtml::_('script', 'system/helpsite.js', array('version' => 'auto', 'relative' => true));

		$showDefault = (string) $this->getAttribute('showDefault') === 'false' ? 'false' : 'true';

		$html = parent::getInput();
		$button = '<button
						type="button"
						class="btn btn-small"
						id="helpsite-refresh"
						rel="' . $this->id . '"
						showDefault="' . $showDefault . '"
					>
					<span>' . \JText::_('JGLOBAL_HELPREFRESH_BUTTON') . '</span>
					</button>';

		return $html . $button;
	}
}
src/Form/Field/UserField.php000064400000007552152177723700011700 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;
use Joomla\CMS\User\User;

/**
 * Field to select a user ID from a modal list.
 *
 * @since  1.6
 */
class UserField extends FormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'User';

	/**
	 * Filtering groups
	 *
	 * @var   array
	 * @since 3.5
	 */
	protected $groups = null;

	/**
	 * Users to exclude from the list of users
	 *
	 * @var   array
	 * @since 3.5
	 */
	protected $excluded = null;

	/**
	 * Layout to render
	 *
	 * @var   string
	 * @since 3.5
	 */
	protected $layout = 'joomla.form.field.user';

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.7.0
	 *
	 * @see     JFormField::setup()
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		// If user can't access com_users the field should be readonly.
		if ($return && !$this->readonly)
		{
			$this->readonly = !Factory::getUser()->authorise('core.manage', 'com_users');
		}

		return $return;
	}

	/**
	 * Method to get the user field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());

	}

	/**
	 * Get the data that is going to be passed to the layout
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	public function getLayoutData()
	{
		// Get the basic field data
		$data = parent::getLayoutData();

		// Initialize value
		$name = \JText::_('JLIB_FORM_SELECT_USER');

		if (is_numeric($this->value))
		{
			$name = User::getInstance($this->value)->name;
		}
		// Handle the special case for "current".
		elseif (strtoupper($this->value) === 'CURRENT')
		{
			// 'CURRENT' is not a reasonable value to be placed in the html
			$current = Factory::getUser();

			$this->value = $current->id;

			$data['value'] = $this->value;

			$name = $current->name;
		}

		// User lookup went wrong, we assign the value instead.
		if ($name === null && $this->value)
		{
			$name = $this->value;
		}

		$extraData = array(
			'userName'  => $name,
			'groups'    => $this->getGroups(),
			'excluded'  => $this->getExcluded(),
		);

		return array_merge($data, $extraData);
	}

	/**
	 * Method to get the filtering groups (null means no filtering)
	 *
	 * @return  mixed  Array of filtering groups or null.
	 *
	 * @since   1.6
	 */
	protected function getGroups()
	{
		if (isset($this->element['groups']))
		{
			return explode(',', $this->element['groups']);
		}

		return;
	}

	/**
	 * Method to get the users to exclude from the list of users
	 *
	 * @return  mixed  Array of users to exclude or null to to not exclude them
	 *
	 * @since   1.6
	 */
	protected function getExcluded()
	{
		if (isset($this->element['exclude']))
		{
			return explode(',', $this->element['exclude']);
		}

		return;
	}
}
src/Form/Field/RedirectStatusField.php000064400000001466152177723700013725 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Redirect Status field.
 *
 * @since  3.8.0
 */
class RedirectStatusField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.8.0
	 */
	public $type = 'Redirect_Status';

	/**
	 * Available statuses
	 *
	 * @var  array
	 * @since  3.8.0
	 */
	protected $predefinedOptions = array(
		'-2' => 'JTRASHED',
		'0'  => 'JDISABLED',
		'1'  => 'JENABLED',
		'2'  => 'JARCHIVED',
		'*'  => 'JALL',
	);
}
src/Form/Field/TagField.php000064400000013073152177723700011470 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Utilities\ArrayHelper;

FormHelper::loadFieldClass('list');

/**
 * List of Tags field.
 *
 * @since  3.1
 */
class TagField extends \JFormFieldList
{
	/**
	 * A flexible tag list that respects access controls
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $type = 'Tag';

	/**
	 * Flag to work with nested tag field
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	public $isNested = null;

	/**
	 * com_tags parameters
	 *
	 * @var    \Joomla\Registry\Registry
	 * @since  3.1
	 */
	protected $comParams = null;

	/**
	 * Constructor
	 *
	 * @since  3.1
	 */
	public function __construct()
	{
		parent::__construct();

		// Load com_tags config
		$this->comParams = ComponentHelper::getParams('com_tags');
	}

	/**
	 * Method to get the field input for a tag field.
	 *
	 * @return  string  The field input.
	 *
	 * @since   3.1
	 */
	protected function getInput()
	{
		// AJAX mode requires ajax-chosen
		if (!$this->isNested())
		{
			// Get the field id
			$id    = isset($this->element['id']) ? $this->element['id'] : null;
			$cssId = '#' . $this->getId($id, $this->element['name']);

			// Load the ajax-chosen customised field
			\JHtml::_('tag.ajaxfield', $cssId, $this->allowCustom());
		}

		if (!is_array($this->value) && !empty($this->value))
		{
			if ($this->value instanceof TagsHelper)
			{
				if (empty($this->value->tags))
				{
					$this->value = array();
				}
				else
				{
					$this->value = $this->value->tags;
				}
			}

			// String in format 2,5,4
			if (is_string($this->value))
			{
				$this->value = explode(',', $this->value);
			}
		}

		return parent::getInput();
	}

	/**
	 * Method to get a list of tags
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.1
	 */
	protected function getOptions()
	{
		$published = $this->element['published'] ?: array(0, 1);
		$app       = Factory::getApplication();
		$tag       = $app->getLanguage()->getTag();

		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('DISTINCT a.id AS value, a.path, a.title AS text, a.level, a.published, a.lft')
			->from('#__tags AS a')
			->join('LEFT', $db->qn('#__tags') . ' AS b ON a.lft > b.lft AND a.rgt < b.rgt');

		// Limit Options in multilanguage
		if ($app->isClient('site') && Multilanguage::isEnabled())
		{
			$lang = ComponentHelper::getParams('com_tags')->get('tag_list_language_filter');

			if ($lang == 'current_language')
			{
				$query->where('a.language in (' . $db->quote($tag) . ',' . $db->quote('*') . ')');
			}
		}
		// Filter language
		elseif (!empty($this->element['language']))
		{
			if (strpos($this->element['language'], ',') !== false)
			{
				$language = implode(',', $db->quote(explode(',', $this->element['language'])));
			}
			else
			{
				$language = $db->quote($this->element['language']);
			}

			$query->where($db->quoteName('a.language') . ' IN (' . $language . ')');
		}

		$query->where($db->qn('a.lft') . ' > 0');

		// Filter on the published state
		if (is_numeric($published))
		{
			$query->where('a.published = ' . (int) $published);
		}
		elseif (is_array($published))
		{
			$published = ArrayHelper::toInteger($published);
			$query->where('a.published IN (' . implode(',', $published) . ')');
		}

		$query->order('a.lft ASC');

		// Get the options.
		$db->setQuery($query);

		try
		{
			$options = $db->loadObjectList();
		}
		catch (\RuntimeException $e)
		{
			return array();
		}

		// Block the possibility to set a tag as it own parent
		if ($this->form->getName() === 'com_tags.tag')
		{
			$id   = (int) $this->form->getValue('id', 0);

			foreach ($options as $option)
			{
				if ($option->value == $id)
				{
					$option->disable = true;
				}
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		// Prepare nested data
		if ($this->isNested())
		{
			$this->prepareOptionsNested($options);
		}
		else
		{
			$options = TagsHelper::convertPathsToNames($options);
		}

		return $options;
	}

	/**
	 * Add "-" before nested tags, depending on level
	 *
	 * @param   array  &$options  Array of tags
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.1
	 */
	protected function prepareOptionsNested(&$options)
	{
		if ($options)
		{
			foreach ($options as &$option)
			{
				$repeat = (isset($option->level) && $option->level - 1 >= 0) ? $option->level - 1 : 0;
				$option->text = str_repeat('- ', $repeat) . $option->text;
			}
		}

		return $options;
	}

	/**
	 * Determine if the field has to be tagnested
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function isNested()
	{
		if ($this->isNested === null)
		{
			// If mode="nested" || ( mode not set & config = nested )
			if (isset($this->element['mode']) && (string) $this->element['mode'] === 'nested'
				|| !isset($this->element['mode']) && $this->comParams->get('tag_field_ajax_mode', 1) == 0)
			{
				$this->isNested = true;
			}
		}

		return $this->isNested;
	}

	/**
	 * Determines if the field allows or denies custom values
	 *
	 * @return  boolean
	 */
	public function allowCustom()
	{
		if (isset($this->element['custom']) && (string) $this->element['custom'] === 'deny')
		{
			return false;
		}

		return true;
	}
}
src/Form/Field/RegistrationdaterangeField.php000064400000002760152177723700015303 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('predefinedlist');

/**
 * Registration Date Range field.
 *
 * @since  3.2
 */
class RegistrationdaterangeField extends \JFormFieldPredefinedList
{
	/**
	 * The form field type.
	 *
	 * @var		string
	 * @since   3.2
	 */
	protected $type = 'RegistrationDateRange';

	/**
	 * Available options
	 *
	 * @var  array
	 * @since  3.2
	 */
	protected $predefinedOptions = array(
		'today'       => 'COM_USERS_OPTION_RANGE_TODAY',
		'past_week'   => 'COM_USERS_OPTION_RANGE_PAST_WEEK',
		'past_1month' => 'COM_USERS_OPTION_RANGE_PAST_1MONTH',
		'past_3month' => 'COM_USERS_OPTION_RANGE_PAST_3MONTH',
		'past_6month' => 'COM_USERS_OPTION_RANGE_PAST_6MONTH',
		'past_year'   => 'COM_USERS_OPTION_RANGE_PAST_YEAR',
		'post_year'   => 'COM_USERS_OPTION_RANGE_POST_YEAR',
	);

	/**
	 * Method to instantiate the form field object.
	 *
	 * @param   Form  $form  The form to attach to the form field object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($form = null)
	{
		parent::__construct($form);

		// Load the required language
		$lang = Factory::getLanguage();
		$lang->load('com_users', JPATH_ADMINISTRATOR);
	}
}
src/Form/Field/FrontendlanguageField.php000064400000003730152177723700014237 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('list');

/**
 * Provides a list of published content languages with home pages
 *
 * @see    JFormFieldLanguage for a select list of application languages.
 * @since  3.5
 */
class FrontendlanguageField extends \JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $type = 'Frontend_Language';

	/**
	 * Method to get the field options for frontend published content languages with homes.
	 *
	 * @return  array  The options the field is going to show.
	 *
	 * @since   3.5
	 */
	protected function getOptions()
	{
		// Get the database object and a new query object.
		$db    = Factory::getDbo();
		$query = $db->getQuery(true);

		$query->select('a.lang_code AS value, a.title AS text')
			->from($db->quoteName('#__languages') . ' AS a')
			->where('a.published = 1')
			->order('a.title');

		// Select the language home pages.
		$query->select('l.home, l.language')
			->innerJoin($db->quoteName('#__menu') . ' AS l ON l.language=a.lang_code AND l.home=1 AND l.published=1 AND l.language <> ' . $db->quote('*'))
			->innerJoin($db->quoteName('#__extensions') . ' AS e ON e.element = a.lang_code')
			->where('e.client_id = 0')
			->where('e.enabled = 1')
			->where('e.state = 0');

		$db->setQuery($query);

		try
		{
			$languages = $db->loadObjectList();
		}
		catch (\RuntimeException $e)
		{
			$languages = array();

			if (Factory::getUser()->authorise('core.admin'))
			{
				Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
			}
		}

		// Merge any additional options in the XML definition.
		return array_merge(parent::getOptions(), $languages);
	}
}
src/Form/Field/TemplatestyleField.php000064400000011237152177723700013611 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('groupedlist');

/**
 * Supports a select grouped list of template styles
 *
 * @since  1.6
 */
class TemplatestyleField extends \JFormFieldGroupedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'TemplateStyle';

	/**
	 * The client name.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $clientName;

	/**
	 * The template.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $template;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'clientName':
			case 'template':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'clientName':
			case 'template':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			// Get the clientName template.
			$this->clientName = $this->element['client'] ? (string) $this->element['client'] : 'site';
			$this->template = (string) $this->element['template'];
		}

		return $result;
	}

	/**
	 * Method to get the list of template style options grouped by template.
	 * Use the client attribute to specify a specific client.
	 * Use the template attribute to specify a specific template
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   1.6
	 */
	protected function getGroups()
	{
		$groups = array();
		$lang = Factory::getLanguage();

		// Get the client and client_id.
		$client = ApplicationHelper::getClientInfo($this->clientName, true);

		// Get the template.
		$template = $this->template;

		// Get the database object and a new query object.
		$db = Factory::getDbo();
		$query = $db->getQuery(true);

		// Build the query.
		$query->select('s.id, s.title, e.name as name, s.template')
			->from('#__template_styles as s')
			->where('s.client_id = ' . (int) $client->id)
			->order('template')
			->order('title');

		if ($template)
		{
			$query->where('s.template = ' . $db->quote($template));
		}

		$query->join('LEFT', '#__extensions as e on e.element=s.template')
			->where('e.enabled = 1')
			->where($db->quoteName('e.type') . ' = ' . $db->quote('template'));

		// Set the query and load the styles.
		$db->setQuery($query);
		$styles = $db->loadObjectList();

		// Build the grouped list array.
		if ($styles)
		{
			foreach ($styles as $style)
			{
				$template = $style->template;
				$lang->load('tpl_' . $template . '.sys', $client->path, null, false, true)
					|| $lang->load('tpl_' . $template . '.sys', $client->path . '/templates/' . $template, null, false, true);
				$name = \JText::_($style->name);

				// Initialize the group if necessary.
				if (!isset($groups[$name]))
				{
					$groups[$name] = array();
				}

				$groups[$name][] = \JHtml::_('select.option', $style->id, $style->title);
			}
		}

		// Merge any additional groups in the XML definition.
		$groups = array_merge(parent::getGroups(), $groups);

		return $groups;
	}
}
src/Form/Field/CaptchaField.php000064400000010245152177723700012316 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Captcha\Captcha;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormField;

/**
 * Captcha field.
 *
 * @since  2.5
 */
class CaptchaField extends FormField
{
	/**
	 * The field type.
	 *
	 * @var    string
	 * @since  2.5
	 */
	protected $type = 'Captcha';

	/**
	 * The captcha base instance of our type.
	 *
	 * @var Captcha
	 */
	protected $_captcha;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'plugin':
			case 'namespace':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'plugin':
			case 'namespace':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   2.5
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		$app = Factory::getApplication();

		$default = $app->get('captcha');

		if ($app->isClient('site'))
		{
			$default = $app->getParams()->get('captcha', $default);
		}

		$plugin = $this->element['plugin'] ?
			(string) $this->element['plugin'] :
			$default;

		$this->plugin = $plugin;

		if ($plugin === 0 || $plugin === '0' || $plugin === '' || $plugin === null)
		{
			$this->hidden = true;

			return false;
		}
		else
		{
			// Force field to be required. There's no reason to have a captcha if it is not required.
			// Obs: Don't put required="required" in the xml file, you just need to have validate="captcha"
			$this->required = true;

			if (strpos($this->class, 'required') === false)
			{
				$this->class .= ' required';
			}
		}

		$this->namespace = $this->element['namespace'] ? (string) $this->element['namespace'] : $this->form->getName();

		try
		{
			// Get an instance of the captcha class that we are using
			$this->_captcha = Captcha::getInstance($this->plugin, array('namespace' => $this->namespace));

			/**
			 * Give the captcha instance a possibility to react on the setup-process,
			 * e.g. by altering the XML structure of the field, for example hiding the label
			 * when using invisible captchas.
			 */
			$this->_captcha->setupField($this, $element);
		}
		catch (\RuntimeException $e)
		{
			$this->_captcha = null;
			\JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');

			return false;
		}

		return $result;
	}

	/**
	 * Method to get the field input.
	 *
	 * @return  string  The field input.
	 *
	 * @since   2.5
	 */
	protected function getInput()
	{
		if ($this->hidden || $this->_captcha == null)
		{
			return '';
		}

		try
		{
			return $this->_captcha->display($this->name, $this->id, $this->class);
		}
		catch (\RuntimeException $e)
		{
			\JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
		}
		return '';
	}
}
src/Form/Field/EditorField.php000064400000017033152177723700012203 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('textarea');

/**
 * A textarea field for content creation
 *
 * @see    JEditor
 * @since  1.6
 */
class EditorField extends \JFormFieldTextarea
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'Editor';

	/**
	 * The Editor object.
	 *
	 * @var    Editor
	 * @since  1.6
	 */
	protected $editor;

	/**
	 * The height of the editor.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $height;

	/**
	 * The width of the editor.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $width;

	/**
	 * The assetField of the editor.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $assetField;

	/**
	 * The authorField of the editor.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $authorField;

	/**
	 * The asset of the editor.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $asset;

	/**
	 * The buttons of the editor.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $buttons;

	/**
	 * The hide of the editor.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $hide;

	/**
	 * The editorType of the editor.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $editorType;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'height':
			case 'width':
			case 'assetField':
			case 'authorField':
			case 'asset':
			case 'buttons':
			case 'hide':
			case 'editorType':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'height':
			case 'width':
			case 'assetField':
			case 'authorField':
			case 'asset':
				$this->$name = (string) $value;
				break;

			case 'buttons':
				$value = (string) $value;

				if ($value === 'true' || $value === 'yes' || $value === '1')
				{
					$this->buttons = true;
				}
				elseif ($value === 'false' || $value === 'no' || $value === '0')
				{
					$this->buttons = false;
				}
				else
				{
					$this->buttons = explode(',', $value);
				}
				break;

			case 'hide':
				$value = (string) $value;
				$this->hide = $value ? explode(',', $value) : array();
				break;

			case 'editorType':
				// Can be in the form of: editor="desired|alternative".
				$this->editorType  = explode('|', trim((string) $value));
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			$this->height      = $this->element['height'] ? (string) $this->element['height'] : '500';
			$this->width       = $this->element['width'] ? (string) $this->element['width'] : '100%';
			$this->assetField  = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
			$this->authorField = $this->element['created_by_field'] ? (string) $this->element['created_by_field'] : 'created_by';
			$this->asset       = $this->form->getValue($this->assetField) ?: (string) $this->element['asset_id'];

			$buttons    = (string) $this->element['buttons'];
			$hide       = (string) $this->element['hide'];
			$editorType = (string) $this->element['editor'];

			if ($buttons === 'true' || $buttons === 'yes' || $buttons === '1')
			{
				$this->buttons = true;
			}
			elseif ($buttons === 'false' || $buttons === 'no' || $buttons === '0')
			{
				$this->buttons = false;
			}
			else
			{
				$this->buttons = !empty($hide) ? explode(',', $buttons) : array();
			}

			$this->hide        = !empty($hide) ? explode(',', (string) $this->element['hide']) : array();
			$this->editorType  = !empty($editorType) ? explode('|', trim($editorType)) : array();
		}

		return $result;
	}

	/**
	 * Method to get the field input markup for the editor area
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		// Get an editor object.
		$editor = $this->getEditor();
		$params = array(
			'autofocus' => $this->autofocus,
			'readonly'  => $this->readonly || $this->disabled,
			'syntax'    => (string) $this->element['syntax'],
		);

		return $editor->display(
			$this->name,
			htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8'),
			$this->width,
			$this->height,
			$this->columns,
			$this->rows,
			$this->buttons ? (is_array($this->buttons) ? array_merge($this->buttons, $this->hide) : $this->hide) : false,
			$this->id,
			$this->asset,
			$this->form->getValue($this->authorField),
			$params
		);
	}

	/**
	 * Method to get an Editor object based on the form field.
	 *
	 * @return  Editor  The Editor object.
	 *
	 * @since   1.6
	 */
	protected function getEditor()
	{
		// Only create the editor if it is not already created.
		if (empty($this->editor))
		{
			$editor = null;

			if ($this->editorType)
			{
				// Get the list of editor types.
				$types = $this->editorType;

				// Get the database object.
				$db = Factory::getDbo();

				// Iterate over the types looking for an existing editor.
				foreach ($types as $element)
				{
					// Build the query.
					$query = $db->getQuery(true)
						->select('element')
						->from('#__extensions')
						->where('element = ' . $db->quote($element))
						->where('folder = ' . $db->quote('editors'))
						->where('enabled = 1');

					// Check of the editor exists.
					$db->setQuery($query, 0, 1);
					$editor = $db->loadResult();

					// If an editor was found stop looking.
					if ($editor)
					{
						break;
					}
				}
			}

			// Create the JEditor instance based on the given editor.
			if ($editor === null)
			{
				$editor = Factory::getConfig()->get('editor');
			}

			$this->editor = Editor::getInstance($editor);
		}

		return $this->editor;
	}

	/**
	 * Method to get the JEditor output for an onSave event.
	 *
	 * @return  string  The JEditor object output.
	 *
	 * @since       1.6
	 * @deprecated  4.0  Will be removed without replacement
	 * @see         Editor::save()
	 */
	public function save()
	{
		$editor = $this->getEditor();

		if (!method_exists($editor, 'save'))
		{
			return '';
		}

		return $editor->save($this->id);
	}
}
src/Form/Field/ModulepositionField.php000064400000010112152177723700013756 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Field;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormHelper;

FormHelper::loadFieldClass('text');

/**
 * Module Position field.
 *
 * @since  1.6
 */
class ModulepositionField extends \JFormFieldText
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $type = 'ModulePosition';

	/**
	 * The client ID.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $clientId;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'clientId':
				return $this->clientId;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'clientId':
				$this->clientId = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     FormField::setup()
	 * @since   3.2
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result === true)
		{
			// Get the client id.
			$clientId = $this->element['client_id'];

			if (!isset($clientId))
			{
				$clientName = $this->element['client'];

				if (isset($clientName))
				{
					$client = ApplicationHelper::getClientInfo($clientName, true);
					$clientId = $client->id;
				}
			}

			if (!isset($clientId) && $this->form instanceof Form)
			{
				$clientId = $this->form->getValue('client_id');
			}

			$this->clientId = (int) $clientId;
		}

		return $result;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string	The field input markup.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		// Load the modal behavior script.
		\JHtml::_('behavior.modal', 'a.modal');

		// Build the script.
		$script = array();
		$script[] = '	function jSelectPosition_' . $this->id . '(name) {';
		$script[] = '		document.getElementById("' . $this->id . '").value = name;';
		$script[] = '		jModalClose();';
		$script[] = '	}';

		// Add the script to the document head.
		Factory::getDocument()->addScriptDeclaration(implode("\n", $script));

		// Setup variables for display.
		$html = array();
		$link = 'index.php?option=com_modules&view=positions&layout=modal&tmpl=component&function=jSelectPosition_' . $this->id
			. '&amp;client_id=' . $this->clientId;

		// The current user display field.
		$html[] = '<div class="input-append">';
		$html[] = parent::getInput()
			. '<a class="btn modal" title="' . \JText::_('COM_MODULES_CHANGE_POSITION_TITLE') . '"  href="' . $link
			. '" rel="{handler: \'iframe\', size: {x: 800, y: 450}}">'
			. \JText::_('COM_MODULES_CHANGE_POSITION_BUTTON') . '</a>';
		$html[] = '</div>';

		return implode("\n", $html);
	}
}
src/Form/FormWrapper.php000064400000006560152177723700011235 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for FormHelper
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
 */
class FormWrapper
{
	/**
	 * Helper wrapper method for loadFieldType
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  JFormField object on success, false otherwise.
	 *
	 * @see     FormHelper::loadFieldType()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function loadFieldType($type, $new = true)
	{
		return FormHelper::loadFieldType($type, $new);
	}

	/**
	 * Helper wrapper method for loadRuleType
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  JFormField object on success, false otherwise.
	 *
	 * @see     FormHelper::loadRuleType()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function loadRuleType($type, $new = true)
	{
		return FormHelper::loadRuleType($type, $new);
	}

	/**
	 * Helper wrapper method for loadFieldClass
	 *
	 * @param   string  $type  Type of a field whose class should be loaded.
	 *
	 * @return  mixed  Class name on success or false otherwise.
	 *
	 * @see     FormHelper::loadFieldClass()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function loadFieldClass($type)
	{
		return FormHelper::loadFieldClass($type);
	}

	/**
	 * Helper wrapper method for loadRuleClass
	 *
	 * @param   string  $type  Type of a rule whose class should be loaded.
	 *
	 * @return  mixed  Class name on success or false otherwise.
	 *
	 * @see     FormHelper::loadRuleClass()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function loadRuleClass($type)
	{
		return FormHelper::loadRuleClass($type);
	}

	/**
	 * Helper wrapper method for addFieldPath
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FormHelper::addFieldPath()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function addFieldPath($new = null)
	{
		return FormHelper::addFieldPath($new);
	}

	/**
	 * Helper wrapper method for addFormPath
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FormHelper::addFormPath()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function addFormPath($new = null)
	{
		return FormHelper::addFormPath($new);
	}

	/**
	 * Helper wrapper method for addRulePath
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FormHelper::addRulePath()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Form\FormHelper` directly
	 */
	public function addRulePath($new = null)
	{
		return FormHelper::addRulePath($new);
	}
}
src/Form/FormRule.php000064400000005041152177723700010515 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

// Detect if we have full UTF-8 and unicode PCRE support.
if (!defined('JCOMPAT_UNICODE_PROPERTIES'))
{
	/**
	 * Flag indicating UTF-8 and PCRE support is present
	 *
	 * @var    boolean
	 * @since  1.6
	 */
	define('JCOMPAT_UNICODE_PROPERTIES', (bool) @preg_match('/\pL/u', 'a'));
}

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.6
 */
class FormRule
{
	/**
	 * The regular expression to use in testing a form field value.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $regex;

	/**
	 * The regular expression modifiers to use when testing a form field value.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $modifiers;

	/**
	 * Method to test the value.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.6
	 * @throws  \UnexpectedValueException if rule is invalid.
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Check for a valid regex.
		if (empty($this->regex))
		{
			throw new \UnexpectedValueException(sprintf('%s has invalid regex.', get_class($this)));
		}

		// Add unicode property support if available.
		if (JCOMPAT_UNICODE_PROPERTIES)
		{
			$this->modifiers = (strpos($this->modifiers, 'u') !== false) ? $this->modifiers : $this->modifiers . 'u';
		}

		// Test the value against the regular expression.
		if (preg_match(chr(1) . $this->regex . chr(1) . $this->modifiers, $value))
		{
			return true;
		}

		return false;
	}
}
src/Form/FormHelper.php000064400000031252152177723700011030 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form;

defined('JPATH_PLATFORM') or die;

use Joomla\String\Normalise;
use Joomla\String\StringHelper;

\JLoader::import('joomla.filesystem.path');

/**
 * Form's helper class.
 * Provides a storage for filesystem's paths where Form's entities reside and methods for creating those entities.
 * Also stores objects with entities' prototypes for further reusing.
 *
 * @since  1.7.0
 */
class FormHelper
{
	/**
	 * Array with paths where entities(field, rule, form) can be found.
	 *
	 * Array's structure:
	 *
	 * paths:
	 * {ENTITY_NAME}:
	 * - /path/1
	 * - /path/2
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $paths;

	/**
	 * The class namespaces.
	 *
	 * @var   string
	 * @since 3.8.0
	 */
	protected static $prefixes = array('field' => array(), 'form' => array(), 'rule' => array());

	/**
	 * Static array of Form's entity objects for re-use.
	 * Prototypes for all fields and rules are here.
	 *
	 * Array's structure:
	 * entities:
	 * {ENTITY_NAME}:
	 * {KEY}: {OBJECT}
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $entities = array('field' => array(), 'form' => array(), 'rule' => array());

	/**
	 * Method to load a form field object given a type.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  FormField|boolean  FormField object on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function loadFieldType($type, $new = true)
	{
		return self::loadType('field', $type, $new);
	}

	/**
	 * Method to load a form rule object given a type.
	 *
	 * @param   string   $type  The rule type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  FormRule|boolean  FormRule object on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function loadRuleType($type, $new = true)
	{
		return self::loadType('rule', $type, $new);
	}

	/**
	 * Method to load a form entity object given a type.
	 * Each type is loaded only once and then used as a prototype for other objects of same type.
	 * Please, use this method only with those entities which support types (forms don't support them).
	 *
	 * @param   string   $entity  The entity.
	 * @param   string   $type    The entity type.
	 * @param   boolean  $new     Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  mixed  Entity object on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	protected static function loadType($entity, $type, $new = true)
	{
		// Reference to an array with current entity's type instances
		$types = &self::$entities[$entity];

		$key = md5($type);

		// Return an entity object if it already exists and we don't need a new one.
		if (isset($types[$key]) && $new === false)
		{
			return $types[$key];
		}

		$class = self::loadClass($entity, $type);

		if ($class === false)
		{
			return false;
		}

		// Instantiate a new type object.
		$types[$key] = new $class;

		return $types[$key];
	}

	/**
	 * Attempt to import the JFormField class file if it isn't already imported.
	 * You can use this method outside of JForm for loading a field for inheritance or composition.
	 *
	 * @param   string  $type  Type of a field whose class should be loaded.
	 *
	 * @return  string|boolean  Class name on success or false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function loadFieldClass($type)
	{
		return self::loadClass('field', $type);
	}

	/**
	 * Attempt to import the JFormRule class file if it isn't already imported.
	 * You can use this method outside of JForm for loading a rule for inheritance or composition.
	 *
	 * @param   string  $type  Type of a rule whose class should be loaded.
	 *
	 * @return  string|boolean  Class name on success or false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function loadRuleClass($type)
	{
		return self::loadClass('rule', $type);
	}

	/**
	 * Load a class for one of the form's entities of a particular type.
	 * Currently, it makes sense to use this method for the "field" and "rule" entities
	 * (but you can support more entities in your subclass).
	 *
	 * @param   string  $entity  One of the form entities (field or rule).
	 * @param   string  $type    Type of an entity.
	 *
	 * @return  string|boolean  Class name on success or false otherwise.
	 *
	 * @since   1.7.0
	 */
	protected static function loadClass($entity, $type)
	{
		// Check if there is a class in the registered namespaces
		foreach (self::addPrefix($entity) as $prefix)
		{
			// Treat underscores as namespace
			$name = Normalise::toSpaceSeparated($type);
			$name = str_ireplace(' ', '\\', ucwords($name));

			// Compile the classname
			$class = rtrim($prefix, '\\') . '\\' . ucfirst($name) . ucfirst($entity);

			// Check if the class exists
			if (class_exists($class))
			{
				return $class;
			}
		}

		$prefix = 'J';

		if (strpos($type, '.'))
		{
			list($prefix, $type) = explode('.', $type);
		}

		$class = StringHelper::ucfirst($prefix, '_') . 'Form' . StringHelper::ucfirst($entity, '_') . StringHelper::ucfirst($type, '_');

		if (class_exists($class))
		{
			return $class;
		}

		// Get the field search path array.
		$paths = self::addPath($entity);

		// If the type is complex, add the base type to the paths.
		if ($pos = strpos($type, '_'))
		{
			// Add the complex type prefix to the paths.
			for ($i = 0, $n = count($paths); $i < $n; $i++)
			{
				// Derive the new path.
				$path = $paths[$i] . '/' . strtolower(substr($type, 0, $pos));

				// If the path does not exist, add it.
				if (!in_array($path, $paths))
				{
					$paths[] = $path;
				}
			}

			// Break off the end of the complex type.
			$type = substr($type, $pos + 1);
		}

		// Try to find the class file.
		$type = strtolower($type) . '.php';

		foreach ($paths as $path)
		{
			$file = \JPath::find($path, $type);

			if (!$file)
			{
				continue;
			}

			require_once $file;

			if (class_exists($class))
			{
				break;
			}
		}

		// Check for all if the class exists.
		return class_exists($class) ? $class : false;
	}

	/**
	 * Method to add a path to the list of field include paths.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   1.7.0
	 */
	public static function addFieldPath($new = null)
	{
		return self::addPath('field', $new);
	}

	/**
	 * Method to add a path to the list of form include paths.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   1.7.0
	 */
	public static function addFormPath($new = null)
	{
		return self::addPath('form', $new);
	}

	/**
	 * Method to add a path to the list of rule include paths.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   1.7.0
	 */
	public static function addRulePath($new = null)
	{
		return self::addPath('rule', $new);
	}

	/**
	 * Method to add a path to the list of include paths for one of the form's entities.
	 * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
	 *
	 * @param   string  $entity  Form's entity name for which paths will be added.
	 * @param   mixed   $new     A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   1.7.0
	 */
	protected static function addPath($entity, $new = null)
	{
		// Reference to an array with paths for current entity
		$paths = &self::$paths[$entity];

		// Add the default entity's search path if not set.
		if (empty($paths))
		{
			// While we support limited number of entities (form, field and rule)
			// we can do this simple pluralisation:
			$entity_plural = $entity . 's';

			/*
			 * But when someday we would want to support more entities, then we should consider adding
			 * an inflector class to "libraries/joomla/utilities" and use it here (or somebody can use a real inflector in his subclass).
			 * See also: pluralization snippet by Paul Osman in JControllerForm's constructor.
			 */
			$paths[] = __DIR__ . '/' . $entity_plural;
		}

		// Force the new path(s) to an array.
		settype($new, 'array');

		// Add the new paths to the stack if not already there.
		foreach ($new as $path)
		{
			if (!in_array($path, $paths))
			{
				array_unshift($paths, trim($path));
			}

			if (!is_dir($path))
			{
				array_unshift($paths, trim($path));
			}
		}

		return $paths;
	}

	/**
	 * Method to add a namespace prefix to the list of field lookups.
	 *
	 * @param   mixed  $new  A namespaces or array of namespaces to add.
	 *
	 * @return  array  The list of namespaces that have been added.
	 *
	 * @since   3.8.0
	 */
	public static function addFieldPrefix($new = null)
	{
		return self::addPrefix('field', $new);
	}

	/**
	 * Method to add a namespace to the list of form lookups.
	 *
	 * @param   mixed  $new  A namespace or array of namespaces to add.
	 *
	 * @return  array  The list of namespaces that have been added.
	 *
	 * @since   3.8.0
	 */
	public static function addFormPrefix($new = null)
	{
		return self::addPrefix('form', $new);
	}

	/**
	 * Method to add a namespace to the list of rule lookups.
	 *
	 * @param   mixed  $new  A namespace or array of namespaces to add.
	 *
	 * @return  array  The list of namespaces that have been added.
	 *
	 * @since   3.8.0
	 */
	public static function addRulePrefix($new = null)
	{
		return self::addPrefix('rule', $new);
	}

	/**
	 * Method to add a namespace to the list of namespaces for one of the form's entities.
	 * Currently supported entities: field, rule and form. You are free to support your own in a subclass.
	 *
	 * @param   string  $entity  Form's entity name for which paths will be added.
	 * @param   mixed   $new     A namespace or array of namespaces to add.
	 *
	 * @return  array  The list of namespaces that have been added.
	 *
	 * @since   3.8.0
	 */
	protected static function addPrefix($entity, $new = null)
	{
		// Reference to an array with namespaces for current entity
		$prefixes = &self::$prefixes[$entity];

		// Add the default entity's search namespace if not set.
		if (empty($prefixes))
		{
			$prefixes[] = __NAMESPACE__ . '\\' . ucfirst($entity);
		}

		// Force the new namespace(s) to an array.
		settype($new, 'array');

		// Add the new paths to the stack if not already there.
		foreach ($new as $prefix)
		{
			$prefix = trim($prefix);

			if (in_array($prefix, $prefixes))
			{
				continue;
			}

			array_unshift($prefixes, $prefix);
		}

		return $prefixes;
	}

	/**
	 * Parse the show on conditions
	 *
	 * @param   string  $showOn       Show on conditions.
	 * @param   string  $formControl  Form name.
	 * @param   string  $group        The dot-separated form group path.
	 *
	 * @return  array   Array with show on conditions.
	 *
	 * @since   3.7.0
	 */
	public static function parseShowOnConditions($showOn, $formControl = null, $group = null)
	{
		// Process the showon data.
		if (!$showOn)
		{
			return array();
		}

		$formPath = $formControl ?: '';

		if ($group)
		{
			$groups = explode('.', $group);

			// An empty formControl leads to invalid shown property
			// Use the 1st part of the group instead to avoid.
			if (empty($formPath) && isset($groups[0]))
			{
				$formPath = $groups[0];
				array_shift($groups);
			}

			foreach ($groups as $group)
			{
				$formPath .= '[' . $group . ']';
			}
		}

		$showOnData  = array();
		$showOnParts = preg_split('#(\[AND\]|\[OR\])#', $showOn, -1, PREG_SPLIT_DELIM_CAPTURE);
		$op          = '';

		foreach ($showOnParts as $showOnPart)
		{
			if (($showOnPart === '[AND]') || $showOnPart === '[OR]')
			{
				$op = trim($showOnPart, '[]');
				continue;
			}

			$compareEqual     = strpos($showOnPart, '!:') === false;
			$showOnPartBlocks = explode(($compareEqual ? ':' : '!:'), $showOnPart, 2);

			if (strpos($showOnPartBlocks[0], '.') !== false)
			{
				if ($formControl)
				{
					$field = $formControl . ('[' . str_replace('.', '][', $showOnPartBlocks[0]) . ']');
				}
				else
				{
					$groupParts = explode('.', $showOnPartBlocks[0]);
					$field      = array_shift($groupParts) . '[' . join('][', $groupParts) . ']';
				}
			}
			else
			{
				$field = $formPath ? $formPath . '[' . $showOnPartBlocks[0] . ']' : $showOnPartBlocks[0];
			}

			$showOnData[] = array(
				'field'  => $field,
				'values' => explode(',', $showOnPartBlocks[1]),
				'sign'   => $compareEqual === true ? '=' : '!=',
				'op'     => $op,
			);

			if ($op !== '')
			{
				$op = '';
			}
		}

		return $showOnData;
	}
}
src/Form/Rule/NotequalsRule.php000064400000004266152177723700012504 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class NotequalsRule extends FormRule
{
	/**
	 * Method to test if two values are not equal. To use this rule, the form
	 * XML needs a validate attribute of equals and a field attribute
	 * that is equal to the field to test against.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 * @throws  \UnexpectedValueException
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$field = (string) $element['field'];

		// Check that a validation field is set.
		if (!$field)
		{
			throw new \UnexpectedValueException(sprintf('$field empty in %s::test', get_class($this)));
		}

		if ($input === null)
		{
			throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', get_class($this)));
		}

		// Test the two values against each other.
		if ($value != $input->get($field))
		{
			return true;
		}

		return false;
	}
}
src/Form/Rule/EmailRule.php000064400000014025152177723700011552 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class EmailRule extends FormRule
{
	/**
	 * The regular expression to use in testing a form field value.
	 *
	 * @var    string
	 * @since  1.7.0
	 * @link   http://www.w3.org/TR/html-markup/input.email.html
	 */
	protected $regex = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$";

	/**
	 * Method to test the email address and optionally check for uniqueness.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  mixed  Boolean true if field value is valid, Exception on failure.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		// If the tld attribute is present, change the regular expression to require at least 2 characters for it.
		$tld = ((string) $element['tld'] == 'tld' || (string) $element['tld'] == 'required');

		if ($tld)
		{
			$this->regex = "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])"
				. '?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$';
		}

		// Determine if the multiple attribute is present
		$multiple = ((string) $element['multiple'] == 'true' || (string) $element['multiple'] == 'multiple');

		if (!$multiple)
		{
			// Handle idn email addresses by converting to punycode.
			$value = \JStringPunycode::emailToPunycode($value);

			// Test the value against the regular expression.
			if (!parent::test($element, $value, $group, $input, $form))
			{
				return new \UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
			}
		}
		else
		{
			$values = explode(',', $value);

			foreach ($values as $value)
			{
				// Handle idn email addresses by converting to punycode.
				$value = \JStringPunycode::emailToPunycode($value);

				// Test the value against the regular expression.
				if (!parent::test($element, $value, $group, $input, $form))
				{
					return new \UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));
				}
			}
		}

		/**
		 * validDomains value should consist of component name and the name of domain list field in component's configuration, separated by a dot.
		 * This allows different components and contexts to use different lists.
		 * If value is incomplete, com_users.domains is used as fallback.
		 */
		$validDomains = (isset($element['validDomains']) && $element['validDomains'] != 'false');

		if ($validDomains && !$multiple)
		{
			$config = explode('.', $element['validDomains'], 2);

			if (count($config) > 1)
			{
				$domains = ComponentHelper::getParams($config[0])->get($config[1]);
			}
			else
			{
				$domains = ComponentHelper::getParams('com_users')->get('domains');
			}

			if ($domains)
			{
				$emailDomain = explode('@', $value);
				$emailDomain = $emailDomain[1];
				$emailParts  = array_reverse(explode('.', $emailDomain));
				$emailCount  = count($emailParts);
				$allowed     = true;

				foreach ($domains as $domain)
				{
					$domainParts = array_reverse(explode('.', $domain->name));
					$status      = 0;

					// Don't run if the email has less segments than the rule.
					if ($emailCount < count($domainParts))
					{
						continue;
					}

					foreach ($emailParts as $key => $emailPart)
					{
						if (!isset($domainParts[$key]) || $domainParts[$key] == $emailPart || $domainParts[$key] == '*')
						{
							$status++;
						}
					}

					// All segments match, check whether to allow the domain or not.
					if ($status === $emailCount)
					{
						if ($domain->rule == 0)
						{
							$allowed = false;
						}
						else
						{
							$allowed = true;
						}
					}
				}

				// If domain is not allowed, fail validation. Otherwise continue.
				if (!$allowed)
				{
					return new \UnexpectedValueException(\JText::sprintf('JGLOBAL_EMAIL_DOMAIN_NOT_ALLOWED', $emailDomain));
				}
			}
		}

		// Check if we should test for uniqueness. This only can be used if multiple is not true
		$unique = ((string) $element['unique'] == 'true' || (string) $element['unique'] == 'unique');

		if ($unique && !$multiple)
		{
			// Get the database object and a new query object.
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true);

			// Build the query.
			$query->select('COUNT(*)')
				->from('#__users')
				->where('email = ' . $db->quote($value));

			// Get the extra field check attribute.
			$userId = ($form instanceof Form) ? $form->getValue('id') : '';
			$query->where($db->quoteName('id') . ' <> ' . (int) $userId);

			// Set and query the database.
			$db->setQuery($query);
			$duplicate = (bool) $db->loadResult();

			if ($duplicate)
			{
				return new \UnexpectedValueException(\JText::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));
			}
		}

		return true;
	}
}
src/Form/Rule/CalendarRule.php000064400000003673152177723700012243 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Date\Date;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform
 *
 * @since  3.7.0
 */
class CalendarRule extends FormRule
{
	/**
	 * Method to test the calendar value for a valid parts.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   3.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		if (strtolower($value) == 'now')
		{
			return true;
		}

		try
		{
			return \JFactory::getDate($value) instanceof Date;
		}
		catch (\Exception $e)
		{
			return false;
		}
	}
}
src/Form/Rule/SubFormRule.php000064400000004654152177723700012107 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;

/**
 * Form rule to validate subforms field-wise.
 *
 * @since  3.9.7
 */
class SubformRule extends FormRule
{
	/**
	 * Method to test given values for a subform..
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   3.9.7
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Get the form field object.
		$field = $form->getField($element['name'], $group);

		if (!($field instanceof \JFormFieldSubform))
		{
			throw new \UnexpectedValueException(sprintf('%s is no subform field.', $element['name']));
		}

		$subForm = $field->loadSubForm();

		// Multiple values: Validate every row.
		if ($field->multiple)
		{
			foreach ($value as $row)
			{
				if ($subForm->validate($row) === false)
				{
					// Pass the first error that occurred on the subform validation.
					$errors = $subForm->getErrors();

					if (!empty($errors[0]))
					{
						return $errors[0];
					}

					return false;
				}
			}
		}
		// Single value.
		else
		{
			if ($subForm->validate($value) === false)
			{
				// Pass the first error that occurred on the subform validation.
				$errors = $subForm->getErrors();

				if (!empty($errors[0]))
				{
					return $errors[0];
				}

				return false;
			}
		}

		return true;
	}
}
src/Form/Rule/EqualsRule.php000064400000004611152177723700011755 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class EqualsRule extends FormRule
{
	/**
	 * Method to test if two values are equal. To use this rule, the form
	 * XML needs a validate attribute of equals and a field attribute
	 * that is equal to the field to test against.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 * @throws  \UnexpectedValueException
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$field = (string) $element['field'];

		// Check that a validation field is set.
		if (!$field)
		{
			throw new \UnexpectedValueException(sprintf('$field empty in %s::test', get_class($this)));
		}

		if (is_null($form))
		{
			throw new \InvalidArgumentException(sprintf('The value for $form must not be null in %s', get_class($this)));
		}

		if (is_null($input))
		{
			throw new \InvalidArgumentException(sprintf('The value for $input must not be null in %s', get_class($this)));
		}

		$test = $input->get($field);

		if (isset($group) && $group !== '')
		{
			$test = $input->get($group . '.' . $field);
		}

		// Test the two values against each other.
		return $value == $test;
	}
}
src/Form/Rule/BooleanRule.php000064400000001356152177723700012105 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\FormRule;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class BooleanRule extends FormRule
{
	/**
	 * The regular expression to use in testing a form field value.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $regex = '^(?:[01]|true|false)$';

	/**
	 * The regular expression modifiers to use when testing a form field value.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $modifiers = 'i';
}
src/Form/Rule/RulesRule.php000064400000006661152177723700011624 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class RulesRule extends FormRule
{
	/**
	 * Method to test the value.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Get the possible field actions and the ones posted to validate them.
		$fieldActions = self::getFieldActions($element);
		$valueActions = self::getValueActions($value);

		// Make sure that all posted actions are in the list of possible actions for the field.
		foreach ($valueActions as $action)
		{
			if (!in_array($action, $fieldActions))
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Method to get the list of permission action names from the form field value.
	 *
	 * @param   mixed  $value  The form field value to validate.
	 *
	 * @return  array  A list of permission action names from the form field value.
	 *
	 * @since   1.7.0
	 */
	protected function getValueActions($value)
	{
		$actions = array();

		// Iterate over the asset actions and add to the actions.
		foreach ((array) $value as $name => $rules)
		{
			$actions[] = $name;
		}

		return $actions;
	}

	/**
	 * Method to get the list of possible permission action names for the form field.
	 *
	 * @param   \SimpleXMLElement  $element  The \SimpleXMLElement object representing the `<field>` tag for the form field object.
	 *
	 * @return  array  A list of permission action names from the form field element definition.
	 *
	 * @since   1.7.0
	 */
	protected function getFieldActions(\SimpleXMLElement $element)
	{
		$actions = array();

		// Initialise some field attributes.
		$section = $element['section'] ? (string) $element['section'] : '';
		$component = $element['component'] ? (string) $element['component'] : '';

		// Get the asset actions for the element.
		$elActions = Access::getActions($component, $section);

		// Iterate over the asset actions and add to the actions.
		foreach ($elActions as $item)
		{
			$actions[] = $item->name;
		}

		// Iterate over the children and add to the actions.
		foreach ($element->children() as $el)
		{
			if ($el->getName() == 'action')
			{
				$actions[] = (string) $el['name'];
			}
		}

		return $actions;
	}
}
src/Form/Rule/CaptchaRule.php000064400000004162152177723700012067 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Captcha\Captcha;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Framework.
 *
 * @since  2.5
 */
class CaptchaRule extends FormRule
{
	/**
	 * Method to test if the Captcha is correct.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   2.5
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$app    = \JFactory::getApplication();
		$plugin = $app->get('captcha');

		if ($app->isClient('site'))
		{
			$plugin = $app->getParams()->get('captcha', $plugin);
		}

		$namespace = $element['namespace'] ?: $form->getName();

		// Use 0 for none
		if ($plugin === 0 || $plugin === '0')
		{
			return true;
		}

		try
		{
			$captcha = Captcha::getInstance((string) $plugin, array('namespace' => (string) $namespace));
			return $captcha->checkAnswer($value);
		}
		catch (\RuntimeException $e)
		{
			\JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
		}
		return false;
	}
}
src/Form/Rule/PasswordRule.php000064400000014405152177723700012327 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  3.1.2
 */
class PasswordRule extends FormRule
{
	/**
	 * Method to test if two values are not equal. To use this rule, the form
	 * XML needs a validate attribute of equals and a field attribute
	 * that is equal to the field to test against.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   3.1.2
	 * @throws  \InvalidArgumentException
	 * @throws  \UnexpectedValueException
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$meter            = isset($element['strengthmeter']) ? ' meter="0"' : '1';
		$threshold        = isset($element['threshold']) ? (int) $element['threshold'] : 66;
		$minimumLength    = isset($element['minimum_length']) ? (int) $element['minimum_length'] : 4;
		$minimumIntegers  = isset($element['minimum_integers']) ? (int) $element['minimum_integers'] : 0;
		$minimumSymbols   = isset($element['minimum_symbols']) ? (int) $element['minimum_symbols'] : 0;
		$minimumUppercase = isset($element['minimum_uppercase']) ? (int) $element['minimum_uppercase'] : 0;
		$minimumLowercase = isset($element['minimum_lowercase']) ? (int) $element['minimum_lowercase'] : 0;

		// If we have parameters from com_users, use those instead.
		// Some of these may be empty for legacy reasons.
		$params = ComponentHelper::getParams('com_users');

		if (!empty($params))
		{
			$minimumLengthp    = $params->get('minimum_length');
			$minimumIntegersp  = $params->get('minimum_integers');
			$minimumSymbolsp   = $params->get('minimum_symbols');
			$minimumUppercasep = $params->get('minimum_uppercase');
			$minimumLowercasep = $params->get('minimum_lowercase');
			$meterp            = $params->get('meter');
			$thresholdp        = $params->get('threshold');

			empty($minimumLengthp) ? : $minimumLength = (int) $minimumLengthp;
			empty($minimumIntegersp) ? : $minimumIntegers = (int) $minimumIntegersp;
			empty($minimumSymbolsp) ? : $minimumSymbols = (int) $minimumSymbolsp;
			empty($minimumUppercasep) ? : $minimumUppercase = (int) $minimumUppercasep;
			empty($minimumLowercasep) ? : $minimumLowercase = (int) $minimumLowercasep;
			empty($meterp) ? : $meter = $meterp;
			empty($thresholdp) ? : $threshold = $thresholdp;
		}

		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] === 'true' || (string) $element['required'] === 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		$valueLength = strlen($value);

		// Load language file of com_users component
		\JFactory::getLanguage()->load('com_users');

		// We set a maximum length to prevent abuse since it is unfiltered.
		if ($valueLength > 4096)
		{
			\JFactory::getApplication()->enqueueMessage(\JText::_('COM_USERS_MSG_PASSWORD_TOO_LONG'), 'warning');
		}

		// We don't allow white space inside passwords
		$valueTrim = trim($value);

		// Set a variable to check if any errors are made in password
		$validPassword = true;

		if (strlen($valueTrim) !== $valueLength)
		{
			\JFactory::getApplication()->enqueueMessage(
				\JText::_('COM_USERS_MSG_SPACES_IN_PASSWORD'),
				'warning'
			);

			$validPassword = false;
		}

		// Minimum number of integers required
		if (!empty($minimumIntegers))
		{
			$nInts = preg_match_all('/[0-9]/', $value, $imatch);

			if ($nInts < $minimumIntegers)
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::plural('COM_USERS_MSG_NOT_ENOUGH_INTEGERS_N', $minimumIntegers),
					'warning'
				);

				$validPassword = false;
			}
		}

		// Minimum number of symbols required
		if (!empty($minimumSymbols))
		{
			$nsymbols = preg_match_all('[\W]', $value, $smatch);

			if ($nsymbols < $minimumSymbols)
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::plural('COM_USERS_MSG_NOT_ENOUGH_SYMBOLS_N', $minimumSymbols),
					'warning'
				);

				$validPassword = false;
			}
		}

		// Minimum number of upper case ASCII characters required
		if (!empty($minimumUppercase))
		{
			$nUppercase = preg_match_all('/[A-Z]/', $value, $umatch);

			if ($nUppercase < $minimumUppercase)
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::plural('COM_USERS_MSG_NOT_ENOUGH_UPPERCASE_LETTERS_N', $minimumUppercase),
					'warning'
				);

				$validPassword = false;
			}
		}

		// Minimum number of lower case ASCII characters required
		if (!empty($minimumLowercase))
		{
			$nLowercase = preg_match_all('/[a-z]/', $value, $umatch);

			if ($nLowercase < $minimumLowercase)
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::plural('COM_USERS_MSG_NOT_ENOUGH_LOWERCASE_LETTERS_N', $minimumLowercase),
					'warning'
				);

				$validPassword = false;
			}
		}

		// Minimum length option
		if (!empty($minimumLength))
		{
			if (strlen((string) $value) < $minimumLength)
			{
				\JFactory::getApplication()->enqueueMessage(
					\JText::plural('COM_USERS_MSG_PASSWORD_TOO_SHORT_N', $minimumLength),
					'warning'
				);

				$validPassword = false;
			}
		}

		// If valid has violated any rules above return false.
		if (!$validPassword)
		{
			return false;
		}

		return true;
	}
}
src/Form/Rule/UsernameRule.php000064400000004073152177723700012304 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class UsernameRule extends FormRule
{
	/**
	 * Method to test the username for uniqueness.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Get the database object and a new query object.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);

		// Build the query.
		$query->select('COUNT(*)')
			->from('#__users')
			->where('username = ' . $db->quote($value));

		// Get the extra field check attribute.
		$userId = ($form instanceof Form) ? $form->getValue('id') : '';
		$query->where($db->quoteName('id') . ' <> ' . (int) $userId);

		// Set and query the database.
		$db->setQuery($query);
		$duplicate = (bool) $db->loadResult();

		if ($duplicate)
		{
			return false;
		}

		return true;
	}
}
src/Form/Rule/OptionsRule.php000064400000005337152177723700012164 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 * Requires the value entered be one of the options in a field of type="list"
 *
 * @since  1.7.0
 */
class OptionsRule extends FormRule
{
	/**
	 * Method to test the value.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Check if the field is required.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		// Make an array of all available option values.
		$options = array();

		// Create the field
		$field = null;

		if ($form)
		{
			$field = $form->getField((string) $element->attributes()->name, $group);
		}

		// When the field exists, the real options are fetched.
		// This is needed for fields which do have dynamic options like from a database.
		if ($field && is_array($field->options))
		{
			foreach ($field->options as $opt)
			{
				$options[] = $opt->value;
			}
		}
		else
		{
			foreach ($element->option as $opt)
			{
				$options[] = $opt->attributes()->value;
			}
		}

		// There may be multiple values in the form of an array (if the element is checkboxes, for example).
		if (is_array($value))
		{
			// If all values are in the $options array, $diff will be empty and the options valid.
			$diff = array_diff($value, $options);

			return empty($diff);
		}
		else
		{
			// In this case value must be a string
			return in_array((string) $value, $options);
		}
	}
}
src/Form/Rule/ColorRule.php000064400000004076152177723700011606 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class ColorRule extends FormRule
{
	/**
	 * Method to test for a valid color in hexadecimal.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$value = trim($value);

		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		if ($value[0] != '#')
		{
			return false;
		}

		// Remove the leading # if present to validate the numeric part
		$value = ltrim($value, '#');

		// The value must be 6 or 3 characters long
		if (!((strlen($value) == 6 || strlen($value) == 3) && ctype_xdigit($value)))
		{
			return false;
		}

		return true;
	}
}
src/Form/Rule/TelRule.php000064400000006215152177723700011251 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform
 *
 * @since  1.7.0
 */
class TelRule extends FormRule
{
	/**
	 * Method to test the url for a valid parts.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		/*
		 * @link http://www.nanpa.com/
		 * @link http://tools.ietf.org/html/rfc4933
		 * @link http://www.itu.int/rec/T-REC-E.164/en
		 *
		 * Regex by Steve Levithan
		 * @link http://blog.stevenlevithan.com/archives/validate-phone-number
		 * @note that valid ITU-T and EPP must begin with +.
		 */
		$regexarray = array(
			'NANP' => '/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/',
			'ITU-T' => '/^\+(?:[0-9] ?){6,14}[0-9]$/',
			'EPP' => '/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/',
		);

		if (isset($element['plan']))
		{
			$plan = (string) $element['plan'];

			if ($plan == 'northamerica' || $plan == 'us')
			{
				$plan = 'NANP';
			}
			elseif ($plan == 'International' || $plan == 'int' || $plan == 'missdn' || !$plan)
			{
				$plan = 'ITU-T';
			}
			elseif ($plan == 'IETF')
			{
				$plan = 'EPP';
			}

			$regex = $regexarray[$plan];

			// Test the value against the regular expression.
			if (preg_match($regex, $value) == false)
			{
				return false;
			}
		}
		else
		{
			/*
			 * If the rule is set but no plan is selected just check that there are between
			 * 7 and 15 digits inclusive and no illegal characters (but common number separators
			 * are allowed).
			 */
			$cleanvalue = preg_replace('/[+. \-(\)]/', '', $value);
			$regex = '/^[0-9]{7,15}?$/';

			if (preg_match($regex, $cleanvalue) == true)
			{
				return true;
			}
			else
			{
				return false;
			}
		}

		return true;
	}
}
src/Form/Rule/UrlRule.php000064400000010520152177723700011261 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Uri\UriHelper;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class UrlRule extends FormRule
{
	/**
	 * Method to test an external or internal url for all valid parts.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   1.7.0
	 * @link    http://www.w3.org/Addressing/URL/url-spec.txt
	 * @see	    JString
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// If the field is empty and not required, the field is valid.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if (!$required && empty($value))
		{
			return true;
		}

		$urlParts = UriHelper::parse_url($value);

		// See http://www.w3.org/Addressing/URL/url-spec.txt
		// Use the full list or optionally specify a list of permitted schemes.
		if ($element['schemes'] == '')
		{
			$scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'mailto', 'news', 'prospero', 'telnet', 'rlogin', 'sftp', 'tn3270', 'wais',
				'mid', 'cid', 'nntp', 'tel', 'urn', 'ldap', 'file', 'fax', 'modem', 'git');
		}
		else
		{
			$scheme = explode(',', $element['schemes']);
		}

		/*
		 * Note that parse_url() does not always parse accurately without a scheme,
		 * but at least the path should be set always. Note also that parse_url()
		 * returns False for seriously malformed URLs instead of an associative array.
		 * @link https://www.php.net/manual/en/function.parse-url.php
		 */
		if ($urlParts === false || !array_key_exists('scheme', $urlParts))
		{
			/*
			 * The function parse_url() returned false (seriously malformed URL) or no scheme
			 * was found and the relative option is not set: in both cases the field is not valid.
			 */
			if ($urlParts === false || !$element['relative'])
			{
				$element->addAttribute('message', \JText::sprintf('JLIB_FORM_VALIDATE_FIELD_URL_SCHEMA_MISSING', $value, implode(', ', $scheme)));

				return false;
			}

			// The best we can do for the rest is make sure that the path exists and is valid UTF-8.
			if (!array_key_exists('path', $urlParts) || !StringHelper::valid((string) $urlParts['path']))
			{
				return false;
			}

			// The internal URL seems to be good.
			return true;
		}

		// Scheme found, check all parts found.
		$urlScheme = (string) $urlParts['scheme'];
		$urlScheme = strtolower($urlScheme);

		if (in_array($urlScheme, $scheme) == false)
		{
			return false;
		}

		// For some schemes here must be two slashes.
		$scheme = array('http', 'https', 'ftp', 'ftps', 'gopher', 'wais', 'prospero', 'sftp', 'telnet', 'git');

		if (in_array($urlScheme, $scheme) && substr($value, strlen($urlScheme), 3) !== '://')
		{
			return false;
		}

		// The best we can do for the rest is make sure that the strings are valid UTF-8
		// and the port is an integer.
		if (array_key_exists('host', $urlParts) && !StringHelper::valid((string) $urlParts['host']))
		{
			return false;
		}

		if (array_key_exists('port', $urlParts) && !is_int((int) $urlParts['port']))
		{
			return false;
		}

		if (array_key_exists('path', $urlParts) && !StringHelper::valid((string) $urlParts['path']))
		{
			return false;
		}

		return true;
	}
}
src/Form/Rule/NumberRule.php000064400000004167152177723700011761 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form Rule class for the Joomla Platform.
 *
 * @since  3.5
 */
class NumberRule extends FormRule
{
	/**
	 * Method to test the range for a number value using min and max attributes.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   3.5
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		// Check if the field is required.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		// If the value is empty and the field is not required return True.
		if (($value === '' || $value === null) && ! $required)
		{
			return true;
		}

		$float_value = (float) $value;

		if (isset($element['min']))
		{
			$min = (float) $element['min'];

			if ($min > $float_value)
			{
				return false;
			}
		}

		if (isset($element['max']))
		{
			$max = (float) $element['max'];

			if ($max < $float_value)
			{
				return false;
			}
		}

		return true;
	}
}
src/Form/Rule/ExistsRule.php000064400000004253152177723700012004 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form\Rule;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\Form\FormRule;
use Joomla\Registry\Registry;

/**
 * Form rule class to determine if a value exists in a database table.
 *
 * @since  3.9.0
 */
class ExistsRule extends FormRule
{
	/**
	 * Method to test the username for uniqueness.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate against the entire form.
	 * @param   Form               $form     The form object for which the field is being tested.
	 *
	 * @return  boolean  True if the value is valid, false otherwise.
	 *
	 * @since   3.9.0
	 */
	public function test(\SimpleXMLElement $element, $value, $group = null, Registry $input = null, Form $form = null)
	{
		$value = trim($value);

		$existsTable  = (string) $element['exists_table'];
		$existsColumn = (string) $element['exists_column'];

		// We cannot validate without a table name
		if ($existsTable === '')
		{
			return true;
		}

		// Assume a default column name of `id`
		if ($existsColumn === '')
		{
			$existsColumn = 'id';
		}

		$db = Factory::getDbo();

		// Set and query the database.
		$exists = $db->setQuery(
			$db->getQuery(true)
				->select('COUNT(*)')
				->from($db->quoteName($existsTable))
				->where($db->quoteName($existsColumn) . ' = ' . $db->quote($value))
		)->loadResult();

		return (int) $exists > 0;
	}
}
src/Form/Form.php000064400000202012152177723700007662 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Factory\LegacyFormFactory;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

\JLoader::import('joomla.filesystem.path');

/**
 * Form Class for the Joomla Platform.
 *
 * This class implements a robust API for constructing, populating, filtering, and validating forms.
 * It uses XML definitions to construct form fields and a variety of field and rule classes to
 * render and validate the form.
 *
 * @link   http://www.w3.org/TR/html4/interact/forms.html
 * @link   http://www.w3.org/TR/html5/forms.html
 * @since  1.7.0
 */
class Form
{
	/**
	 * The Registry data store for form fields during display.
	 *
	 * @var    Registry
	 * @since  1.7.0
	 */
	protected $data;

	/**
	 * The form object errors array.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $errors = array();

	/**
	 * The name of the form instance.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $name;

	/**
	 * The form object options for use in rendering and validation.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $options = array();

	/**
	 * The form XML definition.
	 *
	 * @var    \SimpleXMLElement
	 * @since  1.7.0
	 */
	protected $xml;

	/**
	 * Form instances.
	 *
	 * @var    Form[]
	 * @since  1.7.0
	 */
	protected static $forms = array();

	/**
	 * Alows extensions to implement repeating elements
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	public $repeat = false;

	/**
	 * Method to instantiate the form object.
	 *
	 * @param   string  $name     The name of the form.
	 * @param   array   $options  An array of form options.
	 *
	 * @since   1.7.0
	 */
	public function __construct($name, array $options = array())
	{
		// Set the name for the form.
		$this->name = $name;

		// Initialise the Registry data.
		$this->data = new Registry;

		// Set the options if specified.
		$this->options['control'] = isset($options['control']) ? $options['control'] : false;
	}

	/**
	 * Method to bind data to the form.
	 *
	 * @param   mixed  $data  An array or object of data to bind to the form.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function bind($data)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		// The data must be an object or array.
		if (!is_object($data) && !is_array($data))
		{
			return false;
		}

		$this->bindLevel(null, $data);

		return true;
	}

	/**
	 * Method to bind data to the form for the group level.
	 *
	 * @param   string  $group  The dot-separated form group path on which to bind the data.
	 * @param   mixed   $data   An array or object of data to bind to the form for the group level.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function bindLevel($group, $data)
	{
		// Ensure the input data is an array.
		if (is_object($data))
		{
			if ($data instanceof Registry)
			{
				// Handle a Registry.
				$data = $data->toArray();
			}
			elseif ($data instanceof \JObject)
			{
				// Handle a JObject.
				$data = $data->getProperties();
			}
			else
			{
				// Handle other types of objects.
				$data = (array) $data;
			}
		}

		// Process the input data.
		foreach ($data as $k => $v)
		{
			$level = $group ? $group . '.' . $k : $k;

			if ($this->findField($k, $group))
			{
				// If the field exists set the value.
				$this->data->set($level, $v);
			}
			elseif (is_object($v) || ArrayHelper::isAssociative($v))
			{
				// If the value is an object or an associative array, hand it off to the recursive bind level method.
				$this->bindLevel($level, $v);
			}
		}
	}

	/**
	 * Method to filter the form data.
	 *
	 * @param   array   $data   An array of field values to filter.
	 * @param   string  $group  The dot-separated form group path on which to filter the fields.
	 *
	 * @return  mixed  Array or false.
	 *
	 * @since   1.7.0
	 */
	public function filter($data, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		$input = new Registry($data);
		$output = new Registry;

		// Get the fields for which to filter the data.
		$fields = $this->findFieldsByGroup($group);

		if (!$fields)
		{
			// PANIC!
			return false;
		}

		// Filter the fields.
		foreach ($fields as $field)
		{
			$name = (string) $field['name'];

			// Get the field groups for the element.
			$attrs = $field->xpath('ancestor::fields[@name]/@name');
			$groups = array_map('strval', $attrs ? $attrs : array());
			$group = implode('.', $groups);

			$key = $group ? $group . '.' . $name : $name;

			// Filter the value if it exists.
			if ($input->exists($key))
			{
				$output->set($key, $this->filterField($field, $input->get($key, (string) $field['default'])));
			}
		}

		return $output->toArray();
	}

	/**
	 * Return all errors, if any.
	 *
	 * @return  array  Array of error messages or RuntimeException objects.
	 *
	 * @since   1.7.0
	 */
	public function getErrors()
	{
		return $this->errors;
	}

	/**
	 * Method to get a form field represented as a JFormField object.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value  The optional value to use as the default for the field.
	 *
	 * @return  \JFormField|boolean  The JFormField object for the field or boolean false on error.
	 *
	 * @since   1.7.0
	 */
	public function getField($name, $group = null, $value = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Attempt to find the field by name and group.
		$element = $this->findField($name, $group);

		// If the field element was not found return false.
		if (!$element)
		{
			return false;
		}

		return $this->loadField($element, $group, $value);
	}

	/**
	 * Method to get an attribute value from a field XML element.  If the attribute doesn't exist or
	 * is null then the optional default value will be used.
	 *
	 * @param   string  $name       The name of the form field for which to get the attribute value.
	 * @param   string  $attribute  The name of the attribute for which to get a value.
	 * @param   mixed   $default    The optional default value to use if no attribute value exists.
	 * @param   string  $group      The optional dot-separated form group path on which to find the field.
	 *
	 * @return  mixed  The attribute value for the field.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function getFieldAttribute($name, $attribute, $default = null, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::getFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Find the form field element from the definition.
		$element = $this->findField($name, $group);

		// If the element exists and the attribute exists for the field return the attribute value.
		if (($element instanceof \SimpleXMLElement) && strlen((string) $element[$attribute]))
		{
			return (string) $element[$attribute];
		}

		// Otherwise return the given default value.
		else
		{
			return $default;
		}
	}

	/**
	 * Method to get an array of JFormField objects in a given fieldset by name.  If no name is
	 * given then all fields are returned.
	 *
	 * @param   string  $set  The optional name of the fieldset.
	 *
	 * @return  \JFormField[]  The array of JFormField objects in the fieldset.
	 *
	 * @since   1.7.0
	 */
	public function getFieldset($set = null)
	{
		$fields = array();

		// Get all of the field elements in the fieldset.
		if ($set)
		{
			$elements = $this->findFieldsByFieldset($set);
		}

		// Get all fields.
		else
		{
			$elements = $this->findFieldsByGroup();
		}

		// If no field elements were found return empty.
		if (empty($elements))
		{
			return $fields;
		}

		// Build the result array from the found field elements.
		foreach ($elements as $element)
		{
			// Get the field groups for the element.
			$attrs = $element->xpath('ancestor::fields[@name]/@name');
			$groups = array_map('strval', $attrs ? $attrs : array());
			$group = implode('.', $groups);

			// If the field is successfully loaded add it to the result array.
			if ($field = $this->loadField($element, $group))
			{
				$fields[$field->id] = $field;
			}
		}

		return $fields;
	}

	/**
	 * Method to get an array of fieldset objects optionally filtered over a given field group.
	 *
	 * @param   string  $group  The dot-separated form group path on which to filter the fieldsets.
	 *
	 * @return  array  The array of fieldset objects.
	 *
	 * @since   1.7.0
	 */
	public function getFieldsets($group = null)
	{
		$fieldsets = array();
		$sets = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return $fieldsets;
		}

		if ($group)
		{
			// Get the fields elements for a given group.
			$elements = &$this->findGroup($group);

			foreach ($elements as &$element)
			{
				// Get an array of <fieldset /> elements and fieldset attributes within the fields element.
				if ($tmp = $element->xpath('descendant::fieldset[@name] | descendant::field[@fieldset]/@fieldset'))
				{
					$sets = array_merge($sets, (array) $tmp);
				}
			}
		}
		else
		{
			// Get an array of <fieldset /> elements and fieldset attributes.
			$sets = $this->xml->xpath('//fieldset[@name and not(ancestor::field/form/*)] | //field[@fieldset and not(ancestor::field/form/*)]/@fieldset');
		}

		// If no fieldsets are found return empty.
		if (empty($sets))
		{
			return $fieldsets;
		}

		// Process each found fieldset.
		foreach ($sets as $set)
		{
			if ((string) $set['hidden'] == 'true')
			{
				continue;
			}

			// Are we dealing with a fieldset element?
			if ((string) $set['name'])
			{
				// Only create it if it doesn't already exist.
				if (empty($fieldsets[(string) $set['name']]))
				{
					// Build the fieldset object.
					$fieldset = (object) array('name' => '', 'label' => '', 'description' => '');

					foreach ($set->attributes() as $name => $value)
					{
						$fieldset->$name = (string) $value;
					}

					// Add the fieldset object to the list.
					$fieldsets[$fieldset->name] = $fieldset;
				}
			}

			// Must be dealing with a fieldset attribute.
			else
			{
				// Only create it if it doesn't already exist.
				if (empty($fieldsets[(string) $set]))
				{
					// Attempt to get the fieldset element for data (throughout the entire form document).
					$tmp = $this->xml->xpath('//fieldset[@name="' . (string) $set . '"]');

					// If no element was found, build a very simple fieldset object.
					if (empty($tmp))
					{
						$fieldset = (object) array('name' => (string) $set, 'label' => '', 'description' => '');
					}

					// Build the fieldset object from the element.
					else
					{
						$fieldset = (object) array('name' => '', 'label' => '', 'description' => '');

						foreach ($tmp[0]->attributes() as $name => $value)
						{
							$fieldset->$name = (string) $value;
						}
					}

					// Add the fieldset object to the list.
					$fieldsets[$fieldset->name] = $fieldset;
				}
			}
		}

		return $fieldsets;
	}

	/**
	 * Method to get the form control. This string serves as a container for all form fields. For
	 * example, if there is a field named 'foo' and a field named 'bar' and the form control is
	 * empty the fields will be rendered like: `<input name="foo" />` and `<input name="bar" />`.  If
	 * the form control is set to 'joomla' however, the fields would be rendered like:
	 * `<input name="joomla[foo]" />` and `<input name="joomla[bar]" />`.
	 *
	 * @return  string  The form control string.
	 *
	 * @since   1.7.0
	 */
	public function getFormControl()
	{
		return (string) $this->options['control'];
	}

	/**
	 * Method to get an array of JFormField objects in a given field group by name.
	 *
	 * @param   string   $group   The dot-separated form group path for which to get the form fields.
	 * @param   boolean  $nested  True to also include fields in nested groups that are inside of the
	 *                            group for which to find fields.
	 *
	 * @return  \JFormField[]  The array of JFormField objects in the field group.
	 *
	 * @since   1.7.0
	 */
	public function getGroup($group, $nested = false)
	{
		$fields = array();

		// Get all of the field elements in the field group.
		$elements = $this->findFieldsByGroup($group, $nested);

		// If no field elements were found return empty.
		if (empty($elements))
		{
			return $fields;
		}

		// Build the result array from the found field elements.
		foreach ($elements as $element)
		{
			// Get the field groups for the element.
			$attrs  = $element->xpath('ancestor::fields[@name]/@name');
			$groups = array_map('strval', $attrs ? $attrs : array());
			$group  = implode('.', $groups);

			// If the field is successfully loaded add it to the result array.
			if ($field = $this->loadField($element, $group))
			{
				$fields[$field->id] = $field;
			}
		}

		return $fields;
	}

	/**
	 * Method to get a form field markup for the field input.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value  The optional value to use as the default for the field.
	 *
	 * @return  string  The form field markup.
	 *
	 * @since   1.7.0
	 */
	public function getInput($name, $group = null, $value = null)
	{
		// Attempt to get the form field.
		if ($field = $this->getField($name, $group, $value))
		{
			return $field->input;
		}

		return '';
	}

	/**
	 * Method to get the label for a field input.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  string  The form field label.
	 *
	 * @since   1.7.0
	 */
	public function getLabel($name, $group = null)
	{
		// Attempt to get the form field.
		if ($field = $this->getField($name, $group))
		{
			return $field->label;
		}

		return '';
	}

	/**
	 * Method to get the form name.
	 *
	 * @return  string  The name of the form.
	 *
	 * @since   1.7.0
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Method to get the value of a field.
	 *
	 * @param   string  $name     The name of the field for which to get the value.
	 * @param   string  $group    The optional dot-separated form group path on which to get the value.
	 * @param   mixed   $default  The optional default value of the field value is empty.
	 *
	 * @return  mixed  The value of the field or the default value if empty.
	 *
	 * @since   1.7.0
	 */
	public function getValue($name, $group = null, $default = null)
	{
		// If a group is set use it.
		if ($group)
		{
			$return = $this->data->get($group . '.' . $name, $default);
		}
		else
		{
			$return = $this->data->get($name, $default);
		}

		return $return;
	}

	/**
	 * Method to get a control group with label and input.
	 *
	 * @param   string  $name     The name of the field for which to get the value.
	 * @param   string  $group    The optional dot-separated form group path on which to get the value.
	 * @param   mixed   $default  The optional default value of the field value is empty.
	 *
	 * @return  string  A string containing the html for the control goup
	 *
	 * @since      3.2
	 * @deprecated 3.2.3  Use renderField() instead of getControlGroup
	 */
	public function getControlGroup($name, $group = null, $default = null)
	{
		\JLog::add('Form->getControlGroup() is deprecated use Form->renderField().', \JLog::WARNING, 'deprecated');

		return $this->renderField($name, $group, $default);
	}

	/**
	 * Method to get all control groups with label and input of a fieldset.
	 *
	 * @param   string  $name  The name of the fieldset for which to get the values.
	 *
	 * @return  string  A string containing the html for the control goups
	 *
	 * @since      3.2
	 * @deprecated 3.2.3 Use renderFieldset() instead of getControlGroups
	 */
	public function getControlGroups($name)
	{
		\JLog::add('Form->getControlGroups() is deprecated use Form->renderFieldset().', \JLog::WARNING, 'deprecated');

		return $this->renderFieldset($name);
	}

	/**
	 * Method to get a control group with label and input.
	 *
	 * @param   string  $name     The name of the field for which to get the value.
	 * @param   string  $group    The optional dot-separated form group path on which to get the value.
	 * @param   mixed   $default  The optional default value of the field value is empty.
	 * @param   array   $options  Any options to be passed into the rendering of the field
	 *
	 * @return  string  A string containing the html for the control goup
	 *
	 * @since   3.2.3
	 */
	public function renderField($name, $group = null, $default = null, $options = array())
	{
		$field = $this->getField($name, $group, $default);

		if ($field)
		{
			return $field->renderField($options);
		}

		return '';
	}

	/**
	 * Method to get all control groups with label and input of a fieldset.
	 *
	 * @param   string  $name     The name of the fieldset for which to get the values.
	 * @param   array   $options  Any options to be passed into the rendering of the field
	 *
	 * @return  string  A string containing the html for the control goups
	 *
	 * @since   3.2.3
	 */
	public function renderFieldset($name, $options = array())
	{
		$fields = $this->getFieldset($name);
		$html = array();

		foreach ($fields as $field)
		{
			$html[] = $field->renderField($options);
		}

		return implode('', $html);
	}

	/**
	 * Method to load the form description from an XML string or object.
	 *
	 * The replace option works per field.  If a field being loaded already exists in the current
	 * form definition then the behavior or load will vary depending upon the replace flag.  If it
	 * is set to true, then the existing field will be replaced in its exact location by the new
	 * field being loaded.  If it is false, then the new field being loaded will be ignored and the
	 * method will move on to the next field to load.
	 *
	 * @param   string  $data     The name of an XML string or object.
	 * @param   string  $replace  Flag to toggle whether form fields should be replaced if a field
	 *                            already exists with the same group/name.
	 * @param   string  $xpath    An optional xpath to search for the fields.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function load($data, $replace = true, $xpath = false)
	{
		// If the data to load isn't already an XML element or string return false.
		if ((!($data instanceof \SimpleXMLElement)) && (!is_string($data)))
		{
			return false;
		}

		// Attempt to load the XML if a string.
		if (is_string($data))
		{
			try
			{
				$data = new \SimpleXMLElement($data);
			}
			catch (\Exception $e)
			{
				return false;
			}

			// Make sure the XML loaded correctly.
			if (!$data)
			{
				return false;
			}
		}

		// If we have no XML definition at this point let's make sure we get one.
		if (empty($this->xml))
		{
			// If no XPath query is set to search for fields, and we have a <form />, set it and return.
			if (!$xpath && ($data->getName() == 'form'))
			{
				$this->xml = $data;

				// Synchronize any paths found in the load.
				$this->syncPaths();

				return true;
			}

			// Create a root element for the form.
			else
			{
				$this->xml = new \SimpleXMLElement('<form></form>');
			}
		}

		// Get the XML elements to load.
		$elements = array();

		if ($xpath)
		{
			$elements = $data->xpath($xpath);
		}
		elseif ($data->getName() == 'form')
		{
			$elements = $data->children();
		}

		// If there is nothing to load return true.
		if (empty($elements))
		{
			return true;
		}

		// Load the found form elements.
		foreach ($elements as $element)
		{
			// Get an array of fields with the correct name.
			$fields = $element->xpath('descendant-or-self::field');

			foreach ($fields as $field)
			{
				// Get the group names as strings for ancestor fields elements.
				$attrs = $field->xpath('ancestor::fields[@name]/@name');
				$groups = array_map('strval', $attrs ? $attrs : array());

				// Check to see if the field exists in the current form.
				if ($current = $this->findField((string) $field['name'], implode('.', $groups)))
				{
					// If set to replace found fields, replace the data and remove the field so we don't add it twice.
					if ($replace)
					{
						$olddom = dom_import_simplexml($current);
						$loadeddom = dom_import_simplexml($field);
						$addeddom = $olddom->ownerDocument->importNode($loadeddom, true);
						$olddom->parentNode->replaceChild($addeddom, $olddom);
						$loadeddom->parentNode->removeChild($loadeddom);
					}
					else
					{
						unset($field);
					}
				}
			}

			// Merge the new field data into the existing XML document.
			self::addNode($this->xml, $element);
		}

		// Synchronize any paths found in the load.
		$this->syncPaths();

		return true;
	}

	/**
	 * Method to load the form description from an XML file.
	 *
	 * The reset option works on a group basis. If the XML file references
	 * groups that have already been created they will be replaced with the
	 * fields in the new XML file unless the $reset parameter has been set
	 * to false.
	 *
	 * @param   string  $file   The filesystem path of an XML file.
	 * @param   string  $reset  Flag to toggle whether form fields should be replaced if a field
	 *                          already exists with the same group/name.
	 * @param   string  $xpath  An optional xpath to search for the fields.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function loadFile($file, $reset = true, $xpath = false)
	{
		// Check to see if the path is an absolute path.
		if (!is_file($file))
		{
			// Not an absolute path so let's attempt to find one using JPath.
			$file = \JPath::find(self::addFormPath(), strtolower($file) . '.xml');

			// If unable to find the file return false.
			if (!$file)
			{
				return false;
			}
		}

		// Attempt to load the XML file.
		$xml = simplexml_load_file($file);

		return $this->load($xml, $reset, $xpath);
	}

	/**
	 * Method to remove a field from the form definition.
	 *
	 * @param   string  $name   The name of the form field for which remove.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function removeField($name, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::removeField `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Find the form field element from the definition.
		$element = $this->findField($name, $group);

		// If the element exists remove it from the form definition.
		if ($element instanceof \SimpleXMLElement)
		{
			$dom = dom_import_simplexml($element);
			$dom->parentNode->removeChild($dom);

			return true;
		}

		return false;
	}

	/**
	 * Method to remove a group from the form definition.
	 *
	 * @param   string  $group  The dot-separated form group path for the group to remove.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function removeGroup($group)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::removeGroup `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Get the fields elements for a given group.
		$elements = &$this->findGroup($group);

		foreach ($elements as &$element)
		{
			$dom = dom_import_simplexml($element);
			$dom->parentNode->removeChild($dom);
		}

		return true;
	}

	/**
	 * Method to reset the form data store and optionally the form XML definition.
	 *
	 * @param   boolean  $xml  True to also reset the XML form definition.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function reset($xml = false)
	{
		unset($this->data);
		$this->data = new Registry;

		if ($xml)
		{
			unset($this->xml);
			$this->xml = new \SimpleXMLElement('<form></form>');
		}

		return true;
	}

	/**
	 * Method to set a field XML element to the form definition.  If the replace flag is set then
	 * the field will be set whether it already exists or not.  If it isn't set, then the field
	 * will not be replaced if it already exists.
	 *
	 * @param   \SimpleXMLElement  $element   The XML element object representation of the form field.
	 * @param   string             $group     The optional dot-separated form group path on which to set the field.
	 * @param   boolean            $replace   True to replace an existing field if one already exists.
	 * @param   string             $fieldset  The name of the fieldset we are adding the field to.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function setField(\SimpleXMLElement $element, $group = null, $replace = true, $fieldset = 'default')
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::setField `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Find the form field element from the definition.
		$old = $this->findField((string) $element['name'], $group);

		// If an existing field is found and replace flag is false do nothing and return true.
		if (!$replace && !empty($old))
		{
			return true;
		}

		// If an existing field is found and replace flag is true remove the old field.
		if ($replace && !empty($old) && ($old instanceof \SimpleXMLElement))
		{
			$dom = dom_import_simplexml($old);

			// Get the parent element, this should be the fieldset
			$parent   = $dom->parentNode;
			$fieldset = $parent->getAttribute('name');

			$parent->removeChild($dom);
		}

		// Create the search path
		$path = '//';

		if (!empty($group))
		{
			$path .= 'fields[@name="' . $group . '"]/';
		}

		$path .= 'fieldset[@name="' . $fieldset . '"]';

		$fs = $this->xml->xpath($path);

		if (isset($fs[0]) && ($fs[0] instanceof \SimpleXMLElement))
		{
			// Add field to the form.
			self::addNode($fs[0], $element);

			// Synchronize any paths found in the load.
			$this->syncPaths();

			return true;
		}

		// We couldn't find a fieldset to add the field. Now we are checking, if we have set only a group
		if (!empty($group))
		{
			$fields = &$this->findGroup($group);

			// If an appropriate fields element was found for the group, add the element.
			if (isset($fields[0]) && ($fields[0] instanceof \SimpleXMLElement))
			{
				self::addNode($fields[0], $element);
			}

			// Synchronize any paths found in the load.
			$this->syncPaths();

			return true;
		}

		// We couldn't find a parent so we are adding it at root level

		// Add field to the form.
		self::addNode($this->xml, $element);

		// Synchronize any paths found in the load.
		$this->syncPaths();

		return true;
	}

	/**
	 * Method to set an attribute value for a field XML element.
	 *
	 * @param   string  $name       The name of the form field for which to set the attribute value.
	 * @param   string  $attribute  The name of the attribute for which to set a value.
	 * @param   mixed   $value      The value to set for the attribute.
	 * @param   string  $group      The optional dot-separated form group path on which to find the field.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function setFieldAttribute($name, $attribute, $value, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::setFieldAttribute `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Find the form field element from the definition.
		$element = $this->findField($name, $group);

		// If the element doesn't exist return false.
		if (!($element instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Otherwise set the attribute and return true.
		else
		{
			$element[$attribute] = $value;

			// Synchronize any paths found in the load.
			$this->syncPaths();

			return true;
		}
	}

	/**
	 * Method to set some field XML elements to the form definition.  If the replace flag is set then
	 * the fields will be set whether they already exists or not.  If it isn't set, then the fields
	 * will not be replaced if they already exist.
	 *
	 * @param   array    &$elements  The array of XML element object representations of the form fields.
	 * @param   string   $group      The optional dot-separated form group path on which to set the fields.
	 * @param   boolean  $replace    True to replace existing fields if they already exist.
	 * @param   string   $fieldset   The name of the fieldset we are adding the field to.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function setFields(&$elements, $group = null, $replace = true, $fieldset = 'default')
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			throw new \UnexpectedValueException(sprintf('%s::setFields `xml` is not an instance of SimpleXMLElement', get_class($this)));
		}

		// Make sure the elements to set are valid.
		foreach ($elements as $element)
		{
			if (!($element instanceof \SimpleXMLElement))
			{
				throw new \UnexpectedValueException(sprintf('$element not SimpleXMLElement in %s::setFields', get_class($this)));
			}
		}

		// Set the fields.
		$return = true;

		foreach ($elements as $element)
		{
			if (!$this->setField($element, $group, $replace, $fieldset))
			{
				$return = false;
			}
		}

		// Synchronize any paths found in the load.
		$this->syncPaths();

		return $return;
	}

	/**
	 * Method to set the value of a field. If the field does not exist in the form then the method
	 * will return false.
	 *
	 * @param   string  $name   The name of the field for which to set the value.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value  The value to set for the field.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setValue($name, $group = null, $value = null)
	{
		// If the field does not exist return false.
		if (!$this->findField($name, $group))
		{
			return false;
		}

		// If a group is set use it.
		if ($group)
		{
			$this->data->set($group . '.' . $name, $value);
		}
		else
		{
			$this->data->set($name, $value);
		}

		return true;
	}

	/**
	 * Method to validate form data.
	 *
	 * Validation warnings will be pushed into JForm::errors and should be
	 * retrieved with JForm::getErrors() when validate returns boolean false.
	 *
	 * @param   array   $data   An array of field values to validate.
	 * @param   string  $group  The optional dot-separated form group path on which to filter the
	 *                          fields to be validated.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function validate($data, $group = null)
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		$return = true;

		// Create an input registry object from the data to validate.
		$input = new Registry($data);

		// Get the fields for which to validate the data.
		$fields = $this->findFieldsByGroup($group);

		if (!$fields)
		{
			// PANIC!
			return false;
		}

		// Validate the fields.
		foreach ($fields as $field)
		{
			$value = null;
			$name = (string) $field['name'];

			// Get the group names as strings for ancestor fields elements.
			$attrs = $field->xpath('ancestor::fields[@name]/@name');
			$groups = array_map('strval', $attrs ? $attrs : array());
			$group = implode('.', $groups);

			// Get the value from the input data.
			if ($group)
			{
				$value = $input->get($group . '.' . $name);
			}
			else
			{
				$value = $input->get($name);
			}

			// Validate the field.
			$valid = $this->validateField($field, $group, $value, $input);

			// Check for an error.
			if ($valid instanceof \Exception)
			{
				$this->errors[] = $valid;
				$return         = false;
			}
		}

		return $return;
	}

	/**
	 * Method to apply an input filter to a value based on field data.
	 *
	 * @param   string  $element  The XML element object representation of the form field.
	 * @param   mixed   $value    The value to filter for the field.
	 *
	 * @return  mixed   The filtered value.
	 *
	 * @since   1.7.0
	 */
	protected function filterField($element, $value)
	{
		// Make sure there is a valid SimpleXMLElement.
		if (!($element instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Get the field filter type.
		$filter = (string) $element['filter'];

		// Process the input value based on the filter.
		$return = null;

		switch (strtoupper($filter))
		{
			// Access Control Rules.
			case 'RULES':
				$return = array();

				foreach ((array) $value as $action => $ids)
				{
					// Build the rules array.
					$return[$action] = array();

					foreach ($ids as $id => $p)
					{
						if ($p !== '')
						{
							$return[$action][$id] = ($p == '1' || $p == 'true') ? true : false;
						}
					}
				}
				break;

			// Do nothing, thus leaving the return value as null.
			case 'UNSET':
				break;

			// No Filter.
			case 'RAW':
				$return = $value;
				break;

			// Filter the input as an array of integers.
			case 'INT_ARRAY':
				// Make sure the input is an array.
				if (is_object($value))
				{
					$value = get_object_vars($value);
				}

				$value = is_array($value) ? $value : array($value);

				$value = ArrayHelper::toInteger($value);
				$return = $value;
				break;

			// Filter safe HTML.
			case 'SAFEHTML':
				$return = \JFilterInput::getInstance(null, null, 1, 1)->clean($value, 'html');
				break;

			// Convert a date to UTC based on the server timezone offset.
			case 'SERVER_UTC':
				if ((int) $value > 0)
				{
					// Check if we have a localised date format
					$translateFormat = (string) $element['translateformat'];

					if ($translateFormat && $translateFormat != 'false')
					{
						$showTime = (string) $element['showtime'];
						$showTime = ($showTime && $showTime != 'false');
						$format   = ($showTime) ? \JText::_('DATE_FORMAT_FILTER_DATETIME') : \JText::_('DATE_FORMAT_FILTER_DATE');
						$date     = date_parse_from_format($format, $value);
						$value    = (int) $date['year'] . '-' . (int) $date['month'] . '-' . (int) $date['day'];

						if ($showTime)
						{
							$value .= ' ' . (int) $date['hour'] . ':' . (int) $date['minute'] . ':' . (int) $date['second'];
						}
					}

					// Get the server timezone setting.
					$offset = \JFactory::getConfig()->get('offset');

					// Return an SQL formatted datetime string in UTC.
					try
					{
						$return = \JFactory::getDate($value, $offset)->toSql();
					}
					catch (\Exception $e)
					{
						\JFactory::getApplication()->enqueueMessage(
							\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', \JText::_((string) $element['label'])),
							'warning'
						);

						$return = '';
					}
				}
				else
				{
					$return = '';
				}
				break;

			// Convert a date to UTC based on the user timezone offset.
			case 'USER_UTC':
				if ((int) $value > 0)
				{
					// Check if we have a localised date format
					$translateFormat = (string) $element['translateformat'];

					if ($translateFormat && $translateFormat != 'false')
					{
						$showTime = (string) $element['showtime'];
						$showTime = ($showTime && $showTime != 'false');
						$format   = ($showTime) ? \JText::_('DATE_FORMAT_FILTER_DATETIME') : \JText::_('DATE_FORMAT_FILTER_DATE');
						$date     = date_parse_from_format($format, $value);
						$value    = (int) $date['year'] . '-' . (int) $date['month'] . '-' . (int) $date['day'];

						if ($showTime)
						{
							$value .= ' ' . (int) $date['hour'] . ':' . (int) $date['minute'] . ':' . (int) $date['second'];
						}
					}

					// Get the user timezone setting defaulting to the server timezone setting.
					$offset = \JFactory::getUser()->getTimezone();

					// Return a MySQL formatted datetime string in UTC.
					try
					{
						$return = \JFactory::getDate($value, $offset)->toSql();
					}
					catch (\Exception $e)
					{
						\JFactory::getApplication()->enqueueMessage(
							\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', \JText::_((string) $element['label'])),
							'warning'
						);

						$return = '';
					}
				}
				else
				{
					$return = '';
				}
				break;

			/*
			 * Ensures a protocol is present in the saved field unless the relative flag is set.
			 * Only use when the only permitted protocols require '://'.
			 * See JFormRuleUrl for list of these.
			 */

			case 'URL':
				if (empty($value))
				{
					return false;
				}

				// This cleans some of the more dangerous characters but leaves special characters that are valid.
				$value = \JFilterInput::getInstance()->clean($value, 'html');
				$value = trim($value);

				// <>" are never valid in a uri see http://www.ietf.org/rfc/rfc1738.txt.
				$value = str_replace(array('<', '>', '"'), '', $value);

				// Check for a protocol
				$protocol = parse_url($value, PHP_URL_SCHEME);

				// If there is no protocol and the relative option is not specified,
				// we assume that it is an external URL and prepend http://.
				if (($element['type'] == 'url' && !$protocol &&  !$element['relative'])
					|| (!$element['type'] == 'url' && !$protocol))
				{
					$protocol = 'http';

					// If it looks like an internal link, then add the root.
					if (substr($value, 0, 9) == 'index.php')
					{
						$value = \JUri::root() . $value;
					}

					// Otherwise we treat it as an external link.
					else
					{
						// Put the url back together.
						$value = $protocol . '://' . $value;
					}
				}

				// If relative URLS are allowed we assume that URLs without protocols are internal.
				elseif (!$protocol && $element['relative'])
				{
					$host = \JUri::getInstance('SERVER')->gethost();

					// If it starts with the host string, just prepend the protocol.
					if (substr($value, 0) == $host)
					{
						$value = 'http://' . $value;
					}

					// Otherwise if it doesn't start with "/" prepend the prefix of the current site.
					elseif (substr($value, 0, 1) != '/')
					{
						$value = \JUri::root(true) . '/' . $value;
					}
				}

				$value = \JStringPunycode::urlToPunycode($value);
				$return = $value;
				break;

			case 'TEL':
				$value = trim($value);

				// Does it match the NANP pattern?
				if (preg_match('/^(?:\+?1[-. ]?)?\(?([2-9][0-8][0-9])\)?[-. ]?([2-9][0-9]{2})[-. ]?([0-9]{4})$/', $value) == 1)
				{
					$number = (string) preg_replace('/[^\d]/', '', $value);

					if (substr($number, 0, 1) == 1)
					{
						$number = substr($number, 1);
					}

					if (substr($number, 0, 2) == '+1')
					{
						$number = substr($number, 2);
					}

					$result = '1.' . $number;
				}

				// If not, does it match ITU-T?
				elseif (preg_match('/^\+(?:[0-9] ?){6,14}[0-9]$/', $value) == 1)
				{
					$countrycode = substr($value, 0, strpos($value, ' '));
					$countrycode = (string) preg_replace('/[^\d]/', '', $countrycode);
					$number = strstr($value, ' ');
					$number = (string) preg_replace('/[^\d]/', '', $number);
					$result = $countrycode . '.' . $number;
				}

				// If not, does it match EPP?
				elseif (preg_match('/^\+[0-9]{1,3}\.[0-9]{4,14}(?:x.+)?$/', $value) == 1)
				{
					if (strstr($value, 'x'))
					{
						$xpos = strpos($value, 'x');
						$value = substr($value, 0, $xpos);
					}

					$result = str_replace('+', '', $value);
				}

				// Maybe it is already ccc.nnnnnnn?
				elseif (preg_match('/[0-9]{1,3}\.[0-9]{4,14}$/', $value) == 1)
				{
					$result = $value;
				}

				// If not, can we make it a string of digits?
				else
				{
					$value = (string) preg_replace('/[^\d]/', '', $value);

					if ($value != null && strlen($value) <= 15)
					{
						$length = strlen($value);

						// If it is fewer than 13 digits assume it is a local number
						if ($length <= 12)
						{
							$result = '.' . $value;
						}
						else
						{
							// If it has 13 or more digits let's make a country code.
							$cclen = $length - 12;
							$result = substr($value, 0, $cclen) . '.' . substr($value, $cclen);
						}
					}

					// If not let's not save anything.
					else
					{
						$result = '';
					}
				}

				$return = $result;

				break;
			default:
				if ($element['type'] == 'subform')
				{
					$field   = $this->loadField($element);
					$subForm = $field->loadSubForm();

					if ($field->multiple && !empty($value))
					{
						$return = array();

						foreach ($value as $key => $val)
						{
							$return[$key] = $subForm->filter($val);
						}
					}
					else
					{
						$return = $subForm->filter($value);
					}

					break;
				}
				// Check for a callback filter.
				if (strpos($filter, '::') !== false && is_callable(explode('::', $filter)))
				{
					$return = call_user_func(explode('::', $filter), $value);
				}

				// Filter using a callback function if specified.
				elseif (function_exists($filter))
				{
					$return = call_user_func($filter, $value);
				}

				// Check for empty value and return empty string if no value is required,
				// otherwise filter using JFilterInput. All HTML code is filtered by default.
				else
				{
					$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

					if (($value === '' || $value === null) && ! $required)
					{
						$return = '';
					}
					else
					{
						$return = \JFilterInput::getInstance()->clean($value, $filter);
					}
				}
				break;
		}

		return $return;
	}

	/**
	 * Method to get a form field represented as an XML element object.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  \SimpleXMLElement|boolean  The XML element object for the field or boolean false on error.
	 *
	 * @since   1.7.0
	 */
	protected function findField($name, $group = null)
	{
		$element = false;
		$fields = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Let's get the appropriate field element based on the method arguments.
		if ($group)
		{
			// Get the fields elements for a given group.
			$elements = &$this->findGroup($group);

			// Get all of the field elements with the correct name for the fields elements.
			foreach ($elements as $el)
			{
				// If there are matching field elements add them to the fields array.
				if ($tmp = $el->xpath('descendant::field[@name="' . $name . '" and not(ancestor::field/form/*)]'))
				{
					$fields = array_merge($fields, $tmp);
				}
			}

			// Make sure something was found.
			if (!$fields)
			{
				return false;
			}

			// Use the first correct match in the given group.
			$groupNames = explode('.', $group);

			foreach ($fields as &$field)
			{
				// Get the group names as strings for ancestor fields elements.
				$attrs = $field->xpath('ancestor::fields[@name]/@name');
				$names = array_map('strval', $attrs ? $attrs : array());

				// If the field is in the exact group use it and break out of the loop.
				if ($names == (array) $groupNames)
				{
					$element = &$field;
					break;
				}
			}
		}
		else
		{
			// Get an array of fields with the correct name.
			$fields = $this->xml->xpath('//field[@name="' . $name . '" and not(ancestor::field/form/*)]');

			// Make sure something was found.
			if (!$fields)
			{
				return false;
			}

			// Search through the fields for the right one.
			foreach ($fields as &$field)
			{
				// If we find an ancestor fields element with a group name then it isn't what we want.
				if ($field->xpath('ancestor::fields[@name]'))
				{
					continue;
				}

				// Found it!
				else
				{
					$element = &$field;
					break;
				}
			}
		}

		return $element;
	}

	/**
	 * Method to get an array of `<field>` elements from the form XML document which are in a specified fieldset by name.
	 *
	 * @param   string  $name  The name of the fieldset.
	 *
	 * @return  \SimpleXMLElement[]|boolean  Boolean false on error or array of SimpleXMLElement objects.
	 *
	 * @since   1.7.0
	 */
	protected function &findFieldsByFieldset($name)
	{
		$false = false;

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return $false;
		}

		/*
		 * Get an array of <field /> elements that are underneath a <fieldset /> element
		 * with the appropriate name attribute, and also any <field /> elements with
		 * the appropriate fieldset attribute. To allow repeatable elements only fields
		 * which are not descendants of other fields are selected.
		 */
		$fields = $this->xml->xpath('(//fieldset[@name="' . $name . '"]//field | //field[@fieldset="' . $name . '"])[not(ancestor::field)]');

		return $fields;
	}

	/**
	 * Method to get an array of `<field>` elements from the form XML document which are in a control group by name.
	 *
	 * @param   mixed    $group   The optional dot-separated form group path on which to find the fields.
	 *                            Null will return all fields. False will return fields not in a group.
	 * @param   boolean  $nested  True to also include fields in nested groups that are inside of the
	 *                            group for which to find fields.
	 *
	 * @return  \SimpleXMLElement[]|boolean  Boolean false on error or array of SimpleXMLElement objects.
	 *
	 * @since   1.7.0
	 */
	protected function &findFieldsByGroup($group = null, $nested = false)
	{
		$false = false;
		$fields = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return $false;
		}

		// Get only fields in a specific group?
		if ($group)
		{
			// Get the fields elements for a given group.
			$elements = &$this->findGroup($group);

			// Get all of the field elements for the fields elements.
			foreach ($elements as $element)
			{
				// If there are field elements add them to the return result.
				if ($tmp = $element->xpath('descendant::field'))
				{
					// If we also want fields in nested groups then just merge the arrays.
					if ($nested)
					{
						$fields = array_merge($fields, $tmp);
					}

					// If we want to exclude nested groups then we need to check each field.
					else
					{
						$groupNames = explode('.', $group);

						foreach ($tmp as $field)
						{
							// Get the names of the groups that the field is in.
							$attrs = $field->xpath('ancestor::fields[@name]/@name');
							$names = array_map('strval', $attrs ? $attrs : array());

							// If the field is in the specific group then add it to the return list.
							if ($names == (array) $groupNames)
							{
								$fields = array_merge($fields, array($field));
							}
						}
					}
				}
			}
		}
		elseif ($group === false)
		{
			// Get only field elements not in a group.
			$fields = $this->xml->xpath('descendant::fields[not(@name)]/field | descendant::fields[not(@name)]/fieldset/field ');
		}
		else
		{
			// Get an array of all the <field /> elements.
			$fields = $this->xml->xpath('//field[not(ancestor::field/form/*)]');
		}

		return $fields;
	}

	/**
	 * Method to get a form field group represented as an XML element object.
	 *
	 * @param   string  $group  The dot-separated form group path on which to find the group.
	 *
	 * @return  \SimpleXMLElement[]|boolean  An array of XML element objects for the group or boolean false on error.
	 *
	 * @since   1.7.0
	 */
	protected function &findGroup($group)
	{
		$false = false;
		$groups = array();
		$tmp = array();

		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return $false;
		}

		// Make sure there is actually a group to find.
		$group = explode('.', $group);

		if (!empty($group))
		{
			// Get any fields elements with the correct group name.
			$elements = $this->xml->xpath('//fields[@name="' . (string) $group[0] . '" and not(ancestor::field/form/*)]');

			// Check to make sure that there are no parent groups for each element.
			foreach ($elements as $element)
			{
				if (!$element->xpath('ancestor::fields[@name]'))
				{
					$tmp[] = $element;
				}
			}

			// Iterate through the nested groups to find any matching form field groups.
			for ($i = 1, $n = count($group); $i < $n; $i++)
			{
				// Initialise some loop variables.
				$validNames = array_slice($group, 0, $i + 1);
				$current = $tmp;
				$tmp = array();

				// Check to make sure that there are no parent groups for each element.
				foreach ($current as $element)
				{
					// Get any fields elements with the correct group name.
					$children = $element->xpath('descendant::fields[@name="' . (string) $group[$i] . '"]');

					// For the found fields elements validate that they are in the correct groups.
					foreach ($children as $fields)
					{
						// Get the group names as strings for ancestor fields elements.
						$attrs = $fields->xpath('ancestor-or-self::fields[@name]/@name');
						$names = array_map('strval', $attrs ? $attrs : array());

						// If the group names for the fields element match the valid names at this
						// level add the fields element.
						if ($validNames == $names)
						{
							$tmp[] = $fields;
						}
					}
				}
			}

			// Only include valid XML objects.
			foreach ($tmp as $element)
			{
				if ($element instanceof \SimpleXMLElement)
				{
					$groups[] = $element;
				}
			}
		}

		return $groups;
	}

	/**
	 * Method to load, setup and return a JFormField object based on field data.
	 *
	 * @param   string  $element  The XML element object representation of the form field.
	 * @param   string  $group    The optional dot-separated form group path on which to find the field.
	 * @param   mixed   $value    The optional value to use as the default for the field.
	 *
	 * @return  \JFormField|boolean  The JFormField object for the field or boolean false on error.
	 *
	 * @since   1.7.0
	 */
	protected function loadField($element, $group = null, $value = null)
	{
		// Make sure there is a valid SimpleXMLElement.
		if (!($element instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Get the field type.
		$type = $element['type'] ? (string) $element['type'] : 'text';

		// Load the JFormField object for the field.
		$field = $this->loadFieldType($type);

		// If the object could not be loaded, get a text field object.
		if ($field === false)
		{
			$field = $this->loadFieldType('text');
		}

		/*
		 * Get the value for the form field if not set.
		 * Default to the translated version of the 'default' attribute
		 * if 'translate_default' attribute if set to 'true' or '1'
		 * else the value of the 'default' attribute for the field.
		 */
		if ($value === null)
		{
			$default = (string) ($element['default'] ? $element['default'] : $element->default);

			if (($translate = $element['translate_default']) && ((string) $translate == 'true' || (string) $translate == '1'))
			{
				$lang = \JFactory::getLanguage();

				if ($lang->hasKey($default))
				{
					$debug = $lang->setDebug(false);
					$default = \JText::_($default);
					$lang->setDebug($debug);
				}
				else
				{
					$default = \JText::_($default);
				}
			}

			$value = $this->getValue((string) $element['name'], $group, $default);
		}

		// Setup the JFormField object.
		$field->setForm($this);

		if ($field->setup($element, $value, $group))
		{
			return $field;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Proxy for {@link FormHelper::loadFieldType()}.
	 *
	 * @param   string   $type  The field type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  FormField|boolean  FormField object on success, false otherwise.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Use FormHelper::loadFieldType() directly
	 */
	protected function loadFieldType($type, $new = true)
	{
		return FormHelper::loadFieldType($type, $new);
	}

	/**
	 * Proxy for FormHelper::loadRuleType().
	 *
	 * @param   string   $type  The rule type.
	 * @param   boolean  $new   Flag to toggle whether we should get a new instance of the object.
	 *
	 * @return  FormRule|boolean  FormRule object on success, false otherwise.
	 *
	 * @see     FormHelper::loadRuleType()
	 * @since   1.7.0
	 * @deprecated  4.0  Use FormHelper::loadRuleType() directly
	 */
	protected function loadRuleType($type, $new = true)
	{
		return FormHelper::loadRuleType($type, $new);
	}

	/**
	 * Method to synchronize any field, form or rule paths contained in the XML document.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @todo    Maybe we should receive all addXXXpaths attributes at once?
	 */
	protected function syncPaths()
	{
		// Make sure there is a valid JForm XML document.
		if (!($this->xml instanceof \SimpleXMLElement))
		{
			return false;
		}

		// Get any addfieldpath attributes from the form definition.
		$paths = $this->xml->xpath('//*[@addfieldpath]/@addfieldpath');
		$paths = array_map('strval', $paths ? $paths : array());

		// Add the field paths.
		foreach ($paths as $path)
		{
			$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
			self::addFieldPath($path);
		}

		// Get any addformpath attributes from the form definition.
		$paths = $this->xml->xpath('//*[@addformpath]/@addformpath');
		$paths = array_map('strval', $paths ? $paths : array());

		// Add the form paths.
		foreach ($paths as $path)
		{
			$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
			self::addFormPath($path);
		}

		// Get any addrulepath attributes from the form definition.
		$paths = $this->xml->xpath('//*[@addrulepath]/@addrulepath');
		$paths = array_map('strval', $paths ? $paths : array());

		// Add the rule paths.
		foreach ($paths as $path)
		{
			$path = JPATH_ROOT . '/' . ltrim($path, '/\\');
			self::addRulePath($path);
		}

		// Get any addfieldprefix attributes from the form definition.
		$prefixes = $this->xml->xpath('//*[@addfieldprefix]/@addfieldprefix');
		$prefixes = array_map('strval', $prefixes ? $prefixes : array());

		// Add the field prefixes.
		foreach ($prefixes as $prefix)
		{
			FormHelper::addFieldPrefix($prefix);
		}

		// Get any addformprefix attributes from the form definition.
		$prefixes = $this->xml->xpath('//*[@addformprefix]/@addformprefix');
		$prefixes = array_map('strval', $prefixes ? $prefixes : array());

		// Add the field prefixes.
		foreach ($prefixes as $prefix)
		{
			FormHelper::addFormPrefix($prefix);
		}

		// Get any addruleprefix attributes from the form definition.
		$prefixes = $this->xml->xpath('//*[@addruleprefix]/@addruleprefix');
		$prefixes = array_map('strval', $prefixes ? $prefixes : array());

		// Add the field prefixes.
		foreach ($prefixes as $prefix)
		{
			FormHelper::addRulePrefix($prefix);
		}

		return true;
	}

	/**
	 * Method to validate a JFormField object based on field data.
	 *
	 * @param   \SimpleXMLElement  $element  The XML element object representation of the form field.
	 * @param   string             $group    The optional dot-separated form group path on which to find the field.
	 * @param   mixed              $value    The optional value to use as the default for the field.
	 * @param   Registry           $input    An optional Registry object with the entire data set to validate
	 *                                      against the entire form.
	 *
	 * @return  boolean  Boolean true if field value is valid, Exception on failure.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 * @throws  \UnexpectedValueException
	 */
	protected function validateField(\SimpleXMLElement $element, $group = null, $value = null, Registry $input = null)
	{
		$valid = true;

		// Check if the field is required.
		$required = ((string) $element['required'] == 'true' || (string) $element['required'] == 'required');

		if ($input)
		{
			$disabled = ((string) $element['disabled'] == 'true' || (string) $element['disabled'] == 'disabled');

			$fieldExistsInRequestData = $input->exists((string) $element['name']) || $input->exists($group . '.' . (string) $element['name']);

			// If the field is disabled but it is passed in the request this is invalid as disabled fields are not added to the request
			if ($disabled && $fieldExistsInRequestData)
			{
				return new \RuntimeException(\JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $element['name']));
			}
		}

		if ($required)
		{
			// If the field is required and the value is empty return an error message.
			if (($value === '') || ($value === null))
			{
				if ($element['label'])
				{
					$message = \JText::_($element['label']);
				}
				else
				{
					$message = \JText::_($element['name']);
				}

				$message = \JText::sprintf('JLIB_FORM_VALIDATE_FIELD_REQUIRED', $message);

				return new \RuntimeException($message);
			}
		}

		// Get the field validation rule.
		if ($type = (string) $element['validate'])
		{
			// Load the JFormRule object for the field.
			$rule = $this->loadRuleType($type);

			// If the object could not be loaded return an error message.
			if ($rule === false)
			{
				throw new \UnexpectedValueException(sprintf('%s::validateField() rule `%s` missing.', get_class($this), $type));
			}

			// Run the field validation rule test.
			$valid = $rule->test($element, $value, $group, $input, $this);

			// Check for an error in the validation test.
			if ($valid instanceof \Exception)
			{
				return $valid;
			}
		}

		if ($valid !== false && $element['type'] == 'subform')
		{
			$field   = $this->loadField($element);
			$subForm = $field->loadSubForm();

			if ($field->multiple)
			{
				foreach ($value as $key => $val)
				{
					$val = (array) $val;

					$valid = $subForm->validate($val);

					if ($valid === false)
					{
						break;
					}
				}
			}
			else
			{
				$valid = $subForm->validate($value);
			}

			if ($valid === false)
			{
				$errors = $subForm->getErrors();

				foreach ($errors as $error)
				{
					return $error;
				}
			}
		}

		// Check if the field is valid.
		if ($valid === false)
		{
			// Does the field have a defined error message?
			$message = (string) $element['message'];

			if ($message)
			{
				$message = \JText::_($element['message']);

				return new \UnexpectedValueException($message);
			}
			else
			{
				$message = \JText::_($element['label']);
				$message = \JText::sprintf('JLIB_FORM_VALIDATE_FIELD_INVALID', $message);

				return new \UnexpectedValueException($message);
			}
		}

		return true;
	}

	/**
	 * Proxy for {@link FormHelper::addFieldPath()}.
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @since   1.7.0
	 */
	public static function addFieldPath($new = null)
	{
		return FormHelper::addFieldPath($new);
	}

	/**
	 * Proxy for FormHelper::addFormPath().
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FormHelper::addFormPath()
	 * @since   1.7.0
	 */
	public static function addFormPath($new = null)
	{
		return FormHelper::addFormPath($new);
	}

	/**
	 * Proxy for FormHelper::addRulePath().
	 *
	 * @param   mixed  $new  A path or array of paths to add.
	 *
	 * @return  array  The list of paths that have been added.
	 *
	 * @see     FormHelper::addRulePath()
	 * @since   1.7.0
	 */
	public static function addRulePath($new = null)
	{
		return FormHelper::addRulePath($new);
	}

	/**
	 * Method to get an instance of a form.
	 *
	 * @param   string          $name     The name of the form.
	 * @param   string          $data     The name of an XML file or string to load as the form definition.
	 * @param   array           $options  An array of form options.
	 * @param   boolean         $replace  Flag to toggle whether form fields should be replaced if a field
	 *                                    already exists with the same group/name.
	 * @param   string|boolean  $xpath    An optional xpath to search for the fields.
	 *
	 * @return  Form  JForm instance.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException if no data provided.
	 * @throws  \RuntimeException if the form could not be loaded.
	 */
	public static function getInstance($name, $data = null, $options = array(), $replace = true, $xpath = false)
	{
		// Reference to array with form instances
		$forms = &self::$forms;

		// Only instantiate the form if it does not already exist.
		if (!isset($forms[$name]))
		{
			$data = trim($data);

			if (empty($data))
			{
				throw new \InvalidArgumentException(sprintf('%1$s(%2$s, *%3$s*)', __METHOD__, $name, gettype($data)));
			}

			// Instantiate the form.
			$forms[$name] = new static($name, $options);

			// Load the data.
			if (substr($data, 0, 1) == '<')
			{
				if ($forms[$name]->load($data, $replace, $xpath) == false)
				{
					throw new \RuntimeException(sprintf('%s() could not load form', __METHOD__));
				}
			}
			else
			{
				if ($forms[$name]->loadFile($data, $replace, $xpath) == false)
				{
					throw new \RuntimeException(sprintf('%s() could not load file', __METHOD__));
				}
			}
		}

		return $forms[$name];
	}

	/**
	 * Adds a new child SimpleXMLElement node to the source.
	 *
	 * @param   \SimpleXMLElement  $source  The source element on which to append.
	 * @param   \SimpleXMLElement  $new     The new element to append.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected static function addNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
	{
		// Add the new child node.
		$node = $source->addChild($new->getName(), htmlspecialchars(trim($new)));

		// Add the attributes of the child node.
		foreach ($new->attributes() as $name => $value)
		{
			$node->addAttribute($name, $value);
		}

		// Add any children of the new node.
		foreach ($new->children() as $child)
		{
			self::addNode($node, $child);
		}
	}

	/**
	 * Update the attributes of a child node
	 *
	 * @param   \SimpleXMLElement  $source  The source element on which to append the attributes
	 * @param   \SimpleXMLElement  $new     The new element to append
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected static function mergeNode(\SimpleXMLElement $source, \SimpleXMLElement $new)
	{
		// Update the attributes of the child node.
		foreach ($new->attributes() as $name => $value)
		{
			if (isset($source[$name]))
			{
				$source[$name] = (string) $value;
			}
			else
			{
				$source->addAttribute($name, $value);
			}
		}
	}

	/**
	 * Merges new elements into a source `<fields>` element.
	 *
	 * @param   \SimpleXMLElement  $source  The source element.
	 * @param   \SimpleXMLElement  $new     The new element to merge.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected static function mergeNodes(\SimpleXMLElement $source, \SimpleXMLElement $new)
	{
		// The assumption is that the inputs are at the same relative level.
		// So we just have to scan the children and deal with them.

		// Update the attributes of the child node.
		foreach ($new->attributes() as $name => $value)
		{
			if (isset($source[$name]))
			{
				$source[$name] = (string) $value;
			}
			else
			{
				$source->addAttribute($name, $value);
			}
		}

		foreach ($new->children() as $child)
		{
			$type = $child->getName();
			$name = $child['name'];

			// Does this node exist?
			$fields = $source->xpath($type . '[@name="' . $name . '"]');

			if (empty($fields))
			{
				// This node does not exist, so add it.
				self::addNode($source, $child);
			}
			else
			{
				// This node does exist.
				switch ($type)
				{
					case 'field':
						self::mergeNode($fields[0], $child);
						break;

					default:
						self::mergeNodes($fields[0], $child);
						break;
				}
			}
		}
	}

	/**
	 * Returns the value of an attribute of the form itself
	 *
	 * @param   string  $name     Name of the attribute to get
	 * @param   mixed   $default  Optional value to return if attribute not found
	 *
	 * @return  mixed             Value of the attribute / default
	 *
	 * @since   3.2
	 */
	public function getAttribute($name, $default = null)
	{
		if ($this->xml instanceof \SimpleXMLElement)
		{
			$attributes = $this->xml->attributes();

			// Ensure that the attribute exists
			if (property_exists($attributes, $name))
			{
				$value = $attributes->$name;

				if ($value !== null)
				{
					return (string) $value;
				}
			}
		}

		return $default;
	}

	/**
	 * Getter for the form data
	 *
	 * @return   Registry  Object with the data
	 *
	 * @since    3.2
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Method to get the XML form object
	 *
	 * @return  \SimpleXMLElement  The form XML object
	 *
	 * @since   3.2
	 */
	public function getXml()
	{
		return $this->xml;
	}

	/**
	 * Method to get a form field represented as an XML element object.
	 *
	 * @param   string  $name   The name of the form field.
	 * @param   string  $group  The optional dot-separated form group path on which to find the field.
	 *
	 * @return  \SimpleXMLElement|boolean  The XML element object for the field or boolean false on error.
	 *
	 * @since   3.7.0
	 */
	public function getFieldXml($name, $group = null)
	{
		return $this->findField($name, $group);
	}
}
src/Form/FormField.php000064400000055741152177723700010645 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Form;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\String\Normalise;
use Joomla\String\StringHelper;

/**
 * Abstract Form Field class for the Joomla Platform.
 *
 * @since  1.7.0
 */
abstract class FormField
{
	/**
	 * The description text for the form field. Usually used in tooltips.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $description;

	/**
	 * The hint text for the form field used to display hint inside the field.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $hint;

	/**
	 * The autocomplete state for the form field.  If 'off' element will not be automatically
	 * completed by browser.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $autocomplete = 'on';

	/**
	 * The spellcheck state for the form field.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $spellcheck = true;

	/**
	 * The autofocus request for the form field.  If true element will be automatically
	 * focused on document load.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $autofocus = false;

	/**
	 * The SimpleXMLElement object of the `<field>` XML element that describes the form field.
	 *
	 * @var    \SimpleXMLElement
	 * @since  1.7.0
	 */
	protected $element;

	/**
	 * The Form object of the form attached to the form field.
	 *
	 * @var    Form
	 * @since  1.7.0
	 */
	protected $form;

	/**
	 * The form control prefix for field names from the JForm object attached to the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $formControl;

	/**
	 * The hidden state for the form field.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $hidden = false;

	/**
	 * True to translate the field label string.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $translateLabel = true;

	/**
	 * True to translate the field description string.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $translateDescription = true;

	/**
	 * True to translate the field hint string.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $translateHint = true;

	/**
	 * The document id for the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $id;

	/**
	 * The input for the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $input;

	/**
	 * The label for the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $label;

	/**
	 * The multiple state for the form field.  If true then multiple values are allowed for the
	 * field.  Most often used for list field types.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $multiple = false;

	/**
	 * Allows extensions to create repeat elements
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	public $repeat = false;

	/**
	 * The pattern (Reg Ex) of value of the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $pattern;

	/**
	 * The validation text of invalid value of the form field.
	 *
	 * @var    string
	 * @since  4.0
	 */
	protected $validationtext;

	/**
	 * The name of the form field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $name;

	/**
	 * The name of the field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $fieldname;

	/**
	 * The group of the field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $group;

	/**
	 * The required state for the form field.  If true then there must be a value for the field to
	 * be considered valid.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $required = false;

	/**
	 * The disabled state for the form field.  If true then the field will be disabled and user can't
	 * interact with the field.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $disabled = false;

	/**
	 * The readonly state for the form field.  If true then the field will be readonly.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $readonly = false;

	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type;

	/**
	 * The validation method for the form field.  This value will determine which method is used
	 * to validate the value for a field.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $validate;

	/**
	 * The value of the form field.
	 *
	 * @var    mixed
	 * @since  1.7.0
	 */
	protected $value;

	/**
	 * The default value of the form field.
	 *
	 * @var    mixed
	 * @since  1.7.0
	 */
	protected $default;

	/**
	 * The size of the form field.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $size;

	/**
	 * The class of the form field
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $class;

	/**
	 * The label's CSS class of the form field
	 *
	 * @var    mixed
	 * @since  1.7.0
	 */
	protected $labelclass;

	/**
	 * The javascript onchange of the form field.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $onchange;

	/**
	 * The javascript onclick of the form field.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $onclick;

	/**
	 * The conditions to show/hide the field.
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	protected $showon;

	/**
	 * The count value for generated name field
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected static $count = 0;

	/**
	 * The string used for generated fields names
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected static $generated_fieldname = '__field';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $layout;

	/**
	 * Layout to render the form field
	 *
	 * @var  string
	 */
	protected $renderLayout = 'joomla.form.renderfield';

	/**
	 * Layout to render the label
	 *
	 * @var  string
	 */
	protected $renderLabelLayout = 'joomla.form.renderlabel';

	/**
	 * Method to instantiate the form field object.
	 *
	 * @param   Form  $form  The form to attach to the form field object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($form = null)
	{
		// If there is a form passed into the constructor set the form and form control properties.
		if ($form instanceof Form)
		{
			$this->form = $form;
			$this->formControl = $form->getFormControl();
		}

		// Detect the field type if not set
		if (!isset($this->type))
		{
			$parts = Normalise::fromCamelCase(get_called_class(), true);

			if ($parts[0] == 'J')
			{
				$this->type = StringHelper::ucfirst($parts[count($parts) - 1], '_');
			}
			else
			{
				$this->type = StringHelper::ucfirst($parts[0], '_') . StringHelper::ucfirst($parts[count($parts) - 1], '_');
			}
		}
	}

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   1.7.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'description':
			case 'hint':
			case 'formControl':
			case 'hidden':
			case 'id':
			case 'multiple':
			case 'name':
			case 'required':
			case 'type':
			case 'validate':
			case 'value':
			case 'class':
			case 'layout':
			case 'labelclass':
			case 'size':
			case 'onchange':
			case 'onclick':
			case 'fieldname':
			case 'group':
			case 'disabled':
			case 'readonly':
			case 'autofocus':
			case 'autocomplete':
			case 'spellcheck':
			case 'validationtext':
			case 'showon':
				return $this->$name;

			case 'input':
				// If the input hasn't yet been generated, generate it.
				if (empty($this->input))
				{
					$this->input = $this->getInput();
				}

				return $this->input;

			case 'label':
				// If the label hasn't yet been generated, generate it.
				if (empty($this->label))
				{
					$this->label = $this->getLabel();
				}

				return $this->label;

			case 'title':
				return $this->getTitle();
		}

		return;
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'class':
				// Removes spaces from left & right and extra spaces from middle
				$value = preg_replace('/\s+/', ' ', trim((string) $value));

			case 'description':
			case 'hint':
			case 'value':
			case 'labelclass':
			case 'layout':
			case 'onchange':
			case 'onclick':
			case 'validate':
			case 'pattern':
			case 'validationtext':
			case 'group':
			case 'showon':
			case 'default':
				$this->$name = (string) $value;
				break;

			case 'id':
				$this->id = $this->getId((string) $value, $this->fieldname);
				break;

			case 'fieldname':
				$this->fieldname = $this->getFieldName((string) $value);
				break;

			case 'name':
				$this->fieldname = $this->getFieldName((string) $value);
				$this->name = $this->getName($this->fieldname);
				break;

			case 'multiple':
				// Allow for field classes to force the multiple values option.
				$value = (string) $value;
				$value = $value === '' && isset($this->forceMultiple) ? (string) $this->forceMultiple : $value;

			case 'required':
			case 'disabled':
			case 'readonly':
			case 'autofocus':
			case 'hidden':
				$value = (string) $value;
				$this->$name = ($value === 'true' || $value === $name || $value === '1');
				break;

			case 'autocomplete':
				$value = (string) $value;
				$value = ($value == 'on' || $value == '') ? 'on' : $value;
				$this->$name = ($value === 'false' || $value === 'off' || $value === '0') ? false : $value;
				break;

			case 'spellcheck':
			case 'translateLabel':
			case 'translateDescription':
			case 'translateHint':
				$value = (string) $value;
				$this->$name = !($value === 'false' || $value === 'off' || $value === '0');
				break;

			case 'translate_label':
				$value = (string) $value;
				$this->translateLabel = $this->translateLabel && !($value === 'false' || $value === 'off' || $value === '0');
				break;

			case 'translate_description':
				$value = (string) $value;
				$this->translateDescription = $this->translateDescription && !($value === 'false' || $value === 'off' || $value === '0');
				break;

			case 'size':
				$this->$name = (int) $value;
				break;

			default:
				if (property_exists(__CLASS__, $name))
				{
					\JLog::add("Cannot access protected / private property $name of " . __CLASS__);
				}
				else
				{
					$this->$name = $value;
				}
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   Form  $form  The JForm object to attach to the form field.
	 *
	 * @return  FormField  The form field object so that the method can be used in a chain.
	 *
	 * @since   1.7.0
	 */
	public function setForm(Form $form)
	{
		$this->form = $form;
		$this->formControl = $form->getFormControl();

		return $this;
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   \SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed              $value    The form field value to validate.
	 * @param   string             $group    The field name group control value. This acts as as an array container for the field.
	 *                                       For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                       full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setup(\SimpleXMLElement $element, $value, $group = null)
	{
		// Make sure there is a valid JFormField XML element.
		if ((string) $element->getName() != 'field')
		{
			return false;
		}

		// Reset the input and label values.
		$this->input = null;
		$this->label = null;

		// Set the XML element object.
		$this->element = $element;

		// Set the group of the field.
		$this->group = $group;

		$attributes = array(
			'multiple', 'name', 'id', 'hint', 'class', 'description', 'labelclass', 'onchange', 'onclick', 'validate', 'pattern', 'validationtext', 'default',
			'required', 'disabled', 'readonly', 'autofocus', 'hidden', 'autocomplete', 'spellcheck', 'translateHint', 'translateLabel',
			'translate_label', 'translateDescription', 'translate_description', 'size', 'showon');

		$this->default = isset($element['value']) ? (string) $element['value'] : $this->default;

		// Set the field default value.
		if ($element['multiple'] && is_string($value) && is_array(json_decode($value, true)))
		{
			$this->value = (array) json_decode($value);
		}
		else
		{
			$this->value = $value;
		}

		foreach ($attributes as $attributeName)
		{
			$this->__set($attributeName, $element[$attributeName]);
		}

		// Allow for repeatable elements
		$repeat = (string) $element['repeat'];
		$this->repeat = ($repeat == 'true' || $repeat == 'multiple' || (!empty($this->form->repeat) && $this->form->repeat == 1));

		// Set the visibility.
		$this->hidden = ($this->hidden || (string) $element['type'] == 'hidden');

		$this->layout = !empty($this->element['layout']) ? (string) $this->element['layout'] : $this->layout;

		// Add required to class list if field is required.
		if ($this->required)
		{
			$this->class = trim($this->class . ' required');
		}

		return true;
	}

	/**
	 * Simple method to set the value
	 *
	 * @param   mixed  $value  Value to set
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function setValue($value)
	{
		$this->value = $value;
	}

	/**
	 * Method to get the id used for the field input tag.
	 *
	 * @param   string  $fieldId    The field element id.
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The id to be used for the field input tag.
	 *
	 * @since   1.7.0
	 */
	protected function getId($fieldId, $fieldName)
	{
		$id = '';

		// If there is a form control set for the attached form add it first.
		if ($this->formControl)
		{
			$id .= $this->formControl;
		}

		// If the field is in a group add the group control to the field id.
		if ($this->group)
		{
			// If we already have an id segment add the group control as another level.
			if ($id)
			{
				$id .= '_' . str_replace('.', '_', $this->group);
			}
			else
			{
				$id .= str_replace('.', '_', $this->group);
			}
		}

		// If we already have an id segment add the field id/name as another level.
		if ($id)
		{
			$id .= '_' . ($fieldId ? $fieldId : $fieldName);
		}
		else
		{
			$id .= ($fieldId ? $fieldId : $fieldName);
		}

		// Clean up any invalid characters.
		$id = preg_replace('#\W#', '_', $id);

		// If this is a repeatable element, add the repeat count to the ID
		if ($this->repeat)
		{
			$repeatCounter = empty($this->form->repeatCounter) ? 0 : $this->form->repeatCounter;
			$id .= '-' . $repeatCounter;

			if (strtolower($this->type) == 'radio')
			{
				$id .= '-';
			}
		}

		return $id;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new \UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the field title.
	 *
	 * @return  string  The field title.
	 *
	 * @since   1.7.0
	 */
	protected function getTitle()
	{
		$title = '';

		if ($this->hidden)
		{
			return $title;
		}

		// Get the label text from the XML element, defaulting to the element name.
		$title = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
		$title = $this->translateLabel ? \JText::_($title) : $title;

		return $title;
	}

	/**
	 * Method to get the field label markup.
	 *
	 * @return  string  The field label markup.
	 *
	 * @since   1.7.0
	 */
	protected function getLabel()
	{
		if ($this->hidden)
		{
			return '';
		}

		$data = $this->getLayoutData();

		// Forcing the Alias field to display the tip below
		$position = $this->element['name'] == 'alias' ? ' data-placement="bottom" ' : '';

		// Here mainly for B/C with old layouts. This can be done in the layouts directly
		$extraData = array(
			'text'        => $data['label'],
			'for'         => $this->id,
			'classes'     => explode(' ', $data['labelclass']),
			'position'    => $position,
		);

		return $this->getRenderer($this->renderLabelLayout)->render(array_merge($data, $extraData));
	}

	/**
	 * Method to get the name used for the field input tag.
	 *
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The name to be used for the field input tag.
	 *
	 * @since   1.7.0
	 */
	protected function getName($fieldName)
	{
		// To support repeated element, extensions can set this in plugin->onRenderSettings

		$name = '';

		// If there is a form control set for the attached form add it first.
		if ($this->formControl)
		{
			$name .= $this->formControl;
		}

		// If the field is in a group add the group control to the field name.
		if ($this->group)
		{
			// If we already have a name segment add the group control as another level.
			$groups = explode('.', $this->group);

			if ($name)
			{
				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
			else
			{
				$name .= array_shift($groups);

				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
		}

		// If we already have a name segment add the field name as another level.
		if ($name)
		{
			$name .= '[' . $fieldName . ']';
		}
		else
		{
			$name .= $fieldName;
		}

		// If the field should support multiple values add the final array segment.
		if ($this->multiple)
		{
			switch (strtolower((string) $this->element['type']))
			{
				case 'text':
				case 'textarea':
				case 'email':
				case 'password':
				case 'radio':
				case 'calendar':
				case 'editor':
				case 'hidden':
					break;
				default:
					$name .= '[]';
			}
		}

		return $name;
	}

	/**
	 * Method to get the field name used.
	 *
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The field name
	 *
	 * @since   1.7.0
	 */
	protected function getFieldName($fieldName)
	{
		if ($fieldName)
		{
			return $fieldName;
		}
		else
		{
			self::$count = self::$count + 1;

			return self::$generated_fieldname . self::$count;
		}
	}

	/**
	 * Method to get an attribute of the field
	 *
	 * @param   string  $name     Name of the attribute to get
	 * @param   mixed   $default  Optional value to return if attribute not found
	 *
	 * @return  mixed             Value of the attribute / default
	 *
	 * @since   3.2
	 */
	public function getAttribute($name, $default = null)
	{
		if ($this->element instanceof \SimpleXMLElement)
		{
			$attributes = $this->element->attributes();

			// Ensure that the attribute exists
			if ($attributes->$name !== null)
			{
				return (string) $attributes->$name;
			}
		}

		return $default;
	}

	/**
	 * Method to get a control group with label and input.
	 *
	 * @return  string  A string containing the html for the control group
	 *
	 * @since      3.2
	 * @deprecated 3.2.3 Use renderField() instead
	 */
	public function getControlGroup()
	{
		\JLog::add('FormField->getControlGroup() is deprecated use FormField->renderField().', \JLog::WARNING, 'deprecated');

		return $this->renderField();
	}

	/**
	 * Render a layout of this field
	 *
	 * @param   string  $layoutId  Layout identifier
	 * @param   array   $data      Optional data for the layout
	 *
	 * @return  string
	 *
	 * @since   3.5
	 */
	public function render($layoutId, $data = array())
	{
		$data = array_merge($this->getLayoutData(), $data);

		return $this->getRenderer($layoutId)->render($data);
	}

	/**
	 * Method to get a control group with label and input.
	 *
	 * @param   array  $options  Options to be passed into the rendering of the field
	 *
	 * @return  string  A string containing the html for the control group
	 *
	 * @since   3.2
	 */
	public function renderField($options = array())
	{
		if ($this->hidden)
		{
			return $this->getInput();
		}

		if (!isset($options['class']))
		{
			$options['class'] = '';
		}

		$options['rel'] = '';

		if (empty($options['hiddenLabel']) && $this->getAttribute('hiddenLabel'))
		{
			$options['hiddenLabel'] = true;
		}

		if ($this->showon)
		{
			$options['rel']           = ' data-showon=\'' .
				json_encode(FormHelper::parseShowOnConditions($this->showon, $this->formControl, $this->group)) . '\'';
			$options['showonEnabled'] = true;
		}

		$data = array(
			'input'   => $this->getInput(),
			'label'   => $this->getLabel(),
			'options' => $options,
		);

		return $this->getRenderer($this->renderLayout)->render($data);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.5
	 */
	protected function getLayoutData()
	{
		// Label preprocess
		$label = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
		$label = $this->translateLabel ? \JText::_($label) : $label;

		// Description preprocess
		$description = !empty($this->description) ? $this->description : null;
		$description = !empty($description) && $this->translateDescription ? \JText::_($description) : $description;

		$alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);

		return array(
			'autocomplete'   => $this->autocomplete,
			'autofocus'      => $this->autofocus,
			'class'          => $this->class,
			'description'    => $description,
			'disabled'       => $this->disabled,
			'field'          => $this,
			'group'          => $this->group,
			'hidden'         => $this->hidden,
			'hint'           => $this->translateHint ? \JText::alt($this->hint, $alt) : $this->hint,
			'id'             => $this->id,
			'label'          => $label,
			'labelclass'     => $this->labelclass,
			'multiple'       => $this->multiple,
			'name'           => $this->name,
			'onchange'       => $this->onchange,
			'onclick'        => $this->onclick,
			'pattern'        => $this->pattern,
			'validationtext' => $this->validationtext,
			'readonly'       => $this->readonly,
			'repeat'         => $this->repeat,
			'required'       => (bool) $this->required,
			'size'           => $this->size,
			'spellcheck'     => $this->spellcheck,
			'validate'       => $this->validate,
			'value'          => $this->value,
		);
	}

	/**
	 * Allow to override renderer include paths in child fields
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	protected function getLayoutPaths()
	{
		$renderer = new FileLayout('default');

		return $renderer->getDefaultIncludePaths();
	}

	/**
	 * Get the renderer
	 *
	 * @param   string  $layoutId  Id to load
	 *
	 * @return  FileLayout
	 *
	 * @since   3.5
	 */
	protected function getRenderer($layoutId = 'default')
	{
		$renderer = new FileLayout($layoutId);

		$renderer->setDebug($this->isDebugEnabled());

		$layoutPaths = $this->getLayoutPaths();

		if ($layoutPaths)
		{
			$renderer->setIncludePaths($layoutPaths);
		}

		return $renderer;
	}

	/**
	 * Is debug enabled for this field
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	protected function isDebugEnabled()
	{
		return $this->getAttribute('debug', 'false') === 'true';
	}
}
src/Http/Response.php000064400000001175152177723700010600 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http;

defined('JPATH_PLATFORM') or die;

/**
 * HTTP response data object class.
 *
 * @since  1.7.3
 */
class Response
{
	/**
	 * @var    integer  The server response code.
	 * @since  1.7.3
	 */
	public $code;

	/**
	 * @var    array  Response headers.
	 * @since  1.7.3
	 */
	public $headers = array();

	/**
	 * @var    string  Server response body.
	 * @since  1.7.3
	 */
	public $body;
}
src/Http/HttpFactory.php000064400000005616152177723700011255 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;

/**
 * HTTP factory class.
 *
 * @since  3.0.0
 */
class HttpFactory
{
	/**
	 * method to receive Http instance.
	 *
	 * @param   Registry  $options   Client options object.
	 * @param   mixed     $adapters  Adapter (string) or queue of adapters (array) to use for communication.
	 *
	 * @return  Http      Joomla Http class
	 *
	 * @throws  \RuntimeException
	 *
	 * @since   3.0.0
	 */
	public static function getHttp(Registry $options = null, $adapters = null)
	{
		if (empty($options))
		{
			$options = new Registry;
		}

		if (!$driver = self::getAvailableDriver($options, $adapters))
		{
			throw new \RuntimeException('No transport driver available.');
		}

		return new Http($options, $driver);
	}

	/**
	 * Finds an available http transport object for communication
	 *
	 * @param   Registry  $options  Option for creating http transport object
	 * @param   mixed     $default  Adapter (string) or queue of adapters (array) to use
	 *
	 * @return  TransportInterface Interface sub-class
	 *
	 * @since   3.0.0
	 */
	public static function getAvailableDriver(Registry $options, $default = null)
	{
		if (is_null($default))
		{
			$availableAdapters = self::getHttpTransports();
		}
		else
		{
			settype($default, 'array');
			$availableAdapters = $default;
		}

		// Check if there is at least one available http transport adapter
		if (!count($availableAdapters))
		{
			return false;
		}

		foreach ($availableAdapters as $adapter)
		{
			$class = __NAMESPACE__ . '\\Transport\\' . ucfirst($adapter) . 'Transport';

			if (!class_exists($class))
			{
				$class = 'JHttpTransport' . ucfirst($adapter);
			}

			if (class_exists($class) && $class::isSupported())
			{
				return new $class($options);
			}
		}

		return false;
	}

	/**
	 * Get the http transport handlers
	 *
	 * @return  array  An array of available transport handlers
	 *
	 * @since   3.0.0
	 */
	public static function getHttpTransports()
	{
		$names = array();
		$iterator = new \DirectoryIterator(__DIR__ . '/Transport');

		/** @type  $file  \DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if ($file->isFile() && $file->getExtension() == 'php')
			{
				$names[] = substr($fileName, 0, strrpos($fileName, 'Transport.'));
			}
		}

		// Keep alphabetical order across all environments
		sort($names);

		// If curl is available set it to the first position
		if ($key = array_search('Curl', $names))
		{
			unset($names[$key]);
			array_unshift($names, 'Curl');
		}

		return $names;
	}
}
src/Http/Wrapper/FactoryWrapper.php000064400000003417152177723700013373 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http\Wrapper;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Http\TransportInterface;

/**
 * Wrapper class for HttpFactory
 *
 * @package     Joomla.Platform
 * @subpackage  Http
 * @since       3.4
 */
class FactoryWrapper
{
	/**
	 * Helper wrapper method for getHttp
	 *
	 * @param   Registry  $options   Client options object.
	 * @param   mixed     $adapters  Adapter (string) or queue of adapters (array) to use for communication.
	 *
	 * @return  Http      Joomla Http class
	 *
	 * @see     HttpFactory::getHttp()
	 * @since   3.4
	 * @throws  \RuntimeException
	 */
	public function getHttp(Registry $options = null, $adapters = null)
	{
		return HttpFactory::getHttp($options, $adapters);
	}

	/**
	 * Helper wrapper method for getAvailableDriver
	 *
	 * @param   Registry  $options  Option for creating http transport object.
	 * @param   mixed     $default  Adapter (string) or queue of adapters (array) to use.
	 *
	 * @return  TransportInterface  Interface sub-class
	 *
	 * @see     HttpFactory::getAvailableDriver()
	 * @since   3.4
	 */
	public function getAvailableDriver(Registry $options, $default = null)
	{
		return HttpFactory::getAvailableDriver($options, $default);
	}

	/**
	 * Helper wrapper method for getHttpTransports
	 *
	 * @return array  An array of available transport handlers
	 *
	 * @see     HttpFactory::getHttpTransports()
	 * @since   3.4
	 */
	public function getHttpTransports()
	{
		return HttpFactory::getHttpTransports();
	}
}
src/Http/Transport/cacert.pem000064400001000627152177723700012233 0ustar00##
## Bundle of CA Root Certificates
##
## Certificate data from CentOS 7 as of Mar 3 2017
##
## This is a bundle of X.509 certificates of public Certificate Authorities
## (CA). These were automatically extracted from CentOS /etc/pki/tls/certs/ca-bundle.crt
##
## It contains the certificates in PEM format and therefore
## can be directly used with curl / libcurl / php_curl, or with
## an Apache+mod_ssl webserver for SSL client authentication.
## Just configure this file as the SSLCACertificateFile.
##

-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
NitjrFgBazMpUIaD8QFI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
Fp1hBWeAyNDYpQcCNJgEjTME1A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDNjCCAp+gAwIBAgIQNhIilsXjOKUgodJfTNcJVDANBgkqhkiG9w0BAQUFADCB
zjELMAkGA1UEBhMCWkExFTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJ
Q2FwZSBUb3duMR0wGwYDVQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UE
CxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhh
d3RlIFByZW1pdW0gU2VydmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNl
cnZlckB0aGF3dGUuY29tMB4XDTk2MDgwMTAwMDAwMFoXDTIxMDEwMTIzNTk1OVow
gc4xCzAJBgNVBAYTAlpBMRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcT
CUNhcGUgVG93bjEdMBsGA1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNV
BAsTH0NlcnRpZmljYXRpb24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRo
YXd0ZSBQcmVtaXVtIFNlcnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1z
ZXJ2ZXJAdGhhd3RlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2
aovXwlue2oFBYo847kkEVdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560
ZXUCTe/LCaIhUdib0GfQug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j
+ao6hnO2RlNYyIkFvYMRuHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/
BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQBlkKyID1bZ5jA01CbH0FDxkt5r1DmI
CSLGpmODA/eZd9iy5Ri4XWPz1HP7bJyZePFLeH0ZJMMrAoT4vCLZiiLXoPxx7JGH
IPG47LHlVYCsPVLIOQ7C8MAFT9aCdYy9X9LcdpoFEsmvcsPcJX6kTY4XpeCHf+Ga
WuFg3GQjPEIuTQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
oJ2daZH9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE
AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw
CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ
BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND
VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb
qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY
HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo
G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA
lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr
IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/
0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH
k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47
4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO
m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa
cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl
uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI
KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls
ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG
AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2
VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT
VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG
CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA
cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA
QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA
7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA
cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA
QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA
czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu
aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt
aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud
DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF
BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp
D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU
JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m
AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD
vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms
tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH
7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h
I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA
h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF
d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H
pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFtTCCA52gAwIBAgIIYY3HhjsBggUwDQYJKoZIhvcNAQEFBQAwRDEWMBQGA1UE
AwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZFRElDT00x
CzAJBgNVBAYTAkVTMB4XDTA4MDQxODE2MjQyMloXDTI4MDQxMzE2MjQyMlowRDEW
MBQGA1UEAwwNQUNFRElDT00gUm9vdDEMMAoGA1UECwwDUEtJMQ8wDQYDVQQKDAZF
RElDT00xCzAJBgNVBAYTAkVTMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
AgEA/5KV4WgGdrQsyFhIyv2AVClVYyT/kGWbEHV7w2rbYgIB8hiGtXxaOLHkWLn7
09gtn70yN78sFW2+tfQh0hOR2QetAQXW8713zl9CgQr5auODAKgrLlUTY4HKRxx7
XBZXehuDYAQ6PmXDzQHe3qTWDLqO3tkE7hdWIpuPY/1NFgu3e3eM+SW10W2ZEi5P
Grjm6gSSrj0RuVFCPYewMYWveVqc/udOXpJPQ/yrOq2lEiZmueIM15jO1FillUAK
t0SdE3QrwqXrIhWYENiLxQSfHY9g5QYbm8+5eaA9oiM/Qj9r+hwDezCNzmzAv+Yb
X79nuIQZ1RXve8uQNjFiybwCq0Zfm/4aaJQ0PZCOrfbkHQl/Sog4P75n/TSW9R28
MHTLOO7VbKvU/PQAtwBbhTIWdjPp2KOZnQUAqhbm84F9b32qhm2tFXTTxKJxqvQU
fecyuB+81fFOvW8XAjnXDpVCOscAPukmYxHqC9FK/xidstd7LzrZlvvoHpKuE1XI
2Sf23EgbsCTBheN3nZqk8wwRHQ3ItBTutYJXCb8gWH8vIiPYcMt5bMlL8qkqyPyH
K9caUPgn6C9D4zq92Fdx/c6mUlv53U3t5fZvie27k5x2IXXwkkwp9y+cAS7+UEae
ZAwUswdbxcJzbPEHXEUkFDWug/FqTYl6+rPYLWbwNof1K1MCAwEAAaOBqjCBpzAP
BgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKaz4SsrSbbXc6GqlPUB53NlTKxQ
MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUprPhKytJttdzoaqU9QHnc2VMrFAw
RAYDVR0gBD0wOzA5BgRVHSAAMDEwLwYIKwYBBQUHAgEWI2h0dHA6Ly9hY2VkaWNv
bS5lZGljb21ncm91cC5jb20vZG9jMA0GCSqGSIb3DQEBBQUAA4ICAQDOLAtSUWIm
fQwng4/F9tqgaHtPkl7qpHMyEVNEskTLnewPeUKzEKbHDZ3Ltvo/Onzqv4hTGzz3
gvoFNTPhNahXwOf9jU8/kzJPeGYDdwdY6ZXIfj7QeQCM8htRM5u8lOk6e25SLTKe
I6RF+7YuE7CLGLHdztUdp0J/Vb77W7tH1PwkzQSulgUV1qzOMPPKC8W64iLgpq0i
5ALudBF/TP94HTXa5gI06xgSYXcGCRZj6hitoocf8seACQl1ThCojz2GuHURwCRi
ipZ7SkXp7FnFvmuD5uHorLUwHv4FB4D54SMNUI8FmP8sX+g7tq3PgbUhh8oIKiMn
MCArz+2UW6yyetLHKKGKC5tNSixthT8Jcjxn4tncB7rrZXtaAWPWkFtPF2Y9fwsZ
o5NjEFIqnxQWWOLcpfShFosOkYuByptZ+thrkQdlVV9SH686+5DdaaVbnG0OLLb6
zqylfDJKZ0DcMDQj3dcEI2bw/FWAp/tmGYI1Z2JwOV5vx+qQQEQIHriy1tvuWacN
GHk0vFQYXlPKNFHtRQrmjseCNj6nOGOpMCwXEGCSn1WHElkQwg9naRHMTh5+Spqt
r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK
Z05phkOTOPu220+DkdRgfks+KzgHVZhepA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE
BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w
MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290
IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC
SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1
ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv
UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX
4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9
KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/
gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb
rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ
51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F
be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe
KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F
v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn
fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7
jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz
ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt
ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL
e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70
jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz
WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V
SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j
pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX
X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok
fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R
K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU
ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU
LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT
LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw
MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML
QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD
VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA
A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul
CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n
tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl
dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch
PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC
+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O
BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl
MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk
ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB
IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X
7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz
43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY
eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl
pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA
WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx
MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB
ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV
BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV
6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX
GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP
dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH
1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF
62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW
BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw
AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL
MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU
cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv
b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6
IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/
iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao
GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh
4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm
XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3
b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1
MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK
EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh
BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq
xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G
87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i
2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U
WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1
0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G
A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr
pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL
ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm
aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv
hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm
hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X
dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3
P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y
iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no
xqE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP
Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr
ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL
MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1
yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr
VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/
nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG
XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj
vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt
Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g
N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC
nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz
dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL
MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp
cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y
YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua
kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL
QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp
6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG
yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i
QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ
KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO
tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu
QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ
Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u
olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48
x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE
BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz
dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG
A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U
cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf
qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ
JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ
+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS
s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5
HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7
70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG
V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S
qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S
5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia
C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX
OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE
FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2
KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg
Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B
8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ
MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc
0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ
u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF
u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH
YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8
GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO
RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e
KeC2uAloGRwYQw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC
VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ
cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ
BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt
VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D
0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9
ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G
A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs
aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I
flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDoDCCAoigAwIBAgIBMTANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJKUDEc
MBoGA1UEChMTSmFwYW5lc2UgR292ZXJubWVudDEWMBQGA1UECxMNQXBwbGljYXRp
b25DQTAeFw0wNzEyMTIxNTAwMDBaFw0xNzEyMTIxNTAwMDBaMEMxCzAJBgNVBAYT
AkpQMRwwGgYDVQQKExNKYXBhbmVzZSBHb3Zlcm5tZW50MRYwFAYDVQQLEw1BcHBs
aWNhdGlvbkNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp23gdE6H
j6UG3mii24aZS2QNcfAKBZuOquHMLtJqO8F6tJdhjYq+xpqcBrSGUeQ3DnR4fl+K
f5Sk10cI/VBaVuRorChzoHvpfxiSQE8tnfWuREhzNgaeZCw7NCPbXCbkcXmP1G55
IrmTwcrNwVbtiGrXoDkhBFcsovW8R0FPXjQilbUfKW1eSvNNcr5BViCH/OlQR9cw
FO5cjFW6WY2H/CPek9AEjP3vbb3QesmlOmpyM8ZKDQUXKi17safY1vC+9D/qDiht
QWEjdnjDuGWk81quzMKq2edY3rZ+nYVunyoKb58DKTCXKB28t89UKU5RMfkntigm
/qJj5kEW8DOYRwIDAQABo4GeMIGbMB0GA1UdDgQWBBRUWssmP3HMlEYNllPqa0jQ
k/5CdTAOBgNVHQ8BAf8EBAMCAQYwWQYDVR0RBFIwUKROMEwxCzAJBgNVBAYTAkpQ
MRgwFgYDVQQKDA/ml6XmnKzlm73mlL/lupwxIzAhBgNVBAsMGuOCouODl+ODquOC
seODvOOCt+ODp+ODs0NBMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADlqRHZ3ODrso2dGD/mLBqj7apAxzn7s2tGJfHrrLgy9mTLnsCTWw//1sogJ
hyzjVOGjprIIC8CFqMjSnHH2HZ9g/DgzE+Ge3Atf2hZQKXsvcJEPmbo0NI2VdMV+
eKlmXb3KIXdCEKxmJj3ekav9FfBv7WxfEPjzFvYDio+nEhEMy/0/ecGc/WLuo89U
DNErXxc+4z6/wCs+CZv+iKZ+tJIX/COUgb1up8WMwusRRdv4QcmWdupwX3kSa+Sj
B1oF7ydJzyGfikwJcGapJsErEU4z0g781mzSDjJkaP+tBXhfAx2o45CsJOAPQKdL
rosot4LKGAfmt1t06SAZf7IbiVQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE
AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG
EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM
FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC
REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp
Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM
VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+
SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ
4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L
cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi
eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV
HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG
A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3
DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j
vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP
DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc
maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D
lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv
KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE
BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h
cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy
MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg
Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9
thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM
cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG
L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i
NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h
X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b
m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy
Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja
EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T
KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF
6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh
OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD
VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD
VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp
cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv
ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl
AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF
661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9
am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1
ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481
PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS
3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k
SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF
3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM
ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g
StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz
Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB
jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ
RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD
VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX
DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y
ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy
VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr
mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr
IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK
mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu
XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy
dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye
jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1
BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3
DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92
9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx
jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0
Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz
ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS
R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDUzCCAjugAwIBAgIBATANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg
Q2xhc3MgMiBDQSAxMB4XDTA2MTAxMzEwMjUwOVoXDTE2MTAxMzEwMjUwOVowSzEL
MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD
VQQDDBRCdXlwYXNzIENsYXNzIDIgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAIs8B0XY9t/mx8q6jUPFR42wWsE425KEHK8T1A9vNkYgxC7McXA0
ojTTNy7Y3Tp3L8DrKehc0rWpkTSHIln+zNvnma+WwajHQN2lFYxuyHyXA8vmIPLX
l18xoS830r7uvqmtqEyeIWZDO6i88wmjONVZJMHCR3axiFyCO7srpgTXjAePzdVB
HfCuuCkslFJgNJQ72uA40Z0zPhX0kzLFANq1KWYOOngPIVJfAuWSeyXTkh4vFZ2B
5J2O6O+JzhRMVB0cgRJNcKi+EAUXfh/RuFdV7c27UsKwHnjCTTZoy1YmwVLBvXb3
WNVyfh9EdrsAiR0WnVE1703CVu9r4Iw7DekCAwEAAaNCMEAwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUP42aWYv8e3uco684sDntkHGA1sgwDgYDVR0PAQH/BAQD
AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAVGn4TirnoB6NLJzKyQJHyIdFkhb5jatLP
gcIV1Xp+DCmsNx4cfHZSldq1fyOhKXdlyTKdqC5Wq2B2zha0jX94wNWZUYN/Xtm+
DKhQ7SLHrQVMdvvt7h5HZPb3J31cKA9FxVxiXqaakZG3Uxcu3K1gnZZkOb1naLKu
BctN518fV4bVIJwo+28TOPX2EZL2fZleHwzoq0QkKXJAPTZSr4xYkHPB7GEseaHs
h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk
LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr
6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV
L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91
1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx
MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ
QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB
arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr
Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi
FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS
P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN
9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz
uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h
9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s
A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t
OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo
+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7
KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2
DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us
H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ
I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7
5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h
3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz
Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd
MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg
Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow
TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw
HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB
BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y
ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E
N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9
tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX
0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c
/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X
KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY
zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS
O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D
34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP
K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3
AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv
Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj
QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV
cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS
IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2
HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa
O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv
033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u
dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE
kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41
3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD
u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq
4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBATANBgkqhkiG9w0BAQUFADBKMQswCQYDVQQGEwJTSzET
MBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcgYS5zLjERMA8GA1UE
AxMIQ0EgRGlzaWcwHhcNMDYwMzIyMDEzOTM0WhcNMTYwMzIyMDEzOTM0WjBKMQsw
CQYDVQQGEwJTSzETMBEGA1UEBxMKQnJhdGlzbGF2YTETMBEGA1UEChMKRGlzaWcg
YS5zLjERMA8GA1UEAxMIQ0EgRGlzaWcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQCS9jHBfYj9mQGp2HvycXXxMcbzdWb6UShGhJd4NLxs/LxFWYgmGErE
Nx+hSkS943EE9UQX4j/8SFhvXJ56CbpRNyIjZkMhsDxkovhqFQ4/61HhVKndBpnX
mjxUizkDPw/Fzsbrg3ICqB9x8y34dQjbYkzo+s7552oftms1grrijxaSfQUMbEYD
XcDtab86wYqg6I7ZuUUohwjstMoVvoLdtUSLLa2GDGhibYVW8qwUYzrG0ZmsNHhW
S8+2rT+MitcE5eN4TPWGqvWP+j1scaMtymfraHtuM6kMgiioTGohQBUgDCZbg8Kp
FhXAJIJdKxatymP2dACw30PEEGBWZ2NFAgMBAAGjgf8wgfwwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUjbJJaJ1yCCW5wCf1UJNWSEZx+Y8wDgYDVR0PAQH/BAQD
AgEGMDYGA1UdEQQvMC2BE2Nhb3BlcmF0b3JAZGlzaWcuc2uGFmh0dHA6Ly93d3cu
ZGlzaWcuc2svY2EwZgYDVR0fBF8wXTAtoCugKYYnaHR0cDovL3d3dy5kaXNpZy5z
ay9jYS9jcmwvY2FfZGlzaWcuY3JsMCygKqAohiZodHRwOi8vY2EuZGlzaWcuc2sv
Y2EvY3JsL2NhX2Rpc2lnLmNybDAaBgNVHSAEEzARMA8GDSuBHpGT5goAAAABAQEw
DQYJKoZIhvcNAQEFBQADggEBAF00dGFMrzvY/59tWDYcPQuBDRIrRhCA/ec8J9B6
yKm2fnQwM6M6int0wHl5QpNt/7EpFIKrIYwvF/k/Ji/1WcbvgAa3mkkp7M5+cTxq
EEHA9tOasnxakZzArFvITV734VP/Q3f8nktnbNfzg9Gg4H8l37iYC5oyOGwwoPP/
CBUz91BKez6jPiCp3C9WgArtQVCwyfTssuMmRAAOb54GvCKWU3BlxFAKRmukLyeB
EicTXxChds6KezfqwzlhA5WYOudsiCUI/HloDYd9Yvi0X/vF2Ey9WLw/Q1vUHgFN
PGO+I++MzVpQuGhU+QqZMxEA4Z7CRneC9VkGjCFMhwnN5ag=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy
MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe
NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH
PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I
x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe
QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR
yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO
QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912
H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ
QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD
i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs
nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1
rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI
hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM
tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf
GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb
lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka
+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal
TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i
nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3
gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr
G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os
zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x
L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICCTCCAY+gAwIBAgIQaEpYcIBr8I8C+vbe6LCQkDAKBggqhkjOPQQDAzBGMQsw
CQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNVBAMT
EkNBIFdvU2lnbiBFQ0MgUm9vdDAeFw0xNDExMDgwMDU4NThaFw00NDExMDgwMDU4
NThaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRlZDEb
MBkGA1UEAxMSQ0EgV29TaWduIEVDQyBSb290MHYwEAYHKoZIzj0CAQYFK4EEACID
YgAE4f2OuEMkq5Z7hcK6C62N4DrjJLnSsb6IOsq/Srj57ywvr1FQPEd1bPiUt5v8
KB7FVMxjnRZLU8HnIKvNrCXSf4/CwVqCXjCLelTOA7WRf6qU0NGKSMyCBSah1VES
1ns2o0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQUqv3VWqP2h4syhf3RMluARZPzA7gwCgYIKoZIzj0EAwMDaAAwZQIxAOSkhLCB
1T2wdKyUpOgOPQB0TKGXa/kNUTyh2Tv0Daupn75OcsqF1NnstTJFGG+rrQIwfcf3
aWMvoeGY7xMQ0Xk/0f7qO3/eVvSQsRUR2LIiFdAvwyYua/GRspBl9JrmkO5K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD
TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y
aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx
MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j
aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP
T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03
sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL
TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5
/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp
7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz
EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt
hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP
a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot
aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg
TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV
PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv
cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL
tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB
ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT
ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL
jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS
ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy
P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19
xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d
Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN
5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe
/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z
AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVTCCAj2gAwIBAgIESTMAATANBgkqhkiG9w0BAQUFADAyMQswCQYDVQQGEwJD
TjEOMAwGA1UEChMFQ05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwHhcNMDcwNDE2
MDcwOTE0WhcNMjcwNDE2MDcwOTE0WjAyMQswCQYDVQQGEwJDTjEOMAwGA1UEChMF
Q05OSUMxEzARBgNVBAMTCkNOTklDIFJPT1QwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDTNfc/c3et6FtzF8LRb+1VvG7q6KR5smzDo+/hn7E7SIX1mlwh
IhAsxYLO2uOabjfhhyzcuQxauohV3/2q2x8x6gHx3zkBwRP9SFIhxFXf2tizVHa6
dLG3fdfA6PZZxU3Iva0fFNrfWEQlMhkqx35+jq44sDB7R3IJMfAw28Mbdim7aXZO
V/kbZKKTVrdvmW7bCgScEeOAH8tjlBAKqeFkgjH5jCftppkA9nCTGPihNIaj3XrC
GHn2emU1z5DrvTOTn1OrczvmmzQgLx3vqR1jGqCA2wMv+SYahtKNu6m+UjqHZ0gN
v7Sg2Ca+I19zN38m5pIEo3/PIKe38zrKy5nLAgMBAAGjczBxMBEGCWCGSAGG+EIB
AQQEAwIABzAfBgNVHSMEGDAWgBRl8jGtKvf33VKWCscCwQ7vptU7ETAPBgNVHRMB
Af8EBTADAQH/MAsGA1UdDwQEAwIB/jAdBgNVHQ4EFgQUZfIxrSr3991SlgrHAsEO
76bVOxEwDQYJKoZIhvcNAQEFBQADggEBAEs17szkrr/Dbq2flTtLP1se31cpolnK
OOK5Gv+e5m4y3R6u6jW39ZORTtpC4cMXYFDy0VwmuYK36m3knITnA3kXr5g9lNvH
ugDnuL8BV8F3RTIMO/G0HAiw/VGgod2aHRM2mm23xzy54cXZF/qD1T0VoDy7Hgvi
yJA/qIYM/PmLXoXLT1tLYhFHxUV8BS9BsZ4QaRuZluBVeftOhpm4lNqGOGqTo+fL
buXf6iFViZx9fX+Y9QCJ7uOEwFyWtcVG6kbghVW2G8kS1sHNzYDzAgE8yGnLRUhj
2JTQ7IUOO04RZfSCjKY9ri4ilAnIXOo8gV0WKgOXFlUJ24pBgp5mmxE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB
gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV
BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw
MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl
YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P
RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0
aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3
UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI
2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8
Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp
+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+
DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O
nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW
/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g
PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u
QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY
SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv
IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/
RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4
zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd
BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB
ZQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT
IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw
MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy
ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N
T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR
FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J
cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW
BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm
fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB
hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV
BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT
EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR
Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR
6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X
pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC
9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV
/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf
Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z
+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w
qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah
SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC
u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf
Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq
crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E
FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB
/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl
wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM
4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV
2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna
FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ
CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK
boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke
jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL
S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb
QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl
0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB
NVOFBkpdn627G190
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
tGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQayXaioidfLwPBbOxemFFRDANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxLTArBgNV
BAMTJENlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbiBHMjAeFw0xNDEx
MDgwMDU4NThaFw00NDExMDgwMDU4NThaMFgxCzAJBgNVBAYTAkNOMRowGAYDVQQK
ExFXb1NpZ24gQ0EgTGltaXRlZDEtMCsGA1UEAxMkQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkgb2YgV29TaWduIEcyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvsXEoCKASU+/2YcRxlPhuw+9YH+v9oIOH9ywjj2X4FA8jzrvZjtFB5sg+OPX
JYY1kBaiXW8wGQiHC38Gsp1ij96vkqVg1CuAmlI/9ZqD6TRay9nVYlzmDuDfBpgO
gHzKtB0TiGsOqCR3A9DuW/PKaZE1OVbFbeP3PU9ekzgkyhjpJMuSA93MHD0JcOQg
5PGurLtzaaNjOg9FD6FKmsLRY6zLEPg95k4ot+vElbGs/V6r+kHLXZ1L3PR8du9n
fwB6jdKgGlxNIuG12t12s9R23164i5jIFFTMaxeSt+BKv0mUYQs4kI9dJGwlezt5
2eJ+na2fmKEG/HgUYFf47oB3sQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU+mCp62XF3RYUCE4MD42b4Pdkr2cwDQYJ
KoZIhvcNAQELBQADggEBAFfDejaCnI2Y4qtAqkePx6db7XznPWZaOzG73/MWM5H8
fHulwqZm46qwtyeYP0nXYGdnPzZPSsvxFPpahygc7Y9BMsaV+X3avXtbwrAh449G
3CE4Q3RM+zD4F3LBMvzIkRfEzFg3TgvMWvchNSiDbGAtROtSjFA9tWwS1/oJu2yy
SrHFieT801LYYRf+epSEj3m2M1m6D8QL4nCgS3gu+sif/a+RZQp4OBXllxcU3fng
LDT4ONCEIgDAFFEYKwLcMFrw6AF8NTojrwjkr6qOKEJJLvD1mTS+7Q9LGOHSJDy7
XUe3IfKN0QqZjuNuPq1w4I+5ysxugTH2e5x6eeRncRg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV
BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X
DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ
BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4
QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny
gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw
zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q
130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2
JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw
ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT
AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj
AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG
9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h
bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc
fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu
HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w
t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw
WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFnDCCA4SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxJjAk
BgNVBAMMHUNlcnRpbm9taXMgLSBBdXRvcml0w6kgUmFjaW5lMB4XDTA4MDkxNzA4
Mjg1OVoXDTI4MDkxNzA4Mjg1OVowYzELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNl
cnRpbm9taXMxFzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMSYwJAYDVQQDDB1DZXJ0
aW5vbWlzIC0gQXV0b3JpdMOpIFJhY2luZTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
ADCCAgoCggIBAJ2Fn4bT46/HsmtuM+Cet0I0VZ35gb5j2CN2DpdUzZlMGvE5x4jY
F1AMnmHawE5V3udauHpOd4cN5bjr+p5eex7Ezyh0x5P1FMYiKAT5kcOrJ3NqDi5N
8y4oH3DfVS9O7cdxbwlyLu3VMpfQ8Vh30WC8Tl7bmoT2R2FFK/ZQpn9qcSdIhDWe
rP5pqZ56XjUl+rSnSTV3lqc2W+HN3yNw2F1MpQiD8aYkOBOo7C+ooWfHpi2GR+6K
/OybDnT0K0kCe5B1jPyZOQE51kqJ5Z52qz6WKDgmi92NjMD2AR5vpTESOH2VwnHu
7XSu5DaiQ3XV8QCb4uTXzEIDS3h65X27uK4uIJPT5GHfceF2Z5c/tt9qc1pkIuVC
28+BA5PY9OMQ4HL2AHCs8MF6DwV/zzRpRbWT5BnbUhYjBYkOjUjkJW+zeL9i9Qf6
lSTClrLooyPCXQP8w9PlfMl1I9f09bze5N/NgL+RiH2nE7Q5uiy6vdFrzPOlKO1E
nn1So2+WLhl+HPNbxxaOu2B9d2ZHVIIAEWBsMsGoOBvrbpgT1u449fCfDu/+MYHB
0iSVL1N6aaLwD4ZFjliCK0wi1F6g530mJ0jfJUaNSih8hp75mxpZuWW/Bd22Ql09
5gBIgl4g9xGC3srYn+Y3RyYe63j3YcNBZFgCQfna4NH4+ej9Uji29YnfAgMBAAGj
WzBZMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQN
jLZh2kS40RR9w759XkjwzspqsDAXBgNVHSAEEDAOMAwGCiqBegFWAgIAAQEwDQYJ
KoZIhvcNAQEFBQADggIBACQ+YAZ+He86PtvqrxyaLAEL9MW12Ukx9F1BjYkMTv9s
ov3/4gbIOZ/xWqndIlgVqIrTseYyCYIDbNc/CMf4uboAbbnW/FIyXaR/pDGUu7ZM
OH8oMDX/nyNTt7buFHAAQCvaR6s0fl6nVjBhK4tDrP22iCj1a7Y+YEq6QpA0Z43q
619FVDsXrIvkxmUP7tCMXWY5zjKn2BCXwH40nJ+U8/aGH88bc62UeYdocMMzpXDn
2NU4lG9jeeu/Cg4I58UvD0KgKxRA/yHgBcUn4YQRE7rWhh1BCxMjidPJC+iKunqj
o3M3NYB9Ergzd0A4wPpeMNLytqOx1qKVl4GbUu1pTP+A5FPbVFsDbVRfsbjvJL1v
nxHDx2TCDyhihWZeGnuyt++uNckZM6i4J9szVb9o4XVIRFb7zdNIu0eJOqxp9YDG
5ERQL1TEqkPFMTFYvZbF6nVsmnWxTfj3l/+WFvKXTej28xH5On2KOG4Ey+HTRRWq
pdEdnV1j6CTmNhTih60bWfVEm/vXd3wfAXBioSAaosUaKPQhA+4u2cGA6rnZgtZb
dsLLO7XSAPCjDuGtbkD326C00EauFddEwk01+dIL8hf2rGbVJLJP0RyZwG71fet0
BLj5TXcJ17TPBzAJ8bgAVtkXFhYKK4bfjwEZGuW7gmP/vgt2Fl43N+bYdJeimUV5
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET
MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb
BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz
MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx
FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g
Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2
fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl
LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV
WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF
TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb
5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc
CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri
wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ
wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG
m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4
F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng
WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0
2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF
AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/
0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw
F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS
g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj
qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN
h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/
ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V
btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj
Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ
8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW
gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw
PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz
cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9
MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz
IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ
ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR
VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL
kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd
EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas
H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0
HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud
DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4
QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu
Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/
AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8
yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR
FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA
ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB
kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7
l7+ijrRU
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDDDCCAfSgAwIBAgIDAQAgMA0GCSqGSIb3DQEBBQUAMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTAeFw0wMjA2MTExMDQ2MzlaFw0yNzA2MTExMDQ2MzlaMD4xCzAJBgNVBAYTAlBM
MRswGQYDVQQKExJVbml6ZXRvIFNwLiB6IG8uby4xEjAQBgNVBAMTCUNlcnR1bSBD
QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM6xwS7TT3zNJc4YPk/E
jG+AanPIW1H4m9LcuwBcsaD8dQPugfCI7iNS6eYVM42sLQnFdvkrOYCJ5JdLkKWo
ePhzQ3ukYbDYWMzhbGZ+nPMJXlVjhNWo7/OxLjBos8Q82KxujZlakE403Daaj4GI
ULdtlkIJ89eVgw1BS7Bqa/j8D35in2fE7SZfECYPCE/wpFcozo+47UX2bu4lXapu
Ob7kky/ZR6By6/qmW6/KUz/iDsaWVhFu9+lmqSbYf5VT7QqFiLpPKaVCjF62/IUg
AKpoC6EahQGcxEZjgoi2IrHu/qpGWX7PNSzVttpd90gzFFS269lvzs2I1qsb2pY7
HVkCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEA
uI3O7+cUus/usESSbLQ5PqKEbq24IXfS1HeCh+YgQYHu4vgRt2PRFze+GXYkHAQa
TOs9qmdvLdTN/mUxcMUbpgIKumB7bVjCmkn+YzILa+M6wKyrO7Do0wlRjBCDxjTg
xSvgGrZgFCdsMneMvLJymM/NzD+5yCRCFNZX/OYmQ6kd5YCQzgNUKD73P9P4Te1q
CjqTE5s7FCMTY5w/0YcneeVMUeMBrYVdGjux1XMQpNPyvG5k9VpWkKjHDkx0Dy5x
O/fIR/RpbxXyEV6DHpx8Uq79AtoSqFlnGNu8cN2bsWntgM6JQEhqDjXKKWYVIZQs
6GAqm4VKQPNriiTsBhYscw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM
MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D
ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU
cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3
WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg
Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw
IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH
UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM
TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU
BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM
kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x
AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV
HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y
sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL
I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8
J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY
VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI
03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz
IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz
MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj
dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw
EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp
MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9
28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq
VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q
DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR
5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL
ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a
Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl
UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s
+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5
Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj
ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx
hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV
HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1
+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN
YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t
L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy
ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt
IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV
HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w
DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW
PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF
5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1
glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH
FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2
pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD
xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG
tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq
jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De
fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg
OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ
d0jQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID9zCCAt+gAwIBAgIESJ8AATANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
Q04xMjAwBgNVBAoMKUNoaW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24g
Q2VudGVyMUcwRQYDVQQDDD5DaGluYSBJbnRlcm5ldCBOZXR3b3JrIEluZm9ybWF0
aW9uIENlbnRlciBFViBDZXJ0aWZpY2F0ZXMgUm9vdDAeFw0xMDA4MzEwNzExMjVa
Fw0zMDA4MzEwNzExMjVaMIGKMQswCQYDVQQGEwJDTjEyMDAGA1UECgwpQ2hpbmEg
SW50ZXJuZXQgTmV0d29yayBJbmZvcm1hdGlvbiBDZW50ZXIxRzBFBgNVBAMMPkNo
aW5hIEludGVybmV0IE5ldHdvcmsgSW5mb3JtYXRpb24gQ2VudGVyIEVWIENlcnRp
ZmljYXRlcyBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAm35z
7r07eKpkQ0H1UN+U8i6yjUqORlTSIRLIOTJCBumD1Z9S7eVnAztUwYyZmczpwA//
DdmEEbK40ctb3B75aDFk4Zv6dOtouSCV98YPjUesWgbdYavi7NifFy2cyjw1l1Vx
zUOFsUcW9SxTgHbP0wBkvUCZ3czY28Sf1hNfQYOL+Q2HklY0bBoQCxfVWhyXWIQ8
hBouXJE0bhlffxdpxWXvayHG1VA6v2G5BY3vbzQ6sm8UY78WO5upKv23KzhmBsUs
4qpnHkWnjQRmQvaPK++IIGmPMowUc9orhpFjIpryp9vOiYurXccUwVswah+xt54u
gQEC7c+WXmPbqOY4twIDAQABo2MwYTAfBgNVHSMEGDAWgBR8cks5x8DbYqVPm6oY
NJKiyoOCWTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E
FgQUfHJLOcfA22KlT5uqGDSSosqDglkwDQYJKoZIhvcNAQEFBQADggEBACrDx0M3
j92tpLIM7twUbY8opJhJywyA6vPtI2Z1fcXTIWd50XPFtQO3WKwMVC/GVhMPMdoG
52U7HW8228gd+f2ABsqjPWYWqJ1MFn3AlUa1UeTiH9fqBk1jjZaM7+czV0I664zB
echNdn3e9rG3geCg+aF4RhcaVpjwTj2rHO3sOdwHSPdj/gauwqRcalsyiMXHM4Ws
ZkJHwlgkmeHlPuV1LI5D1l08eB6olYIpUNHRFrrvwb562bTYzB5MRuF3sTGrvSrI
zo9uoV1/A3U05K2JRVRevq4opbs/eHnrc7MKDf2+yfdWrPa37S+bISnHOLaVxATy
wy39FCqQmbkHzJ8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp
ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow
fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV
BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM
cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S
HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996
CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk
3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz
6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV
HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud
EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv
Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw
Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww
DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0
5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj
Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI
gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ
aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl
izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0
aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla
MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO
BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD
VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW
fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt
TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL
fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW
1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7
kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G
A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v
ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo
dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu
Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/
HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32
pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS
jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+
xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn
dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG
A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh
bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE
ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS
b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5
7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS
J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y
HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP
t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz
FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY
XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/
MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw
hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js
MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA
A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj
Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx
XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o
omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc
A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW
WL1WMRJOEcgh4LMRkWXbtKaIOM5V
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha
ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM
HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03
UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42
tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R
ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM
lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp
/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G
A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G
A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj
dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy
MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl
cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js
L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL
BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni
acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0
o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K
zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8
PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y
Johw1+qRzT65ysCQblrGXnRl11z+o+I=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF
MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD
bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw
NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV
BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn
ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0
3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z
qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR
p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8
HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw
ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea
HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw
Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh
c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E
RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt
dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku
Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp
3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05
nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF
CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na
xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc
MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj
IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB
IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE
RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl
U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290
IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU
ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC
QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr
rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S
NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc
QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH
txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP
BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC
AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp
tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa
IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl
6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+
xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU
Cm26OWMohpLzGITY+9HPBVZkVw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c
JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP
mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+
wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4
VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/
AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB
AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun
pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC
dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf
fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm
NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx
H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe
+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv
b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG
EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl
cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA
n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc
biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp
EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA
bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu
YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB
AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW
BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI
QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I
0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni
lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9
B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv
ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo
IhNzbM8m9Yop5w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg
RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq
hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf
Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q
RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/
BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD
AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY
JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv
6pZjamVFkpUBtA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw
CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu
ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe
Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw
EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x
IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF
K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG
fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO
Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd
BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx
AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/
oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8
sycX
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg
RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV
UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu
Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y
ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If
xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV
ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO
DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ
jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/
CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi
EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM
fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY
uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK
chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t
9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD
ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2
SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd
+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc
fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa
sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N
cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N
0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie
4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI
r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1
/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm
gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV
BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC
aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV
BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1
Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz
MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+
BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp
em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN
ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY
B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH
D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF
Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo
q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D
k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH
fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut
dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM
ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8
zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn
rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX
U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6
Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5
XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF
Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR
HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY
GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c
77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3
+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK
vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6
FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl
yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P
AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD
y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d
NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF5zCCA8+gAwIBAgIITK9zQhyOdAIwDQYJKoZIhvcNAQEFBQAwgYAxODA2BgNV
BAMML0VCRyBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMTcwNQYDVQQKDC5FQkcgQmlsacWfaW0gVGVrbm9sb2ppbGVyaSB2ZSBIaXpt
ZXRsZXJpIEEuxZ4uMQswCQYDVQQGEwJUUjAeFw0wNjA4MTcwMDIxMDlaFw0xNjA4
MTQwMDMxMDlaMIGAMTgwNgYDVQQDDC9FQkcgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTE3MDUGA1UECgwuRUJHIEJpbGnFn2ltIFRl
a25vbG9qaWxlcmkgdmUgSGl6bWV0bGVyaSBBLsWeLjELMAkGA1UEBhMCVFIwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDuoIRh0DpqZhAy2DE4f6en5f2h
4fuXd7hxlugTlkaDT7byX3JWbhNgpQGR4lvFzVcfd2NR/y8927k/qqk153nQ9dAk
tiHq6yOU/im/+4mRDGSaBUorzAzu8T2bgmmkTPiab+ci2hC6X5L8GCcKqKpE+i4s
tPtGmggDg3KriORqcsnlZR9uKg+ds+g75AxuetpX/dfreYteIAbTdgtsApWjluTL
dlHRKJ2hGvxEok3MenaoDT2/F08iiFD9rrbskFBKW5+VQarKD7JK/oCZTqNGFav4
c0JqwmZ2sQomFd2TkuzbqV9UIlKRcF0T6kjsbgNs2d1s/OsNA/+mgxKb8amTD8Um
TDGyY5lhcucqZJnSuOl14nypqZoaqsNW2xCaPINStnuWt6yHd6i58mcLlEOzrz5z
+kI2sSXFCjEmN1ZnuqMLfdb3ic1nobc6HmZP9qBVFCVMLDMNpkGMvQQxahByCp0O
Lna9XvNRiYuoP1Vzv9s6xiQFlpJIqkuNKgPlV5EQ9GooFW5Hd4RcUXSfGenmHmMW
OeMRFeNYGkS9y8RsZteEBt8w9DeiQyJ50hBs37vmExH8nYQKE3vwO9D8owrXieqW
fo1IhR5kX9tUoqzVegJ5a9KK8GfaZXINFHDk6Y54jzJ0fFfy1tb0Nokb+Clsi7n2
l9GkLqq+CxnCRelwXQIDAJ3Zo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
/wQEAwIBBjAdBgNVHQ4EFgQU587GT/wWZ5b6SqMHwQSny2re2kcwHwYDVR0jBBgw
FoAU587GT/wWZ5b6SqMHwQSny2re2kcwDQYJKoZIhvcNAQEFBQADggIBAJuYml2+
8ygjdsZs93/mQJ7ANtyVDR2tFcU22NU57/IeIl6zgrRdu0waypIN30ckHrMk2pGI
6YNw3ZPX6bqz3xZaPt7gyPvT/Wwp+BVGoGgmzJNSroIBk5DKd8pNSe/iWtkqvTDO
TLKBtjDOWU/aWR1qeqRFsIImgYZ29fUQALjuswnoT4cCB64kXPBfrAowzIpAoHME
wfuJJPaaHFy3PApnNgUIMbOv2AFoKuB4j3TeuFGkjGwgPaL7s9QJ/XvCgKqTbCmY
Iai7FvOpEl90tYeY8pUm3zTvilORiF0alKM/fCL414i6poyWqD1SNGKfAB5UVUJn
xk1Gj7sURT0KlhaOEKGXmdXTMIXM3rRyt7yKPBgpaP3ccQfuJDlq+u2lrDgv+R4Q
DgZxGhBM/nV+/x5XOULK1+EVoVZVWRvRo68R2E7DpSvvkL/A7IITW43WciyTTo9q
Kd+FPNMN4KIYEsxVL0e3p5sC/kH2iExt2qkBR4NkJ2IQgtYSe14DHzSpyZH+r11t
hie3I6p1GMog57AP14kOpmciY/SDQSsGS7tY1dHXt7kQY9iJSrSq3RZj9W6+YKH4
7ejWkE8axsWgKdOnIaj1Wjz3x0miIZpKlVIglnKaZsv30oZDfCK+lvm9AahH3eU7
QPl1K5srRmSGjR70j/sHd9DqSaIcjVIUpgqT
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB
8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy
dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1
YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3
dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh
IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD
LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG
EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g
KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD
ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu
bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg
ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R
85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm
4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV
HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd
QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t
lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB
o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4
opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo
dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW
ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN
AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y
/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k
SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy
Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS
Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl
nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML
RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp
bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5
IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3
MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3
LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp
YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG
A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq
K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe
sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX
MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT
XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/
HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH
4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub
j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo
U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf
zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b
u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+
bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er
fF6adulZkMV8gzURZVE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0
Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW
KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl
cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw
NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw
NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy
ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV
BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo
Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4
4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9
KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI
rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi
94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB
sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi
gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo
kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE
vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA
A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t
O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua
AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP
9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/
eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m
0vdXcDazv/wor3ElhVsT/h5/WrQ8
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG
A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3
d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu
dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq
RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy
MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD
VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0
L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g
Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD
ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi
A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt
ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH
Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC
R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX
hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC
VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50
cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs
IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz
dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy
NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu
dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt
dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0
aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T
RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN
cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW
wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1
U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0
jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP
BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN
BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/
jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ
Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v
1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R
nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH
VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs
IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg
R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A
PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8
Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL
TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL
5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7
S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe
2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE
FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap
EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td
EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv
/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN
A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0
abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF
I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz
4iIprn2DQKi6bA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo
R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx
MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK
Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp
ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9
AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA
ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0
7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W
kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI
mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G
A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ
KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1
6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl
4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K
oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj
UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU
AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL
MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj
KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2
MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0
eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV
BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw
NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV
BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH
MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL
So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal
tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG
CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT
qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz
rD6ogRLQy7rQkgu2npaqBA+K
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB
mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT
MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s
eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv
cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ
BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg
MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0
BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz
+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm
hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn
5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W
JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL
DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC
huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB
AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB
zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN
kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD
AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH
SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G
spki4cErx5z481+oghLrGREt
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy
c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE
BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0
IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV
VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8
cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT
QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh
F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v
c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w
mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd
VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX
teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ
f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe
Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+
nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB
/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY
MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc
aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX
IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn
ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z
uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN
Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja
QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW
koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9
ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt
DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm
bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW
MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy
c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD
VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1
c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81
WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG
FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq
XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL
se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb
KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd
IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73
y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt
hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc
QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4
Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV
HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ
KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z
dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ
L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr
Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo
ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY
T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz
GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m
1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV
OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX
QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ
FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F
uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX
kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs
ewv4n4Q=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk
MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH
bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX
DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD
QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc
8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke
hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI
KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg
515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO
xwy8p2Fp8fc74SrL+SvzZpA3
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1
MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL
v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8
eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq
tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd
C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa
zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB
mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH
V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n
bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG
3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs
J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO
291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS
ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd
AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7
TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
WD9f
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD
VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0
IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3
MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD
aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx
MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy
cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG
A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl
BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed
KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7
G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2
zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4
ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG
HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2
Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V
yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e
beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r
6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh
wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog
zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW
BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr
ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp
ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk
cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt
YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC
CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow
KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI
hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ
UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz
X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x
fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz
a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd
Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd
SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O
AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso
M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge
v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z
09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
ReYNnyicsbkqWletNw+vHX/bvZ8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT
EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp
ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz
NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH
EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE
AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD
E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH
/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy
DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh
GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR
tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA
AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX
WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu
9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr
gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo
2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO
LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI
4uJEvlz36hz1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix
RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1
dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p
YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw
NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK
EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl
cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl
c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz
dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ
fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns
bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD
75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP
FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV
HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp
5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu
b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA
A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p
6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8
TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7
dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys
Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI
l7WdmplNsDz4SgCbZN2fOUvRJ9e4
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx
FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg
Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG
A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr
b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ
jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn
PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh
ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9
nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h
q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED
MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC
mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3
7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB
oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs
EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO
fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi
AmvZWg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEAjCCAuqgAwIBAgIFORFFEJQwDQYJKoZIhvcNAQEFBQAwgYUxCzAJBgNVBAYT
AkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAMBgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQ
TS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEOMAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG
9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2LmZyMB4XDTAyMTIxMzE0MjkyM1oXDTIw
MTAxNzE0MjkyMlowgYUxCzAJBgNVBAYTAkZSMQ8wDQYDVQQIEwZGcmFuY2UxDjAM
BgNVBAcTBVBhcmlzMRAwDgYDVQQKEwdQTS9TR0ROMQ4wDAYDVQQLEwVEQ1NTSTEO
MAwGA1UEAxMFSUdDL0ExIzAhBgkqhkiG9w0BCQEWFGlnY2FAc2dkbi5wbS5nb3V2
LmZyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsh/R0GLFMzvABIaI
s9z4iPf930Pfeo2aSVz2TqrMHLmh6yeJ8kbpO0px1R2OLc/mratjUMdUC24SyZA2
xtgv2pGqaMVy/hcKshd+ebUyiHDKcMCWSo7kVc0dJ5S/znIq7Fz5cyD+vfcuiWe4
u0dzEvfRNWk68gq5rv9GQkaiv6GFGvm/5P9JhfejcIYyHF2fYPepraX/z9E0+X1b
F8bc1g4oa8Ld8fUzaJ1O/Id8NhLWo4DoQw1VYZTqZDdH6nfK0LJYBcNdfrGoRpAx
Vs5wKpayMLh35nnAvSk7/ZR3TL0gzUEl4C7HG7vupARB0l2tEmqKm0f7yd1GQOGd
PDPQtQIDAQABo3cwdTAPBgNVHRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBRjAVBgNV
HSAEDjAMMAoGCCqBegF5AQEBMB0GA1UdDgQWBBSjBS8YYFDCiQrdKyFP/45OqDAx
NjAfBgNVHSMEGDAWgBSjBS8YYFDCiQrdKyFP/45OqDAxNjANBgkqhkiG9w0BAQUF
AAOCAQEABdwm2Pp3FURo/C9mOnTgXeQp/wYHE4RKq89toB9RlPhJy3Q2FLwV3duJ
L92PoF189RLrn544pEfMs5bZvpwlqwN+Mw+VgQ39FuCIvjfwbF3QMZsyK10XZZOY
YLxuj7GoPB7ZHPOpJkL5ZB3C55L29B5aqhlSXa/oovdgoPaN8In1buAKBQGVyYsg
Crpa/JosPL3Dt8ldeCUFP1YUmwza+zpI/pdpXsoQhvdOlgQITeywvl3cO45Pwf2a
NjSaTFR+FwNIlQgRHAdvhQh+XU3Endv7rs6y0bO4g2wdsrN58dhwmX7wEwLOXt1R
0982gaEbeC9xs/FZTEYYKKuF0mBWWg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu
VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw
MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw
JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT
3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU
+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp
S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1
bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi
T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL
vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK
Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK
dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT
c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv
l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N
iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD
ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH
6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt
LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93
nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3
+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK
W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT
AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq
l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG
4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ
mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A
7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN
MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu
VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN
MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0
MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi
MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7
ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy
RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS
bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF
/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R
3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw
EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy
9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V
GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ
2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV
WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD
W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/
BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN
AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj
t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV
DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9
TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G
lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW
mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df
WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5
+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ
tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA
GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv
8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4
MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6
ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD
VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j
b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq
scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO
xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H
LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX
uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD
yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+
JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q
rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN
BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L
hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB
QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+
HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu
Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg
QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB
BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx
MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA
A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb
laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56
awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo
JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw
LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT
VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk
LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb
UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/
QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+
naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls
QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE5jCCA86gAwIBAgIEO45L/DANBgkqhkiG9w0BAQUFADBdMRgwFgYJKoZIhvcN
AQkBFglwa2lAc2suZWUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKExlBUyBTZXJ0aWZp
dHNlZXJpbWlza2Vza3VzMRAwDgYDVQQDEwdKdXVyLVNLMB4XDTAxMDgzMDE0MjMw
MVoXDTE2MDgyNjE0MjMwMVowXTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVlMQsw
CQYDVQQGEwJFRTEiMCAGA1UEChMZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1czEQ
MA4GA1UEAxMHSnV1ci1TSzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AIFxNj4zB9bjMI0TfncyRsvPGbJgMUaXhvSYRqTCZUXP00B841oiqBB4M8yIsdOB
SvZiF3tfTQou0M+LI+5PAk676w7KvRhj6IAcjeEcjT3g/1tf6mTll+g/mX8MCgkz
ABpTpyHhOEvWgxutr2TC+Rx6jGZITWYfGAriPrsfB2WThbkasLnE+w0R9vXW+RvH
LCu3GFH+4Hv2qEivbDtPL+/40UceJlfwUR0zlv/vWT3aTdEVNMfqPxZIe5EcgEMP
PbgFPtGzlc3Yyg/CQ2fbt5PgIoIuvvVoKIO5wTtpeyDaTpxt4brNj3pssAki14sL
2xzVWiZbDcDq5WDQn/413z8CAwEAAaOCAawwggGoMA8GA1UdEwEB/wQFMAMBAf8w
ggEWBgNVHSAEggENMIIBCTCCAQUGCisGAQQBzh8BAQEwgfYwgdAGCCsGAQUFBwIC
MIHDHoHAAFMAZQBlACAAcwBlAHIAdABpAGYAaQBrAGEAYQB0ACAAbwBuACAAdgDk
AGwAagBhAHMAdABhAHQAdQBkACAAQQBTAC0AaQBzACAAUwBlAHIAdABpAGYAaQB0
AHMAZQBlAHIAaQBtAGkAcwBrAGUAcwBrAHUAcwAgAGEAbABhAG0ALQBTAEsAIABz
AGUAcgB0AGkAZgBpAGsAYQBhAHQAaQBkAGUAIABrAGkAbgBuAGkAdABhAG0AaQBz
AGUAawBzMCEGCCsGAQUFBwIBFhVodHRwOi8vd3d3LnNrLmVlL2Nwcy8wKwYDVR0f
BCQwIjAgoB6gHIYaaHR0cDovL3d3dy5zay5lZS9qdXVyL2NybC8wHQYDVR0OBBYE
FASqekej5ImvGs8KQKcYP2/v6X2+MB8GA1UdIwQYMBaAFASqekej5ImvGs8KQKcY
P2/v6X2+MA4GA1UdDwEB/wQEAwIB5jANBgkqhkiG9w0BAQUFAAOCAQEAe8EYlFOi
CfP+JmeaUOTDBS8rNXiRTHyoERF5TElZrMj3hWVcRrs7EKACr81Ptcw2Kuxd/u+g
kcm2k298gFTsxwhwDY77guwqYHhpNjbRxZyLabVAyJRld/JXIWY7zoVAtjNjGr95
HvxcHdMdkxuLDF2FvZkwMhgJkVLpfKG6/2SSmuz+Ne6ML678IIbsSt4beDI3poHS
na9aEhbKmVv8b20OxaAehsmR0FyYgl9jDIpaq9iVpszLita/ZEuOyoqysOkhMp6q
qIWYNIE5ITuoOlIyPfZrN4YGWhWY3PARZv40ILcD9EEQfTmEeZZyY7aWAuVrua0Z
TbvGRNs2yyqcjg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHqDCCBpCgAwIBAgIRAMy4579OKRr9otxmpRwsDxEwDQYJKoZIhvcNAQEFBQAw
cjELMAkGA1UEBhMCSFUxETAPBgNVBAcTCEJ1ZGFwZXN0MRYwFAYDVQQKEw1NaWNy
b3NlYyBMdGQuMRQwEgYDVQQLEwtlLVN6aWdubyBDQTEiMCAGA1UEAxMZTWljcm9z
ZWMgZS1Temlnbm8gUm9vdCBDQTAeFw0wNTA0MDYxMjI4NDRaFw0xNzA0MDYxMjI4
NDRaMHIxCzAJBgNVBAYTAkhVMREwDwYDVQQHEwhCdWRhcGVzdDEWMBQGA1UEChMN
TWljcm9zZWMgTHRkLjEUMBIGA1UECxMLZS1Temlnbm8gQ0ExIjAgBgNVBAMTGU1p
Y3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDtyADVgXvNOABHzNuEwSFpLHSQDCHZU4ftPkNEU6+r+ICbPHiN1I2u
uO/TEdyB5s87lozWbxXGd36hL+BfkrYn13aaHUM86tnsL+4582pnS4uCzyL4ZVX+
LMsvfUh6PXX5qqAnu3jCBspRwn5mS6/NoqdNAoI/gqyFxuEPkEeZlApxcpMqyabA
vjxWTHOSJ/FrtfX9/DAFYJLG65Z+AZHCabEeHXtTRbjcQR/Ji3HWVBTji1R4P770
Yjtb9aPs1ZJ04nQw7wHb4dSrmZsqa/i9phyGI0Jf7Enemotb9HI6QMVJPqW+jqpx
62z69Rrkav17fVVA71hu5tnVvCSrwe+3AgMBAAGjggQ3MIIEMzBnBggrBgEFBQcB
AQRbMFkwKAYIKwYBBQUHMAGGHGh0dHBzOi8vcmNhLmUtc3ppZ25vLmh1L29jc3Aw
LQYIKwYBBQUHMAKGIWh0dHA6Ly93d3cuZS1zemlnbm8uaHUvUm9vdENBLmNydDAP
BgNVHRMBAf8EBTADAQH/MIIBcwYDVR0gBIIBajCCAWYwggFiBgwrBgEEAYGoGAIB
AQEwggFQMCgGCCsGAQUFBwIBFhxodHRwOi8vd3d3LmUtc3ppZ25vLmh1L1NaU1ov
MIIBIgYIKwYBBQUHAgIwggEUHoIBEABBACAAdABhAG4A+gBzAO0AdAB2AOEAbgB5
ACAA6QByAHQAZQBsAG0AZQB6AOkAcwDpAGgAZQB6ACAA6QBzACAAZQBsAGYAbwBn
AGEAZADhAHMA4QBoAG8AegAgAGEAIABTAHoAbwBsAGcA4QBsAHQAYQB0APMAIABT
AHoAbwBsAGcA4QBsAHQAYQB0AOEAcwBpACAAUwB6AGEAYgDhAGwAeQB6AGEAdABh
ACAAcwB6AGUAcgBpAG4AdAAgAGsAZQBsAGwAIABlAGwAagDhAHIAbgBpADoAIABo
AHQAdABwADoALwAvAHcAdwB3AC4AZQAtAHMAegBpAGcAbgBvAC4AaAB1AC8AUwBa
AFMAWgAvMIHIBgNVHR8EgcAwgb0wgbqggbeggbSGIWh0dHA6Ly93d3cuZS1zemln
bm8uaHUvUm9vdENBLmNybIaBjmxkYXA6Ly9sZGFwLmUtc3ppZ25vLmh1L0NOPU1p
Y3Jvc2VjJTIwZS1Temlnbm8lMjBSb290JTIwQ0EsT1U9ZS1Temlnbm8lMjBDQSxP
PU1pY3Jvc2VjJTIwTHRkLixMPUJ1ZGFwZXN0LEM9SFU/Y2VydGlmaWNhdGVSZXZv
Y2F0aW9uTGlzdDtiaW5hcnkwDgYDVR0PAQH/BAQDAgEGMIGWBgNVHREEgY4wgYuB
EGluZm9AZS1zemlnbm8uaHWkdzB1MSMwIQYDVQQDDBpNaWNyb3NlYyBlLVN6aWdu
w7MgUm9vdCBDQTEWMBQGA1UECwwNZS1TemlnbsOzIEhTWjEWMBQGA1UEChMNTWlj
cm9zZWMgS2Z0LjERMA8GA1UEBxMIQnVkYXBlc3QxCzAJBgNVBAYTAkhVMIGsBgNV
HSMEgaQwgaGAFMegSXUWYYTbMUuE0vE3QJDvTtz3oXakdDByMQswCQYDVQQGEwJI
VTERMA8GA1UEBxMIQnVkYXBlc3QxFjAUBgNVBAoTDU1pY3Jvc2VjIEx0ZC4xFDAS
BgNVBAsTC2UtU3ppZ25vIENBMSIwIAYDVQQDExlNaWNyb3NlYyBlLVN6aWdubyBS
b290IENBghEAzLjnv04pGv2i3GalHCwPETAdBgNVHQ4EFgQUx6BJdRZhhNsxS4TS
8TdAkO9O3PcwDQYJKoZIhvcNAQEFBQADggEBANMTnGZjWS7KXHAM/IO8VbH0jgds
ZifOwTsgqRy7RlRw7lrMoHfqaEQn6/Ip3Xep1fvj1KcExJW4C+FEaGAHQzAxQmHl
7tnlJNUb3+FKG6qfx1/4ehHqE5MAyopYse7tDk2016g2JnzgOsHVV4Lxdbb9iV/a
86g4nzUGCM4ilb7N1fy+W955a9x6qWVmvrElWl/tftOsRm1M9DKHtCAE4Gx4sHfR
hUZLphK3dehKyVZs15KrnfVJONJPU+NVkBHbmJbGSfI+9J8b4PeI3CVimUTYc78/
MPMMNz7UwiiAc7EBt51alhQBS6kRnSlqLtBdgcDPsiBDxwPgN05dCtxZICU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD
VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0
ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G
CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y
OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx
FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp
Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o
dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP
kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc
cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U
fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7
N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC
xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1
+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM
Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG
SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h
mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk
ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775
tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c
2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t
HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG
EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3
MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl
cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR
dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB
pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM
b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm
aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz
IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT
lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz
AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5
VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG
ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2
BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG
AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M
U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh
bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C
+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC
bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F
uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2
XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGfTCCBWWgAwIBAgICAQMwDQYJKoZIhvcNAQEEBQAwga8xCzAJBgNVBAYTAkhV
MRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMe
TmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0
dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBLb3pqZWd5em9pIChDbGFzcyBB
KSBUYW51c2l0dmFueWtpYWRvMB4XDTk5MDIyNDIzMTQ0N1oXDTE5MDIxOTIzMTQ0
N1owga8xCzAJBgNVBAYTAkhVMRAwDgYDVQQIEwdIdW5nYXJ5MREwDwYDVQQHEwhC
dWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9uc2FnaSBLZnQu
MRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE2MDQGA1UEAxMtTmV0TG9jayBL
b3pqZWd5em9pIChDbGFzcyBBKSBUYW51c2l0dmFueWtpYWRvMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvHSMD7tM9DceqQWC2ObhbHDqeLVu0ThEDaiD
zl3S1tWBxdRL51uUcCbbO51qTGL3cfNk1mE7PetzozfZz+qMkjvN9wfcZnSX9EUi
3fRc4L9t875lM+QVOr/bmJBVOMTtplVjC7B4BPTjbsE/jvxReB+SnoPC/tmwqcm8
WgD/qaiYdPv2LD4VOQ22BFWoDpggQrOxJa1+mm9dU7GrDPzr4PN6s6iz/0b2Y6LY
Oph7tqyF/7AlT3Rj5xMHpQqPBffAZG9+pyeAlt7ULoZgx2srXnN7F+eRP2QM2Esi
NCubMvJIH5+hCoR64sKtlz2O1cH5VqNQ6ca0+pii7pXmKgOM3wIDAQABo4ICnzCC
ApswDgYDVR0PAQH/BAQDAgAGMBIGA1UdEwEB/wQIMAYBAf8CAQQwEQYJYIZIAYb4
QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1GSUdZRUxFTSEgRXplbiB0
YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFub3MgU3pvbGdhbHRhdGFz
aSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBhbGFwamFuIGtlc3p1bHQu
IEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExvY2sgS2Z0LiB0ZXJtZWtm
ZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGlnaXRhbGlzIGFsYWlyYXMg
ZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0IGVsbGVub3J6ZXNpIGVs
amFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJhc2EgbWVndGFsYWxoYXRv
IGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGphbiBhIGh0dHBzOi8vd3d3
Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJoZXRvIGF6IGVsbGVub3J6
ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBPUlRBTlQhIFRoZSBpc3N1
YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmljYXRlIGlzIHN1YmplY3Qg
dG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBodHRwczovL3d3dy5uZXRs
b2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNAbmV0bG9jay5uZXQuMA0G
CSqGSIb3DQEBBAUAA4IBAQBIJEb3ulZv+sgoA0BO5TE5ayZrU3/b39/zcT0mwBQO
xmd7I6gMc90Bu8bKbjc5VdXHjFYgDigKDtIqpLBJUsY4B/6+CgmM0ZjPytoUMaFP
0jn8DxEsQ8Pdq5PHVT5HfBgaANzze9jyf1JsIPQLX2lS9O74silg6+NJMSEN1rUQ
QeJBCWziGppWS3cC9qCbmieH6FUpccKQn0V4GuEVZD3QDtigdp+uxdAu6tYPVuxk
f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK
8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi
MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu
MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp
dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV
UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO
ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz
c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP
OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl
mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF
BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4
qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw
gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB
BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu
bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp
dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8
6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/
h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH
/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv
wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN
pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB
ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly
aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl
ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w
NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G
A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD
VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX
SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR
VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2
w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF
mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg
4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9
4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw
DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw
EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx
SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2
ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8
vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa
hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi
Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ
/L7fCg0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt
MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg
Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i
YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x
CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG
b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh
bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3
HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx
WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX
1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk
u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P
99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r
M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw
AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB
BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh
cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5
gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO
ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf
aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic
Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIJhjCCB26gAwIBAgIBCzANBgkqhkiG9w0BAQsFADCCAR4xPjA8BgNVBAMTNUF1
dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIFJhaXogZGVsIEVzdGFkbyBWZW5lem9s
YW5vMQswCQYDVQQGEwJWRTEQMA4GA1UEBxMHQ2FyYWNhczEZMBcGA1UECBMQRGlz
dHJpdG8gQ2FwaXRhbDE2MDQGA1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0
aWZpY2FjaW9uIEVsZWN0cm9uaWNhMUMwQQYDVQQLEzpTdXBlcmludGVuZGVuY2lh
IGRlIFNlcnZpY2lvcyBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9uaWNhMSUwIwYJ
KoZIhvcNAQkBFhZhY3JhaXpAc3VzY2VydGUuZ29iLnZlMB4XDTEwMTIyODE2NTEw
MFoXDTIwMTIyNTIzNTk1OVowgdExJjAkBgkqhkiG9w0BCQEWF2NvbnRhY3RvQHBy
b2NlcnQubmV0LnZlMQ8wDQYDVQQHEwZDaGFjYW8xEDAOBgNVBAgTB01pcmFuZGEx
KjAoBgNVBAsTIVByb3ZlZWRvciBkZSBDZXJ0aWZpY2Fkb3MgUFJPQ0VSVDE2MDQG
A1UEChMtU2lzdGVtYSBOYWNpb25hbCBkZSBDZXJ0aWZpY2FjaW9uIEVsZWN0cm9u
aWNhMQswCQYDVQQGEwJWRTETMBEGA1UEAxMKUFNDUHJvY2VydDCCAiIwDQYJKoZI
hvcNAQEBBQADggIPADCCAgoCggIBANW39KOUM6FGqVVhSQ2oh3NekS1wwQYalNo9
7BVCwfWMrmoX8Yqt/ICV6oNEolt6Vc5Pp6XVurgfoCfAUFM+jbnADrgV3NZs+J74
BCXfgI8Qhd19L3uA3VcAZCP4bsm+lU/hdezgfl6VzbHvvnpC2Mks0+saGiKLt38G
ieU89RLAu9MLmV+QfI4tL3czkkohRqipCKzx9hEC2ZUWno0vluYC3XXCFCpa1sl9
JcLB/KpnheLsvtF8PPqv1W7/U0HU9TI4seJfxPmOEO8GqQKJ/+MMbpfg353bIdD0
PghpbNjU5Db4g7ayNo+c7zo3Fn2/omnXO1ty0K+qP1xmk6wKImG20qCZyFSTXai2
0b1dCl53lKItwIKOvMoDKjSuc/HUtQy9vmebVOvh+qBa7Dh+PsHMosdEMXXqP+UH
0quhJZb25uSgXTcYOWEAM11G1ADEtMo88aKjPvM6/2kwLkDd9p+cJsmWN63nOaK/
6mnbVSKVUyqUtd+tFjiBdWbjxywbk5yqjKPK2Ww8F22c3HxT4CAnQzb5EuE8XL1m
v6JpIzi4mWCZDlZTOpx+FIywBm/xhnaQr/2v/pDGj59/i5IjnOcVdo/Vi5QTcmn7
K2FjiO/mpF7moxdqWEfLcU8UC17IAggmosvpr2uKGcfLFFb14dq12fy/czja+eev
bqQ34gcnAgMBAAGjggMXMIIDEzASBgNVHRMBAf8ECDAGAQH/AgEBMDcGA1UdEgQw
MC6CD3N1c2NlcnRlLmdvYi52ZaAbBgVghl4CAqASDBBSSUYtRy0yMDAwNDAzNi0w
MB0GA1UdDgQWBBRBDxk4qpl/Qguk1yeYVKIXTC1RVDCCAVAGA1UdIwSCAUcwggFD
gBStuyIdxuDSAaj9dlBSk+2YwU2u06GCASakggEiMIIBHjE+MDwGA1UEAxM1QXV0
b3JpZGFkIGRlIENlcnRpZmljYWNpb24gUmFpeiBkZWwgRXN0YWRvIFZlbmV6b2xh
bm8xCzAJBgNVBAYTAlZFMRAwDgYDVQQHEwdDYXJhY2FzMRkwFwYDVQQIExBEaXN0
cml0byBDYXBpdGFsMTYwNAYDVQQKEy1TaXN0ZW1hIE5hY2lvbmFsIGRlIENlcnRp
ZmljYWNpb24gRWxlY3Ryb25pY2ExQzBBBgNVBAsTOlN1cGVyaW50ZW5kZW5jaWEg
ZGUgU2VydmljaW9zIGRlIENlcnRpZmljYWNpb24gRWxlY3Ryb25pY2ExJTAjBgkq
hkiG9w0BCQEWFmFjcmFpekBzdXNjZXJ0ZS5nb2IudmWCAQowDgYDVR0PAQH/BAQD
AgEGME0GA1UdEQRGMESCDnByb2NlcnQubmV0LnZloBUGBWCGXgIBoAwMClBTQy0w
MDAwMDKgGwYFYIZeAgKgEgwQUklGLUotMzE2MzUzNzMtNzB2BgNVHR8EbzBtMEag
RKBChkBodHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9sY3IvQ0VSVElGSUNBRE8t
UkFJWi1TSEEzODRDUkxERVIuY3JsMCOgIaAfhh1sZGFwOi8vYWNyYWl6LnN1c2Nl
cnRlLmdvYi52ZTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUHMAGGG2h0dHA6Ly9v
Y3NwLnN1c2NlcnRlLmdvYi52ZTBBBgNVHSAEOjA4MDYGBmCGXgMBAjAsMCoGCCsG
AQUFBwIBFh5odHRwOi8vd3d3LnN1c2NlcnRlLmdvYi52ZS9kcGMwDQYJKoZIhvcN
AQELBQADggIBACtZ6yKZu4SqT96QxtGGcSOeSwORR3C7wJJg7ODU523G0+1ng3dS
1fLld6c2suNUvtm7CpsR72H0xpkzmfWvADmNg7+mvTV+LFwxNG9s2/NkAZiqlCxB
3RWGymspThbASfzXg0gTB1GEMVKIu4YXx2sviiCtxQuPcD4quxtxj7mkoP3Yldmv
Wb8lK5jpY5MvYB7Eqvh39YtsL+1+LrVPQA3uvFd359m21D+VJzog1eWuq2w1n8Gh
HVnchIHuTQfiSLaeS5UtQbHh6N5+LwUeaO6/u5BlOsju6rEYNxxik6SgMexxbJHm
pHmJWhSnFFAFTKQAVzAswbVhltw+HoSvOULP5dAssSS830DD7X9jSr3hTxJkhpXz
sOfIt+FTvZLm8wyWuevo5pLtp4EJFAv8lXrPj9Y0TzYS3F7RNHXGRoAvlQSMx4bE
qCaJqD8Zm4G7UaRKhqsLEQ+xrmNTbSjq3TNWOByyrYDT13K9mmyZY+gAu0F2Bbdb
mRiKw7gSXFbPVgx96OLP7bx0R/vu0xdOIk9W/1DzLuY5poLWccret9W6aAjtmcz9
opLLabid+Qqkpj5PkygqYWwHJgD/ll9ohri4zspV4KuxPX+Y1zMOWj3YeMLEYC/H
YvBhkdI4sPaeVdtAgAUSM84dkpvRabP/v/GSCmE1P93+hvS84Bpxs2Km
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz
MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw
IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR
dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp
li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D
rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ
WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug
F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU
xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC
Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv
dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw
ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl
IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy
ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh
Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI
KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T
KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq
y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p
dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD
VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL
MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk
fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8
7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R
cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y
mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW
xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK
SnQ2+Q==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00
MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV
wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe
rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341
68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh
4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp
UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o
abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc
3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G
KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt
hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO
Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt
zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD
ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC
MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2
cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN
qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5
YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv
b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2
8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k
NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj
ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp
q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt
nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa
GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg
Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J
WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB
rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp
+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1
ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i
Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz
PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og
/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH
oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI
yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud
EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2
A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL
MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT
ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f
BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn
g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl
fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K
WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha
B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc
hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR
TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD
mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z
ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y
4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza
8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00
MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf
qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW
n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym
c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+
O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1
o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j
IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq
IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz
8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh
vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l
7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG
cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD
ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66
AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC
roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga
W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n
lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE
+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV
csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd
dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg
KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM
HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4
WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x
GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv
b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV
BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W
YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM
V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB
4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr
H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd
8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv
vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT
mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe
btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc
T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt
WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ
c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A
4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD
VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG
CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0
aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0
aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu
dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw
czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G
A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC
TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg
Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0
7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem
d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd
+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B
4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN
t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x
DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57
k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s
zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j
Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT
mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK
4SVhM7JZG+Ju1zdXtg2pEto=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL
BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc
BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00
MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM
aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR
/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu
FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR
U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c
ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR
FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k
A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw
eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl
sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp
VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q
A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+
ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD
ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px
KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI
FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv
oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg
u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP
0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf
3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl
8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+
DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN
PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/
ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDYTCCAkmgAwIBAgIQCgEBAQAAAnwAAAAKAAAAAjANBgkqhkiG9w0BAQUFADA6
MRkwFwYDVQQKExBSU0EgU2VjdXJpdHkgSW5jMR0wGwYDVQQLExRSU0EgU2VjdXJp
dHkgMjA0OCBWMzAeFw0wMTAyMjIyMDM5MjNaFw0yNjAyMjIyMDM5MjNaMDoxGTAX
BgNVBAoTEFJTQSBTZWN1cml0eSBJbmMxHTAbBgNVBAsTFFJTQSBTZWN1cml0eSAy
MDQ4IFYzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAt49VcdKA3Xtp
eafwGFAyPGJn9gqVB93mG/Oe2dJBVGutn3y+Gc37RqtBaB4Y6lXIL5F4iSj7Jylg
/9+PjDvJSZu1pJTOAeo+tWN7fyb9Gd3AIb2E0S1PRsNO3Ng3OTsor8udGuorryGl
wSMiuLgbWhOHV4PR8CDn6E8jQrAApX2J6elhc5SYcSa8LWrg903w8bYqODGBDSnh
AMFRD0xS+ARaqn1y07iHKrtjEAMqs6FPDVpeRrc9DvV07Jmf+T0kgYim3WBU6JU2
PcYJk5qjEoAAVZkZR73QpXzDuvsf9/UP+Ky5tfQ3mBMY3oVbtwyCO4dvlTlYMNpu
AWgXIszACwIDAQABo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB
BjAfBgNVHSMEGDAWgBQHw1EwpKrpRa41JPr/JCwz0LGdjDAdBgNVHQ4EFgQUB8NR
MKSq6UWuNST6/yQsM9CxnYwwDQYJKoZIhvcNAQEFBQADggEBAF8+hnZuuDU8TjYc
HnmYv/3VEhF5Ug7uMYm83X/50cYVIeiKAVQNOvtUudZj1LGqlk2iQk3UUx+LEN5/
Zb5gEydxiKRz44Rj0aRV4VCT5hsOedBnvEbIvz8XDZXmxpBp3ue0L96VfdASPz0+
f00/FGj1EVDVwfSQpQgdMWD/YIwjVAqv/qFuxdF6Kmh4zx6CCiC0H63lhbJqaHVO
rSU3lIW+vaHU6rcMSzyd6BIA8F+sDeGscGNz9395nzIlQnQFgCi/vcEkllgVsRch
6YlL2weIZ/QVrXA+L02FO8K32/6YaCOJ4XQP3vTFhGMpG8zLB8kApKnXwiJPZ9d3
7CAFYd4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIGizCCBXOgAwIBAgIEO0XlaDANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJF
UzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJ
R1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwHhcN
MDEwNzA2MTYyMjQ3WhcNMjEwNzAxMTUyMjQ3WjBoMQswCQYDVQQGEwJFUzEfMB0G
A1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0GA1UECxMGUEtJR1ZBMScw
JQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVuY2lhbmEwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGKqtXETcvIorKA3Qdyu0togu8M1JAJke+
WmmmO3I2F0zo37i7L3bhQEZ0ZQKQUgi0/6iMweDHiVYQOTPvaLRfX9ptI6GJXiKj
SgbwJ/BXufjpTjJ3Cj9BZPPrZe52/lSqfR0grvPXdMIKX/UIKFIIzFVd0g/bmoGl
u6GzwZTNVOAydTGRGmKy3nXiz0+J2ZGQD0EbtFpKd71ng+CT516nDOeB0/RSrFOy
A8dEJvt55cs0YFAQexvba9dHq198aMpunUEDEO5rmXteJajCq+TA81yc477OMUxk
Hl6AovWDfgzWyoxVjr7gvkkHD6MkQXpYHYTqWBLI4bft75PelAgxAgMBAAGjggM7
MIIDNzAyBggrBgEFBQcBAQQmMCQwIgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLnBr
aS5ndmEuZXMwEgYDVR0TAQH/BAgwBgEB/wIBAjCCAjQGA1UdIASCAiswggInMIIC
IwYKKwYBBAG/VQIBADCCAhMwggHoBggrBgEFBQcCAjCCAdoeggHWAEEAdQB0AG8A
cgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEAYwBpAPMAbgAgAFIA
YQDtAHoAIABkAGUAIABsAGEAIABHAGUAbgBlAHIAYQBsAGkAdABhAHQAIABWAGEA
bABlAG4AYwBpAGEAbgBhAC4ADQAKAEwAYQAgAEQAZQBjAGwAYQByAGEAYwBpAPMA
bgAgAGQAZQAgAFAAcgDhAGMAdABpAGMAYQBzACAAZABlACAAQwBlAHIAdABpAGYA
aQBjAGEAYwBpAPMAbgAgAHEAdQBlACAAcgBpAGcAZQAgAGUAbAAgAGYAdQBuAGMA
aQBvAG4AYQBtAGkAZQBuAHQAbwAgAGQAZQAgAGwAYQAgAHAAcgBlAHMAZQBuAHQA
ZQAgAEEAdQB0AG8AcgBpAGQAYQBkACAAZABlACAAQwBlAHIAdABpAGYAaQBjAGEA
YwBpAPMAbgAgAHMAZQAgAGUAbgBjAHUAZQBuAHQAcgBhACAAZQBuACAAbABhACAA
ZABpAHIAZQBjAGMAaQDzAG4AIAB3AGUAYgAgAGgAdAB0AHAAOgAvAC8AdwB3AHcA
LgBwAGsAaQAuAGcAdgBhAC4AZQBzAC8AYwBwAHMwJQYIKwYBBQUHAgEWGWh0dHA6
Ly93d3cucGtpLmd2YS5lcy9jcHMwHQYDVR0OBBYEFHs100DSHHgZZu90ECjcPk+y
eAT8MIGVBgNVHSMEgY0wgYqAFHs100DSHHgZZu90ECjcPk+yeAT8oWykajBoMQsw
CQYDVQQGEwJFUzEfMB0GA1UEChMWR2VuZXJhbGl0YXQgVmFsZW5jaWFuYTEPMA0G
A1UECxMGUEtJR1ZBMScwJQYDVQQDEx5Sb290IENBIEdlbmVyYWxpdGF0IFZhbGVu
Y2lhbmGCBDtF5WgwDQYJKoZIhvcNAQEFBQADggEBACRhTvW1yEICKrNcda3Fbcrn
lD+laJWIwVTAEGmiEi8YPyVQqHxK6sYJ2fR1xkDar1CdPaUWu20xxsdzCkj+IHLt
b8zog2EWRpABlUt9jppSCS/2bxzkoXHPjCpaF3ODR00PNvsETUlR4hTJZGH71BTg
9J63NI8KJr2XXPR5OkowGcytT6CYirQxlyric21+eLj4iIlPsSKRZEv1UN4D2+XF
ducTZnV+ZfsBn5OHiJ35Rld8TWCvmHMTI6QgkYH60GFmuH3Rr9ZvHmw96RH9qfmC
IoaZM3Fa6hlXPZHNqcCjbgcTpsnt+GijnsNacgmHKNHEc8RzGF9QdRYxn7fofMM=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr
MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG
A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0
MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp
Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD
QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz
i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8
h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV
MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9
UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni
8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC
h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD
VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB
AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm
KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ
X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr
QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5
pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN
QSdJQO7e5iNEOdyhIta6A/I=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz
MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv
cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz
Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO
0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao
wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj
7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS
8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT
BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg
JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC
NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3
6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/
3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm
D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS
CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR
3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x
GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx
MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg
Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ
iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa
/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ
jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI
HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7
sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w
gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw
KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG
AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L
URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO
H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm
I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY
iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc
f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX
DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy
dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj
YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV
OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr
zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM
VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ
hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO
ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw
awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs
OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3
DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF
coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc
okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8
t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy
1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/
SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY
MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t
dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5
WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD
VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8
9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ
DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9
Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N
QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ
xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G
A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T
AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG
kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr
Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5
Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU
JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot
RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP
MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx
MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV
BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o
Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt
5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s
3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej
vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu
8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw
DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG
MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil
zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/
3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD
FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6
Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y
MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg
TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS
b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS
M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC
UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d
Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p
rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l
pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb
j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC
KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS
/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X
cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH
1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP
px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB
/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7
MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI
eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u
2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS
v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC
wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy
CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e
vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6
Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa
Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL
eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8
FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc
7uzXLg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDujCCAqKgAwIBAgIEAJiWijANBgkqhkiG9w0BAQUFADBVMQswCQYDVQQGEwJO
TDEeMBwGA1UEChMVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSYwJAYDVQQDEx1TdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQTAeFw0wMjEyMTcwOTIzNDlaFw0xNTEy
MTYwOTE1MzhaMFUxCzAJBgNVBAYTAk5MMR4wHAYDVQQKExVTdGFhdCBkZXIgTmVk
ZXJsYW5kZW4xJjAkBgNVBAMTHVN0YWF0IGRlciBOZWRlcmxhbmRlbiBSb290IENB
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmNK1URF6gaYUmHFtvszn
ExvWJw56s2oYHLZhWtVhCb/ekBPHZ+7d89rFDBKeNVU+LCeIQGv33N0iYfXCxw71
9tV2U02PjLwYdjeFnejKScfST5gTCaI+Ioicf9byEGW07l8Y1Rfj+MX94p2i71MO
hXeiD+EwR+4A5zN9RGcaC1Hoi6CeUJhoNFIfLm0B8mBF8jHrqTFoKbt6QZ7GGX+U
tFE5A3+y3qcym7RHjm+0Sq7lr7HcsBthvJly3uSJt3omXdozSVtSnA71iq3DuD3o
BmrC1SoLbHuEvVYFy4ZlkuxEK7COudxwC0barbxjiDn622r+I/q85Ej0ZytqERAh
SQIDAQABo4GRMIGOMAwGA1UdEwQFMAMBAf8wTwYDVR0gBEgwRjBEBgRVHSAAMDww
OgYIKwYBBQUHAgEWLmh0dHA6Ly93d3cucGtpb3ZlcmhlaWQubmwvcG9saWNpZXMv
cm9vdC1wb2xpY3kwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSofeu8Y6R0E3QA
7Jbg0zTBLL9s+DANBgkqhkiG9w0BAQUFAAOCAQEABYSHVXQ2YcG70dTGFagTtJ+k
/rvuFbQvBgwp8qiSpGEN/KtcCFtREytNwiphyPgJWPwtArI5fZlmgb9uXJVFIGzm
eafR2Bwp/MIgJ1HI8XxdNGdphREwxgDS1/PTfLbwMVcoEoJz6TMvplW0C5GUR5z6
u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy
7ZAEwbEpkcUwb8GpcjPM/l0WFywRaed+/sWDCN+83CI6LiBpIzlWYGeQiy52OfsR
iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX
DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291
qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp
uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU
Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE
pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp
5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M
UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN
GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy
5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv
6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK
eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6
B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/
BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov
L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV
HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG
SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS
CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen
5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897
IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK
gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL
+63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL
vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm
bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk
N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC
Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z
ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO
TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh
dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX
DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl
ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv
b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP
cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW
IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX
xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy
KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR
9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az
5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8
6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7
Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP
bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt
BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt
XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd
INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD
U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp
LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8
Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp
gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh
/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw
0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A
fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq
4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR
1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/
QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM
94B7IWcnMFk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs
ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw
MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6
b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj
aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp
Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg
nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1
HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N
Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN
dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0
HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO
BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G
CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU
sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3
4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg
8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K
pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx
EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT
HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs
ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5
MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD
VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy
ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy
dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p
OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2
8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K
Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe
hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk
6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw
DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q
AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI
bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB
ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z
qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd
iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn
0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN
sSi6
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE
FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j
ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js
LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM
BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0
Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy
dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh
cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh
YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg
dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp
bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ
YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT
TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ
9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8
jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW
FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz
ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1
ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L
EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu
L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq
yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC
O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V
um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh
NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9
MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi
U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh
cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk
pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf
OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C
Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT
Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi
HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM
Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w
+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+
Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3
Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B
26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID
AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul
F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC
ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w
ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk
aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0
YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg
c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0
aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93
d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG
CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1
dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF
wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS
Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst
0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc
pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl
CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF
P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK
1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm
KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE
JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ
8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm
fyWl8kgAwKQB2j8=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW
MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1
OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG
A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G
CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ
JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD
vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo
D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/
Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW
RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK
HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN
nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM
0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i
UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9
Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg
TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE
AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL
BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K
2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX
UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl
6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK
9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ
HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI
wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY
XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l
IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo
hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr
so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln
biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF
MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT
d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8
76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+
bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c
6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE
emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd
MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt
MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y
MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y
FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi
aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM
gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB
qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7
lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn
8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov
L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6
45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO
UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5
O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC
bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv
GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a
77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC
hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3
92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp
Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w
ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt
Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE
BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu
IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow
RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY
U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv
Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br
YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF
nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH
6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt
eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/
c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ
MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH
HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf
jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6
5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB
rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU
F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c
wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0
cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB
AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp
WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9
xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ
2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ
IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8
aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X
em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR
dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/
OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+
hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy
tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQXAuFXAvnWUHfV8w/f52oNjANBgkqhkiG9w0BAQUFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMTAeFw0wNTA4MTgxMjA2MjBaFw0yNTA4MTgyMjA2MjBaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAxMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0LmwqAzZuz8h+BvVM5OAFmUgdbI9
m2BtRsiMMW8Xw/qabFbtPMWRV8PNq5ZJkCoZSx6jbVfd8StiKHVFXqrWW/oLJdih
FvkcxC7mlSpnzNApbjyFNDhhSbEAn9Y6cV9Nbc5fuankiX9qUvrKm/LcqfmdmUc/
TilftKaNXXsLmREDA/7n29uj/x2lzZAeAR81sH8A25Bvxn570e56eqeqDFdvpG3F
EzuwpdntMhy0XmeLVNxzh+XTF3xmUHJd1BpYwdnP2IkCb6dJtDZd0KTeByy2dbco
kdaXvij1mB7qWybJvbCXc9qukSbraMH5ORXWZ0sKbU/Lz7DkQnGMU3nn7uHbHaBu
HYwadzVcFh4rUx80i9Fs/PJnB3r1re3WmquhsUvhzDdf/X/NTa64H5xD+SpYVUNF
vJbNcA78yeNmuk6NO4HLFWR7uZToXTNShXEuT46iBhFRyePLoW4xCGQMwtI89Tbo
19AOeCMgkckkKmUpWyL3Ic6DXqTz3kvTaI9GdVyDCW4pa8RwjPWd1yAv/0bSKzjC
L3UcPX7ape8eYIVpQtPM+GP+HkM5haa2Y0EQs3MevNP6yn0WR+Kn1dCjigoIlmJW
bjTb2QK5MHXjBNLnj8KwEUAKrNVxAmKLMb7dxiNYMUJDLXT5xp6mig/p/r+D5kNX
JLrvRjSq1xIBOO0CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwABBgdghXQBUwABMBIGA1UdEwEB/wQIMAYBAf8CAQcwHwYDVR0j
BBgwFoAUAyUv3m+CATpcLNwroWm1Z9SM0/0wHQYDVR0OBBYEFAMlL95vggE6XCzc
K6FptWfUjNP9MA0GCSqGSIb3DQEBBQUAA4ICAQA1EMvspgQNDQ/NwNurqPKIlwzf
ky9NfEBWMXrrpA9gzXrzvsMnjgM+pN0S734edAY8PzHyHHuRMSG08NBsl9Tpl7Ik
Vh5WwzW9iAUPWxAaZOHHgjD5Mq2eUCzneAXQMbFamIp1TpBcahQq4FJHgmDmHtqB
sfsUC1rxn9KVuj7QG9YVHaO+htXbD8BJZLsuUBlL0iT43R4HVtA4oJVwIHaM190e
3p9xxCPvgxNcoyQVTSlAPGrEqdi3pkSlDfTgnXceQHAm/NrZNuR55LU/vJtlvrsR
ls/bxig5OgjOR1tTWsWZ/l2p3e9M1MalrQLmjAcSHm8D0W+go/MpvRLHUKKwf4ip
mXeascClOS5cfGniLLDqN2qk4Vrh9VDlg++luyqI54zb/W1elxmofmZ1a3Hqv7HH
b6D0jqTsNFFbjCYDcKF31QESVwA12yPeDooomf2xEG9L/zgtYE4snOtnta1J7ksf
rK/7DZBaZmBwXarNeNQk7shBoJMBkpxqnvy5JMWzFYJ+vq6VK+uxwNrjAWALXmms
hFZhvnEX/h0TD/7Gh0Xp/jKgGg0TpJRVcaUWi7rKibCyx/yP2FS1k2Kdzs9Z+z0Y
zirLNRWCXf9UIltxUvu3yf5gmwBBZPCqKuy2QkPOiWaByIufOVQDJdMWNY6E0F/6
MBr1mmz0DlP5OlvRHA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF2TCCA8GgAwIBAgIQHp4o6Ejy5e/DfEoeWhhntjANBgkqhkiG9w0BAQsFADBk
MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0
YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxGzAZBgNVBAMTElN3aXNzY29tIFJvb3Qg
Q0EgMjAeFw0xMTA2MjQwODM4MTRaFw0zMTA2MjUwNzM4MTRaMGQxCzAJBgNVBAYT
AmNoMREwDwYDVQQKEwhTd2lzc2NvbTElMCMGA1UECxMcRGlnaXRhbCBDZXJ0aWZp
Y2F0ZSBTZXJ2aWNlczEbMBkGA1UEAxMSU3dpc3Njb20gUm9vdCBDQSAyMIICIjAN
BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlUJOhJ1R5tMJ6HJaI2nbeHCOFvEr
jw0DzpPMLgAIe6szjPTpQOYXTKueuEcUMncy3SgM3hhLX3af+Dk7/E6J2HzFZ++r
0rk0X2s682Q2zsKwzxNoysjL67XiPS4h3+os1OD5cJZM/2pYmLcX5BtS5X4HAB1f
2uY+lQS3aYg5oUFgJWFLlTloYhyxCwWJwDaCFCE/rtuh/bxvHGCGtlOUSbkrRsVP
ACu/obvLP+DHVxxX6NZp+MEkUp2IVd3Chy50I9AU/SpHWrumnf2U5NGKpV+GY3aF
y6//SSj8gO1MedK75MDvAe5QQQg1I3ArqRa0jG6F6bYRzzHdUyYb3y1aSgJA/MTA
tukxGggo5WDDH8SQjhBiYEQN7Aq+VRhxLKX0srwVYv8c474d2h5Xszx+zYIdkeNL
6yxSNLCK/RJOlrDrcH+eOfdmQrGrrFLadkBXeyq96G4DsguAhYidDMfCd7Camlf0
uPoTXGiTOmekl9AbmbeGMktg2M7v0Ax/lZ9vh0+Hio5fCHyqW/xavqGRn1V9TrAL
acywlKinh/LTSlDcX3KwFnUey7QYYpqwpzmqm59m2I2mbJYV4+by+PGDYmy7Velh
k6M99bFXi08jsJvllGov34zflVEpYKELKeRcVVi3qPyZ7iVNTA6z00yPhOgpD/0Q
VAKFyPnlw4vP5w8CAwEAAaOBhjCBgzAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0hBBYw
FDASBgdghXQBUwIBBgdghXQBUwIBMBIGA1UdEwEB/wQIMAYBAf8CAQcwHQYDVR0O
BBYEFE0mICKJS9PVpAqhb97iEoHF8TwuMB8GA1UdIwQYMBaAFE0mICKJS9PVpAqh
b97iEoHF8TwuMA0GCSqGSIb3DQEBCwUAA4ICAQAyCrKkG8t9voJXiblqf/P0wS4R
fbgZPnm3qKhyN2abGu2sEzsOv2LwnN+ee6FTSA5BesogpxcbtnjsQJHzQq0Qw1zv
/2BZf82Fo4s9SBwlAjxnffUy6S8w5X2lejjQ82YqZh6NM4OKb3xuqFp1mrjX2lhI
REeoTPpMSQpKwhI3qEAMw8jh0FcNlzKVxzqfl9NX+Ave5XLzo9v/tdhZsnPdTSpx
srpJ9csc1fV5yJmz/MFMdOO0vSk3FQQoHt5FRnDsr7p4DooqzgB53MBfGWcsa0vv
aGgLQ+OswWIJ76bdZWGgr4RVSJFSHMYlkSrQwSIjYVmvRRGFHQEkNI/Ps/8XciAT
woCqISxxOQ7Qj1zB09GOInJGTB2Wrk9xseEFKZZZ9LuedT3PDTcNYtsmjGOpI99n
Bjx8Oto0QuFmtEYE3saWmA9LSHokMnWRn6z3aOkquVVlzl1h0ydw2Df+n7mvoC5W
t6NlUe07qxS/TFED6F+KBZvuim6c779o+sjaC+NCydAXFJy3SuCvkychVSa1ZC+N
8f+mQAWFBVzKBxlcCxMoTFh/wqXvRdpg065lYZ1Tg3TCrvJcwhbtkj6EPnNgiLx2
9CzP0H1907he0ZESEOnN3col49XtmS++dYFLJPlFRpTJKSFTnCZFqhMX5OfNeOI5
wSsSnqaeG8XmDtkx2Q==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF4DCCA8igAwIBAgIRAPL6ZOJ0Y9ON/RAdBB92ylgwDQYJKoZIhvcNAQELBQAw
ZzELMAkGA1UEBhMCY2gxETAPBgNVBAoTCFN3aXNzY29tMSUwIwYDVQQLExxEaWdp
dGFsIENlcnRpZmljYXRlIFNlcnZpY2VzMR4wHAYDVQQDExVTd2lzc2NvbSBSb290
IEVWIENBIDIwHhcNMTEwNjI0MDk0NTA4WhcNMzEwNjI1MDg0NTA4WjBnMQswCQYD
VQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0YWwgQ2Vy
dGlmaWNhdGUgU2VydmljZXMxHjAcBgNVBAMTFVN3aXNzY29tIFJvb3QgRVYgQ0Eg
MjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMT3HS9X6lds93BdY7Bx
UglgRCgzo3pOCvrY6myLURYaVa5UJsTMRQdBTxB5f3HSek4/OE6zAMaVylvNwSqD
1ycfMQ4jFrclyxy0uYAyXhqdk/HoPGAsp15XGVhRXrwsVgu42O+LgrQ8uMIkqBPH
oCE2G3pXKSinLr9xJZDzRINpUKTk4RtiGZQJo/PDvO/0vezbE53PnUgJUmfANykR
HvvSEaeFGHR55E+FFOtSN+KxRdjMDUN/rhPSays/p8LiqG12W0OfvrSdsyaGOx9/
5fLoZigWJdBLlzin5M8J0TbDC77aO0RYjb7xnglrPvMyxyuHxuxenPaHZa0zKcQv
idm5y8kDnftslFGXEBuGCxobP/YCfnvUxVFkKJ3106yDgYjTdLRZncHrYTNaRdHL
OdAGalNgHa/2+2m8atwBz735j9m9W8E6X47aD0upm50qKGsaCnw8qyIL5XctcfaC
NYGu+HuB5ur+rPQam3Rc6I8k9l2dRsQs0h4rIWqDJ2dVSqTjyDKXZpBy2uPUZC5f
46Fq9mDU5zXNysRojddxyNMkM3OxbPlq4SjbX8Y96L5V5jcb7STZDxmPX2MYWFCB
UWVv8p9+agTnNCRxunZLWB4ZvRVgRaoMEkABnRDixzgHcgplwLa7JSnaFp6LNYth
7eVxV4O1PHGf40+/fh6Bn0GXAgMBAAGjgYYwgYMwDgYDVR0PAQH/BAQDAgGGMB0G
A1UdIQQWMBQwEgYHYIV0AVMCAgYHYIV0AVMCAjASBgNVHRMBAf8ECDAGAQH/AgED
MB0GA1UdDgQWBBRF2aWBbj2ITY1x0kbBbkUe88SAnTAfBgNVHSMEGDAWgBRF2aWB
bj2ITY1x0kbBbkUe88SAnTANBgkqhkiG9w0BAQsFAAOCAgEAlDpzBp9SSzBc1P6x
XCX5145v9Ydkn+0UjrgEjihLj6p7jjm02Vj2e6E1CqGdivdj5eu9OYLU43otb98T
PLr+flaYC/NUn81ETm484T4VvwYmneTwkLbUwp4wLh/vx3rEUMfqe9pQy3omywC0
Wqu1kx+AiYQElY2NfwmTv9SoqORjbdlk5LgpWgi/UOGED1V7XwgiG/W9mR4U9s70
WBCCswo9GcG/W6uqmdjyMb3lOGbcWAXH7WMaLgqXfIeTK7KK4/HsGOV1timH59yL
Gn602MnTihdsfSlEvoqq9X46Lmgxk7lq2prg2+kupYTNHAq4Sgj5nPFhJpiTt3tm
7JFe3VE/23MPrQRYCd0EApUKPtN236YQHoA96M2kZNEzx5LH4k5E4wnJTsJdhw4S
nr8PyQUQ3nqjsTzyP6WqJ3mtMX0f/fwZacXduT98zca0wjAefm6S139hdlqP65VN
vBFuIXxZN5nQBrz5Bm0yFqXZaajh3DyAHmBR3NdUIR7KYndP+tiPsys6DXhyyWhB
WkdKwqPrGtcKqzwyVcgKEZzfdNbwQBUdyLmPtTbFr/giuMod89a2GQ+fYWVq6nTI
fI/DT11lgh/ZDYnadXL77/FHZxOzyNEZiCcmmpl5fx7kLD977vHeTYuWl8PVP3wb
I+2ksx0WckNLIOFZfsLorSa/ovc=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd
AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC
FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi
1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq
jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ
wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/
WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy
NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC
uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw
IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6
g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN
9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP
BSeOE6Fuwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx
KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd
BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl
YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1
OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy
aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50
ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN
8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/
RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4
hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5
ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM
EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj
QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1
A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy
WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ
1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30
6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT
91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml
e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p
TpPDpFQUWw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEPTCCAyWgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBvzE/MD0GA1UEAww2VMOc
UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx
c8SxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMV4wXAYDVQQKDFVUw5xS
S1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kg
SGl6bWV0bGVyaSBBLsWeLiAoYykgQXJhbMSxayAyMDA3MB4XDTA3MTIyNTE4Mzcx
OVoXDTE3MTIyMjE4MzcxOVowgb8xPzA9BgNVBAMMNlTDnFJLVFJVU1QgRWxla3Ry
b25payBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsTELMAkGA1UEBhMC
VFIxDzANBgNVBAcMBkFua2FyYTFeMFwGA1UECgxVVMOcUktUUlVTVCBCaWxnaSDE
sGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkgQS7F
ni4gKGMpIEFyYWzEsWsgMjAwNzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAKu3PgqMyKVYFeaK7yc9SrToJdPNM8Ig3BnuiD9NYvDdE3ePYakqtdTyuTFY
KTsvP2qcb3N2Je40IIDu6rfwxArNK4aUyeNgsURSsloptJGXg9i3phQvKUmi8wUG
+7RP2qFsmmaf8EMJyupyj+sA1zU511YXRxcw9L6/P8JorzZAwan0qafoEGsIiveG
HtyaKhUG9qPw9ODHFNRRf8+0222vR5YXm3dx2KdxnSQM9pQ/hTEST7ruToK4uT6P
IzdezKKqdfcYbwnTrqdUKDT74eA7YH2gvnmJhsifLfkKS8RQouf9eRbHegsYz85M
733WB2+Y8a+xwXrXgTW4qhe04MsCAwEAAaNCMEAwHQYDVR0OBBYEFCnFkKslrxHk
Yb+j/4hhkeYO/pyBMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQAQDdr4Ouwo0RSVgrESLFF6QSU2TJ/sPx+EnWVUXKgW
AkD6bho3hO9ynYYKVZ1WKKxmLNA6VpM0ByWtCLCPyA8JWcqdmBzlVPi5RX9ql2+I
aE1KBiY3iAIOtsbWcpnOa3faYjGkVh+uX4132l32iPwa2Z61gfAyuOOI0JzzaqC5
mxRZNTZPz/OOXl0XrRWV2N2y1RVuAE6zS89mlOTgzbUF2mNXi+WzqtvALhyQRNsa
XRik7r4EW5nVcV9VZWRi1aKbBFmGyGJ353yCRWo9F7/snXUMrqNvWtMvmDb08PUZ
qxFdyKbjKlhqQgnDvZImZjINXQhVdP+MmNAKpoRq0Tl9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx
EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT
VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5
NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT
B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF
10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz
0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh
MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH
zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc
46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2
yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi
laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP
oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA
BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE
qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm
4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL
1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn
LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF
H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo
RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+
nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh
15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW
6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW
nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j
wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz
aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy
KwbQBM0=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES
MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU
V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz
WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO
LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE
AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH
K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX
RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z
rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx
3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq
hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC
MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls
XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D
lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn
aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ
YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/
MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj
YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow
PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp
Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR
IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q
gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy
yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts
F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2
jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx
ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC
VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK
YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH
EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN
Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud
DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE
MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK
UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ
TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf
qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK
ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE
JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7
hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1
EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm
nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX
udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz
ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe
LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl
pYYsfPQS
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw
NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv
b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD
VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F
VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1
7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X
Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+
/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs
81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm
dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe
Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu
sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4
pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs
slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ
arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD
VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG
9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl
dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx
0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj
TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed
Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7
Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI
OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7
vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW
t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn
HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx
SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF
MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL
ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx
MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc
MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+
AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH
iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj
vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA
0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB
OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/
BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E
FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01
GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW
zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4
1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE
f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F
jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN
ZetX2fNXlrtIzYE=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFzCCA/+gAwIBAgIBETANBgkqhkiG9w0BAQUFADCCASsxCzAJBgNVBAYTAlRS
MRgwFgYDVQQHDA9HZWJ6ZSAtIEtvY2FlbGkxRzBFBgNVBAoMPlTDvHJraXllIEJp
bGltc2VsIHZlIFRla25vbG9qaWsgQXJhxZ90xLFybWEgS3VydW11IC0gVMOcQsSw
VEFLMUgwRgYDVQQLDD9VbHVzYWwgRWxla3Ryb25payB2ZSBLcmlwdG9sb2ppIEFy
YcWfdMSxcm1hIEVuc3RpdMO8c8O8IC0gVUVLQUUxIzAhBgNVBAsMGkthbXUgU2Vy
dGlmaWthc3lvbiBNZXJrZXppMUowSAYDVQQDDEFUw5xCxLBUQUsgVUVLQUUgS8O2
ayBTZXJ0aWZpa2EgSGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSAtIFPDvHLDvG0gMzAe
Fw0wNzA4MjQxMTM3MDdaFw0xNzA4MjExMTM3MDdaMIIBKzELMAkGA1UEBhMCVFIx
GDAWBgNVBAcMD0dlYnplIC0gS29jYWVsaTFHMEUGA1UECgw+VMO8cmtpeWUgQmls
aW1zZWwgdmUgVGVrbm9sb2ppayBBcmHFn3TEsXJtYSBLdXJ1bXUgLSBUw5xCxLBU
QUsxSDBGBgNVBAsMP1VsdXNhbCBFbGVrdHJvbmlrIHZlIEtyaXB0b2xvamkgQXJh
xZ90xLFybWEgRW5zdGl0w7xzw7wgLSBVRUtBRTEjMCEGA1UECwwaS2FtdSBTZXJ0
aWZpa2FzeW9uIE1lcmtlemkxSjBIBgNVBAMMQVTDnELEsFRBSyBVRUtBRSBLw7Zr
IFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIC0gU8O8csO8bSAzMIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAim1L/xCIOsP2fpTo6iBkcK4h
gb46ezzb8R1Sf1n68yJMlaCQvEhOEav7t7WNeoMojCZG2E6VQIdhn8WebYGHV2yK
O7Rm6sxA/OOqbLLLAdsyv9Lrhc+hDVXDWzhXcLh1xnnRFDDtG1hba+818qEhTsXO
fJlfbLm4IpNQp81McGq+agV/E5wrHur+R84EpW+sky58K5+eeROR6Oqeyjh1jmKw
lZMq5d/pXpduIF9fhHpEORlAHLpVK/swsoHvhOPc7Jg4OQOFCKlUAwUp8MmPi+oL
hmUZEdPpCSPeaJMDyTYcIW7OjGbxmTDY17PDHfiBLqi9ggtm/oLL4eAagsNAgQID
AQABo0IwQDAdBgNVHQ4EFgQUvYiHyY/2pAoLquvF/pEjnatKijIwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAB18+kmP
NOm3JpIWmgV050vQbTlswyb2zrgxvMTfvCr4N5EY3ATIZJkrGG2AA1nJrvhY0D7t
wyOfaTyGOBye79oneNGEN3GKPEs5z35FBtYt2IpNeBLWrcLTy9LQQfMmNkqblWwM
7uXRQydmwYj3erMgbOqwaSvHIOgMA8RBBZniP+Rr+KCGgceExh/VS4ESshYhLBOh
gLJeDEoTniDYYkCrkOpkSi+sDQESeUWoL4cZaMjihccwsnX5OD+ywJO0a+IDRM5n
oN+J1q2MdqMTw5RhK2vZbMEHCiIHhWyFJEapvj+LeISCfiQMnf2BN+MlqO02TpUs
yZyQ2uypQjyttgI=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE
BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn
aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg
QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0
MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD
VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom
/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR
Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3
4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z
5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0
hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID
AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX
SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l
VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf
peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF
Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW
+qtB4Uu2NQvAmxU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG
EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp
IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB
LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI
aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx
NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV
BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2
ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs
ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x
eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9
+bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA
z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p
u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p
lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB
AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq
FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC
QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy
o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID
gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm
9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG
tAuYSyher4hYyw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL
MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl
eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT
JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx
MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT
Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg
VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm
aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo
I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng
o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G
A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD
VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB
zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW
RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
jjxDah2nGN59PRbxYvnKkKj9
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB
lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug
Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho
dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt
SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG
A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe
MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v
d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh
cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn
0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ
M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a
MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd
oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI
DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy
oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD
VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0
dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy
bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF
BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM
//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli
CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE
CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t
3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS
KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp
U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg
SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln
biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5
IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm
GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve
fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw
AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ
aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj
aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW
kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC
4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga
FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL
MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW
ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln
biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp
U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y
aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1
nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex
t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz
SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG
BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+
rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/
NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH
BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy
aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv
MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE
p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y
5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK
WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ
4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N
hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB
vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W
ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX
MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0
IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y
IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh
bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF
9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH
H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H
LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN
/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT
rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw
WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs
exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud
DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4
sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+
seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz
4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+
BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR
lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3
7M2CYfE45k+XmCpajQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw
CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl
cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu
LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT
aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD
VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT
aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ
bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu
IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg
LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b
N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t
KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu
kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm
CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ
Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu
imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te
2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe
DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC
/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p
F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt
TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMCVVMx
IDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxs
cyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9v
dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcxMjEzMTcwNzU0WhcNMjIxMjE0
MDAwNzU0WjCBhTELMAkGA1UEBhMCVVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdl
bGxzU2VjdXJlMRwwGgYDVQQLDBNXZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQD
DC1XZWxsc1NlY3VyZSBQdWJsaWMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDub7S9eeKPCCGeOARBJe+r
WxxTkqxtnt3CxC5FlAM1iGd0V+PfjLindo8796jE2yljDpFoNoqXjopxaAkH5OjU
Dk/41itMpBb570OYj7OeUt9tkTmPOL13i0Nj67eT/DBMHAGTthP796EfvyXhdDcs
HqRePGj4S78NuR4uNuip5Kf4D8uCdXw1LSLWwr8L87T8bJVhHlfXBIEyg1J55oNj
z7fLY4sR4r1e6/aN7ZVyKLSsEmLpSjPmgzKuBXWVvYSV2ypcm44uDLiBK0HmOFaf
SZtsdvqKXfcBeYF8wYNABf5x/Qw/zE5gCQ5lRxAvAcAFP4/4s0HvWkJ+We/Slwxl
AgMBAAGjggE0MIIBMDAPBgNVHRMBAf8EBTADAQH/MDkGA1UdHwQyMDAwLqAsoCqG
KGh0dHA6Ly9jcmwucGtpLndlbGxzZmFyZ28uY29tL3dzcHJjYS5jcmwwDgYDVR0P
AQH/BAQDAgHGMB0GA1UdDgQWBBQmlRkQ2eihl5H/3BnZtQQ+0nMKajCBsgYDVR0j
BIGqMIGngBQmlRkQ2eihl5H/3BnZtQQ+0nMKaqGBi6SBiDCBhTELMAkGA1UEBhMC
VVMxIDAeBgNVBAoMF1dlbGxzIEZhcmdvIFdlbGxzU2VjdXJlMRwwGgYDVQQLDBNX
ZWxscyBGYXJnbyBCYW5rIE5BMTYwNAYDVQQDDC1XZWxsc1NlY3VyZSBQdWJsaWMg
Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHmCAQEwDQYJKoZIhvcNAQEFBQADggEB
ALkVsUSRzCPIK0134/iaeycNzXK7mQDKfGYZUMbVmO2rvwNa5U3lHshPcZeG1eMd
/ZDJPHV3V3p9+N701NX3leZ0bh08rnyd2wIDBSxxSyU+B+NemvVmFymIGjifz6pB
A4SXa5M4esowRBskRDPQ5NHcKDj0E0M1NSljqHyita04pO2t/caaH/+Xc/77szWn
k4bGdpEA5qxRFsQnMlzbc9qlk1eOPm01JghZ1edE13YgY+esE2fDbbFwRnzVlhE9
iW9dqKHrjQrawx0zbKPqZxmamX9LPYNRKh3KL4YMon4QLSvUFpULB6ouFJJJtylv
2G0xffX8oRAHh84vWdw+WNs=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV
BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw
MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX
b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp
dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN
rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U
fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc
f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2
ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M
x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR
aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch
zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar
uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K
mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA
Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv
HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H
EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1
LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ
MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e
JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN
g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp
dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab
R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ
PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce
xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+
J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl
OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT
ee5Ehr7XHuQe+w==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG
MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV
BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw
MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl
ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r
D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1
9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf
v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk
UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L
NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb
+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V
qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K
yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G
AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK
J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC
AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O
BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4
WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6
yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj
/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6
jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2
ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX
X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n
FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D
u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l
O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le
ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1
2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB
gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk
MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY
UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx
NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3
dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy
dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB
dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6
38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP
KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q
DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4
qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa
JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi
PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P
BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs
jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0
eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD
ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR
vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt
qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa
IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy
i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ
O+7ETPTsJ3xCwnR8gooJybQDJbw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT
AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD
QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP
MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do
0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ
UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d
RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ
OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv
JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C
AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O
BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ
LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY
MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ
44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I
Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw
i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN
9u6wWk5JRFRYX0KD
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe
MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0
ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe
Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw
IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL
SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF
AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH
SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh
ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X
DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1
TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ
fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA
sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU
WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS
nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH
dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip
NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC
AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF
MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH
ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB
uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl
PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP
JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/
gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2
j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6
5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB
o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS
/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z
Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE
W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D
hNQ+IIX3Sj0rnP0qCglN6oH4EZw=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB
qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV
BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw
NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j
LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG
A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl
IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs
W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta
3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk
6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6
Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J
NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA
MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP
r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU
DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz
YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX
xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2
/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/
LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7
jVaMaA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp
IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi
BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw
MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh
d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig
YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v
dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/
BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6
papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E
BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K
DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3
KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox
XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
MdRAGmI0Nj81Aa6sY6A=
-----END CERTIFICATE-----
src/Http/Transport/StreamTransport.php000064400000016760152177723700014154 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http\Transport;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;

/**
 * HTTP transport class for using PHP streams.
 *
 * @since  1.7.3
 */
class StreamTransport implements TransportInterface
{
	/**
	 * @var    Registry  The client options.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  Client options object.
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function __construct(Registry $options)
	{
		// Verify that URLs can be used with fopen();
		if (!ini_get('allow_url_fopen'))
		{
			throw new \RuntimeException('Cannot use a stream transport when "allow_url_fopen" is disabled.');
		}

		// Verify that fopen() is available.
		if (!self::isSupported())
		{
			throw new \RuntimeException('Cannot use a stream transport when fopen() is not available or "allow_url_fopen" is disabled.');
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a HttpResponse object with the response.
	 *
	 * @param   string   $method     The HTTP method for sending the request.
	 * @param   Uri      $uri        The URI to the resource to request.
	 * @param   mixed    $data       Either an associative array or a string to be sent with the request.
	 * @param   array    $headers    An array of request headers to send with the request.
	 * @param   integer  $timeout    Read timeout in seconds.
	 * @param   string   $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function request($method, Uri $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		// Create the stream context options array with the required method offset.
		$options = array('method' => strtoupper($method));

		// If data exists let's encode it and make sure our Content-Type header is set.
		if (isset($data))
		{
			// If the data is a scalar value simply add it to the stream context options.
			if (is_scalar($data))
			{
				$options['content'] = $data;
			}
			// Otherwise we need to encode the value first.
			else
			{
				$options['content'] = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			$headers['Content-Length'] = strlen($options['content']);
		}

		// If an explicit timeout is given user it.
		if (isset($timeout))
		{
			$options['timeout'] = (int) $timeout;
		}

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$options['user_agent'] = $userAgent;
		}

		// Ignore HTTP errors so that we can capture them.
		$options['ignore_errors'] = 1;

		// Follow redirects.
		$options['follow_location'] = (int) $this->options->get('follow_location', 1);

		// Set any custom transport options
		foreach ($this->options->get('transport.stream', array()) as $key => $value)
		{
			$options[$key] = $value;
		}

		// Add the proxy configuration, if any.
		$config = \JFactory::getConfig();

		if ($config->get('proxy_enable'))
		{
			$options['proxy'] = $config->get('proxy_host') . ':' . $config->get('proxy_port');
			$options['request_fulluri'] = true;

			// Put any required authorization into the headers array to be handled later
			// TODO: do we need to support any auth type other than Basic?
			if ($user = $config->get('proxy_user'))
			{
				$auth = base64_encode($config->get('proxy_user') . ':' . $config->get('proxy_pass'));

				$headers['Proxy-Authorization'] = 'Basic ' . $auth;
			}
		}

		// Build the headers string for the request.
		$headerEntries = array();

		if (isset($headers))
		{
			foreach ($headers as $key => $value)
			{
				$headerEntries[] = $key . ': ' . $value;
			}

			// Add the headers string into the stream context options array.
			$options['header'] = implode("\r\n", $headerEntries);
		}

		// Get the current context options.
		$contextOptions = stream_context_get_options(stream_context_get_default());

		// Add our options to the current ones, if any.
		$contextOptions['http'] = isset($contextOptions['http']) ? array_merge($contextOptions['http'], $options) : $options;

		// Create the stream context for the request.
		$context = stream_context_create(
			array(
				'http' => $options,
				'ssl' => array(
					'verify_peer'   => true,
					'cafile'        => $this->options->get('stream.certpath', __DIR__ . '/cacert.pem'),
					'verify_depth'  => 5,
				),
			)
		);

		// Authentification, if needed
		if ($this->options->get('userauth') && $this->options->get('passwordauth'))
		{
			$uri->setUser($this->options->get('userauth'));
			$uri->setPass($this->options->get('passwordauth'));
		}

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		// Open the stream for reading.
		$stream = @fopen((string) $uri, 'r', false, $context);

		if (!$stream)
		{
			if (!$php_errormsg)
			{
				// Error but nothing from php? Create our own
				$php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
			}

			// Restore error tracking to give control to the exception handler
			ini_set('track_errors', $track_errors);

			throw new \RuntimeException($php_errormsg);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		// Get the metadata for the stream, including response headers.
		$metadata = stream_get_meta_data($stream);

		// Get the contents from the stream.
		$content = stream_get_contents($stream);

		// Close the stream.
		fclose($stream);

		if (isset($metadata['wrapper_data']['headers']))
		{
			$headers = $metadata['wrapper_data']['headers'];
		}
		elseif (isset($metadata['wrapper_data']))
		{
			$headers = $metadata['wrapper_data'];
		}
		else
		{
			$headers = array();
		}

		return $this->getResponse($headers, $content);
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   array   $headers  The response headers as an array.
	 * @param   string  $body     The response body as a string.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \UnexpectedValueException
	 */
	protected function getResponse(array $headers, $body)
	{
		// Create the response object.
		$return = new Response;

		// Set the body for the response.
		$return->body = $body;

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
		$code = $matches[0];

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}

		// No valid response code was detected.
		else
		{
			throw new \UnexpectedValueException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to check if http transport stream available for use
	 *
	 * @return  boolean  true if available else false
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('fopen') && is_callable('fopen') && ini_get('allow_url_fopen');
	}
}
src/Http/Transport/SocketTransport.php000064400000020607152177723700014144 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http\Transport;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;

/**
 * HTTP transport class for using sockets directly.
 *
 * @since  1.7.3
 */
class SocketTransport implements TransportInterface
{
	/**
	 * @var    array  Reusable socket connections.
	 * @since  1.7.3
	 */
	protected $connections;

	/**
	 * @var    Registry  The client options.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  Client options object.
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function __construct(Registry $options)
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('Cannot use a socket transport when fsockopen() is not available.');
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a HttpResponse object with the response.
	 *
	 * @param   string   $method     The HTTP method for sending the request.
	 * @param   Uri      $uri        The URI to the resource to request.
	 * @param   mixed    $data       Either an associative array or a string to be sent with the request.
	 * @param   array    $headers    An array of request headers to send with the request.
	 * @param   integer  $timeout    Read timeout in seconds.
	 * @param   string   $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function request($method, Uri $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		$connection = $this->connect($uri, $timeout);

		// Make sure the connection is alive and valid.
		if (is_resource($connection))
		{
			// Make sure the connection has not timed out.
			$meta = stream_get_meta_data($connection);

			if ($meta['timed_out'])
			{
				throw new \RuntimeException('Server connection timed out.');
			}
		}
		else
		{
			throw new \RuntimeException('Not connected to server.');
		}

		// Get the request path from the URI object.
		$path = $uri->toString(array('path', 'query'));

		// If we have data to send make sure our request is setup for it.
		if (!empty($data))
		{
			// If the data is not a scalar value encode it to be sent with the request.
			if (!is_scalar($data))
			{
				$data = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			$headers['Content-Length'] = strlen($data);
		}

		// Build the request payload.
		$request = array();
		$request[] = strtoupper($method) . ' ' . ((empty($path)) ? '/' : $path) . ' HTTP/1.0';
		$request[] = 'Host: ' . $uri->getHost();

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$headers['User-Agent'] = $userAgent;
		}

		// If there are custom headers to send add them to the request payload.
		if (is_array($headers))
		{
			foreach ($headers as $k => $v)
			{
				$request[] = $k . ': ' . $v;
			}
		}

		// Set any custom transport options
		foreach ($this->options->get('transport.socket', array()) as $value)
		{
			$request[] = $value;
		}

		// If we have data to send add it to the request payload.
		if (!empty($data))
		{
			$request[] = null;
			$request[] = $data;
		}

		// Authentification, if needed
		if ($this->options->get('userauth') && $this->options->get('passwordauth'))
		{
			$request[] = 'Authorization: Basic ' . base64_encode($this->options->get('userauth') . ':' . $this->options->get('passwordauth'));
		}

		// Send the request to the server.
		fwrite($connection, implode("\r\n", $request) . "\r\n\r\n");

		// Get the response data from the server.
		$content = '';

		while (!feof($connection))
		{
			$content .= fgets($connection, 4096);
		}

		$content = $this->getResponse($content);

		// Follow Http redirects
		if ($content->code >= 301 && $content->code < 400 && isset($content->headers['Location']))
		{
			return $this->request($method, new Uri($content->headers['Location']), $data, $headers, $timeout, $userAgent);
		}

		return $content;
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   string  $content  The complete server response, including headers.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \UnexpectedValueException
	 */
	protected function getResponse($content)
	{
		// Create the response object.
		$return = new Response;

		if (empty($content))
		{
			throw new \UnexpectedValueException('No content in response.');
		}

		// Split the response into headers and body.
		$response = explode("\r\n\r\n", $content, 2);

		// Get the response headers as an array.
		$headers = explode("\r\n", $response[0]);

		// Set the body for the response.
		$return->body = empty($response[1]) ? '' : $response[1];

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);
		$code = $matches[0];

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}

		// No valid response code was detected.
		else
		{
			throw new \UnexpectedValueException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to connect to a server and get the resource.
	 *
	 * @param   Uri      $uri      The URI to connect with.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  resource  Socket connection resource.
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	protected function connect(Uri $uri, $timeout = null)
	{
		$errno = null;
		$err = null;

		// Get the host from the uri.
		$host = ($uri->isSsl()) ? 'ssl://' . $uri->getHost() : $uri->getHost();

		// If the port is not explicitly set in the URI detect it.
		if (!$uri->getPort())
		{
			$port = ($uri->getScheme() == 'https') ? 443 : 80;
		}

		// Use the set port.
		else
		{
			$port = $uri->getPort();
		}

		// Build the connection key for resource memory caching.
		$key = md5($host . $port);

		// If the connection already exists, use it.
		if (!empty($this->connections[$key]) && is_resource($this->connections[$key]))
		{
			// Connection reached EOF, cannot be used anymore
			$meta = stream_get_meta_data($this->connections[$key]);

			if ($meta['eof'])
			{
				if (!fclose($this->connections[$key]))
				{
					throw new \RuntimeException('Cannot close connection');
				}
			}

			// Make sure the connection has not timed out.
			elseif (!$meta['timed_out'])
			{
				return $this->connections[$key];
			}
		}

		if (!is_numeric($timeout))
		{
			$timeout = ini_get('default_socket_timeout');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		// PHP sends a warning if the uri does not exists; we silence it and throw an exception instead.
		// Attempt to connect to the server
		$connection = @fsockopen($host, $port, $errno, $err, $timeout);

		if (!$connection)
		{
			if (!$php_errormsg)
			{
				// Error but nothing from php? Create our own
				$php_errormsg = sprintf('Could not connect to resource: %s', $uri, $err, $errno);
			}

			// Restore error tracking to give control to the exception handler
			ini_set('track_errors', $track_errors);

			throw new \RuntimeException($php_errormsg);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		// Since the connection was successful let's store it in case we need to use it later.
		$this->connections[$key] = $connection;

		// If an explicit timeout is set, set it.
		if (isset($timeout))
		{
			stream_set_timeout($this->connections[$key], (int) $timeout);
		}

		return $this->connections[$key];
	}

	/**
	 * Method to check if http transport socket available for use
	 *
	 * @return  boolean   True if available else false
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('fsockopen') && is_callable('fsockopen') && !\JFactory::getConfig()->get('proxy_enable');
	}
}
src/Http/Transport/CurlTransport.php000064400000024024152177723700013616 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http\Transport;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Http\Response;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;

/**
 * HTTP transport class for using cURL.
 *
 * @since  1.7.3
 */
class CurlTransport implements TransportInterface
{
	/**
	 * @var    Registry  The client options.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * Constructor. CURLOPT_FOLLOWLOCATION must be disabled when open_basedir or safe_mode are enabled.
	 *
	 * @param   Registry  $options  Client options object.
	 *
	 * @link    https://www.php.net/manual/en/function.curl-setopt.php
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function __construct(Registry $options)
	{
		if (!function_exists('curl_init') || !is_callable('curl_init'))
		{
			throw new \RuntimeException('Cannot use a cURL transport when curl_init() is not available.');
		}

		$this->options = $options;
	}

	/**
	 * Send a request to the server and return a HttpResponse object with the response.
	 *
	 * @param   string   $method     The HTTP method for sending the request.
	 * @param   Uri      $uri        The URI to the resource to request.
	 * @param   mixed    $data       Either an associative array or a string to be sent with the request.
	 * @param   array    $headers    An array of request headers to send with the request.
	 * @param   integer  $timeout    Read timeout in seconds.
	 * @param   string   $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function request($method, Uri $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null)
	{
		// Setup the cURL handle.
		$ch = curl_init();

		$options = array();

		// Set the request method.
		switch (strtoupper($method))
		{
			case 'GET':
				$options[CURLOPT_HTTPGET] = true;
				break;

			case 'POST':
				$options[CURLOPT_POST] = true;
				break;

			case 'PUT':
			default:
				$options[CURLOPT_CUSTOMREQUEST] = strtoupper($method);
				break;
		}

		// Don't wait for body when $method is HEAD
		$options[CURLOPT_NOBODY] = ($method === 'HEAD');

		// Initialize the certificate store
		$options[CURLOPT_CAINFO] = $this->options->get('curl.certpath', __DIR__ . '/cacert.pem');

		// If data exists let's encode it and make sure our Content-type header is set.
		if (isset($data))
		{
			// If the data is a scalar value simply add it to the cURL post fields.
			if (is_scalar($data) || (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') === 0))
			{
				$options[CURLOPT_POSTFIELDS] = $data;
			}

			// Otherwise we need to encode the value first.
			else
			{
				$options[CURLOPT_POSTFIELDS] = http_build_query($data);
			}

			if (!isset($headers['Content-Type']))
			{
				$headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=utf-8';
			}

			// Add the relevant headers.
			if (is_scalar($options[CURLOPT_POSTFIELDS]))
			{
				$headers['Content-Length'] = strlen($options[CURLOPT_POSTFIELDS]);
			}
		}

		// Build the headers string for the request.
		$headerArray = array();

		if (isset($headers))
		{
			foreach ($headers as $key => $value)
			{
				$headerArray[] = $key . ': ' . $value;
			}

			// Add the headers string into the stream context options array.
			$options[CURLOPT_HTTPHEADER] = $headerArray;
		}

		// Curl needs the accepted encoding header as option
		if (isset($headers['Accept-Encoding']))
		{
			$options[CURLOPT_ENCODING] = $headers['Accept-Encoding'];
		}

		// If an explicit timeout is given user it.
		if (isset($timeout))
		{
			$options[CURLOPT_TIMEOUT] = (int) $timeout;
			$options[CURLOPT_CONNECTTIMEOUT] = (int) $timeout;
		}

		// If an explicit user agent is given use it.
		if (isset($userAgent))
		{
			$options[CURLOPT_USERAGENT] = $userAgent;
		}

		// Set the request URL.
		$options[CURLOPT_URL] = (string) $uri;

		// We want our headers. :-)
		$options[CURLOPT_HEADER] = true;

		// Return it... echoing it would be tacky.
		$options[CURLOPT_RETURNTRANSFER] = true;

		// Override the Expect header to prevent cURL from confusing itself in its own stupidity.
		// Link: http://the-stickman.com/web-development/php-and-curl-disabling-100-continue-header/
		$options[CURLOPT_HTTPHEADER][] = 'Expect:';

		// Follow redirects if server config allows
		if ($this->redirectsAllowed())
		{
			$options[CURLOPT_FOLLOWLOCATION] = (bool) $this->options->get('follow_location', true);
		}

		// Proxy configuration
		$config = \JFactory::getConfig();

		if ($config->get('proxy_enable'))
		{
			$options[CURLOPT_PROXY] = $config->get('proxy_host') . ':' . $config->get('proxy_port');

			if ($user = $config->get('proxy_user'))
			{
				$options[CURLOPT_PROXYUSERPWD] = $user . ':' . $config->get('proxy_pass');
			}
		}

		// Set any custom transport options
		foreach ($this->options->get('transport.curl', array()) as $key => $value)
		{
			$options[$key] = $value;
		}

		// Authentification, if needed
		if ($this->options->get('userauth') && $this->options->get('passwordauth'))
		{
			$options[CURLOPT_USERPWD] = $this->options->get('userauth') . ':' . $this->options->get('passwordauth');
			$options[CURLOPT_HTTPAUTH] = CURLAUTH_BASIC;
		}

		// Set the cURL options.
		curl_setopt_array($ch, $options);

		// Execute the request and close the connection.
		$content = curl_exec($ch);

		// Check if the content is a string. If it is not, it must be an error.
		if (!is_string($content))
		{
			$message = curl_error($ch);

			if (empty($message))
			{
				// Error but nothing from cURL? Create our own
				$message = 'No HTTP response received';
			}

			throw new \RuntimeException($message);
		}

		// Get the request information.
		$info = curl_getinfo($ch);

		// Close the connection.
		curl_close($ch);

		$response = $this->getResponse($content, $info);

		// Manually follow redirects if server doesn't allow to follow location using curl
		if ($response->code >= 301 && $response->code < 400 && isset($response->headers['Location']) && (bool) $this->options->get('follow_location', true))
		{
			$redirect_uri = new Uri($response->headers['Location']);

			if (in_array($redirect_uri->getScheme(), array('file', 'scp')))
			{
				throw new \RuntimeException('Curl redirect cannot be used in file or scp requests.');
			}

			$response = $this->request($method, $redirect_uri, $data, $headers, $timeout, $userAgent);
		}

		return $response;
	}

	/**
	 * Method to get a response object from a server response.
	 *
	 * @param   string  $content  The complete server response, including headers
	 *                            as a string if the response has no errors.
	 * @param   array   $info     The cURL request information.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 * @throws  \UnexpectedValueException
	 */
	protected function getResponse($content, $info)
	{
		// Create the response object.
		$return = new Response;

		// Try to get header size
		if (isset($info['header_size']))
		{
			$headerString = trim(substr($content, 0, $info['header_size']));
			$headerArray  = explode("\r\n\r\n", $headerString);

			// Get the last set of response headers as an array.
			$headers = explode("\r\n", array_pop($headerArray));

			// Set the body for the response.
			$return->body = substr($content, $info['header_size']);
		}
		// Fallback and try to guess header count by redirect count
		else
		{
			// Get the number of redirects that occurred.
			$redirects = isset($info['redirect_count']) ? $info['redirect_count'] : 0;

			/*
			 * Split the response into headers and body. If cURL encountered redirects, the headers for the redirected requests will
			 * also be included. So we split the response into header + body + the number of redirects and only use the last two
			 * sections which should be the last set of headers and the actual body.
			 */
			$response = explode("\r\n\r\n", $content, 2 + $redirects);

			// Set the body for the response.
			$return->body = array_pop($response);

			// Get the last set of response headers as an array.
			$headers = explode("\r\n", array_pop($response));
		}

		// Get the response code from the first offset of the response headers.
		preg_match('/[0-9]{3}/', array_shift($headers), $matches);

		$code = count($matches) ? $matches[0] : null;

		if (is_numeric($code))
		{
			$return->code = (int) $code;
		}

		// No valid response code was detected.
		else
		{
			throw new \UnexpectedValueException('No HTTP response code found.');
		}

		// Add the response headers to the response object.
		foreach ($headers as $header)
		{
			$pos = strpos($header, ':');
			$return->headers[trim(substr($header, 0, $pos))] = trim(substr($header, ($pos + 1)));
		}

		return $return;
	}

	/**
	 * Method to check if HTTP transport cURL is available for use
	 *
	 * @return boolean true if available, else false
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('curl_version') && curl_version();
	}

	/**
	 * Check if redirects are allowed
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	private function redirectsAllowed()
	{
		$curlVersion = curl_version();

		// In PHP 5.6.0 or later there are no issues with curl redirects
		if (version_compare(PHP_VERSION, '5.6', '>='))
		{
			// But if open_basedir is enabled we also need to check if libcurl version is 7.19.4 or higher
			if (!ini_get('open_basedir') || version_compare($curlVersion['version'], '7.19.4', '>='))
			{
				return true;
			}
		}

		// From PHP 5.4.0 to 5.5.30 curl redirects are only allowed if open_basedir is disabled
		elseif (version_compare(PHP_VERSION, '5.4', '>='))
		{
			if (!ini_get('open_basedir'))
			{
				return true;
			}
		}

		// From PHP 5.1.5 to 5.3.30 curl redirects are only allowed if safe_mode and open_basedir are disabled
		else
		{
			if (!ini_get('safe_mode') && !ini_get('open_basedir'))
			{
				return true;
			}
		}

		return false;
	}
}
src/Http/Http.php000064400000022245152177723700007722 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Http\TransportInterface;
use Joomla\CMS\Uri\Uri;

/**
 * HTTP client class.
 *
 * @since  1.7.3
 */
class Http
{
	/**
	 * @var    Registry  Options for the HTTP client.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * @var    TransportInterface  The HTTP transport object to use in sending HTTP requests.
	 * @since  1.7.3
	 */
	protected $transport;

	/**
	 * Constructor.
	 *
	 * @param   Registry            $options    Client options object. If the registry contains any headers.* elements,
	 *                                          these will be added to the request headers.
	 * @param   TransportInterface  $transport  The HTTP transport object.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Registry $options = null, TransportInterface $transport = null)
	{
		$this->options   = isset($options) ? $options : new Registry;
		$this->transport = isset($transport) ? $transport : HttpFactory::getAvailableDriver($this->options);
	}

	/**
	 * Get an option from the HTTP client.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   1.7.3
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the HTTP client.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  Http  This object for method chaining.
	 *
	 * @since   1.7.3
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}

	/**
	 * Method to send the OPTIONS command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function options($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('OPTIONS', new Uri($url), null, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the HEAD command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function head($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('HEAD', new Uri($url), null, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the GET command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function get($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('GET', new Uri($url), null, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the POST command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   mixed    $data     Either an associative array or a string to be sent with the request.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function post($url, $data, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('POST', new Uri($url), $data, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the PUT command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   mixed    $data     Either an associative array or a string to be sent with the request.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function put($url, $data, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('PUT', new Uri($url), $data, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the DELETE command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function delete($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('DELETE', new Uri($url), null, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the TRACE command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function trace($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('TRACE', new Uri($url), null, $headers, $timeout, $this->options->get('userAgent', null));
	}

	/**
	 * Method to send the PATCH command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   mixed    $data     Either an associative array or a string to be sent with the request.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  Response
	 *
	 * @since   3.0.1
	 */
	public function patch($url, $data, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('timeout'))
		{
			$timeout = $this->options->get('timeout');
		}

		return $this->transport->request('PATCH', new Uri($url), $data, $headers, $timeout, $this->options->get('userAgent', null));
	}
}
src/Http/TransportInterface.php000064400000002776152177723700012627 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Http;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\CMS\Uri\Uri;

/**
 * HTTP transport class interface.
 *
 * @since  1.7.3
 */
interface TransportInterface
{
	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  Client options object.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Registry $options);

	/**
	 * Send a request to the server and return a HttpResponse object with the response.
	 *
	 * @param   string   $method     The HTTP method for sending the request.
	 * @param   Uri      $uri        The URI to the resource to request.
	 * @param   mixed    $data       Either an associative array or a string to be sent with the request.
	 * @param   array    $headers    An array of request headers to send with the request.
	 * @param   integer  $timeout    Read timeout in seconds.
	 * @param   string   $userAgent  The optional user agent string to send with the request.
	 *
	 * @return  Response
	 *
	 * @since   1.7.3
	 */
	public function request($method, Uri $uri, $data = null, array $headers = null, $timeout = null, $userAgent = null);

	/**
	 * Method to check if HTTP transport is available for use
	 *
	 * @return  boolean  True if available else false
	 *
	 * @since   3.0.0
	 */
	public static function isSupported();
}
src/Help/Help.php000064400000010256152177723700007643 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Help;

defined('JPATH_PLATFORM') or die;

/**
 * Help system class
 *
 * @since  1.5
 */
class Help
{
	/**
	 * Create a URL for a given help key reference
	 *
	 * @param   string   $ref           The name of the help screen (its key reference)
	 * @param   boolean  $useComponent  Use the help file in the component directory
	 * @param   string   $override      Use this URL instead of any other
	 * @param   string   $component     Name of component (or null for current component)
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function createUrl($ref, $useComponent = false, $override = null, $component = null)
	{
		$local = false;
		$app   = \JFactory::getApplication();

		if ($component === null)
		{
			$component = \JApplicationHelper::getComponentName();
		}

		//  Determine the location of the help file.  At this stage the URL
		//  can contain substitution codes that will be replaced later.

		if ($override)
		{
			$url = $override;
		}
		else
		{
			// Get the global help URL.
			$url = $app->get('helpurl');

			// Component help URL overrides user and global.
			if ($useComponent)
			{
				// Look for help URL in component parameters.
				$params = \JComponentHelper::getParams($component);
				$url    = $params->get('helpURL');

				if ($url == '')
				{
					$local = true;
					$url   = 'components/{component}/help/{language}/{keyref}';
				}
			}

			// Set up a local help URL.
			if (!$url)
			{
				$local = true;
				$url   = 'help/{language}/{keyref}';
			}
		}

		// If the URL is local then make sure we have a valid file extension on the URL.
		if ($local)
		{
			if (!preg_match('#\.html$|\.xml$#i', $ref))
			{
				$url .= '.html';
			}
		}

		/*
		 *  Replace substitution codes in the URL.
		 */
		$lang    = \JFactory::getLanguage();
		$version = new \JVersion;
		$jver    = explode('.', $version->getShortVersion());
		$jlang   = explode('-', $lang->getTag());

		$debug  = $lang->setDebug(false);
		$keyref = \JText::_($ref);
		$lang->setDebug($debug);

		// Replace substitution codes in help URL.
		$search = array(
			// Application name (eg. 'Administrator')
			'{app}',
			// Component name (eg. 'com_content')
			'{component}',
			// Help screen key reference
			'{keyref}',
			// Full language code (eg. 'en-GB')
			'{language}',
			// Short language code (eg. 'en')
			'{langcode}',
			// Region code (eg. 'GB')
			'{langregion}',
			// Joomla major version number
			'{major}',
			// Joomla minor version number
			'{minor}',
			// Joomla maintenance version number
			'{maintenance}',
		);

		$replace = array(
			// {app}
			$app->getName(),
			// {component}
			$component,
			// {keyref}
			$keyref,
			// {language}
			$lang->getTag(),
			// {langcode}
			$jlang[0],
			// {langregion}
			$jlang[1],
			// {major}
			$jver[0],
			// {minor}
			$jver[1],
			// {maintenance}
			$jver[2],
		);

		// If the help file is local then check it exists.
		// If it doesn't then fallback to English.
		if ($local)
		{
			$try = str_replace($search, $replace, $url);

			if (!is_file(JPATH_BASE . '/' . $try))
			{
				$replace[3] = 'en-GB';
				$replace[4] = 'en';
				$replace[5] = 'GB';
			}
		}

		$url = str_replace($search, $replace, $url);

		return $url;
	}

	/**
	 * Builds a list of the help sites which can be used in a select option.
	 *
	 * @param   string  $pathToXml  Path to an XML file.
	 *
	 * @return  array  An array of arrays (text, value, selected).
	 *
	 * @since   1.5
	 */
	public static function createSiteList($pathToXml)
	{
		$list = array();
		$xml  = false;

		if (!empty($pathToXml))
		{
			$xml = simplexml_load_file($pathToXml);
		}

		if (!$xml)
		{
			$option['text']  = 'English (GB) help.joomla.org';
			$option['value'] = 'http://help.joomla.org';

			$list[] = (object) $option;
		}
		else
		{
			$option = array();

			foreach ($xml->sites->site as $site)
			{
				$option['text']  = (string) $site;
				$option['value'] = (string) $site->attributes()->url;

				$list[] = (object) $option;
			}
		}

		return $list;
	}
}
src/Plugin/CMSPlugin.php000064400000006212152177723700011117 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Plugin;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Plugin Class
 *
 * @since  1.5
 */
abstract class CMSPlugin extends \JEvent
{
	/**
	 * A Registry object holding the parameters for the plugin
	 *
	 * @var    Registry
	 * @since  1.5
	 */
	public $params = null;

	/**
	 * The name of the plugin
	 *
	 * @var    string
	 * @since  1.5
	 */
	protected $_name = null;

	/**
	 * The plugin type
	 *
	 * @var    string
	 * @since  1.5
	 */
	protected $_type = null;

	/**
	 * Affects constructor behavior. If true, language files will be loaded automatically.
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	protected $autoloadLanguage = false;

	/**
	 * Constructor
	 *
	 * @param   object  &$subject  The object to observe
	 * @param   array   $config    An optional associative array of configuration settings.
	 *                             Recognized key values include 'name', 'group', 'params', 'language'
	 *                             (this list is not meant to be comprehensive).
	 *
	 * @since   1.5
	 */
	public function __construct(&$subject, $config = array())
	{
		// Get the parameters.
		if (isset($config['params']))
		{
			if ($config['params'] instanceof Registry)
			{
				$this->params = $config['params'];
			}
			else
			{
				$this->params = new Registry($config['params']);
			}
		}

		// Get the plugin name.
		if (isset($config['name']))
		{
			$this->_name = $config['name'];
		}

		// Get the plugin type.
		if (isset($config['type']))
		{
			$this->_type = $config['type'];
		}

		// Load the language files if needed.
		if ($this->autoloadLanguage)
		{
			$this->loadLanguage();
		}

		if (property_exists($this, 'app'))
		{
			$reflection = new \ReflectionClass($this);

			if ($reflection->getProperty('app')->isPrivate() === false && $this->app === null)
			{
				$this->app = \JFactory::getApplication();
			}
		}

		if (property_exists($this, 'db'))
		{
			$reflection = new \ReflectionClass($this);

			if ($reflection->getProperty('db')->isPrivate() === false && $this->db === null)
			{
				$this->db = \JFactory::getDbo();
			}
		}

		parent::__construct($subject);
	}

	/**
	 * Loads the plugin language file
	 *
	 * @param   string  $extension  The extension for which a language file should be loaded
	 * @param   string  $basePath   The basepath to use
	 *
	 * @return  boolean  True, if the file has successfully loaded.
	 *
	 * @since   1.5
	 */
	public function loadLanguage($extension = '', $basePath = JPATH_ADMINISTRATOR)
	{
		if (empty($extension))
		{
			$extension = 'Plg_' . $this->_type . '_' . $this->_name;
		}

		$extension = strtolower($extension);
		$lang      = \JFactory::getLanguage();

		// If language already loaded, don't load it again.
		if ($lang->getPaths($extension))
		{
			return true;
		}

		return $lang->load($extension, $basePath, null, false, true)
			|| $lang->load($extension, JPATH_PLUGINS . '/' . $this->_type . '/' . $this->_name, null, false, true);
	}
}
src/Plugin/PluginHelper.php000064400000021412152177723700011713 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Plugin;

defined('JPATH_PLATFORM') or die;

/**
 * Plugin helper class
 *
 * @since  1.5
 */
abstract class PluginHelper
{
	/**
	 * A persistent cache of the loaded plugins.
	 *
	 * @var    array
	 * @since  1.7
	 */
	protected static $plugins = null;

	/**
	 * Get the path to a layout from a Plugin
	 *
	 * @param   string  $type    Plugin type
	 * @param   string  $name    Plugin name
	 * @param   string  $layout  Layout name
	 *
	 * @return  string  Layout path
	 *
	 * @since   3.0
	 */
	public static function getLayoutPath($type, $name, $layout = 'default')
	{
		$template = \JFactory::getApplication()->getTemplate();
		$defaultLayout = $layout;

		if (strpos($layout, ':') !== false)
		{
			// Get the template and file name from the string
			$temp = explode(':', $layout);
			$template = $temp[0] === '_' ? $template : $temp[0];
			$layout = $temp[1];
			$defaultLayout = $temp[1] ?: 'default';
		}

		// Build the template and base path for the layout
		$tPath = JPATH_THEMES . '/' . $template . '/html/plg_' . $type . '_' . $name . '/' . $layout . '.php';
		$bPath = JPATH_PLUGINS . '/' . $type . '/' . $name . '/tmpl/' . $defaultLayout . '.php';
		$dPath = JPATH_PLUGINS . '/' . $type . '/' . $name . '/tmpl/default.php';

		// If the template has a layout override use it
		if (file_exists($tPath))
		{
			return $tPath;
		}
		elseif (file_exists($bPath))
		{
			return $bPath;
		}
		else
		{
			return $dPath;
		}
	}

	/**
	 * Get the plugin data of a specific type if no specific plugin is specified
	 * otherwise only the specific plugin data is returned.
	 *
	 * @param   string  $type    The plugin type, relates to the subdirectory in the plugins directory.
	 * @param   string  $plugin  The plugin name.
	 *
	 * @return  mixed  An array of plugin data objects, or a plugin data object.
	 *
	 * @since   1.5
	 */
	public static function getPlugin($type, $plugin = null)
	{
		$result = array();
		$plugins = static::load();

		// Find the correct plugin(s) to return.
		if (!$plugin)
		{
			foreach ($plugins as $p)
			{
				// Is this the right plugin?
				if ($p->type === $type)
				{
					$result[] = $p;
				}
			}
		}
		else
		{
			foreach ($plugins as $p)
			{
				// Is this plugin in the right group?
				if ($p->type === $type && $p->name === $plugin)
				{
					$result = $p;
					break;
				}
			}
		}

		return $result;
	}

	/**
	 * Checks if a plugin is enabled.
	 *
	 * @param   string  $type    The plugin type, relates to the subdirectory in the plugins directory.
	 * @param   string  $plugin  The plugin name.
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 */
	public static function isEnabled($type, $plugin = null)
	{
		$result = static::getPlugin($type, $plugin);

		return !empty($result);
	}

	/**
	 * Loads all the plugin files for a particular type if no specific plugin is specified
	 * otherwise only the specific plugin is loaded.
	 *
	 * @param   string             $type        The plugin type, relates to the subdirectory in the plugins directory.
	 * @param   string             $plugin      The plugin name.
	 * @param   boolean            $autocreate  Autocreate the plugin.
	 * @param   \JEventDispatcher  $dispatcher  Optionally allows the plugin to use a different dispatcher.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.5
	 */
	public static function importPlugin($type, $plugin = null, $autocreate = true, \JEventDispatcher $dispatcher = null)
	{
		static $loaded = array();

		// Check for the default args, if so we can optimise cheaply
		$defaults = false;

		if ($plugin === null && $autocreate === true && $dispatcher === null)
		{
			$defaults = true;
		}

		// Ensure we have a dispatcher now so we can correctly track the loaded plugins
		$dispatcher = $dispatcher ?: \JEventDispatcher::getInstance();

		// Get the dispatcher's hash to allow plugins to be registered to unique dispatchers
		$dispatcherHash = spl_object_hash($dispatcher);

		if (!isset($loaded[$dispatcherHash]))
		{
			$loaded[$dispatcherHash] = array();
		}

		if (!$defaults || !isset($loaded[$dispatcherHash][$type]))
		{
			$results = null;

			// Load the plugins from the database.
			$plugins = static::load();

			// Get the specified plugin(s).
			for ($i = 0, $t = count($plugins); $i < $t; $i++)
			{
				if ($plugins[$i]->type === $type && ($plugin === null || $plugins[$i]->name === $plugin))
				{
					static::import($plugins[$i], $autocreate, $dispatcher);
					$results = true;
				}
			}

			// Bail out early if we're not using default args
			if (!$defaults)
			{
				return $results;
			}

			$loaded[$dispatcherHash][$type] = $results;
		}

		return $loaded[$dispatcherHash][$type];
	}

	/**
	 * Loads the plugin file.
	 *
	 * @param   object             $plugin      The plugin.
	 * @param   boolean            $autocreate  True to autocreate.
	 * @param   \JEventDispatcher  $dispatcher  Optionally allows the plugin to use a different dispatcher.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use PluginHelper::import() instead
	 */
	protected static function _import($plugin, $autocreate = true, \JEventDispatcher $dispatcher = null)
	{
		static::import($plugin, $autocreate, $dispatcher);
	}

	/**
	 * Loads the plugin file.
	 *
	 * @param   object             $plugin      The plugin.
	 * @param   boolean            $autocreate  True to autocreate.
	 * @param   \JEventDispatcher  $dispatcher  Optionally allows the plugin to use a different dispatcher.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected static function import($plugin, $autocreate = true, \JEventDispatcher $dispatcher = null)
	{
		static $paths = array();

		// Ensure we have a dispatcher now so we can correctly track the loaded paths
		$dispatcher = $dispatcher ?: \JEventDispatcher::getInstance();

		// Get the dispatcher's hash to allow paths to be tracked against unique dispatchers
		$dispatcherHash = spl_object_hash($dispatcher);

		if (!isset($paths[$dispatcherHash]))
		{
			$paths[$dispatcherHash] = array();
		}

		$plugin->type = preg_replace('/[^A-Z0-9_\.-]/i', '', $plugin->type);
		$plugin->name = preg_replace('/[^A-Z0-9_\.-]/i', '', $plugin->name);

		$path = JPATH_PLUGINS . '/' . $plugin->type . '/' . $plugin->name . '/' . $plugin->name . '.php';

		if (!isset($paths[$dispatcherHash][$path]))
		{
			if (file_exists($path))
			{
				if (!isset($paths[$dispatcherHash][$path]))
				{
					require_once $path;
				}

				$paths[$dispatcherHash][$path] = true;

				if ($autocreate)
				{
					$className = 'Plg' . str_replace('-', '', $plugin->type) . $plugin->name;

					if ($plugin->type == 'editors-xtd')
					{
						// This type doesn't follow the convention
						$className = 'PlgEditorsXtd' . $plugin->name;

						if (!class_exists($className))
						{
							$className = 'PlgButton' . $plugin->name;
						}
					}

					if (class_exists($className))
					{
						// Load the plugin from the database.
						if (!isset($plugin->params))
						{
							// Seems like this could just go bye bye completely
							$plugin = static::getPlugin($plugin->type, $plugin->name);
						}

						// Instantiate and register the plugin.
						new $className($dispatcher, (array) $plugin);
					}
				}
			}
			else
			{
				$paths[$dispatcherHash][$path] = false;
			}
		}
	}

	/**
	 * Loads the published plugins.
	 *
	 * @return  array  An array of published plugins
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use PluginHelper::load() instead
	 */
	protected static function _load()
	{
		return static::load();
	}

	/**
	 * Loads the published plugins.
	 *
	 * @return  array  An array of published plugins
	 *
	 * @since   3.2
	 */
	protected static function load()
	{
		if (static::$plugins !== null)
		{
			return static::$plugins;
		}

		$levels = implode(',', \JFactory::getUser()->getAuthorisedViewLevels());

		/** @var \JCacheControllerCallback $cache */
		$cache = \JFactory::getCache('com_plugins', 'callback');

		$loader = function () use ($levels)
		{
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select(
					$db->quoteName(
						array(
							'folder',
							'element',
							'params',
							'extension_id'
						),
						array(
							'type',
							'name',
							'params',
							'id'
						)
					)
				)
				->from('#__extensions')
				->where('enabled = 1')
				->where('type = ' . $db->quote('plugin'))
				->where('state IN (0,1)')
				->where('access IN (' . $levels . ')')
				->order('ordering');
			$db->setQuery($query);

			return $db->loadObjectList();
		};

		try
		{
			static::$plugins = $cache->get($loader, array(), md5($levels), false);
		}
		catch (\JCacheException $cacheException)
		{
			static::$plugins = $loader();
		}

		return static::$plugins;
	}
}
src/Editor/Editor.php000064400000026104152177723700010536 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Editor;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Editor class to handle WYSIWYG editors
 *
 * @since  1.5
 */
class Editor extends \JObject
{
	/**
	 * An array of Observer objects to notify
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected $_observers = array();

	/**
	 * The state of the observable object
	 *
	 * @var    mixed
	 * @since  1.5
	 */
	protected $_state = null;

	/**
	 * A multi dimensional array of [function][] = key for observers
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected $_methods = array();

	/**
	 * Editor Plugin object
	 *
	 * @var    object
	 * @since  1.5
	 */
	protected $_editor = null;

	/**
	 * Editor Plugin name
	 *
	 * @var    string
	 * @since  1.5
	 */
	protected $_name = null;

	/**
	 * Object asset
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $asset = null;

	/**
	 * Object author
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $author = null;

	/**
	 * Editor instances container.
	 *
	 * @var    Editor[]
	 * @since  2.5
	 */
	protected static $instances = array();

	/**
	 * Constructor
	 *
	 * @param   string  $editor  The editor name
	 */
	public function __construct($editor = 'none')
	{
		$this->_name = $editor;
	}

	/**
	 * Returns the global Editor object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $editor  The editor to use.
	 *
	 * @return  Editor The Editor object.
	 *
	 * @since   1.5
	 */
	public static function getInstance($editor = 'none')
	{
		$signature = serialize($editor);

		if (empty(self::$instances[$signature]))
		{
			self::$instances[$signature] = new Editor($editor);
		}

		return self::$instances[$signature];
	}

	/**
	 * Get the state of the Editor object
	 *
	 * @return  mixed    The state of the object.
	 *
	 * @since   1.5
	 */
	public function getState()
	{
		return $this->_state;
	}

	/**
	 * Attach an observer object
	 *
	 * @param   array|object  $observer  An observer object to attach or an array with handler and event keys
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function attach($observer)
	{
		if (is_array($observer))
		{
			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
			{
				return;
			}

			// Make sure we haven't already attached this array as an observer
			foreach ($this->_observers as $check)
			{
				if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			end($this->_observers);
			$methods = array($observer['event']);
		}
		else
		{
			if (!($observer instanceof Editor))
			{
				return;
			}

			// Make sure we haven't already attached this object as an observer
			$class = get_class($observer);

			foreach ($this->_observers as $check)
			{
				if ($check instanceof $class)
				{
					return;
				}
			}

			$this->_observers[] = $observer;

			// @todo We require an Editor object above but get the methods from \JPlugin - something isn't right here!
			$methods = array_diff(get_class_methods($observer), get_class_methods('\JPlugin'));
		}

		$key = key($this->_observers);

		foreach ($methods as $method)
		{
			$method = strtolower($method);

			if (!isset($this->_methods[$method]))
			{
				$this->_methods[$method] = array();
			}

			$this->_methods[$method][] = $key;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @param   object  $observer  An observer object to detach.
	 *
	 * @return  boolean  True if the observer object was detached.
	 *
	 * @since   1.5
	 */
	public function detach($observer)
	{
		$retval = false;

		$key = array_search($observer, $this->_observers);

		if ($key !== false)
		{
			unset($this->_observers[$key]);
			$retval = true;

			foreach ($this->_methods as &$method)
			{
				$k = array_search($key, $method);

				if ($k !== false)
				{
					unset($method[$k]);
				}
			}
		}

		return $retval;
	}

	/**
	 * Initialise the editor
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @deprecated 4.0 This function will not load any custom tag from 4.0 forward, use JHtml::script
	 */
	public function initialise()
	{
		// Check if editor is already loaded
		if ($this->_editor === null)
		{
			return;
		}

		$args['event'] = 'onInit';

		$return    = '';
		$results[] = $this->_editor->update($args);

		foreach ($results as $result)
		{
			if (trim($result))
			{
				// @todo remove code: $return .= $result;
				$return = $result;
			}
		}

		$document = \JFactory::getDocument();

		if (!empty($return) && method_exists($document, 'addCustomTag'))
		{
			$document->addCustomTag($return);
		}
	}

	/**
	 * Display the editor area.
	 *
	 * @param   string   $name     The control name.
	 * @param   string   $html     The contents of the text area.
	 * @param   string   $width    The width of the text area (px or %).
	 * @param   string   $height   The height of the text area (px or %).
	 * @param   integer  $col      The number of columns for the textarea.
	 * @param   integer  $row      The number of rows for the textarea.
	 * @param   boolean  $buttons  True and the editor buttons will be displayed.
	 * @param   string   $id       An optional ID for the textarea (note: since 1.6). If not supplied the name is used.
	 * @param   string   $asset    The object asset
	 * @param   object   $author   The author.
	 * @param   array    $params   Associative array of editor parameters.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public function display($name, $html, $width, $height, $col, $row, $buttons = true, $id = null, $asset = null, $author = null, $params = array())
	{
		$this->asset = $asset;
		$this->author = $author;
		$this->_loadEditor($params);

		// Check whether editor is already loaded
		if ($this->_editor === null)
		{
			\JFactory::getApplication()->enqueueMessage(\JText::_('JLIB_NO_EDITOR_PLUGIN_PUBLISHED'), 'error');

			return;
		}

		// Backwards compatibility. Width and height should be passed without a semicolon from now on.
		// If editor plugins need a unit like "px" for CSS styling, they need to take care of that
		$width = str_replace(';', '', $width);
		$height = str_replace(';', '', $height);

		$return = null;

		$args['name'] = $name;
		$args['content'] = $html;
		$args['width'] = $width;
		$args['height'] = $height;
		$args['col'] = $col;
		$args['row'] = $row;
		$args['buttons'] = $buttons;
		$args['id'] = $id ?: $name;
		$args['asset'] = $asset;
		$args['author'] = $author;
		$args['params'] = $params;
		$args['event'] = 'onDisplay';

		$results[] = $this->_editor->update($args);

		foreach ($results as $result)
		{
			if (trim($result))
			{
				$return .= $result;
			}
		}

		return $return;
	}

	/**
	 * Save the editor content
	 *
	 * @param   string  $editor  The name of the editor control
	 *
	 * @return  string
	 *
	 * @since   1.5
	 *
	 * @deprecated 4.0 Bind functionality to form submit through javascript
	 */
	public function save($editor)
	{
		$this->_loadEditor();

		// Check whether editor is already loaded
		if ($this->_editor === null)
		{
			return;
		}

		$args[] = $editor;
		$args['event'] = 'onSave';

		$return = '';
		$results[] = $this->_editor->update($args);

		foreach ($results as $result)
		{
			if (trim($result))
			{
				$return .= $result;
			}
		}

		return $return;
	}

	/**
	 * Get the editor contents
	 *
	 * @param   string  $editor  The name of the editor control
	 *
	 * @return  string
	 *
	 * @since   1.5
	 *
	 * @deprecated 4.0 Use Joomla.editors API, see core.js
	 */
	public function getContent($editor)
	{
		$this->_loadEditor();

		$args['name'] = $editor;
		$args['event'] = 'onGetContent';

		$return = '';
		$results[] = $this->_editor->update($args);

		foreach ($results as $result)
		{
			if (trim($result))
			{
				$return .= $result;
			}
		}

		return $return;
	}

	/**
	 * Set the editor contents
	 *
	 * @param   string  $editor  The name of the editor control
	 * @param   string  $html    The contents of the text area
	 *
	 * @return  string
	 *
	 * @since   1.5
	 *
	 * @deprecated 4.0 Use Joomla.editors API, see core.js
	 */
	public function setContent($editor, $html)
	{
		$this->_loadEditor();

		$args['name'] = $editor;
		$args['html'] = $html;
		$args['event'] = 'onSetContent';

		$return = '';
		$results[] = $this->_editor->update($args);

		foreach ($results as $result)
		{
			if (trim($result))
			{
				$return .= $result;
			}
		}

		return $return;
	}

	/**
	 * Get the editor extended buttons (usually from plugins)
	 *
	 * @param   string  $editor   The name of the editor.
	 * @param   mixed   $buttons  Can be boolean or array, if boolean defines if the buttons are
	 *                            displayed, if array defines a list of buttons not to show.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function getButtons($editor, $buttons = true)
	{
		$result = array();

		if (is_bool($buttons) && !$buttons)
		{
			return $result;
		}

		// Get plugins
		$plugins = \JPluginHelper::getPlugin('editors-xtd');

		foreach ($plugins as $plugin)
		{
			if (is_array($buttons) && in_array($plugin->name, $buttons))
			{
				continue;
			}

			\JPluginHelper::importPlugin('editors-xtd', $plugin->name, false);
			$className = 'PlgEditorsXtd' . $plugin->name;

			if (!class_exists($className))
			{
				$className = 'PlgButton' . $plugin->name;
			}

			if (class_exists($className))
			{
				$plugin = new $className($this, (array) $plugin);
			}

			// Try to authenticate
			if (!method_exists($plugin, 'onDisplay'))
			{
				continue;
			}

			$button = $plugin->onDisplay($editor, $this->asset, $this->author);

			if (empty($button))
			{
				continue;
			}

			if (is_array($button))
			{
				$result = array_merge($result, $button);
				continue;
			}

			$result[] = $button;
		}

		return $result;
	}

	/**
	 * Load the editor
	 *
	 * @param   array  $config  Associative array of editor config parameters
	 *
	 * @return  mixed
	 *
	 * @since   1.5
	 */
	protected function _loadEditor($config = array())
	{
		// Check whether editor is already loaded
		if ($this->_editor !== null)
		{
			return;
		}

		// Build the path to the needed editor plugin
		$name = \JFilterInput::getInstance()->clean($this->_name, 'cmd');
		$path = JPATH_PLUGINS . '/editors/' . $name . '/' . $name . '.php';

		if (!is_file($path))
		{
			\JLog::add(\JText::_('JLIB_HTML_EDITOR_CANNOT_LOAD'), \JLog::WARNING, 'jerror');

			return false;
		}

		// Require plugin file
		require_once $path;

		// Get the plugin
		$plugin = \JPluginHelper::getPlugin('editors', $this->_name);

		// If no plugin is published we get an empty array and there not so much to do with it
		if (empty($plugin))
		{
			return false;
		}

		$params = new Registry($plugin->params);
		$params->loadArray($config);
		$plugin->params = $params;

		// Build editor plugin classname
		$name = 'PlgEditor' . $this->_name;

		if ($this->_editor = new $name($this, (array) $plugin))
		{
			// Load plugin parameters
			$this->initialise();
			\JPluginHelper::importPlugin('editors-xtd');
		}
	}
}
src/Mail/MailHelper.php000064400000010456152177723700010771 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Mail;

defined('JPATH_PLATFORM') or die;

/**
 * Email helper class, provides static methods to perform various tasks relevant
 * to the Joomla email routines.
 *
 * TODO: Test these methods as the regex work is first run and not tested thoroughly
 *
 * @since  1.7.0
 */
abstract class MailHelper
{
	/**
	 * Cleans single line inputs.
	 *
	 * @param   string  $value  String to be cleaned.
	 *
	 * @return  string  Cleaned string.
	 *
	 * @since   1.7.0
	 */
	public static function cleanLine($value)
	{
		$value = \JStringPunycode::emailToPunycode($value);

		return trim(preg_replace('/(%0A|%0D|\n+|\r+)/i', '', $value));
	}

	/**
	 * Cleans multi-line inputs.
	 *
	 * @param   string  $value  Multi-line string to be cleaned.
	 *
	 * @return  string  Cleaned multi-line string.
	 *
	 * @since   1.7.0
	 */
	public static function cleanText($value)
	{
		return trim(preg_replace('/(%0A|%0D|\n+|\r+)(content-type:|to:|cc:|bcc:)/i', '', $value));
	}

	/**
	 * Cleans any injected headers from the email body.
	 *
	 * @param   string  $body  email body string.
	 *
	 * @return  string  Cleaned email body string.
	 *
	 * @since   1.7.0
	 */
	public static function cleanBody($body)
	{
		// Strip all email headers from a string
		return preg_replace("/((From:|To:|Cc:|Bcc:|Subject:|Content-type:) ([\S]+))/", '', $body);
	}

	/**
	 * Cleans any injected headers from the subject string.
	 *
	 * @param   string  $subject  email subject string.
	 *
	 * @return  string  Cleaned email subject string.
	 *
	 * @since   1.7.0
	 */
	public static function cleanSubject($subject)
	{
		return preg_replace("/((From:|To:|Cc:|Bcc:|Content-type:) ([\S]+))/", '', $subject);
	}

	/**
	 * Verifies that an email address does not have any extra headers injected into it.
	 *
	 * @param   string  $address  email address.
	 *
	 * @return  mixed   email address string or boolean false if injected headers are present.
	 *
	 * @since   1.7.0
	 */
	public static function cleanAddress($address)
	{
		if (preg_match("[\s;,]", $address))
		{
			return false;
		}

		return $address;
	}

	/**
	 * Verifies that the string is in a proper email address format.
	 *
	 * @param   string  $email  String to be verified.
	 *
	 * @return  boolean  True if string has the correct format; false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function isEmailAddress($email)
	{
		// Split the email into a local and domain
		$atIndex = strrpos($email, '@');
		$domain = substr($email, $atIndex + 1);
		$local = substr($email, 0, $atIndex);

		// Check Length of domain
		$domainLen = strlen($domain);

		if ($domainLen < 1 || $domainLen > 255)
		{
			return false;
		}

		/*
		 * Check the local address
		 * We're a bit more conservative about what constitutes a "legal" address, that is, a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-
		 * The first and last character in local cannot be a period ('.')
		 * Also, period should not appear 2 or more times consecutively
		 */
		$allowed = "a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-";
		$regex = "/^[$allowed][\.$allowed]{0,63}$/";

		if (!preg_match($regex, $local) || substr($local, -1) == '.' || $local[0] == '.' || preg_match('/\.\./', $local))
		{
			return false;
		}

		// No problem if the domain looks like an IP address, ish
		$regex = '/^[0-9\.]+$/';

		if (preg_match($regex, $domain))
		{
			return true;
		}

		// Check Lengths
		$localLen = strlen($local);

		if ($localLen < 1 || $localLen > 64)
		{
			return false;
		}

		// Check the domain
		$domain_array = explode('.', $domain);
		$regex = '/^[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/';

		foreach ($domain_array as $domain)
		{
			// Convert domain to punycode
			$domain = \JStringPunycode::toPunycode($domain);

			// Must be something
			if (!$domain)
			{
				return false;
			}

			// Check for invalid characters
			if (!preg_match($regex, $domain))
			{
				return false;
			}

			// Check for a dash at the beginning of the domain
			if (strpos($domain, '-') === 0)
			{
				return false;
			}

			// Check for a dash at the end of the domain
			$length = strlen($domain) - 1;

			if (strpos($domain, '-', $length) === $length)
			{
				return false;
			}
		}

		return true;
	}
}
src/Mail/Mail.php000064400000050266152177723700007634 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Mail;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;

/**
 * Email Class.  Provides a common interface to send email from the Joomla! Platform
 *
 * @since  1.7.0
 */
class Mail extends \PHPMailer
{
	/**
	 * Mail instances container.
	 *
	 * @var    Mail[]
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Charset of the message.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $CharSet = 'utf-8';

	/**
	 * Constructor
	 *
	 * @param   boolean  $exceptions  Flag if Exceptions should be thrown
	 *
	 * @since   1.7.0
	 */
	public function __construct($exceptions = true)
	{
		parent::__construct($exceptions);

		// PHPMailer has an issue using the relative path for its language files
		$this->setLanguage('en_gb', __DIR__ . '/language/');

		// Configure a callback function to handle errors when $this->edebug() is called
		$this->Debugoutput = function ($message, $level)
		{
			Log::add(sprintf('Error in Mail API: %s', $message), Log::ERROR, 'mail');
		};

		// If debug mode is enabled then set SMTPDebug to the maximum level
		if (defined('JDEBUG') && JDEBUG)
		{
			$this->SMTPDebug = 4;
		}

		// Don't disclose the PHPMailer version
		$this->XMailer = ' ';

		/*
		 * PHPMailer 5.2 can't validate e-mail addresses with the new regex library used in PHP 7.3+
		 * Setting $validator to "php" uses the native php function filter_var
		 *
		 * @see https://github.com/joomla/joomla-cms/issues/24707
		 */
		if (version_compare(PHP_VERSION, '7.3.0', '>='))
		{
			\PHPMailer::$validator = 'php';
		}
	}

	/**
	 * Returns the global email object, only creating it if it doesn't already exist.
	 *
	 * NOTE: If you need an instance to use that does not have the global configuration
	 * values, use an id string that is not 'Joomla'.
	 *
	 * @param   string   $id          The id string for the Mail instance [optional]
	 * @param   boolean  $exceptions  Flag if Exceptions should be thrown [optional]
	 *
	 * @return  Mail  The global Mail object
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($id = 'Joomla', $exceptions = true)
	{
		if (empty(self::$instances[$id]))
		{
			self::$instances[$id] = new Mail($exceptions);
		}

		return self::$instances[$id];
	}

	/**
	 * Send the mail
	 *
	 * @return  boolean|\JException  Boolean true if successful, boolean false if the `mailonline` configuration is set to 0,
	 *                              or a JException object if the mail function does not exist or sending the message fails.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function Send()
	{
		if (Factory::getConfig()->get('mailonline', 1))
		{
			if (($this->Mailer == 'mail') && !function_exists('mail'))
			{
				return \JError::raiseNotice(500, \JText::_('JLIB_MAIL_FUNCTION_DISABLED'));
			}

			try
			{
				// Try sending with default settings
				$result = parent::send();
			}
			catch (\phpmailerException $e)
			{
				$result = false;

				if ($this->SMTPAutoTLS)
				{
					/**
					 * PHPMailer has an issue with servers with invalid certificates
					 *
					 * See: https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting#opportunistic-tls
					 */
					$this->SMTPAutoTLS = false;

					try
					{
						// Try it again with TLS turned off
						$result = parent::send();
					}
					catch (\phpmailerException $e)
					{
						// Keep false for B/C compatibility
						$result = false;
					}
				}
			}

			if ($result == false)
			{
				$result = \JError::raiseNotice(500, \JText::_($this->ErrorInfo));
			}

			return $result;
		}

		Factory::getApplication()->enqueueMessage(\JText::_('JLIB_MAIL_FUNCTION_OFFLINE'), 'warning');

		return false;
	}

	/**
	 * Set the From and FromName properties.
	 *
	 * @param   string   $address  The sender email address
	 * @param   string   $name     The sender name
	 * @param   boolean  $auto     Whether to also set the Sender address, defaults to true
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function setFrom($address, $name = '', $auto = true)
	{
		try
		{
			if (parent::setFrom($address, $name, $auto) === false)
			{
				return false;
			}
		}
		catch (\phpmailerException $e)
		{
			// The parent method will have already called the logging callback, just log our deprecated error handling message
			Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

			return false;
		}
	}

	/**
	 * Set the email sender
	 *
	 * @param   mixed  $from  email address and Name of sender
	 *                        <code>array([0] => email Address, [1] => Name)</code>
	 *                        or as a string
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function setSender($from)
	{
		// Wrapped in try/catch if PHPMailer is configured to throw exceptions
		try
		{
			if (is_array($from))
			{
				// If $from is an array we assume it has an address and a name
				if (isset($from[2]))
				{
					// If it is an array with entries, use them
					$result = $this->setFrom(MailHelper::cleanLine($from[0]), MailHelper::cleanLine($from[1]), (bool) $from[2]);
				}
				else
				{
					$result = $this->setFrom(MailHelper::cleanLine($from[0]), MailHelper::cleanLine($from[1]));
				}
			}
			elseif (is_string($from))
			{
				// If it is a string we assume it is just the address
				$result = $this->setFrom(MailHelper::cleanLine($from));
			}
			else
			{
				// If it is neither, we log a message and throw an exception
				Log::add(\JText::sprintf('JLIB_MAIL_INVALID_EMAIL_SENDER', $from), Log::WARNING, 'jerror');

				throw new \UnexpectedValueException(sprintf('Invalid email Sender: %s, Mail::setSender(%s)', $from));
			}

			// Check for boolean false return if exception handling is disabled
			if ($result === false)
			{
				return false;
			}
		}
		catch (\phpmailerException $e)
		{
			// The parent method will have already called the logging callback, just log our deprecated error handling message
			Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

			return false;
		}

		return $this;
	}

	/**
	 * Set the email subject
	 *
	 * @param   string  $subject  Subject of the email
	 *
	 * @return  Mail  Returns this object for chaining.
	 *
	 * @since   1.7.0
	 */
	public function setSubject($subject)
	{
		$this->Subject = MailHelper::cleanLine($subject);

		return $this;
	}

	/**
	 * Set the email body
	 *
	 * @param   string  $content  Body of the email
	 *
	 * @return  Mail  Returns this object for chaining.
	 *
	 * @since   1.7.0
	 */
	public function setBody($content)
	{
		/*
		 * Filter the Body
		 * TODO: Check for XSS
		 */
		$this->Body = MailHelper::cleanText($content);

		return $this;
	}

	/**
	 * Add recipients to the email.
	 *
	 * @param   mixed   $recipient  Either a string or array of strings [email address(es)]
	 * @param   mixed   $name       Either a string or array of strings [name(s)]
	 * @param   string  $method     The parent method's name.
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 */
	protected function add($recipient, $name = '', $method = 'addAddress')
	{
		$method = lcfirst($method);

		// If the recipient is an array, add each recipient... otherwise just add the one
		if (is_array($recipient))
		{
			if (is_array($name))
			{
				$combined = array_combine($recipient, $name);

				if ($combined === false)
				{
					throw new \InvalidArgumentException("The number of elements for each array isn't equal.");
				}

				foreach ($combined as $recipientEmail => $recipientName)
				{
					$recipientEmail = MailHelper::cleanLine($recipientEmail);
					$recipientName = MailHelper::cleanLine($recipientName);

					// Wrapped in try/catch if PHPMailer is configured to throw exceptions
					try
					{
						// Check for boolean false return if exception handling is disabled
						if (call_user_func('parent::' . $method, $recipientEmail, $recipientName) === false)
						{
							return false;
						}
					}
					catch (\phpmailerException $e)
					{
						// The parent method will have already called the logging callback, just log our deprecated error handling message
						Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

						return false;
					}
				}
			}
			else
			{
				$name = MailHelper::cleanLine($name);

				foreach ($recipient as $to)
				{
					$to = MailHelper::cleanLine($to);

					// Wrapped in try/catch if PHPMailer is configured to throw exceptions
					try
					{
						// Check for boolean false return if exception handling is disabled
						if (call_user_func('parent::' . $method, $to, $name) === false)
						{
							return false;
						}
					}
					catch (\phpmailerException $e)
					{
						// The parent method will have already called the logging callback, just log our deprecated error handling message
						Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

						return false;
					}
				}
			}
		}
		else
		{
			$recipient = MailHelper::cleanLine($recipient);

			// Wrapped in try/catch if PHPMailer is configured to throw exceptions
			try
			{
				// Check for boolean false return if exception handling is disabled
				if (call_user_func('parent::' . $method, $recipient, $name) === false)
				{
					return false;
				}
			}
			catch (\phpmailerException $e)
			{
				// The parent method will have already called the logging callback, just log our deprecated error handling message
				Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

				return false;
			}
		}

		return $this;
	}

	/**
	 * Add recipients to the email
	 *
	 * @param   mixed  $recipient  Either a string or array of strings [email address(es)]
	 * @param   mixed  $name       Either a string or array of strings [name(s)]
	 *
	 * @return  Mail|boolean  Returns this object for chaining.
	 *
	 * @since   1.7.0
	 */
	public function addRecipient($recipient, $name = '')
	{
		return $this->add($recipient, $name, 'addAddress');
	}

	/**
	 * Add carbon copy recipients to the email
	 *
	 * @param   mixed  $cc    Either a string or array of strings [email address(es)]
	 * @param   mixed  $name  Either a string or array of strings [name(s)]
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   1.7.0
	 */
	public function addCc($cc, $name = '')
	{
		// If the carbon copy recipient is an array, add each recipient... otherwise just add the one
		if (isset($cc))
		{
			return $this->add($cc, $name, 'addCC');
		}

		return $this;
	}

	/**
	 * Add blind carbon copy recipients to the email
	 *
	 * @param   mixed  $bcc   Either a string or array of strings [email address(es)]
	 * @param   mixed  $name  Either a string or array of strings [name(s)]
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   1.7.0
	 */
	public function addBcc($bcc, $name = '')
	{
		// If the blind carbon copy recipient is an array, add each recipient... otherwise just add the one
		if (isset($bcc))
		{
			return $this->add($bcc, $name, 'addBCC');
		}

		return $this;
	}

	/**
	 * Add file attachment to the email
	 *
	 * @param   mixed   $path         Either a string or array of strings [filenames]
	 * @param   mixed   $name         Either a string or array of strings [names]. N.B. if this is an array it must contain the same
	 *                                number of elements as the array of paths supplied.
	 * @param   mixed   $encoding     The encoding of the attachment
	 * @param   mixed   $type         The mime type
	 * @param   string  $disposition  The disposition of the attachment
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   3.0.1
	 * @throws  \InvalidArgumentException
	 */
	public function addAttachment($path, $name = '', $encoding = 'base64', $type = 'application/octet-stream', $disposition = 'attachment')
	{
		// If the file attachments is an array, add each file... otherwise just add the one
		if (isset($path))
		{
			// Wrapped in try/catch if PHPMailer is configured to throw exceptions
			try
			{
				$result = true;

				if (is_array($path))
				{
					if (!empty($name) && count($path) != count($name))
					{
						throw new \InvalidArgumentException('The number of attachments must be equal with the number of name');
					}

					foreach ($path as $key => $file)
					{
						if (!empty($name))
						{
							$result = parent::addAttachment($file, $name[$key], $encoding, $type, $disposition);
						}
						else
						{
							$result = parent::addAttachment($file, $name, $encoding, $type, $disposition);
						}
					}
				}
				else
				{
					$result = parent::addAttachment($path, $name, $encoding, $type, $disposition);
				}

				// Check for boolean false return if exception handling is disabled
				if ($result === false)
				{
					return false;
				}
			}
			catch (\phpmailerException $e)
			{
				// The parent method will have already called the logging callback, just log our deprecated error handling message
				Log::add(__METHOD__ . '() will not catch phpmailerException objects as of 4.0.', Log::WARNING, 'deprecated');

				return false;
			}
		}

		return $this;
	}

	/**
	 * Unset all file attachments from the email
	 *
	 * @return  Mail  Returns this object for chaining.
	 *
	 * @since   3.0.1
	 */
	public function clearAttachments()
	{
		parent::clearAttachments();

		return $this;
	}

	/**
	 * Unset file attachments specified by array index.
	 *
	 * @param   integer  $index  The numerical index of the attachment to remove
	 *
	 * @return  Mail  Returns this object for chaining.
	 *
	 * @since   3.0.1
	 */
	public function removeAttachment($index = 0)
	{
		if (isset($this->attachment[$index]))
		{
			unset($this->attachment[$index]);
		}

		return $this;
	}

	/**
	 * Add Reply to email address(es) to the email
	 *
	 * @param   mixed  $replyto  Either a string or array of strings [email address(es)]
	 * @param   mixed  $name     Either a string or array of strings [name(s)]
	 *
	 * @return  Mail|boolean  Returns this object for chaining on success or boolean false on failure.
	 *
	 * @since   1.7.0
	 */
	public function addReplyTo($replyto, $name = '')
	{
		return $this->add($replyto, $name, 'addReplyTo');
	}

	/**
	 * Sets message type to HTML
	 *
	 * @param   boolean  $ishtml  Boolean true or false.
	 *
	 * @return  Mail  Returns this object for chaining.
	 *
	 * @since   3.1.4
	 */
	public function isHtml($ishtml = true)
	{
		parent::isHTML($ishtml);

		return $this;
	}

	/**
	 * Send messages using $Sendmail.
	 *
	 * This overrides the parent class to remove the restriction on the executable's name containing the word "sendmail"
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function isSendmail()
	{
		// Prefer the Joomla configured sendmail path and default to the configured PHP path otherwise
		$sendmail = Factory::getConfig()->get('sendmail', ini_get('sendmail_path'));

		// And if we still don't have a path, then use the system default for Linux
		if (empty($sendmail))
		{
			$sendmail = '/usr/sbin/sendmail';
		}

		$this->Sendmail = $sendmail;
		$this->Mailer   = 'sendmail';
	}

	/**
	 * Use sendmail for sending the email
	 *
	 * @param   string  $sendmail  Path to sendmail [optional]
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function useSendmail($sendmail = null)
	{
		$this->Sendmail = $sendmail;

		if (!empty($this->Sendmail))
		{
			$this->isSendmail();

			return true;
		}
		else
		{
			$this->isMail();

			return false;
		}
	}

	/**
	 * Use SMTP for sending the email
	 *
	 * @param   string   $auth    SMTP Authentication [optional]
	 * @param   string   $host    SMTP Host [optional]
	 * @param   string   $user    SMTP Username [optional]
	 * @param   string   $pass    SMTP Password [optional]
	 * @param   string   $secure  Use secure methods
	 * @param   integer  $port    The SMTP port
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function useSmtp($auth = null, $host = null, $user = null, $pass = null, $secure = null, $port = 25)
	{
		$this->SMTPAuth = $auth;
		$this->Host = $host;
		$this->Username = $user;
		$this->Password = $pass;
		$this->Port = $port;

		if ($secure == 'ssl' || $secure == 'tls')
		{
			$this->SMTPSecure = $secure;
		}

		if (($this->SMTPAuth !== null && $this->Host !== null && $this->Username !== null && $this->Password !== null)
			|| ($this->SMTPAuth === null && $this->Host !== null))
		{
			$this->isSMTP();

			return true;
		}
		else
		{
			$this->isMail();

			return false;
		}
	}

	/**
	 * Function to send an email
	 *
	 * @param   string   $from         From email address
	 * @param   string   $fromName     From name
	 * @param   mixed    $recipient    Recipient email address(es)
	 * @param   string   $subject      email subject
	 * @param   string   $body         Message body
	 * @param   boolean  $mode         false = plain text, true = HTML
	 * @param   mixed    $cc           CC email address(es)
	 * @param   mixed    $bcc          BCC email address(es)
	 * @param   mixed    $attachment   Attachment file name(s)
	 * @param   mixed    $replyTo      Reply to email address(es)
	 * @param   mixed    $replyToName  Reply to name(s)
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function sendMail($from, $fromName, $recipient, $subject, $body, $mode = false, $cc = null, $bcc = null, $attachment = null,
		$replyTo = null, $replyToName = null)
	{
		// Create config object
		$config = Factory::getConfig();

		$this->setSubject($subject);
		$this->setBody($body);

		// Are we sending the email as HTML?
		$this->isHtml($mode);

		/*
		 * Do not send the message if adding any of the below items fails
		 */

		if ($this->addRecipient($recipient) === false)
		{
			return false;
		}

		if ($this->addCc($cc) === false)
		{
			return false;
		}

		if ($this->addBcc($bcc) === false)
		{
			return false;
		}

		if ($this->addAttachment($attachment) === false)
		{
			return false;
		}

		// Take care of reply email addresses
		if (is_array($replyTo))
		{
			$numReplyTo = count($replyTo);

			for ($i = 0; $i < $numReplyTo; $i++)
			{
				if ($this->addReplyTo($replyTo[$i], $replyToName[$i]) === false)
				{
					return false;
				}
			}
		}
		elseif (isset($replyTo))
		{
			if ($this->addReplyTo($replyTo, $replyToName) === false)
			{
				return false;
			}
		}
		elseif ($config->get('replyto'))
		{
			$this->addReplyTo($config->get('replyto'), $config->get('replytoname'));
		}

		// Add sender to replyTo only if no replyTo received
		$autoReplyTo = (empty($this->ReplyTo)) ? true : false;

		if ($this->setSender(array($from, $fromName, $autoReplyTo)) === false)
		{
			return false;
		}

		return $this->Send();
	}

	/**
	 * Sends mail to administrator for approval of a user submission
	 *
	 * @param   string  $adminName   Name of administrator
	 * @param   string  $adminEmail  Email address of administrator
	 * @param   string  $email       [NOT USED TODO: Deprecate?]
	 * @param   string  $type        Type of item to approve
	 * @param   string  $title       Title of item to approve
	 * @param   string  $author      Author of item to approve
	 * @param   string  $url         A URL to included in the mail
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Without replacement please implement it in your own code
	 */
	public function sendAdminMail($adminName, $adminEmail, $email, $type, $title, $author, $url = null)
	{
		$subject = \JText::sprintf('JLIB_MAIL_USER_SUBMITTED', $type);

		$message = sprintf(\JText::_('JLIB_MAIL_MSG_ADMIN'), $adminName, $type, $title, $author, $url, $url, 'administrator', $type);
		$message .= \JText::_('JLIB_MAIL_MSG') . "\n";

		if ($this->addRecipient($adminEmail) === false)
		{
			return false;
		}

		$this->setSubject($subject);
		$this->setBody($message);

		return $this->Send();
	}
}
src/Mail/language/phpmailer.lang-en_gb.php000064400000003360152177723700014477 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Mail
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

$PHPMAILER_LANG['authenticate']         = JText::_('PHPMAILER_AUTHENTICATE');
$PHPMAILER_LANG['connect_host']         = JText::_('PHPMAILER_CONNECT_HOST');
$PHPMAILER_LANG['data_not_accepted']    = JText::_('PHPMAILER_DATA_NOT_ACCEPTED');
$PHPMAILER_LANG['empty_message']        = JText::_('PHPMAILER_EMPTY_MESSAGE');
$PHPMAILER_LANG['encoding']             = JText::_('PHPMAILER_ENCODING');
$PHPMAILER_LANG['execute']              = JText::_('PHPMAILER_EXECUTE');
$PHPMAILER_LANG['file_access']          = JText::_('PHPMAILER_FILE_ACCESS');
$PHPMAILER_LANG['file_open']            = JText::_('PHPMAILER_FILE_OPEN');
$PHPMAILER_LANG['from_failed']          = JText::_('PHPMAILER_FROM_FAILED');
$PHPMAILER_LANG['instantiate']          = JText::_('PHPMAILER_INSTANTIATE');
$PHPMAILER_LANG['invalid_address']      = JText::_('PHPMAILER_INVALID_ADDRESS');
$PHPMAILER_LANG['mailer_not_supported'] = JText::_('PHPMAILER_MAILER_IS_NOT_SUPPORTED');
$PHPMAILER_LANG['provide_address']      = JText::_('PHPMAILER_PROVIDE_ADDRESS');
$PHPMAILER_LANG['recipients_failed']    = JText::_('PHPMAILER_RECIPIENTS_FAILED');
$PHPMAILER_LANG['signing']              = JText::_('PHPMAILER_SIGNING_ERROR');
$PHPMAILER_LANG['smtp_connect_failed']  = JText::_('PHPMAILER_SMTP_CONNECT_FAILED');
$PHPMAILER_LANG['smtp_error']           = JText::_('PHPMAILER_SMTP_ERROR');
$PHPMAILER_LANG['variable_set']         = JText::_('PHPMAILER_VARIABLE_SET');
$PHPMAILER_LANG['extension_missing']    = JText::_('PHPMAILER_EXTENSION_MISSING');
src/Mail/MailWrapper.php000064400000004512152177723700011166 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Mail;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for MailHelper
 *
 * @package     Joomla.Platform
 * @subpackage  Mail
 * @since       3.4
 * @deprecated  4.0 Will be removed without replacement
 */
class MailWrapper
{
	/**
	 * Helper wrapper method for cleanLine
	 *
	 * @param   string  $value  String to be cleaned.
	 *
	 * @return  string  Cleaned string.
	 *
	 * @see     MailHelper::cleanLine()
	 * @since   3.4
	 */
	public function cleanLine($value)
	{
		return MailHelper::cleanLine($value);
	}

	/**
	 * Helper wrapper method for cleanText
	 *
	 * @param   string  $value  Multi-line string to be cleaned.
	 *
	 * @return  string  Cleaned multi-line string.
	 *
	 * @see     MailHelper::cleanText()
	 * @since   3.4
	 */
	public function cleanText($value)
	{
		return MailHelper::cleanText($value);
	}

	/**
	 * Helper wrapper method for cleanBody
	 *
	 * @param   string  $body  email body string.
	 *
	 * @return  string  Cleaned email body string.
	 *
	 * @see     MailHelper::cleanBody()
	 * @since   3.4
	 */
	public function cleanBody($body)
	{
		return MailHelper::cleanBody($body);
	}

	/**
	 * Helper wrapper method for cleanSubject
	 *
	 * @param   string  $subject  email subject string.
	 *
	 * @return  string  Cleaned email subject string.
	 *
	 * @see     MailHelper::cleanSubject()
	 * @since   3.4
	 */
	public function cleanSubject($subject)
	{
		return MailHelper::cleanSubject($subject);
	}

	/**
	 * Helper wrapper method for cleanAddress
	 *
	 * @param   string  $address  email address.
	 *
	 * @return  mixed   email address string or boolean false if injected headers are present
	 *
	 * @see     MailHelper::cleanAddress()
	 * @since   3.4
	 */
	public function cleanAddress($address)
	{
		return MailHelper::cleanAddress($address);
	}

	/**
	 * Helper wrapper method for isEmailAddress
	 *
	 * @param   string  $email  String to be verified.
	 *
	 * @return boolean  True if string has the correct format; false otherwise.
	 *
	 * @see     MailHelper::isEmailAddress()
	 * @since   3.4
	 */
	public function isEmailAddress($email)
	{
		return MailHelper::isEmailAddress($email);
	}
}
src/Language/Language.php000064400000072533152177723700011337 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

use Joomla\String\StringHelper;

/**
 * Languages/translation handler class
 *
 * @since  1.7.0
 */
class Language
{
	/**
	 * Array of Language objects
	 *
	 * @var    Language[]
	 * @since  1.7.0
	 */
	protected static $languages = array();

	/**
	 * Debug language, If true, highlights if string isn't found.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $debug = false;

	/**
	 * The default language, used when a language file in the requested language does not exist.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $default = 'en-GB';

	/**
	 * An array of orphaned text.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $orphans = array();

	/**
	 * Array holding the language metadata.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $metadata = null;

	/**
	 * Array holding the language locale or boolean null if none.
	 *
	 * @var    array|boolean
	 * @since  1.7.0
	 */
	protected $locale = null;

	/**
	 * The language to load.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $lang = null;

	/**
	 * A nested array of language files that have been loaded
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $paths = array();

	/**
	 * List of language files that are in error state
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $errorfiles = array();

	/**
	 * Translations
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $strings = array();

	/**
	 * An array of used text, used during debugging.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $used = array();

	/**
	 * Counter for number of loads.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $counter = 0;

	/**
	 * An array used to store overrides.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $override = array();

	/**
	 * Name of the transliterator function for this language.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $transliterator = null;

	/**
	 * Name of the pluralSuffixesCallback function for this language.
	 *
	 * @var    callable
	 * @since  1.7.0
	 */
	protected $pluralSuffixesCallback = null;

	/**
	 * Name of the ignoredSearchWordsCallback function for this language.
	 *
	 * @var    callable
	 * @since  1.7.0
	 */
	protected $ignoredSearchWordsCallback = null;

	/**
	 * Name of the lowerLimitSearchWordCallback function for this language.
	 *
	 * @var    callable
	 * @since  1.7.0
	 */
	protected $lowerLimitSearchWordCallback = null;

	/**
	 * Name of the upperLimitSearchWordCallback function for this language.
	 *
	 * @var    callable
	 * @since  1.7.0
	 */
	protected $upperLimitSearchWordCallback = null;

	/**
	 * Name of the searchDisplayedCharactersNumberCallback function for this language.
	 *
	 * @var    callable
	 * @since  1.7.0
	 */
	protected $searchDisplayedCharactersNumberCallback = null;

	/**
	 * Constructor activating the default information of the language.
	 *
	 * @param   string   $lang   The language
	 * @param   boolean  $debug  Indicates if language debugging is enabled.
	 *
	 * @since   1.7.0
	 */
	public function __construct($lang = null, $debug = false)
	{
		$this->strings = array();

		if ($lang == null)
		{
			$lang = $this->default;
		}

		$this->lang = $lang;
		$this->metadata = LanguageHelper::getMetadata($this->lang);
		$this->setDebug($debug);

		$this->override = $this->parse(JPATH_BASE . '/language/overrides/' . $lang . '.override.ini');

		// Look for a language specific localise class
		$class = str_replace('-', '_', $lang . 'Localise');
		$paths = array();

		if (defined('JPATH_SITE'))
		{
			// Note: Manual indexing to enforce load order.
			$paths[0] = JPATH_SITE . "/language/overrides/$lang.localise.php";
			$paths[2] = JPATH_SITE . "/language/$lang/$lang.localise.php";
		}

		if (defined('JPATH_ADMINISTRATOR'))
		{
			// Note: Manual indexing to enforce load order.
			$paths[1] = JPATH_ADMINISTRATOR . "/language/overrides/$lang.localise.php";
			$paths[3] = JPATH_ADMINISTRATOR . "/language/$lang/$lang.localise.php";
		}

		ksort($paths);
		$path = reset($paths);

		while (!class_exists($class) && $path)
		{
			if (file_exists($path))
			{
				require_once $path;
			}

			$path = next($paths);
		}

		if (class_exists($class))
		{
			/**
			 * Class exists. Try to find
			 * -a transliterate method,
			 * -a getPluralSuffixes method,
			 * -a getIgnoredSearchWords method
			 * -a getLowerLimitSearchWord method
			 * -a getUpperLimitSearchWord method
			 * -a getSearchDisplayCharactersNumber method
			 */
			if (method_exists($class, 'transliterate'))
			{
				$this->transliterator = array($class, 'transliterate');
			}

			if (method_exists($class, 'getPluralSuffixes'))
			{
				$this->pluralSuffixesCallback = array($class, 'getPluralSuffixes');
			}

			if (method_exists($class, 'getIgnoredSearchWords'))
			{
				$this->ignoredSearchWordsCallback = array($class, 'getIgnoredSearchWords');
			}

			if (method_exists($class, 'getLowerLimitSearchWord'))
			{
				$this->lowerLimitSearchWordCallback = array($class, 'getLowerLimitSearchWord');
			}

			if (method_exists($class, 'getUpperLimitSearchWord'))
			{
				$this->upperLimitSearchWordCallback = array($class, 'getUpperLimitSearchWord');
			}

			if (method_exists($class, 'getSearchDisplayedCharactersNumber'))
			{
				$this->searchDisplayedCharactersNumberCallback = array($class, 'getSearchDisplayedCharactersNumber');
			}
		}

		$this->load();
	}

	/**
	 * Returns a language object.
	 *
	 * @param   string   $lang   The language to use.
	 * @param   boolean  $debug  The debug mode.
	 *
	 * @return  Language  The Language object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($lang, $debug = false)
	{
		if (!isset(self::$languages[$lang . $debug]))
		{
			self::$languages[$lang . $debug] = new Language($lang, $debug);
		}

		return self::$languages[$lang . $debug];
	}

	/**
	 * Translate function, mimics the php gettext (alias _) function.
	 *
	 * The function checks if $jsSafe is true, then if $interpretBackslashes is true.
	 *
	 * @param   string   $string                The string to translate
	 * @param   boolean  $jsSafe                Make the result javascript safe
	 * @param   boolean  $interpretBackSlashes  Interpret \t and \n
	 *
	 * @return  string  The translation of the string
	 *
	 * @since   1.7.0
	 */
	public function _($string, $jsSafe = false, $interpretBackSlashes = true)
	{
		// Detect empty string
		if ($string == '')
		{
			return '';
		}

		$key = strtoupper($string);

		if (isset($this->strings[$key]))
		{
			$string = $this->strings[$key];

			// Store debug information
			if ($this->debug)
			{
				$value = \JFactory::getApplication()->get('debug_lang_const') == 0 ? $key : $string;
				$string = '**' . $value . '**';

				$caller = $this->getCallerInfo();

				if (!array_key_exists($key, $this->used))
				{
					$this->used[$key] = array();
				}

				$this->used[$key][] = $caller;
			}
		}
		else
		{
			if ($this->debug)
			{
				$caller = $this->getCallerInfo();
				$caller['string'] = $string;

				if (!array_key_exists($key, $this->orphans))
				{
					$this->orphans[$key] = array();
				}

				$this->orphans[$key][] = $caller;

				$string = '??' . $string . '??';
			}
		}

		if ($jsSafe)
		{
			// Javascript filter
			$string = addslashes($string);
		}
		elseif ($interpretBackSlashes)
		{
			if (strpos($string, '\\') !== false)
			{
				// Interpret \n and \t characters
				$string = str_replace(array('\\\\', '\t', '\n'), array("\\", "\t", "\n"), $string);
			}
		}

		return $string;
	}

	/**
	 * Transliterate function
	 *
	 * This method processes a string and replaces all accented UTF-8 characters by unaccented
	 * ASCII-7 "equivalents".
	 *
	 * @param   string  $string  The string to transliterate.
	 *
	 * @return  string  The transliteration of the string.
	 *
	 * @since   1.7.0
	 */
	public function transliterate($string)
	{
		if ($this->transliterator !== null)
		{
			return call_user_func($this->transliterator, $string);
		}

		$string = Transliterate::utf8_latin_to_ascii($string);
		$string = StringHelper::strtolower($string);

		return $string;
	}

	/**
	 * Getter for transliteration function
	 *
	 * @return  callable  The transliterator function
	 *
	 * @since   1.7.0
	 */
	public function getTransliterator()
	{
		return $this->transliterator;
	}

	/**
	 * Set the transliteration function.
	 *
	 * @param   callable  $function  Function name or the actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setTransliterator($function)
	{
		$previous = $this->transliterator;
		$this->transliterator = $function;

		return $previous;
	}

	/**
	 * Returns an array of suffixes for plural rules.
	 *
	 * @param   integer  $count  The count number the rule is for.
	 *
	 * @return  array    The array of suffixes.
	 *
	 * @since   1.7.0
	 */
	public function getPluralSuffixes($count)
	{
		if ($this->pluralSuffixesCallback !== null)
		{
			return call_user_func($this->pluralSuffixesCallback, $count);
		}
		else
		{
			return array((string) $count);
		}
	}

	/**
	 * Getter for pluralSuffixesCallback function.
	 *
	 * @return  callable  Function name or the actual function.
	 *
	 * @since   1.7.0
	 */
	public function getPluralSuffixesCallback()
	{
		return $this->pluralSuffixesCallback;
	}

	/**
	 * Set the pluralSuffixes function.
	 *
	 * @param   callable  $function  Function name or actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setPluralSuffixesCallback($function)
	{
		$previous = $this->pluralSuffixesCallback;
		$this->pluralSuffixesCallback = $function;

		return $previous;
	}

	/**
	 * Returns an array of ignored search words
	 *
	 * @return  array  The array of ignored search words.
	 *
	 * @since   1.7.0
	 */
	public function getIgnoredSearchWords()
	{
		if ($this->ignoredSearchWordsCallback !== null)
		{
			return call_user_func($this->ignoredSearchWordsCallback);
		}
		else
		{
			return array();
		}
	}

	/**
	 * Getter for ignoredSearchWordsCallback function.
	 *
	 * @return  callable  Function name or the actual function.
	 *
	 * @since   1.7.0
	 */
	public function getIgnoredSearchWordsCallback()
	{
		return $this->ignoredSearchWordsCallback;
	}

	/**
	 * Setter for the ignoredSearchWordsCallback function
	 *
	 * @param   callable  $function  Function name or actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setIgnoredSearchWordsCallback($function)
	{
		$previous = $this->ignoredSearchWordsCallback;
		$this->ignoredSearchWordsCallback = $function;

		return $previous;
	}

	/**
	 * Returns a lower limit integer for length of search words
	 *
	 * @return  integer  The lower limit integer for length of search words (3 if no value was set for a specific language).
	 *
	 * @since   1.7.0
	 */
	public function getLowerLimitSearchWord()
	{
		if ($this->lowerLimitSearchWordCallback !== null)
		{
			return call_user_func($this->lowerLimitSearchWordCallback);
		}
		else
		{
			return 3;
		}
	}

	/**
	 * Getter for lowerLimitSearchWordCallback function
	 *
	 * @return  callable  Function name or the actual function.
	 *
	 * @since   1.7.0
	 */
	public function getLowerLimitSearchWordCallback()
	{
		return $this->lowerLimitSearchWordCallback;
	}

	/**
	 * Setter for the lowerLimitSearchWordCallback function.
	 *
	 * @param   callable  $function  Function name or actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setLowerLimitSearchWordCallback($function)
	{
		$previous = $this->lowerLimitSearchWordCallback;
		$this->lowerLimitSearchWordCallback = $function;

		return $previous;
	}

	/**
	 * Returns an upper limit integer for length of search words
	 *
	 * @return  integer  The upper limit integer for length of search words (200 if no value was set or if default value is < 200).
	 *
	 * @since   1.7.0
	 */
	public function getUpperLimitSearchWord()
	{
		if ($this->upperLimitSearchWordCallback !== null && call_user_func($this->upperLimitSearchWordCallback) > 200)
		{
			return call_user_func($this->upperLimitSearchWordCallback);
		}

		return 200;
	}

	/**
	 * Getter for upperLimitSearchWordCallback function
	 *
	 * @return  callable  Function name or the actual function.
	 *
	 * @since   1.7.0
	 */
	public function getUpperLimitSearchWordCallback()
	{
		return $this->upperLimitSearchWordCallback;
	}

	/**
	 * Setter for the upperLimitSearchWordCallback function
	 *
	 * @param   callable  $function  Function name or the actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setUpperLimitSearchWordCallback($function)
	{
		$previous = $this->upperLimitSearchWordCallback;
		$this->upperLimitSearchWordCallback = $function;

		return $previous;
	}

	/**
	 * Returns the number of characters displayed in search results.
	 *
	 * @return  integer  The number of characters displayed (200 if no value was set for a specific language).
	 *
	 * @since   1.7.0
	 */
	public function getSearchDisplayedCharactersNumber()
	{
		if ($this->searchDisplayedCharactersNumberCallback !== null)
		{
			return call_user_func($this->searchDisplayedCharactersNumberCallback);
		}
		else
		{
			return 200;
		}
	}

	/**
	 * Getter for searchDisplayedCharactersNumberCallback function
	 *
	 * @return  callable  Function name or the actual function.
	 *
	 * @since   1.7.0
	 */
	public function getSearchDisplayedCharactersNumberCallback()
	{
		return $this->searchDisplayedCharactersNumberCallback;
	}

	/**
	 * Setter for the searchDisplayedCharactersNumberCallback function.
	 *
	 * @param   callable  $function  Function name or the actual function.
	 *
	 * @return  callable  The previous function.
	 *
	 * @since   1.7.0
	 */
	public function setSearchDisplayedCharactersNumberCallback($function)
	{
		$previous = $this->searchDisplayedCharactersNumberCallback;
		$this->searchDisplayedCharactersNumberCallback = $function;

		return $previous;
	}

	/**
	 * Checks if a language exists.
	 *
	 * This is a simple, quick check for the directory that should contain language files for the given user.
	 *
	 * @param   string  $lang      Language to check.
	 * @param   string  $basePath  Optional path to check.
	 *
	 * @return  boolean  True if the language exists.
	 *
	 * @since   1.7.0
	 * @deprecated   3.7.0, use LanguageHelper::exists() instead.
	 */
	public static function exists($lang, $basePath = JPATH_BASE)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::exists() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::exists($lang, $basePath);
	}

	/**
	 * Loads a single language file and appends the results to the existing strings
	 *
	 * @param   string   $extension  The extension for which a language file should be loaded.
	 * @param   string   $basePath   The basepath to use.
	 * @param   string   $lang       The language to load, default null for the current language.
	 * @param   boolean  $reload     Flag that will force a language to be reloaded if set to true.
	 * @param   boolean  $default    Flag that force the default language to be loaded if the current does not exist.
	 *
	 * @return  boolean  True if the file has successfully loaded.
	 *
	 * @since   1.7.0
	 */
	public function load($extension = 'joomla', $basePath = JPATH_BASE, $lang = null, $reload = false, $default = true)
	{
		// If language is null set as the current language.
		if (!$lang)
		{
			$lang = $this->lang;
		}

		// Load the default language first if we're not debugging and a non-default language is requested to be loaded
		// with $default set to true
		if (!$this->debug && ($lang != $this->default) && $default)
		{
			$this->load($extension, $basePath, $this->default, false, true);
		}

		$path = LanguageHelper::getLanguagePath($basePath, $lang);

		$internal = $extension == 'joomla' || $extension == '';
		$filename = $internal ? $lang : $lang . '.' . $extension;
		$filename = "$path/$filename.ini";

		if (isset($this->paths[$extension][$filename]) && !$reload)
		{
			// This file has already been tested for loading.
			$result = $this->paths[$extension][$filename];
		}
		else
		{
			// Load the language file
			$result = $this->loadLanguage($filename, $extension);

			// Check whether there was a problem with loading the file
			if ($result === false && $default)
			{
				// No strings, so either file doesn't exist or the file is invalid
				$oldFilename = $filename;

				// Check the standard file name
				if (!$this->debug)
				{
					$path = LanguageHelper::getLanguagePath($basePath, $this->default);

					$filename = $internal ? $this->default : $this->default . '.' . $extension;
					$filename = "$path/$filename.ini";

					// If the one we tried is different than the new name, try again
					if ($oldFilename != $filename)
					{
						$result = $this->loadLanguage($filename, $extension, false);
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Loads a language file.
	 *
	 * This method will not note the successful loading of a file - use load() instead.
	 *
	 * @param   string  $fileName   The name of the file.
	 * @param   string  $extension  The name of the extension.
	 *
	 * @return  boolean  True if new strings have been added to the language
	 *
	 * @see     Language::load()
	 * @since   1.7.0
	 */
	protected function loadLanguage($fileName, $extension = 'unknown')
	{
		$this->counter++;

		$result  = false;
		$strings = $this->parse($fileName);

		if ($strings !== array())
		{
			$this->strings = array_replace($this->strings, $strings, $this->override);
			$result = true;
		}

		// Record the result of loading the extension's file.
		if (!isset($this->paths[$extension]))
		{
			$this->paths[$extension] = array();
		}

		$this->paths[$extension][$fileName] = $result;

		return $result;
	}

	/**
	 * Parses a language file.
	 *
	 * @param   string  $fileName  The name of the file.
	 *
	 * @return  array  The array of parsed strings.
	 *
	 * @since   1.7.0
	 */
	protected function parse($fileName)
	{
		$strings = \JLanguageHelper::parseIniFile($fileName, $this->debug);

		// Debug the ini file if needed.
		if ($this->debug === true && file_exists($fileName))
		{
			$this->debugFile($fileName);
		}

		return $strings;
	}

	/**
	 * Debugs a language file
	 *
	 * @param   string  $filename  Absolute path to the file to debug
	 *
	 * @return  integer  A count of the number of parsing errors
	 *
	 * @since   3.6.3
	 * @throws  \InvalidArgumentException
	 */
	public function debugFile($filename)
	{
		// Make sure our file actually exists
		if (!file_exists($filename))
		{
			throw new \InvalidArgumentException(
				sprintf('Unable to locate file "%s" for debugging', $filename)
			);
		}

		// Initialise variables for manually parsing the file for common errors.
		$blacklist = array('YES', 'NO', 'NULL', 'FALSE', 'ON', 'OFF', 'NONE', 'TRUE');
		$debug = $this->getDebug();
		$this->debug = false;
		$errors = array();
		$php_errormsg = null;

		// Open the file as a stream.
		$file = new \SplFileObject($filename);

		foreach ($file as $lineNumber => $line)
		{
			// Avoid BOM error as BOM is OK when using parse_ini.
			if ($lineNumber == 0)
			{
				$line = str_replace("\xEF\xBB\xBF", '', $line);
			}

			$line = trim($line);

			// Ignore comment lines.
			if (!strlen($line) || $line['0'] == ';')
			{
				continue;
			}

			// Ignore grouping tag lines, like: [group]
			if (preg_match('#^\[[^\]]*\](\s*;.*)?$#', $line))
			{
				continue;
			}

			// Remove the "_QQ_" from the equation
			$line = str_replace('"_QQ_"', '', $line);

			// Remove any escaped double quotes \" from the equation
			$line = str_replace('\"', '', $line);

			$realNumber = $lineNumber + 1;

			// Check for any incorrect uses of _QQ_.
			if (strpos($line, '_QQ_') !== false)
			{
				$errors[] = $realNumber;
				continue;
			}

			// Check for odd number of double quotes.
			if (substr_count($line, '"') % 2 != 0)
			{
				$errors[] = $realNumber;
				continue;
			}

			// Check that the line passes the necessary format.
			if (!preg_match('#^[A-Z][A-Z0-9_\*\-\.]*\s*=\s*".*"(\s*;.*)?$#', $line))
			{
				$errors[] = $realNumber;
				continue;
			}

			// Check that the key is not in the blacklist.
			$key = strtoupper(trim(substr($line, 0, strpos($line, '='))));

			if (in_array($key, $blacklist))
			{
				$errors[] = $realNumber;
			}
		}

		// Check if we encountered any errors.
		if (count($errors))
		{
			$this->errorfiles[$filename] = $filename . ' : error(s) in line(s) ' . implode(', ', $errors);
		}
		elseif ($php_errormsg)
		{
			// We didn't find any errors but there's probably a parse notice.
			$this->errorfiles['PHP' . $filename] = 'PHP parser errors :' . $php_errormsg;
		}

		$this->debug = $debug;

		return count($errors);
	}

	/**
	 * Get a metadata language property.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed  The value of the property.
	 *
	 * @since   1.7.0
	 */
	public function get($property, $default = null)
	{
		if (isset($this->metadata[$property]))
		{
			return $this->metadata[$property];
		}

		return $default;
	}

	/**
	 * Determine who called Language or JText.
	 *
	 * @return  array  Caller information.
	 *
	 * @since   1.7.0
	 */
	protected function getCallerInfo()
	{
		// Try to determine the source if none was provided
		if (!function_exists('debug_backtrace'))
		{
			return;
		}

		$backtrace = debug_backtrace();
		$info = array();

		// Search through the backtrace to our caller
		$continue = true;

		while ($continue && next($backtrace))
		{
			$step = current($backtrace);
			$class = @ $step['class'];

			// We're looking for something outside of language.php
			if ($class != '\\Joomla\\CMS\\Language\\Language' && $class != 'JText')
			{
				$info['function'] = @ $step['function'];
				$info['class'] = $class;
				$info['step'] = prev($backtrace);

				// Determine the file and name of the file
				$info['file'] = @ $step['file'];
				$info['line'] = @ $step['line'];

				$continue = false;
			}
		}

		return $info;
	}

	/**
	 * Getter for Name.
	 *
	 * @return  string  Official name element of the language.
	 *
	 * @since   1.7.0
	 */
	public function getName()
	{
		return $this->metadata['name'];
	}

	/**
	 * Get a list of language files that have been loaded.
	 *
	 * @param   string  $extension  An optional extension name.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function getPaths($extension = null)
	{
		if (isset($extension))
		{
			if (isset($this->paths[$extension]))
			{
				return $this->paths[$extension];
			}

			return;
		}
		else
		{
			return $this->paths;
		}
	}

	/**
	 * Get a list of language files that are in error state.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function getErrorFiles()
	{
		return $this->errorfiles;
	}

	/**
	 * Getter for the language tag (as defined in RFC 3066)
	 *
	 * @return  string  The language tag.
	 *
	 * @since   1.7.0
	 */
	public function getTag()
	{
		return $this->metadata['tag'];
	}

	/**
	 * Getter for the calendar type
	 *
	 * @return  string  The calendar type.
	 *
	 * @since   3.7.0
	 */
	public function getCalendar()
	{
		if (isset($this->metadata['calendar']))
		{
			return $this->metadata['calendar'];
		}
		else
		{
			return 'gregorian';
		}
	}

	/**
	 * Get the RTL property.
	 *
	 * @return  boolean  True is it an RTL language.
	 *
	 * @since   1.7.0
	 */
	public function isRtl()
	{
		return (bool) $this->metadata['rtl'];
	}

	/**
	 * Set the Debug property.
	 *
	 * @param   boolean  $debug  The debug setting.
	 *
	 * @return  boolean  Previous value.
	 *
	 * @since   1.7.0
	 */
	public function setDebug($debug)
	{
		$previous = $this->debug;
		$this->debug = (boolean) $debug;

		return $previous;
	}

	/**
	 * Get the Debug property.
	 *
	 * @return  boolean  True is in debug mode.
	 *
	 * @since   1.7.0
	 */
	public function getDebug()
	{
		return $this->debug;
	}

	/**
	 * Get the default language code.
	 *
	 * @return  string  Language code.
	 *
	 * @since   1.7.0
	 */
	public function getDefault()
	{
		return $this->default;
	}

	/**
	 * Set the default language code.
	 *
	 * @param   string  $lang  The language code.
	 *
	 * @return  string  Previous value.
	 *
	 * @since   1.7.0
	 */
	public function setDefault($lang)
	{
		$previous = $this->default;
		$this->default = $lang;

		return $previous;
	}

	/**
	 * Get the list of orphaned strings if being tracked.
	 *
	 * @return  array  Orphaned text.
	 *
	 * @since   1.7.0
	 */
	public function getOrphans()
	{
		return $this->orphans;
	}

	/**
	 * Get the list of used strings.
	 *
	 * Used strings are those strings requested and found either as a string or a constant.
	 *
	 * @return  array  Used strings.
	 *
	 * @since   1.7.0
	 */
	public function getUsed()
	{
		return $this->used;
	}

	/**
	 * Determines is a key exists.
	 *
	 * @param   string  $string  The key to check.
	 *
	 * @return  boolean  True, if the key exists.
	 *
	 * @since   1.7.0
	 */
	public function hasKey($string)
	{
		$key = strtoupper($string);

		return isset($this->strings[$key]);
	}

	/**
	 * Returns an associative array holding the metadata.
	 *
	 * @param   string  $lang  The name of the language.
	 *
	 * @return  mixed  If $lang exists return key/value pair with the language metadata, otherwise return NULL.
	 *
	 * @since   1.7.0
	 * @deprecated   3.7.0, use LanguageHelper::getMetadata() instead.
	 */
	public static function getMetadata($lang)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::getMetadata() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::getMetadata($lang);
	}

	/**
	 * Returns a list of known languages for an area
	 *
	 * @param   string  $basePath  The basepath to use
	 *
	 * @return  array  key/value pair with the language file and real name.
	 *
	 * @since   1.7.0
	 * @deprecated   3.7.0, use LanguageHelper::getKnownLanguages() instead.
	 */
	public static function getKnownLanguages($basePath = JPATH_BASE)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::getKnownLanguages() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::getKnownLanguages($basePath);
	}

	/**
	 * Get the path to a language
	 *
	 * @param   string  $basePath  The basepath to use.
	 * @param   string  $language  The language tag.
	 *
	 * @return  string  language related path or null.
	 *
	 * @since   1.7.0
	 * @deprecated   3.7.0, use LanguageHelper::getLanguagePath() instead.
	 */
	public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::getLanguagePath() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::getLanguagePath($basePath, $language);
	}

	/**
	 * Set the language attributes to the given language.
	 *
	 * Once called, the language still needs to be loaded using Language::load().
	 *
	 * @param   string  $lang  Language code.
	 *
	 * @return  string  Previous value.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 (CMS) - Instantiate a new Language object instead
	 */
	public function setLanguage($lang)
	{
		\JLog::add(__METHOD__ . ' is deprecated. Instantiate a new Language object instead.', \JLog::WARNING, 'deprecated');

		$previous = $this->lang;
		$this->lang = $lang;
		$this->metadata = LanguageHelper::getMetadata($this->lang);

		return $previous;
	}

	/**
	 * Get the language locale based on current language.
	 *
	 * @return  array  The locale according to the language.
	 *
	 * @since   1.7.0
	 */
	public function getLocale()
	{
		if (!isset($this->locale))
		{
			$locale = str_replace(' ', '', isset($this->metadata['locale']) ? $this->metadata['locale'] : '');

			if ($locale)
			{
				$this->locale = explode(',', $locale);
			}
			else
			{
				$this->locale = false;
			}
		}

		return $this->locale;
	}

	/**
	 * Get the first day of the week for this language.
	 *
	 * @return  integer  The first day of the week according to the language
	 *
	 * @since   1.7.0
	 */
	public function getFirstDay()
	{
		return (int) (isset($this->metadata['firstDay']) ? $this->metadata['firstDay'] : 0);
	}

	/**
	 * Get the weekends days for this language.
	 *
	 * @return  string  The weekend days of the week separated by a comma according to the language
	 *
	 * @since   3.2
	 */
	public function getWeekEnd()
	{
		return (isset($this->metadata['weekEnd']) && $this->metadata['weekEnd']) ? $this->metadata['weekEnd'] : '0,6';
	}

	/**
	 * Searches for language directories within a certain base dir.
	 *
	 * @param   string  $dir  directory of files.
	 *
	 * @return  array  Array holding the found languages as filename => real name pairs.
	 *
	 * @since   1.7.0
	 * @deprecated   3.7.0, use LanguageHelper::parseLanguageFiles() instead.
	 */
	public static function parseLanguageFiles($dir = null)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::parseLanguageFiles() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::parseLanguageFiles($dir);
	}

	/**
	 * Parse XML file for language information.
	 *
	 * @param   string  $path  Path to the XML files.
	 *
	 * @return  array  Array holding the found metadata as a key => value pair.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 * @deprecated   3.7.0, use LanguageHelper::parseXMLLanguageFile() instead.
	 */
	public static function parseXMLLanguageFile($path)
	{
		\JLog::add(__METHOD__ . '() is deprecated, use LanguageHelper::parseXMLLanguageFile() instead.', \JLog::WARNING, 'deprecated');

		return LanguageHelper::parseXMLLanguageFile($path);
	}
}
src/Language/Text.php000064400000027001152177723700010526 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Log\Log;

/**
 * Text handling class.
 *
 * @since  1.7.0
 */
class Text
{
	/**
	 * JavaScript strings
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $strings = array();

	/**
	 * Translates a string into the current language.
	 *
	 * Examples:
	 * `<script>alert(Joomla.JText._('<?php echo Text::_("JDEFAULT", array("script"=>true)); ?>'));</script>`
	 * will generate an alert message containing 'Default'
	 * `<?php echo Text::_("JDEFAULT"); ?>` will generate a 'Default' string
	 *
	 * @param   string   $string                The string to translate.
	 * @param   mixed    $jsSafe                Boolean: Make the result javascript safe.
	 * @param   boolean  $interpretBackSlashes  To interpret backslashes (\\=\, \n=carriage return, \t=tabulation)
	 * @param   boolean  $script                To indicate that the string will be push in the javascript language store
	 *
	 * @return  string  The translated string or the key if $script is true
	 *
	 * @since   1.7.0
	 */
	public static function _($string, $jsSafe = false, $interpretBackSlashes = true, $script = false)
	{
		if (is_array($jsSafe))
		{
			if (array_key_exists('interpretBackSlashes', $jsSafe))
			{
				$interpretBackSlashes = (boolean) $jsSafe['interpretBackSlashes'];
			}

			if (array_key_exists('script', $jsSafe))
			{
				$script = (boolean) $jsSafe['script'];
			}

			$jsSafe = array_key_exists('jsSafe', $jsSafe) ? (boolean) $jsSafe['jsSafe'] : false;
		}

		if (self::passSprintf($string, $jsSafe, $interpretBackSlashes, $script))
		{
			return $string;
		}

		$lang = Factory::getLanguage();

		if ($script)
		{
			static::$strings[$string] = $lang->_($string, $jsSafe, $interpretBackSlashes);

			return $string;
		}

		return $lang->_($string, $jsSafe, $interpretBackSlashes);
	}

	/**
	 * Checks the string if it should be interpreted as sprintf and runs sprintf over it.
	 *
	 * @param   string   &$string               The string to translate.
	 * @param   mixed    $jsSafe                Boolean: Make the result javascript safe.
	 * @param   boolean  $interpretBackSlashes  To interpret backslashes (\\=\, \n=carriage return, \t=tabulation)
	 * @param   boolean  $script                To indicate that the string will be push in the javascript language store
	 *
	 * @return  boolean  Whether the string be interpreted as sprintf
	 *
	 * @since   3.4.4
	 */
	private static function passSprintf(&$string, $jsSafe = false, $interpretBackSlashes = true, $script = false)
	{
		// Check if string contains a comma
		if (strpos($string, ',') === false)
		{
			return false;
		}

		$lang = Factory::getLanguage();
		$string_parts = explode(',', $string);

		// Pass all parts through the Text translator
		foreach ($string_parts as $i => $str)
		{
			$string_parts[$i] = $lang->_($str, $jsSafe, $interpretBackSlashes);
		}

		$first_part = array_shift($string_parts);

		// Replace custom named placeholders with sprinftf style placeholders
		$first_part = preg_replace('/\[\[%([0-9]+):[^\]]*\]\]/', '%\1$s', $first_part);

		// Check if string contains sprintf placeholders
		if (!preg_match('/%([0-9]+\$)?s/', $first_part))
		{
			return false;
		}

		$final_string = vsprintf($first_part, $string_parts);

		// Return false if string hasn't changed
		if ($first_part === $final_string)
		{
			return false;
		}

		$string = $final_string;

		if ($script)
		{
			foreach ($string_parts as $i => $str)
			{
				static::$strings[$str] = $string_parts[$i];
			}
		}

		return true;
	}

	/**
	 * Translates a string into the current language.
	 *
	 * Examples:
	 * `<?php echo Text::alt('JALL', 'language'); ?>` will generate a 'All' string in English but a "Toutes" string in French
	 * `<?php echo Text::alt('JALL', 'module'); ?>` will generate a 'All' string in English but a "Tous" string in French
	 *
	 * @param   string   $string                The string to translate.
	 * @param   string   $alt                   The alternate option for global string
	 * @param   mixed    $jsSafe                Boolean: Make the result javascript safe.
	 * @param   boolean  $interpretBackSlashes  To interpret backslashes (\\=\, \n=carriage return, \t=tabulation)
	 * @param   boolean  $script                To indicate that the string will be pushed in the javascript language store
	 *
	 * @return  string  The translated string or the key if $script is true
	 *
	 * @since   1.7.0
	 */
	public static function alt($string, $alt, $jsSafe = false, $interpretBackSlashes = true, $script = false)
	{
		if (Factory::getLanguage()->hasKey($string . '_' . $alt))
		{
			$string .= '_' . $alt;
		}

		return static::_($string, $jsSafe, $interpretBackSlashes, $script);
	}

	/**
	 * Like Text::sprintf but tries to pluralise the string.
	 *
	 * Note that this method can take a mixed number of arguments as for the sprintf function.
	 *
	 * The last argument can take an array of options:
	 *
	 * array('jsSafe'=>boolean, 'interpretBackSlashes'=>boolean, 'script'=>boolean)
	 *
	 * where:
	 *
	 * jsSafe is a boolean to generate a javascript safe strings.
	 * interpretBackSlashes is a boolean to interpret backslashes \\->\, \n->new line, \t->tabulation.
	 * script is a boolean to indicate that the string will be push in the javascript language store.
	 *
	 * Examples:
	 * `<script>alert(Joomla.JText._('<?php echo Text::plural("COM_PLUGINS_N_ITEMS_UNPUBLISHED", 1, array("script"=>true)); ?>'));</script>`
	 * will generate an alert message containing '1 plugin successfully disabled'
	 * `<?php echo Text::plural('COM_PLUGINS_N_ITEMS_UNPUBLISHED', 1); ?>` will generate a '1 plugin successfully disabled' string
	 *
	 * @param   string   $string  The format string.
	 * @param   integer  $n       The number of items
	 *
	 * @return  string  The translated strings or the key if 'script' is true in the array of options
	 *
	 * @since   1.7.0
	 */
	public static function plural($string, $n)
	{
		$lang = Factory::getLanguage();
		$args = func_get_args();
		$count = count($args);

		if ($count < 1)
		{
			return '';
		}

		if ($count == 1)
		{
			// Default to the normal sprintf handling.
			$args[0] = $lang->_($string);

			return call_user_func_array('sprintf', $args);
		}

		// Try the key from the language plural potential suffixes
		$found = false;
		$suffixes = $lang->getPluralSuffixes((int) $n);
		array_unshift($suffixes, (int) $n);

		foreach ($suffixes as $suffix)
		{
			$key = $string . '_' . $suffix;

			if ($lang->hasKey($key))
			{
				$found = true;
				break;
			}
		}

		if (!$found)
		{
			// Not found so revert to the original.
			$key = $string;
		}

		if (is_array($args[$count - 1]))
		{
			$args[0] = $lang->_(
				$key, array_key_exists('jsSafe', $args[$count - 1]) ? $args[$count - 1]['jsSafe'] : false,
				array_key_exists('interpretBackSlashes', $args[$count - 1]) ? $args[$count - 1]['interpretBackSlashes'] : true
			);

			if (array_key_exists('script', $args[$count - 1]) && $args[$count - 1]['script'])
			{
				static::$strings[$key] = call_user_func_array('sprintf', $args);

				return $key;
			}
		}
		else
		{
			$args[0] = $lang->_($key);
		}

		return call_user_func_array('sprintf', $args);
	}

	/**
	 * Passes a string thru a sprintf.
	 *
	 * Note that this method can take a mixed number of arguments as for the sprintf function.
	 *
	 * The last argument can take an array of options:
	 *
	 * array('jsSafe'=>boolean, 'interpretBackSlashes'=>boolean, 'script'=>boolean)
	 *
	 * where:
	 *
	 * jsSafe is a boolean to generate a javascript safe strings.
	 * interpretBackSlashes is a boolean to interpret backslashes \\->\, \n->new line, \t->tabulation.
	 * script is a boolean to indicate that the string will be push in the javascript language store.
	 *
	 * @param   string  $string  The format string.
	 *
	 * @return  string  The translated strings or the key if 'script' is true in the array of options.
	 *
	 * @since   1.7.0
	 */
	public static function sprintf($string)
	{
		$lang = Factory::getLanguage();
		$args = func_get_args();
		$count = count($args);

		if ($count < 1)
		{
			return '';
		}

		if (is_array($args[$count - 1]))
		{
			$args[0] = $lang->_(
				$string, array_key_exists('jsSafe', $args[$count - 1]) ? $args[$count - 1]['jsSafe'] : false,
				array_key_exists('interpretBackSlashes', $args[$count - 1]) ? $args[$count - 1]['interpretBackSlashes'] : true
			);

			if (array_key_exists('script', $args[$count - 1]) && $args[$count - 1]['script'])
			{
				static::$strings[$string] = call_user_func_array('sprintf', $args);

				return $string;
			}
		}
		else
		{
			$args[0] = $lang->_($string);
		}

		// Replace custom named placeholders with sprintf style placeholders
		$args[0] = preg_replace('/\[\[%([0-9]+):[^\]]*\]\]/', '%\1$s', $args[0]);

		return call_user_func_array('sprintf', $args);
	}

	/**
	 * Passes a string thru an printf.
	 *
	 * Note that this method can take a mixed number of arguments as for the sprintf function.
	 *
	 * @param   string  $string  The format string.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public static function printf($string)
	{
		$lang = Factory::getLanguage();
		$args = func_get_args();
		$count = count($args);

		if ($count < 1)
		{
			return '';
		}

		if (is_array($args[$count - 1]))
		{
			$args[0] = $lang->_(
				$string, array_key_exists('jsSafe', $args[$count - 1]) ? $args[$count - 1]['jsSafe'] : false,
				array_key_exists('interpretBackSlashes', $args[$count - 1]) ? $args[$count - 1]['interpretBackSlashes'] : true
			);
		}
		else
		{
			$args[0] = $lang->_($string);
		}

		return call_user_func_array('printf', $args);
	}

	/**
	 * Translate a string into the current language and stores it in the JavaScript language store.
	 *
	 * @param   string   $string                The Text key.
	 * @param   boolean  $jsSafe                Ensure the output is JavaScript safe.
	 * @param   boolean  $interpretBackSlashes  Interpret \t and \n.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public static function script($string = null, $jsSafe = false, $interpretBackSlashes = true)
	{
		if ($string === null)
		{
			Log::add(
				sprintf(
					'As of 3.7.0, passing a null value for the first argument of %1$s() is deprecated and will not be supported in 4.0.'
					. ' Use the %2$s::getScriptStrings() method to get the strings from the JavaScript language store instead.',
					__METHOD__,
					__CLASS__
				),
				Log::WARNING,
				'deprecated'
			);
		}

		if (is_array($jsSafe))
		{
			if (array_key_exists('interpretBackSlashes', $jsSafe))
			{
				$interpretBackSlashes = (boolean) $jsSafe['interpretBackSlashes'];
			}

			if (array_key_exists('jsSafe', $jsSafe))
			{
				$jsSafe = (boolean) $jsSafe['jsSafe'];
			}
			else
			{
				$jsSafe = false;
			}
		}

		// Add the string to the array if not null.
		if ($string !== null)
		{
			// Normalize the key and translate the string.
			static::$strings[strtoupper($string)] = Factory::getLanguage()->_($string, $jsSafe, $interpretBackSlashes);

			// Load core.js dependency
			HTMLHelper::_('behavior.core');

			// Update Joomla.JText script options
			Factory::getDocument()->addScriptOptions('joomla.jtext', static::$strings, false);
		}

		return static::getScriptStrings();
	}

	/**
	 * Get the strings that have been loaded to the JavaScript language store.
	 *
	 * @return  array
	 *
	 * @since   3.7.0
	 */
	public static function getScriptStrings()
	{
		return static::$strings;
	}
}
src/Language/Wrapper/LanguageHelperWrapper.php000064400000003633152177723700015453 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language\Wrapper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Language\LanguageHelper;

/**
 * Wrapper class for LanguageHelper
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\Language\LanguageHelper` directly
 */
class LanguageHelperWrapper
{
	/**
	 * Helper wrapper method for createLanguageList
	 *
	 * @param   string   $actualLanguage  Client key for the area.
	 * @param   string   $basePath        Base path to use.
	 * @param   boolean  $caching         True if caching is used.
	 * @param   boolean  $installed       Get only installed languages.
	 *
	 * @return  array  List of system languages.
	 *
	 * @see     LanguageHelper::createLanguageList
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Language\LanguageHelper` directly
	 */
	public function createLanguageList($actualLanguage, $basePath = JPATH_BASE, $caching = false, $installed = false)
	{
		return LanguageHelper::createLanguageList($actualLanguage, $basePath, $caching, $installed);
	}

	/**
	 * Helper wrapper method for detectLanguage
	 *
	 * @return  string  locale or null if not found.
	 *
	 * @see     LanguageHelper::detectLanguage
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Language\LanguageHelper` directly
	 */
	public function detectLanguage()
	{
		return LanguageHelper::detectLanguage();
	}

	/**
	 * Helper wrapper method for getLanguages
	 *
	 * @param   string  $key  Array key
	 *
	 * @return  array  An array of published languages.
	 *
	 * @see     LanguageHelper::getLanguages
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Language\LanguageHelper` directly
	 */
	public function getLanguages($key = 'default')
	{
		return LanguageHelper::getLanguages($key);
	}
}
src/Language/Wrapper/TransliterateWrapper.php000064400000002001152177723700015375 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language\Wrapper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Language\Transliterate;

/**
 * Wrapper class for Transliterate
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\Language\Transliterate` directly
 */
class TransliterateWrapper
{
	/**
	 * Helper wrapper method for utf8_latin_to_ascii
	 *
	 * @param   string   $string  String to transliterate.
	 * @param   integer  $case    Optionally specify upper or lower case. Default to null.
	 *
	 * @return  string  Transliterated string.
	 *
	 * @see     Transliterate::utf8_latin_to_ascii()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Language\Transliterate` directly
	 */
	public function utf8_latin_to_ascii($string, $case = 0)
	{
		return Transliterate::utf8_latin_to_ascii($string, $case);
	}
}
src/Language/Wrapper/JTextWrapper.php000064400000007160152177723700013625 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language\Wrapper;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for JText
 *
 * @since       3.4
 * @deprecated  4.0  Use `JText` directly
 */
class JTextWrapper
{
	/**
	 * Helper wrapper method for _
	 *
	 * @param   string   $string                The string to translate.
	 * @param   mixed    $jsSafe                Boolean: Make the result javascript safe.
	 * @param   boolean  $interpretBackSlashes  To interpret backslashes (\\=\, \n=carriage return, \t=tabulation).
	 * @param   boolean  $script                To indicate that the string will be push in the javascript language store.
	 *
	 * @return  string  The translated string or the key if $script is true.
	 *
	 * @see     \JText::_
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function _($string, $jsSafe = false, $interpretBackSlashes = true, $script = false)
	{
		return \JText::_($string, $jsSafe, $interpretBackSlashes, $script);
	}

	/**
	 * Helper wrapper method for alt
	 *
	 * @param   string   $string                The string to translate.
	 * @param   string   $alt                   The alternate option for global string.
	 * @param   mixed    $jsSafe                Boolean: Make the result javascript safe.
	 * @param   boolean  $interpretBackSlashes  To interpret backslashes (\\=\, \n=carriage return, \t=tabulation).
	 * @param   boolean  $script                To indicate that the string will be pushed in the javascript language store.
	 *
	 * @return  string  The translated string or the key if $script is true.
	 *
	 * @see     \JText::alt
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function alt($string, $alt, $jsSafe = false, $interpretBackSlashes = true, $script = false)
	{
		return \JText::alt($string, $alt, $jsSafe, $interpretBackSlashes, $script);
	}

	/**
	 * Helper wrapper method for plural
	 *
	 * @param   string   $string  The format string.
	 * @param   integer  $n       The number of items.
	 *
	 * @return  string  The translated strings or the key if 'script' is true in the array of options.
	 *
	 * @see     \JText::plural
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function plural($string, $n)
	{
		return \JText::plural($string, $n);
	}

	/**
	 * Helper wrapper method for sprintf
	 *
	 * @param   string  $string  The format string.
	 *
	 * @return  string  The translated strings or the key if 'script' is true in the array of options.
	 *
	 * @see     \JText::sprintf
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function sprintf($string)
	{
		return \JText::sprintf($string);
	}

	/**
	 * Helper wrapper method for printf
	 *
	 * @param   string  $string  The format string.
	 *
	 * @return  mixed
	 *
	 * @see     \JText::printf
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function printf($string)
	{
		return \JText::printf($string);
	}

	/**
	 * Helper wrapper method for script
	 *
	 * @param   string   $string                The \JText key.
	 * @param   boolean  $jsSafe                Ensure the output is JavaScript safe.
	 * @param   boolean  $interpretBackSlashes  Interpret \t and \n.
	 *
	 * @return  string
	 *
	 * @see     \JText::script
	 * @since   3.4
	 * @deprecated  4.0  Use `JText` directly
	 */
	public function script($string = null, $jsSafe = false, $interpretBackSlashes = true)
	{
		return \JText::script($string, $jsSafe, $interpretBackSlashes);
	}
}
src/Language/Stemmer/Porteren.php000064400000023641152177723700013022 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @copyright  Copyright (C) 2005 Richard Heyes (http://www.phpguru.org/). All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language\Stemmer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Language\LanguageStemmer;

/**
 * Porter English stemmer class.
 *
 * This class was adapted from one written by Richard Heyes.
 * See copyright and link information above.
 *
 * @since       3.0.0
 * @deprecated  4.0 Use wamania/php-stemmer
 */
class Porteren extends LanguageStemmer
{
	/**
	 * Regex for matching a consonant.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	private static $_regex_consonant = '(?:[bcdfghjklmnpqrstvwxz]|(?<=[aeiou])y|^y)';

	/**
	 * Regex for matching a vowel
	 * @var    string
	 * @since  3.0.0
	 */
	private static $_regex_vowel = '(?:[aeiou]|(?<![aeiou])y)';

	/**
	 * Method to stem a token and return the root.
	 *
	 * @param   string  $token  The token to stem.
	 * @param   string  $lang   The language of the token.
	 *
	 * @return  string  The root token.
	 *
	 * @since   3.0.0
	 */
	public function stem($token, $lang)
	{
		// Check if the token is long enough to merit stemming.
		if (strlen($token) <= 2)
		{
			return $token;
		}

		// Check if the language is English or All.
		if ($lang !== 'en')
		{
			return $token;
		}

		// Stem the token if it is not in the cache.
		if (!isset($this->cache[$lang][$token]))
		{
			// Stem the token.
			$result = $token;
			$result = self::_step1ab($result);
			$result = self::_step1c($result);
			$result = self::_step2($result);
			$result = self::_step3($result);
			$result = self::_step4($result);
			$result = self::_step5($result);

			// Add the token to the cache.
			$this->cache[$lang][$token] = $result;
		}

		return $this->cache[$lang][$token];
	}

	/**
	 * Step 1
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step1ab($word)
	{
		// Part a
		if (substr($word, -1) == 's')
		{
				self::_replace($word, 'sses', 'ss')
			|| self::_replace($word, 'ies', 'i')
			|| self::_replace($word, 'ss', 'ss')
			|| self::_replace($word, 's', '');
		}

		// Part b
		if (substr($word, -2, 1) != 'e' || !self::_replace($word, 'eed', 'ee', 0))
		{
			// First rule
			$v = self::$_regex_vowel;

			// Check ing and ed
			// Note use of && and OR, for precedence reasons
			if (preg_match("#$v+#", substr($word, 0, -3)) && self::_replace($word, 'ing', '')
				|| preg_match("#$v+#", substr($word, 0, -2)) && self::_replace($word, 'ed', ''))
			{
				// If one of above two test successful
				if (!self::_replace($word, 'at', 'ate') && !self::_replace($word, 'bl', 'ble') && !self::_replace($word, 'iz', 'ize'))
				{
					// Double consonant ending
					if (self::_doubleConsonant($word) && substr($word, -2) != 'll' && substr($word, -2) != 'ss' && substr($word, -2) != 'zz')
					{
						$word = substr($word, 0, -1);
					}
					elseif (self::_m($word) == 1 && self::_cvc($word))
					{
						$word .= 'e';
					}
				}
			}
		}

		return $word;
	}

	/**
	 * Step 1c
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step1c($word)
	{
		$v = self::$_regex_vowel;

		if (substr($word, -1) == 'y' && preg_match("#$v+#", substr($word, 0, -1)))
		{
			self::_replace($word, 'y', 'i');
		}

		return $word;
	}

	/**
	 * Step 2
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step2($word)
	{
		switch (substr($word, -2, 1))
		{
			case 'a':
					self::_replace($word, 'ational', 'ate', 0)
				|| self::_replace($word, 'tional', 'tion', 0);
				break;
			case 'c':
					self::_replace($word, 'enci', 'ence', 0)
				|| self::_replace($word, 'anci', 'ance', 0);
				break;
			case 'e':
				self::_replace($word, 'izer', 'ize', 0);
				break;
			case 'g':
				self::_replace($word, 'logi', 'log', 0);
				break;
			case 'l':
					self::_replace($word, 'entli', 'ent', 0)
				|| self::_replace($word, 'ousli', 'ous', 0)
				|| self::_replace($word, 'alli', 'al', 0)
				|| self::_replace($word, 'bli', 'ble', 0)
				|| self::_replace($word, 'eli', 'e', 0);
				break;
			case 'o':
					self::_replace($word, 'ization', 'ize', 0)
				|| self::_replace($word, 'ation', 'ate', 0)
				|| self::_replace($word, 'ator', 'ate', 0);
				break;
			case 's':
					self::_replace($word, 'iveness', 'ive', 0)
				|| self::_replace($word, 'fulness', 'ful', 0)
				|| self::_replace($word, 'ousness', 'ous', 0)
				|| self::_replace($word, 'alism', 'al', 0);
				break;
			case 't':
					self::_replace($word, 'biliti', 'ble', 0)
				|| self::_replace($word, 'aliti', 'al', 0)
				|| self::_replace($word, 'iviti', 'ive', 0);
				break;
		}

		return $word;
	}

	/**
	 * Step 3
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step3($word)
	{
		switch (substr($word, -2, 1))
		{
			case 'a':
				self::_replace($word, 'ical', 'ic', 0);
				break;
			case 's':
				self::_replace($word, 'ness', '', 0);
				break;
			case 't':
					self::_replace($word, 'icate', 'ic', 0)
				|| self::_replace($word, 'iciti', 'ic', 0);
				break;
			case 'u':
				self::_replace($word, 'ful', '', 0);
				break;
			case 'v':
				self::_replace($word, 'ative', '', 0);
				break;
			case 'z':
				self::_replace($word, 'alize', 'al', 0);
				break;
		}

		return $word;
	}

	/**
	 * Step 4
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step4($word)
	{
		switch (substr($word, -2, 1))
		{
			case 'a':
				self::_replace($word, 'al', '', 1);
				break;
			case 'c':
					self::_replace($word, 'ance', '', 1)
				|| self::_replace($word, 'ence', '', 1);
				break;
			case 'e':
				self::_replace($word, 'er', '', 1);
				break;
			case 'i':
				self::_replace($word, 'ic', '', 1);
				break;
			case 'l':
					self::_replace($word, 'able', '', 1)
				|| self::_replace($word, 'ible', '', 1);
				break;
			case 'n':
					self::_replace($word, 'ant', '', 1)
				|| self::_replace($word, 'ement', '', 1)
				|| self::_replace($word, 'ment', '', 1)
				|| self::_replace($word, 'ent', '', 1);
				break;
			case 'o':
				if (substr($word, -4) == 'tion' || substr($word, -4) == 'sion')
				{
					self::_replace($word, 'ion', '', 1);
				}
				else
				{
					self::_replace($word, 'ou', '', 1);
				}
				break;
			case 's':
				self::_replace($word, 'ism', '', 1);
				break;
			case 't':
					self::_replace($word, 'ate', '', 1)
				|| self::_replace($word, 'iti', '', 1);
				break;
			case 'u':
				self::_replace($word, 'ous', '', 1);
				break;
			case 'v':
				self::_replace($word, 'ive', '', 1);
				break;
			case 'z':
				self::_replace($word, 'ize', '', 1);
				break;
		}

		return $word;
	}

	/**
	 * Step 5
	 *
	 * @param   string  $word  The token to stem.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	private static function _step5($word)
	{
		// Part a
		if (substr($word, -1) == 'e')
		{
			if (self::_m(substr($word, 0, -1)) > 1)
			{
				self::_replace($word, 'e', '');
			}
			elseif (self::_m(substr($word, 0, -1)) == 1)
			{
				if (!self::_cvc(substr($word, 0, -1)))
				{
					self::_replace($word, 'e', '');
				}
			}
		}

		// Part b
		if (self::_m($word) > 1 && self::_doubleConsonant($word) && substr($word, -1) == 'l')
		{
			$word = substr($word, 0, -1);
		}

		return $word;
	}

	/**
	 * Replaces the first string with the second, at the end of the string. If third
	 * arg is given, then the preceding string must match that m count at least.
	 *
	 * @param   string   &$str   String to check
	 * @param   string   $check  Ending to check for
	 * @param   string   $repl   Replacement string
	 * @param   integer  $m      Optional minimum number of m() to meet
	 *
	 * @return  boolean  Whether the $check string was at the end
	 *                   of the $str string. True does not necessarily mean
	 *                   that it was replaced.
	 *
	 * @since   3.0.0
	 */
	private static function _replace(&$str, $check, $repl, $m = null)
	{
		$len = 0 - strlen($check);

		if (substr($str, $len) == $check)
		{
			$substr = substr($str, 0, $len);

			if (is_null($m) || self::_m($substr) > $m)
			{
				$str = $substr . $repl;
			}

			return true;
		}

		return false;
	}

	/**
	 * m() measures the number of consonant sequences in $str. if c is
	 * a consonant sequence and v a vowel sequence, and <..> indicates arbitrary
	 * presence,
	 *
	 * <c><v>       gives 0
	 * <c>vc<v>     gives 1
	 * <c>vcvc<v>   gives 2
	 * <c>vcvcvc<v> gives 3
	 *
	 * @param   string  $str  The string to return the m count for
	 *
	 * @return  integer  The m count
	 *
	 * @since   3.0.0
	 */
	private static function _m($str)
	{
		$c = self::$_regex_consonant;
		$v = self::$_regex_vowel;

		$str = preg_replace("#^$c+#", '', $str);
		$str = preg_replace("#$v+$#", '', $str);

		preg_match_all("#($v+$c+)#", $str, $matches);

		return count($matches[1]);
	}

	/**
	 * Returns true/false as to whether the given string contains two
	 * of the same consonant next to each other at the end of the string.
	 *
	 * @param   string  $str  String to check
	 *
	 * @return  boolean  Result
	 *
	 * @since   3.0.0
	 */
	private static function _doubleConsonant($str)
	{
		$c = self::$_regex_consonant;

		return preg_match("#$c{2}$#", $str, $matches) && $matches[0]{0} == $matches[0]{1};
	}

	/**
	 * Checks for ending CVC sequence where second C is not W, X or Y
	 *
	 * @param   string  $str  String to check
	 *
	 * @return  boolean  Result
	 *
	 * @since   3.0.0
	 */
	private static function _cvc($str)
	{
		$c = self::$_regex_consonant;
		$v = self::$_regex_vowel;

		$result = preg_match("#($c$v$c)$#", $str, $matches)
			&& strlen($matches[1]) == 3
			&& $matches[1]{2} != 'w'
			&& $matches[1]{2} != 'x'
			&& $matches[1]{2} != 'y';

		return $result;
	}
}
src/Language/LanguageStemmer.php000064400000003410152177723700012660 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

/**
 * Stemmer base class.
 *
 * @since       3.0.0
 * @deprecated  4.0 Use wamania/php-stemmer
 */
abstract class LanguageStemmer
{
	/**
	 * An internal cache of stemmed tokens.
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $cache = array();

	/**
	 * @var    array  LanguageStemmer instances.
	 * @since  3.0.0
	 */
	protected static $instances = array();

	/**
	 * Method to get a stemmer, creating it if necessary.
	 *
	 * @param   string  $adapter  The type of stemmer to load.
	 *
	 * @return  LanguageStemmer  A LanguageStemmer instance.
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException on invalid stemmer.
	 */
	public static function getInstance($adapter)
	{
		// Only create one stemmer for each adapter.
		if (isset(self::$instances[$adapter]))
		{
			return self::$instances[$adapter];
		}

		// Setup the adapter for the stemmer.
		$class = 'Joomla\\CMS\\Language\\Stemmer\\' . ucfirst(trim($adapter));

		// Check if a stemmer exists for the adapter.
		if (!class_exists($class))
		{
			// Throw invalid adapter exception.
			throw new \RuntimeException(\JText::sprintf('JLIB_STEMMER_INVALID_STEMMER', $adapter));
		}

		self::$instances[$adapter] = new $class;

		return self::$instances[$adapter];
	}

	/**
	 * Method to stem a token and return the root.
	 *
	 * @param   string  $token  The token to stem.
	 * @param   string  $lang   The language of the token.
	 *
	 * @return  string  The root token.
	 *
	 * @since   3.0.0
	 */
	abstract public function stem($token, $lang);
}
src/Language/Associations.php000064400000011214152177723700012240 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Utitlity class for associations in multilang
 *
 * @since  3.1
 */
class Associations
{
	/**
	 * Get the associations.
	 *
	 * @param   string   $extension   The name of the component.
	 * @param   string   $tablename   The name of the table.
	 * @param   string   $context     The context
	 * @param   integer  $id          The primary key value.
	 * @param   string   $pk          The name of the primary key in the given $table.
	 * @param   string   $aliasField  If the table has an alias field set it here. Null to not use it
	 * @param   string   $catField    If the table has a catid field set it here. Null to not use it
	 * @param   array    $advClause   Additional advanced 'where' clause; use c as parent column key, c2 as associations column key
	 *
	 * @return  array  The associated items
	 *
	 * @since   3.1
	 *
	 * @throws  \Exception
	 */
	public static function getAssociations($extension, $tablename, $context, $id, $pk = 'id', $aliasField = 'alias', $catField = 'catid',
		$advClause = array())
	{
		// To avoid doing duplicate database queries.
		static $multilanguageAssociations = array();

		// Multilanguage association array key. If the key is already in the array we don't need to run the query again, just return it.
		$queryKey = md5(serialize(array_merge(array($extension, $tablename, $context, $id), $advClause)));

		if (!isset($multilanguageAssociations[$queryKey]))
		{
			$multilanguageAssociations[$queryKey] = array();

			$db = \JFactory::getDbo();
			$categoriesExtraSql = (($tablename === '#__categories') ? ' AND c2.extension = ' . $db->quote($extension) : '');
			$query = $db->getQuery(true)
				->select($db->quoteName('c2.language'))
				->from($db->quoteName($tablename, 'c'))
				->join('INNER', $db->quoteName('#__associations', 'a') . ' ON a.id = c.' . $db->quoteName($pk) . ' AND a.context=' . $db->quote($context))
				->join('INNER', $db->quoteName('#__associations', 'a2') . ' ON ' . $db->quoteName('a.key') . ' = ' . $db->quoteName('a2.key'))
				->join('INNER', $db->quoteName($tablename, 'c2') . ' ON a2.id = c2.' . $db->quoteName($pk) . $categoriesExtraSql);

			// Use alias field ?
			if (!empty($aliasField))
			{
				$query->select(
					$query->concatenate(
						array(
							$db->quoteName('c2.' . $pk),
							$db->quoteName('c2.' . $aliasField),
						),
						':'
					) . ' AS ' . $db->quoteName($pk)
				);
			}
			else
			{
				$query->select($db->quoteName('c2.' . $pk));
			}

			// Use catid field ?
			if (!empty($catField))
			{
				$query->join(
						'INNER',
						$db->quoteName('#__categories', 'ca') . ' ON ' . $db->quoteName('c2.' . $catField) . ' = ca.id AND ca.extension = ' . $db->quote($extension)
					)
					->select(
						$query->concatenate(
							array('ca.id', 'ca.alias'),
							':'
						) . ' AS ' . $db->quoteName($catField)
					);
			}

			$query->where('c.' . $pk . ' = ' . (int) $id);

			if ($tablename === '#__categories')
			{
				$query->where('c.extension = ' . $db->quote($extension));
			}

			// Advanced where clause
			if (!empty($advClause))
			{
				foreach ($advClause as $clause)
				{
					$query->where($clause);
				}
			}

			$db->setQuery($query);

			try
			{
				$items = $db->loadObjectList('language');
			}
			catch (\RuntimeException $e)
			{
				throw new \Exception($e->getMessage(), 500, $e);
			}

			if ($items)
			{
				foreach ($items as $tag => $item)
				{
					// Do not return itself as result
					if ((int) $item->{$pk} !== $id)
					{
						$multilanguageAssociations[$queryKey][$tag] = $item;
					}
				}
			}
		}

		return $multilanguageAssociations[$queryKey];
	}

	/**
	 * Method to determine if the language filter Associations parameter is enabled.
	 * This works for both site and administrator.
	 *
	 * @return  boolean  True if the parameter is implemented; false otherwise.
	 *
	 * @since   3.2
	 */
	public static function isEnabled()
	{
		// Flag to avoid doing multiple database queries.
		static $tested = false;

		// Status of language filter parameter.
		static $enabled = false;

		if (Multilanguage::isEnabled())
		{
			// If already tested, don't test again.
			if (!$tested)
			{
				$plugin = \JPluginHelper::getPlugin('system', 'languagefilter');

				if (!empty($plugin))
				{
					$params = new Registry($plugin->params);
					$enabled  = (boolean) $params->get('item_associations', true);
				}

				$tested = true;
			}
		}

		return $enabled;
	}
}
src/Language/LanguageHelper.php000064400000042065152177723700012474 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

/**
 * Language helper class
 *
 * @since  1.5
 */
class LanguageHelper
{
	/**
	 * Builds a list of the system languages which can be used in a select option
	 *
	 * @param   string   $actualLanguage  Client key for the area
	 * @param   string   $basePath        Base path to use
	 * @param   boolean  $caching         True if caching is used
	 * @param   boolean  $installed       Get only installed languages
	 *
	 * @return  array  List of system languages
	 *
	 * @since   1.5
	 */
	public static function createLanguageList($actualLanguage, $basePath = JPATH_BASE, $caching = false, $installed = false)
	{
		$list      = array();
		$clientId  = $basePath === JPATH_ADMINISTRATOR ? 1 : 0;
		$languages = $installed ? static::getInstalledLanguages($clientId, true) : self::getKnownLanguages($basePath);

		foreach ($languages as $languageCode => $language)
		{
			$metadata = $installed ? $language->metadata : $language;

			$list[] = array(
				'text'     => isset($metadata['nativeName']) ? $metadata['nativeName'] : $metadata['name'],
				'value'    => $languageCode,
				'selected' => $languageCode === $actualLanguage ? 'selected="selected"' : null,
			);
		}

		return $list;
	}

	/**
	 * Tries to detect the language.
	 *
	 * @return  string  locale or null if not found
	 *
	 * @since   1.5
	 */
	public static function detectLanguage()
	{
		if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
		{
			$browserLangs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
			$systemLangs = self::getLanguages();

			foreach ($browserLangs as $browserLang)
			{
				// Slice out the part before ; on first step, the part before - on second, place into array
				$browserLang = substr($browserLang, 0, strcspn($browserLang, ';'));
				$primary_browserLang = substr($browserLang, 0, 2);

				foreach ($systemLangs as $systemLang)
				{
					// Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
					$Jinstall_lang = $systemLang->lang_code;

					if (strlen($Jinstall_lang) < 6)
					{
						if (strtolower($browserLang) == strtolower(substr($systemLang->lang_code, 0, strlen($browserLang))))
						{
							return $systemLang->lang_code;
						}
						elseif ($primary_browserLang == substr($systemLang->lang_code, 0, 2))
						{
							$primaryDetectedLang = $systemLang->lang_code;
						}
					}
				}

				if (isset($primaryDetectedLang))
				{
					return $primaryDetectedLang;
				}
			}
		}

		return;
	}

	/**
	 * Get available languages
	 *
	 * @param   string  $key  Array key
	 *
	 * @return  array  An array of published languages
	 *
	 * @since   1.6
	 */
	public static function getLanguages($key = 'default')
	{
		static $languages;

		if (empty($languages))
		{
			// Installation uses available languages
			if (\JFactory::getApplication()->getClientId() == 2)
			{
				$languages[$key] = array();
				$knownLangs = self::getKnownLanguages(JPATH_BASE);

				foreach ($knownLangs as $metadata)
				{
					// Take off 3 letters iso code languages as they can't match browsers' languages and default them to en
					$obj = new \stdClass;
					$obj->lang_code = $metadata['tag'];
					$languages[$key][] = $obj;
				}
			}
			else
			{
				$cache = \JFactory::getCache('com_languages', '');

				if ($cache->contains('languages'))
				{
					$languages = $cache->get('languages');
				}
				else
				{
					$db = \JFactory::getDbo();
					$query = $db->getQuery(true)
						->select('*')
						->from('#__languages')
						->where('published=1')
						->order('ordering ASC');
					$db->setQuery($query);

					$languages['default'] = $db->loadObjectList();
					$languages['sef'] = array();
					$languages['lang_code'] = array();

					if (isset($languages['default'][0]))
					{
						foreach ($languages['default'] as $lang)
						{
							$languages['sef'][$lang->sef] = $lang;
							$languages['lang_code'][$lang->lang_code] = $lang;
						}
					}

					$cache->store($languages, 'languages');
				}
			}
		}

		return $languages[$key];
	}

	/**
	 * Get a list of installed languages.
	 *
	 * @param   integer  $clientId         The client app id.
	 * @param   boolean  $processMetaData  Fetch Language metadata.
	 * @param   boolean  $processManifest  Fetch Language manifest.
	 * @param   string   $pivot            The pivot of the returning array.
	 * @param   string   $orderField       Field to order the results.
	 * @param   string   $orderDirection   Direction to order the results.
	 *
	 * @return  array  Array with the installed languages.
	 *
	 * @since   3.7.0
	 */
	public static function getInstalledLanguages($clientId = null, $processMetaData = false, $processManifest = false, $pivot = 'element',
		$orderField = null, $orderDirection = null)
	{
		static $installedLanguages = null;

		if ($installedLanguages === null)
		{
			$cache = \JFactory::getCache('com_languages', '');

			if ($cache->contains('installedlanguages'))
			{
				$installedLanguages = $cache->get('installedlanguages');
			}
			else
			{
				$db = \JFactory::getDbo();

				$query = $db->getQuery(true)
					->select($db->quoteName(array('element', 'name', 'client_id', 'extension_id')))
					->from($db->quoteName('#__extensions'))
					->where($db->quoteName('type') . ' = ' . $db->quote('language'))
					->where($db->quoteName('state') . ' = 0')
					->where($db->quoteName('enabled') . ' = 1');

				$installedLanguages = $db->setQuery($query)->loadObjectList();

				$cache->store($installedLanguages, 'installedlanguages');
			}
		}

		$clients   = $clientId === null ? array(0, 1) : array((int) $clientId);
		$languages = array(
			0 => array(),
			1 => array(),
		);

		foreach ($installedLanguages as $language)
		{
			// If the language client is not needed continue cycle. Drop for performance.
			if (!in_array((int) $language->client_id, $clients))
			{
				continue;
			}

			$lang = $language;

			if ($processMetaData || $processManifest)
			{
				$clientPath = (int) $language->client_id === 0 ? JPATH_SITE : JPATH_ADMINISTRATOR;
				$metafile   = self::getLanguagePath($clientPath, $language->element) . '/' . $language->element . '.xml';

				// Process the language metadata.
				if ($processMetaData)
				{
					try
					{
						$lang->metadata = self::parseXMLLanguageFile($metafile);
					}

					// Not able to process xml language file. Fail silently.
					catch (\Exception $e)
					{
						\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), \JLog::WARNING, 'language');

						continue;
					}

					// No metadata found, not a valid language. Fail silently.
					if (!is_array($lang->metadata))
					{
						\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), \JLog::WARNING, 'language');

						continue;
					}
				}

				// Process the language manifest.
				if ($processManifest)
				{
					try
					{
						$lang->manifest = \JInstaller::parseXMLInstallFile($metafile);
					}

					// Not able to process xml language file. Fail silently.
					catch (\Exception $e)
					{
						\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METAFILE', $language->element, $metafile), \JLog::WARNING, 'language');

						continue;
					}

					// No metadata found, not a valid language. Fail silently.
					if (!is_array($lang->manifest))
					{
						\JLog::add(\JText::sprintf('JLIB_LANGUAGE_ERROR_CANNOT_LOAD_METADATA', $language->element, $metafile), \JLog::WARNING, 'language');

						continue;
					}
				}
			}

			$languages[$language->client_id][] = $lang;
		}

		// Order the list, if needed.
		if ($orderField !== null && $orderDirection !== null)
		{
			$orderDirection = strtolower($orderDirection) === 'desc' ? -1 : 1;

			foreach ($languages as $cId => $language)
			{
				// If the language client is not needed continue cycle. Drop for performance.
				if (!in_array($cId, $clients))
				{
					continue;
				}

				$languages[$cId] = ArrayHelper::sortObjects($languages[$cId], $orderField, $orderDirection, true, true);
			}
		}

		// Add the pivot, if needed.
		if ($pivot !== null)
		{
			foreach ($languages as $cId => $language)
			{
				// If the language client is not needed continue cycle. Drop for performance.
				if (!in_array($cId, $clients))
				{
					continue;
				}

				$languages[$cId] = ArrayHelper::pivot($languages[$cId], $pivot);
			}
		}

		return $clientId !== null ? $languages[$clientId] : $languages;
	}

	/**
	 * Get a list of content languages.
	 *
	 * @param   array    $publishedStates  Array with the content language published states. Empty array for all.
	 * @param   boolean  $checkInstalled   Check if the content language is installed.
	 * @param   string   $pivot            The pivot of the returning array.
	 * @param   string   $orderField       Field to order the results.
	 * @param   string   $orderDirection   Direction to order the results.
	 *
	 * @return  array  Array of the content languages.
	 *
	 * @since   3.7.0
	 */
	public static function getContentLanguages($publishedStates = array(1), $checkInstalled = true, $pivot = 'lang_code', $orderField = null,
		$orderDirection = null)
	{
		static $contentLanguages = null;

		if ($contentLanguages === null)
		{
			$cache = \JFactory::getCache('com_languages', '');

			if ($cache->contains('contentlanguages'))
			{
				$contentLanguages = $cache->get('contentlanguages');
			}
			else
			{
				$db = \JFactory::getDbo();

				$query = $db->getQuery(true)
					->select('*')
					->from($db->quoteName('#__languages'));

				$contentLanguages = $db->setQuery($query)->loadObjectList();

				$cache->store($contentLanguages, 'contentlanguages');
			}
		}

		$languages = $contentLanguages;

		// B/C layer. Before 3.8.3.
		if ($publishedStates === true)
		{
			$publishedStates = array(1);
		}
		elseif ($publishedStates === false)
		{
			$publishedStates = array();
		}

		// Check the language published state, if needed.
		if (count($publishedStates) > 0)
		{
			foreach ($languages as $key => $language)
			{
				if (!in_array((int) $language->published, $publishedStates, true))
				{
					unset($languages[$key]);
				}
			}
		}

		// Check if the language is installed, if needed.
		if ($checkInstalled)
		{
			$languages = array_values(array_intersect_key(ArrayHelper::pivot($languages, 'lang_code'), static::getInstalledLanguages(0)));
		}

		// Order the list, if needed.
		if ($orderField !== null && $orderDirection !== null)
		{
			$languages = ArrayHelper::sortObjects($languages, $orderField, strtolower($orderDirection) === 'desc' ? -1 : 1, true, true);
		}

		// Add the pivot, if needed.
		if ($pivot !== null)
		{
			$languages = ArrayHelper::pivot($languages, $pivot);
		}

		return $languages;
	}

	/**
	 * Parse strings from a language file.
	 *
	 * @param   string   $fileName  The language ini file path.
	 * @param   boolean  $debug     If set to true debug language ini file.
	 *
	 * @return  boolean  True if saved, false otherwise.
	 *
	 * @since   3.9.0
	 */
	public static function parseIniFile($fileName, $debug = false)
	{
		// Check if file exists.
		if (!file_exists($fileName))
		{
			return array();
		}

		// @deprecated 3.9.0 Usage of "_QQ_" is deprecated. Use escaped double quotes (\") instead.
		if (!defined('_QQ_'))
		{
			/**
			 * Defines a placeholder for a double quote character (") in a language file
			 *
			 * @var    string
			 * @since  1.6
			 * @deprecated  4.0 Use escaped double quotes (\") instead.
			 */
			define('_QQ_', '"');
		}

		// Capture hidden PHP errors from the parsing.
		if ($debug === true)
		{
			// See https://www.php.net/manual/en/reserved.variables.phperrormsg.php
			$php_errormsg = null;

			$trackErrors = ini_get('track_errors');
			ini_set('track_errors', true);
		}

		// This was required for https://github.com/joomla/joomla-cms/issues/17198 but not sure what server setup
		// issue it is solving
		$disabledFunctions = explode(',', ini_get('disable_functions'));
		$isParseIniFileDisabled = in_array('parse_ini_file', array_map('trim', $disabledFunctions));

		if (!function_exists('parse_ini_file') || $isParseIniFileDisabled)
		{
			$contents = file_get_contents($fileName);
			$contents = str_replace('_QQ_', '"\""', $contents);
			$strings = @parse_ini_string($contents);
		}
		else
		{
			$strings = @parse_ini_file($fileName);
		}

		// Restore error tracking to what it was before.
		if ($debug === true)
		{
			ini_set('track_errors', $trackErrors);
		}

		return is_array($strings) ? $strings : array();
	}

	/**
	 * Save strings to a language file.
	 *
	 * @param   string  $fileName  The language ini file path.
	 * @param   array   $strings   The array of strings.
	 *
	 * @return  boolean  True if saved, false otherwise.
	 *
	 * @since   3.7.0
	 */
	public static function saveToIniFile($fileName, array $strings)
	{
		\JLoader::register('\JFile', JPATH_LIBRARIES . '/joomla/filesystem/file.php');

		// Escape double quotes.
		foreach ($strings as $key => $string)
		{
			$strings[$key] = addcslashes($string, '"');
		}

		// Write override.ini file with the strings.
		$registry = new Registry($strings);

		return \JFile::write($fileName, $registry->toString('INI'));
	}

	/**
	 * Checks if a language exists.
	 *
	 * This is a simple, quick check for the directory that should contain language files for the given user.
	 *
	 * @param   string  $lang      Language to check.
	 * @param   string  $basePath  Optional path to check.
	 *
	 * @return  boolean  True if the language exists.
	 *
	 * @since   3.7.0
	 */
	public static function exists($lang, $basePath = JPATH_BASE)
	{
		static $paths = array();

		// Return false if no language was specified
		if (!$lang)
		{
			return false;
		}

		$path = $basePath . '/language/' . $lang;

		// Return previous check results if it exists
		if (isset($paths[$path]))
		{
			return $paths[$path];
		}

		// Check if the language exists
		$paths[$path] = is_dir($path);

		return $paths[$path];
	}

	/**
	 * Returns an associative array holding the metadata.
	 *
	 * @param   string  $lang  The name of the language.
	 *
	 * @return  mixed  If $lang exists return key/value pair with the language metadata, otherwise return NULL.
	 *
	 * @since   3.7.0
	 */
	public static function getMetadata($lang)
	{
		$file   = self::getLanguagePath(JPATH_BASE, $lang) . '/' . $lang . '.xml';
		$result = null;

		if (is_file($file))
		{
			$result = self::parseXMLLanguageFile($file);
		}

		if (empty($result))
		{
			return;
		}

		return $result;
	}

	/**
	 * Returns a list of known languages for an area
	 *
	 * @param   string  $basePath  The basepath to use
	 *
	 * @return  array  key/value pair with the language file and real name.
	 *
	 * @since   3.7.0
	 */
	public static function getKnownLanguages($basePath = JPATH_BASE)
	{
		return self::parseLanguageFiles(self::getLanguagePath($basePath));
	}

	/**
	 * Get the path to a language
	 *
	 * @param   string  $basePath  The basepath to use.
	 * @param   string  $language  The language tag.
	 *
	 * @return  string  language related path or null.
	 *
	 * @since   3.7.0
	 */
	public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
	{
		return $basePath . '/language' . (!empty($language) ? '/' . $language : '');
	}

	/**
	 * Searches for language directories within a certain base dir.
	 *
	 * @param   string  $dir  directory of files.
	 *
	 * @return  array  Array holding the found languages as filename => real name pairs.
	 *
	 * @since   3.7.0
	 */
	public static function parseLanguageFiles($dir = null)
	{
		$languages = array();

		// Search main language directory for subdirectories
		foreach (glob($dir . '/*', GLOB_NOSORT | GLOB_ONLYDIR) as $directory)
		{
			// But only directories with lang code format
			if (preg_match('#/[a-z]{2,3}-[A-Z]{2}$#', $directory))
			{
				$dirPathParts = pathinfo($directory);
				$file         = $directory . '/' . $dirPathParts['filename'] . '.xml';

				if (!is_file($file))
				{
					continue;
				}

				try
				{
					// Get installed language metadata from xml file and merge it with lang array
					if ($metadata = self::parseXMLLanguageFile($file))
					{
						$languages = array_replace($languages, array($dirPathParts['filename'] => $metadata));
					}
				}
				catch (\RuntimeException $e)
				{
				}
			}
		}

		return $languages;
	}

	/**
	 * Parse XML file for language information.
	 *
	 * @param   string  $path  Path to the XML files.
	 *
	 * @return  array  Array holding the found metadata as a key => value pair.
	 *
	 * @since   3.7.0
	 * @throws  \RuntimeException
	 */
	public static function parseXMLLanguageFile($path)
	{
		if (!is_readable($path))
		{
			throw new \RuntimeException('File not found or not readable');
		}

		// Try to load the file
		$xml = simplexml_load_file($path);

		if (!$xml)
		{
			return;
		}

		// Check that it's a metadata file
		if ((string) $xml->getName() != 'metafile')
		{
			return;
		}

		$metadata = array();

		foreach ($xml->metadata->children() as $child)
		{
			$metadata[$child->getName()] = (string) $child;
		}

		return $metadata;
	}
}
src/Language/Transliterate.php000064400000011741152177723700012427 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

/**
 * Class to transliterate strings
 *
 * @since  1.7.0
 * @note   Port of phputf8's utf8_accents_to_ascii()
 */
class Transliterate
{
	/**
	 * Returns strings transliterated from UTF-8 to Latin
	 *
	 * @param   string   $string  String to transliterate
	 * @param   integer  $case    Optionally specify upper or lower case. Default to null.
	 *
	 * @return  string  Transliterated string
	 *
	 * @since   1.7.0
	 */
	public static function utf8_latin_to_ascii($string, $case = 0)
	{
		static $UTF8_LOWER_ACCENTS = null;
		static $UTF8_UPPER_ACCENTS = null;

		if ($case <= 0)
		{
			if (is_null($UTF8_LOWER_ACCENTS))
			{
				$UTF8_LOWER_ACCENTS = array(
					'à' => 'a',
					'ô' => 'o',
					'ď' => 'd',
					'ḟ' => 'f',
					'ë' => 'e',
					'š' => 's',
					'ơ' => 'o',
					'ß' => 'ss',
					'ă' => 'a',
					'ř' => 'r',
					'ț' => 't',
					'ň' => 'n',
					'ā' => 'a',
					'ķ' => 'k',
					'ŝ' => 's',
					'ỳ' => 'y',
					'ņ' => 'n',
					'ĺ' => 'l',
					'ħ' => 'h',
					'ṗ' => 'p',
					'ó' => 'o',
					'ú' => 'u',
					'ě' => 'e',
					'é' => 'e',
					'ç' => 'c',
					'ẁ' => 'w',
					'ċ' => 'c',
					'õ' => 'o',
					'ṡ' => 's',
					'ø' => 'o',
					'ģ' => 'g',
					'ŧ' => 't',
					'ș' => 's',
					'ė' => 'e',
					'ĉ' => 'c',
					'ś' => 's',
					'î' => 'i',
					'ű' => 'u',
					'ć' => 'c',
					'ę' => 'e',
					'ŵ' => 'w',
					'ṫ' => 't',
					'ū' => 'u',
					'č' => 'c',
					'ö' => 'oe',
					'è' => 'e',
					'ŷ' => 'y',
					'ą' => 'a',
					'ł' => 'l',
					'ų' => 'u',
					'ů' => 'u',
					'ş' => 's',
					'ğ' => 'g',
					'ļ' => 'l',
					'ƒ' => 'f',
					'ž' => 'z',
					'ẃ' => 'w',
					'ḃ' => 'b',
					'å' => 'a',
					'ì' => 'i',
					'ï' => 'i',
					'ḋ' => 'd',
					'ť' => 't',
					'ŗ' => 'r',
					'ä' => 'ae',
					'í' => 'i',
					'ŕ' => 'r',
					'ê' => 'e',
					'ü' => 'ue',
					'ò' => 'o',
					'ē' => 'e',
					'ñ' => 'n',
					'ń' => 'n',
					'ĥ' => 'h',
					'ĝ' => 'g',
					'đ' => 'd',
					'ĵ' => 'j',
					'ÿ' => 'y',
					'ũ' => 'u',
					'ŭ' => 'u',
					'ư' => 'u',
					'ţ' => 't',
					'ý' => 'y',
					'ő' => 'o',
					'â' => 'a',
					'ľ' => 'l',
					'ẅ' => 'w',
					'ż' => 'z',
					'ī' => 'i',
					'ã' => 'a',
					'ġ' => 'g',
					'ṁ' => 'm',
					'ō' => 'o',
					'ĩ' => 'i',
					'ù' => 'u',
					'į' => 'i',
					'ź' => 'z',
					'á' => 'a',
					'û' => 'u',
					'þ' => 'th',
					'ð' => 'dh',
					'æ' => 'ae',
					'µ' => 'u',
					'ĕ' => 'e',
					'œ' => 'oe',
				);
			}

			$string = str_replace(array_keys($UTF8_LOWER_ACCENTS), array_values($UTF8_LOWER_ACCENTS), $string);
		}

		if ($case >= 0)
		{
			if (is_null($UTF8_UPPER_ACCENTS))
			{
				$UTF8_UPPER_ACCENTS = array(
					'À' => 'A',
					'Ô' => 'O',
					'Ď' => 'D',
					'Ḟ' => 'F',
					'Ë' => 'E',
					'Š' => 'S',
					'Ơ' => 'O',
					'Ă' => 'A',
					'Ř' => 'R',
					'Ț' => 'T',
					'Ň' => 'N',
					'Ā' => 'A',
					'Ķ' => 'K',
					'Ŝ' => 'S',
					'Ỳ' => 'Y',
					'Ņ' => 'N',
					'Ĺ' => 'L',
					'Ħ' => 'H',
					'Ṗ' => 'P',
					'Ó' => 'O',
					'Ú' => 'U',
					'Ě' => 'E',
					'É' => 'E',
					'Ç' => 'C',
					'Ẁ' => 'W',
					'Ċ' => 'C',
					'Õ' => 'O',
					'Ṡ' => 'S',
					'Ø' => 'O',
					'Ģ' => 'G',
					'Ŧ' => 'T',
					'Ș' => 'S',
					'Ė' => 'E',
					'Ĉ' => 'C',
					'Ś' => 'S',
					'Î' => 'I',
					'Ű' => 'U',
					'Ć' => 'C',
					'Ę' => 'E',
					'Ŵ' => 'W',
					'Ṫ' => 'T',
					'Ū' => 'U',
					'Č' => 'C',
					'Ö' => 'Oe',
					'È' => 'E',
					'Ŷ' => 'Y',
					'Ą' => 'A',
					'Ł' => 'L',
					'Ų' => 'U',
					'Ů' => 'U',
					'Ş' => 'S',
					'Ğ' => 'G',
					'Ļ' => 'L',
					'Ƒ' => 'F',
					'Ž' => 'Z',
					'Ẃ' => 'W',
					'Ḃ' => 'B',
					'Å' => 'A',
					'Ì' => 'I',
					'Ï' => 'I',
					'Ḋ' => 'D',
					'Ť' => 'T',
					'Ŗ' => 'R',
					'Ä' => 'Ae',
					'Í' => 'I',
					'Ŕ' => 'R',
					'Ê' => 'E',
					'Ü' => 'Ue',
					'Ò' => 'O',
					'Ē' => 'E',
					'Ñ' => 'N',
					'Ń' => 'N',
					'Ĥ' => 'H',
					'Ĝ' => 'G',
					'Đ' => 'D',
					'Ĵ' => 'J',
					'Ÿ' => 'Y',
					'Ũ' => 'U',
					'Ŭ' => 'U',
					'Ư' => 'U',
					'Ţ' => 'T',
					'Ý' => 'Y',
					'Ő' => 'O',
					'Â' => 'A',
					'Ľ' => 'L',
					'Ẅ' => 'W',
					'Ż' => 'Z',
					'Ī' => 'I',
					'Ã' => 'A',
					'Ġ' => 'G',
					'Ṁ' => 'M',
					'Ō' => 'O',
					'Ĩ' => 'I',
					'Ù' => 'U',
					'Į' => 'I',
					'Ź' => 'Z',
					'Á' => 'A',
					'Û' => 'U',
					'Þ' => 'Th',
					'Ð' => 'Dh',
					'Æ' => 'Ae',
					'Ĕ' => 'E',
					'Œ' => 'Oe',
				);
			}

			$string = str_replace(array_keys($UTF8_UPPER_ACCENTS), array_values($UTF8_UPPER_ACCENTS), $string);
		}

		return $string;
	}
}
src/Language/Multilanguage.php000064400000005322152177723700012402 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Language;

defined('JPATH_PLATFORM') or die;

/**
 * Utitlity class for multilang
 *
 * @since  2.5.4
 */
class Multilanguage
{
	/**
	 * Method to determine if the language filter plugin is enabled.
	 * This works for both site and administrator.
	 *
	 * @return  boolean  True if site is supporting multiple languages; false otherwise.
	 *
	 * @since   2.5.4
	 */
	public static function isEnabled()
	{
		// Flag to avoid doing multiple database queries.
		static $tested = false;

		// Status of language filter plugin.
		static $enabled = false;

		// Get application object.
		$app = \JFactory::getApplication();

		// If being called from the frontend, we can avoid the database query.
		if ($app->isClient('site'))
		{
			$enabled = $app->getLanguageFilter();

			return $enabled;
		}

		// If already tested, don't test again.
		if (!$tested)
		{
			// Determine status of language filter plugin.
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('enabled')
				->from($db->quoteName('#__extensions'))
				->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
				->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
				->where($db->quoteName('element') . ' = ' . $db->quote('languagefilter'));
			$db->setQuery($query);

			$enabled = $db->loadResult();
			$tested = true;
		}

		return (bool) $enabled;
	}

	/**
	 * Method to return a list of published site languages.
	 *
	 * @return  array of language extension objects.
	 *
	 * @since   3.5
	 * @deprecated   3.7.0  Use \JLanguageHelper::getInstalledLanguages(0) instead.
	 */
	public static function getSiteLangs()
	{
		\JLog::add(__METHOD__ . ' is deprecated. Use \JLanguageHelper::getInstalledLanguages(0) instead.', \JLog::WARNING, 'deprecated');

		return \JLanguageHelper::getInstalledLanguages(0);
	}

	/**
	 * Method to return a list of language home page menu items.
	 *
	 * @return  array of menu objects.
	 *
	 * @since   3.5
	 */
	public static function getSiteHomePages()
	{
		// To avoid doing duplicate database queries.
		static $multilangSiteHomePages = null;

		if (!isset($multilangSiteHomePages))
		{
			// Check for Home pages languages.
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('language')
				->select('id')
				->from($db->quoteName('#__menu'))
				->where('home = 1')
				->where('published = 1')
				->where('client_id = 0');
			$db->setQuery($query);

			$multilangSiteHomePages = $db->loadObjectList('language');
		}

		return $multilangSiteHomePages;
	}
}
src/Crypt/Key.php000064400000003000152177723700007701 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt;

defined('JPATH_PLATFORM') or die;

/**
 * Encryption key object for the Joomla Platform.
 *
 * @property-read  string  $type  The key type.
 *
 * @since  3.0.0
 */
class Key
{
	/**
	 * @var    string  The private key.
	 * @since  3.0.0
	 */
	public $private;

	/**
	 * @var    string  The public key.
	 * @since  3.0.0
	 */
	public $public;

	/**
	 * @var    string  The key type.
	 * @since  3.0.0
	 */
	protected $type;

	/**
	 * Constructor.
	 *
	 * @param   string  $type     The key type.
	 * @param   string  $private  The private key.
	 * @param   string  $public   The public key.
	 *
	 * @since   3.0.0
	 */
	public function __construct($type, $private = null, $public = null)
	{
		// Set the key type.
		$this->type = (string) $type;

		// Set the optional public/private key strings.
		$this->private = isset($private) ? (string) $private : null;
		$this->public  = isset($public) ? (string) $public : null;
	}

	/**
	 * Magic method to return some protected property values.
	 *
	 * @param   string  $name  The name of the property to return.
	 *
	 * @return  mixed
	 *
	 * @since   3.0.0
	 */
	public function __get($name)
	{
		if ($name == 'type')
		{
			return $this->type;
		}

		trigger_error('Cannot access property ' . __CLASS__ . '::' . $name, E_USER_WARNING);
	}
}
src/Crypt/Cipher/CryptoCipher.php000064400000006176152177723700013017 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Key;

/**
 * Crypt cipher for encryption, decryption and key generation via the php-encryption library.
 *
 * @since  3.5
 */
class CryptoCipher implements CipherInterface
{
	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 * @param   Key     $key   The key object to use for decryption.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.5
	 * @throws  \RuntimeException
	 */
	public function decrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != 'crypto')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected crypto.');
		}

		// Decrypt the data.
		try
		{
			return \Crypto::Decrypt($data, $key->public);
		}
		catch (\InvalidCiphertextException $ex)
		{
			throw new \RuntimeException('DANGER! DANGER! The ciphertext has been tampered with!', $ex->getCode(), $ex);
		}
		catch (\CryptoTestFailedException $ex)
		{
			throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
		}
		catch (\CannotPerformOperationException $ex)
		{
			throw new \RuntimeException('Cannot safely perform decryption', $ex->getCode(), $ex);
		}
	}

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 * @param   Key     $key   The key object to use for encryption.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.5
	 * @throws  \RuntimeException
	 */
	public function encrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != 'crypto')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected crypto.');
		}

		// Encrypt the data.
		try
		{
			return \Crypto::Encrypt($data, $key->public);
		}
		catch (\CryptoTestFailedException $ex)
		{
			throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
		}
		catch (\CannotPerformOperationException $ex)
		{
			throw new \RuntimeException('Cannot safely perform encryption', $ex->getCode(), $ex);
		}
	}

	/**
	 * Method to generate a new encryption key object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.5
	 * @throws  \RuntimeException
	 */
	public function generateKey(array $options = array())
	{
		// Create the new encryption key object.
		$key = new Key('crypto');

		// Generate the encryption key.
		try
		{
			$key->public = \Crypto::CreateNewRandomKey();
		}
		catch (\CryptoTestFailedException $ex)
		{
			throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
		}
		catch (\CannotPerformOperationException $ex)
		{
			throw new \RuntimeException('Cannot safely create a key', $ex->getCode(), $ex);
		}

		// Explicitly flag the private as unused in this cipher.
		$key->private = 'unused';

		return $key;
	}
}
src/Crypt/Cipher/McryptCipher.php000064400000010727152177723700013012 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\Key;

/**
 * Crypt cipher for mcrypt algorithm encryption, decryption and key generation.
 *
 * @since       3.0.0
 * @deprecated  4.0   Without replacment use CryptoCipher
 */
abstract class McryptCipher implements CipherInterface
{
	/**
	 * @var    integer  The mcrypt cipher constant.
	 * @link   https://www.php.net/manual/en/mcrypt.ciphers.php
	 * @since  3.0.0
	 */
	protected $type;

	/**
	 * @var    integer  The mcrypt block cipher mode.
	 * @link   https://www.php.net/manual/en/mcrypt.constants.php
	 * @since  3.0.0
	 */
	protected $mode;

	/**
	 * @var    string  The Crypt key type for validation.
	 * @since  3.0.0
	 */
	protected $keyType;

	/**
	 * Constructor.
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	public function __construct()
	{
		if (!is_callable('mcrypt_encrypt'))
		{
			throw new \RuntimeException('The mcrypt extension is not available.');
		}
	}

	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 * @param   Key     $key   The key object to use for decryption.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function decrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != $this->keyType)
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected ' . $this->keyType . '.');
		}

		// Decrypt the data.
		$decrypted = trim(mcrypt_decrypt($this->type, $key->private, $data, $this->mode, $key->public));

		return $decrypted;
	}

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 * @param   Key     $key   The key object to use for encryption.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function encrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != $this->keyType)
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected ' . $this->keyType . '.');
		}

		// Encrypt the data.
		$encrypted = mcrypt_encrypt($this->type, $key->private, $data, $this->mode, $key->public);

		return $encrypted;
	}

	/**
	 * Method to generate a new encryption key object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function generateKey(array $options = array())
	{
		// Create the new encryption key object.
		$key = new Key($this->keyType);

		// Generate an initialisation vector based on the algorithm.
		$key->public = mcrypt_create_iv(mcrypt_get_iv_size($this->type, $this->mode), MCRYPT_DEV_URANDOM);

		// Get the salt and password setup.
		$salt = (isset($options['salt'])) ? $options['salt'] : substr(pack('h*', md5(Crypt::genRandomBytes())), 0, 16);

		if (!isset($options['password']))
		{
			throw new \InvalidArgumentException('Password is not set.');
		}

		// Generate the derived key.
		$key->private = $this->pbkdf2($options['password'], $salt, mcrypt_get_key_size($this->type, $this->mode));

		return $key;
	}

	/**
	 * PBKDF2 Implementation for deriving keys.
	 *
	 * @param   string   $p   Password
	 * @param   string   $s   Salt
	 * @param   integer  $kl  Key length
	 * @param   integer  $c   Iteration count
	 * @param   string   $a   Hash algorithm
	 *
	 * @return  string  The derived key.
	 *
	 * @link    https://en.wikipedia.org/wiki/PBKDF2
	 * @link    http://www.ietf.org/rfc/rfc2898.txt
	 * @since   3.0.0
	 */
	public function pbkdf2($p, $s, $kl, $c = 10000, $a = 'sha256')
	{
		// Hash length.
		$hl = strlen(hash($a, null, true));

		// Key blocks to compute.
		$kb = ceil($kl / $hl);

		// Derived key.
		$dk = '';

		// Create the key.
		for ($block = 1; $block <= $kb; $block++)
		{
			// Initial hash for this block.
			$ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);

			// Perform block iterations.
			for ($i = 1; $i < $c; $i++)
			{
				$ib ^= ($b = hash_hmac($a, $b, $p, true));
			}

			// Append the iterated block.
			$dk .= $ib;
		}

		// Return derived key of correct length.
		return substr($dk, 0, $kl);
	}
}
src/Crypt/Cipher/SimpleCipher.php000064400000012376152177723700012767 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\Key;

/**
 * Crypt cipher for Simple encryption, decryption and key generation.
 *
 * @since       3.0.0
 * @deprecated  4.0 (CMS)
 */
class SimpleCipher implements CipherInterface
{
	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 * @param   Key     $key   The key[/pair] object to use for decryption.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function decrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != 'simple')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected simple.');
		}

		$decrypted = '';
		$tmp = $key->public;

		// Convert the HEX input into an array of integers and get the number of characters.
		$chars = $this->_hexToIntArray($data);
		$charCount = count($chars);

		// Repeat the key as many times as necessary to ensure that the key is at least as long as the input.
		for ($i = 0; $i < $charCount; $i = strlen($tmp))
		{
			$tmp = $tmp . $tmp;
		}

		// Get the XOR values between the ASCII values of the input and key characters for all input offsets.
		for ($i = 0; $i < $charCount; $i++)
		{
			$decrypted .= chr($chars[$i] ^ ord($tmp[$i]));
		}

		return $decrypted;
	}

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 * @param   Key     $key   The key[/pair] object to use for encryption.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function encrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type != 'simple')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected simple.');
		}

		$encrypted = '';
		$tmp = $key->private;

		// Split up the input into a character array and get the number of characters.
		$chars = preg_split('//', $data, -1, PREG_SPLIT_NO_EMPTY);
		$charCount = count($chars);

		// Repeat the key as many times as necessary to ensure that the key is at least as long as the input.
		for ($i = 0; $i < $charCount; $i = strlen($tmp))
		{
			$tmp = $tmp . $tmp;
		}

		// Get the XOR values between the ASCII values of the input and key characters for all input offsets.
		for ($i = 0; $i < $charCount; $i++)
		{
			$encrypted .= $this->_intToHex(ord($tmp[$i]) ^ ord($chars[$i]));
		}

		return $encrypted;
	}

	/**
	 * Method to generate a new encryption key[/pair] object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.0.0
	 */
	public function generateKey(array $options = array())
	{
		// Create the new encryption key[/pair] object.
		$key = new Key('simple');

		// Just a random key of a given length.
		$key->private = Crypt::genRandomBytes(256);
		$key->public  = $key->private;

		return $key;
	}

	/**
	 * Convert hex to an integer
	 *
	 * @param   string   $s  The hex string to convert.
	 * @param   integer  $i  The offset?
	 *
	 * @return  integer
	 *
	 * @since   1.7.0
	 */
	private function _hexToInt($s, $i)
	{
		$j = (int) $i * 2;
		$k = 0;
		$s1 = (string) $s;

		// Get the character at position $j.
		$c = substr($s1, $j, 1);

		// Get the character at position $j + 1.
		$c1 = substr($s1, $j + 1, 1);

		switch ($c)
		{
			case 'A':
				$k += 160;
				break;
			case 'B':
				$k += 176;
				break;
			case 'C':
				$k += 192;
				break;
			case 'D':
				$k += 208;
				break;
			case 'E':
				$k += 224;
				break;
			case 'F':
				$k += 240;
				break;
			case ' ':
				$k += 0;
				break;
			default:
				(int) $k = $k + (16 * (int) $c);
				break;
		}

		switch ($c1)
		{
			case 'A':
				$k += 10;
				break;
			case 'B':
				$k += 11;
				break;
			case 'C':
				$k += 12;
				break;
			case 'D':
				$k += 13;
				break;
			case 'E':
				$k += 14;
				break;
			case 'F':
				$k += 15;
				break;
			case ' ':
				$k += 0;
				break;
			default:
				$k += (int) $c1;
				break;
		}

		return $k;
	}

	/**
	 * Convert hex to an array of integers
	 *
	 * @param   string  $hex  The hex string to convert to an integer array.
	 *
	 * @return  array  An array of integers.
	 *
	 * @since   1.7.0
	 */
	private function _hexToIntArray($hex)
	{
		$array = array();

		$j = (int) strlen($hex) / 2;

		for ($i = 0; $i < $j; $i++)
		{
			$array[$i] = (int) $this->_hexToInt($hex, $i);
		}

		return $array;
	}

	/**
	 * Convert an integer to a hexadecimal string.
	 *
	 * @param   integer  $i  An integer value to convert to a hex string.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	private function _intToHex($i)
	{
		// Sanitize the input.
		$i = (int) $i;

		// Get the first character of the hexadecimal string if there is one.
		$j = (int) ($i / 16);

		if ($j === 0)
		{
			$s = ' ';
		}
		else
		{
			$s = strtoupper(dechex($j));
		}

		// Get the second character of the hexadecimal string.
		$k = $i - $j * 16;
		$s = $s . strtoupper(dechex($k));

		return $s;
	}
}
src/Crypt/Cipher/TripleDesCipher.php000064400000001711152177723700013420 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

/**
 * JCrypt cipher for Triple DES encryption, decryption and key generation.
 *
 * @since       3.0.0
 * @deprecated  4.0   Without replacement use CryptoCipher
 */
class TripleDesCipher extends McryptCipher
{
	/**
	 * @var    integer  The mcrypt cipher constant.
	 * @link   https://www.php.net/manual/en/mcrypt.ciphers.php
	 * @since  3.0.0
	 */
	protected $type = MCRYPT_3DES;

	/**
	 * @var    integer  The mcrypt block cipher mode.
	 * @link   https://www.php.net/manual/en/mcrypt.constants.php
	 * @since  3.0.0
	 */
	protected $mode = MCRYPT_MODE_CBC;

	/**
	 * @var    string  The Crypt key type for validation.
	 * @since  3.0.0
	 */
	protected $keyType = '3des';
}
src/Crypt/Cipher/SodiumCipher.php000064400000005743152177723700012776 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\CipherInterface;
use Joomla\CMS\Crypt\Key;
use ParagonIE\Sodium\Compat;

/**
 * JCrypt cipher for sodium algorithm encryption, decryption and key generation.
 *
 * @since  3.8.0
 */
class SodiumCipher implements CipherInterface
{
	/**
	 * The message nonce to be used with encryption/decryption
	 *
	 * @var    string
	 * @since  3.8.0
	 */
	private $nonce;

	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 * @param   Key     $key   The key object to use for decryption.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.8.0
	 * @throws  \RuntimeException
	 */
	public function decrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type !== 'sodium')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected sodium.');
		}

		if (!$this->nonce)
		{
			throw new \RuntimeException('Missing nonce to decrypt data');
		}

		$decrypted = Compat::crypto_box_open(
			$data,
			$this->nonce,
			Compat::crypto_box_keypair_from_secretkey_and_publickey($key->private, $key->public)
		);

		if ($decrypted === false)
		{
			throw new \RuntimeException('Malformed message or invalid MAC');
		}

		return $decrypted;
	}

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 * @param   Key     $key   The key object to use for encryption.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.8.0
	 * @throws  \RuntimeException
	 */
	public function encrypt($data, Key $key)
	{
		// Validate key.
		if ($key->type !== 'sodium')
		{
			throw new \InvalidArgumentException('Invalid key of type: ' . $key->type . '.  Expected sodium.');
		}

		if (!$this->nonce)
		{
			throw new \RuntimeException('Missing nonce to decrypt data');
		}

		return Compat::crypto_box(
			$data,
			$this->nonce,
			Compat::crypto_box_keypair_from_secretkey_and_publickey($key->private, $key->public)
		);
	}

	/**
	 * Method to generate a new encryption key object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.8.0
	 * @throws  RuntimeException
	 */
	public function generateKey(array $options = array())
	{
		// Create the new encryption key object.
		$key = new Key('sodium');

		// Generate the encryption key.
		$pair = Compat::crypto_box_keypair();

		$key->public  = Compat::crypto_box_publickey($pair);
		$key->private = Compat::crypto_box_secretkey($pair);

		return $key;
	}

	/**
	 * Set the nonce to use for encrypting/decrypting messages
	 *
	 * @param   string  $nonce  The message nonce
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function setNonce($nonce)
	{
		$this->nonce = $nonce;
	}
}
src/Crypt/Cipher/Rijndael256Cipher.php000064400000001733152177723700013516 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

/**
 * Crypt cipher for Rijndael 256 encryption, decryption and key generation.
 *
 * @since       3.0.0
 * @deprecated  4.0   Without replacment use CryptoCipher
 */
class Rijndael256Cipher extends McryptCipher
{
	/**
	 * @var    integer  The mcrypt cipher constant.
	 * @link   https://www.php.net/manual/en/mcrypt.ciphers.php
	 * @since  3.0.0
	 */
	protected $type = MCRYPT_RIJNDAEL_256;

	/**
	 * @var    integer  The mcrypt block cipher mode.
	 * @link   https://www.php.net/manual/en/mcrypt.constants.php
	 * @since  3.0.0
	 */
	protected $mode = MCRYPT_MODE_CBC;

	/**
	 * @var    string  The JCrypt key type for validation.
	 * @since  3.0.0
	 */
	protected $keyType = 'rijndael256';
}
src/Crypt/Cipher/BlowfishCipher.php000064400000001715152177723700013306 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Cipher;

defined('JPATH_PLATFORM') or die;

/**
 * Crypt cipher for Blowfish encryption, decryption and key generation.
 *
 * @since       3.0.0
 * @deprecated  4.0   Without replacment use CryptoCipher
 */
class BlowfishCipher extends McryptCipher
{
	/**
	 * @var    integer  The mcrypt cipher constant.
	 * @link   https://www.php.net/manual/en/mcrypt.ciphers.php
	 * @since  3.0.0
	 */
	protected $type = MCRYPT_BLOWFISH;

	/**
	 * @var    integer  The mcrypt block cipher mode.
	 * @link   https://www.php.net/manual/en/mcrypt.constants.php
	 * @since  3.0.0
	 */
	protected $mode = MCRYPT_MODE_CBC;

	/**
	 * @var    string  The JCrypt key type for validation.
	 * @since  3.0.0
	 */
	protected $keyType = 'blowfish';
}
src/Crypt/Password/SimpleCryptPassword.php000064400000011156152177723700014764 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt\Password;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\Crypt;
use Joomla\CMS\Crypt\CryptPassword;

/**
 * Joomla Platform Password Crypter
 *
 * @since       3.0.1
 * @deprecated  4.0  Use PHP 5.5's native password hashing API
 */
class SimpleCryptPassword implements CryptPassword
{
	/**
	 * @var    integer  The cost parameter for hashing algorithms.
	 * @since  3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	protected $cost = 10;

	/**
	 * @var    string   The default hash type
	 * @since  3.1.4
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	protected $defaultType = '$2y$';

	/**
	 * Creates a password hash
	 *
	 * @param   string  $password  The password to hash.
	 * @param   string  $type      The hash type.
	 *
	 * @return  mixed  The hashed password or false if the password is too long.
	 *
	 * @since   3.0.1
	 * @throws  \InvalidArgumentException
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function create($password, $type = null)
	{
		if (empty($type))
		{
			$type = $this->defaultType;
		}

		switch ($type)
		{
			case '$2a$':
			case CryptPassword::BLOWFISH:

				$type = '$2a$';

				if (Crypt::hasStrongPasswordSupport())
				{
					$type = '$2y$';
				}

				$salt = $type . str_pad($this->cost, 2, '0', STR_PAD_LEFT) . '$' . $this->getSalt(22);

				return crypt($password, $salt);

			case CryptPassword::MD5:
				$salt = $this->getSalt(12);

				$salt = '$1$' . $salt;

				return crypt($password, $salt);

			case CryptPassword::JOOMLA:
				$salt = $this->getSalt(32);

				return md5($password . $salt) . ':' . $salt;

			default:
				throw new \InvalidArgumentException(sprintf('Hash type %s is not supported', $type));
				break;
		}
	}

	/**
	 * Sets the cost parameter for the generated hash for algorithms that use a cost factor.
	 *
	 * @param   integer  $cost  The new cost value.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function setCost($cost)
	{
		$this->cost = $cost;
	}

	/**
	 * Generates a salt of specified length. The salt consists of characters in the set [./0-9A-Za-z].
	 *
	 * @param   integer  $length  The number of characters to return.
	 *
	 * @return  string  The string of random characters.
	 *
	 * @since   3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	protected function getSalt($length)
	{
		$bytes = ceil($length * 6 / 8);

		$randomData = str_replace('+', '.', base64_encode(Crypt::genRandomBytes($bytes)));

		return substr($randomData, 0, $length);
	}

	/**
	 * Verifies a password hash
	 *
	 * @param   string  $password  The password to verify.
	 * @param   string  $hash      The password hash to check.
	 *
	 * @return  boolean  True if the password is valid, false otherwise.
	 *
	 * @since   3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function verify($password, $hash)
	{
		// Check if the hash is a blowfish hash.
		if (substr($hash, 0, 4) == '$2a$' || substr($hash, 0, 4) == '$2y$')
		{
			$type = '$2a$';

			if (Crypt::hasStrongPasswordSupport())
			{
				$type = '$2y$';
			}

			return password_verify($password, $hash);
		}

		// Check if the hash is an MD5 hash.
		if (substr($hash, 0, 3) == '$1$')
		{
			return Crypt::timingSafeCompare(crypt($password, $hash), $hash);
		}

		// Check if the hash is a Joomla hash.
		if (preg_match('#[a-z0-9]{32}:[A-Za-z0-9]{32}#', $hash) === 1)
		{
			// Check the password
			$parts = explode(':', $hash);
			$salt  = @$parts[1];

			// Compile the hash to compare
			// If the salt is empty AND there is a ':' in the original hash, we must append ':' at the end
			$testcrypt = md5($password . $salt) . ($salt ? ':' . $salt : (strpos($hash, ':') !== false ? ':' : ''));

			return Crypt::timingSafeCompare($hash, $testcrypt);
		}

		return false;
	}

	/**
	 * Sets a default type
	 *
	 * @param   string  $type  The value to set as default.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function setDefaultType($type)
	{
		if (!empty($type))
		{
			$this->defaultType = $type;
		}
	}

	/**
	 * Gets the default type
	 *
	 * @return   string  $type  The default type
	 *
	 * @since   3.1.4
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function getDefaultType()
	{
		return $this->defaultType;
	}
}
src/Crypt/CipherInterface.php000064400000002260152177723700012213 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt;

defined('JPATH_PLATFORM') or die;

/**
 * Crypt cipher interface.
 *
 * @since  3.0.0
 */
interface CipherInterface
{
	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 * @param   Key     $key   The key[/pair] object to use for decryption.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.0.0
	 */
	public function decrypt($data, Key $key);

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 * @param   Key     $key   The key[/pair] object to use for encryption.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.0.0
	 */
	public function encrypt($data, Key $key);

	/**
	 * Method to generate a new encryption key[/pair] object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.0.0
	 */
	public function generateKey(array $options = array());
}
src/Crypt/Crypt.php000064400000013372152177723700010267 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Crypt\Cipher\SimpleCipher;
use Joomla\CMS\Log\Log;

/**
 * Crypt is a Joomla Platform class for handling basic encryption/decryption of data.
 *
 * @since  3.0.0
 */
class Crypt
{
	/**
	 * @var    CipherInterface  The encryption cipher object.
	 * @since  3.0.0
	 */
	private $_cipher;

	/**
	 * @var    Key  The encryption key[/pair)].
	 * @since  3.0.0
	 */
	private $_key;

	/**
	 * Object Constructor takes an optional key to be used for encryption/decryption. If no key is given then the
	 * secret word from the configuration object is used.
	 *
	 * @param   CipherInterface  $cipher  The encryption cipher object.
	 * @param   Key              $key     The encryption key[/pair)].
	 *
	 * @since   3.0.0
	 */
	public function __construct(CipherInterface $cipher = null, Key $key = null)
	{
		// Set the encryption key[/pair)].
		$this->_key = $key;

		// Set the encryption cipher.
		$this->_cipher = isset($cipher) ? $cipher : new SimpleCipher;
	}

	/**
	 * Method to decrypt a data string.
	 *
	 * @param   string  $data  The encrypted string to decrypt.
	 *
	 * @return  string  The decrypted data string.
	 *
	 * @since   3.0.0
	 * @throws  \InvalidArgumentException
	 */
	public function decrypt($data)
	{
		try
		{
			return $this->_cipher->decrypt($data, $this->_key);
		}
		catch (\InvalidArgumentException $e)
		{
			return false;
		}
	}

	/**
	 * Method to encrypt a data string.
	 *
	 * @param   string  $data  The data string to encrypt.
	 *
	 * @return  string  The encrypted data string.
	 *
	 * @since   3.0.0
	 */
	public function encrypt($data)
	{
		return $this->_cipher->encrypt($data, $this->_key);
	}

	/**
	 * Method to generate a new encryption key[/pair] object.
	 *
	 * @param   array  $options  Key generation options.
	 *
	 * @return  Key
	 *
	 * @since   3.0.0
	 */
	public function generateKey(array $options = array())
	{
		return $this->_cipher->generateKey($options);
	}

	/**
	 * Method to set the encryption key[/pair] object.
	 *
	 * @param   Key  $key  The key object to set.
	 *
	 * @return  Crypt
	 *
	 * @since   3.0.0
	 */
	public function setKey(Key $key)
	{
		$this->_key = $key;

		return $this;
	}

	/**
	 * Generate random bytes.
	 *
	 * @param   integer  $length  Length of the random data to generate
	 *
	 * @return  string  Random binary data
	 *
	 * @since   3.0.0
	 */
	public static function genRandomBytes($length = 16)
	{
		return random_bytes($length);
	}

	/**
	 * A timing safe comparison method.
	 *
	 * This defeats hacking attempts that use timing based attack vectors.
	 *
	 * NOTE: Length will leak.
	 *
	 * @param   string  $known    A known string to check against.
	 * @param   string  $unknown  An unknown string to check.
	 *
	 * @return  boolean  True if the two strings are exactly the same.
	 *
	 * @since   3.2
	 */
	public static function timingSafeCompare($known, $unknown)
	{
		// This function is native in PHP as of 5.6 and backported via the symfony/polyfill-56 library
		return hash_equals((string) $known, (string) $unknown);
	}

	/**
	 * Tests for the availability of updated crypt().
	 * Based on a method by Anthony Ferrera
	 *
	 * @return  boolean  Always returns true since 3.3
	 *
	 * @note    To be removed when PHP 5.3.7 or higher is the minimum supported version.
	 * @link    https://github.com/ircmaxell/password_compat/blob/master/version-test.php
	 * @since   3.2
	 * @deprecated  4.0
	 */
	public static function hasStrongPasswordSupport()
	{
		// Log usage of deprecated function
		Log::add(__METHOD__ . '() is deprecated without replacement.', Log::WARNING, 'deprecated');

		if (!defined('PASSWORD_DEFAULT'))
		{
			// Always make sure that the password hashing API has been defined.
			include_once JPATH_ROOT . '/vendor/ircmaxell/password-compat/lib/password.php';
		}

		return true;
	}

	/**
	 * Safely detect a string's length
	 *
	 * This method is derived from \ParagonIE\Halite\Util::safeStrlen()
	 *
	 * @param   string  $str  String to check the length of
	 *
	 * @return  integer
	 *
	 * @since   3.5
	 * @ref     mbstring.func_overload
	 * @throws  \RuntimeException
	 */
	public static function safeStrlen($str)
	{
		static $exists = null;

		if ($exists === null)
		{
			$exists = function_exists('mb_strlen');
		}

		if ($exists)
		{
			$length = mb_strlen($str, '8bit');

			if ($length === false)
			{
				throw new \RuntimeException('mb_strlen() failed unexpectedly');
			}

			return $length;
		}

		// If we reached here, we can rely on strlen to count bytes:
		return \strlen($str);
	}

	/**
	 * Safely extract a substring
	 *
	 * This method is derived from \ParagonIE\Halite\Util::safeSubstr()
	 *
	 * @param   string   $str     The string to extract the substring from
	 * @param   integer  $start   The starting position to extract from
	 * @param   integer  $length  The length of the string to return
	 *
	 * @return  string
	 *
	 * @since   3.5
	 */
	public static function safeSubstr($str, $start, $length = null)
	{
		static $exists = null;

		if ($exists === null)
		{
			$exists = function_exists('mb_substr');
		}

		if ($exists)
		{
			// In PHP 5.3 mb_substr($str, 0, NULL, '8bit') returns an empty string, so we have to find the length ourselves.
			if ($length === null)
			{
				if ($start >= 0)
				{
					$length = static::safeStrlen($str) - $start;
				}
				else
				{
					$length = -$start;
				}
			}

			return mb_substr($str, $start, $length, '8bit');
		}

		// Unlike mb_substr(), substr() doesn't accept NULL for length
		if ($length !== null)
		{
			return substr($str, $start, $length);
		}

		return substr($str, $start);
	}
}
src/Crypt/CryptPassword.php000064400000003302152177723700012002 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Crypt;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Password Hashing Interface
 *
 * @since       3.0.1
 * @deprecated  4.0  Use PHP 5.5's native password hashing API
 */
interface CryptPassword
{
	const BLOWFISH = '$2y$';

	const JOOMLA = 'Joomla';

	const PBKDF = '$pbkdf$';

	const MD5 = '$1$';

	/**
	 * Creates a password hash
	 *
	 * @param   string  $password  The password to hash.
	 * @param   string  $type      The type of hash. This determines the prefix of the hashing function.
	 *
	 * @return  string  The hashed password.
	 *
	 * @since   3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function create($password, $type = null);

	/**
	 * Verifies a password hash
	 *
	 * @param   string  $password  The password to verify.
	 * @param   string  $hash      The password hash to check.
	 *
	 * @return  boolean  True if the password is valid, false otherwise.
	 *
	 * @since   3.0.1
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function verify($password, $hash);

	/**
	 * Sets a default prefix
	 *
	 * @param   string  $type  The prefix to set as default
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function setDefaultType($type);

	/**
	 * Gets the default type
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @deprecated  4.0  Use PHP 5.5's native password hashing API
	 */
	public function getDefaultType();
}
src/Crypt/README.md000064400000005053152177723700007731 0ustar00# Important Security Information

If you're going to use JCrypt in any of your extensions, make *sure* you use **CryptoCipher** or **SodiumCipher**; These are the only two which are cryptographically secure.

```php
use Joomla\CMS\Crypt\Cipher\SodiumCipher;

$cipher = new SodiumCipher;
$key    = $cipher->generateKey();
$data   = 'My encrypted data.';

$cipher->setNonce(\Sodium\randombytes_buf(\Sodium\CRYPTO_BOX_NONCEBYTES));

$encrypted = $cipher->encrypt($data, $key);
$decrypted = $cipher->decrypt($encrypted, $key);

if ($decrypted !== $data)
{
	throw new RuntimeException('The data was not decrypted correctly.');
}
```

```php
use Joomla\CMS\Crypt\Cipher\CryptoCipher;

$cipher = new CryptoCipher();
$key = $cipher->generateKey(); // Store this for long-term use

$message = "We're all living on a yellow submarine!";
$ciphertext = $cipher->encrypt($message, $key);
$decrypted = $cipher->decrypt($ciphertext, $key);
```

## Avoid these Ciphers if Possible

* `JCryptCipher3Des`
* `JCryptCipherBlowfish`
* `JCryptCipherMcrypt`
* `JCryptCipherRijndael256`

All of these ciphers are vulnerable to something called a [chosen-ciphertext attack](https://en.wikipedia.org/wiki/Chosen-ciphertext_attack). The only provable way to prevent chosen-ciphertext attacks is to [use authenticated encryption](https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly), preferrably in an [Encrypt-then-MAC construction](http://www.thoughtcrime.org/blog/the-cryptographic-doom-principle/).

The only JCrypt cipher that meets the *authenticated encryption* criteria is **`JCryptCipherCrypto`**.

## Absolutely Avoid JCryptCipherSimple

`JCryptCipherSimple` is deprecated and will be removed in Joomla 4. It's vulnerable to a known plaintext attack: If you know any information about the plaintext (e.g. the first character is '<'), an attacker can recover bits of the encryption key with ease.

If an attacker can influence the message, they can actually steal your encryption key. Here's how:

1. Feed `str_repeat('A', 256)` into your application, towards `JCryptCipherSimple`.
2. Observe the output of the cipher (the ciphertext).
3. Run it through this code:

```php
function recoverJcryptCipherSimpleKey($ciphertext, $knownPlaintext)
{
    $key = '';
    for ($i = 0; $i < strlen($knownPlaintext); ++$i) {
      $key.= chr(ord($ciphertext[$i]) ^ ord($knownPlaintext[$i]));
    }
}

$key = recoverJcryptCipherSimpleKey(
    $someEncryptedTextOutput,
    str_repeat('A', 256)
);
```

Given how trivial it is to steal the encryption key from this cipher, you absolutely should not use it.
src/Component/ComponentHelper.php000064400000031770152177723700013133 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Component\Exception\MissingComponentException;
use Joomla\Registry\Registry;

/**
 * Component helper class
 *
 * @since  1.5
 */
class ComponentHelper
{
	/**
	 * The component list cache
	 *
	 * @var    ComponentRecord[]
	 * @since  1.6
	 */
	protected static $components = array();

	/**
	 * Get the component information.
	 *
	 * @param   string   $option  The component option.
	 * @param   boolean  $strict  If set and the component does not exist, the enabled attribute will be set to false.
	 *
	 * @return  ComponentRecord  An object with the information for the component.
	 *
	 * @since   1.5
	 */
	public static function getComponent($option, $strict = false)
	{
		$components = static::getComponents();

		if (isset($components[$option]))
		{
			return $components[$option];
		}

		$result = new ComponentRecord;
		$result->enabled = $strict ? false : true;
		$result->setParams(new Registry);

		return $result;
	}

	/**
	 * Checks if the component is enabled
	 *
	 * @param   string  $option  The component option.
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 */
	public static function isEnabled($option)
	{
		$components = static::getComponents();

		return isset($components[$option]) && $components[$option]->enabled;
	}

	/**
	 * Checks if a component is installed
	 *
	 * @param   string  $option  The component option.
	 *
	 * @return  integer
	 *
	 * @since   3.4
	 */
	public static function isInstalled($option)
	{
		$components = static::getComponents();

		return isset($components[$option]) ? 1 : 0;
	}

	/**
	 * Gets the parameter object for the component
	 *
	 * @param   string   $option  The option for the component.
	 * @param   boolean  $strict  If set and the component does not exist, false will be returned
	 *
	 * @return  Registry  A Registry object.
	 *
	 * @see     Registry
	 * @since   1.5
	 */
	public static function getParams($option, $strict = false)
	{
		return static::getComponent($option, $strict)->getParams();
	}

	/**
	 * Applies the global text filters to arbitrary text as per settings for current user groups
	 *
	 * @param   string  $text  The string to filter
	 *
	 * @return  string  The filtered string
	 *
	 * @since   2.5
	 */
	public static function filterText($text)
	{
		// Punyencoding utf8 email addresses
		$text = \JFilterInput::getInstance()->emailToPunycode($text);

		// Filter settings
		$config     = static::getParams('com_config');
		$user       = \JFactory::getUser();
		$userGroups = Access::getGroupsByUser($user->get('id'));

		$filters = $config->get('filters');

		$blackListTags       = array();
		$blackListAttributes = array();

		$customListTags       = array();
		$customListAttributes = array();

		$whiteListTags       = array();
		$whiteListAttributes = array();

		$whiteList  = false;
		$blackList  = false;
		$customList = false;
		$unfiltered = false;

		// Cycle through each of the user groups the user is in.
		// Remember they are included in the Public group as well.
		foreach ($userGroups as $groupId)
		{
			// May have added a group by not saved the filters.
			if (!isset($filters->$groupId))
			{
				continue;
			}

			// Each group the user is in could have different filtering properties.
			$filterData = $filters->$groupId;
			$filterType = strtoupper($filterData->filter_type);

			if ($filterType === 'NH')
			{
				// Maximum HTML filtering.
			}
			elseif ($filterType === 'NONE')
			{
				// No HTML filtering.
				$unfiltered = true;
			}
			else
			{
				// Blacklist or whitelist.
				// Preprocess the tags and attributes.
				$tags           = explode(',', $filterData->filter_tags);
				$attributes     = explode(',', $filterData->filter_attributes);
				$tempTags       = array();
				$tempAttributes = array();

				foreach ($tags as $tag)
				{
					$tag = trim($tag);

					if ($tag)
					{
						$tempTags[] = $tag;
					}
				}

				foreach ($attributes as $attribute)
				{
					$attribute = trim($attribute);

					if ($attribute)
					{
						$tempAttributes[] = $attribute;
					}
				}

				// Collect the blacklist or whitelist tags and attributes.
				// Each list is cummulative.
				if ($filterType === 'BL')
				{
					$blackList           = true;
					$blackListTags       = array_merge($blackListTags, $tempTags);
					$blackListAttributes = array_merge($blackListAttributes, $tempAttributes);
				}
				elseif ($filterType === 'CBL')
				{
					// Only set to true if Tags or Attributes were added
					if ($tempTags || $tempAttributes)
					{
						$customList           = true;
						$customListTags       = array_merge($customListTags, $tempTags);
						$customListAttributes = array_merge($customListAttributes, $tempAttributes);
					}
				}
				elseif ($filterType === 'WL')
				{
					$whiteList           = true;
					$whiteListTags       = array_merge($whiteListTags, $tempTags);
					$whiteListAttributes = array_merge($whiteListAttributes, $tempAttributes);
				}
			}
		}

		// Remove duplicates before processing (because the blacklist uses both sets of arrays).
		$blackListTags        = array_unique($blackListTags);
		$blackListAttributes  = array_unique($blackListAttributes);
		$customListTags       = array_unique($customListTags);
		$customListAttributes = array_unique($customListAttributes);
		$whiteListTags        = array_unique($whiteListTags);
		$whiteListAttributes  = array_unique($whiteListAttributes);

		if (!$unfiltered)
		{
			// Custom blacklist precedes Default blacklist
			if ($customList)
			{
				$filter = \JFilterInput::getInstance(array(), array(), 1, 1);

				// Override filter's default blacklist tags and attributes
				if ($customListTags)
				{
					$filter->tagBlacklist = $customListTags;
				}

				if ($customListAttributes)
				{
					$filter->attrBlacklist = $customListAttributes;
				}
			}
			// Blacklists take second precedence.
			elseif ($blackList)
			{
				// Remove the whitelisted tags and attributes from the black-list.
				$blackListTags       = array_diff($blackListTags, $whiteListTags);
				$blackListAttributes = array_diff($blackListAttributes, $whiteListAttributes);

				$filter = \JFilterInput::getInstance($blackListTags, $blackListAttributes, 1, 1);

				// Remove whitelisted tags from filter's default blacklist
				if ($whiteListTags)
				{
					$filter->tagBlacklist = array_diff($filter->tagBlacklist, $whiteListTags);
				}

				// Remove whitelisted attributes from filter's default blacklist
				if ($whiteListAttributes)
				{
					$filter->attrBlacklist = array_diff($filter->attrBlacklist, $whiteListAttributes);
				}
			}
			// Whitelists take third precedence.
			elseif ($whiteList)
			{
				// Turn off XSS auto clean
				$filter = \JFilterInput::getInstance($whiteListTags, $whiteListAttributes, 0, 0, 0);
			}
			// No HTML takes last place.
			else
			{
				$filter = \JFilterInput::getInstance();
			}

			$text = $filter->clean($text, 'html');
		}

		return $text;
	}

	/**
	 * Render the component.
	 *
	 * @param   string  $option  The component option.
	 * @param   array   $params  The component parameters
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @throws  MissingComponentException
	 */
	public static function renderComponent($option, $params = array())
	{
		$app = \JFactory::getApplication();

		// Load template language files.
		$template = $app->getTemplate(true)->template;
		$lang = \JFactory::getLanguage();
		$lang->load('tpl_' . $template, JPATH_BASE, null, false, true)
			|| $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", null, false, true);

		if (empty($option))
		{
			throw new MissingComponentException(\JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
		}

		if (JDEBUG)
		{
			\JProfiler::getInstance('Application')->mark('beforeRenderComponent ' . $option);
		}

		// Record the scope
		$scope = $app->scope;

		// Set scope to component name
		$app->scope = $option;

		// Build the component path.
		$option = preg_replace('/[^A-Z0-9_\.-]/i', '', $option);
		$file = substr($option, 4);

		// Define component path.
		if (!defined('JPATH_COMPONENT'))
		{
			/**
			 * Defines the path to the active component for the request
			 *
			 * Note this constant is application aware and is different for each application (site/admin).
			 *
			 * @var    string
			 * @since  1.5
			 */
			define('JPATH_COMPONENT', JPATH_BASE . '/components/' . $option);
		}

		if (!defined('JPATH_COMPONENT_SITE'))
		{
			/**
			 * Defines the path to the site element of the active component for the request
			 *
			 * @var    string
			 * @since  1.5
			 */
			define('JPATH_COMPONENT_SITE', JPATH_SITE . '/components/' . $option);
		}

		if (!defined('JPATH_COMPONENT_ADMINISTRATOR'))
		{
			/**
			 * Defines the path to the admin element of the active component for the request
			 *
			 * @var    string
			 * @since  1.5
			 */
			define('JPATH_COMPONENT_ADMINISTRATOR', JPATH_ADMINISTRATOR . '/components/' . $option);
		}

		$path = JPATH_COMPONENT . '/' . $file . '.php';

		// If component is disabled throw error
		if (!static::isEnabled($option) || !file_exists($path))
		{
			throw new MissingComponentException(\JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'), 404);
		}

		// Load common and local language files.
		$lang->load($option, JPATH_BASE, null, false, true) || $lang->load($option, JPATH_COMPONENT, null, false, true);

		// Handle template preview outlining.
		$contents = null;

		// Execute the component.
		$contents = static::executeComponent($path);

		// Revert the scope
		$app->scope = $scope;

		if (JDEBUG)
		{
			\JProfiler::getInstance('Application')->mark('afterRenderComponent ' . $option);
		}

		return $contents;
	}

	/**
	 * Execute the component.
	 *
	 * @param   string  $path  The component path.
	 *
	 * @return  string  The component output
	 *
	 * @since   1.7
	 */
	protected static function executeComponent($path)
	{
		ob_start();
		require_once $path;

		return ob_get_clean();
	}

	/**
	 * Load the installed components into the components property.
	 *
	 * @param   string  $option  The element value for the extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use JComponentHelper::load() instead
	 */
	protected static function _load($option)
	{
		return static::load($option);
	}

	/**
	 * Load the installed components into the components property.
	 *
	 * @param   string  $option  The element value for the extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.2
	 * @note    As of 4.0 this method will be restructured to only load the data into memory
	 */
	protected static function load($option)
	{
		$loader = function ()
		{
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName(array('extension_id', 'element', 'params', 'enabled'), array('id', 'option', null, null)))
				->from($db->quoteName('#__extensions'))
				->where($db->quoteName('type') . ' = ' . $db->quote('component'))
				->where($db->quoteName('state') . ' = 0')
				->where($db->quoteName('enabled') . ' = 1');
			$db->setQuery($query);

			return $db->loadObjectList('option', '\JComponentRecord');
		};

		/** @var \JCacheControllerCallback $cache */
		$cache = \JFactory::getCache('_system', 'callback');

		try
		{
			static::$components = $cache->get($loader, array(), __METHOD__);
		}
		catch (\JCacheException $e)
		{
			static::$components = $loader();
		}

		// Core CMS will use '*' as a placeholder for required parameter in this method. In 4.0 this will not be passed at all.
		if (isset($option) && $option != '*')
		{
			// Log deprecated warning and display missing component warning only if using deprecated format.
			try
			{
				\JLog::add(
					sprintf(
						'Passing a parameter into %s() is deprecated and will be removed in 4.0. Read %s::$components directly after loading the data.',
						__METHOD__,
						__CLASS__
					),
					\JLog::WARNING,
					'deprecated'
				);
			}
			catch (\RuntimeException $e)
			{
				// Informational log only
			}

			if (empty(static::$components[$option]))
			{
				/*
				 * Fatal error
				 *
				 * It is possible for this error to be reached before the global \JLanguage instance has been loaded so we check for its presence
				 * before logging the error to ensure a human friendly message is always given
				 */

				if (\JFactory::$language)
				{
					$msg = \JText::sprintf('JLIB_APPLICATION_ERROR_COMPONENT_NOT_LOADING', $option, \JText::_('JLIB_APPLICATION_ERROR_COMPONENT_NOT_FOUND'));
				}
				else
				{
					$msg = sprintf('Error loading component: %1$s, %2$s', $option, 'Component not found.');
				}

				\JLog::add($msg, \JLog::WARNING, 'jerror');

				return false;
			}
		}

		return true;
	}

	/**
	 * Get installed components
	 *
	 * @return  ComponentRecord[]  The components property
	 *
	 * @since   3.6.3
	 */
	public static function getComponents()
	{
		if (empty(static::$components))
		{
			static::load('*');
		}

		return static::$components;
	}
}
src/Component/Exception/MissingComponentException.php000064400000001634152177723700017136 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Exception;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Router\Exception\RouteNotFoundException;

/**
 * Exception class defining an error for a missing component
 *
 * @since  3.7.0
 */
class MissingComponentException extends RouteNotFoundException
{
	/**
	 * Constructor
	 *
	 * @param   string      $message   The Exception message to throw.
	 * @param   integer     $code      The Exception code.
	 * @param   \Exception  $previous  The previous exception used for the exception chaining.
	 *
	 * @since   3.7.0
	 */
	public function __construct($message = '', $code = 404, \Exception $previous = null)
	{
		parent::__construct($message, $code, $previous);
	}
}
src/Component/Router/RouterView.php000064400000012623152177723700013420 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\Rules\RulesInterface;

/**
 * View-based component routing class
 *
 * @since  3.5
 */
abstract class RouterView extends RouterBase
{
	/**
	 * Name of the router of the component
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $name;

	/**
	 * Array of rules
	 *
	 * @var    RulesInterface[]
	 * @since  3.5
	 */
	protected $rules = array();

	/**
	 * Views of the component
	 *
	 * @var    RouterViewConfiguration[]
	 * @since  3.5
	 */
	protected $views = array();

	/**
	 * Register the views of a component
	 *
	 * @param   RouterViewConfiguration  $view  View configuration object
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function registerView(RouterViewConfiguration $view)
	{
		$this->views[$view->name] = $view;
	}

	/**
	 * Return an array of registered view objects
	 *
	 * @return  RouterViewConfiguration[] Array of registered view objects
	 *
	 * @since   3.5
	 */
	public function getViews()
	{
		return $this->views;
	}

	/**
	 * Get the path of views from target view to root view
	 * including content items of a nestable view
	 *
	 * @param   array  $query  Array of query elements
	 *
	 * @return  array List of views including IDs of content items
	 *
	 * @since   3.5
	 */
	public function getPath($query)
	{
		$views  = $this->getViews();
		$result = array();

		// Get the right view object
		if (isset($query['view']) && isset($views[$query['view']]))
		{
			$viewobj = $views[$query['view']];
		}

		// Get the path from the current item to the root view with all IDs
		if (isset($viewobj))
		{
			$path     = array_reverse($viewobj->path);
			$start    = true;
			$childkey = false;

			foreach ($path as $element)
			{
				$view = $views[$element];

				if ($start)
				{
					$key   = $view->key;
					$start = false;
				}
				else
				{
					$key = $childkey;
				}

				$childkey = $view->parent_key;

				if (($key || $view->key) && is_callable(array($this, 'get' . ucfirst($view->name) . 'Segment')))
				{
					if (isset($query[$key]))
					{
						$result[$view->name] = call_user_func_array(array($this, 'get' . ucfirst($view->name) . 'Segment'), array($query[$key], $query));
					}
					elseif (isset($query[$view->key]))
					{
						$result[$view->name] = call_user_func_array(array($this, 'get' . ucfirst($view->name) . 'Segment'), array($query[$view->key], $query));
					}
					else
					{
						$result[$view->name] = array();
					}
				}
				else
				{
					$result[$view->name] = true;
				}
			}
		}

		return $result;
	}

	/**
	 * Get all currently attached rules
	 *
	 * @return  RulesInterface[]  All currently attached rules in an array
	 *
	 * @since   3.5
	 */
	public function getRules()
	{
		return $this->rules;
	}

	/**
	 * Add a number of router rules to the object
	 *
	 * @param   RulesInterface[]  $rules  Array of JComponentRouterRulesInterface objects
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function attachRules($rules)
	{
		foreach ($rules as $rule)
		{
			$this->attachRule($rule);
		}
	}

	/**
	 * Attach a build rule
	 *
	 * @param   RulesInterface  $rule  The function to be called.
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function attachRule(RulesInterface $rule)
	{
		$this->rules[] = $rule;
	}

	/**
	 * Remove a build rule
	 *
	 * @param   RulesInterface  $rule  The rule to be removed.
	 *
	 * @return   boolean  Was a rule removed?
	 *
	 * @since   3.5
	 */
	public function detachRule(RulesInterface $rule)
	{
		foreach ($this->rules as $id => $r)
		{
			if ($r == $rule)
			{
				unset($this->rules[$id]);

				return true;
			}
		}

		return false;
	}

	/**
	 * Generic method to preprocess a URL
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.5
	 */
	public function preprocess($query)
	{
		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->preprocess($query);
		}

		return $query;
	}

	/**
	 * Build method for URLs
	 *
	 * @param   array  &$query  Array of query elements
	 *
	 * @return  array  Array of URL segments
	 *
	 * @since   3.5
	 */
	public function build(&$query)
	{
		$segments = array();

		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->build($query, $segments);
		}

		return $segments;
	}

	/**
	 * Parse method for URLs
	 *
	 * @param   array  &$segments  Array of URL string-segments
	 *
	 * @return  array  Associative array of query values
	 *
	 * @since   3.5
	 */
	public function parse(&$segments)
	{
		$vars = array();

		// Process the parsed variables based on custom defined rules
		foreach ($this->rules as $rule)
		{
			$rule->parse($segments, $vars);
		}

		return $vars;
	}

	/**
	 * Method to return the name of the router
	 *
	 * @return  string  Name of the router
	 *
	 * @since   3.5
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$r = null;

			if (!preg_match('/(.*)Router/i', get_class($this), $r))
			{
				throw new \Exception('JLIB_APPLICATION_ERROR_ROUTER_GET_NAME', 500);
			}

			$this->name = strtolower($r[1]);
		}

		return $this->name;
	}
}
src/Component/Router/Rules/StandardRules.php000064400000015231152177723700015150 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule for the standard handling of component routing
 *
 * @since  3.4
 */
class StandardRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var    RouterView
	 * @since  3.4
	 */
	protected $router;

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query)
	{
	}

	/**
	 * Parse the URL
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars)
	{
		// Get the views and the currently active query vars
		$views  = $this->router->getViews();
		$active = $this->router->menu->getActive();

		if ($active)
		{
			$vars = array_merge($active->query, $vars);
		}

		// We don't have a view or its not a view of this component! We stop here
		if (!isset($vars['view']) || !isset($views[$vars['view']]))
		{
			return;
		}

		// Copy the segments, so that we can iterate over all of them and at the same time modify the original segments
		$tempSegments = $segments;

		// Iterate over the segments as long as a segment fits
		foreach ($tempSegments as $segment)
		{
			// Our current view is nestable. We need to check first if the segment fits to that
			if ($views[$vars['view']]->nestable)
			{
				if (is_callable(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id')))
				{
					$key = call_user_func_array(array($this->router, 'get' . ucfirst($views[$vars['view']]->name) . 'Id'), array($segment, $vars));

					// Did we get a proper key? If not, we need to look in the child-views
					if ($key)
					{
						$vars[$views[$vars['view']]->key] = $key;

						array_shift($segments);

						continue;
					}
				}
				else
				{
					// The router is not complete. The get<View>Id() method is missing.
					return;
				}
			}

			// Lets find the right view that belongs to this segment
			$found = false;

			foreach ($views[$vars['view']]->children as $view)
			{
				if (!$view->key)
				{
					if ($view->name === $segment)
					{
						// The segment is a view name
						$parent       = $views[$vars['view']];
						$vars['view'] = $view->name;
						$found        = true;

						if ($view->parent_key && isset($vars[$parent->key]))
						{
							$parent_key              = $vars[$parent->key];
							$vars[$view->parent_key] = $parent_key;

							unset($vars[$parent->key]);
						}

						break;
					}
				}
				elseif (is_callable(array($this->router, 'get' . ucfirst($view->name) . 'Id')))
				{
					// Hand the data over to the router specific method and see if there is a content item that fits
					$key = call_user_func_array(array($this->router, 'get' . ucfirst($view->name) . 'Id'), array($segment, $vars));

					if ($key)
					{
						// Found the right view and the right item
						$parent       = $views[$vars['view']];
						$vars['view'] = $view->name;
						$found        = true;

						if ($view->parent_key && isset($vars[$parent->key]))
						{
							$parent_key              = $vars[$parent->key];
							$vars[$view->parent_key] = $parent_key;

							unset($vars[$parent->key]);
						}

						$vars[$view->key] = $key;

						break;
					}
				}
			}

			if (!$found)
			{
				return;
			}

			array_shift($segments);
		}
	}

	/**
	 * Build a standard URL
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments)
	{
		if (!isset($query['Itemid'], $query['view']))
		{
			return;
		}

		// Get the menu item belonging to the Itemid that has been found
		$item = $this->router->menu->getItem($query['Itemid']);

		if ($item === null
			|| $item->component !== 'com_' . $this->router->getName()
			|| !isset($item->query['view']))
		{
			return;
		}

		// Get menu item layout
		$mLayout = isset($item->query['layout']) ? $item->query['layout'] : null;

		// Get all views for this component
		$views = $this->router->getViews();

		// Return directly when the URL of the Itemid is identical with the URL to build
		if ($item->query['view'] === $query['view'])
		{
			$view = $views[$query['view']];

			if (!$view->key)
			{
				unset($query['view']);

				if (isset($query['layout']) && $mLayout === $query['layout'])
				{
					unset($query['layout']);
				}

				return;
			}

			if (isset($query[$view->key]) && $item->query[$view->key] == (int) $query[$view->key])
			{
				unset($query[$view->key]);

				while ($view)
				{
					unset($query[$view->parent_key]);

					$view = $view->parent;
				}

				unset($query['view']);

				if (isset($query['layout']) && $mLayout === $query['layout'])
				{
					unset($query['layout']);
				}

				return;
			}
		}

		// Get the path from the view of the current URL and parse it to the menu item
		$path  = array_reverse($this->router->getPath($query), true);
		$found = false;

		foreach ($path as $element => $ids)
		{
			$view = $views[$element];

			if ($found === false && $item->query['view'] === $element)
			{
				if ($view->nestable)
				{
					$found = true;
				}
				elseif ($view->children)
				{
					$found = true;

					continue;
				}
			}

			if ($found === false)
			{
				// Jump to the next view
				continue;
			}

			if ($ids)
			{
				if ($view->nestable)
				{
					$found2 = false;

					foreach (array_reverse($ids, true) as $id => $segment)
					{
						if ($found2)
						{
							$segments[] = str_replace(':', '-', $segment);
						}
						elseif ((int) $item->query[$view->key] === (int) $id)
						{
							$found2 = true;
						}
					}
				}
				elseif ($ids === true)
				{
					$segments[] = $element;
				}
				else
				{
					$segments[] = str_replace(':', '-', current($ids));
				}
			}

			if ($view->parent_key)
			{
				// Remove parent key from query
				unset($query[$view->parent_key]);
			}
		}

		if ($found)
		{
			unset($query[$views[$query['view']]->key], $query['view']);

			if (isset($query['layout']) && $mLayout === $query['layout'])
			{
				unset($query['layout']);
			}
		}
	}
}
src/Component/Router/Rules/NomenuRules.php000064400000005524152177723700014655 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule to process URLs without a menu item
 *
 * @since  3.4
 */
class NomenuRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var RouterView
	 * @since 3.4
	 */
	protected $router;

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function preprocess(&$query)
	{
	}

	/**
	 * Parse a menu-less URL
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars)
	{
		$active = $this->router->menu->getActive();

		if (!is_object($active))
		{
			$views = $this->router->getViews();

			if (isset($views[$segments[0]]))
			{
				$vars['view'] = array_shift($segments);

				if (isset($views[$vars['view']]->key) && isset($segments[0]))
				{
					$vars[$views[$vars['view']]->key] = preg_replace('/-/', ':', array_shift($segments), 1);
				}
			}
		}
	}

	/**
	 * Build a menu-less URL
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments)
	{
		$menu_found = false;

		if (isset($query['Itemid']))
		{
			$item = $this->router->menu->getItem($query['Itemid']);

			if (!isset($query['option']) || ($item && $item->query['option'] === $query['option']))
			{
				$menu_found = true;
			}
		}

		if (!$menu_found && isset($query['view']))
		{
			$views = $this->router->getViews();

			if (isset($views[$query['view']]))
			{
				$view = $views[$query['view']];
				$segments[] = $query['view'];

				if ($view->key && isset($query[$view->key]))
				{
					if (is_callable(array($this->router, 'get' . ucfirst($view->name) . 'Segment')))
					{
						$result = call_user_func_array(array($this->router, 'get' . ucfirst($view->name) . 'Segment'), array($query[$view->key], $query));
						$segments[] = str_replace(':', '-', array_shift($result));
					}
					else
					{
						$segments[] = str_replace(':', '-', $query[$view->key]);
					}

					unset($query[$views[$query['view']]->key]);
				}

				unset($query['view']);
			}
		}
	}
}
src/Component/Router/Rules/RulesInterface.php000064400000003157152177723700015314 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

/**
 * RouterRules interface for Joomla
 *
 * @since  3.4
 */
interface RulesInterface
{
	/**
	 * Prepares a query set to be handed over to the build() method.
	 * This should complete a partial query set to work as a complete non-SEFed
	 * URL and in general make sure that all information is present and properly
	 * formatted. For example, the Itemid should be retrieved and set here.
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query);

	/**
	 * Parses a URI to retrieve informations for the right route through
	 * the component.
	 * This method should retrieve all its input from its method arguments.
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function parse(&$segments, &$vars);

	/**
	 * Builds URI segments from a query to encode the necessary informations
	 * for a route in a human-readable URL.
	 * This method should retrieve all its input from its method arguments.
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function build(&$query, &$segments);
}
src/Component/Router/Rules/MenuRules.php000064400000015202152177723700014312 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router\Rules;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterView;

/**
 * Rule to identify the right Itemid for a view in a component
 *
 * @since  3.4
 */
class MenuRules implements RulesInterface
{
	/**
	 * Router this rule belongs to
	 *
	 * @var   RouterView
	 * @since 3.4
	 */
	protected $router;

	/**
	 * Lookup array of the menu items
	 *
	 * @var   array
	 * @since 3.4
	 */
	protected $lookup = array();

	/**
	 * Class constructor.
	 *
	 * @param   RouterView  $router  Router this rule belongs to
	 *
	 * @since   3.4
	 */
	public function __construct(RouterView $router)
	{
		$this->router = $router;

		$this->buildLookup();
	}

	/**
	 * Finds the right Itemid for this query
	 *
	 * @param   array  &$query  The query array to process
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function preprocess(&$query)
	{
		$active = $this->router->menu->getActive();

		/**
		 * If the active item id is not the same as the supplied item id or we have a supplied item id and no active
		 * menu item then we just use the supplied menu item and continue
		 */
		if (isset($query['Itemid']) && ($active === null || $query['Itemid'] != $active->id))
		{
			return;
		}

		// Get query language
		$language = isset($query['lang']) ? $query['lang'] : '*';

		if (!isset($this->lookup[$language]))
		{
			$this->buildLookup($language);
		}

		// Check if the active menu item matches the requested query
		if ($active !== null && isset($query['Itemid']))
		{
			// Check if active->query and supplied query are the same
			$match = true;

			foreach ($active->query as $k => $v)
			{
				if (isset($query[$k]) && $v !== $query[$k])
				{
					// Compare again without alias
					if (is_string($v) && $v == current(explode(':', $query[$k], 2)))
					{
						continue;
					}

					$match = false;
					break;
				}
			}

			if ($match)
			{
				// Just use the supplied menu item
				return;
			}
		}

		$needles = $this->router->getPath($query);

		$layout = isset($query['layout']) && $query['layout'] !== 'default' ? ':' . $query['layout'] : '';

		if ($needles)
		{
			foreach ($needles as $view => $ids)
			{
				$viewLayout = $view . $layout;

				if ($layout && isset($this->lookup[$language][$viewLayout]))
				{
					if (is_bool($ids))
					{
						$query['Itemid'] = $this->lookup[$language][$viewLayout];

						return;
					}

					foreach ($ids as $id => $segment)
					{
						if (isset($this->lookup[$language][$viewLayout][(int) $id]))
						{
							$query['Itemid'] = $this->lookup[$language][$viewLayout][(int) $id];

							return;
						}
					}
				}

				if (isset($this->lookup[$language][$view]))
				{
					if (is_bool($ids))
					{
						$query['Itemid'] = $this->lookup[$language][$view];

						return;
					}

					foreach ($ids as $id => $segment)
					{
						if (isset($this->lookup[$language][$view][(int) $id]))
						{
							$query['Itemid'] = $this->lookup[$language][$view][(int) $id];

							return;
						}
					}
				}
			}
		}

		// Check if the active menuitem matches the requested language
		if ($active && $active->component === 'com_' . $this->router->getName()
			&& ($language === '*' || in_array($active->language, array('*', $language)) || !\JLanguageMultilang::isEnabled()))
		{
			$query['Itemid'] = $active->id;
			return;
		}

		// If not found, return language specific home link
		$default = $this->router->menu->getDefault($language);

		if (!empty($default->id))
		{
			$query['Itemid'] = $default->id;
		}
	}

	/**
	 * Method to build the lookup array
	 *
	 * @param   string  $language  The language that the lookup should be built up for
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	protected function buildLookup($language = '*')
	{
		// Prepare the reverse lookup array.
		if (!isset($this->lookup[$language]))
		{
			$this->lookup[$language] = array();

			$component  = ComponentHelper::getComponent('com_' . $this->router->getName());
			$views = $this->router->getViews();

			$attributes = array('component_id');
			$values     = array((int) $component->id);

			$attributes[] = 'language';
			$values[]     = array($language, '*');

			$items = $this->router->menu->getItems($attributes, $values);

			foreach ($items as $item)
			{
				if (isset($item->query['view'], $views[$item->query['view']]))
				{
					$view = $item->query['view'];

					$layout = '';

					if (isset($item->query['layout']))
					{
						$layout = ':' . $item->query['layout'];
					}

					if ($views[$view]->key)
					{
						if (!isset($this->lookup[$language][$view . $layout]))
						{
							$this->lookup[$language][$view . $layout] = array();
						}

						if (!isset($this->lookup[$language][$view]))
						{
							$this->lookup[$language][$view] = array();
						}

						// If menuitem has no key set, we assume 0.
						if (!isset($item->query[$views[$view]->key]))
						{
							$item->query[$views[$view]->key] = 0;
						}

						/**
						 * Here it will become a bit tricky
						 * language != * can override existing entries
						 * language == * cannot override existing entries
						 */
						if (!isset($this->lookup[$language][$view . $layout][$item->query[$views[$view]->key]]) || $item->language !== '*')
						{
							$this->lookup[$language][$view . $layout][$item->query[$views[$view]->key]] = $item->id;
							$this->lookup[$language][$view][$item->query[$views[$view]->key]] = $item->id;
						}
					}
					else
					{
						/**
						 * Here it will become a bit tricky
						 * language != * can override existing entries
						 * language == * cannot override existing entries
						 */
						if (!isset($this->lookup[$language][$view . $layout]) || $item->language !== '*')
						{
							$this->lookup[$language][$view . $layout] = $item->id;
							$this->lookup[$language][$view] = $item->id;
						}
					}
				}
			}
		}
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$segments  The URL segments to parse
	 * @param   array  &$vars      The vars that result from the segments
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function parse(&$segments, &$vars)
	{
	}

	/**
	 * Dummymethod to fullfill the interface requirements
	 *
	 * @param   array  &$query     The vars that should be converted
	 * @param   array  &$segments  The URL segments to create
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @codeCoverageIgnore
	 */
	public function build(&$query, &$segments)
	{
	}
}
src/Component/Router/RouterInterface.php000064400000003115152177723700014402 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Component routing interface
 *
 * @since  3.3
 */
interface RouterInterface
{
	/**
	 * Prepare-method for URLs
	 * This method is meant to validate and complete the URL parameters.
	 * For example it can add the Itemid or set a language parameter.
	 * This method is executed on each URL, regardless of SEF mode switched
	 * on or not.
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query);

	/**
	 * Build method for URLs
	 * This method is meant to transform the query parameters into a more human
	 * readable form. It is only executed when SEF mode is switched on.
	 *
	 * @param   array  &$query  An array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.3
	 */
	public function build(&$query);

	/**
	 * Parse method for URLs
	 * This method is meant to transform the human readable URL back into
	 * query parameters. It is only executed when SEF mode is switched on.
	 *
	 * @param   array  &$segments  The segments of the URL to parse.
	 *
	 * @return  array  The URL attributes to be used by the application.
	 *
	 * @since   3.3
	 */
	public function parse(&$segments);
}
src/Component/Router/RouterViewConfiguration.php000064400000010176152177723700016151 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * View-configuration class for the view-based component router
 *
 * @since  3.5
 */
class RouterViewConfiguration
{
	/**
	 * Name of the view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $name;

	/**
	 * Key of the view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $key = false;

	/**
	 * Parentview of this one
	 *
	 * @var    RouterViewconfiguration
	 * @since  3.5
	 */
	public $parent = false;

	/**
	 * Key of the parent view
	 *
	 * @var    string
	 * @since  3.5
	 */
	public $parent_key = false;

	/**
	 * Is this view nestable?
	 *
	 * @var    bool
	 * @since  3.5
	 */
	public $nestable = false;

	/**
	 * Layouts that are supported by this view
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $layouts = array('default');

	/**
	 * Child-views of this view
	 *
	 * @var    RouterViewconfiguration[]
	 * @since  3.5
	 */
	public $children = array();

	/**
	 * Keys used for this parent view by the child views
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $child_keys = array();

	/**
	 * Path of views from this one to the root view
	 *
	 * @var    array
	 * @since  3.5
	 */
	public $path = array();

	/**
	 * Constructor for the View-configuration class
	 *
	 * @param   string  $name  Name of the view
	 *
	 * @since   3.5
	 */
	public function __construct($name)
	{
		$this->name   = $name;
		$this->path[] = $name;
	}

	/**
	 * Set the name of the view
	 *
	 * @param   string  $name  Name of the view
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setName($name)
	{
		$this->name = $name;

		array_pop($this->path);
		$this->path[] = $name;

		return $this;
	}

	/**
	 * Set the key-identifier for the view
	 *
	 * @param   string  $key  Key of the view
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setKey($key)
	{
		$this->key = $key;

		return $this;
	}

	/**
	 * Set the parent view of this view
	 *
	 * @param   RouterViewconfiguration  $parent      Parent view object
	 * @param   string                   $parent_key  Key of the parent view in this context
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setParent(RouterViewconfiguration $parent, $parent_key = false)
	{
		if ($this->parent)
		{
			$key = array_search($this, $this->parent->children);

			if ($key !== false)
			{
				unset($this->parent->children[$key]);
			}

			if ($this->parent_key)
			{
				$child_key = array_search($this->parent_key, $this->parent->child_keys);
				unset($this->parent->child_keys[$child_key]);
			}
		}

		$this->parent       = $parent;
		$parent->children[] = $this;

		$this->path   = $parent->path;
		$this->path[] = $this->name;

		$this->parent_key = $parent_key;

		if ($parent_key)
		{
			$parent->child_keys[] = $parent_key;
		}

		return $this;
	}

	/**
	 * Set if this view is nestable or not
	 *
	 * @param   bool  $isNestable  If set to true, the view is nestable
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function setNestable($isNestable = true)
	{
		$this->nestable = (bool) $isNestable;

		return $this;
	}

	/**
	 * Add a layout to this view
	 *
	 * @param   string  $layout  Layouts that this view supports
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function addLayout($layout)
	{
		$this->layouts[] = $layout;
		$this->layouts   = array_unique($this->layouts);

		return $this;
	}

	/**
	 * Remove a layout from this view
	 *
	 * @param   string  $layout  Layouts that this view supports
	 *
	 * @return  RouterViewconfiguration  This object for chaining
	 *
	 * @since   3.5
	 */
	public function removeLayout($layout)
	{
		$key = array_search($layout, $this->layouts);

		if ($key !== false)
		{
			unset($this->layouts[$key]);
		}

		return $this;
	}
}
src/Component/Router/RouterBase.php000064400000002572152177723700013362 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Base component routing class
 *
 * @since  3.3
 */
abstract class RouterBase implements RouterInterface
{
	/**
	 * Application object to use in the router
	 *
	 * @var    \JApplicationCms
	 * @since  3.4
	 */
	public $app;

	/**
	 * Menu object to use in the router
	 *
	 * @var    \JMenu
	 * @since  3.4
	 */
	public $menu;

	/**
	 * Class constructor.
	 *
	 * @param   \JApplicationCms  $app   Application-object that the router should use
	 * @param   \JMenu            $menu  Menu-object that the router should use
	 *
	 * @since   3.4
	 */
	public function __construct($app = null, $menu = null)
	{
		if ($app)
		{
			$this->app = $app;
		}
		else
		{
			$this->app = \JFactory::getApplication('site');
		}

		if ($menu)
		{
			$this->menu = $menu;
		}
		else
		{
			$this->menu = $this->app->getMenu();
		}
	}

	/**
	 * Generic method to preprocess a URL
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query)
	{
		return $query;
	}
}
src/Component/Router/RouterLegacy.php000064400000004261152177723700013711 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component\Router;

defined('JPATH_PLATFORM') or die;

/**
 * Default routing class for missing or legacy component routers
 *
 * @since  3.3
 */
class RouterLegacy implements RouterInterface
{
	/**
	 * Name of the component
	 *
	 * @var    string
	 * @since  3.3
	 */
	protected $component;

	/**
	 * Constructor
	 *
	 * @param   string  $component  Component name without the com_ prefix this router should react upon
	 *
	 * @since   3.3
	 */
	public function __construct($component)
	{
		$this->component = $component;
	}

	/**
	 * Generic preprocess function for missing or legacy component router
	 *
	 * @param   array  $query  An associative array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.3
	 */
	public function preprocess($query)
	{
		return $query;
	}

	/**
	 * Generic build function for missing or legacy component router
	 *
	 * @param   array  &$query  An array of URL arguments
	 *
	 * @return  array  The URL arguments to use to assemble the subsequent URL.
	 *
	 * @since   3.3
	 */
	public function build(&$query)
	{
		$function = $this->component . 'BuildRoute';

		if (function_exists($function))
		{
			$segments = $function($query);
			$total    = count($segments);

			for ($i = 0; $i < $total; $i++)
			{
				$segments[$i] = str_replace(':', '-', $segments[$i]);
			}

			return $segments;
		}

		return array();
	}

	/**
	 * Generic parse function for missing or legacy component router
	 *
	 * @param   array  &$segments  The segments of the URL to parse.
	 *
	 * @return  array  The URL attributes to be used by the application.
	 *
	 * @since   3.3
	 */
	public function parse(&$segments)
	{
		$function = $this->component . 'ParseRoute';

		if (function_exists($function))
		{
			$total = count($segments);

			for ($i = 0; $i < $total; $i++)
			{
				$segments[$i] = preg_replace('/-/', ':', $segments[$i], 1);
			}

			return $function($segments);
		}

		return array();
	}
}
src/Component/ComponentRecord.php000064400000005307152177723700013127 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Component;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Object representing a component extension record
 *
 * @since  3.7.0
 * @note   As of 4.0 this class will no longer extend JObject
 */
class ComponentRecord extends \JObject
{
	/**
	 * Primary key
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $id;

	/**
	 * The component name
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $option;

	/**
	 * The component parameters
	 *
	 * @var    string|Registry
	 * @since  3.7.0
	 * @note   This field is protected to require reading this field to proxy through the getter to convert the params to a Registry instance
	 */
	protected $params;

	/**
	 * Indicates if this component is enabled
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $enabled;

	/**
	 * Class constructor
	 *
	 * @param   array  $data  The component record data to load
	 *
	 * @since   3.7.0
	 */
	public function __construct($data = array())
	{
		foreach ((array) $data as $key => $value)
		{
			$this->$key = $value;
		}
	}

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.7.0
	 * @deprecated  4.0  Access the item parameters through the `getParams()` method
	 */
	public function __get($name)
	{
		if ($name === 'params')
		{
			return $this->getParams();
		}

		return $this->get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 * @deprecated  4.0  Set the item parameters through the `setParams()` method
	 */
	public function __set($name, $value)
	{
		if ($name === 'params')
		{
			$this->setParams($value);

			return;
		}

		$this->set($name, $value);
	}

	/**
	 * Returns the menu item parameters
	 *
	 * @return  Registry
	 *
	 * @since   3.7.0
	 */
	public function getParams()
	{
		if (!($this->params instanceof Registry))
		{
			$this->params = new Registry($this->params);
		}

		return $this->params;
	}

	/**
	 * Sets the menu item parameters
	 *
	 * @param   Registry|string  $params  The data to be stored as the parameters
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function setParams($params)
	{
		$this->params = $params;
	}
}
src/Session/MetadataManager.php000064400000007674152177723700012533 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Session;

defined('JPATH_PLATFORM') or die;

use Joomla\Application\AbstractApplication;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\User\User;

/**
 * Manager for optional session metadata.
 *
 * @since  3.8.6
 * @internal
 */
final class MetadataManager
{
	/**
	 * Application object.
	 *
	 * @var    AbstractApplication
	 * @since  3.8.6
	 */
	private $app;

	/**
	 * Database driver.
	 *
	 * @var    \JDatabaseDriver
	 * @since  3.8.6
	 */
	private $db;

	/**
	 * MetadataManager constructor.
	 *
	 * @param   AbstractApplication  $app  Application object.
	 * @param   \JDatabaseDriver     $db   Database driver.
	 *
	 * @since   3.8.6
	 */
	public function __construct(AbstractApplication $app, \JDatabaseDriver $db)
	{
		$this->app = $app;
		$this->db  = $db;
	}

	/**
	 * Create the metadata record if it does not exist.
	 *
	 * @param   Session  $session  The session to create the metadata record for.
	 * @param   User     $user     The user to associate with the record.
	 *
	 * @return  void
	 *
	 * @since   3.8.6
	 * @throws  \RuntimeException
	 */
	public function createRecordIfNonExisting(Session $session, User $user)
	{
		$query = $this->db->getQuery(true)
			->select($this->db->quoteName('session_id'))
			->from($this->db->quoteName('#__session'))
			->where($this->db->quoteName('session_id') . ' = ' . $this->db->quote($session->getId()));

		$this->db->setQuery($query, 0, 1);
		$exists = $this->db->loadResult();

		// If the session record doesn't exist initialise it.
		if ($exists)
		{
			return;
		}

		$query->clear();

		$time = $session->isNew() ? time() : $session->get('session.timer.start');

		$columns = array(
			$this->db->quoteName('session_id'),
			$this->db->quoteName('guest'),
			$this->db->quoteName('time'),
			$this->db->quoteName('userid'),
			$this->db->quoteName('username'),
		);

		$values = array(
			$this->db->quote($session->getId()),
			(int) $user->guest,
			(int) $time,
			(int) $user->id,
			$this->db->quote($user->username),
		);

		if ($this->app instanceof CMSApplication && !$this->app->get('shared_session', '0'))
		{
			$columns[] = $this->db->quoteName('client_id');
			$values[] = (int) $this->app->getClientId();
		}

		$query->insert($this->db->quoteName('#__session'))
			->columns($columns)
			->values(implode(', ', $values));

		$this->db->setQuery($query);

		try
		{
			$this->db->execute();
		}
		catch (\RuntimeException $e)
		{
			/*
			 * Because of how our session handlers are structured, we must abort the request if this insert query fails,
			 * especially in the case of the database handler which does not support "INSERT or UPDATE" logic. With the
			 * change to the `joomla/session` Framework package in 4.0, where the required logic is implemented in the
			 * handlers, we can change this catch block so that the error is gracefully handled and does not result
			 * in a fatal error for the request.
			 */
			throw new \RuntimeException(\JText::_('JERROR_SESSION_STARTUP'), $e->getCode(), $e);
		}
	}

	/**
	 * Delete records with a timestamp prior to the given time.
	 *
	 * @param   integer  $time  The time records should be deleted if expired before.
	 *
	 * @return  void
	 *
	 * @since   3.8.6
	 */
	public function deletePriorTo($time)
	{
		$query = $this->db->getQuery(true)
			->delete($this->db->quoteName('#__session'))
			->where($this->db->quoteName('time') . ' < ' . (int) $time);

		$this->db->setQuery($query);

		try
		{
			$this->db->execute();
		}
		catch (\JDatabaseExceptionExecuting $exception)
		{
			/*
			 * The database API logs errors on failures so we don't need to add any error handling mechanisms here.
			 * Since garbage collection does not result in a fatal error when run in the session API, we don't allow it here either.
			 */
		}
	}
}
src/Session/Session.php000064400000052722152177723700011135 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Session;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Input\Input;
use Joomla\CMS\User\UserHelper;

/**
 * Class for managing HTTP sessions
 *
 * Provides access to session-state values as well as session-level
 * settings and lifetime management methods.
 * Based on the standard PHP session handling mechanism it provides
 * more advanced features such as expire timeouts.
 *
 * @since  1.7.0
 */
class Session implements \IteratorAggregate
{
	/**
	 * Internal state.
	 * One of 'inactive'|'active'|'expired'|'destroyed'|'error'
	 *
	 * @var    string
	 * @see    Session::getState()
	 * @since  1.7.0
	 */
	protected $_state = 'inactive';

	/**
	 * Maximum age of unused session in seconds
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_expire = 900;

	/**
	 * The session store object.
	 *
	 * @var    \JSessionStorage
	 * @since  1.7.0
	 */
	protected $_store = null;

	/**
	 * Security policy.
	 * List of checks that will be done.
	 *
	 * Default values:
	 * - fix_browser
	 * - fix_adress
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_security = array('fix_browser');

	/**
	 * Session instances container.
	 *
	 * @var    Session
	 * @since  1.7.3
	 */
	protected static $instance;

	/**
	 * The type of storage for the session.
	 *
	 * @var    string
	 * @since  3.0.1
	 */
	protected $storeName;

	/**
	 * Holds the \JInput object
	 *
	 * @var    \JInput
	 * @since  3.0.1
	 */
	private $_input = null;

	/**
	 * Holds the event dispatcher object
	 *
	 * @var    \JEventDispatcher
	 * @since  3.0.1
	 */
	private $_dispatcher = null;

	/**
	 * Holds the event dispatcher object
	 *
	 * @var    \JSessionHandlerInterface
	 * @since  3.5
	 */
	protected $_handler = null;

	/**
	 * Internal data store for the session data
	 *
	 * @var  \Joomla\Registry\Registry
	 */
	protected $data;

	/**
	 * Constructor
	 *
	 * @param   string                     $store             The type of storage for the session.
	 * @param   array                      $options           Optional parameters
	 * @param   \JSessionHandlerInterface  $handlerInterface  The session handler
	 *
	 * @since   1.7.0
	 */
	public function __construct($store = 'none', array $options = array(), \JSessionHandlerInterface $handlerInterface = null)
	{
		// Set the session handler
		$this->_handler = $handlerInterface instanceof \JSessionHandlerInterface ? $handlerInterface : new \JSessionHandlerJoomla($options);

		// Initialize the data variable, let's avoid fatal error if the session is not corretly started (ie in CLI).
		$this->data = new \Joomla\Registry\Registry;

		// Clear any existing sessions
		if ($this->_handler->getId())
		{
			$this->_handler->clear();
		}

		// Create handler
		$this->_store = \JSessionStorage::getInstance($store, $options);

		$this->storeName = $store;

		$this->_setOptions($options);

		$this->_state = 'inactive';
	}

	/**
	 * Magic method to get read-only access to properties.
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  mixed   The value of the property
	 *
	 * @since   3.0.1
	 */
	public function __get($name)
	{
		if ($name === 'storeName')
		{
			return $this->$name;
		}

		if ($name === 'state' || $name === 'expire')
		{
			$property = '_' . $name;

			return $this->$property;
		}
	}

	/**
	 * Returns the global Session object, only creating it if it doesn't already exist.
	 *
	 * @param   string                     $store             The type of storage for the session.
	 * @param   array                      $options           An array of configuration options.
	 * @param   \JSessionHandlerInterface  $handlerInterface  The session handler
	 *
	 * @return  Session  The Session object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($store, $options, \JSessionHandlerInterface $handlerInterface = null)
	{
		if (!is_object(self::$instance))
		{
			self::$instance = new Session($store, $options, $handlerInterface);
		}

		return self::$instance;
	}

	/**
	 * Get current state of session
	 *
	 * @return  string  The session state
	 *
	 * @since   1.7.0
	 */
	public function getState()
	{
		return $this->_state;
	}

	/**
	 * Get expiration time in seconds
	 *
	 * @return  integer  The session expiration time in seconds
	 *
	 * @since   1.7.0
	 */
	public function getExpire()
	{
		return $this->_expire;
	}

	/**
	 * Get a session token, if a token isn't set yet one will be generated.
	 *
	 * Tokens are used to secure forms from spamming attacks. Once a token
	 * has been generated the system will check the post request to see if
	 * it is present, if not it will invalidate the session.
	 *
	 * @param   boolean  $forceNew  If true, force a new token to be created
	 *
	 * @return  string  The session token
	 *
	 * @since   1.7.0
	 */
	public function getToken($forceNew = false)
	{
		$token = $this->get('session.token');

		// Create a token
		if ($token === null || $forceNew)
		{
			$token = $this->_createToken();
			$this->set('session.token', $token);
		}

		return $token;
	}

	/**
	 * Method to determine if a token exists in the session. If not the
	 * session will be set to expired
	 *
	 * @param   string   $tCheck       Hashed token to be verified
	 * @param   boolean  $forceExpire  If true, expires the session
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function hasToken($tCheck, $forceExpire = true)
	{
		// Check if a token exists in the session
		$tStored = $this->get('session.token');

		// Check token
		if (($tStored !== $tCheck))
		{
			if ($forceExpire)
			{
				$this->_state = 'expired';
			}

			return false;
		}

		return true;
	}

	/**
	 * Method to determine a hash for anti-spoofing variable names
	 *
	 * @param   boolean  $forceNew  If true, force a new token to be created
	 *
	 * @return  string  Hashed var name
	 *
	 * @since   1.7.0
	 */
	public static function getFormToken($forceNew = false)
	{
		$user    = \JFactory::getUser();
		$session = \JFactory::getSession();

		return ApplicationHelper::getHash($user->get('id', 0) . $session->getToken($forceNew));
	}

	/**
	 * Retrieve an external iterator.
	 *
	 * @return  \ArrayIterator
	 *
	 * @since   3.0.1
	 */
	public function getIterator()
	{
		return new \ArrayIterator($this->getData());
	}

	/**
	 * Checks for a form token in the request.
	 *
	 * Use in conjunction with \JHtml::_('form.token') or Session::getFormToken.
	 *
	 * @param   string  $method  The request method in which to look for the token key.
	 *
	 * @return  boolean  True if found and valid, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function checkToken($method = 'post')
	{
		$token = self::getFormToken();
		$app = \JFactory::getApplication();

		// Check from header first
		if ($token === $app->input->server->get('HTTP_X_CSRF_TOKEN', '', 'alnum'))
		{
			return true;
		}

		// Then fallback to HTTP query
		if (!$app->input->$method->get($token, '', 'alnum'))
		{
			if (\JFactory::getSession()->isNew())
			{
				// Redirect to login screen.
				$app->enqueueMessage(\JText::_('JLIB_ENVIRONMENT_SESSION_EXPIRED'), 'warning');
				$app->redirect(\JRoute::_('index.php'));

				return true;
			}

			return false;
		}

		return true;
	}

	/**
	 * Get session name
	 *
	 * @return  string  The session name
	 *
	 * @since   1.7.0
	 */
	public function getName()
	{
		if ($this->getState() === 'destroyed')
		{
			// @TODO : raise error
			return;
		}

		return $this->_handler->getName();
	}

	/**
	 * Get session id
	 *
	 * @return  string  The session id
	 *
	 * @since   1.7.0
	 */
	public function getId()
	{
		if ($this->getState() === 'destroyed')
		{
			// @TODO : raise error
			return;
		}

		return $this->_handler->getId();
	}

	/**
	 * Returns a clone of the internal data pointer
	 *
	 * @return  \Joomla\Registry\Registry
	 */
	public function getData()
	{
		return clone $this->data;
	}

	/**
	 * Get the session handlers
	 *
	 * @return  array  An array of available session handlers
	 *
	 * @since   1.7.0
	 */
	public static function getStores()
	{
		$connectors = array();

		// Get an iterator and loop trough the driver classes.
		$iterator = new \DirectoryIterator(JPATH_LIBRARIES . '/joomla/session/storage');

		/** @type  $file  \DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Derive the class name from the type.
			$class = str_ireplace('.php', '', 'JSessionStorage' . ucfirst(trim($fileName)));

			// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
			if (!class_exists($class))
			{
				continue;
			}

			// Sweet!  Our class exists, so now we just need to know if it passes its test method.
			if ($class::isSupported())
			{
				// Connector names should not have file extensions.
				$connectors[] = str_ireplace('.php', '', $fileName);
			}
		}

		return $connectors;
	}

	/**
	 * Shorthand to check if the session is active
	 *
	 * @return  boolean
	 *
	 * @since   3.0.1
	 */
	public function isActive()
	{
		return (bool) ($this->getState() == 'active');
	}

	/**
	 * Check whether this session is currently created
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function isNew()
	{
		return (bool) ($this->get('session.counter') === 1);
	}

	/**
	 * Check whether this session is currently created
	 *
	 * @param   Input              $input       Input object for the session to use.
	 * @param   \JEventDispatcher  $dispatcher  Dispatcher object for the session to use.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function initialise(Input $input, \JEventDispatcher $dispatcher = null)
	{
		// With the introduction of the handler class this variable is no longer required
		// however we keep setting it for b/c
		$this->_input      = $input;

		// Nasty workaround to deal in a b/c way with JInput being required in the 3.4+ Handler class.
		if ($this->_handler instanceof \JSessionHandlerJoomla)
		{
			$this->_handler->input = $input;
		}

		$this->_dispatcher = $dispatcher;
	}

	/**
	 * Get data from the session store
	 *
	 * @param   string  $name       Name of a variable
	 * @param   mixed   $default    Default value of a variable if not set
	 * @param   string  $namespace  Namespace to use, default to 'default'
	 *
	 * @return  mixed  Value of a variable
	 *
	 * @since   1.7.0
	 */
	public function get($name, $default = null, $namespace = 'default')
	{
		if (!$this->isActive())
		{
			$this->start();
		}

		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() === 'destroyed')
		{
			// @TODO :: generated error here
			$error = null;

			return $error;
		}

		return $this->data->get($namespace . '.' . $name, $default);
	}

	/**
	 * Set data into the session store.
	 *
	 * @param   string  $name       Name of a variable.
	 * @param   mixed   $value      Value of a variable.
	 * @param   string  $namespace  Namespace to use, default to 'default'.
	 *
	 * @return  mixed  Old value of a variable.
	 *
	 * @since   1.7.0
	 */
	public function set($name, $value = null, $namespace = 'default')
	{
		if (!$this->isActive())
		{
			$this->start();
		}

		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return;
		}

		$prev = $this->data->get($namespace . '.' . $name, null);
		$this->data->set($namespace . '.' . $name, $value);

		return $prev;
	}

	/**
	 * Check whether data exists in the session store
	 *
	 * @param   string  $name       Name of variable
	 * @param   string  $namespace  Namespace to use, default to 'default'
	 *
	 * @return  boolean  True if the variable exists
	 *
	 * @since   1.7.0
	 */
	public function has($name, $namespace = 'default')
	{
		if (!$this->isActive())
		{
			$this->start();
		}

		// Add prefix to namespace to avoid collisions.
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return;
		}

		return !is_null($this->data->get($namespace . '.' . $name, null));
	}

	/**
	 * Unset data from the session store
	 *
	 * @param   string  $name       Name of variable
	 * @param   string  $namespace  Namespace to use, default to 'default'
	 *
	 * @return  mixed   The value from session or NULL if not set
	 *
	 * @since   1.7.0
	 */
	public function clear($name, $namespace = 'default')
	{
		if (!$this->isActive())
		{
			$this->start();
		}

		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return;
		}

		return $this->data->set($namespace . '.' . $name, null);
	}

	/**
	 * Start a session.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function start()
	{
		if ($this->getState() === 'active')
		{
			return;
		}

		$this->_start();

		$this->_state = 'active';

		// Initialise the session
		$this->_setCounter();
		$this->_setTimers();

		// Perform security checks
		if (!$this->_validate())
		{
			// If the session isn't valid because it expired try to restart it
			// else destroy it.
			if ($this->_state === 'expired')
			{
				$this->restart();
			}
			else
			{
				$this->destroy();
			}
		}

		if ($this->_dispatcher instanceof \JEventDispatcher)
		{
			$this->_dispatcher->trigger('onAfterSessionStart');
		}
	}

	/**
	 * Start a session.
	 *
	 * Creates a session (or resumes the current one based on the state of the session)
	 *
	 * @return  boolean  true on success
	 *
	 * @since   1.7.0
	 */
	protected function _start()
	{
		$this->_handler->start();

		// Ok let's unserialize the whole thing
		// Try loading data from the session
		if (isset($_SESSION['joomla']) && !empty($_SESSION['joomla']))
		{
			$data = $_SESSION['joomla'];

			$data = base64_decode($data);

			$this->data = unserialize($data);
		}

		// Temporary, PARTIAL, data migration of existing session data to avoid logout on update from J < 3.4.7
		if (isset($_SESSION['__default']) && !empty($_SESSION['__default']))
		{
			$migratableKeys = array(
				'user',
				'session.token',
				'session.counter',
				'session.timer.start',
				'session.timer.last',
				'session.timer.now'
			);

			foreach ($migratableKeys as $migratableKey)
			{
				if (!empty($_SESSION['__default'][$migratableKey]))
				{
					// Don't overwrite existing session data
					if (!is_null($this->data->get('__default.' . $migratableKey, null)))
					{
						continue;
					}

					$this->data->set('__default.' . $migratableKey, $_SESSION['__default'][$migratableKey]);
					unset($_SESSION['__default'][$migratableKey]);
				}
			}

			/**
			 * Finally, empty the __default key since we no longer need it. Don't unset it completely, we need this
			 * for the administrator/components/com_admin/script.php to detect upgraded sessions and perform a full
			 * session cleanup.
			 */
			$_SESSION['__default'] = array();
		}

		return true;
	}

	/**
	 * Frees all session variables and destroys all data registered to a session
	 *
	 * This method resets the data pointer and destroys all of the data associated
	 * with the current session in its storage. It forces a new session to be
	 * started after this method is called. It does not unset the session cookie.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     session_destroy()
	 * @see     session_unset()
	 * @since   1.7.0
	 */
	public function destroy()
	{
		// Session was already destroyed
		if ($this->getState() === 'destroyed')
		{
			return true;
		}

		// Kill session
		$this->_handler->clear();

		// Create new data storage
		$this->data = new \Joomla\Registry\Registry;

		$this->_state = 'destroyed';

		return true;
	}

	/**
	 * Restart an expired or locked session.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     Session::destroy()
	 * @since   1.7.0
	 */
	public function restart()
	{
		$this->destroy();

		if ($this->getState() !== 'destroyed')
		{
			// @TODO :: generated error here
			return false;
		}

		// Re-register the session handler after a session has been destroyed, to avoid PHP bug
		$this->_store->register();

		$this->_state = 'restart';

		// Regenerate session id
		$this->_start();
		$this->_handler->regenerate(true, null);
		$this->_state = 'active';

		if (!$this->_validate())
		{
			/**
			 * Destroy the session if it's not valid - we can't restart the session here unlike in the start method
			 * else we risk recursion.
			 */
			$this->destroy();
		}

		$this->_setCounter();

		return true;
	}

	/**
	 * Create a new session and copy variables from the old one
	 *
	 * @return  boolean $result true on success
	 *
	 * @since   1.7.0
	 */
	public function fork()
	{
		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return false;
		}

		// Restart session with new id
		$this->_handler->regenerate(true, null);

		return true;
	}

	/**
	 * Writes session data and ends session
	 *
	 * Session data is usually stored after your script terminated without the need
	 * to call Session::close(), but as session data is locked to prevent concurrent
	 * writes only one script may operate on a session at any time. When using
	 * framesets together with sessions you will experience the frames loading one
	 * by one due to this locking. You can reduce the time needed to load all the
	 * frames by ending the session as soon as all changes to session variables are
	 * done.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function close()
	{
		$this->_handler->save();
		$this->_state = 'inactive';
	}

	/**
	 * Delete expired session data
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.8.6
	 */
	public function gc()
	{
		return $this->_store->gc($this->getExpire());
	}

	/**
	 * Set the session handler
	 *
	 * @param   \JSessionHandlerInterface  $handler  The session handler
	 *
	 * @return  void
	 */
	public function setHandler(\JSessionHandlerInterface $handler)
	{
		$this->_handler = $handler;
	}

	/**
	 * Create a token-string
	 *
	 * @param   integer  $length  Length of string
	 *
	 * @return  string  Generated token
	 *
	 * @since   1.7.0
	 */
	protected function _createToken($length = 32)
	{
		return UserHelper::genRandomPassword($length);
	}

	/**
	 * Set counter of session usage
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	protected function _setCounter()
	{
		$counter = $this->get('session.counter', 0);
		++$counter;

		$this->set('session.counter', $counter);

		return true;
	}

	/**
	 * Set the session timers
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	protected function _setTimers()
	{
		if (!$this->has('session.timer.start'))
		{
			$start = time();

			$this->set('session.timer.start', $start);
			$this->set('session.timer.last', $start);
			$this->set('session.timer.now', $start);
		}

		$this->set('session.timer.last', $this->get('session.timer.now'));
		$this->set('session.timer.now', time());

		return true;
	}

	/**
	 * Set additional session options
	 *
	 * @param   array  $options  List of parameter
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	protected function _setOptions(array $options)
	{
		// Set name
		if (isset($options['name']))
		{
			$this->_handler->setName(md5($options['name']));
		}

		// Set id
		if (isset($options['id']))
		{
			$this->_handler->setId($options['id']);
		}

		// Set expire time
		if (isset($options['expire']))
		{
			$this->_expire = $options['expire'];
		}

		// Get security options
		if (isset($options['security']))
		{
			$this->_security = explode(',', $options['security']);
		}

		// Sync the session maxlifetime
		if (!headers_sent())
		{
			ini_set('session.gc_maxlifetime', $this->_expire);
		}

		return true;
	}

	/**
	 * Do some checks for security reason
	 *
	 * - timeout check (expire)
	 * - ip-fixiation
	 * - browser-fixiation
	 *
	 * If one check failed, session data has to be cleaned.
	 *
	 * @param   boolean  $restart  Reactivate session
	 *
	 * @return  boolean  True on success
	 *
	 * @link    http://shiflett.org/articles/the-truth-about-sessions
	 * @since   1.7.0
	 */
	protected function _validate($restart = false)
	{
		// Allow to restart a session
		if ($restart)
		{
			$this->_state = 'active';

			$this->set('session.client.address', null);
			$this->set('session.client.forwarded', null);
			$this->set('session.client.browser', null);
			$this->set('session.token', null);
		}

		// Check if session has expired
		if ($this->getExpire())
		{
			$curTime = $this->get('session.timer.now', 0);
			$maxTime = $this->get('session.timer.last', 0) + $this->getExpire();

			// Empty session variables
			if ($maxTime < $curTime)
			{
				$this->_state = 'expired';

				return false;
			}
		}

		// Check for client address
		if (in_array('fix_adress', $this->_security) && isset($_SERVER['REMOTE_ADDR'])
			&& filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) !== false)
		{
			$ip = $this->get('session.client.address');

			if ($ip === null)
			{
				$this->set('session.client.address', $_SERVER['REMOTE_ADDR']);
			}
			elseif ($_SERVER['REMOTE_ADDR'] !== $ip)
			{
				$this->_state = 'error';

				return false;
			}
		}

		// Record proxy forwarded for in the session in case we need it later
		if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && filter_var($_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP) !== false)
		{
			$this->set('session.client.forwarded', $_SERVER['HTTP_X_FORWARDED_FOR']);
		}

		return true;
	}
}
src/Session/Exception/UnsupportedStorageException.php000064400000000707152177723700017200 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Session\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an unsupported session storage object
 *
 * @since  3.6.3
 */
class UnsupportedStorageException extends \RuntimeException
{
}
src/Layout/BaseLayout.php000064400000012533152177723700011410 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Layout;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Base class for rendering a display layout
 *
 * @link   https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
 * @since  3.0
 */
class BaseLayout implements LayoutInterface
{
	/**
	 * Options object
	 *
	 * @var    Registry
	 * @since  3.2
	 */
	protected $options = null;

	/**
	 * Data for the layout
	 *
	 * @var    array
	 * @since  3.5
	 */
	protected $data = array();

	/**
	 * Debug information messages
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $debugMessages = array();

	/**
	 * Set the options
	 *
	 * @param   array|Registry  $options  Array / Registry object with the options to load
	 *
	 * @return  BaseLayout  Instance of $this to allow chaining.
	 *
	 * @since   3.2
	 */
	public function setOptions($options = null)
	{
		// Received Registry
		if ($options instanceof Registry)
		{
			$this->options = $options;
		}
		// Received array
		elseif (is_array($options))
		{
			$this->options = new Registry($options);
		}
		else
		{
			$this->options = new Registry;
		}

		return $this;
	}

	/**
	 * Get the options
	 *
	 * @return  Registry  Object with the options
	 *
	 * @since   3.2
	 */
	public function getOptions()
	{
		// Always return a Registry instance
		if (!($this->options instanceof Registry))
		{
			$this->resetOptions();
		}

		return $this->options;
	}

	/**
	 * Function to empty all the options
	 *
	 * @return  BaseLayout  Instance of $this to allow chaining.
	 *
	 * @since   3.2
	 */
	public function resetOptions()
	{
		return $this->setOptions(null);
	}

	/**
	 * Method to escape output.
	 *
	 * @param   string  $output  The output to escape.
	 *
	 * @return  string  The escaped output.
	 *
	 * @note the ENT_COMPAT flag will be replaced by ENT_QUOTES in Joomla 4.0 to also escape single quotes
	 *
	 * @since   3.0
	 */
	public function escape($output)
	{
		return htmlspecialchars($output, ENT_COMPAT, 'UTF-8');
	}

	/**
	 * Get the debug messages array
	 *
	 * @return  array
	 *
	 * @since   3.2
	 */
	public function getDebugMessages()
	{
		return $this->debugMessages;
	}

	/**
	 * Method to render the layout.
	 *
	 * @param   array  $displayData  Array of properties available for use inside the layout file to build the displayed output
	 *
	 * @return  string  The necessary HTML to display the layout
	 *
	 * @since   3.0
	 */
	public function render($displayData)
	{
		// Automatically merge any previously data set if $displayData is an array
		if (is_array($displayData))
		{
			$displayData = array_merge($this->data, $displayData);
		}

		return '';
	}

	/**
	 * Render the list of debug messages
	 *
	 * @return  string  Output text/HTML code
	 *
	 * @since   3.2
	 */
	public function renderDebugMessages()
	{
		return implode($this->debugMessages, "\n");
	}

	/**
	 * Add a debug message to the debug messages array
	 *
	 * @param   string  $message  Message to save
	 *
	 * @return  self
	 *
	 * @since   3.2
	 */
	public function addDebugMessage($message)
	{
		$this->debugMessages[] = $message;

		return $this;
	}

	/**
	 * Clear the debug messages array
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function clearDebugMessages()
	{
		$this->debugMessages = array();

		return $this;
	}

	/**
	 * Render a layout with debug info
	 *
	 * @param   mixed  $data  Data passed to the layout
	 *
	 * @return  string
	 *
	 * @since    3.5
	 */
	public function debug($data = array())
	{
		$this->setDebug(true);

		$output = $this->render($data);

		$this->setDebug(false);

		return $output;
	}

	/**
	 * Method to get the value from the data array
	 *
	 * @param   string  $key           Key to search for in the data array
	 * @param   mixed   $defaultValue  Default value to return if the key is not set
	 *
	 * @return  mixed   Value from the data array | defaultValue if doesn't exist
	 *
	 * @since   3.5
	 */
	public function get($key, $defaultValue = null)
	{
		return isset($this->data[$key]) ? $this->data[$key] : $defaultValue;
	}

	/**
	 * Get the data being rendered
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Check if debug mode is enabled
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function isDebugEnabled()
	{
		return $this->getOptions()->get('debug', false) === true;
	}

	/**
	 * Method to set a value in the data array. Example: $layout->set('items', $items);
	 *
	 * @param   string  $key    Key for the data array
	 * @param   mixed   $value  Value to assign to the key
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function set($key, $value)
	{
		$this->data[(string) $key] = $value;

		return $this;
	}

	/**
	 * Set the the data passed the layout
	 *
	 * @param   array  $data  Array with the data for the layout
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function setData(array $data)
	{
		$this->data = $data;

		return $this;
	}

	/**
	 * Change the debug mode
	 *
	 * @param   boolean  $debug  Enable / Disable debug
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function setDebug($debug)
	{
		$this->options->set('debug', (boolean) $debug);

		return $this;
	}
}
src/Layout/LayoutInterface.php000064400000001704152177723700012434 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Layout;

defined('JPATH_PLATFORM') or die;

/**
 * Interface to handle display layout
 *
 * @link   https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
 * @since  3.0
 */
interface LayoutInterface
{
	/**
	 * Method to escape output.
	 *
	 * @param   string  $output  The output to escape.
	 *
	 * @return  string  The escaped output.
	 *
	 * @since   3.0
	 */
	public function escape($output);

	/**
	 * Method to render the layout.
	 *
	 * @param   array  $displayData  Array of properties available for use inside the layout file to build the displayed output
	 *
	 * @return  string  The rendered layout.
	 *
	 * @since   3.0
	 */
	public function render($displayData);
}
src/Layout/LayoutHelper.php000064400000004735152177723700011762 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Layout;

defined('JPATH_PLATFORM') or die;

/**
 * Helper to render a Layout object, storing a base path
 *
 * @link   https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
 * @since  3.1
 */
class LayoutHelper
{
	/**
	 * A default base path that will be used if none is provided when calling the render method.
	 * Note that FileLayout itself will defaults to JPATH_ROOT . '/layouts' if no basePath is supplied at all
	 *
	 * @var    string
	 * @since  3.1
	 */
	public static $defaultBasePath = '';

	/**
	 * Method to render a layout with debug info
	 *
	 * @param   string  $layoutFile   Dot separated path to the layout file, relative to base path
	 * @param   mixed   $displayData  Object which properties are used inside the layout file to build displayed output
	 * @param   string  $basePath     Base path to use when loading layout files
	 * @param   mixed   $options      Optional custom options to load. Registry or array format
	 *
	 * @return  string
	 *
	 * @since   3.5
	 */
	public static function debug($layoutFile, $displayData = null, $basePath = '', $options = null)
	{
		$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;

		// Make sure we send null to FileLayout if no path set
		$basePath = empty($basePath) ? null : $basePath;
		$layout = new FileLayout($layoutFile, $basePath, $options);

		return $layout->debug($displayData);
	}

	/**
	 * Method to render the layout.
	 *
	 * @param   string  $layoutFile   Dot separated path to the layout file, relative to base path
	 * @param   mixed   $displayData  Object which properties are used inside the layout file to build displayed output
	 * @param   string  $basePath     Base path to use when loading layout files
	 * @param   mixed   $options      Optional custom options to load. Registry or array format
	 *
	 * @return  string
	 *
	 * @since   3.1
	 */
	public static function render($layoutFile, $displayData = null, $basePath = '', $options = null)
	{
		$basePath = empty($basePath) ? self::$defaultBasePath : $basePath;

		// Make sure we send null to FileLayout if no path set
		$basePath = empty($basePath) ? null : $basePath;
		$layout = new FileLayout($layoutFile, $basePath, $options);

		return $layout->render($displayData);
	}
}
src/Layout/FileLayout.php000064400000033301152177723700011411 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Layout;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;

/**
 * Base class for rendering a display layout
 * loaded from from a layout file
 *
 * @link   https://docs.joomla.org/Special:MyLanguage/Sharing_layouts_across_views_or_extensions_with_JLayout
 * @since  3.0
 */
class FileLayout extends BaseLayout
{
	/**
	 * Cached layout paths
	 *
	 * @var    array
	 * @since  3.5
	 */
	protected static $cache = array();

	/**
	 * Dot separated path to the layout file, relative to base path
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $layoutId = '';

	/**
	 * Base path to use when loading layout files
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected $basePath = null;

	/**
	 * Full path to actual layout files, after possible template override check
	 *
	 * @var    string
	 * @since  3.0.3
	 */
	protected $fullPath = null;

	/**
	 * Paths to search for layouts
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected $includePaths = array();

	/**
	 * Method to instantiate the file-based layout.
	 *
	 * @param   string  $layoutId  Dot separated path to the layout file, relative to base path
	 * @param   string  $basePath  Base path to use when loading layout files
	 * @param   mixed   $options   Optional custom options to load. Registry or array format [@since 3.2]
	 *
	 * @since   3.0
	 */
	public function __construct($layoutId, $basePath = null, $options = null)
	{
		// Initialise / Load options
		$this->setOptions($options);

		// Main properties
		$this->setLayout($layoutId);
		$this->basePath = $basePath;

		// Init Enviroment
		$this->setComponent($this->options->get('component', 'auto'));
		$this->setClient($this->options->get('client', 'auto'));
	}

	/**
	 * Method to render the layout.
	 *
	 * @param   array  $displayData  Array of properties available for use inside the layout file to build the displayed output
	 *
	 * @return  string  The necessary HTML to display the layout
	 *
	 * @since   3.0
	 */
	public function render($displayData = array())
	{
		$this->clearDebugMessages();

		// Inherit base output from parent class
		$layoutOutput = '';

		// Automatically merge any previously data set if $displayData is an array
		if (is_array($displayData))
		{
			$displayData = array_merge($this->data, $displayData);
		}

		// Check possible overrides, and build the full path to layout file
		$path = $this->getPath();

		if ($this->isDebugEnabled())
		{
			echo '<pre>' . $this->renderDebugMessages() . '</pre>';
		}

		// Nothing to show
		if (empty($path))
		{
			return $layoutOutput;
		}

		ob_start();
		include $path;
		$layoutOutput .= ob_get_contents();
		ob_end_clean();

		return $layoutOutput;
	}

	/**
	 * Method to finds the full real file path, checking possible overrides
	 *
	 * @return  string  The full path to the layout file
	 *
	 * @since   3.0
	 */
	protected function getPath()
	{
		\JLoader::import('joomla.filesystem.path');

		$layoutId     = $this->getLayoutId();
		$includePaths = $this->getIncludePaths();
		$suffixes     = $this->getSuffixes();

		$this->addDebugMessage('<strong>Layout:</strong> ' . $this->layoutId);

		if (!$layoutId)
		{
			$this->addDebugMessage('<strong>There is no active layout</strong>');

			return;
		}

		if (!$includePaths)
		{
			$this->addDebugMessage('<strong>There are no folders to search for layouts:</strong> ' . $layoutId);

			return;
		}

		$hash = md5(
			json_encode(
				array(
					'paths'    => $includePaths,
					'suffixes' => $suffixes,
				)
			)
		);

		if (!empty(static::$cache[$layoutId][$hash]))
		{
			$this->addDebugMessage('<strong>Cached path:</strong> ' . static::$cache[$layoutId][$hash]);

			return static::$cache[$layoutId][$hash];
		}

		$this->addDebugMessage('<strong>Include Paths:</strong> ' . print_r($includePaths, true));

		// Search for suffixed versions. Example: tags.j31.php
		if ($suffixes)
		{
			$this->addDebugMessage('<strong>Suffixes:</strong> ' . print_r($suffixes, true));

			foreach ($suffixes as $suffix)
			{
				$rawPath  = str_replace('.', '/', $this->layoutId) . '.' . $suffix . '.php';
				$this->addDebugMessage('<strong>Searching layout for:</strong> ' . $rawPath);

				if ($foundLayout = \JPath::find($this->includePaths, $rawPath))
				{
					$this->addDebugMessage('<strong>Found layout:</strong> ' . $this->fullPath);

					static::$cache[$layoutId][$hash] = $foundLayout;

					return static::$cache[$layoutId][$hash];
				}
			}
		}

		// Standard version
		$rawPath  = str_replace('.', '/', $this->layoutId) . '.php';
		$this->addDebugMessage('<strong>Searching layout for:</strong> ' . $rawPath);

		$foundLayout = \JPath::find($this->includePaths, $rawPath);

		if (!$foundLayout)
		{
			$this->addDebugMessage('<strong>Unable to find layout: </strong> ' . $layoutId);

			return;
		}

		$this->addDebugMessage('<strong>Found layout:</strong> ' . $foundLayout);

		static::$cache[$layoutId][$hash] = $foundLayout;

		return static::$cache[$layoutId][$hash];
	}

	/**
	 * Add one path to include in layout search. Proxy of addIncludePaths()
	 *
	 * @param   string  $path  The path to search for layouts
	 *
	 * @return  self
	 *
	 * @since   3.2
	 */
	public function addIncludePath($path)
	{
		$this->addIncludePaths($path);

		return $this;
	}

	/**
	 * Add one or more paths to include in layout search
	 *
	 * @param   string  $paths  The path or array of paths to search for layouts
	 *
	 * @return  self
	 *
	 * @since   3.2
	 */
	public function addIncludePaths($paths)
	{
		if (empty($paths))
		{
			return $this;
		}

		$includePaths = $this->getIncludePaths();

		if (is_array($paths))
		{
			$includePaths = array_unique(array_merge($paths, $includePaths));
		}
		else
		{
			array_unshift($includePaths, $paths);
		}

		$this->setIncludePaths($includePaths);

		return $this;
	}

	/**
	 * Clear the include paths
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function clearIncludePaths()
	{
		$this->includePaths = array();

		return $this;
	}

	/**
	 * Get the active include paths
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	public function getIncludePaths()
	{
		if (empty($this->includePaths))
		{
			$this->includePaths = $this->getDefaultIncludePaths();
		}

		return $this->includePaths;
	}

	/**
	 * Get the active layout id
	 *
	 * @return  string
	 *
	 * @since   3.5
	 */
	public function getLayoutId()
	{
		return $this->layoutId;
	}

	/**
	 * Get the active suffixes
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	public function getSuffixes()
	{
		return $this->getOptions()->get('suffixes', array());
	}

	/**
	 * Load the automatically generated language suffixes.
	 * Example: array('es-ES', 'es', 'ltr')
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function loadLanguageSuffixes()
	{
		$lang = \JFactory::getLanguage();

		$langTag = $lang->getTag();
		$langParts = explode('-', $langTag);

		$suffixes = array($langTag, $langParts[0]);
		$suffixes[] = $lang->isRTL() ? 'rtl' : 'ltr';

		$this->setSuffixes($suffixes);

		return $this;
	}

	/**
	 * Load the automatically generated version suffixes.
	 * Example: array('j311', 'j31', 'j3')
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function loadVersionSuffixes()
	{
		$cmsVersion = new \JVersion;

		// Example j311
		$fullVersion = 'j' . str_replace('.', '', $cmsVersion->getShortVersion());

		// Create suffixes like array('j311', 'j31', 'j3')
		$suffixes = array(
			$fullVersion,
			substr($fullVersion, 0, 3),
			substr($fullVersion, 0, 2),
		);

		$this->setSuffixes(array_unique($suffixes));

		return $this;
	}

	/**
	 * Remove one path from the layout search
	 *
	 * @param   string  $path  The path to remove from the layout search
	 *
	 * @return  self
	 *
	 * @since   3.2
	 */
	public function removeIncludePath($path)
	{
		$this->removeIncludePaths($path);

		return $this;
	}

	/**
	 * Remove one or more paths to exclude in layout search
	 *
	 * @param   string  $paths  The path or array of paths to remove for the layout search
	 *
	 * @return  self
	 *
	 * @since   3.2
	 */
	public function removeIncludePaths($paths)
	{
		if (!empty($paths))
		{
			$paths = (array) $paths;

			$this->includePaths = array_diff($this->includePaths, $paths);
		}

		return $this;
	}

	/**
	 * Validate that the active component is valid
	 *
	 * @param   string  $option  URL Option of the component. Example: com_content
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	protected function validComponent($option = null)
	{
		// By default we will validate the active component
		$component = ($option !== null) ? $option : $this->options->get('component', null);

		// Valid option format
		if (!empty($component) && substr_count($component, 'com_'))
		{
			// Latest check: component exists and is enabled
			return ComponentHelper::isEnabled($component);
		}

		return false;
	}

	/**
	 * Method to change the component where search for layouts
	 *
	 * @param   string  $option  URL Option of the component. Example: com_content
	 *
	 * @return  mixed  Component option string | null for none
	 *
	 * @since   3.2
	 */
	public function setComponent($option)
	{
		$component = null;

		switch ((string) $option)
		{
			case 'none':
				$component = null;
				break;

			case 'auto':
				$component = ApplicationHelper::getComponentName();
				break;

			default:
				$component = $option;
				break;
		}

		// Extra checks
		if (!$this->validComponent($component))
		{
			$component = null;
		}

		$this->options->set('component', $component);

		// Refresh include paths
		$this->refreshIncludePaths();
	}

	/**
	 * Function to initialise the application client
	 *
	 * @param   mixed  $client  Frontend: 'site' or 0 | Backend: 'admin' or 1
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function setClient($client)
	{
		// Force string conversion to avoid unexpected states
		switch ((string) $client)
		{
			case 'site':
			case '0':
				$client = 0;
				break;

			case 'admin':
			case '1':
				$client = 1;
				break;

			default:
				$client = (int) \JFactory::getApplication()->isClient('administrator');
				break;
		}

		$this->options->set('client', $client);

		// Refresh include paths
		$this->refreshIncludePaths();
	}

	/**
	 * Change the layout
	 *
	 * @param   string  $layoutId  Layout to render
	 *
	 * @return  self
	 *
	 * @since   3.2
	 *
	 * @deprecated  3.5  Use setLayoutId()
	 */
	public function setLayout($layoutId)
	{
		// Log usage of deprecated function
		\JLog::add(__METHOD__ . '() is deprecated, use FileLayout::setLayoutId() instead.', \JLog::WARNING, 'deprecated');

		return $this->setLayoutId($layoutId);
	}

	/**
	 * Set the active layout id
	 *
	 * @param   string  $layoutId  Layout identifier
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function setLayoutId($layoutId)
	{
		$this->layoutId = $layoutId;
		$this->fullPath = null;

		return $this;
	}

	/**
	 * Refresh the list of include paths
	 *
	 * @return  self
	 *
	 * @since   3.2
	 *
	 * @deprecated  3.5  Use FileLayout::clearIncludePaths()
	 */
	protected function refreshIncludePaths()
	{
		// Log usage of deprecated function
		\JLog::add(__METHOD__ . '() is deprecated, use FileLayout::clearIncludePaths() instead.', \JLog::WARNING, 'deprecated');

		$this->clearIncludePaths();

		return $this;
	}

	/**
	 * Get the default array of include paths
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	public function getDefaultIncludePaths()
	{
		// Reset includePaths
		$paths = array();

		// (1 - highest priority) Received a custom high priority path
		if ($this->basePath !== null)
		{
			$paths[] = rtrim($this->basePath, DIRECTORY_SEPARATOR);
		}

		// Component layouts & overrides if exist
		$component = $this->options->get('component', null);

		if (!empty($component))
		{
			// (2) Component template overrides path
			$paths[] = JPATH_THEMES . '/' . \JFactory::getApplication()->getTemplate() . '/html/layouts/' . $component;

			// (3) Component path
			if ($this->options->get('client') == 0)
			{
				$paths[] = JPATH_SITE . '/components/' . $component . '/layouts';
			}
			else
			{
				$paths[] = JPATH_ADMINISTRATOR . '/components/' . $component . '/layouts';
			}
		}

		// (4) Standard Joomla! layouts overriden
		$paths[] = JPATH_THEMES . '/' . \JFactory::getApplication()->getTemplate() . '/html/layouts';

		// (5 - lower priority) Frontend base layouts
		$paths[] = JPATH_ROOT . '/layouts';

		return $paths;
	}

	/**
	 * Set the include paths to search for layouts
	 *
	 * @param   array  $paths  Array with paths to search in
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function setIncludePaths($paths)
	{
		$this->includePaths = (array) $paths;

		return $this;
	}

	/**
	 * Set suffixes to search layouts
	 *
	 * @param   mixed  $suffixes  String with a single suffix or 'auto' | 'none' or array of suffixes
	 *
	 * @return  self
	 *
	 * @since   3.5
	 */
	public function setSuffixes(array $suffixes)
	{
		$this->options->set('suffixes', $suffixes);

		return $this;
	}

	/**
	 * Render a layout with the same include paths & options
	 *
	 * @param   string  $layoutId     The identifier for the sublayout to be searched in a subfolder with the name of the current layout
	 * @param   mixed   $displayData  Data to be rendered
	 *
	 * @return  string  The necessary HTML to display the layout
	 *
	 * @since   3.2
	 */
	public function sublayout($layoutId, $displayData)
	{
		// Sublayouts are searched in a subfolder with the name of the current layout
		if (!empty($this->layoutId))
		{
			$layoutId = $this->layoutId . '.' . $layoutId;
		}

		$sublayout = new static($layoutId, $this->basePath, $this->options);
		$sublayout->includePaths = $this->includePaths;

		return $sublayout->render($displayData);
	}
}
src/Input/Input.php000064400000014024152177723700010256 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;

/**
 * Joomla! Input Base Class
 *
 * This is an abstracted input class used to manage retrieving data from the application environment.
 *
 * @since       1.7.0
 * @deprecated  5.0  Use Joomla\Input\Input instead
 *
 * @property-read   Input   $get
 * @property-read   Input   $post
 * @property-read   Input   $request
 * @property-read   Input   $server
 * @property-read   Input   $env
 * @property-read   Files   $files
 * @property-read   Cookie  $cookie
 */
class Input extends \Joomla\Input\Input
{
	/**
	 * Container with allowed superglobals
	 *
	 * @var    array
	 * @since  3.8.9
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	private static $allowedGlobals = array('REQUEST', 'GET', 'POST', 'FILES', 'SERVER', 'ENV');

	/**
	 * Input objects
	 *
	 * @var    Input[]
	 * @since  1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	protected $inputs = array();

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is $_REQUEST)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	public function __construct($source = null, array $options = array())
	{
		if (!isset($options['filter']))
		{
			$this->filter = InputFilter::getInstance();
		}

		parent::__construct($source, $options);
	}

	/**
	 * Magic method to get an input object
	 *
	 * @param   mixed  $name  Name of the input object to retrieve.
	 *
	 * @return  Input  The request input object
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	public function __get($name)
	{
		if (isset($this->inputs[$name]))
		{
			return $this->inputs[$name];
		}

		$className = '\\Joomla\\CMS\\Input\\' . ucfirst($name);

		if (class_exists($className))
		{
			$this->inputs[$name] = new $className(null, $this->options);

			return $this->inputs[$name];
		}

		$superGlobal = '_' . strtoupper($name);

		if (in_array(strtoupper($name), self::$allowedGlobals, true) && isset($GLOBALS[$superGlobal]))
		{
			$this->inputs[$name] = new Input($GLOBALS[$superGlobal], $this->options);

			return $this->inputs[$name];
		}

		// Try using the parent class
		return parent::__get($name);
	}

	/**
	 * Gets an array of values from the request.
	 *
	 * @param   array   $vars           Associative array of keys and filter types to apply.
	 *                                  If empty and datasource is null, all the input data will be returned
	 *                                  but filtered using the filter given by the parameter defaultFilter in
	 *                                  JFilterInput::clean.
	 * @param   mixed   $datasource     Array to retrieve data from, or null.
	 * @param   string  $defaultFilter  Default filter used in JFilterInput::clean if vars is empty and
	 *                                  datasource is null. If 'unknown', the default case is used in
	 *                                  JFilterInput::clean.
	 *
	 * @return  mixed  The filtered input data.
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	public function getArray(array $vars = array(), $datasource = null, $defaultFilter = 'unknown')
	{
		return $this->getArrayRecursive($vars, $datasource, $defaultFilter, false);
	}

	/**
	 * Gets an array of values from the request.
	 *
	 * @param   array   $vars           Associative array of keys and filter types to apply.
	 *                                  If empty and datasource is null, all the input data will be returned
	 *                                  but filtered using the filter given by the parameter defaultFilter in
	 *                                  JFilterInput::clean.
	 * @param   mixed   $datasource     Array to retrieve data from, or null.
	 * @param   string  $defaultFilter  Default filter used in JFilterInput::clean if vars is empty and
	 *                                  datasource is null. If 'unknown', the default case is used in
	 *                                  JFilterInput::clean.
	 * @param   bool    $recursion      Flag to indicate a recursive function call.
	 *
	 * @return  mixed  The filtered input data.
	 *
	 * @since   3.4.2
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	protected function getArrayRecursive(array $vars = array(), $datasource = null, $defaultFilter = 'unknown', $recursion = false)
	{
		if (empty($vars) && is_null($datasource))
		{
			$vars = $this->data;
		}
		else
		{
			if (!$recursion)
			{
				$defaultFilter = null;
			}
		}

		$results = array();

		foreach ($vars as $k => $v)
		{
			if (is_array($v))
			{
				if (is_null($datasource))
				{
					$results[$k] = $this->getArrayRecursive($v, $this->get($k, null, 'array'), $defaultFilter, true);
				}
				else
				{
					$results[$k] = $this->getArrayRecursive($v, $datasource[$k], $defaultFilter, true);
				}
			}
			else
			{
				$filter = isset($defaultFilter) ? $defaultFilter : $v;

				if (is_null($datasource))
				{
					$results[$k] = $this->get($k, null, $filter);
				}
				elseif (isset($datasource[$k]))
				{
					$results[$k] = $this->filter->clean($datasource[$k], $filter);
				}
				else
				{
					$results[$k] = $this->filter->clean(null, $filter);
				}
			}
		}

		return $results;
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  Input  The input object.
	 *
	 * @since   3.0.0
	 * @deprecated  5.0  Use Joomla\Input\Input instead
	 */
	public function unserialize($input)
	{
		// Unserialize the options, data, and inputs.
		list($this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}
	}
}
src/Input/Cli.php000064400000010277152177723700007674 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;

/**
 * Joomla! Input CLI Class
 *
 * @since       1.7.0
 * @deprecated  5.0  Use Joomla\Input\Cli instead
 */
class Cli extends Input
{
	/**
	 * The executable that was called to run the CLI script.
	 *
	 * @var    string
	 * @since  1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	public $executable;

	/**
	 * The additional arguments passed to the script that are not associated
	 * with a specific argument name.
	 *
	 * @var    array
	 * @since  1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	public $args = array();

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is $_REQUEST)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}

		// Get the command line options
		$this->parseArguments();

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Method to serialize the input.
	 *
	 * @return  string  The serialized input.
	 *
	 * @since   3.0.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	public function serialize()
	{
		// Load all of the inputs.
		$this->loadAllInputs();

		// Remove $_ENV and $_SERVER from the inputs.
		$inputs = $this->inputs;
		unset($inputs['env']);
		unset($inputs['server']);

		// Serialize the executable, args, options, data, and inputs.
		return serialize(array($this->executable, $this->args, $this->options, $this->data, $inputs));
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  Input  The input object.
	 *
	 * @since   3.0.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	public function unserialize($input)
	{
		// Unserialize the executable, args, options, data, and inputs.
		list($this->executable, $this->args, $this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}
	}

	/**
	 * Initialise the options and arguments
	 *
	 * Not supported: -abc c-value
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cli instead
	 */
	protected function parseArguments()
	{
		$argv = $_SERVER['argv'];

		$this->executable = array_shift($argv);

		$out = array();

		for ($i = 0, $j = count($argv); $i < $j; $i++)
		{
			$arg = $argv[$i];

			// --foo --bar=baz
			if (substr($arg, 0, 2) === '--')
			{
				$eqPos = strpos($arg, '=');

				// --foo
				if ($eqPos === false)
				{
					$key = substr($arg, 2);

					// --foo value
					if ($i + 1 < $j && $argv[$i + 1][0] !== '-')
					{
						$value = $argv[$i + 1];
						$i++;
					}
					else
					{
						$value = isset($out[$key]) ? $out[$key] : true;
					}

					$out[$key] = $value;
				}

				// --bar=baz
				else
				{
					$key = substr($arg, 2, $eqPos - 2);
					$value = substr($arg, $eqPos + 1);
					$out[$key] = $value;
				}
			}
			elseif (substr($arg, 0, 1) === '-')
			// -k=value -abc
			{
				// -k=value
				if (substr($arg, 2, 1) === '=')
				{
					$key = substr($arg, 1, 1);
					$value = substr($arg, 3);
					$out[$key] = $value;
				}
				else
				// -abc
				{
					$chars = str_split(substr($arg, 1));

					foreach ($chars as $char)
					{
						$key = $char;
						$value = isset($out[$key]) ? $out[$key] : true;
						$out[$key] = $value;
					}

					// -a a-value
					if ((count($chars) === 1) && ($i + 1 < $j) && ($argv[$i + 1][0] !== '-'))
					{
						$out[$key] = $argv[$i + 1];
						$i++;
					}
				}
			}
			else
			{
				// Plain-arg
				$this->args[] = $arg;
			}
		}

		$this->data = $out;
	}
}
src/Input/Json.php000064400000003317152177723700010073 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;

/**
 * Joomla! Input JSON Class
 *
 * This class decodes a JSON string from the raw request data and makes it available via
 * the standard JInput interface.
 *
 * @since       3.0.1
 * @deprecated  5.0  Use Joomla\Input\Json instead
 */
class Json extends Input
{
	/**
	 * @var    string  The raw JSON string from the request.
	 * @since  3.0.1
	 * @deprecated  5.0  Use Joomla\Input\Json instead
	 */
	private $_raw;

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is the raw HTTP input decoded from JSON)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   3.0.1
	 * @deprecated  5.0  Use Joomla\Input\Json instead
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}

		if (is_null($source))
		{
			$this->_raw = file_get_contents('php://input');
			$this->data = json_decode($this->_raw, true);
		}
		else
		{
			$this->data = & $source;
		}

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets the raw JSON string from the request.
	 *
	 * @return  string  The raw JSON string from the request.
	 *
	 * @since   3.0.1
	 * @deprecated  5.0  Use Joomla\Input\Json instead
	 */
	public function getRaw()
	{
		return $this->_raw;
	}
}
src/Input/Files.php000064400000006421152177723700010223 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;

/**
 * Joomla! Input Files Class
 *
 * @since       1.7.0
 * @deprecated  5.0  Use Joomla\Input\Files instead
 */
class Files extends Input
{
	/**
	 * The pivoted data from a $_FILES or compatible array.
	 *
	 * @var    array
	 * @since  1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Files instead
	 */
	protected $decodedData = array();

	/**
	 * The class constructor.
	 *
	 * @param   array  $source   The source argument is ignored. $_FILES is always used.
	 * @param   array  $options  An optional array of configuration options:
	 *                           filter : a custom JFilterInput object.
	 *
	 * @since   3.0.0
	 * @deprecated  5.0  Use Joomla\Input\Files instead
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}

		// Set the data source.
		$this->data = & $_FILES;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     The name of the input property (usually the name of the files INPUT tag) to get.
	 * @param   mixed   $default  The default value to return if the named property does not exist.
	 * @param   string  $filter   The filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @see     JFilterInput::clean()
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Files instead
	 */
	public function get($name, $default = null, $filter = 'cmd')
	{
		if (isset($this->data[$name]))
		{
			$results = $this->decodeData(
				array(
					$this->data[$name]['name'],
					$this->data[$name]['type'],
					$this->data[$name]['tmp_name'],
					$this->data[$name]['error'],
					$this->data[$name]['size'],
				)
			);

			// Prevent returning an unsafe file unless speciffically requested
			if ($filter != 'raw')
			{
				$isSafe = InputFilter::isSafeFile($results);

				if (!$isSafe)
				{
					return $default;
				}
			}

			return $results;
		}

		return $default;
	}

	/**
	 * Method to decode a data array.
	 *
	 * @param   array  $data  The data array to decode.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Files instead
	 */
	protected function decodeData(array $data)
	{
		$result = array();

		if (is_array($data[0]))
		{
			foreach ($data[0] as $k => $v)
			{
				$result[$k] = $this->decodeData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
			}

			return $result;
		}

		return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
	}

	/**
	 * Sets a value.
	 *
	 * @param   string  $name   The name of the input property to set.
	 * @param   mixed   $value  The value to assign to the input property.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Files instead
	 */
	public function set($name, $value)
	{
	}
}
src/Input/Cookie.php000064400000010443152177723700010371 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Input;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filter\InputFilter;

/**
 * Joomla! Input Cookie Class
 *
 * @since       1.7.0
 * @deprecated  5.0  Use Joomla\Input\Cookie instead
 */
class Cookie extends Input
{
	/**
	 * Constructor.
	 *
	 * @param   array  $source   Ignored.
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cookie instead
	 */
	public function __construct(array $source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = InputFilter::getInstance();
		}

		// Set the data source.
		$this->data = & $_COOKIE;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Sets a value
	 *
	 * @param   string   $name      Name of the value to set.
	 * @param   mixed    $value     Value to assign to the input.
	 * @param   integer  $expire    The time the cookie expires. This is a Unix timestamp so is in number
	 *                              of seconds since the epoch. In other words, you'll most likely set this
	 *                              with the time() function plus the number of seconds before you want it
	 *                              to expire. Or you might use mktime(). time()+60*60*24*30 will set the
	 *                              cookie to expire in 30 days. If set to 0, or omitted, the cookie will
	 *                              expire at the end of the session (when the browser closes).
	 * @param   string   $path      The path on the server in which the cookie will be available on. If set
	 *                              to '/', the cookie will be available within the entire domain. If set to
	 *                              '/foo/', the cookie will only be available within the /foo/ directory and
	 *                              all sub-directories such as /foo/bar/ of domain. The default value is the
	 *                              current directory that the cookie is being set in.
	 * @param   string   $domain    The domain that the cookie is available to. To make the cookie available
	 *                              on all subdomains of example.com (including example.com itself) then you'd
	 *                              set it to '.example.com'. Although some browsers will accept cookies without
	 *                              the initial ., RFC 2109 requires it to be included. Setting the domain to
	 *                              'www.example.com' or '.www.example.com' will make the cookie only available
	 *                              in the www subdomain.
	 * @param   boolean  $secure    Indicates that the cookie should only be transmitted over a secure HTTPS
	 *                              connection from the client. When set to TRUE, the cookie will only be set
	 *                              if a secure connection exists. On the server-side, it's on the programmer
	 *                              to send this kind of cookie only on secure connection (e.g. with respect
	 *                              to $_SERVER["HTTPS"]).
	 * @param   boolean  $httpOnly  When TRUE the cookie will be made accessible only through the HTTP protocol.
	 *                              This means that the cookie won't be accessible by scripting languages, such
	 *                              as JavaScript. This setting can effectively help to reduce identity theft
	 *                              through XSS attacks (although it is not supported by all browsers).
	 *
	 * @return  void
	 *
	 * @link    http://www.ietf.org/rfc/rfc2109.txt
	 * @see     setcookie()
	 * @since   1.7.0
	 * @deprecated  5.0  Use Joomla\Input\Cookie instead
	 */
	public function set($name, $value, $expire = 0, $path = '', $domain = '', $secure = false, $httpOnly = false)
	{
		if (is_array($value))
		{
			foreach ($value as $key => $val)
			{
				setcookie($name . "[$key]", $val, $expire, $path, $domain, $secure, $httpOnly);
			}
		}
		else
		{
			setcookie($name, $value, $expire, $path, $domain, $secure, $httpOnly);
		}

		$this->data[$name] = $value;
	}
}
src/Pagination/Pagination.php000064400000055752152177723700012257 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Pagination;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;

/**
 * Pagination Class. Provides a common interface for content pagination for the Joomla! CMS.
 *
 * @since  1.5
 */
class Pagination
{
	/**
	 * @var    integer  The record number to start displaying from.
	 * @since  1.5
	 */
	public $limitstart = null;

	/**
	 * @var    integer  Number of rows to display per page.
	 * @since  1.5
	 */
	public $limit = null;

	/**
	 * @var    integer  Total number of rows.
	 * @since  1.5
	 */
	public $total = null;

	/**
	 * @var    integer  Prefix used for request variables.
	 * @since  1.6
	 */
	public $prefix = null;

	/**
	 * @var    integer  Value pagination object begins at
	 * @since  3.0
	 */
	public $pagesStart;

	/**
	 * @var    integer  Value pagination object ends at
	 * @since  3.0
	 */
	public $pagesStop;

	/**
	 * @var    integer  Current page
	 * @since  3.0
	 */
	public $pagesCurrent;

	/**
	 * @var    integer  Total number of pages
	 * @since  3.0
	 */
	public $pagesTotal;

	/**
	 * @var    boolean  The flag indicates whether to add limitstart=0 to URL
	 * @since  3.9.0
	 */
	public $hideEmptyLimitstart = false;

	/**
	 * @var    boolean  View all flag
	 * @since  3.0
	 */
	protected $viewall = false;

	/**
	 * Additional URL parameters to be added to the pagination URLs generated by the class.  These
	 * may be useful for filters and extra values when dealing with lists and GET requests.
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected $additionalUrlParams = array();

	/**
	 * @var    CMSApplication  The application object
	 * @since  3.4
	 */
	protected $app = null;

	/**
	 * Pagination data object
	 *
	 * @var    object
	 * @since  3.4
	 */
	protected $data;

	/**
	 * Constructor.
	 *
	 * @param   integer         $total       The total number of items.
	 * @param   integer         $limitstart  The offset of the item to start at.
	 * @param   integer         $limit       The number of items to display per page.
	 * @param   string          $prefix      The prefix used for request variables.
	 * @param   CMSApplication  $app         The application object
	 *
	 * @since   1.5
	 */
	public function __construct($total, $limitstart, $limit, $prefix = '', CMSApplication $app = null)
	{
		// Value/type checking.
		$this->total = (int) $total;
		$this->limitstart = (int) max($limitstart, 0);
		$this->limit = (int) max($limit, 0);
		$this->prefix = $prefix;
		$this->app = $app ?: \JFactory::getApplication();

		if ($this->limit > $this->total)
		{
			$this->limitstart = 0;
		}

		if (!$this->limit)
		{
			$this->limit = $total;
			$this->limitstart = 0;
		}

		/*
		 * If limitstart is greater than total (i.e. we are asked to display records that don't exist)
		 * then set limitstart to display the last natural page of results
		 */
		if ($this->limitstart > $this->total - $this->limit)
		{
			$this->limitstart = max(0, (int) (ceil($this->total / $this->limit) - 1) * $this->limit);
		}

		// Set the total pages and current page values.
		if ($this->limit > 0)
		{
			$this->pagesTotal = (int) ceil($this->total / $this->limit);
			$this->pagesCurrent = (int) ceil(($this->limitstart + 1) / $this->limit);
		}

		// Set the pagination iteration loop values.
		$displayedPages = 10;
		$this->pagesStart = $this->pagesCurrent - ($displayedPages / 2);

		if ($this->pagesStart < 1)
		{
			$this->pagesStart = 1;
		}

		if ($this->pagesStart + $displayedPages > $this->pagesTotal)
		{
			$this->pagesStop = $this->pagesTotal;

			if ($this->pagesTotal < $displayedPages)
			{
				$this->pagesStart = 1;
			}
			else
			{
				$this->pagesStart = $this->pagesTotal - $displayedPages + 1;
			}
		}
		else
		{
			$this->pagesStop = $this->pagesStart + $displayedPages - 1;
		}

		// If we are viewing all records set the view all flag to true.
		if ($limit === 0)
		{
			$this->viewall = true;
		}
	}

	/**
	 * Method to set an additional URL parameter to be added to all pagination class generated
	 * links.
	 *
	 * @param   string  $key    The name of the URL parameter for which to set a value.
	 * @param   mixed   $value  The value to set for the URL parameter.
	 *
	 * @return  mixed  The old value for the parameter.
	 *
	 * @since   1.6
	 */
	public function setAdditionalUrlParam($key, $value)
	{
		// Get the old value to return and set the new one for the URL parameter.
		$result = isset($this->additionalUrlParams[$key]) ? $this->additionalUrlParams[$key] : null;

		// If the passed parameter value is null unset the parameter, otherwise set it to the given value.
		if ($value === null)
		{
			unset($this->additionalUrlParams[$key]);
		}
		else
		{
			$this->additionalUrlParams[$key] = $value;
		}

		return $result;
	}

	/**
	 * Method to get an additional URL parameter (if it exists) to be added to
	 * all pagination class generated links.
	 *
	 * @param   string  $key  The name of the URL parameter for which to get the value.
	 *
	 * @return  mixed  The value if it exists or null if it does not.
	 *
	 * @since   1.6
	 */
	public function getAdditionalUrlParam($key)
	{
		$result = isset($this->additionalUrlParams[$key]) ? $this->additionalUrlParams[$key] : null;

		return $result;
	}

	/**
	 * Return the rationalised offset for a row with a given index.
	 *
	 * @param   integer  $index  The row index
	 *
	 * @return  integer  Rationalised offset for a row with a given index.
	 *
	 * @since   1.5
	 */
	public function getRowOffset($index)
	{
		return $index + 1 + $this->limitstart;
	}

	/**
	 * Return the pagination data object, only creating it if it doesn't already exist.
	 *
	 * @return  \stdClass  Pagination data object.
	 *
	 * @since   1.5
	 */
	public function getData()
	{
		if (!$this->data)
		{
			$this->data = $this->_buildDataObject();
		}

		return $this->data;
	}

	/**
	 * Create and return the pagination pages counter string, ie. Page 2 of 4.
	 *
	 * @return  string   Pagination pages counter string.
	 *
	 * @since   1.5
	 */
	public function getPagesCounter()
	{
		$html = null;

		if ($this->pagesTotal > 1)
		{
			$html .= \JText::sprintf('JLIB_HTML_PAGE_CURRENT_OF_TOTAL', $this->pagesCurrent, $this->pagesTotal);
		}

		return $html;
	}

	/**
	 * Create and return the pagination result set counter string, e.g. Results 1-10 of 42
	 *
	 * @return  string   Pagination result set counter string.
	 *
	 * @since   1.5
	 */
	public function getResultsCounter()
	{
		$html = null;
		$fromResult = $this->limitstart + 1;

		// If the limit is reached before the end of the list.
		if ($this->limitstart + $this->limit < $this->total)
		{
			$toResult = $this->limitstart + $this->limit;
		}
		else
		{
			$toResult = $this->total;
		}

		// If there are results found.
		if ($this->total > 0)
		{
			$msg = \JText::sprintf('JLIB_HTML_RESULTS_OF', $fromResult, $toResult, $this->total);
			$html .= "\n" . $msg;
		}
		else
		{
			$html .= "\n" . \JText::_('JLIB_HTML_NO_RECORDS_FOUND');
		}

		return $html;
	}

	/**
	 * Create and return the pagination page list string, ie. Previous, Next, 1 2 3 ... x.
	 *
	 * @return  string  Pagination page list string.
	 *
	 * @since   1.5
	 */
	public function getPagesLinks()
	{
		// Build the page navigation list.
		$data = $this->_buildDataObject();

		$list           = array();
		$list['prefix'] = $this->prefix;

		$itemOverride = false;
		$listOverride = false;

		$chromePath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/pagination.php';

		if (file_exists($chromePath))
		{
			include_once $chromePath;

			/*
			 * @deprecated 4.0 Item rendering should use a layout
			 */
			if (function_exists('pagination_item_active') && function_exists('pagination_item_inactive'))
			{
				\JLog::add(
					'pagination_item_active and pagination_item_inactive are deprecated. Use the layout joomla.pagination.link instead.',
					\JLog::WARNING,
					'deprecated'
				);

				$itemOverride = true;
			}

			/*
			 * @deprecated 4.0 The list rendering is now a layout.
			 * @see Pagination::_list_render()
			 */
			if (function_exists('pagination_list_render'))
			{
				\JLog::add('pagination_list_render is deprecated. Use the layout joomla.pagination.list instead.', \JLog::WARNING, 'deprecated');
				$listOverride = true;
			}
		}

		// Build the select list
		if ($data->all->base !== null)
		{
			$list['all']['active'] = true;
			$list['all']['data']   = $itemOverride ? pagination_item_active($data->all) : $this->_item_active($data->all);
		}
		else
		{
			$list['all']['active'] = false;
			$list['all']['data']   = $itemOverride ? pagination_item_inactive($data->all) : $this->_item_inactive($data->all);
		}

		if ($data->start->base !== null)
		{
			$list['start']['active'] = true;
			$list['start']['data']   = $itemOverride ? pagination_item_active($data->start) : $this->_item_active($data->start);
		}
		else
		{
			$list['start']['active'] = false;
			$list['start']['data']   = $itemOverride ? pagination_item_inactive($data->start) : $this->_item_inactive($data->start);
		}

		if ($data->previous->base !== null)
		{
			$list['previous']['active'] = true;
			$list['previous']['data']   = $itemOverride ? pagination_item_active($data->previous) : $this->_item_active($data->previous);
		}
		else
		{
			$list['previous']['active'] = false;
			$list['previous']['data']   = $itemOverride ? pagination_item_inactive($data->previous) : $this->_item_inactive($data->previous);
		}

		// Make sure it exists
		$list['pages'] = array();

		foreach ($data->pages as $i => $page)
		{
			if ($page->base !== null)
			{
				$list['pages'][$i]['active'] = true;
				$list['pages'][$i]['data']   = $itemOverride ? pagination_item_active($page) : $this->_item_active($page);
			}
			else
			{
				$list['pages'][$i]['active'] = false;
				$list['pages'][$i]['data']   = $itemOverride ? pagination_item_inactive($page) : $this->_item_inactive($page);
			}
		}

		if ($data->next->base !== null)
		{
			$list['next']['active'] = true;
			$list['next']['data']   = $itemOverride ? pagination_item_active($data->next) : $this->_item_active($data->next);
		}
		else
		{
			$list['next']['active'] = false;
			$list['next']['data']   = $itemOverride ? pagination_item_inactive($data->next) : $this->_item_inactive($data->next);
		}

		if ($data->end->base !== null)
		{
			$list['end']['active'] = true;
			$list['end']['data']   = $itemOverride ? pagination_item_active($data->end) : $this->_item_active($data->end);
		}
		else
		{
			$list['end']['active'] = false;
			$list['end']['data']   = $itemOverride ? pagination_item_inactive($data->end) : $this->_item_inactive($data->end);
		}

		if ($this->total > $this->limit)
		{
			return $listOverride ? pagination_list_render($list) : $this->_list_render($list);
		}
		else
		{
			return '';
		}
	}

	/**
	 * Get the pagination links
	 *
	 * @param   string  $layoutId  Layout to render the links
	 * @param   array   $options   Optional array with settings for the layout
	 *
	 * @return  string  Pagination links.
	 *
	 * @since   3.3
	 */
	public function getPaginationLinks($layoutId = 'joomla.pagination.links', $options = array())
	{
		// Allow to receive a null layout
		$layoutId = $layoutId === null ? 'joomla.pagination.links' : $layoutId;

		$list = array(
			'prefix'       => $this->prefix,
			'limit'        => $this->limit,
			'limitstart'   => $this->limitstart,
			'total'        => $this->total,
			'limitfield'   => $this->getLimitBox(),
			'pagescounter' => $this->getPagesCounter(),
			'pages'        => $this->getPaginationPages(),
			'pagesTotal'   => $this->pagesTotal,
		);

		return \JLayoutHelper::render($layoutId, array('list' => $list, 'options' => $options));
	}

	/**
	 * Create and return the pagination pages list, ie. Previous, Next, 1 2 3 ... x.
	 *
	 * @return  array  Pagination pages list.
	 *
	 * @since   3.3
	 */
	public function getPaginationPages()
	{
		$list = array();

		if ($this->total > $this->limit)
		{
			// Build the page navigation list.
			$data = $this->_buildDataObject();

			// All
			$list['all']['active'] = $data->all->base !== null;
			$list['all']['data']   = $data->all;

			// Start
			$list['start']['active'] = $data->start->base !== null;
			$list['start']['data']   = $data->start;

			// Previous link
			$list['previous']['active'] = $data->previous->base !== null;
			$list['previous']['data']   = $data->previous;

			// Make sure it exists
			$list['pages'] = array();

			foreach ($data->pages as $i => $page)
			{
				$list['pages'][$i]['active'] = $page->base !== null;
				$list['pages'][$i]['data']   = $page;
			}

			$list['next']['active'] = $data->next->base !== null;
			$list['next']['data']   = $data->next;

			$list['end']['active'] = $data->end->base !== null;
			$list['end']['data']   = $data->end;
		}

		return $list;
	}

	/**
	 * Return the pagination footer.
	 *
	 * @return  string  Pagination footer.
	 *
	 * @since   1.5
	 */
	public function getListFooter()
	{
		// Keep B/C for overrides done with chromes
		$chromePath = JPATH_THEMES . '/' . $this->app->getTemplate() . '/html/pagination.php';

		if (file_exists($chromePath))
		{
			include_once $chromePath;

			if (function_exists('pagination_list_footer'))
			{
				\JLog::add('pagination_list_footer is deprecated. Use the layout joomla.pagination.links instead.', \JLog::WARNING, 'deprecated');

				$list = array(
					'prefix'       => $this->prefix,
					'limit'        => $this->limit,
					'limitstart'   => $this->limitstart,
					'total'        => $this->total,
					'limitfield'   => $this->getLimitBox(),
					'pagescounter' => $this->getPagesCounter(),
					'pageslinks'   => $this->getPagesLinks(),
				);

				return pagination_list_footer($list);
			}
		}

		return $this->getPaginationLinks();
	}

	/**
	 * Creates a dropdown box for selecting how many records to show per page.
	 *
	 * @return  string  The HTML for the limit # input box.
	 *
	 * @since   1.5
	 */
	public function getLimitBox()
	{
		$limits = array();

		// Make the option list.
		for ($i = 5; $i <= 30; $i += 5)
		{
			$limits[] = \JHtml::_('select.option', "$i");
		}

		$limits[] = \JHtml::_('select.option', '50', \JText::_('J50'));
		$limits[] = \JHtml::_('select.option', '100', \JText::_('J100'));
		$limits[] = \JHtml::_('select.option', '0', \JText::_('JALL'));

		$selected = $this->viewall ? 0 : $this->limit;

		// Build the select list.
		if ($this->app->isClient('administrator'))
		{
			$html = \JHtml::_(
				'select.genericlist',
				$limits,
				$this->prefix . 'limit',
				'class="inputbox input-mini" size="1" onchange="Joomla.submitform();"',
				'value',
				'text',
				$selected
			);
		}
		else
		{
			$html = \JHtml::_(
				'select.genericlist',
				$limits,
				$this->prefix . 'limit',
				'class="inputbox input-mini" size="1" onchange="this.form.submit()"',
				'value',
				'text',
				$selected
			);
		}

		return $html;
	}

	/**
	 * Return the icon to move an item UP.
	 *
	 * @param   integer  $i          The row index.
	 * @param   boolean  $condition  True to show the icon.
	 * @param   string   $task       The task to fire.
	 * @param   string   $alt        The image alternative text string.
	 * @param   boolean  $enabled    An optional setting for access control on the action.
	 * @param   string   $checkbox   An optional prefix for checkboxes.
	 *
	 * @return  string   Either the icon to move an item up or a space.
	 *
	 * @since   1.5
	 */
	public function orderUpIcon($i, $condition = true, $task = 'orderup', $alt = 'JLIB_HTML_MOVE_UP', $enabled = true, $checkbox = 'cb')
	{
		if (($i > 0 || ($i + $this->limitstart > 0)) && $condition)
		{
			return \JHtml::_('jgrid.orderUp', $i, $task, '', $alt, $enabled, $checkbox);
		}
		else
		{
			return '&#160;';
		}
	}

	/**
	 * Return the icon to move an item DOWN.
	 *
	 * @param   integer  $i          The row index.
	 * @param   integer  $n          The number of items in the list.
	 * @param   boolean  $condition  True to show the icon.
	 * @param   string   $task       The task to fire.
	 * @param   string   $alt        The image alternative text string.
	 * @param   boolean  $enabled    An optional setting for access control on the action.
	 * @param   string   $checkbox   An optional prefix for checkboxes.
	 *
	 * @return  string   Either the icon to move an item down or a space.
	 *
	 * @since   1.5
	 */
	public function orderDownIcon($i, $n, $condition = true, $task = 'orderdown', $alt = 'JLIB_HTML_MOVE_DOWN', $enabled = true, $checkbox = 'cb')
	{
		if (($i < $n - 1 || $i + $this->limitstart < $this->total - 1) && $condition)
		{
			return \JHtml::_('jgrid.orderDown', $i, $task, '', $alt, $enabled, $checkbox);
		}
		else
		{
			return '&#160;';
		}
	}

	/**
	 * Create the HTML for a list footer
	 *
	 * @param   array  $list  Pagination list data structure.
	 *
	 * @return  string  HTML for a list footer
	 *
	 * @since   1.5
	 */
	protected function _list_footer($list)
	{
		$html = "<div class=\"list-footer\">\n";

		$html .= "\n<div class=\"limit\">" . \JText::_('JGLOBAL_DISPLAY_NUM') . $list['limitfield'] . "</div>";
		$html .= $list['pageslinks'];
		$html .= "\n<div class=\"counter\">" . $list['pagescounter'] . "</div>";

		$html .= "\n<input type=\"hidden\" name=\"" . $list['prefix'] . "limitstart\" value=\"" . $list['limitstart'] . "\" />";
		$html .= "\n</div>";

		return $html;
	}

	/**
	 * Create the html for a list footer
	 *
	 * @param   array  $list  Pagination list data structure.
	 *
	 * @return  string  HTML for a list start, previous, next,end
	 *
	 * @since   1.5
	 */
	protected function _list_render($list)
	{
		return \JLayoutHelper::render('joomla.pagination.list', array('list' => $list));
	}

	/**
	 * Method to create an active pagination link to the item
	 *
	 * @param   PaginationObject  $item  The object with which to make an active link.
	 *
	 * @return  string  HTML link
	 *
	 * @since   1.5
	 * @note    As of 4.0 this method will proxy to `\JLayoutHelper::render('joomla.pagination.link', ['data' => $item, 'active' => true])`
	 */
	protected function _item_active(PaginationObject $item)
	{
		$title = '';
		$class = '';

		if (!is_numeric($item->text))
		{
			\JHtml::_('bootstrap.tooltip');
			$title = ' title="' . $item->text . '"';
			$class = 'hasTooltip ';
		}

		if ($this->app->isClient('administrator'))
		{
			return '<a' . $title . ' href="#" onclick="document.adminForm.' . $this->prefix
			. 'limitstart.value=' . ($item->base > 0 ? $item->base : '0') . '; Joomla.submitform();return false;">' . $item->text . '</a>';
		}
		else
		{
			return '<a' . $title . ' href="' . $item->link . '" class="' . $class . 'pagenav">' . $item->text . '</a>';
		}
	}

	/**
	 * Method to create an inactive pagination string
	 *
	 * @param   PaginationObject  $item  The item to be processed
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @note    As of 4.0 this method will proxy to `\JLayoutHelper::render('joomla.pagination.link', ['data' => $item, 'active' => false])`
	 */
	protected function _item_inactive(PaginationObject $item)
	{
		if ($this->app->isClient('administrator'))
		{
			return '<span>' . $item->text . '</span>';
		}
		else
		{
			return '<span class="pagenav">' . $item->text . '</span>';
		}
	}

	/**
	 * Create and return the pagination data object.
	 *
	 * @return  \stdClass  Pagination data object.
	 *
	 * @since   1.5
	 */
	protected function _buildDataObject()
	{
		$data = new \stdClass;

		// Build the additional URL parameters string.
		$params = '';

		if (!empty($this->additionalUrlParams))
		{
			foreach ($this->additionalUrlParams as $key => $value)
			{
				$params .= '&' . $key . '=' . $value;
			}
		}

		$data->all = new PaginationObject(\JText::_('JLIB_HTML_VIEW_ALL'), $this->prefix);

		if (!$this->viewall)
		{
			$data->all->base = '0';
			$data->all->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=');
		}

		// Set the start and previous data objects.
		$data->start    = new PaginationObject(\JText::_('JLIB_HTML_START'), $this->prefix);
		$data->previous = new PaginationObject(\JText::_('JPREV'), $this->prefix);

		if ($this->pagesCurrent > 1)
		{
			$page = ($this->pagesCurrent - 2) * $this->limit;

			if ($this->hideEmptyLimitstart)
			{
				$data->start->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=');
			}
			else
			{
				$data->start->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=0');
			}

			$data->start->base    = '0';
			$data->previous->base = $page;

			if ($page === 0 && $this->hideEmptyLimitstart)
			{
				$data->previous->link = $data->start->link;
			}
			else
			{
				$data->previous->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $page);
			}
		}

		// Set the next and end data objects.
		$data->next = new PaginationObject(\JText::_('JNEXT'), $this->prefix);
		$data->end  = new PaginationObject(\JText::_('JLIB_HTML_END'), $this->prefix);

		if ($this->pagesCurrent < $this->pagesTotal)
		{
			$next = $this->pagesCurrent * $this->limit;
			$end  = ($this->pagesTotal - 1) * $this->limit;

			$data->next->base = $next;
			$data->next->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $next);
			$data->end->base  = $end;
			$data->end->link  = \JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $end);
		}

		$data->pages = array();
		$stop        = $this->pagesStop;

		for ($i = $this->pagesStart; $i <= $stop; $i++)
		{
			$offset = ($i - 1) * $this->limit;

			$data->pages[$i] = new PaginationObject($i, $this->prefix);

			if ($i != $this->pagesCurrent || $this->viewall)
			{
				$data->pages[$i]->base = $offset;

				if ($offset === 0 && $this->hideEmptyLimitstart)
				{
					$data->pages[$i]->link = $data->start->link;
				}
				else
				{
					$data->pages[$i]->link = \JRoute::_($params . '&' . $this->prefix . 'limitstart=' . $offset);
				}
			}
			else
			{
				$data->pages[$i]->active = true;
			}
		}

		return $data;
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 * @deprecated  4.0  Access the properties directly.
	 */
	public function set($property, $value = null)
	{
		\JLog::add('Pagination::set() is deprecated. Access the properties directly.', \JLog::WARNING, 'deprecated');

		if (strpos($property, '.'))
		{
			$prop     = explode('.', $property);
			$prop[1]  = ucfirst($prop[1]);
			$property = implode($prop);
		}

		$this->$property = $value;
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed    The value of the property.
	 *
	 * @since   3.0
	 * @deprecated  4.0  Access the properties directly.
	 */
	public function get($property, $default = null)
	{
		\JLog::add('Pagination::get() is deprecated. Access the properties directly.', \JLog::WARNING, 'deprecated');

		if (strpos($property, '.'))
		{
			$prop     = explode('.', $property);
			$prop[1]  = ucfirst($prop[1]);
			$property = implode($prop);
		}

		if (isset($this->$property))
		{
			return $this->$property;
		}

		return $default;
	}
}
src/Pagination/PaginationObject.php000064400000002710152177723700013370 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Pagination;

defined('JPATH_PLATFORM') or die;

/**
 * Pagination object representing a particular item in the pagination lists.
 *
 * @since  1.5
 */
class PaginationObject
{
	/**
	 * @var    string  The link text.
	 * @since  1.5
	 */
	public $text;

	/**
	 * @var    integer  The number of rows as a base offset.
	 * @since  1.5
	 */
	public $base;

	/**
	 * @var    string  The link URL.
	 * @since  1.5
	 */
	public $link;

	/**
	 * @var    integer  The prefix used for request variables.
	 * @since  1.6
	 */
	public $prefix;

	/**
	 * @var    boolean  Flag whether the object is the 'active' page
	 * @since  3.0
	 */
	public $active;

	/**
	 * Class constructor.
	 *
	 * @param   string   $text    The link text.
	 * @param   string   $prefix  The prefix used for request variables.
	 * @param   integer  $base    The number of rows as a base offset.
	 * @param   string   $link    The link URL.
	 * @param   boolean  $active  Flag whether the object is the 'active' page
	 *
	 * @since   1.5
	 */
	public function __construct($text, $prefix = '', $base = null, $link = null, $active = false)
	{
		$this->text   = $text;
		$this->prefix = $prefix;
		$this->base   = $base;
		$this->link   = $link;
		$this->active = $active;
	}
}
src/Association/AssociationExtensionInterface.php000064400000001141152177723700016322 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Association;

defined('JPATH_PLATFORM') or die;

/**
 * Association Extension Interface for the helper classes
 *
 * @since  3.7.0
 */
interface AssociationExtensionInterface
{
	/**
	 * Checks if the extension supports associations
	 *
	 * @return  boolean  Supports the extension associations
	 *
	 * @since   3.7.0
	 */
	public function hasAssociationsSupport();
}
src/Association/AssociationExtensionHelper.php000064400000013126152177723700015647 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Association;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Association Extension Helper
 *
 * @since  3.7.0
 */
abstract class AssociationExtensionHelper  implements AssociationExtensionInterface
{
	/**
	 * The extension name
	 *
	 * @var     array  $extension
	 *
	 * @since   3.7.0
	 */
	protected $extension = 'com_??';

	/**
	 * Array of item types
	 *
	 * @var     array  $itemTypes
	 *
	 * @since   3.7.0
	 */
	protected $itemTypes = array();

	/**
	 * Has the extension association support
	 *
	 * @var     boolean  $associationsSupport
	 *
	 * @since   3.7.0
	 */
	protected $associationsSupport = false;

	/**
	 * Checks if the extension supports associations
	 *
	 * @return  boolean  Supports the extension associations
	 *
	 * @since   3.7.0
	 */
	public function hasAssociationsSupport()
	{
		return $this->associationsSupport;
	}

	/**
	 * Get the item types
	 *
	 * @return  array  Array of item types
	 *
	 * @since  3.7.0
	 */
	public function getItemTypes()
	{
		return $this->itemTypes;
	}

	/**
	 * Get the associated items for an item
	 *
	 * @param   string  $typeName  The item type
	 * @param   int     $itemId    The id of item for which we need the associated items
	 *
	 * @return   array
	 *
	 * @since    3.7.0
	 */
	public function getAssociationList($typeName, $itemId)
	{
		$items = array();

		$associations = $this->getAssociations($typeName, $itemId);

		foreach ($associations as $key => $association)
		{
			$items[$key] = ArrayHelper::fromObject($this->getItem($typeName, (int) $association->id), false);
		}

		return $items;
	}

	/**
	 * Get information about the type
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of item types
	 *
	 * @since   3.7.0
	 */
	public function getType($typeName = '')
	{
		$fields  = $this->getFieldsTemplate();
		$tables  = array();
		$joins   = array();
		$support = $this->getSupportTemplate();
		$title   = '';

		return array(
			'fields'  => $fields,
			'support' => $support,
			'tables'  => $tables,
			'joins'   => $joins,
			'title'   => $title
		);
	}

	/**
	 * Get information about the fields the type provides
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of support information
	 *
	 * @since   3.7.0
	 */
	public function getTypeFields($typeName)
	{
		return $this->getTypeInformation($typeName, 'fields');
	}

	/**
	 * Get information about the fields the type provides
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of support information
	 *
	 * @since   3.7.0
	 */
	public function getTypeSupport($typeName)
	{
		return $this->getTypeInformation($typeName, 'support');
	}

	/**
	 * Get information about the tables the type use
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of support information
	 *
	 * @since   3.7.0
	 */
	public function getTypeTables($typeName)
	{
		return $this->getTypeInformation($typeName, 'tables');
	}

	/**
	 * Get information about the table joins for the type
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of support information
	 *
	 * @since   3.7.0
	 */
	public function getTypeJoins($typeName)
	{
		return $this->getTypeInformation($typeName, 'joins');
	}

	/**
	 * Get the type title
	 *
	 * @param   string  $typeName  The item type
	 *
	 * @return  array  Array of support information
	 *
	 * @since   3.7.0
	 */
	public function getTypeTitle($typeName)
	{
		$type = $this->getType($typeName);

		if (!array_key_exists('title', $type))
		{
			return '';
		}

		return $type['title'];
	}

	/**
	 * Get information about the type
	 *
	 * @param   string  $typeName  The item type
	 * @param   string  $part      part of the information
	 *
	 * @return  array Array of support information
	 *
	 * @since   3.7.0
	 */
	private function getTypeInformation($typeName, $part = 'support')
	{
		$type = $this->getType($typeName);

		if (!array_key_exists($part, $type))
		{
			return array();
		}

		return $type[$part];
	}

	/**
	 * Get a table field name for a type
	 *
	 * @param   string  $typeName   The item type
	 * @param   string  $fieldName  The item type
	 *
	 * @return  string
	 *
	 * @since   3.7.0
	 */
	public function getTypeFieldName($typeName, $fieldName)
	{
		$fields = $this->getTypeFields($typeName);

		if (!array_key_exists($fieldName, $fields))
		{
			return '';
		}

		$tmp = $fields[$fieldName];
		$pos = strpos($tmp, '.');

		if ($pos === false)
		{
			return $tmp;
		}

		return substr($tmp, $pos + 1);
	}

	/**
	 * Get default values for support array
	 *
	 * @return  array
	 *
	 * @since   3.7.0
	 */
	protected function getSupportTemplate()
	{
		return array(
			'state'    => false,
			'acl'      => false,
			'checkout' => false
		);
	}

	/**
	 * Get default values for fields array
	 *
	 * @return  array
	 *
	 * @since   3.7.0
	 */
	protected function getFieldsTemplate()
	{
		return array(
			'id'                  => 'a.id',
			'title'               => 'a.title',
			'alias'               => 'a.alias',
			'ordering'            => 'a.ordering',
			'menutype'            => '',
			'level'               => '',
			'catid'               => 'a.catid',
			'language'            => 'a.language',
			'access'              => 'a.access',
			'state'               => 'a.state',
			'created_user_id'     => 'a.created_by',
			'checked_out'         => 'a.checked_out',
			'checked_out_time'    => 'a.checked_out_time'
		);
	}
}
src/Image/ImageFilter.php000064400000001777152177723700011305 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Image;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;

/**
 * Class to manipulate an image.
 *
 * @since       1.7.3
 * @deprecated  5.0  Use Joomla\Image\ImageFilter instead.
 */
abstract class ImageFilter extends \Joomla\Image\ImageFilter
{
	/**
	 * Class constructor.
	 *
	 * @param   resource  $handle  The image resource on which to apply the filter.
	 *
	 * @since   1.7.3
	 * @deprecated  5.0  Use Joomla\Image\ImageFilter instead.
	 */
	public function __construct($handle)
	{
		Log::add('Joomla\CMS\Image\ImageFilter is deprecated, use Joomla\Image\ImageFilter instead.', Log::WARNING, 'deprecated');

		// Inject the PSR-3 compatible logger in for forward compatibility
		$this->setLogger(Log::createDelegatedLogger());

		parent::__construct($handle);
	}
}
src/Image/Image.php000064400000004316152177723700010127 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Image;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;

/**
 * Class to manipulate an image.
 *
 * @since       1.7.3
 * @deprecated  5.0 Use the class \Joomla\Image\Image instead
 */
class Image extends \Joomla\Image\Image
{
	/**
	 * Class constructor.
	 *
	 * @param   mixed  $source  Either a file path for a source image or a GD resource handler for an image.
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	public function __construct($source = null)
	{
		Log::add('Joomla\CMS\Image\Image is deprecated, use Joomla\Image\Image instead.', Log::WARNING, 'deprecated');

		// Inject the PSR-3 compatible logger in for forward compatibility
		$this->setLogger(Log::createDelegatedLogger());

		parent::__construct($source);
	}

	/**
	 * Method to get an image filter instance of a specified type.
	 *
	 * @param   string  $type  The image filter type to get.
	 *
	 * @return  ImageFilter
	 *
	 * @since   1.7.3
	 * @throws  \RuntimeException
	 */
	protected function getFilterInstance($type)
	{
		try
		{
			return parent::getFilterInstance($type);
		}
		catch (\RuntimeException $e)
		{
			// Ignore, filter is probably not namespaced
		}

		// Sanitize the filter type.
		$type = strtolower(preg_replace('#[^A-Z0-9_]#i', '', $type));

		// Verify that the filter type exists.
		$className = 'JImageFilter' . ucfirst($type);

		if (!class_exists($className))
		{
			Log::add('The ' . ucfirst($type) . ' image filter is not available.', Log::ERROR);
			throw new \RuntimeException('The ' . ucfirst($type) . ' image filter is not available.');
		}

		// Instantiate the filter object.
		$instance = new $className($this->getHandle());

		// Verify that the filter type is valid.
		if (!($instance instanceof ImageFilter))
		{
			// @codeCoverageIgnoreStart
			Log::add('The ' . ucfirst($type) . ' image filter is not valid.', Log::ERROR);
			throw new \RuntimeException('The ' . ucfirst($type) . ' image filter is not valid.');

			// @codeCoverageIgnoreEnd
		}

		return $instance;
	}
}
src/Uri/Uri.php000064400000022165152177723700007363 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Uri;

defined('JPATH_PLATFORM') or die;

/**
 * JUri Class
 *
 * This class serves two purposes. First it parses a URI and provides a common interface
 * for the Joomla Platform to access and manipulate a URI.  Second it obtains the URI of
 * the current executing script from the server regardless of server.
 *
 * @since  1.7.0
 */
class Uri extends \Joomla\Uri\Uri
{
	/**
	 * @var    Uri[]  An array of JUri instances.
	 * @since  1.7.0
	 */
	protected static $instances = array();

	/**
	 * @var    array  The current calculated base url segments.
	 * @since  1.7.0
	 */
	protected static $base = array();

	/**
	 * @var    array  The current calculated root url segments.
	 * @since  1.7.0
	 */
	protected static $root = array();

	/**
	 * @var    string  The current url.
	 * @since  1.7.0
	 */
	protected static $current;

	/**
	 * Returns the global JUri object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $uri  The URI to parse.  [optional: if null uses script URI]
	 *
	 * @return  Uri  The URI object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($uri = 'SERVER')
	{
		if (empty(static::$instances[$uri]))
		{
			// Are we obtaining the URI from the server?
			if ($uri == 'SERVER')
			{
				// Determine if the request was over SSL (HTTPS).
				if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) != 'off'))
				{
					$https = 's://';
				}
				elseif ((isset($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
					!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) &&
					(strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) !== 'http')))
				{
					$https = 's://';
				}
				else
				{
					$https = '://';
				}

				/*
				 * Since we are assigning the URI from the server variables, we first need
				 * to determine if we are running on apache or IIS.  If PHP_SELF and REQUEST_URI
				 * are present, we will assume we are running on apache.
				 */

				if (!empty($_SERVER['PHP_SELF']) && !empty($_SERVER['REQUEST_URI']))
				{
					// To build the entire URI we need to prepend the protocol, and the http host
					// to the URI string.
					$theURI = 'http' . $https . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
				}
				else
				{
					/*
					 * Since we do not have REQUEST_URI to work with, we will assume we are
					 * running on IIS and will therefore need to work some magic with the SCRIPT_NAME and
					 * QUERY_STRING environment variables.
					 *
					 * IIS uses the SCRIPT_NAME variable instead of a REQUEST_URI variable... thanks, MS
					 */
					$theURI = 'http' . $https . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'];

					// If the query string exists append it to the URI string
					if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING']))
					{
						$theURI .= '?' . $_SERVER['QUERY_STRING'];
					}
				}

				// Extra cleanup to remove invalid chars in the URL to prevent injections through the Host header
				$theURI = str_replace(array("'", '"', '<', '>'), array('%27', '%22', '%3C', '%3E'), $theURI);
			}
			else
			{
				// We were given a URI
				$theURI = $uri;
			}

			static::$instances[$uri] = new static($theURI);
		}

		return static::$instances[$uri];
	}

	/**
	 * Returns the base URI for the request.
	 *
	 * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
	 *
	 * @return  string  The base URI string
	 *
	 * @since   1.7.0
	 */
	public static function base($pathonly = false)
	{
		// Get the base request path.
		if (empty(static::$base))
		{
			$config = \JFactory::getConfig();
			$uri = static::getInstance();
			$live_site = ($uri->isSsl()) ? str_replace('http://', 'https://', $config->get('live_site')) : $config->get('live_site');

			if (trim($live_site) != '')
			{
				$uri = static::getInstance($live_site);
				static::$base['prefix'] = $uri->toString(array('scheme', 'host', 'port'));
				static::$base['path'] = rtrim($uri->toString(array('path')), '/\\');

				if (defined('JPATH_BASE') && defined('JPATH_ADMINISTRATOR'))
				{
					if (JPATH_BASE == JPATH_ADMINISTRATOR)
					{
						static::$base['path'] .= '/administrator';
					}
				}
			}
			else
			{
				static::$base['prefix'] = $uri->toString(array('scheme', 'host', 'port'));

				if (strpos(php_sapi_name(), 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($_SERVER['REQUEST_URI']))
				{
					// PHP-CGI on Apache with "cgi.fix_pathinfo = 0"

					// We shouldn't have user-supplied PATH_INFO in PHP_SELF in this case
					// because PHP will not work with PATH_INFO at all.
					$script_name = $_SERVER['PHP_SELF'];
				}
				else
				{
					// Others
					$script_name = $_SERVER['SCRIPT_NAME'];
				}

				// Extra cleanup to remove invalid chars in the URL to prevent injections through broken server implementation
				$script_name = str_replace(array("'", '"', '<', '>'), array('%27', '%22', '%3C', '%3E'), $script_name);

				static::$base['path'] = rtrim(dirname($script_name), '/\\');
			}
		}

		return $pathonly === false ? static::$base['prefix'] . static::$base['path'] . '/' : static::$base['path'];
	}

	/**
	 * Returns the root URI for the request.
	 *
	 * @param   boolean  $pathonly  If false, prepend the scheme, host and port information. Default is false.
	 * @param   string   $path      The path
	 *
	 * @return  string  The root URI string.
	 *
	 * @since   1.7.0
	 */
	public static function root($pathonly = false, $path = null)
	{
		// Get the scheme
		if (empty(static::$root))
		{
			$uri = static::getInstance(static::base());
			static::$root['prefix'] = $uri->toString(array('scheme', 'host', 'port'));
			static::$root['path'] = rtrim($uri->toString(array('path')), '/\\');
		}

		// Get the scheme
		if (isset($path))
		{
			static::$root['path'] = $path;
		}

		return $pathonly === false ? static::$root['prefix'] . static::$root['path'] . '/' : static::$root['path'];
	}

	/**
	 * Returns the URL for the request, minus the query.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public static function current()
	{
		// Get the current URL.
		if (empty(static::$current))
		{
			$uri = static::getInstance();
			static::$current = $uri->toString(array('scheme', 'host', 'port', 'path'));
		}

		return static::$current;
	}

	/**
	 * Method to reset class static members for testing and other various issues.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function reset()
	{
		static::$instances = array();
		static::$base = array();
		static::$root = array();
		static::$current = '';
	}

	/**
	 * Set the URI path string. Note we keep this method here so it uses the old _cleanPath function
	 *
	 * @param   string  $path  The URI path string.
	 *
	 * @return  void
	 *
	 * @since       1.7.0
	 * @deprecated  4.0  Use {@link \Joomla\Uri\Uri::setPath()}
	 * @note        Present to proxy calls to the deprecated {@link JUri::_cleanPath()} method.
	 */
	public function setPath($path)
	{
		$this->path = $this->_cleanPath($path);
	}

	/**
	 * Checks if the supplied URL is internal
	 *
	 * @param   string  $url  The URL to check.
	 *
	 * @return  boolean  True if Internal.
	 *
	 * @since   1.7.0
	 */
	public static function isInternal($url)
	{
		$uri = static::getInstance($url);
		$base = $uri->toString(array('scheme', 'host', 'port', 'path'));
		$host = $uri->toString(array('scheme', 'host', 'port'));

		// @see JUriTest
		if (empty($host) && strpos($uri->path, 'index.php') === 0
			|| !empty($host) && preg_match('#' . preg_quote(static::base(), '#') . '#', $base)
			|| !empty($host) && $host === static::getInstance(static::base())->host && strpos($uri->path, 'index.php') !== false
			|| !empty($host) && $base === $host && preg_match('#' . preg_quote($base, '#') . '#', static::base()))
		{
			return true;
		}

		return false;
	}

	/**
	 * Build a query from an array (reverse of the PHP parse_str()).
	 *
	 * @param   array  $params  The array of key => value pairs to return as a query string.
	 *
	 * @return  string  The resulting query string.
	 *
	 * @see     parse_str()
	 * @since   1.7.0
	 * @note    The parent method is protected, this exposes it as public for B/C
	 */
	public static function buildQuery(array $params)
	{
		return parent::buildQuery($params);
	}

	/**
	 * Parse a given URI and populate the class fields.
	 *
	 * @param   string  $uri  The URI string to parse.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @note    The parent method is protected, this exposes it as public for B/C
	 */
	public function parse($uri)
	{
		return parent::parse($uri);
	}

	/**
	 * Resolves //, ../ and ./ from a path and returns
	 * the result. Eg:
	 *
	 * /foo/bar/../boo.php    => /foo/boo.php
	 * /foo/bar/../../boo.php => /boo.php
	 * /foo/bar/.././/boo.php => /foo/boo.php
	 *
	 * @param   string  $path  The URI path to clean.
	 *
	 * @return  string  Cleaned and resolved URI path.
	 *
	 * @since       1.7.0
	 * @deprecated  4.0   Use {@link \Joomla\Uri\Uri::cleanPath()} instead
	 */
	protected function _cleanPath($path)
	{
		return parent::cleanPath($path);
	}
}
src/Captcha/Google/HttpBridgePostRequestMethod.php000064400000003002152177723700016245 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Captcha\Google;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Http\Http;
use Joomla\CMS\Http\HttpFactory;
use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;

/**
 * Bridges the Joomla! HTTP API to the Google Recaptcha RequestMethod interface for a POST request.
 *
 * @since  3.9.0
 */
final class HttpBridgePostRequestMethod implements RequestMethod
{
	/**
	 * URL to which requests are sent.
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';

	/**
	 * The HTTP adapter
	 *
	 * @var    Http
	 * @since  3.9.0
	 */
	private $http;

	/**
	 * Class constructor.
	 *
	 * @param   Http|null  $http  The HTTP adapter
	 *
	 * @since   3.9.0
	 */
	public function __construct(Http $http = null)
	{
		$this->http = $http ?: HttpFactory::getHttp();
	}

	/**
	 * Submit the request with the specified parameters.
	 *
	 * @param   RequestParameters  $params  Request parameters
	 *
	 * @return  string  Body of the reCAPTCHA response
	 *
	 * @since   3.9.0
	 */
	public function submit(RequestParameters $params)
	{
		try
		{
			$response = $this->http->post(self::SITE_VERIFY_URL, $params->toArray());

			return (string) $response->body;
		}
		catch (\UnexpectedValueException $exception)
		{
			return '';
		}
	}
}

src/Captcha/Captcha.php000064400000016753152177723700011001 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Captcha;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Registry\Registry;

/**
 * Joomla! Captcha base object
 *
 * @abstract
 * @package     Joomla.Libraries
 * @subpackage  Captcha
 * @since       2.5
 */
class Captcha extends \JObject
{
	/**
	 * An array of Observer objects to notify
	 *
	 * @var    array
	 * @since  2.5
	 */
	protected $_observers = array();

	/**
	 * The state of the observable object
	 *
	 * @var    mixed
	 * @since  2.5
	 */
	protected $_state = null;

	/**
	 * A multi dimensional array of [function][] = key for observers
	 *
	 * @var    array
	 * @since  2.5
	 */
	protected $_methods = array();

	/**
	 * Captcha Plugin object
	 *
	 * @var	   CMSPlugin
	 * @since  2.5
	 */
	private $_captcha;

	/**
	 * Editor Plugin name
	 *
	 * @var    string
	 * @since  2.5
	 */
	private $_name;

	/**
	 * Array of instances of this class.
	 *
	 * @var	   Captcha[]
	 * @since  2.5
	 */
	private static $_instances = array();

	/**
	 * Class constructor.
	 *
	 * @param   string  $captcha  The plugin to use.
	 * @param   array   $options  Associative array of options.
	 *
	 * @since   2.5
	 * @throws  \RuntimeException
	 */
	public function __construct($captcha, $options)
	{
		$this->_name = $captcha;
		$this->_load($options);
	}

	/**
	 * Returns the global Captcha object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $captcha  The plugin to use.
	 * @param   array   $options  Associative array of options.
	 *
	 * @return  Captcha|null  Instance of this class.
	 *
	 * @since   2.5
	 * @throws  \RuntimeException
	 */
	public static function getInstance($captcha, array $options = array())
	{
		$signature = md5(serialize(array($captcha, $options)));

		if (empty(self::$_instances[$signature]))
		{
			self::$_instances[$signature] = new Captcha($captcha, $options);
		}

		return self::$_instances[$signature];
	}

	/**
	 * Fire the onInit event to initialise the captcha plugin.
	 *
	 * @param   string  $id  The id of the field.
	 *
	 * @return  boolean  True on success
	 *
	 * @since	2.5
	 * @throws  \RuntimeException
	 */
	public function initialise($id)
	{
		$args['id']    = $id;
		$args['event'] = 'onInit';

		$this->_captcha->update($args);

		return true;
	}

	/**
	 * Get the HTML for the captcha.
	 *
	 * @param   string  $name   The control name.
	 * @param   string  $id     The id for the control.
	 * @param   string  $class  Value for the HTML class attribute
	 *
	 * @return  mixed  The return value of the function "onDisplay" of the selected Plugin.
	 *
	 * @since   2.5
	 * @throws  \RuntimeException
	 */
	public function display($name, $id, $class = '')
	{
		// Check if captcha is already loaded.
		if ($this->_captcha === null)
		{
			return;
		}

		// Initialise the Captcha.
		if (!$this->initialise($id))
		{
			return;
		}

		$args['name']  = $name;
		$args['id']    = $id ?: $name;
		$args['class'] = $class;
		$args['event'] = 'onDisplay';

		return $this->_captcha->update($args);
	}

	/**
	 * Checks if the answer is correct.
	 *
	 * @param   string  $code  The answer.
	 *
	 * @return  bool    Whether the provided answer was correct
	 *
	 * @since	2.5
	 * @throws  \RuntimeException
	 */
	public function checkAnswer($code)
	{
		// Check if captcha is already loaded
		if ($this->_captcha === null)
		{
			return;
		}

		$args['code']  = $code;
		$args['event'] = 'onCheckAnswer';

		return $this->_captcha->update($args);
	}

	/**
	 * Method to react on the setup of a captcha field. Gives the possibility
	 * to change the field and/or the XML element for the field.
	 *
	 * @param   \Joomla\CMS\Form\Field\CaptchaField  $field    Captcha field instance
	 * @param   \SimpleXMLElement                    $element  XML form definition
	 *
	 * @return void
	 */
	public function setupField(\Joomla\CMS\Form\Field\CaptchaField $field, \SimpleXMLElement $element)
	{
		if ($this->_captcha === null)
		{
			return;
		}

		$args = array(
			'event' => 'onSetupField',
			'field' => $field,
			'element' => $element,
		);

		// Forward to the captcha plugin
		return $this->_captcha->update($args);
	}

	/**
	 * Load the Captcha plugin.
	 *
	 * @param   array  $options  Associative array of options.
	 *
	 * @return  void
	 *
	 * @since	2.5
	 * @throws  \RuntimeException
	 */
	private function _load(array $options = array())
	{
		// Build the path to the needed captcha plugin
		$name = \JFilterInput::getInstance()->clean($this->_name, 'cmd');
		$path = JPATH_PLUGINS . '/captcha/' . $name . '/' . $name . '.php';

		if (!is_file($path))
		{
			throw new \RuntimeException(\JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name));
		}

		// Require plugin file
		require_once $path;

		// Get the plugin
		$plugin = PluginHelper::getPlugin('captcha', $this->_name);

		if (!$plugin)
		{
			throw new \RuntimeException(\JText::sprintf('JLIB_CAPTCHA_ERROR_PLUGIN_NOT_FOUND', $name));
		}

		// Check for already loaded params
		if (!($plugin->params instanceof Registry))
		{
			$params = new Registry($plugin->params);
			$plugin->params = $params;
		}

		// Build captcha plugin classname
		$name = 'PlgCaptcha' . $this->_name;
		$this->_captcha = new $name($this, (array) $plugin, $options);
	}

	/**
	 * Get the state of the Captcha object
	 *
	 * @return  mixed  The state of the object.
	 *
	 * @since   2.5
	 */
	public function getState()
	{
		return $this->_state;
	}

	/**
	 * Attach an observer object
	 *
	 * @param   object  $observer  An observer object to attach
	 *
	 * @return  void
	 *
	 * @since   2.5
	 */
	public function attach($observer)
	{
		if (is_array($observer))
		{
			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
			{
				return;
			}

			// Make sure we haven't already attached this array as an observer
			foreach ($this->_observers as $check)
			{
				if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			end($this->_observers);
			$methods = array($observer['event']);
		}
		else
		{
			if (!($observer instanceof Editor))
			{
				return;
			}

			// Make sure we haven't already attached this object as an observer
			$class = get_class($observer);

			foreach ($this->_observers as $check)
			{
				if ($check instanceof $class)
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			$methods = array_diff(get_class_methods($observer), get_class_methods('\JPlugin'));
		}

		$key = key($this->_observers);

		foreach ($methods as $method)
		{
			$method = strtolower($method);

			if (!isset($this->_methods[$method]))
			{
				$this->_methods[$method] = array();
			}

			$this->_methods[$method][] = $key;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @param   object  $observer  An observer object to detach.
	 *
	 * @return  boolean  True if the observer object was detached.
	 *
	 * @since   2.5
	 */
	public function detach($observer)
	{
		$retval = false;

		$key = array_search($observer, $this->_observers);

		if ($key !== false)
		{
			unset($this->_observers[$key]);
			$retval = true;

			foreach ($this->_methods as &$method)
			{
				$k = array_search($key, $method);

				if ($k !== false)
				{
					unset($method[$k]);
				}
			}
		}

		return $retval;
	}
}
src/Pathway/Pathway.php000064400000011605152177723700011114 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Pathway;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;

/**
 * Class to maintain a pathway.
 *
 * The user's navigated path within the application.
 *
 * @since  1.5
 */
class Pathway
{
	/**
	 * @var    array  Array to hold the pathway item objects
	 * @since  1.5
	 * @deprecated  4.0  Will convert to $pathway
	 */
	protected $_pathway = array();

	/**
	 * @var    integer  Integer number of items in the pathway
	 * @since  1.5
	 * @deprecated  4.0  Will convert to $count
	 */
	protected $_count = 0;

	/**
	 * JPathway instances container.
	 *
	 * @var    Pathway[]
	 * @since  1.7
	 */
	protected static $instances = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $options  The class options.
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
	}

	/**
	 * Returns a Pathway object
	 *
	 * @param   string  $client   The name of the client
	 * @param   array   $options  An associative array of options
	 *
	 * @return  Pathway  A Pathway object.
	 *
	 * @since   1.5
	 * @throws  \RuntimeException
	 */
	public static function getInstance($client, $options = array())
	{
		if (empty(self::$instances[$client]))
		{
			// Create a Pathway object
			$classname = 'JPathway' . ucfirst($client);

			if (!class_exists($classname))
			{
				// @deprecated 4.0 Everything in this block is deprecated but the warning is only logged after the file_exists
				// Load the pathway object
				$info = ApplicationHelper::getClientInfo($client, true);

				if (is_object($info))
				{
					$path = $info->path . '/includes/pathway.php';

					\JLoader::register($classname, $path);

					if (class_exists($classname))
					{
						\JLog::add('Non-autoloadable Pathway subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
					}
				}
			}

			if (class_exists($classname))
			{
				self::$instances[$client] = new $classname($options);
			}
			else
			{
				throw new \RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_PATHWAY_LOAD', $client), 500);
			}
		}

		return self::$instances[$client];
	}

	/**
	 * Return the Pathway items array
	 *
	 * @return  array  Array of pathway items
	 *
	 * @since   1.5
	 */
	public function getPathway()
	{
		$pw = $this->_pathway;

		// Use array_values to reset the array keys numerically
		return array_values($pw);
	}

	/**
	 * Set the Pathway items array.
	 *
	 * @param   array  $pathway  An array of pathway objects.
	 *
	 * @return  array  The previous pathway data.
	 *
	 * @since   1.5
	 */
	public function setPathway($pathway)
	{
		$oldPathway = $this->_pathway;

		// Set the new pathway.
		$this->_pathway = array_values((array) $pathway);

		return array_values($oldPathway);
	}

	/**
	 * Create and return an array of the pathway names.
	 *
	 * @return  array  Array of names of pathway items
	 *
	 * @since   1.5
	 */
	public function getPathwayNames()
	{
		$names = array();

		// Build the names array using just the names of each pathway item
		foreach ($this->_pathway as $item)
		{
			$names[] = $item->name;
		}

		// Use array_values to reset the array keys numerically
		return array_values($names);
	}

	/**
	 * Create and add an item to the pathway.
	 *
	 * @param   string  $name  The name of the item.
	 * @param   string  $link  The link to the item.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 */
	public function addItem($name, $link = '')
	{
		$ret = false;

		if ($this->_pathway[] = $this->makeItem($name, $link))
		{
			$ret = true;
			$this->_count++;
		}

		return $ret;
	}

	/**
	 * Set item name.
	 *
	 * @param   integer  $id    The id of the item on which to set the name.
	 * @param   string   $name  The name to set.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 */
	public function setItemName($id, $name)
	{
		$ret = false;

		if (isset($this->_pathway[$id]))
		{
			$this->_pathway[$id]->name = $name;
			$ret = true;
		}

		return $ret;
	}

	/**
	 * Create and return a new pathway object.
	 *
	 * @param   string  $name  Name of the item
	 * @param   string  $link  Link to the item
	 *
	 * @return  Pathway  Pathway item object
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use makeItem() instead
	 * @codeCoverageIgnore
	 */
	protected function _makeItem($name, $link)
	{
		return $this->makeItem($name, $link);
	}

	/**
	 * Create and return a new pathway object.
	 *
	 * @param   string  $name  Name of the item
	 * @param   string  $link  Link to the item
	 *
	 * @return  Pathway  Pathway item object
	 *
	 * @since   3.1
	 */
	protected function makeItem($name, $link)
	{
		$item = new \stdClass;
		$item->name = html_entity_decode($name, ENT_COMPAT, 'UTF-8');
		$item->link = $link;

		return $item;
	}
}
src/Pathway/SitePathway.php000064400000003670152177723700011744 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Pathway;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Multilanguage;

/**
 * Class to manage the site application pathway.
 *
 * @since  1.5
 */
class SitePathway extends Pathway
{
	/**
	 * Class constructor.
	 *
	 * @param   array  $options  The class options.
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
		$this->_pathway = array();

		$app  = CMSApplication::getInstance('site');
		$menu = $app->getMenu();
		$lang = \JFactory::getLanguage();

		if ($item = $menu->getActive())
		{
			$menus = $menu->getMenu();

			// Look for the home menu
			if (Multilanguage::isEnabled())
			{
				$home = $menu->getDefault($lang->getTag());
			}
			else
			{
				$home  = $menu->getDefault();
			}

			if (is_object($home) && ($item->id != $home->id))
			{
				foreach ($item->tree as $menupath)
				{
					$link = $menu->getItem($menupath);

					switch ($link->type)
					{
						case 'separator':
						case 'heading':
							$url = null;
							break;

						case 'url':
							if ((strpos($link->link, 'index.php?') === 0) && (strpos($link->link, 'Itemid=') === false))
							{
								// If this is an internal Joomla link, ensure the Itemid is set.
								$url = $link->link . '&Itemid=' . $link->id;
							}
							else
							{
								$url = $link->link;
							}
							break;

						case 'alias':
							// If this is an alias use the item id stored in the parameters to make the link.
							$url = 'index.php?Itemid=' . $link->params->get('aliasoptions');
							break;

						default:
							$url = $link->link . '&Itemid=' . $link->id;
							break;
					}

					$this->addItem($menus[$menupath]->title, $url);
				}
			}
		}
	}
}
src/Document/Feed/FeedItem.php000064400000004114152177723700012162 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Feed;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing a feed item
 *
 * @since  1.7.0
 */
class FeedItem
{
	/**
	 * Title item element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $title;

	/**
	 * Link item element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $link;

	/**
	 * Description item element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $description;

	/**
	 * Author item element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $author;

	/**
	 * Author email element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $authorEmail;

	/**
	 * Category element
	 *
	 * optional
	 *
	 * @var    array or string
	 * @since  1.7.0
	 */
	public $category;

	/**
	 * Comments element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $comments;

	/**
	 * Enclosure element
	 *
	 * @var    FeedEnclosure
	 * @since  1.7.0
	 */
	public $enclosure = null;

	/**
	 * Guid element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $guid;

	/**
	 * Published date
	 *
	 * optional
	 *
	 * May be in one of the following formats:
	 *
	 * RFC 822:
	 * "Mon, 20 Jan 03 18:05:41 +0400"
	 * "20 Jan 03 18:05:41 +0000"
	 *
	 * ISO 8601:
	 * "2003-01-20T18:05:41+04:00"
	 *
	 * Unix:
	 * 1043082341
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $date;

	/**
	 * Source element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $source;

	/**
	 * Set the FeedEnclosure for this item
	 *
	 * @param   FeedEnclosure  $enclosure  The FeedEnclosure to add to the feed.
	 *
	 * @return  FeedItem instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setEnclosure(FeedEnclosure $enclosure)
	{
		$this->enclosure = $enclosure;

		return $this;
	}
}
src/Document/Feed/FeedImage.php000064400000002045152177723700012307 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Feed;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing a feed image
 *
 * @since  1.7.0
 */
class FeedImage
{
	/**
	 * Title image attribute
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $title = '';

	/**
	 * URL image attribute
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $url = '';

	/**
	 * Link image attribute
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $link = '';

	/**
	 * Width image attribute
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $width;

	/**
	 * Title feed attribute
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $height;

	/**
	 * Title feed attribute
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $description;
}
src/Document/Feed/FeedEnclosure.php000064400000001343152177723700013224 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Feed;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing a feed enclosure
 *
 * @since  1.7.0
 */
class FeedEnclosure
{
	/**
	 * URL enclosure element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $url = '';

	/**
	 * Length enclosure element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $length = '';

	/**
	 * Type enclosure element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $type = '';
}
src/Document/Opensearch/OpensearchUrl.php000064400000001437152177723700014503 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Opensearch;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing an OpenSearch URL
 *
 * @since  1.7.0
 */
class OpensearchUrl
{
	/**
	 * Type item element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $type = 'text/html';

	/**
	 * Rel item element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $rel = 'results';

	/**
	 * Template item element. Has to contain the {searchTerms} parameter to work.
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $template;
}
src/Document/Opensearch/OpensearchImage.php000064400000001555152177723700014764 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Opensearch;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing an OpenSearch image
 *
 * @since  1.7.0
 */
class OpensearchImage
{
	/**
	 * The images MIME type
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $type = '';

	/**
	 * URL of the image or the image as base64 encoded value
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $data = '';

	/**
	 * The image's width
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $width;

	/**
	 * The image's height
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $height;
}
src/Document/ImageDocument.php000064400000002617152177723700012364 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

/**
 * ImageDocument class, provides an easy interface to output image data
 *
 * @since  3.0.0
 */
class ImageDocument extends Document
{
	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since   3.0.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set mime type
		$this->_mime = 'image/png';

		// Set document type
		$this->_type = 'image';
	}

	/**
	 * Render the document.
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string  The rendered data
	 *
	 * @since   3.0.0
	 */
	public function render($cache = false, $params = array())
	{
		// Get the image type
		$type = \JFactory::getApplication()->input->get('type', 'png');

		switch ($type)
		{
			case 'jpg':
			case 'jpeg':
				$this->_mime = 'image/jpeg';
				break;
			case 'gif':
				$this->_mime = 'image/gif';
				break;
			case 'png':
			default:
				$this->_mime = 'image/png';
				break;
		}

		$this->_charset = null;

		parent::render();

		return $this->getBuffer();
	}
}
src/Document/RawDocument.php000064400000002071152177723700012065 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

/**
 * RawDocument class, provides an easy interface to parse and display raw output
 *
 * @since  1.7.0
 */
class RawDocument extends Document
{
	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set mime type
		$this->_mime = 'text/html';

		// Set document type
		$this->_type = 'raw';
	}

	/**
	 * Render the document.
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string  The rendered data
	 *
	 * @since   1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		parent::render();

		return $this->getBuffer();
	}
}
src/Document/Renderer/Feed/AtomRenderer.php000064400000014760152177723700014645 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Uri\Uri;

/**
 * AtomRenderer is a feed that implements the atom specification
 *
 * Please note that just by using this class you won't automatically
 * produce valid atom files. For example, you have to specify either an editor
 * for the feed or an author for every single feed item.
 *
 * @link   http://www.atomenabled.org/developers/syndication/atom-format-spec.php
 * @since  3.5
 *
 * @property-read  \Joomla\CMS\Document\FeedDocument  $_doc  Reference to the Document object that instantiated the renderer
 */
class AtomRenderer extends DocumentRenderer
{
	/**
	 * Document mime type
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $_mime = 'application/atom+xml';

	/**
	 * Render the feed.
	 *
	 * @param   string  $name     The name of the element to render
	 * @param   array   $params   Array of values
	 * @param   string  $content  Override the output of the renderer
	 *
	 * @return  string  The output of the script
	 *
	 * @see     DocumentRenderer::render()
	 * @since   3.5
	 */
	public function render($name = '', $params = null, $content = null)
	{
		$app = \JFactory::getApplication();

		// Gets and sets timezone offset from site configuration
		$tz  = new \DateTimeZone($app->get('offset'));
		$now = \JFactory::getDate();
		$now->setTimeZone($tz);

		$data = $this->_doc;

		$url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
		$syndicationURL = \JRoute::_('&format=feed&type=atom');

		$title = $data->getTitle();

		if ($app->get('sitename_pagetitles', 0) == 1)
		{
			$title = \JText::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
		}
		elseif ($app->get('sitename_pagetitles', 0) == 2)
		{
			$title = \JText::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
		}

		$feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');

		$feed = "<feed xmlns=\"http://www.w3.org/2005/Atom\" ";

		if ($data->getLanguage() != '')
		{
			$feed .= " xml:lang=\"" . $data->getLanguage() . "\"";
		}

		$feed .= ">\n";
		$feed .= "	<title type=\"text\">" . $feed_title . "</title>\n";
		$feed .= "	<subtitle type=\"text\">" . htmlspecialchars($data->getDescription(), ENT_COMPAT, 'UTF-8') . "</subtitle>\n";

		if (!empty($data->category))
		{
			if (is_array($data->category))
			{
				foreach ($data->category as $cat)
				{
					$feed .= "	<category term=\"" . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "\" />\n";
				}
			}
			else
			{
				$feed .= "	<category term=\"" . htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') . "\" />\n";
			}
		}

		$feed .= "	<link rel=\"alternate\" type=\"text/html\" href=\"" . $url . "\"/>\n";
		$feed .= "	<id>" . str_replace(' ', '%20', $data->getBase()) . "</id>\n";
		$feed .= "	<updated>" . htmlspecialchars($now->toISO8601(true), ENT_COMPAT, 'UTF-8') . "</updated>\n";

		if ($data->editor != '')
		{
			$feed .= "	<author>\n";
			$feed .= "		<name>" . $data->editor . "</name>\n";

			if ($data->editorEmail != '')
			{
				$feed .= "		<email>" . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . "</email>\n";
			}

			$feed .= "	</author>\n";
		}

		$versionHtmlEscaped = '';

		if ($app->get('MetaVersion', 0))
		{
			$minorVersion       = \JVersion::MAJOR_VERSION . '.' . \JVersion::MINOR_VERSION;
			$versionHtmlEscaped = ' version="' . htmlspecialchars($minorVersion, ENT_COMPAT, 'UTF-8') . '"';
		}

		$feed .= "	<generator uri=\"https://www.joomla.org\"" . $versionHtmlEscaped . ">" . $data->getGenerator() . "</generator>\n";
		$feed .= "	<link rel=\"self\" type=\"application/atom+xml\" href=\"" . str_replace(' ', '%20', $url . $syndicationURL) . "\"/>\n";

		for ($i = 0, $count = count($data->items); $i < $count; $i++)
		{
			$itemlink = $data->items[$i]->link;

			if (preg_match('/[\x80-\xFF]/', $itemlink))
			{
				$itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
			}

			$feed .= "	<entry>\n";
			$feed .= "		<title>" . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . "</title>\n";
			$feed .= "		<link rel=\"alternate\" type=\"text/html\" href=\"" . $url . $itemlink . "\"/>\n";

			if ($data->items[$i]->date == '')
			{
				$data->items[$i]->date = $now->toUnix();
			}

			$itemDate = \JFactory::getDate($data->items[$i]->date);
			$itemDate->setTimeZone($tz);
			$feed .= "		<published>" . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . "</published>\n";
			$feed .= "		<updated>" . htmlspecialchars($itemDate->toISO8601(true), ENT_COMPAT, 'UTF-8') . "</updated>\n";

			if (empty($data->items[$i]->guid))
			{
				$itemGuid = str_replace(' ', '%20', $url . $itemlink);
			}
			else
			{
				$itemGuid = htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8');
			}

			$feed .= "		<id>" . $itemGuid . "</id>\n";

			if ($data->items[$i]->author != '')
			{
				$feed .= "		<author>\n";
				$feed .= "			<name>" . htmlspecialchars($data->items[$i]->author, ENT_COMPAT, 'UTF-8') . "</name>\n";

				if (!empty($data->items[$i]->authorEmail))
				{
					$feed .= "			<email>" . htmlspecialchars($data->items[$i]->authorEmail, ENT_COMPAT, 'UTF-8') . "</email>\n";
				}

				$feed .= "		</author>\n";
			}

			if (!empty($data->items[$i]->description))
			{
				$feed .= "		<summary type=\"html\">" . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . "</summary>\n";
				$feed .= "		<content type=\"html\">" . htmlspecialchars($this->_relToAbs($data->items[$i]->description), ENT_COMPAT, 'UTF-8') . "</content>\n";
			}

			if (!empty($data->items[$i]->category))
			{
				if (is_array($data->items[$i]->category))
				{
					foreach ($data->items[$i]->category as $cat)
					{
						$feed .= "		<category term=\"" . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "\" />\n";
					}
				}
				else
				{
					$feed .= "		<category term=\"" . htmlspecialchars($data->items[$i]->category, ENT_COMPAT, 'UTF-8') . "\" />\n";
				}
			}

			if ($data->items[$i]->enclosure != null)
			{
				$feed .= "		<link rel=\"enclosure\" href=\"" . $data->items[$i]->enclosure->url . "\" type=\""
					. $data->items[$i]->enclosure->type . "\"  length=\"" . $data->items[$i]->enclosure->length . "\" />\n";
			}

			$feed .= "	</entry>\n";
		}

		$feed .= "</feed>\n";

		return $feed;
	}
}
src/Document/Renderer/Feed/RssRenderer.php000064400000017615152177723700014516 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Feed;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Uri\Uri;

/**
 * RssRenderer is a feed that implements RSS 2.0 Specification
 *
 * @link   http://www.rssboard.org/rss-specification
 * @since  3.5
 *
 * @property-read  \Joomla\CMS\Document\FeedDocument  $_doc  Reference to the Document object that instantiated the renderer
 */
class RssRenderer extends DocumentRenderer
{
	/**
	 * Renderer mime type
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $_mime = 'application/rss+xml';

	/**
	 * Render the feed.
	 *
	 * @param   string  $name     The name of the element to render
	 * @param   array   $params   Array of values
	 * @param   string  $content  Override the output of the renderer
	 *
	 * @return  string  The output of the script
	 *
	 * @see     DocumentRenderer::render()
	 * @since   3.5
	 */
	public function render($name = '', $params = null, $content = null)
	{
		$app = \JFactory::getApplication();

		// Gets and sets timezone offset from site configuration
		$tz  = new \DateTimeZone($app->get('offset'));
		$now = \JFactory::getDate();
		$now->setTimeZone($tz);

		$data = $this->_doc;

		$url = Uri::getInstance()->toString(array('scheme', 'user', 'pass', 'host', 'port'));
		$syndicationURL = \JRoute::_('&format=feed&type=rss');

		$title = $data->getTitle();

		if ($app->get('sitename_pagetitles', 0) == 1)
		{
			$title = \JText::sprintf('JPAGETITLE', $app->get('sitename'), $data->getTitle());
		}
		elseif ($app->get('sitename_pagetitles', 0) == 2)
		{
			$title = \JText::sprintf('JPAGETITLE', $data->getTitle(), $app->get('sitename'));
		}

		$feed_title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');

		$datalink = $data->getLink();

		if (preg_match('/[\x80-\xFF]/', $datalink))
		{
			$datalink = implode('/', array_map('rawurlencode', explode('/', $datalink)));
		}

		$feed = "<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n";
		$feed .= "	<channel>\n";
		$feed .= "		<title>" . $feed_title . "</title>\n";
		$feed .= "		<description><![CDATA[" . $data->getDescription() . "]]></description>\n";
		$feed .= "		<link>" . str_replace(' ', '%20', $url . $datalink) . "</link>\n";
		$feed .= "		<lastBuildDate>" . htmlspecialchars($now->toRFC822(true), ENT_COMPAT, 'UTF-8') . "</lastBuildDate>\n";
		$feed .= "		<generator>" . $data->getGenerator() . "</generator>\n";
		$feed .= "		<atom:link rel=\"self\" type=\"application/rss+xml\" href=\"" . str_replace(' ', '%20', $url . $syndicationURL) . "\"/>\n";

		if ($data->image != null)
		{
			$feed .= "		<image>\n";
			$feed .= "			<url>" . $data->image->url . "</url>\n";
			$feed .= "			<title>" . htmlspecialchars($data->image->title, ENT_COMPAT, 'UTF-8') . "</title>\n";
			$feed .= "			<link>" . str_replace(' ', '%20', $data->image->link) . "</link>\n";

			if ($data->image->width != '')
			{
				$feed .= "			<width>" . $data->image->width . "</width>\n";
			}

			if ($data->image->height != '')
			{
				$feed .= "			<height>" . $data->image->height . "</height>\n";
			}

			if ($data->image->description != '')
			{
				$feed .= "			<description><![CDATA[" . $data->image->description . "]]></description>\n";
			}

			$feed .= "		</image>\n";
		}

		if ($data->getLanguage() !== '')
		{
			$feed .= "		<language>" . $data->getLanguage() . "</language>\n";
		}

		if ($data->copyright != '')
		{
			$feed .= "		<copyright>" . htmlspecialchars($data->copyright, ENT_COMPAT, 'UTF-8') . "</copyright>\n";
		}

		if ($data->editorEmail != '')
		{
			$feed .= "		<managingEditor>" . htmlspecialchars($data->editorEmail, ENT_COMPAT, 'UTF-8') . ' ('
				. htmlspecialchars($data->editor, ENT_COMPAT, 'UTF-8') . ")</managingEditor>\n";
		}

		if ($data->webmaster != '')
		{
			$feed .= "		<webMaster>" . htmlspecialchars($data->webmaster, ENT_COMPAT, 'UTF-8') . "</webMaster>\n";
		}

		if ($data->pubDate != '')
		{
			$pubDate = \JFactory::getDate($data->pubDate);
			$pubDate->setTimeZone($tz);
			$feed .= "		<pubDate>" . htmlspecialchars($pubDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . "</pubDate>\n";
		}

		if (!empty($data->category))
		{
			if (is_array($data->category))
			{
				foreach ($data->category as $cat)
				{
					$feed .= "		<category>" . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "</category>\n";
				}
			}
			else
			{
				$feed .= "		<category>" . htmlspecialchars($data->category, ENT_COMPAT, 'UTF-8') . "</category>\n";
			}
		}

		if ($data->docs != '')
		{
			$feed .= "		<docs>" . htmlspecialchars($data->docs, ENT_COMPAT, 'UTF-8') . "</docs>\n";
		}

		if ($data->ttl != '')
		{
			$feed .= "		<ttl>" . htmlspecialchars($data->ttl, ENT_COMPAT, 'UTF-8') . "</ttl>\n";
		}

		if ($data->rating != '')
		{
			$feed .= "		<rating>" . htmlspecialchars($data->rating, ENT_COMPAT, 'UTF-8') . "</rating>\n";
		}

		if ($data->skipHours != '')
		{
			$feed .= "		<skipHours>" . htmlspecialchars($data->skipHours, ENT_COMPAT, 'UTF-8') . "</skipHours>\n";
		}

		if ($data->skipDays != '')
		{
			$feed .= "		<skipDays>" . htmlspecialchars($data->skipDays, ENT_COMPAT, 'UTF-8') . "</skipDays>\n";
		}

		for ($i = 0, $count = count($data->items); $i < $count; $i++)
		{
			$itemlink = $data->items[$i]->link;

			if (preg_match('/[\x80-\xFF]/', $itemlink))
			{
				$itemlink = implode('/', array_map('rawurlencode', explode('/', $itemlink)));
			}

			if ((strpos($itemlink, 'http://') === false) && (strpos($itemlink, 'https://') === false))
			{
				$itemlink = str_replace(' ', '%20', $url . $itemlink);
			}

			$feed .= "		<item>\n";
			$feed .= "			<title>" . htmlspecialchars(strip_tags($data->items[$i]->title), ENT_COMPAT, 'UTF-8') . "</title>\n";
			$feed .= "			<link>" . str_replace(' ', '%20', $itemlink) . "</link>\n";

			if (empty($data->items[$i]->guid))
			{
				$feed .= "			<guid isPermaLink=\"true\">" . str_replace(' ', '%20', $itemlink) . "</guid>\n";
			}
			else
			{
				$feed .= "			<guid isPermaLink=\"false\">" . htmlspecialchars($data->items[$i]->guid, ENT_COMPAT, 'UTF-8') . "</guid>\n";
			}

			$feed .= "			<description><![CDATA[" . $this->_relToAbs($data->items[$i]->description) . "]]></description>\n";

			if ($data->items[$i]->authorEmail != '')
			{
				$feed .= '			<author>'
					. htmlspecialchars($data->items[$i]->authorEmail . ' (' . $data->items[$i]->author . ')', ENT_COMPAT, 'UTF-8') . "</author>\n";
			}

			/*
			 * @todo: On hold
			 * if ($data->items[$i]->source!='')
			 * {
			 *   $data.= "			<source>" . htmlspecialchars($data->items[$i]->source, ENT_COMPAT, 'UTF-8') . "</source>\n";
			 * }
			 */

			if (empty($data->items[$i]->category) === false)
			{
				if (is_array($data->items[$i]->category))
				{
					foreach ($data->items[$i]->category as $cat)
					{
						$feed .= "			<category>" . htmlspecialchars($cat, ENT_COMPAT, 'UTF-8') . "</category>\n";
					}
				}
				else
				{
					$feed .= "			<category>" . htmlspecialchars($data->items[$i]->category, ENT_COMPAT, 'UTF-8') . "</category>\n";
				}
			}

			if ($data->items[$i]->comments != '')
			{
				$feed .= "			<comments>" . htmlspecialchars($data->items[$i]->comments, ENT_COMPAT, 'UTF-8') . "</comments>\n";
			}

			if ($data->items[$i]->date != '')
			{
				$itemDate = \JFactory::getDate($data->items[$i]->date);
				$itemDate->setTimeZone($tz);
				$feed .= "			<pubDate>" . htmlspecialchars($itemDate->toRFC822(true), ENT_COMPAT, 'UTF-8') . "</pubDate>\n";
			}

			if ($data->items[$i]->enclosure != null)
			{
				$feed .= "			<enclosure url=\"";
				$feed .= $data->items[$i]->enclosure->url;
				$feed .= "\" length=\"";
				$feed .= $data->items[$i]->enclosure->length;
				$feed .= "\" type=\"";
				$feed .= $data->items[$i]->enclosure->type;
				$feed .= "\"/>\n";
			}

			$feed .= "		</item>\n";
		}

		$feed .= "	</channel>\n";
		$feed .= "</rss>\n";

		return $feed;
	}
}
src/Document/Renderer/Html/MessageRenderer.php000064400000004113152177723700015361 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Html;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Layout\LayoutHelper;

/**
 * HTML document renderer for the system message queue
 *
 * @since  3.5
 */
class MessageRenderer extends DocumentRenderer
{
	/**
	 * Renders the error stack and returns the results as a string
	 *
	 * @param   string  $name     Not used.
	 * @param   array   $params   Associative array of values
	 * @param   string  $content  Not used.
	 *
	 * @return  string  The output of the script
	 *
	 * @since   3.5
	 */
	public function render($name, $params = array(), $content = null)
	{
		$msgList     = $this->getData();
		$displayData = array(
			'msgList' => $msgList,
			'name'    => $name,
			'params'  => $params,
			'content' => $content,
		);

		$app        = \JFactory::getApplication();
		$chromePath = JPATH_THEMES . '/' . $app->getTemplate() . '/html/message.php';

		if (file_exists($chromePath))
		{
			include_once $chromePath;
		}

		if (function_exists('renderMessage'))
		{
			Log::add('renderMessage() is deprecated. Override system message rendering with layouts instead.', Log::WARNING, 'deprecated');

			return renderMessage($msgList);
		}

		return LayoutHelper::render('joomla.system.message', $displayData);
	}

	/**
	 * Get and prepare system message data for output
	 *
	 * @return  array  An array contains system message
	 *
	 * @since   3.5
	 */
	private function getData()
	{
		// Initialise variables.
		$lists = array();

		// Get the message queue
		$messages = \JFactory::getApplication()->getMessageQueue();

		// Build the sorted message list
		if (is_array($messages) && !empty($messages))
		{
			foreach ($messages as $msg)
			{
				if (isset($msg['type']) && isset($msg['message']))
				{
					$lists[$msg['type']][] = $msg['message'];
				}
			}
		}

		return $lists;
	}
}
src/Document/Renderer/Html/HeadRenderer.php000064400000025077152177723700014652 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Html;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\TagsHelper;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\ArrayHelper;

/**
 * HTML document renderer for the document `<head>` element
 *
 * @since  3.5
 */
class HeadRenderer extends DocumentRenderer
{
	/**
	 * Renders the document head and returns the results as a string
	 *
	 * @param   string  $head     (unused)
	 * @param   array   $params   Associative array of values
	 * @param   string  $content  The script
	 *
	 * @return  string  The output of the script
	 *
	 * @since   3.5
	 */
	public function render($head, $params = array(), $content = null)
	{
		return $this->fetchHead($this->_doc);
	}

	/**
	 * Generates the head HTML and return the results as a string
	 *
	 * @param   JDocumentHtml  $document  The document for which the head will be created
	 *
	 * @return  string  The head hTML
	 *
	 * @since   3.5
	 * @deprecated  4.0  Method code will be moved into the render method
	 */
	public function fetchHead($document)
	{
		// Convert the tagids to titles
		if (isset($document->_metaTags['name']['tags']))
		{
			$tagsHelper = new TagsHelper;
			$document->_metaTags['name']['tags'] = implode(', ', $tagsHelper->getTagNames($document->_metaTags['name']['tags']));
		}

		if ($document->getScriptOptions())
		{
			\JHtml::_('behavior.core');
		}

		// Trigger the onBeforeCompileHead event
		$app = \JFactory::getApplication();
		$app->triggerEvent('onBeforeCompileHead');

		// Get line endings
		$lnEnd        = $document->_getLineEnd();
		$tab          = $document->_getTab();
		$tagEnd       = ' />';
		$buffer       = '';
		$mediaVersion = $document->getMediaVersion();

		// Generate charset when using HTML5 (should happen first)
		if ($document->isHtml5())
		{
			$buffer .= $tab . '<meta charset="' . $document->getCharset() . '" />' . $lnEnd;
		}

		// Generate base tag (need to happen early)
		$base = $document->getBase();

		if (!empty($base))
		{
			$buffer .= $tab . '<base href="' . $base . '" />' . $lnEnd;
		}

		// Generate META tags (needs to happen as early as possible in the head)
		foreach ($document->_metaTags as $type => $tag)
		{
			foreach ($tag as $name => $content)
			{
				if ($type == 'http-equiv' && !($document->isHtml5() && $name == 'content-type'))
				{
					$buffer .= $tab . '<meta http-equiv="' . $name . '" content="' . htmlspecialchars($content, ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
				}
				elseif ($type != 'http-equiv' && !empty($content))
				{
					if (is_array($content))
					{
						foreach ($content as $value)
						{
							$buffer .= $tab . '<meta ' . $type . '="' . $name . '" content="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
						}
					}
					else
					{
						$buffer .= $tab . '<meta ' . $type . '="' . $name . '" content="' . htmlspecialchars($content, ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
					}
				}
			}
		}

		// Don't add empty descriptions
		$documentDescription = $document->getDescription();

		if ($documentDescription)
		{
			$buffer .= $tab . '<meta name="description" content="' . htmlspecialchars($documentDescription, ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
		}

		// Don't add empty generators
		$generator = $document->getGenerator();

		if ($generator)
		{
			$buffer .= $tab . '<meta name="generator" content="' . htmlspecialchars($generator, ENT_COMPAT, 'UTF-8') . '" />' . $lnEnd;
		}

		$buffer .= $tab . '<title>' . htmlspecialchars($document->getTitle(), ENT_COMPAT, 'UTF-8') . '</title>' . $lnEnd;

		// Generate link declarations
		foreach ($document->_links as $link => $linkAtrr)
		{
			$buffer .= $tab . '<link href="' . $link . '" ' . $linkAtrr['relType'] . '="' . $linkAtrr['relation'] . '"';

			if (is_array($linkAtrr['attribs']))
			{
				if ($temp = ArrayHelper::toString($linkAtrr['attribs']))
				{
					$buffer .= ' ' . $temp;
				}
			}

			$buffer .= ' />' . $lnEnd;
		}

		$defaultCssMimes = array('text/css');

		// Generate stylesheet links
		foreach ($document->_styleSheets as $src => $attribs)
		{
			// Check if stylesheet uses IE conditional statements.
			$conditional = isset($attribs['options']) && isset($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;

			// Check if script uses media version.
			if (isset($attribs['options']['version']) && $attribs['options']['version'] && strpos($src, '?') === false
				&& ($mediaVersion || $attribs['options']['version'] !== 'auto'))
			{
				$src .= '?' . ($attribs['options']['version'] === 'auto' ? $mediaVersion : $attribs['options']['version']);
			}

			$buffer .= $tab;

			// This is for IE conditional statements support.
			if (!is_null($conditional))
			{
				$buffer .= '<!--[if ' . $conditional . ']>';
			}

			$buffer .= '<link href="' . $src . '" rel="stylesheet"';

			// Add script tag attributes.
			foreach ($attribs as $attrib => $value)
			{
				// Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
				if ($attrib === 'options')
				{
					continue;
				}

				// Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
				if (in_array($attrib, array('type', 'mime')) && $document->isHtml5() && in_array($value, $defaultCssMimes))
				{
					continue;
				}

				// Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
				if ($attrib === 'mime')
				{
					$attrib = 'type';
				}

				// Add attribute to script tag output.
				$buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');

				// Json encode value if it's an array.
				$value = !is_scalar($value) ? json_encode($value) : $value;

				$buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
			}

			$buffer .= $tagEnd;

			// This is for IE conditional statements support.
			if (!is_null($conditional))
			{
				$buffer .= '<![endif]-->';
			}

			$buffer .= $lnEnd;
		}

		// Generate stylesheet declarations
		foreach ($document->_style as $type => $content)
		{
			$buffer .= $tab . '<style';

			if (!is_null($type) && (!$document->isHtml5() || !in_array($type, $defaultCssMimes)))
			{
				$buffer .= ' type="' . $type . '"';
			}

			$buffer .= '>' . $lnEnd;

			// This is for full XHTML support.
			if ($document->_mime != 'text/html')
			{
				$buffer .= $tab . $tab . '/*<![CDATA[*/' . $lnEnd;
			}

			$buffer .= $content . $lnEnd;

			// See above note
			if ($document->_mime != 'text/html')
			{
				$buffer .= $tab . $tab . '/*]]>*/' . $lnEnd;
			}

			$buffer .= $tab . '</style>' . $lnEnd;
		}

		// Generate scripts options
		$scriptOptions = $document->getScriptOptions();

		if (!empty($scriptOptions))
		{
			$buffer .= $tab . '<script type="application/json" class="joomla-script-options new">';

			$prettyPrint = (JDEBUG && defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : false);
			$jsonOptions = json_encode($scriptOptions, $prettyPrint);
			$jsonOptions = $jsonOptions ? $jsonOptions : '{}';

			$buffer .= $jsonOptions;
			$buffer .= '</script>' . $lnEnd;
		}

		$defaultJsMimes         = array('text/javascript', 'application/javascript', 'text/x-javascript', 'application/x-javascript');
		$html5NoValueAttributes = array('defer', 'async');

		// Generate script file links
		foreach ($document->_scripts as $src => $attribs)
		{
			// Check if script uses IE conditional statements.
			$conditional = isset($attribs['options']) && isset($attribs['options']['conditional']) ? $attribs['options']['conditional'] : null;

			// Check if script uses media version.
			if (isset($attribs['options']['version']) && $attribs['options']['version'] && strpos($src, '?') === false
				&& ($mediaVersion || $attribs['options']['version'] !== 'auto'))
			{
				$src .= '?' . ($attribs['options']['version'] === 'auto' ? $mediaVersion : $attribs['options']['version']);
			}

			$buffer .= $tab;

			// This is for IE conditional statements support.
			if (!is_null($conditional))
			{
				$buffer .= '<!--[if ' . $conditional . ']>';
			}

			$buffer .= '<script src="' . $src . '"';

			// Add script tag attributes.
			foreach ($attribs as $attrib => $value)
			{
				// Don't add the 'options' attribute. This attribute is for internal use (version, conditional, etc).
				if ($attrib === 'options')
				{
					continue;
				}

				// Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
				if (in_array($attrib, array('type', 'mime')) && $document->isHtml5() && in_array($value, $defaultJsMimes))
				{
					continue;
				}

				// B/C: If defer and async is false or empty don't render the attribute.
				if (in_array($attrib, array('defer', 'async')) && !$value)
				{
					continue;
				}

				// Don't add type attribute if document is HTML5 and it's a default mime type. 'mime' is for B/C.
				if ($attrib === 'mime')
				{
					$attrib = 'type';
				}
				// B/C defer and async can be set to yes when using the old method.
				elseif (in_array($attrib, array('defer', 'async')) && $value === true)
				{
					$value = $attrib;
				}

				// Add attribute to script tag output.
				$buffer .= ' ' . htmlspecialchars($attrib, ENT_COMPAT, 'UTF-8');

				if (!($document->isHtml5() && in_array($attrib, $html5NoValueAttributes)))
				{
					// Json encode value if it's an array.
					$value = !is_scalar($value) ? json_encode($value) : $value;

					$buffer .= '="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"';
				}
			}

			$buffer .= '></script>';

			// This is for IE conditional statements support.
			if (!is_null($conditional))
			{
				$buffer .= '<![endif]-->';
			}

			$buffer .= $lnEnd;
		}

		// Generate script declarations
		foreach ($document->_script as $type => $content)
		{
			$buffer .= $tab . '<script';

			if (!is_null($type) && (!$document->isHtml5() || !in_array($type, $defaultJsMimes)))
			{
				$buffer .= ' type="' . $type . '"';
			}

			$buffer .= '>' . $lnEnd;

			// This is for full XHTML support.
			if ($document->_mime != 'text/html')
			{
				$buffer .= $tab . $tab . '//<![CDATA[' . $lnEnd;
			}

			$buffer .= $content . $lnEnd;

			// See above note
			if ($document->_mime != 'text/html')
			{
				$buffer .= $tab . $tab . '//]]>' . $lnEnd;
			}

			$buffer .= $tab . '</script>' . $lnEnd;
		}

		// Output the custom tags - array_unique makes sure that we don't output the same tags twice
		foreach (array_unique($document->_custom) as $custom)
		{
			$buffer .= $tab . $custom . $lnEnd;
		}

		return ltrim($buffer, $tab);
	}
}
src/Document/Renderer/Html/ModuleRenderer.php000064400000005255152177723700015232 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Html;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\Registry\Registry;

/**
 * HTML document renderer for a single module
 *
 * @since  3.5
 */
class ModuleRenderer extends DocumentRenderer
{
	/**
	 * Renders a module script and returns the results as a string
	 *
	 * @param   string  $module   The name of the module to render
	 * @param   array   $attribs  Associative array of values
	 * @param   string  $content  If present, module information from the buffer will be used
	 *
	 * @return  string  The output of the script
	 *
	 * @since   3.5
	 */
	public function render($module, $attribs = array(), $content = null)
	{
		if (!is_object($module))
		{
			$title = isset($attribs['title']) ? $attribs['title'] : null;

			$module = ModuleHelper::getModule($module, $title);

			if (!is_object($module))
			{
				if (is_null($content))
				{
					return '';
				}

				/**
				 * If module isn't found in the database but data has been pushed in the buffer
				 * we want to render it
				 */
				$tmp = $module;
				$module = new \stdClass;
				$module->params = null;
				$module->module = $tmp;
				$module->id = 0;
				$module->user = 0;
			}
		}

		// Set the module content
		if (!is_null($content))
		{
			$module->content = $content;
		}

		// Get module parameters
		$params = new Registry($module->params);

		// Use parameters from template
		if (isset($attribs['params']))
		{
			$template_params = new Registry(html_entity_decode($attribs['params'], ENT_COMPAT, 'UTF-8'));
			$params->merge($template_params);
			$module = clone $module;
			$module->params = (string) $params;
		}

		// Default for compatibility purposes. Set cachemode parameter or use JModuleHelper::moduleCache from within the module instead
		$cachemode = $params->get('cachemode', 'oldstatic');

		if ($params->get('cache', 0) == 1 && \JFactory::getConfig()->get('caching') >= 1 && $cachemode != 'id' && $cachemode != 'safeuri')
		{
			// Default to itemid creating method and workarounds on
			$cacheparams = new \stdClass;
			$cacheparams->cachemode = $cachemode;
			$cacheparams->class = 'JModuleHelper';
			$cacheparams->method = 'renderModule';
			$cacheparams->methodparams = array($module, $attribs);

			return ModuleHelper::ModuleCache($module, $params, $cacheparams);
		}

		return ModuleHelper::renderModule($module, $attribs);
	}
}
src/Document/Renderer/Html/ComponentRenderer.php000064400000001615152177723700015743 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Html;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;

/**
 * HTML document renderer for the component output
 *
 * @since  3.5
 */
class ComponentRenderer extends DocumentRenderer
{
	/**
	 * Renders a component script and returns the results as a string
	 *
	 * @param   string  $component  The name of the component to render
	 * @param   array   $params     Associative array of values
	 * @param   string  $content    Content script
	 *
	 * @return  string  The output of the script
	 *
	 * @since   3.5
	 */
	public function render($component = null, $params = array(), $content = null)
	{
		return $content;
	}
}
src/Document/Renderer/Html/ModulesRenderer.php000064400000003600152177723700015405 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document\Renderer\Html;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\DocumentRenderer;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Layout\LayoutHelper;

/**
 * HTML document renderer for a module position
 *
 * @since  3.5
 */
class ModulesRenderer extends DocumentRenderer
{
	/**
	 * Renders multiple modules script and returns the results as a string
	 *
	 * @param   string  $position  The position of the modules to render
	 * @param   array   $params    Associative array of values
	 * @param   string  $content   Module content
	 *
	 * @return  string  The output of the script
	 *
	 * @since   3.5
	 */
	public function render($position, $params = array(), $content = null)
	{
		$renderer = $this->_doc->loadRenderer('module');
		$buffer   = '';

		$app          = \JFactory::getApplication();
		$user         = \JFactory::getUser();
		$frontediting = ($app->isClient('site') && $app->get('frontediting', 1) && !$user->guest);
		$menusEditing = ($app->get('frontediting', 1) == 2) && $user->authorise('core.edit', 'com_menus');

		foreach (ModuleHelper::getModules($position) as $mod)
		{
			$moduleHtml = $renderer->render($mod, $params, $content);

			if ($frontediting && trim($moduleHtml) != '' && $user->authorise('module.edit.frontend', 'com_modules.module.' . $mod->id))
			{
				$displayData = array('moduleHtml' => &$moduleHtml, 'module' => $mod, 'position' => $position, 'menusediting' => $menusEditing);
				LayoutHelper::render('joomla.edit.frontediting_modules', $displayData);
			}

			$buffer .= $moduleHtml;
		}

		\JEventDispatcher::getInstance()->trigger('onAfterRenderModules', array(&$buffer, &$params));

		return $buffer;
	}
}
src/Document/FeedDocument.php000064400000007736152177723700012214 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\Feed\FeedImage;
use Joomla\CMS\Document\Feed\FeedItem;

/**
 * FeedDocument class, provides an easy interface to parse and display any feed document
 *
 * @since  1.7.0
 */
class FeedDocument extends Document
{
	/**
	 * Syndication URL feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $syndicationURL = '';

	/**
	 * Image feed element
	 *
	 * optional
	 *
	 * @var    FeedImage
	 * @since  1.7.0
	 */
	public $image = null;

	/**
	 * Copyright feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $copyright = '';

	/**
	 * Published date feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $pubDate = '';

	/**
	 * Lastbuild date feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $lastBuildDate = '';

	/**
	 * Editor feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $editor = '';

	/**
	 * Docs feed element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $docs = '';

	/**
	 * Editor email feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $editorEmail = '';

	/**
	 * Webmaster email feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $webmaster = '';

	/**
	 * Category feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $category = '';

	/**
	 * TTL feed attribute
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $ttl = '';

	/**
	 * Rating feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $rating = '';

	/**
	 * Skiphours feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $skipHours = '';

	/**
	 * Skipdays feed element
	 *
	 * optional
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $skipDays = '';

	/**
	 * The feed items collection
	 *
	 * @var    FeedItem[]
	 * @since  1.7.0
	 */
	public $items = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since  1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set document type
		$this->_type = 'feed';
	}

	/**
	 * Render the document
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string The rendered data
	 *
	 * @since   1.7.0
	 * @throws  \Exception
	 * @todo    Make this cacheable
	 */
	public function render($cache = false, $params = array())
	{
		// Get the feed type
		$type = \JFactory::getApplication()->input->get('type', 'rss');

		// Instantiate feed renderer and set the mime encoding
		$renderer = $this->loadRenderer(($type) ? $type : 'rss');

		if (!($renderer instanceof DocumentRenderer))
		{
			throw new \Exception(JText::_('JGLOBAL_RESOURCE_NOT_FOUND'), 404);
		}

		$this->setMimeEncoding($renderer->getContentType());

		// Output
		// Generate prolog
		$data = "<?xml version=\"1.0\" encoding=\"" . $this->_charset . "\"?>\n";
		$data .= "<!-- generator=\"" . $this->getGenerator() . "\" -->\n";

		// Generate stylesheet links
		foreach ($this->_styleSheets as $src => $attr)
		{
			$data .= "<?xml-stylesheet href=\"$src\" type=\"" . $attr['type'] . "\"?>\n";
		}

		// Render the feed
		$data .= $renderer->render();

		parent::render();

		return $data;
	}

	/**
	 * Adds a FeedItem to the feed.
	 *
	 * @param   FeedItem  $item  The feeditem to add to the feed.
	 *
	 * @return  FeedDocument  instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addItem(FeedItem $item)
	{
		$item->source = $this->link;
		$this->items[] = $item;

		return $this;
	}
}
src/Document/DocumentRenderer.php000064400000003466152177723700013113 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Uri\Uri;

/**
 * Abstract class for a renderer
 *
 * @since  1.7.0
 */
class DocumentRenderer
{
	/**
	 * Reference to the Document object that instantiated the renderer
	 *
	 * @var    Document
	 * @since  1.7.0
	 */
	protected $_doc = null;

	/**
	 * Renderer mime type
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_mime = 'text/html';

	/**
	 * Class constructor
	 *
	 * @param   Document  $doc  A reference to the Document object that instantiated the renderer
	 *
	 * @since   1.7.0
	 */
	public function __construct(Document $doc)
	{
		$this->_doc = $doc;
	}

	/**
	 * Renders a script and returns the results as a string
	 *
	 * @param   string  $name     The name of the element to render
	 * @param   array   $params   Array of values
	 * @param   string  $content  Override the output of the renderer
	 *
	 * @return  string  The output of the script
	 *
	 * @since   1.7.0
	 */
	public function render($name, $params = null, $content = null)
	{
	}

	/**
	 * Return the content type of the renderer
	 *
	 * @return  string  The contentType
	 *
	 * @since   1.7.0
	 */
	public function getContentType()
	{
		return $this->_mime;
	}

	/**
	 * Convert links in a text from relative to absolute
	 *
	 * @param   string  $text  The text processed
	 *
	 * @return  string   Text with converted links
	 *
	 * @since   1.7.0
	 */
	protected function _relToAbs($text)
	{
		$base = Uri::base();
		$text = preg_replace("/(href|src)=\"(?!http|ftp|https|mailto|data|\/\/)([^\"]*)\"/", "$1=\"$base\$2\"", $text);

		return $text;
	}
}
src/Document/OpensearchDocument.php000064400000012205152177723700013423 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Document\Opensearch\OpensearchImage;
use Joomla\CMS\Document\Opensearch\OpensearchUrl;
use Joomla\CMS\Uri\Uri;

/**
 * Opensearch class, provides an easy interface to display an Opensearch document
 *
 * @link   http://www.opensearch.org/
 * @since  1.7.0
 */
class OpensearchDocument extends Document
{
	/**
	 * ShortName element
	 *
	 * required
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	private $_shortName = '';

	/**
	 * Images collection
	 *
	 * optional
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	private $_images = array();

	/**
	 * The url collection
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	private $_urls = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since  1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set document type
		$this->_type = 'opensearch';

		// Set mime type
		$this->_mime = 'application/opensearchdescription+xml';

		// Add the URL for self updating
		$update = new OpensearchUrl;
		$update->type = 'application/opensearchdescription+xml';
		$update->rel = 'self';
		$update->template = \JRoute::_(Uri::getInstance());
		$this->addUrl($update);

		// Add the favicon as the default image
		// Try to find a favicon by checking the template and root folder
		$app = \JFactory::getApplication();
		$dirs = array(JPATH_THEMES . '/' . $app->getTemplate(), JPATH_BASE);

		foreach ($dirs as $dir)
		{
			if (file_exists($dir . '/favicon.ico'))
			{
				$path = str_replace(JPATH_BASE, '', $dir);
				$path = str_replace('\\', '/', $path);
				$favicon = new OpensearchImage;

				if ($path == '')
				{
					$favicon->data = Uri::base() . 'favicon.ico';
				}
				else
				{
					if ($path[0] == '/')
					{
						$path = substr($path, 1);
					}

					$favicon->data = Uri::base() . $path . '/favicon.ico';
				}

				$favicon->height = '16';
				$favicon->width = '16';
				$favicon->type = 'image/vnd.microsoft.icon';

				$this->addImage($favicon);

				break;
			}
		}
	}

	/**
	 * Render the document
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string  The rendered data
	 *
	 * @since   1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		$xml = new \DOMDocument('1.0', 'utf-8');

		if (defined('JDEBUG') && JDEBUG)
		{
			$xml->formatOutput = true;
		}

		// The Opensearch Namespace
		$osns = 'http://a9.com/-/spec/opensearch/1.1/';

		// Create the root element
		$elOs = $xml->createElementNs($osns, 'OpenSearchDescription');

		$elShortName = $xml->createElementNs($osns, 'ShortName');
		$elShortName->appendChild($xml->createTextNode(htmlspecialchars($this->_shortName)));
		$elOs->appendChild($elShortName);

		$elDescription = $xml->createElementNs($osns, 'Description');
		$elDescription->appendChild($xml->createTextNode(htmlspecialchars($this->description)));
		$elOs->appendChild($elDescription);

		// Always set the accepted input encoding to UTF-8
		$elInputEncoding = $xml->createElementNs($osns, 'InputEncoding');
		$elInputEncoding->appendChild($xml->createTextNode('UTF-8'));
		$elOs->appendChild($elInputEncoding);

		foreach ($this->_images as $image)
		{
			$elImage = $xml->createElementNs($osns, 'Image');
			$elImage->setAttribute('type', $image->type);
			$elImage->setAttribute('width', $image->width);
			$elImage->setAttribute('height', $image->height);
			$elImage->appendChild($xml->createTextNode(htmlspecialchars($image->data)));
			$elOs->appendChild($elImage);
		}

		foreach ($this->_urls as $url)
		{
			$elUrl = $xml->createElementNs($osns, 'Url');
			$elUrl->setAttribute('type', $url->type);

			// Results is the default value so we don't need to add it
			if ($url->rel != 'results')
			{
				$elUrl->setAttribute('rel', $url->rel);
			}

			$elUrl->setAttribute('template', $url->template);
			$elOs->appendChild($elUrl);
		}

		$xml->appendChild($elOs);
		parent::render();

		return $xml->saveXml();
	}

	/**
	 * Sets the short name
	 *
	 * @param   string  $name  The name.
	 *
	 * @return  OpensearchDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setShortName($name)
	{
		$this->_shortName = $name;

		return $this;
	}

	/**
	 * Adds a URL to the Opensearch description.
	 *
	 * @param   OpensearchUrl  $url  The url to add to the description.
	 *
	 * @return  OpensearchDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addUrl(OpensearchUrl $url)
	{
		$this->_urls[] = $url;

		return $this;
	}

	/**
	 * Adds an image to the Opensearch description.
	 *
	 * @param   OpensearchImage  $image  The image to add to the description.
	 *
	 * @return  OpensearchDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addImage(OpensearchImage $image)
	{
		$this->_images[] = $image;

		return $this;
	}
}
src/Document/HtmlDocument.php000064400000046136152177723700012252 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Uri\Uri;
use Joomla\Registry\Registry;

jimport('joomla.utilities.utility');

/**
 * HtmlDocument class, provides an easy interface to parse and display a HTML document
 *
 * @since  1.7.0
 */
class HtmlDocument extends Document
{
	/**
	 * Array of Header `<link>` tags
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_links = array();

	/**
	 * Array of custom tags
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_custom = array();

	/**
	 * Name of the template
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $template = null;

	/**
	 * Base url
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $baseurl = null;

	/**
	 * Array of template parameters
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $params = null;

	/**
	 * File name
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_file = null;

	/**
	 * String holding parsed template
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_template = '';

	/**
	 * Array of parsed template JDoc tags
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_template_tags = array();

	/**
	 * Integer with caching setting
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $_caching = null;

	/**
	 * Set to true when the document should be output as HTML5
	 *
	 * @var    boolean
	 * @since  3.0.0
	 *
	 * @note  4.0  Will be replaced by $html5 and the default value will be true.
	 */
	private $_html5 = null;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set document type
		$this->_type = 'html';

		// Set default mime type and document metadata (metadata syncs with mime type by default)
		$this->setMimeEncoding('text/html');
	}

	/**
	 * Get the HTML document head data
	 *
	 * @return  array  The document head data in array form
	 *
	 * @since   1.7.0
	 */
	public function getHeadData()
	{
		$data = array();
		$data['title']       = $this->title;
		$data['description'] = $this->description;
		$data['link']        = $this->link;
		$data['metaTags']    = $this->_metaTags;
		$data['links']       = $this->_links;
		$data['styleSheets'] = $this->_styleSheets;
		$data['style']       = $this->_style;
		$data['scripts']     = $this->_scripts;
		$data['script']      = $this->_script;
		$data['custom']      = $this->_custom;
		$data['scriptText']  = \JText::getScriptStrings();

		return $data;
	}

	/**
	 * Reset the HTML document head data
	 *
	 * @param   mixed  $types  type or types of the heads elements to reset
	 *
	 * @return  HtmlDocument  instance of $this to allow chaining
	 *
	 * @since   3.7.0
	 */
	public function resetHeadData($types = null)
	{
		if (is_null($types))
		{
			$this->title        = '';
			$this->description  = '';
			$this->link         = '';
			$this->_metaTags    = array();
			$this->_links       = array();
			$this->_styleSheets = array();
			$this->_style       = array();
			$this->_scripts     = array();
			$this->_script      = array();
			$this->_custom      = array();
		}

		if (is_array($types))
		{
			foreach ($types as $type)
			{
				$this->resetHeadDatum($type);
			}
		}

		if (is_string($types))
		{
			$this->resetHeadDatum($types);
		}

		return $this;
	}

	/**
	 * Reset a part the HTML document head data
	 *
	 * @param   string  $type  type of the heads elements to reset
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	private function resetHeadDatum($type)
	{
		switch ($type)
		{
			case 'title':
			case 'description':
			case 'link':
				$this->{$type} = '';
				break;

			case 'metaTags':
			case 'links':
			case 'styleSheets':
			case 'style':
			case 'scripts':
			case 'script':
			case 'custom':
				$realType = '_' . $type;
				$this->{$realType} = array();
				break;
		}
	}

	/**
	 * Set the HTML document head data
	 *
	 * @param   array  $data  The document head data in array form
	 *
	 * @return  HtmlDocument|null instance of $this to allow chaining or null for empty input data
	 *
	 * @since   1.7.0
	 */
	public function setHeadData($data)
	{
		if (empty($data) || !is_array($data))
		{
			return;
		}

		$this->title        = (isset($data['title']) && !empty($data['title'])) ? $data['title'] : $this->title;
		$this->description  = (isset($data['description']) && !empty($data['description'])) ? $data['description'] : $this->description;
		$this->link         = (isset($data['link']) && !empty($data['link'])) ? $data['link'] : $this->link;
		$this->_metaTags    = (isset($data['metaTags']) && !empty($data['metaTags'])) ? $data['metaTags'] : $this->_metaTags;
		$this->_links       = (isset($data['links']) && !empty($data['links'])) ? $data['links'] : $this->_links;
		$this->_styleSheets = (isset($data['styleSheets']) && !empty($data['styleSheets'])) ? $data['styleSheets'] : $this->_styleSheets;
		$this->_style       = (isset($data['style']) && !empty($data['style'])) ? $data['style'] : $this->_style;
		$this->_scripts     = (isset($data['scripts']) && !empty($data['scripts'])) ? $data['scripts'] : $this->_scripts;
		$this->_script      = (isset($data['script']) && !empty($data['script'])) ? $data['script'] : $this->_script;
		$this->_custom      = (isset($data['custom']) && !empty($data['custom'])) ? $data['custom'] : $this->_custom;

		if (isset($data['scriptText']) && !empty($data['scriptText']))
		{
			foreach ($data['scriptText'] as $key => $string)
			{
				\JText::script($key, $string);
			}
		}

		return $this;
	}

	/**
	 * Merge the HTML document head data
	 *
	 * @param   array  $data  The document head data in array form
	 *
	 * @return  HtmlDocument|null instance of $this to allow chaining or null for empty input data
	 *
	 * @since   1.7.0
	 */
	public function mergeHeadData($data)
	{
		if (empty($data) || !is_array($data))
		{
			return;
		}

		$this->title = (isset($data['title']) && !empty($data['title']) && !stristr($this->title, $data['title']))
			? $this->title . $data['title']
			: $this->title;
		$this->description = (isset($data['description']) && !empty($data['description']) && !stristr($this->description, $data['description']))
			? $this->description . $data['description']
			: $this->description;
		$this->link = (isset($data['link'])) ? $data['link'] : $this->link;

		if (isset($data['metaTags']))
		{
			foreach ($data['metaTags'] as $type1 => $data1)
			{
				$booldog = $type1 == 'http-equiv' ? true : false;

				foreach ($data1 as $name2 => $data2)
				{
					$this->setMetaData($name2, $data2, $booldog);
				}
			}
		}

		$this->_links = (isset($data['links']) && !empty($data['links']) && is_array($data['links']))
			? array_unique(array_merge($this->_links, $data['links']), SORT_REGULAR)
			: $this->_links;
		$this->_styleSheets = (isset($data['styleSheets']) && !empty($data['styleSheets']) && is_array($data['styleSheets']))
			? array_merge($this->_styleSheets, $data['styleSheets'])
			: $this->_styleSheets;

		if (isset($data['style']))
		{
			foreach ($data['style'] as $type => $stdata)
			{
				if (!isset($this->_style[strtolower($type)]) || !stristr($stdata, $this->_style[strtolower($type)]))
				{
					$this->addStyleDeclaration($stdata, $type);
				}
			}
		}

		$this->_scripts = (isset($data['scripts']) && !empty($data['scripts']) && is_array($data['scripts']))
			? array_merge($this->_scripts, $data['scripts'])
			: $this->_scripts;

		if (isset($data['script']))
		{
			foreach ($data['script'] as $type => $sdata)
			{
				if (!isset($this->_script[strtolower($type)]) || !stristr($sdata, $this->_script[strtolower($type)]))
				{
					$this->addScriptDeclaration($sdata, $type);
				}
			}
		}

		$this->_custom = (isset($data['custom']) && !empty($data['custom']) && is_array($data['custom']))
			? array_unique(array_merge($this->_custom, $data['custom']))
			: $this->_custom;

		return $this;
	}

	/**
	 * Adds `<link>` tags to the head of the document
	 *
	 * $relType defaults to 'rel' as it is the most common relation type used.
	 * ('rev' refers to reverse relation, 'rel' indicates normal, forward relation.)
	 * Typical tag: `<link href="index.php" rel="Start">`
	 *
	 * @param   string  $href      The link that is being related.
	 * @param   string  $relation  Relation of link.
	 * @param   string  $relType   Relation type attribute.  Either rel or rev (default: 'rel').
	 * @param   array   $attribs   Associative array of remaining attributes.
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addHeadLink($href, $relation, $relType = 'rel', $attribs = array())
	{
		$this->_links[$href]['relation'] = $relation;
		$this->_links[$href]['relType'] = $relType;
		$this->_links[$href]['attribs'] = $attribs;

		return $this;
	}

	/**
	 * Adds a shortcut icon (favicon)
	 *
	 * This adds a link to the icon shown in the favorites list or on
	 * the left of the url in the address bar. Some browsers display
	 * it on the tab, as well.
	 *
	 * @param   string  $href      The link that is being related.
	 * @param   string  $type      File type
	 * @param   string  $relation  Relation of link
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addFavicon($href, $type = 'image/vnd.microsoft.icon', $relation = 'shortcut icon')
	{
		$href = str_replace('\\', '/', $href);
		$this->addHeadLink($href, $relation, 'rel', array('type' => $type));

		return $this;
	}

	/**
	 * Adds a custom HTML string to the head block
	 *
	 * @param   string  $html  The HTML to add to the head
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addCustomTag($html)
	{
		$this->_custom[] = trim($html);

		return $this;
	}

	/**
	 * Returns whether the document is set up to be output as HTML5
	 *
	 * @return  boolean true when HTML5 is used
	 *
	 * @since   3.0.0
	 */
	public function isHtml5()
	{
		return $this->_html5;
	}

	/**
	 * Sets whether the document should be output as HTML5
	 *
	 * @param   bool  $state  True when HTML5 should be output
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function setHtml5($state)
	{
		if (is_bool($state))
		{
			$this->_html5 = $state;
		}
	}

	/**
	 * Get the contents of a document include
	 *
	 * @param   string  $type     The type of renderer
	 * @param   string  $name     The name of the element to render
	 * @param   array   $attribs  Associative array of remaining attributes.
	 *
	 * @return  mixed|string The output of the renderer
	 *
	 * @since   1.7.0
	 */
	public function getBuffer($type = null, $name = null, $attribs = array())
	{
		// If no type is specified, return the whole buffer
		if ($type === null)
		{
			return parent::$_buffer;
		}

		$title = (isset($attribs['title'])) ? $attribs['title'] : null;

		if (isset(parent::$_buffer[$type][$name][$title]))
		{
			return parent::$_buffer[$type][$name][$title];
		}

		$renderer = $this->loadRenderer($type);

		if ($this->_caching == true && $type == 'modules')
		{
			$cache = \JFactory::getCache('com_modules', '');
			$hash = md5(serialize(array($name, $attribs, null, $renderer)));
			$cbuffer = $cache->get('cbuffer_' . $type);

			if (isset($cbuffer[$hash]))
			{
				return Cache::getWorkarounds($cbuffer[$hash], array('mergehead' => 1));
			}
			else
			{
				$options = array();
				$options['nopathway'] = 1;
				$options['nomodules'] = 1;
				$options['modulemode'] = 1;

				$this->setBuffer($renderer->render($name, $attribs, null), $type, $name);
				$data = parent::$_buffer[$type][$name][$title];

				$tmpdata = Cache::setWorkarounds($data, $options);

				$cbuffer[$hash] = $tmpdata;

				$cache->store($cbuffer, 'cbuffer_' . $type);
			}
		}
		else
		{
			$this->setBuffer($renderer->render($name, $attribs, null), $type, $name, $title);
		}

		return parent::$_buffer[$type][$name][$title];
	}

	/**
	 * Set the contents a document includes
	 *
	 * @param   string  $content  The content to be set in the buffer.
	 * @param   array   $options  Array of optional elements.
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setBuffer($content, $options = array())
	{
		// The following code is just for backward compatibility.
		if (func_num_args() > 1 && !is_array($options))
		{
			$args = func_get_args();
			$options = array();
			$options['type'] = $args[1];
			$options['name'] = (isset($args[2])) ? $args[2] : null;
			$options['title'] = (isset($args[3])) ? $args[3] : null;
		}

		parent::$_buffer[$options['type']][$options['name']][$options['title']] = $content;

		return $this;
	}

	/**
	 * Parses the template and populates the buffer
	 *
	 * @param   array  $params  Parameters for fetching the template
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function parse($params = array())
	{
		return $this->_fetchTemplate($params)->_parseTemplate();
	}

	/**
	 * Outputs the template to the browser.
	 *
	 * @param   boolean  $caching  If true, cache the output
	 * @param   array    $params   Associative array of attributes
	 *
	 * @return  string The rendered data
	 *
	 * @since   1.7.0
	 */
	public function render($caching = false, $params = array())
	{
		$this->_caching = $caching;

		if (empty($this->_template))
		{
			$this->parse($params);
		}

		$data = $this->_renderTemplate();
		parent::render();

		return $data;
	}

	/**
	 * Count the modules based on the given condition
	 *
	 * @param   string  $condition  The condition to use
	 *
	 * @return  integer  Number of modules found
	 *
	 * @since   1.7.0
	 */
	public function countModules($condition)
	{
		$operators = '(\+|\-|\*|\/|==|\!=|\<\>|\<|\>|\<=|\>=|and|or|xor)';
		$words = preg_split('# ' . $operators . ' #', $condition, null, PREG_SPLIT_DELIM_CAPTURE);

		if (count($words) === 1)
		{
			$name = strtolower($words[0]);
			$result = ((isset(parent::$_buffer['modules'][$name])) && (parent::$_buffer['modules'][$name] === false))
				? 0 : count(ModuleHelper::getModules($name));

			return $result;
		}

		Log::add('Using an expression in HtmlDocument::countModules() is deprecated.', Log::WARNING, 'deprecated');

		for ($i = 0, $n = count($words); $i < $n; $i += 2)
		{
			// Odd parts (modules)
			$name = strtolower($words[$i]);
			$words[$i] = ((isset(parent::$_buffer['modules'][$name])) && (parent::$_buffer['modules'][$name] === false))
				? 0
				: count(ModuleHelper::getModules($name));
		}

		$str = 'return ' . implode(' ', $words) . ';';

		return eval($str);
	}

	/**
	 * Count the number of child menu items of the current active menu item
	 *
	 * @return  integer  Number of child menu items
	 *
	 * @since   1.7.0
	 */
	public function countMenuChildren()
	{
		static $children;

		if (!isset($children))
		{
			$db = \JFactory::getDbo();
			$app = \JFactory::getApplication();
			$menu = $app->getMenu();
			$active = $menu->getActive();
			$children = 0;

			if ($active)
			{
				$query = $db->getQuery(true)
					->select('COUNT(*)')
					->from('#__menu')
					->where('parent_id = ' . $active->id)
					->where('published = 1');
				$db->setQuery($query);
				$children = $db->loadResult();
			}
		}

		return $children;
	}

	/**
	 * Load a template file
	 *
	 * @param   string  $directory  The name of the template
	 * @param   string  $filename   The actual filename
	 *
	 * @return  string  The contents of the template
	 *
	 * @since   1.7.0
	 */
	protected function _loadTemplate($directory, $filename)
	{
		$contents = '';

		// Check to see if we have a valid template file
		if (file_exists($directory . '/' . $filename))
		{
			// Store the file path
			$this->_file = $directory . '/' . $filename;

			// Get the file content
			ob_start();
			require $directory . '/' . $filename;
			$contents = ob_get_contents();
			ob_end_clean();
		}

		// Try to find a favicon by checking the template and root folder
		$icon = '/favicon.ico';

		foreach (array($directory, JPATH_BASE) as $dir)
		{
			if (file_exists($dir . $icon))
			{
				$path = str_replace(JPATH_BASE, '', $dir);
				$path = str_replace('\\', '/', $path);
				$this->addFavicon(Uri::base(true) . $path . $icon);
				break;
			}
		}

		return $contents;
	}

	/**
	 * Fetch the template, and initialise the params
	 *
	 * @param   array  $params  Parameters to determine the template
	 *
	 * @return  HtmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	protected function _fetchTemplate($params = array())
	{
		// Check
		$directory = isset($params['directory']) ? $params['directory'] : 'templates';
		$filter = \JFilterInput::getInstance();
		$template = $filter->clean($params['template'], 'cmd');
		$file = $filter->clean($params['file'], 'cmd');

		if (!file_exists($directory . '/' . $template . '/' . $file))
		{
			$template = 'system';
		}

		if (!file_exists($directory . '/' . $template . '/' . $file))
		{
			$file = 'index.php';
		}

		// Load the language file for the template
		$lang = \JFactory::getLanguage();

		// 1.5 or core then 1.6
		$lang->load('tpl_' . $template, JPATH_BASE, null, false, true)
			|| $lang->load('tpl_' . $template, $directory . '/' . $template, null, false, true);

		// Assign the variables
		$this->template = $template;
		$this->baseurl = Uri::base(true);
		$this->params = isset($params['params']) ? $params['params'] : new Registry;

		// Load
		$this->_template = $this->_loadTemplate($directory . '/' . $template, $file);

		return $this;
	}

	/**
	 * Parse a document template
	 *
	 * @return  HtmlDocument  instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	protected function _parseTemplate()
	{
		$matches = array();

		if (preg_match_all('#<jdoc:include\ type="([^"]+)"(.*)\/>#iU', $this->_template, $matches))
		{
			$template_tags_first = array();
			$template_tags_last = array();

			// Step through the jdocs in reverse order.
			for ($i = count($matches[0]) - 1; $i >= 0; $i--)
			{
				$type = $matches[1][$i];
				$attribs = empty($matches[2][$i]) ? array() : \JUtility::parseAttributes($matches[2][$i]);
				$name = isset($attribs['name']) ? $attribs['name'] : null;

				// Separate buffers to be executed first and last
				if ($type == 'module' || $type == 'modules')
				{
					$template_tags_first[$matches[0][$i]] = array('type' => $type, 'name' => $name, 'attribs' => $attribs);
				}
				else
				{
					$template_tags_last[$matches[0][$i]] = array('type' => $type, 'name' => $name, 'attribs' => $attribs);
				}
			}

			// Reverse the last array so the jdocs are in forward order.
			$template_tags_last = array_reverse($template_tags_last);

			$this->_template_tags = $template_tags_first + $template_tags_last;
		}

		return $this;
	}

	/**
	 * Render pre-parsed template
	 *
	 * @return string rendered template
	 *
	 * @since   1.7.0
	 */
	protected function _renderTemplate()
	{
		$replace = array();
		$with = array();

		foreach ($this->_template_tags as $jdoc => $args)
		{
			$replace[] = $jdoc;
			$with[] = $this->getBuffer($args['type'], $args['name'], $args['attribs']);
		}

		return str_replace($replace, $with, $this->_template);
	}
}
src/Document/ErrorDocument.php000064400000011272152177723700012430 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Uri\Uri;

/**
 * ErrorDocument class, provides an easy interface to parse and display an error page
 *
 * @since  1.7.0
 */
class ErrorDocument extends Document
{
	/**
	 * Document base URL
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $baseurl = '';

	/**
	 * Flag if debug mode has been enabled
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	public $debug = false;

	/**
	 * Error Object
	 *
	 * @var    \Exception|\Throwable
	 * @since  1.7.0
	 */
	public $error;

	/**
	 * Name of the template
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $template = null;

	/**
	 * File name
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_file = null;

	/**
	 * Error Object
	 *
	 * @var    \Exception|\Throwable
	 * @since  1.7.0
	 */
	protected $_error;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of attributes
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set mime type
		$this->_mime = 'text/html';

		// Set document type
		$this->_type = 'error';
	}

	/**
	 * Set error object
	 *
	 * @param   \Exception|\Throwable  $error  Error object to set
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function setError($error)
	{
		$expectedClass = PHP_MAJOR_VERSION >= 7 ? '\\Throwable' : '\\Exception';

		if ($error instanceof $expectedClass)
		{
			$this->_error = & $error;

			return true;
		}

		return false;
	}

	/**
	 * Render the document
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string   The rendered data
	 *
	 * @since   1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		// If no error object is set return null
		if (!isset($this->_error))
		{
			return;
		}

		// Set the status header
		$status = $this->_error->getCode();

		if ($status < 400 || $status > 599)
		{
			$status = 500;
		}

		$errorReporting = \JFactory::getConfig()->get('error_reporting');

		if ($errorReporting === "development" || $errorReporting === "maximum")
		{
			$status .= ' ' . str_replace("\n", ' ', $this->_error->getMessage());
		}

		\JFactory::getApplication()->setHeader('status', $status);

		$file = 'error.php';

		// Check template
		$directory = isset($params['directory']) ? $params['directory'] : 'templates';
		$template = isset($params['template']) ? \JFilterInput::getInstance()->clean($params['template'], 'cmd') : 'system';

		if (!file_exists($directory . '/' . $template . '/' . $file))
		{
			$template = 'system';
		}

		// Set variables
		$this->baseurl = Uri::base(true);
		$this->template = $template;
		$this->debug = isset($params['debug']) ? $params['debug'] : false;
		$this->error = $this->_error;

		// Load the language file for the template if able
		if (\JFactory::$language)
		{
			$lang = \JFactory::getLanguage();

			// 1.5 or core then 1.6
			$lang->load('tpl_' . $template, JPATH_BASE, null, false, true)
				|| $lang->load('tpl_' . $template, $directory . '/' . $template, null, false, true);
		}

		// Load
		$data = $this->_loadTemplate($directory . '/' . $template, $file);

		parent::render();

		return $data;
	}

	/**
	 * Load a template file
	 *
	 * @param   string  $directory  The name of the template
	 * @param   string  $filename   The actual filename
	 *
	 * @return  string  The contents of the template
	 *
	 * @since   1.7.0
	 */
	public function _loadTemplate($directory, $filename)
	{
		$contents = '';

		// Check to see if we have a valid template file
		if (file_exists($directory . '/' . $filename))
		{
			// Store the file path
			$this->_file = $directory . '/' . $filename;

			// Get the file content
			ob_start();
			require_once $directory . '/' . $filename;
			$contents = ob_get_contents();
			ob_end_clean();
		}

		return $contents;
	}

	/**
	 * Render the backtrace
	 *
	 * @return  string  The contents of the backtrace
	 *
	 * @since   1.7.0
	 */
	public function renderBacktrace()
	{
		// If no error object is set return null
		if (!isset($this->_error))
		{
			return;
		}

		// The back trace
		$backtrace = $this->_error->getTrace();

		// Add the position of the actual file
		array_unshift($backtrace, array('file' => $this->_error->getFile(), 'line' => $this->_error->getLine(), 'function' => ''));

		return LayoutHelper::render('joomla.error.backtrace', array('backtrace' => $backtrace));
	}
}
src/Document/Document.php000064400000063776152177723700011436 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Date\Date;

/**
 * Document class, provides an easy interface to parse and display a document
 *
 * @since  1.7.0
 */
class Document
{
	/**
	 * Document title
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $title = '';

	/**
	 * Document description
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $description = '';

	/**
	 * Document full URL
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $link = '';

	/**
	 * Document base URL
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $base = '';

	/**
	 * Contains the document language setting
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $language = 'en-gb';

	/**
	 * Contains the document direction setting
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $direction = 'ltr';

	/**
	 * Document generator
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_generator = 'Joomla! - Open Source Content Management';

	/**
	 * Document modified date
	 *
	 * @var    string|Date
	 * @since  1.7.0
	 */
	public $_mdate = '';

	/**
	 * Tab string
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_tab = "\11";

	/**
	 * Contains the line end string
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_lineEnd = "\12";

	/**
	 * Contains the character encoding string
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_charset = 'utf-8';

	/**
	 * Document mime type
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_mime = '';

	/**
	 * Document namespace
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_namespace = '';

	/**
	 * Document profile
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_profile = '';

	/**
	 * Array of linked scripts
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_scripts = array();

	/**
	 * Array of scripts placed in the header
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_script = array();

	/**
	 * Array of scripts options
	 *
	 * @var    array
	 */
	protected $scriptOptions = array();

	/**
	 * Array of linked style sheets
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_styleSheets = array();

	/**
	 * Array of included style declarations
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_style = array();

	/**
	 * Array of meta tags
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_metaTags = array();

	/**
	 * The rendering engine
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	public $_engine = null;

	/**
	 * The document type
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_type = null;

	/**
	 * Array of buffered output
	 *
	 * @var    mixed (depends on the renderer)
	 * @since  1.7.0
	 */
	public static $_buffer = null;

	/**
	 * Document instances container.
	 *
	 * @var    array
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Media version added to assets
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $mediaVersion = null;

	/**
	 * Class constructor.
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		if (array_key_exists('lineend', $options))
		{
			$this->setLineEnd($options['lineend']);
		}

		if (array_key_exists('charset', $options))
		{
			$this->setCharset($options['charset']);
		}

		if (array_key_exists('language', $options))
		{
			$this->setLanguage($options['language']);
		}

		if (array_key_exists('direction', $options))
		{
			$this->setDirection($options['direction']);
		}

		if (array_key_exists('tab', $options))
		{
			$this->setTab($options['tab']);
		}

		if (array_key_exists('link', $options))
		{
			$this->setLink($options['link']);
		}

		if (array_key_exists('base', $options))
		{
			$this->setBase($options['base']);
		}

		if (array_key_exists('mediaversion', $options))
		{
			$this->setMediaVersion($options['mediaversion']);
		}
	}

	/**
	 * Returns the global Document object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $type        The document type to instantiate
	 * @param   array   $attributes  Array of attributes
	 *
	 * @return  object  The document object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($type = 'html', $attributes = array())
	{
		$signature = serialize(array($type, $attributes));

		if (empty(self::$instances[$signature]))
		{
			$type  = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
			$ntype = null;

			// Determine the path and class
			$class = __NAMESPACE__ . '\\' . ucfirst($type) . 'Document';

			if (!class_exists($class))
			{
				$class = 'JDocument' . ucfirst($type);
			}

			if (!class_exists($class))
			{
				// @deprecated 4.0 - Document objects should be autoloaded instead
				$path = __DIR__ . '/' . $type . '/' . $type . '.php';

				\JLoader::register($class, $path);

				if (class_exists($class))
				{
					\JLog::add('Non-autoloadable Document subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
				}
				// Default to the raw format
				else
				{
					$ntype = $type;
					$class = 'JDocumentRaw';
				}
			}

			$instance = new $class($attributes);
			self::$instances[$signature] = $instance;

			if (!is_null($ntype))
			{
				// Set the type to the Document type originally requested
				$instance->setType($ntype);
			}
		}

		return self::$instances[$signature];
	}

	/**
	 * Set the document type
	 *
	 * @param   string  $type  Type document is to set to
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setType($type)
	{
		$this->_type = $type;

		return $this;
	}

	/**
	 * Returns the document type
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getType()
	{
		return $this->_type;
	}

	/**
	 * Get the contents of the document buffer
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function getBuffer()
	{
		return self::$_buffer;
	}

	/**
	 * Set the contents of the document buffer
	 *
	 * @param   string  $content  The content to be set in the buffer.
	 * @param   array   $options  Array of optional elements.
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setBuffer($content, $options = array())
	{
		self::$_buffer = $content;

		return $this;
	}

	/**
	 * Gets a meta tag.
	 *
	 * @param   string  $name       Name of the meta HTML tag
	 * @param   string  $attribute  Attribute to use in the meta HTML tag
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getMetaData($name, $attribute = 'name')
	{
		// B/C old http_equiv parameter.
		if (!is_string($attribute))
		{
			$attribute = $attribute == true ? 'http-equiv' : 'name';
		}

		if ($name == 'generator')
		{
			$result = $this->getGenerator();
		}
		elseif ($name == 'description')
		{
			$result = $this->getDescription();
		}
		else
		{
			$result = isset($this->_metaTags[$attribute]) && isset($this->_metaTags[$attribute][$name]) ? $this->_metaTags[$attribute][$name] : '';
		}

		return $result;
	}

	/**
	 * Sets or alters a meta tag.
	 *
	 * @param   string  $name       Name of the meta HTML tag
	 * @param   mixed   $content    Value of the meta HTML tag as array or string
	 * @param   string  $attribute  Attribute to use in the meta HTML tag
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setMetaData($name, $content, $attribute = 'name')
	{
		// Pop the element off the end of array if target function expects a string or this http_equiv parameter.
		if (is_array($content) && (in_array($name, array('generator', 'description')) || !is_string($attribute)))
		{
			$content = array_pop($content);
		}

		// B/C old http_equiv parameter.
		if (!is_string($attribute))
		{
			$attribute = $attribute == true ? 'http-equiv' : 'name';
		}

		if ($name == 'generator')
		{
			$this->setGenerator($content);
		}
		elseif ($name == 'description')
		{
			$this->setDescription($content);
		}
		else
		{
			$this->_metaTags[$attribute][$name] = $content;
		}

		return $this;
	}

	/**
	 * Adds a linked script to the page
	 *
	 * @param   string  $url      URL to the linked script.
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 * @deprecated 4.0  The (url, mime, defer, async) method signature is deprecated, use (url, options, attributes) instead.
	 */
	public function addScript($url, $options = array(), $attribs = array())
	{
		// B/C before 3.7.0
		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
		{
			\JLog::add('The addScript method signature used has changed, use (url, options, attributes) instead.', \JLog::WARNING, 'deprecated');

			$argList = func_get_args();
			$options = array();
			$attribs = array();

			// Old mime type parameter.
			if (!empty($argList[1]))
			{
				$attribs['mime'] = $argList[1];
			}

			// Old defer parameter.
			if (isset($argList[2]) && $argList[2])
			{
				$attribs['defer'] = true;
			}

			// Old async parameter.
			if (isset($argList[3]) && $argList[3])
			{
				$attribs['async'] = true;
			}
		}

		// Default value for type.
		if (!isset($attribs['type']) && !isset($attribs['mime']))
		{
			$attribs['type'] = 'text/javascript';
		}

		$this->_scripts[$url]            = isset($this->_scripts[$url]) ? array_replace($this->_scripts[$url], $attribs) : $attribs;
		$this->_scripts[$url]['options'] = isset($this->_scripts[$url]['options']) ? array_replace($this->_scripts[$url]['options'], $options) : $options;

		return $this;
	}

	/**
	 * Adds a linked script to the page with a version to allow to flush it. Ex: myscript.js?54771616b5bceae9df03c6173babf11d
	 * If not specified Joomla! automatically handles versioning
	 *
	 * @param   string  $url      URL to the linked script.
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   3.2
	 * @deprecated 4.0  This method is deprecated, use addScript(url, options, attributes) instead.
	 */
	public function addScriptVersion($url, $options = array(), $attribs = array())
	{
		\JLog::add('The method is deprecated, use addScript(url, attributes, options) instead.', \JLog::WARNING, 'deprecated');

		// B/C before 3.7.0
		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
		{
			$argList = func_get_args();
			$options = array();
			$attribs = array();

			// Old version parameter.
			$options['version'] = isset($argList[1]) && !is_null($argList[1]) ? $argList[1] : 'auto';

			// Old mime type parameter.
			if (!empty($argList[2]))
			{
				$attribs['mime'] = $argList[2];
			}

			// Old defer parameter.
			if (isset($argList[3]) && $argList[3])
			{
				$attribs['defer'] = true;
			}

			// Old async parameter.
			if (isset($argList[4]) && $argList[4])
			{
				$attribs['async'] = true;
			}
		}
		// Default value for version.
		else
		{
			$options['version'] = 'auto';
		}

		return $this->addScript($url, $options, $attribs);
	}

	/**
	 * Adds a script to the page
	 *
	 * @param   string  $content  Script
	 * @param   string  $type     Scripting mime (defaults to 'text/javascript')
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addScriptDeclaration($content, $type = 'text/javascript')
	{
		if (!isset($this->_script[strtolower($type)]))
		{
			$this->_script[strtolower($type)] = $content;
		}
		else
		{
			$this->_script[strtolower($type)] .= chr(13) . $content;
		}

		return $this;
	}

	/**
	 * Add option for script
	 *
	 * @param   string  $key      Name in Storage
	 * @param   mixed   $options  Scrip options as array or string
	 * @param   bool    $merge    Whether merge with existing (true) or replace (false)
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   3.5
	 */
	public function addScriptOptions($key, $options, $merge = true)
	{
		if (empty($this->scriptOptions[$key]))
		{
			$this->scriptOptions[$key] = array();
		}

		if ($merge && is_array($options))
		{
			$this->scriptOptions[$key] = array_replace_recursive($this->scriptOptions[$key], $options);
		}
		else
		{
			$this->scriptOptions[$key] = $options;
		}

		return $this;
	}

	/**
	 * Get script(s) options
	 *
	 * @param   string  $key  Name in Storage
	 *
	 * @return  array  Options for given $key, or all script options
	 *
	 * @since   3.5
	 */
	public function getScriptOptions($key = null)
	{
		if ($key)
		{
			return (empty($this->scriptOptions[$key])) ? array() : $this->scriptOptions[$key];
		}
		else
		{
			return $this->scriptOptions;
		}
	}

	/**
	 * Adds a linked stylesheet to the page
	 *
	 * @param   string  $url      URL to the linked style sheet
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 * @deprecated 4.0  The (url, mime, media, attribs) method signature is deprecated, use (url, options, attributes) instead.
	 */
	public function addStyleSheet($url, $options = array(), $attribs = array())
	{
		// B/C before 3.7.0
		if (is_string($options))
		{
			\JLog::add('The addStyleSheet method signature used has changed, use (url, options, attributes) instead.', \JLog::WARNING, 'deprecated');

			$argList = func_get_args();
			$options = array();
			$attribs = array();

			// Old mime type parameter.
			if (!empty($argList[1]))
			{
				$attribs['type'] = $argList[1];
			}

			// Old media parameter.
			if (isset($argList[2]) && $argList[2])
			{
				$attribs['media'] = $argList[2];
			}

			// Old attribs parameter.
			if (isset($argList[3]) && $argList[3])
			{
				$attribs = array_replace($attribs, $argList[3]);
			}
		}

		// Default value for type.
		if (!isset($attribs['type']) && !isset($attribs['mime']))
		{
			$attribs['type'] = 'text/css';
		}

		$this->_styleSheets[$url] = isset($this->_styleSheets[$url]) ? array_replace($this->_styleSheets[$url], $attribs) : $attribs;

		if (isset($this->_styleSheets[$url]['options']))
		{
			$this->_styleSheets[$url]['options'] = array_replace($this->_styleSheets[$url]['options'], $options);
		}
		else
		{
			$this->_styleSheets[$url]['options'] = $options;
		}

		return $this;
	}

	/**
	 * Adds a linked stylesheet version to the page. Ex: template.css?54771616b5bceae9df03c6173babf11d
	 * If not specified Joomla! automatically handles versioning
	 *
	 * @param   string  $url      URL to the linked style sheet
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'stylesheet', 'data-test' => 1)
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   3.2
	 * @deprecated 4.0  This method is deprecated, use addStyleSheet(url, options, attributes) instead.
	 */
	public function addStyleSheetVersion($url, $options = array(), $attribs = array())
	{
		\JLog::add('The method is deprecated, use addStyleSheet(url, attributes, options) instead.', \JLog::WARNING, 'deprecated');

		// B/C before 3.7.0
		if (!is_array($options) && (!is_array($attribs) || $attribs === array()))
		{
			$argList = func_get_args();
			$options = array();
			$attribs = array();

			// Old version parameter.
			$options['version'] = isset($argList[1]) && !is_null($argList[1]) ? $argList[1] : 'auto';

			// Old mime type parameter.
			if (!empty($argList[2]))
			{
				$attribs['mime'] = $argList[2];
			}

			// Old media parameter.
			if (isset($argList[3]) && $argList[3])
			{
				$attribs['media'] = $argList[3];
			}

			// Old attribs parameter.
			if (isset($argList[4]) && $argList[4])
			{
				$attribs = array_replace($attribs, $argList[4]);
			}
		}
		// Default value for version.
		else
		{
			$options['version'] = 'auto';
		}

		return $this->addStyleSheet($url, $options, $attribs);
	}

	/**
	 * Adds a stylesheet declaration to the page
	 *
	 * @param   string  $content  Style declarations
	 * @param   string  $type     Type of stylesheet (defaults to 'text/css')
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function addStyleDeclaration($content, $type = 'text/css')
	{
		if (!isset($this->_style[strtolower($type)]))
		{
			$this->_style[strtolower($type)] = $content;
		}
		else
		{
			$this->_style[strtolower($type)] .= chr(13) . $content;
		}

		return $this;
	}

	/**
	 * Sets the document charset
	 *
	 * @param   string  $type  Charset encoding string
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setCharset($type = 'utf-8')
	{
		$this->_charset = $type;

		return $this;
	}

	/**
	 * Returns the document charset encoding.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getCharset()
	{
		return $this->_charset;
	}

	/**
	 * Sets the global document language declaration. Default is English (en-gb).
	 *
	 * @param   string  $lang  The language to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setLanguage($lang = 'en-gb')
	{
		$this->language = strtolower($lang);

		return $this;
	}

	/**
	 * Returns the document language.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getLanguage()
	{
		return $this->language;
	}

	/**
	 * Sets the global document direction declaration. Default is left-to-right (ltr).
	 *
	 * @param   string  $dir  The language direction to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setDirection($dir = 'ltr')
	{
		$this->direction = strtolower($dir);

		return $this;
	}

	/**
	 * Returns the document direction declaration.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getDirection()
	{
		return $this->direction;
	}

	/**
	 * Sets the title of the document
	 *
	 * @param   string  $title  The title to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setTitle($title)
	{
		$this->title = $title;

		return $this;
	}

	/**
	 * Return the title of the document.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getTitle()
	{
		return $this->title;
	}

	/**
	 * Set the assets version
	 *
	 * @param   string  $mediaVersion  Media version to use
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   3.2
	 */
	public function setMediaVersion($mediaVersion)
	{
		$this->mediaVersion = strtolower($mediaVersion);

		return $this;
	}

	/**
	 * Return the media version
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getMediaVersion()
	{
		return $this->mediaVersion;
	}

	/**
	 * Sets the base URI of the document
	 *
	 * @param   string  $base  The base URI to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setBase($base)
	{
		$this->base = $base;

		return $this;
	}

	/**
	 * Return the base URI of the document.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getBase()
	{
		return $this->base;
	}

	/**
	 * Sets the description of the document
	 *
	 * @param   string  $description  The description to set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setDescription($description)
	{
		$this->description = $description;

		return $this;
	}

	/**
	 * Return the description of the document.
	 *
	 * @return  string
	 *
	 * @since    1.7.0
	 */
	public function getDescription()
	{
		return $this->description;
	}

	/**
	 * Sets the document link
	 *
	 * @param   string  $url  A url
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setLink($url)
	{
		$this->link = $url;

		return $this;
	}

	/**
	 * Returns the document base url
	 *
	 * @return string
	 *
	 * @since   1.7.0
	 */
	public function getLink()
	{
		return $this->link;
	}

	/**
	 * Sets the document generator
	 *
	 * @param   string  $generator  The generator to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setGenerator($generator)
	{
		$this->_generator = $generator;

		return $this;
	}

	/**
	 * Returns the document generator
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getGenerator()
	{
		return $this->_generator;
	}

	/**
	 * Sets the document modified date
	 *
	 * @param   string|Date  $date  The date to be set
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 */
	public function setModifiedDate($date)
	{
		if (!is_string($date) && !($date instanceof Date))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'The $date parameter of %1$s must be a string or a %2$s instance, a %3$s was given.',
					__METHOD__ . '()',
					'Joomla\\CMS\\Date\\Date',
					gettype($date) === 'object' ? (get_class($date) . ' instance') : gettype($date)
				)
			);
		}

		$this->_mdate = $date;

		return $this;
	}

	/**
	 * Returns the document modified date
	 *
	 * @return  string|Date
	 *
	 * @since   1.7.0
	 */
	public function getModifiedDate()
	{
		return $this->_mdate;
	}

	/**
	 * Sets the document MIME encoding that is sent to the browser.
	 *
	 * This usually will be text/html because most browsers cannot yet
	 * accept the proper mime settings for XHTML: application/xhtml+xml
	 * and to a lesser extent application/xml and text/xml. See the W3C note
	 * ({@link http://www.w3.org/TR/xhtml-media-types/
	 * http://www.w3.org/TR/xhtml-media-types/}) for more details.
	 *
	 * @param   string   $type  The document type to be sent
	 * @param   boolean  $sync  Should the type be synced with HTML?
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 *
	 * @link    http://www.w3.org/TR/xhtml-media-types
	 */
	public function setMimeEncoding($type = 'text/html', $sync = true)
	{
		$this->_mime = strtolower($type);

		// Syncing with metadata
		if ($sync)
		{
			$this->setMetaData('content-type', $type . '; charset=' . $this->_charset, true);
		}

		return $this;
	}

	/**
	 * Return the document MIME encoding that is sent to the browser.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function getMimeEncoding()
	{
		return $this->_mime;
	}

	/**
	 * Sets the line end style to Windows, Mac, Unix or a custom string.
	 *
	 * @param   string  $style  "win", "mac", "unix" or custom string.
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setLineEnd($style)
	{
		switch ($style)
		{
			case 'win':
				$this->_lineEnd = "\15\12";
				break;
			case 'unix':
				$this->_lineEnd = "\12";
				break;
			case 'mac':
				$this->_lineEnd = "\15";
				break;
			default:
				$this->_lineEnd = $style;
		}

		return $this;
	}

	/**
	 * Returns the lineEnd
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function _getLineEnd()
	{
		return $this->_lineEnd;
	}

	/**
	 * Sets the string used to indent HTML
	 *
	 * @param   string  $string  String used to indent ("\11", "\t", '  ', etc.).
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setTab($string)
	{
		$this->_tab = $string;

		return $this;
	}

	/**
	 * Returns a string containing the unit for indenting HTML
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function _getTab()
	{
		return $this->_tab;
	}

	/**
	 * Load a renderer
	 *
	 * @param   string  $type  The renderer type
	 *
	 * @return  DocumentRenderer
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function loadRenderer($type)
	{
		// Determine the path and class
		$class = __NAMESPACE__ . '\\Renderer\\' . ucfirst($this->getType()) . '\\' . ucfirst($type) . 'Renderer';

		if (!class_exists($class))
		{
			$class = 'JDocumentRenderer' . ucfirst($this->getType()) . ucfirst($type);
		}

		if (!class_exists($class))
		{
			// "Legacy" class name structure
			$class = 'JDocumentRenderer' . $type;

			if (!class_exists($class))
			{
				// @deprecated 4.0 - Non-autoloadable class support is deprecated, only log a message though if a file is found
				$path = __DIR__ . '/' . $this->getType() . '/renderer/' . $type . '.php';

				if (!file_exists($path))
				{
					throw new \RuntimeException('Unable to load renderer class', 500);
				}

				\JLoader::register($class, $path);

				\JLog::add('Non-autoloadable JDocumentRenderer subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');

				// If the class still doesn't exist after including the path, we've got issues
				if (!class_exists($class))
				{
					throw new \RuntimeException('Unable to load renderer class', 500);
				}
			}
		}

		return new $class($this);
	}

	/**
	 * Parses the document and prepares the buffers
	 *
	 * @param   array  $params  The array of parameters
	 *
	 * @return  Document instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function parse($params = array())
	{
		return $this;
	}

	/**
	 * Outputs the document
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  void  The rendered data
	 *
	 * @since   1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		$app = \JFactory::getApplication();

		if ($mdate = $this->getModifiedDate())
		{
			if (!($mdate instanceof Date))
			{
				$mdate = new Date($mdate);
			}

			$app->modifiedDate = $mdate;
		}

		$app->mimeType = $this->_mime;
		$app->charSet  = $this->_charset;
	}
}
src/Document/JsonDocument.php000064400000004147152177723700012253 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

/**
 * JsonDocument class, provides an easy interface to parse and display JSON output
 *
 * @link   http://www.json.org/
 * @since  1.7.0
 */
class JsonDocument extends Document
{
	/**
	 * Document name
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_name = 'joomla';

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since  1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set mime type
		if (isset($_SERVER['HTTP_ACCEPT'])
			&& strpos($_SERVER['HTTP_ACCEPT'], 'application/json') === false
			&& strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false)
		{
			// Internet Explorer < 10
			$this->_mime = 'text/plain';
		}
		else
		{
			$this->_mime = 'application/json';
		}

		// Set document type
		$this->_type = 'json';
	}

	/**
	 * Render the document.
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string  The rendered data
	 *
	 * @since  1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		$app = \JFactory::getApplication();

		$app->allowCache(false);

		if ($this->_mime == 'application/json')
		{
			// Browser other than Internet Explorer < 10
			$app->setHeader('Content-Disposition', 'attachment; filename="' . $this->getName() . '.json"', true);
		}

		parent::render();

		return $this->getBuffer();
	}

	/**
	 * Returns the document name
	 *
	 * @return  string
	 *
	 * @since  1.7.0
	 */
	public function getName()
	{
		return $this->_name;
	}

	/**
	 * Sets the document name
	 *
	 * @param   string  $name  Document name
	 *
	 * @return  JsonDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setName($name = 'joomla')
	{
		$this->_name = $name;

		return $this;
	}
}
src/Document/XmlDocument.php000064400000004736152177723700012106 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Document;

defined('JPATH_PLATFORM') or die;

/**
 * XmlDocument class, provides an easy interface to parse and display XML output
 *
 * @since  1.7.0
 */
class XmlDocument extends Document
{
	/**
	 * Document name
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $name = 'joomla';

	/**
	 * Flag indicating the document should be downloaded (Content-Disposition = attachment) versus displayed inline
	 *
	 * @var    boolean
	 * @since  3.9.0
	 */
	protected $isDownload = false;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Associative array of options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		// Set mime type
		$this->_mime = 'application/xml';

		// Set document type
		$this->_type = 'xml';
	}

	/**
	 * Render the document.
	 *
	 * @param   boolean  $cache   If true, cache the output
	 * @param   array    $params  Associative array of attributes
	 *
	 * @return  string  The rendered data
	 *
	 * @since  1.7.0
	 */
	public function render($cache = false, $params = array())
	{
		parent::render();

		$disposition = $this->isDownload ? 'attachment' : 'inline';

		\JFactory::getApplication()->setHeader('Content-disposition', $disposition . '; filename="' . $this->getName() . '.xml"', true);

		return $this->getBuffer();
	}

	/**
	 * Returns the document name
	 *
	 * @return  string
	 *
	 * @since  1.7.0
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Sets the document name
	 *
	 * @param   string  $name  Document name
	 *
	 * @return  XmlDocument instance of $this to allow chaining
	 *
	 * @since   1.7.0
	 */
	public function setName($name = 'joomla')
	{
		$this->name = $name;

		return $this;
	}

	/**
	 * Check if this document is intended for download
	 *
	 * @return  string
	 *
	 * @since   3.9.0
	 */
	public function isDownload()
	{
		return $this->isDownload;
	}

	/**
	 * Sets the document's download state
	 *
	 * @param   boolean  $download  If true, this document will be downloaded; if false, this document will be displayed inline
	 *
	 * @return  XmlDocument instance of $this to allow chaining
	 *
	 * @since   3.9.0
	 */
	public function setDownload($download = false)
	{
		$this->isDownload = $download;

		return $this;
	}
}
src/Log/Log.php000064400000022246152177723700007327 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Log Class
 *
 * This class hooks into the global log configuration settings to allow for user configured
 * logging events to be sent to where the user wishes them to be sent. On high load sites
 * Syslog is probably the best (pure PHP function), then the text file based loggers (CSV, W3c
 * or plain Formattedtext) and finally MySQL offers the most features (e.g. rapid searching)
 * but will incur a performance hit due to INSERT being issued.
 *
 * @since  1.7.0
 */
class Log
{
	/**
	 * All log priorities.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const ALL = 30719;

	/**
	 * The system is unusable.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const EMERGENCY = 1;

	/**
	 * Action must be taken immediately.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const ALERT = 2;

	/**
	 * Critical conditions.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const CRITICAL = 4;

	/**
	 * Error conditions.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const ERROR = 8;

	/**
	 * Warning conditions.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const WARNING = 16;

	/**
	 * Normal, but significant condition.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const NOTICE = 32;

	/**
	 * Informational message.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const INFO = 64;

	/**
	 * Debugging message.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const DEBUG = 128;

	/**
	 * The global Log instance.
	 *
	 * @var    Log
	 * @since  1.7.0
	 */
	protected static $instance;

	/**
	 * Container for Logger configurations.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $configurations = array();

	/**
	 * Container for Logger objects.
	 *
	 * @var    Logger[]
	 * @since  1.7.0
	 */
	protected $loggers = array();

	/**
	 * Lookup array for loggers.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $lookup = array();

	/**
	 * Constructor.
	 *
	 * @since   1.7.0
	 */
	protected function __construct()
	{
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   mixed    $entry     The LogEntry object to add to the log or the message for a new LogEntry object.
	 * @param   integer  $priority  Message priority.
	 * @param   string   $category  Type of entry
	 * @param   string   $date      Date of entry (defaults to now if not specified or blank)
	 * @param   array    $context   An optional array with additional message context.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function add($entry, $priority = self::INFO, $category = '', $date = null, array $context = array())
	{
		// Automatically instantiate the singleton object if not already done.
		if (empty(static::$instance))
		{
			static::setInstance(new Log);
		}

		// If the entry object isn't a LogEntry object let's make one.
		if (!($entry instanceof LogEntry))
		{
			$entry = new LogEntry((string) $entry, $priority, $category, $date, $context);
		}

		static::$instance->addLogEntry($entry);
	}

	/**
	 * Add a logger to the Log instance.  Loggers route log entries to the correct files/systems to be logged.
	 *
	 * @param   array    $options     The object configuration array.
	 * @param   integer  $priorities  Message priority
	 * @param   array    $categories  Types of entry
	 * @param   boolean  $exclude     If true, all categories will be logged except those in the $categories array
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function addLogger(array $options, $priorities = self::ALL, $categories = array(), $exclude = false)
	{
		// Automatically instantiate the singleton object if not already done.
		if (empty(static::$instance))
		{
			static::setInstance(new Log);
		}

		static::$instance->addLoggerInternal($options, $priorities, $categories, $exclude);
	}

	/**
	 * Add a logger to the Log instance.  Loggers route log entries to the correct files/systems to be logged.
	 * This method allows you to extend Log completely.
	 *
	 * @param   array    $options     The object configuration array.
	 * @param   integer  $priorities  Message priority
	 * @param   array    $categories  Types of entry
	 * @param   boolean  $exclude     If true, all categories will be logged except those in the $categories array
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function addLoggerInternal(array $options, $priorities = self::ALL, $categories = array(), $exclude = false)
	{
		// The default logger is the formatted text log file.
		if (empty($options['logger']))
		{
			$options['logger'] = 'formattedtext';
		}

		$options['logger'] = strtolower($options['logger']);

		// Special case - if a Closure object is sent as the callback (in case of CallbackLogger)
		// Closure objects are not serializable so swap it out for a unique id first then back again later
		if (isset($options['callback']))
		{
			if (is_a($options['callback'], 'closure'))
			{
				$callback = $options['callback'];
				$options['callback'] = spl_object_hash($options['callback']);
			}
			elseif (is_array($options['callback']) && count($options['callback']) == 2 && is_object($options['callback'][0]))
			{
				$callback = $options['callback'];
				$options['callback'] = spl_object_hash($options['callback'][0]) . '::' . $options['callback'][1];
			}
		}

		// Generate a unique signature for the Log instance based on its options.
		$signature = md5(serialize($options));

		// Now that the options array has been serialized, swap the callback back in
		if (isset($callback))
		{
			$options['callback'] = $callback;
		}

		// Register the configuration if it doesn't exist.
		if (empty($this->configurations[$signature]))
		{
			$this->configurations[$signature] = $options;
		}

		$this->lookup[$signature] = (object) array(
			'priorities' => $priorities,
			'categories' => array_map('strtolower', (array) $categories),
			'exclude' => (bool) $exclude,
		);
	}

	/**
	 * Creates a delegated PSR-3 compatible logger from the current singleton instance. This method always returns a new delegated logger.
	 *
	 * @return  DelegatingPsrLogger
	 *
	 * @since   3.8.0
	 */
	public static function createDelegatedLogger()
	{
		// Ensure a singleton instance has been created first
		if (empty(static::$instance))
		{
			static::setInstance(new static);
		}

		return new DelegatingPsrLogger(static::$instance);
	}

	/**
	 * Returns a reference to the a Log object, only creating it if it doesn't already exist.
	 * Note: This is principally made available for testing and internal purposes.
	 *
	 * @param   Log  $instance  The logging object instance to be used by the static methods.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public static function setInstance($instance)
	{
		if (($instance instanceof Log) || $instance === null)
		{
			static::$instance = & $instance;
		}
	}

	/**
	 * Method to add an entry to the appropriate loggers.
	 *
	 * @param   LogEntry  $entry  The LogEntry object to send to the loggers.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function addLogEntry(LogEntry $entry)
	{
		// Find all the appropriate loggers based on priority and category for the entry.
		$loggers = $this->findLoggers($entry->priority, $entry->category);

		foreach ((array) $loggers as $signature)
		{
			// Attempt to instantiate the logger object if it doesn't already exist.
			if (empty($this->loggers[$signature]))
			{
				$class = __NAMESPACE__ . '\\Logger\\' . ucfirst($this->configurations[$signature]['logger']) . 'Logger';

				if (!class_exists($class))
				{
					throw new \RuntimeException('Unable to create a Logger instance: ' . $class);
				}

				$this->loggers[$signature] = new $class($this->configurations[$signature]);
			}

			// Add the entry to the logger.
			$this->loggers[$signature]->addEntry(clone $entry);
		}
	}

	/**
	 * Method to find the loggers to use based on priority and category values.
	 *
	 * @param   integer  $priority  Message priority.
	 * @param   string   $category  Type of entry
	 *
	 * @return  array  The array of loggers to use for the given priority and category values.
	 *
	 * @since   1.7.0
	 */
	protected function findLoggers($priority, $category)
	{
		$loggers = array();

		// Sanitize inputs.
		$priority = (int) $priority;
		$category = strtolower($category);

		// Let's go iterate over the loggers and get all the ones we need.
		foreach ((array) $this->lookup as $signature => $rules)
		{
			// Check to make sure the priority matches the logger.
			if ($priority & $rules->priorities)
			{
				if ($rules->exclude)
				{
					// If either there are no set categories or the category (including the empty case) is not in the list of excluded categories, add this logger.
					if (empty($rules->categories) || !in_array($category, $rules->categories))
					{
						$loggers[] = $signature;
					}
				}
				else
				{
					// If either there are no set categories (meaning all) or the specific category is set, add this logger.
					if (empty($rules->categories) || in_array($category, $rules->categories))
					{
						$loggers[] = $signature;
					}
				}
			}
		}

		return $loggers;
	}
}
src/Log/LogEntry.php000064400000005153152177723700010347 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Date\Date;

/**
 * Joomla! Log Entry class
 *
 * This class is designed to hold log entries for either writing to an engine, or for
 * supported engines, retrieving lists and building in memory (PHP based) search operations.
 *
 * @since  1.7.0
 */
class LogEntry
{
	/**
	 * Application responsible for log entry.
	 * @var    string
	 * @since  1.7.0
	 */
	public $category;

	/**
	 * The message context.
	 *
	 * @var    array
	 * @since  3.8.0
	 */
	public $context;

	/**
	 * The date the message was logged.
	 * @var    Date
	 * @since  1.7.0
	 */
	public $date;

	/**
	 * Message to be logged.
	 * @var    string
	 * @since  1.7.0
	 */
	public $message;

	/**
	 * The priority of the message to be logged.
	 * @var    string
	 * @since  1.7.0
	 * @see    LogEntry::$priorities
	 */
	public $priority = Log::INFO;

	/**
	 * List of available log priority levels [Based on the Syslog default levels].
	 * @var    array
	 * @since  1.7.0
	 */
	protected $priorities = array(
		Log::EMERGENCY,
		Log::ALERT,
		Log::CRITICAL,
		Log::ERROR,
		Log::WARNING,
		Log::NOTICE,
		Log::INFO,
		Log::DEBUG,
	);

	/**
	 * Call stack and back trace of the logged call.
	 * @var    array
	 * @since  3.1.4
	 */
	public $callStack = array();

	/**
	 * Constructor
	 *
	 * @param   string  $message   The message to log.
	 * @param   int     $priority  Message priority based on {$this->priorities}.
	 * @param   string  $category  Type of entry
	 * @param   string  $date      Date of entry (defaults to now if not specified or blank)
	 * @param   array   $context   An optional array with additional message context.
	 *
	 * @since   1.7.0
	 */
	public function __construct($message, $priority = Log::INFO, $category = '', $date = null, array $context = array())
	{
		$this->message = (string) $message;

		// Sanitize the priority.
		if (!in_array($priority, $this->priorities, true))
		{
			$priority = Log::INFO;
		}

		$this->priority = $priority;
		$this->context  = $context;

		// Sanitize category if it exists.
		if (!empty($category))
		{
			$this->category = (string) strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $category));
		}

		// Get the current call stack and back trace (without args to save memory).
		$this->callStack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);

		// Get the date as a Date object.
		$this->date = new Date($date ? $date : 'now');
	}
}
src/Log/Logger/DatabaseLogger.php000064400000007662152177723700012676 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

/**
 * Joomla! MySQL Database Log class
 *
 * This class is designed to output logs to a specific MySQL database table. Fields in this
 * table are based on the Syslog style of log output. This is designed to allow quick and
 * easy searching.
 *
 * @since  1.7.0
 */
class DatabaseLogger extends Logger
{
	/**
	 * The name of the database driver to use for connecting to the database.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $driver = 'mysqli';

	/**
	 * The host name (or IP) of the server with which to connect for the logger.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $host = '127.0.0.1';

	/**
	 * The database server user to connect as for the logger.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $user = 'root';

	/**
	 * The password to use for connecting to the database server.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $password = '';

	/**
	 * The name of the database table to use for the logger.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $database = 'logging';

	/**
	 * The database table to use for logging entries.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $table = 'jos_';

	/**
	 * The database driver object for the logger.
	 *
	 * @var    \JDatabaseDriver
	 * @since  1.7.0
	 */
	protected $db;

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   1.7.0
	 */
	public function __construct(array &$options)
	{
		// Call the parent constructor.
		parent::__construct($options);

		// If both the database object and driver options are empty we want to use the system database connection.
		if (empty($this->options['db_driver']))
		{
			$this->db = \JFactory::getDbo();
			$this->driver = null;
			$this->host = null;
			$this->user = null;
			$this->password = null;
			$this->database = null;
			$this->prefix = null;
		}
		else
		{
			$this->db = null;
			$this->driver = (empty($this->options['db_driver'])) ? 'mysqli' : $this->options['db_driver'];
			$this->host = (empty($this->options['db_host'])) ? '127.0.0.1' : $this->options['db_host'];
			$this->user = (empty($this->options['db_user'])) ? 'root' : $this->options['db_user'];
			$this->password = (empty($this->options['db_pass'])) ? '' : $this->options['db_pass'];
			$this->database = (empty($this->options['db_database'])) ? 'logging' : $this->options['db_database'];
			$this->prefix = (empty($this->options['db_prefix'])) ? 'jos_' : $this->options['db_prefix'];
		}

		// The table name is independent of how we arrived at the connection object.
		$this->table = (empty($this->options['db_table'])) ? '#__log_entries' : $this->options['db_table'];
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function addEntry(LogEntry $entry)
	{
		// Connect to the database if not connected.
		if (empty($this->db))
		{
			$this->connect();
		}

		// Convert the date.
		$entry->date = $entry->date->toSql(false, $this->db);

		$this->db->insertObject($this->table, $entry);
	}

	/**
	 * Method to connect to the database server based on object properties.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function connect()
	{
		// Build the configuration object to use for JDatabaseDriver.
		$options = array(
			'driver' => $this->driver,
			'host' => $this->host,
			'user' => $this->user,
			'password' => $this->password,
			'database' => $this->database,
			'prefix' => $this->prefix,
		);

		$this->db = \JDatabaseDriver::getInstance($options);
	}
}
src/Log/Logger/EchoLogger.php000064400000002410152177723700012032 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

/**
 * Joomla Echo logger class.
 *
 * @since  1.7.0
 */
class EchoLogger extends Logger
{
	/**
	 * Value to use at the end of an echoed log entry to separate lines.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $line_separator = "\n";

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   3.0.0
	 */
	public function __construct(array &$options)
	{
		parent::__construct($options);

		if (!empty($this->options['line_separator']))
		{
			$this->line_separator = $this->options['line_separator'];
		}
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function addEntry(LogEntry $entry)
	{
		echo $this->priorities[$entry->priority] . ': '
			. $entry->message . (empty($entry->category) ? '' : ' [' . $entry->category . ']')
			. $this->line_separator;
	}
}
src/Log/Logger/CallbackLogger.php000064400000003123152177723700012652 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

/**
 * Joomla! Callback Log class
 *
 * This class allows logging to be handled by a callback function.
 * This allows unprecedented flexibility in the way logging can be handled.
 *
 * @since  3.0.1
 */
class CallbackLogger extends Logger
{
	/**
	 * The function to call when an entry is added
	 *
	 * @var    callable
	 * @since  3.0.1
	 */
	protected $callback;

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   3.0.1
	 * @throws  \RuntimeException
	 */
	public function __construct(array &$options)
	{
		// Call the parent constructor.
		parent::__construct($options);

		// Throw an exception if there is not a valid callback
		if (!isset($this->options['callback']) || !is_callable($this->options['callback']))
		{
			throw new \RuntimeException(sprintf('%s created without valid callback function.', get_class($this)));
		}

		$this->callback = $this->options['callback'];
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @throws  \RuntimeException
	 */
	public function addEntry(LogEntry $entry)
	{
		// Pass the log entry to the callback function
		call_user_func($this->callback, $entry);
	}
}
src/Log/Logger/SyslogLogger.php000064400000006446152177723700012451 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

/**
 * Joomla! Syslog Log class
 *
 * This class is designed to call the PHP Syslog function call which is then sent to the
 * system wide log system. For Linux/Unix based systems this is the syslog subsystem, for
 * the Windows based implementations this can be found in the Event Log. For Windows,
 * permissions may prevent PHP from properly outputting messages.
 *
 * @since  1.7.0
 */
class SyslogLogger extends Logger
{
	/**
	 * Translation array for LogEntry priorities to SysLog priority names.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $priorities = array(
		Log::EMERGENCY => 'EMERG',
		Log::ALERT => 'ALERT',
		Log::CRITICAL => 'CRIT',
		Log::ERROR => 'ERR',
		Log::WARNING => 'WARNING',
		Log::NOTICE => 'NOTICE',
		Log::INFO => 'INFO',
		Log::DEBUG => 'DEBUG',
	);

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   1.7.0
	 */
	public function __construct(array &$options)
	{
		// Call the parent constructor.
		parent::__construct($options);

		// Ensure that we have an identity string for the Syslog entries.
		if (empty($this->options['sys_ident']))
		{
			$this->options['sys_ident'] = 'Joomla Platform';
		}

		// If the option to add the process id to Syslog entries is set use it, otherwise default to true.
		if (isset($this->options['sys_add_pid']))
		{
			$this->options['sys_add_pid'] = (bool) $this->options['sys_add_pid'];
		}
		else
		{
			$this->options['sys_add_pid'] = true;
		}

		// If the option to also send Syslog entries to STDERR is set use it, otherwise default to false.
		if (isset($this->options['sys_use_stderr']))
		{
			$this->options['sys_use_stderr'] = (bool) $this->options['sys_use_stderr'];
		}
		else
		{
			$this->options['sys_use_stderr'] = false;
		}

		// Build the Syslog options from our log object options.
		$sysOptions = 0;

		if ($this->options['sys_add_pid'])
		{
			$sysOptions = $sysOptions | LOG_PID;
		}

		if ($this->options['sys_use_stderr'])
		{
			$sysOptions = $sysOptions | LOG_PERROR;
		}

		// Default logging facility is LOG_USER for Windows compatibility.
		$sysFacility = LOG_USER;

		// If we have a facility passed in and we're not on Windows, reset it.
		if (isset($this->options['sys_facility']) && !IS_WIN)
		{
			$sysFacility = $this->options['sys_facility'];
		}

		// Open the Syslog connection.
		openlog((string) $this->options['sys_ident'], $sysOptions, $sysFacility);
	}

	/**
	 * Destructor.
	 *
	 * @since   1.7.0
	 */
	public function __destruct()
	{
		closelog();
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function addEntry(LogEntry $entry)
	{
		// Generate the value for the priority based on predefined constants.
		$priority = constant(strtoupper('LOG_' . $this->priorities[$entry->priority]));

		// Send the entry to Syslog.
		syslog($priority, '[' . $entry->category . '] ' . $entry->message);
	}
}
src/Log/Logger/W3cLogger.php000064400000002257152177723700011621 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! W3C Logging class
 *
 * This class is designed to build log files based on the W3C specification.
 *
 * @link   https://www.w3.org/TR/WD-logfile.html
 * @since  1.7.0
 */
class W3cLogger extends FormattedtextLogger
{
	/**
	 * The format which each entry follows in the log file.
	 *
	 * All fields must be named in all caps and be within curly brackets eg. {FOOBAR}.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $format = '{DATE}	{TIME}	{PRIORITY}	{CLIENTIP}	{CATEGORY}	{MESSAGE}';

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   1.7.0
	 */
	public function __construct(array &$options)
	{
		// The name of the text file defaults to 'error.w3c.php' if not explicitly given.
		if (empty($options['text_file']))
		{
			$options['text_file'] = 'error.w3c.php';
		}

		// Call the parent constructor.
		parent::__construct($options);
	}
}
src/Log/Logger/MessagequeueLogger.php000064400000002723152177723700013614 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;
use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

/**
 * Joomla MessageQueue logger class.
 *
 * This class is designed to output logs to a specific MySQL database table. Fields in this
 * table are based on the Syslog style of log output. This is designed to allow quick and
 * easy searching.
 *
 * @since  1.7.0
 */
class MessagequeueLogger extends Logger
{
	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function addEntry(LogEntry $entry)
	{
		switch ($entry->priority)
		{
			case Log::EMERGENCY:
			case Log::ALERT:
			case Log::CRITICAL:
			case Log::ERROR:
				\JFactory::getApplication()->enqueueMessage($entry->message, 'error');
				break;
			case Log::WARNING:
				\JFactory::getApplication()->enqueueMessage($entry->message, 'warning');
				break;
			case Log::NOTICE:
				\JFactory::getApplication()->enqueueMessage($entry->message, 'notice');
				break;
			case Log::INFO:
				\JFactory::getApplication()->enqueueMessage($entry->message, 'message');
				break;
			default:
				// Ignore other priorities.
				break;
		}
	}
}
src/Log/Logger/FormattedtextLogger.php000064400000017114152177723700014015 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log\Logger;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\LogEntry;
use Joomla\CMS\Log\Logger;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');

/**
 * Joomla! Formatted Text File Log class
 *
 * This class is designed to use as a base for building formatted text files for output. By
 * default it emulates the Syslog style format output. This is a disk based output format.
 *
 * @since  1.7.0
 */
class FormattedtextLogger extends Logger
{
	/**
	 * The format which each entry follows in the log file.
	 *
	 * All fields must be named in all caps and be within curly brackets eg. {FOOBAR}.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $format = '{DATETIME}	{PRIORITY} {CLIENTIP}	{CATEGORY}	{MESSAGE}';

	/**
	 * The parsed fields from the format string.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $fields = array();

	/**
	 * The full filesystem path for the log file.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $path;

	/**
	 * If true, all writes will be deferred as long as possible.
	 * NOTE: Deferred logs may never be written if the application encounters a fatal error.
	 *
	 * @var    boolean
	 * @since  3.9.0
	 */
	protected $defer = false;

	/**
	 * If deferring, entries will be stored here prior to writing.
	 *
	 * @var    array
	 * @since  3.9.0
	 */
	protected $deferredEntries = array();

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   1.7.0
	 */
	public function __construct(array &$options)
	{
		// Call the parent constructor.
		parent::__construct($options);

		// The name of the text file defaults to 'error.php' if not explicitly given.
		if (empty($this->options['text_file']))
		{
			$this->options['text_file'] = 'error.php';
		}

		// The name of the text file path defaults to that which is set in configuration if not explicitly given.
		if (empty($this->options['text_file_path']))
		{
			$this->options['text_file_path'] = \JFactory::getConfig()->get('log_path');
		}

		// False to treat the log file as a php file.
		if (empty($this->options['text_file_no_php']))
		{
			$this->options['text_file_no_php'] = false;
		}

		// Build the full path to the log file.
		$this->path = $this->options['text_file_path'] . '/' . $this->options['text_file'];

		// Use the default entry format unless explicitly set otherwise.
		if (!empty($this->options['text_entry_format']))
		{
			$this->format = (string) $this->options['text_entry_format'];
		}

		// Wait as long as possible before writing logs
		if (!empty($this->options['defer']))
		{
			$this->defer = (boolean) $this->options['defer'];
		}

		// Build the fields array based on the format string.
		$this->parseFields();
	}

	/**
	 * If deferred, write all pending logs.
	 *
	 * @since  3.9.0
	 */
	public function __destruct()
	{
		// Nothing to do
		if (!$this->defer || empty($this->deferredEntries))
		{
			return;
		}

		// Initialise the file if not already done.
		$this->initFile();

		// Format all lines and write to file.
		$lines = array_map(array($this, 'formatLine'), $this->deferredEntries);

		if (!\JFile::append($this->path, implode("\n", $lines) . "\n"))
		{
			throw new \RuntimeException('Cannot write to log file.');
		}
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function addEntry(LogEntry $entry)
	{
		// Store the entry to be written later.
		if ($this->defer)
		{
			$this->deferredEntries[] = $entry;
		}
		// Write it immediately.
		else
		{
			// Initialise the file if not already done.
			$this->initFile();

			// Write the new entry to the file.
			$line = $this->formatLine($entry);
			$line .= "\n";

			if (!\JFile::append($this->path, $line))
			{
				throw new \RuntimeException('Cannot write to log file.');
			}
		}
	}

	/**
	 * Format a line for the log file.
	 *
	 * @param   JLogEntry  $entry  The log entry to format as a string.
	 *
	 * @return  String
	 *
	 * @since  3.9.0
	 */
	protected function formatLine(LogEntry $entry)
	{
		// Set some default field values if not already set.
		if (!isset($entry->clientIP))
		{
			// Check for proxies as well.
			if (isset($_SERVER['REMOTE_ADDR']))
			{
				$entry->clientIP = $_SERVER['REMOTE_ADDR'];
			}
			elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
			{
				$entry->clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
			}
			elseif (isset($_SERVER['HTTP_CLIENT_IP']))
			{
				$entry->clientIP = $_SERVER['HTTP_CLIENT_IP'];
			}
		}

		// If the time field is missing or the date field isn't only the date we need to rework it.
		if ((strlen($entry->date) != 10) || !isset($entry->time))
		{
			// Get the date and time strings in GMT.
			$entry->datetime = $entry->date->toISO8601();
			$entry->time = $entry->date->format('H:i:s', false);
			$entry->date = $entry->date->format('Y-m-d', false);
		}

		// Get a list of all the entry keys and make sure they are upper case.
		$tmp = array_change_key_case(get_object_vars($entry), CASE_UPPER);

		// Decode the entry priority into an English string.
		$tmp['PRIORITY'] = $this->priorities[$entry->priority];

		// Fill in field data for the line.
		$line = $this->format;

		foreach ($this->fields as $field)
		{
			$line = str_replace('{' . $field . '}', (isset($tmp[$field])) ? $tmp[$field] : '-', $line);
		}

		return $line;
	}

	/**
	 * Method to generate the log file header.
	 *
	 * @return  string  The log file header
	 *
	 * @since   1.7.0
	 */
	protected function generateFileHeader()
	{
		$head = array();

		// Build the log file header.

		// If the no php flag is not set add the php die statement.
		if (empty($this->options['text_file_no_php']))
		{
			// Blank line to prevent information disclose: https://bugs.php.net/bug.php?id=60677
			$head[] = '#';
			$head[] = '#<?php die(\'Forbidden.\'); ?>';
		}

		$head[] = '#Date: ' . gmdate('Y-m-d H:i:s') . ' UTC';
		$head[] = '#Software: ' . \JPlatform::getLongVersion();
		$head[] = '';

		// Prepare the fields string
		$head[] = '#Fields: ' . strtolower(str_replace('}', '', str_replace('{', '', $this->format)));
		$head[] = '';

		return implode("\n", $head);
	}

	/**
	 * Method to initialise the log file.  This will create the folder path to the file if it doesn't already
	 * exist and also get a new file header if the file doesn't already exist.  If the file already exists it
	 * will simply open it for writing.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function initFile()
	{
		// We only need to make sure the file exists
		if (\JFile::exists($this->path))
		{
			return;
		}

		// Make sure the folder exists in which to create the log file.
		\JFolder::create(dirname($this->path));

		// Build the log file header.
		$head = $this->generateFileHeader();

		if (!\JFile::write($this->path, $head))
		{
			throw new \RuntimeException('Cannot write to log file.');
		}
	}

	/**
	 * Method to parse the format string into an array of fields.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function parseFields()
	{
		$this->fields = array();
		$matches = array();

		// Get all of the available fields in the format string.
		preg_match_all('/{(.*?)}/i', $this->format, $matches);

		// Build the parsed fields list based on the found fields.
		foreach ($matches[1] as $match)
		{
			$this->fields[] = strtoupper($match);
		}
	}
}
src/Log/DelegatingPsrLogger.php000064400000005272152177723700012476 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log;

use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;

/**
 * Delegating logger which delegates log messages received from the PSR-3 interface to the Joomla! Log object.
 *
 * @since  3.8.0
 */
class DelegatingPsrLogger extends AbstractLogger
{
	/**
	 * The Log instance to delegate messages to.
	 *
	 * @var    Log
	 * @since  3.8.0
	 */
	protected $logger;

	/**
	 * Mapping array to map a PSR-3 level to a Joomla priority.
	 *
	 * @var    array
	 * @since  3.8.0
	 */
	protected $priorityMap = array(
		LogLevel::EMERGENCY => Log::EMERGENCY,
		LogLevel::ALERT     => Log::ALERT,
		LogLevel::CRITICAL  => Log::CRITICAL,
		LogLevel::ERROR     => Log::ERROR,
		LogLevel::WARNING   => Log::WARNING,
		LogLevel::NOTICE    => Log::NOTICE,
		LogLevel::INFO      => Log::INFO,
		LogLevel::DEBUG     => Log::DEBUG
	);

	/**
	 * Constructor.
	 *
	 * @param   Log  $logger  The Log instance to delegate messages to.
	 *
	 * @since   3.8.0
	 */
	public function __construct(Log $logger)
	{
		$this->logger = $logger;
	}

	/**
	 * Logs with an arbitrary level.
	 *
	 * @param   mixed   $level    The log level.
	 * @param   string  $message  The log message.
	 * @param   array   $context  Additional message context.
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 * @throws  InvalidArgumentException
	 */
	public function log($level, $message, array $context = array())
	{
		// Make sure the log level is valid
		if (!array_key_exists($level, $this->priorityMap))
		{
			throw new \InvalidArgumentException('An invalid log level has been given.');
		}

		// Map the level to Joomla's priority
		$priority = $this->priorityMap[$level];

		$category = null;
		$date     = null;

		// If a message category is given, map it
		if (!empty($context['category']))
		{
			$category = $context['category'];
		}

		// If a message timestamp is given, map it
		if (!empty($context['date']))
		{
			$date = $context['date'];
		}

		// Joomla's logging API will only process a string or a LogEntry object, if $message is an object without __toString() we can't use it
		if (!is_string($message) && !($message instanceof LogEntry))
		{
			if (!is_object($message) || !method_exists($message, '__toString'))
			{
				throw new \InvalidArgumentException(
					'The message must be a string, a LogEntry object, or an object implementing the __toString() method.'
				);
			}

			$message = (string) $message;
		}

		$this->logger->add($message, $priority, $category, $date, $context);
	}
}
src/Log/Logger.php000064400000002723152177723700010023 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Log;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Logger Base Class
 *
 * This class is used to be the basis of logger classes to allow for defined functions
 * to exist regardless of the child class.
 *
 * @since  3.0.1
 */
abstract class Logger
{
	/**
	 * Options array for the JLog instance.
	 *
	 * @var    array
	 * @since  3.0.1
	 */
	protected $options = array();

	/**
	 * Translation array for LogEntry priorities to text strings.
	 *
	 * @var    array
	 * @since  3.0.1
	 */
	protected $priorities = array(
		Log::EMERGENCY => 'EMERGENCY',
		Log::ALERT     => 'ALERT',
		Log::CRITICAL  => 'CRITICAL',
		Log::ERROR     => 'ERROR',
		Log::WARNING   => 'WARNING',
		Log::NOTICE    => 'NOTICE',
		Log::INFO      => 'INFO',
		Log::DEBUG     => 'DEBUG',
	);

	/**
	 * Constructor.
	 *
	 * @param   array  &$options  Log object options.
	 *
	 * @since   3.0.1
	 */
	public function __construct(array &$options)
	{
		// Set the options for the class.
		$this->options = & $options;
	}

	/**
	 * Method to add an entry to the log.
	 *
	 * @param   LogEntry  $entry  The log entry object to add to the log.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @throws  \RuntimeException
	 */
	abstract public function addEntry(LogEntry $entry);
}
src/Updater/DownloadSource.php000064400000002725152177723700012421 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

/**
 * Data object representing a download source given as part of an update's `<downloads>` element
 *
 * @since  3.8.3
 */
class DownloadSource
{
	/**
	 * Defines a BZIP2 download package
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const FORMAT_TAR_BZIP = 'bz2';

	/**
	 * Defines a TGZ download package
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const FORMAT_TAR_GZ = 'gz';

	/**
	 * Defines a ZIP download package
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	const FORMAT_ZIP = 'zip';

	/**
	 * Defines a full package download type
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	const TYPE_FULL = 'full';

	/**
	 * Defines a patch package download type
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const TYPE_PATCH = 'patch';

	/**
	 * Defines an upgrade package download type
	 *
	 * @var    string
	 * @since  3.8.4
	 */
	const TYPE_UPGRADE = 'upgrade';

	/**
	 * The download type
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $type = self::TYPE_FULL;

	/**
	 * The download file's format
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $format = self::FORMAT_ZIP;

	/**
	 * The URL to retrieve the package from
	 *
	 * @var    string
	 * @since  3.8.3
	 */
	public $url;
}
src/Updater/Updater.php000064400000026207152177723700011076 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Table\Table;

\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.folder');
\JLoader::import('joomla.filesystem.path');
\JLoader::import('joomla.base.adapter');

/**
 * Updater Class
 *
 * @since  1.7.0
 */
class Updater extends \JAdapter
{
	/**
	 * Development snapshots, nightly builds, pre-release versions and so on
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_DEV = 0;

	/**
	 * Alpha versions (work in progress, things are likely to be broken)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_ALPHA = 1;

	/**
	 * Beta versions (major functionality in place, show-stopper bugs are likely to be present)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_BETA = 2;

	/**
	 * Release Candidate versions (almost stable, minor bugs might be present)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_RC = 3;

	/**
	 * Stable versions (production quality code)
	 *
	 * @var    integer
	 * @since  3.4
	 */
	const STABILITY_STABLE = 4;

	/**
	 * Updater instance container.
	 *
	 * @var    Updater
	 * @since  1.7.3
	 */
	protected static $instance;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   3.1
	 */
	public function __construct($basepath = __DIR__, $classprefix = '\\Joomla\\CMS\\Updater\\Adapter', $adapterfolder = 'Adapter')
	{
		parent::__construct($basepath, $classprefix, $adapterfolder);
	}

	/**
	 * Returns a reference to the global Installer object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return  Updater  An installer object
	 *
	 * @since   1.7.0
	 */
	public static function getInstance()
	{
		if (!isset(self::$instance))
		{
			self::$instance = new Updater;
		}

		return self::$instance;
	}

	/**
	 * Finds the update for an extension. Any discovered updates are stored in the #__updates table.
	 *
	 * @param   int|array  $eid                Extension Identifier or list of Extension Identifiers; if zero use all
	 *                                         sites
	 * @param   integer    $cacheTimeout       How many seconds to cache update information; if zero, force reload the
	 *                                         update information
	 * @param   integer    $minimum_stability  Minimum stability for the updates; 0=dev, 1=alpha, 2=beta, 3=rc,
	 *                                         4=stable
	 * @param   boolean    $includeCurrent     Should I include the current version in the results?
	 *
	 * @return  boolean True if there are updates
	 *
	 * @since   1.7.0
	 */
	public function findUpdates($eid = 0, $cacheTimeout = 0, $minimum_stability = self::STABILITY_STABLE, $includeCurrent = false)
	{
		$retval = false;

		$results = $this->getUpdateSites($eid);

		if (empty($results))
		{
			return $retval;
		}

		$now              = time();
		$earliestTime     = $now - $cacheTimeout;
		$sitesWithUpdates = array();

		if ($cacheTimeout > 0)
		{
			$sitesWithUpdates = $this->getSitesWithUpdates($earliestTime);
		}

		foreach ($results as $result)
		{
			/**
			 * If we have already checked for updates within the cache timeout period we will report updates available
			 * only if there are update records matching this update site. Then we skip processing of the update site
			 * since it's already processed within the cache timeout period.
			 */
			if (($cacheTimeout > 0)
				&& isset($result['last_check_timestamp'])
				&& ($result['last_check_timestamp'] >= $earliestTime))
			{
				$retval = $retval || in_array($result['update_site_id'], $sitesWithUpdates);

				continue;
			}

			$updateObjects = $this->getUpdateObjectsForSite($result, $minimum_stability, $includeCurrent);

			if (!empty($updateObjects))
			{
				$retval = true;

				/** @var \JTableUpdate $update */
				foreach ($updateObjects as $update)
				{
					$update->check();
					$update->store();
				}
			}

			// Finally, update the last update check timestamp
			$this->updateLastCheckTimestamp($result['update_site_id']);
		}

		return $retval;
	}

	/**
	 * Finds an update for an extension
	 *
	 * @param   integer  $id  Id of the extension
	 *
	 * @return  mixed
	 *
	 * @since   3.6.0
	 *
	 * @deprecated  4.0  No replacement.
	 */
	public function update($id)
	{
		$updaterow = Table::getInstance('update');
		$updaterow->load($id);
		$update = new Update;

		if ($update->loadFromXml($updaterow->detailsurl))
		{
			return $update->install();
		}

		return false;
	}

	/**
	 * Returns the update site records for an extension with ID $eid. If $eid is zero all enabled update sites records
	 * will be returned.
	 *
	 * @param   int  $eid  The extension ID to fetch.
	 *
	 * @return  array
	 *
	 * @since   3.6.0
	 */
	private function getUpdateSites($eid = 0)
	{
		$db    = $this->getDbo();
		$query = $db->getQuery(true);

		$query->select('DISTINCT a.update_site_id, a.type, a.location, a.last_check_timestamp, a.extra_query')
			->from($db->quoteName('#__update_sites', 'a'))
			->where('a.enabled = 1');

		if ($eid)
		{
			$query->join('INNER', '#__update_sites_extensions AS b ON a.update_site_id = b.update_site_id');

			if (is_array($eid))
			{
				$query->where('b.extension_id IN (' . implode(',', $eid) . ')');
			}
			elseif ((int) $eid)
			{
				$query->where('b.extension_id = ' . $eid);
			}
		}

		$db->setQuery($query);

		$result = $db->loadAssocList();

		if (!is_array($result))
		{
			return array();
		}

		return $result;
	}

	/**
	 * Loads the contents of an update site record $updateSite and returns the update objects
	 *
	 * @param   array  $updateSite         The update site record to process
	 * @param   int    $minimum_stability  Minimum stability for the returned update records
	 * @param   bool   $includeCurrent     Should I also include the current version?
	 *
	 * @return  array  The update records. Empty array if no updates are found.
	 *
	 * @since   3.6.0
	 */
	private function getUpdateObjectsForSite($updateSite, $minimum_stability = self::STABILITY_STABLE, $includeCurrent = false)
	{
		$retVal = array();

		$this->setAdapter($updateSite['type']);

		if (!isset($this->_adapters[$updateSite['type']]))
		{
			// Ignore update sites requiring adapters we don't have installed
			return $retVal;
		}

		$updateSite['minimum_stability'] = $minimum_stability;

		// Get the update information from the remote update XML document
		/** @var UpdateAdapter $adapter */
		$adapter       = $this->_adapters[ $updateSite['type']];
		$update_result = $adapter->findUpdate($updateSite);

		// Version comparison operator.
		$operator = $includeCurrent ? 'ge' : 'gt';

		if (is_array($update_result))
		{
			// If we have additional update sites in the remote (collection) update XML document, parse them
			if (array_key_exists('update_sites', $update_result) && count($update_result['update_sites']))
			{
				$thisUrl = trim($updateSite['location']);
				$thisId  = (int) $updateSite['update_site_id'];

				foreach ($update_result['update_sites'] as $extraUpdateSite)
				{
					$extraUrl = trim($extraUpdateSite['location']);
					$extraId  = (int) $extraUpdateSite['update_site_id'];

					// Do not try to fetch the same update site twice
					if (($thisId == $extraId) || ($thisUrl == $extraUrl))
					{
						continue;
					}

					$extraUpdates = $this->getUpdateObjectsForSite($extraUpdateSite, $minimum_stability);

					if (count($extraUpdates))
					{
						$retVal = array_merge($retVal, $extraUpdates);
					}
				}
			}

			if (array_key_exists('updates', $update_result) && count($update_result['updates']))
			{
				/** @var \JTableUpdate $current_update */
				foreach ($update_result['updates'] as $current_update)
				{
					$current_update->extra_query = $updateSite['extra_query'];

					/** @var \JTableUpdate $update */
					$update = Table::getInstance('update');

					/** @var \JTableExtension $extension */
					$extension = Table::getInstance('extension');

					$uid = $update
						->find(
							array(
								'element'   => $current_update->get('element'),
								'type'      => $current_update->get('type'),
								'client_id' => $current_update->get('client_id'),
								'folder'    => $current_update->get('folder'),
							)
						);

					$eid = $extension
						->find(
							array(
								'element'   => $current_update->get('element'),
								'type'      => $current_update->get('type'),
								'client_id' => $current_update->get('client_id'),
								'folder'    => $current_update->get('folder'),
							)
						);

					if (!$uid)
					{
						// Set the extension id
						if ($eid)
						{
							// We have an installed extension, check the update is actually newer
							$extension->load($eid);
							$data = json_decode($extension->manifest_cache, true);

							if (version_compare($current_update->version, $data['version'], $operator) == 1)
							{
								$current_update->extension_id = $eid;
								$retVal[] = $current_update;
							}
						}
						else
						{
							// A potentially new extension to be installed
							$retVal[] = $current_update;
						}
					}
					else
					{
						$update->load($uid);

						// If there is an update, check that the version is newer then replaces
						if (version_compare($current_update->version, $update->version, $operator) == 1)
						{
							$retVal[] = $current_update;
						}
					}
				}
			}
		}

		return $retVal;
	}

	/**
	 * Returns the IDs of the update sites with cached updates
	 *
	 * @param   int  $timestamp  Optional. If set, only update sites checked before $timestamp will be taken into
	 *                           account.
	 *
	 * @return  array  The IDs of the update sites with cached updates
	 *
	 * @since   3.6.0
	 */
	private function getSitesWithUpdates($timestamp = 0)
	{
		$db = Factory::getDbo();

		$query = $db->getQuery(true)
			->select('DISTINCT update_site_id')
			->from('#__updates');

		if ($timestamp)
		{
			$subQuery = $db->getQuery(true)
				->select('update_site_id')
				->from('#__update_sites')
				->where($db->qn('last_check_timestamp') . ' IS NULL', 'OR')
				->where($db->qn('last_check_timestamp') . ' <= ' . $db->q($timestamp), 'OR');

			$query->where($db->qn('update_site_id') . ' IN (' . $subQuery . ')');
		}

		$retVal = $db->setQuery($query)->loadColumn(0);

		if (empty($retVal))
		{
			return array();
		}

		return $retVal;
	}

	/**
	 * Update the last check timestamp of an update site
	 *
	 * @param   int  $updateSiteId  The update site ID to mark as just checked
	 *
	 * @return  void
	 *
	 * @since   3.6.0
	 */
	private function updateLastCheckTimestamp($updateSiteId)
	{
		$timestamp = time();
		$db        = Factory::getDbo();

		$query = $db->getQuery(true)
			->update($db->quoteName('#__update_sites'))
			->set($db->quoteName('last_check_timestamp') . ' = ' . $db->quote($timestamp))
			->where($db->quoteName('update_site_id') . ' = ' . $db->quote($updateSiteId));
		$db->setQuery($query);
		$db->execute();
	}
}
src/Updater/UpdateAdapter.php000064400000016461152177723700012216 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;

\JLoader::import('joomla.base.adapterinstance');

/**
 * UpdateAdapter class.
 *
 * @since  1.7.0
 */
abstract class UpdateAdapter extends \JAdapterInstance
{
	/**
	 * Resource handle for the XML Parser
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $xmlParser;

	/**
	 * Element call stack
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stack = array('base');

	/**
	 * ID of update site
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $updateSiteId = 0;

	/**
	 * Columns in the extensions table to be updated
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $updatecols = array('NAME', 'ELEMENT', 'TYPE', 'FOLDER', 'CLIENT', 'VERSION', 'DESCRIPTION', 'INFOURL', 'EXTRA_QUERY');

	/**
	 * Should we try appending a .xml extension to the update site's URL?
	 *
	 * @var   bool
	 */
	protected $appendExtension = false;

	/**
	 * The name of the update site (used in logging)
	 *
	 * @var   string
	 */
	protected $updateSiteName = '';

	/**
	 * The update site URL from which we will get the update information
	 *
	 * @var   string
	 */
	protected $_url = '';

	/**
	 * The minimum stability required for updates to be taken into account. The possible values are:
	 * 0	dev			Development snapshots, nightly builds, pre-release versions and so on
	 * 1	alpha		Alpha versions (work in progress, things are likely to be broken)
	 * 2	beta		Beta versions (major functionality in place, show-stopper bugs are likely to be present)
	 * 3	rc			Release Candidate versions (almost stable, minor bugs might be present)
	 * 4	stable		Stable versions (production quality code)
	 *
	 * @var    int
	 * @since  14.1
	 *
	 * @see    Updater
	 */
	protected $minimum_stability = Updater::STABILITY_STABLE;

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Gets the reference to the last tag
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getLastTag()
	{
		return $this->stack[count($this->stack) - 1];
	}

	/**
	 * Finds an update
	 *
	 * @param   array  $options  Options to use: update_site_id: the unique ID of the update site to look at
	 *
	 * @return  array  Update_sites and updates discovered
	 *
	 * @since   1.7.0
	 */
	abstract public function findUpdate($options);

	/**
	 * Toggles the enabled status of an update site. Update sites are disabled before getting the update information
	 * from their URL and enabled afterwards. If the URL fetch fails with a PHP fatal error (e.g. timeout) the faulty
	 * update site will remain disabled the next time we attempt to load the update information.
	 *
	 * @param   int   $update_site_id  The numeric ID of the update site to enable/disable
	 * @param   bool  $enabled         Enable the site when true, disable it when false
	 *
	 * @return  void
	 */
	protected function toggleUpdateSite($update_site_id, $enabled = true)
	{
		$update_site_id = (int) $update_site_id;
		$enabled = (bool) $enabled;

		if (empty($update_site_id))
		{
			return;
		}

		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->update($db->qn('#__update_sites'))
			->set($db->qn('enabled') . ' = ' . $db->q($enabled ? 1 : 0))
			->where($db->qn('update_site_id') . ' = ' . $db->q($update_site_id));
		$db->setQuery($query);

		try
		{
			$db->execute();
		}
		catch (\RuntimeException $e)
		{
			// Do nothing
		}
	}

	/**
	 * Get the name of an update site. This is used in logging.
	 *
	 * @param   int  $updateSiteId  The numeric ID of the update site
	 *
	 * @return  string  The name of the update site or an empty string if it's not found
	 */
	protected function getUpdateSiteName($updateSiteId)
	{
		$updateSiteId = (int) $updateSiteId;

		if (empty($updateSiteId))
		{
			return '';
		}

		$db = $this->parent->getDbo();
		$query = $db->getQuery(true)
			->select($db->qn('name'))
			->from($db->qn('#__update_sites'))
			->where($db->qn('update_site_id') . ' = ' . $db->q($updateSiteId));
		$db->setQuery($query);

		$name = '';

		try
		{
			$name = $db->loadResult();
		}
		catch (\RuntimeException $e)
		{
			// Do nothing
		}

		return $name;
	}

	/**
	 * Try to get the raw HTTP response from the update site, hopefully containing the update XML.
	 *
	 * @param   array  $options  The update options, see findUpdate() in children classes
	 *
	 * @return  boolean|\JHttpResponse  False if we can't connect to the site, JHttpResponse otherwise
	 *
	 * @throws  \Exception
	 */
	protected function getUpdateSiteResponse($options = array())
	{
		$url = trim($options['location']);
		$this->_url = &$url;
		$this->updateSiteId = $options['update_site_id'];

		if (!isset($options['update_site_name']))
		{
			$options['update_site_name'] = $this->getUpdateSiteName($this->updateSiteId);
		}

		$this->updateSiteName  = $options['update_site_name'];
		$this->appendExtension = false;

		if (array_key_exists('append_extension', $options))
		{
			$this->appendExtension = $options['append_extension'];
		}

		if ($this->appendExtension && (substr($url, -4) != '.xml'))
		{
			if (substr($url, -1) != '/')
			{
				$url .= '/';
			}

			$url .= 'extension.xml';
		}

		// Disable the update site. If the get() below fails with a fatal error (e.g. timeout) the faulty update
		// site will remain disabled
		$this->toggleUpdateSite($this->updateSiteId, false);

		$startTime = microtime(true);

		$version    = new Version;
		$httpOption = new Registry;
		$httpOption->set('userAgent', $version->getUserAgent('Joomla', true, false));

		// JHttp transport throws an exception when there's no response.
		try
		{
			$http = HttpFactory::getHttp($httpOption);
			$response = $http->get($url, array(), 20);
		}
		catch (\RuntimeException $e)
		{
			$response = null;
		}

		// Enable the update site. Since the get() returned the update site should remain enabled
		$this->toggleUpdateSite($this->updateSiteId, true);

		// Log the time it took to load this update site's information
		$endTime    = microtime(true);
		$timeToLoad = sprintf('%0.2f', $endTime - $startTime);
		Log::add(
			"Loading information from update site #{$this->updateSiteId} with name " .
			"\"$this->updateSiteName\" and URL $url took $timeToLoad seconds", Log::INFO, 'updater'
		);

		if ($response === null || $response->code !== 200)
		{
			// If the URL is missing the .xml extension, try appending it and retry loading the update
			if (!$this->appendExtension && (substr($url, -4) != '.xml'))
			{
				$options['append_extension'] = true;

				return $this->getUpdateSiteResponse($options);
			}

			// Log the exact update site name and URL which could not be loaded
			Log::add('Error opening url: ' . $url . ' for update site: ' . $this->updateSiteName, Log::WARNING, 'updater');
			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_OPEN_UPDATE_SITE', $this->updateSiteId, $this->updateSiteName, $url), 'warning');

			return false;
		}

		return $response;
	}
}
src/Updater/Update.php000064400000030471152177723700010712 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Http\HttpFactory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Registry\Registry;

/**
 * Update class. It is used by Updater::update() to install an update. Use Updater::findUpdates() to find updates for
 * an extension.
 *
 * @since  1.7.0
 */
class Update extends \JObject
{
	/**
	 * Update manifest `<name>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $name;

	/**
	 * Update manifest `<description>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $description;

	/**
	 * Update manifest `<element>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $element;

	/**
	 * Update manifest `<type>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type;

	/**
	 * Update manifest `<version>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $version;

	/**
	 * Update manifest `<infourl>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $infourl;

	/**
	 * Update manifest `<client>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $client;

	/**
	 * Update manifest `<group>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $group;

	/**
	 * Update manifest `<downloads>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $downloads;

	/**
	 * Update manifest `<downloadsource>` elements
	 *
	 * @var    DownloadSource[]
	 * @since  3.8.3
	 */
	protected $downloadSources = array();

	/**
	 * Update manifest `<tags>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $tags;

	/**
	 * Update manifest `<maintainer>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $maintainer;

	/**
	 * Update manifest `<maintainerurl>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $maintainerurl;

	/**
	 * Update manifest `<category>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $category;

	/**
	 * Update manifest `<relationships>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $relationships;

	/**
	 * Update manifest `<targetplatform>` element
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $targetplatform;

	/**
	 * Extra query for download URLs
	 *
	 * @var    string
	 * @since  3.2.0
	 */
	protected $extra_query;

	/**
	 * Resource handle for the XML Parser
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $xmlParser;

	/**
	 * Element call stack
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stack = array('base');

	/**
	 * Unused state array
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $stateStore = array();

	/**
	 * Object containing the current update data
	 *
	 * @var    \stdClass
	 * @since  3.0.0
	 */
	protected $currentUpdate;

	/**
	 * Object containing the latest update data
	 *
	 * @var    \stdClass
	 * @since  3.0.0
	 */
	protected $latest;

	/**
	 * The minimum stability required for updates to be taken into account. The possible values are:
	 * 0	dev			Development snapshots, nightly builds, pre-release versions and so on
	 * 1	alpha		Alpha versions (work in progress, things are likely to be broken)
	 * 2	beta		Beta versions (major functionality in place, show-stopper bugs are likely to be present)
	 * 3	rc			Release Candidate versions (almost stable, minor bugs might be present)
	 * 4	stable		Stable versions (production quality code)
	 *
	 * @var    int
	 * @since  14.1
	 *
	 * @see    Updater
	 */
	protected $minimum_stability = Updater::STABILITY_STABLE;

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Get the last position in stack count
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function _getLastTag()
	{
		return $this->stack[count($this->stack) - 1];
	}

	/**
	 * XML Start Element callback
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of the tag found
	 * @param   array   $attrs   Attributes of the tag
	 *
	 * @return  void
	 *
	 * @note    This is public because it is called externally
	 * @since   1.7.0
	 */
	public function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			// This is a new update; create a current update
			case 'UPDATE':
				$this->currentUpdate = new \stdClass;
				break;

			// Handle the array of download sources
			case 'DOWNLOADSOURCE':
				$source = new DownloadSource;

				foreach ($attrs as $key => $data)
				{
					$key = strtolower($key);
					$source->$key = $data;
				}

				$this->downloadSources[] = $source;

				break;

			// Don't do anything
			case 'UPDATES':
				break;

			// For everything else there's...the default!
			default:
				$name = strtolower($name);

				if (!isset($this->currentUpdate->$name))
				{
					$this->currentUpdate->$name = new \stdClass;
				}

				$this->currentUpdate->$name->_data = '';

				foreach ($attrs as $key => $data)
				{
					$key = strtolower($key);
					$this->currentUpdate->$name->$key = $data;
				}
				break;
		}
	}

	/**
	 * Callback for closing the element
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of element that was closed
	 *
	 * @return  void
	 *
	 * @note    This is public because it is called externally
	 * @since   1.7.0
	 */
	public function _endElement($parser, $name)
	{
		array_pop($this->stack);

		switch ($name)
		{
			// Closing update, find the latest version and check
			case 'UPDATE':
				$product = strtolower(InputFilter::getInstance()->clean(Version::PRODUCT, 'cmd'));

				// Support for the min_dev_level and max_dev_level attributes is deprecated, a regexp should be used instead
				if (isset($this->currentUpdate->targetplatform->min_dev_level) || isset($this->currentUpdate->targetplatform->max_dev_level))
				{
					Log::add(
						'Support for the min_dev_level and max_dev_level attributes of an update\'s <targetplatform> tag is deprecated and'
						. ' will be removed in 4.0. The full version should be specified in the version attribute and may optionally be a regexp.',
						Log::WARNING,
						'deprecated'
					);
				}

				/*
				 * Check that the product matches and that the version matches (optionally a regexp)
				 *
				 * Check for optional min_dev_level and max_dev_level attributes to further specify targetplatform (e.g., 3.0.1)
				 */
				$patchVersion = $this->get('jversion.dev_level', Version::PATCH_VERSION);
				$patchMinimumSupported = !isset($this->currentUpdate->targetplatform->min_dev_level)
					|| $patchVersion >= $this->currentUpdate->targetplatform->min_dev_level;

				$patchMaximumSupported = !isset($this->currentUpdate->targetplatform->max_dev_level)
					|| $patchVersion <= $this->currentUpdate->targetplatform->max_dev_level;

				if (isset($this->currentUpdate->targetplatform->name)
					&& $product == $this->currentUpdate->targetplatform->name
					&& preg_match('/^' . $this->currentUpdate->targetplatform->version . '/', $this->get('jversion.full', JVERSION))
					&& $patchMinimumSupported
					&& $patchMaximumSupported)
				{
					$phpMatch = false;

					// Check if PHP version supported via <php_minimum> tag, assume true if tag isn't present
					if (!isset($this->currentUpdate->php_minimum) || version_compare(PHP_VERSION, $this->currentUpdate->php_minimum->_data, '>='))
					{
						$phpMatch = true;
					}

					$dbMatch = false;

					// Check if DB & version is supported via <supported_databases> tag, assume supported if tag isn't present
					if (isset($this->currentUpdate->supported_databases))
					{
						$db           = Factory::getDbo();
						$dbType       = strtolower($db->getServerType());
						$dbVersion    = $db->getVersion();
						$supportedDbs = $this->currentUpdate->supported_databases;

						// Do we have an entry for the database?
						if (isset($supportedDbs->$dbType))
						{
							$minumumVersion = $supportedDbs->$dbType;
							$dbMatch        = version_compare($dbVersion, $minumumVersion, '>=');
						}
					}
					else
					{
						// Set to true if the <supported_databases> tag is not set
						$dbMatch = true;
					}

					// Check minimum stability
					$stabilityMatch = true;

					if (isset($this->currentUpdate->stability) && ($this->currentUpdate->stability < $this->minimum_stability))
					{
						$stabilityMatch = false;
					}

					if ($phpMatch && $stabilityMatch && $dbMatch)
					{
						if (isset($this->latest))
						{
							if (version_compare($this->currentUpdate->version->_data, $this->latest->version->_data, '>') == 1)
							{
								$this->latest = $this->currentUpdate;
							}
						}
						else
						{
							$this->latest = $this->currentUpdate;
						}
					}
				}
				break;
			case 'UPDATES':
				// If the latest item is set then we transfer it to where we want to
				if (isset($this->latest))
				{
					foreach (get_object_vars($this->latest) as $key => $val)
					{
						$this->$key = $val;
					}

					unset($this->latest);
					unset($this->currentUpdate);
				}
				elseif (isset($this->currentUpdate))
				{
					// The update might be for an older version of j!
					unset($this->currentUpdate);
				}
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $data    The data.
	 *
	 * @return  void
	 *
	 * @note    This is public because its called externally.
	 * @since   1.7.0
	 */
	public function _characterData($parser, $data)
	{
		$tag = $this->_getLastTag();

		// Throw the data for this item together
		$tag = strtolower($tag);

		if ($tag == 'tag')
		{
			$this->currentUpdate->stability = $this->stabilityTagToInteger((string) $data);

			return;
		}

		if ($tag == 'downloadsource')
		{
			// Grab the last source so we can append the URL
			$source = end($this->downloadSources);
			$source->url = $data;

			return;
		}

		if (isset($this->currentUpdate->$tag))
		{
			$this->currentUpdate->$tag->_data .= $data;
		}
	}

	/**
	 * Loads an XML file from a URL.
	 *
	 * @param   string  $url                The URL.
	 * @param   int     $minimum_stability  The minimum stability required for updating the extension {@see Updater}
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function loadFromXml($url, $minimum_stability = Updater::STABILITY_STABLE)
	{
		$version    = new Version;
		$httpOption = new Registry;
		$httpOption->set('userAgent', $version->getUserAgent('Joomla', true, false));

		try
		{
			$http = HttpFactory::getHttp($httpOption);
			$response = $http->get($url);
		}
		catch (\RuntimeException $e)
		{
			$response = null;
		}

		if ($response === null || $response->code !== 200)
		{
			// TODO: Add a 'mark bad' setting here somehow
			Log::add(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_OPEN_URL', $url), Log::WARNING, 'jerror');

			return false;
		}

		$this->minimum_stability = $minimum_stability;

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');
		xml_set_character_data_handler($this->xmlParser, '_characterData');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			Log::add(
				sprintf(
					'XML error: %s at line %d', xml_error_string(xml_get_error_code($this->xmlParser)),
					xml_get_current_line_number($this->xmlParser)
				),
				Log::WARNING, 'updater'
			);

			return false;
		}

		xml_parser_free($this->xmlParser);

		return true;
	}

	/**
	 * Converts a tag to numeric stability representation. If the tag doesn't represent a known stability level (one of
	 * dev, alpha, beta, rc, stable) it is ignored.
	 *
	 * @param   string  $tag  The tag string, e.g. dev, alpha, beta, rc, stable
	 *
	 * @return  integer
	 *
	 * @since   3.4
	 */
	protected function stabilityTagToInteger($tag)
	{
		$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' . strtoupper($tag);

		if (defined($constant))
		{
			return constant($constant);
		}

		return Updater::STABILITY_STABLE;
	}
}
src/Updater/Adapter/CollectionAdapter.php000064400000014073152177723700014444 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Version;

/**
 * Collection Update Adapter Class
 *
 * @since  1.7.0
 */
class CollectionAdapter extends UpdateAdapter
{
	/**
	 * Root of the tree
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	protected $base;

	/**
	 * Tree of objects
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $parent = array(0);

	/**
	 * Used to control if an item has a child or not
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $pop_parent = 0;

	/**
	 * A list of discovered update sites
	 *
	 * @var  array
	 */
	protected $update_sites = array();

	/**
	 * A list of discovered updates
	 *
	 * @var  array
	 */
	protected $updates = array();

	/**
	 * Gets the reference to the current direct parent
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	protected function _getStackLocation()
	{
		return implode('->', $this->stack);
	}

	/**
	 * Get the parent tag
	 *
	 * @return  string   parent
	 *
	 * @since   1.7.0
	 */
	protected function _getParent()
	{
		return end($this->parent);
	}

	/**
	 * Opening an XML element
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of element that is opened
	 * @param   array   $attrs   Array of attributes for the element
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			case 'CATEGORY':
				if (isset($attrs['REF']))
				{
					$this->update_sites[] = array('type' => 'collection', 'location' => $attrs['REF'], 'update_site_id' => $this->updateSiteId);
				}
				else
				{
					// This item will have children, so prepare to attach them
					$this->pop_parent = 1;
				}
				break;
			case 'EXTENSION':
				$update = Table::getInstance('update');
				$update->set('update_site_id', $this->updateSiteId);

				foreach ($this->updatecols as $col)
				{
					// Reset the values if it doesn't exist
					if (!array_key_exists($col, $attrs))
					{
						$attrs[$col] = '';

						if ($col == 'CLIENT')
						{
							$attrs[$col] = 'site';
						}
					}
				}

				$client = ApplicationHelper::getClientInfo($attrs['CLIENT'], 1);

				if (isset($client->id))
				{
					$attrs['CLIENT_ID'] = $client->id;
				}

				// Lower case all of the fields
				foreach ($attrs as $key => $attr)
				{
					$values[strtolower($key)] = $attr;
				}

				// Only add the update if it is on the same platform and release as we are
				$ver = new Version;

				// Lower case and remove the exclamation mark
				$product = strtolower(InputFilter::getInstance()->clean($ver::PRODUCT, 'cmd'));

				/*
				 * Set defaults, the extension file should clarify in case but it may be only available in one version
				 * This allows an update site to specify a targetplatform
				 * targetplatformversion can be a regexp, so 1.[56] would be valid for an extension that supports 1.5 and 1.6
				 * Note: Whilst the version is a regexp here, the targetplatform is not (new extension per platform)
				 * Additionally, the version is a regexp here and it may also be in an extension file if the extension is
				 * compatible against multiple versions of the same platform (e.g. a library)
				 */
				if (!isset($values['targetplatform']))
				{
					$values['targetplatform'] = $product;
				}

				// Set this to ourself as a default
				if (!isset($values['targetplatformversion']))
				{
					$values['targetplatformversion'] = $ver::RELEASE;
				}

				// Set this to ourselves as a default
				// validate that we can install the extension
				if ($product == $values['targetplatform'] && preg_match('/^' . $values['targetplatformversion'] . '/', JVERSION))
				{
					$update->bind($values);
					$this->updates[] = $update;
				}
				break;
		}
	}

	/**
	 * Closing an XML element
	 * Note: This is a protected function though has to be exposed externally as a callback
	 *
	 * @param   object  $parser  Parser object
	 * @param   string  $name    Name of the element closing
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _endElement($parser, $name)
	{
		array_pop($this->stack);

		switch ($name)
		{
			case 'CATEGORY':
				if ($this->pop_parent)
				{
					$this->pop_parent = 0;
					array_pop($this->parent);
				}
				break;
		}
	}

	// Note: we don't care about char data in collection because there should be none

	/**
	 * Finds an update
	 *
	 * @param   array  $options  Options to use: update_site_id: the unique ID of the update site to look at
	 *
	 * @return  array  Update_sites and updates discovered
	 *
	 * @since   1.7.0
	 */
	public function findUpdate($options)
	{
		$response = $this->getUpdateSiteResponse($options);

		if ($response === false)
		{
			return false;
		}

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			// If the URL is missing the .xml extension, try appending it and retry loading the update
			if (!$this->appendExtension && (substr($this->_url, -4) != '.xml'))
			{
				$options['append_extension'] = true;

				return $this->findUpdate($options);
			}

			Log::add('Error parsing url: ' . $this->_url, Log::WARNING, 'updater');

			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_COLLECTION_PARSE_URL', $this->_url), 'warning');

			return false;
		}

		// TODO: Decrement the bad counter if non-zero
		return array('update_sites' => $this->update_sites, 'updates' => $this->updates);
	}
}
src/Updater/Adapter/ExtensionAdapter.php000064400000024772152177723700014334 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Updater\Adapter;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Filter\InputFilter;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Updater\UpdateAdapter;
use Joomla\CMS\Updater\Updater;
use Joomla\CMS\Version;

/**
 * Extension class for updater
 *
 * @since  1.7.0
 */
class ExtensionAdapter extends UpdateAdapter
{
	/**
	 * Start element parser callback.
	 *
	 * @param   object  $parser  The parser object.
	 * @param   string  $name    The name of the element.
	 * @param   array   $attrs   The attributes of the element.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _startElement($parser, $name, $attrs = array())
	{
		$this->stack[] = $name;
		$tag           = $this->_getStackLocation();

		// Reset the data
		if (isset($this->$tag))
		{
			$this->$tag->_data = '';
		}

		switch ($name)
		{
			case 'UPDATE':
				$this->currentUpdate = Table::getInstance('update');
				$this->currentUpdate->update_site_id = $this->updateSiteId;
				$this->currentUpdate->detailsurl = $this->_url;
				$this->currentUpdate->folder = '';
				$this->currentUpdate->client_id = 1;
				break;

			// Don't do anything
			case 'UPDATES':
				break;

			default:
				if (in_array($name, $this->updatecols))
				{
					$name = strtolower($name);
					$this->currentUpdate->$name = '';
				}

				if ($name == 'TARGETPLATFORM')
				{
					$this->currentUpdate->targetplatform = $attrs;
				}

				if ($name == 'PHP_MINIMUM')
				{
					$this->currentUpdate->php_minimum = '';
				}

				if ($name == 'SUPPORTED_DATABASES')
				{
					$this->currentUpdate->supported_databases = $attrs;
				}
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $name    The name of the element.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _endElement($parser, $name)
	{
		array_pop($this->stack);

		// @todo remove code: echo 'Closing: '. $name .'<br />';
		switch ($name)
		{
			case 'UPDATE':
				// Lower case and remove the exclamation mark
				$product = strtolower(InputFilter::getInstance()->clean(Version::PRODUCT, 'cmd'));

				// Support for the min_dev_level and max_dev_level attributes is deprecated, a regexp should be used instead
				if (isset($this->currentUpdate->targetplatform->min_dev_level) || isset($this->currentUpdate->targetplatform->max_dev_level))
				{
					Log::add(
						'Support for the min_dev_level and max_dev_level attributes of an update\'s <targetplatform> tag is deprecated and'
						. ' will be removed in 4.0. The full version should be specified in the version attribute and may optionally be a regexp.',
						Log::WARNING,
						'deprecated'
					);
				}

				/*
				 * Check that the product matches and that the version matches (optionally a regexp)
				 *
				 * Check for optional min_dev_level and max_dev_level attributes to further specify targetplatform (e.g., 3.0.1)
				 */
				$patchMinimumSupported = !isset($this->currentUpdate->targetplatform->min_dev_level)
					|| Version::PATCH_VERSION >= $this->currentUpdate->targetplatform->min_dev_level;
				$patchMaximumSupported = !isset($this->currentUpdate->targetplatform->max_dev_level)
					|| Version::PATCH_VERSION <= $this->currentUpdate->targetplatform->max_dev_level;

				if ($product == $this->currentUpdate->targetplatform['NAME']
					&& preg_match('/^' . $this->currentUpdate->targetplatform['VERSION'] . '/', JVERSION)
					&& $patchMinimumSupported
					&& $patchMaximumSupported)
				{
					// Check if PHP version supported via <php_minimum> tag, assume true if tag isn't present
					if (!isset($this->currentUpdate->php_minimum) || version_compare(PHP_VERSION, $this->currentUpdate->php_minimum, '>='))
					{
						$phpMatch = true;
					}
					else
					{
						// Notify the user of the potential update
						$msg = \JText::sprintf(
							'JLIB_INSTALLER_AVAILABLE_UPDATE_PHP_VERSION',
							$this->currentUpdate->name,
							$this->currentUpdate->version,
							$this->currentUpdate->php_minimum,
							PHP_VERSION
						);

						Factory::getApplication()->enqueueMessage($msg, 'warning');

						$phpMatch = false;
					}

					$dbMatch = false;

					// Check if DB & version is supported via <supported_databases> tag, assume supported if tag isn't present
					if (isset($this->currentUpdate->supported_databases))
					{
						$db           = Factory::getDbo();
						$dbType       = strtoupper($db->getServerType());
						$dbVersion    = $db->getVersion();
						$supportedDbs = $this->currentUpdate->supported_databases;

						// Do we have an entry for the database?
						if (array_key_exists($dbType, $supportedDbs))
						{
							$minumumVersion = $supportedDbs[$dbType];
							$dbMatch        = version_compare($dbVersion, $minumumVersion, '>=');

							if (!$dbMatch)
							{
								// Notify the user of the potential update
								$dbMsg = \JText::sprintf(
									'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_MINIMUM',
									$this->currentUpdate->name,
									$this->currentUpdate->version,
									\JText::_($db->name),
									$dbVersion,
									$minumumVersion
								);

								Factory::getApplication()->enqueueMessage($dbMsg, 'warning');
							}
						}
						else
						{
							// Notify the user of the potential update
							$dbMsg = \JText::sprintf(
								'JLIB_INSTALLER_AVAILABLE_UPDATE_DB_TYPE',
								$this->currentUpdate->name,
								$this->currentUpdate->version,
								\JText::_($db->name)
							);

							Factory::getApplication()->enqueueMessage($dbMsg, 'warning');
						}
					}
					else
					{
						// Set to true if the <supported_databases> tag is not set
						$dbMatch = true;
					}

					// Check minimum stability
					$stabilityMatch = true;

					if (isset($this->currentUpdate->stability) && ($this->currentUpdate->stability < $this->minimum_stability))
					{
						$stabilityMatch = false;
					}

					// Some properties aren't valid fields in the update table so unset them to prevent J! from trying to store them
					unset($this->currentUpdate->targetplatform);

					if (isset($this->currentUpdate->php_minimum))
					{
						unset($this->currentUpdate->php_minimum);
					}

					if (isset($this->currentUpdate->supported_databases))
					{
						unset($this->currentUpdate->supported_databases);
					}

					if (isset($this->currentUpdate->stability))
					{
						unset($this->currentUpdate->stability);
					}

					// If the PHP version and minimum stability checks pass, consider this version as a possible update
					if ($phpMatch && $stabilityMatch && $dbMatch)
					{
						if (isset($this->latest))
						{
							// We already have a possible update. Check the version.
							if (version_compare($this->currentUpdate->version, $this->latest->version, '>') == 1)
							{
								$this->latest = $this->currentUpdate;
							}
						}
						else
						{
							// We don't have any possible updates yet, assume this is an available update.
							$this->latest = $this->currentUpdate;
						}
					}
				}
				break;

			case 'UPDATES':
				// :D
				break;
		}
	}

	/**
	 * Character Parser Function
	 *
	 * @param   object  $parser  Parser object.
	 * @param   object  $data    The data.
	 *
	 * @return  void
	 *
	 * @note    This is public because its called externally.
	 * @since   1.7.0
	 */
	protected function _characterData($parser, $data)
	{
		$tag = $this->_getLastTag();

		if (in_array($tag, $this->updatecols))
		{
			$tag = strtolower($tag);
			$this->currentUpdate->$tag .= $data;
		}

		if ($tag == 'PHP_MINIMUM')
		{
			$this->currentUpdate->php_minimum = $data;
		}

		if ($tag == 'TAG')
		{
			$this->currentUpdate->stability = $this->stabilityTagToInteger((string) $data);
		}
	}

	/**
	 * Finds an update.
	 *
	 * @param   array  $options  Update options.
	 *
	 * @return  array  Array containing the array of update sites and array of updates
	 *
	 * @since   1.7.0
	 */
	public function findUpdate($options)
	{
		$response = $this->getUpdateSiteResponse($options);

		if ($response === false)
		{
			return false;
		}

		if (array_key_exists('minimum_stability', $options))
		{
			$this->minimum_stability = $options['minimum_stability'];
		}

		$this->xmlParser = xml_parser_create('');
		xml_set_object($this->xmlParser, $this);
		xml_set_element_handler($this->xmlParser, '_startElement', '_endElement');
		xml_set_character_data_handler($this->xmlParser, '_characterData');

		if (!xml_parse($this->xmlParser, $response->body))
		{
			// If the URL is missing the .xml extension, try appending it and retry loading the update
			if (!$this->appendExtension && (substr($this->_url, -4) != '.xml'))
			{
				$options['append_extension'] = true;

				return $this->findUpdate($options);
			}

			Log::add('Error parsing url: ' . $this->_url, Log::WARNING, 'updater');

			$app = Factory::getApplication();
			$app->enqueueMessage(\JText::sprintf('JLIB_UPDATER_ERROR_EXTENSION_PARSE_URL', $this->_url), 'warning');

			return false;
		}

		xml_parser_free($this->xmlParser);

		if (isset($this->latest))
		{
			if (isset($this->latest->client) && strlen($this->latest->client))
			{
				if (is_numeric($this->latest->client))
				{
					$byName = false;

					// <client> has to be 'administrator' or 'site', numeric values are deprecated. See https://docs.joomla.org/Special:MyLanguage/Design_of_JUpdate
					Log::add(
						'Using numeric values for <client> in the updater xml is deprecated. Use \'administrator\' or \'site\' instead.',
						Log::WARNING, 'deprecated'
					);
				}
				else
				{
					$byName = true;
				}

				$this->latest->client_id = ApplicationHelper::getClientInfo($this->latest->client, $byName)->id;
				unset($this->latest->client);
			}

			$updates = array($this->latest);
		}
		else
		{
			$updates = array();
		}

		return array('update_sites' => array(), 'updates' => $updates);
	}

	/**
	 * Converts a tag to numeric stability representation. If the tag doesn't represent a known stability level (one of
	 * dev, alpha, beta, rc, stable) it is ignored.
	 *
	 * @param   string  $tag  The tag string, e.g. dev, alpha, beta, rc, stable
	 *
	 * @return  integer
	 *
	 * @since   3.4
	 */
	protected function stabilityTagToInteger($tag)
	{
		$constant = '\\Joomla\\CMS\\Updater\\Updater::STABILITY_' . strtoupper($tag);

		if (defined($constant))
		{
			return constant($constant);
		}

		return Updater::STABILITY_STABLE;
	}
}
src/UCM/UCM.php000064400000000542152177723700007130 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\UCM;

defined('JPATH_PLATFORM') or die;

/**
 * Interface to handle UCM
 *
 * @since  3.1
 */
interface UCM
{
}
src/UCM/UCMType.php000064400000014146152177723700007777 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\UCM;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\BaseApplication;

/**
 * UCM Class for handling content types
 *
 * @property-read  string  $core_content_id
 * @property-read  string  $core_type_alias
 * @property-read  string  $core_title
 * @property-read  string  $core_alias
 * @property-read  string  $core_body
 * @property-read  string  $core_state
 *
 * @property-read  string  $core_checked_out_time
 * @property-read  string  $core_checked_out_user_id
 * @property-read  string  $core_access
 * @property-read  string  $core_params
 * @property-read  string  $core_featured
 * @property-read  string  $core_metadata
 * @property-read  string  $core_created_user_id
 * @property-read  string  $core_created_by_alias
 * @property-read  string  $core_created_time
 * @property-read  string  $core_modified_user_id
 * @property-read  string  $core_modified_time
 * @property-read  string  $core_language
 * @property-read  string  $core_publish_up
 * @property-read  string  $core_publish_down
 * @property-read  string  $core_content_item_id
 * @property-read  string  $asset_id
 * @property-read  string  $core_images
 * @property-read  string  $core_urls
 * @property-read  string  $core_hits
 * @property-read  string  $core_version
 * @property-read  string  $core_ordering
 * @property-read  string  $core_metakey
 * @property-read  string  $core_metadesc
 * @property-read  string  $core_catid
 * @property-read  string  $core_xreference
 * @property-read  string  $core_typeid
 *
 * @since  3.1
 */
class UCMType implements UCM
{
	/**
	 * The UCM Type
	 *
	 * @var    UCMType
	 * @since  3.1
	 */
	public $type;

	/**
	 * The Database object
	 *
	 * @var    \JDatabaseDriver
	 * @since  3.1
	 */
	protected $db;

	/**
	 * The alias for the content type
	 *
	 * @var	   string
	 * @since  3.1
	 */
	protected $alias;

	/**
	 * Class constructor
	 *
	 * @param   string            $alias        The alias for the item
	 * @param   \JDatabaseDriver  $database     The database object
	 * @param   BaseApplication   $application  The application object
	 *
	 * @since   3.1
	 */
	public function __construct($alias = null, \JDatabaseDriver $database = null, BaseApplication $application = null)
	{
		$this->db = $database ?: \JFactory::getDbo();
		$app      = $application ?: \JFactory::getApplication();

		// Make the best guess we can in the absence of information.
		$this->alias = $alias ?: $app->input->get('option') . '.' . $app->input->get('view');
		$this->type  = $this->getTypeByAlias($this->alias);
	}

	/**
	 * Get the Content Type
	 *
	 * @param   integer  $pk  The primary key of the alias type
	 *
	 * @return  object  The UCM Type data
	 *
	 * @since   3.1
	 */
	public function getType($pk = null)
	{
		if (!$pk)
		{
			return $this->getTypeByAlias($this->alias);
		}

		$query = $this->db->getQuery(true);
		$query->select('ct.*');
		$query->from($this->db->quoteName('#__content_types', 'ct'));

		$query->where($this->db->quoteName('ct.type_id') . ' = ' . (int) $pk);
		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

	/**
	 * Get the Content Type from the alias
	 *
	 * @param   string  $typeAlias  The alias for the type
	 *
	 * @return  object  The UCM Type data
	 *
	 * @since   3.2
	 */
	public function getTypeByAlias($typeAlias = null)
	{
		$query = $this->db->getQuery(true);
		$query->select('ct.*');
		$query->from($this->db->quoteName('#__content_types', 'ct'));
		$query->where($this->db->quoteName('ct.type_alias') . ' = ' . $this->db->quote($typeAlias));

		$this->db->setQuery($query);

		return $this->db->loadObject();
	}

	/**
	 * Get the Content Type from the table class name
	 *
	 * @param   string  $tableName  The table for the type
	 *
	 * @return  mixed  The UCM Type data if found, false if no match is found
	 *
	 * @since   3.2
	 */
	public function getTypeByTable($tableName)
	{
		$query = $this->db->getQuery(true);
		$query->select('ct.*');
		$query->from($this->db->quoteName('#__content_types', 'ct'));

		// $query->where($this->db->quoteName('ct.type_alias') . ' = ' . (int) $typeAlias);
		$this->db->setQuery($query);

		$types = $this->db->loadObjectList();

		foreach ($types as $type)
		{
			$tableFromType = json_decode($type->table);
			$tableNameFromType = $tableFromType->special->prefix . $tableFromType->special->type;

			if ($tableNameFromType === $tableName)
			{
				return $type;
			}
		}

		return false;
	}

	/**
	 * Retrieves the UCM type ID
	 *
	 * @param   string  $alias  The string of the type alias
	 *
	 * @return  mixed  The ID of the requested type or false if type is not found
	 *
	 * @since   3.1
	 */
	public function getTypeId($alias = null)
	{
		if (!$alias)
		{
			$alias = $this->alias;
		}

		$query = $this->db->getQuery(true);
		$query->select('ct.type_id');
		$query->from($this->db->quoteName('#__content_types', 'ct'));
		$query->where($this->db->quoteName('ct.type_alias') . ' = ' . $this->db->q($alias));

		$this->db->setQuery($query);

		$id = $this->db->loadResult();

		if (!$id)
		{
			return false;
		}

		return $id;
	}

	/**
	 * Method to expand the field mapping
	 *
	 * @param   boolean  $assoc  True to return an associative array.
	 *
	 * @return  mixed  Array or object with field mappings. Defaults to object.
	 *
	 * @since   3.2
	 */
	public function fieldmapExpand($assoc = false)
	{
		if (!empty($this->type->field_mappings))
		{
			return $this->fieldmap = json_decode($this->type->field_mappings, $assoc);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Magic method to get the name of the field mapped to a ucm field (core_something).
	 *
	 * @param   string  $ucmField  The name of the field in JTableCorecontent
	 *
	 * @return  string  The name mapped to the $ucmField for a given content type
	 *
	 * @since   3.2
	 */
	public function __get($ucmField)
	{
		if (!isset($this->fieldmap))
		{
			$this->fieldmapExpand(false);
		}

		return isset($this->fieldmap->common->$ucmField) ? $this->fieldmap->common->$ucmField : null;
	}
}
src/UCM/UCMContent.php000064400000013253152177723700010466 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\UCM;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;

/**
 * Base class for implementing UCM
 *
 * @since  3.1
 */
class UCMContent extends UCMBase
{
	/**
	 * The related table object
	 *
	 * @var    Table
	 * @since  3.1
	 */
	protected $table;

	/**
	 * The UCM data array
	 *
	 * @var    array
	 * @since  3.1
	 */
	public $ucmData;

	/**
	 * Instantiate UCMContent.
	 *
	 * @param   TableInterface  $table  The table object
	 * @param   string          $alias  The type alias
	 * @param   UCMType         $type   The type object
	 *
	 * @since   3.1
	 */
	public function __construct(TableInterface $table = null, $alias = null, UCMType $type = null)
	{
		parent::__construct($alias, $type);

		if ($table)
		{
			$this->table = $table;
		}
		else
		{
			$tableObject = json_decode($this->type->type->table);
			$this->table = Table::getInstance($tableObject->special->type, $tableObject->special->prefix, $tableObject->special->config);
		}
	}

	/**
	 * Method to save the data
	 *
	 * @param   array    $original  The original data to be saved
	 * @param   UCMType  $type      The UCM Type object
	 *
	 * @return  boolean  true
	 *
	 * @since   3.1
	 */
	public function save($original = null, UCMType $type = null)
	{
		$type    = $type ?: $this->type;
		$ucmData = $original ? $this->mapData($original, $type) : $this->ucmData;

		// Store the Common fields
		$this->store($ucmData['common']);

		// Store the special fields
		if (isset($ucmData['special']))
		{
			$table = $this->table;
			$this->store($ucmData['special'], $table, '');
		}

		return true;
	}

	/**
	 * Delete content from the Core Content table
	 *
	 * @param   mixed    $pk    The string/array of id's to delete
	 * @param   UCMType  $type  The content type object
	 *
	 * @return  boolean  True if success
	 *
	 * @since   3.1
	 */
	public function delete($pk, UCMType $type = null)
	{
		$db   = \JFactory::getDbo();
		$type = $type ?: $this->type;

		if (is_array($pk))
		{
			$pk = implode(',', $pk);
		}

		$query = $db->getQuery(true)
			->delete('#__ucm_content')
			->where($db->quoteName('core_type_id') . ' = ' . (int) $type->type_id)
			->where($db->quoteName('core_content_item_id') . ' IN (' . $pk . ')');

		$db->setQuery($query);
		$db->execute();

		return true;
	}

	/**
	 * Map the original content to the Core Content fields
	 *
	 * @param   array    $original  The original data array
	 * @param   UCMType  $type      Type object for this data
	 *
	 * @return  array  $ucmData  The mapped UCM data
	 *
	 * @since   3.1
	 */
	public function mapData($original, UCMType $type = null)
	{
		$contentType = isset($type) ? $type : $this->type;

		$fields = json_decode($contentType->type->field_mappings);

		$ucmData = array();

		$common = is_object($fields->common) ? $fields->common : $fields->common[0];

		foreach ($common as $i => $field)
		{
			if ($field && $field !== 'null' && array_key_exists($field, $original))
			{
				$ucmData['common'][$i] = $original[$field];
			}
		}

		if (array_key_exists('special', $ucmData))
		{
			$special = is_object($fields->special) ? $fields->special : $fields->special[0];

			foreach ($special as $i => $field)
			{
				if ($field && $field !== 'null' && array_key_exists($field, $original))
				{
					$ucmData['special'][$i] = $original[$field];
				}
			}
		}

		$ucmData['common']['core_type_alias'] = $contentType->type->type_alias;
		$ucmData['common']['core_type_id']    = $contentType->type->type_id;

		if (isset($ucmData['special']))
		{
			$ucmData['special']['ucm_id'] = $ucmData['common']['ucm_id'];
		}

		$this->ucmData = $ucmData;

		return $this->ucmData;
	}

	/**
	 * Store data to the appropriate table
	 *
	 * @param   array           $data        Data to be stored
	 * @param   TableInterface  $table       JTable Object
	 * @param   boolean         $primaryKey  Flag that is true for data that are using #__ucm_content as their primary table
	 *
	 * @return  boolean  true on success
	 *
	 * @since   3.1
	 */
	protected function store($data, TableInterface $table = null, $primaryKey = null)
	{
		$table = $table ?: Table::getInstance('Corecontent');

		$typeId     = $this->getType()->type->type_id;
		$primaryKey = $primaryKey ?: $this->getPrimaryKey($typeId, $data['core_content_item_id']);

		if (!$primaryKey)
		{
			// Store the core UCM mappings
			$baseData = array();
			$baseData['ucm_type_id']     = $typeId;
			$baseData['ucm_item_id']     = $data['core_content_item_id'];
			$baseData['ucm_language_id'] = ContentHelper::getLanguageId($data['core_language']);

			if (parent::store($baseData))
			{
				$primaryKey = $this->getPrimaryKey($typeId, $data['core_content_item_id']);
			}
		}

		return parent::store($data, $table, $primaryKey);
	}

	/**
	 * Get the value of the primary key from #__ucm_base
	 *
	 * @param   string   $typeId         The ID for the type
	 * @param   integer  $contentItemId  Value of the primary key in the legacy or secondary table
	 *
	 * @return  integer  The integer of the primary key
	 *
	 * @since   3.1
	 */
	public function getPrimaryKey($typeId, $contentItemId)
	{
		$db = \JFactory::getDbo();
		$queryccid = $db->getQuery(true);
		$queryccid->select($db->quoteName('ucm_id'))
			->from($db->quoteName('#__ucm_base'))
			->where(
				array(
					$db->quoteName('ucm_item_id') . ' = ' . $db->quote($contentItemId),
					$db->quoteName('ucm_type_id') . ' = ' . $db->quote($typeId),
				)
			);
		$db->setQuery($queryccid);

		return $db->loadResult();
	}
}
src/UCM/UCMBase.php000064400000005352152177723700007727 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\UCM;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;

/**
 * Base class for implementing UCM
 *
 * @since  3.1
 */
class UCMBase implements UCM
{
	/**
	 * The UCM type object
	 *
	 * @var    UCMType
	 * @since  3.1
	 */
	protected $type;

	/**
	 * The alias for the content table
	 *
	 * @var    string
	 * @since  3.1
	 */
	protected $alias;

	/**
	 * Instantiate the UCMBase.
	 *
	 * @param   string   $alias  The alias string
	 * @param   UCMType  $type   The type object
	 *
	 * @since   3.1
	 */
	public function __construct($alias = null, UCMType $type = null)
	{
		// Setup dependencies.
		$input = \JFactory::getApplication()->input;
		$this->alias = isset($alias) ? $alias : $input->get('option') . '.' . $input->get('view');

		$this->type = isset($type) ? $type : $this->getType();
	}

	/**
	 * Store data to the appropriate table
	 *
	 * @param   array           $data        Data to be stored
	 * @param   TableInterface  $table       Table Object
	 * @param   string          $primaryKey  The primary key name
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.1
	 * @throws  \Exception
	 */
	protected function store($data, TableInterface $table = null, $primaryKey = null)
	{
		if (!$table)
		{
			$table = Table::getInstance('Ucm');
		}

		$ucmId      = isset($data['ucm_id']) ? $data['ucm_id'] : null;
		$primaryKey = $primaryKey ?: $ucmId;

		if (isset($primaryKey))
		{
			$table->load($primaryKey);
		}

		try
		{
			$table->bind($data);
		}
		catch (\RuntimeException $e)
		{
			throw new \Exception($e->getMessage(), 500, $e);
		}

		try
		{
			$table->store();
		}
		catch (\RuntimeException $e)
		{
			throw new \Exception($e->getMessage(), 500, $e);
		}

		return true;
	}

	/**
	 * Get the UCM Content type.
	 *
	 * @return  UCMType  The UCM content type
	 *
	 * @since   3.1
	 */
	public function getType()
	{
		if (!$this->type)
		{
			$this->type = new UCMType($this->alias);
		}

		return $this->type;
	}

	/**
	 * Method to map the base ucm fields
	 *
	 * @param   array    $original  Data array
	 * @param   UCMType  $type      UCM Content Type
	 *
	 * @return  array  Data array of UCM mappings
	 *
	 * @since   3.1
	 */
	public function mapBase($original, UCMType $type = null)
	{
		$type = $type ?: $this->type;

		$data = array(
			'ucm_type_id' => $type->id,
			'ucm_item_id' => $original[$type->primary_key],
			'ucm_language_id' => ContentHelper::getLanguageId($original['language']),
		);

		return $data;
	}
}
src/Helper/ModuleHelper.php000064400000042065152177723700011672 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\Registry\Registry;

/**
 * Module helper class
 *
 * @since  1.5
 */
abstract class ModuleHelper
{
	/**
	 * Get module by name (real, eg 'Breadcrumbs' or folder, eg 'mod_breadcrumbs')
	 *
	 * @param   string  $name   The name of the module
	 * @param   string  $title  The title of the module, optional
	 *
	 * @return  \stdClass  The Module object
	 *
	 * @since   1.5
	 */
	public static function &getModule($name, $title = null)
	{
		$result = null;
		$modules =& static::load();
		$total = count($modules);

		for ($i = 0; $i < $total; $i++)
		{
			// Match the name of the module
			if ($modules[$i]->name === $name || $modules[$i]->module === $name)
			{
				// Match the title if we're looking for a specific instance of the module
				if (!$title || $modules[$i]->title === $title)
				{
					// Found it
					$result = &$modules[$i];
					break;
				}
			}
		}

		// If we didn't find it, and the name is mod_something, create a dummy object
		if ($result === null && strpos($name, 'mod_') === 0)
		{
			$result            = new \stdClass;
			$result->id        = 0;
			$result->title     = '';
			$result->module    = $name;
			$result->position  = '';
			$result->content   = '';
			$result->showtitle = 0;
			$result->control   = '';
			$result->params    = '';
		}

		return $result;
	}

	/**
	 * Get modules by position
	 *
	 * @param   string  $position  The position of the module
	 *
	 * @return  array  An array of module objects
	 *
	 * @since   1.5
	 */
	public static function &getModules($position)
	{
		$position = strtolower($position);
		$result = array();
		$input  = \JFactory::getApplication()->input;

		$modules =& static::load();

		$total = count($modules);

		for ($i = 0; $i < $total; $i++)
		{
			if ($modules[$i]->position === $position)
			{
				$result[] = &$modules[$i];
			}
		}

		if (count($result) === 0)
		{
			if ($input->getBool('tp') && ComponentHelper::getParams('com_templates')->get('template_positions_display'))
			{
				$result[0] = static::getModule('mod_' . $position);
				$result[0]->title = $position;
				$result[0]->position = $position;
			}
		}

		return $result;
	}

	/**
	 * Checks if a module is enabled. A given module will only be returned
	 * if it meets the following criteria: it is enabled, it is assigned to
	 * the current menu item or all items, and the user meets the access level
	 * requirements.
	 *
	 * @param   string  $module  The module name
	 *
	 * @return  boolean See description for conditions.
	 *
	 * @since   1.5
	 */
	public static function isEnabled($module)
	{
		$result = static::getModule($module);

		return $result !== null && $result->id !== 0;
	}

	/**
	 * Render the module.
	 *
	 * @param   object  $module   A module object.
	 * @param   array   $attribs  An array of attributes for the module (probably from the XML).
	 *
	 * @return  string  The HTML content of the module output.
	 *
	 * @since   1.5
	 */
	public static function renderModule($module, $attribs = array())
	{
		static $chrome;

		// Check that $module is a valid module object
		if (!is_object($module) || !isset($module->module) || !isset($module->params))
		{
			if (JDEBUG)
			{
				\JLog::addLogger(array('text_file' => 'jmodulehelper.log.php'), \JLog::ALL, array('modulehelper'));
				\JLog::add('ModuleHelper::renderModule($module) expects a module object', \JLog::DEBUG, 'modulehelper');
			}

			return;
		}

		if (JDEBUG)
		{
			\JProfiler::getInstance('Application')->mark('beforeRenderModule ' . $module->module . ' (' . $module->title . ')');
		}

		$app = \JFactory::getApplication();

		// Record the scope.
		$scope = $app->scope;

		// Set scope to component name
		$app->scope = $module->module;

		// Get module parameters
		$params = new Registry($module->params);

		// Get the template
		$template = $app->getTemplate();

		// Get module path
		$module->module = preg_replace('/[^A-Z0-9_\.-]/i', '', $module->module);
		$path = JPATH_BASE . '/modules/' . $module->module . '/' . $module->module . '.php';

		// Load the module
		if (file_exists($path))
		{
			$lang = \JFactory::getLanguage();

			$coreLanguageDirectory      = JPATH_BASE;
			$extensionLanguageDirectory = dirname($path);

			$langPaths = $lang->getPaths();

			// Only load the module's language file if it hasn't been already
			if (!$langPaths || (!isset($langPaths[$coreLanguageDirectory]) && !isset($langPaths[$extensionLanguageDirectory])))
			{
				// 1.5 or Core then 1.6 3PD
				$lang->load($module->module, $coreLanguageDirectory, null, false, true) ||
					$lang->load($module->module, $extensionLanguageDirectory, null, false, true);
			}

			$content = '';
			ob_start();
			include $path;
			$module->content = ob_get_contents() . $content;
			ob_end_clean();
		}

		// Load the module chrome functions
		if (!$chrome)
		{
			$chrome = array();
		}

		include_once JPATH_THEMES . '/system/html/modules.php';
		$chromePath = JPATH_THEMES . '/' . $template . '/html/modules.php';

		if (!isset($chrome[$chromePath]))
		{
			if (file_exists($chromePath))
			{
				include_once $chromePath;
			}

			$chrome[$chromePath] = true;
		}

		// Check if the current module has a style param to override template module style
		$paramsChromeStyle = $params->get('style');

		if ($paramsChromeStyle)
		{
			$attribs['style'] = preg_replace('/^(system|' . $template . ')\-/i', '', $paramsChromeStyle);
		}

		// Make sure a style is set
		if (!isset($attribs['style']))
		{
			$attribs['style'] = 'none';
		}

		// Dynamically add outline style
		if ($app->input->getBool('tp') && ComponentHelper::getParams('com_templates')->get('template_positions_display'))
		{
			$attribs['style'] .= ' outline';
		}

		// If the $module is nulled it will return an empty content, otherwise it will render the module normally.
		$app->triggerEvent('onRenderModule', array(&$module, &$attribs));

		if ($module === null || !isset($module->content))
		{
			return '';
		}

		foreach (explode(' ', $attribs['style']) as $style)
		{
			$chromeMethod = 'modChrome_' . $style;

			// Apply chrome and render module
			if (function_exists($chromeMethod))
			{
				$module->style = $attribs['style'];

				ob_start();
				$chromeMethod($module, $params, $attribs);
				$module->content = ob_get_contents();
				ob_end_clean();
			}
		}

		// Revert the scope
		$app->scope = $scope;

		$app->triggerEvent('onAfterRenderModule', array(&$module, &$attribs));

		if (JDEBUG)
		{
			\JProfiler::getInstance('Application')->mark('afterRenderModule ' . $module->module . ' (' . $module->title . ')');
		}

		return $module->content;
	}

	/**
	 * Get the path to a layout for a module
	 *
	 * @param   string  $module  The name of the module
	 * @param   string  $layout  The name of the module layout. If alternative layout, in the form template:filename.
	 *
	 * @return  string  The path to the module layout
	 *
	 * @since   1.5
	 */
	public static function getLayoutPath($module, $layout = 'default')
	{
		$template = \JFactory::getApplication()->getTemplate();
		$defaultLayout = $layout;

		if (strpos($layout, ':') !== false)
		{
			// Get the template and file name from the string
			$temp = explode(':', $layout);
			$template = $temp[0] === '_' ? $template : $temp[0];
			$layout = $temp[1];
			$defaultLayout = $temp[1] ?: 'default';
		}

		// Build the template and base path for the layout
		$tPath = JPATH_THEMES . '/' . $template . '/html/' . $module . '/' . $layout . '.php';
		$bPath = JPATH_BASE . '/modules/' . $module . '/tmpl/' . $defaultLayout . '.php';
		$dPath = JPATH_BASE . '/modules/' . $module . '/tmpl/default.php';

		// If the template has a layout override use it
		if (file_exists($tPath))
		{
			return $tPath;
		}

		if (file_exists($bPath))
		{
			return $bPath;
		}

		return $dPath;
	}

	/**
	 * Load published modules.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use ModuleHelper::load() instead
	 */
	protected static function &_load()
	{
		return static::load();
	}

	/**
	 * Load published modules.
	 *
	 * @return  array
	 *
	 * @since   3.2
	 */
	protected static function &load()
	{
		static $modules;

		if (isset($modules))
		{
			return $modules;
		}

		$app = \JFactory::getApplication();

		$modules = null;

		$app->triggerEvent('onPrepareModuleList', array(&$modules));

		// If the onPrepareModuleList event returns an array of modules, then ignore the default module list creation
		if (!is_array($modules))
		{
			$modules = static::getModuleList();
		}

		$app->triggerEvent('onAfterModuleList', array(&$modules));

		$modules = static::cleanModuleList($modules);

		$app->triggerEvent('onAfterCleanModuleList', array(&$modules));

		return $modules;
	}

	/**
	 * Module list
	 *
	 * @return  array
	 */
	public static function getModuleList()
	{
		$app = \JFactory::getApplication();
		$Itemid = $app->input->getInt('Itemid', 0);
		$groups = implode(',', \JFactory::getUser()->getAuthorisedViewLevels());
		$lang = \JFactory::getLanguage()->getTag();
		$clientId = (int) $app->getClientId();

		// Build a cache ID for the resulting data object
		$cacheId = $groups . '.' . $clientId . '.' . $Itemid;

		$db = \JFactory::getDbo();

		$query = $db->getQuery(true)
			->select('m.id, m.title, m.module, m.position, m.content, m.showtitle, m.params, mm.menuid')
			->from('#__modules AS m')
			->join('LEFT', '#__modules_menu AS mm ON mm.moduleid = m.id')
			->where('m.published = 1')
			->join('LEFT', '#__extensions AS e ON e.element = m.module AND e.client_id = m.client_id')
			->where('e.enabled = 1');

		$date = \JFactory::getDate();
		$now = $date->toSql();
		$nullDate = $db->getNullDate();
		$query->where('(m.publish_up = ' . $db->quote($nullDate) . ' OR m.publish_up <= ' . $db->quote($now) . ')')
			->where('(m.publish_down = ' . $db->quote($nullDate) . ' OR m.publish_down >= ' . $db->quote($now) . ')')
			->where('m.access IN (' . $groups . ')')
			->where('m.client_id = ' . $clientId)
			->where('(mm.menuid = ' . $Itemid . ' OR mm.menuid <= 0)');

		// Filter by language
		if ($app->isClient('site') && $app->getLanguageFilter())
		{
			$query->where('m.language IN (' . $db->quote($lang) . ',' . $db->quote('*') . ')');
			$cacheId .= $lang . '*';
		}

		if ($app->isClient('administrator') && static::isAdminMultilang())
		{
			$query->where('m.language IN (' . $db->quote($lang) . ',' . $db->quote('*') . ')');
			$cacheId .= $lang . '*';
		}

		$query->order('m.position, m.ordering');

		// Set the query
		$db->setQuery($query);

		try
		{
			/** @var \JCacheControllerCallback $cache */
			$cache = \JFactory::getCache('com_modules', 'callback');

			$modules = $cache->get(array($db, 'loadObjectList'), array(), md5($cacheId), false);
		}
		catch (\RuntimeException $e)
		{
			\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_MODULE_LOAD', $e->getMessage()), \JLog::WARNING, 'jerror');

			return array();
		}

		return $modules;
	}

	/**
	 * Clean the module list
	 *
	 * @param   array  $modules  Array with module objects
	 *
	 * @return  array
	 */
	public static function cleanModuleList($modules)
	{
		// Apply negative selections and eliminate duplicates
		$Itemid = \JFactory::getApplication()->input->getInt('Itemid');
		$negId = $Itemid ? -(int) $Itemid : false;
		$clean = array();
		$dupes = array();

		foreach ($modules as $i => $module)
		{
			// The module is excluded if there is an explicit prohibition
			$negHit = ($negId === (int) $module->menuid);

			if (isset($dupes[$module->id]))
			{
				// If this item has been excluded, keep the duplicate flag set,
				// but remove any item from the modules array.
				if ($negHit)
				{
					unset($clean[$module->id]);
				}

				continue;
			}

			$dupes[$module->id] = true;

			// Only accept modules without explicit exclusions.
			if ($negHit)
			{
				continue;
			}

			$module->name = substr($module->module, 4);
			$module->style = null;
			$module->position = strtolower($module->position);

			$clean[$module->id] = $module;
		}

		unset($dupes);

		// Return to simple indexing that matches the query order.
		return array_values($clean);
	}

	/**
	 * Module cache helper
	 *
	 * Caching modes:
	 * To be set in XML:
	 * 'static'      One cache file for all pages with the same module parameters
	 * 'oldstatic'   1.5 definition of module caching, one cache file for all pages
	 *               with the same module id and user aid,
	 * 'itemid'      Changes on itemid change, to be called from inside the module:
	 * 'safeuri'     Id created from $cacheparams->modeparams array,
	 * 'id'          Module sets own cache id's
	 *
	 * @param   object  $module        Module object
	 * @param   object  $moduleparams  Module parameters
	 * @param   object  $cacheparams   Module cache parameters - id or URL parameters, depending on the module cache mode
	 *
	 * @return  string
	 *
	 * @see     \JFilterInput::clean()
	 * @since   1.6
	 */
	public static function moduleCache($module, $moduleparams, $cacheparams)
	{
		if (!isset($cacheparams->modeparams))
		{
			$cacheparams->modeparams = null;
		}

		if (!isset($cacheparams->cachegroup))
		{
			$cacheparams->cachegroup = $module->module;
		}

		$user = \JFactory::getUser();
		$conf = \JFactory::getConfig();

		/** @var \JCacheControllerCallback $cache */
		$cache = \JFactory::getCache($cacheparams->cachegroup, 'callback');

		// Turn cache off for internal callers if parameters are set to off and for all logged in users
		if ($moduleparams->get('owncache') === 0 || $moduleparams->get('owncache') === '0' || $conf->get('caching') == 0 || $user->get('id'))
		{
			$cache->setCaching(false);
		}

		// Module cache is set in seconds, global cache in minutes, setLifeTime works in minutes
		$cache->setLifeTime($moduleparams->get('cache_time', $conf->get('cachetime') * 60) / 60);

		$wrkaroundoptions = array('nopathway' => 1, 'nohead' => 0, 'nomodules' => 1, 'modulemode' => 1, 'mergehead' => 1);

		$wrkarounds = true;
		$view_levels = md5(serialize($user->getAuthorisedViewLevels()));

		switch ($cacheparams->cachemode)
		{
			case 'id':
				$ret = $cache->get(
					array($cacheparams->class, $cacheparams->method),
					$cacheparams->methodparams,
					$cacheparams->modeparams,
					$wrkarounds,
					$wrkaroundoptions
				);
				break;

			case 'safeuri':
				$secureid = null;

				if (is_array($cacheparams->modeparams))
				{
					$input   = \JFactory::getApplication()->input;
					$uri     = $input->getArray();
					$safeuri = new \stdClass;
					$noHtmlFilter = \JFilterInput::getInstance();

					foreach ($cacheparams->modeparams as $key => $value)
					{
						// Use int filter for id/catid to clean out spamy slugs
						if (isset($uri[$key]))
						{
							$safeuri->$key = $noHtmlFilter->clean($uri[$key], $value);
						}
					}
				}

				$secureid = md5(serialize(array($safeuri, $cacheparams->method, $moduleparams)));
				$ret = $cache->get(
					array($cacheparams->class, $cacheparams->method),
					$cacheparams->methodparams,
					$module->id . $view_levels . $secureid,
					$wrkarounds,
					$wrkaroundoptions
				);
				break;

			case 'static':
				$ret = $cache->get(
					array($cacheparams->class, $cacheparams->method),
					$cacheparams->methodparams,
					$module->module . md5(serialize($cacheparams->methodparams)),
					$wrkarounds,
					$wrkaroundoptions
				);
				break;

			// Provided for backward compatibility, not really useful.
			case 'oldstatic':
				$ret = $cache->get(
					array($cacheparams->class, $cacheparams->method),
					$cacheparams->methodparams,
					$module->id . $view_levels,
					$wrkarounds,
					$wrkaroundoptions
				);
				break;

			case 'itemid':
			default:
				$ret = $cache->get(
					array($cacheparams->class, $cacheparams->method),
					$cacheparams->methodparams,
					$module->id . $view_levels . \JFactory::getApplication()->input->getInt('Itemid', null),
					$wrkarounds,
					$wrkaroundoptions
				);
				break;
		}

		return $ret;
	}

	/**
	 * Method to determine if filtering by language is enabled in back-end for modules.
	 *
	 * @return  boolean  True if enabled; false otherwise.
	 *
	 * @since   3.8.0
	 */
	public static function isAdminMultilang()
	{
		static $enabled = false;

		if (count(LanguageHelper::getInstalledLanguages(1)) > 1)
		{
			$enabled = (bool) ComponentHelper::getParams('com_modules')->get('adminlangfilter', 0);
		}

		return $enabled;
	}

	/**
	 * Get module by id
	 *
	 * @param   string  $id  The id of the module
	 *
	 * @return  \stdClass  The Module object
	 *
	 * @since   3.9.0
	 */
	public static function &getModuleById($id)
	{
		$modules =& static::load();

		$total = count($modules);

		for ($i = 0; $i < $total; $i++)
		{
			// Match the id of the module
			if ($modules[$i]->id === $id)
			{
				// Found it
				return $modules[$i];
			}
		}

		// If we didn't find it, create a dummy object
		$result            = new \stdClass;
		$result->id        = 0;
		$result->title     = '';
		$result->module    = '';
		$result->position  = '';
		$result->content   = '';
		$result->showtitle = 0;
		$result->control   = '';
		$result->params    = '';

		return $result;
	}
}
src/Helper/CMSHelper.php000064400000006201152177723700011057 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\TableInterface;
use Joomla\Registry\Registry;

/**
 * Base Helper class.
 *
 * @since  3.2
 */
class CMSHelper
{
	/**
	 * Gets the current language
	 *
	 * @param   boolean  $detectBrowser  Flag indicating whether to use the browser language as a fallback.
	 *
	 * @return  string  The language string
	 *
	 * @since   3.2
	 */
	public function getCurrentLanguage($detectBrowser = true)
	{
		$app = Factory::getApplication();
		$langCode = null;

		// Get the languagefilter parameters
		if (Multilanguage::isEnabled())
		{
			$plugin       = PluginHelper::getPlugin('system', 'languagefilter');
			$pluginParams = new Registry($plugin->params);

			if ((int) $pluginParams->get('lang_cookie', 1) === 1)
			{
				$langCode = $app->input->cookie->getString(ApplicationHelper::getHash('language'));
			}
			else
			{
				$langCode = Factory::getSession()->get('plg_system_languagefilter.language');
			}
		}

		// No cookie - let's try to detect browser language or use site default
		if (!$langCode)
		{
			if ($detectBrowser)
			{
				$langCode = LanguageHelper::detectLanguage();
			}
			else
			{
				$langCode = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
			}
		}

		return $langCode;
	}

	/**
	 * Gets the associated language ID
	 *
	 * @param   string  $langCode  The language code to look up
	 *
	 * @return  integer  The language ID
	 *
	 * @since   3.2
	 */
	public function getLanguageId($langCode)
	{
		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('lang_id')
			->from('#__languages')
			->where($db->quoteName('lang_code') . ' = ' . $db->quote($langCode));
		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Gets a row of data from a table
	 *
	 * @param   TableInterface  $table  Table instance for a row.
	 *
	 * @return  array  Associative array of all columns and values for a row in a table.
	 *
	 * @since   3.2
	 */
	public function getRowData(TableInterface $table)
	{
		$fields = $table->getFields();
		$data = array();

		foreach ($fields as &$field)
		{
			$columnName = $field->Field;
			$value = $table->$columnName;
			$data[$columnName] = $value;
		}

		return $data;
	}

	/**
	 * Method to get an object containing all of the table columns and values.
	 *
	 * @param   TableInterface  $table  Table object.
	 *
	 * @return  \stdClass  Contains all of the columns and values.
	 *
	 * @since   3.2
	 */
	public function getDataObject(TableInterface $table)
	{
		$fields = $table->getFields();
		$dataObject = new \stdClass;

		foreach ($fields as $field)
		{
			$fieldName = $field->Field;
			$dataObject->$fieldName = $table->get($fieldName);
		}

		return $dataObject;
	}
}
src/Helper/UserGroupsHelper.php000064400000013057152177723700012562 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

/**
 * Helper to deal with user groups.
 *
 * @since  3.6.3
 */
final class UserGroupsHelper
{
	/**
	 * Indicates the current helper instance is the singleton instance.
	 *
	 * @var    integer
	 * @since  3.6.3
	 */
	const MODE_SINGLETON = 1;

	/**
	 * Indicates the current helper instance is a standalone class instance.
	 *
	 * @var    integer
	 * @since  3.6.3
	 */
	const MODE_INSTANCE = 2;

	/**
	 * Singleton instance.
	 *
	 * @var    array
	 * @since  3.6.3
	 */
	private static $instance;

	/**
	 * Available user groups
	 *
	 * @var    array
	 * @since  3.6.3
	 */
	private $groups = array();

	/**
	 * Mode this class is working: singleton or std instance
	 *
	 * @var    integer
	 * @since  3.6.3
	 */
	private $mode;

	/**
	 * Total available groups
	 *
	 * @var    integer
	 * @since  3.6.3
	 */
	private $total;

	/**
	 * Constructor
	 *
	 * @param   array    $groups  Array of groups
	 * @param   integer  $mode    Working mode for this class
	 *
	 * @since   3.6.3
	 */
	public function __construct(array $groups = array(), $mode = self::MODE_INSTANCE)
	{
		$this->mode = (int) $mode;

		if ($groups)
		{
			$this->setGroups($groups);
		}
	}

	/**
	 * Count loaded user groups.
	 *
	 * @return  integer
	 *
	 * @since   3.6.3
	 */
	public function count()
	{
		return count($this->groups);
	}

	/**
	 * Get the helper instance.
	 *
	 * @return  self
	 *
	 * @since   3.6.3
	 */
	public static function getInstance()
	{
		if (static::$instance === null)
		{
			// Only here to avoid code style issues...
			$groups = array();

			static::$instance = new static($groups, static::MODE_SINGLETON);
		}

		return static::$instance;
	}

	/**
	 * Get a user group by its id.
	 *
	 * @param   integer  $id  Group identifier
	 *
	 * @return  mixed  stdClass on success. False otherwise
	 *
	 * @since   3.6.3
	 */
	public function get($id)
	{
		if ($this->has($id))
		{
			return $this->groups[$id];
		}

		// Singleton will load groups as they are requested
		if ($this->isSingleton())
		{
			$this->groups[$id] = $this->load($id);

			return $this->groups[$id];
		}

		return false;
	}

	/**
	 * Get the list of existing user groups.
	 *
	 * @return  array
	 *
	 * @since   3.6.3
	 */
	public function getAll()
	{
		if ($this->isSingleton() && $this->total() !== $this->count())
		{
			$this->loadAll();
		}

		return $this->groups;
	}

	/**
	 * Check if a group is in the list.
	 *
	 * @param   integer  $id  Group identifier
	 *
	 * @return  boolean
	 *
	 * @since   3.6.3
	 */
	public function has($id)
	{
		return (array_key_exists($id, $this->groups) && $this->groups[$id] !== false);
	}

	/**
	 * Check if this instance is a singleton.
	 *
	 * @return  boolean
	 *
	 * @since   3.6.3
	 */
	private function isSingleton()
	{
		return $this->mode === static::MODE_SINGLETON;
	}

	/**
	 * Get total available user groups in database.
	 *
	 * @return  integer
	 *
	 * @since   3.6.3
	 */
	public function total()
	{
		if ($this->total === null)
		{
			$db = \JFactory::getDbo();

			$query = $db->getQuery(true)
				->select('count(id)')
				->from('#__usergroups');

			$db->setQuery($query);

			$this->total = (int) $db->loadResult();
		}

		return $this->total;
	}

	/**
	 * Load a group from database.
	 *
	 * @param   integer  $id  Group identifier
	 *
	 * @return  mixed
	 *
	 * @since   3.6.3
	 */
	public function load($id)
	{
		$db = \JFactory::getDbo();

		$query = $db->getQuery(true)
			->select('*')
			->from('#__usergroups')
			->where('id = ' . (int) $id);

		$db->setQuery($query);

		$group = $db->loadObject();

		if (!$group)
		{
			return false;
		}

		return $this->populateGroupData($group);
	}

	/**
	 * Load all user groups from the database.
	 *
	 * @return  self
	 *
	 * @since   3.6.3
	 */
	public function loadAll()
	{
		$this->groups = array();

		$db = \JFactory::getDbo();

		$query = $db->getQuery(true)
			->select('*')
			->from('#__usergroups')
			->order('lft ASC');

		$db->setQuery($query);

		$groups = $db->loadObjectList('id');

		$this->groups = $groups ?: array();
		$this->populateGroupsData();

		return $this;
	}

	/**
	 * Populates extra information for groups.
	 *
	 * @return  array
	 *
	 * @since   3.6.3
	 */
	private function populateGroupsData()
	{
		foreach ($this->groups as $group)
		{
			$this->populateGroupData($group);
		}

		return $this->groups;
	}

	/**
	 * Populate data for a specific user group.
	 *
	 * @param   \stdClass  $group  Group
	 *
	 * @return  \stdClass
	 *
	 * @since   3.6.3
	 */
	public function populateGroupData($group)
	{
		if (!$group || property_exists($group, 'path'))
		{
			return $group;
		}

		$parentId = (int) $group->parent_id;

		if ($parentId === 0)
		{
			$group->path = array($group->id);
			$group->level = 0;

			return $group;
		}

		$parentGroup = $this->has($parentId) ? $this->get($parentId) : $this->load($parentId);

		if (!property_exists($parentGroup, 'path'))
		{
			$parentGroup = $this->populateGroupData($parentGroup);
		}

		$group->path = array_merge($parentGroup->path, array($group->id));
		$group->level = count($group->path) - 1;

		return $group;
	}

	/**
	 * Set the groups to be used as source.
	 *
	 * @param   array  $groups  Array of user groups.
	 *
	 * @return  self
	 *
	 * @since   3.6.3
	 */
	public function setGroups(array $groups)
	{
		$this->groups = $groups;
		$this->populateGroupsData();
		$this->total  = count($groups);

		return $this;
	}
}
src/Helper/SearchHelper.php000064400000003557152177723700011655 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;

/**
 * Helper class for Joomla! Search components
 *
 * @since  3.0
 */
class SearchHelper
{
	/**
	 * Method to log search terms to the database
	 *
	 * @param   string  $term       The term being searched
	 * @param   string  $component  The component being used for the search
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function logSearch($term, $component)
	{
		// Initialise our variables
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);
		$enable_log_searches = ComponentHelper::getParams($component)->get('enabled');

		// Sanitise the term for the database
		$search_term = $db->escape(trim(strtolower($term)));

		if ($enable_log_searches)
		{
			// Query the table to determine if the term has been searched previously
			$query->select($db->quoteName('hits'))
				->from($db->quoteName('#__core_log_searches'))
				->where($db->quoteName('search_term') . ' = ' . $db->quote($search_term));
			$db->setQuery($query);
			$hits = (int) $db->loadResult();

			// Reset the $query object
			$query->clear();

			// Update the table based on the results
			if ($hits)
			{
				$query->update($db->quoteName('#__core_log_searches'))
					->set('hits = (hits + 1)')
					->where($db->quoteName('search_term') . ' = ' . $db->quote($search_term));
			}
			else
			{
				$query->insert($db->quoteName('#__core_log_searches'))
					->columns(array($db->quoteName('search_term'), $db->quoteName('hits')))
					->values($db->quote($search_term) . ', 1');
			}

			// Execute the update query
			$db->setQuery($query);
			$db->execute();
		}
	}
}
src/Helper/ContentHelper.php000064400000020556152177723700012060 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Access\Access;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\LanguageHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;

/**
 * Helper for standard content style extensions.
 * This class mainly simplifies static helper methods often repeated in individual components
 *
 * @since  3.1
 */
class ContentHelper
{
	/**
	 * Configure the Linkbar. Must be implemented by each extension.
	 *
	 * @param   string  $vName  The name of the active view.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public static function addSubmenu($vName)
	{
	}

	/**
	 * Adds Count relations for Category and Tag Managers
	 *
	 * @param   stdClass[]  &$items  The category or tag objects
	 * @param   stdClass    $config  Configuration object allowing to use a custom relations table
	 *
	 * @return  stdClass[]
	 *
	 * @since   3.9.1
	 */
	public static function countRelations(&$items, $config)
	{
		$db = Factory::getDbo();

		// Allow custom state / condition values and custom column names to support custom components
		$counter_names = isset($config->counter_names) ? $config->counter_names : array(
			'-2' => 'count_trashed',
			'0'  => 'count_unpublished',
			'1'  => 'count_published',
			'2'  => 'count_archived',
		);

		// Index category objects by their ID
		$records = array();

		foreach ($items as $item)
		{
			$records[(int) $item->id] = $item;
		}

		// The relation query does not return a value for cases without relations of a particular state / condition, set zero as default
		foreach ($items as $item)
		{
			foreach ($counter_names as $n)
			{
				$item->{$n} = 0;
			}
		}

		// Table alias for related data table below will be 'c', and state / condition column is inside related data table
		$related_tbl = $db->quoteName('#__' . $config->related_tbl, 'c');
		$state_col   = $db->quoteName('c.' . $config->state_col);

		// Supported cases
		switch ($config->relation_type)
		{
			case 'tag_assigments':
				$recid_col = $db->quoteName('ct.' . $config->group_col);

				$query = $db->getQuery(true)
					->from($db->quoteName('#__contentitem_tag_map', 'ct'))
					->join('INNER', $related_tbl . ' ON ' . $db->quoteName('ct.content_item_id') . ' = ' . $db->quoteName('c.id') . ' AND ' .
						$db->quoteName('ct.type_alias') . ' = ' . $db->quote($config->extension)
					);
				break;

			case 'category_or_group':
				$recid_col = $db->quoteName('c.' . $config->group_col);

				$query = $db->getQuery(true)
					->from($related_tbl);
				break;

			default:
				return $items;
		}

		/**
		 * Get relation counts for all category objects with single query
		 * NOTE: 'state IN', allows counting specific states / conditions only, also prevents warnings with custom states / conditions, do not remove
		 */
		$query
			->select($recid_col . ' AS catid, ' . $state_col . ' AS state, COUNT(*) AS count')
			->where($recid_col . ' IN (' . implode(',', array_keys($records)) . ')')
			->where($state_col . ' IN (' . implode(',', array_keys($counter_names)) . ')')
			->group($recid_col . ', ' . $state_col);

		$relationsAll = $db->setQuery($query)->loadObjectList();

		// Loop through the DB data overwritting the above zeros with the found count
		foreach ($relationsAll as $relation)
		{
			// Sanity check in case someone removes the state IN above ... and some views may start throwing warnings
			if (isset($counter_names[$relation->state]))
			{
				$id = (int) $relation->catid;
				$cn = $counter_names[$relation->state];

				$records[$id]->{$cn} = $relation->count;
			}
		}

		return $items;
	}

	/**
	 * Gets a list of the actions that can be performed.
	 *
	 * @param   integer  $categoryId  The category ID.
	 * @param   integer  $id          The item ID.
	 * @param   string   $assetName   The asset name
	 *
	 * @return  \JObject
	 *
	 * @since   3.1
	 * @deprecated  3.2  Use ContentHelper::getActions() instead
	 */
	public static function _getActions($categoryId = 0, $id = 0, $assetName = '')
	{
		// Log usage of deprecated function
		Log::add(__METHOD__ . '() is deprecated, use ContentHelper::getActions() with new arguments order instead.', Log::WARNING, 'deprecated');

		// Reverted a change for version 2.5.6
		$user   = Factory::getUser();
		$result = new \JObject;

		$path = JPATH_ADMINISTRATOR . '/components/' . $assetName . '/access.xml';

		if (empty($id) && empty($categoryId))
		{
			$section = 'component';
		}
		elseif (empty($id))
		{
			$section = 'category';
			$assetName .= '.category.' . (int) $categoryId;
		}
		else
		{
			// Used only in com_content
			$section = 'article';
			$assetName .= '.article.' . (int) $id;
		}

		$actions = Access::getActionsFromFile($path, "/access/section[@name='" . $section . "']/");

		foreach ($actions as $action)
		{
			$result->set($action->name, $user->authorise($action->name, $assetName));
		}

		return $result;
	}

	/**
	 * Gets a list of the actions that can be performed.
	 *
	 * @param   string   $component  The component name.
	 * @param   string   $section    The access section name.
	 * @param   integer  $id         The item ID.
	 *
	 * @return  \JObject
	 *
	 * @since   3.2
	 */
	public static function getActions($component = '', $section = '', $id = 0)
	{
		// Check for deprecated arguments order
		if (is_int($component) || $component === null)
		{
			$result = self::_getActions($component, $section, $id);

			return $result;
		}

		$assetName = $component;

		if ($section && $id)
		{
			$assetName .= '.' . $section . '.' . (int) $id;
		}

		$result = new \JObject;

		$user = Factory::getUser();

		$actions = Access::getActionsFromFile(
			JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml', '/access/section[@name="component"]/'
		);

		if ($actions === false)
		{
			Log::add(
				\JText::sprintf('JLIB_ERROR_COMPONENTS_ACL_CONFIGURATION_FILE_MISSING_OR_IMPROPERLY_STRUCTURED', $component), Log::ERROR, 'jerror'
			);

			return $result;
		}

		foreach ($actions as $action)
		{
			$result->set($action->name, $user->authorise($action->name, $assetName));
		}

		return $result;
	}

	/**
	 * Gets the current language
	 *
	 * @param   boolean  $detectBrowser  Flag indicating whether to use the browser language as a fallback.
	 *
	 * @return  string  The language string
	 *
	 * @since   3.1
	 * @note    CmsHelper::getCurrentLanguage is the preferred method
	 */
	public static function getCurrentLanguage($detectBrowser = true)
	{
		$app = Factory::getApplication();
		$langCode = null;

		// Get the languagefilter parameters
		if (Multilanguage::isEnabled())
		{
			$plugin       = PluginHelper::getPlugin('system', 'languagefilter');
			$pluginParams = new Registry($plugin->params);

			if ((int) $pluginParams->get('lang_cookie', 1) === 1)
			{
				$langCode = $app->input->cookie->getString(ApplicationHelper::getHash('language'));
			}
			else
			{
				$langCode = Factory::getSession()->get('plg_system_languagefilter.language');
			}
		}

		// No cookie - let's try to detect browser language or use site default
		if (!$langCode)
		{
			if ($detectBrowser)
			{
				$langCode = LanguageHelper::detectLanguage();
			}
			else
			{
				$langCode = ComponentHelper::getParams('com_languages')->get('site', 'en-GB');
			}
		}

		return $langCode;
	}

	/**
	 * Gets the associated language ID
	 *
	 * @param   string  $langCode  The language code to look up
	 *
	 * @return  integer  The language ID
	 *
	 * @since   3.1
	 * @note    CmsHelper::getLanguage() is the preferred method.
	 */
	public static function getLanguageId($langCode)
	{
		$db    = Factory::getDbo();
		$query = $db->getQuery(true)
			->select('lang_id')
			->from('#__languages')
			->where($db->quoteName('lang_code') . ' = ' . $db->quote($langCode));
		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Gets a row of data from a table
	 *
	 * @param   Table  $table  Table instance for a row.
	 *
	 * @return  array  Associative array of all columns and values for a row in a table.
	 *
	 * @since   3.1
	 */
	public function getRowData(Table $table)
	{
		$data = new CMSHelper;

		return $data->getRowData($table);
	}
}
src/Helper/MediaHelper.php000064400000025510152177723700011460 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;

/**
 * Media helper class
 *
 * @since  3.2
 */
class MediaHelper
{
	/**
	 * Checks if the file is an image
	 *
	 * @param   string  $fileName  The filename
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public function isImage($fileName)
	{
		static $imageTypes = 'xcf|odg|gif|jpg|png|bmp';

		return preg_match("/\.(?:$imageTypes)$/i", $fileName);
	}

	/**
	 * Gets the file extension for purposed of using an icon
	 *
	 * @param   string  $fileName  The filename
	 *
	 * @return  string  File extension to determine icon
	 *
	 * @since   3.2
	 */
	public static function getTypeIcon($fileName)
	{
		return strtolower(substr($fileName, strrpos($fileName, '.') + 1));
	}

	/**
	 * Get the Mime type
	 *
	 * @param   string   $file     The link to the file to be checked
	 * @param   boolean  $isImage  True if the passed file is an image else false
	 *
	 * @return  mixed    the mime type detected false on error
	 *
	 * @since   3.7.2
	 */
	private function getMimeType($file, $isImage = false)
	{
		// If we can't detect anything mime is false
		$mime = false;

		try
		{
			if ($isImage && function_exists('exif_imagetype'))
			{
				$mime = image_type_to_mime_type(exif_imagetype($file));
			}
			elseif ($isImage && function_exists('getimagesize'))
			{
				$imagesize = getimagesize($file);
				$mime      = isset($imagesize['mime']) ? $imagesize['mime'] : false;
			}
			elseif (function_exists('mime_content_type'))
			{
				// We have mime magic.
				$mime = mime_content_type($file);
			}
			elseif (function_exists('finfo_open'))
			{
				// We have fileinfo
				$finfo = finfo_open(FILEINFO_MIME_TYPE);
				$mime  = finfo_file($finfo, $file);
				finfo_close($finfo);
			}
		}
		catch (\Exception $e)
		{
			// If we have any kind of error here => false;
			return false;
		}

		// If we can't detect the mime try it again
		if ($mime === 'application/octet-stream' && $isImage === true)
		{
			$mime = $this->getMimeType($file, false);
		}

		// We have a mime here
		return $mime;
	}

	/**
	 * Checks the Mime type
	 *
	 * @param   string  $mime       The mime to be checked
	 * @param   string  $component  The optional name for the component storing the parameters
	 *
	 * @return  boolean  true if mime type checking is disabled or it passes the checks else false
	 *
	 * @since   3.7
	 */
	private function checkMimeType($mime, $component = 'com_media')
	{
		$params = ComponentHelper::getParams($component);

		if ($params->get('check_mime', 1))
		{
			// Get the mime type configuration
			$allowedMime = array_map('trim', explode(',', $params->get('upload_mime')));

			// Mime should be available and in the whitelist
			return !empty($mime) && in_array($mime, $allowedMime);
		}

		// We don't check mime at all or it passes the checks
		return true;
	}

	/**
	 * Checks if the file can be uploaded
	 *
	 * @param   array   $file       File information
	 * @param   string  $component  The option name for the component storing the parameters
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public function canUpload($file, $component = 'com_media')
	{
		$app    = \JFactory::getApplication();
		$params = ComponentHelper::getParams($component);

		if (empty($file['name']))
		{
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_UPLOAD_INPUT'), 'error');

			return false;
		}

		jimport('joomla.filesystem.file');

		if (str_replace(' ', '', $file['name']) !== $file['name'] || $file['name'] !== \JFile::makeSafe($file['name']))
		{
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILENAME'), 'error');

			return false;
		}

		$filetypes = explode('.', $file['name']);

		if (count($filetypes) < 2)
		{
			// There seems to be no extension
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'), 'error');

			return false;
		}

		array_shift($filetypes);

		// Media file names should never have executable extensions buried in them.
		$executable = array(
			'php', 'js', 'exe', 'phtml', 'java', 'perl', 'py', 'asp', 'dll', 'go', 'ade', 'adp', 'bat', 'chm', 'cmd', 'com', 'cpl', 'hta', 'ins', 'isp',
			'jse', 'lib', 'mde', 'msc', 'msp', 'mst', 'pif', 'scr', 'sct', 'shb', 'sys', 'vb', 'vbe', 'vbs', 'vxd', 'wsc', 'wsf', 'wsh',
		);

		$check = array_intersect($filetypes, $executable);

		if (!empty($check))
		{
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'), 'error');

			return false;
		}

		$filetype  = array_pop($filetypes);
		$allowable = array_map('trim', explode(',', $params->get('upload_extensions')));
		$ignored   = array_map('trim', explode(',', $params->get('ignore_extensions')));

		if ($filetype == '' || $filetype == false || (!in_array($filetype, $allowable) && !in_array($filetype, $ignored)))
		{
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETYPE'), 'error');

			return false;
		}

		$maxSize = (int) ($params->get('upload_maxsize', 0) * 1024 * 1024);

		if ($maxSize > 0 && (int) $file['size'] > $maxSize)
		{
			$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETOOLARGE'), 'error');

			return false;
		}

		if ($params->get('restrict_uploads', 1))
		{
			$images = array_map('trim', explode(',', $params->get('image_extensions')));

			if (in_array($filetype, $images))
			{
				// If tmp_name is empty, then the file was bigger than the PHP limit
				if (!empty($file['tmp_name']))
				{
					// Get the mime type this is an image file
					$mime = $this->getMimeType($file['tmp_name'], true);

					// Did we get anything useful?
					if ($mime != false)
					{
						$result = $this->checkMimeType($mime, $component);

						// If the mime type is not allowed we don't upload it and show the mime code error to the user
						if ($result === false)
						{
							$app->enqueueMessage(\JText::sprintf('JLIB_MEDIA_ERROR_WARNINVALID_MIMETYPE', $mime), 'error');

							return false;
						}
					}
					// We can't detect the mime type so it looks like an invalid image
					else
					{
						$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNINVALID_IMG'), 'error');

						return false;
					}
				}
				else
				{
					$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNFILETOOLARGE'), 'error');

					return false;
				}
			}
			elseif (!in_array($filetype, $ignored))
			{
				// Get the mime type this is not an image file
				$mime = $this->getMimeType($file['tmp_name'], false);

				// Did we get anything useful?
				if ($mime != false)
				{
					$result = $this->checkMimeType($mime, $component);

					// If the mime type is not allowed we don't upload it and show the mime code error to the user
					if ($result === false)
					{
						$app->enqueueMessage(\JText::sprintf('JLIB_MEDIA_ERROR_WARNINVALID_MIMETYPE', $mime), 'error');

						return false;
					}
				}
				// We can't detect the mime type so it looks like an invalid file
				else
				{
					$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNINVALID_MIME'), 'error');

					return false;
				}

				if (!\JFactory::getUser()->authorise('core.manage', $component))
				{
					$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNNOTADMIN'), 'error');

					return false;
				}
			}
		}

		$xss_check = file_get_contents($file['tmp_name'], false, null, -1, 256);

		$html_tags = array(
			'abbr', 'acronym', 'address', 'applet', 'area', 'audioscope', 'base', 'basefont', 'bdo', 'bgsound', 'big', 'blackface', 'blink',
			'blockquote', 'body', 'bq', 'br', 'button', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'comment', 'custom', 'dd', 'del',
			'dfn', 'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'fn', 'font', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
			'head', 'hr', 'html', 'iframe', 'ilayer', 'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'layer', 'legend', 'li', 'limittext',
			'link', 'listing', 'map', 'marquee', 'menu', 'meta', 'multicol', 'nobr', 'noembed', 'noframes', 'noscript', 'nosmartquotes', 'object',
			'ol', 'optgroup', 'option', 'param', 'plaintext', 'pre', 'rt', 'ruby', 's', 'samp', 'script', 'select', 'server', 'shadow', 'sidebar',
			'small', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'title',
			'tr', 'tt', 'ul', 'var', 'wbr', 'xml', 'xmp', '!DOCTYPE', '!--',
		);

		foreach ($html_tags as $tag)
		{
			// A tag is '<tagname ', so we need to add < and a space or '<tagname>'
			if (stripos($xss_check, '<' . $tag . ' ') !== false || stripos($xss_check, '<' . $tag . '>') !== false)
			{
				$app->enqueueMessage(\JText::_('JLIB_MEDIA_ERROR_WARNIEXSS'), 'error');

				return false;
			}
		}

		return true;
	}

	/**
	 * Calculate the size of a resized image
	 *
	 * @param   integer  $width   Image width
	 * @param   integer  $height  Image height
	 * @param   integer  $target  Target size
	 *
	 * @return  array  The new width and height
	 *
	 * @since   3.2
	 */
	public static function imageResize($width, $height, $target)
	{
		/*
		 * Takes the larger size of the width and height and applies the
		 * formula accordingly. This is so this script will work
		 * dynamically with any size image
		 */
		if ($width > $height)
		{
			$percentage = ($target / $width);
		}
		else
		{
			$percentage = ($target / $height);
		}

		// Gets the new value and applies the percentage, then rounds the value
		$width  = round($width * $percentage);
		$height = round($height * $percentage);

		return array($width, $height);
	}

	/**
	 * Counts the files and directories in a directory that are not php or html files.
	 *
	 * @param   string  $dir  Directory name
	 *
	 * @return  array  The number of media files and directories in the given directory
	 *
	 * @since   3.2
	 */
	public function countFiles($dir)
	{
		$total_file = 0;
		$total_dir  = 0;

		if (is_dir($dir))
		{
			$d = dir($dir);

			while (($entry = $d->read()) !== false)
			{
				if ($entry[0] !== '.' && strpos($entry, '.html') === false && strpos($entry, '.php') === false && is_file($dir . DIRECTORY_SEPARATOR . $entry))
				{
					$total_file++;
				}

				if ($entry[0] !== '.' && is_dir($dir . DIRECTORY_SEPARATOR . $entry))
				{
					$total_dir++;
				}
			}

			$d->close();
		}

		return array($total_file, $total_dir);
	}

	/**
	 * Small helper function that properly converts any
	 * configuration options to their byte representation.
	 *
	 * @param   string|integer  $val  The value to be converted to bytes.
	 *
	 * @return integer The calculated bytes value from the input.
	 *
	 * @since 3.3
	 */
	public function toBytes($val)
	{
		switch ($val[strlen($val) - 1])
		{
			case 'M':
			case 'm':
				return (int) $val * 1048576;
			case 'K':
			case 'k':
				return (int) $val * 1024;
			case 'G':
			case 'g':
				return (int) $val * 1073741824;
			default:
				return $val;
		}
	}
}
src/Helper/AuthenticationHelper.php000064400000002424152177723700013417 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

/**
 * Authentication helper class
 *
 * @since  3.6.3
 */
abstract class AuthenticationHelper
{
	/**
	 * Get the Two Factor Authentication Methods available.
	 *
	 * @return  array  Two factor authentication methods.
	 *
	 * @since   3.6.3
	 */
	public static function getTwoFactorMethods()
	{
		// Get all the Two Factor Authentication plugins.
		\JPluginHelper::importPlugin('twofactorauth');

		// Trigger onUserTwofactorIdentify event and return the two factor enabled plugins.
		$identities = \JEventDispatcher::getInstance()->trigger('onUserTwofactorIdentify', array());

		// Generate array with two factor auth methods.
		$options = array(
			\JHtml::_('select.option', 'none', \JText::_('JGLOBAL_OTPMETHOD_NONE'), 'value', 'text'),
		);

		if (!empty($identities))
		{
			foreach ($identities as $identity)
			{
				if (!is_object($identity))
				{
					continue;
				}

				$options[] = \JHtml::_('select.option', $identity->method, $identity->title, 'value', 'text');
			}
		}

		return $options;
	}
}
src/Helper/TagsHelper.php000064400000073135152177723700011345 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Table\TableInterface;
use Joomla\Utilities\ArrayHelper;

/**
 * Tags helper class, provides methods to perform various tasks relevant
 * tagging of content.
 *
 * @since  3.1
 */
class TagsHelper extends CMSHelper
{
	/**
	 * Helper object for storing and deleting tag information.
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	protected $tagsChanged = false;

	/**
	 * Whether up replace all tags or just add tags
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	protected $replaceTags = false;

	/**
	 * Alias for querying mapping and content type table.
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $typeAlias = null;

	/**
	 * Method to add tag rows to mapping table.
	 *
	 * @param   integer         $ucmId  ID of the #__ucm_content item being tagged
	 * @param   TableInterface  $table  Table object being tagged
	 * @param   array           $tags   Array of tags to be applied.
	 *
	 * @return  boolean  true on success, otherwise false.
	 *
	 * @since   3.1
	 */
	public function addTagMapping($ucmId, TableInterface $table, $tags = array())
	{
		$db = $table->getDbo();
		$key = $table->getKeyName();
		$item = $table->$key;
		$typeId = $this->getTypeId($this->typeAlias);

		// Insert the new tag maps
		if (strpos('#', implode(',', $tags)) === false)
		{
			$tags = self::createTagsFromField($tags);
		}

		// Prevent saving duplicate tags
		$tags = array_unique($tags);

		$query = $db->getQuery(true);
		$query->insert('#__contentitem_tag_map');
		$query->columns(
			array(
				$db->quoteName('type_alias'),
				$db->quoteName('core_content_id'),
				$db->quoteName('content_item_id'),
				$db->quoteName('tag_id'),
				$db->quoteName('tag_date'),
				$db->quoteName('type_id'),
			)
		);

		foreach ($tags as $tag)
		{
			$query->values(
				$db->quote($this->typeAlias)
				. ', ' . (int) $ucmId
				. ', ' . (int) $item
				. ', ' . $db->quote($tag)
				. ', ' . $query->currentTimestamp()
				. ', ' . (int) $typeId
			);
		}

		$db->setQuery($query);

		return (boolean) $db->execute();
	}

	/**
	 * Function that converts tags paths into paths of names
	 *
	 * @param   array  $tags  Array of tags
	 *
	 * @return  array
	 *
	 * @since   3.1
	 */
	public static function convertPathsToNames($tags)
	{
		// We will replace path aliases with tag names
		if ($tags)
		{
			// Create an array with all the aliases of the results
			$aliases = array();

			foreach ($tags as $tag)
			{
				if (!empty($tag->path))
				{
					if ($pathParts = explode('/', $tag->path))
					{
						$aliases = array_merge($aliases, $pathParts);
					}
				}
			}

			// Get the aliases titles in one single query and map the results
			if ($aliases)
			{
				// Remove duplicates
				$aliases = array_unique($aliases);

				$db = \JFactory::getDbo();

				$query = $db->getQuery(true)
					->select('alias, title')
					->from('#__tags')
					->where('alias IN (' . implode(',', array_map(array($db, 'quote'), $aliases)) . ')');
				$db->setQuery($query);

				try
				{
					$aliasesMapper = $db->loadAssocList('alias');
				}
				catch (\RuntimeException $e)
				{
					return false;
				}

				// Rebuild the items path
				if ($aliasesMapper)
				{
					foreach ($tags as $tag)
					{
						$namesPath = array();

						if (!empty($tag->path))
						{
							if ($pathParts = explode('/', $tag->path))
							{
								foreach ($pathParts as $alias)
								{
									if (isset($aliasesMapper[$alias]))
									{
										$namesPath[] = $aliasesMapper[$alias]['title'];
									}
									else
									{
										$namesPath[] = $alias;
									}
								}

								$tag->text = implode('/', $namesPath);
							}
						}
					}
				}
			}
		}

		return $tags;
	}

	/**
	 * Create any new tags by looking for #new# in the strings
	 *
	 * @param   array  $tags  Tags text array from the field
	 *
	 * @return  mixed   If successful, metadata with new tag titles replaced by tag ids. Otherwise false.
	 *
	 * @since   3.1
	 */
	public function createTagsFromField($tags)
	{
		if (empty($tags) || $tags[0] == '')
		{
			return;
		}
		else
		{
			// We will use the tags table to store them
			Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
			$tagTable  = Table::getInstance('Tag', 'TagsTable');
			$newTags   = array();
			$canCreate = \JFactory::getUser()->authorise('core.create', 'com_tags');

			foreach ($tags as $key => $tag)
			{
				// User is not allowed to create tags, so don't create.
				if (!$canCreate && strpos($tag, '#new#') !== false)
				{
					continue;
				}

				// Remove the #new# prefix that identifies new tags
				$tagText = str_replace('#new#', '', $tag);

				if ($tagText === $tag)
				{
					$newTags[] = (int) $tag;
				}
				else
				{
					// Clear old data if exist
					$tagTable->reset();

					// Try to load the selected tag
					if ($tagTable->load(array('title' => $tagText)))
					{
						$newTags[] = (int) $tagTable->id;
					}
					else
					{
						// Prepare tag data
						$tagTable->id = 0;
						$tagTable->title = $tagText;
						$tagTable->published = 1;

						// $tagTable->language = property_exists ($item, 'language') ? $item->language : '*';
						$tagTable->language = '*';
						$tagTable->access = 1;

						// Make this item a child of the root tag
						$tagTable->setLocation($tagTable->getRootId(), 'last-child');

						// Try to store tag
						if ($tagTable->check())
						{
							// Assign the alias as path (autogenerated tags have always level 1)
							$tagTable->path = $tagTable->alias;

							if ($tagTable->store())
							{
								$newTags[] = (int) $tagTable->id;
							}
						}
					}
				}
			}

			// At this point $tags is an array of all tag ids
			$this->tags = $newTags;
			$result = $newTags;
		}

		return $result;
	}

	/**
	 * Create any new tags by looking for #new# in the metadata
	 *
	 * @param   string  $metadata  Metadata JSON string
	 *
	 * @return  mixed   If successful, metadata with new tag titles replaced by tag ids. Otherwise false.
	 *
	 * @since   3.1
	 * @deprecated  4.0  This method is no longer used in the CMS and will not be replaced.
	 */
	public function createTagsFromMetadata($metadata)
	{
		$metaObject = json_decode($metadata);

		if (empty($metaObject->tags))
		{
			return $metadata;
		}

		$tags = $metaObject->tags;

		if (empty($tags) || !is_array($tags))
		{
			$result = $metadata;
		}
		else
		{
			// We will use the tags table to store them
			Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
			$tagTable = Table::getInstance('Tag', 'TagsTable');
			$newTags = array();

			foreach ($tags as $tag)
			{
				// Remove the #new# prefix that identifies new tags
				$tagText = str_replace('#new#', '', $tag);

				if ($tagText === $tag)
				{
					$newTags[] = (int) $tag;
				}
				else
				{
					// Clear old data if exist
					$tagTable->reset();

					// Try to load the selected tag
					if ($tagTable->load(array('title' => $tagText)))
					{
						$newTags[] = (int) $tagTable->id;
					}
					else
					{
						// Prepare tag data
						$tagTable->id = 0;
						$tagTable->title = $tagText;
						$tagTable->published = 1;

						// $tagTable->language = property_exists ($item, 'language') ? $item->language : '*';
						$tagTable->language = '*';
						$tagTable->access = 1;

						// Make this item a child of the root tag
						$tagTable->setLocation($tagTable->getRootId(), 'last-child');

						// Try to store tag
						if ($tagTable->check())
						{
							// Assign the alias as path (autogenerated tags have always level 1)
							$tagTable->path = $tagTable->alias;

							if ($tagTable->store())
							{
								$newTags[] = (int) $tagTable->id;
							}
						}
					}
				}
			}

			// At this point $tags is an array of all tag ids
			$metaObject->tags = $newTags;
			$result = json_encode($metaObject);
		}

		return $result;
	}

	/**
	 * Method to delete the tag mappings and #__ucm_content record for for an item
	 *
	 * @param   TableInterface  $table          Table object of content table where delete occurred
	 * @param   integer|array   $contentItemId  ID of the content item. Or an array of key/value pairs with array key
	 *                                          being a primary key name and value being the content item ID. Note
	 *                                          multiple primary keys are not supported
	 *
	 * @return  boolean  true on success, false on failure
	 *
	 * @since   3.1
	 * @throws  \InvalidArgumentException
	 */
	public function deleteTagData(TableInterface $table, $contentItemId)
	{
		$key = $table->getKeyName();

		if (!is_array($contentItemId))
		{
			$contentItemId = array($key => $contentItemId);
		}

		// If we have multiple items for the content item primary key we currently don't support this so
		// throw an InvalidArgumentException for now
		if (count($contentItemId) != 1)
		{
			throw new \InvalidArgumentException('Multiple primary keys are not supported as a content item id');
		}

		$result = $this->unTagItem($contentItemId[$key], $table);

		/** @var  \JTableCorecontent $ucmContentTable */
		$ucmContentTable = Table::getInstance('Corecontent');

		return $result && $ucmContentTable->deleteByContentId($contentItemId[$key], $this->typeAlias);
	}

	/**
	 * Method to get a list of tags for an item, optionally with the tag data.
	 *
	 * @param   string   $contentType  Content type alias. Dot separated.
	 * @param   integer  $id           Id of the item to retrieve tags for.
	 * @param   boolean  $getTagData   If true, data from the tags table will be included, defaults to true.
	 *
	 * @return  array    Array of of tag objects
	 *
	 * @since   3.1
	 */
	public function getItemTags($contentType, $id, $getTagData = true)
	{
		// Initialize some variables.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('m.tag_id'))
			->from($db->quoteName('#__contentitem_tag_map') . ' AS m ')
			->where(
				array(
					$db->quoteName('m.type_alias') . ' = ' . $db->quote($contentType),
					$db->quoteName('m.content_item_id') . ' = ' . (int) $id,
					$db->quoteName('t.published') . ' = 1',
				)
			);

		$user = \JFactory::getUser();
		$groups = implode(',', $user->getAuthorisedViewLevels());

		$query->where('t.access IN (' . $groups . ')');

		// Optionally filter on language
		$language = ComponentHelper::getParams('com_tags')->get('tag_list_language_filter', 'all');

		if ($language !== 'all')
		{
			if ($language === 'current_language')
			{
				$language = $this->getCurrentLanguage();
			}

			$query->where($db->quoteName('language') . ' IN (' . $db->quote($language) . ', ' . $db->quote('*') . ')');
		}

		if ($getTagData)
		{
			$query->select($db->quoteName('t') . '.*');
		}

		$query->join('INNER', $db->quoteName('#__tags') . ' AS t ' . ' ON ' . $db->quoteName('m.tag_id') . ' = ' . $db->quoteName('t.id'));

		$db->setQuery($query);
		$this->itemTags = $db->loadObjectList();

		return $this->itemTags;
	}

	/**
	 * Method to get a list of tags for a given item.
	 * Normally used for displaying a list of tags within a layout
	 *
	 * @param   mixed   $ids     The id or array of ids (primary key) of the item to be tagged.
	 * @param   string  $prefix  Dot separated string with the option and view to be used for a url.
	 *
	 * @return  string   Comma separated list of tag Ids.
	 *
	 * @since   3.1
	 */
	public function getTagIds($ids, $prefix)
	{
		if (empty($ids))
		{
			return;
		}

		/**
		 * Ids possible formats:
		 * ---------------------
		 * 	$id = 1;
		 *  $id = array(1,2);
		 *  $id = array('1,3,4,19');
		 *  $id = '1,3';
		 */
		$ids = (array) $ids;
		$ids = implode(',', $ids);
		$ids = explode(',', $ids);
		$ids = ArrayHelper::toInteger($ids);

		$db = \JFactory::getDbo();

		// Load the tags.
		$query = $db->getQuery(true)
			->select($db->quoteName('t.id'))
			->from($db->quoteName('#__tags') . ' AS t ')
			->join(
				'INNER', $db->quoteName('#__contentitem_tag_map') . ' AS m'
				. ' ON ' . $db->quoteName('m.tag_id') . ' = ' . $db->quoteName('t.id')
				. ' AND ' . $db->quoteName('m.type_alias') . ' = ' . $db->quote($prefix)
				. ' AND ' . $db->quoteName('m.content_item_id') . ' IN ( ' . implode(',', $ids) . ')'
			);

		$db->setQuery($query);

		// Add the tags to the content data.
		$tagsList = $db->loadColumn();
		$this->tags = implode(',', $tagsList);

		return $this->tags;
	}

	/**
	 * Method to get a query to retrieve a detailed list of items for a tag.
	 *
	 * @param   mixed    $tagId            Tag or array of tags to be matched
	 * @param   mixed    $typesr           Null, type or array of type aliases for content types to be included in the results
	 * @param   boolean  $includeChildren  True to include the results from child tags
	 * @param   string   $orderByOption    Column to order the results by
	 * @param   string   $orderDir         Direction to sort the results in
	 * @param   boolean  $anyOrAll         True to include items matching at least one tag, false to include
	 *                                     items all tags in the array.
	 * @param   string   $languageFilter   Optional filter on language. Options are 'all', 'current' or any string.
	 * @param   string   $stateFilter      Optional filtering on publication state, defaults to published or unpublished.
	 *
	 * @return  \JDatabaseQuery  Query to retrieve a list of tags
	 *
	 * @since   3.1
	 */
	public function getTagItemsQuery($tagId, $typesr = null, $includeChildren = false, $orderByOption = 'c.core_title', $orderDir = 'ASC',
		$anyOrAll = true, $languageFilter = 'all', $stateFilter = '0,1')
	{
		// Create a new query object.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);
		$user = \JFactory::getUser();
		$nullDate = $db->quote($db->getNullDate());
		$nowDate = $db->quote(\JFactory::getDate()->toSql());

		// Force ids to array and sanitize
		$tagIds = (array) $tagId;
		$tagIds = implode(',', $tagIds);
		$tagIds = explode(',', $tagIds);
		$tagIds = ArrayHelper::toInteger($tagIds);

		$ntagsr = count($tagIds);

		// If we want to include children we have to adjust the list of tags.
		// We do not search child tags when the match all option is selected.
		if ($includeChildren)
		{
			$tagTreeArray = array();

			foreach ($tagIds as $tag)
			{
				$this->getTagTreeArray($tag, $tagTreeArray);
			}

			$tagIds = array_unique(array_merge($tagIds, $tagTreeArray));
		}

		// Sanitize filter states
		$stateFilters = explode(',', $stateFilter);
		$stateFilters = ArrayHelper::toInteger($stateFilters);

		// M is the mapping table. C is the core_content table. Ct is the content_types table.
		$query
			->select(
				'm.type_alias'
				. ', ' . 'm.content_item_id'
				. ', ' . 'm.core_content_id'
				. ', ' . 'count(m.tag_id) AS match_count'
				. ', ' . 'MAX(m.tag_date) as tag_date'
				. ', ' . 'MAX(c.core_title) AS core_title'
				. ', ' . 'MAX(c.core_params) AS core_params'
			)
			->select('MAX(c.core_alias) AS core_alias, MAX(c.core_body) AS core_body, MAX(c.core_state) AS core_state, MAX(c.core_access) AS core_access')
			->select(
				'MAX(c.core_metadata) AS core_metadata'
				. ', ' . 'MAX(c.core_created_user_id) AS core_created_user_id'
				. ', ' . 'MAX(c.core_created_by_alias) AS core_created_by_alias'
			)
			->select('MAX(c.core_created_time) as core_created_time, MAX(c.core_images) as core_images')
			->select('CASE WHEN c.core_modified_time = ' . $nullDate . ' THEN c.core_created_time ELSE c.core_modified_time END as core_modified_time')
			->select('MAX(c.core_language) AS core_language, MAX(c.core_catid) AS core_catid')
			->select('MAX(c.core_publish_up) AS core_publish_up, MAX(c.core_publish_down) as core_publish_down')
			->select('MAX(ct.type_title) AS content_type_title, MAX(ct.router) AS router')

			->from('#__contentitem_tag_map AS m')
			->join(
				'INNER',
				'#__ucm_content AS c ON m.type_alias = c.core_type_alias AND m.core_content_id = c.core_content_id AND c.core_state IN ('
					. implode(',', $stateFilters) . ')'
					. (in_array('0', $stateFilters) ? '' : ' AND (c.core_publish_up = ' . $nullDate
					. ' OR c.core_publish_up <= ' . $nowDate . ') '
					. ' AND (c.core_publish_down = ' . $nullDate . ' OR  c.core_publish_down >= ' . $nowDate . ')')
			)
			->join('INNER', '#__content_types AS ct ON ct.type_alias = m.type_alias')

			// Join over categories for get only tags from published categories
			->join('LEFT', '#__categories AS tc ON tc.id = c.core_catid')

			// Join over the users for the author and email
			->select("CASE WHEN c.core_created_by_alias > ' ' THEN c.core_created_by_alias ELSE ua.name END AS author")
			->select('ua.email AS author_email')

			->join('LEFT', '#__users AS ua ON ua.id = c.core_created_user_id')

			->where('m.tag_id IN (' . implode(',', $tagIds) . ')')
			->where('(c.core_catid = 0 OR tc.published = 1)');

		// Optionally filter on language
		if (empty($language))
		{
			$language = $languageFilter;
		}

		if ($language !== 'all')
		{
			if ($language === 'current_language')
			{
				$language = $this->getCurrentLanguage();
			}

			$query->where($db->quoteName('c.core_language') . ' IN (' . $db->quote($language) . ', ' . $db->quote('*') . ')');
		}

		// Get the type data, limited to types in the request if there are any specified.
		$typesarray = self::getTypes('assocList', $typesr, false);

		$typeAliases = array();

		foreach ($typesarray as $type)
		{
			$typeAliases[] = $db->quote($type['type_alias']);
		}

		$query->where('m.type_alias IN (' . implode(',', $typeAliases) . ')');

		$groups = '0,' . implode(',', array_unique($user->getAuthorisedViewLevels()));
		$query->where('c.core_access IN (' . $groups . ')')
			->group('m.type_alias, m.content_item_id, m.core_content_id, core_modified_time, core_created_time, core_created_by_alias, author, author_email');

		// Use HAVING if matching all tags and we are matching more than one tag.
		if ($ntagsr > 1 && $anyOrAll != 1 && $includeChildren != 1)
		{
			// The number of results should equal the number of tags requested.
			$query->having("COUNT('m.tag_id') = " . (int) $ntagsr);
		}

		// Set up the order by using the option chosen
		if ($orderByOption === 'match_count')
		{
			$orderBy = 'COUNT(m.tag_id)';
		}
		else
		{
			$orderBy = 'MAX(' . $db->quoteName($orderByOption) . ')';
		}

		$query->order($orderBy . ' ' . $orderDir);

		return $query;
	}

	/**
	 * Function that converts tag ids to their tag names
	 *
	 * @param   array  $tagIds  Array of integer tag ids.
	 *
	 * @return  array  An array of tag names.
	 *
	 * @since   3.1
	 */
	public function getTagNames($tagIds)
	{
		$tagNames = array();

		if (is_array($tagIds) && count($tagIds) > 0)
		{
			$tagIds = ArrayHelper::toInteger($tagIds);

			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName('title'))
				->from($db->quoteName('#__tags'))
				->where($db->quoteName('id') . ' IN (' . implode(',', $tagIds) . ')');
			$query->order($db->quoteName('title'));

			$db->setQuery($query);
			$tagNames = $db->loadColumn();
		}

		return $tagNames;
	}

	/**
	 * Method to get an array of tag ids for the current tag and its children
	 *
	 * @param   integer  $id             An optional ID
	 * @param   array    &$tagTreeArray  Array containing the tag tree
	 *
	 * @return  mixed
	 *
	 * @since   3.1
	 */
	public function getTagTreeArray($id, &$tagTreeArray = array())
	{
		// Get a level row instance.
		Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
		$table = Table::getInstance('Tag', 'TagsTable');

		if ($table->isLeaf($id))
		{
			$tagTreeArray[] = $id;

			return $tagTreeArray;
		}

		$tagTree = $table->getTree($id);

		// Attempt to load the tree
		if ($tagTree)
		{
			foreach ($tagTree as $tag)
			{
				$tagTreeArray[] = $tag->id;
			}

			return $tagTreeArray;
		}
	}

	/**
	 * Method to get the type id for a type alias.
	 *
	 * @param   string  $typeAlias  A type alias.
	 *
	 * @return  string  Name of the table for a type
	 *
	 * @since   3.1
	 * @deprecated  4.0  Use \JUcmType::getTypeId() instead
	 */
	public function getTypeId($typeAlias)
	{
		$contentType = new \JUcmType;

		return $contentType->getTypeId($typeAlias);
	}

	/**
	 * Method to get a list of types with associated data.
	 *
	 * @param   string   $arrayType    Optionally specify that the returned list consist of objects, associative arrays, or arrays.
	 *                                 Options are: rowList, assocList, and objectList
	 * @param   array    $selectTypes  Optional array of type ids to limit the results to. Often from a request.
	 * @param   boolean  $useAlias     If true, the alias is used to match, if false the type_id is used.
	 *
	 * @return  array   Array of of types
	 *
	 * @since   3.1
	 */
	public static function getTypes($arrayType = 'objectList', $selectTypes = null, $useAlias = true)
	{
		// Initialize some variables.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('*');

		if (!empty($selectTypes))
		{
			$selectTypes = (array) $selectTypes;

			if ($useAlias)
			{
				$selectTypes = array_map(array($db, 'quote'), $selectTypes);

				$query->where($db->quoteName('type_alias') . ' IN (' . implode(',', $selectTypes) . ')');
			}
			else
			{
				$selectTypes = ArrayHelper::toInteger($selectTypes);

				$query->where($db->quoteName('type_id') . ' IN (' . implode(',', $selectTypes) . ')');
			}
		}

		$query->from($db->quoteName('#__content_types'));

		$db->setQuery($query);

		switch ($arrayType)
		{
			case 'assocList':
				$types = $db->loadAssocList();
				break;

			case 'rowList':
				$types = $db->loadRowList();
				break;

			case 'objectList':
			default:
				$types = $db->loadObjectList();
				break;
		}

		return $types;
	}

	/**
	 * Function that handles saving tags used in a table class after a store()
	 *
	 * @param   TableInterface  $table    Table being processed
	 * @param   array           $newTags  Array of new tags
	 * @param   boolean         $replace  Flag indicating if all exising tags should be replaced
	 *
	 * @return  boolean
	 *
	 * @since   3.1
	 */
	public function postStoreProcess(TableInterface $table, $newTags = array(), $replace = true)
	{
		if (!empty($table->newTags) && empty($newTags))
		{
			$newTags = $table->newTags;
		}

		// If existing row, check to see if tags have changed.
		$newTable = clone $table;
		$newTable->reset();

		$result = true;

		// Process ucm_content and ucm_base if either tags have changed or we have some tags.
		if ($this->tagsChanged || (!empty($newTags) && $newTags[0] != ''))
		{
			if (!$newTags && $replace == true)
			{
				// Delete all tags data
				$key = $table->getKeyName();
				$result = $this->deleteTagData($table, $table->$key);
			}
			else
			{
				// Process the tags
				$data = $this->getRowData($table);
				$ucmContentTable = Table::getInstance('Corecontent');

				$ucm = new \JUcmContent($table, $this->typeAlias);
				$ucmData = $data ? $ucm->mapData($data) : $ucm->ucmData;

				$primaryId = $ucm->getPrimaryKey($ucmData['common']['core_type_id'], $ucmData['common']['core_content_item_id']);
				$result = $ucmContentTable->load($primaryId);
				$result = $result && $ucmContentTable->bind($ucmData['common']);
				$result = $result && $ucmContentTable->check();
				$result = $result && $ucmContentTable->store();
				$ucmId = $ucmContentTable->core_content_id;

				// Store the tag data if the article data was saved and run related methods.
				$result = $result && $this->tagItem($ucmId, $table, $newTags, $replace);
			}
		}

		return $result;
	}

	/**
	 * Function that preProcesses data from a table prior to a store() to ensure proper tag handling
	 *
	 * @param   TableInterface  $table    Table being processed
	 * @param   array           $newTags  Array of new tags
	 *
	 * @return  null
	 *
	 * @since   3.1
	 */
	public function preStoreProcess(TableInterface $table, $newTags = array())
	{
		if ($newTags != array())
		{
			$this->newTags = $newTags;
		}

		// If existing row, check to see if tags have changed.
		$oldTable = clone $table;
		$oldTable->reset();
		$key = $oldTable->getKeyName();
		$typeAlias = $this->typeAlias;

		if ($oldTable->$key && $oldTable->load())
		{
			$this->oldTags = $this->getTagIds($oldTable->$key, $typeAlias);
		}

		// New items with no tags bypass this step.
		if ((!empty($newTags) && is_string($newTags) || (isset($newTags[0]) && $newTags[0] != '')) || isset($this->oldTags))
		{
			if (is_array($newTags))
			{
				$newTags = implode(',', $newTags);
			}

			// We need to process tags if the tags have changed or if we have a new row
			$this->tagsChanged = (empty($this->oldTags) && !empty($newTags)) ||(!empty($this->oldTags) && $this->oldTags != $newTags) || !$table->$key;
		}
	}

	/**
	 * Function to search tags
	 *
	 * @param   array  $filters  Filter to apply to the search
	 *
	 * @return  array
	 *
	 * @since   3.1
	 */
	public static function searchTags($filters = array())
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('a.id AS value')
			->select('a.path AS text')
			->select('a.path')
			->from('#__tags AS a')
			->join('LEFT', $db->quoteName('#__tags', 'b') . ' ON a.lft > b.lft AND a.rgt < b.rgt');

		// Filter language
		if (!empty($filters['flanguage']))
		{
			$query->where('a.language IN (' . $db->quote($filters['flanguage']) . ',' . $db->quote('*') . ') ');
		}

		// Do not return root
		$query->where($db->quoteName('a.alias') . ' <> ' . $db->quote('root'));

		// Search in title or path
		if (!empty($filters['like']))
		{
			$query->where(
				'(' . $db->quoteName('a.title') . ' LIKE ' . $db->quote('%' . $filters['like'] . '%')
					. ' OR ' . $db->quoteName('a.path') . ' LIKE ' . $db->quote('%' . $filters['like'] . '%') . ')'
			);
		}

		// Filter title
		if (!empty($filters['title']))
		{
			$query->where($db->quoteName('a.title') . ' = ' . $db->quote($filters['title']));
		}

		// Filter on the published state
		if (isset($filters['published']) && is_numeric($filters['published']))
		{
			$query->where('a.published = ' . (int) $filters['published']);
		}

		// Filter on the access level
		if (isset($filters['access']) && is_array($filters['access']) && count($filters['access']))
		{
			$groups = ArrayHelper::toInteger($filters['access']);
			$query->where('a.access IN (' . implode(",", $groups) . ')');
		}

		// Filter by parent_id
		if (isset($filters['parent_id']) && is_numeric($filters['parent_id']))
		{
			Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_tags/tables');
			$tagTable = Table::getInstance('Tag', 'TagsTable');

			if ($children = $tagTable->getTree($filters['parent_id']))
			{
				foreach ($children as $child)
				{
					$childrenIds[] = $child->id;
				}

				$query->where('a.id IN (' . implode(',', $childrenIds) . ')');
			}
		}

		$query->group('a.id, a.title, a.level, a.lft, a.rgt, a.parent_id, a.published, a.path')
			->order('a.lft ASC');

		// Get the options.
		$db->setQuery($query);

		try
		{
			$results = $db->loadObjectList();
		}
		catch (\RuntimeException $e)
		{
			return array();
		}

		// We will replace path aliases with tag names
		return self::convertPathsToNames($results);
	}

	/**
	 * Method to delete all instances of a tag from the mapping table. Generally used when a tag is deleted.
	 *
	 * @param   integer  $tag_id  The tag_id (primary key) for the deleted tag.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public function tagDeleteInstances($tag_id)
	{
		// Delete the old tag maps.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete($db->quoteName('#__contentitem_tag_map'))
			->where($db->quoteName('tag_id') . ' = ' . (int) $tag_id);
		$db->setQuery($query);
		$db->execute();
	}

	/**
	 * Method to add or update tags associated with an item.
	 *
	 * @param   integer         $ucmId    Id of the #__ucm_content item being tagged
	 * @param   TableInterface  $table    Table object being tagged
	 * @param   array           $tags     Array of tags to be applied.
	 * @param   boolean         $replace  Flag indicating if all exising tags should be replaced
	 *
	 * @return  boolean  true on success, otherwise false.
	 *
	 * @since   3.1
	 */
	public function tagItem($ucmId, TableInterface $table, $tags = array(), $replace = true)
	{
		$key = $table->get('_tbl_key');
		$oldTags = $this->getTagIds((int) $table->$key, $this->typeAlias);
		$oldTags = explode(',', $oldTags);
		$result = $this->unTagItem($ucmId, $table);

		if ($replace)
		{
			$newTags = $tags;
		}
		else
		{
			if ($tags == array())
			{
				$newTags = $table->newTags;
			}
			else
			{
				$newTags = $tags;
			}

			if ($oldTags[0] != '')
			{
				$newTags = array_unique(array_merge($newTags, $oldTags));
			}
		}

		if (is_array($newTags) && count($newTags) > 0 && $newTags[0] != '')
		{
			$result = $result && $this->addTagMapping($ucmId, $table, $newTags);
		}

		return $result;
	}

	/**
	 * Method to untag an item
	 *
	 * @param   integer         $contentId  ID of the content item being untagged
	 * @param   TableInterface  $table      Table object being untagged
	 * @param   array           $tags       Array of tags to be untagged. Use an empty array to untag all existing tags.
	 *
	 * @return  boolean  true on success, otherwise false.
	 *
	 * @since   3.1
	 */
	public function unTagItem($contentId, TableInterface $table, $tags = array())
	{
		$key = $table->getKeyName();
		$id = $table->$key;
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete('#__contentitem_tag_map')
			->where($db->quoteName('type_alias') . ' = ' . $db->quote($this->typeAlias))
			->where($db->quoteName('content_item_id') . ' = ' . (int) $id);

		if (is_array($tags) && count($tags) > 0)
		{
			$tags = ArrayHelper::toInteger($tags);

			$query->where($db->quoteName('tag_id') . ' IN (' . implode(',', $tags) . ')');
		}

		$db->setQuery($query);

		return (boolean) $db->execute();
	}
}
src/Helper/ContentHistoryHelper.php000064400000011105152177723700013430 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Table\Table;

/**
 * Versions helper class, provides methods to perform various tasks relevant
 * versioning of content.
 *
 * @since  3.2
 */
class ContentHistoryHelper extends CMSHelper
{
	/**
	 * Alias for storing type in versions table
	 *
	 * @var    string
	 * @since  3.2
	 */
	public $typeAlias = null;

	/**
	 * Constructor
	 *
	 * @param   string  $typeAlias  The type of content to be versioned (for example, 'com_content.article').
	 *
	 * @since   3.2
	 */
	public function __construct($typeAlias = null)
	{
		$this->typeAlias = $typeAlias;
	}

	/**
	 * Method to delete the history for an item.
	 *
	 * @param   Table  $table  Table object being versioned
	 *
	 * @return  boolean  true on success, otherwise false.
	 *
	 * @since   3.2
	 */
	public function deleteHistory($table)
	{
		$key = $table->getKeyName();
		$id = $table->$key;
		$typeTable = Table::getInstance('Contenttype', 'JTable');
		$typeId = $typeTable->getTypeId($this->typeAlias);
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);
		$query->delete($db->quoteName('#__ucm_history'))
			->where($db->quoteName('ucm_item_id') . ' = ' . (int) $id)
			->where($db->quoteName('ucm_type_id') . ' = ' . (int) $typeId);
		$db->setQuery($query);

		return $db->execute();
	}

	/**
	 * Method to get a list of available versions of this item.
	 *
	 * @param   integer  $typeId  Type id for this component item.
	 * @param   mixed    $id      Primary key of row to get history for.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   3.2
	 */
	public function getHistory($typeId, $id)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);
		$query->select($db->quoteName('h.version_note') . ',' . $db->quoteName('h.save_date') . ',' . $db->quoteName('u.name'))
			->from($db->quoteName('#__ucm_history') . ' AS h ')
			->leftJoin($db->quoteName('#__users') . ' AS u ON ' . $db->quoteName('u.id') . ' = ' . $db->quoteName('h.editor_user_id'))
			->where($db->quoteName('ucm_item_id') . ' = ' . $db->quote($id))
			->where($db->quoteName('ucm_type_id') . ' = ' . (int) $typeId)
			->order($db->quoteName('save_date') . ' DESC ');
		$db->setQuery($query);

		return $db->loadObjectList();
	}

	/**
	 * Method to save a version snapshot to the content history table.
	 *
	 * @param   Table  $table  Table object being versioned
	 *
	 * @return  boolean  True on success, otherwise false.
	 *
	 * @since   3.2
	 */
	public function store($table)
	{
		$dataObject = $this->getDataObject($table);
		$historyTable = Table::getInstance('Contenthistory', 'JTable');
		$typeTable = Table::getInstance('Contenttype', 'JTable');
		$typeTable->load(array('type_alias' => $this->typeAlias));
		$historyTable->set('ucm_type_id', $typeTable->type_id);

		$key = $table->getKeyName();
		$historyTable->set('ucm_item_id', $table->$key);

		// Don't store unless we have a non-zero item id
		if (!$historyTable->ucm_item_id)
		{
			return true;
		}

		$historyTable->set('version_data', json_encode($dataObject));
		$input = \JFactory::getApplication()->input;
		$data = $input->get('jform', array(), 'array');
		$versionName = false;

		if (isset($data['version_note']))
		{
			$versionName = \JFilterInput::getInstance()->clean($data['version_note'], 'string');
			$historyTable->set('version_note', $versionName);
		}

		// Don't save if hash already exists and same version note
		$historyTable->set('sha1_hash', $historyTable->getSha1($dataObject, $typeTable));

		if ($historyRow = $historyTable->getHashMatch())
		{
			if (!$versionName || ($historyRow->version_note === $versionName))
			{
				return true;
			}
			else
			{
				// Update existing row to set version note
				$historyTable->set('version_id', $historyRow->version_id);
			}
		}

		$result = $historyTable->store();

		// Load history_limit config from extension.
		$aliasParts = explode('.', $this->typeAlias);

		$context = isset($aliasParts[1]) ? $aliasParts[1] : '';

		$maxVersionsContext = ComponentHelper::getParams($aliasParts[0])->get('history_limit' . '_' . $context, 0);

		if ($maxVersionsContext)
		{
			$historyTable->deleteOldVersions($maxVersionsContext);
		}
		elseif ($maxVersions = ComponentHelper::getParams($aliasParts[0])->get('history_limit', 0))
		{
			$historyTable->deleteOldVersions($maxVersions);
		}

		return $result;
	}
}
src/Helper/RouteHelper.php000064400000016044152177723700011541 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Language\Multilanguage;

/**
 * Route Helper
 *
 * A class providing basic routing for urls that are for content types found in
 * the #__content_types table and rows found in the #__ucm_content table.
 *
 * @since  3.1
 */
class RouteHelper
{
	/**
	 * @var    array  Holds the reverse lookup
	 * @since  3.1
	 */
	protected static $lookup;

	/**
	 * @var    string  Option for the extension (such as com_content)
	 * @since  3.1
	 */
	protected $extension;

	/**
	 * @var    string  Value of the primary key in the content type table
	 * @since  3.1
	 */
	protected $id;

	/**
	 * @var    string  Name of the view for the url
	 * @since  3.1
	 */
	protected $view;

	/**
	 * A method to get the route for a specific item
	 *
	 * @param   integer  $id         Value of the primary key for the item in its content table
	 * @param   string   $typealias  The type_alias for the item being routed. Of the form extension.view.
	 * @param   string   $link       The link to be routed
	 * @param   string   $language   The language of the content for multilingual sites
	 * @param   integer  $catid      Optional category id
	 *
	 * @return  string  The route of the item
	 *
	 * @since   3.1
	 */
	public function getRoute($id, $typealias, $link = '', $language = null, $catid = null)
	{
		$typeExploded = explode('.', $typealias);

		if (isset($typeExploded[1]))
		{
			$this->view = $typeExploded[1];
			$this->extension = $typeExploded[0];
		}
		else
		{
			$this->view = \JFactory::getApplication()->input->getCmd('view');
			$this->extension = \JFactory::getApplication()->input->getCmd('option');
		}

		$name = ucfirst(substr_replace($this->extension, '', 0, 4));

		$needles = array();

		if (isset($this->view))
		{
			$needles[$this->view] = array((int) $id);
		}

		if (empty($link))
		{
			// Create the link
			$link = 'index.php?option=' . $this->extension . '&view=' . $this->view . '&id=' . $id;
		}

		if ($catid > 1)
		{
			$categories = \JCategories::getInstance($name);

			if ($categories)
			{
				$category = $categories->get((int) $catid);

				if ($category)
				{
					$needles['category'] = array_reverse($category->getPath());
					$needles['categories'] = $needles['category'];
					$link .= '&catid=' . $catid;
				}
			}
		}

		// Deal with languages only if needed
		if (!empty($language) && $language !== '*' && Multilanguage::isEnabled())
		{
			$link .= '&lang=' . $language;
			$needles['language'] = $language;
		}

		if ($item = $this->findItem($needles))
		{
			$link .= '&Itemid=' . $item;
		}

		return $link;
	}

	/**
	 * Method to find the item in the menu structure
	 *
	 * @param   array  $needles  Array of lookup values
	 *
	 * @return  mixed
	 *
	 * @since   3.1
	 */
	protected function findItem($needles = array())
	{
		$app      = \JFactory::getApplication();
		$menus    = $app->getMenu('site');
		$language = isset($needles['language']) ? $needles['language'] : '*';

		// $this->extension may not be set if coming from a static method, check it
		if ($this->extension === null)
		{
			$this->extension = $app->input->getCmd('option');
		}

		// Prepare the reverse lookup array.
		if (!isset(static::$lookup[$language]))
		{
			static::$lookup[$language] = array();

			$component = ComponentHelper::getComponent($this->extension);

			$attributes = array('component_id');
			$values     = array($component->id);

			if ($language !== '*')
			{
				$attributes[] = 'language';
				$values[]     = array($needles['language'], '*');
			}

			$items = $menus->getItems($attributes, $values);

			foreach ($items as $item)
			{
				if (isset($item->query) && isset($item->query['view']))
				{
					$view = $item->query['view'];

					if (!isset(static::$lookup[$language][$view]))
					{
						static::$lookup[$language][$view] = array();
					}

					if (isset($item->query['id']))
					{
						if (is_array($item->query['id']))
						{
							$item->query['id'] = $item->query['id'][0];
						}

						/*
						 * Here it will become a bit tricky
						 * $language != * can override existing entries
						 * $language == * cannot override existing entries
						 */
						if ($item->language !== '*' || !isset(static::$lookup[$language][$view][$item->query['id']]))
						{
							static::$lookup[$language][$view][$item->query['id']] = $item->id;
						}
					}
				}
			}
		}

		if ($needles)
		{
			foreach ($needles as $view => $ids)
			{
				if (isset(static::$lookup[$language][$view]))
				{
					foreach ($ids as $id)
					{
						if (isset(static::$lookup[$language][$view][(int) $id]))
						{
							return static::$lookup[$language][$view][(int) $id];
						}
					}
				}
			}
		}

		$active = $menus->getActive();

		if ($active && $active->component === $this->extension && ($active->language === '*' || !Multilanguage::isEnabled()))
		{
			return $active->id;
		}

		// If not found, return language specific home link
		$default = $menus->getDefault($language);

		return !empty($default->id) ? $default->id : null;
	}

	/**
	 * Fetches the category route
	 *
	 * @param   mixed   $catid      Category ID or \JCategoryNode instance
	 * @param   mixed   $language   Language code
	 * @param   string  $extension  Extension to lookup
	 *
	 * @return  string
	 *
	 * @since   3.2
	 *
	 * @throws  \InvalidArgumentException
	 */
	public static function getCategoryRoute($catid, $language = 0, $extension = '')
	{
		// Note: $extension is required but has to be an optional argument in the function call due to argument order
		if (empty($extension))
		{
			throw new \InvalidArgumentException(sprintf('$extension is a required argument in %s()', __METHOD__));
		}

		if ($catid instanceof \JCategoryNode)
		{
			$id       = $catid->id;
			$category = $catid;
		}
		else
		{
			$extensionName = ucfirst(substr($extension, 4));
			$id            = (int) $catid;
			$category      = \JCategories::getInstance($extensionName)->get($id);
		}

		if ($id < 1)
		{
			$link = '';
		}
		else
		{
			$link = 'index.php?option=' . $extension . '&view=category&id=' . $id;

			$needles = array(
				'category' => array($id),
			);

			if ($language && $language !== '*' && Multilanguage::isEnabled())
			{
				$link .= '&lang=' . $language;
				$needles['language'] = $language;
			}

			// Create the link
			if ($category)
			{
				$catids                = array_reverse($category->getPath());
				$needles['category']   = $catids;
				$needles['categories'] = $catids;
			}

			if ($item = static::lookupItem($needles))
			{
				$link .= '&Itemid=' . $item;
			}
		}

		return $link;
	}

	/**
	 * Static alias to findItem() used to find the item in the menu structure
	 *
	 * @param   array  $needles  Array of lookup values
	 *
	 * @return  mixed
	 *
	 * @since   3.2
	 */
	protected static function lookupItem($needles = array())
	{
		$instance = new static;

		return $instance->findItem($needles);
	}
}
src/Helper/LibraryHelper.php000064400000011171152177723700012043 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Helper;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Library helper class
 *
 * @since  3.2
 */
class LibraryHelper
{
	/**
	 * The component list cache
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $libraries = array();

	/**
	 * Get the library information.
	 *
	 * @param   string   $element  Element of the library in the extensions table.
	 * @param   boolean  $strict   If set and the library does not exist, the enabled attribute will be set to false.
	 *
	 * @return  \stdClass   An object with the library's information.
	 *
	 * @since   3.2
	 */
	public static function getLibrary($element, $strict = false)
	{
		// Is already cached?
		if (isset(static::$libraries[$element]) || static::loadLibrary($element))
		{
			$result = static::$libraries[$element];

			// Convert the params to an object.
			if (is_string($result->params))
			{
				$result->params = new Registry($result->params);
			}
		}
		else
		{
			$result = new \stdClass;
			$result->enabled = $strict ? false : true;
			$result->params = new Registry;
		}

		return $result;
	}

	/**
	 * Checks if a library is enabled
	 *
	 * @param   string  $element  Element of the library in the extensions table.
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public static function isEnabled($element)
	{
		return static::getLibrary($element, true)->enabled;
	}

	/**
	 * Gets the parameter object for the library
	 *
	 * @param   string   $element  Element of the library in the extensions table.
	 * @param   boolean  $strict   If set and the library does not exist, false will be returned
	 *
	 * @return  Registry  A Registry object.
	 *
	 * @see     Registry
	 * @since   3.2
	 */
	public static function getParams($element, $strict = false)
	{
		return static::getLibrary($element, $strict)->params;
	}

	/**
	 * Save the parameters object for the library
	 *
	 * @param   string    $element  Element of the library in the extensions table.
	 * @param   Registry  $params   Params to save
	 *
	 * @return  Registry  A Registry object.
	 *
	 * @see     Registry
	 * @since   3.2
	 */
	public static function saveParams($element, $params)
	{
		if (static::isEnabled($element))
		{
			// Save params in DB
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->update($db->quoteName('#__extensions'))
				->set($db->quoteName('params') . ' = ' . $db->quote($params->toString()))
				->where($db->quoteName('type') . ' = ' . $db->quote('library'))
				->where($db->quoteName('element') . ' = ' . $db->quote($element));
			$db->setQuery($query);

			$result = $db->execute();

			// Update params in libraries cache
			if ($result && isset(static::$libraries[$element]))
			{
				static::$libraries[$element]->params = $params;
			}

			return $result;
		}

		return false;
	}

	/**
	 * Load the installed library into the libraries property.
	 *
	 * @param   string  $element  The element value for the extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.2
	 * @deprecated  4.0  Use LibraryHelper::loadLibrary() instead
	 */
	protected static function _load($element)
	{
		return static::loadLibrary($element);
	}

	/**
	 * Load the installed library into the libraries property.
	 *
	 * @param   string  $element  The element value for the extension
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.7.0
	 */
	protected static function loadLibrary($element)
	{
		$loader = function($element)
		{
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName(array('extension_id', 'element', 'params', 'enabled'), array('id', 'option', null, null)))
				->from($db->quoteName('#__extensions'))
				->where($db->quoteName('type') . ' = ' . $db->quote('library'))
				->where($db->quoteName('element') . ' = ' . $db->quote($element));
			$db->setQuery($query);

			return $db->loadObject();
		};

		/** @var \JCacheControllerCallback $cache */
		$cache = \JFactory::getCache('_system', 'callback');

		try
		{
			static::$libraries[$element] = $cache->get($loader, array($element), __METHOD__ . $element);
		}
		catch (\JCacheException $e)
		{
			static::$libraries[$element] = $loader($element);
		}

		if (empty(static::$libraries[$element]))
		{
			// Fatal error.
			$error = \JText::_('JLIB_APPLICATION_ERROR_LIBRARY_NOT_FOUND');
			\JLog::add(\JText::sprintf('JLIB_APPLICATION_ERROR_LIBRARY_NOT_LOADING', $element, $error), \JLog::WARNING, 'jerror');

			return false;
		}

		return true;
	}
}
src/User/UserWrapper.php000064400000020117152177723700011255 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\User;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for UserHelper
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
 */
class UserWrapper
{
	/**
	 * Helper wrapper method for addUserToGroup
	 *
	 * @param   integer  $userId   The id of the user.
	 * @param   integer  $groupId  The id of the group.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     UserHelper::addUserToGroup()
	 * @since   3.4
	 * @throws  \RuntimeException
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function addUserToGroup($userId, $groupId)
	{
		return UserHelper::addUserToGroup($userId, $groupId);
	}

	/**
	 * Helper wrapper method for getUserGroups
	 *
	 * @param   integer  $userId  The id of the user.
	 *
	 * @return  array    List of groups
	 *
	 * @see     UserHelper::addUserToGroup()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function getUserGroups($userId)
	{
		return UserHelper::getUserGroups($userId);
	}

	/**
	 * Helper wrapper method for removeUserFromGroup
	 *
	 * @param   integer  $userId   The id of the user.
	 * @param   integer  $groupId  The id of the group.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     UserHelper::removeUserFromGroup()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function removeUserFromGroup($userId, $groupId)
	{
		return UserHelper::removeUserFromGroup($userId, $groupId);
	}

	/**
	 * Helper wrapper method for setUserGroups
	 *
	 * @param   integer  $userId  The id of the user.
	 * @param   array    $groups  An array of group ids to put the user in.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     UserHelper::setUserGroups()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function setUserGroups($userId, $groups)
	{
		return UserHelper::setUserGroups($userId, $groups);
	}

	/**
	 * Helper wrapper method for getProfile
	 *
	 * @param   integer  $userId  The id of the user.
	 *
	 * @return  object
	 *
	 * @see     UserHelper::getProfile()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function getProfile($userId = 0)
	{
		return UserHelper::getProfile($userId);
	}

	/**
	 * Helper wrapper method for activateUser
	 *
	 * @param   string  $activation  Activation string
	 *
	 * @return  boolean  True on success
	 *
	 * @see     UserHelper::activateUser()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function activateUser($activation)
	{
		return UserHelper::activateUser($activation);
	}

	/**
	 * Helper wrapper method for getUserId
	 *
	 * @param   string  $username  The username to search on.
	 *
	 * @return  integer  The user id or 0 if not found.
	 *
	 * @see     UserHelper::getUserId()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function getUserId($username)
	{
		return UserHelper::getUserId($username);
	}

	/**
	 * Helper wrapper method for hashPassword
	 *
	 * @param   string   $password   The plaintext password to encrypt.
	 * @param   integer  $algorithm  The hashing algorithm to use, represented by `PASSWORD_*` constants.
	 * @param   array    $options    The options for the algorithm to use.
	 *
	 * @return  string  The encrypted password.
	 *
	 * @see     UserHelper::hashPassword()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function hashPassword($password, $algorithm = PASSWORD_BCRYPT, array $options = array())
	{
		return UserHelper::hashPassword($password, $algorithm, $options);
	}

	/**
	 * Helper wrapper method for verifyPassword
	 *
	 * @param   string   $password  The plaintext password to check.
	 * @param   string   $hash      The hash to verify against.
	 * @param   integer  $user_id   ID of the user if the password hash should be updated
	 *
	 * @return  boolean  True if the password and hash match, false otherwise
	 *
	 * @see     UserHelper::verifyPassword()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function verifyPassword($password, $hash, $user_id = 0)
	{
		return UserHelper::verifyPassword($password, $hash, $user_id);
	}

	/**
	 * Helper wrapper method for getCryptedPassword
	 *
	 * @param   string   $plaintext     The plaintext password to encrypt.
	 * @param   string   $salt          The salt to use to encrypt the password. []
	 *                                  If not present, a new salt will be
	 *                                  generated.
	 * @param   string   $encryption    The kind of password encryption to use.
	 *                                  Defaults to md5-hex.
	 * @param   boolean  $show_encrypt  Some password systems prepend the kind of
	 *                                  encryption to the crypted password ({SHA},
	 *                                  etc). Defaults to false.
	 *
	 * @return  string  The encrypted password.
	 *
	 * @see     UserHelper::getCryptedPassword()
	 * @since   3.4
	 * @deprecated  4.0
	 */
	public function getCryptedPassword($plaintext, $salt = '', $encryption = 'md5-hex', $show_encrypt = false)
	{
		return UserHelper::getCryptedPassword($plaintext, $salt, $encryption, $show_encrypt);
	}

	/**
	 * Helper wrapper method for getSalt
	 *
	 * @param   string  $encryption  The kind of password encryption to use.
	 *                               Defaults to md5-hex.
	 * @param   string  $seed        The seed to get the salt from (probably a
	 *                               previously generated password). Defaults to
	 *                               generating a new seed.
	 * @param   string  $plaintext   The plaintext password that we're generating
	 *                               a salt for. Defaults to none.
	 *
	 * @return  string  The generated or extracted salt.
	 *
	 * @see     UserHelper::getSalt()
	 * @since   3.4
	 * @deprecated  4.0
	 */
	public function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '')
	{
		return UserHelper::getSalt($encryption, $seed, $plaintext);
	}

	/**
	 * Helper wrapper method for genRandomPassword
	 *
	 * @param   integer  $length  Length of the password to generate
	 *
	 * @return  string  Random Password
	 *
	 * @see     UserHelper::genRandomPassword()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function genRandomPassword($length = 8)
	{
		return UserHelper::genRandomPassword($length);
	}

	/**
	 * Helper wrapper method for invalidateCookie
	 *
	 * @param   string  $userId      User ID for this user
	 * @param   string  $cookieName  Series id (cookie name decoded)
	 *
	 * @return  boolean  True on success
	 *
	 * @see     UserHelper::invalidateCookie()
	 * @since   3.4
	 * @deprecated  4.0
	 */
	public function invalidateCookie($userId, $cookieName)
	{
		return UserHelper::invalidateCookie($userId, $cookieName);
	}

	/**
	 * Helper wrapper method for clearExpiredTokens
	 *
	 * @return  mixed  Database query result
	 *
	 * @see     UserHelper::clearExpiredTokens()
	 * @since   3.4
	 * @deprecated  4.0
	 */
	public function clearExpiredTokens()
	{
		return UserHelper::clearExpiredTokens();
	}

	/**
	 * Helper wrapper method for getRememberCookieData
	 *
	 * @return  mixed  An array of information from an authentication cookie or false if there is no cookie
	 *
	 * @see     UserHelper::getRememberCookieData()
	 * @since   3.4
	 * @deprecated  4.0
	 */
	public function getRememberCookieData()
	{
		return UserHelper::getRememberCookieData();
	}

	/**
	 * Helper wrapper method for getShortHashedUserAgent
	 *
	 * @return  string  A hashed user agent string with version replaced by 'abcd'
	 *
	 * @see     UserHelper::getShortHashedUserAgent()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	public function getShortHashedUserAgent()
	{
		return UserHelper::getShortHashedUserAgent();
	}
}
src/User/User.php000064400000050711152177723700007717 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\User;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Table\Table;
use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

/**
 * User class.  Handles all application interaction with a user
 *
 * @since  1.7.0
 */
class User extends \JObject
{
	/**
	 * A cached switch for if this user has root access rights.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $isRoot = null;

	/**
	 * Unique id
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $id = null;

	/**
	 * The user's real name (or nickname)
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $name = null;

	/**
	 * The login name
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $username = null;

	/**
	 * The email
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $email = null;

	/**
	 * MD5 encrypted password
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $password = null;

	/**
	 * Clear password, only available when a new password is set for a user
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $password_clear = '';

	/**
	 * Block status
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $block = null;

	/**
	 * Should this user receive system email
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $sendEmail = null;

	/**
	 * Date the user was registered
	 *
	 * @var    \DateTime
	 * @since  1.7.0
	 */
	public $registerDate = null;

	/**
	 * Date of last visit
	 *
	 * @var    \DateTime
	 * @since  1.7.0
	 */
	public $lastvisitDate = null;

	/**
	 * Activation hash
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $activation = null;

	/**
	 * User parameters
	 *
	 * @var    Registry
	 * @since  1.7.0
	 */
	public $params = null;

	/**
	 * Associative array of user names => group ids
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $groups = array();

	/**
	 * Guest status
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $guest = null;

	/**
	 * Last Reset Time
	 *
	 * @var    string
	 * @since  3.0.1
	 */
	public $lastResetTime = null;

	/**
	 * Count since last Reset Time
	 *
	 * @var    int
	 * @since  3.0.1
	 */
	public $resetCount = null;

	/**
	 * Flag to require the user's password be reset
	 *
	 * @var    int
	 * @since  3.2
	 */
	public $requireReset = null;

	/**
	 * User parameters
	 *
	 * @var    Registry
	 * @since  1.7.0
	 */
	protected $_params = null;

	/**
	 * Authorised access groups
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_authGroups = null;

	/**
	 * Authorised access levels
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_authLevels = null;

	/**
	 * Authorised access actions
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_authActions = null;

	/**
	 * Error message
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_errorMsg = null;

	/**
	 * UserWrapper object
	 *
	 * @var    UserWrapper
	 * @since  3.4
	 * @deprecated  4.0  Use `Joomla\CMS\User\UserHelper` directly
	 */
	protected $userHelper = null;

	/**
	 * @var    array  User instances container.
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Constructor activating the default information of the language
	 *
	 * @param   integer      $identifier  The primary key of the user to load (optional).
	 * @param   UserWrapper  $userHelper  The UserWrapper for the static methods. [@deprecated 4.0]
	 *
	 * @since   1.7.0
	 */
	public function __construct($identifier = 0, UserWrapper $userHelper = null)
	{
		if (null === $userHelper)
		{
			$userHelper = new UserWrapper;
		}

		$this->userHelper = $userHelper;

		// Create the user parameters object
		$this->_params = new Registry;

		// Load the user if it exists
		if (!empty($identifier))
		{
			$this->load($identifier);
		}
		else
		{
			// Initialise
			$this->id = 0;
			$this->sendEmail = 0;
			$this->aid = 0;
			$this->guest = 1;
		}
	}

	/**
	 * Returns the global User object, only creating it if it doesn't already exist.
	 *
	 * @param   integer      $identifier  The primary key of the user to load (optional).
	 * @param   UserWrapper  $userHelper  The UserWrapper for the static methods. [@deprecated 4.0]
	 *
	 * @return  User  The User object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($identifier = 0, UserWrapper $userHelper = null)
	{
		if (null === $userHelper)
		{
			$userHelper = new UserWrapper;
		}

		// Find the user id
		if (!is_numeric($identifier))
		{
			if (!$id = $userHelper->getUserId($identifier))
			{
				// If the $identifier doesn't match with any id, just return an empty User.
				return new User;
			}
		}
		else
		{
			$id = $identifier;
		}

		// If the $id is zero, just return an empty User.
		// Note: don't cache this user because it'll have a new ID on save!
		if ($id === 0)
		{
			return new User;
		}

		// Check if the user ID is already cached.
		if (empty(self::$instances[$id]))
		{
			$user = new User($id, $userHelper);
			self::$instances[$id] = $user;
		}

		return self::$instances[$id];
	}

	/**
	 * Method to get a parameter value
	 *
	 * @param   string  $key      Parameter key
	 * @param   mixed   $default  Parameter default value
	 *
	 * @return  mixed  The value or the default if it did not exist
	 *
	 * @since   1.7.0
	 */
	public function getParam($key, $default = null)
	{
		return $this->_params->get($key, $default);
	}

	/**
	 * Method to set a parameter
	 *
	 * @param   string  $key    Parameter key
	 * @param   mixed   $value  Parameter value
	 *
	 * @return  mixed  Set parameter value
	 *
	 * @since   1.7.0
	 */
	public function setParam($key, $value)
	{
		return $this->_params->set($key, $value);
	}

	/**
	 * Method to set a default parameter if it does not exist
	 *
	 * @param   string  $key    Parameter key
	 * @param   mixed   $value  Parameter value
	 *
	 * @return  mixed  Set parameter value
	 *
	 * @since   1.7.0
	 */
	public function defParam($key, $value)
	{
		return $this->_params->def($key, $value);
	}

	/**
	 * Method to check User object authorisation against an access control
	 * object and optionally an access extension object
	 *
	 * @param   string  $action     The name of the action to check for permission.
	 * @param   string  $assetname  The name of the asset on which to perform the action.
	 *
	 * @return  boolean  True if authorised
	 *
	 * @since   1.7.0
	 */
	public function authorise($action, $assetname = null)
	{
		// Make sure we only check for core.admin once during the run.
		if ($this->isRoot === null)
		{
			$this->isRoot = false;

			// Check for the configuration file failsafe.
			$rootUser = \JFactory::getConfig()->get('root_user');

			// The root_user variable can be a numeric user ID or a username.
			if (is_numeric($rootUser) && $this->id > 0 && $this->id == $rootUser)
			{
				$this->isRoot = true;
			}
			elseif ($this->username && $this->username == $rootUser)
			{
				$this->isRoot = true;
			}
			elseif ($this->id > 0)
			{
				// Get all groups against which the user is mapped.
				$identities = $this->getAuthorisedGroups();
				array_unshift($identities, $this->id * -1);

				if (Access::getAssetRules(1)->allow('core.admin', $identities))
				{
					$this->isRoot = true;

					return true;
				}
			}
		}

		return $this->isRoot ? true : (bool) Access::check($this->id, $action, $assetname);
	}

	/**
	 * Method to return a list of all categories that a user has permission for a given action
	 *
	 * @param   string  $component  The component from which to retrieve the categories
	 * @param   string  $action     The name of the section within the component from which to retrieve the actions.
	 *
	 * @return  array  List of categories that this group can do this action to (empty array if none). Categories must be published.
	 *
	 * @since   1.7.0
	 */
	public function getAuthorisedCategories($component, $action)
	{
		// Brute force method: get all published category rows for the component and check each one
		// TODO: Modify the way permissions are stored in the db to allow for faster implementation and better scaling
		$db = \JFactory::getDbo();

		$subQuery = $db->getQuery(true)
			->select('id,asset_id')
			->from('#__categories')
			->where('extension = ' . $db->quote($component))
			->where('published = 1');

		$query = $db->getQuery(true)
			->select('c.id AS id, a.name AS asset_name')
			->from('(' . (string) $subQuery . ') AS c')
			->join('INNER', '#__assets AS a ON c.asset_id = a.id');
		$db->setQuery($query);
		$allCategories = $db->loadObjectList('id');
		$allowedCategories = array();

		foreach ($allCategories as $category)
		{
			if ($this->authorise($action, $category->asset_name))
			{
				$allowedCategories[] = (int) $category->id;
			}
		}

		return $allowedCategories;
	}

	/**
	 * Gets an array of the authorised access levels for the user
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function getAuthorisedViewLevels()
	{
		if ($this->_authLevels === null)
		{
			$this->_authLevels = array();
		}

		if (empty($this->_authLevels))
		{
			$this->_authLevels = Access::getAuthorisedViewLevels($this->id);
		}

		return $this->_authLevels;
	}

	/**
	 * Gets an array of the authorised user groups
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function getAuthorisedGroups()
	{
		if ($this->_authGroups === null)
		{
			$this->_authGroups = array();
		}

		if (empty($this->_authGroups))
		{
			$this->_authGroups = Access::getGroupsByUser($this->id);
		}

		return $this->_authGroups;
	}

	/**
	 * Clears the access rights cache of this user
	 *
	 * @return  void
	 *
	 * @since   3.4.0
	 */
	public function clearAccessRights()
	{
		$this->_authLevels = null;
		$this->_authGroups = null;
		$this->isRoot = null;
		Access::clearStatics();
	}

	/**
	 * Pass through method to the table for setting the last visit date
	 *
	 * @param   integer  $timestamp  The timestamp, defaults to 'now'.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setLastVisit($timestamp = null)
	{
		// Create the user table object
		$table = $this->getTable();
		$table->load($this->id);

		return $table->setLastVisit($timestamp);
	}

	/**
	 * Method to get the user parameters
	 *
	 * This method used to load the user parameters from a file.
	 *
	 * @return  object   The user parameters object.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Instead use User::getParam()
	 */
	public function getParameters()
	{
		// @codeCoverageIgnoreStart
		\JLog::add('User::getParameters() is deprecated. User::getParam().', \JLog::WARNING, 'deprecated');

		return $this->_params;

		// @codeCoverageIgnoreEnd
	}

	/**
	 * Method to get the user timezone.
	 *
	 * If the user didn't set a timezone, it will return the server timezone
	 *
	 * @return \DateTimeZone
	 *
	 * @since 3.7.0
	 */
	public function getTimezone()
	{
		$timezone = $this->getParam('timezone', \JFactory::getApplication()->get('offset', 'GMT'));

		return new \DateTimeZone($timezone);
	}

	/**
	 * Method to get the user parameters
	 *
	 * @param   object  $params  The user parameters object
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setParameters($params)
	{
		$this->_params = $params;
	}

	/**
	 * Method to get the user table object
	 *
	 * This function uses a static variable to store the table name of the user table to
	 * instantiate. You can call this function statically to set the table name if
	 * needed.
	 *
	 * @param   string  $type    The user table name to be used
	 * @param   string  $prefix  The user table prefix to be used
	 *
	 * @return  object  The user table object
	 *
	 * @note    At 4.0 this method will no longer be static
	 * @since   1.7.0
	 */
	public static function getTable($type = null, $prefix = 'JTable')
	{
		static $tabletype;

		// Set the default tabletype;
		if (!isset($tabletype))
		{
			$tabletype['name'] = 'user';
			$tabletype['prefix'] = 'JTable';
		}

		// Set a custom table type is defined
		if (isset($type))
		{
			$tabletype['name'] = $type;
			$tabletype['prefix'] = $prefix;
		}

		// Create the user table object
		return Table::getInstance($tabletype['name'], $tabletype['prefix']);
	}

	/**
	 * Method to bind an associative array of data to a user object
	 *
	 * @param   array  &$array  The associative array to bind to the object
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function bind(&$array)
	{
		// Let's check to see if the user is new or not
		if (empty($this->id))
		{
			// Check the password and create the crypted password
			if (empty($array['password']))
			{
				$array['password'] = $this->userHelper->genRandomPassword();
				$array['password2'] = $array['password'];
			}

			// Not all controllers check the password, although they should.
			// Hence this code is required:
			if (isset($array['password2']) && $array['password'] != $array['password2'])
			{
				\JFactory::getApplication()->enqueueMessage(\JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'), 'error');

				return false;
			}

			$this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');

			$array['password'] = $this->userHelper->hashPassword($array['password']);

			// Set the registration timestamp
			$this->set('registerDate', \JFactory::getDate()->toSql());

			// Check that username is not greater than 150 characters
			$username = $this->get('username');

			if (strlen($username) > 150)
			{
				$username = substr($username, 0, 150);
				$this->set('username', $username);
			}
		}
		else
		{
			// Updating an existing user
			if (!empty($array['password']))
			{
				if ($array['password'] != $array['password2'])
				{
					$this->setError(\JText::_('JLIB_USER_ERROR_PASSWORD_NOT_MATCH'));

					return false;
				}

				$this->password_clear = ArrayHelper::getValue($array, 'password', '', 'string');

				// Check if the user is reusing the current password if required to reset their password
				if ($this->requireReset == 1 && $this->userHelper->verifyPassword($this->password_clear, $this->password))
				{
					$this->setError(\JText::_('JLIB_USER_ERROR_CANNOT_REUSE_PASSWORD'));

					return false;
				}

				$array['password'] = $this->userHelper->hashPassword($array['password']);

				// Reset the change password flag
				$array['requireReset'] = 0;
			}
			else
			{
				$array['password'] = $this->password;
			}
		}

		if (array_key_exists('params', $array))
		{
			$this->_params->loadArray($array['params']);

			if (is_array($array['params']))
			{
				$params = (string) $this->_params;
			}
			else
			{
				$params = $array['params'];
			}

			$this->params = $params;
		}

		// Bind the array
		if (!$this->setProperties($array))
		{
			$this->setError(\JText::_('JLIB_USER_ERROR_BIND_ARRAY'));

			return false;
		}

		// Make sure its an integer
		$this->id = (int) $this->id;

		return true;
	}

	/**
	 * Method to save the User object to the database
	 *
	 * @param   boolean  $updateOnly  Save the object only if not a new user
	 *                                Currently only used in the user reset password method.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public function save($updateOnly = false)
	{
		// Create the user table object
		$table = $this->getTable();
		$this->params = (string) $this->_params;
		$table->bind($this->getProperties());

		// Allow an exception to be thrown.
		try
		{
			// Check and store the object.
			if (!$table->check())
			{
				$this->setError($table->getError());

				return false;
			}

			// If user is made a Super Admin group and user is NOT a Super Admin

			// @todo ACL - this needs to be acl checked

			$my = \JFactory::getUser();

			// Are we creating a new user
			$isNew = empty($this->id);

			// If we aren't allowed to create new users return
			if ($isNew && $updateOnly)
			{
				return true;
			}

			// Get the old user
			$oldUser = new User($this->id);

			// Access Checks

			// The only mandatory check is that only Super Admins can operate on other Super Admin accounts.
			// To add additional business rules, use a user plugin and throw an Exception with onUserBeforeSave.

			// Check if I am a Super Admin
			$iAmSuperAdmin = $my->authorise('core.admin');

			$iAmRehashingSuperadmin = false;

			if (($my->id == 0 && !$isNew) && $this->id == $oldUser->id && $oldUser->authorise('core.admin') && $oldUser->password != $this->password)
			{
				$iAmRehashingSuperadmin = true;
			}

			// We are only worried about edits to this account if I am not a Super Admin.
			if ($iAmSuperAdmin != true && $iAmRehashingSuperadmin != true)
			{
				// I am not a Super Admin, and this one is, so fail.
				if (!$isNew && Access::check($this->id, 'core.admin'))
				{
					throw new \RuntimeException('User not Super Administrator');
				}

				if ($this->groups != null)
				{
					// I am not a Super Admin and I'm trying to make one.
					foreach ($this->groups as $groupId)
					{
						if (Access::checkGroup($groupId, 'core.admin'))
						{
							throw new \RuntimeException('User not Super Administrator');
						}
					}
				}
			}

			// Fire the onUserBeforeSave event.
			PluginHelper::importPlugin('user');
			$dispatcher = \JEventDispatcher::getInstance();

			$result = $dispatcher->trigger('onUserBeforeSave', array($oldUser->getProperties(), $isNew, $this->getProperties()));

			if (in_array(false, $result, true))
			{
				// Plugin will have to raise its own error or throw an exception.
				return false;
			}

			// Store the user data in the database
			$result = $table->store();

			// Set the id for the User object in case we created a new user.
			if (empty($this->id))
			{
				$this->id = $table->get('id');
			}

			if ($my->id == $table->id)
			{
				$registry = new Registry($table->params);
				$my->setParameters($registry);
			}

			// Fire the onUserAfterSave event
			$dispatcher->trigger('onUserAfterSave', array($this->getProperties(), $isNew, $result, $this->getError()));
		}
		catch (\Exception $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		return $result;
	}

	/**
	 * Method to delete the User object from the database
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function delete()
	{
		PluginHelper::importPlugin('user');

		// Trigger the onUserBeforeDelete event
		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onUserBeforeDelete', array($this->getProperties()));

		// Create the user table object
		$table = $this->getTable();

		if (!$result = $table->delete($this->id))
		{
			$this->setError($table->getError());
		}

		// Trigger the onUserAfterDelete event
		$dispatcher->trigger('onUserAfterDelete', array($this->getProperties(), $result, $this->getError()));

		return $result;
	}

	/**
	 * Method to load a User object by user id number
	 *
	 * @param   mixed  $id  The user id of the user to load
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function load($id)
	{
		// Create the user table object
		$table = $this->getTable();

		// Load the UserModel object based on the user id or throw a warning.
		if (!$table->load($id))
		{
			// Reset to guest user
			$this->guest = 1;

			\JLog::add(\JText::sprintf('JLIB_USER_ERROR_UNABLE_TO_LOAD_USER', $id), \JLog::WARNING, 'jerror');

			return false;
		}

		/*
		 * Set the user parameters using the default XML file.  We might want to
		 * extend this in the future to allow for the ability to have custom
		 * user parameters, but for right now we'll leave it how it is.
		 */

		if ($table->params)
		{
			$this->_params->loadString($table->params);
		}

		// Assuming all is well at this point let's bind the data
		$this->setProperties($table->getProperties());

		// The user is no longer a guest
		if ($this->id != 0)
		{
			$this->guest = 0;
		}
		else
		{
			$this->guest = 1;
		}

		return true;
	}

	/**
	 * Method to allow serialize the object with minimal properties.
	 *
	 * @return  array  The names of the properties to include in serialization.
	 *
	 * @since   3.6.0
	 */
	public function __sleep()
	{
		return array('id');
	}

	/**
	 * Method to recover the full object on unserialize.
	 *
	 * @return  void
	 *
	 * @since   3.6.0
	 */
	public function __wakeup()
	{
		// Initialise some variables
		$this->userHelper = new UserWrapper;
		$this->_params    = new Registry;

		// Load the user if it exists
		if (!empty($this->id) && $this->load($this->id))
		{
			// Push user into cached instances.
			self::$instances[$this->id] = $this;
		}
		else
		{
			// Initialise
			$this->id = 0;
			$this->sendEmail = 0;
			$this->aid = 0;
			$this->guest = 1;
		}
	}
}
src/User/UserHelper.php000064400000052415152177723700011062 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\User;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Utilities\ArrayHelper;

/**
 * Authorisation helper class, provides static methods to perform various tasks relevant
 * to the Joomla user and authorisation classes
 *
 * This class has influences and some method logic from the Horde Auth package
 *
 * @since  1.7.0
 */
abstract class UserHelper
{
	/**
	 * Method to add a user to a group.
	 *
	 * @param   integer  $userId   The id of the user.
	 * @param   integer  $groupId  The id of the group.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public static function addUserToGroup($userId, $groupId)
	{
		// Get the user object.
		$user = new User((int) $userId);

		// Add the user to the group if necessary.
		if (!in_array($groupId, $user->groups))
		{
			// Check whether the group exists.
			$db = \JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName('id'))
				->from($db->quoteName('#__usergroups'))
				->where($db->quoteName('id') . ' = ' . (int) $groupId);
			$db->setQuery($query);

			// If the group does not exist, return an exception.
			if ($db->loadResult() === null)
			{
				throw new \RuntimeException('Access Usergroup Invalid');
			}

			// Add the group data to the user object.
			$user->groups[$groupId] = $groupId;

			// Store the user object.
			$user->save();
		}

		// Set the group data for any preloaded user objects.
		$temp         = User::getInstance((int) $userId);
		$temp->groups = $user->groups;

		if (\JFactory::getSession()->getId())
		{
			// Set the group data for the user object in the session.
			$temp = \JFactory::getUser();

			if ($temp->id == $userId)
			{
				$temp->groups = $user->groups;
			}
		}

		return true;
	}

	/**
	 * Method to get a list of groups a user is in.
	 *
	 * @param   integer  $userId  The id of the user.
	 *
	 * @return  array    List of groups
	 *
	 * @since   1.7.0
	 */
	public static function getUserGroups($userId)
	{
		// Get the user object.
		$user = User::getInstance((int) $userId);

		return isset($user->groups) ? $user->groups : array();
	}

	/**
	 * Method to remove a user from a group.
	 *
	 * @param   integer  $userId   The id of the user.
	 * @param   integer  $groupId  The id of the group.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function removeUserFromGroup($userId, $groupId)
	{
		// Get the user object.
		$user = User::getInstance((int) $userId);

		// Remove the user from the group if necessary.
		$key = array_search($groupId, $user->groups);

		if ($key !== false)
		{
			// Remove the user from the group.
			unset($user->groups[$key]);

			// Store the user object.
			$user->save();
		}

		// Set the group data for any preloaded user objects.
		$temp = \JFactory::getUser((int) $userId);
		$temp->groups = $user->groups;

		// Set the group data for the user object in the session.
		$temp = \JFactory::getUser();

		if ($temp->id == $userId)
		{
			$temp->groups = $user->groups;
		}

		return true;
	}

	/**
	 * Method to set the groups for a user.
	 *
	 * @param   integer  $userId  The id of the user.
	 * @param   array    $groups  An array of group ids to put the user in.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function setUserGroups($userId, $groups)
	{
		// Get the user object.
		$user = User::getInstance((int) $userId);

		// Set the group ids.
		$groups = ArrayHelper::toInteger($groups);
		$user->groups = $groups;

		// Get the titles for the user groups.
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('id') . ', ' . $db->quoteName('title'))
			->from($db->quoteName('#__usergroups'))
			->where($db->quoteName('id') . ' = ' . implode(' OR ' . $db->quoteName('id') . ' = ', $user->groups));
		$db->setQuery($query);
		$results = $db->loadObjectList();

		// Set the titles for the user groups.
		for ($i = 0, $n = count($results); $i < $n; $i++)
		{
			$user->groups[$results[$i]->id] = $results[$i]->id;
		}

		// Store the user object.
		$user->save();

		if (session_id())
		{
			// Set the group data for any preloaded user objects.
			$temp = \JFactory::getUser((int) $userId);
			$temp->groups = $user->groups;

			// Set the group data for the user object in the session.
			$temp = \JFactory::getUser();

			if ($temp->id == $userId)
			{
				$temp->groups = $user->groups;
			}
		}

		return true;
	}

	/**
	 * Gets the user profile information
	 *
	 * @param   integer  $userId  The id of the user.
	 *
	 * @return  object
	 *
	 * @since   1.7.0
	 */
	public static function getProfile($userId = 0)
	{
		if ($userId == 0)
		{
			$user   = \JFactory::getUser();
			$userId = $user->id;
		}

		// Get the dispatcher and load the user's plugins.
		$dispatcher = \JEventDispatcher::getInstance();
		PluginHelper::importPlugin('user');

		$data = new \JObject;
		$data->id = $userId;

		// Trigger the data preparation event.
		$dispatcher->trigger('onContentPrepareData', array('com_users.profile', &$data));

		return $data;
	}

	/**
	 * Method to activate a user
	 *
	 * @param   string  $activation  Activation string
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function activateUser($activation)
	{
		$db = \JFactory::getDbo();

		// Let's get the id of the user we want to activate
		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from($db->quoteName('#__users'))
			->where($db->quoteName('activation') . ' = ' . $db->quote($activation))
			->where($db->quoteName('block') . ' = 1')
			->where($db->quoteName('lastvisitDate') . ' = ' . $db->quote($db->getNullDate()));
		$db->setQuery($query);
		$id = (int) $db->loadResult();

		// Is it a valid user to activate?
		if ($id)
		{
			$user = User::getInstance((int) $id);

			$user->set('block', '0');
			$user->set('activation', '');

			// Time to take care of business.... store the user.
			if (!$user->save())
			{
				\JLog::add($user->getError(), \JLog::WARNING, 'jerror');

				return false;
			}
		}
		else
		{
			\JLog::add(\JText::_('JLIB_USER_ERROR_UNABLE_TO_FIND_USER'), \JLog::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Returns userid if a user exists
	 *
	 * @param   string  $username  The username to search on.
	 *
	 * @return  integer  The user id or 0 if not found.
	 *
	 * @since   1.7.0
	 */
	public static function getUserId($username)
	{
		// Initialise some variables
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from($db->quoteName('#__users'))
			->where($db->quoteName('username') . ' = ' . $db->quote($username));
		$db->setQuery($query, 0, 1);

		return $db->loadResult();
	}

	/**
	 * Hashes a password using the current encryption.
	 *
	 * @param   string   $password   The plaintext password to encrypt.
	 * @param   integer  $algorithm  The hashing algorithm to use, represented by `PASSWORD_*` constants.
	 * @param   array    $options    The options for the algorithm to use.
	 *
	 * @return  string  The encrypted password.
	 *
	 * @since   3.2.1
	 */
	public static function hashPassword($password, $algorithm = PASSWORD_BCRYPT, array $options = array())
	{
		// \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the worst case
		\JCrypt::hasStrongPasswordSupport();

		return password_hash($password, $algorithm, $options);
	}

	/**
	 * Formats a password using the current encryption. If the user ID is given
	 * and the hash does not fit the current hashing algorithm, it automatically
	 * updates the hash.
	 *
	 * @param   string   $password  The plaintext password to check.
	 * @param   string   $hash      The hash to verify against.
	 * @param   integer  $user_id   ID of the user if the password hash should be updated
	 *
	 * @return  boolean  True if the password and hash match, false otherwise
	 *
	 * @since   3.2.1
	 */
	public static function verifyPassword($password, $hash, $user_id = 0)
	{
		$passwordAlgorithm = PASSWORD_BCRYPT;

		// If we are using phpass
		if (strpos($hash, '$P$') === 0)
		{
			// Use PHPass's portable hashes with a cost of 10.
			$phpass = new \PasswordHash(10, true);

			$match = $phpass->CheckPassword($password, $hash);

			$rehash = true;
		}
		// Check for Argon2id hashes
		elseif (strpos($hash, '$argon2id') === 0)
		{
			// This implementation is not supported through any existing polyfills
			$match = password_verify($password, $hash);

			$rehash = password_needs_rehash($hash, PASSWORD_ARGON2ID);

			$passwordAlgorithm = PASSWORD_ARGON2ID;
		}
		// Check for Argon2i hashes
		elseif (strpos($hash, '$argon2i') === 0)
		{
			// This implementation is not supported through any existing polyfills
			$match = password_verify($password, $hash);

			$rehash = password_needs_rehash($hash, PASSWORD_ARGON2I);

			$passwordAlgorithm = PASSWORD_ARGON2I;
		}
		// Check for bcrypt hashes
		elseif (strpos($hash, '$2') === 0)
		{
			// \JCrypt::hasStrongPasswordSupport() includes a fallback for us in the worst case
			\JCrypt::hasStrongPasswordSupport();
			$match = password_verify($password, $hash);

			$rehash = password_needs_rehash($hash, PASSWORD_BCRYPT);
		}
		elseif (substr($hash, 0, 8) == '{SHA256}')
		{
			// Check the password
			$parts     = explode(':', $hash);
			$salt      = @$parts[1];
			$testcrypt = static::getCryptedPassword($password, $salt, 'sha256', true);

			$match = \JCrypt::timingSafeCompare($hash, $testcrypt);

			$rehash = true;
		}
		else
		{
			// Check the password
			$parts = explode(':', $hash);
			$salt  = @$parts[1];

			$rehash = true;

			// Compile the hash to compare
			// If the salt is empty AND there is a ':' in the original hash, we must append ':' at the end
			$testcrypt = md5($password . $salt) . ($salt ? ':' . $salt : (strpos($hash, ':') !== false ? ':' : ''));

			$match = \JCrypt::timingSafeCompare($hash, $testcrypt);
		}

		// If we have a match and rehash = true, rehash the password with the current algorithm.
		if ((int) $user_id > 0 && $match && $rehash)
		{
			$user = new User($user_id);
			$user->password = static::hashPassword($password, $passwordAlgorithm);
			$user->save();
		}

		return $match;
	}

	/**
	 * Formats a password using the old encryption methods.
	 *
	 * @param   string   $plaintext     The plaintext password to encrypt.
	 * @param   string   $salt          The salt to use to encrypt the password. []
	 *                                  If not present, a new salt will be
	 *                                  generated.
	 * @param   string   $encryption    The kind of password encryption to use.
	 *                                  Defaults to md5-hex.
	 * @param   boolean  $show_encrypt  Some password systems prepend the kind of
	 *                                  encryption to the crypted password ({SHA},
	 *                                  etc). Defaults to false.
	 *
	 * @return  string  The encrypted password.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public static function getCryptedPassword($plaintext, $salt = '', $encryption = 'md5-hex', $show_encrypt = false)
	{
		// Get the salt to use.
		$salt = static::getSalt($encryption, $salt, $plaintext);

		// Encrypt the password.
		switch ($encryption)
		{
			case 'plain':
				return $plaintext;

			case 'sha':
				$encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext));

				return ($show_encrypt) ? '{SHA}' . $encrypted : $encrypted;

			case 'crypt':
			case 'crypt-des':
			case 'crypt-md5':
			case 'crypt-blowfish':
				return ($show_encrypt ? '{crypt}' : '') . crypt($plaintext, $salt);

			case 'md5-base64':
				$encrypted = base64_encode(mhash(MHASH_MD5, $plaintext));

				return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted;

			case 'ssha':
				$encrypted = base64_encode(mhash(MHASH_SHA1, $plaintext . $salt) . $salt);

				return ($show_encrypt) ? '{SSHA}' . $encrypted : $encrypted;

			case 'smd5':
				$encrypted = base64_encode(mhash(MHASH_MD5, $plaintext . $salt) . $salt);

				return ($show_encrypt) ? '{SMD5}' . $encrypted : $encrypted;

			case 'aprmd5':
				$length = strlen($plaintext);
				$context = $plaintext . '$apr1$' . $salt;
				$binary = static::_bin(md5($plaintext . $salt . $plaintext));

				for ($i = $length; $i > 0; $i -= 16)
				{
					$context .= substr($binary, 0, ($i > 16 ? 16 : $i));
				}

				for ($i = $length; $i > 0; $i >>= 1)
				{
					$context .= ($i & 1) ? chr(0) : $plaintext[0];
				}

				$binary = static::_bin(md5($context));

				for ($i = 0; $i < 1000; $i++)
				{
					$new = ($i & 1) ? $plaintext : substr($binary, 0, 16);

					if ($i % 3)
					{
						$new .= $salt;
					}

					if ($i % 7)
					{
						$new .= $plaintext;
					}

					$new .= ($i & 1) ? substr($binary, 0, 16) : $plaintext;
					$binary = static::_bin(md5($new));
				}

				$p = array();

				for ($i = 0; $i < 5; $i++)
				{
					$k = $i + 6;
					$j = $i + 12;

					if ($j == 16)
					{
						$j = 5;
					}

					$p[] = static::_toAPRMD5((ord($binary[$i]) << 16) | (ord($binary[$k]) << 8) | (ord($binary[$j])), 5);
				}

				return '$apr1$' . $salt . '$' . implode('', $p) . static::_toAPRMD5(ord($binary[11]), 3);

			case 'sha256':
				$encrypted = ($salt) ? hash('sha256', $plaintext . $salt) . ':' . $salt : hash('sha256', $plaintext);

				return ($show_encrypt) ? '{SHA256}' . $encrypted : '{SHA256}' . $encrypted;

			case 'md5-hex':
			default:
				$encrypted = ($salt) ? md5($plaintext . $salt) : md5($plaintext);

				return ($show_encrypt) ? '{MD5}' . $encrypted : $encrypted;
		}
	}

	/**
	 * Returns a salt for the appropriate kind of password encryption using the old encryption methods.
	 * Optionally takes a seed and a plaintext password, to extract the seed
	 * of an existing password, or for encryption types that use the plaintext
	 * in the generation of the salt.
	 *
	 * @param   string  $encryption  The kind of password encryption to use.
	 *                               Defaults to md5-hex.
	 * @param   string  $seed        The seed to get the salt from (probably a
	 *                               previously generated password). Defaults to
	 *                               generating a new seed.
	 * @param   string  $plaintext   The plaintext password that we're generating
	 *                               a salt for. Defaults to none.
	 *
	 * @return  string  The generated or extracted salt.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public static function getSalt($encryption = 'md5-hex', $seed = '', $plaintext = '')
	{
		// Encrypt the password.
		switch ($encryption)
		{
			case 'crypt':
			case 'crypt-des':
				if ($seed)
				{
					return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 2);
				}
				else
				{
					return substr(md5(mt_rand()), 0, 2);
				}
				break;

			case 'sha256':
				if ($seed)
				{
					return preg_replace('|^{sha256}|i', '', $seed);
				}
				else
				{
					return static::genRandomPassword(16);
				}
				break;

			case 'crypt-md5':
				if ($seed)
				{
					return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 12);
				}
				else
				{
					return '$1$' . substr(md5(\JCrypt::genRandomBytes()), 0, 8) . '$';
				}
				break;

			case 'crypt-blowfish':
				if ($seed)
				{
					return substr(preg_replace('|^{crypt}|i', '', $seed), 0, 30);
				}
				else
				{
					return '$2y$10$' . substr(md5(\JCrypt::genRandomBytes()), 0, 22) . '$';
				}
				break;

			case 'ssha':
				if ($seed)
				{
					return substr(preg_replace('|^{SSHA}|', '', $seed), -20);
				}
				else
				{
					return mhash_keygen_s2k(MHASH_SHA1, $plaintext, substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4);
				}
				break;

			case 'smd5':
				if ($seed)
				{
					return substr(preg_replace('|^{SMD5}|', '', $seed), -16);
				}
				else
				{
					return mhash_keygen_s2k(MHASH_MD5, $plaintext, substr(pack('h*', md5(\JCrypt::genRandomBytes())), 0, 8), 4);
				}
				break;

			case 'aprmd5': // 64 characters that are valid for APRMD5 passwords.
				$APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

				if ($seed)
				{
					return substr(preg_replace('/^\$apr1\$(.{8}).*/', '\\1', $seed), 0, 8);
				}
				else
				{
					$salt = '';

					for ($i = 0; $i < 8; $i++)
					{
						$salt .= $APRMD5{mt_rand(0, 63)};
					}

					return $salt;
				}
				break;

			default:
				$salt = '';

				if ($seed)
				{
					$salt = $seed;
				}

				return $salt;
				break;
		}
	}

	/**
	 * Generate a random password
	 *
	 * @param   integer  $length  Length of the password to generate
	 *
	 * @return  string  Random Password
	 *
	 * @since   1.7.0
	 */
	public static function genRandomPassword($length = 8)
	{
		$salt = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
		$base = strlen($salt);
		$makepass = '';

		/*
		 * Start with a cryptographic strength random string, then convert it to
		 * a string with the numeric base of the salt.
		 * Shift the base conversion on each character so the character
		 * distribution is even, and randomize the start shift so it's not
		 * predictable.
		 */
		$random = \JCrypt::genRandomBytes($length + 1);
		$shift = ord($random[0]);

		for ($i = 1; $i <= $length; ++$i)
		{
			$makepass .= $salt[($shift + ord($random[$i])) % $base];
			$shift += ord($random[$i]);
		}

		return $makepass;
	}

	/**
	 * Converts to allowed 64 characters for APRMD5 passwords.
	 *
	 * @param   string   $value  The value to convert.
	 * @param   integer  $count  The number of characters to convert.
	 *
	 * @return  string  $value converted to the 64 MD5 characters.
	 *
	 * @since   1.7.0
	 */
	protected static function _toAPRMD5($value, $count)
	{
		// 64 characters that are valid for APRMD5 passwords.
		$APRMD5 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

		$aprmd5 = '';
		$count = abs($count);

		while (--$count)
		{
			$aprmd5 .= $APRMD5[$value & 0x3f];
			$value >>= 6;
		}

		return $aprmd5;
	}

	/**
	 * Converts hexadecimal string to binary data.
	 *
	 * @param   string  $hex  Hex data.
	 *
	 * @return  string  Binary data.
	 *
	 * @since   1.7.0
	 */
	private static function _bin($hex)
	{
		$bin = '';
		$length = strlen($hex);

		for ($i = 0; $i < $length; $i += 2)
		{
			$tmp = sscanf(substr($hex, $i, 2), '%x');
			$bin .= chr(array_shift($tmp));
		}

		return $bin;
	}

	/**
	 * Method to remove a cookie record from the database and the browser
	 *
	 * @param   string  $userId      User ID for this user
	 * @param   string  $cookieName  Series id (cookie name decoded)
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.2
	 * @deprecated  4.0  This is handled in the authentication plugin itself. The 'invalid' column in the db should be removed as well
	 */
	public static function invalidateCookie($userId, $cookieName)
	{
		$db = \JFactory::getDbo();
		$query = $db->getQuery(true);

		// Invalidate cookie in the database
		$query
			->update($db->quoteName('#__user_keys'))
			->set($db->quoteName('invalid') . ' = 1')
			->where($db->quoteName('user_id') . ' = ' . $db->quote($userId));

		$db->setQuery($query)->execute();

		// Destroy the cookie in the browser.
		$app = \JFactory::getApplication();
		$app->input->cookie->set($cookieName, '', 1, $app->get('cookie_path', '/'), $app->get('cookie_domain', ''));

		return true;
	}

	/**
	 * Clear all expired tokens for all users.
	 *
	 * @return  mixed  Database query result
	 *
	 * @since   3.2
	 * @deprecated  4.0  This is handled in the authentication plugin itself
	 */
	public static function clearExpiredTokens()
	{
		$now = time();

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->delete('#__user_keys')
			->where($db->quoteName('time') . ' < ' . $db->quote($now));

		return $db->setQuery($query)->execute();
	}

	/**
	 * Method to get the remember me cookie data
	 *
	 * @return  mixed  An array of information from an authentication cookie or false if there is no cookie
	 *
	 * @since   3.2
	 * @deprecated  4.0  This is handled in the authentication plugin itself
	 */
	public static function getRememberCookieData()
	{
		// Create the cookie name
		$cookieName = static::getShortHashedUserAgent();

		// Fetch the cookie value
		$app = \JFactory::getApplication();
		$cookieValue = $app->input->cookie->get($cookieName);

		if (!empty($cookieValue))
		{
			return explode('.', $cookieValue);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get a hashed user agent string that does not include browser version.
	 * Used when frequent version changes cause problems.
	 *
	 * @return  string  A hashed user agent string with version replaced by 'abcd'
	 *
	 * @since   3.2
	 */
	public static function getShortHashedUserAgent()
	{
		$ua = \JFactory::getApplication()->client;
		$uaString = $ua->userAgent;
		$browserVersion = $ua->browserVersion;
		$uaShort = str_replace($browserVersion, 'abcd', $uaString);

		return md5(\JUri::base() . $uaShort);
	}

	/**
	 * Check if there is a super user in the user ids.
	 *
	 * @param   array  $userIds  An array of user IDs on which to operate
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @since   3.6.5
	 */
	public static function checkSuperUserInUsers(array $userIds)
	{
		foreach ($userIds as $userId)
		{
			foreach (static::getUserGroups($userId) as $userGroupId)
			{
				if (Access::checkGroup($userGroupId, 'core.admin'))
				{
					return true;
				}
			}
		}

		return false;
	}
}
src/HTML/HTMLHelper.php000064400000103717152177723700010540 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\HTML;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Environment\Browser;
use Joomla\CMS\Factory;
use Joomla\CMS\Layout\LayoutHelper;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Uri\Uri;
use Joomla\Utilities\ArrayHelper;

\JLoader::import('joomla.environment.browser');
\JLoader::import('joomla.filesystem.file');
\JLoader::import('joomla.filesystem.path');

/**
 * Utility class for all HTML drawing classes
 *
 * @since  1.5
 */
abstract class HTMLHelper
{
	/**
	 * Option values related to the generation of HTML output. Recognized
	 * options are:
	 *     fmtDepth, integer. The current indent depth.
	 *     fmtEol, string. The end of line string, default is linefeed.
	 *     fmtIndent, string. The string to use for indentation, default is
	 *     tab.
	 *
	 * @var    array
	 * @since  1.5
	 */
	public static $formatOptions = array('format.depth' => 0, 'format.eol' => "\n", 'format.indent' => "\t");

	/**
	 * An array to hold included paths
	 *
	 * @var    string[]
	 * @since  1.5
	 */
	protected static $includePaths = array();

	/**
	 * An array to hold method references
	 *
	 * @var    callable[]
	 * @since  1.6
	 */
	protected static $registry = array();

	/**
	 * Method to extract a key
	 *
	 * @param   string  $key  The name of helper method to load, (prefix).(class).function
	 *                        prefix and class are optional and can be used to load custom html helpers.
	 *
	 * @return  array  Contains lowercase key, prefix, file, function.
	 *
	 * @since   1.6
	 */
	protected static function extract($key)
	{
		$key = preg_replace('#[^A-Z0-9_\.]#i', '', $key);

		// Check to see whether we need to load a helper file
		$parts = explode('.', $key);

		$prefix = count($parts) === 3 ? array_shift($parts) : 'JHtml';
		$file   = count($parts) === 2 ? array_shift($parts) : '';
		$func   = array_shift($parts);

		return array(strtolower($prefix . '.' . $file . '.' . $func), $prefix, $file, $func);
	}

	/**
	 * Class loader method
	 *
	 * Additional arguments may be supplied and are passed to the sub-class.
	 * Additional include paths are also able to be specified for third-party use
	 *
	 * @param   string  $key  The name of helper method to load, (prefix).(class).function
	 *                        prefix and class are optional and can be used to load custom
	 *                        html helpers.
	 *
	 * @return  mixed  Result of HTMLHelper::call($function, $args)
	 *
	 * @since   1.5
	 * @throws  \InvalidArgumentException
	 */
	public static function _($key)
	{
		list($key, $prefix, $file, $func) = static::extract($key);

		if (array_key_exists($key, static::$registry))
		{
			$function = static::$registry[$key];
			$args     = func_get_args();

			// Remove function name from arguments
			array_shift($args);

			return static::call($function, $args);
		}

		$className = $prefix . ucfirst($file);

		if (!class_exists($className))
		{
			$path = \JPath::find(static::$includePaths, strtolower($file) . '.php');

			if (!$path)
			{
				throw new \InvalidArgumentException(sprintf('%s %s not found.', $prefix, $file), 500);
			}

			\JLoader::register($className, $path);

			if (!class_exists($className))
			{
				throw new \InvalidArgumentException(sprintf('%s not found.', $className), 500);
			}
		}

		$toCall = array($className, $func);

		if (!is_callable($toCall))
		{
			throw new \InvalidArgumentException(sprintf('%s::%s not found.', $className, $func), 500);
		}

		static::register($key, $toCall);
		$args = func_get_args();

		// Remove function name from arguments
		array_shift($args);

		return static::call($toCall, $args);
	}

	/**
	 * Registers a function to be called with a specific key
	 *
	 * @param   string  $key       The name of the key
	 * @param   string  $function  Function or method
	 *
	 * @return  boolean  True if the function is callable
	 *
	 * @since   1.6
	 */
	public static function register($key, $function)
	{
		list($key) = static::extract($key);

		if (is_callable($function))
		{
			static::$registry[$key] = $function;

			return true;
		}

		return false;
	}

	/**
	 * Removes a key for a method from registry.
	 *
	 * @param   string  $key  The name of the key
	 *
	 * @return  boolean  True if a set key is unset
	 *
	 * @since   1.6
	 */
	public static function unregister($key)
	{
		list($key) = static::extract($key);

		if (isset(static::$registry[$key]))
		{
			unset(static::$registry[$key]);

			return true;
		}

		return false;
	}

	/**
	 * Test if the key is registered.
	 *
	 * @param   string  $key  The name of the key
	 *
	 * @return  boolean  True if the key is registered.
	 *
	 * @since   1.6
	 */
	public static function isRegistered($key)
	{
		list($key) = static::extract($key);

		return isset(static::$registry[$key]);
	}

	/**
	 * Function caller method
	 *
	 * @param   callable  $function  Function or method to call
	 * @param   array     $args      Arguments to be passed to function
	 *
	 * @return  mixed   Function result or false on error.
	 *
	 * @link    https://www.php.net/manual/en/function.call-user-func-array.php
	 * @since   1.6
	 * @throws  \InvalidArgumentException
	 */
	protected static function call($function, $args)
	{
		if (!is_callable($function))
		{
			throw new \InvalidArgumentException('Function not supported', 500);
		}

		// PHP 5.3 workaround
		$temp = array();

		foreach ($args as &$arg)
		{
			$temp[] = &$arg;
		}

		return call_user_func_array($function, $temp);
	}

	/**
	 * Write a `<a>` element
	 *
	 * @param   string        $url      The relative URL to use for the href attribute
	 * @param   string        $text     The target attribute to use
	 * @param   array|string  $attribs  Attributes to be added to the `<a>` element
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function link($url, $text, $attribs = null)
	{
		if (is_array($attribs))
		{
			$attribs = ArrayHelper::toString($attribs);
		}

		return '<a href="' . $url . '" ' . $attribs . '>' . $text . '</a>';
	}

	/**
	 * Write a `<iframe>` element
	 *
	 * @param   string        $url       The relative URL to use for the src attribute.
	 * @param   string        $name      The target attribute to use.
	 * @param   array|string  $attribs   Attributes to be added to the `<iframe>` element
	 * @param   string        $noFrames  The message to display if the iframe tag is not supported.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function iframe($url, $name, $attribs = null, $noFrames = '')
	{
		if (is_array($attribs))
		{
			$attribs = ArrayHelper::toString($attribs);
		}

		return '<iframe src="' . $url . '" ' . $attribs . ' name="' . $name . '">' . $noFrames . '</iframe>';
	}

	/**
	 * Include version with MD5SUM file in path.
	 *
	 * @param   string  $path  Folder name to search into (images, css, js, ...).
	 *
	 * @return  string  Query string to add.
	 *
	 * @since   3.7.0
	 *
	 * @deprecated   4.0  Usage of MD5SUM files is deprecated, use version instead.
	 */
	protected static function getMd5Version($path)
	{
		$md5 = dirname($path) . '/MD5SUM';

		if (file_exists($md5))
		{
			Log::add('Usage of MD5SUM files is deprecated, use version instead.', Log::WARNING, 'deprecated');

			return '?' . file_get_contents($md5);
		}

		return '';
	}

	/**
	 * Compute the files to be included
	 *
	 * @param   string   $folder          Folder name to search in (i.e. images, css, js).
	 * @param   string   $file            Path to file.
	 * @param   boolean  $relative        Flag if the path to the file is relative to the /media folder (and searches in template).
	 * @param   boolean  $detect_browser  Flag if the browser should be detected to include specific browser files.
	 * @param   boolean  $detect_debug    Flag if debug mode is enabled to include uncompressed files if debug is on.
	 *
	 * @return  array    files to be included.
	 *
	 * @see     JBrowser
	 * @since   1.6
	 */
	protected static function includeRelativeFiles($folder, $file, $relative, $detect_browser, $detect_debug)
	{
		// If http is present in filename just return it as an array
		if (strpos($file, 'http') === 0 || strpos($file, '//') === 0)
		{
			return array($file);
		}

		// Extract extension and strip the file
		$strip = \JFile::stripExt($file);
		$ext   = \JFile::getExt($file);

		// Prepare array of files
		$includes = array();

		// Detect browser and compute potential files
		if ($detect_browser)
		{
			$navigator = Browser::getInstance();
			$browser   = $navigator->getBrowser();
			$major     = $navigator->getMajor();
			$minor     = $navigator->getMinor();

			// Try to include files named filename.ext, filename_browser.ext, filename_browser_major.ext, filename_browser_major_minor.ext
			// where major and minor are the browser version names
			$potential = array(
				$strip,
				$strip . '_' . $browser,
				$strip . '_' . $browser . '_' . $major,
				$strip . '_' . $browser . '_' . $major . '_' . $minor,
			);
		}
		else
		{
			$potential = array($strip);
		}

		// If relative search in template directory or media directory
		if ($relative)
		{
			// Get the template
			$template = Factory::getApplication()->getTemplate();

			// For each potential files
			foreach ($potential as $strip)
			{
				$files = array();

				// Detect debug mode
				if ($detect_debug && Factory::getConfig()->get('debug'))
				{
					/*
					 * Detect if we received a file in the format name.min.ext
					 * If so, strip the .min part out, otherwise append -uncompressed
					 */
					if (strlen($strip) > 4 && preg_match('#\.min$#', $strip))
					{
						$files[] = preg_replace('#\.min$#', '.', $strip) . $ext;
					}
					else
					{
						$files[] = $strip . '-uncompressed.' . $ext;
					}
				}

				$files[] = $strip . '.' . $ext;

				/*
				 * Loop on 1 or 2 files and break on first found.
				 * Add the content of the MD5SUM file located in the same folder to URL to ensure cache browser refresh
				 * This MD5SUM file must represent the signature of the folder content
				 */
				foreach ($files as $file)
				{
					// If the file is in the template folder
					$path = JPATH_THEMES . "/$template/$folder/$file";

					if (file_exists($path))
					{
						$includes[] = Uri::base(true) . "/templates/$template/$folder/$file" . static::getMd5Version($path);

						break;
					}
					else
					{
						// If the file contains any /: it can be in a media extension subfolder
						if (strpos($file, '/'))
						{
							// Divide the file extracting the extension as the first part before /
							list($extension, $file) = explode('/', $file, 2);

							// If the file yet contains any /: it can be a plugin
							if (strpos($file, '/'))
							{
								// Divide the file extracting the element as the first part before /
								list($element, $file) = explode('/', $file, 2);

								// Try to deal with plugins group in the media folder
								$path = JPATH_ROOT . "/media/$extension/$element/$folder/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/media/$extension/$element/$folder/$file" . static::getMd5Version($path);

									break;
								}

								// Try to deal with classical file in a media subfolder called element
								$path = JPATH_ROOT . "/media/$extension/$folder/$element/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/media/$extension/$folder/$element/$file" . static::getMd5Version($path);

									break;
								}

								// Try to deal with system files in the template folder
								$path = JPATH_THEMES . "/$template/$folder/system/$element/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/templates/$template/$folder/system/$element/$file" . static::getMd5Version($path);

									break;
								}

								// Try to deal with system files in the media folder
								$path = JPATH_ROOT . "/media/system/$folder/$element/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/media/system/$folder/$element/$file" . static::getMd5Version($path);

									break;
								}
							}
							else
							{
								// Try to deals in the extension media folder
								$path = JPATH_ROOT . "/media/$extension/$folder/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/media/$extension/$folder/$file" . static::getMd5Version($path);

									break;
								}

								// Try to deal with system files in the template folder
								$path = JPATH_THEMES . "/$template/$folder/system/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/templates/$template/$folder/system/$file" . static::getMd5Version($path);

									break;
								}

								// Try to deal with system files in the media folder
								$path = JPATH_ROOT . "/media/system/$folder/$file";

								if (file_exists($path))
								{
									$includes[] = Uri::root(true) . "/media/system/$folder/$file" . static::getMd5Version($path);

									break;
								}
							}
						}
						// Try to deal with system files in the media folder
						else
						{
							$path = JPATH_ROOT . "/media/system/$folder/$file";

							if (file_exists($path))
							{
								$includes[] = Uri::root(true) . "/media/system/$folder/$file" . static::getMd5Version($path);

								break;
							}
						}
					}
				}
			}
		}
		// If not relative and http is not present in filename
		else
		{
			foreach ($potential as $strip)
			{
				$files = array();

				// Detect debug mode
				if ($detect_debug && Factory::getConfig()->get('debug'))
				{
					/*
					 * Detect if we received a file in the format name.min.ext
					 * If so, strip the .min part out, otherwise append -uncompressed
					 */
					if (strlen($strip) > 4 && preg_match('#\.min$#', $strip))
					{
						$files[] = preg_replace('#\.min$#', '.', $strip) . $ext;
					}
					else
					{
						$files[] = $strip . '-uncompressed.' . $ext;
					}
				}

				$files[] = $strip . '.' . $ext;

				/*
				 * Loop on 1 or 2 files and break on first found.
				 * Add the content of the MD5SUM file located in the same folder to URL to ensure cache browser refresh
				 * This MD5SUM file must represent the signature of the folder content
				 */
				foreach ($files as $file)
				{
					$path = JPATH_ROOT . "/$file";

					if (file_exists($path))
					{
						$includes[] = Uri::root(true) . "/$file" . static::getMd5Version($path);

						break;
					}
				}
			}
		}

		return $includes;
	}

	/**
	 * Write a `<img>` element
	 *
	 * @param   string        $file        The relative or absolute URL to use for the src attribute.
	 * @param   string        $alt         The alt text.
	 * @param   array|string  $attribs     Attributes to be added to the `<img>` element
	 * @param   boolean       $relative    Flag if the path to the file is relative to the /media folder (and searches in template).
	 * @param   integer       $returnPath  Defines the return value for the method:
	 *                                     -1: Returns a `<img>` tag without looking for relative files
	 *                                     0: Returns a `<img>` tag while searching for relative files
	 *                                     1: Returns the file path to the image while searching for relative files
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function image($file, $alt, $attribs = null, $relative = false, $returnPath = 0)
	{
		$returnPath = (int) $returnPath;

		if ($returnPath !== -1)
		{
			$includes = static::includeRelativeFiles('images', $file, $relative, false, false);
			$file = count($includes) ? $includes[0] : null;
		}

		// If only path is required
		if ($returnPath === 1)
		{
			return $file;
		}

		return '<img src="' . $file . '" alt="' . $alt . '" ' . trim((is_array($attribs) ? ArrayHelper::toString($attribs) : $attribs) . ' /') . '>';
	}

	/**
	 * Write a `<link>` element to load a CSS file
	 *
	 * @param   string  $file     Path to file
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
	 *
	 * @return  array|string|null  nothing if $returnPath is false, null, path or array of path if specific CSS browser files were detected
	 *
	 * @see     Browser
	 * @since   1.5
	 * @deprecated 4.0  The (file, attribs, relative, pathOnly, detectBrowser, detectDebug) method signature is deprecated,
	 *                  use (file, options, attributes) instead.
	 */
	public static function stylesheet($file, $options = array(), $attribs = array())
	{
		// B/C before 3.7.0
		if (!is_array($attribs))
		{
			Log::add('The stylesheet method signature used has changed, use (file, options, attributes) instead.', Log::WARNING, 'deprecated');

			$argList = func_get_args();
			$options = array();

			// Old parameters.
			$attribs                  = isset($argList[1]) ? $argList[1] : array();
			$options['relative']      = isset($argList[2]) ? $argList[2] : false;
			$options['pathOnly']      = isset($argList[3]) ? $argList[3] : false;
			$options['detectBrowser'] = isset($argList[4]) ? $argList[4] : true;
			$options['detectDebug']   = isset($argList[5]) ? $argList[5] : true;
		}
		else
		{
			$options['relative']      = isset($options['relative']) ? $options['relative'] : false;
			$options['pathOnly']      = isset($options['pathOnly']) ? $options['pathOnly'] : false;
			$options['detectBrowser'] = isset($options['detectBrowser']) ? $options['detectBrowser'] : true;
			$options['detectDebug']   = isset($options['detectDebug']) ? $options['detectDebug'] : true;
		}

		$includes = static::includeRelativeFiles('css', $file, $options['relative'], $options['detectBrowser'], $options['detectDebug']);

		// If only path is required
		if ($options['pathOnly'])
		{
			if (count($includes) === 0)
			{
				return;
			}

			if (count($includes) === 1)
			{
				return $includes[0];
			}

			return $includes;
		}

		// If inclusion is required
		$document = Factory::getDocument();

		foreach ($includes as $include)
		{
			// If there is already a version hash in the script reference (by using deprecated MD5SUM).
			if ($pos = strpos($include, '?') !== false)
			{
				$options['version'] = substr($include, $pos + 1);
			}

			$document->addStyleSheet($include, $options, $attribs);
		}
	}

	/**
	 * Write a `<script>` element to load a JavaScript file
	 *
	 * @param   string  $file     Path to file.
	 * @param   array   $options  Array of options. Example: array('version' => 'auto', 'conditional' => 'lt IE 9')
	 * @param   array   $attribs  Array of attributes. Example: array('id' => 'scriptid', 'async' => 'async', 'data-test' => 1)
	 *
	 * @return  array|string|null  Nothing if $returnPath is false, null, path or array of path if specific JavaScript browser files were detected
	 *
	 * @see     HTMLHelper::stylesheet()
	 * @since   1.5
	 * @deprecated 4.0  The (file, framework, relative, pathOnly, detectBrowser, detectDebug) method signature is deprecated,
	 *                  use (file, options, attributes) instead.
	 */
	public static function script($file, $options = array(), $attribs = array())
	{
		// B/C before 3.7.0
		if (!is_array($options))
		{
			Log::add('The script method signature used has changed, use (file, options, attributes) instead.', Log::WARNING, 'deprecated');

			$argList = func_get_args();
			$options = array();
			$attribs = array();

			// Old parameters.
			$options['framework']     = isset($argList[1]) ? $argList[1] : false;
			$options['relative']      = isset($argList[2]) ? $argList[2] : false;
			$options['pathOnly']      = isset($argList[3]) ? $argList[3] : false;
			$options['detectBrowser'] = isset($argList[4]) ? $argList[4] : true;
			$options['detectDebug']   = isset($argList[5]) ? $argList[5] : true;
		}
		else
		{
			$options['framework']     = isset($options['framework']) ? $options['framework'] : false;
			$options['relative']      = isset($options['relative']) ? $options['relative'] : false;
			$options['pathOnly']      = isset($options['pathOnly']) ? $options['pathOnly'] : false;
			$options['detectBrowser'] = isset($options['detectBrowser']) ? $options['detectBrowser'] : true;
			$options['detectDebug']   = isset($options['detectDebug']) ? $options['detectDebug'] : true;
		}

		// Include MooTools framework
		if ($options['framework'])
		{
			static::_('behavior.framework');
		}

		$includes = static::includeRelativeFiles('js', $file, $options['relative'], $options['detectBrowser'], $options['detectDebug']);

		// If only path is required
		if ($options['pathOnly'])
		{
			if (count($includes) === 0)
			{
				return;
			}

			if (count($includes) === 1)
			{
				return $includes[0];
			}

			return $includes;
		}

		// If inclusion is required
		$document = Factory::getDocument();

		foreach ($includes as $include)
		{
			// If there is already a version hash in the script reference (by using deprecated MD5SUM).
			if ($pos = strpos($include, '?') !== false)
			{
				$options['version'] = substr($include, $pos + 1);
			}

			$document->addScript($include, $options, $attribs);
		}
	}

	/**
	 * Set format related options.
	 *
	 * Updates the formatOptions array with all valid values in the passed array.
	 *
	 * @param   array  $options  Option key/value pairs.
	 *
	 * @return  void
	 *
	 * @see     HTMLHelper::$formatOptions
	 * @since   1.5
	 */
	public static function setFormatOptions($options)
	{
		foreach ($options as $key => $val)
		{
			if (isset(static::$formatOptions[$key]))
			{
				static::$formatOptions[$key] = $val;
			}
		}
	}

	/**
	 * Returns formated date according to a given format and time zone.
	 *
	 * @param   string   $input      String in a format accepted by date(), defaults to "now".
	 * @param   string   $format     The date format specification string (see {@link PHP_MANUAL#date}).
	 * @param   mixed    $tz         Time zone to be used for the date.  Special cases: boolean true for user
	 *                               setting, boolean false for server setting.
	 * @param   boolean  $gregorian  True to use Gregorian calendar.
	 *
	 * @return  string    A date translated by the given format and time zone.
	 *
	 * @see     strftime
	 * @since   1.5
	 */
	public static function date($input = 'now', $format = null, $tz = true, $gregorian = false)
	{
		// UTC date converted to user time zone.
		if ($tz === true)
		{
			// Get a date object based on UTC.
			$date = Factory::getDate($input, 'UTC');

			// Set the correct time zone based on the user configuration.
			$date->setTimezone(Factory::getUser()->getTimezone());
		}
		// UTC date converted to server time zone.
		elseif ($tz === false)
		{
			// Get a date object based on UTC.
			$date = Factory::getDate($input, 'UTC');

			// Set the correct time zone based on the server configuration.
			$date->setTimezone(new \DateTimeZone(Factory::getConfig()->get('offset')));
		}
		// No date conversion.
		elseif ($tz === null)
		{
			$date = Factory::getDate($input);
		}
		// UTC date converted to given time zone.
		else
		{
			// Get a date object based on UTC.
			$date = Factory::getDate($input, 'UTC');

			// Set the correct time zone based on the server configuration.
			$date->setTimezone(new \DateTimeZone($tz));
		}

		// If no format is given use the default locale based format.
		if (!$format)
		{
			$format = \JText::_('DATE_FORMAT_LC1');
		}
		// $format is an existing language key
		elseif (Factory::getLanguage()->hasKey($format))
		{
			$format = \JText::_($format);
		}

		if ($gregorian)
		{
			return $date->format($format, true);
		}

		return $date->calendar($format, true);
	}

	/**
	 * Creates a tooltip with an image as button
	 *
	 * @param   string  $tooltip  The tip string.
	 * @param   mixed   $title    The title of the tooltip or an associative array with keys contained in
	 *                            {'title','image','text','href','alt'} and values corresponding to parameters of the same name.
	 * @param   string  $image    The image for the tip, if no text is provided.
	 * @param   string  $text     The text for the tip.
	 * @param   string  $href     A URL that will be used to create the link.
	 * @param   string  $alt      The alt attribute for img tag.
	 * @param   string  $class    CSS class for the tool tip.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function tooltip($tooltip, $title = '', $image = 'tooltip.png', $text = '', $href = '', $alt = 'Tooltip', $class = 'hasTooltip')
	{
		if (is_array($title))
		{
			foreach (array('image', 'text', 'href', 'alt', 'class') as $param)
			{
				if (isset($title[$param]))
				{
					$$param = $title[$param];
				}
			}

			if (isset($title['title']))
			{
				$title = $title['title'];
			}
			else
			{
				$title = '';
			}
		}

		if (!$text)
		{
			$alt = htmlspecialchars($alt, ENT_COMPAT, 'UTF-8');
			$text = static::image($image, $alt, null, true);
		}

		if ($href)
		{
			$tip = '<a href="' . $href . '">' . $text . '</a>';
		}
		else
		{
			$tip = $text;
		}

		if ($class === 'hasTip')
		{
			// Still using MooTools tooltips!
			$tooltip = htmlspecialchars($tooltip, ENT_COMPAT, 'UTF-8');

			if ($title)
			{
				$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
				$tooltip = $title . '::' . $tooltip;
			}
		}
		else
		{
			$tooltip = self::tooltipText($title, $tooltip, 0);
		}

		return '<span class="' . $class . '" title="' . $tooltip . '">' . $tip . '</span>';
	}

	/**
	 * Converts a double colon separated string or 2 separate strings to a string ready for bootstrap tooltips
	 *
	 * @param   string   $title      The title of the tooltip (or combined '::' separated string).
	 * @param   string   $content    The content to tooltip.
	 * @param   boolean  $translate  If true will pass texts through JText.
	 * @param   boolean  $escape     If true will pass texts through htmlspecialchars.
	 *
	 * @return  string  The tooltip string
	 *
	 * @since   3.1.2
	 */
	public static function tooltipText($title = '', $content = '', $translate = true, $escape = true)
	{
		// Initialise return value.
		$result = '';

		// Don't process empty strings
		if ($content !== '' || $title !== '')
		{
			// Split title into title and content if the title contains '::' (old Mootools format).
			if ($content === '' && !(strpos($title, '::') === false))
			{
				list($title, $content) = explode('::', $title, 2);
			}

			// Pass texts through JText if required.
			if ($translate)
			{
				$title = \JText::_($title);
				$content = \JText::_($content);
			}

			// Use only the content if no title is given.
			if ($title === '')
			{
				$result = $content;
			}
			// Use only the title, if title and text are the same.
			elseif ($title === $content)
			{
				$result = '<strong>' . $title . '</strong>';
			}
			// Use a formatted string combining the title and content.
			elseif ($content !== '')
			{
				$result = '<strong>' . $title . '</strong><br />' . $content;
			}
			else
			{
				$result = $title;
			}

			// Escape everything, if required.
			if ($escape)
			{
				$result = htmlspecialchars($result);
			}
		}

		return $result;
	}

	/**
	 * Displays a calendar control field
	 *
	 * @param   string  $value    The date value
	 * @param   string  $name     The name of the text field
	 * @param   string  $id       The id of the text field
	 * @param   string  $format   The date format
	 * @param   mixed   $attribs  Additional HTML attributes
	 *                            The array can have the following keys:
	 *                            readonly      Sets the readonly parameter for the input tag
	 *                            disabled      Sets the disabled parameter for the input tag
	 *                            autofocus     Sets the autofocus parameter for the input tag
	 *                            autocomplete  Sets the autocomplete parameter for the input tag
	 *                            filter        Sets the filter for the input tag
	 *
	 * @return  string  HTML markup for a calendar field
	 *
	 * @since   1.5
	 *
	 */
	public static function calendar($value, $name, $id, $format = '%Y-%m-%d', $attribs = array())
	{
		$tag       = Factory::getLanguage()->getTag();
		$calendar  = Factory::getLanguage()->getCalendar();
		$direction = strtolower(Factory::getDocument()->getDirection());

		// Get the appropriate file for the current language date helper
		$helperPath = 'system/fields/calendar-locales/date/gregorian/date-helper.min.js';

		if (!empty($calendar) && is_dir(JPATH_ROOT . '/media/system/js/fields/calendar-locales/date/' . strtolower($calendar)))
		{
			$helperPath = 'system/fields/calendar-locales/date/' . strtolower($calendar) . '/date-helper.min.js';
		}

		// Get the appropriate locale file for the current language
		$localesPath = 'system/fields/calendar-locales/en.js';

		if (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower($tag) . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . strtolower($tag) . '.js';
		}
		elseif (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . $tag . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . $tag . '.js';
		}
		elseif (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js';
		}

		$readonly     = isset($attribs['readonly']) && $attribs['readonly'] === 'readonly';
		$disabled     = isset($attribs['disabled']) && $attribs['disabled'] === 'disabled';
		$autocomplete = isset($attribs['autocomplete']) && $attribs['autocomplete'] === '';
		$autofocus    = isset($attribs['autofocus']) && $attribs['autofocus'] === '';
		$required     = isset($attribs['required']) && $attribs['required'] === '';
		$filter       = isset($attribs['filter']) && $attribs['filter'] === '';
		$todayBtn     = isset($attribs['todayBtn']) ? $attribs['todayBtn'] : true;
		$weekNumbers  = isset($attribs['weekNumbers']) ? $attribs['weekNumbers'] : true;
		$showTime     = isset($attribs['showTime']) ? $attribs['showTime'] : false;
		$fillTable    = isset($attribs['fillTable']) ? $attribs['fillTable'] : true;
		$timeFormat   = isset($attribs['timeFormat']) ? $attribs['timeFormat'] : 24;
		$singleHeader = isset($attribs['singleHeader']) ? $attribs['singleHeader'] : false;
		$hint         = isset($attribs['placeholder']) ? $attribs['placeholder'] : '';
		$class        = isset($attribs['class']) ? $attribs['class'] : '';
		$onchange     = isset($attribs['onChange']) ? $attribs['onChange'] : '';
		$minYear      = isset($attribs['minYear']) ? $attribs['minYear'] : null;
		$maxYear      = isset($attribs['maxYear']) ? $attribs['maxYear'] : null;

		$showTime     = ($showTime) ? "1" : "0";
		$todayBtn     = ($todayBtn) ? "1" : "0";
		$weekNumbers  = ($weekNumbers) ? "1" : "0";
		$fillTable    = ($fillTable) ? "1" : "0";
		$singleHeader = ($singleHeader) ? "1" : "0";

		// Format value when not nulldate ('0000-00-00 00:00:00'), otherwise blank it as it would result in 1970-01-01.
		if ($value && $value !== Factory::getDbo()->getNullDate() && strtotime($value) !== false)
		{
			$tz = date_default_timezone_get();
			date_default_timezone_set('UTC');
			$inputvalue = strftime($format, strtotime($value));
			date_default_timezone_set($tz);
		}
		else
		{
			$inputvalue = '';
		}

		$data = array(
			'id'           => $id,
			'name'         => $name,
			'class'        => $class,
			'value'        => $inputvalue,
			'format'       => $format,
			'filter'       => $filter,
			'required'     => $required,
			'readonly'     => $readonly,
			'disabled'     => $disabled,
			'hint'         => $hint,
			'autofocus'    => $autofocus,
			'autocomplete' => $autocomplete,
			'todaybutton'  => $todayBtn,
			'weeknumbers'  => $weekNumbers,
			'showtime'     => $showTime,
			'filltable'    => $fillTable,
			'timeformat'   => $timeFormat,
			'singleheader' => $singleHeader,
			'tag'          => $tag,
			'helperPath'   => $helperPath,
			'localesPath'  => $localesPath,
			'direction'    => $direction,
			'onchange'     => $onchange,
			'minYear'      => $minYear,
			'maxYear'      => $maxYear,
		);

		return LayoutHelper::render('joomla.form.field.calendar', $data, null, null);
	}

	/**
	 * Add a directory where HTMLHelper should search for helpers. You may
	 * either pass a string or an array of directories.
	 *
	 * @param   string  $path  A path to search.
	 *
	 * @return  array  An array with directory elements
	 *
	 * @since   1.5
	 */
	public static function addIncludePath($path = '')
	{
		// Loop through the path directories
		foreach ((array) $path as $dir)
		{
			if (!empty($dir) && !in_array($dir, static::$includePaths))
			{
				array_unshift(static::$includePaths, \JPath::clean($dir));
			}
		}

		return static::$includePaths;
	}

	/**
	 * Internal method to get a JavaScript object notation string from an array
	 *
	 * @param   array  $array  The array to convert to JavaScript object notation
	 *
	 * @return  string  JavaScript object notation representation of the array
	 *
	 * @since   3.0
	 * @deprecated  4.0 Use `json_encode()` or `Joomla\Registry\Registry::toString('json')` instead
	 */
	public static function getJSObject(array $array = array())
	{
		Log::add(
			__METHOD__ . " is deprecated. Use json_encode() or \\Joomla\\Registry\\Registry::toString('json') instead.",
			Log::WARNING,
			'deprecated'
		);

		$elements = array();

		foreach ($array as $k => $v)
		{
			// Don't encode either of these types
			if ($v === null || is_resource($v))
			{
				continue;
			}

			// Safely encode as a Javascript string
			$key = json_encode((string) $k);

			if (is_bool($v))
			{
				$elements[] = $key . ': ' . ($v ? 'true' : 'false');
			}
			elseif (is_numeric($v))
			{
				$elements[] = $key . ': ' . ($v + 0);
			}
			elseif (is_string($v))
			{
				if (strpos($v, '\\') === 0)
				{
					// Items such as functions and JSON objects are prefixed with \, strip the prefix and don't encode them
					$elements[] = $key . ': ' . substr($v, 1);
				}
				else
				{
					// The safest way to insert a string
					$elements[] = $key . ': ' . json_encode((string) $v);
				}
			}
			else
			{
				$elements[] = $key . ': ' . static::getJSObject(is_object($v) ? get_object_vars($v) : $v);
			}
		}

		return '{' . implode(',', $elements) . '}';
	}
}
src/Profiler/Profiler.php000064400000010700152177723700011421 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Profiler;

defined('JPATH_PLATFORM') or die;

/**
 * Utility class to assist in the process of benchmarking the execution
 * of sections of code to understand where time is being spent.
 *
 * @since  1.7.0
 */
class Profiler
{
	/**
	 * @var    integer  The start time.
	 * @since  3.0.0
	 */
	protected $start = 0;

	/**
	 * @var    string  The prefix to use in the output
	 * @since  3.0.0
	 */
	protected $prefix = '';

	/**
	 * @var    array  The buffer of profiling messages.
	 * @since  3.0.0
	 */
	protected $buffer = null;

	/**
	 * @var    array  The profiling messages.
	 * @since  3.0.0
	 */
	protected $marks = null;

	/**
	 * @var    float  The previous time marker
	 * @since  3.0.0
	 */
	protected $previousTime = 0.0;

	/**
	 * @var    float  The previous memory marker
	 * @since  3.0.0
	 */
	protected $previousMem = 0.0;

	/**
	 * @var    array  JProfiler instances container.
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Constructor
	 *
	 * @param   string  $prefix  Prefix for mark messages
	 *
	 * @since   1.7.0
	 */
	public function __construct($prefix = '')
	{
		$this->start = microtime(1);
		$this->prefix = $prefix;
		$this->marks = array();
		$this->buffer = array();
	}

	/**
	 * Returns the global Profiler object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $prefix  Prefix used to distinguish profiler objects.
	 *
	 * @return  Profiler  The Profiler object.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($prefix = '')
	{
		if (empty(self::$instances[$prefix]))
		{
			self::$instances[$prefix] = new Profiler($prefix);
		}

		return self::$instances[$prefix];
	}

	/**
	 * Output a time mark
	 *
	 * @param   string  $label  A label for the time mark
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function mark($label)
	{
		$current = microtime(1) - $this->start;
		$currentMem = memory_get_usage() / 1048576;

		$m = (object) array(
			'prefix' => $this->prefix,
			'time' => ($current > $this->previousTime ? '+' : '-') . (($current - $this->previousTime) * 1000),
			'totalTime' => ($current * 1000),
			'memory' => ($currentMem > $this->previousMem ? '+' : '-') . ($currentMem - $this->previousMem),
			'totalMemory' => $currentMem,
			'label' => $label,
		);
		$this->marks[] = $m;

		$mark = sprintf(
			'%s %.3f seconds (%.3f); %0.2f MB (%0.3f) - %s',
			$m->prefix,
			$m->totalTime / 1000,
			$m->time / 1000,
			$m->totalMemory,
			$m->memory,
			$m->label
		);
		$this->buffer[] = $mark;

		$this->previousTime = $current;
		$this->previousMem = $currentMem;

		return $mark;
	}

	/**
	 * Get the current time.
	 *
	 * @return  float The current time
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use PHP's microtime(1)
	 */
	public static function getmicrotime()
	{
		list ($usec, $sec) = explode(' ', microtime());

		return (float) $usec + (float) $sec;
	}

	/**
	 * Get information about current memory usage.
	 *
	 * @return  integer  The memory usage
	 *
	 * @link    PHP_MANUAL#memory_get_usage
	 * @since   1.7.0
	 * @deprecated  4.0 - Use PHP's native memory_get_usage()
	 */
	public function getMemory()
	{
		return memory_get_usage();
	}

	/**
	 * Get all profiler marks.
	 *
	 * Returns an array of all marks created since the Profiler object
	 * was instantiated.  Marks are objects as per {@link JProfiler::mark()}.
	 *
	 * @return  array  Array of profiler marks
	 *
	 * @since   1.7.0
	 */
	public function getMarks()
	{
		return $this->marks;
	}

	/**
	 * Get all profiler mark buffers.
	 *
	 * Returns an array of all mark buffers created since the Profiler object
	 * was instantiated.  Marks are strings as per {@link Profiler::mark()}.
	 *
	 * @return  array  Array of profiler marks
	 *
	 * @since   1.7.0
	 */
	public function getBuffer()
	{
		return $this->buffer;
	}

	/**
	 * Sets the start time.
	 *
	 * @param   double  $startTime  Unix timestamp in microseconds for setting the Profiler start time.
	 * @param   int     $startMem   Memory amount in bytes for setting the Profiler start memory.
	 *
	 * @return  $this   For chaining
	 *
	 * @since   3.0.0
	 */
	public function setStart($startTime = 0.0, $startMem = 0)
	{
		$this->start       = (double) $startTime;
		$this->previousMem = (int) $startMem / 1048576;

		return $this;
	}
}
src/Version.php000064400000017443152177723700007515 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Helper\LibraryHelper;

/**
 * Version information class for the Joomla CMS.
 *
 * @since  1.0
 */
final class Version
{
	/**
	 * Product name.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const PRODUCT = 'Joomla!';

	/**
	 * Major release version.
	 *
	 * @var    integer
	 * @since  3.8.0
	 */
	const MAJOR_VERSION = 3;

	/**
	 * Minor release version.
	 *
	 * @var    integer
	 * @since  3.8.0
	 */
	const MINOR_VERSION = 9;

	/**
	 * Patch release version.
	 *
	 * @var    integer
	 * @since  3.8.0
	 */
	const PATCH_VERSION = 10;

	/**
	 * Extra release version info.
	 *
	 * This constant when not empty adds an additional identifier to the version string to reflect the development state.
	 * For example, for 3.8.0 when this is set to 'dev' the version string will be `3.8.0-dev`.
	 *
	 * @var    string
	 * @since  3.8.0
	 */
	const EXTRA_VERSION = '';

	/**
	 * Release version.
	 *
	 * @var    string
	 * @since  3.5
	 * @deprecated  4.0  Use separated version constants instead
	 */
	const RELEASE = '3.9';

	/**
	 * Maintenance version.
	 *
	 * @var    string
	 * @since  3.5
	 * @deprecated  4.0  Use separated version constants instead
	 */
	const DEV_LEVEL = '10';

	/**
	 * Development status.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const DEV_STATUS = 'Stable';

	/**
	 * Build number.
	 *
	 * @var    string
	 * @since  3.5
	 * @deprecated  4.0
	 */
	const BUILD = '';

	/**
	 * Code name.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const CODENAME = 'Amani';

	/**
	 * Release date.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELDATE = '10-July-2019';

	/**
	 * Release time.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELTIME = '15:57';

	/**
	 * Release timezone.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const RELTZ = 'GMT';

	/**
	 * Copyright Notice.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const COPYRIGHT = 'Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.';

	/**
	 * Link text.
	 *
	 * @var    string
	 * @since  3.5
	 */
	const URL = '<a href="https://www.joomla.org">Joomla!</a> is Free Software released under the GNU General Public License.';

	/**
	 * Magic getter providing access to constants previously defined as class member vars.
	 *
	 * @param   string  $name  The name of the property.
	 *
	 * @return  mixed   A value if the property name is valid.
	 *
	 * @since   3.5
	 * @deprecated  4.0  Access the constants directly
	 */
	public function __get($name)
	{
		if (defined("JVersion::$name"))
		{
			\JLog::add(
				'Accessing Version data through class member variables is deprecated, use the corresponding constant instead.',
				\JLog::WARNING,
				'deprecated'
			);

			return constant("\\Joomla\\CMS\\Version::$name");
		}

		$trace = debug_backtrace();
		trigger_error(
			'Undefined constant via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
			E_USER_NOTICE
		);
	}

	/**
	 * Check if we are in development mode
	 *
	 * @return  boolean
	 *
	 * @since   3.4.3
	 */
	public function isInDevelopmentState()
	{
		return strtolower(self::DEV_STATUS) !== 'stable';
	}

	/**
	 * Compares two a "PHP standardized" version number against the current Joomla version.
	 *
	 * @param   string  $minimum  The minimum version of the Joomla which is compatible.
	 *
	 * @return  boolean True if the version is compatible.
	 *
	 * @link    https://www.php.net/version_compare
	 * @since   1.0
	 */
	public function isCompatible($minimum)
	{
		return version_compare(JVERSION, $minimum, 'ge');
	}

	/**
	 * Method to get the help file version.
	 *
	 * @return  string  Version suffix for help files.
	 *
	 * @since   1.0
	 */
	public function getHelpVersion()
	{
		return '.' . self::MAJOR_VERSION . self::MINOR_VERSION;
	}

	/**
	 * Gets a "PHP standardized" version string for the current Joomla.
	 *
	 * @return  string  Version string.
	 *
	 * @since   1.5
	 */
	public function getShortVersion()
	{
		$version = self::MAJOR_VERSION . '.' . self::MINOR_VERSION . '.' . self::PATCH_VERSION;

		// Has to be assigned to a variable to support PHP 5.3 and 5.4
		$extraVersion = self::EXTRA_VERSION;

		if (!empty($extraVersion))
		{
			$version .= '-' . $extraVersion;
		}

		return $version;
	}

	/**
	 * Gets a version string for the current Joomla with all release information.
	 *
	 * @return  string  Complete version string.
	 *
	 * @since   1.5
	 */
	public function getLongVersion()
	{
		return self::PRODUCT . ' ' . $this->getShortVersion() . ' '
			. self::DEV_STATUS . ' [ ' . self::CODENAME . ' ] ' . self::RELDATE . ' '
			. self::RELTIME . ' ' . self::RELTZ;
	}

	/**
	 * Returns the user agent.
	 *
	 * @param   string  $component    Name of the component.
	 * @param   bool    $mask         Mask as Mozilla/5.0 or not.
	 * @param   bool    $add_version  Add version afterwards to component.
	 *
	 * @return  string  User Agent.
	 *
	 * @since   1.0
	 */
	public function getUserAgent($component = null, $mask = false, $add_version = true)
	{
		if ($component === null)
		{
			$component = 'Framework';
		}

		if ($add_version)
		{
			$component .= '/' . self::RELEASE;
		}

		// If masked pretend to look like Mozilla 5.0 but still identify ourselves.
		if ($mask)
		{
			return 'Mozilla/5.0 ' . self::PRODUCT . '/' . self::RELEASE . '.' . self::DEV_LEVEL . ($component ? ' ' . $component : '');
		}
		else
		{
			return self::PRODUCT . '/' . self::RELEASE . '.' . self::DEV_LEVEL . ($component ? ' ' . $component : '');
		}
	}

	/**
	 * Generate a media version string for assets
	 * Public to allow third party developers to use it
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function generateMediaVersion()
	{
		$date = new \JDate;

		return md5($this->getLongVersion() . \JFactory::getConfig()->get('secret') . $date->toSql());
	}

	/**
	 * Gets a media version which is used to append to Joomla core media files.
	 *
	 * This media version is used to append to Joomla core media in order to trick browsers into
	 * reloading the CSS and JavaScript, because they think the files are renewed.
	 * The media version is renewed after Joomla core update, install, discover_install and uninstallation.
	 *
	 * @return  string  The media version.
	 *
	 * @since   3.2
	 */
	public function getMediaVersion()
	{
		// Load the media version and cache it for future use
		static $mediaVersion = null;

		if ($mediaVersion === null)
		{
			// Get the joomla library params
			$params = LibraryHelper::getParams('joomla');

			// Get the media version
			$mediaVersion = $params->get('mediaversion', '');

			// Refresh assets in debug mode or when the media version is not set
			if (JDEBUG || empty($mediaVersion))
			{
				$mediaVersion = $this->generateMediaVersion();

				$this->setMediaVersion($mediaVersion);
			}
		}

		return $mediaVersion;
	}

	/**
	 * Function to refresh the media version
	 *
	 * @return  Version  Instance of $this to allow chaining.
	 *
	 * @since   3.2
	 */
	public function refreshMediaVersion()
	{
		$newMediaVersion = $this->generateMediaVersion();

		return $this->setMediaVersion($newMediaVersion);
	}

	/**
	 * Sets the media version which is used to append to Joomla core media files.
	 *
	 * @param   string  $mediaVersion  The media version.
	 *
	 * @return  Version  Instance of $this to allow chaining.
	 *
	 * @since   3.2
	 */
	public function setMediaVersion($mediaVersion)
	{
		// Do not allow empty media versions
		if (!empty($mediaVersion))
		{
			// Get library parameters
			$params = LibraryHelper::getParams('joomla');

			$params->set('mediaversion', $mediaVersion);

			// Save modified params
			LibraryHelper::saveParams('joomla', $params);
		}

		return $this;
	}
}
src/Table/Ucm.php000064400000001064152177723700007633 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * UCM map table
 *
 * @since  3.1
 */
class Ucm extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_base', 'ucm_id', $db);
	}
}
src/Table/Usergroup.php000064400000014164152177723700011107 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Usergroup table class.
 *
 * @since  1.7.0
 */
class Usergroup extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__usergroups', 'id', $db);
	}

	/**
	 * Method to check the current record to save
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		// Validate the title.
		if ((trim($this->title)) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE'));

			return false;
		}

		// Check for a duplicate parent_id, title.
		// There is a unique index on the (parent_id, title) field in the table.
		$db = $this->_db;
		$query = $db->getQuery(true)
			->select('COUNT(title)')
			->from($this->_tbl)
			->where('title = ' . $db->quote(trim($this->title)))
			->where('parent_id = ' . (int) $this->parent_id)
			->where('id <> ' . (int) $this->id);
		$db->setQuery($query);

		if ($db->loadResult() > 0)
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERGROUP_TITLE_EXISTS'));

			return false;
		}

		return true;
	}

	/**
	 * Method to recursively rebuild the nested set tree.
	 *
	 * @param   integer  $parent_id  The root of the tree to rebuild.
	 * @param   integer  $left       The left id to start with in building the tree.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function rebuild($parent_id = 0, $left = 0)
	{
		// Get the database object
		$db = $this->_db;

		// Get all children of this node
		$db->setQuery('SELECT id FROM ' . $this->_tbl . ' WHERE parent_id=' . (int) $parent_id . ' ORDER BY parent_id, title');
		$children = $db->loadColumn();

		// The right value of this node is the left value + 1
		$right = $left + 1;

		// Execute this function recursively over all children
		for ($i = 0, $n = count($children); $i < $n; $i++)
		{
			// $right is the current right value, which is incremented on recursion return
			$right = $this->rebuild($children[$i], $right);

			// If there is an update failure, return false to break out of the recursion
			if ($right === false)
			{
				return false;
			}
		}

		// We've got the left value, and now that we've processed
		// the children of this node we also know the right value
		$db->setQuery('UPDATE ' . $this->_tbl . ' SET lft=' . (int) $left . ', rgt=' . (int) $right . ' WHERE id=' . (int) $parent_id);

		// If there is an update failure, return false to break out of the recursion
		try
		{
			$db->execute();
		}
		catch (\JDatabaseExceptionExecuting $e)
		{
			return false;
		}

		// Return the right value of this node + 1
		return $right + 1;
	}

	/**
	 * Inserts a new row if id is zero or updates an existing row in the database table
	 *
	 * @param   boolean  $updateNulls  If false, null object variables are not updated
	 *
	 * @return  boolean  True if successful, false otherwise and an internal error message is set
	 *
	 * @since   1.7.0
	 */
	public function store($updateNulls = false)
	{
		if ($result = parent::store($updateNulls))
		{
			// Rebuild the nested set tree.
			$this->rebuild();
		}

		return $result;
	}

	/**
	 * Delete this object and its dependencies
	 *
	 * @param   integer  $oid  The primary key of the user group to delete.
	 *
	 * @return  mixed  Boolean or Exception.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 * @throws  \UnexpectedValueException on data error.
	 */
	public function delete($oid = null)
	{
		if ($oid)
		{
			$this->load($oid);
		}

		if ($this->id == 0)
		{
			throw new \UnexpectedValueException('Usergroup not found');
		}

		if ($this->parent_id == 0)
		{
			throw new \UnexpectedValueException('Root usergroup cannot be deleted.');
		}

		if ($this->lft == 0 || $this->rgt == 0)
		{
			throw new \UnexpectedValueException('Left-Right data inconsistency. Cannot delete usergroup.');
		}

		$db = $this->_db;

		// Select the usergroup ID and its children
		$query = $db->getQuery(true)
			->select($db->quoteName('c.id'))
			->from($db->quoteName($this->_tbl) . 'AS c')
			->where($db->quoteName('c.lft') . ' >= ' . (int) $this->lft)
			->where($db->quoteName('c.rgt') . ' <= ' . (int) $this->rgt);
		$db->setQuery($query);
		$ids = $db->loadColumn();

		if (empty($ids))
		{
			throw new \UnexpectedValueException('Left-Right data inconsistency. Cannot delete usergroup.');
		}

		// Delete the usergroup and its children
		$query->clear()
			->delete($db->quoteName($this->_tbl))
			->where($db->quoteName('id') . ' IN (' . implode(',', $ids) . ')');
		$db->setQuery($query);
		$db->execute();

		// Delete the usergroup in view levels
		$replace = array();

		foreach ($ids as $id)
		{
			$replace[] = ',' . $db->quote("[$id,") . ',' . $db->quote('[') . ')';
			$replace[] = ',' . $db->quote(",$id,") . ',' . $db->quote(',') . ')';
			$replace[] = ',' . $db->quote(",$id]") . ',' . $db->quote(']') . ')';
			$replace[] = ',' . $db->quote("[$id]") . ',' . $db->quote('[]') . ')';
		}

		$query->clear()
			->select('id, rules')
			->from('#__viewlevels');
		$db->setQuery($query);
		$rules = $db->loadObjectList();

		$match_ids = array();

		foreach ($rules as $rule)
		{
			foreach ($ids as $id)
			{
				if (strstr($rule->rules, '[' . $id) || strstr($rule->rules, ',' . $id) || strstr($rule->rules, $id . ']'))
				{
					$match_ids[] = $rule->id;
				}
			}
		}

		if (!empty($match_ids))
		{
			$query->clear()
				->set('rules=' . str_repeat('replace(', 4 * count($ids)) . 'rules' . implode('', $replace))
				->update('#__viewlevels')
				->where('id IN (' . implode(',', $match_ids) . ')');
			$db->setQuery($query);
			$db->execute();
		}

		// Delete the user to usergroup mappings for the group(s) from the database.
		$query->clear()
			->delete($db->quoteName('#__user_usergroup_map'))
			->where($db->quoteName('group_id') . ' IN (' . implode(',', $ids) . ')');
		$db->setQuery($query);
		$db->execute();

		return true;
	}
}
src/Table/UpdateSite.php000064400000002056152177723700011160 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Update site table
 * Stores the update sites for extensions
 *
 * @package     Joomla.Platform
 * @subpackage  Table
 * @since       3.4
 */
class UpdateSite extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   3.4
	 */
	public function __construct($db)
	{
		parent::__construct('#__update_sites', 'update_site_id', $db);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True if the object is ok
	 *
	 * @see     Table::check()
	 * @since   3.4
	 */
	public function check()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->location) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		return true;
	}
}
src/Table/User.php000064400000031371152177723700010031 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

/**
 * Users table
 *
 * @since  1.7.0
 */
class User extends Table
{
	/**
	 * Associative array of group ids => group ids for the user
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $groups;

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since  1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__users', 'id', $db);

		// Initialise.
		$this->id = 0;
		$this->sendEmail = 0;
	}

	/**
	 * Method to load a user, user groups, and any other necessary data
	 * from the database so that it can be bound to the user object.
	 *
	 * @param   integer  $userId  An optional user id.
	 * @param   boolean  $reset   False if row not found or on error
	 *                           (internal error state set in that case).
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   1.7.0
	 */
	public function load($userId = null, $reset = true)
	{
		// Get the id to load.
		if ($userId !== null)
		{
			$this->id = $userId;
		}
		else
		{
			$userId = $this->id;
		}

		// Check for a valid id to load.
		if ($userId === null)
		{
			return false;
		}

		// Reset the table.
		$this->reset();

		// Load the user data.
		$query = $this->_db->getQuery(true)
			->select('*')
			->from($this->_db->quoteName('#__users'))
			->where($this->_db->quoteName('id') . ' = ' . (int) $userId);
		$this->_db->setQuery($query);
		$data = (array) $this->_db->loadAssoc();

		if (!count($data))
		{
			return false;
		}

		// Convert email from punycode
		$data['email'] = \JStringPunycode::emailToUTF8($data['email']);

		// Bind the data to the table.
		$return = $this->bind($data);

		if ($return !== false)
		{
			// Load the user groups.
			$query->clear()
				->select($this->_db->quoteName('g.id'))
				->select($this->_db->quoteName('g.title'))
				->from($this->_db->quoteName('#__usergroups') . ' AS g')
				->join('INNER', $this->_db->quoteName('#__user_usergroup_map') . ' AS m ON m.group_id = g.id')
				->where($this->_db->quoteName('m.user_id') . ' = ' . (int) $userId);
			$this->_db->setQuery($query);

			// Add the groups to the user data.
			$this->groups = $this->_db->loadAssocList('id', 'id');
		}

		return $return;
	}

	/**
	 * Method to bind the user, user groups, and any other necessary data.
	 *
	 * @param   array  $array   The data to bind.
	 * @param   mixed  $ignore  An array or space separated list of fields to ignore.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   1.7.0
	 */
	public function bind($array, $ignore = '')
	{
		if (array_key_exists('params', $array) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		// Attempt to bind the data.
		$return = parent::bind($array, $ignore);

		// Load the real group data based on the bound ids.
		if ($return && !empty($this->groups))
		{
			// Set the group ids.
			$this->groups = ArrayHelper::toInteger($this->groups);

			// Get the titles for the user groups.
			$query = $this->_db->getQuery(true)
				->select($this->_db->quoteName('id'))
				->select($this->_db->quoteName('title'))
				->from($this->_db->quoteName('#__usergroups'))
				->where($this->_db->quoteName('id') . ' = ' . implode(' OR ' . $this->_db->quoteName('id') . ' = ', $this->groups));
			$this->_db->setQuery($query);

			// Set the titles for the user groups.
			$this->groups = $this->_db->loadAssocList('id', 'id');
		}

		return $return;
	}

	/**
	 * Validation and filtering
	 *
	 * @return  boolean  True if satisfactory
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		// Set user id to null istead of 0, if needed
		if ($this->id === 0)
		{
			$this->id = null;
		}

		$filterInput = \JFilterInput::getInstance();

		// Validate user information
		if ($filterInput->clean($this->name, 'TRIM') == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_YOUR_NAME'));

			return false;
		}

		if ($filterInput->clean($this->username, 'TRIM') == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_PLEASE_ENTER_A_USER_NAME'));

			return false;
		}

		if (preg_match('#[<>"\'%;()&\\\\]|\\.\\./#', $this->username) || strlen(utf8_decode($this->username)) < 2
			|| $filterInput->clean($this->username, 'TRIM') !== $this->username)
		{
			$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_VALID_AZ09', 2));

			return false;
		}

		if (($filterInput->clean($this->email, 'TRIM') == '') || !\JMailHelper::isEmailAddress($this->email))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_VALID_MAIL'));

			return false;
		}

		// Convert email to punycode for storage
		$this->email = \JStringPunycode::emailToPunycode($this->email);

		// Set the registration timestamp
		if (empty($this->registerDate) || $this->registerDate == $this->_db->getNullDate())
		{
			$this->registerDate = \JFactory::getDate()->toSql();
		}

		// Set the lastvisitDate timestamp
		if (empty($this->lastvisitDate))
		{
			$this->lastvisitDate = $this->_db->getNullDate();
		}

		// Set the lastResetTime timestamp
		if (empty($this->lastResetTime))
		{
			$this->lastResetTime = $this->_db->getNullDate();
		}

		// Check for existing username
		$query = $this->_db->getQuery(true)
			->select($this->_db->quoteName('id'))
			->from($this->_db->quoteName('#__users'))
			->where($this->_db->quoteName('username') . ' = ' . $this->_db->quote($this->username))
			->where($this->_db->quoteName('id') . ' != ' . (int) $this->id);
		$this->_db->setQuery($query);

		$xid = (int) $this->_db->loadResult();

		if ($xid && $xid != (int) $this->id)
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERNAME_INUSE'));

			return false;
		}

		// Check for existing email
		$query->clear()
			->select($this->_db->quoteName('id'))
			->from($this->_db->quoteName('#__users'))
			->where($this->_db->quoteName('email') . ' = ' . $this->_db->quote($this->email))
			->where($this->_db->quoteName('id') . ' != ' . (int) $this->id);
		$this->_db->setQuery($query);
		$xid = (int) $this->_db->loadResult();

		if ($xid && $xid != (int) $this->id)
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_EMAIL_INUSE'));

			return false;
		}

		// Check for root_user != username
		$config = \JFactory::getConfig();
		$rootUser = $config->get('root_user');

		if (!is_numeric($rootUser))
		{
			$query->clear()
				->select($this->_db->quoteName('id'))
				->from($this->_db->quoteName('#__users'))
				->where($this->_db->quoteName('username') . ' = ' . $this->_db->quote($rootUser));
			$this->_db->setQuery($query);
			$xid = (int) $this->_db->loadResult();

			if ($rootUser == $this->username && (!$xid || $xid && $xid != (int) $this->id)
				|| $xid && $xid == (int) $this->id && $rootUser != $this->username)
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_USERNAME_CANNOT_CHANGE'));

				return false;
			}
		}

		return true;
	}

	/**
	 * Method to store a row in the database from the Table instance properties.
	 *
	 * If a primary key value is set the row with that primary key value will be updated with the instance property values.
	 * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function store($updateNulls = false)
	{
		// Get the table key and key value.
		$k = $this->_tbl_key;
		$key = $this->$k;

		// TODO: This is a dumb way to handle the groups.
		// Store groups locally so as to not update directly.
		$groups = $this->groups;
		unset($this->groups);

		// Insert or update the object based on presence of a key value.
		if ($key)
		{
			// Already have a table key, update the row.
			$this->_db->updateObject($this->_tbl, $this, $this->_tbl_key, $updateNulls);
		}
		else
		{
			// Don't have a table key, insert the row.
			$this->_db->insertObject($this->_tbl, $this, $this->_tbl_key);
		}

		// Reset groups to the local object.
		$this->groups = $groups;

		$query = $this->_db->getQuery(true);

		// Store the group data if the user data was saved.
		if (is_array($this->groups) && count($this->groups))
		{
			// Grab all usergroup entries for the user
			$query -> clear()
				-> select($this->_db->quoteName('group_id'))
				-> from($this->_db->quoteName('#__user_usergroup_map'))
				-> where($this->_db->quoteName('user_id') . ' = ' . (int) $this->id);

			$this->_db->setQuery($query);
			$result = $this->_db->loadObjectList();

			// Loop through them and check if database contains something $this->groups does not
			if (count($result))
			{
				foreach ($result as $map)
				{
					if (array_key_exists($map->group_id, $this->groups))
					{
						// It already exists, no action required
						unset($groups[$map->group_id]);
					}
					else
					{
						// It should be removed
						$query -> clear()
							-> delete($this->_db->quoteName('#__user_usergroup_map'))
							-> where($this->_db->quoteName('user_id') . ' = ' . (int) $this->id)
							-> where($this->_db->quoteName('group_id') . ' = ' . (int) $map->group_id);

						$this->_db->setQuery($query);
						$this->_db->execute();
					}
				}
			}

			// If there is anything left in this->groups it needs to be inserted
			if (count($groups))
			{
				// Set the new user group maps.
				$query->clear()
					->insert($this->_db->quoteName('#__user_usergroup_map'))
					->columns(array($this->_db->quoteName('user_id'), $this->_db->quoteName('group_id')));

				// Have to break this up into individual queries for cross-database support.
				foreach ($groups as $group)
				{
					$query->clear('values')
						->values($this->id . ', ' . $group);
					$this->_db->setQuery($query);
					$this->_db->execute();
				}
			}

			unset($groups);
		}

		// If a user is blocked, delete the cookie login rows
		if ($this->block == (int) 1)
		{
			$query->clear()
				->delete($this->_db->quoteName('#__user_keys'))
				->where($this->_db->quoteName('user_id') . ' = ' . $this->_db->quote($this->username));
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		return true;
	}

	/**
	 * Method to delete a user, user groups, and any other necessary data from the database.
	 *
	 * @param   integer  $userId  An optional user id.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   1.7.0
	 */
	public function delete($userId = null)
	{
		// Set the primary key to delete.
		$k = $this->_tbl_key;

		if ($userId)
		{
			$this->$k = (int) $userId;
		}

		// Delete the user.
		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName($this->_tbl_key) . ' = ' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		// Delete the user group maps.
		$query->clear()
			->delete($this->_db->quoteName('#__user_usergroup_map'))
			->where($this->_db->quoteName('user_id') . ' = ' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		/*
		 * Clean Up Related Data.
		 */

		$query->clear()
			->delete($this->_db->quoteName('#__messages_cfg'))
			->where($this->_db->quoteName('user_id') . ' = ' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		$query->clear()
			->delete($this->_db->quoteName('#__messages'))
			->where($this->_db->quoteName('user_id_to') . ' = ' . (int) $this->$k);
		$this->_db->setQuery($query);
		$this->_db->execute();

		$query->clear()
			->delete($this->_db->quoteName('#__user_keys'))
			->where($this->_db->quoteName('user_id') . ' = ' . $this->_db->quote($this->username));
		$this->_db->setQuery($query);
		$this->_db->execute();

		return true;
	}

	/**
	 * Updates last visit time of user
	 *
	 * @param   integer  $timeStamp  The timestamp, defaults to 'now'.
	 * @param   integer  $userId     The user id (optional).
	 *
	 * @return  boolean  False if an error occurs
	 *
	 * @since   1.7.0
	 */
	public function setLastVisit($timeStamp = null, $userId = null)
	{
		// Check for User ID
		if (is_null($userId))
		{
			if (isset($this))
			{
				$userId = $this->id;
			}
			else
			{
				jexit('No userid in setLastVisit');
			}
		}

		// If no timestamp value is passed to function, than current time is used.
		$date = \JFactory::getDate($timeStamp);

		// Update the database row for the user.
		$db = $this->_db;
		$query = $db->getQuery(true)
			->update($db->quoteName($this->_tbl))
			->set($db->quoteName('lastvisitDate') . '=' . $db->quote($date->toSql()))
			->where($db->quoteName('id') . '=' . (int) $userId);
		$db->setQuery($query);
		$db->execute();

		return true;
	}
}
src/Table/TableInterface.php000064400000006655152177723700011772 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Table class interface.
 *
 * @since  3.2
 */
interface TableInterface
{
	/**
	 * Method to bind an associative array or object to the TableInterface instance.
	 *
	 * This method only binds properties that are publicly accessible and optionally takes an array of properties to ignore when binding.
	 *
	 * @param   mixed  $src     An associative array or object to bind to the TableInterface instance.
	 * @param   mixed  $ignore  An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 * @throws  \UnexpectedValueException
	 */
	public function bind($src, $ignore = array());

	/**
	 * Method to perform sanity checks on the TableInterface instance properties to ensure they are safe to store in the database.
	 *
	 * Implementations of this interface should use this method to make sure the data they are storing in the database is safe and
	 * as expected before storage.
	 *
	 * @return  boolean  True if the instance is sane and able to be stored in the database.
	 *
	 * @since   3.2
	 */
	public function check();

	/**
	 * Method to delete a record.
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 * @throws  \UnexpectedValueException
	 */
	public function delete($pk = null);

	/**
	 * Method to get the \JDatabaseDriver object.
	 *
	 * @return  \JDatabaseDriver  The internal database driver object.
	 *
	 * @since   3.2
	 */
	public function getDbo();

	/**
	 * Method to get the primary key field name for the table.
	 *
	 * @return  string  The name of the primary key for the table.
	 *
	 * @since   3.2
	 */
	public function getKeyName();

	/**
	 * Method to load a row from the database by primary key and bind the fields to the TableInterface instance properties.
	 *
	 * @param   mixed    $keys   An optional primary key value to load the row by, or an array of fields to match.  If not
	 *                           set the instance property value is used.
	 * @param   boolean  $reset  True to reset the default values before loading the new row.
	 *
	 * @return  boolean  True if successful. False if row not found.
	 *
	 * @since   3.2
	 * @throws  \RuntimeException
	 * @throws  \UnexpectedValueException
	 */
	public function load($keys = null, $reset = true);

	/**
	 * Method to reset class properties to the defaults set in the class definition.
	 *
	 * It will ignore the primary key as well as any private class properties.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function reset();

	/**
	 * Method to store a row in the database from the TableInterface instance properties.
	 *
	 * If a primary key value is set the row with that primary key value will be updated with the instance property values.
	 * If no primary key value is set a new row will be inserted into the database with the properties from the TableInterface instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 */
	public function store($updateNulls = false);
}
src/Table/Menu.php000064400000017654152177723700010027 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Language\Multilanguage;
use Joomla\Registry\Registry;

/**
 * Menu table
 *
 * @since  1.5
 */
class Menu extends Nested
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__menu', 'id', $db);

		// Set the default access level.
		$this->access = (int) \JFactory::getConfig()->get('access');
	}

	/**
	 * Overloaded bind function
	 *
	 * @param   array  $array   Named array
	 * @param   mixed  $ignore  An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error
	 *
	 * @see     Table::bind()
	 * @since   1.5
	 */
	public function bind($array, $ignore = '')
	{
		// Verify that the default home menu is not unset
		if ($this->home == '1' && $this->language === '*' && $array['home'] == '0')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT_DEFAULT'));

			return false;
		}

		// Verify that the default home menu set to "all" languages" is not unset
		if ($this->home == '1' && $this->language === '*' && $array['language'] !== '*')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_CANNOT_UNSET_DEFAULT'));

			return false;
		}

		// Verify that the default home menu is not unpublished
		if ($this->home == '1' && $this->language === '*' && $array['published'] != '1')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_UNPUBLISH_DEFAULT_HOME'));

			return false;
		}

		if (isset($array['params']) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True on success
	 *
	 * @see     Table::check()
	 * @since   1.5
	 */
	public function check()
	{
		// Check for a title.
		if (trim($this->title) === '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MENUITEM'));

			return false;
		}

		// Check for a path.
		if (trim($this->path) === '')
		{
			$this->path = $this->alias;
		}

		// Check for params.
		if (trim($this->params) === '')
		{
			$this->params = '{}';
		}

		// Check for img.
		if (trim($this->img) === '')
		{
			$this->img = ' ';
		}

		// Cast the home property to an int for checking.
		$this->home = (int) $this->home;

		// Verify that the home item is a component.
		if ($this->home && $this->type !== 'component')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_COMPONENT'));

			return false;
		}

		return true;
	}

	/**
	 * Overloaded store function
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  mixed  False on failure, positive integer on success.
	 *
	 * @see     Table::store()
	 * @since   1.6
	 */
	public function store($updateNulls = false)
	{
		$db = $this->getDbo();

		// Verify that the alias is unique
		$table = Table::getInstance('Menu', 'JTable', array('dbo' => $db));

		$originalAlias = trim($this->alias);
		$this->alias   = !$originalAlias ? $this->title : $originalAlias;
		$this->alias   = ApplicationHelper::stringURLSafe(trim($this->alias), $this->language);

		if ($this->parent_id == 1 && $this->client_id == 0)
		{
			// Verify that a first level menu item alias is not 'component'.
			if ($this->alias == 'component')
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_COMPONENT'));

				return false;
			}

			// Verify that a first level menu item alias is not the name of a folder.
			jimport('joomla.filesystem.folder');

			if (in_array($this->alias, \JFolder::folders(JPATH_ROOT)))
			{
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENU_ROOT_ALIAS_FOLDER', $this->alias, $this->alias));

				return false;
			}
		}

		// If alias still empty (for instance, new menu item with chinese characters with no unicode alias setting).
		if (empty($this->alias))
		{
			$this->alias = \JFactory::getDate()->format('Y-m-d-H-i-s');
		}
		else
		{
			$itemSearch = array('alias' => $this->alias, 'parent_id' => $this->parent_id, 'client_id' => (int) $this->client_id);
			$error      = false;

			// Check if the alias already exists. For multilingual site.
			if (Multilanguage::isEnabled() && (int) $this->client_id == 0)
			{
				// If there is a menu item at the same level with the same alias (in the All or the same language).
				if (($table->load(array_replace($itemSearch, array('language' => '*'))) && ($table->id != $this->id || $this->id == 0))
					|| ($table->load(array_replace($itemSearch, array('language' => $this->language))) && ($table->id != $this->id || $this->id == 0))
					|| ($this->language === '*' && $this->id == 0 && $table->load($itemSearch)))
				{
					$error = true;
				}
				// When editing an item with All language check if there are more menu items with the same alias in any language.
				elseif ($this->language === '*' && $this->id != 0)
				{
					$query = $db->getQuery(true)
						->select('id')
						->from($db->quoteName('#__menu'))
						->where($db->quoteName('parent_id') . ' = 1')
						->where($db->quoteName('client_id') . ' = 0')
						->where($db->quoteName('id') . ' != ' . (int) $this->id)
						->where($db->quoteName('alias') . ' = ' . $db->quote($this->alias));

					$otherMenuItemId = (int) $db->setQuery($query)->loadResult();

					if ($otherMenuItemId)
					{
						$table->load(array('id' => $otherMenuItemId));
						$error = true;
					}
				}
			}
			// Check if the alias already exists. For monolingual site.
			else
			{
				// If there is a menu item at the same level with the same alias (in any language).
				if ($table->load($itemSearch) && ($table->id != $this->id || $this->id == 0))
				{
					$error = true;
				}
			}

			// The alias already exists. Enqueue an error message.
			if ($error)
			{
				$menuTypeTable = Table::getInstance('MenuType', 'JTable', array('dbo' => $db));
				$menuTypeTable->load(array('menutype' => $table->menutype));
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENU_UNIQUE_ALIAS', $this->alias, $table->title, $menuTypeTable->title));

				return false;
			}
		}

		if ($this->home == '1')
		{
			// Verify that the home page for this menu is unique.
			if ($table->load(
					array(
					'menutype' => $this->menutype,
					'client_id' => (int) $this->client_id,
					'home' => '1',
					)
				)
				&& ($table->language != $this->language))
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_HOME_NOT_UNIQUE_IN_MENU'));

				return false;
			}

			// Verify that the home page for this language is unique per client id
			if ($table->load(array('home' => '1', 'language' => $this->language, 'client_id' => (int) $this->client_id)))
			{
				if ($table->checked_out && $table->checked_out != $this->checked_out)
				{
					$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENU_DEFAULT_CHECKIN_USER_MISMATCH'));

					return false;
				}

				$table->home = 0;
				$table->checked_out = 0;
				$table->checked_out_time = $db->getNullDate();
				$table->store();
			}
		}

		if (!parent::store($updateNulls))
		{
			return false;
		}

		// Get the new path in case the node was moved
		$pathNodes = $this->getPath();
		$segments = array();

		foreach ($pathNodes as $node)
		{
			// Don't include root in path
			if ($node->alias !== 'root')
			{
				$segments[] = $node->alias;
			}
		}

		$newPath = trim(implode('/', $segments), ' /\\');

		// Use new path for partial rebuild of table
		// Rebuild will return positive integer on success, false on failure
		return $this->rebuild($this->{$this->_tbl_key}, $this->lft, $this->level, $newPath) > 0;
	}
}
src/Table/Language.php000064400000006410152177723700010632 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Languages table.
 *
 * @since  1.7.0
 */
class Language extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__languages', 'lang_id', $db);
	}

	/**
	 * Overloaded check method to ensure data integrity
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_NO_TITLE'));

			return false;
		}

		return true;
	}

	/**
	 * Overrides Table::store to check unique fields.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   2.5.0
	 */
	public function store($updateNulls = false)
	{
		$table = Table::getInstance('Language', 'JTable', array('dbo' => $this->getDbo()));

		// Verify that the language code is unique
		if ($table->load(array('lang_code' => $this->lang_code)) && ($table->lang_id != $this->lang_id || $this->lang_id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_LANG_CODE'));

			return false;
		}

		// Verify that the sef field is unique
		if ($table->load(array('sef' => $this->sef)) && ($table->lang_id != $this->lang_id || $this->lang_id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE'));

			return false;
		}

		// Verify that the image field is unique
		if ($this->image && $table->load(array('image' => $this->image)) && ($table->lang_id != $this->lang_id || $this->lang_id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_LANGUAGE_UNIQUE_IMAGE'));

			return false;
		}

		return parent::store($updateNulls);
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   3.8.0
	 */
	protected function _getAssetName()
	{
		return 'com_languages.language.' . $this->lang_id;
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return  string
	 *
	 * @since   3.8.0
	 */
	protected function _getAssetTitle()
	{
		return $this->title;
	}

	/**
	 * Method to get the parent asset under which to register this one.
	 * By default, all assets are registered to the ROOT node with ID,
	 * which will default to 1 if none exists.
	 * The extended class can define a table and id to lookup.  If the
	 * asset does not exist it will be created.
	 *
	 * @param   Table    $table  A Table object for the asset parent.
	 * @param   integer  $id     Id to look up
	 *
	 * @return  integer
	 *
	 * @since   3.8.0
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;
		$asset   = Table::getInstance('asset');

		if ($asset->loadByName('com_languages'))
		{
			$assetId = $asset->id;
		}

		return $assetId === null ? parent::_getAssetParentId($table, $id) : $assetId;
	}
}
src/Table/CoreContent.php000064400000025110152177723700011330 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

/**
 * Core content table
 *
 * @since  3.1
 */
class CoreContent extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_content', 'core_content_id', $db);
	}

	/**
	 * Overloaded bind function
	 *
	 * @param   array  $array   Named array
	 * @param   mixed  $ignore  An optional array or space separated list of properties
	 *                          to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error string
	 *
	 * @see     Table::bind()
	 * @since   3.1
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['core_params']) && is_array($array['core_params']))
		{
			$registry = new Registry($array['core_params']);
			$array['core_params'] = (string) $registry;
		}

		if (isset($array['core_metadata']) && is_array($array['core_metadata']))
		{
			$registry = new Registry($array['core_metadata']);
			$array['core_metadata'] = (string) $registry;
		}

		if (isset($array['core_images']) && is_array($array['core_images']))
		{
			$registry = new Registry($array['core_images']);
			$array['core_images'] = (string) $registry;
		}

		if (isset($array['core_urls']) && is_array($array['core_urls']))
		{
			$registry = new Registry($array['core_urls']);
			$array['core_urls'] = (string) $registry;
		}

		if (isset($array['core_body']) && is_array($array['core_body']))
		{
			$registry = new Registry($array['core_body']);
			$array['core_body'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @see     Table::check()
	 * @since   3.1
	 */
	public function check()
	{
		if (trim($this->core_title) === '')
		{
			$this->setError(\JText::_('JLIB_CMS_WARNING_PROVIDE_VALID_NAME'));

			return false;
		}

		if (trim($this->core_alias) === '')
		{
			$this->core_alias = $this->core_title;
		}

		$this->core_alias = \JApplicationHelper::stringURLSafe($this->core_alias);

		if (trim(str_replace('-', '', $this->core_alias)) === '')
		{
			$this->core_alias = \JFactory::getDate()->format('Y-m-d-H-i-s');
		}

		// Not Null sanity check
		if (empty($this->core_images))
		{
			$this->core_images = '{}';
		}

		if (empty($this->core_urls))
		{
			$this->core_urls = '{}';
		}

		// Check the publish down date is not earlier than publish up.
		if ($this->core_publish_down < $this->core_publish_up && $this->core_publish_down > $this->_db->getNullDate())
		{
			// Swap the dates.
			$temp = $this->core_publish_up;
			$this->core_publish_up = $this->core_publish_down;
			$this->core_publish_down = $temp;
		}

		// Clean up keywords -- eliminate extra spaces between phrases
		// and cr (\r) and lf (\n) characters from string
		if (!empty($this->core_metakey))
		{
			// Only process if not empty

			// Array of characters to remove
			$bad_characters = array("\n", "\r", "\"", '<', '>');

			// Remove bad characters
			$after_clean = StringHelper::str_ireplace($bad_characters, '', $this->core_metakey);

			// Create array using commas as delimiter
			$keys = explode(',', $after_clean);

			$clean_keys = array();

			foreach ($keys as $key)
			{
				if (trim($key))
				{
					// Ignore blank keywords
					$clean_keys[] = trim($key);
				}
			}

			// Put array back together delimited by ", "
			$this->core_metakey = implode(', ', $clean_keys);
		}

		return true;
	}

	/**
	 * Override JTable delete method to include deleting corresponding row from #__ucm_base.
	 *
	 * @param   integer  $pk  primary key value to delete. Must be set or throws an exception.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 * @throws  \UnexpectedValueException
	 */
	public function delete($pk = null)
	{
		$baseTable = Table::getInstance('Ucm', 'JTable', array('dbo' => $this->getDbo()));

		return parent::delete($pk) && $baseTable->delete($pk);
	}

	/**
	 * Method to delete a row from the #__ucm_content table by content_item_id.
	 *
	 * @param   integer  $contentItemId  value of the core_content_item_id to delete. Corresponds to the primary key of the content table.
	 * @param   string   $typeAlias      Alias for the content type
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 * @throws  \UnexpectedValueException
	 */
	public function deleteByContentId($contentItemId = null, $typeAlias = null)
	{
		if ($contentItemId === null || ((int) $contentItemId) === 0)
		{
			throw new \UnexpectedValueException('Null content item key not allowed.');
		}

		if ($typeAlias === null)
		{
			throw new \UnexpectedValueException('Null type alias not allowed.');
		}

		$db = $this->getDbo();
		$query = $db->getQuery(true);
		$query->select($db->quoteName('core_content_id'))
			->from($db->quoteName('#__ucm_content'))
			->where($db->quoteName('core_content_item_id') . ' = ' . (int) $contentItemId)
			->where($db->quoteName('core_type_alias') . ' = ' . $db->quote($typeAlias));
		$db->setQuery($query);

		if ($ucmId = $db->loadResult())
		{
			return $this->delete($ucmId);
		}
		else
		{
			return true;
		}
	}

	/**
	 * Overrides Table::store to set modified data and user id.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 */
	public function store($updateNulls = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::getUser();

		if ($this->core_content_id)
		{
			// Existing item
			$this->core_modified_time = $date->toSql();
			$this->core_modified_user_id = $user->get('id');
			$isNew = false;
		}
		else
		{
			// New content item. A content item core_created_time and core_created_user_id field can be set by the user,
			// so we don't touch either of these if they are set.
			if (!(int) $this->core_created_time)
			{
				$this->core_created_time = $date->toSql();
			}

			if (empty($this->core_created_user_id))
			{
				$this->core_created_user_id = $user->get('id');
			}

			$isNew = true;
		}

		$oldRules = $this->getRules();

		if (empty($oldRules))
		{
			$this->setRules('{}');
		}

		$result = parent::store($updateNulls);

		return $result && $this->storeUcmBase($updateNulls, $isNew);
	}

	/**
	 * Insert or update row in ucm_base table
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 * @param   boolean  $isNew        if true, need to insert. Otherwise update.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 */
	protected function storeUcmBase($updateNulls = false, $isNew = false)
	{
		// Store the ucm_base row
		$db         = $this->getDbo();
		$query      = $db->getQuery(true);
		$languageId = \JHelperContent::getLanguageId($this->core_language);

		// Selecting "all languages" doesn't give a language id - we can't store a blank string in non mysql databases, so save 0 (the default value)
		if (!$languageId)
		{
			$languageId = '0';
		}

		if ($isNew)
		{
			$query->insert($db->quoteName('#__ucm_base'))
				->columns(array($db->quoteName('ucm_id'), $db->quoteName('ucm_item_id'), $db->quoteName('ucm_type_id'), $db->quoteName('ucm_language_id')))
				->values(
					$db->quote($this->core_content_id) . ', '
					. $db->quote($this->core_content_item_id) . ', '
					. $db->quote($this->core_type_id) . ', '
					. $db->quote($languageId)
			);
		}
		else
		{
			$query->update($db->quoteName('#__ucm_base'))
				->set($db->quoteName('ucm_item_id') . ' = ' . $db->quote($this->core_content_item_id))
				->set($db->quoteName('ucm_type_id') . ' = ' . $db->quote($this->core_type_id))
				->set($db->quoteName('ucm_language_id') . ' = ' . $db->quote($languageId))
				->where($db->quoteName('ucm_id') . ' = ' . $db->quote($this->core_content_id));
		}

		$db->setQuery($query);

		return $db->execute();
	}

	/**
	 * Method to set the publishing state for a row or list of rows in the database
	 * table. The method respects checked out rows by other users and will attempt
	 * to checkin rows that it can after adjustments are made.
	 *
	 * @param   mixed    $pks     An optional array of primary key values to update.  If not set the instance property value is used.
	 * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
	 * @param   integer  $userId  The user id of the user performing the operation.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		$k = $this->_tbl_key;

		// Sanitize input.
		$pks    = ArrayHelper::toInteger($pks);
		$userId = (int) $userId;
		$state  = (int) $state;

		// If there are no primary keys set check to see if the instance key is set.
		if (empty($pks))
		{
			if ($this->$k)
			{
				$pks = array($this->$k);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

				return false;
			}
		}

		$pksImploded = implode(',', $pks);

		// Get the JDatabaseQuery object
		$query = $this->_db->getQuery(true);

		// Update the publishing state for rows with the given primary keys.
		$query->update($this->_db->quoteName($this->_tbl))
			->set($this->_db->quoteName('core_state') . ' = ' . (int) $state)
			->where($this->_db->quoteName($k) . 'IN (' . $pksImploded . ')');

		// Determine if there is checkin support for the table.
		$checkin = false;

		if (property_exists($this, 'core_checked_out_user_id') && property_exists($this, 'core_checked_out_time'))
		{
			$checkin = true;
			$query->where(
				' ('
				. $this->_db->quoteName('core_checked_out_user_id') . ' = 0 OR ' . $this->_db->quoteName('core_checked_out_user_id') . ' = ' . (int) $userId
				. ')'
			);
		}

		$this->_db->setQuery($query);

		try
		{
			$this->_db->execute();
		}
		catch (\RuntimeException $e)
		{
			$this->setError($e->getMessage());

			return false;
		}

		// If checkin is supported and all rows were adjusted, check them in.
		if ($checkin && count($pks) === $this->_db->getAffectedRows())
		{
			// Checkin the rows.
			foreach ($pks as $pk)
			{
				$this->checkin($pk);
			}
		}

		// If the JTable instance value is in the list of primary keys that were set, set the instance.
		if (in_array($this->$k, $pks))
		{
			$this->core_state = $state;
		}

		$this->setError('');

		return true;
	}
}
src/Table/Content.php000064400000021427152177723700010526 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\CMS\Table\Observer\ContentHistory as ContentHistoryObserver;
use Joomla\Registry\Registry;
use Joomla\String\StringHelper;

/**
 * Content table
 *
 * @since       1.5
 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
 */
class Content extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   1.5
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__content', 'id', $db);

		Tags::createObserver($this, array('typeAlias' => 'com_content.article'));
		ContentHistoryObserver::createObserver($this, array('typeAlias' => 'com_content.article'));

		// Set the alias since the column is called state
		$this->setColumnAlias('published', 'state');
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	protected function _getAssetName()
	{
		$k = $this->_tbl_key;

		return 'com_content.article.' . (int) $this->$k;
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	protected function _getAssetTitle()
	{
		return $this->title;
	}

	/**
	 * Method to get the parent asset id for the record
	 *
	 * @param   Table    $table  A Table object (optional) for the asset parent
	 * @param   integer  $id     The id (optional) of the content.
	 *
	 * @return  integer
	 *
	 * @since   1.6
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;

		// This is an article under a category.
		if ($this->catid)
		{
			// Build the query to get the asset id for the parent category.
			$query = $this->_db->getQuery(true)
				->select($this->_db->quoteName('asset_id'))
				->from($this->_db->quoteName('#__categories'))
				->where($this->_db->quoteName('id') . ' = ' . (int) $this->catid);

			// Get the asset id from the database.
			$this->_db->setQuery($query);

			if ($result = $this->_db->loadResult())
			{
				$assetId = (int) $result;
			}
		}

		// Return the asset id.
		if ($assetId)
		{
			return $assetId;
		}
		else
		{
			return parent::_getAssetParentId($table, $id);
		}
	}

	/**
	 * Overloaded bind function
	 *
	 * @param   array  $array   Named array
	 * @param   mixed  $ignore  An optional array or space separated list of properties
	 *                          to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error string
	 *
	 * @see     Table::bind()
	 * @since   1.6
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	public function bind($array, $ignore = '')
	{
		// Search for the {readmore} tag and split the text up accordingly.
		if (isset($array['articletext']))
		{
			$pattern = '#<hr\s+id=("|\')system-readmore("|\')\s*\/*>#i';
			$tagPos = preg_match($pattern, $array['articletext']);

			if ($tagPos == 0)
			{
				$this->introtext = $array['articletext'];
				$this->fulltext = '';
			}
			else
			{
				list ($this->introtext, $this->fulltext) = preg_split($pattern, $array['articletext'], 2);
			}
		}

		if (isset($array['attribs']) && is_array($array['attribs']))
		{
			$registry = new Registry($array['attribs']);
			$array['attribs'] = (string) $registry;
		}

		if (isset($array['metadata']) && is_array($array['metadata']))
		{
			$registry = new Registry($array['metadata']);
			$array['metadata'] = (string) $registry;
		}

		// Bind the rules.
		if (isset($array['rules']) && is_array($array['rules']))
		{
			$rules = new Rules($array['rules']);
			$this->setRules($rules);
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @see     Table::check()
	 * @since   1.5
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	public function check()
	{
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('COM_CONTENT_WARNING_PROVIDE_VALID_NAME'));

			return false;
		}

		if (trim($this->alias) == '')
		{
			$this->alias = $this->title;
		}

		$this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);

		if (trim(str_replace('-', '', $this->alias)) == '')
		{
			$this->alias = \JFactory::getDate()->format('Y-m-d-H-i-s');
		}

		if (trim(str_replace('&nbsp;', '', $this->fulltext)) == '')
		{
			$this->fulltext = '';
		}

		/**
		 * Ensure any new items have compulsory fields set. This is needed for things like
		 * frontend editing where we don't show all the fields or using some kind of API
		 */
		if (!$this->id)
		{
			// Images can be an empty json string
			if (!isset($this->images))
			{
				$this->images = '{}';
			}

			// URLs can be an empty json string
			if (!isset($this->urls))
			{
				$this->urls = '{}';
			}

			// Attributes (article params) can be an empty json string
			if (!isset($this->attribs))
			{
				$this->attribs = '{}';
			}

			// Metadata can be an empty json string
			if (!isset($this->metadata))
			{
				$this->metadata = '{}';
			}
		}

		// Check the publish down date is not earlier than publish up.
		if ($this->publish_down < $this->publish_up && $this->publish_down > $this->_db->getNullDate())
		{
			// Swap the dates.
			$temp = $this->publish_up;
			$this->publish_up = $this->publish_down;
			$this->publish_down = $temp;
		}

		// Clean up keywords -- eliminate extra spaces between phrases
		// and cr (\r) and lf (\n) characters from string
		if (!empty($this->metakey))
		{
			// Only process if not empty

			// Array of characters to remove
			$bad_characters = array("\n", "\r", "\"", '<', '>');

			// Remove bad characters
			$after_clean = StringHelper::str_ireplace($bad_characters, '', $this->metakey);

			// Create array using commas as delimiter
			$keys = explode(',', $after_clean);

			$clean_keys = array();

			foreach ($keys as $key)
			{
				if (trim($key))
				{
					// Ignore blank keywords
					$clean_keys[] = trim($key);
				}
			}

			// Put array back together delimited by ", "
			$this->metakey = implode(', ', $clean_keys);
		}

		return true;
	}

	/**
	 * Gets the default asset values for a component.
	 *
	 * @param   string  $component  The component asset name to search for
	 *
	 * @return  Rules  The Rules object for the asset
	 *
	 * @since   3.4
	 * @deprecated  3.4 Class will be removed upon completion of transition to UCM
	 */
	protected function getDefaultAssetValues($component)
	{
		// Need to find the asset id by the name of the component.
		$db = $this->getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('id'))
			->from($db->quoteName('#__assets'))
			->where($db->quoteName('name') . ' = ' . $db->quote($component));
		$db->setQuery($query);
		$assetId = (int) $db->loadResult();

		return Access::getAssetRules($assetId);
	}

	/**
	 * Overrides Table::store to set modified data and user id.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 * @deprecated  3.1.4 Class will be removed upon completion of transition to UCM
	 */
	public function store($updateNulls = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::getUser();

		$this->modified = $date->toSql();

		if ($this->id)
		{
			// Existing item
			$this->modified_by = $user->get('id');
		}
		else
		{
			// New article. An article created and created_by field can be set by the user,
			// so we don't touch either of these if they are set.
			if (!(int) $this->created)
			{
				$this->created = $date->toSql();
			}

			if (empty($this->created_by))
			{
				$this->created_by = $user->get('id');
			}
		}

		// Verify that the alias is unique
		$table = Table::getInstance('Content', 'JTable', array('dbo' => $this->getDbo()));

		if ($table->load(array('alias' => $this->alias, 'catid' => $this->catid)) && ($table->id != $this->id || $this->id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_ARTICLE_UNIQUE_ALIAS'));

			return false;
		}

		return parent::store($updateNulls);
	}
}
src/Table/Nested.php000064400000140536152177723700010341 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  1.7.0
 */
class Nested extends Table
{
	/**
	 * Object property holding the primary key of the parent node.  Provides adjacency list data for nodes.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $parent_id;

	/**
	 * Object property holding the depth level of the node in the tree.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $level;

	/**
	 * Object property holding the left value of the node for managing its placement in the nested sets tree.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $lft;

	/**
	 * Object property holding the right value of the node for managing its placement in the nested sets tree.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $rgt;

	/**
	 * Object property holding the alias of this node used to constuct the full text path, forward-slash delimited.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $alias;

	/**
	 * Object property to hold the location type to use when storing the row.
	 *
	 * @var    string
	 * @since  1.7.0
	 * @see    Nested::$_validLocations
	 */
	protected $_location;

	/**
	 * Object property to hold the primary key of the location reference node to use when storing the row.
	 *
	 * A combination of location type and reference node describes where to store the current node in the tree.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $_location_id;

	/**
	 * An array to cache values in recursive processes.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $_cache = array();

	/**
	 * Debug level
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $_debug = 0;

	/**
	 * Cache for the root ID
	 *
	 * @var    integer
	 * @since  3.3
	 */
	protected static $root_id = 0;

	/**
	 * Array declaring the valid location values for moving a node
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	private $_validLocations = array('before', 'after', 'first-child', 'last-child');

	/**
	 * Sets the debug level on or off
	 *
	 * @param   integer  $level  0 = off, 1 = on
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function debug($level)
	{
		$this->_debug = (int) $level;
	}

	/**
	 * Method to get an array of nodes from a given node to its root.
	 *
	 * @param   integer  $pk          Primary key of the node for which to get the path.
	 * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
	 *
	 * @return  mixed    An array of node objects including the start node.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error
	 */
	public function getPath($pk = null, $diagnostic = false)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Get the path from the node to the root.
		$select = ($diagnostic) ? 'p.' . $k . ', p.parent_id, p.level, p.lft, p.rgt' : 'p.*';
		$query = $this->_db->getQuery(true)
			->select($select)
			->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p')
			->where('n.lft BETWEEN p.lft AND p.rgt')
			->where('n.' . $k . ' = ' . (int) $pk)
			->order('p.lft');

		$this->_db->setQuery($query);

		return $this->_db->loadObjectList();
	}

	/**
	 * Method to get a node and all its child nodes.
	 *
	 * @param   integer  $pk          Primary key of the node for which to get the tree.
	 * @param   boolean  $diagnostic  Only select diagnostic data for the nested sets.
	 *
	 * @return  mixed    Boolean false on failure or array of node objects on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function getTree($pk = null, $diagnostic = false)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Get the node and children as a tree.
		$select = ($diagnostic) ? 'n.' . $k . ', n.parent_id, n.level, n.lft, n.rgt' : 'n.*';
		$query = $this->_db->getQuery(true)
			->select($select)
			->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p')
			->where('n.lft BETWEEN p.lft AND p.rgt')
			->where('p.' . $k . ' = ' . (int) $pk)
			->order('n.lft');

		return $this->_db->setQuery($query)->loadObjectList();
	}

	/**
	 * Method to determine if a node is a leaf node in the tree (has no children).
	 *
	 * @param   integer  $pk  Primary key of the node to check.
	 *
	 * @return  boolean  True if a leaf node, false if not or null if the node does not exist.
	 *
	 * @note    Since 3.0.0 this method returns null if the node does not exist.
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function isLeaf($pk = null)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;
		$node = $this->_getNode($pk);

		// Get the node by primary key.
		if (empty($node))
		{
			// Error message set in getNode method.
			return;
		}

		// The node is a leaf node.
		return ($node->rgt - $node->lft) == 1;
	}

	/**
	 * Method to set the location of a node in the tree object.  This method does not
	 * save the new location to the database, but will set it in the object so
	 * that when the node is stored it will be stored in the new location.
	 *
	 * @param   integer  $referenceId  The primary key of the node to reference new location by.
	 * @param   string   $position     Location type string.
	 *
	 * @return  void
	 *
	 * @note    Since 3.0.0 this method returns void and throws an \InvalidArgumentException when an invalid position is passed.
	 * @see     Nested::$_validLocations
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 */
	public function setLocation($referenceId, $position = 'after')
	{
		// Make sure the location is valid.
		if (!in_array($position, $this->_validLocations))
		{
			throw new \InvalidArgumentException(
				sprintf('Invalid location "%1$s" given, valid values are %2$s', $position, implode(', ', $this->_validLocations))
			);
		}

		// Set the location properties.
		$this->_location = $position;
		$this->_location_id = $referenceId;
	}

	/**
	 * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
	 * Negative numbers move the row up in the sequence and positive numbers move it down.
	 *
	 * @param   integer  $delta  The direction and magnitude to move the row in the ordering sequence.
	 * @param   string   $where  WHERE clause to use for limiting the selection of rows to compact the
	 *                           ordering values.
	 *
	 * @return  mixed    Boolean true on success.
	 *
	 * @since   1.7.0
	 */
	public function move($delta, $where = '')
	{
		$k = $this->_tbl_key;
		$pk = $this->$k;

		$query = $this->_db->getQuery(true)
			->select($k)
			->from($this->_tbl)
			->where('parent_id = ' . $this->parent_id);

		if ($where)
		{
			$query->where($where);
		}

		if ($delta > 0)
		{
			$query->where('rgt > ' . $this->rgt)
				->order('rgt ASC');
			$position = 'after';
		}
		else
		{
			$query->where('lft < ' . $this->lft)
				->order('lft DESC');
			$position = 'before';
		}

		$this->_db->setQuery($query);
		$referenceId = $this->_db->loadResult();

		if ($referenceId)
		{
			return $this->moveByReference($referenceId, $position, $pk);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to move a node and its children to a new location in the tree.
	 *
	 * @param   integer  $referenceId      The primary key of the node to reference new location by.
	 * @param   string   $position         Location type string. ['before', 'after', 'first-child', 'last-child']
	 * @param   integer  $pk               The primary key of the node to move.
	 * @param   boolean  $recursiveUpdate  Flag indicate that method recursiveUpdatePublishedColumn should be call.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function moveByReference($referenceId, $position = 'after', $pk = null, $recursiveUpdate = true)
	{
		if ($this->_debug)
		{
			echo "\nMoving ReferenceId:$referenceId, Position:$position, PK:$pk";
		}

		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Get the node by id.
		if (!$node = $this->_getNode($pk))
		{
			// Error message set in getNode method.
			return false;
		}

		// Get the ids of child nodes.
		$query = $this->_db->getQuery(true)
			->select($k)
			->from($this->_tbl)
			->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);

		$children = $this->_db->setQuery($query)->loadColumn();

		if ($this->_debug)
		{
			$this->_logtable(false);
		}

		// Cannot move the node to be a child of itself.
		if (in_array($referenceId, $children))
		{
			$this->setError(
				new \UnexpectedValueException(
					sprintf('%1$s::moveByReference() is trying to make record ID %2$d a child of itself.', get_class($this), $pk)
				)
			);

			return false;
		}

		// Lock the table for writing.
		if (!$this->_lock())
		{
			return false;
		}

		/*
		 * Move the sub-tree out of the nested sets by negating its left and right values.
		 */
		$query->clear()
			->update($this->_tbl)
			->set('lft = lft * (-1), rgt = rgt * (-1)')
			->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		/*
		 * Close the hole in the tree that was opened by removing the sub-tree from the nested sets.
		 */
		// Compress the left values.
		$query->clear()
			->update($this->_tbl)
			->set('lft = lft - ' . (int) $node->width)
			->where('lft > ' . (int) $node->rgt);
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		// Compress the right values.
		$query->clear()
			->update($this->_tbl)
			->set('rgt = rgt - ' . (int) $node->width)
			->where('rgt > ' . (int) $node->rgt);
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		// We are moving the tree relative to a reference node.
		if ($referenceId)
		{
			// Get the reference node by primary key.
			if (!$reference = $this->_getNode($referenceId))
			{
				// Error message set in getNode method.
				$this->_unlock();

				return false;
			}

			// Get the reposition data for shifting the tree and re-inserting the node.
			if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, $position))
			{
				// Error message set in getNode method.
				$this->_unlock();

				return false;
			}
		}
		// We are moving the tree to be the last child of the root node
		else
		{
			// Get the last root node as the reference node.
			$query->clear()
				->select($this->_tbl_key . ', parent_id, level, lft, rgt')
				->from($this->_tbl)
				->where('parent_id = 0')
				->order('lft DESC');
			$this->_db->setQuery($query, 0, 1);
			$reference = $this->_db->loadObject();

			if ($this->_debug)
			{
				$this->_logtable(false);
			}

			// Get the reposition data for re-inserting the node after the found root.
			if (!$repositionData = $this->_getTreeRepositionData($reference, $node->width, 'last-child'))
			{
				// Error message set in getNode method.
				$this->_unlock();

				return false;
			}
		}

		/*
		 * Create space in the nested sets at the new location for the moved sub-tree.
		 */

		// Shift left values.
		$query->clear()
			->update($this->_tbl)
			->set('lft = lft + ' . (int) $node->width)
			->where($repositionData->left_where);
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		// Shift right values.
		$query->clear()
			->update($this->_tbl)
			->set('rgt = rgt + ' . (int) $node->width)
			->where($repositionData->right_where);
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		/*
		 * Calculate the offset between where the node used to be in the tree and
		 * where it needs to be in the tree for left ids (also works for right ids).
		 */
		$offset = $repositionData->new_lft - $node->lft;
		$levelOffset = $repositionData->new_level - $node->level;

		// Move the nodes back into position in the tree using the calculated offsets.
		$query->clear()
			->update($this->_tbl)
			->set('rgt = ' . (int) $offset . ' - rgt')
			->set('lft = ' . (int) $offset . ' - lft')
			->set('level = level + ' . (int) $levelOffset)
			->where('lft < 0');
		$this->_db->setQuery($query);

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');

		// Set the correct parent id for the moved node if required.
		if ($node->parent_id != $repositionData->new_parent_id)
		{
			$query = $this->_db->getQuery(true)
				->update($this->_tbl);

			// Update the title and alias fields if they exist for the table.
			$fields = $this->getFields();

			if (property_exists($this, 'title') && $this->title !== null)
			{
				$query->set('title = ' . $this->_db->quote($this->title));
			}

			if (array_key_exists('alias', $fields)  && $this->alias !== null)
			{
				$query->set('alias = ' . $this->_db->quote($this->alias));
			}

			$query->set('parent_id = ' . (int) $repositionData->new_parent_id)
				->where($this->_tbl_key . ' = ' . (int) $node->$k);
			$this->_db->setQuery($query);

			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_MOVE_FAILED');
		}

		// Unlock the table for writing.
		$this->_unlock();

		if (property_exists($this, 'published') && $recursiveUpdate)
		{
			$this->recursiveUpdatePublishedColumn($node->$k);
		}

		// Set the object values.
		$this->parent_id = $repositionData->new_parent_id;
		$this->level = $repositionData->new_level;
		$this->lft = $repositionData->new_lft;
		$this->rgt = $repositionData->new_rgt;

		return true;
	}

	/**
	 * Method to delete a node and, optionally, its child nodes from the table.
	 *
	 * @param   integer  $pk        The primary key of the node to delete.
	 * @param   boolean  $children  True to delete child nodes, false to move them up a level.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function delete($pk = null, $children = true)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeDelete', array($pk));

		// Lock the table for writing.
		if (!$this->_lock())
		{
			// Error message set in lock method.
			return false;
		}

		// If tracking assets, remove the asset first.
		if ($this->_trackAssets)
		{
			$name = $this->_getAssetName();
			$asset = Table::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));

			// Lock the table for writing.
			if (!$asset->_lock())
			{
				// Error message set in lock method.
				return false;
			}

			if ($asset->loadByName($name))
			{
				// Delete the node in assets table.
				if (!$asset->delete(null, $children))
				{
					$this->setError($asset->getError());
					$asset->_unlock();

					return false;
				}

				$asset->_unlock();
			}
			else
			{
				$this->setError($asset->getError());
				$asset->_unlock();

				return false;
			}
		}

		// Get the node by id.
		$node = $this->_getNode($pk);

		if (empty($node))
		{
			// Error message set in getNode method.
			$this->_unlock();

			return false;
		}

		$query = $this->_db->getQuery(true);

		// Should we delete all children along with the node?
		if ($children)
		{
			// Delete the node and all of its children.
			$query->clear()
				->delete($this->_tbl)
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Compress the left values.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft - ' . (int) $node->width)
				->where('lft > ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Compress the right values.
			$query->clear()
				->update($this->_tbl)
				->set('rgt = rgt - ' . (int) $node->width)
				->where('rgt > ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
		}
		// Leave the children and move them up a level.
		else
		{
			// Delete the node.
			$query->clear()
				->delete($this->_tbl)
				->where('lft = ' . (int) $node->lft);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Shift all node's children up a level.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft - 1')
				->set('rgt = rgt - 1')
				->set('level = level - 1')
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Adjust all the parent values for direct children of the deleted node.
			$query->clear()
				->update($this->_tbl)
				->set('parent_id = ' . (int) $node->parent_id)
				->where('parent_id = ' . (int) $node->$k);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Shift all of the left values that are right of the node.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft - 2')
				->where('lft > ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');

			// Shift all of the right values that are right of the node.
			$query->clear()
				->update($this->_tbl)
				->set('rgt = rgt - 2')
				->where('rgt > ' . (int) $node->rgt);
			$this->_runQuery($query, 'JLIB_DATABASE_ERROR_DELETE_FAILED');
		}

		// Unlock the table for writing.
		$this->_unlock();

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterDelete', array($pk));

		return true;
	}

	/**
	 * Checks that the object is valid and able to be stored.
	 *
	 * This method checks that the parent_id is non-zero and exists in the database.
	 * Note that the root node (parent_id = 0) cannot be manipulated with this class.
	 *
	 * @return  boolean  True if all checks pass.
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		$this->parent_id = (int) $this->parent_id;

		// Set up a mini exception handler.
		try
		{
			// Check that the parent_id field is valid.
			if ($this->parent_id == 0)
			{
				throw new \UnexpectedValueException(sprintf('Invalid `parent_id` [%1$d] in %2$s::check()', $this->parent_id, get_class($this)));
			}

			$query = $this->_db->getQuery(true)
				->select('1')
				->from($this->_tbl)
				->where($this->_tbl_key . ' = ' . $this->parent_id);

			if (!$this->_db->setQuery($query)->loadResult())
			{
				throw new \UnexpectedValueException(sprintf('Invalid `parent_id` [%1$d] in %2$s::check()', $this->parent_id, get_class($this)));
			}
		}
		catch (\UnexpectedValueException $e)
		{
			// Validation error - record it and return false.
			$this->setError($e);

			return false;
		}

		return true;
	}

	/**
	 * Method to store a node in the database table.
	 *
	 * @param   boolean  $updateNulls  True to update null values as well.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function store($updateNulls = false)
	{
		$k = $this->_tbl_key;

		// Implement \JObservableInterface: Pre-processing by observers
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->update('onBeforeStore', array($updateNulls, $k));
		}

		if ($this->_debug)
		{
			echo "\n" . get_class($this) . "::store\n";
			$this->_logtable(true, false);
		}

		/*
		 * If the primary key is empty, then we assume we are inserting a new node into the
		 * tree.  From this point we would need to determine where in the tree to insert it.
		 */
		if (empty($this->$k))
		{
			/*
			 * We are inserting a node somewhere in the tree with a known reference
			 * node.  We have to make room for the new node and set the left and right
			 * values before we insert the row.
			 */
			if ($this->_location_id >= 0)
			{
				// Lock the table for writing.
				if (!$this->_lock())
				{
					// Error message set in lock method.
					return false;
				}

				// We are inserting a node relative to the last root node.
				if ($this->_location_id == 0)
				{
					// Get the last root node as the reference node.
					$query = $this->_db->getQuery(true)
						->select($this->_tbl_key . ', parent_id, level, lft, rgt')
						->from($this->_tbl)
						->where('parent_id = 0')
						->order('lft DESC');
					$this->_db->setQuery($query, 0, 1);
					$reference = $this->_db->loadObject();

					if ($this->_debug)
					{
						$this->_logtable(false);
					}
				}
				// We have a real node set as a location reference.
				else
				{
					// Get the reference node by primary key.
					if (!$reference = $this->_getNode($this->_location_id))
					{
						// Error message set in getNode method.
						$this->_unlock();

						return false;
					}
				}

				// Get the reposition data for shifting the tree and re-inserting the node.
				if (!($repositionData = $this->_getTreeRepositionData($reference, 2, $this->_location)))
				{
					// Error message set in getNode method.
					$this->_unlock();

					return false;
				}

				// Create space in the tree at the new location for the new node in left ids.
				$query = $this->_db->getQuery(true)
					->update($this->_tbl)
					->set('lft = lft + 2')
					->where($repositionData->left_where);
				$this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');

				// Create space in the tree at the new location for the new node in right ids.
				$query->clear()
					->update($this->_tbl)
					->set('rgt = rgt + 2')
					->where($repositionData->right_where);
				$this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');

				// Set the object values.
				$this->parent_id = $repositionData->new_parent_id;
				$this->level = $repositionData->new_level;
				$this->lft = $repositionData->new_lft;
				$this->rgt = $repositionData->new_rgt;
			}
			else
			{
				// Negative parent ids are invalid
				$e = new \UnexpectedValueException(sprintf('%s::store() used a negative _location_id', get_class($this)));
				$this->setError($e);

				return false;
			}
		}
		/*
		 * If we have a given primary key then we assume we are simply updating this
		 * node in the tree.  We should assess whether or not we are moving the node
		 * or just updating its data fields.
		 */
		else
		{
			// If the location has been set, move the node to its new location.
			if ($this->_location_id > 0)
			{
				// Skip recursiveUpdatePublishedColumn method, it will be called later.
				if (!$this->moveByReference($this->_location_id, $this->_location, $this->$k, false))
				{
					// Error message set in move method.
					return false;
				}
			}

			// Lock the table for writing.
			if (!$this->_lock())
			{
				// Error message set in lock method.
				return false;
			}
		}

		// Implement \JObservableInterface: We do not want parent::store to update observers,
		// since tables are locked and we are updating it from this level of store():

		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$oldCallObservers = $this->_observers->doCallObservers(false);
		}

		$result = parent::store($updateNulls);

		// Implement \JObservableInterface: Restore previous callable observers state:
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->doCallObservers($oldCallObservers);
		}

		if ($result)
		{
			if ($this->_debug)
			{
				$this->_logtable();
			}
		}

		// Unlock the table for writing.
		$this->_unlock();

		if (property_exists($this, 'published'))
		{
			$this->recursiveUpdatePublishedColumn($this->$k);
		}

		// Implement \JObservableInterface: Post-processing by observers
		// 2.5 upgrade issue - check if property_exists before executing
		if (property_exists($this, '_observers'))
		{
			$this->_observers->update('onAfterStore', array(&$result));
		}

		return $result;
	}

	/**
	 * Method to set the publishing state for a node or list of nodes in the database
	 * table.  The method respects rows checked out by other users and will attempt
	 * to checkin rows that it can after adjustments are made. The method will not
	 * allow you to set a publishing state higher than any ancestor node and will
	 * not allow you to set a publishing state on a node with a checked out child.
	 *
	 * @param   mixed    $pks     An optional array of primary key values to update.  If not
	 *                            set the instance property value is used.
	 * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
	 * @param   integer  $userId  The user id of the user performing the operation.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		$k = $this->_tbl_key;

		$query     = $this->_db->getQuery(true);
		$table     = $this->_db->quoteName($this->_tbl);
		$published = $this->_db->quoteName($this->getColumnAlias('published'));
		$key       = $this->_db->quoteName($k);

		// Sanitize input.
		$pks    = ArrayHelper::toInteger($pks);
		$userId = (int) $userId;
		$state  = (int) $state;

		// If $state > 1, then we allow state changes even if an ancestor has lower state
		// (for example, can change a child state to Archived (2) if an ancestor is Published (1)
		$compareState = ($state > 1) ? 1 : $state;

		// If there are no primary keys set check to see if the instance key is set.
		if (empty($pks))
		{
			if ($this->$k)
			{
				$pks = explode(',', $this->$k);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$e = new \UnexpectedValueException(sprintf('%s::publish(%s, %d, %d) empty.', get_class($this), $pks[0], $state, $userId));
				$this->setError($e);

				return false;
			}
		}

		// Determine if there is checkout support for the table.
		$checkoutSupport = (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'));

		// Iterate over the primary keys to execute the publish action if possible.
		foreach ($pks as $pk)
		{
			// Get the node by primary key.
			if (!$node = $this->_getNode($pk))
			{
				// Error message set in getNode method.
				return false;
			}

			// If the table has checkout support, verify no children are checked out.
			if ($checkoutSupport)
			{
				// Ensure that children are not checked out.
				$query->clear()
					->select('COUNT(' . $k . ')')
					->from($this->_tbl)
					->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt)
					->where('(checked_out <> 0 AND checked_out <> ' . (int) $userId . ')');
				$this->_db->setQuery($query);

				// Check for checked out children.
				if ($this->_db->loadResult())
				{
					// TODO Convert to a conflict exception when available.
					$e = new \RuntimeException(sprintf('%s::publish(%s, %d, %d) checked-out conflict.', get_class($this), $pks[0], $state, $userId));

					$this->setError($e);

					return false;
				}
			}

			// If any parent nodes have lower published state values, we cannot continue.
			if ($node->parent_id)
			{
				// Get any ancestor nodes that have a lower publishing state.
				$query->clear()
					->select('1')
					->from($table)
					->where('lft < ' . (int) $node->lft)
					->where('rgt > ' . (int) $node->rgt)
					->where('parent_id > 0')
					->where($published . ' < ' . (int) $compareState);

				// Just fetch one row (one is one too many).
				$this->_db->setQuery($query, 0, 1);

				if ($this->_db->loadResult())
				{
					$e = new \UnexpectedValueException(
						sprintf('%s::publish(%s, %d, %d) ancestors have lower state.', get_class($this), $pks[0], $state, $userId)
					);
					$this->setError($e);

					return false;
				}
			}

			$this->recursiveUpdatePublishedColumn($pk, $state);

			// If checkout support exists for the object, check the row in.
			if ($checkoutSupport)
			{
				$this->checkin($pk);
			}
		}

		// If the Table instance value is in the list of primary keys that were set, set the instance.
		if (in_array($this->$k, $pks))
		{
			$this->published = $state;
		}

		$this->setError('');

		return true;
	}

	/**
	 * Method to move a node one position to the left in the same level.
	 *
	 * @param   integer  $pk  Primary key of the node to move.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function orderUp($pk)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Lock the table for writing.
		if (!$this->_lock())
		{
			// Error message set in lock method.
			return false;
		}

		// Get the node by primary key.
		$node = $this->_getNode($pk);

		if (empty($node))
		{
			// Error message set in getNode method.
			$this->_unlock();

			return false;
		}

		// Get the left sibling node.
		$sibling = $this->_getNode($node->lft - 1, 'right');

		if (empty($sibling))
		{
			// Error message set in getNode method.
			$this->_unlock();

			return false;
		}

		try
		{
			// Get the primary keys of child nodes.
			$query = $this->_db->getQuery(true)
				->select($this->_tbl_key)
				->from($this->_tbl)
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);

			$children = $this->_db->setQuery($query)->loadColumn();

			// Shift left and right values for the node and its children.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft - ' . (int) $sibling->width)
				->set('rgt = rgt - ' . (int) $sibling->width)
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
			$this->_db->setQuery($query)->execute();

			// Shift left and right values for the sibling and its children.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft + ' . (int) $node->width)
				->set('rgt = rgt + ' . (int) $node->width)
				->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt)
				->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')');
			$this->_db->setQuery($query)->execute();
		}
		catch (\RuntimeException $e)
		{
			$this->_unlock();
			throw $e;
		}

		// Unlock the table for writing.
		$this->_unlock();

		return true;
	}

	/**
	 * Method to move a node one position to the right in the same level.
	 *
	 * @param   integer  $pk  Primary key of the node to move.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function orderDown($pk)
	{
		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Lock the table for writing.
		if (!$this->_lock())
		{
			// Error message set in lock method.
			return false;
		}

		// Get the node by primary key.
		$node = $this->_getNode($pk);

		if (empty($node))
		{
			// Error message set in getNode method.
			$this->_unlock();

			return false;
		}

		$query = $this->_db->getQuery(true);

		// Get the right sibling node.
		$sibling = $this->_getNode($node->rgt + 1, 'left');

		if (empty($sibling))
		{
			// Error message set in getNode method.
			$query->_unlock($this->_db);
			$this->_locked = false;

			return false;
		}

		try
		{
			// Get the primary keys of child nodes.
			$query->clear()
				->select($this->_tbl_key)
				->from($this->_tbl)
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
			$this->_db->setQuery($query);
			$children = $this->_db->loadColumn();

			// Shift left and right values for the node and its children.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft + ' . (int) $sibling->width)
				->set('rgt = rgt + ' . (int) $sibling->width)
				->where('lft BETWEEN ' . (int) $node->lft . ' AND ' . (int) $node->rgt);
			$this->_db->setQuery($query)->execute();

			// Shift left and right values for the sibling and its children.
			$query->clear()
				->update($this->_tbl)
				->set('lft = lft - ' . (int) $node->width)
				->set('rgt = rgt - ' . (int) $node->width)
				->where('lft BETWEEN ' . (int) $sibling->lft . ' AND ' . (int) $sibling->rgt)
				->where($this->_tbl_key . ' NOT IN (' . implode(',', $children) . ')');
			$this->_db->setQuery($query)->execute();
		}
		catch (\RuntimeException $e)
		{
			$this->_unlock();
			throw $e;
		}

		// Unlock the table for writing.
		$this->_unlock();

		return true;
	}

	/**
	 * Gets the ID of the root item in the tree
	 *
	 * @return  mixed  The primary id of the root row, or false if not found and the internal error is set.
	 *
	 * @since   1.7.0
	 */
	public function getRootId()
	{
		if ((int) self::$root_id > 0)
		{
			return self::$root_id;
		}

		// Get the root item.
		$k = $this->_tbl_key;

		// Test for a unique record with parent_id = 0
		$query = $this->_db->getQuery(true)
			->select($k)
			->from($this->_tbl)
			->where('parent_id = 0');

		$result = $this->_db->setQuery($query)->loadColumn();

		if (count($result) == 1)
		{
			self::$root_id = $result[0];

			return self::$root_id;
		}

		// Test for a unique record with lft = 0
		$query->clear()
			->select($k)
			->from($this->_tbl)
			->where('lft = 0');

		$result = $this->_db->setQuery($query)->loadColumn();

		if (count($result) == 1)
		{
			self::$root_id = $result[0];

			return self::$root_id;
		}

		$fields = $this->getFields();

		if (array_key_exists('alias', $fields))
		{
			// Test for a unique record alias = root
			$query->clear()
				->select($k)
				->from($this->_tbl)
				->where('alias = ' . $this->_db->quote('root'));

			$result = $this->_db->setQuery($query)->loadColumn();

			if (count($result) == 1)
			{
				self::$root_id = $result[0];

				return self::$root_id;
			}
		}

		$e = new \UnexpectedValueException(sprintf('%s::getRootId', get_class($this)));
		$this->setError($e);
		self::$root_id = false;

		return false;
	}

	/**
	 * Method to recursively rebuild the whole nested set tree.
	 *
	 * @param   integer  $parentId  The root of the tree to rebuild.
	 * @param   integer  $leftId    The left id to start with in building the tree.
	 * @param   integer  $level     The level to assign to the current nodes.
	 * @param   string   $path      The path to the current nodes.
	 *
	 * @return  integer  1 + value of root rgt on success, false on failure
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = '')
	{
		// If no parent is provided, try to find it.
		if ($parentId === null)
		{
			// Get the root item.
			$parentId = $this->getRootId();

			if ($parentId === false)
			{
				return false;
			}
		}

		$query = $this->_db->getQuery(true);

		// Build the structure of the recursive query.
		if (!isset($this->_cache['rebuild.sql']))
		{
			$query->clear()
				->select($this->_tbl_key . ', alias')
				->from($this->_tbl)
				->where('parent_id = %d');

			// If the table has an ordering field, use that for ordering.
			$orderingField = $this->getColumnAlias('ordering');

			if (property_exists($this, $orderingField))
			{
				$query->order('parent_id, ' . $this->_db->quoteName($orderingField) . ', lft');
			}
			else
			{
				$query->order('parent_id, lft');
			}

			$this->_cache['rebuild.sql'] = (string) $query;
		}

		// Make a shortcut to database object.

		// Assemble the query to find all children of this node.
		$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId));

		$children = $this->_db->loadObjectList();

		// The right value of this node is the left value + 1
		$rightId = $leftId + 1;

		// Execute this function recursively over all children
		foreach ($children as $node)
		{
			/*
			 * $rightId is the current right value, which is incremented on recursion return.
			 * Increment the level for the children.
			 * Add this item's alias to the path (but avoid a leading /)
			 */
			$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1, $path . (empty($path) ? '' : '/') . $node->alias);

			// If there is an update failure, return false to break out of the recursion.
			if ($rightId === false)
			{
				return false;
			}
		}

		// We've got the left value, and now that we've processed
		// the children of this node we also know the right value.
		$query->clear()
			->update($this->_tbl)
			->set('lft = ' . (int) $leftId)
			->set('rgt = ' . (int) $rightId)
			->set('level = ' . (int) $level)
			->set('path = ' . $this->_db->quote($path))
			->where($this->_tbl_key . ' = ' . (int) $parentId);
		$this->_db->setQuery($query)->execute();

		// Return the right value of this node + 1.
		return $rightId + 1;
	}

	/**
	 * Method to rebuild the node's path field from the alias values of the nodes from the current node to the root node of the tree.
	 *
	 * @param   integer  $pk  Primary key of the node for which to get the path.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function rebuildPath($pk = null)
	{
		$fields = $this->getFields();

		// If there is no alias or path field, just return true.
		if (!array_key_exists('alias', $fields) || !array_key_exists('path', $fields))
		{
			return true;
		}

		$k = $this->_tbl_key;
		$pk = (is_null($pk)) ? $this->$k : $pk;

		// Get the aliases for the path from the node to the root node.
		$query = $this->_db->getQuery(true)
			->select('p.alias')
			->from($this->_tbl . ' AS n, ' . $this->_tbl . ' AS p')
			->where('n.lft BETWEEN p.lft AND p.rgt')
			->where('n.' . $this->_tbl_key . ' = ' . (int) $pk)
			->order('p.lft');
		$this->_db->setQuery($query);

		$segments = $this->_db->loadColumn();

		// Make sure to remove the root path if it exists in the list.
		if ($segments[0] == 'root')
		{
			array_shift($segments);
		}

		// Build the path.
		$path = trim(implode('/', $segments), ' /\\');

		// Update the path field for the node.
		$query->clear()
			->update($this->_tbl)
			->set('path = ' . $this->_db->quote($path))
			->where($this->_tbl_key . ' = ' . (int) $pk);

		$this->_db->setQuery($query)->execute();

		// Update the current record's path to the new one:
		$this->path = $path;

		return true;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties (except $_errors).
	 *
	 * @return  void
	 *
	 * @since   3.2.1
	 */
	public function reset()
	{
		parent::reset();

		// Reset the location properties.
		$this->setLocation(0);
	}

	/**
	 * Method to update order of table rows
	 *
	 * @param   array  $idArray    id numbers of rows to be reordered.
	 * @param   array  $lft_array  lft values of rows to be reordered.
	 *
	 * @return  integer  1 + value of root rgt on success, false on failure.
	 *
	 * @since   1.7.0
	 * @throws  \Exception on database error.
	 */
	public function saveorder($idArray = null, $lft_array = null)
	{
		try
		{
			$query = $this->_db->getQuery(true);

			// Validate arguments
			if (is_array($idArray) && is_array($lft_array) && count($idArray) == count($lft_array))
			{
				for ($i = 0, $count = count($idArray); $i < $count; $i++)
				{
					// Do an update to change the lft values in the table for each id
					$query->clear()
						->update($this->_tbl)
						->where($this->_tbl_key . ' = ' . (int) $idArray[$i])
						->set('lft = ' . (int) $lft_array[$i]);

					$this->_db->setQuery($query)->execute();

					if ($this->_debug)
					{
						$this->_logtable();
					}
				}

				return $this->rebuild();
			}
			else
			{
				return false;
			}
		}
		catch (\Exception $e)
		{
			$this->_unlock();
			throw $e;
		}
	}

	/**
	 * Method to recursive update published column for children rows.
	 *
	 * @param   integer  $pk        Id number of row which published column was changed.
	 * @param   integer  $newState  An optional value for published column of row identified by $pk.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.7.0
	 * @throws  \RuntimeException on database error.
	 */
	protected function recursiveUpdatePublishedColumn($pk, $newState = null)
	{
		$query     = $this->_db->getQuery(true);
		$table     = $this->_db->quoteName($this->_tbl);
		$key       = $this->_db->quoteName($this->_tbl_key);
		$published = $this->_db->quoteName($this->getColumnAlias('published'));

		if ($newState !== null)
		{
			// Use a new published state in changed row.
			$newState = "(CASE WHEN p2.$key = " . (int) $pk . " THEN " . (int) $newState . " ELSE p2.$published END)";
		}
		else
		{
			$newState = "p2.$published";
		}

		/**
		 * We have to calculate the correct value for c2.published
		 * based on p2.published and own c2.published column,
		 * where (p2) is parent category is and (c2) current category
		 *
		 * p2.published <= c2.published AND p2.published > 0 THEN c2.published
		 *            2 <=  2 THEN  2 (If archived in archived then archived)
		 *            1 <=  2 THEN  2 (If archived in published then archived)
		 *            1 <=  1 THEN  1 (If published in published then published)
		 *
		 * p2.published >  c2.published AND c2.published > 0 THEN p2.published
		 *            2 >   1 THEN  2 (If published in archived then archived)
		 *
		 * p2.published >  c2.published THEN c2.published ELSE p2.published
		 *            2 >  -2 THEN -2 (If trashed in archived then trashed)
		 *            2 >   0 THEN  0 (If unpublished in archived then unpublished)
		 *            1 >   0 THEN  0 (If unpublished in published then unpublished)
		 *            0 >  -2 THEN -2 (If trashed in unpublished then trashed)
		 * ELSE
		 *            0 <=  2 THEN  0 (If archived in unpublished then unpublished)
		 *            0 <=  1 THEN  0 (If published in unpublished then unpublished)
		 *            0 <=  0 THEN  0 (If unpublished in unpublished then unpublished)
		 *           -2 <= -2 THEN -2 (If trashed in trashed then trashed)
		 *           -2 <=  0 THEN -2 (If unpublished in trashed then trashed)
		 *           -2 <=  1 THEN -2 (If published in trashed then trashed)
		 *           -2 <=  2 THEN -2 (If archived in trashed then trashed)
		 */

		// Find node and all children keys
		$query->select("c.$key")
			->from("$table AS node")
			->leftJoin("$table AS c ON node.lft <= c.lft AND c.rgt <= node.rgt")
			->where("node.$key = " . (int) $pk);

		$pks = $this->_db->setQuery($query)->loadColumn();

		// Prepare a list of correct published states.
		$subquery = (string) $query->clear()
			->select("c2.$key AS newId")
			->select("CASE WHEN MIN($newState) > 0 THEN MAX($newState) ELSE MIN($newState) END AS newPublished")
			->from("$table AS c2")
			->innerJoin("$table AS p2 ON p2.lft <= c2.lft AND c2.rgt <= p2.rgt")
			->where("c2.$key IN (" . implode(',', $pks) . ")")
			->group("c2.$key");

		// Update and cascade the publishing state.
		$query->clear()
			->update("$table AS c")
			->innerJoin("($subquery) AS c2 ON c2.newId = c.$key")
			->set("$published = c2.newPublished")
			->where("c.$key IN (" . implode(',', $pks) . ")");

		$this->_runQuery($query, 'JLIB_DATABASE_ERROR_STORE_FAILED');

		return true;
	}

	/**
	 * Method to get nested set properties for a node in the tree.
	 *
	 * @param   integer  $id   Value to look up the node by.
	 * @param   string   $key  An optional key to look up the node by (parent | left | right).
	 *                         If omitted, the primary key of the table is used.
	 *
	 * @return  mixed    Boolean false on failure or node object on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException on database error.
	 */
	protected function _getNode($id, $key = null)
	{
		// Determine which key to get the node base on.
		switch ($key)
		{
			case 'parent':
				$k = 'parent_id';
				break;

			case 'left':
				$k = 'lft';
				break;

			case 'right':
				$k = 'rgt';
				break;

			default:
				$k = $this->_tbl_key;
				break;
		}

		// Get the node data.
		$query = $this->_db->getQuery(true)
			->select($this->_tbl_key . ', parent_id, level, lft, rgt')
			->from($this->_tbl)
			->where($k . ' = ' . (int) $id);

		$row = $this->_db->setQuery($query, 0, 1)->loadObject();

		// Check for no $row returned
		if (empty($row))
		{
			$e = new \UnexpectedValueException(sprintf('%s::_getNode(%d, %s) failed.', get_class($this), $id, $key));
			$this->setError($e);

			return false;
		}

		// Do some simple calculations.
		$row->numChildren = (int) ($row->rgt - $row->lft - 1) / 2;
		$row->width = (int) $row->rgt - $row->lft + 1;

		return $row;
	}

	/**
	 * Method to get various data necessary to make room in the tree at a location
	 * for a node and its children.  The returned data object includes conditions
	 * for SQL WHERE clauses for updating left and right id values to make room for
	 * the node as well as the new left and right ids for the node.
	 *
	 * @param   object   $referenceNode  A node object with at least a 'lft' and 'rgt' with
	 *                                   which to make room in the tree around for a new node.
	 * @param   integer  $nodeWidth      The width of the node for which to make room in the tree.
	 * @param   string   $position       The position relative to the reference node where the room
	 *                                   should be made.
	 *
	 * @return  mixed    Boolean false on failure or data object on success.
	 *
	 * @since   1.7.0
	 */
	protected function _getTreeRepositionData($referenceNode, $nodeWidth, $position = 'before')
	{
		// Make sure the reference an object with a left and right id.
		if (!is_object($referenceNode) || !(isset($referenceNode->lft) && isset($referenceNode->rgt)))
		{
			return false;
		}

		// A valid node cannot have a width less than 2.
		if ($nodeWidth < 2)
		{
			return false;
		}

		$k = $this->_tbl_key;
		$data = new \stdClass;

		// Run the calculations and build the data object by reference position.
		switch ($position)
		{
			case 'first-child':
				$data->left_where = 'lft > ' . $referenceNode->lft;
				$data->right_where = 'rgt >= ' . $referenceNode->lft;

				$data->new_lft = $referenceNode->lft + 1;
				$data->new_rgt = $referenceNode->lft + $nodeWidth;
				$data->new_parent_id = $referenceNode->$k;
				$data->new_level = $referenceNode->level + 1;
				break;

			case 'last-child':
				$data->left_where = 'lft > ' . ($referenceNode->rgt);
				$data->right_where = 'rgt >= ' . ($referenceNode->rgt);

				$data->new_lft = $referenceNode->rgt;
				$data->new_rgt = $referenceNode->rgt + $nodeWidth - 1;
				$data->new_parent_id = $referenceNode->$k;
				$data->new_level = $referenceNode->level + 1;
				break;

			case 'before':
				$data->left_where = 'lft >= ' . $referenceNode->lft;
				$data->right_where = 'rgt >= ' . $referenceNode->lft;

				$data->new_lft = $referenceNode->lft;
				$data->new_rgt = $referenceNode->lft + $nodeWidth - 1;
				$data->new_parent_id = $referenceNode->parent_id;
				$data->new_level = $referenceNode->level;
				break;

			default:
			case 'after':
				$data->left_where = 'lft > ' . $referenceNode->rgt;
				$data->right_where = 'rgt > ' . $referenceNode->rgt;

				$data->new_lft = $referenceNode->rgt + 1;
				$data->new_rgt = $referenceNode->rgt + $nodeWidth;
				$data->new_parent_id = $referenceNode->parent_id;
				$data->new_level = $referenceNode->level;
				break;
		}

		if ($this->_debug)
		{
			echo "\nRepositioning Data for $position" . "\n-----------------------------------" . "\nLeft Where:    $data->left_where"
				. "\nRight Where:   $data->right_where" . "\nNew Lft:       $data->new_lft" . "\nNew Rgt:       $data->new_rgt"
				. "\nNew Parent ID: $data->new_parent_id" . "\nNew Level:     $data->new_level" . "\n";
		}

		return $data;
	}

	/**
	 * Method to create a log table in the buffer optionally showing the query and/or data.
	 *
	 * @param   boolean  $showData   True to show data
	 * @param   boolean  $showQuery  True to show query
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.7.0
	 */
	protected function _logtable($showData = true, $showQuery = true)
	{
		$sep = "\n" . str_pad('', 40, '-');
		$buffer = '';

		if ($showQuery)
		{
			$buffer .= "\n" . htmlspecialchars($this->_db->getQuery(), ENT_QUOTES, 'UTF-8') . $sep;
		}

		if ($showData)
		{
			$query = $this->_db->getQuery(true)
				->select($this->_tbl_key . ', parent_id, lft, rgt, level')
				->from($this->_tbl)
				->order($this->_tbl_key);
			$this->_db->setQuery($query);

			$rows = $this->_db->loadRowList();
			$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $this->_tbl_key, 'par', 'lft', 'rgt');
			$buffer .= $sep;

			foreach ($rows as $row)
			{
				$buffer .= sprintf("\n| %4s | %4s | %4s | %4s |", $row[0], $row[1], $row[2], $row[3]);
			}

			$buffer .= $sep;
		}

		echo $buffer;
	}

	/**
	 * Runs a query and unlocks the database on an error.
	 *
	 * @param   mixed   $query         A string or \JDatabaseQuery object.
	 * @param   string  $errorMessage  Unused.
	 *
	 * @return  boolean  void
	 *
	 * @note    Since 3.0.0 this method returns void and will rethrow the database exception.
	 * @since   1.7.0
	 * @throws  \Exception on database error.
	 */
	protected function _runQuery($query, $errorMessage)
	{
		// Prepare to catch an exception.
		try
		{
			$this->_db->setQuery($query)->execute();

			if ($this->_debug)
			{
				$this->_logtable();
			}
		}
		catch (\Exception $e)
		{
			// Unlock the tables and rethrow.
			$this->_unlock();

			throw $e;
		}
	}
}
src/Table/MenuType.php000064400000017244152177723700010664 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;

/**
 * Menu Types table
 *
 * @since  1.6
 */
class MenuType extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.6
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__menu_types', 'id', $db);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @see     Table::check()
	 * @since   1.6
	 */
	public function check()
	{
		$this->menutype = ApplicationHelper::stringURLSafe($this->menutype);

		if (empty($this->menutype))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MENUTYPE_EMPTY'));

			return false;
		}

		// Sanitise data.
		if (trim($this->title) === '')
		{
			$this->title = $this->menutype;
		}

		// Check for unique menutype.
		$query = $this->_db->getQuery(true)
			->select('COUNT(id)')
			->from($this->_db->quoteName('#__menu_types'))
			->where($this->_db->quoteName('menutype') . ' = ' . $this->_db->quote($this->menutype))
			->where($this->_db->quoteName('id') . ' <> ' . (int) $this->id);
		$this->_db->setQuery($query);

		if ($this->_db->loadResult())
		{
			$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_MENUTYPE_EXISTS', $this->menutype));

			return false;
		}

		return true;
	}

	/**
	 * Method to store a row in the database from the Table instance properties.
	 *
	 * If a primary key value is set the row with that primary key value will be updated with the instance property values.
	 * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 */
	public function store($updateNulls = false)
	{
		if ($this->id)
		{
			// Get the user id
			$userId = \JFactory::getUser()->id;

			// Get the old value of the table
			$table = Table::getInstance('Menutype', 'JTable', array('dbo' => $this->getDbo()));
			$table->load($this->id);

			// Verify that no items are checked out
			$query = $this->_db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype=' . $this->_db->quote($table->menutype))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(
					\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
				);

				return false;
			}

			// Verify that no module for this menu are checked out
			$query->clear()
				->select('id')
				->from('#__modules')
				->where('module=' . $this->_db->quote('mod_menu'))
				->where('params LIKE ' . $this->_db->quote('%"menutype":' . json_encode($table->menutype) . '%'))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(
					\JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE_CHECKOUT'))
				);

				return false;
			}

			// Update the menu items
			$query->clear()
				->update('#__menu')
				->set('menutype=' . $this->_db->quote($this->menutype))
				->where('menutype=' . $this->_db->quote($table->menutype));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the module items
			$query->clear()
				->update('#__modules')
				->set(
				'params=REPLACE(params,' . $this->_db->quote('"menutype":' . json_encode($table->menutype)) . ',' .
				$this->_db->quote('"menutype":' . json_encode($this->menutype)) . ')'
			);
			$query->where('module=' . $this->_db->quote('mod_menu'))
				->where('params LIKE ' . $this->_db->quote('%"menutype":' . json_encode($table->menutype) . '%'));
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		return parent::store($updateNulls);
	}

	/**
	 * Method to delete a row from the database table by primary key value.
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 */
	public function delete($pk = null)
	{
		$k = $this->_tbl_key;
		$pk = $pk === null ? $this->$k : $pk;

		// If no primary key is given, return false.
		if ($pk !== null)
		{
			// Get the user id
			$userId = \JFactory::getUser()->id;

			// Get the old value of the table
			$table = Table::getInstance('Menutype', 'JTable', array('dbo' => $this->getDbo()));
			$table->load($pk);

			// Verify that no items are checked out
			$query = $this->_db->getQuery(true)
				->select('id')
				->from('#__menu')
				->where('menutype=' . $this->_db->quote($table->menutype))
				->where('(checked_out NOT IN (0,' . (int) $userId . ') OR home=1 AND language=' . $this->_db->quote('*') . ')');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED', get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE')));

				return false;
			}

			// Verify that no module for this menu are checked out
			$query->clear()
				->select('id')
				->from('#__modules')
				->where('module=' . $this->_db->quote('mod_menu'))
				->where('params LIKE ' . $this->_db->quote('%"menutype":' . json_encode($table->menutype) . '%'))
				->where('checked_out !=' . (int) $userId)
				->where('checked_out !=0');
			$this->_db->setQuery($query);

			if ($this->_db->loadRowList())
			{
				$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_DELETE_FAILED', get_class($this), \JText::_('JLIB_DATABASE_ERROR_MENUTYPE')));

				return false;
			}

			// Delete the menu items
			$query->clear()
				->delete('#__menu')
				->where('menutype=' . $this->_db->quote($table->menutype));
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the module items
			$query->clear()
				->delete('#__modules')
				->where('module=' . $this->_db->quote('mod_menu'))
				->where('params LIKE ' . $this->_db->quote('%"menutype":' . json_encode($table->menutype) . '%'));
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		return parent::delete($pk);
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   3.6
	 */
	protected function _getAssetName()
	{
		return 'com_menus.menu.' . $this->id;
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return  string
	 *
	 * @since   3.6
	 */
	protected function _getAssetTitle()
	{
		return $this->title;
	}

	/**
	 * Method to get the parent asset under which to register this one.
	 * By default, all assets are registered to the ROOT node with ID,
	 * which will default to 1 if none exists.
	 * The extended class can define a table and id to lookup.  If the
	 * asset does not exist it will be created.
	 *
	 * @param   Table    $table  A Table object for the asset parent.
	 * @param   integer  $id     Id to look up
	 *
	 * @return  integer
	 *
	 * @since   3.6
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;
		$asset = Table::getInstance('asset');

		if ($asset->loadByName('com_menus'))
		{
			$assetId = $asset->id;
		}

		return $assetId === null ? parent::_getAssetParentId($table, $id) : $assetId;
	}
}
src/Table/Category.php000064400000013433152177723700010667 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Rules;
use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Table\Observer\ContentHistory;
use Joomla\CMS\Table\Observer\Tags;
use Joomla\Registry\Registry;

/**
 * Category table
 *
 * @since  1.5
 */
class Category extends Nested
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__categories', 'id', $db);

		Tags::createObserver($this, array('typeAlias' => '{extension}.category'));
		ContentHistory::createObserver($this, array('typeAlias' => '{extension}.category'));

		$this->access = (int) \JFactory::getConfig()->get('access');
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 */
	protected function _getAssetName()
	{
		$k = $this->_tbl_key;

		return $this->extension . '.category.' . (int) $this->$k;
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 */
	protected function _getAssetTitle()
	{
		return $this->title;
	}

	/**
	 * Get the parent asset id for the record
	 *
	 * @param   Table    $table  A JTable object for the asset parent.
	 * @param   integer  $id     The id for the asset
	 *
	 * @return  integer  The id of the asset's parent
	 *
	 * @since   1.6
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;

		// This is a category under a category.
		if ($this->parent_id > 1)
		{
			// Build the query to get the asset id for the parent category.
			$query = $this->_db->getQuery(true)
				->select($this->_db->quoteName('asset_id'))
				->from($this->_db->quoteName('#__categories'))
				->where($this->_db->quoteName('id') . ' = ' . $this->parent_id);

			// Get the asset id from the database.
			$this->_db->setQuery($query);

			if ($result = $this->_db->loadResult())
			{
				$assetId = (int) $result;
			}
		}
		// This is a category that needs to parent with the extension.
		elseif ($assetId === null)
		{
			// Build the query to get the asset id for the parent category.
			$query = $this->_db->getQuery(true)
				->select($this->_db->quoteName('id'))
				->from($this->_db->quoteName('#__assets'))
				->where($this->_db->quoteName('name') . ' = ' . $this->_db->quote($this->extension));

			// Get the asset id from the database.
			$this->_db->setQuery($query);

			if ($result = $this->_db->loadResult())
			{
				$assetId = (int) $result;
			}
		}

		// Return the asset id.
		if ($assetId)
		{
			return $assetId;
		}
		else
		{
			return parent::_getAssetParentId($table, $id);
		}
	}

	/**
	 * Override check function
	 *
	 * @return  boolean
	 *
	 * @see     Table::check()
	 * @since   1.5
	 */
	public function check()
	{
		// Check for a title.
		if (trim($this->title) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_CATEGORY'));

			return false;
		}

		$this->alias = trim($this->alias);

		if (empty($this->alias))
		{
			$this->alias = $this->title;
		}

		$this->alias = ApplicationHelper::stringURLSafe($this->alias, $this->language);

		if (trim(str_replace('-', '', $this->alias)) == '')
		{
			$this->alias = \JFactory::getDate()->format('Y-m-d-H-i-s');
		}

		return true;
	}

	/**
	 * Overloaded bind function.
	 *
	 * @param   array   $array   named array
	 * @param   string  $ignore  An optional array or space separated list of properties
	 *                           to ignore while binding.
	 *
	 * @return  mixed   Null if operation was satisfactory, otherwise returns an error
	 *
	 * @see     Table::bind()
	 * @since   1.6
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		if (isset($array['metadata']) && is_array($array['metadata']))
		{
			$registry = new Registry($array['metadata']);
			$array['metadata'] = (string) $registry;
		}

		// Bind the rules.
		if (isset($array['rules']) && is_array($array['rules']))
		{
			$rules = new Rules($array['rules']);
			$this->setRules($rules);
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Overridden Table::store to set created/modified and user id.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 */
	public function store($updateNulls = false)
	{
		$date = \JFactory::getDate();
		$user = \JFactory::getUser();

		$this->modified_time = $date->toSql();

		if ($this->id)
		{
			// Existing category
			$this->modified_user_id = $user->get('id');
		}
		else
		{
			// New category. A category created_time and created_user_id field can be set by the user,
			// so we don't touch either of these if they are set.
			if (!(int) $this->created_time)
			{
				$this->created_time = $date->toSql();
			}

			if (empty($this->created_user_id))
			{
				$this->created_user_id = $user->get('id');
			}
		}

		// Verify that the alias is unique
		$table = Table::getInstance('Category', 'JTable', array('dbo' => $this->getDbo()));

		if ($table->load(array('alias' => $this->alias, 'parent_id' => (int) $this->parent_id, 'extension' => $this->extension))
			&& ($table->id != $this->id || $this->id == 0))
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_CATEGORY_UNIQUE_ALIAS'));

			return false;
		}

		return parent::store($updateNulls);
	}
}
src/Table/ContentHistory.php000064400000015137152177723700012111 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Content History table.
 *
 * @since  3.2
 */
class ContentHistory extends Table
{
	/**
	 * Array of object fields to unset from the data object before calculating SHA1 hash. This allows us to detect a meaningful change
	 * in the database row using the hash. This can be read from the #__content_types content_history_options column.
	 *
	 * @var    array
	 * @since  3.2
	 */
	public $ignoreChanges = array();

	/**
	 * Array of object fields to convert to integers before calculating SHA1 hash. Some values are stored differently
	 * when an item is created than when the item is changed and saved. This works around that issue.
	 * This can be read from the #__content_types content_history_options column.
	 *
	 * @var    array
	 * @since  3.2
	 */
	public $convertToInt = array();

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__ucm_history', 'version_id', $db);
		$this->ignoreChanges = array(
			'modified_by',
			'modified_user_id',
			'modified',
			'modified_time',
			'checked_out',
			'checked_out_time',
			'version',
			'hits',
			'path',
		);
		$this->convertToInt  = array('publish_up', 'publish_down', 'ordering', 'featured');
	}

	/**
	 * Overrides Table::store to set modified hash, user id, and save date.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.2
	 */
	public function store($updateNulls = false)
	{
		$this->set('character_count', strlen($this->get('version_data')));
		$typeTable = Table::getInstance('ContentType', 'JTable', array('dbo' => $this->getDbo()));
		$typeTable->load($this->ucm_type_id);

		if (!isset($this->sha1_hash))
		{
			$this->set('sha1_hash', $this->getSha1($this->get('version_data'), $typeTable));
		}

		// Modify author and date only when not toggling Keep Forever
		if ($this->get('keep_forever') === null)
		{
			$this->set('editor_user_id', \JFactory::getUser()->id);
			$this->set('save_date', \JFactory::getDate()->toSql());
		}

		return parent::store($updateNulls);
	}

	/**
	 * Utility method to get the hash after removing selected values. This lets us detect changes other than
	 * modified date (which will change on every save).
	 *
	 * @param   mixed        $jsonData   Either an object or a string with json-encoded data
	 * @param   ContentType  $typeTable  Table object with data for this content type
	 *
	 * @return  string  SHA1 hash on success. Empty string on failure.
	 *
	 * @since   3.2
	 */
	public function getSha1($jsonData, ContentType $typeTable)
	{
		$object = is_object($jsonData) ? $jsonData : json_decode($jsonData);

		if (isset($typeTable->content_history_options) && is_object(json_decode($typeTable->content_history_options)))
		{
			$options = json_decode($typeTable->content_history_options);
			$this->ignoreChanges = isset($options->ignoreChanges) ? $options->ignoreChanges : $this->ignoreChanges;
			$this->convertToInt = isset($options->convertToInt) ? $options->convertToInt : $this->convertToInt;
		}

		foreach ($this->ignoreChanges as $remove)
		{
			if (property_exists($object, $remove))
			{
				unset($object->$remove);
			}
		}

		// Convert integers, booleans, and nulls to strings to get a consistent hash value
		foreach ($object as $name => $value)
		{
			if (is_object($value))
			{
				// Go one level down for JSON column values
				foreach ($value as $subName => $subValue)
				{
					$object->$subName = is_int($subValue) || is_bool($subValue) || $subValue === null ? (string) $subValue : $subValue;
				}
			}
			else
			{
				$object->$name = is_int($value) || is_bool($value) || $value === null ? (string) $value : $value;
			}
		}

		// Work around empty values
		foreach ($this->convertToInt as $convert)
		{
			if (isset($object->$convert))
			{
				$object->$convert = (int) $object->$convert;
			}
		}

		if (isset($object->review_time))
		{
			$object->review_time = (int) $object->review_time;
		}

		return sha1(json_encode($object));
	}

	/**
	 * Utility method to get a matching row based on the hash value and id columns.
	 * This lets us check to make sure we don't save duplicate versions.
	 *
	 * @return  string  SHA1 hash on success. Empty string on failure.
	 *
	 * @since   3.2
	 */
	public function getHashMatch()
	{
		$db    = $this->_db;
		$query = $db->getQuery(true);
		$query->select('*')
			->from($db->quoteName('#__ucm_history'))
			->where($db->quoteName('ucm_item_id') . ' = ' . (int) $this->get('ucm_item_id'))
			->where($db->quoteName('ucm_type_id') . ' = ' . (int) $this->get('ucm_type_id'))
			->where($db->quoteName('sha1_hash') . ' = ' . $db->quote($this->get('sha1_hash')));
		$db->setQuery($query, 0, 1);

		return $db->loadObject();
	}

	/**
	 * Utility method to remove the oldest versions of an item, saving only the most recent versions.
	 *
	 * @param   integer  $maxVersions  The maximum number of versions to save. All others will be deleted.
	 *
	 * @return  boolean   true on success, false on failure.
	 *
	 * @since   3.2
	 */
	public function deleteOldVersions($maxVersions)
	{
		$result = true;

		// Get the list of version_id values we want to save
		$db    = $this->_db;
		$query = $db->getQuery(true);
		$query->select($db->quoteName('version_id'))
			->from($db->quoteName('#__ucm_history'))
			->where($db->quoteName('ucm_item_id') . ' = ' . (int) $this->get('ucm_item_id'))
			->where($db->quoteName('ucm_type_id') . ' = ' . (int) $this->get('ucm_type_id'))
			->where($db->quoteName('keep_forever') . ' != 1')
			->order($db->quoteName('save_date') . ' DESC ');
		$db->setQuery($query, 0, (int) $maxVersions);
		$idsToSave = $db->loadColumn(0);

		// Don't process delete query unless we have at least the maximum allowed versions
		if (count($idsToSave) === (int) $maxVersions)
		{
			// Delete any rows not in our list and and not flagged to keep forever.
			$query = $db->getQuery(true);
			$query->delete($db->quoteName('#__ucm_history'))
				->where($db->quoteName('ucm_item_id') . ' = ' . (int) $this->get('ucm_item_id'))
				->where($db->quoteName('ucm_type_id') . ' = ' . (int) $this->get('ucm_type_id'))
				->where($db->quoteName('version_id') . ' NOT IN (' . implode(',', $idsToSave) . ')')
				->where($db->quoteName('keep_forever') . ' != 1');
			$db->setQuery($query);
			$result = (boolean) $db->execute();
		}

		return $result;
	}
}
src/Table/Module.php000064400000010506152177723700010335 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Rules;
use Joomla\Registry\Registry;

/**
 * Module table
 *
 * @since  1.5
 */
class Module extends Table
{
	/**
	 * Constructor.
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 */
	public function __construct(\JDatabaseDriver $db)
	{
		parent::__construct('#__modules', 'id', $db);

		$this->access = (int) \JFactory::getConfig()->get('access');
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	protected function _getAssetName()
	{
		$k = $this->_tbl_key;

		return 'com_modules.module.' . (int) $this->$k;
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	protected function _getAssetTitle()
	{
		return $this->title;
	}

	/**
	 * Method to get the parent asset id for the record
	 *
	 * @param   Table    $table  A Table object (optional) for the asset parent
	 * @param   integer  $id     The id (optional) of the content.
	 *
	 * @return  integer
	 *
	 * @since   3.2
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		$assetId = null;

		// This is a module that needs to parent with the extension.
		if ($assetId === null)
		{
			// Build the query to get the asset id of the parent component.
			$query = $this->_db->getQuery(true)
				->select($this->_db->quoteName('id'))
				->from($this->_db->quoteName('#__assets'))
				->where($this->_db->quoteName('name') . ' = ' . $this->_db->quote('com_modules'));

			// Get the asset id from the database.
			$this->_db->setQuery($query);

			if ($result = $this->_db->loadResult())
			{
				$assetId = (int) $result;
			}
		}

		// Return the asset id.
		if ($assetId)
		{
			return $assetId;
		}
		else
		{
			return parent::_getAssetParentId($table, $id);
		}
	}

	/**
	 * Overloaded check function.
	 *
	 * @return  boolean  True if the instance is sane and able to be stored in the database.
	 *
	 * @see     Table::check()
	 * @since   1.5
	 */
	public function check()
	{
		// Check for valid name
		if (trim($this->title) === '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_MODULE'));

			return false;
		}

		// Prevent to save too large content > 65535 
		if ((strlen($this->content) > 65535) || (strlen($this->params) > 65535))
		{
			$this->setError(\JText::_('COM_MODULES_FIELD_CONTENT_TOO_LARGE'));

			return false;
		}

		// Check the publish down date is not earlier than publish up.
		if ((int) $this->publish_down > 0 && $this->publish_down < $this->publish_up)
		{
			// Swap the dates.
			$temp = $this->publish_up;
			$this->publish_up = $this->publish_down;
			$this->publish_down = $temp;
		}

		return true;
	}

	/**
	 * Overloaded bind function.
	 *
	 * @param   array  $array   Named array.
	 * @param   mixed  $ignore  An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error
	 *
	 * @see     Table::bind()
	 * @since   1.5
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		// Bind the rules.
		if (isset($array['rules']) && is_array($array['rules']))
		{
			$rules = new Rules($array['rules']);
			$this->setRules($rules);
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Stores a module.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   3.7.0
	 */
	public function store($updateNulls = false)
	{
		// Set publish_up, publish_down and checked_out_time to null date if not set
		if (!$this->publish_up)
		{
			$this->publish_up = $this->_db->getNullDate();
		}

		if (!$this->publish_down)
		{
			$this->publish_down = $this->_db->getNullDate();
		}

		return parent::store($updateNulls);
	}
}
src/Table/Update.php000064400000004625152177723700010337 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Update table
 * Stores updates temporarily
 *
 * @since  1.7.0
 */
class Update extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__updates', 'update_id', $db);
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True if the object is ok
	 *
	 * @see     Table::check()
	 * @since   1.7.0
	 */
	public function check()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->element) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		if (!$this->update_id && !$this->data)
		{
			$this->data = '';
		}

		return true;
	}

	/**
	 * Overloaded bind function
	 *
	 * @param   array  $array   Named array
	 * @param   mixed  $ignore  An optional array or space separated list of properties
	 *                          to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error
	 *
	 * @see     Table::bind()
	 * @since   1.7.0
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		if (isset($array['control']) && is_array($array['control']))
		{
			$registry = new Registry($array['control']);
			$array['control'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Method to create and execute a SELECT WHERE query.
	 *
	 * @param   array  $options  Array of options
	 *
	 * @return  string  Results of query
	 *
	 * @since   1.7.0
	 */
	public function find($options = array())
	{
		$where = array();

		foreach ($options as $col => $val)
		{
			$where[] = $col . ' = ' . $this->_db->quote($val);
		}

		$query = $this->_db->getQuery(true)
			->select($this->_db->quoteName($this->_tbl_key))
			->from($this->_db->quoteName($this->_tbl))
			->where(implode(' AND ', $where));
		$this->_db->setQuery($query);

		return $this->_db->loadResult();
	}
}
src/Table/Extension.php000064400000011542152177723700011065 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;
use Joomla\Utilities\ArrayHelper;

/**
 * Extension table
 *
 * @since  1.7.0
 */
class Extension extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__extensions', 'extension_id', $db);

		// Set the alias since the column is called enabled
		$this->setColumnAlias('published', 'enabled');
	}

	/**
	 * Overloaded check function
	 *
	 * @return  boolean  True if the object is ok
	 *
	 * @see     Table::check()
	 * @since   1.7.0
	 */
	public function check()
	{
		// Check for valid name
		if (trim($this->name) == '' || trim($this->element) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_MUSTCONTAIN_A_TITLE_EXTENSION'));

			return false;
		}

		if (!$this->extension_id)
		{
			if (!$this->custom_data)
			{
				$this->custom_data = '';
			}

			if (!$this->system_data)
			{
				$this->system_data = '';
			}
		}

		return true;
	}

	/**
	 * Overloaded bind function
	 *
	 * @param   array  $array   Named array
	 * @param   mixed  $ignore  An optional array or space separated list of properties
	 * to ignore while binding.
	 *
	 * @return  mixed  Null if operation was satisfactory, otherwise returns an error
	 *
	 * @see     Table::bind()
	 * @since   1.7.0
	 */
	public function bind($array, $ignore = '')
	{
		if (isset($array['params']) && is_array($array['params']))
		{
			$registry = new Registry($array['params']);
			$array['params'] = (string) $registry;
		}

		if (isset($array['control']) && is_array($array['control']))
		{
			$registry = new Registry($array['control']);
			$array['control'] = (string) $registry;
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Method to create and execute a SELECT WHERE query.
	 *
	 * @param   array  $options  Array of options
	 *
	 * @return  string  The database query result
	 *
	 * @since   1.7.0
	 */
	public function find($options = array())
	{
		// Get the \JDatabaseQuery object
		$query = $this->_db->getQuery(true);

		foreach ($options as $col => $val)
		{
			$query->where($col . ' = ' . $this->_db->quote($val));
		}

		$query->select($this->_db->quoteName('extension_id'))
			->from($this->_db->quoteName('#__extensions'));
		$this->_db->setQuery($query);

		return $this->_db->loadResult();
	}

	/**
	 * Method to set the publishing state for a row or list of rows in the database
	 * table.  The method respects checked out rows by other users and will attempt
	 * to checkin rows that it can after adjustments are made.
	 *
	 * @param   mixed    $pks     An optional array of primary key values to update.  If not
	 *                            set the instance property value is used.
	 * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
	 * @param   integer  $userId  The user id of the user performing the operation.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		$k = $this->_tbl_key;

		// Sanitize input.
		$pks = ArrayHelper::toInteger($pks);
		$userId = (int) $userId;
		$state = (int) $state;

		// If there are no primary keys set check to see if the instance key is set.
		if (empty($pks))
		{
			if ($this->$k)
			{
				$pks = array($this->$k);
			}
			// Nothing to set publishing state on, return false.
			else
			{
				$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

				return false;
			}
		}

		// Build the WHERE clause for the primary keys.
		$where = $k . '=' . implode(' OR ' . $k . '=', $pks);

		// Determine if there is checkin support for the table.
		if (!property_exists($this, 'checked_out') || !property_exists($this, 'checked_out_time'))
		{
			$checkin = '';
		}
		else
		{
			$checkin = ' AND (checked_out = 0 OR checked_out = ' . (int) $userId . ')';
		}

		// Update the publishing state for rows with the given primary keys.
		$query = $this->_db->getQuery(true)
			->update($this->_db->quoteName($this->_tbl))
			->set($this->_db->quoteName('enabled') . ' = ' . (int) $state)
			->where('(' . $where . ')' . $checkin);
		$this->_db->setQuery($query);
		$this->_db->execute();

		// If checkin is supported and all rows were adjusted, check them in.
		if ($checkin && (count($pks) == $this->_db->getAffectedRows()))
		{
			// Checkin the rows.
			foreach ($pks as $pk)
			{
				$this->checkin($pk);
			}
		}

		// If the Table instance value is in the list of primary keys that were set, set the instance.
		if (in_array($this->$k, $pks))
		{
			$this->enabled = $state;
		}

		$this->setError('');

		return true;
	}
}
src/Table/Asset.php000064400000011141152177723700010163 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  1.7.0
 */
class Asset extends Nested
{
	/**
	 * The primary key of the asset.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $id = null;

	/**
	 * The unique name of the asset.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $name = null;

	/**
	 * The human readable title of the asset.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $title = null;

	/**
	 * The rules for the asset stored in a JSON string
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $rules = null;

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__assets', 'id', $db);
	}

	/**
	 * Method to load an asset by its name.
	 *
	 * @param   string  $name  The name of the asset.
	 *
	 * @return  integer
	 *
	 * @since   1.7.0
	 */
	public function loadByName($name)
	{
		return $this->load(array('name' => $name));
	}

	/**
	 * Assert that the nested set data is valid.
	 *
	 * @return  boolean  True if the instance is sane and able to be stored in the database.
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		$this->parent_id = (int) $this->parent_id;

		if (empty($this->rules))
		{
			$this->rules = '{}';
		}

		// Nested does not allow parent_id = 0, override this.
		if ($this->parent_id > 0)
		{
			// Get the \JDatabaseQuery object
			$query = $this->_db->getQuery(true)
				->select('1')
				->from($this->_db->quoteName($this->_tbl))
				->where($this->_db->quoteName('id') . ' = ' . $this->parent_id);

			if ($this->_db->setQuery($query, 0, 1)->loadResult())
			{
				return true;
			}

			$this->setError(\JText::_('JLIB_DATABASE_ERROR_INVALID_PARENT_ID'));

			return false;
		}

		return true;
	}

	/**
	 * Method to recursively rebuild the whole nested set tree.
	 *
	 * @param   integer  $parentId  The root of the tree to rebuild.
	 * @param   integer  $leftId    The left id to start with in building the tree.
	 * @param   integer  $level     The level to assign to the current nodes.
	 * @param   string   $path      The path to the current nodes.
	 *
	 * @return  integer  1 + value of root rgt on success, false on failure
	 *
	 * @since   3.5
	 * @throws  \RuntimeException on database error.
	 */
	public function rebuild($parentId = null, $leftId = 0, $level = 0, $path = null)
	{
		// If no parent is provided, try to find it.
		if ($parentId === null)
		{
			// Get the root item.
			$parentId = $this->getRootId();

			if ($parentId === false)
			{
				return false;
			}
		}

		$query = $this->_db->getQuery(true);

		// Build the structure of the recursive query.
		if (!isset($this->_cache['rebuild.sql']))
		{
			$query->clear()
				->select($this->_tbl_key)
				->from($this->_tbl)
				->where('parent_id = %d');

			// If the table has an ordering field, use that for ordering.
			if (property_exists($this, 'ordering'))
			{
				$query->order('parent_id, ordering, lft');
			}
			else
			{
				$query->order('parent_id, lft');
			}

			$this->_cache['rebuild.sql'] = (string) $query;
		}

		// Make a shortcut to database object.

		// Assemble the query to find all children of this node.
		$this->_db->setQuery(sprintf($this->_cache['rebuild.sql'], (int) $parentId));

		$children = $this->_db->loadObjectList();

		// The right value of this node is the left value + 1
		$rightId = $leftId + 1;

		// Execute this function recursively over all children
		foreach ($children as $node)
		{
			/*
			 * $rightId is the current right value, which is incremented on recursion return.
			 * Increment the level for the children.
			 * Add this item's alias to the path (but avoid a leading /)
			 */
			$rightId = $this->rebuild($node->{$this->_tbl_key}, $rightId, $level + 1);

			// If there is an update failure, return false to break out of the recursion.
			if ($rightId === false)
			{
				return false;
			}
		}

		// We've got the left value, and now that we've processed
		// the children of this node we also know the right value.
		$query->clear()
			->update($this->_tbl)
			->set('lft = ' . (int) $leftId)
			->set('rgt = ' . (int) $rightId)
			->set('level = ' . (int) $level)
			->where($this->_tbl_key . ' = ' . (int) $parentId);
		$this->_db->setQuery($query)->execute();

		// Return the right value of this node + 1.
		return $rightId + 1;
	}
}
src/Table/Table.php000064400000124357152177723700010151 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

\JLoader::import('joomla.filesystem.path');

/**
 * Abstract Table class
 *
 * Parent class to all tables.
 *
 * @since  1.7.0
 * @tutorial  Joomla.Platform/jtable.cls
 */
abstract class Table extends \JObject implements \JObservableInterface, \JTableInterface
{
	/**
	 * Include paths for searching for Table classes.
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	private static $_includePaths = array();

	/**
	 * Name of the database table to model.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_tbl = '';

	/**
	 * Name of the primary key field in the table.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_tbl_key = '';

	/**
	 * Name of the primary key fields in the table.
	 *
	 * @var    array
	 * @since  3.0.1
	 */
	protected $_tbl_keys = array();

	/**
	 * \JDatabaseDriver object.
	 *
	 * @var    \JDatabaseDriver
	 * @since  1.7.0
	 */
	protected $_db;

	/**
	 * Should rows be tracked as ACL assets?
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $_trackAssets = false;

	/**
	 * The rules associated with this record.
	 *
	 * @var    \JAccessRules  A \JAccessRules object.
	 * @since  1.7.0
	 */
	protected $_rules;

	/**
	 * Indicator that the tables have been locked.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $_locked = false;

	/**
	 * Indicates that the primary keys autoincrement.
	 *
	 * @var    boolean
	 * @since  3.1.4
	 */
	protected $_autoincrement = true;

	/**
	 * Generic observers for this Table (Used e.g. for tags Processing)
	 *
	 * @var    \JObserverUpdater
	 * @since  3.1.2
	 */
	protected $_observers;

	/**
	 * Array with alias for "special" columns such as ordering, hits etc etc
	 *
	 * @var    array
	 * @since  3.4.0
	 */
	protected $_columnAlias = array();

	/**
	 * An array of key names to be json encoded in the bind function
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $_jsonEncode = array();

	/**
	 * Object constructor to set table and key fields.  In most cases this will
	 * be overridden by child classes to explicitly set the table and key fields
	 * for a particular database table.
	 *
	 * @param   string            $table  Name of the table to model.
	 * @param   mixed             $key    Name of the primary key field in the table or array of field names that compose the primary key.
	 * @param   \JDatabaseDriver  $db     \JDatabaseDriver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($table, $key, $db)
	{
		// Set internal variables.
		$this->_tbl = $table;

		// Set the key to be an array.
		if (is_string($key))
		{
			$key = array($key);
		}
		elseif (is_object($key))
		{
			$key = (array) $key;
		}

		$this->_tbl_keys = $key;

		if (count($key) == 1)
		{
			$this->_autoincrement = true;
		}
		else
		{
			$this->_autoincrement = false;
		}

		// Set the singular table key for backwards compatibility.
		$this->_tbl_key = $this->getKeyName();

		$this->_db = $db;

		// Initialise the table properties.
		$fields = $this->getFields();

		if ($fields)
		{
			foreach ($fields as $name => $v)
			{
				// Add the field if it is not already present.
				if (!property_exists($this, $name))
				{
					$this->$name = null;
				}
			}
		}

		// If we are tracking assets, make sure an access field exists and initially set the default.
		if (property_exists($this, 'asset_id'))
		{
			$this->_trackAssets = true;
		}

		// If the access property exists, set the default.
		if (property_exists($this, 'access'))
		{
			$this->access = (int) \JFactory::getConfig()->get('access');
		}

		// Implement \JObservableInterface:
		// Create observer updater and attaches all observers interested by $this class:
		$this->_observers = new \JObserverUpdater($this);
		\JObserverMapper::attachAllObservers($this);
	}

	/**
	 * Implement \JObservableInterface:
	 * Adds an observer to this instance.
	 * This method will be called fron the constructor of classes implementing \JObserverInterface
	 * which is instanciated by the constructor of $this with \JObserverMapper::attachAllObservers($this)
	 *
	 * @param   \JObserverInterface|\JTableObserver  $observer  The observer object
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function attachObserver(\JObserverInterface $observer)
	{
		$this->_observers->attachObserver($observer);
	}

	/**
	 * Gets the instance of the observer of class $observerClass
	 *
	 * @param   string  $observerClass  The observer class-name to return the object of
	 *
	 * @return  \JTableObserver|null
	 *
	 * @since   3.1.2
	 */
	public function getObserverOfClass($observerClass)
	{
		return $this->_observers->getObserverOfClass($observerClass);
	}

	/**
	 * Get the columns from database table.
	 *
	 * @param   bool  $reload  flag to reload cache
	 *
	 * @return  mixed  An array of the field names, or false if an error occurs.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function getFields($reload = false)
	{
		static $cache = null;

		if ($cache === null || $reload)
		{
			// Lookup the fields for this table only once.
			$name   = $this->_tbl;
			$fields = $this->_db->getTableColumns($name, false);

			if (empty($fields))
			{
				throw new \UnexpectedValueException(sprintf('No columns found for %s table', $name));
			}

			$cache = $fields;
		}

		return $cache;
	}

	/**
	 * Static method to get an instance of a Table class if it can be found in the table include paths.
	 *
	 * To add include paths for searching for Table classes see Table::addIncludePath().
	 *
	 * @param   string  $type    The type (name) of the Table class to get an instance of.
	 * @param   string  $prefix  An optional prefix for the table class name.
	 * @param   array   $config  An optional array of configuration values for the Table object.
	 *
	 * @return  Table|boolean   A Table object if found or boolean false on failure.
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($type, $prefix = 'JTable', $config = array())
	{
		// Sanitize and prepare the table class name.
		$type       = preg_replace('/[^A-Z0-9_\.-]/i', '', $type);
		$tableClass = $prefix . ucfirst($type);

		// Only try to load the class if it doesn't already exist.
		if (!class_exists($tableClass))
		{
			// Search for the class file in the JTable include paths.
			jimport('joomla.filesystem.path');

			$paths = self::addIncludePath();
			$pathIndex = 0;

			while (!class_exists($tableClass) && $pathIndex < count($paths))
			{
				if ($tryThis = \JPath::find($paths[$pathIndex++], strtolower($type) . '.php'))
				{
					// Import the class file.
					include_once $tryThis;
				}
			}

			if (!class_exists($tableClass))
			{
				/*
				* If unable to find the class file in the Table include paths. Return false.
				* The warning JLIB_DATABASE_ERROR_NOT_SUPPORTED_FILE_NOT_FOUND has been removed in 3.6.3.
				* In 4.0 an Exception (type to be determined) will be thrown.
				* For more info see https://github.com/joomla/joomla-cms/issues/11570
				*/

				return false;
			}
		}

		// If a database object was passed in the configuration array use it, otherwise get the global one from \JFactory.
		$db = isset($config['dbo']) ? $config['dbo'] : \JFactory::getDbo();

		// Instantiate a new table class and return it.
		return new $tableClass($db);
	}

	/**
	 * Add a filesystem path where Table should search for table class files.
	 *
	 * @param   array|string  $path  A filesystem path or array of filesystem paths to add.
	 *
	 * @return  array  An array of filesystem paths to find Table classes in.
	 *
	 * @since   1.7.0
	 */
	public static function addIncludePath($path = null)
	{
		// If the internal paths have not been initialised, do so with the base table path.
		if (empty(self::$_includePaths))
		{
			self::$_includePaths = array(__DIR__);
		}

		// Convert the passed path(s) to add to an array.
		settype($path, 'array');

		// If we have new paths to add, do so.
		if (!empty($path))
		{
			// Check and add each individual new path.
			foreach ($path as $dir)
			{
				// Sanitize path.
				$dir = trim($dir);

				// Add to the front of the list so that custom paths are searched first.
				if (!in_array($dir, self::$_includePaths))
				{
					array_unshift(self::$_includePaths, $dir);
				}
			}
		}

		return self::$_includePaths;
	}

	/**
	 * Method to compute the default name of the asset.
	 * The default name is in the form table_name.id
	 * where id is the value of the primary key of the table.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function _getAssetName()
	{
		$keys = array();

		foreach ($this->_tbl_keys as $k)
		{
			$keys[] = (int) $this->$k;
		}

		return $this->_tbl . '.' . implode('.', $keys);
	}

	/**
	 * Method to return the title to use for the asset table.
	 *
	 * In tracking the assets a title is kept for each asset so that there is some context available in a unified access manager.
	 * Usually this would just return $this->title or $this->name or whatever is being used for the primary name of the row.
	 * If this method is not overridden, the asset name is used.
	 *
	 * @return  string  The string to use as the title in the asset table.
	 *
	 * @since   1.7.0
	 */
	protected function _getAssetTitle()
	{
		return $this->_getAssetName();
	}

	/**
	 * Method to get the parent asset under which to register this one.
	 *
	 * By default, all assets are registered to the ROOT node with ID, which will default to 1 if none exists.
	 * An extended class can define a table and ID to lookup.  If the asset does not exist it will be created.
	 *
	 * @param   Table    $table  A Table object for the asset parent.
	 * @param   integer  $id     Id to look up
	 *
	 * @return  integer
	 *
	 * @since   1.7.0
	 */
	protected function _getAssetParentId(Table $table = null, $id = null)
	{
		// For simple cases, parent to the asset root.
		/** @var  \JTableAsset  $assets */
		$assets = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
		$rootId = $assets->getRootId();

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

		return 1;
	}

	/**
	 * Method to append the primary keys for this table to a query.
	 *
	 * @param   \JDatabaseQuery  $query  A query object to append.
	 * @param   mixed            $pk     Optional primary key parameter.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function appendPrimaryKeys($query, $pk = null)
	{
		if (is_null($pk))
		{
			foreach ($this->_tbl_keys as $k)
			{
				$query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($this->$k));
			}
		}
		else
		{
			if (is_string($pk))
			{
				$pk = array($this->_tbl_key => $pk);
			}

			$pk = (object) $pk;

			foreach ($this->_tbl_keys as $k)
			{
				$query->where($this->_db->quoteName($k) . ' = ' . $this->_db->quote($pk->$k));
			}
		}
	}

	/**
	 * Method to get the database table name for the class.
	 *
	 * @return  string  The name of the database table being modeled.
	 *
	 * @since   1.7.0
	 */
	public function getTableName()
	{
		return $this->_tbl;
	}

	/**
	 * Method to get the primary key field name for the table.
	 *
	 * @param   boolean  $multiple  True to return all primary keys (as an array) or false to return just the first one (as a string).
	 *
	 * @return  mixed  Array of primary key field names or string containing the first primary key field.
	 *
	 * @since   1.7.0
	 */
	public function getKeyName($multiple = false)
	{
		// Count the number of keys
		if (count($this->_tbl_keys))
		{
			if ($multiple)
			{
				// If we want multiple keys, return the raw array.
				return $this->_tbl_keys;
			}
			else
			{
				// If we want the standard method, just return the first key.
				return $this->_tbl_keys[0];
			}
		}

		return '';
	}

	/**
	 * Method to get the \JDatabaseDriver object.
	 *
	 * @return  \JDatabaseDriver  The internal database driver object.
	 *
	 * @since   1.7.0
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Method to set the \JDatabaseDriver object.
	 *
	 * @param   \JDatabaseDriver  $db  A \JDatabaseDriver object to be used by the table object.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function setDbo($db)
	{
		$this->_db = $db;

		return true;
	}

	/**
	 * Method to set rules for the record.
	 *
	 * @param   mixed  $input  A \JAccessRules object, JSON string, or array.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setRules($input)
	{
		if ($input instanceof \JAccessRules)
		{
			$this->_rules = $input;
		}
		else
		{
			$this->_rules = new \JAccessRules($input);
		}
	}

	/**
	 * Method to get the rules for the record.
	 *
	 * @return  \JAccessRules object
	 *
	 * @since   1.7.0
	 */
	public function getRules()
	{
		return $this->_rules;
	}

	/**
	 * Method to reset class properties to the defaults set in the class
	 * definition. It will ignore the primary key as well as any private class
	 * properties (except $_errors).
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function reset()
	{
		// Get the default values for the class from the table.
		foreach ($this->getFields() as $k => $v)
		{
			// If the property is not the primary key or private, reset it.
			if (!in_array($k, $this->_tbl_keys) && (strpos($k, '_') !== 0))
			{
				$this->$k = $v->Default;
			}
		}

		// Reset table errors
		$this->_errors = array();
	}

	/**
	 * Method to bind an associative array or object to the Table instance.This
	 * method only binds properties that are publicly accessible and optionally
	 * takes an array of properties to ignore when binding.
	 *
	 * @param   array|object  $src     An associative array or object to bind to the Table instance.
	 * @param   array|string  $ignore  An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 */
	public function bind($src, $ignore = array())
	{
		// JSON encode any fields required
		if (!empty($this->_jsonEncode))
		{
			foreach ($this->_jsonEncode as $field)
			{
				if (isset($src[$field]) && is_array($src[$field]))
				{
					$src[$field] = json_encode($src[$field]);
				}
			}
		}

		// Check if the source value is an array or object
		if (!is_object($src) && !is_array($src))
		{
			throw new \InvalidArgumentException(
				sprintf(
					'Could not bind the data source in %1$s::bind(), the source must be an array or object but a "%2$s" was given.',
					get_class($this),
					gettype($src)
				)
			);
		}

		// If the source value is an object, get its accessible properties.
		if (is_object($src))
		{
			$src = get_object_vars($src);
		}

		// If the ignore value is a string, explode it over spaces.
		if (!is_array($ignore))
		{
			$ignore = explode(' ', $ignore);
		}

		// Bind the source value, excluding the ignored fields.
		foreach ($this->getProperties() as $k => $v)
		{
			// Only process fields not in the ignore array.
			if (!in_array($k, $ignore))
			{
				if (isset($src[$k]))
				{
					$this->$k = $src[$k];
				}
			}
		}

		return true;
	}

	/**
	 * Method to load a row from the database by primary key and bind the fields to the Table instance properties.
	 *
	 * @param   mixed    $keys   An optional primary key value to load the row by, or an array of fields to match.
	 *                           If not set the instance property value is used.
	 * @param   boolean  $reset  True to reset the default values before loading the new row.
	 *
	 * @return  boolean  True if successful. False if row not found.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 * @throws  \UnexpectedValueException
	 */
	public function load($keys = null, $reset = true)
	{
		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeLoad', array($keys, $reset));

		if (empty($keys))
		{
			$empty = true;
			$keys  = array();

			// If empty, use the value of the current key
			foreach ($this->_tbl_keys as $key)
			{
				$empty      = $empty && empty($this->$key);
				$keys[$key] = $this->$key;
			}

			// If empty primary key there's is no need to load anything
			if ($empty)
			{
				return true;
			}
		}
		elseif (!is_array($keys))
		{
			// Load by primary key.
			$keyCount = count($this->_tbl_keys);

			if ($keyCount)
			{
				if ($keyCount > 1)
				{
					throw new \InvalidArgumentException('Table has multiple primary keys specified, only one primary key value provided.');
				}

				$keys = array($this->getKeyName() => $keys);
			}
			else
			{
				throw new \RuntimeException('No table keys defined.');
			}
		}

		if ($reset)
		{
			$this->reset();
		}

		// Initialise the query.
		$query = $this->_db->getQuery(true)
			->select('*')
			->from($this->_tbl);
		$fields = array_keys($this->getProperties());

		foreach ($keys as $field => $value)
		{
			// Check that $field is in the table.
			if (!in_array($field, $fields))
			{
				throw new \UnexpectedValueException(sprintf('Missing field in database: %s &#160; %s.', get_class($this), $field));
			}

			// Add the search tuple to the query.
			$query->where($this->_db->quoteName($field) . ' = ' . $this->_db->quote($value));
		}

		$this->_db->setQuery($query);

		$row = $this->_db->loadAssoc();

		// Check that we have a result.
		if (empty($row))
		{
			$result = false;
		}
		else
		{
			// Bind the object with the row and return.
			$result = $this->bind($row);
		}

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterLoad', array(&$result, $row));

		return $result;
	}

	/**
	 * Method to perform sanity checks on the Table instance properties to ensure they are safe to store in the database.
	 *
	 * Child classes should override this method to make sure the data they are storing in the database is safe and as expected before storage.
	 *
	 * @return  boolean  True if the instance is sane and able to be stored in the database.
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		return true;
	}

	/**
	 * Method to store a row in the database from the Table instance properties.
	 *
	 * If a primary key value is set the row with that primary key value will be updated with the instance property values.
	 * If no primary key value is set a new row will be inserted into the database with the properties from the Table instance.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function store($updateNulls = false)
	{
		$result = true;

		$k = $this->_tbl_keys;

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeStore', array($updateNulls, $k));

		$currentAssetId = 0;

		if (!empty($this->asset_id))
		{
			$currentAssetId = $this->asset_id;
		}

		// The asset id field is managed privately by this class.
		if ($this->_trackAssets)
		{
			unset($this->asset_id);
		}

		// If a primary key exists update the object, otherwise insert it.
		if ($this->hasPrimaryKey())
		{
			$this->_db->updateObject($this->_tbl, $this, $this->_tbl_keys, $updateNulls);
		}
		else
		{
			$this->_db->insertObject($this->_tbl, $this, $this->_tbl_keys[0]);
		}

		// If the table is not set to track assets return true.
		if ($this->_trackAssets)
		{
			if ($this->_locked)
			{
				$this->_unlock();
			}

			/*
			 * Asset Tracking
			 */
			$parentId = $this->_getAssetParentId();
			$name     = $this->_getAssetName();
			$title    = $this->_getAssetTitle();

			/** @var  \JTableAsset  $asset */
			$asset = self::getInstance('Asset', 'JTable', array('dbo' => $this->getDbo()));
			$asset->loadByName($name);

			// Re-inject the asset id.
			$this->asset_id = $asset->id;

			// Check for an error.
			$error = $asset->getError();

			if ($error)
			{
				$this->setError($error);

				return false;
			}
			else
			{
				// Specify how a new or moved node asset is inserted into the tree.
				if (empty($this->asset_id) || $asset->parent_id != $parentId)
				{
					$asset->setLocation($parentId, 'last-child');
				}

				// Prepare the asset to be stored.
				$asset->parent_id = $parentId;
				$asset->name      = $name;
				$asset->title     = $title;

				if ($this->_rules instanceof \JAccessRules)
				{
					$asset->rules = (string) $this->_rules;
				}

				if (!$asset->check() || !$asset->store($updateNulls))
				{
					$this->setError($asset->getError());

					return false;
				}
				else
				{
					// Create an asset_id or heal one that is corrupted.
					if (empty($this->asset_id) || ($currentAssetId != $this->asset_id && !empty($this->asset_id)))
					{
						// Update the asset_id field in this table.
						$this->asset_id = (int) $asset->id;

						$query = $this->_db->getQuery(true)
							->update($this->_db->quoteName($this->_tbl))
							->set('asset_id = ' . (int) $this->asset_id);
						$this->appendPrimaryKeys($query);
						$this->_db->setQuery($query)->execute();
					}
				}
			}
		}

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterStore', array(&$result));

		return $result;
	}

	/**
	 * Method to provide a shortcut to binding, checking and storing a Table instance to the database table.
	 *
	 * The method will check a row in once the data has been stored and if an ordering filter is present will attempt to reorder
	 * the table rows based on the filter.  The ordering filter is an instance property name.  The rows that will be reordered
	 * are those whose value matches the Table instance for the property specified.
	 *
	 * @param   array|object  $src             An associative array or object to bind to the Table instance.
	 * @param   string        $orderingFilter  Filter for the order updating
	 * @param   array|string  $ignore          An optional array or space separated list of properties to ignore while binding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function save($src, $orderingFilter = '', $ignore = '')
	{
		// Attempt to bind the source to the instance.
		if (!$this->bind($src, $ignore))
		{
			return false;
		}

		// Run any sanity checks on the instance and verify that it is ready for storage.
		if (!$this->check())
		{
			return false;
		}

		// Attempt to store the properties to the database table.
		if (!$this->store())
		{
			return false;
		}

		// Attempt to check the row in, just in case it was checked out.
		if (!$this->checkin())
		{
			return false;
		}

		// If an ordering filter is set, attempt reorder the rows in the table based on the filter and value.
		if ($orderingFilter)
		{
			$filterValue = $this->$orderingFilter;
			$this->reorder($orderingFilter ? $this->_db->quoteName($orderingFilter) . ' = ' . $this->_db->quote($filterValue) : '');
		}

		// Set the error to empty and return true.
		$this->setError('');

		return true;
	}

	/**
	 * Method to delete a row from the database table by primary key value.
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function delete($pk = null)
	{
		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($this->_tbl_key => $pk);
		}

		foreach ($this->_tbl_keys as $key)
		{
			$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];

			if ($pk[$key] === null)
			{
				throw new \UnexpectedValueException('Null primary key not allowed.');
			}

			$this->$key = $pk[$key];
		}

		// Implement \JObservableInterface: Pre-processing by observers
		$this->_observers->update('onBeforeDelete', array($pk));

		// If tracking assets, remove the asset first.
		if ($this->_trackAssets)
		{
			// Get the asset name
			$name  = $this->_getAssetName();
			/** @var  \JTableAsset  $asset */
			$asset = self::getInstance('Asset');

			if ($asset->loadByName($name))
			{
				if (!$asset->delete())
				{
					$this->setError($asset->getError());

					return false;
				}
			}
		}

		// Delete the row by primary key.
		$query = $this->_db->getQuery(true)
			->delete($this->_tbl);
		$this->appendPrimaryKeys($query, $pk);

		$this->_db->setQuery($query);

		// Check for a database error.
		$this->_db->execute();

		// Implement \JObservableInterface: Post-processing by observers
		$this->_observers->update('onAfterDelete', array($pk));

		return true;
	}

	/**
	 * Method to check a row out if the necessary properties/fields exist.
	 *
	 * To prevent race conditions while editing rows in a database, a row can be checked out if the fields 'checked_out' and 'checked_out_time'
	 * are available. While a row is checked out, any attempt to store the row by a user other than the one who checked the row out should be
	 * held until the row is checked in again.
	 *
	 * @param   integer  $userId  The Id of the user checking out the row.
	 * @param   mixed    $pk      An optional primary key value to check out.  If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function checkOut($userId, $pk = null)
	{
		$checkedOutField = $this->getColumnAlias('checked_out');
		$checkedOutTimeField = $this->getColumnAlias('checked_out_time');

		// If there is no checked_out or checked_out_time field, just return true.
		if (!property_exists($this, $checkedOutField) || !property_exists($this, $checkedOutTimeField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($this->_tbl_key => $pk);
		}

		foreach ($this->_tbl_keys as $key)
		{
			$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];

			if ($pk[$key] === null)
			{
				throw new \UnexpectedValueException('Null primary key not allowed.');
			}
		}

		// Get the current time in the database format.
		$time = \JFactory::getDate()->toSql();

		// Check the row out by primary key.
		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($this->_db->quoteName($checkedOutField) . ' = ' . (int) $userId)
			->set($this->_db->quoteName($checkedOutTimeField) . ' = ' . $this->_db->quote($time));
		$this->appendPrimaryKeys($query, $pk);
		$this->_db->setQuery($query);
		$this->_db->execute();

		// Set table values in the object.
		$this->$checkedOutField      = (int) $userId;
		$this->$checkedOutTimeField = $time;

		return true;
	}

	/**
	 * Method to check a row in if the necessary properties/fields exist.
	 *
	 * Checking a row in will allow other users the ability to edit the row.
	 *
	 * @param   mixed  $pk  An optional primary key value to check out.  If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function checkIn($pk = null)
	{
		$checkedOutField = $this->getColumnAlias('checked_out');
		$checkedOutTimeField = $this->getColumnAlias('checked_out_time');

		// If there is no checked_out or checked_out_time field, just return true.
		if (!property_exists($this, $checkedOutField) || !property_exists($this, $checkedOutTimeField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$this->$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($this->_tbl_key => $pk);
		}

		foreach ($this->_tbl_keys as $key)
		{
			$pk[$key] = empty($pk[$key]) ? $this->$key : $pk[$key];

			if ($pk[$key] === null)
			{
				throw new \UnexpectedValueException('Null primary key not allowed.');
			}
		}

		// Check the row in by primary key.
		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($this->_db->quoteName($checkedOutField) . ' = 0')
			->set($this->_db->quoteName($checkedOutTimeField) . ' = ' . $this->_db->quote($this->_db->getNullDate()));
		$this->appendPrimaryKeys($query, $pk);
		$this->_db->setQuery($query);

		// Check for a database error.
		$this->_db->execute();

		// Set table values in the object.
		$this->$checkedOutField      = 0;
		$this->$checkedOutTimeField = '';

		$dispatcher = \JEventDispatcher::getInstance();
		$dispatcher->trigger('onAfterCheckin', array($this->_tbl));

		return true;
	}

	/**
	 * Validate that the primary key has been set.
	 *
	 * @return  boolean  True if the primary key(s) have been set.
	 *
	 * @since   3.1.4
	 */
	public function hasPrimaryKey()
	{
		if ($this->_autoincrement)
		{
			$empty = true;

			foreach ($this->_tbl_keys as $key)
			{
				$empty = $empty && empty($this->$key);
			}
		}
		else
		{
			$query = $this->_db->getQuery(true)
				->select('COUNT(*)')
				->from($this->_tbl);
			$this->appendPrimaryKeys($query);

			$this->_db->setQuery($query);
			$count = $this->_db->loadResult();

			if ($count == 1)
			{
				$empty = false;
			}
			else
			{
				$empty = true;
			}
		}

		return !$empty;
	}

	/**
	 * Method to increment the hits for a row if the necessary property/field exists.
	 *
	 * @param   mixed  $pk  An optional primary key value to increment. If not set the instance property value is used.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function hit($pk = null)
	{
		$hitsField = $this->getColumnAlias('hits');

		// If there is no hits field, just return true.
		if (!property_exists($this, $hitsField))
		{
			return true;
		}

		if (is_null($pk))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				$pk[$key] = $this->$key;
			}
		}
		elseif (!is_array($pk))
		{
			$pk = array($this->_tbl_key => $pk);
		}

		foreach ($this->_tbl_keys as $key)
		{
			$pk[$key] = is_null($pk[$key]) ? $this->$key : $pk[$key];

			if ($pk[$key] === null)
			{
				throw new \UnexpectedValueException('Null primary key not allowed.');
			}
		}

		// Check the row in by primary key.
		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($this->_db->quoteName($hitsField) . ' = (' . $this->_db->quoteName($hitsField) . ' + 1)');
		$this->appendPrimaryKeys($query, $pk);
		$this->_db->setQuery($query);
		$this->_db->execute();

		// Set table values in the object.
		$this->hits++;

		return true;
	}

	/**
	 * Method to determine if a row is checked out and therefore uneditable by a user.
	 *
	 * If the row is checked out by the same user, then it is considered not checked out -- as the user can still edit it.
	 *
	 * @param   integer  $with     The user ID to preform the match with, if an item is checked out by this user the function will return false.
	 * @param   integer  $against  The user ID to perform the match against when the function is used as a static function.
	 *
	 * @return  boolean  True if checked out.
	 *
	 * @since   1.7.0
	 */
	public function isCheckedOut($with = 0, $against = null)
	{
		// Handle the non-static case.
		if (isset($this) && ($this instanceof Table) && is_null($against))
		{
			$checkedOutField = $this->getColumnAlias('checked_out');
			$against = $this->get($checkedOutField);
		}

		// The item is not checked out or is checked out by the same user.
		if (!$against || ($against == $with))
		{
			return false;
		}

		$db = \JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('COUNT(userid)')
			->from($db->quoteName('#__session'))
			->where($db->quoteName('userid') . ' = ' . (int) $against);
		$db->setQuery($query);
		$checkedOut = (boolean) $db->loadResult();

		// If a session exists for the user then it is checked out.
		return $checkedOut;
	}

	/**
	 * Method to get the next ordering value for a group of rows defined by an SQL WHERE clause.
	 *
	 * This is useful for placing a new item last in a group of items in the table.
	 *
	 * @param   string  $where  WHERE clause to use for selecting the MAX(ordering) for the table.
	 *
	 * @return  integer  The next ordering value.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function getNextOrder($where = '')
	{
		// Check if there is an ordering field set
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			throw new \UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
		}

		// Get the largest ordering value for a given where clause.
		$query = $this->_db->getQuery(true)
			->select('MAX(' . $this->_db->quoteName($orderingField) . ')')
			->from($this->_tbl);

		if ($where)
		{
			$query->where($where);
		}

		$this->_db->setQuery($query);
		$max = (int) $this->_db->loadResult();

		// Return the largest ordering value + 1.
		return $max + 1;
	}

	/**
	 * Get the primary key values for this table using passed in values as a default.
	 *
	 * @param   array  $keys  Optional primary key values to use.
	 *
	 * @return  array  An array of primary key names and values.
	 *
	 * @since   3.1.4
	 */
	public function getPrimaryKey(array $keys = array())
	{
		foreach ($this->_tbl_keys as $key)
		{
			if (!isset($keys[$key]))
			{
				if (!empty($this->$key))
				{
					$keys[$key] = $this->$key;
				}
			}
		}

		return $keys;
	}

	/**
	 * Method to compact the ordering values of rows in a group of rows defined by an SQL WHERE clause.
	 *
	 * @param   string  $where  WHERE clause to use for limiting the selection of rows to compact the ordering values.
	 *
	 * @return  mixed  Boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function reorder($where = '')
	{
		// Check if there is an ordering field set
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			throw new \UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
		}

		$quotedOrderingField = $this->_db->quoteName($orderingField);

		$subquery = $this->_db->getQuery(true)
			->from($this->_tbl)
			->selectRowNumber($quotedOrderingField, 'new_ordering');

		$query = $this->_db->getQuery(true)
			->update($this->_tbl)
			->set($quotedOrderingField . ' = sq.new_ordering');

		$innerOn = array();

		// Get the primary keys for the selection.
		foreach ($this->_tbl_keys as $i => $k)
		{
			$subquery->select($this->_db->quoteName($k, 'pk__' . $i));
			$innerOn[] = $this->_db->quoteName($k) . ' = sq.' . $this->_db->quoteName('pk__' . $i);
		}

		// Setup the extra where and ordering clause data.
		if ($where)
		{
			$subquery->where($where);
			$query->where($where);
		}

		$subquery->where($quotedOrderingField . ' >= 0');
		$query->where($quotedOrderingField . ' >= 0');

		$query->innerJoin('(' . (string) $subquery . ') AS sq ON ' . implode(' AND ', $innerOn));

		$this->_db->setQuery($query);
		$this->_db->execute();

		return true;
	}

	/**
	 * Method to move a row in the ordering sequence of a group of rows defined by an SQL WHERE clause.
	 *
	 * Negative numbers move the row up in the sequence and positive numbers move it down.
	 *
	 * @param   integer  $delta  The direction and magnitude to move the row in the ordering sequence.
	 * @param   string   $where  WHERE clause to use for limiting the selection of rows to compact the ordering values.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 */
	public function move($delta, $where = '')
	{
		// Check if there is an ordering field set
		$orderingField = $this->getColumnAlias('ordering');

		if (!property_exists($this, $orderingField))
		{
			throw new \UnexpectedValueException(sprintf('%s does not support ordering.', get_class($this)));
		}

		$quotedOrderingField = $this->_db->quoteName($orderingField);

		// If the change is none, do nothing.
		if (empty($delta))
		{
			return true;
		}

		$row   = null;
		$query = $this->_db->getQuery(true);

		// Select the primary key and ordering values from the table.
		$query->select(implode(',', $this->_tbl_keys) . ', ' . $quotedOrderingField)
			->from($this->_tbl);

		// If the movement delta is negative move the row up.
		if ($delta < 0)
		{
			$query->where($quotedOrderingField . ' < ' . (int) $this->$orderingField)
				->order($quotedOrderingField . ' DESC');
		}
		// If the movement delta is positive move the row down.
		elseif ($delta > 0)
		{
			$query->where($quotedOrderingField . ' > ' . (int) $this->$orderingField)
				->order($quotedOrderingField . ' ASC');
		}

		// Add the custom WHERE clause if set.
		if ($where)
		{
			$query->where($where);
		}

		// Select the first row with the criteria.
		$this->_db->setQuery($query, 0, 1);
		$row = $this->_db->loadObject();

		// If a row is found, move the item.
		if (!empty($row))
		{
			// Update the ordering field for this instance to the row's ordering value.
			$query->clear()
				->update($this->_tbl)
				->set($quotedOrderingField . ' = ' . (int) $row->$orderingField);
			$this->appendPrimaryKeys($query);
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the ordering field for the row to this instance's ordering value.
			$query->clear()
				->update($this->_tbl)
				->set($quotedOrderingField . ' = ' . (int) $this->$orderingField);
			$this->appendPrimaryKeys($query, $row);
			$this->_db->setQuery($query);
			$this->_db->execute();

			// Update the instance value.
			$this->$orderingField = $row->$orderingField;
		}
		else
		{
			// Update the ordering field for this instance.
			$query->clear()
				->update($this->_tbl)
				->set($quotedOrderingField . ' = ' . (int) $this->$orderingField);
			$this->appendPrimaryKeys($query);
			$this->_db->setQuery($query);
			$this->_db->execute();
		}

		return true;
	}

	/**
	 * Method to set the publishing state for a row or list of rows in the database table.
	 *
	 * The method respects checked out rows by other users and will attempt to checkin rows that it can after adjustments are made.
	 *
	 * @param   mixed    $pks     An optional array of primary key values to update. If not set the instance property value is used.
	 * @param   integer  $state   The publishing state. eg. [0 = unpublished, 1 = published]
	 * @param   integer  $userId  The user ID of the user performing the operation.
	 *
	 * @return  boolean  True on success; false if $pks is empty.
	 *
	 * @since   1.7.0
	 */
	public function publish($pks = null, $state = 1, $userId = 0)
	{
		// Sanitize input
		$userId = (int) $userId;
		$state  = (int) $state;

		if (!is_null($pks))
		{
			if (!is_array($pks))
			{
				$pks = array($pks);
			}

			foreach ($pks as $key => $pk)
			{
				if (!is_array($pk))
				{
					$pks[$key] = array($this->_tbl_key => $pk);
				}
			}
		}

		// If there are no primary keys set check to see if the instance key is set.
		if (empty($pks))
		{
			$pk = array();

			foreach ($this->_tbl_keys as $key)
			{
				if ($this->$key)
				{
					$pk[$key] = $this->$key;
				}
				// We don't have a full primary key - return false
				else
				{
					$this->setError(\JText::_('JLIB_DATABASE_ERROR_NO_ROWS_SELECTED'));

					return false;
				}
			}

			$pks = array($pk);
		}

		$publishedField = $this->getColumnAlias('published');
		$checkedOutField = $this->getColumnAlias('checked_out');

		foreach ($pks as $pk)
		{
			// Update the publishing state for rows with the given primary keys.
			$query = $this->_db->getQuery(true)
				->update($this->_tbl)
				->set($this->_db->quoteName($publishedField) . ' = ' . (int) $state);

			// If publishing, set published date/time if not previously set
			if ($state && property_exists($this, 'publish_up') && (int) $this->publish_up == 0)
			{
				$nowDate = $this->_db->quote(\JFactory::getDate()->toSql());
				$query->set($this->_db->quoteName($this->getColumnAlias('publish_up')) . ' = ' . $nowDate);
			}

			// Determine if there is checkin support for the table.
			if (property_exists($this, 'checked_out') || property_exists($this, 'checked_out_time'))
			{
				$query->where(
					'('
						. $this->_db->quoteName($checkedOutField) . ' = 0'
						. ' OR ' . $this->_db->quoteName($checkedOutField) . ' = ' . (int) $userId
						. ' OR ' . $this->_db->quoteName($checkedOutField) . ' IS NULL'
					. ')'
				);
				$checkin = true;
			}
			else
			{
				$checkin = false;
			}

			// Build the WHERE clause for the primary keys.
			$this->appendPrimaryKeys($query, $pk);

			$this->_db->setQuery($query);

			try
			{
				$this->_db->execute();
			}
			catch (\RuntimeException $e)
			{
				$this->setError($e->getMessage());

				return false;
			}

			// If checkin is supported and all rows were adjusted, check them in.
			if ($checkin && (count($pks) == $this->_db->getAffectedRows()))
			{
				$this->checkin($pk);
			}

			// If the Table instance value is in the list of primary keys that were set, set the instance.
			$ours = true;

			foreach ($this->_tbl_keys as $key)
			{
				if ($this->$key != $pk[$key])
				{
					$ours = false;
				}
			}

			if ($ours)
			{
				$this->$publishedField = $state;
			}
		}

		$this->setError('');

		return true;
	}

	/**
	 * Method to lock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function _lock()
	{
		$this->_db->lockTable($this->_tbl);
		$this->_locked = true;

		return true;
	}

	/**
	 * Method to return the real name of a "special" column such as ordering, hits, published
	 * etc etc. In this way you are free to follow your db naming convention and use the
	 * built in \Joomla functions.
	 *
	 * @param   string  $column  Name of the "special" column (ie ordering, hits)
	 *
	 * @return  string  The string that identify the special
	 *
	 * @since   3.4
	 */
	public function getColumnAlias($column)
	{
		// Get the column data if set
		if (isset($this->_columnAlias[$column]))
		{
			$return = $this->_columnAlias[$column];
		}
		else
		{
			$return = $column;
		}

		// Sanitize the name
		$return = preg_replace('#[^A-Z0-9_]#i', '', $return);

		return $return;
	}

	/**
	 * Method to register a column alias for a "special" column.
	 *
	 * @param   string  $column       The "special" column (ie ordering)
	 * @param   string  $columnAlias  The real column name (ie foo_ordering)
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public function setColumnAlias($column, $columnAlias)
	{
		// Santize the column name alias
		$column = strtolower($column);
		$column = preg_replace('#[^A-Z0-9_]#i', '', $column);

		// Set the column alias internally
		$this->_columnAlias[$column] = $columnAlias;
	}

	/**
	 * Method to unlock the database table for writing.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	protected function _unlock()
	{
		$this->_db->unlockTables();
		$this->_locked = false;

		return true;
	}
}
src/Table/ViewLevel.php000064400000003532152177723700011013 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Viewlevels table class.
 *
 * @since  1.7.0
 */
class ViewLevel extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($db)
	{
		parent::__construct('#__viewlevels', 'id', $db);
	}

	/**
	 * Method to bind the data.
	 *
	 * @param   array  $array   The data to bind.
	 * @param   mixed  $ignore  An array or space separated list of fields to ignore.
	 *
	 * @return  boolean  True on success, false on failure.
	 *
	 * @since   1.7.0
	 */
	public function bind($array, $ignore = '')
	{
		// Bind the rules as appropriate.
		if (isset($array['rules']))
		{
			if (is_array($array['rules']))
			{
				$array['rules'] = json_encode($array['rules']);
			}
		}

		return parent::bind($array, $ignore);
	}

	/**
	 * Method to check the current record to save
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public function check()
	{
		// Validate the title.
		if ((trim($this->title)) == '')
		{
			$this->setError(\JText::_('JLIB_DATABASE_ERROR_VIEWLEVEL'));

			return false;
		}

		// Check for a duplicate title.
		$db = $this->_db;
		$query = $db->getQuery(true)
			->select('COUNT(title)')
			->from($db->quoteName('#__viewlevels'))
			->where($db->quoteName('title') . ' = ' . $db->quote($this->title))
			->where($db->quoteName('id') . ' != ' . (int) $this->id);
		$db->setQuery($query);

		if ($db->loadResult() > 0)
		{
			$this->setError(\JText::sprintf('JLIB_DATABASE_ERROR_USERLEVEL_NAME_EXISTS', $this->title));

			return false;
		}

		return true;
	}
}
src/Table/ContentType.php000064400000007010152177723700011360 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table;

defined('JPATH_PLATFORM') or die;

/**
 * Tags table
 *
 * @since  3.1
 */
class ContentType extends Table
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db  A database connector object
	 *
	 * @since   3.1
	 */
	public function __construct($db)
	{
		parent::__construct('#__content_types', 'type_id', $db);
	}

	/**
	 * Overloaded check method to ensure data integrity.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 * @throws  \UnexpectedValueException
	 */
	public function check()
	{
		// Check for valid name.
		if (trim($this->type_title) === '')
		{
			throw new \UnexpectedValueException(sprintf('The title is empty'));
		}

		$this->type_title = ucfirst($this->type_title);

		if (empty($this->type_alias))
		{
			throw new \UnexpectedValueException(sprintf('The type_alias is empty'));
		}

		return true;
	}

	/**
	 * Overridden Table::store.
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1
	 */
	public function store($updateNulls = false)
	{
		// Verify that the alias is unique
		$table = Table::getInstance('Contenttype', 'JTable', array('dbo' => $this->getDbo()));

		if ($table->load(array('type_alias' => $this->type_alias)) && ($table->type_id != $this->type_id || $this->type_id == 0))
		{
			$this->setError(\JText::_('COM_TAGS_ERROR_UNIQUE_ALIAS'));

			return false;
		}

		return parent::store($updateNulls);
	}

	/**
	 * Method to expand the field mapping
	 *
	 * @param   boolean  $assoc  True to return an associative array.
	 *
	 * @return  mixed  Array or object with field mappings. Defaults to object.
	 *
	 * @since   3.1
	 */
	public function fieldmapExpand($assoc = true)
	{
		return $this->fieldmap = json_decode($this->fieldmappings, $assoc);
	}

	/**
	 * Method to get the id given the type alias
	 *
	 * @param   string  $typeAlias  Content type alias (for example, 'com_content.article').
	 *
	 * @return  mixed  type_id for this alias if successful, otherwise null.
	 *
	 * @since   3.2
	 */
	public function getTypeId($typeAlias)
	{
		$db = $this->_db;
		$query = $db->getQuery(true);
		$query->select($db->quoteName('type_id'))
			->from($db->quoteName($this->_tbl))
			->where($db->quoteName('type_alias') . ' = ' . $db->quote($typeAlias));
		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Method to get the Table object for the content type from the table object.
	 *
	 * @return  mixed  Table object on success, otherwise false.
	 *
	 * @since   3.2
	 *
	 * @throws  \RuntimeException
	 */
	public function getContentTable()
	{
		$result = false;
		$tableInfo = json_decode($this->table);

		if (is_object($tableInfo) && isset($tableInfo->special))
		{
			if (is_object($tableInfo->special) && isset($tableInfo->special->type) && isset($tableInfo->special->prefix))
			{
				$class = isset($tableInfo->special->class) ? $tableInfo->special->class : 'Joomla\\CMS\\Table\\Table';

				if (!class_implements($class, 'Joomla\\CMS\\Table\\TableInterface'))
				{
					// This isn't an instance of TableInterface. Abort.
					throw new \RuntimeException('Class must be an instance of Joomla\\CMS\\Table\\TableInterface');
				}

				$result = $class::getInstance($tableInfo->special->type, $tableInfo->special->prefix);
			}
		}

		return $result;
	}
}
src/Table/Observer/AbstractObserver.php000064400000005131152177723700014150 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Table\TableInterface;
use Joomla\CMS\Table\Table;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  3.1.2
 */
abstract class AbstractObserver implements \JObserverInterface
{
	/**
	 * The observed table
	 *
	 * @var    Table
	 * @since  3.1.2
	 */
	protected $table;

	/**
	 * Constructor: Associates to $table $this observer
	 *
	 * @param   TableInterface  $table  Table to be observed
	 *
	 * @since   3.1.2
	 */
	public function __construct(TableInterface $table)
	{
		$table->attachObserver($this);
		$this->table = $table;
	}

	/**
	 * Pre-processor for $table->load($keys, $reset)
	 *
	 * @param   mixed    $keys   An optional primary key value to load the row by, or an array of fields to match.  If not
	 *                           set the instance property value is used.
	 * @param   boolean  $reset  True to reset the default values before loading the new row.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeLoad($keys, $reset)
	{
	}

	/**
	 * Post-processor for $table->load($keys, $reset)
	 *
	 * @param   boolean  &$result  The result of the load
	 * @param   array    $row      The loaded (and already binded to $this->table) row of the database table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterLoad(&$result, $row)
	{
	}

	/**
	 * Pre-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  $updateNulls  The result of the load
	 * @param   string   $tableKey     The key of the table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeStore($updateNulls, $tableKey)
	{
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  &$result  The result of the store
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterStore(&$result)
	{
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
	}

	/**
	 * Post-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  The deleted primary key value.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterDelete($pk)
	{
	}
}
src/Table/Observer/Tags.php000064400000012001152177723700011565 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

/**
 * Abstract class defining methods that can be
 * implemented by an Observer class of a Table class (which is an Observable).
 * Attaches $this Observer to the $table in the constructor.
 * The classes extending this class should not be instanciated directly, as they
 * are automatically instanciated by the \JObserverMapper
 *
 * @since  3.1.2
 */
class Tags extends AbstractObserver
{
	/**
	 * Helper object for managing tags
	 *
	 * @var    \JHelperTags
	 * @since  3.1.2
	 */
	protected $tagsHelper;

	/**
	 * The pattern for this table's TypeAlias
	 *
	 * @var    string
	 * @since  3.1.2
	 */
	protected $typeAliasPattern = null;

	/**
	 * Override for postStoreProcess param newTags, Set by setNewTags, used by onAfterStore and onBeforeStore
	 *
	 * @var    array
	 * @since  3.1.2
	 */
	protected $newTags = false;

	/**
	 * Override for postStoreProcess param replaceTags. Set by setNewTags, used by onAfterStore
	 *
	 * @var    boolean
	 * @since  3.1.2
	 */
	protected $replaceTags = true;

	/**
	 * Not public, so marking private and deprecated, but needed internally in parseTypeAlias for
	 * PHP < 5.4.0 as it's not passing context $this to closure function.
	 *
	 * @var         Tags
	 * @since       3.1.2
	 * @deprecated  Never use this
	 * @private
	 */
	public static $_myTableForPregreplaceOnly;

	/**
	 * Creates the associated observer instance and attaches it to the $observableObject
	 * Creates the associated tags helper class instance
	 * $typeAlias can be of the form "{variableName}.type", automatically replacing {variableName} with table-instance variables variableName
	 *
	 * @param   \JObservableInterface  $observableObject  The subject object to be observed
	 * @param   array                  $params            ( 'typeAlias' => $typeAlias )
	 *
	 * @return  Tags
	 *
	 * @since   3.1.2
	 */
	public static function createObserver(\JObservableInterface $observableObject, $params = array())
	{
		$typeAlias = $params['typeAlias'];

		$observer = new self($observableObject);

		$observer->tagsHelper = new \JHelperTags;
		$observer->typeAliasPattern = $typeAlias;

		return $observer;
	}

	/**
	 * Pre-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  $updateNulls  The result of the load
	 * @param   string   $tableKey     The key of the table
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onBeforeStore($updateNulls, $tableKey)
	{
		$this->parseTypeAlias();

		if (empty($this->table->tagsHelper->tags))
		{
			$this->tagsHelper->preStoreProcess($this->table);
		}
		else
		{
			$this->tagsHelper->preStoreProcess($this->table, (array) $this->table->tagsHelper->tags);
		}
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 * You can change optional params newTags and replaceTags of tagsHelper with method setNewTagsToAdd
	 *
	 * @param   boolean  &$result  The result of the load
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function onAfterStore(&$result)
	{
		if ($result)
		{
			if (empty($this->table->tagsHelper->tags))
			{
				$result = $this->tagsHelper->postStoreProcess($this->table);
			}
			else
			{
				$result = $this->tagsHelper->postStoreProcess($this->table, $this->table->tagsHelper->tags);
			}

			// Restore default values for the optional params:
			$this->newTags = array();
			$this->replaceTags = true;
		}
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
		$this->parseTypeAlias();
		$this->tagsHelper->deleteTagData($this->table, $pk);
	}

	/**
	 * Sets the new tags to be added or to replace existing tags
	 *
	 * @param   array    $newTags      New tags to be added to or replace current tags for an item
	 * @param   boolean  $replaceTags  Replace tags (true) or add them (false)
	 *
	 * @return  boolean
	 *
	 * @since   3.1.2
	 */
	public function setNewTags($newTags, $replaceTags)
	{
		$this->parseTypeAlias();

		return $this->tagsHelper->postStoreProcess($this->table, $newTags, $replaceTags);
	}

	/**
	 * Internal method
	 * Parses a TypeAlias of the form "{variableName}.type", replacing {variableName} with table-instance variables variableName
	 * Storing result into $this->tagsHelper->typeAlias
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	protected function parseTypeAlias()
	{
		// Needed for PHP < 5.4.0 as it's not passing context $this to closure function
		static::$_myTableForPregreplaceOnly = $this->table;

		$this->tagsHelper->typeAlias = preg_replace_callback('/{([^}]+)}/',
			function($matches)
			{
				return Tags::$_myTableForPregreplaceOnly->{$matches[1]};
			},
			$this->typeAliasPattern
		);
	}
}
src/Table/Observer/ContentHistory.php000064400000007114152177723700013674 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Table\Observer;

defined('JPATH_PLATFORM') or die;

/**
 * Table class supporting modified pre-order tree traversal behavior.
 *
 * @since  3.2
 */
class ContentHistory extends AbstractObserver
{
	/**
	 * Helper object for storing and deleting version history information associated with this table observer
	 *
	 * @var    \JHelperContenthistory
	 * @since  3.2
	 */
	protected $contenthistoryHelper;

	/**
	 * The pattern for this table's TypeAlias
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $typeAliasPattern = null;

	/**
	 * Not public, so marking private and deprecated, but needed internally in parseTypeAlias for
	 * PHP < 5.4.0 as it's not passing context $this to closure function.
	 *
	 * @var         ContentHistory
	 * @since       3.2
	 * @deprecated  Never use this
	 * @private
	 */
	public static $_myTableForPregreplaceOnly;

	/**
	 * Creates the associated observer instance and attaches it to the $observableObject
	 * Creates the associated content history helper class instance
	 * $typeAlias can be of the form "{variableName}.type", automatically replacing {variableName} with table-instance variables variableName
	 *
	 * @param   \JObservableInterface  $observableObject  The subject object to be observed
	 * @param   array                  $params            ( 'typeAlias' => $typeAlias )
	 *
	 * @return  ContentHistory
	 *
	 * @since   3.2
	 */
	public static function createObserver(\JObservableInterface $observableObject, $params = array())
	{
		$typeAlias = $params['typeAlias'];

		$observer = new self($observableObject);

		$observer->contenthistoryHelper = new \JHelperContenthistory($typeAlias);
		$observer->typeAliasPattern = $typeAlias;

		return $observer;
	}

	/**
	 * Post-processor for $table->store($updateNulls)
	 *
	 * @param   boolean  &$result  The result of the load
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function onAfterStore(&$result)
	{
		if ($result)
		{
			$this->parseTypeAlias();
			$aliasParts = explode('.', $this->contenthistoryHelper->typeAlias);

			if (\JComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
			{
				$this->contenthistoryHelper->store($this->table);
			}
		}
	}

	/**
	 * Pre-processor for $table->delete($pk)
	 *
	 * @param   mixed  $pk  An optional primary key value to delete.  If not set the instance property value is used.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @throws  \UnexpectedValueException
	 */
	public function onBeforeDelete($pk)
	{
		$this->parseTypeAlias();
		$aliasParts = explode('.', $this->contenthistoryHelper->typeAlias);

		if (\JComponentHelper::getParams($aliasParts[0])->get('save_history', 0))
		{
			$this->parseTypeAlias();
			$this->contenthistoryHelper->deleteHistory($this->table);
		}
	}

	/**
	 * Internal method
	 * Parses a TypeAlias of the form "{variableName}.type", replacing {variableName} with table-instance variables variableName
	 * Storing result into $this->contenthistoryHelper->typeAlias
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function parseTypeAlias()
	{
		// Needed for PHP < 5.4.0 as it's not passing context $this to closure function
		static::$_myTableForPregreplaceOnly = $this->table;

		$this->contenthistoryHelper->typeAlias = preg_replace_callback('/{([^}]+)}/',
			function($matches)
			{
				return ContentHistory::$_myTableForPregreplaceOnly->{$matches[1]};
			},
			$this->typeAliasPattern
		);
	}
}
src/String/PunycodeHelper.php000064400000012133152177723700012253 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\String;

defined('JPATH_PLATFORM') or die;

use Joomla\Uri\UriHelper;

\JLoader::register('idna_convert', JPATH_LIBRARIES . '/idna_convert/idna_convert.class.php');

/**
 * Joomla Platform String Punycode Class
 *
 * Class for handling UTF-8 URLs
 * Wraps the Punycode library
 * All functions assume the validity of utf-8 URLs.
 *
 * @since  3.1.2
 */
abstract class PunycodeHelper
{
	/**
	 * Transforms a UTF-8 string to a Punycode string
	 *
	 * @param   string  $utfString  The UTF-8 string to transform
	 *
	 * @return  string  The punycode string
	 *
	 * @since   3.1.2
	 */
	public static function toPunycode($utfString)
	{
		$idn = new \idna_convert;

		return $idn->encode($utfString);
	}

	/**
	 * Transforms a Punycode string to a UTF-8 string
	 *
	 * @param   string  $punycodeString  The Punycode string to transform
	 *
	 * @return  string  The UF-8 URL
	 *
	 * @since   3.1.2
	 */
	public static function fromPunycode($punycodeString)
	{
		$idn = new \idna_convert;

		return $idn->decode($punycodeString);
	}

	/**
	 * Transforms a UTF-8 URL to a Punycode URL
	 *
	 * @param   string  $uri  The UTF-8 URL to transform
	 *
	 * @return  string  The punycode URL
	 *
	 * @since   3.1.2
	 */
	public static function urlToPunycode($uri)
	{
		$parsed = UriHelper::parse_url($uri);

		if (!isset($parsed['host']) || $parsed['host'] == '')
		{
			// If there is no host we do not need to convert it.
			return $uri;
		}

		$host = $parsed['host'];
		$hostExploded = explode('.', $host);
		$newhost = '';

		foreach ($hostExploded as $hostex)
		{
			$hostex = static::toPunycode($hostex);
			$newhost .= $hostex . '.';
		}

		$newhost = substr($newhost, 0, -1);
		$newuri = '';

		if (!empty($parsed['scheme']))
		{
			// Assume :// is required although it is not always.
			$newuri .= $parsed['scheme'] . '://';
		}

		if (!empty($newhost))
		{
			$newuri .= $newhost;
		}

		if (!empty($parsed['port']))
		{
			$newuri .= ':' . $parsed['port'];
		}

		if (!empty($parsed['path']))
		{
			$newuri .= $parsed['path'];
		}

		if (!empty($parsed['query']))
		{
			$newuri .= '?' . $parsed['query'];
		}

		if (!empty($parsed['fragment']))
		{
			$newuri .= '#' . $parsed['fragment'];
		}

		return $newuri;
	}

	/**
	 * Transforms a Punycode URL to a UTF-8 URL
	 *
	 * @param   string  $uri  The Punycode URL to transform
	 *
	 * @return  string  The UTF-8 URL
	 *
	 * @since   3.1.2
	 */
	public static function urlToUTF8($uri)
	{
		if (empty($uri))
		{
			return;
		}

		$parsed = UriHelper::parse_url($uri);

		if (!isset($parsed['host']) || $parsed['host'] == '')
		{
			// If there is no host we do not need to convert it.
			return $uri;
		}

		$host = $parsed['host'];
		$hostExploded = explode('.', $host);
		$newhost = '';

		foreach ($hostExploded as $hostex)
		{
			$hostex = self::fromPunycode($hostex);
			$newhost .= $hostex . '.';
		}

		$newhost = substr($newhost, 0, -1);
		$newuri = '';

		if (!empty($parsed['scheme']))
		{
			// Assume :// is required although it is not always.
			$newuri .= $parsed['scheme'] . '://';
		}

		if (!empty($newhost))
		{
			$newuri .= $newhost;
		}

		if (!empty($parsed['port']))
		{
			$newuri .= ':' . $parsed['port'];
		}

		if (!empty($parsed['path']))
		{
			$newuri .= $parsed['path'];
		}

		if (!empty($parsed['query']))
		{
			$newuri .= '?' . $parsed['query'];
		}

		if (!empty($parsed['fragment']))
		{
			$newuri .= '#' . $parsed['fragment'];
		}

		return $newuri;
	}

	/**
	 * Transforms a UTF-8 email to a Punycode email
	 * This assumes a valid email address
	 *
	 * @param   string  $email  The UTF-8 email to transform
	 *
	 * @return  string  The punycode email
	 *
	 * @since   3.1.2
	 */
	public static function emailToPunycode($email)
	{
		$explodedAddress = explode('@', $email);

		// Not addressing UTF-8 user names
		$newEmail = $explodedAddress[0];

		if (!empty($explodedAddress[1]))
		{
			$domainExploded = explode('.', $explodedAddress[1]);
			$newdomain = '';

			foreach ($domainExploded as $domainex)
			{
				$domainex = static::toPunycode($domainex);
				$newdomain .= $domainex . '.';
			}

			$newdomain = substr($newdomain, 0, -1);
			$newEmail = $newEmail . '@' . $newdomain;
		}

		return $newEmail;
	}

	/**
	 * Transforms a Punycode email to a UTF-8 email
	 * This assumes a valid email address
	 *
	 * @param   string  $email  The punycode email to transform
	 *
	 * @return  string  The punycode email
	 *
	 * @since   3.1.2
	 */
	public static function emailToUTF8($email)
	{
		$explodedAddress = explode('@', $email);

		// Not addressing UTF-8 user names
		$newEmail = $explodedAddress[0];

		if (!empty($explodedAddress[1]))
		{
			$domainExploded = explode('.', $explodedAddress[1]);
			$newdomain = '';

			foreach ($domainExploded as $domainex)
			{
				$domainex = static::fromPunycode($domainex);
				$newdomain .= $domainex . '.';
			}

			$newdomain = substr($newdomain, 0, -1);
			$newEmail = $newEmail . '@' . $newdomain;
		}

		return $newEmail;
	}
}
src/Categories/Categories.php000064400000023761152177723700012242 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Categories;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Language\Multilanguage;

/**
 * Categories Class.
 *
 * @since  1.6
 */
class Categories
{
	/**
	 * Array to hold the object instances
	 *
	 * @var    Categories[]
	 * @since  1.6
	 */
	public static $instances = array();

	/**
	 * Array of category nodes
	 *
	 * @var    CategoryNode[]
	 * @since  1.6
	 */
	protected $_nodes;

	/**
	 * Array of checked categories -- used to save values when _nodes are null
	 *
	 * @var    boolean[]
	 * @since  1.6
	 */
	protected $_checkedCategories;

	/**
	 * Name of the extension the categories belong to
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_extension = null;

	/**
	 * Name of the linked content table to get category content count
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_table = null;

	/**
	 * Name of the category field
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_field = null;

	/**
	 * Name of the key field
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_key = null;

	/**
	 * Name of the items state field
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_statefield = null;

	/**
	 * Array of options
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected $_options = null;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Array of options
	 *
	 * @since   1.6
	 */
	public function __construct($options)
	{
		$this->_extension  = $options['extension'];
		$this->_table      = $options['table'];
		$this->_field      = isset($options['field']) && $options['field'] ? $options['field'] : 'catid';
		$this->_key        = isset($options['key']) && $options['key'] ? $options['key'] : 'id';
		$this->_statefield = isset($options['statefield']) ? $options['statefield'] : 'state';

		$options['access']      = isset($options['access']) ? $options['access'] : 'true';
		$options['published']   = isset($options['published']) ? $options['published'] : 1;
		$options['countItems']  = isset($options['countItems']) ? $options['countItems'] : 0;
		$options['currentlang'] = Multilanguage::isEnabled() ? Factory::getLanguage()->getTag() : 0;

		$this->_options = $options;
	}

	/**
	 * Returns a reference to a Categories object
	 *
	 * @param   string  $extension  Name of the categories extension
	 * @param   array   $options    An array of options
	 *
	 * @return  Categories|boolean  Categories object on success, boolean false if an object does not exist
	 *
	 * @since   1.6
	 */
	public static function getInstance($extension, $options = array())
	{
		$hash = md5(strtolower($extension) . serialize($options));

		if (isset(self::$instances[$hash]))
		{
			return self::$instances[$hash];
		}

		$parts = explode('.', $extension);
		$component = 'com_' . strtolower($parts[0]);
		$section = count($parts) > 1 ? $parts[1] : '';
		$classname = ucfirst(substr($component, 4)) . ucfirst($section) . 'Categories';

		if (!class_exists($classname))
		{
			$path = JPATH_SITE . '/components/' . $component . '/helpers/category.php';

			\JLoader::register($classname, $path);

			if (!class_exists($classname))
			{
				return false;
			}
		}

		self::$instances[$hash] = new $classname($options);

		return self::$instances[$hash];
	}

	/**
	 * Loads a specific category and all its children in a CategoryNode object
	 *
	 * @param   mixed    $id         an optional id integer or equal to 'root'
	 * @param   boolean  $forceload  True to force  the _load method to execute
	 *
	 * @return  CategoryNode|null|boolean  CategoryNode object or null if $id is not valid
	 *
	 * @since   1.6
	 */
	public function get($id = 'root', $forceload = false)
	{
		if ($id !== 'root')
		{
			$id = (int) $id;

			if ($id == 0)
			{
				$id = 'root';
			}
		}

		// If this $id has not been processed yet, execute the _load method
		if ((!isset($this->_nodes[$id]) && !isset($this->_checkedCategories[$id])) || $forceload)
		{
			$this->_load($id);
		}

		// If we already have a value in _nodes for this $id, then use it.
		if (isset($this->_nodes[$id]))
		{
			return $this->_nodes[$id];
		}
		// If we processed this $id already and it was not valid, then return null.
		elseif (isset($this->_checkedCategories[$id]))
		{
			return;
		}

		return false;
	}

	/**
	 * Returns the extension of the category.
	 *
	 * @return   string  The extension
	 *
	 * @since   3.9.0
	 */
	public function getExtension()
	{
		return $this->_extension;
	}

	/**
	 * Load method
	 *
	 * @param   integer  $id  Id of category to load
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	protected function _load($id)
	{
		/** @var JDatabaseDriver */
		$db   = Factory::getDbo();
		$app  = Factory::getApplication();
		$user = Factory::getUser();
		$extension = $this->_extension;

		// Record that has this $id has been checked
		$this->_checkedCategories[$id] = true;

		$query = $db->getQuery(true)
			->select('c.id, c.asset_id, c.access, c.alias, c.checked_out, c.checked_out_time,
				c.created_time, c.created_user_id, c.description, c.extension, c.hits, c.language, c.level,
				c.lft, c.metadata, c.metadesc, c.metakey, c.modified_time, c.note, c.params, c.parent_id,
				c.path, c.published, c.rgt, c.title, c.modified_user_id, c.version'
			);

		$case_when = ' CASE WHEN ';
		$case_when .= $query->charLength('c.alias', '!=', '0');
		$case_when .= ' THEN ';
		$c_id = $query->castAsChar('c.id');
		$case_when .= $query->concatenate(array($c_id, 'c.alias'), ':');
		$case_when .= ' ELSE ';
		$case_when .= $c_id . ' END as slug';

		$query->select($case_when)
			->where('(c.extension=' . $db->quote($extension) . ' OR c.extension=' . $db->quote('system') . ')');

		if ($this->_options['access'])
		{
			$query->where('c.access IN (' . implode(',', $user->getAuthorisedViewLevels()) . ')');
		}

		if ($this->_options['published'] == 1)
		{
			$query->where('c.published = 1');
		}

		$query->order('c.lft');

		// Note: s for selected id
		if ($id != 'root')
		{
			// Get the selected category
			$query->from($db->quoteName('#__categories', 's'))
				->where('s.id = ' . (int) $id);

			if ($app->isClient('site') && Multilanguage::isEnabled())
			{
				// For the most part, we use c.lft column, which index is properly used instead of c.rgt
				$query->innerJoin(
					$db->quoteName('#__categories', 'c')
					. ' ON (s.lft < c.lft AND c.lft < s.rgt AND c.language IN ('
					. $db->quote(Factory::getLanguage()->getTag()) . ',' . $db->quote('*') . '))'
					. ' OR (c.lft <= s.lft AND s.rgt <= c.rgt)'
				);
			}
			else
			{
				$query->innerJoin(
					$db->quoteName('#__categories', 'c')
					. ' ON (s.lft <= c.lft AND c.lft < s.rgt)'
					. ' OR (c.lft < s.lft AND s.rgt < c.rgt)'
				);
			}
		}
		else
		{
			$query->from($db->quoteName('#__categories', 'c'));

			if ($app->isClient('site') && Multilanguage::isEnabled())
			{
				$query->where('c.language IN (' . $db->quote(Factory::getLanguage()->getTag()) . ',' . $db->quote('*') . ')');
			}
		}

		// Note: i for item
		if ($this->_options['countItems'] == 1)
		{
			$subQuery = $db->getQuery(true)
				->select('COUNT(i.' . $db->quoteName($this->_key) . ')')
				->from($db->quoteName($this->_table, 'i'))
				->where('i.' . $db->quoteName($this->_field) . ' = c.id');

			if ($this->_options['published'] == 1)
			{
				$subQuery->where('i.' . $this->_statefield . ' = 1');
			}

			if ($this->_options['currentlang'] !== 0)
			{
				$subQuery->where('(i.language = ' . $db->quote('*')
					. ' OR i.language = ' . $db->quote($this->_options['currentlang']) . ')'
				);
			}

			$query->select('(' . $subQuery . ') AS numitems');
		}

		// Get the results
		$db->setQuery($query);
		$results = $db->loadObjectList('id');
		$childrenLoaded = false;

		if (count($results))
		{
			// Foreach categories
			foreach ($results as $result)
			{
				// Deal with root category
				if ($result->id == 1)
				{
					$result->id = 'root';
				}

				// Deal with parent_id
				if ($result->parent_id == 1)
				{
					$result->parent_id = 'root';
				}

				// Create the node
				if (!isset($this->_nodes[$result->id]))
				{
					// Create the CategoryNode and add to _nodes
					$this->_nodes[$result->id] = new CategoryNode($result, $this);

					// If this is not root and if the current node's parent is in the list or the current node parent is 0
					if ($result->id != 'root' && (isset($this->_nodes[$result->parent_id]) || $result->parent_id == 1))
					{
						// Compute relationship between node and its parent - set the parent in the _nodes field
						$this->_nodes[$result->id]->setParent($this->_nodes[$result->parent_id]);
					}

					// If the node's parent id is not in the _nodes list and the node is not root (doesn't have parent_id == 0),
					// then remove the node from the list
					if (!(isset($this->_nodes[$result->parent_id]) || $result->parent_id == 0))
					{
						unset($this->_nodes[$result->id]);
						continue;
					}

					if ($result->id == $id || $childrenLoaded)
					{
						$this->_nodes[$result->id]->setAllLoaded();
						$childrenLoaded = true;
					}
				}
				elseif ($result->id == $id || $childrenLoaded)
				{
					// Create the CategoryNode
					$this->_nodes[$result->id] = new CategoryNode($result, $this);

					if ($result->id != 'root' && (isset($this->_nodes[$result->parent_id]) || $result->parent_id))
					{
						// Compute relationship between node and its parent
						$this->_nodes[$result->id]->setParent($this->_nodes[$result->parent_id]);
					}

					// If the node's parent id is not in the _nodes list and the node is not root (doesn't have parent_id == 0),
					// then remove the node from the list
					if (!(isset($this->_nodes[$result->parent_id]) || $result->parent_id == 0))
					{
						unset($this->_nodes[$result->id]);
						continue;
					}

					if ($result->id == $id || $childrenLoaded)
					{
						$this->_nodes[$result->id]->setAllLoaded();
						$childrenLoaded = true;
					}
				}
			}
		}
		else
		{
			$this->_nodes[$id] = null;
		}
	}
}
src/Categories/CategoryNode.php000064400000025220152177723700012530 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Categories;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Helper class to load Categorytree
 *
 * @since  1.6
 */
class CategoryNode extends \JObject
{
	/**
	 * Primary key
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $id = null;

	/**
	 * The id of the category in the asset table
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $asset_id = null;

	/**
	 * The id of the parent of category in the asset table, 0 for category root
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $parent_id = null;

	/**
	 * The lft value for this category in the category tree
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $lft = null;

	/**
	 * The rgt value for this category in the category tree
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $rgt = null;

	/**
	 * The depth of this category's position in the category tree
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $level = null;

	/**
	 * The extension this category is associated with
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $extension = null;

	/**
	 * The menu title for the category (a short name)
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $title = null;

	/**
	 * The the alias for the category
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $alias = null;

	/**
	 * Description of the category.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $description = null;

	/**
	 * The publication status of the category
	 *
	 * @var    boolean
	 * @since  1.6
	 */
	public $published = null;

	/**
	 * Whether the category is or is not checked out
	 *
	 * @var    boolean
	 * @since  1.6
	 */
	public $checked_out = 0;

	/**
	 * The time at which the category was checked out
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $checked_out_time = 0;

	/**
	 * Access level for the category
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $access = null;

	/**
	 * JSON string of parameters
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $params = null;

	/**
	 * Metadata description
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $metadesc = null;

	/**
	 * Key words for metadata
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $metakey = null;

	/**
	 * JSON string of other metadata
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $metadata = null;

	/**
	 * The ID of the user who created the category
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $created_user_id = null;

	/**
	 * The time at which the category was created
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $created_time = null;

	/**
	 * The ID of the user who last modified the category
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $modified_user_id = null;

	/**
	 * The time at which the category was modified
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $modified_time = null;

	/**
	 * Nmber of times the category has been viewed
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $hits = null;

	/**
	 * The language for the category in xx-XX format
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $language = null;

	/**
	 * Number of items in this category or descendants of this category
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $numitems = null;

	/**
	 * Number of children items
	 *
	 * @var    integer
	 * @since  1.6
	 */
	public $childrennumitems = null;

	/**
	 * Slug fo the category (used in URL)
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $slug = null;

	/**
	 * Array of  assets
	 *
	 * @var    array
	 * @since  1.6
	 */
	public $assets = null;

	/**
	 * Parent Category object
	 *
	 * @var    CategoryNode
	 * @since  1.6
	 */
	protected $_parent = null;

	/**
	 * Array of Children
	 *
	 * @var    CategoryNode[]
	 * @since  1.6
	 */
	protected $_children = array();

	/**
	 * Path from root to this category
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected $_path = array();

	/**
	 * Category left of this one
	 *
	 * @var    CategoryNode
	 * @since  1.6
	 */
	protected $_leftSibling = null;

	/**
	 * Category right of this one
	 *
	 * @var    CategoryNode
	 * @since  1.6
	 */
	protected $_rightSibling = null;

	/**
	 * Flag if all children have been loaded
	 *
	 * @var    boolean
	 * @since  1.6
	 */
	protected $_allChildrenloaded = false;

	/**
	 * Constructor of this tree
	 *
	 * @var    CategoryNode
	 * @since  1.6
	 */
	protected $_constructor = null;

	/**
	 * Class constructor
	 *
	 * @param   array         $category     The category data.
	 * @param   CategoryNode  $constructor  The tree constructor.
	 *
	 * @since   1.6
	 */
	public function __construct($category = null, $constructor = null)
	{
		if ($category)
		{
			$this->setProperties($category);

			if ($constructor)
			{
				$this->_constructor = $constructor;
			}

			return true;
		}

		return false;
	}

	/**
	 * Set the parent of this category
	 *
	 * If the category already has a parent, the link is unset
	 *
	 * @param   CategoryNode|null  $parent  CategoryNode for the parent to be set or null
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function setParent($parent)
	{
		if ($parent instanceof CategoryNode || is_null($parent))
		{
			if (!is_null($this->_parent))
			{
				$key = array_search($this, $this->_parent->_children);
				unset($this->_parent->_children[$key]);
			}

			if (!is_null($parent))
			{
				$parent->_children[] = & $this;
			}

			$this->_parent = $parent;

			if ($this->id != 'root')
			{
				if ($this->parent_id != 1)
				{
					$this->_path = $parent->getPath();
				}

				$this->_path[$this->id] = $this->id . ':' . $this->alias;
			}

			if (count($parent->_children) > 1)
			{
				end($parent->_children);
				$this->_leftSibling = prev($parent->_children);
				$this->_leftSibling->_rightsibling = & $this;
			}
		}
	}

	/**
	 * Add child to this node
	 *
	 * If the child already has a parent, the link is unset
	 *
	 * @param   CategoryNode  $child  The child to be added.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function addChild($child)
	{
		if ($child instanceof CategoryNode)
		{
			$child->setParent($this);
		}
	}

	/**
	 * Remove a specific child
	 *
	 * @param   integer  $id  ID of a category
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function removeChild($id)
	{
		$key = array_search($this, $this->_parent->_children);
		unset($this->_parent->_children[$key]);
	}

	/**
	 * Get the children of this node
	 *
	 * @param   boolean  $recursive  False by default
	 *
	 * @return  CategoryNode[]  The children
	 *
	 * @since   1.6
	 */
	public function &getChildren($recursive = false)
	{
		if (!$this->_allChildrenloaded)
		{
			$temp = $this->_constructor->get($this->id, true);

			if ($temp)
			{
				$this->_children = $temp->getChildren();
				$this->_leftSibling = $temp->getSibling(false);
				$this->_rightSibling = $temp->getSibling(true);
				$this->setAllLoaded();
			}
		}

		if ($recursive)
		{
			$items = array();

			foreach ($this->_children as $child)
			{
				$items[] = $child;
				$items = array_merge($items, $child->getChildren(true));
			}

			return $items;
		}

		return $this->_children;
	}

	/**
	 * Get the parent of this node
	 *
	 * @return  CategoryNode
	 *
	 * @since   1.6
	 */
	public function getParent()
	{
		return $this->_parent;
	}

	/**
	 * Test if this node has children
	 *
	 * @return  boolean  True if there is a child
	 *
	 * @since   1.6
	 */
	public function hasChildren()
	{
		return count($this->_children);
	}

	/**
	 * Test if this node has a parent
	 *
	 * @return  boolean  True if there is a parent
	 *
	 * @since   1.6
	 */
	public function hasParent()
	{
		return $this->getParent() != null;
	}

	/**
	 * Function to set the left or right sibling of a category
	 *
	 * @param   CategoryNode  $sibling  CategoryNode object for the sibling
	 * @param   boolean       $right    If set to false, the sibling is the left one
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function setSibling($sibling, $right = true)
	{
		if ($right)
		{
			$this->_rightSibling = $sibling;
		}
		else
		{
			$this->_leftSibling = $sibling;
		}
	}

	/**
	 * Returns the right or left sibling of a category
	 *
	 * @param   boolean  $right  If set to false, returns the left sibling
	 *
	 * @return  CategoryNode|null  CategoryNode object with the sibling information or null if there is no sibling on that side.
	 *
	 * @since   1.6
	 */
	public function getSibling($right = true)
	{
		if (!$this->_allChildrenloaded)
		{
			$temp = $this->_constructor->get($this->id, true);
			$this->_children = $temp->getChildren();
			$this->_leftSibling = $temp->getSibling(false);
			$this->_rightSibling = $temp->getSibling(true);
			$this->setAllLoaded();
		}

		if ($right)
		{
			return $this->_rightSibling;
		}
		else
		{
			return $this->_leftSibling;
		}
	}

	/**
	 * Returns the category parameters
	 *
	 * @return  Registry
	 *
	 * @since   1.6
	 */
	public function getParams()
	{
		if (!($this->params instanceof Registry))
		{
			$this->params = new Registry($this->params);
		}

		return $this->params;
	}

	/**
	 * Returns the category metadata
	 *
	 * @return  Registry  A Registry object containing the metadata
	 *
	 * @since   1.6
	 */
	public function getMetadata()
	{
		if (!($this->metadata instanceof Registry))
		{
			$this->metadata = new Registry($this->metadata);
		}

		return $this->metadata;
	}

	/**
	 * Returns the category path to the root category
	 *
	 * @return  array
	 *
	 * @since   1.6
	 */
	public function getPath()
	{
		return $this->_path;
	}

	/**
	 * Returns the user that created the category
	 *
	 * @param   boolean  $modified_user  Returns the modified_user when set to true
	 *
	 * @return  \JUser  A \JUser object containing a userid
	 *
	 * @since   1.6
	 */
	public function getAuthor($modified_user = false)
	{
		if ($modified_user)
		{
			return \JFactory::getUser($this->modified_user_id);
		}

		return \JFactory::getUser($this->created_user_id);
	}

	/**
	 * Set to load all children
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function setAllLoaded()
	{
		$this->_allChildrenloaded = true;

		foreach ($this->_children as $child)
		{
			$child->setAllLoaded();
		}
	}

	/**
	 * Returns the number of items.
	 *
	 * @param   boolean  $recursive  If false number of children, if true number of descendants
	 *
	 * @return  integer  Number of children or descendants
	 *
	 * @since   1.6
	 */
	public function getNumItems($recursive = false)
	{
		if ($recursive)
		{
			$count = $this->numitems;

			foreach ($this->getChildren() as $child)
			{
				$count = $count + $child->getNumItems(true);
			}

			return $count;
		}

		return $this->numitems;
	}
}
src/Schema/ChangeSet.php000064400000016253152177723700011127 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Schema;

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');

/**
 * Contains a set of JSchemaChange objects for a particular instance of Joomla.
 * Each of these objects contains a DDL query that should have been run against
 * the database when this database was created or updated. This enables the
 * Installation Manager to check that the current database schema is up to date.
 *
 * @since  2.5
 */
class ChangeSet
{
	/**
	 * Array of ChangeItem objects
	 *
	 * @var    ChangeItem[]
	 * @since  2.5
	 */
	protected $changeItems = array();

	/**
	 * \JDatabaseDriver object
	 *
	 * @var    \JDatabaseDriver
	 * @since  2.5
	 */
	protected $db = null;

	/**
	 * Folder where SQL update files will be found
	 *
	 * @var    string
	 * @since  2.5
	 */
	protected $folder = null;

	/**
	 * The singleton instance of this object
	 *
	 * @var    ChangeSet
	 * @since  3.5.1
	 */
	protected static $instance;

	/**
	 * Constructor: builds array of $changeItems by processing the .sql files in a folder.
	 * The folder for the Joomla core updates is `administrator/components/com_admin/sql/updates/<database>`.
	 *
	 * @param   \JDatabaseDriver  $db      The current database object
	 * @param   string            $folder  The full path to the folder containing the update queries
	 *
	 * @since   2.5
	 */
	public function __construct($db, $folder = null)
	{
		$this->db = $db;
		$this->folder = $folder;
		$updateFiles = $this->getUpdateFiles();
		$updateQueries = $this->getUpdateQueries($updateFiles);

		foreach ($updateQueries as $obj)
		{
			$changeItem = ChangeItem::getInstance($db, $obj->file, $obj->updateQuery);

			if ($changeItem->queryType === 'UTF8CNV')
			{
				// Execute the special update query for utf8mb4 conversion status reset
				try
				{
					$this->db->setQuery($changeItem->updateQuery)->execute();
				}
				catch (\RuntimeException $e)
				{
					\JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
				}
			}
			else
			{
				// Normal change item
				$this->changeItems[] = $changeItem;
			}
		}

		// If on mysql, add a query at the end to check for utf8mb4 conversion status
		if ($this->db->getServerType() === 'mysql')
		{
			// Let the update query be something harmless which should always succeed
			$tmpSchemaChangeItem = ChangeItem::getInstance(
				$db,
				'database.php',
				'UPDATE ' . $this->db->quoteName('#__utf8_conversion')
				. ' SET ' . $this->db->quoteName('converted') . ' = 0;');

			// Set to not skipped
			$tmpSchemaChangeItem->checkStatus = 0;

			// Set the check query
			if ($this->db->hasUTF8mb4Support())
			{
				$converted = 2;
				$tmpSchemaChangeItem->queryType = 'UTF8_CONVERSION_UTF8MB4';
			}
			else
			{
				$converted = 1;
				$tmpSchemaChangeItem->queryType = 'UTF8_CONVERSION_UTF8';
			}

			$tmpSchemaChangeItem->checkQuery = 'SELECT '
				. $this->db->quoteName('converted')
				. ' FROM ' . $this->db->quoteName('#__utf8_conversion')
				. ' WHERE ' . $this->db->quoteName('converted') . ' = ' . $converted;

			// Set expected records from check query
			$tmpSchemaChangeItem->checkQueryExpected = 1;

			$tmpSchemaChangeItem->msgElements = array();

			$this->changeItems[] = $tmpSchemaChangeItem;
		}
	}

	/**
	 * Returns a reference to the ChangeSet object, only creating it if it doesn't already exist.
	 *
	 * @param   \JDatabaseDriver  $db      The current database object
	 * @param   string            $folder  The full path to the folder containing the update queries
	 *
	 * @return  ChangeSet
	 *
	 * @since   2.5
	 */
	public static function getInstance($db, $folder = null)
	{
		if (!is_object(static::$instance))
		{
			static::$instance = new ChangeSet($db, $folder);
		}

		return static::$instance;
	}

	/**
	 * Checks the database and returns an array of any errors found.
	 * Note these are not database errors but rather situations where
	 * the current schema is not up to date.
	 *
	 * @return   array Array of errors if any.
	 *
	 * @since    2.5
	 */
	public function check()
	{
		$errors = array();

		foreach ($this->changeItems as $item)
		{
			if ($item->check() === -2)
			{
				// Error found
				$errors[] = $item;
			}
		}

		return $errors;
	}

	/**
	 * Runs the update query to apply the change to the database
	 *
	 * @return  void
	 *
	 * @since   2.5
	 */
	public function fix()
	{
		$this->check();

		foreach ($this->changeItems as $item)
		{
			$item->fix();
		}
	}

	/**
	 * Returns an array of results for this set
	 *
	 * @return  array  associative array of changeitems grouped by unchecked, ok, error, and skipped
	 *
	 * @since   2.5
	 */
	public function getStatus()
	{
		$result = array('unchecked' => array(), 'ok' => array(), 'error' => array(), 'skipped' => array());

		foreach ($this->changeItems as $item)
		{
			switch ($item->checkStatus)
			{
				case 0:
					$result['unchecked'][] = $item;
					break;
				case 1:
					$result['ok'][] = $item;
					break;
				case -2:
					$result['error'][] = $item;
					break;
				case -1:
					$result['skipped'][] = $item;
					break;
			}
		}

		return $result;
	}

	/**
	 * Gets the current database schema, based on the highest version number.
	 * Note that the .sql files are named based on the version and date, so
	 * the file name of the last file should match the database schema version
	 * in the #__schemas table.
	 *
	 * @return  string  the schema version for the database
	 *
	 * @since   2.5
	 */
	public function getSchema()
	{
		$updateFiles = $this->getUpdateFiles();
		$result = new \SplFileInfo(array_pop($updateFiles));

		return $result->getBasename('.sql');
	}

	/**
	 * Get list of SQL update files for this database
	 *
	 * @return  array  list of sql update full-path names
	 *
	 * @since   2.5
	 */
	private function getUpdateFiles()
	{
		// Get the folder from the database name
		$sqlFolder = $this->db->getServerType();

		// For `mssql` server types, convert the type to `sqlazure`
		if ($sqlFolder === 'mssql')
		{
			$sqlFolder = 'sqlazure';
		}

		// Default folder to core com_admin
		if (!$this->folder)
		{
			$this->folder = JPATH_ADMINISTRATOR . '/components/com_admin/sql/updates/';
		}

		return \JFolder::files(
			$this->folder . '/' . $sqlFolder, '\.sql$', 1, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX'), array('^\..*', '.*~'), true
		);
	}

	/**
	 * Get array of SQL queries
	 *
	 * @param   array  $sqlfiles  Array of .sql update filenames.
	 *
	 * @return  array  Array of \stdClass objects where:
	 *                    file=filename,
	 *                    update_query = text of SQL update query
	 *
	 * @since   2.5
	 */
	private function getUpdateQueries(array $sqlfiles)
	{
		// Hold results as array of objects
		$result = array();

		foreach ($sqlfiles as $file)
		{
			$buffer = file_get_contents($file);

			// Create an array of queries from the sql file
			$queries = \JDatabaseDriver::splitSql($buffer);

			foreach ($queries as $query)
			{
				$fileQueries = new \stdClass;
				$fileQueries->file = $file;
				$fileQueries->updateQuery = $query;
				$result[] = $fileQueries;
			}
		}

		return $result;
	}
}
src/Schema/ChangeItem/PostgresqlChangeItem.php000064400000024264152177723700015363 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Schema\ChangeItem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Schema\ChangeItem;

/**
 * Checks the database schema against one PostgreSQL DDL query to see if it has been run.
 *
 * @since  3.0
 */
class PostgresqlChangeItem extends ChangeItem
{
	/**
	 * Checks a DDL query to see if it is a known type
	 * If yes, build a check query to see if the DDL has been run on the database.
	 * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated.
	 * The $msgElements contains the text to create the user message.
	 * The $checkQuery contains the SQL query to check whether the schema change has
	 * been run against the current database. The $queryType contains the type of
	 * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX).
	 * The $checkStatus field is set to zero if the query is created
	 *
	 * If not successful, $checkQuery is empty and , and $checkStatus is -1.
	 * For example, this will happen if the current line is a non-DDL statement.
	 *
	 * @return void
	 *
	 * @since  3.0
	 */
	protected function buildCheckQuery()
	{
		// Initialize fields in case we can't create a check query
		$this->checkStatus = -1; // change status to skipped

		$result = null;
		$splitIntoWords = "~'[^']*'(*SKIP)(*F)|\s+~";
		$splitIntoActions = "~'[^']*'(*SKIP)(*F)|\([^)]*\)(*SKIP)(*F)|,~";

		// Remove any newlines
		$this->updateQuery = str_replace("\n", '', $this->updateQuery);

		// Remove trailing whitespace and semicolon
		$this->updateQuery = rtrim($this->updateQuery, "; \t\n\r\0\x0B");

		// Fix up extra spaces around () and in general
		$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#');
		$replace = array('($3)', '$1');
		$updateQuery = preg_replace($find, $replace, $this->updateQuery);
		$wordArray = preg_split($splitIntoWords, $updateQuery, null, PREG_SPLIT_NO_EMPTY);

		$totalWords = count($wordArray);

		// First, make sure we have an array of at least 6 elements
		// if not, we can't make a check query for this one
		if ($totalWords < 6)
		{
			// Done with method
			return;
		}

		// We can only make check queries for alter table and create table queries
		$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);

		if ($command === 'ALTER TABLE')
		{
			// Check only the last action
			$actions = ltrim(substr($updateQuery, strpos($updateQuery, $wordArray[2]) + strlen($wordArray[2])));
			$actions = preg_split($splitIntoActions, $actions);

			// Get the last action
			$lastActionArray = preg_split($splitIntoWords, end($actions), null, PREG_SPLIT_NO_EMPTY);

			// Replace all actions by the last one
			array_splice($wordArray, 3, $totalWords, $lastActionArray);

			$alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]);

			if ($alterCommand === 'ADD COLUMN')
			{
				$result = 'SELECT column_name'
					. ' FROM information_schema.columns'
					. ' WHERE table_name='
					. $this->fixQuote($wordArray[2])
					. ' AND column_name=' . $this->fixQuote($wordArray[5]);

				$this->queryType = 'ADD_COLUMN';
				$this->msgElements = array(
					$this->fixQuote($wordArray[2]),
					$this->fixQuote($wordArray[5])
				);
			}
			elseif ($alterCommand === 'DROP COLUMN')
			{
				$result = 'SELECT column_name'
					. ' FROM information_schema.columns'
					. ' WHERE table_name='
					. $this->fixQuote($wordArray[2])
					. ' AND column_name=' . $this->fixQuote($wordArray[5]);

				$this->queryType = 'DROP_COLUMN';
				$this->checkQueryExpected = 0;
				$this->msgElements = array(
					$this->fixQuote($wordArray[2]),
					$this->fixQuote($wordArray[5])
				);
			}
			elseif ($alterCommand === 'ALTER COLUMN')
			{
				$alterAction = strtoupper($wordArray[6]);

				if ($alterAction === 'TYPE')
				{
					$type = implode(' ', array_slice($wordArray, 7));

					if ($pos = stripos($type, ' USING '))
					{
						$type = substr($type, 0, $pos);
					}

					if ($pos = strpos($type, '('))
					{
						$datatype = substr($type, 0, $pos);
					}
					else
					{
						$datatype = $type;
					}

					$result = 'SELECT column_name, data_type '
						. 'FROM information_schema.columns WHERE table_name='
						. $this->fixQuote($wordArray[2]) . ' AND column_name='
						. $this->fixQuote($wordArray[5])
						. ' AND data_type=' . $this->fixQuote($datatype);

					if ($datatype === 'character varying')
					{
						$result .= ' AND character_maximum_length = ' . (int) substr($type, $pos + 1);
					}

					$this->queryType = 'CHANGE_COLUMN_TYPE';
					$this->msgElements = array(
						$this->fixQuote($wordArray[2]),
						$this->fixQuote($wordArray[5]),
						$type
					);
				}
				elseif ($alterAction === 'SET')
				{
					$alterType = strtoupper($wordArray[7]);

					if ($alterType === 'NOT' && strtoupper($wordArray[8]) === 'NULL')
					{
						$result = 'SELECT column_name, data_type, is_nullable'
							. ' FROM information_schema.columns'
							. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
							. ' AND column_name=' . $this->fixQuote($wordArray[5])
							. ' AND is_nullable=' . $this->fixQuote('NO');

						$this->queryType = 'CHANGE_COLUMN_TYPE';
						$this->msgElements = array(
							$this->fixQuote($wordArray[2]),
							$this->fixQuote($wordArray[5]),
							'NOT NULL'
						);
					}
					elseif ($alterType === 'DEFAULT')
					{
						$result = 'SELECT column_name, data_type, is_nullable'
							. ' FROM information_schema.columns'
							. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
							. ' AND column_name=' . $this->fixQuote($wordArray[5])
							. ' AND (CASE (position(' . $this->db->quote('::') . ' in column_default))'
							. ' WHEN 0 THEN '
							. ' column_default = ' . $this->db->quote($wordArray[8])
							. ' ELSE '
							. ' substring(column_default, 1, (position(' . $this->db->quote('::')
							. ' in column_default) -1))  = ' . $this->db->quote($wordArray[8])
							. ' END)';

						$this->queryType = 'CHANGE_COLUMN_TYPE';
						$this->msgElements = array(
							$this->fixQuote($wordArray[2]),
							$this->fixQuote($wordArray[5]),
							'DEFAULT ' . $wordArray[8]
						);
					}
				}
				elseif ($alterAction === 'DROP')
				{
					$alterType = strtoupper($wordArray[7]);

					if ($alterType === 'DEFAULT')
					{
						$result = 'SELECT column_name, data_type, is_nullable , column_default'
							. ' FROM information_schema.columns'
							. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
							. ' AND column_name=' . $this->fixQuote($wordArray[5])
							. ' AND column_default IS NOT NULL';

						$this->queryType = 'CHANGE_COLUMN_TYPE';
						$this->checkQueryExpected = 0;
						$this->msgElements = array(
							$this->fixQuote($wordArray[2]),
							$this->fixQuote($wordArray[5]),
							'NOT DEFAULT'
						);
					}
					elseif ($alterType === 'NOT' && strtoupper($wordArray[8]) === 'NULL')
					{
						$result = 'SELECT column_name, data_type, is_nullable , column_default'
							. ' FROM information_schema.columns'
							. ' WHERE table_name=' . $this->fixQuote($wordArray[2])
							. ' AND column_name=' . $this->fixQuote($wordArray[5])
							. ' AND is_nullable = ' . $this->fixQuote('NO');

						$this->queryType = 'CHANGE_COLUMN_TYPE';
						$this->checkQueryExpected = 0;
						$this->msgElements = array(
							$this->fixQuote($wordArray[2]),
							$this->fixQuote($wordArray[5]),
							'NULL'
						);
					}
				}
			}
		}
		elseif ($command === 'DROP INDEX')
		{
			if (strtoupper($wordArray[2] . $wordArray[3]) === 'IFEXISTS')
			{
				$idx = $this->fixQuote($wordArray[4]);
			}
			else
			{
				$idx = $this->fixQuote($wordArray[2]);
			}

			$result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx;
			$this->queryType = 'DROP_INDEX';
			$this->checkQueryExpected = 0;
			$this->msgElements = array($this->fixQuote($idx));
		}
		elseif ($command === 'CREATE INDEX' || (strtoupper($command . $wordArray[2]) === 'CREATE UNIQUE INDEX'))
		{
			if ($wordArray[1] === 'UNIQUE')
			{
				$idx = $this->fixQuote($wordArray[3]);
				$table = $this->fixQuote($wordArray[5]);
			}
			else
			{
				$idx = $this->fixQuote($wordArray[2]);
				$table = $this->fixQuote($wordArray[4]);
			}

			$result = 'SELECT * FROM pg_indexes WHERE indexname=' . $idx . ' AND tablename=' . $table;
			$this->queryType = 'ADD_INDEX';
			$this->checkQueryExpected = 1;
			$this->msgElements = array($table, $idx);
		}

		if ($command === 'CREATE TABLE')
		{
			if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) === 'IFNOTEXISTS')
			{
				$table = $this->fixQuote($wordArray[5]);
			}
			else
			{
				$table = $this->fixQuote($wordArray[2]);
			}

			$result = 'SELECT table_name FROM information_schema.tables WHERE table_name=' . $table;
			$this->queryType = 'CREATE_TABLE';
			$this->checkQueryExpected = 1;
			$this->msgElements = array($table);
		}

		// Set fields based on results
		if ($this->checkQuery = $result)
		{
			// Unchecked status
			$this->checkStatus = 0;
		}
		else
		{
			// Skipped
			$this->checkStatus = -1;
		}
	}

	/**
	 * Fix up integer. Fixes problem with PostgreSQL integer descriptions.
	 * If you change a column to "integer unsigned" it shows
	 * as "int(10) unsigned" in the check query.
	 *
	 * @param   string  $type1  the column type
	 * @param   string  $type2  the column attributes
	 *
	 * @return  string  The original or changed column type.
	 *
	 * @since   3.0
	 */
	private function fixInteger($type1, $type2)
	{
		$result = $type1;

		if (strtolower($type1) === 'integer' && strtolower(substr($type2, 0, 8)) === 'unsigned')
		{
			$result = 'unsigned int(10)';
		}

		return $result;
	}

	/**
	 * Fixes up a string for inclusion in a query.
	 * Replaces name quote character with normal quote for literal.
	 * Drops trailing semicolon. Injects the database prefix.
	 *
	 * @param   string  $string  The input string to be cleaned up.
	 *
	 * @return  string  The modified string.
	 *
	 * @since   3.0
	 */
	private function fixQuote($string)
	{
		$string = str_replace('"', '', $string);
		$string = str_replace(';', '', $string);
		$string = str_replace('#__', $this->db->getPrefix(), $string);

		return $this->db->quote($string);
	}
}
src/Schema/ChangeItem/MysqlChangeItem.php000064400000026755152177723700014334 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Schema\ChangeItem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Schema\ChangeItem;

/**
 * Checks the database schema against one MySQL DDL query to see if it has been run.
 *
 * @since  2.5
 */
class MysqlChangeItem extends ChangeItem
{
	/**
	 * Checks a DDL query to see if it is a known type
	 * If yes, build a check query to see if the DDL has been run on the database.
	 * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated.
	 * The $msgElements contains the text to create the user message.
	 * The $checkQuery contains the SQL query to check whether the schema change has
	 * been run against the current database. The $queryType contains the type of
	 * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX).
	 * The $checkStatus field is set to zero if the query is created
	 *
	 * If not successful, $checkQuery is empty and , and $checkStatus is -1.
	 * For example, this will happen if the current line is a non-DDL statement.
	 *
	 * @return void
	 *
	 * @since  2.5
	 */
	protected function buildCheckQuery()
	{
		// Initialize fields in case we can't create a check query
		$this->checkStatus = -1; // change status to skipped
		$result = null;

		// Remove any newlines
		$this->updateQuery = str_replace("\n", '', $this->updateQuery);

		// Fix up extra spaces around () and in general
		$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#');
		$replace = array('($3)', '$1');
		$updateQuery = preg_replace($find, $replace, $this->updateQuery);
		$wordArray = preg_split("~'[^']*'(*SKIP)(*F)|\s+~u", trim($updateQuery, "; \t\n\r\0\x0B"));

		// First, make sure we have an array of at least 6 elements
		// if not, we can't make a check query for this one
		if (count($wordArray) < 6)
		{
			// Done with method
			return;
		}

		// We can only make check queries for alter table and create table queries
		$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);

		// Check for special update statement to reset utf8mb4 conversion status
		if (($command === 'UPDATE `#__UTF8_CONVERSION`'
			|| $command === 'UPDATE #__UTF8_CONVERSION')
			&& strtoupper($wordArray[2]) === 'SET'
			&& strtolower(substr(str_replace('`', '', $wordArray[3]), 0, 9)) === 'converted')
		{
			// Statement is special statement to reset conversion status
			$this->queryType = 'UTF8CNV';

			// Done with method
			return;
		}

		if ($command === 'ALTER TABLE')
		{
			$alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]);

			if ($alterCommand === 'ADD COLUMN')
			{
				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[5]);
				$this->queryType = 'ADD_COLUMN';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]));
			}
			elseif ($alterCommand === 'ADD INDEX' || $alterCommand === 'ADD KEY')
			{
				if ($pos = strpos($wordArray[5], '('))
				{
					$index = $this->fixQuote(substr($wordArray[5], 0, $pos));
				}
				else
				{
					$index = $this->fixQuote($wordArray[5]);
				}

				$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index;
				$this->queryType = 'ADD_INDEX';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif ($alterCommand === 'ADD UNIQUE')
			{
				$idxIndexName = 5;

				if (isset($wordArray[6]))
				{
					$addCmdCheck = strtoupper($wordArray[5]);

					if ($addCmdCheck === 'INDEX' || $addCmdCheck === 'KEY')
					{
						$idxIndexName = 6;
					}
				}

				if ($pos = strpos($wordArray[$idxIndexName], '('))
				{
					$index = $this->fixQuote(substr($wordArray[$idxIndexName], 0, $pos));
				}
				else
				{
					$index = $this->fixQuote($wordArray[$idxIndexName]);
				}

				$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index;
				$this->queryType = 'ADD_INDEX';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif ($alterCommand === 'DROP INDEX' || $alterCommand === 'DROP KEY')
			{
				$index = $this->fixQuote($wordArray[5]);
				$result = 'SHOW INDEXES IN ' . $wordArray[2] . ' WHERE Key_name = ' . $index;
				$this->queryType = 'DROP_INDEX';
				$this->checkQueryExpected = 0;
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif ($alterCommand === 'DROP COLUMN')
			{
				$index = $this->fixQuote($wordArray[5]);
				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE Field = ' . $index;
				$this->queryType = 'DROP_COLUMN';
				$this->checkQueryExpected = 0;
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif (strtoupper($wordArray[3]) === 'MODIFY')
			{
				// Kludge to fix problem with "integer unsigned"
				$type = $wordArray[5];

				if (isset($wordArray[6]))
				{
					$type = $this->fixInteger($wordArray[5], $wordArray[6]);
				}

				// Detect changes in NULL and in DEFAULT column attributes
				$changesArray = array_slice($wordArray, 6);
				$defaultCheck = $this->checkDefault($changesArray, $type);
				$nullCheck = $this->checkNull($changesArray);

				/**
				 * When we made the UTF8MB4 conversion then text becomes medium text - so loosen the checks to these two types
				 * otherwise (for example) the profile fields profile_value check fails - see https://github.com/joomla/joomla-cms/issues/9258
				 */
				$typeCheck = $this->fixUtf8mb4TypeChecks($type);

				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[4])
					. ' AND ' . $typeCheck
					. ($defaultCheck ? ' AND ' . $defaultCheck : '')
					. ($nullCheck ? ' AND ' . $nullCheck : '');
				$this->queryType = 'CHANGE_COLUMN_TYPE';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4]), $type);
			}
			elseif (strtoupper($wordArray[3]) === 'CHANGE')
			{
				// Kludge to fix problem with "integer unsigned"
				$type = $wordArray[6];

				if (isset($wordArray[7]))
				{
					$type = $this->fixInteger($wordArray[6], $wordArray[7]);
				}
				
				// Detect changes in NULL and in DEFAULT column attributes
				$changesArray = array_slice($wordArray, 6);
				$defaultCheck = $this->checkDefault($changesArray, $type);
				$nullCheck = $this->checkNull($changesArray);

				/**
				 * When we made the UTF8MB4 conversion then text becomes medium text - so loosen the checks to these two types
				 * otherwise (for example) the profile fields profile_value check fails - see https://github.com/joomla/joomla-cms/issues/9258
				 */
				$typeCheck = $this->fixUtf8mb4TypeChecks($type);

				$result = 'SHOW COLUMNS IN ' . $wordArray[2] . ' WHERE field = ' . $this->fixQuote($wordArray[5])
					. ' AND ' . $typeCheck
					. ($defaultCheck ? ' AND ' . $defaultCheck : '')
					. ($nullCheck ? ' AND ' . $nullCheck : '');
				$this->queryType = 'CHANGE_COLUMN_TYPE';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]), $type);
			}
		}

		if ($command === 'CREATE TABLE')
		{
			if (strtoupper($wordArray[2] . $wordArray[3] . $wordArray[4]) === 'IFNOTEXISTS')
			{
				$table = $wordArray[5];
			}
			else
			{
				$table = $wordArray[2];
			}

			$result = 'SHOW TABLES LIKE ' . $this->fixQuote($table);
			$this->queryType = 'CREATE_TABLE';
			$this->msgElements = array($this->fixQuote($table));
		}

		// Set fields based on results
		if ($this->checkQuery = $result)
		{
			// Unchecked status
			$this->checkStatus = 0;
		}
		else
		{
			// Skipped
			$this->checkStatus = -1;
		}
	}

	/**
	 * Fix up integer. Fixes problem with MySQL integer descriptions.
	 * If you change a column to "integer unsigned" it shows
	 * as "int(10) unsigned" in the check query.
	 *
	 * @param   string  $type1  the column type
	 * @param   string  $type2  the column attributes
	 *
	 * @return  string  The original or changed column type.
	 *
	 * @since   2.5
	 */
	private function fixInteger($type1, $type2)
	{
		$result = $type1;

		if (strtolower($type1) === 'integer' && strtolower(substr($type2, 0, 8)) === 'unsigned')
		{
			$result = 'int(10) unsigned';
		}
		elseif (strtolower(substr($type2, 0, 8)) === 'unsigned')
		{
			$result = $type1 . ' unsigned';
		}

		return $result;
	}

	/**
	 * Fixes up a string for inclusion in a query.
	 * Replaces name quote character with normal quote for literal.
	 * Drops trailing semicolon. Injects the database prefix.
	 *
	 * @param   string  $string  The input string to be cleaned up.
	 *
	 * @return  string  The modified string.
	 *
	 * @since   2.5
	 */
	private function fixQuote($string)
	{
		$string = str_replace('`', '', $string);
		$string = str_replace(';', '', $string);
		$string = str_replace('#__', $this->db->getPrefix(), $string);

		return $this->db->quote($string);
	}

	/**
	 * Make check query for column changes/modifications tolerant
	 * for automatic type changes of text columns, e.g. from TEXT
	 * to MEDIUMTEXT, after comnversion from utf8 to utf8mb4
	 *
	 * @param   string  $type  The column type found in the update query
	 *
	 * @return  string  The condition for type check in the check query
	 *
	 * @since   3.5
	 */
	private function fixUtf8mb4TypeChecks($type)
	{
		$fixedType = str_replace(';', '', $type);

		if ($this->db->hasUTF8mb4Support())
		{
			$uType = strtoupper($fixedType);

			if ($uType === 'TINYTEXT')
			{
				$typeCheck = 'type IN (' . $this->db->quote('TINYTEXT') . ',' . $this->db->quote('TEXT') . ')';
			}
			elseif ($uType === 'TEXT')
			{
				$typeCheck = 'type IN (' . $this->db->quote('TEXT') . ',' . $this->db->quote('MEDIUMTEXT') . ')';
			}
			elseif ($uType === 'MEDIUMTEXT')
			{
				$typeCheck = 'type IN (' . $this->db->quote('MEDIUMTEXT') . ',' . $this->db->quote('LONGTEXT') . ')';
			}
			else
			{
				$typeCheck = 'type = ' . $this->db->quote($fixedType);
			}
		}
		else
		{
			$typeCheck = 'type = ' . $this->db->quote($fixedType);
		}

		return $typeCheck;
	}

	/**
	 * Create query clause for column changes/modifications for NULL attribute
	 *
	 * @param   array  $changesArray  The array of words after COLUMN name
	 *
	 * @return  string  The query clause for NULL check in the check query
	 *
	 * @since   3.8.6
	 */
	private function checkNull($changesArray)
	{
		// Find NULL keyword
		$index = array_search('null', array_map('strtolower', $changesArray));

		// Create the check
		if ($index !== false)
		{
			if ($index == 0 || strtolower($changesArray[$index - 1]) !== 'not')
			{
				return ' `null` = ' . $this->db->quote('YES');
			}
			else
			{
				return ' `null` = ' . $this->db->quote('NO');
			}
		}

		return false;
	}

	/**
	 * Create query clause for column changes/modifications for DEFAULT attribute
	 *
	 * @param   array   $changesArray  The array of words after COLUMN name
	 * @param   string  $type          The type of the COLUMN
	 *
	 * @return  string  The query clause for DEFAULT check in the check query
	 *
	 * @since   3.8.6
	 */
	private function checkDefault($changesArray, $type)
	{
		// Skip types that do not support default values
		$type = strtolower($type);
		if (substr($type, -4) === 'text' || substr($type, -4) === 'blob')
		{
			return false;
		}

		// Find DEFAULT keyword
		$index = array_search('default', array_map('strtolower', $changesArray));
	
		// Create the check
		if ($index !== false)
		{
			if (strtolower($changesArray[$index + 1]) === 'null')
			{
				return ' `default` IS NULL';
			}
			else
			{
				return ' `default` = ' . $changesArray[$index + 1];
			}
		}

		return false;
	}
}
src/Schema/ChangeItem/SqlsrvChangeItem.php000064400000011571152177723700014507 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Schema\ChangeItem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Schema\ChangeItem;

/**
 * Checks the database schema against one SQL Server DDL query to see if it has been run.
 *
 * @since  2.5
 */
class SqlsrvChangeItem extends ChangeItem
{
	/**
	 * Checks a DDL query to see if it is a known type
	 * If yes, build a check query to see if the DDL has been run on the database.
	 * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated.
	 * The $msgElements contains the text to create the user message.
	 * The $checkQuery contains the SQL query to check whether the schema change has
	 * been run against the current database. The $queryType contains the type of
	 * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX).
	 * The $checkStatus field is set to zero if the query is created
	 *
	 * If not successful, $checkQuery is empty and , and $checkStatus is -1.
	 * For example, this will happen if the current line is a non-DDL statement.
	 *
	 * @return void
	 *
	 * @since  2.5
	 */
	protected function buildCheckQuery()
	{
		// Initialize fields in case we can't create a check query
		$this->checkStatus = -1; // change status to skipped
		$result = null;

		// Remove any newlines
		$this->updateQuery = str_replace("\n", '', $this->updateQuery);

		// Fix up extra spaces around () and in general
		$find = array('#((\s*)\(\s*([^)\s]+)\s*)(\))#', '#(\s)(\s*)#');
		$replace = array('($3)', '$1');
		$updateQuery = preg_replace($find, $replace, $this->updateQuery);
		$wordArray = explode(' ', $updateQuery);

		// First, make sure we have an array of at least 6 elements
		// if not, we can't make a check query for this one
		if (count($wordArray) < 6)
		{
			// Done with method
			return;
		}

		// We can only make check queries for alter table and create table queries
		$command = strtoupper($wordArray[0] . ' ' . $wordArray[1]);

		if ($command === 'ALTER TABLE')
		{
			$alterCommand = strtoupper($wordArray[3] . ' ' . $wordArray[4]);

			if ($alterCommand === 'ADD')
			{
				$result = 'SELECT * FROM INFORMATION_SCHEMA.Columns ' . $wordArray[2] . ' WHERE COLUMN_NAME = ' . $this->fixQuote($wordArray[5]);
				$this->queryType = 'ADD';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[5]));
			}
			elseif ($alterCommand === 'CREATE INDEX')
			{
				$index = $this->fixQuote(substr($wordArray[5], 0, strpos($wordArray[5], '(')));
				$result = 'SELECT * FROM SYS.INDEXES ' . $wordArray[2] . ' WHERE name = ' . $index;
				$this->queryType = 'CREATE INDEX';
				$this->msgElements = array($this->fixQuote($wordArray[2]), $index);
			}
			elseif (strtoupper($wordArray[3]) === 'MODIFY' || strtoupper($wordArray[3]) === 'CHANGE')
			{
				$result = 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS  WHERE table_name = ' . $this->fixQuote($wordArray[2]);
				$this->queryType = 'ALTER COLUMN COLUMN_NAME =' . $this->fixQuote($wordArray[4]);
				$this->msgElements = array($this->fixQuote($wordArray[2]), $this->fixQuote($wordArray[4]));
			}
		}

		if ($command === 'CREATE TABLE')
		{
			$table = $wordArray[2];
			$result = 'SELECT * FROM sys.TABLES WHERE NAME = ' . $this->fixQuote($table);
			$this->queryType = 'CREATE_TABLE';
			$this->msgElements = array($this->fixQuote($table));
		}

		// Set fields based on results
		if ($this->checkQuery = $result)
		{
			// Unchecked status
			$this->checkStatus = 0;
		}
		else
		{
			// Skipped
			$this->checkStatus = -1;
		}
	}

	/**
	 * Fix up integer. Fixes problem with MySQL integer descriptions.
	 * If you change a column to "integer unsigned" it shows
	 * as "int(10) unsigned" in the check query.
	 *
	 * @param   string  $type1  the column type
	 * @param   string  $type2  the column attributes
	 *
	 * @return  string  The original or changed column type.
	 *
	 * @since   2.5
	 */
	private function fixInteger($type1, $type2)
	{
		$result = $type1;

		if (strtolower($type1) === 'integer' && strtolower(substr($type2, 0, 8)) === 'unsigned')
		{
			$result = 'int';
		}

		return $result;
	}

	/**
	 * Fixes up a string for inclusion in a query.
	 * Replaces name quote character with normal quote for literal.
	 * Drops trailing semicolon. Injects the database prefix.
	 *
	 * @param   string  $string  The input string to be cleaned up.
	 *
	 * @return  string  The modified string.
	 *
	 * @since   2.5
	 */
	private function fixQuote($string)
	{
		$string = str_replace('[', '', $string);
		$string = str_replace(']', '', $string);
		$string = str_replace('"', '', $string);
		$string = str_replace(';', '', $string);
		$string = str_replace('#__', $this->db->getPrefix(), $string);

		return $this->db->quote($string);
	}
}
src/Schema/ChangeItem.php000064400000014667152177723700011301 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Schema;

defined('JPATH_PLATFORM') or die;

/**
 * Each object represents one query, which is one line from a DDL SQL query.
 * This class is used to check the site's database to see if the DDL query has been run.
 * If not, it provides the ability to fix the database by re-running the DDL query.
 * The queries are parsed from the update files in the folder
 * `administrator/components/com_admin/sql/updates/<database>`.
 * These updates are run automatically if the site was updated using com_installer.
 * However, it is possible that the program files could be updated without udpating
 * the database (for example, if a user just copies the new files over the top of an
 * existing installation).
 *
 * This is an abstract class. We need to extend it for each database and add a
 * buildCheckQuery() method that creates the query to check that a DDL query has been run.
 *
 * @since  2.5
 */
abstract class ChangeItem
{
	/**
	 * Update file: full path file name where query was found
	 *
	 * @var    string
	 * @since  2.5
	 */
	public $file = null;

	/**
	 * Update query: query used to change the db schema (one line from the file)
	 *
	 * @var    string
	 * @since  2.5
	 */
	public $updateQuery = null;

	/**
	 * Check query: query used to check the db schema
	 *
	 * @var    string
	 * @since  2.5
	 */
	public $checkQuery = null;

	/**
	 * Check query result: expected result of check query if database is up to date
	 *
	 * @var    string
	 * @since  2.5
	 */
	public $checkQueryExpected = 1;

	/**
	 * \JDatabaseDriver object
	 *
	 * @var    \JDatabaseDriver
	 * @since  2.5
	 */
	public $db = null;

	/**
	 * Query type: To be used in building a language key for a
	 * message to tell user what was checked / changed
	 * Possible values: ADD_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX
	 *
	 * @var    string
	 * @since  2.5
	 */
	public $queryType = null;

	/**
	 * Array with values for use in a \JText::sprintf statment indicating what was checked
	 *
	 * Tells you what the message should be, based on which elements are defined, as follows:
	 *     For ADD_TABLE: table
	 *     For ADD_COLUMN: table, column
	 *     For CHANGE_COLUMN_TYPE: table, column, type
	 *     For ADD_INDEX: table, index
	 *
	 * @var    array
	 * @since  2.5
	 */
	public $msgElements = array();

	/**
	 * Checked status
	 *
	 * @var    integer   0=not checked, -1=skipped, -2=failed, 1=succeeded
	 * @since  2.5
	 */
	public $checkStatus = 0;

	/**
	 * Rerun status
	 *
	 * @var    int   0=not rerun, -1=skipped, -2=failed, 1=succeeded
	 * @since  2.5
	 */
	public $rerunStatus = 0;

	/**
	 * Constructor: builds check query and message from $updateQuery
	 *
	 * @param   \JDatabaseDriver  $db     Database connector object
	 * @param   string            $file   Full path name of the sql file
	 * @param   string            $query  Text of the sql query (one line of the file)
	 *
	 * @since   2.5
	 */
	public function __construct($db, $file, $query)
	{
		$this->updateQuery = $query;
		$this->file = $file;
		$this->db = $db;
		$this->buildCheckQuery();
	}

	/**
	 * Returns a reference to the ChangeItem object.
	 *
	 * @param   \JDatabaseDriver  $db     Database connector object
	 * @param   string            $file   Full path name of the sql file
	 * @param   string            $query  Text of the sql query (one line of the file)
	 *
	 * @return  ChangeItem  instance based on the database driver
	 *
	 * @since   2.5
	 * @throws  \RuntimeException if class for database driver not found
	 */
	public static function getInstance($db, $file, $query)
	{
		// Get the class name
		$serverType = $db->getServerType();

		// For `mssql` server types, convert the type to `sqlsrv`
		if ($serverType === 'mssql')
		{
			$serverType = 'sqlsrv';
		}

		$class = '\\Joomla\\CMS\\Schema\\ChangeItem\\' . ucfirst($serverType) . 'ChangeItem';

		// If the class exists, return it.
		if (class_exists($class))
		{
			return new $class($db, $file, $query);
		}

		throw new \RuntimeException(sprintf('ChangeItem child class not found for the %s database driver', $serverType), 500);
	}

	/**
	 * Checks a DDL query to see if it is a known type
	 * If yes, build a check query to see if the DDL has been run on the database.
	 * If successful, the $msgElements, $queryType, $checkStatus and $checkQuery fields are populated.
	 * The $msgElements contains the text to create the user message.
	 * The $checkQuery contains the SQL query to check whether the schema change has
	 * been run against the current database. The $queryType contains the type of
	 * DDL query that was run (for example, CREATE_TABLE, ADD_COLUMN, CHANGE_COLUMN_TYPE, ADD_INDEX).
	 * The $checkStatus field is set to zero if the query is created
	 *
	 * If not successful, $checkQuery is empty and , and $checkStatus is -1.
	 * For example, this will happen if the current line is a non-DDL statement.
	 *
	 * @return void
	 *
	 * @since  2.5
	 */
	abstract protected function buildCheckQuery();

	/**
	 * Runs the check query and checks that 1 row is returned
	 * If yes, return true, otherwise return false
	 *
	 * @return  boolean  true on success, false otherwise
	 *
	 * @since  2.5
	 */
	public function check()
	{
		$this->checkStatus = -1;

		if ($this->checkQuery)
		{
			$this->db->setQuery($this->checkQuery);

			try
			{
				$rows = $this->db->loadRowList(0);
			}
			catch (\RuntimeException $e)
			{
				// Still render the error message from the Exception object
				\JFactory::getApplication()->enqueueMessage($e->getMessage(), 'error');
				$this->checkStatus = -2;

				return $this->checkStatus;
			}

			if (count($rows) === $this->checkQueryExpected)
			{
				$this->checkStatus = 1;

				return $this->checkStatus;
			}

			$this->checkStatus = -2;
		}

		return $this->checkStatus;
	}

	/**
	 * Runs the update query to apply the change to the database
	 *
	 * @return  void
	 *
	 * @since   2.5
	 */
	public function fix()
	{
		if ($this->checkStatus === -2)
		{
			// At this point we have a failed query
			$query = $this->db->convertUtf8mb4QueryToUtf8($this->updateQuery);
			$this->db->setQuery($query);

			if ($this->db->execute())
			{
				if ($this->check())
				{
					$this->checkStatus = 1;
					$this->rerunStatus = 1;
				}
				else
				{
					$this->rerunStatus = -2;
				}
			}
			else
			{
				$this->rerunStatus = -2;
			}
		}
	}
}
src/Filter/Wrapper/OutputFilterWrapper.php000064400000010012152177723700014725 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filter\Wrapper;

defined('JPATH_PLATFORM') or die;

use Joomla\Filter\OutputFilter;

/**
 * Wrapper class for OutputFilter
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
 */
class OutputFilterWrapper
{
	/**
	 * Helper wrapper method for objectHTMLSafe
	 *
	 * @param   object   &$mixed        An object to be parsed.
	 * @param   integer  $quote_style   The optional quote style for the htmlspecialchars function.
	 * @param   mixed    $exclude_keys  An optional string single field name or array of field names not.
	 *
	 * @return  void
	 *
	 * @see     OutputFilter::objectHTMLSafe()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function objectHTMLSafe(&$mixed, $quote_style = 3, $exclude_keys = '')
	{
		return OutputFilter::objectHTMLSafe($mixed, $quote_style, $exclude_keys);
	}

	/**
	 * Helper wrapper method for linkXHTMLSafe
	 *
	 * @param   string  $input  String to process.
	 *
	 * @return  string  Processed string.
	 *
	 * @see     OutputFilter::linkXHTMLSafe()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function linkXHTMLSafe($input)
	{
		return OutputFilter::linkXHTMLSafe($input);
	}

	/**
	 * Helper wrapper method for stringURLSafe
	 *
	 * @param   string  $string  String to process.
	 *
	 * @return  string  Processed string.
	 *
	 * @see     OutputFilter::stringURLSafe()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function stringURLSafe($string)
	{
		return OutputFilter::stringURLSafe($string);
	}

	/**
	 * Helper wrapper method for stringURLUnicodeSlug
	 *
	 * @param   string  $string  String to process.
	 *
	 * @return  string  Processed string.
	 *
	 * @see     OutputFilter::stringURLUnicodeSlug()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function stringURLUnicodeSlug($string)
	{
		return OutputFilter::stringURLUnicodeSlug($string);
	}

	/**
	 * Helper wrapper method for ampReplace
	 *
	 * @param   string  $text  Text to process.
	 *
	 * @return  string  Processed string.
	 *
	 * @see     OutputFilter::ampReplace()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function ampReplace($text)
	{
		return OutputFilter::ampReplace($text);
	}

	/**
	 * Helper wrapper method for _ampReplaceCallback
	 *
	 * @param   string  $m  String to process.
	 *
	 * @return  string  Replaced string.
	 *
	 * @see     OutputFilter::_ampReplaceCallback()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function _ampReplaceCallback($m)
	{
		return OutputFilter::_ampReplaceCallback($m);
	}

	/**
	 * Helper wrapper method for cleanText
	 *
	 * @param   string  &$text  Text to clean.
	 *
	 * @return  string  Cleaned text.
	 *
	 * @see     OutputFilter::cleanText()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function cleanText(&$text)
	{
		return OutputFilter::cleanText($text);
	}

	/**
	 * Helper wrapper method for stripImages
	 *
	 * @param   string  $string  Sting to be cleaned.
	 *
	 * @return  string  Cleaned string.
	 *
	 * @see     OutputFilter::stripImages()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function stripImages($string)
	{
		return OutputFilter::stripImages($string);
	}

	/**
	 * Helper wrapper method for stripIframes
	 *
	 * @param   string  $string  Sting to be cleaned.
	 *
	 * @return  string  Cleaned string.
	 *
	 * @see     OutputFilter::stripIframes()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Filter\OutputFilter` directly
	 */
	public function stripIframes($string)
	{
		return OutputFilter::stripIframes($string);
	}
}
src/Filter/OutputFilter.php000064400000006153152177723700011757 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filter;

defined('JPATH_PLATFORM') or die;

use Joomla\Filter\OutputFilter as BaseOutputFilter;
use Joomla\String\StringHelper;
use Joomla\CMS\Language\Language;

/**
 * OutputFilter
 *
 * @since  1.7.0
 */
class OutputFilter extends BaseOutputFilter
{
	/**
	 * This method processes a string and replaces all instances of & with &amp; in links only.
	 *
	 * @param   string  $input  String to process
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.7.0
	 */
	public static function linkXHTMLSafe($input)
	{
		$regex = 'href="([^"]*(&(amp;){0})[^"]*)*?"';

		return preg_replace_callback("#$regex#i", array('\\Joomla\\CMS\\Filter\\OutputFilter', '_ampReplaceCallback'), $input);
	}

	/**
	 * This method processes a string and escapes it for use in JavaScript
	 *
	 * @param   string  $string  String to process
	 *
	 * @return  string  Processed text
	 */
	public static function stringJSSafe($string)
	{
		for ($i = 0, $l = strlen($string), $new_str = ''; $i < $l; $i++)
		{
			$new_str .= (ord(substr($string, $i, 1)) < 16 ? '\\x0' : '\\x') . dechex(ord(substr($string, $i, 1)));
		}

		return $new_str;
	}

	/**
	 * This method processes a string and replaces all accented UTF-8 characters by unaccented
	 * ASCII-7 "equivalents", whitespaces are replaced by hyphens and the string is lowercase.
	 *
	 * @param   string  $string    String to process
	 * @param   string  $language  Language to transilterate to
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.7.0
	 */
	public static function stringURLSafe($string, $language = '')
	{
		// Remove any '-' from the string since they will be used as concatenaters
		$str = str_replace('-', ' ', $string);

		// Transliterate on the language requested (fallback to current language if not specified)
		$lang = $language == '' || $language == '*' ? \JFactory::getLanguage() : Language::getInstance($language);
		$str = $lang->transliterate($str);

		// Trim white spaces at beginning and end of alias and make lowercase
		$str = trim(StringHelper::strtolower($str));

		// Remove any duplicate whitespace, and ensure all characters are alphanumeric
		$str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-', $str);

		// Trim dashes at beginning and end of alias
		$str = trim($str, '-');

		return $str;
	}

	/**
	 * Callback method for replacing & with &amp; in a string
	 *
	 * @param   string  $m  String to process
	 *
	 * @return  string  Replaced string
	 *
	 * @since   3.5
	 */
	public static function ampReplaceCallback($m)
	{
		$rx = '&(?!amp;)';

		return preg_replace('#' . $rx . '#', '&amp;', $m[0]);
	}

	/**
	 * Callback method for replacing & with &amp; in a string
	 *
	 * @param   string  $m  String to process
	 *
	 * @return  string  Replaced string
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use OutputFilter::ampReplaceCallback() instead
	 */
	public static function _ampReplaceCallback($m)
	{
		return static::ampReplaceCallback($m);
	}
}
src/Filter/InputFilter.php000064400000102474152177723700011561 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filter;

defined('JPATH_PLATFORM') or die;

use Joomla\Filter\InputFilter as BaseInputFilter;
use Joomla\String\StringHelper;

/**
 * InputFilter is a class for filtering input from any data source
 *
 * Forked from the php input filter library by: Daniel Morris <dan@rootcube.com>
 * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
 *
 * @since  1.7.0
 */
class InputFilter extends BaseInputFilter
{
	/**
	 * A flag for Unicode Supplementary Characters (4-byte Unicode character) stripping.
	 *
	 * @var    integer
	 *
	 * @since  3.5
	 */
	public $stripUSC = 0;

	/**
	 * Constructor for inputFilter class. Only first parameter is required.
	 *
	 * @param   array    $tagsArray   List of user-defined tags
	 * @param   array    $attrArray   List of user-defined attributes
	 * @param   integer  $tagsMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $attrMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $xssAuto     Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 * @param   integer  $stripUSC    Strip 4-byte unicode characters = 1, no strip = 0, ask the database driver = -1
	 *
	 * @since   1.7.0
	 */
	public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
	{
		// Make sure user defined arrays are in lowercase
		$tagsArray = array_map('strtolower', (array) $tagsArray);
		$attrArray = array_map('strtolower', (array) $attrArray);

		// Assign member variables
		$this->tagsArray = $tagsArray;
		$this->attrArray = $attrArray;
		$this->tagsMethod = $tagsMethod;
		$this->attrMethod = $attrMethod;
		$this->xssAuto = $xssAuto;
		$this->stripUSC = $stripUSC;
		/**
		 * If Unicode Supplementary Characters stripping is not set we have to check with the database driver. If the
		 * driver does not support USCs (i.e. there is no utf8mb4 support) we will enable USC stripping.
		 */
		if ($this->stripUSC === -1)
		{
			try
			{
				// Get the database driver
				$db = \JFactory::getDbo();

				// This trick is required to let the driver determine the utf-8 multibyte support
				$db->connect();

				// And now we can decide if we should strip USCs
				$this->stripUSC = $db->hasUTF8mb4Support() ? 0 : 1;
			}
			catch (\RuntimeException $e)
			{
				// Could not connect to MySQL. Strip USC to be on the safe side.
				$this->stripUSC = 1;
			}
		}
	}

	/**
	 * Returns an input filter object, only creating it if it doesn't already exist.
	 *
	 * @param   array    $tagsArray   List of user-defined tags
	 * @param   array    $attrArray   List of user-defined attributes
	 * @param   integer  $tagsMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $attrMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $xssAuto     Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 * @param   integer  $stripUSC    Strip 4-byte unicode characters = 1, no strip = 0, ask the database driver = -1
	 *
	 * @return  InputFilter  The InputFilter object.
	 *
	 * @since   1.7.0
	 */
	public static function &getInstance($tagsArray = array(), $attrArray = array(), $tagsMethod = 0, $attrMethod = 0, $xssAuto = 1, $stripUSC = -1)
	{
		$sig = md5(serialize(array($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto)));

		if (empty(self::$instances[$sig]))
		{
			self::$instances[$sig] = new InputFilter($tagsArray, $attrArray, $tagsMethod, $attrMethod, $xssAuto, $stripUSC);
		}

		return self::$instances[$sig];
	}

	/**
	 * Method to be called by another php script. Processes for XSS and
	 * specified bad code.
	 *
	 * @param   mixed   $source  Input string/array-of-string to be 'cleaned'
	 * @param   string  $type    The return type for the variable:
	 *                           INT:       An integer, or an array of integers,
	 *                           UINT:      An unsigned integer, or an array of unsigned integers,
	 *                           FLOAT:     A floating point number, or an array of floating point numbers,
	 *                           BOOLEAN:   A boolean value,
	 *                           WORD:      A string containing A-Z or underscores only (not case sensitive),
	 *                           ALNUM:     A string containing A-Z or 0-9 only (not case sensitive),
	 *                           CMD:       A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
	 *                           BASE64:    A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
	 *                           STRING:    A fully decoded and sanitised string (default),
	 *                           HTML:      A sanitised string,
	 *                           ARRAY:     An array,
	 *                           PATH:      A sanitised file path, or an array of sanitised file paths,
	 *                           TRIM:      A string trimmed from normal, non-breaking and multibyte spaces
	 *                           USERNAME:  Do not use (use an application specific filter),
	 *                           RAW:       The raw string is returned with no filtering,
	 *                           unknown:   An unknown filter will act like STRING. If the input is an array it will return an
	 *                                      array of fully decoded and sanitised strings.
	 *
	 * @return  mixed  'Cleaned' version of input parameter
	 *
	 * @since   1.7.0
	 */
	public function clean($source, $type = 'string')
	{
		// Strip Unicode Supplementary Characters when requested to do so
		if ($this->stripUSC)
		{
			// Alternatively: preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xE2\xAF\x91", $source) but it'd be slower.
			$source = $this->stripUSC($source);
		}

		// Handle the type constraint cases
		switch (strtoupper($type))
		{
			case 'INT':
			case 'INTEGER':
				$pattern = '/[-+]?[0-9]+/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (int) $matches[0] : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? (int) $matches[0] : 0;
				}

				break;
			case 'UINT':
				$pattern = '/[-+]?[0-9]+/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? abs((int) $matches[0]) : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? abs((int) $matches[0]) : 0;
				}

				break;
			case 'FLOAT':
			case 'DOUBLE':
				$pattern = '/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (float) $matches[0] : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? (float) $matches[0] : 0;
				}

				break;
			case 'BOOL':
			case 'BOOLEAN':

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (bool) $eachString;
					}
				}
				else
				{
					$result = (bool) $source;
				}

				break;
			case 'WORD':
				$pattern = '/[^A-Z_]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;
			case 'ALNUM':
				$pattern = '/[^A-Z0-9]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;
			case 'CMD':
				$pattern = '/[^A-Z0-9_\.-]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$cleaned  = (string) preg_replace($pattern, '', $eachString);
						$result[] = ltrim($cleaned, '.');
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
					$result = ltrim($result, '.');
				}

				break;
			case 'BASE64':
				$pattern = '/[^A-Z0-9\/+=]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;
			case 'STRING':

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) $this->remove($this->decode((string) $eachString));
					}
				}
				else
				{
					$result = (string) $this->remove($this->decode((string) $source));
				}

				break;
			case 'HTML':

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) $this->remove((string) $eachString);
					}
				}
				else
				{
					$result = (string) $this->remove((string) $source);
				}

				break;
			case 'ARRAY':
				$result = (array) $source;

				break;
			case 'PATH':
				$pattern = '/^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (string) $matches[0] : '';
					}
				}
				else
				{
					preg_match($pattern, $source, $matches);
					$result = isset($matches[0]) ? (string) $matches[0] : '';
				}

				break;
			case 'TRIM':

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$cleaned  = (string) trim($eachString);
						$cleaned  = StringHelper::trim($cleaned, chr(0xE3) . chr(0x80) . chr(0x80));
						$result[] = StringHelper::trim($cleaned, chr(0xC2) . chr(0xA0));
					}
				}
				else
				{
					$result = (string) trim($source);
					$result = StringHelper::trim($result, chr(0xE3) . chr(0x80) . chr(0x80));
					$result = StringHelper::trim($result, chr(0xC2) . chr(0xA0));
				}

				break;
			case 'USERNAME':
				$pattern = '/[\x00-\x1F\x7F<>"\'%&]/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;
			case 'RAW':
				$result = $source;

				break;
			default:

				// Are we dealing with an array?
				if (is_array($source))
				{
					foreach ($source as $key => $value)
					{
						// Filter element for XSS and other 'bad' code etc.
						if (is_string($value))
						{
							$source[$key] = $this->_remove($this->_decode($value));
						}
					}

					$result = $source;
				}
				else
				{
					// Or a string?
					if (is_string($source) && !empty($source))
					{
						// Filter source for XSS and other 'bad' code etc.
						$result = $this->_remove($this->_decode($source));
					}
					else
					{
						// Not an array or string... return the passed parameter
						$result = $source;
					}
				}

				break;
		}

		return $result;
	}

	/**
	 * Function to punyencode utf8 mail when saving content
	 *
	 * @param   string  $text  The strings to encode
	 *
	 * @return  string  The punyencoded mail
	 *
	 * @since   3.5
	 */
	public function emailToPunycode($text)
	{
		$pattern = '/(("mailto:)+[\w\.\-\+]+\@[^"?]+\.+[^."?]+("|\?))/';

		if (preg_match_all($pattern, $text, $matches))
		{
			foreach ($matches[0] as $match)
			{
				$match  = (string) str_replace(array('?', '"'), '', $match);
				$text   = (string) str_replace($match, \JStringPunycode::emailToPunycode($match), $text);
			}
		}

		return $text;
	}

	/**
	 * Checks an uploaded for suspicious naming and potential PHP contents which could indicate a hacking attempt.
	 *
	 * The options you can define are:
	 * null_byte                   Prevent files with a null byte in their name (buffer overflow attack)
	 * forbidden_extensions        Do not allow these strings anywhere in the file's extension
	 * php_tag_in_content          Do not allow `<?php` tag in content
	 * phar_stub_in_content        Do not allow the `__HALT_COMPILER()` phar stub in content
	 * shorttag_in_content         Do not allow short tag `<?` in content
	 * shorttag_extensions         Which file extensions to scan for short tags in content
	 * fobidden_ext_in_content     Do not allow forbidden_extensions anywhere in content
	 * php_ext_content_extensions  Which file extensions to scan for .php in content
	 *
	 * This code is an adaptation and improvement of Admin Tools' UploadShield feature,
	 * relicensed and contributed by its author.
	 *
	 * @param   array  $file     An uploaded file descriptor
	 * @param   array  $options  The scanner options (see the code for details)
	 *
	 * @return  boolean  True of the file is safe
	 *
	 * @since   3.4
	 */
	public static function isSafeFile($file, $options = array())
	{
		$defaultOptions = array(

			// Null byte in file name
			'null_byte'                  => true,

			// Forbidden string in extension (e.g. php matched .php, .xxx.php, .php.xxx and so on)
			'forbidden_extensions'       => array(
				'php', 'phps', 'pht', 'phtml', 'php3', 'php4', 'php5', 'php6', 'php7', 'phar', 'inc', 'pl', 'cgi', 'fcgi', 'java', 'jar', 'py',
			),

			// <?php tag in file contents
			'php_tag_in_content'         => true,

			// <? tag in file contents
			'shorttag_in_content'        => true,

			// __HALT_COMPILER()
			'phar_stub_in_content'        => true,

			// Which file extensions to scan for short tags
			'shorttag_extensions'        => array(
				'inc', 'phps', 'class', 'php3', 'php4', 'php5', 'txt', 'dat', 'tpl', 'tmpl',
			),

			// Forbidden extensions anywhere in the content
			'fobidden_ext_in_content'    => true,

			// Which file extensions to scan for .php in the content
			'php_ext_content_extensions' => array('zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa'),
		);

		$options = array_merge($defaultOptions, $options);

		// Make sure we can scan nested file descriptors
		$descriptors = $file;

		if (isset($file['name']) && isset($file['tmp_name']))
		{
			$descriptors = self::decodeFileData(
				array(
					$file['name'],
					$file['type'],
					$file['tmp_name'],
					$file['error'],
					$file['size'],
				)
			);
		}

		// Handle non-nested descriptors (single files)
		if (isset($descriptors['name']))
		{
			$descriptors = array($descriptors);
		}

		// Scan all descriptors detected
		foreach ($descriptors as $fileDescriptor)
		{
			if (!isset($fileDescriptor['name']))
			{
				// This is a nested descriptor. We have to recurse.
				if (!self::isSafeFile($fileDescriptor, $options))
				{
					return false;
				}

				continue;
			}

			$tempNames     = $fileDescriptor['tmp_name'];
			$intendedNames = $fileDescriptor['name'];

			if (!is_array($tempNames))
			{
				$tempNames = array($tempNames);
			}

			if (!is_array($intendedNames))
			{
				$intendedNames = array($intendedNames);
			}

			$len = count($tempNames);

			for ($i = 0; $i < $len; $i++)
			{
				$tempName     = array_shift($tempNames);
				$intendedName = array_shift($intendedNames);

				// 1. Null byte check
				if ($options['null_byte'])
				{
					if (strstr($intendedName, "\x00"))
					{
						return false;
					}
				}

				// 2. PHP-in-extension check (.php, .php.xxx[.yyy[.zzz[...]]], .xxx[.yyy[.zzz[...]]].php)
				if (!empty($options['forbidden_extensions']))
				{
					$explodedName = explode('.', $intendedName);
					$explodedName =	array_reverse($explodedName);
					array_pop($explodedName);
					$explodedName = array_map('strtolower', $explodedName);

					/*
					 * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
					 * be set, i.e. they should have unique values.
					 */
					foreach ($options['forbidden_extensions'] as $ext)
					{
						if (in_array($ext, $explodedName))
						{
							return false;
						}
					}
				}

				// 3. File contents scanner (PHP tag in file contents)
				if ($options['php_tag_in_content']
					|| $options['shorttag_in_content'] || $options['phar_stub_in_content']
					|| ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions'])))
				{
					$fp = @fopen($tempName, 'r');

					if ($fp !== false)
					{
						$data = '';

						while (!feof($fp))
						{
							$data .= @fread($fp, 131072);

							if ($options['php_tag_in_content'] && stripos($data, '<?php') !== false)
							{
								return false;
							}

							if ($options['phar_stub_in_content'] && stripos($data, '__HALT_COMPILER()') !== false)
							{
								return false;
							}

							if ($options['shorttag_in_content'])
							{
								$suspiciousExtensions = $options['shorttag_extensions'];

								if (empty($suspiciousExtensions))
								{
									$suspiciousExtensions = array(
										'inc', 'phps', 'class', 'php3', 'php4', 'txt', 'dat', 'tpl', 'tmpl',
									);
								}

								/*
								 * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
								 * be set, i.e. they should have unique values.
								 */
								$collide = false;

								foreach ($suspiciousExtensions as $ext)
								{
									if (in_array($ext, $explodedName))
									{
										$collide = true;

										break;
									}
								}

								if ($collide)
								{
									// These are suspicious text files which may have the short tag (<?) in them
									if (strstr($data, '<?'))
									{
										return false;
									}
								}
							}

							if ($options['fobidden_ext_in_content'] && !empty($options['forbidden_extensions']))
							{
								$suspiciousExtensions = $options['php_ext_content_extensions'];

								if (empty($suspiciousExtensions))
								{
									$suspiciousExtensions = array(
										'zip', 'rar', 'tar', 'gz', 'tgz', 'bz2', 'tbz', 'jpa',
									);
								}

								/*
								 * DO NOT USE array_intersect HERE! array_intersect expects the two arrays to
								 * be set, i.e. they should have unique values.
								 */
								$collide = false;

								foreach ($suspiciousExtensions as $ext)
								{
									if (in_array($ext, $explodedName))
									{
										$collide = true;

										break;
									}
								}

								if ($collide)
								{
									/*
									 * These are suspicious text files which may have an executable
									 * file extension in them
									 */
									foreach ($options['forbidden_extensions'] as $ext)
									{
										if (strstr($data, '.' . $ext))
										{
											return false;
										}
									}
								}
							}

							/*
							 * This makes sure that we don't accidentally skip a <?php tag if it's across
							 * a read boundary, even on multibyte strings
							 */
							$data = substr($data, -10);
						}

						fclose($fp);
					}
				}
			}
		}

		return true;
	}

	/**
	 * Method to decode a file data array.
	 *
	 * @param   array  $data  The data array to decode.
	 *
	 * @return  array
	 *
	 * @since   3.4
	 */
	protected static function decodeFileData(array $data)
	{
		$result = array();

		if (is_array($data[0]))
		{
			foreach ($data[0] as $k => $v)
			{
				$result[$k] = self::decodeFileData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
			}

			return $result;
		}

		return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
	}

	/**
	 * Internal method to iteratively remove all unwanted tags and attributes
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::remove() instead
	 */
	protected function _remove($source)
	{
		return $this->remove($source);
	}

	/**
	 * Internal method to iteratively remove all unwanted tags and attributes
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since   3.5
	 */
	protected function remove($source)
	{
		// Check for invalid UTF-8 byte sequence
		if (!preg_match('//u', $source))
		{
			// String contains invalid byte sequence, remove it
			$source = htmlspecialchars_decode(htmlspecialchars($source, ENT_IGNORE, 'UTF-8'));
		}

		// Iteration provides nested tag protection
		do
		{
			$temp = $source;
			$source = $this->_cleanTags($source);
		}
		while ($temp !== $source);

		return $source;
	}

	/**
	 * Internal method to strip a string of certain tags
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::cleanTags() instead
	 */
	protected function _cleanTags($source)
	{
		return $this->cleanTags($source);
	}

	/**
	 * Internal method to strip a string of certain tags
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since   3.5
	 */
	protected function cleanTags($source)
	{
		// First, pre-process this for illegal characters inside attribute values
		$source = $this->_escapeAttributeValues($source);

		// In the beginning we don't really have a tag, so result is empty
		$result = '';
		$offset = 0;
		$length = strlen($source);

		// Is there a tag? If so it will certainly start with a '<'.
		$tagOpenStartOffset = strpos($source, '<');

		// Is there any close tag
		$tagOpenEndOffset = strpos($source, '>');

		while ($offset < $length)
		{
			// Preserve '>' character which exists before related '<'
			if ($tagOpenEndOffset !== false && ($tagOpenStartOffset === false || $tagOpenEndOffset < $tagOpenStartOffset))
			{
				$result .= substr($source, $offset, $tagOpenEndOffset - $offset) . '>';
				$offset  = $tagOpenEndOffset + 1;

				// Search for a new closing indicator
				$tagOpenEndOffset = strpos($source, '>', $offset);

				continue;
			}

			// Add safe text appearing before the '<'
			if ($tagOpenStartOffset > $offset)
			{
				$result .= substr($source, $offset, $tagOpenStartOffset - $offset);
				$offset  = $tagOpenStartOffset;
			}

			// There is no more tags
			if ($tagOpenStartOffset === false && $tagOpenEndOffset === false)
			{
				$result .= substr($source, $offset, $length - $offset);
				$offset  = $length;

				break;
			}

			// Remove every '<' character if '>' does not exists or we have '<>'
			if ($tagOpenStartOffset !== false && $tagOpenEndOffset === false || $tagOpenStartOffset + 1 == $tagOpenEndOffset)
			{
				$offset++;

				// Search for a new opening indicator
				$tagOpenStartOffset = strpos($source, '<', $offset);

				continue;
			}

			// Check for mal-formed tag where we have a second '<' before the '>'
			$nextOpenStartOffset = strpos($source, '<', $tagOpenStartOffset + 1);

			if ($nextOpenStartOffset !== false && $nextOpenStartOffset < $tagOpenEndOffset)
			{
				// At this point we have a mal-formed tag, skip previous '<'
				$offset++;

				// Set a new opening indicator position
				$tagOpenStartOffset = $nextOpenStartOffset;

				continue;
			}

			// Let's get some information about our tag and setup attribute pairs
			// Now we have something like 'span class="" style=""', '/span', 'br/', 'br /' or 'hr disabled /'
			$tagContent = substr($source, $offset + 1, $tagOpenEndOffset - 1 - $offset);

			// All ASCII whitespaces replace by 0x20
			$tagNormalized = preg_replace('/\s/', ' ', $tagContent);
			$tagLength     = strlen($tagContent);
			$spaceOffset   = strpos($tagNormalized, ' ');

			// Are we an open tag or a close tag?
			$isClosingTag     = $tagContent[0] === '/' ? 1 : 0;
			$isSelfClosingTag = substr($tagContent, -1) === '/' ? 1 : 0;

			if ($spaceOffset !== false)
			{
				$tagName = substr($tagContent, $isClosingTag, $spaceOffset - $isClosingTag);
			}
			else
			{
				$tagName = substr($tagContent, $isClosingTag, $tagLength - $isClosingTag - $isSelfClosingTag);
			}

			/*
			 * Exclude all "non-regular" tagnames
			 * OR no tagname
			 * OR remove if xssauto is on and tag is blacklisted
			 */
			if (!$tagName
				|| !preg_match("/^[a-z][a-z0-9]*$/i", $tagName)
				|| ($this->xssAuto && in_array(strtolower($tagName), $this->tagBlacklist)))
			{
				$offset += $tagLength + 2;

				$tagOpenStartOffset = strpos($source, '<', $offset);
				$tagOpenEndOffset   = strpos($source, '>', $offset);

				// Strip tag
				continue;
			}

			$attrSet = array();

			/*
			 * Time to grab any attributes from the tag... need this section in
			 * case attributes have spaces in the values.
			 */
			while ($spaceOffset !== false && $spaceOffset + 1 < $tagLength)
			{
				$attrStartOffset = $spaceOffset + 1;

				// Find position of equal and open quote
				if (preg_match('#= *(")[^"]*(")#', $tagNormalized, $matches, PREG_OFFSET_CAPTURE, $attrStartOffset))
				{
					$equalOffset     = $matches[0][1];
					$quote1Offset    = $matches[1][1];
					$quote2Offset    = $matches[2][1];
					$nextSpaceOffset = strpos($tagNormalized, ' ', $quote2Offset);
				}
				else
				{
					$equalOffset     = strpos($tagNormalized, '=', $attrStartOffset);
					$quote1Offset    = strpos($tagNormalized, '"', $attrStartOffset);
					$nextSpaceOffset = strpos($tagNormalized, ' ', $attrStartOffset);

					if ($quote1Offset !== false)
					{
						$quote2Offset = strpos($tagNormalized, '"', $quote1Offset + 1);
					}
					else
					{
						$quote2Offset = false;
					}
				}

				// Do we have an attribute to process? [check for equal sign]
				if ($tagContent[$attrStartOffset] !== '/'
					&& ($equalOffset && $nextSpaceOffset && $nextSpaceOffset < $equalOffset || !$equalOffset))
				{
					// Search for attribute without value, ex: 'checked/' or 'checked '
					if ($nextSpaceOffset)
					{
						$attrEndOffset = $nextSpaceOffset;
					}
					else
					{
						$attrEndOffset = strpos($tagContent, '/', $attrStartOffset);

						if ($attrEndOffset === false)
						{
							$attrEndOffset = $tagLength;
						}
					}

					// If there is an ending, use this, if not, do not worry.
					if ($attrEndOffset > $attrStartOffset)
					{
						$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset - $attrStartOffset);
					}
				}
				elseif ($equalOffset !== false)
				{
					/*
					 * If the attribute value is wrapped in quotes we need to grab the substring from
					 * the closing quote, otherwise grab until the next space.
					 */
					if ($quote1Offset !== false && $quote2Offset !== false)
					{
						// Add attribute, ex: 'class="body abc"'
						$attrSet[] = substr($tagContent, $attrStartOffset, $quote2Offset + 1 - $attrStartOffset);
					}
					else
					{
						if ($nextSpaceOffset)
						{
							$attrEndOffset = $nextSpaceOffset;
						}
						else
						{
							$attrEndOffset = $tagLength;
						}

						// Add attribute, ex: 'class=body'
						$attrSet[] = substr($tagContent, $attrStartOffset, $attrEndOffset - $attrStartOffset);
					}
				}

				$spaceOffset = $nextSpaceOffset;
			}

			// Is our tag in the user input array?
			$tagFound = in_array(strtolower($tagName), $this->tagsArray);

			// If the tag is allowed let's append it to the output string.
			if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
			{
				// Reconstruct tag with allowed attributes
				if ($isClosingTag)
				{
					$result .= "</$tagName>";
				}
				else
				{
					$attrSet = $this->_cleanAttributes($attrSet);

					// Open or single tag
					$result .= '<' . $tagName;

					if ($attrSet)
					{
						$result .= ' ' . implode(' ', $attrSet);
					}

					// Reformat single tags to XHTML
					if (strpos($source, "</$tagName>", $tagOpenStartOffset) !== false)
					{
						$result .= '>';
					}
					else
					{
						$result .= ' />';
					}
				}
			}

			$offset += $tagLength + 2;

			if ($offset < $length)
			{
				// Find next tag's start and continue iteration
				$tagOpenStartOffset = strpos($source, '<', $offset);
				$tagOpenEndOffset   = strpos($source, '>', $offset);
			}
		}

		return $result;
	}

	/**
	 * Internal method to strip a tag of certain attributes
	 *
	 * @param   array  $attrSet  Array of attribute pairs to filter
	 *
	 * @return  array  Filtered array of attribute pairs
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::cleanAttributes() instead
	 */
	protected function _cleanAttributes($attrSet)
	{
		return $this->cleanAttributes($attrSet);
	}

	/**
	 * Escape < > and " inside attribute values
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Filtered string
	 *
	 * @since    3.5
	 */
	protected function escapeAttributeValues($source)
	{
		$alreadyFiltered = '';
		$remainder = $source;
		$badChars = array('<', '"', '>');
		$escapedChars = array('&lt;', '&quot;', '&gt;');

		/*
		 * Process each portion based on presence of =" and "<space>, "/>, or ">
		 * See if there are any more attributes to process
		 */
		while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, PREG_OFFSET_CAPTURE))
		{
			// Get the portion before the attribute value
			$quotePosition = $matches[0][1];
			$nextBefore = $quotePosition + strlen($matches[0][0]);

			/*
			 * Figure out if we have a single or double quote and look for the matching closing quote
			 * Closing quote should be "/>, ">, "<space>, or " at the end of the string
			 */
			$quote = substr($matches[0][0], -1);
			$pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";

			// Get the portion after attribute value
			if (preg_match($pregMatch, substr($remainder, $nextBefore), $matches, PREG_OFFSET_CAPTURE))
			{
				// We have a closing quote
				$nextAfter = $nextBefore + $matches[0][1];
			}
			else
			{
				// No closing quote
				$nextAfter = strlen($remainder);
			}

			// Get the actual attribute value
			$attributeValue = substr($remainder, $nextBefore, $nextAfter - $nextBefore);

			// Escape bad chars
			$attributeValue = str_replace($badChars, $escapedChars, $attributeValue);
			$attributeValue = $this->_stripCSSExpressions($attributeValue);
			$alreadyFiltered .= substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
			$remainder = substr($remainder, $nextAfter + 1);
		}

		// At this point, we just have to return the $alreadyFiltered and the $remainder
		return $alreadyFiltered . $remainder;
	}

	/**
	 * Try to convert to plaintext
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Plaintext string
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::decode() instead
	 */
	protected function _decode($source)
	{
		return $this->decode($source);
	}

	/**
	 * Try to convert to plaintext
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Plaintext string
	 *
	 * @since   3.5
	 */
	protected function decode($source)
	{
		static $ttr;

		if (!is_array($ttr))
		{
			// Entity decode
			$trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'ISO-8859-1');

			foreach ($trans_tbl as $k => $v)
			{
				$ttr[$v] = utf8_encode($k);
			}
		}

		$source = strtr($source, $ttr);

		// Convert decimal
		$source = preg_replace_callback('/&#(\d+);/m', function($m)
		{
			return utf8_encode(chr($m[1]));
		}, $source
		);

		// Convert hex
		$source = preg_replace_callback('/&#x([a-f0-9]+);/mi', function($m)
		{
			return utf8_encode(chr(hexdec($m[1])));
		}, $source
		);

		return $source;
	}

	/**
	 * Escape < > and " inside attribute values
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Filtered string
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::escapeAttributeValues() instead
	 */
	protected function _escapeAttributeValues($source)
	{
		return $this->escapeAttributeValues($source);
	}

	/**
	 * Remove CSS Expressions in the form of `<property>:expression(...)`
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Filtered string
	 *
	 * @since       1.7.0
	 * @deprecated  4.0 Use InputFilter::stripCSSExpressions() instead
	 */
	protected function _stripCSSExpressions($source)
	{
		return $this->stripCSSExpressions($source);
	}

	/**
	 * Recursively strip Unicode Supplementary Characters from the source. Not: objects cannot be filtered.
	 *
	 * @param   mixed  $source  The data to filter
	 *
	 * @return  mixed  The filtered result
	 *
	 * @since  3.5
	 */
	protected function stripUSC($source)
	{
		if (is_object($source))
		{
			return $source;
		}

		if (is_array($source))
		{
			$filteredArray = array();

			foreach ($source as $k => $v)
			{
				$filteredArray[$k] = $this->stripUSC($v);
			}

			return $filteredArray;
		}

		return preg_replace('/[\xF0-\xF7].../s', "\xE2\xAF\x91", $source);
	}
}
src/Utility/BufferStreamHandler.php000064400000012145152177723700013410 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Utility;

defined('JPATH_PLATFORM') or die;

// Workaround for B/C. Will be removed with 4.0
BufferStreamHandler::stream_register();

/**
 * Generic Buffer stream handler
 *
 * This class provides a generic buffer stream.  It can be used to store/retrieve/manipulate
 * string buffers with the standard PHP filesystem I/O methods.
 *
 * @since  1.7.0
 */
class BufferStreamHandler
{
	/**
	 * Stream position
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $position = 0;

	/**
	 * Buffer name
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $name = null;

	/**
	 * Buffer hash
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	public $buffers = array();

	/**
	 * Status of registering the wrapper
	 *
	 * @var    boolean
	 * @since  3.8.2
	 */
	static private $registered = false;

	/**
	 * Function to register the stream wrapper
	 *
	 * @return  void
	 *
	 * @since  3.8.2
	 */
	public static function stream_register()
	{
		if (!self::$registered)
		{
			stream_wrapper_register('buffer', '\\Joomla\\CMS\\Utility\\BufferStreamHandler');

			self::$registered = true;
		}

		return;
	}

	/**
	 * Function to open file or url
	 *
	 * @param   string   $path          The URL that was passed
	 * @param   string   $mode          Mode used to open the file @see fopen
	 * @param   integer  $options       Flags used by the API, may be STREAM_USE_PATH and
	 *                                  STREAM_REPORT_ERRORS
	 * @param   string   &$opened_path  Full path of the resource. Used with STREAN_USE_PATH option
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @see     streamWrapper::stream_open
	 */
	public function stream_open($path, $mode, $options, &$opened_path)
	{
		$url = parse_url($path);
		$this->name = $url['host'];
		$this->buffers[$this->name] = null;
		$this->position = 0;

		return true;
	}

	/**
	 * Read stream
	 *
	 * @param   integer  $count  How many bytes of data from the current position should be returned.
	 *
	 * @return  mixed    The data from the stream up to the specified number of bytes (all data if
	 *                   the total number of bytes in the stream is less than $count. Null if
	 *                   the stream is empty.
	 *
	 * @see     streamWrapper::stream_read
	 * @since   1.7.0
	 */
	public function stream_read($count)
	{
		$ret = substr($this->buffers[$this->name], $this->position, $count);
		$this->position += strlen($ret);

		return $ret;
	}

	/**
	 * Write stream
	 *
	 * @param   string  $data  The data to write to the stream.
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_write
	 * @since   1.7.0
	 */
	public function stream_write($data)
	{
		$left = substr($this->buffers[$this->name], 0, $this->position);
		$right = substr($this->buffers[$this->name], $this->position + strlen($data));
		$this->buffers[$this->name] = $left . $data . $right;
		$this->position += strlen($data);

		return strlen($data);
	}

	/**
	 * Function to get the current position of the stream
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_tell
	 * @since   1.7.0
	 */
	public function stream_tell()
	{
		return $this->position;
	}

	/**
	 * Function to test for end of file pointer
	 *
	 * @return  boolean  True if the pointer is at the end of the stream
	 *
	 * @see     streamWrapper::stream_eof
	 * @since   1.7.0
	 */
	public function stream_eof()
	{
		return $this->position >= strlen($this->buffers[$this->name]);
	}

	/**
	 * The read write position updates in response to $offset and $whence
	 *
	 * @param   integer  $offset  The offset in bytes
	 * @param   integer  $whence  Position the offset is added to
	 *                            Options are SEEK_SET, SEEK_CUR, and SEEK_END
	 *
	 * @return  boolean  True if updated
	 *
	 * @see     streamWrapper::stream_seek
	 * @since   1.7.0
	 */
	public function stream_seek($offset, $whence)
	{
		switch ($whence)
		{
			case SEEK_SET :
				return $this->seek_set($offset);

			case SEEK_CUR :

				return $this->seek_cur($offset);

			case SEEK_END :

				return $this->seek_end($offset);
		}

		return false;
	}

	/**
	 * Set the position to the offset
	 *
	 * @param   integer  $offset  The offset in bytes
	 *
	 * @return  boolean
	 */
	protected function seek_set($offset)
	{
		if ($offset < 0 || $offset > strlen($this->buffers[$this->name]))
		{
			return false;
		}

		$this->position = $offset;

		return true;
	}

	/**
	 * Adds the offset to current position
	 *
	 * @param   integer  $offset  The offset in bytes
	 *
	 * @return  boolean
	 */
	protected function seek_cur($offset)
	{
		if ($offset < 0)
		{
			return false;
		}

		$this->position += $offset;

		return true;
	}

	/**
	 * Sets the position to the end of the current buffer + offset
	 *
	 * @param   integer  $offset  The offset in bytes
	 *
	 * @return  boolean
	 */
	protected function seek_end($offset)
	{
		$offset += strlen($this->buffers[$this->name]);

		if ($offset < 0)
		{
			return false;
		}

		$this->position = $offset;

		return true;
	}
}
src/Utility/Utility.php000064400000003536152177723700011174 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Utility;

defined('JPATH_PLATFORM') or die;

/**
 * JUtility is a utility functions class
 *
 * @since  1.7.0
 */
class Utility
{
	/**
	 * Method to extract key/value pairs out of a string with XML style attributes
	 *
	 * @param   string  $string  String containing XML style attributes
	 *
	 * @return  array  Key/Value pairs for the attributes
	 *
	 * @since   1.7.0
	 */
	public static function parseAttributes($string)
	{
		$attr = array();
		$retarray = array();

		// Let's grab all the key/value pairs using a regular expression
		preg_match_all('/([\w:-]+)[\s]?=[\s]?"([^"]*)"/i', $string, $attr);

		if (is_array($attr))
		{
			$numPairs = count($attr[1]);

			for ($i = 0; $i < $numPairs; $i++)
			{
				$retarray[$attr[1][$i]] = $attr[2][$i];
			}
		}

		return $retarray;
	}

	/**
	 * Method to get the maximum allowed file size for the HTTP uploads based on the active PHP configuration
	 *
	 * @param   mixed  $custom  A custom upper limit, if the PHP settings are all above this then this will be used
	 *
	 * @return  integer  Size in number of bytes
	 *
	 * @since   3.7.0
	 */
	public static function getMaxUploadSize($custom = null)
	{
		if ($custom)
		{
			$custom = \JHtml::_('number.bytes', $custom, '');

			if ($custom > 0)
			{
				$sizes[] = $custom;
			}
		}

		/*
		 * Read INI settings which affects upload size limits
		 * and Convert each into number of bytes so that we can compare
		 */
		$sizes[] = \JHtml::_('number.bytes', ini_get('post_max_size'), '');
		$sizes[] = \JHtml::_('number.bytes', ini_get('upload_max_filesize'), '');

		// The minimum of these is the limiting factor
		return min($sizes);
	}
}
src/Object/CMSObject.php000064400000011651152177723700011042 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Object;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Object Class
 *
 * This class allows for simple but smart objects with get and set methods
 * and an internal error handler.
 *
 * @since       1.7.0
 * @deprecated  4.0
 */
class CMSObject
{
	/**
	 * An array of error messages or Exception objects.
	 *
	 * @var    array
	 * @since  1.7.0
	 * @see    JError
	 * @deprecated  12.3  JError has been deprecated
	 */
	protected $_errors = array();

	/**
	 * Class constructor, overridden in descendant classes.
	 *
	 * @param   mixed  $properties  Either and associative array or another
	 *                              object to set the initial properties of the object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($properties = null)
	{
		if ($properties !== null)
		{
			$this->setProperties($properties);
		}
	}

	/**
	 * Magic method to convert the object to a string gracefully.
	 *
	 * @return  string  The classname.
	 *
	 * @since   1.7.0
	 * @deprecated 12.3  Classes should provide their own __toString() implementation.
	 */
	public function __toString()
	{
		return get_class($this);
	}

	/**
	 * Sets a default value if not already assigned
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function def($property, $default = null)
	{
		$value = $this->get($property, $default);

		return $this->set($property, $value);
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed    The value of the property.
	 *
	 * @since   1.7.0
	 *
	 * @see     CMSObject::getProperties()
	 */
	public function get($property, $default = null)
	{
		if (isset($this->$property))
		{
			return $this->$property;
		}

		return $default;
	}

	/**
	 * Returns an associative array of object properties.
	 *
	 * @param   boolean  $public  If true, returns only the public properties.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 *
	 * @see     CMSObject::get()
	 */
	public function getProperties($public = true)
	{
		$vars = get_object_vars($this);

		if ($public)
		{
			foreach ($vars as $key => $value)
			{
				if ('_' == substr($key, 0, 1))
				{
					unset($vars[$key]);
				}
			}
		}

		return $vars;
	}

	/**
	 * Get the most recent error message.
	 *
	 * @param   integer  $i         Option error index.
	 * @param   boolean  $toString  Indicates if JError objects should return their error message.
	 *
	 * @return  string   Error message
	 *
	 * @since   1.7.0
	 * @see     JError
	 * @deprecated 12.3  JError has been deprecated
	 */
	public function getError($i = null, $toString = true)
	{
		// Find the error
		if ($i === null)
		{
			// Default, return the last message
			$error = end($this->_errors);
		}
		elseif (!array_key_exists($i, $this->_errors))
		{
			// If $i has been specified but does not exist, return false
			return false;
		}
		else
		{
			$error = $this->_errors[$i];
		}

		// Check if only the string is requested
		if ($error instanceof \Exception && $toString)
		{
			return $error->getMessage();
		}

		return $error;
	}

	/**
	 * Return all errors, if any.
	 *
	 * @return  array  Array of error messages or JErrors.
	 *
	 * @since   1.7.0
	 * @see     JError
	 * @deprecated 12.3  JError has been deprecated
	 */
	public function getErrors()
	{
		return $this->_errors;
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set.
	 *
	 * @return  mixed  Previous value of the property.
	 *
	 * @since   1.7.0
	 */
	public function set($property, $value = null)
	{
		$previous = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;

		return $previous;
	}

	/**
	 * Set the object properties based on a named array/hash.
	 *
	 * @param   mixed  $properties  Either an associative array or another object.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 *
	 * @see     CMSObject::set()
	 */
	public function setProperties($properties)
	{
		if (is_array($properties) || is_object($properties))
		{
			foreach ((array) $properties as $k => $v)
			{
				// Use the set function which might be overridden.
				$this->set($k, $v);
			}

			return true;
		}

		return false;
	}

	/**
	 * Add an error message.
	 *
	 * @param   string  $error  Error message.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @see     JError
	 * @deprecated 12.3  JError has been deprecated
	 */
	public function setError($error)
	{
		$this->_errors[] = $error;
	}
}
src/Response/JsonResponse.php000064400000005160152177723700012307 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Response;

defined('JPATH_PLATFORM') or die;

/**
 * JSON Response class.
 *
 * This class serves to provide the Joomla Platform with a common interface to access
 * response variables for e.g. Ajax requests.
 *
 * @since  3.1
 */
class JsonResponse
{
	/**
	 * Determines whether the request was successful
	 *
	 * @var    boolean
	 * @since  3.1
	 */
	public $success = true;

	/**
	 * The main response message
	 *
	 * @var    string
	 * @since  3.1
	 */
	public $message = null;

	/**
	 * Array of messages gathered in the \JApplication object
	 *
	 * @var    array
	 * @since  3.1
	 */
	public $messages = null;

	/**
	 * The response data
	 *
	 * @var    mixed
	 * @since  3.1
	 */
	public $data = null;

	/**
	 * Constructor
	 *
	 * @param   mixed    $response        The Response data
	 * @param   string   $message         The main response message
	 * @param   boolean  $error           True, if the success flag shall be set to false, defaults to false
	 * @param   boolean  $ignoreMessages  True, if the message queue shouldn't be included, defaults to false
	 *
	 * @since   3.1
	 */
	public function __construct($response = null, $message = null, $error = false, $ignoreMessages = false)
	{
		$this->message = $message;

		// Get the message queue if requested and available
		$app = \JFactory::getApplication();

		if (!$ignoreMessages && $app !== null && is_callable(array($app, 'getMessageQueue')))
		{
			$messages = $app->getMessageQueue();

			// Build the sorted messages list
			if (is_array($messages) && count($messages))
			{
				foreach ($messages as $message)
				{
					if (isset($message['type']) && isset($message['message']))
					{
						$lists[$message['type']][] = $message['message'];
					}
				}
			}

			// If messages exist add them to the output
			if (isset($lists) && is_array($lists))
			{
				$this->messages = $lists;
			}
		}

		// Check if we are dealing with an error
		if ($response instanceof \Exception || $response instanceof \Throwable)
		{
			// Prepare the error response
			$this->success = false;
			$this->message = $response->getMessage();
		}
		else
		{
			// Prepare the response data
			$this->success = !$error;
			$this->data    = $response;
		}
	}

	/**
	 * Magic toString method for sending the response in JSON format
	 *
	 * @return  string  The response in JSON format
	 *
	 * @since   3.1
	 */
	public function __toString()
	{
		return json_encode($this);
	}
}
src/Toolbar/Button/ConfirmButton.php000064400000006146152177723700013534 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a standard button with a confirm dialog
 *
 * @since  3.0
 */
class ConfirmButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var    string
	 */
	protected $_name = 'Confirm';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string   $type      Unused string.
	 * @param   string   $msg       Message to render
	 * @param   string   $name      Name to be used as apart of the id
	 * @param   string   $text      Button text
	 * @param   string   $task      The task associated with the button
	 * @param   boolean  $list      True to allow use of lists
	 * @param   boolean  $hideMenu  True to hide the menu on click
	 *
	 * @return  string   HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Confirm', $msg = '', $name = '', $text = '', $task = '', $list = true, $hideMenu = false)
	{
		// Store all data to the options array for use with JLayout
		$options = array();
		$options['text'] = \JText::_($text);
		$options['msg'] = \JText::_($msg, true);
		$options['class'] = $this->fetchIconClass($name);
		$options['doTask'] = $this->_getCommand($options['msg'], $name, $task, $list);

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.confirm');

		return $layout->render($options);
	}

	/**
	 * Get the button CSS Id
	 *
	 * @param   string   $type      Button type
	 * @param   string   $msg       Message to display
	 * @param   string   $name      Name to be used as apart of the id
	 * @param   string   $text      Button text
	 * @param   string   $task      The task associated with the button
	 * @param   boolean  $list      True to allow use of lists
	 * @param   boolean  $hideMenu  True to hide the menu on click
	 *
	 * @return  string  Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type = 'Confirm', $msg = '', $name = '', $text = '', $task = '', $list = true, $hideMenu = false)
	{
		return $this->_parent->getName() . '-' . $name;
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   object   $msg   The message to display.
	 * @param   string   $name  Not used.
	 * @param   string   $task  The task used by the application
	 * @param   boolean  $list  True is requires a list confirmation.
	 *
	 * @return  string  JavaScript command string
	 *
	 * @since   3.0
	 */
	protected function _getCommand($msg, $name, $task, $list)
	{
		\JText::script('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST');

		$cmd = "if (confirm('" . $msg . "')) { Joomla.submitbutton('" . $task . "'); }";

		if ($list)
		{
			$alert = "alert(Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'));";
			$cmd   = "if (document.adminForm.boxchecked.value == 0) { " . $alert . " } else { " . $cmd . " }";
		}

		return $cmd;
	}
}
src/Toolbar/Button/PopupButton.php000064400000006544152177723700013244 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a modal window button
 *
 * @since  3.0
 */
class PopupButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var    string
	 */
	protected $_name = 'Popup';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string   $type     Unused string, formerly button type.
	 * @param   string   $name     Modal name, used to generate element ID
	 * @param   string   $text     The link text
	 * @param   string   $url      URL for popup
	 * @param   integer  $width    Width of popup
	 * @param   integer  $height   Height of popup
	 * @param   integer  $top      Top attribute.  [@deprecated  Unused, will be removed in 4.0]
	 * @param   integer  $left     Left attribute. [@deprecated  Unused, will be removed in 4.0]
	 * @param   string   $onClose  JavaScript for the onClose event.
	 * @param   string   $title    The title text
	 * @param   string   $footer   The footer html
	 *
	 * @return  string  HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Modal', $name = '', $text = '', $url = '', $width = 640, $height = 480, $top = 0, $left = 0,
		$onClose = '', $title = '', $footer = null)
	{
		// If no $title is set, use the $text element
		if ($title === '')
		{
			$title = $text;
		}

		// Store all data to the options array for use with JLayout
		$options = array();
		$options['name'] = $name;
		$options['text'] = \JText::_($text);
		$options['title'] = \JText::_($title);
		$options['class'] = $this->fetchIconClass($name);
		$options['doTask'] = $this->_getCommand($url);

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.popup');

		$html = array();
		$html[] = $layout->render($options);

		// Place modal div and scripts in a new div
		$html[] = '<div class="btn-group" style="width: 0; margin: 0">';

		// Build the options array for the modal
		$params = array();
		$params['title']  = $options['title'];
		$params['url']    = $options['doTask'];
		$params['height'] = $height;
		$params['width']  = $width;

		if (isset($footer))
		{
			$params['footer'] = $footer;
		}

		$html[] = \JHtml::_('bootstrap.renderModal', 'modal-' . $name, $params);

		// If an $onClose event is passed, add it to the modal JS object
		if ($onClose !== '')
		{
			$html[] = '<script>'
				. 'jQuery(\'#modal-' . $name . '\').on(\'hide\', function () {' . $onClose . ';});'
				. '</script>';
		}

		$html[] = '</div>';

		return implode("\n", $html);
	}

	/**
	 * Get the button id
	 *
	 * @param   string  $type  Button type
	 * @param   string  $name  Button name
	 *
	 * @return  string	Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type, $name)
	{
		return $this->_parent->getName() . '-popup-' . $name;
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   string  $url  URL for popup
	 *
	 * @return  string  JavaScript command string
	 *
	 * @since   3.0
	 */
	private function _getCommand($url)
	{
		if (strpos($url, 'http') !== 0)
		{
			$url = \JUri::base() . $url;
		}

		return $url;
	}
}
src/Toolbar/Button/LinkButton.php000064400000003502152177723700013025 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a link button
 *
 * @since  3.0
 */
class LinkButton extends ToolbarButton
{
	/**
	 * Button type
	 * @var    string
	 */
	protected $_name = 'Link';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string  $type  Unused string.
	 * @param   string  $name  Name to be used as apart of the id
	 * @param   string  $text  Button text
	 * @param   string  $url   The link url
	 *
	 * @return  string  HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Link', $name = 'back', $text = '', $url = null)
	{
		// Store all data to the options array for use with JLayout
		$options = array();
		$options['text'] = \JText::_($text);
		$options['class'] = $this->fetchIconClass($name);
		$options['doTask'] = $this->_getCommand($url);

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.link');

		return $layout->render($options);
	}

	/**
	 * Get the button CSS Id
	 *
	 * @param   string  $type  The button type.
	 * @param   string  $name  The name of the button.
	 *
	 * @return  string  Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type = 'Link', $name = '')
	{
		return $this->_parent->getName() . '-' . $name;
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   object  $url  Button definition
	 *
	 * @return  string  JavaScript command string
	 *
	 * @since   3.0
	 */
	protected function _getCommand($url)
	{
		return $url;
	}
}
src/Toolbar/Button/StandardButton.php000064400000005700152177723700013672 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a standard button
 *
 * @since  3.0
 */
class StandardButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var    string
	 */
	protected $_name = 'Standard';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string   $type  Unused string.
	 * @param   string   $name  The name of the button icon class.
	 * @param   string   $text  Button text.
	 * @param   string   $task  Task associated with the button.
	 * @param   boolean  $list  True to allow lists
	 *
	 * @return  string  HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Standard', $name = '', $text = '', $task = '', $list = true)
	{
		// Store all data to the options array for use with JLayout
		$options = array();
		$options['text']     = \JText::_($text);
		$options['class']    = $this->fetchIconClass($name);
		$options['doTask']   = $this->_getCommand($options['text'], $task, $list);
		$options['btnClass'] = 'btn btn-small button-' . $name;

		if ($name === 'apply' || $name === 'new')
		{
			$options['btnClass'] .= ' btn-success';
			$options['class'] .= ' icon-white';
		}

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.standard');

		return $layout->render($options);
	}

	/**
	 * Get the button CSS Id
	 *
	 * @param   string   $type      Unused string.
	 * @param   string   $name      Name to be used as apart of the id
	 * @param   string   $text      Button text
	 * @param   string   $task      The task associated with the button
	 * @param   boolean  $list      True to allow use of lists
	 * @param   boolean  $hideMenu  True to hide the menu on click
	 *
	 * @return  string  Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type = 'Standard', $name = '', $text = '', $task = '', $list = true, $hideMenu = false)
	{
		return $this->_parent->getName() . '-' . $name;
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   string   $name  The task name as seen by the user
	 * @param   string   $task  The task used by the application
	 * @param   boolean  $list  True is requires a list confirmation.
	 *
	 * @return  string   JavaScript command string
	 *
	 * @since   3.0
	 */
	protected function _getCommand($name, $task, $list)
	{
		\JText::script('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST');

		$cmd = "Joomla.submitbutton('" . $task . "');";

		if ($list)
		{
			$alert = "alert(Joomla.JText._('JLIB_HTML_PLEASE_MAKE_A_SELECTION_FROM_THE_LIST'));";
			$cmd   = "if (document.adminForm.boxchecked.value == 0) { " . $alert . " } else { " . $cmd . " }";
		}

		return $cmd;
	}
}
src/Toolbar/Button/HelpButton.php000064400000004760152177723700013027 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Help\Help;
use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a help popup window button
 *
 * @since  3.0
 */
class HelpButton extends ToolbarButton
{
	/**
	 * @var    string	Button type
	 */
	protected $_name = 'Help';

	/**
	 * Fetches the button HTML code.
	 *
	 * @param   string   $type       Unused string.
	 * @param   string   $ref        The name of the help screen (its key reference).
	 * @param   boolean  $com        Use the help file in the component directory.
	 * @param   string   $override   Use this URL instead of any other.
	 * @param   string   $component  Name of component to get Help (null for current component)
	 *
	 * @return  string
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Help', $ref = '', $com = false, $override = null, $component = null)
	{
		// Store all data to the options array for use with JLayout
		$options = array();
		$options['text']   = \JText::_('JTOOLBAR_HELP');
		$options['doTask'] = $this->_getCommand($ref, $com, $override, $component);

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.help');

		return $layout->render($options);
	}

	/**
	 * Get the button id
	 *
	 * Redefined from JButton class
	 *
	 * @return  string	Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId()
	{
		return $this->_parent->getName() . '-help';
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   string   $ref        The name of the help screen (its key reference).
	 * @param   boolean  $com        Use the help file in the component directory.
	 * @param   string   $override   Use this URL instead of any other.
	 * @param   string   $component  Name of component to get Help (null for current component)
	 *
	 * @return  string   JavaScript command string
	 *
	 * @since   3.0
	 */
	protected function _getCommand($ref, $com, $override, $component)
	{
		// Get Help URL
		$url = Help::createUrl($ref, $com, $override, $component);
		$url = json_encode(htmlspecialchars($url, ENT_QUOTES), JSON_HEX_APOS);
		$url = substr($url, 1, -1);
		$cmd = "Joomla.popupWindow('$url', '" . \JText::_('JHELP', true) . "', 700, 500, 1)";

		return $cmd;
	}
}
src/Toolbar/Button/SliderButton.php000064400000004716152177723700013362 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a button to render an HTML element in a slider container
 *
 * @since  3.0
 */
class SliderButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var    string
	 */
	protected $_name = 'Slider';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string   $type     Unused string, formerly button type.
	 * @param   string   $name     Button name
	 * @param   string   $text     The link text
	 * @param   string   $url      URL for popup
	 * @param   integer  $width    Width of popup
	 * @param   integer  $height   Height of popup
	 * @param   string   $onClose  JavaScript for the onClose event.
	 *
	 * @return  string  HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Slider', $name = '', $text = '', $url = '', $width = 640, $height = 480, $onClose = '')
	{
		\JHtml::_('script', 'jui/cms.js', array('version' => 'auto', 'relative' => true));

		// Store all data to the options array for use with JLayout
		$options = array();
		$options['text'] = \JText::_($text);
		$options['name'] = $name;
		$options['class'] = $this->fetchIconClass($name);
		$options['onClose'] = '';

		$doTask = $this->_getCommand($url);
		$options['doTask'] = 'Joomla.setcollapse(\'' . $doTask . '\', \'' . $name . '\', \'' . $height . '\');';

		if ($onClose)
		{
			$options['onClose'] = ' rel="{onClose: function() {' . $onClose . '}}"';
		}

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.slider');

		return $layout->render($options);
	}

	/**
	 * Get the button id
	 *
	 * @param   string  $type  Button type
	 * @param   string  $name  Button name
	 *
	 * @return  string	Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type, $name)
	{
		return $this->_parent->getName() . '-slider-' . $name;
	}

	/**
	 * Get the JavaScript command for the button
	 *
	 * @param   string  $url  URL for popup
	 *
	 * @return  string  JavaScript command string
	 *
	 * @since   3.0
	 */
	private function _getCommand($url)
	{
		if (strpos($url, 'http') !== 0)
		{
			$url = \JUri::base() . $url;
		}

		return $url;
	}
}
src/Toolbar/Button/SeparatorButton.php000064400000002632152177723700014073 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a button separator
 *
 * @since  3.0
 */
class SeparatorButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var   string
	 */
	protected $_name = 'Separator';

	/**
	 * Get the HTML for a separator in the toolbar
	 *
	 * @param   array  &$definition  Class name and custom width
	 *
	 * @return  string  The HTML for the separator
	 *
	 * @see     ToolbarButton::render()
	 * @since   3.0
	 */
	public function render(&$definition)
	{
		// Store all data to the options array for use with JLayout
		$options = array();

		// Separator class name
		$options['class'] = empty($definition[1]) ? '' : $definition[1];

		// Custom width
		$options['style'] = empty($definition[2]) ? '' : ' style="width:' . (int) $definition[2] . 'px;"';

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new FileLayout('joomla.toolbar.separator');

		return $layout->render($options);
	}

	/**
	 * Empty implementation (not required for separator)
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function fetchButton()
	{
	}
}
src/Toolbar/Button/CustomButton.php000064400000002342152177723700013403 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar\Button;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Toolbar\ToolbarButton;

/**
 * Renders a custom button
 *
 * @since  3.0
 */
class CustomButton extends ToolbarButton
{
	/**
	 * Button type
	 *
	 * @var    string
	 */
	protected $_name = 'Custom';

	/**
	 * Fetch the HTML for the button
	 *
	 * @param   string  $type  Button type, unused string.
	 * @param   string  $html  HTML strng for the button
	 * @param   string  $id    CSS id for the button
	 *
	 * @return  string   HTML string for the button
	 *
	 * @since   3.0
	 */
	public function fetchButton($type = 'Custom', $html = '', $id = 'custom')
	{
		return $html;
	}

	/**
	 * Get the button CSS Id
	 *
	 * @param   string  $type  Not used.
	 * @param   string  $html  Not used.
	 * @param   string  $id    The id prefix for the button.
	 *
	 * @return  string  Button CSS Id
	 *
	 * @since   3.0
	 */
	public function fetchId($type = 'Custom', $html = '', $id = 'custom')
	{
		return $this->_parent->getName() . '-' . $id;
	}
}
src/Toolbar/Toolbar.php000064400000014126152177723700011067 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;

/**
 * ToolBar handler
 *
 * @since  1.5
 */
class Toolbar
{
	/**
	 * Toolbar name
	 *
	 * @var    string
	 */
	protected $_name = array();

	/**
	 * Toolbar array
	 *
	 * @var    array
	 */
	protected $_bar = array();

	/**
	 * Loaded buttons
	 *
	 * @var    array
	 */
	protected $_buttons = array();

	/**
	 * Directories, where button types can be stored.
	 *
	 * @var    array
	 */
	protected $_buttonPath = array();

	/**
	 * Stores the singleton instances of various toolbar.
	 *
	 * @var    Toolbar
	 * @since  2.5
	 */
	protected static $instances = array();

	/**
	 * Constructor
	 *
	 * @param   string  $name  The toolbar name.
	 *
	 * @since   1.5
	 */
	public function __construct($name = 'toolbar')
	{
		$this->_name = $name;

		// Set base path to find buttons.
		$this->_buttonPath[] = __DIR__ . '/button';
	}

	/**
	 * Returns the global Toolbar object, only creating it if it
	 * doesn't already exist.
	 *
	 * @param   string  $name  The name of the toolbar.
	 *
	 * @return  \JToolbar  The JToolbar object.
	 *
	 * @since   1.5
	 */
	public static function getInstance($name = 'toolbar')
	{
		if (empty(self::$instances[$name]))
		{
			self::$instances[$name] = new Toolbar($name);
		}

		return self::$instances[$name];
	}

	/**
	 * Set a value
	 *
	 * @return  string  The set value.
	 *
	 * @since   1.5
	 */
	public function appendButton()
	{
		// Push button onto the end of the toolbar array.
		$btn          = func_get_args();
		$this->_bar[] = $btn;

		return true;
	}

	/**
	 * Get the list of toolbar links.
	 *
	 * @return  array
	 *
	 * @since   1.6
	 */
	public function getItems()
	{
		return $this->_bar;
	}

	/**
	 * Get the name of the toolbar.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 */
	public function getName()
	{
		return $this->_name;
	}

	/**
	 * Get a value.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public function prependButton()
	{
		// Insert button into the front of the toolbar array.
		$btn = func_get_args();
		array_unshift($this->_bar, $btn);

		return true;
	}

	/**
	 * Render a toolbar.
	 *
	 * @return  string  HTML for the toolbar.
	 *
	 * @since   1.5
	 */
	public function render()
	{
		$html = array();

		// Start toolbar div.
		$layout = new FileLayout('joomla.toolbar.containeropen');

		$html[] = $layout->render(array('id' => $this->_name));

		// Render each button in the toolbar.
		foreach ($this->_bar as $button)
		{
			$html[] = $this->renderButton($button);
		}

		// End toolbar div.
		$layout = new FileLayout('joomla.toolbar.containerclose');

		$html[] = $layout->render(array());

		return implode('', $html);
	}

	/**
	 * Render a button.
	 *
	 * @param   object  &$node  A toolbar node.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public function renderButton(&$node)
	{
		// Get the button type.
		$type = $node[0];

		$button = $this->loadButtonType($type);

		// Check for error.
		if ($button === false)
		{
			return \JText::sprintf('JLIB_HTML_BUTTON_NOT_DEFINED', $type);
		}

		return $button->render($node);
	}

	/**
	 * Loads a button type.
	 *
	 * @param   string   $type  Button Type
	 * @param   boolean  $new   False by default
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 */
	public function loadButtonType($type, $new = false)
	{
		$signature = md5($type);

		if ($new === false && isset($this->_buttons[$signature]))
		{
			return $this->_buttons[$signature];
		}

		if (!class_exists('Joomla\\CMS\\Toolbar\\ToolbarButton'))
		{
			\JLog::add(\JText::_('JLIB_HTML_BUTTON_BASE_CLASS'), \JLog::WARNING, 'jerror');

			return false;
		}

		$buttonClass = $this->loadButtonClass($type);

		if (!$buttonClass)
		{
			if (isset($this->_buttonPath))
			{
				$dirs = $this->_buttonPath;
			}
			else
			{
				$dirs = array();
			}

			$file = \JFilterInput::getInstance()->clean(str_replace('_', DIRECTORY_SEPARATOR, strtolower($type)) . '.php', 'path');

			\JLoader::import('joomla.filesystem.path');

			if ($buttonFile = \JPath::find($dirs, $file))
			{
				include_once $buttonFile;
			}
			else
			{
				\JLog::add(\JText::sprintf('JLIB_HTML_BUTTON_NO_LOAD', $buttonClass, $buttonFile), \JLog::WARNING, 'jerror');

				return false;
			}

			$buttonClass = $this->loadButtonClass($type);

			if (!$buttonClass)
			{
				return false;
			}
		}

		$this->_buttons[$signature] = new $buttonClass($this);

		return $this->_buttons[$signature];
	}

	/**
	 * Load the button class including the deprecated ones.
	 *
	 * @param   string  $type  Button Type
	 *
	 * @return  string|null
	 *
	 * @since   3.8.0
	 */
	private function loadButtonClass($type)
	{
		$buttonClasses = array(
			'Joomla\\CMS\\Toolbar\\Button\\' . ucfirst($type) . 'Button',
			// @deprecated 3.8.0
			'JToolbarButton' . ucfirst($type),
			// @deprecated 3.1.4 Remove the acceptance of legacy classes starting with JButton.
			'JButton' . ucfirst($type)
		);

		foreach ($buttonClasses as $buttonClass)
		{
			if (!class_exists($buttonClass))
			{
				continue;
			}

			return $buttonClass;
		}

		return null;
	}

	/**
	 * Add a directory where Toolbar should search for button types in LIFO order.
	 *
	 * You may either pass a string or an array of directories.
	 *
	 * Toolbar will be searching for an element type in the same order you
	 * added them. If the parameter type cannot be found in the custom folders,
	 * it will look in libraries/joomla/html/toolbar/button.
	 *
	 * @param   mixed  $path  Directory or directories to search.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function addButtonPath($path)
	{
		// Loop through the path directories.
		foreach ((array) $path as $dir)
		{
			// No surrounding spaces allowed!
			$dir = trim($dir);

			// Add trailing separators as needed.
			if (substr($dir, -1) !== DIRECTORY_SEPARATOR)
			{
				// Directory
				$dir .= DIRECTORY_SEPARATOR;
			}

			// Add to the top of the search dirs.
			array_unshift($this->_buttonPath, $dir);
		}
	}
}
src/Toolbar/ToolbarButton.php000064400000004445152177723700012266 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Layout\FileLayout;

/**
 * Button base class
 *
 * The JButton is the base class for all JButton types
 *
 * @since  3.0
 */
abstract class ToolbarButton
{
	/**
	 * element name
	 *
	 * This has to be set in the final renderer classes.
	 *
	 * @var    string
	 */
	protected $_name = null;

	/**
	 * reference to the object that instantiated the element
	 *
	 * @var    \JButton
	 */
	protected $_parent = null;

	/**
	 * Constructor
	 *
	 * @param   object  $parent  The parent
	 */
	public function __construct($parent = null)
	{
		$this->_parent = $parent;
	}

	/**
	 * Get the element name
	 *
	 * @return  string   type of the parameter
	 *
	 * @since   3.0
	 */
	public function getName()
	{
		return $this->_name;
	}

	/**
	 * Get the HTML to render the button
	 *
	 * @param   array  &$definition  Parameters to be passed
	 *
	 * @return  string
	 *
	 * @since   3.0
	 */
	public function render(&$definition)
	{
		/*
		 * Initialise some variables
		 */
		$id = call_user_func_array(array(&$this, 'fetchId'), $definition);
		$action = call_user_func_array(array(&$this, 'fetchButton'), $definition);

		// Build id attribute
		if ($id)
		{
			$id = ' id="' . $id . '"';
		}

		// Build the HTML Button
		$options = array();
		$options['id'] = $id;
		$options['action'] = $action;

		$layout = new FileLayout('joomla.toolbar.base');

		return $layout->render($options);
	}

	/**
	 * Method to get the CSS class name for an icon identifier
	 *
	 * Can be redefined in the final class
	 *
	 * @param   string  $identifier  Icon identification string
	 *
	 * @return  string  CSS class name
	 *
	 * @since   3.0
	 */
	public function fetchIconClass($identifier)
	{
		// It's an ugly hack, but this allows templates to define the icon classes for the toolbar
		$layout = new FileLayout('joomla.toolbar.iconclass');

		return $layout->render(array('icon' => $identifier));
	}

	/**
	 * Get the button
	 *
	 * Defined in the final button class
	 *
	 * @return  string
	 *
	 * @since   3.0
	 */
	abstract public function fetchButton();
}
src/Toolbar/ToolbarHelper.php000064400000043507152177723700012234 0ustar00<?php
/**
 * @package    Joomla.Administrator
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Toolbar;

defined('_JEXEC') or die;

use Joomla\CMS\Layout\FileLayout;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;

/**
 * Utility class for the button bar.
 *
 * @since  1.5
 */
abstract class ToolbarHelper
{
	/**
	 * Title cell.
	 * For the title and toolbar to be rendered correctly,
	 * this title function must be called before the starttable function and the toolbars icons
	 * this is due to the nature of how the css has been used to position the title in respect to the toolbar.
	 *
	 * @param   string  $title  The title.
	 * @param   string  $icon   The space-separated names of the image.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function title($title, $icon = 'generic.png')
	{
		$layout = new FileLayout('joomla.toolbar.title');
		$html   = $layout->render(array('title' => $title, 'icon' => $icon));

		$app = Factory::getApplication();
		$app->JComponentTitle = $html;
		Factory::getDocument()->setTitle(strip_tags($title) . ' - ' . $app->get('sitename') . ' - ' . Text::_('JADMINISTRATION'));
	}

	/**
	 * Writes a spacer cell.
	 *
	 * @param   string  $width  The width for the cell
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function spacer($width = '')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a spacer.
		$bar->appendButton('Separator', 'spacer', $width);
	}

	/**
	 * Writes a divider between menu buttons
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function divider()
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a divider.
		$bar->appendButton('Separator', 'divider');
	}

	/**
	 * Writes a custom option and task button for the button bar.
	 *
	 * @param   string  $task        The task to perform (picked up by the switch($task) blocks).
	 * @param   string  $icon        The image to display.
	 * @param   string  $iconOver    The image to display when moused over.
	 * @param   string  $alt         The alt text for the icon image.
	 * @param   bool    $listSelect  True if required to check that a standard list item is checked.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function custom($task = '', $icon = '', $iconOver = '', $alt = '', $listSelect = true)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Strip extension.
		$icon = preg_replace('#\.[^.]*$#', '', $icon);

		// Add a standard button.
		$bar->appendButton('Standard', $icon, $alt, $task, $listSelect);
	}

	/**
	 * Writes a preview button for a given option (opens a popup window).
	 *
	 * @param   string  $url            The name of the popup file (excluding the file extension)
	 * @param   bool    $updateEditors  Unused
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function preview($url = '', $updateEditors = false)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a preview button.
		$bar->appendButton('Popup', 'preview', 'Preview', $url . '&task=preview');
	}

	/**
	 * Writes a preview button for a given option (opens a popup window).
	 *
	 * @param   string  $ref        The name of the popup file (excluding the file extension for an xml file).
	 * @param   bool    $com        Use the help file in the component directory.
	 * @param   string  $override   Use this URL instead of any other
	 * @param   string  $component  Name of component to get Help (null for current component)
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function help($ref, $com = false, $override = null, $component = null)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a help button.
		$bar->appendButton('Help', $ref, $com, $override, $component);
	}

	/**
	 * Writes a cancel button that will go back to the previous page without doing
	 * any other operation.
	 *
	 * @param   string  $alt   Alternative text.
	 * @param   string  $href  URL of the href attribute.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function back($alt = 'JTOOLBAR_BACK', $href = 'javascript:history.back();')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a back button.
		$bar->appendButton('Link', 'back', $alt, $href);
	}

	/**
	 * Creates a button to redirect to a link
	 *
	 * @param   string  $url   The link url
	 * @param   string  $text  Button text
	 * @param   string  $name  Name to be used as apart of the id
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public static function link($url, $text, $name = 'link')
	{
		$bar = Toolbar::getInstance('toolbar');

		$bar->appendButton('Link', $name, $text, $url);
	}

	/**
	 * Writes a media_manager button.
	 *
	 * @param   string  $directory  The subdirectory to upload the media to.
	 * @param   string  $alt        An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function media_manager($directory = '', $alt = 'JTOOLBAR_UPLOAD')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an upload button.
		$bar->appendButton('Popup', 'upload', $alt, 'index.php?option=com_media&tmpl=component&task=popupUpload&folder=' . $directory, 800, 520);
	}

	/**
	 * Writes a common 'default' button for a record.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function makeDefault($task = 'default', $alt = 'JTOOLBAR_DEFAULT')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a default button.
		$bar->appendButton('Standard', 'default', $alt, $task, true);
	}

	/**
	 * Writes a common 'assign' button for a record.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function assign($task = 'assign', $alt = 'JTOOLBAR_ASSIGN')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an assign button.
		$bar->appendButton('Standard', 'assign', $alt, $task, true);
	}

	/**
	 * Writes the common 'new' icon for the button bar.
	 *
	 * @param   string   $task   An override for the task.
	 * @param   string   $alt    An override for the alt text.
	 * @param   boolean  $check  True if required to check that a standard list item is checked.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function addNew($task = 'add', $alt = 'JTOOLBAR_NEW', $check = false)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a new button.
		$bar->appendButton('Standard', 'new', $alt, $task, $check);
	}

	/**
	 * Writes a common 'publish' button.
	 *
	 * @param   string   $task   An override for the task.
	 * @param   string   $alt    An override for the alt text.
	 * @param   boolean  $check  True if required to check that a standard list item is checked.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function publish($task = 'publish', $alt = 'JTOOLBAR_PUBLISH', $check = false)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a publish button.
		$bar->appendButton('Standard', 'publish', $alt, $task, $check);
	}

	/**
	 * Writes a common 'publish' button for a list of records.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function publishList($task = 'publish', $alt = 'JTOOLBAR_PUBLISH')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a publish button (list).
		$bar->appendButton('Standard', 'publish', $alt, $task, true);
	}

	/**
	 * Writes a common 'unpublish' button.
	 *
	 * @param   string   $task   An override for the task.
	 * @param   string   $alt    An override for the alt text.
	 * @param   boolean  $check  True if required to check that a standard list item is checked.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function unpublish($task = 'unpublish', $alt = 'JTOOLBAR_UNPUBLISH', $check = false)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an unpublish button
		$bar->appendButton('Standard', 'unpublish', $alt, $task, $check);
	}

	/**
	 * Writes a common 'unpublish' button for a list of records.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function unpublishList($task = 'unpublish', $alt = 'JTOOLBAR_UNPUBLISH')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an unpublish button (list).
		$bar->appendButton('Standard', 'unpublish', $alt, $task, true);
	}

	/**
	 * Writes a common 'archive' button for a list of records.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function archiveList($task = 'archive', $alt = 'JTOOLBAR_ARCHIVE')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an archive button.
		$bar->appendButton('Standard', 'archive', $alt, $task, true);
	}

	/**
	 * Writes an unarchive button for a list of records.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function unarchiveList($task = 'unarchive', $alt = 'JTOOLBAR_UNARCHIVE')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an unarchive button (list).
		$bar->appendButton('Standard', 'unarchive', $alt, $task, true);
	}

	/**
	 * Writes a common 'edit' button for a list of records.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function editList($task = 'edit', $alt = 'JTOOLBAR_EDIT')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an edit button.
		$bar->appendButton('Standard', 'edit', $alt, $task, true);
	}

	/**
	 * Writes a common 'edit' button for a template html.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function editHtml($task = 'edit_source', $alt = 'JTOOLBAR_EDIT_HTML')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an edit html button.
		$bar->appendButton('Standard', 'edithtml', $alt, $task, true);
	}

	/**
	 * Writes a common 'edit' button for a template css.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function editCss($task = 'edit_css', $alt = 'JTOOLBAR_EDIT_CSS')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an edit css button (hide).
		$bar->appendButton('Standard', 'editcss', $alt, $task, true);
	}

	/**
	 * Writes a common 'delete' button for a list of records.
	 *
	 * @param   string  $msg   Postscript for the 'are you sure' message.
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function deleteList($msg = '', $task = 'remove', $alt = 'JTOOLBAR_DELETE')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a delete button.
		if ($msg)
		{
			$bar->appendButton('Confirm', $msg, 'delete', $alt, $task, true);
		}
		else
		{
			$bar->appendButton('Standard', 'delete', $alt, $task, true);
		}
	}

	/**
	 * Writes a common 'trash' button for a list of records.
	 *
	 * @param   string  $task   An override for the task.
	 * @param   string  $alt    An override for the alt text.
	 * @param   bool    $check  True to allow lists.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function trash($task = 'remove', $alt = 'JTOOLBAR_TRASH', $check = true)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a trash button.
		$bar->appendButton('Standard', 'trash', $alt, $task, $check, false);
	}

	/**
	 * Writes a save button for a given option.
	 * Apply operation leads to a save action only (does not leave edit mode).
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function apply($task = 'apply', $alt = 'JTOOLBAR_APPLY')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add an apply button
		$bar->appendButton('Standard', 'apply', $alt, $task, false);
	}

	/**
	 * Writes a save button for a given option.
	 * Save operation leads to a save and then close action.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function save($task = 'save', $alt = 'JTOOLBAR_SAVE')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a save button.
		$bar->appendButton('Standard', 'save', $alt, $task, false);
	}

	/**
	 * Writes a save and create new button for a given option.
	 * Save and create operation leads to a save and then add action.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public static function save2new($task = 'save2new', $alt = 'JTOOLBAR_SAVE_AND_NEW')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a save and create new button.
		$bar->appendButton('Standard', 'save-new', $alt, $task, false);
	}

	/**
	 * Writes a save as copy button for a given option.
	 * Save as copy operation leads to a save after clearing the key,
	 * then returns user to edit mode with new key.
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public static function save2copy($task = 'save2copy', $alt = 'JTOOLBAR_SAVE_AS_COPY')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a save and create new button.
		$bar->appendButton('Standard', 'save-copy', $alt, $task, false);
	}

	/**
	 * Writes a checkin button for a given option.
	 *
	 * @param   string   $task   An override for the task.
	 * @param   string   $alt    An override for the alt text.
	 * @param   boolean  $check  True if required to check that a standard list item is checked.
	 *
	 * @return  void
	 *
	 * @since   1.7
	 */
	public static function checkin($task = 'checkin', $alt = 'JTOOLBAR_CHECKIN', $check = true)
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a save and create new button.
		$bar->appendButton('Standard', 'checkin', $alt, $task, $check);
	}

	/**
	 * Writes a cancel button and invokes a cancel operation (eg a checkin).
	 *
	 * @param   string  $task  An override for the task.
	 * @param   string  $alt   An override for the alt text.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function cancel($task = 'cancel', $alt = 'JTOOLBAR_CANCEL')
	{
		$bar = Toolbar::getInstance('toolbar');

		// Add a cancel button.
		$bar->appendButton('Standard', 'cancel', $alt, $task, false);
	}

	/**
	 * Writes a configuration button and invokes a cancel operation (eg a checkin).
	 *
	 * @param   string   $component  The name of the component, eg, com_content.
	 * @param   integer  $height     The height of the popup. [UNUSED]
	 * @param   integer  $width      The width of the popup. [UNUSED]
	 * @param   string   $alt        The name of the button.
	 * @param   string   $path       An alternative path for the configuation xml relative to JPATH_SITE.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function preferences($component, $height = '550', $width = '875', $alt = 'JToolbar_Options', $path = '')
	{
		$component = urlencode($component);
		$path = urlencode($path);
		$bar = Toolbar::getInstance('toolbar');

		$uri = (string) Uri::getInstance();
		$return = urlencode(base64_encode($uri));

		// Add a button linking to config for component.
		$bar->appendButton(
			'Link',
			'options',
			$alt,
			'index.php?option=com_config&amp;view=component&amp;component=' . $component . '&amp;path=' . $path . '&amp;return=' . $return
		);
	}

	/**
	 * Writes a version history
	 *
	 * @param   string   $typeAlias  The component and type, for example 'com_content.article'
	 * @param   integer  $itemId     The id of the item, for example the article id.
	 * @param   integer  $height     The height of the popup.
	 * @param   integer  $width      The width of the popup.
	 * @param   string   $alt        The name of the button.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function versions($typeAlias, $itemId, $height = 800, $width = 500, $alt = 'JTOOLBAR_VERSIONS')
	{
		$lang = Factory::getLanguage();
		$lang->load('com_contenthistory', JPATH_ADMINISTRATOR, $lang->getTag(), true);
		$contentTypeTable = Table::getInstance('Contenttype');
		$typeId           = $contentTypeTable->getTypeId($typeAlias);

		// Options array for JLayout
		$options              = array();
		$options['title']     = Text::_($alt);
		$options['height']    = $height;
		$options['width']     = $width;
		$options['itemId']    = $itemId;
		$options['typeId']    = $typeId;
		$options['typeAlias'] = $typeAlias;

		$bar    = Toolbar::getInstance('toolbar');
		$layout = new FileLayout('joomla.toolbar.versions');
		$bar->appendButton('Custom', $layout->render($options), 'versions');
	}

	/**
	 * Displays a modal button
	 *
	 * @param   string  $targetModalId  ID of the target modal box
	 * @param   string  $icon           Icon class to show on modal button
	 * @param   string  $alt            Title for the modal button
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function modal($targetModalId, $icon, $alt)
	{
		$title = Text::_($alt);
		$dhtml = '<button data-toggle="modal" data-target="#' . $targetModalId . '" class="btn btn-small">
			<span class="' . $icon . '" title="' . $title . '"></span> ' . $title . '</button>';

		$bar = Toolbar::getInstance('toolbar');
		$bar->appendButton('Custom', $dhtml, $alt);
	}
}
src/Authentication/Authentication.php000064400000016712152177723700014024 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Authentication;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Plugin\PluginHelper;

/**
 * Authentication class, provides an interface for the Joomla authentication system
 *
 * @since  1.7.0
 */
class Authentication extends \JObject
{
	// Shared success status
	/**
	 * This is the status code returned when the authentication is success (permit login)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_SUCCESS = 1;

	// These are for authentication purposes (username and password is valid)
	/**
	 * Status to indicate cancellation of authentication (unused)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_CANCEL = 2;

	/**
	 * This is the status code returned when the authentication failed (prevent login if no success)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_FAILURE = 4;

	// These are for authorisation purposes (can the user login)
	/**
	 * This is the status code returned when the account has expired (prevent login)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_EXPIRED = 8;

	/**
	 * This is the status code returned when the account has been denied (prevent login)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_DENIED = 16;

	/**
	 * This is the status code returned when the account doesn't exist (not an error)
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	const STATUS_UNKNOWN = 32;

	/**
	 * An array of Observer objects to notify
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $observers = array();

	/**
	 * The state of the observable object
	 *
	 * @var    mixed
	 * @since  3.0.0
	 */
	protected $state = null;

	/**
	 * A multi dimensional array of [function][] = key for observers
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $methods = array();

	/**
	 * @var    Authentication  Authentication instances container.
	 * @since  1.7.3
	 */
	protected static $instance;

	/**
	 * Constructor
	 *
	 * @since   1.7.0
	 */
	public function __construct()
	{
		$isLoaded = PluginHelper::importPlugin('authentication');

		if (!$isLoaded)
		{
			\JLog::add(\JText::_('JLIB_USER_ERROR_AUTHENTICATION_LIBRARIES'), \JLog::WARNING, 'jerror');
		}
	}

	/**
	 * Returns the global authentication object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return  Authentication  The global Authentication object
	 *
	 * @since   1.7.0
	 */
	public static function getInstance()
	{
		if (empty(self::$instance))
		{
			self::$instance = new Authentication;
		}

		return self::$instance;
	}

	/**
	 * Get the state of the Authentication object
	 *
	 * @return  mixed    The state of the object.
	 *
	 * @since   1.7.0
	 */
	public function getState()
	{
		return $this->state;
	}

	/**
	 * Attach an observer object
	 *
	 * @param   object  $observer  An observer object to attach
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function attach($observer)
	{
		if (is_array($observer))
		{
			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
			{
				return;
			}

			// Make sure we haven't already attached this array as an observer
			foreach ($this->observers as $check)
			{
				if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
				{
					return;
				}
			}

			$this->observers[] = $observer;
			end($this->observers);
			$methods = array($observer['event']);
		}
		else
		{
			if (!($observer instanceof Authentication))
			{
				return;
			}

			// Make sure we haven't already attached this object as an observer
			$class = get_class($observer);

			foreach ($this->observers as $check)
			{
				if ($check instanceof $class)
				{
					return;
				}
			}

			$this->observers[] = $observer;
			$methods = array_diff(get_class_methods($observer), get_class_methods('\\JPlugin'));
		}

		$key = key($this->observers);

		foreach ($methods as $method)
		{
			$method = strtolower($method);

			if (!isset($this->methods[$method]))
			{
				$this->methods[$method] = array();
			}

			$this->methods[$method][] = $key;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @param   object  $observer  An observer object to detach.
	 *
	 * @return  boolean  True if the observer object was detached.
	 *
	 * @since   1.7.0
	 */
	public function detach($observer)
	{
		$retval = false;

		$key = array_search($observer, $this->observers);

		if ($key !== false)
		{
			unset($this->observers[$key]);
			$retval = true;

			foreach ($this->methods as &$method)
			{
				$k = array_search($key, $method);

				if ($k !== false)
				{
					unset($method[$k]);
				}
			}
		}

		return $retval;
	}

	/**
	 * Finds out if a set of login credentials are valid by asking all observing
	 * objects to run their respective authentication routines.
	 *
	 * @param   array  $credentials  Array holding the user credentials.
	 * @param   array  $options      Array holding user options.
	 *
	 * @return  AuthenticationResponse  Response object with status variable filled in for last plugin or first successful plugin.
	 *
	 * @see     AuthenticationResponse
	 * @since   1.7.0
	 */
	public function authenticate($credentials, $options = array())
	{
		// Get plugins
		$plugins = PluginHelper::getPlugin('authentication');

		// Create authentication response
		$response = new AuthenticationResponse;

		/*
		 * Loop through the plugins and check if the credentials can be used to authenticate
		 * the user
		 *
		 * Any errors raised in the plugin should be returned via the AuthenticationResponse
		 * and handled appropriately.
		 */
		foreach ($plugins as $plugin)
		{
			$className = 'plg' . $plugin->type . $plugin->name;

			if (class_exists($className))
			{
				$plugin = new $className($this, (array) $plugin);
			}
			else
			{
				// Bail here if the plugin can't be created
				\JLog::add(\JText::sprintf('JLIB_USER_ERROR_AUTHENTICATION_FAILED_LOAD_PLUGIN', $className), \JLog::WARNING, 'jerror');
				continue;
			}

			// Try to authenticate
			$plugin->onUserAuthenticate($credentials, $options, $response);

			// If authentication is successful break out of the loop
			if ($response->status === self::STATUS_SUCCESS)
			{
				if (empty($response->type))
				{
					$response->type = isset($plugin->_name) ? $plugin->_name : $plugin->name;
				}

				break;
			}
		}

		if (empty($response->username))
		{
			$response->username = $credentials['username'];
		}

		if (empty($response->fullname))
		{
			$response->fullname = $credentials['username'];
		}

		if (empty($response->password) && isset($credentials['password']))
		{
			$response->password = $credentials['password'];
		}

		return $response;
	}

	/**
	 * Authorises that a particular user should be able to login
	 *
	 * @param   AuthenticationResponse  $response  response including username of the user to authorise
	 * @param   array                   $options   list of options
	 *
	 * @return  AuthenticationResponse[]  Array of authentication response objects
	 *
	 * @since  1.7.0
	 */
	public static function authorise($response, $options = array())
	{
		// Get plugins in case they haven't been imported already
		PluginHelper::importPlugin('user');

		PluginHelper::importPlugin('authentication');
		$dispatcher = \JEventDispatcher::getInstance();
		$results = $dispatcher->trigger('onUserAuthorisation', array($response, $options));

		return $results;
	}
}
src/Authentication/AuthenticationResponse.php000064400000005117152177723700015540 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Authentication;

defined('JPATH_PLATFORM') or die;

/**
 * Authentication response class, provides an object for storing user and error details
 *
 * @since  1.7.0
 */
class AuthenticationResponse
{
	/**
	 * Response status (see status codes)
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $status = Authentication::STATUS_FAILURE;

	/**
	 * The type of authentication that was successful
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $type = '';

	/**
	 *  The error message
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $error_message = '';

	/**
	 * Any UTF-8 string that the End User wants to use as a username.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $username = '';

	/**
	 * Any UTF-8 string that the End User wants to use as a password.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $password = '';

	/**
	 * The email address of the End User as specified in section 3.4.1 of [RFC2822]
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $email = '';

	/**
	 * UTF-8 string free text representation of the End User's full name.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $fullname = '';

	/**
	 * The End User's date of birth as YYYY-MM-DD. Any values whose representation uses
	 * fewer than the specified number of digits should be zero-padded. The length of this
	 * value MUST always be 10. If the End User user does not want to reveal any particular
	 * component of this value, it MUST be set to zero.
	 *
	 * For instance, if an End User wants to specify that their date of birth is in 1980, but
	 * not the month or day, the value returned SHALL be "1980-00-00".
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $birthdate = '';

	/**
	 * The End User's gender, "M" for male, "F" for female.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $gender = '';

	/**
	 * UTF-8 string free text that SHOULD conform to the End User's country's postal system.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $postcode = '';

	/**
	 * The End User's country of residence as specified by ISO3166.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $country = '';

	/**
	 * End User's preferred language as specified by ISO639.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $language = '';

	/**
	 * ASCII string from TimeZone database
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $timezone = '';
}
src/Access/Rule.php000064400000006530152177723700010173 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

defined('JPATH_PLATFORM') or die;

/**
 * Rule class.
 *
 * @since  2.5.0
 */
class Rule
{
	/**
	 * A named array
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $data = array();

	/**
	 * Constructor.
	 *
	 * The input array must be in the form: array(-42 => true, 3 => true, 4 => false)
	 * or an equivalent JSON encoded string.
	 *
	 * @param   mixed  $identities  A JSON format string (probably from the database) or a named array.
	 *
	 * @since   1.7.0
	 */
	public function __construct($identities)
	{
		// Convert string input to an array.
		if (is_string($identities))
		{
			$identities = json_decode($identities, true);
		}

		$this->mergeIdentities($identities);
	}

	/**
	 * Get the data for the action.
	 *
	 * @return  array  A named array
	 *
	 * @since   1.7.0
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Merges the identities
	 *
	 * @param   mixed  $identities  An integer or array of integers representing the identities to check.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function mergeIdentities($identities)
	{
		if ($identities instanceof Rule)
		{
			$identities = $identities->getData();
		}

		if (is_array($identities))
		{
			foreach ($identities as $identity => $allow)
			{
				$this->mergeIdentity($identity, $allow);
			}
		}
	}

	/**
	 * Merges the values for an identity.
	 *
	 * @param   integer  $identity  The identity.
	 * @param   boolean  $allow     The value for the identity (true == allow, false == deny).
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function mergeIdentity($identity, $allow)
	{
		$identity = (int) $identity;
		$allow = (int) ((boolean) $allow);

		// Check that the identity exists.
		if (isset($this->data[$identity]))
		{
			// Explicit deny always wins a merge.
			if ($this->data[$identity] !== 0)
			{
				$this->data[$identity] = $allow;
			}
		}
		else
		{
			$this->data[$identity] = $allow;
		}
	}

	/**
	 * Checks that this action can be performed by an identity.
	 *
	 * The identity is an integer where +ve represents a user group,
	 * and -ve represents a user.
	 *
	 * @param   mixed  $identities  An integer or array of integers representing the identities to check.
	 *
	 * @return  mixed  True if allowed, false for an explicit deny, null for an implicit deny.
	 *
	 * @since   1.7.0
	 */
	public function allow($identities)
	{
		// Implicit deny by default.
		$result = null;

		// Check that the inputs are valid.
		if (!empty($identities))
		{
			if (!is_array($identities))
			{
				$identities = array($identities);
			}

			foreach ($identities as $identity)
			{
				// Technically the identity just needs to be unique.
				$identity = (int) $identity;

				// Check if the identity is known.
				if (isset($this->data[$identity]))
				{
					$result = (boolean) $this->data[$identity];

					// An explicit deny wins.
					if ($result === false)
					{
						break;
					}
				}
			}
		}

		return $result;
	}

	/**
	 * Convert this object into a JSON encoded string.
	 *
	 * @return  string  JSON encoded string
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		return json_encode($this->data);
	}
}
src/Access/Wrapper/Access.php000064400000013441152177723700012104 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access\Wrapper;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access as StaticAccess;
use Joomla\CMS\Access\Rules as AccessRules;

/**
 * Wrapper class for Access
 *
 * @since       3.4
 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
 */
class Access
{
	/**
	 * Helper wrapper method for addUserToGroup
	 *
	 * @return void
	 *
	 * @see     StaticAccess::clearStatics
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function clearStatics()
	{
		return StaticAccess::clearStatics();
	}

	/**
	 * Helper wrapper method for check
	 *
	 * @param   integer  $userId  Id of the user for which to check authorisation.
	 * @param   string   $action  The name of the action to authorise.
	 * @param   mixed    $asset   Integer asset id or the name of the asset as a string.  Defaults to the global asset node.
	 *
	 * @return boolean  True if authorised.
	 *
	 * @see     StaticAccess::check()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function check($userId, $action, $asset = null)
	{
		return StaticAccess::check($userId, $action, $asset);
	}

	/**
	 * Helper wrapper method for checkGroup
	 *
	 * @param   integer  $groupId  The path to the group for which to check authorisation.
	 * @param   string   $action   The name of the action to authorise.
	 * @param   mixed    $asset    Integer asset id or the name of the asset as a string.  Defaults to the global asset node.
	 *
	 * @return  boolean  True if authorised.
	 *
	 * @see     StaticAccess::checkGroup()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function checkGroup($groupId, $action, $asset = null)
	{
		return StaticAccess::checkGroup($groupId, $action, $asset);
	}

	/**
	 * Helper wrapper method for getAssetRules
	 *
	 * @param   mixed    $asset      Integer asset id or the name of the asset as a string.
	 * @param   boolean  $recursive  True to return the rules object with inherited rules.
	 *
	 * @return  AccessRules   AccessRules object for the asset.
	 *
	 * @see     StaticAccess::getAssetRules
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getAssetRules($asset, $recursive = false)
	{
		return StaticAccess::getAssetRules($asset, $recursive);
	}

	/**
	 * Helper wrapper method for getGroupsByUser
	 *
	 * @param   integer  $userId     Id of the user for which to get the list of groups.
	 * @param   boolean  $recursive  True to include inherited user groups.
	 *
	 * @return  array    List of user group ids to which the user is mapped.
	 *
	 * @see     StaticAccess::getGroupsByUser()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getGroupsByUser($userId, $recursive = true)
	{
		return StaticAccess::getGroupsByUser($userId, $recursive);
	}

	/**
	 * Helper wrapper method for getUsersByGroup
	 *
	 * @param   integer  $groupId    The group Id
	 * @param   boolean  $recursive  Recursively include all child groups (optional)
	 *
	 * @return  array
	 *
	 * @see     StaticAccess::getUsersByGroup()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getUsersByGroup($groupId, $recursive = false)
	{
		return StaticAccess::getUsersByGroup($groupId, $recursive);
	}

	/**
	 * Helper wrapper method for getAuthorisedViewLevels
	 *
	 * @param   integer  $userId  Id of the user for which to get the list of authorised view levels.
	 *
	 * @return  array    List of view levels for which the user is authorised.
	 *
	 * @see     StaticAccess::getAuthorisedViewLevels()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getAuthorisedViewLevels($userId)
	{
		return StaticAccess::getAuthorisedViewLevels($userId);
	}

	/**
	 * Helper wrapper method for getActions
	 *
	 * @param   string  $component  The component from which to retrieve the actions.
	 * @param   string  $section    The name of the section within the component from which to retrieve the actions.
	 *
	 * @return array  List of actions available for the given component and section.
	 *
	 * @see     StaticAccess::getActions()
	 * @since   3.4
	 * @deprecated  4.0  Use StaticAccess::getActionsFromFile or StaticAccess::getActionsFromData instead.
	 */
	public function getActions($component, $section = 'component')
	{
		return StaticAccess::getActions($component, $section);
	}

	/**
	 * Helper wrapper method for getActionsFromFile
	 *
	 * @param   string  $file   The path to the XML file.
	 * @param   string  $xpath  An optional xpath to search for the fields.
	 *
	 * @return  boolean|array   False if case of error or the list of actions available.
	 *
	 * @see     StaticAccess::getActionsFromFile()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getActionsFromFile($file, $xpath = '/access/section[@name=\'component\']/')
	{
		return StaticAccess::getActionsFromFile($file, $xpath);
	}

	/**
	 * Helper wrapper method for getActionsFromData
	 *
	 * @param   string|\SimpleXMLElement  $data   The XML string or an XML element.
	 * @param   string                    $xpath  An optional xpath to search for the fields.
	 *
	 * @return  boolean|array   False if case of error or the list of actions available.
	 *
	 * @see     StaticAccess::getActionsFromData()
	 * @since   3.4
	 * @deprecated  4.0  Use `Joomla\CMS\Access\Access` directly
	 */
	public function getActionsFromData($data, $xpath = '/access/section[@name=\'component\']/')
	{
		return StaticAccess::getActionsFromData($data, $xpath);
	}
}
src/Access/Rules.php000064400000010450152177723700010352 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

defined('JPATH_PLATFORM') or die;

/**
 * Access rules class.
 *
 * @since  2.5.0
 */
class Rules
{
	/**
	 * A named array.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $data = array();

	/**
	 * Constructor.
	 *
	 * The input array must be in the form: array('action' => array(-42 => true, 3 => true, 4 => false))
	 * or an equivalent JSON encoded string, or an object where properties are arrays.
	 *
	 * @param   mixed  $input  A JSON format string (probably from the database) or a nested array.
	 *
	 * @since   1.7.0
	 */
	public function __construct($input = '')
	{
		// Convert in input to an array.
		if (is_string($input))
		{
			$input = json_decode($input, true);
		}
		elseif (is_object($input))
		{
			$input = (array) $input;
		}

		if (is_array($input))
		{
			// Top level keys represent the actions.
			foreach ($input as $action => $identities)
			{
				$this->mergeAction($action, $identities);
			}
		}
	}

	/**
	 * Get the data for the action.
	 *
	 * @return  array  A named array of Rule objects.
	 *
	 * @since   1.7.0
	 */
	public function getData()
	{
		return $this->data;
	}

	/**
	 * Method to merge a collection of Rules.
	 *
	 * @param   mixed  $input  Rule or array of Rules
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function mergeCollection($input)
	{
		// Check if the input is an array.
		if (is_array($input))
		{
			foreach ($input as $actions)
			{
				$this->merge($actions);
			}
		}
	}

	/**
	 * Method to merge actions with this object.
	 *
	 * @param   mixed  $actions  Rule object, an array of actions or a JSON string array of actions.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function merge($actions)
	{
		if (is_string($actions))
		{
			$actions = json_decode($actions, true);
		}

		if (is_array($actions))
		{
			foreach ($actions as $action => $identities)
			{
				$this->mergeAction($action, $identities);
			}
		}
		elseif ($actions instanceof Rules)
		{
			$data = $actions->getData();

			foreach ($data as $name => $identities)
			{
				$this->mergeAction($name, $identities);
			}
		}
	}

	/**
	 * Merges an array of identities for an action.
	 *
	 * @param   string  $action      The name of the action.
	 * @param   array   $identities  An array of identities
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function mergeAction($action, $identities)
	{
		if (isset($this->data[$action]))
		{
			// If exists, merge the action.
			$this->data[$action]->mergeIdentities($identities);
		}
		else
		{
			// If new, add the action.
			$this->data[$action] = new Rule($identities);
		}
	}

	/**
	 * Checks that an action can be performed by an identity.
	 *
	 * The identity is an integer where +ve represents a user group,
	 * and -ve represents a user.
	 *
	 * @param   string  $action    The name of the action.
	 * @param   mixed   $identity  An integer representing the identity, or an array of identities
	 *
	 * @return  mixed   Object or null if there is no information about the action.
	 *
	 * @since   1.7.0
	 */
	public function allow($action, $identity)
	{
		// Check we have information about this action.
		if (isset($this->data[$action]))
		{
			return $this->data[$action]->allow($identity);
		}

		return;
	}

	/**
	 * Get the allowed actions for an identity.
	 *
	 * @param   mixed  $identity  An integer representing the identity or an array of identities
	 *
	 * @return  \JObject  Allowed actions for the identity or identities
	 *
	 * @since   1.7.0
	 */
	public function getAllowed($identity)
	{
		// Sweep for the allowed actions.
		$allowed = new \JObject;

		foreach ($this->data as $name => &$action)
		{
			if ($action->allow($identity))
			{
				$allowed->set($name, true);
			}
		}

		return $allowed;
	}

	/**
	 * Magic method to convert the object to JSON string representation.
	 *
	 * @return  string  JSON representation of the actions array
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		$temp = array();

		foreach ($this->data as $name => $rule)
		{
			if ($data = $rule->getData())
			{
				$temp[$name] = $data;
			}
		}

		return json_encode($temp, JSON_FORCE_OBJECT);
	}
}
src/Access/Exception/NotAllowed.php000064400000000644152177723700013272 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining a not allowed access
 *
 * @since  3.6.3
 */
class NotAllowed extends \RuntimeException
{
}
src/Access/Access.php000064400000110010152177723700010452 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Access;

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;
use Joomla\CMS\Table\Asset;

/**
 * Class that handles all access authorisation routines.
 *
 * @since  1.7.0
 */
class Access
{
	/**
	 * Array of view levels
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $viewLevels = array();

	/**
	 * Array of rules for the asset
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $assetRules = array();

	/**
	 * Array of identities for asset rules
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $assetRulesIdentities = array();

	/**
	 * Array of permissions for an asset type
	 * (Array Key = Asset ID)
	 * Also includes the rules string for the asset
	 *
	 * @var    array
	 * @since  1.7.0
	 * @deprecated  3.7.0  No replacement. Will be removed in 4.0.
	 */
	protected static $assetPermissionsById = array();

	/**
	 * Array of permissions for an asset type
	 * (Array Key = Asset Name)
	 *
	 * @var    array
	 * @since  1.7.0
	 * @deprecated  3.7.0  No replacement. Will be removed in 4.0.
	 */
	protected static $assetPermissionsByName = array();

	/**
	 * Array of the permission parent ID mappings
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $assetPermissionsParentIdMapping = array();

	/**
	 * Array of asset types that have been preloaded
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $preloadedAssetTypes = array();

	/**
	 * Array of loaded user identities
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $identities = array();

	/**
	 * Array of user groups.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $userGroups = array();

	/**
	 * Array of user group paths.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $userGroupPaths = array();

	/**
	 * Array of cached groups by user.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $groupsByUser = array();

	/**
	 * Array of preloaded asset names and ids (key is the asset id).
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	protected static $preloadedAssets = array();

	/**
	 * The root asset id.
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	protected static $rootAssetId = null;

	/**
	 * Method for clearing static caches.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public static function clearStatics()
	{
		self::$viewLevels                      = array();
		self::$assetRules                      = array();
		self::$assetRulesIdentities            = array();
		self::$assetPermissionsParentIdMapping = array();
		self::$preloadedAssetTypes             = array();
		self::$identities                      = array();
		self::$userGroups                      = array();
		self::$userGroupPaths                  = array();
		self::$groupsByUser                    = array();
		self::$preloadedAssets                 = array();
		self::$rootAssetId                     = null;

		// The following properties are deprecated since 3.7.0 and will be removed in 4.0.
		self::$assetPermissionsById   = array();
		self::$assetPermissionsByName = array();
	}

	/**
	 * Method to check if a user is authorised to perform an action, optionally on an asset.
	 *
	 * @param   integer         $userId    Id of the user for which to check authorisation.
	 * @param   string          $action    The name of the action to authorise.
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
	 * @param   boolean         $preload   Indicates whether preloading should be used.
	 *
	 * @return  boolean|null  True if allowed, false for an explicit deny, null for an implicit deny.
	 *
	 * @since   1.7.0
	 */
	public static function check($userId, $action, $assetKey = null, $preload = true)
	{
		// Sanitise inputs.
		$userId = (int) $userId;
		$action = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));

		if (!isset(self::$identities[$userId]))
		{
			// Get all groups against which the user is mapped.
			self::$identities[$userId] = self::getGroupsByUser($userId);
			array_unshift(self::$identities[$userId], $userId * -1);
		}

		return self::getAssetRules($assetKey, true, true, $preload)->allow($action, self::$identities[$userId]);
	}

	/**
	 * Method to preload the Rules object for the given asset type.
	 *
	 * @param   integer|string|array  $assetTypes  The type or name of the asset (e.g. 'com_content.article', 'com_menus.menu.2').
	 *                                             Also accepts the asset id. An array of asset type or a special
	 *                                             'components' string to load all component assets.
	 * @param   boolean               $reload      Set to true to reload from database.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.6
	 * @note    This method will return void in 4.0.
	 */
	public static function preload($assetTypes = 'components', $reload = false)
	{
		// If sent an asset id, we first get the asset type for that asset id.
		if (is_numeric($assetTypes))
		{
			$assetTypes = self::getAssetType($assetTypes);
		}

		// Check for default case:
		$isDefault = is_string($assetTypes) && in_array($assetTypes, array('components', 'component'));

		// Preload the rules for all of the components.
		if ($isDefault)
		{
			self::preloadComponents();

			return true;
		}

		// If we get to this point, this is a regular asset type and we'll proceed with the preloading process.
		if (!is_array($assetTypes))
		{
			$assetTypes = (array) $assetTypes;
		}

		foreach ($assetTypes as $assetType)
		{
			self::preloadPermissions($assetType, $reload);
		}

		return true;
	}

	/**
	 * Method to recursively retrieve the list of parent Asset IDs
	 * for a particular Asset.
	 *
	 * @param   string   $assetType  The asset type, or the asset name, or the extension of the asset
	 *                               (e.g. 'com_content.article', 'com_menus.menu.2', 'com_contact').
	 * @param   integer  $assetId    The numeric asset id.
	 *
	 * @return  array  List of ancestor ids (includes original $assetId).
	 *
	 * @since   1.6
	 */
	protected static function getAssetAncestors($assetType, $assetId)
	{
		// Get the extension name from the $assetType provided
		$extensionName = self::getExtensionNameFromAsset($assetType);

		// Holds the list of ancestors for the Asset ID:
		$ancestors = array();

		// Add in our starting Asset ID:
		$ancestors[] = (int) $assetId;

		// Initialize the variable we'll use in the loop:
		$id = (int) $assetId;

		while ($id !== 0)
		{
			if (isset(self::$assetPermissionsParentIdMapping[$extensionName][$id]))
			{
				$id = (int) self::$assetPermissionsParentIdMapping[$extensionName][$id]->parent_id;

				if ($id !== 0)
				{
					$ancestors[] = $id;
				}
			}
			else
			{
				// Add additional case to break out of the while loop automatically in
				// the case that the ID is non-existent in our mapping variable above.
				break;
			}
		}

		return $ancestors;
	}

	/**
	 * Method to retrieve the list of Asset IDs and their Parent Asset IDs
	 * and store them for later usage in getAssetRules().
	 *
	 * @param   string  $assetType  The asset type, or the asset name, or the extension of the asset
	 *                              (e.g. 'com_content.article', 'com_menus.menu.2', 'com_contact').
	 *
	 * @return  array  List of asset ids (includes parent asset id information).
	 *
	 * @since   1.6
	 * @deprecated  3.7.0  No replacement. Will be removed in 4.0.
	 */
	protected static function &preloadPermissionsParentIdMapping($assetType)
	{
		// Get the extension name from the $assetType provided
		$extensionName = self::getExtensionNameFromAsset($assetType);

		if (!isset(self::$assetPermissionsParentIdMapping[$extensionName]))
		{
			// Get the database connection object.
			$db = \JFactory::getDbo();

			// Get a fresh query object:
			$query    = $db->getQuery(true);

			// Build the database query:
			$query->select('a.id, a.parent_id');
			$query->from('#__assets AS a');
			$query->where('(a.name LIKE ' . $db->quote($extensionName . '.%') . ' OR a.name = ' . $db->quote($extensionName) . ' OR a.id = 1)');

			// Get the Name Permission Map List
			$db->setQuery($query);
			$parentIdMapping = $db->loadObjectList('id');

			self::$assetPermissionsParentIdMapping[$extensionName] = &$parentIdMapping;
		}

		return self::$assetPermissionsParentIdMapping[$extensionName];
	}

	/**
	 * Method to retrieve the Asset Rule strings for this particular
	 * Asset Type and stores them for later usage in getAssetRules().
	 * Stores 2 arrays: one where the list has the Asset ID as the key
	 * and a second one where the Asset Name is the key.
	 *
	 * @param   string   $assetType  The asset type, or the asset name, or the extension of the asset
	 *                               (e.g. 'com_content.article', 'com_menus.menu.2', 'com_contact').
	 * @param   boolean  $reload     Reload the preloaded assets.
	 *
	 * @return  boolean  True
	 *
	 * @since   1.6
	 * @note    This function will return void in 4.0.
	 */
	protected static function preloadPermissions($assetType, $reload = false)
	{
		// Get the extension name from the $assetType provided
		$extensionName = self::getExtensionNameFromAsset($assetType);

		// If asset is a component, make sure that all the component assets are preloaded.
		if ((isset(self::$preloadedAssetTypes[$extensionName]) || isset(self::$preloadedAssetTypes[$assetType])) && !$reload)
		{
			return true;
		}

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('Before Access::preloadPermissions (' . $extensionName . ')');

		// Get the database connection object.
		$db         = \JFactory::getDbo();
		$extraQuery = $db->qn('name') . ' = ' . $db->q($extensionName) . ' OR ' . $db->qn('parent_id') . ' = 0';

		// Get a fresh query object.
		$query = $db->getQuery(true)
			->select($db->qn(array('id', 'name', 'rules', 'parent_id')))
			->from($db->qn('#__assets'))
			->where($db->qn('name') . ' LIKE ' . $db->q($extensionName . '.%') . ' OR ' . $extraQuery);

		// Get the permission map for all assets in the asset extension.
		$assets = $db->setQuery($query)->loadObjectList();

		self::$assetPermissionsParentIdMapping[$extensionName] = array();

		// B/C Populate the old class properties. They are deprecated since 3.7.0 and will be removed in 4.0.
		self::$assetPermissionsById[$assetType]   = array();
		self::$assetPermissionsByName[$assetType] = array();

		foreach ($assets as $asset)
		{
			self::$assetPermissionsParentIdMapping[$extensionName][$asset->id] = $asset;
			self::$preloadedAssets[$asset->id]                                 = $asset->name;

			// B/C Populate the old class properties. They are deprecated since 3.7.0 and will be removed in 4.0.
			self::$assetPermissionsById[$assetType][$asset->id]     = $asset;
			self::$assetPermissionsByName[$assetType][$asset->name] = $asset;
		}

		// Mark asset type and it's extension name as preloaded.
		self::$preloadedAssetTypes[$assetType]     = true;
		self::$preloadedAssetTypes[$extensionName] = true;

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('After Access::preloadPermissions (' . $extensionName . ')');

		return true;
	}

	/**
	 * Method to preload the Rules objects for all components.
	 *
	 * Note: This will only get the base permissions for the component.
	 * e.g. it will get 'com_content', but not 'com_content.article.1' or
	 * any more specific asset type rules.
	 *
	 * @return   array  Array of component names that were preloaded.
	 *
	 * @since    1.6
	 */
	protected static function preloadComponents()
	{
		// If the components already been preloaded do nothing.
		if (isset(self::$preloadedAssetTypes['components']))
		{
			return array();
		}

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('Before Access::preloadComponents (all components)');

		// Add root to asset names list.
		$components = array('root.1');

		// Add enabled components to asset names list.
		foreach (\JComponentHelper::getComponents() as $component)
		{
			if ($component->enabled)
			{
				$components[] = $component->option;
			}
		}

		// Get the database connection object.
		$db = \JFactory::getDbo();

		// Get the asset info for all assets in asset names list.
		$query = $db->getQuery(true)
			->select($db->qn(array('id', 'name', 'rules', 'parent_id')))
			->from($db->qn('#__assets'))
			->where($db->qn('name') . ' IN (' . implode(',', $db->quote($components)) . ')');

		// Get the Name Permission Map List
		$assets = $db->setQuery($query)->loadObjectList();

		$rootAsset = null;

		// First add the root asset and save it to preload memory and mark it as preloaded.
		foreach ($assets as &$asset)
		{
			if ((int) $asset->parent_id === 0)
			{
				$rootAsset                                                       = $asset;
				self::$rootAssetId                                               = $asset->id;
				self::$preloadedAssetTypes[$asset->name]                         = true;
				self::$preloadedAssets[$asset->id]                               = $asset->name;
				self::$assetPermissionsParentIdMapping[$asset->name][$asset->id] = $asset;

				unset($asset);
				break;
			}
		}

		// Now create save the components asset tree to preload memory.
		foreach ($assets as $asset)
		{
			if (!isset(self::$assetPermissionsParentIdMapping[$asset->name]))
			{
				self::$assetPermissionsParentIdMapping[$asset->name] = array($rootAsset->id => $rootAsset, $asset->id => $asset);
				self::$preloadedAssets[$asset->id]                   = $asset->name;
			}
		}

		// Mark all components asset type as preloaded.
		self::$preloadedAssetTypes['components'] = true;

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('After Access::preloadComponents (all components)');

		return $components;
	}

	/**
	 * Method to check if a group is authorised to perform an action, optionally on an asset.
	 *
	 * @param   integer         $groupId   The path to the group for which to check authorisation.
	 * @param   string          $action    The name of the action to authorise.
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
	 * @param   boolean         $preload   Indicates whether preloading should be used.
	 *
	 * @return  boolean  True if authorised.
	 *
	 * @since   1.7.0
	 */
	public static function checkGroup($groupId, $action, $assetKey = null, $preload = true)
	{
		// Sanitize input.
		$groupId = (int) $groupId;
		$action  = strtolower(preg_replace('#[\s\-]+#', '.', trim($action)));

		return self::getAssetRules($assetKey, true, true, $preload)->allow($action, self::getGroupPath($groupId));
	}

	/**
	 * Gets the parent groups that a leaf group belongs to in its branch back to the root of the tree
	 * (including the leaf group id).
	 *
	 * @param   mixed  $groupId  An integer or array of integers representing the identities to check.
	 *
	 * @return  mixed  True if allowed, false for an explicit deny, null for an implicit deny.
	 *
	 * @since   1.7.0
	 */
	protected static function getGroupPath($groupId)
	{
		// Load all the groups to improve performance on intensive groups checks
		$groups = \JHelperUsergroups::getInstance()->getAll();

		if (!isset($groups[$groupId]))
		{
			return array();
		}

		return $groups[$groupId]->path;
	}

	/**
	 * Method to return the Rules object for an asset. The returned object can optionally hold
	 * only the rules explicitly set for the asset or the summation of all inherited rules from
	 * parent assets and explicit rules.
	 *
	 * @param   integer|string  $assetKey              The asset key (asset id or asset name). null fallback to root asset.
	 * @param   boolean         $recursive             True to return the rules object with inherited rules.
	 * @param   boolean         $recursiveParentAsset  True to calculate the rule also based on inherited component/extension rules.
	 * @param   boolean         $preload               Indicates whether preloading should be used.
	 *
	 * @return  Rules  Rules object for the asset.
	 *
	 * @since   1.7.0
	 * @note    The non preloading code will be removed in 4.0. All asset rules should use asset preloading.
	 */
	public static function getAssetRules($assetKey, $recursive = false, $recursiveParentAsset = true, $preload = true)
	{
		// Auto preloads the components assets and root asset (if chosen).
		if ($preload)
		{
			self::preload('components');
		}

		// When asset key is null fallback to root asset.
		$assetKey = self::cleanAssetKey($assetKey);

		// Auto preloads assets for the asset type (if chosen).
		if ($preload)
		{
			self::preload(self::getAssetType($assetKey));
		}

		// Get the asset id and name.
		$assetId = self::getAssetId($assetKey);

		// If asset rules already cached em memory return it (only in full recursive mode).
		if ($recursive && $recursiveParentAsset && $assetId && isset(self::$assetRules[$assetId]))
		{
			return self::$assetRules[$assetId];
		}

		// Get the asset name and the extension name.
		$assetName     = self::getAssetName($assetKey);
		$extensionName = self::getExtensionNameFromAsset($assetName);

		// If asset id does not exist fallback to extension asset, then root asset.
		if (!$assetId)
		{
			if ($extensionName && $assetName !== $extensionName)
			{
				\JLog::add('No asset found for ' . $assetName . ', falling back to ' . $extensionName, \JLog::WARNING, 'assets');

				return self::getAssetRules($extensionName, $recursive, $recursiveParentAsset, $preload);
			}

			if (self::$rootAssetId !== null && $assetName !== self::$preloadedAssets[self::$rootAssetId])
			{
				\JLog::add('No asset found for ' . $assetName . ', falling back to ' . self::$preloadedAssets[self::$rootAssetId], \JLog::WARNING, 'assets');

				return self::getAssetRules(self::$preloadedAssets[self::$rootAssetId], $recursive, $recursiveParentAsset, $preload);
			}
		}

		// Almost all calls can take advantage of preloading.
		if ($assetId && isset(self::$preloadedAssets[$assetId]))
		{
			!JDEBUG ?: \JProfiler::getInstance('Application')->mark('Before Access::getAssetRules (id:' . $assetId . ' name:' . $assetName . ')');

			// Collects permissions for each asset
			$collected = array();

			// If not in any recursive mode. We only want the asset rules.
			if (!$recursive && !$recursiveParentAsset)
			{
				$collected = array(self::$assetPermissionsParentIdMapping[$extensionName][$assetId]->rules);
			}
			// If there is any type of recursive mode.
			else
			{
				$ancestors = array_reverse(self::getAssetAncestors($extensionName, $assetId));

				foreach ($ancestors as $id)
				{
					// If full recursive mode, but not recursive parent mode, do not add the extension asset rules.
					if ($recursive && !$recursiveParentAsset && self::$assetPermissionsParentIdMapping[$extensionName][$id]->name === $extensionName)
					{
						continue;
					}

					// If not full recursive mode, but recursive parent mode, do not add other recursion rules.
					if (!$recursive && $recursiveParentAsset && self::$assetPermissionsParentIdMapping[$extensionName][$id]->name !== $extensionName
						&& self::$assetPermissionsParentIdMapping[$extensionName][$id]->id !== $assetId)
					{
						continue;
					}

					// If empty asset to not add to rules.
					if (self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules === '{}')
					{
						continue;
					}

					$collected[] = self::$assetPermissionsParentIdMapping[$extensionName][$id]->rules;
				}
			}

			/**
			* Hashing the collected rules allows us to store
			* only one instance of the Rules object for
			* Assets that have the same exact permissions...
			* it's a great way to save some memory.
			*/
			$hash = md5(implode(',', $collected));

			if (!isset(self::$assetRulesIdentities[$hash]))
			{
				$rules = new Rules;
				$rules->mergeCollection($collected);

				self::$assetRulesIdentities[$hash] = $rules;
			}

			// Save asset rules to memory cache(only in full recursive mode).
			if ($recursive && $recursiveParentAsset)
			{
				self::$assetRules[$assetId] = self::$assetRulesIdentities[$hash];
			}

			!JDEBUG ?: \JProfiler::getInstance('Application')->mark('After Access::getAssetRules (id:' . $assetId . ' name:' . $assetName . ')');

			return self::$assetRulesIdentities[$hash];
		}

		// Non preloading code. Use old slower method, slower. Only used in rare cases (if any) or without preloading chosen.
		\JLog::add('Asset ' . $assetKey . ' permissions fetch without preloading (slower method).', \JLog::INFO, 'assets');

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('Before Access::getAssetRules (assetKey:' . $assetKey . ')');

		// There's no need to process it with the recursive method for the Root Asset ID.
		if ((int) $assetKey === 1)
		{
			$recursive = false;
		}

		// Get the database connection object.
		$db = \JFactory::getDbo();

		// Build the database query to get the rules for the asset.
		$query = $db->getQuery(true)
			->select($db->qn(($recursive ? 'b.rules' : 'a.rules'), 'rules'))
			->select($db->qn(($recursive ? array('b.id', 'b.name', 'b.parent_id') : array('a.id', 'a.name', 'a.parent_id'))))
			->from($db->qn('#__assets', 'a'));

		// If the asset identifier is numeric assume it is a primary key, else lookup by name.
		$assetString     = is_numeric($assetKey) ? $db->qn('a.id') . ' = ' . $assetKey : $db->qn('a.name') . ' = ' . $db->q($assetKey);
		$extensionString = '';

		if ($recursiveParentAsset && ($extensionName !== $assetKey || is_numeric($assetKey)))
		{
			$extensionString = ' OR ' . $db->qn('a.name') . ' = ' . $db->q($extensionName);
		}

		$recursiveString = $recursive ? ' OR ' . $db->qn('a.parent_id') . ' = 0' : '';

		$query->where('(' . $assetString . $extensionString . $recursiveString . ')');

		// If we want the rules cascading up to the global asset node we need a self-join.
		if ($recursive)
		{
			$query->join('LEFT', $db->qn('#__assets', 'b') . ' ON b.lft <= a.lft AND b.rgt >= a.rgt')
				->order($db->qn('b.lft'));
		}

		// Execute the query and load the rules from the result.
		$result = $db->setQuery($query)->loadObjectList();

		// Get the root even if the asset is not found and in recursive mode
		if (empty($result))
		{
			$assets = new Asset($db);

			$query->clear()
				->select($db->qn(array('id', 'name', 'parent_id', 'rules')))
				->from($db->qn('#__assets'))
				->where($db->qn('id') . ' = ' . $db->q($assets->getRootId()));

			$result = $db->setQuery($query)->loadObjectList();
		}

		$collected = array();

		foreach ($result as $asset)
		{
			$collected[] = $asset->rules;
		}

		// Instantiate and return the Rules object for the asset rules.
		$rules = new Rules;
		$rules->mergeCollection($collected);

		!JDEBUG ?: \JProfiler::getInstance('Application')->mark('Before Access::getAssetRules <strong>Slower</strong> (assetKey:' . $assetKey . ')');

		return $rules;
	}

	/**
	 * Method to clean the asset key to make sure we always have something.
	 *
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name). null fallback to root asset.
	 *
	 * @return  integer|string  Asset id or asset name.
	 *
	 * @since   3.7.0
	 */
	protected static function cleanAssetKey($assetKey = null)
	{
		// If it's a valid asset key, clean it and return it.
		if ($assetKey)
		{
			return strtolower(preg_replace('#[\s\-]+#', '.', trim($assetKey)));
		}

		// Return root asset id if already preloaded.
		if (self::$rootAssetId !== null)
		{
			return self::$rootAssetId;
		}

		// No preload. Return root asset id from Assets.
		$assets = new Asset(\JFactory::getDbo());

		return $assets->getRootId();
	}

	/**
	 * Method to get the asset id from the asset key.
	 *
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name).
	 *
	 * @return  integer  The asset id.
	 *
	 * @since   3.7.0
	 */
	protected static function getAssetId($assetKey)
	{
		static $loaded = array();

		// If the asset is already an id return it.
		if (is_numeric($assetKey))
		{
			return (int) $assetKey;
		}

		if (!isset($loaded[$assetKey]))
		{
			// It's the root asset.
			if (self::$rootAssetId !== null && $assetKey === self::$preloadedAssets[self::$rootAssetId])
			{
				$loaded[$assetKey] = self::$rootAssetId;
			}
			else
			{
				$preloadedAssetsByName = array_flip(self::$preloadedAssets);

				// If we already have the asset name stored in preloading, example, a component, no need to fetch it from table.
				if (isset($preloadedAssetsByName[$assetKey]))
				{
					$loaded[$assetKey] = $preloadedAssetsByName[$assetKey];
				}
				// Else we have to do an extra db query to fetch it from the table fetch it from table.
				else
				{
					$table = new Asset(\JFactory::getDbo());
					$table->load(array('name' => $assetKey));
					$loaded[$assetKey] = $table->id;
				}
			}
		}

		return (int) $loaded[$assetKey];
	}

	/**
	 * Method to get the asset name from the asset key.
	 *
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name).
	 *
	 * @return  string  The asset name (ex: com_content.article.8).
	 *
	 * @since   3.7.0
	 */
	protected static function getAssetName($assetKey)
	{
		static $loaded = array();

		// If the asset is already a string return it.
		if (!is_numeric($assetKey))
		{
			return $assetKey;
		}

		if (!isset($loaded[$assetKey]))
		{
			// It's the root asset.
			if (self::$rootAssetId !== null && $assetKey === self::$rootAssetId)
			{
				$loaded[$assetKey] = self::$preloadedAssets[self::$rootAssetId];
			}
			// If we already have the asset name stored in preloading, example, a component, no need to fetch it from table.
			elseif (isset(self::$preloadedAssets[$assetKey]))
			{
				$loaded[$assetKey] = self::$preloadedAssets[$assetKey];
			}
			// Else we have to do an extra db query to fetch it from the table fetch it from table.
			else
			{
				$table = new Asset(\JFactory::getDbo());
				$table->load($assetKey);
				$loaded[$assetKey] = $table->name;
			}
		}

		return $loaded[$assetKey];
	}

	/**
	 * Method to get the extension name from the asset name.
	 *
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name).
	 *
	 * @return  string  The extension name (ex: com_content).
	 *
	 * @since    1.6
	 */
	public static function getExtensionNameFromAsset($assetKey)
	{
		static $loaded = array();

		if (!isset($loaded[$assetKey]))
		{
			$assetName = self::getAssetName($assetKey);
			$firstDot  = strpos($assetName, '.');

			if ($assetName !== 'root.1' && $firstDot !== false)
			{
				$assetName = substr($assetName, 0, $firstDot);
			}

			$loaded[$assetKey] = $assetName;
		}

		return $loaded[$assetKey];
	}

	/**
	 * Method to get the asset type from the asset name.
	 *
	 * For top level components this returns "components":
	 * 'com_content' returns 'components'
	 *
	 * For other types:
	 * 'com_content.article.1' returns 'com_content.article'
	 * 'com_content.category.1' returns 'com_content.category'
	 *
	 * @param   integer|string  $assetKey  The asset key (asset id or asset name).
	 *
	 * @return  string  The asset type (ex: com_content.article).
	 *
	 * @since    1.6
	 */
	public static function getAssetType($assetKey)
	{
		// If the asset is already a string return it.
		$assetName = self::getAssetName($assetKey);
		$lastDot   = strrpos($assetName, '.');

		if ($assetName !== 'root.1' && $lastDot !== false)
		{
			return substr($assetName, 0, $lastDot);
		}

		return 'components';
	}

	/**
	 * Method to return the title of a user group
	 *
	 * @param   integer  $groupId  Id of the group for which to get the title of.
	 *
	 * @return  string  Tthe title of the group
	 *
	 * @since   3.5
	 */
	public static function getGroupTitle($groupId)
	{
		// Fetch the group title from the database
		$db    = \JFactory::getDbo();
		$query = $db->getQuery(true);
		$query->select('title')
			->from('#__usergroups')
			->where('id = ' . $db->quote($groupId));
		$db->setQuery($query);

		return $db->loadResult();
	}

	/**
	 * Method to return a list of user groups mapped to a user. The returned list can optionally hold
	 * only the groups explicitly mapped to the user or all groups both explicitly mapped and inherited
	 * by the user.
	 *
	 * @param   integer  $userId     Id of the user for which to get the list of groups.
	 * @param   boolean  $recursive  True to include inherited user groups.
	 *
	 * @return  array    List of user group ids to which the user is mapped.
	 *
	 * @since   1.7.0
	 */
	public static function getGroupsByUser($userId, $recursive = true)
	{
		// Creates a simple unique string for each parameter combination:
		$storeId = $userId . ':' . (int) $recursive;

		if (!isset(self::$groupsByUser[$storeId]))
		{
			// TODO: Uncouple this from \JComponentHelper and allow for a configuration setting or value injection.
			if (class_exists('\JComponentHelper'))
			{
				$guestUsergroup = \JComponentHelper::getParams('com_users')->get('guest_usergroup', 1);
			}
			else
			{
				$guestUsergroup = 1;
			}

			// Guest user (if only the actually assigned group is requested)
			if (empty($userId) && !$recursive)
			{
				$result = array($guestUsergroup);
			}
			// Registered user and guest if all groups are requested
			else
			{
				$db = \JFactory::getDbo();

				// Build the database query to get the rules for the asset.
				$query = $db->getQuery(true)
					->select($recursive ? 'b.id' : 'a.id');

				if (empty($userId))
				{
					$query->from('#__usergroups AS a')
						->where('a.id = ' . (int) $guestUsergroup);
				}
				else
				{
					$query->from('#__user_usergroup_map AS map')
						->where('map.user_id = ' . (int) $userId)
						->join('LEFT', '#__usergroups AS a ON a.id = map.group_id');
				}

				// If we want the rules cascading up to the global asset node we need a self-join.
				if ($recursive)
				{
					$query->join('LEFT', '#__usergroups AS b ON b.lft <= a.lft AND b.rgt >= a.rgt');
				}

				// Execute the query and load the rules from the result.
				$db->setQuery($query);
				$result = $db->loadColumn();

				// Clean up any NULL or duplicate values, just in case
				$result = ArrayHelper::toInteger($result);

				if (empty($result))
				{
					$result = array('1');
				}
				else
				{
					$result = array_unique($result);
				}
			}

			self::$groupsByUser[$storeId] = $result;
		}

		return self::$groupsByUser[$storeId];
	}

	/**
	 * Method to return a list of user Ids contained in a Group
	 *
	 * @param   integer  $groupId    The group Id
	 * @param   boolean  $recursive  Recursively include all child groups (optional)
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 * @todo    This method should move somewhere else
	 */
	public static function getUsersByGroup($groupId, $recursive = false)
	{
		// Get a database object.
		$db = \JFactory::getDbo();

		$test = $recursive ? '>=' : '=';

		// First find the users contained in the group
		$query = $db->getQuery(true)
			->select('DISTINCT(user_id)')
			->from('#__usergroups as ug1')
			->join('INNER', '#__usergroups AS ug2 ON ug2.lft' . $test . 'ug1.lft AND ug1.rgt' . $test . 'ug2.rgt')
			->join('INNER', '#__user_usergroup_map AS m ON ug2.id=m.group_id')
			->where('ug1.id=' . $db->quote($groupId));

		$db->setQuery($query);

		$result = $db->loadColumn();

		// Clean up any NULL values, just in case
		$result = ArrayHelper::toInteger($result);

		return $result;
	}

	/**
	 * Method to return a list of view levels for which the user is authorised.
	 *
	 * @param   integer  $userId  Id of the user for which to get the list of authorised view levels.
	 *
	 * @return  array    List of view levels for which the user is authorised.
	 *
	 * @since   1.7.0
	 */
	public static function getAuthorisedViewLevels($userId)
	{
		// Only load the view levels once.
		if (empty(self::$viewLevels))
		{
			// Get a database object.
			$db = \JFactory::getDbo();

			// Build the base query.
			$query = $db->getQuery(true)
				->select('id, rules')
				->from($db->quoteName('#__viewlevels'));

			// Set the query for execution.
			$db->setQuery($query);

			// Build the view levels array.
			foreach ($db->loadAssocList() as $level)
			{
				self::$viewLevels[$level['id']] = (array) json_decode($level['rules']);
			}
		}

		// Initialise the authorised array.
		$authorised = array(1);

		// Check for the recovery mode setting and return early.
		$user      = \JUser::getInstance($userId);
		$root_user = \JFactory::getConfig()->get('root_user');

		if (($user->username && $user->username == $root_user) || (is_numeric($root_user) && $user->id > 0 && $user->id == $root_user))
		{
			// Find the super user levels.
			foreach (self::$viewLevels as $level => $rule)
			{
				foreach ($rule as $id)
				{
					if ($id > 0 && self::checkGroup($id, 'core.admin'))
					{
						$authorised[] = $level;
						break;
					}
				}
			}

			return $authorised;
		}

		// Get all groups that the user is mapped to recursively.
		$groups = self::getGroupsByUser($userId);

		// Find the authorised levels.
		foreach (self::$viewLevels as $level => $rule)
		{
			foreach ($rule as $id)
			{
				if (($id < 0) && (($id * -1) == $userId))
				{
					$authorised[] = $level;
					break;
				}
				// Check to see if the group is mapped to the level.
				elseif (($id >= 0) && in_array($id, $groups))
				{
					$authorised[] = $level;
					break;
				}
			}
		}

		return $authorised;
	}

	/**
	 * Method to return a list of actions for which permissions can be set given a component and section.
	 *
	 * @param   string  $component  The component from which to retrieve the actions.
	 * @param   string  $section    The name of the section within the component from which to retrieve the actions.
	 *
	 * @return  array  List of actions available for the given component and section.
	 *
	 * @since       1.7.0
	 * @deprecated  4.0  Use Access::getActionsFromFile or Access::getActionsFromData instead.
	 * @codeCoverageIgnore
	 */
	public static function getActions($component, $section = 'component')
	{
		\JLog::add(__METHOD__ . ' is deprecated. Use Access::getActionsFromFile or Access::getActionsFromData instead.', \JLog::WARNING, 'deprecated');

		$actions = self::getActionsFromFile(
			JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
			"/access/section[@name='" . $section . "']/"
		);

		if (empty($actions))
		{
			return array();
		}
		else
		{
			return $actions;
		}
	}

	/**
	 * Method to return a list of actions from a file for which permissions can be set.
	 *
	 * @param   string  $file   The path to the XML file.
	 * @param   string  $xpath  An optional xpath to search for the fields.
	 *
	 * @return  boolean|array   False if case of error or the list of actions available.
	 *
	 * @since   3.0.0
	 */
	public static function getActionsFromFile($file, $xpath = "/access/section[@name='component']/")
	{
		if (!is_file($file) || !is_readable($file))
		{
			// If unable to find the file return false.
			return false;
		}
		else
		{
			// Else return the actions from the xml.
			$xml = simplexml_load_file($file);

			return self::getActionsFromData($xml, $xpath);
		}
	}

	/**
	 * Method to return a list of actions from a string or from an xml for which permissions can be set.
	 *
	 * @param   string|\SimpleXMLElement  $data   The XML string or an XML element.
	 * @param   string                    $xpath  An optional xpath to search for the fields.
	 *
	 * @return  boolean|array   False if case of error or the list of actions available.
	 *
	 * @since   3.0.0
	 */
	public static function getActionsFromData($data, $xpath = "/access/section[@name='component']/")
	{
		// If the data to load isn't already an XML element or string return false.
		if ((!($data instanceof \SimpleXMLElement)) && (!is_string($data)))
		{
			return false;
		}

		// Attempt to load the XML if a string.
		if (is_string($data))
		{
			try
			{
				$data = new \SimpleXMLElement($data);
			}
			catch (\Exception $e)
			{
				return false;
			}

			// Make sure the XML loaded correctly.
			if (!$data)
			{
				return false;
			}
		}

		// Initialise the actions array
		$actions = array();

		// Get the elements from the xpath
		$elements = $data->xpath($xpath . 'action[@name][@title][@description]');

		// If there some elements, analyse them
		if (!empty($elements))
		{
			foreach ($elements as $action)
			{
				// Add the action to the actions array
				$actions[] = (object) array(
					'name' => (string) $action['name'],
					'title' => (string) $action['title'],
					'description' => (string) $action['description'],
				);
			}
		}

		// Finally return the actions array
		return $actions;
	}
}
src/Filesystem/Stream.php000064400000074576152177723700011461 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Object\CMSObject;
use Joomla\CMS\Language\Text;

/**
 * Joomla! Stream Interface
 *
 * The Joomla! stream interface is designed to handle files as streams
 * where as the legacy File static class treated files in a rather
 * atomic manner.
 *
 * @note   This class adheres to the stream wrapper operations:
 * @link   https://www.php.net/manual/en/function.stream-get-wrappers.php
 * @link   https://www.php.net/manual/en/intro.stream.php PHP Stream Manual
 * @link   https://www.php.net/manual/en/wrappers.php Stream Wrappers
 * @link   https://www.php.net/manual/en/filters.php Stream Filters
 * @link   https://www.php.net/manual/en/transports.php Socket Transports (used by some options, particularly HTTP proxy)
 * @since  1.7.0
 */
class Stream extends CMSObject
{
	/**
	 * File Mode
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $filemode = 0644;

	/**
	 * Directory Mode
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $dirmode = 0755;

	/**
	 * Default Chunk Size
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $chunksize = 8192;

	/**
	 * Filename
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $filename;

	/**
	 * Prefix of the connection for writing
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $writeprefix;

	/**
	 * Prefix of the connection for reading
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $readprefix;

	/**
	 * Read Processing method
	 * @var    string  gz, bz, f
	 * If a scheme is detected, fopen will be defaulted
	 * To use compression with a network stream use a filter
	 * @since  1.7.0
	 */
	protected $processingmethod = 'f';

	/**
	 * Filters applied to the current stream
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected $filters = array();

	/**
	 * File Handle
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $fh;

	/**
	 * File size
	 *
	 * @var    integer
	 * @since  3.0.0
	 */
	protected $filesize;

	/**
	 * Context to use when opening the connection
	 *
	 * @var    resource
	 * @since  3.0.0
	 */
	protected $context = null;

	/**
	 * Context options; used to rebuild the context
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $contextOptions;

	/**
	 * The mode under which the file was opened
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $openmode;

	/**
	 * Constructor
	 *
	 * @param   string  $writeprefix  Prefix of the stream (optional). Unlike the JPATH_*, this has a final path separator!
	 * @param   string  $readprefix   The read prefix (optional).
	 * @param   array   $context      The context options (optional).
	 *
	 * @since   1.7.0
	 */
	public function __construct($writeprefix = '', $readprefix = '', $context = array())
	{
		$this->writeprefix = $writeprefix;
		$this->readprefix = $readprefix;
		$this->contextOptions = $context;
		$this->_buildContext();
	}

	/**
	 * Destructor
	 *
	 * @since   1.7.0
	 */
	public function __destruct()
	{
		// Attempt to close on destruction if there is a file handle
		if ($this->fh)
		{
			@$this->close();
		}
	}

	/**
	 * Generic File Operations
	 *
	 * Open a stream with some lazy loading smarts
	 *
	 * @param   string    $filename              Filename
	 * @param   string    $mode                  Mode string to use
	 * @param   boolean   $use_include_path      Use the PHP include path
	 * @param   resource  $context               Context to use when opening
	 * @param   boolean   $use_prefix            Use a prefix to open the file
	 * @param   boolean   $relative              Filename is a relative path (if false, strips JPATH_ROOT to make it relative)
	 * @param   boolean   $detectprocessingmode  Detect the processing method for the file and use the appropriate function
	 *                                           to handle output automatically
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function open($filename, $mode = 'r', $use_include_path = false, $context = null,
		$use_prefix = false, $relative = false, $detectprocessingmode = false)
	{
		$filename = $this->_getFilename($filename, $mode, $use_prefix, $relative);

		if (!$filename)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));

			return false;
		}

		$this->filename = $filename;
		$this->openmode = $mode;

		$url = parse_url($filename);
		$retval = false;

		if (isset($url['scheme']))
		{
			// If we're dealing with a Joomla! stream, load it
			if (FilesystemHelper::isJoomlaStream($url['scheme']))
			{
				require_once __DIR__ . '/streams/' . $url['scheme'] . '.php';
			}

			// We have a scheme! force the method to be f
			$this->processingmethod = 'f';
		}
		elseif ($detectprocessingmode)
		{
			$ext = strtolower(File::getExt($this->filename));

			switch ($ext)
			{
				case 'tgz':
				case 'gz':
				case 'gzip':
					$this->processingmethod = 'gz';
					break;

				case 'tbz2':
				case 'bz2':
				case 'bzip2':
					$this->processingmethod = 'bz';
					break;

				default:
					$this->processingmethod = 'f';
					break;
			}
		}

		// Capture PHP errors
		$php_errormsg = 'Error Unknown whilst opening a file';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		// Decide which context to use:
		switch ($this->processingmethod)
		{
			// Gzip doesn't support contexts or streams
			case 'gz':
				$this->fh = gzopen($filename, $mode, $use_include_path);
				break;

			// Bzip2 is much like gzip except it doesn't use the include path
			case 'bz':
				$this->fh = bzopen($filename, $mode);
				break;

			// Fopen can handle streams
			case 'f':
			default:
				// One supplied at open; overrides everything
				if ($context)
				{
					$this->fh = fopen($filename, $mode, $use_include_path, $context);
				}
				// One provided at initialisation
				elseif ($this->context)
				{
					$this->fh = fopen($filename, $mode, $use_include_path, $this->context);
				}
				// No context; all defaults
				else
				{
					$this->fh = fopen($filename, $mode, $use_include_path);
				}

				break;
		}

		if (!$this->fh)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			$retval = true;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Attempt to close a file handle
	 *
	 * Will return false if it failed and true on success
	 * If the file is not open the system will return true, this function destroys the file handle as well
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function close()
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return true;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzclose($this->fh);
				break;

			case 'bz':
				$res = bzclose($this->fh);
				break;

			case 'f':
			default:
				$res = fclose($this->fh);
				break;
		}

		if (!$res)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			// Reset this
			$this->fh = null;
			$retval = true;
		}

		// If we wrote, chmod the file after it's closed
		if ($this->openmode[0] == 'w')
		{
			$this->chmod();
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Work out if we're at the end of the file for a stream
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function eof()
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzeof($this->fh);
				break;

			case 'bz':
			case 'f':
			default:
				$res = feof($this->fh);
				break;
		}

		if ($php_errormsg)
		{
			$this->setError($php_errormsg);
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $res;
	}

	/**
	 * Retrieve the file size of the path
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function filesize()
	{
		if (!$this->filename)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);
		$res = @filesize($this->filename);

		if (!$res)
		{
			$tmp_error = '';

			if ($php_errormsg)
			{
				// Something went wrong.
				// Store the error in case we need it.
				$tmp_error = $php_errormsg;
			}

			$res = FilesystemHelper::remotefsize($this->filename);

			if (!$res)
			{
				if ($tmp_error)
				{
					// Use the php_errormsg from before
					$this->setError($tmp_error);
				}
				else
				{
					// Error but nothing from php? How strange! Create our own
					$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_SIZE'));
				}
			}
			else
			{
				$this->filesize = $res;
				$retval = $res;
			}
		}
		else
		{
			$this->filesize = $res;
			$retval = $res;
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Get a line from the stream source.
	 *
	 * @param   integer  $length  The number of bytes (optional) to read.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function gets($length = 0)
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);
				break;

			case 'bz':
			case 'f':
			default:
				$res = $length ? fgets($this->fh, $length) : fgets($this->fh);
				break;
		}

		if (!$res)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			$retval = $res;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Read a file
	 *
	 * Handles user space streams appropriately otherwise any read will return 8192
	 *
	 * @param   integer  $length  Length of data to read
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/function.fread.php
	 * @since   1.7.0
	 */
	public function read($length = 0)
	{
		if (!$this->filesize && !$length)
		{
			// Get the filesize
			$this->filesize();

			if (!$this->filesize)
			{
				// Set it to the biggest and then wait until eof
				$length = -1;
			}
			else
			{
				$length = $this->filesize;
			}
		}

		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);
		$remaining = $length;

		do
		{
			// Do chunked reads where relevant
			switch ($this->processingmethod)
			{
				case 'bz':
					$res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize);
					break;

				case 'gz':
					$res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize);
					break;

				case 'f':
				default:
					$res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize);
					break;
			}

			if (!$res)
			{
				$this->setError($php_errormsg);

				// Jump from the loop
				$remaining = 0;
			}
			else
			{
				if (!$retval)
				{
					$retval = '';
				}

				$retval .= $res;

				if (!$this->eof())
				{
					$len = strlen($res);
					$remaining -= $len;
				}
				else
				{
					// If it's the end of the file then we've nothing left to read; reset remaining and len
					$remaining = 0;
					$length = strlen($retval);
				}
			}
		}
		while ($remaining || !$length);

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Seek the file
	 *
	 * Note: the return value is different to that of fseek
	 *
	 * @param   integer  $offset  Offset to use when seeking.
	 * @param   integer  $whence  Seek mode to use.
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @link    https://www.php.net/manual/en/function.fseek.php
	 * @since   1.7.0
	 */
	public function seek($offset, $whence = SEEK_SET)
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzseek($this->fh, $offset, $whence);
				break;

			case 'bz':
			case 'f':
			default:
				$res = fseek($this->fh, $offset, $whence);
				break;
		}

		// Seek, interestingly, returns 0 on success or -1 on failure.
		if ($res == -1)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			$retval = true;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Returns the current position of the file read/write pointer.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function tell()
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gztell($this->fh);
				break;

			case 'bz':
			case 'f':
			default:
				$res = ftell($this->fh);
				break;
		}

		// May return 0 so check if it's really false
		if ($res === false)
		{
			$this->setError($php_errormsg);
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		// Return the result
		return $res;
	}

	/**
	 * File write
	 *
	 * Whilst this function accepts a reference, the underlying fwrite
	 * will do a copy! This will roughly double the memory allocation for
	 * any write you do. Specifying chunked will get around this by only
	 * writing in specific chunk sizes. This defaults to 8192 which is a
	 * sane number to use most of the time (change the default with
	 * JStream::set('chunksize', newsize);)
	 * Note: This doesn't support gzip/bzip2 writing like reading does
	 *
	 * @param   string   &$string  Reference to the string to write.
	 * @param   integer  $length   Length of the string to write.
	 * @param   integer  $chunk    Size of chunks to write in.
	 *
	 * @return  boolean
	 *
	 * @link    https://www.php.net/manual/en/function.fwrite.php
	 * @since   1.7.0
	 */
	public function write(&$string, $length = 0, $chunk = 0)
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		// If the length isn't set, set it to the length of the string.
		if (!$length)
		{
			$length = strlen($string);
		}

		// If the chunk isn't set, set it to the default.
		if (!$chunk)
		{
			$chunk = $this->chunksize;
		}

		$retval = true;

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);
		$remaining = $length;
		$start = 0;

		do
		{
			// If the amount remaining is greater than the chunk size, then use the chunk
			$amount = ($remaining > $chunk) ? $chunk : $remaining;
			$res = fwrite($this->fh, substr($string, $start), $amount);

			// Returns false on error or the number of bytes written
			if ($res === false)
			{
				// Returned error
				$this->setError($php_errormsg);
				$retval = false;
				$remaining = 0;
			}
			elseif ($res === 0)
			{
				// Wrote nothing?
				$remaining = 0;
				$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_NO_DATA_WRITTEN'));
			}
			else
			{
				// Wrote something
				$start += $amount;
				$remaining -= $res;
			}
		}
		while ($remaining);

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Chmod wrapper
	 *
	 * @param   string  $filename  File name.
	 * @param   mixed   $mode      Mode to use.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function chmod($filename = '', $mode = 0)
	{
		if (!$filename)
		{
			if (!isset($this->filename) || !$this->filename)
			{
				$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILENAME'));

				return false;
			}

			$filename = $this->filename;
		}

		// If no mode is set use the default
		if (!$mode)
		{
			$mode = $this->filemode;
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);
		$sch = parse_url($filename, PHP_URL_SCHEME);

		// Scheme specific options; ftp's chmod support is fun.
		switch ($sch)
		{
			case 'ftp':
			case 'ftps':
				$res = FilesystemHelper::ftpChmod($filename, $mode);
				break;

			default:
				$res = chmod($filename, $mode);
				break;
		}

		// Seek, interestingly, returns 0 on success or -1 on failure
		if (!$res)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			$retval = true;
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		// Return the result
		return $retval;
	}

	/**
	 * Get the stream metadata
	 *
	 * @return  array  header/metadata
	 *
	 * @link    https://www.php.net/manual/en/function.stream-get-meta-data.php
	 * @since   1.7.0
	 */
	public function get_meta_data()
	{
		if (!$this->fh)
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_FILE_NOT_OPEN'));

			return false;
		}

		return stream_get_meta_data($this->fh);
	}

	/**
	 * Stream contexts
	 * Builds the context from the array
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function _buildContext()
	{
		// According to the manual this always works!
		if (count($this->contextOptions))
		{
			$this->context = @stream_context_create($this->contextOptions);
		}
		else
		{
			$this->context = null;
		}
	}

	/**
	 * Updates the context to the array
	 *
	 * Format is the same as the options for stream_context_create
	 *
	 * @param   array  $context  Options to create the context with
	 *
	 * @return  void
	 *
	 * @link    https://www.php.net/stream_context_create
	 * @since   1.7.0
	 */
	public function setContextOptions($context)
	{
		$this->contextOptions = $context;
		$this->_buildContext();
	}

	/**
	 * Adds a particular options to the context
	 *
	 * @param   string  $wrapper  The wrapper to use
	 * @param   string  $name     The option to set
	 * @param   string  $value    The value of the option
	 *
	 * @return  void
	 *
	 * @link    https://www.php.net/stream_context_create Stream Context Creation
	 * @link    https://www.php.net/manual/en/context.php Context Options for various streams
	 * @since   1.7.0
	 */
	public function addContextEntry($wrapper, $name, $value)
	{
		$this->contextOptions[$wrapper][$name] = $value;
		$this->_buildContext();
	}

	/**
	 * Deletes a particular setting from a context
	 *
	 * @param   string  $wrapper  The wrapper to use
	 * @param   string  $name     The option to unset
	 *
	 * @return  void
	 *
	 * @link    https://www.php.net/stream_context_create
	 * @since   1.7.0
	 */
	public function deleteContextEntry($wrapper, $name)
	{
		// Check whether the wrapper is set
		if (isset($this->contextOptions[$wrapper]))
		{
			// Check that entry is set for that wrapper
			if (isset($this->contextOptions[$wrapper][$name]))
			{
				// Unset the item
				unset($this->contextOptions[$wrapper][$name]);

				// Check that there are still items there
				if (!count($this->contextOptions[$wrapper]))
				{
					// Clean up an empty wrapper context option
					unset($this->contextOptions[$wrapper]);
				}
			}
		}

		// Rebuild the context and apply it to the stream
		$this->_buildContext();
	}

	/**
	 * Applies the current context to the stream
	 *
	 * Use this to change the values of the context after you've opened a stream
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function applyContextToStream()
	{
		$retval = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = 'Unknown error setting context option';
			$track_errors = ini_get('track_errors');
			ini_set('track_errors', true);
			$retval = @stream_context_set_option($this->fh, $this->contextOptions);

			if (!$retval)
			{
				$this->setError($php_errormsg);
			}

			// Restore error tracking to what it was before
			ini_set('track_errors', $track_errors);
		}

		return $retval;
	}

	/**
	 * Stream filters
	 * Append a filter to the chain
	 *
	 * @param   string   $filtername  The key name of the filter.
	 * @param   integer  $read_write  Optional. Defaults to STREAM_FILTER_READ.
	 * @param   array    $params      An array of params for the stream_filter_append call.
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/function.stream-filter-append.php
	 * @since   1.7.0
	 */
	public function appendFilter($filtername, $read_write = STREAM_FILTER_READ, $params = array())
	{
		$res = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = '';
			$track_errors = ini_get('track_errors');
			ini_set('track_errors', true);

			$res = @stream_filter_append($this->fh, $filtername, $read_write, $params);

			if (!$res && $php_errormsg)
			{
				$this->setError($php_errormsg);
			}
			else
			{
				$this->filters[] = &$res;
			}

			// Restore error tracking to what it was before.
			ini_set('track_errors', $track_errors);
		}

		return $res;
	}

	/**
	 * Prepend a filter to the chain
	 *
	 * @param   string   $filtername  The key name of the filter.
	 * @param   integer  $read_write  Optional. Defaults to STREAM_FILTER_READ.
	 * @param   array    $params      An array of params for the stream_filter_prepend call.
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/function.stream-filter-prepend.php
	 * @since   1.7.0
	 */
	public function prependFilter($filtername, $read_write = STREAM_FILTER_READ, $params = array())
	{
		$res = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = '';
			$track_errors = ini_get('track_errors');
			ini_set('track_errors', true);
			$res = @stream_filter_prepend($this->fh, $filtername, $read_write, $params);

			if (!$res && $php_errormsg)
			{
				// Set the error msg
				$this->setError($php_errormsg);
			}
			else
			{
				array_unshift($res, '');
				$res[0] = &$this->filters;
			}

			// Restore error tracking to what it was before.
			ini_set('track_errors', $track_errors);
		}

		return $res;
	}

	/**
	 * Remove a filter, either by resource (handed out from the append or prepend function)
	 * or via getting the filter list)
	 *
	 * @param   resource  &$resource  The resource.
	 * @param   boolean   $byindex    The index of the filter.
	 *
	 * @return  boolean   Result of operation
	 *
	 * @since   1.7.0
	 */
	public function removeFilter(&$resource, $byindex = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		if ($byindex)
		{
			$res = stream_filter_remove($this->filters[$resource]);
		}
		else
		{
			$res = stream_filter_remove($resource);
		}

		if ($res && $php_errormsg)
		{
			$this->setError($php_errormsg);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		return $res;
	}

	/**
	 * Copy a file from src to dest
	 *
	 * @param   string    $src         The file path to copy from.
	 * @param   string    $dest        The file path to copy to.
	 * @param   resource  $context     A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $use_prefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative    Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function copy($src, $dest, $context = null, $use_prefix = true, $relative = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		$chmodDest = $this->_getFilename($dest, 'w', $use_prefix, $relative);

		// Since we're going to open the file directly we need to get the filename.
		// We need to use the same prefix so force everything to write.
		$src = $this->_getFilename($src, 'w', $use_prefix, $relative);
		$dest = $this->_getFilename($dest, 'w', $use_prefix, $relative);

		if ($context)
		{
			// Use the provided context
			$res = @copy($src, $dest, $context);
		}
		elseif ($this->context)
		{
			// Use the objects context
			$res = @copy($src, $dest, $this->context);
		}
		else
		{
			// Don't use any context
			$res = @copy($src, $dest);
		}

		if (!$res && $php_errormsg)
		{
			$this->setError($php_errormsg);
		}
		else
		{
			$this->chmod($chmodDest);
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		return $res;
	}

	/**
	 * Moves a file
	 *
	 * @param   string    $src         The file path to move from.
	 * @param   string    $dest        The file path to move to.
	 * @param   resource  $context     A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $use_prefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative    Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function move($src, $dest, $context = null, $use_prefix = true, $relative = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		$src = $this->_getFilename($src, 'w', $use_prefix, $relative);
		$dest = $this->_getFilename($dest, 'w', $use_prefix, $relative);

		if ($context)
		{
			// Use the provided context
			$res = @rename($src, $dest, $context);
		}
		elseif ($this->context)
		{
			// Use the object's context
			$res = @rename($src, $dest, $this->context);
		}
		else
		{
			// Don't use any context
			$res = @rename($src, $dest);
		}

		if (!$res && $php_errormsg)
		{
			$this->setError($php_errormsg());
		}

		$this->chmod($dest);

		// Restore error tracking to what it was before
		ini_set('track_errors', $track_errors);

		return $res;
	}

	/**
	 * Delete a file
	 *
	 * @param   string    $filename    The file path to delete.
	 * @param   resource  $context     A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $use_prefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative    Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function delete($filename, $context = null, $use_prefix = true, $relative = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$track_errors = ini_get('track_errors');
		ini_set('track_errors', true);

		$filename = $this->_getFilename($filename, 'w', $use_prefix, $relative);

		if ($context)
		{
			// Use the provided context
			$res = @unlink($filename, $context);
		}
		elseif ($this->context)
		{
			// Use the object's context
			$res = @unlink($filename, $this->context);
		}
		else
		{
			// Don't use any context
			$res = @unlink($filename);
		}

		if (!$res && $php_errormsg)
		{
			$this->setError($php_errormsg());
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $track_errors);

		return $res;
	}

	/**
	 * Upload a file
	 *
	 * @param   string    $src         The file path to copy from (usually a temp folder).
	 * @param   string    $dest        The file path to copy to.
	 * @param   resource  $context     A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $use_prefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative    Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function upload($src, $dest, $context = null, $use_prefix = true, $relative = false)
	{
		if (is_uploaded_file($src))
		{
			// Make sure it's an uploaded file
			return $this->copy($src, $dest, $context, $use_prefix, $relative);
		}
		else
		{
			$this->setError(Text::_('JLIB_FILESYSTEM_ERROR_STREAMS_NOT_UPLOADED_FILE'));

			return false;
		}
	}

	/**
	 * Writes a chunk of data to a file.
	 *
	 * @param   string  $filename  The file name.
	 * @param   string  &$buffer   The data to write to the file.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function writeFile($filename, &$buffer)
	{
		if ($this->open($filename, 'w'))
		{
			$result = $this->write($buffer);
			$this->chmod();
			$this->close();

			return $result;
		}

		return false;
	}

	/**
	 * Determine the appropriate 'filename' of a file
	 *
	 * @param   string   $filename    Original filename of the file
	 * @param   string   $mode        Mode string to retrieve the filename
	 * @param   boolean  $use_prefix  Controls the use of a prefix
	 * @param   boolean  $relative    Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function _getFilename($filename, $mode, $use_prefix, $relative)
	{
		if ($use_prefix)
		{
			// Get rid of binary or t, should be at the end of the string
			$tmode = trim($mode, 'btf123456789');

			// Check if it's a write mode then add the appropriate prefix
			// Get rid of JPATH_ROOT (legacy compat) along the way
			if (in_array($tmode, FilesystemHelper::getWriteModes()))
			{
				if (!$relative && $this->writeprefix)
				{
					$filename = str_replace(JPATH_ROOT, '', $filename);
				}

				$filename = $this->writeprefix . $filename;
			}
			else
			{
				if (!$relative && $this->readprefix)
				{
					$filename = str_replace(JPATH_ROOT, '', $filename);
				}

				$filename = $this->readprefix . $filename;
			}
		}

		return $filename;
	}

	/**
	 * Return the internal file handle
	 *
	 * @return  resource  File handler
	 *
	 * @since   1.7.0
	 */
	public function getFileHandle()
	{
		return $this->fh;
	}
}
src/Filesystem/FilesystemHelper.php000064400000016051152177723700013472 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

/**
 * File system helper
 *
 * Holds support functions for the filesystem, particularly the stream
 *
 * @since  1.7.0
 */
class FilesystemHelper
{
	/**
	 * Remote file size function for streams that don't support it
	 *
	 * @param   string  $url  TODO Add text
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/function.filesize.php
	 * @since   1.7.0
	 */
	public static function remotefsize($url)
	{
		$sch = parse_url($url, PHP_URL_SCHEME);

		if (($sch != 'http') && ($sch != 'https') && ($sch != 'ftp') && ($sch != 'ftps'))
		{
			return false;
		}

		if (($sch == 'http') || ($sch == 'https'))
		{
			$headers = get_headers($url, 1);

			if ((!array_key_exists('Content-Length', $headers)))
			{
				return false;
			}

			return $headers['Content-Length'];
		}

		if (($sch == 'ftp') || ($sch == 'ftps'))
		{
			$server = parse_url($url, PHP_URL_HOST);
			$port = parse_url($url, PHP_URL_PORT);
			$path = parse_url($url, PHP_URL_PATH);
			$user = parse_url($url, PHP_URL_USER);
			$pass = parse_url($url, PHP_URL_PASS);

			if ((!$server) || (!$path))
			{
				return false;
			}

			if (!$port)
			{
				$port = 21;
			}

			if (!$user)
			{
				$user = 'anonymous';
			}

			if (!$pass)
			{
				$pass = '';
			}

			switch ($sch)
			{
				case 'ftp':
					$ftpid = ftp_connect($server, $port);
					break;

				case 'ftps':
					$ftpid = ftp_ssl_connect($server, $port);
					break;
			}

			if (!$ftpid)
			{
				return false;
			}

			$login = ftp_login($ftpid, $user, $pass);

			if (!$login)
			{
				return false;
			}

			$ftpsize = ftp_size($ftpid, $path);
			ftp_close($ftpid);

			if ($ftpsize == -1)
			{
				return false;
			}

			return $ftpsize;
		}
	}

	/**
	 * Quick FTP chmod
	 *
	 * @param   string   $url   Link identifier
	 * @param   integer  $mode  The new permissions, given as an octal value.
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/function.ftp-chmod.php
	 * @since   1.7.0
	 */
	public static function ftpChmod($url, $mode)
	{
		$sch = parse_url($url, PHP_URL_SCHEME);

		if (($sch != 'ftp') && ($sch != 'ftps'))
		{
			return false;
		}

		$server = parse_url($url, PHP_URL_HOST);
		$port = parse_url($url, PHP_URL_PORT);
		$path = parse_url($url, PHP_URL_PATH);
		$user = parse_url($url, PHP_URL_USER);
		$pass = parse_url($url, PHP_URL_PASS);

		if ((!$server) || (!$path))
		{
			return false;
		}

		if (!$port)
		{
			$port = 21;
		}

		if (!$user)
		{
			$user = 'anonymous';
		}

		if (!$pass)
		{
			$pass = '';
		}

		switch ($sch)
		{
			case 'ftp':
				$ftpid = ftp_connect($server, $port);
				break;

			case 'ftps':
				$ftpid = ftp_ssl_connect($server, $port);
				break;
		}

		if (!$ftpid)
		{
			return false;
		}

		$login = ftp_login($ftpid, $user, $pass);

		if (!$login)
		{
			return false;
		}

		$res = ftp_chmod($ftpid, $mode, $path);
		ftp_close($ftpid);

		return $res;
	}

	/**
	 * Modes that require a write operation
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public static function getWriteModes()
	{
		return array('w', 'w+', 'a', 'a+', 'r+', 'x', 'x+');
	}

	/**
	 * Stream and Filter Support Operations
	 *
	 * Returns the supported streams, in addition to direct file access
	 * Also includes Joomla! streams as well as PHP streams
	 *
	 * @return  array  Streams
	 *
	 * @since   1.7.0
	 */
	public static function getSupported()
	{
		// Really quite cool what php can do with arrays when you let it...
		static $streams;

		if (!$streams)
		{
			$streams = array_merge(stream_get_wrappers(), self::getJStreams());
		}

		return $streams;
	}

	/**
	 * Returns a list of transports
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public static function getTransports()
	{
		// Is this overkill?
		return stream_get_transports();
	}

	/**
	 * Returns a list of filters
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public static function getFilters()
	{
		// Note: This will look like the getSupported() function with J! filters.
		// TODO: add user space filter loading like user space stream loading
		return stream_get_filters();
	}

	/**
	 * Returns a list of J! streams
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public static function getJStreams()
	{
		static $streams = array();

		if (!$streams)
		{
			$files = new \DirectoryIterator(__DIR__ . '/Streams');

			/* @type  $file  DirectoryIterator */
			foreach ($files as $file)
			{
				// Only load for php files.
				if (!$file->isFile() || $file->getExtension() !== 'php')
				{
					continue;
				}

				$streams[] = str_replace('stream', '', strtolower($file->getBasename('.php')));
			}
		}

		return $streams;
	}

	/**
	 * Determine if a stream is a Joomla stream.
	 *
	 * @param   string  $streamname  The name of a stream
	 *
	 * @return  boolean  True for a Joomla Stream
	 *
	 * @since   1.7.0
	 */
	public static function isJoomlaStream($streamname)
	{
		return in_array($streamname, self::getJStreams());
	}

	/**
	 * Calculates the maximum upload file size and returns string with unit or the size in bytes
	 *
	 * Call it with JFilesystemHelper::fileUploadMaxSize();
	 *
	 * @param   bool  $unit_output  This parameter determines whether the return value should be a string with a unit
	 *
	 * @return  float|string The maximum upload size of files with the appropriate unit or in bytes
	 *
	 * @since   3.4
	 */
	public static function fileUploadMaxSize($unit_output = true)
	{
		static $max_size = false;
		static $output_type = true;

		if ($max_size === false || $output_type != $unit_output)
		{
			$max_size   = self::parseSize(ini_get('post_max_size'));
			$upload_max = self::parseSize(ini_get('upload_max_filesize'));

			if ($upload_max > 0 && ($upload_max < $max_size || $max_size == 0))
			{
				$max_size = $upload_max;
			}

			if ($unit_output == true)
			{
				$max_size = self::parseSizeUnit($max_size);
			}

			$output_type = $unit_output;
		}

		return $max_size;
	}

	/**
	 * Returns the size in bytes without the unit for the comparison
	 *
	 * @param   string  $size  The size which is received from the PHP settings
	 *
	 * @return  float The size in bytes without the unit
	 *
	 * @since   3.4
	 */
	private static function parseSize($size)
	{
		$unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
		$size = preg_replace('/[^0-9\.]/', '', $size);

		$return = round($size);

		if ($unit)
		{
			$return = round($size * pow(1024, stripos('bkmgtpezy', $unit[0])));
		}

		return $return;
	}

	/**
	 * Creates the rounded size of the size with the appropriate unit
	 *
	 * @param   float  $max_size  The maximum size which is allowed for the uploads
	 *
	 * @return  string String with the size and the appropriate unit
	 *
	 * @since   3.4
	 */
	private static function parseSizeUnit($max_size)
	{
		$base     = log($max_size) / log(1024);
		$suffixes = array('', 'k', 'M', 'G', 'T');

		return round(pow(1024, $base - floor($base)), 0) . $suffixes[floor($base)];
	}
}
src/Filesystem/File.php000064400000036465152177723700011100 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Filesystem\Path;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FolderWrapper;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;

/**
 * A File handling class
 *
 * @since  1.7.0
 */
class File
{
	/**
	 * Gets the extension of a file name
	 *
	 * @param   string  $file  The file name
	 *
	 * @return  string  The file extension
	 *
	 * @since   1.7.0
	 */
	public static function getExt($file)
	{
		$dot = strrpos($file, '.');

		if ($dot === false)
		{
			return '';
		}

		return (string) substr($file, $dot + 1);
	}

	/**
	 * Strips the last extension off of a file name
	 *
	 * @param   string  $file  The file name
	 *
	 * @return  string  The file name without the extension
	 *
	 * @since   1.7.0
	 */
	public static function stripExt($file)
	{
		return preg_replace('#\.[^.]*$#', '', $file);
	}

	/**
	 * Makes file name safe to use
	 *
	 * @param   string  $file  The name of the file [not full path]
	 *
	 * @return  string  The sanitised string
	 *
	 * @since   1.7.0
	 */
	public static function makeSafe($file)
	{
		// Remove any trailing dots, as those aren't ever valid file names.
		$file = rtrim($file, '.');

		$regex = array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#', '#^\.#');

		return trim(preg_replace($regex, '', $file));
	}

	/**
	 * Copies a file
	 *
	 * @param   string   $src          The path to the source file
	 * @param   string   $dest         The path to the destination file
	 * @param   string   $path         An optional base path to prefix to the file names
	 * @param   boolean  $use_streams  True to use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function copy($src, $dest, $path = null, $use_streams = false)
	{
		$pathObject = new PathWrapper;

		// Prepend a base path if it exists
		if ($path)
		{
			$src = $pathObject->clean($path . '/' . $src);
			$dest = $pathObject->clean($path . '/' . $dest);
		}

		// Check src path
		if (!is_readable($src))
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_FIND_COPY', $src), Log::WARNING, 'jerror');

			return false;
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			if (!$stream->copy($src, $dest))
			{
				Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_STREAMS', $src, $dest, $stream->getError()), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}
		else
		{
			$FTPOptions = ClientHelper::getCredentials('ftp');

			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// If the parent folder doesn't exist we must create it
				if (!file_exists(dirname($dest)))
				{
					$folderObject = new FolderWrapper;
					$folderObject->create(dirname($dest));
				}

				// Translate the destination path for the FTP account
				$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');

				if (!$ftp->store($src, $dest))
				{
					// FTP connector throws an error
					return false;
				}

				$ret = true;
			}
			else
			{
				if (!@ copy($src, $dest))
				{
					Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_COPY_FAILED_ERR01', $src, $dest), Log::WARNING, 'jerror');

					return false;
				}

				$ret = true;
			}

			return $ret;
		}
	}

	/**
	 * Delete a file or array of files
	 *
	 * @param   mixed  $file  The file name or an array of file names
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function delete($file)
	{
		$FTPOptions = ClientHelper::getCredentials('ftp');
		$pathObject = new PathWrapper;

		if (is_array($file))
		{
			$files = $file;
		}
		else
		{
			$files[] = $file;
		}

		// Do NOT use ftp if it is not enabled
		if ($FTPOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
		}

		foreach ($files as $file)
		{
			$file = $pathObject->clean($file);

			if (!is_file($file))
			{
				continue;
			}

			// Try making the file writable first. If it's read-only, it can't be deleted
			// on Windows, even if the parent folder is writable
			@chmod($file, 0777);

			// In case of restricted permissions we zap it one way or the other
			// as long as the owner is either the webserver or the ftp
			if (@unlink($file))
			{
				// Do nothing
			}
			elseif ($FTPOptions['enabled'] == 1)
			{
				$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');

				if (!$ftp->delete($file))
				{
					// FTP connector throws an error

					return false;
				}
			}
			else
			{
				$filename = basename($file);
				Log::add(Text::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', $filename), Log::WARNING, 'jerror');

				return false;
			}
		}

		return true;
	}

	/**
	 * Moves a file
	 *
	 * @param   string   $src          The path to the source file
	 * @param   string   $dest         The path to the destination file
	 * @param   string   $path         An optional base path to prefix to the file names
	 * @param   boolean  $use_streams  True to use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function move($src, $dest, $path = '', $use_streams = false)
	{
		$pathObject = new PathWrapper;

		if ($path)
		{
			$src = $pathObject->clean($path . '/' . $src);
			$dest = $pathObject->clean($path . '/' . $dest);
		}

		// Check src path
		if (!is_readable($src))
		{
			Log::add(Text::_('JLIB_FILESYSTEM_CANNOT_FIND_SOURCE_FILE'), Log::WARNING, 'jerror');

			return false;
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			if (!$stream->move($src, $dest))
			{
				Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_JFILE_MOVE_STREAMS', $stream->getError()), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}
		else
		{
			$FTPOptions = ClientHelper::getCredentials('ftp');

			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// Translate path for the FTP account
				$src = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
				$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');

				// Use FTP rename to simulate move
				if (!$ftp->rename($src, $dest))
				{
					Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');

					return false;
				}
			}
			else
			{
				if (!@ rename($src, $dest))
				{
					Log::add(Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE'), Log::WARNING, 'jerror');

					return false;
				}
			}

			return true;
		}
	}

	/**
	 * Read the contents of a file
	 *
	 * @param   string   $filename   The full file path
	 * @param   boolean  $incpath    Use include path
	 * @param   integer  $amount     Amount of file to read
	 * @param   integer  $chunksize  Size of chunks to read
	 * @param   integer  $offset     Offset of the file
	 *
	 * @return  mixed  Returns file contents or boolean False if failed
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use the native file_get_contents() instead.
	 */
	public static function read($filename, $incpath = false, $amount = 0, $chunksize = 8192, $offset = 0)
	{
		Log::add(__METHOD__ . ' is deprecated. Use native file_get_contents() syntax.', Log::WARNING, 'deprecated');

		$data = null;

		if ($amount && $chunksize > $amount)
		{
			$chunksize = $amount;
		}

		if (false === $fh = fopen($filename, 'rb', $incpath))
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_READ_UNABLE_TO_OPEN_FILE', $filename), Log::WARNING, 'jerror');

			return false;
		}

		clearstatcache();

		if ($offset)
		{
			fseek($fh, $offset);
		}

		if ($fsize = @ filesize($filename))
		{
			if ($amount && $fsize > $amount)
			{
				$data = fread($fh, $amount);
			}
			else
			{
				$data = fread($fh, $fsize);
			}
		}
		else
		{
			$data = '';

			/*
			 * While it's:
			 * 1: Not the end of the file AND
			 * 2a: No Max Amount set OR
			 * 2b: The length of the data is less than the max amount we want
			 */
			while (!feof($fh) && (!$amount || strlen($data) < $amount))
			{
				$data .= fread($fh, $chunksize);
			}
		}

		fclose($fh);

		return $data;
	}

	/**
	 * Write contents to a file
	 *
	 * @param   string   $file         The full file path
	 * @param   string   $buffer       The buffer to write
	 * @param   boolean  $use_streams  Use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function write($file, $buffer, $use_streams = false)
	{
		@set_time_limit(ini_get('max_execution_time'));

		// If the destination directory doesn't exist we need to create it
		if (!file_exists(dirname($file)))
		{
			$folderObject = new FolderWrapper;

			if ($folderObject->create(dirname($file)) == false)
			{
				return false;
			}
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			// Beef up the chunk size to a meg
			$stream->set('chunksize', (1024 * 1024));

			if (!$stream->writeFile($file, $buffer))
			{
				Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', $file, $stream->getError()), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}
		else
		{
			$FTPOptions = ClientHelper::getCredentials('ftp');
			$pathObject = new PathWrapper;

			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// Translate path for the FTP account and use FTP write buffer to file
				$file = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
				$ret = $ftp->write($file, $buffer);
			}
			else
			{
				$file = $pathObject->clean($file);
				$ret = is_int(file_put_contents($file, $buffer)) ? true : false;
			}

			return $ret;
		}
	}

	/**
	 * Append contents to a file
	 *
	 * @param   string   $file         The full file path
	 * @param   string   $buffer       The buffer to write
	 * @param   boolean  $use_streams  Use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.6.0
	 */
	public static function append($file, $buffer, $use_streams = false)
	{
		@set_time_limit(ini_get('max_execution_time'));

		// If the file doesn't exist, just write instead of append
		if (!file_exists($file))
		{
			return self::write($file, $buffer, $use_streams);
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			// Beef up the chunk size to a meg
			$stream->set('chunksize', (1024 * 1024));

			if ($stream->open($file, 'ab') && $stream->write($buffer) && $stream->close())
			{
				return true;
			}

			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WRITE_STREAMS', $file, $stream->getError()), Log::WARNING, 'jerror');

			return false;
		}
		else
		{
			// Initialise variables.
			$FTPOptions = ClientHelper::getCredentials('ftp');

			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// Translate path for the FTP account and use FTP write buffer to file
				$file = Path::clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $file), '/');
				$ret = $ftp->append($file, $buffer);
			}
			else
			{
				$file = Path::clean($file);
				$ret = is_int(file_put_contents($file, $buffer, FILE_APPEND));
			}

			return $ret;
		}
	}

	/**
	 * Moves an uploaded file to a destination folder
	 *
	 * @param   string   $src              The name of the php (temporary) uploaded file
	 * @param   string   $dest             The path (including filename) to move the uploaded file to
	 * @param   boolean  $use_streams      True to use streams
	 * @param   boolean  $allow_unsafe     Allow the upload of unsafe files
	 * @param   boolean  $safeFileOptions  Options to \JFilterInput::isSafeFile
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.7.0
	 */
	public static function upload($src, $dest, $use_streams = false, $allow_unsafe = false, $safeFileOptions = array())
	{
		if (!$allow_unsafe)
		{
			$descriptor = array(
				'tmp_name' => $src,
				'name'     => basename($dest),
				'type'     => '',
				'error'    => '',
				'size'     => '',
			);

			$isSafe = \JFilterInput::isSafeFile($descriptor, $safeFileOptions);

			if (!$isSafe)
			{
				Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR03', $dest), Log::WARNING, 'jerror');

				return false;
			}
		}

		// Ensure that the path is valid and clean
		$pathObject = new PathWrapper;
		$dest = $pathObject->clean($dest);

		// Create the destination directory if it does not exist
		$baseDir = dirname($dest);

		if (!file_exists($baseDir))
		{
			$folderObject = new FolderWrapper;
			$folderObject->create($baseDir);
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			if (!$stream->upload($src, $dest))
			{
				Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_UPLOAD', $stream->getError()), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}
		else
		{
			$FTPOptions = ClientHelper::getCredentials('ftp');
			$ret = false;

			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// Translate path for the FTP account
				$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');

				// Copy the file to the destination directory
				if (is_uploaded_file($src) && $ftp->store($src, $dest))
				{
					unlink($src);
					$ret = true;
				}
				else
				{
					Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
				}
			}
			else
			{
				if (is_writeable($baseDir) && move_uploaded_file($src, $dest))
				{
					// Short circuit to prevent file permission errors
					if ($pathObject->setPermissions($dest))
					{
						$ret = true;
					}
					else
					{
						Log::add(Text::_('JLIB_FILESYSTEM_ERROR_WARNFS_ERR01'), Log::WARNING, 'jerror');
					}
				}
				else
				{
					Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_WARNFS_ERR04', $src, $dest), Log::WARNING, 'jerror');
				}
			}

			return $ret;
		}
	}

	/**
	 * Wrapper for the standard file_exists function
	 *
	 * @param   string  $file  File path
	 *
	 * @return  boolean  True if path is a file
	 *
	 * @since   1.7.0
	 */
	public static function exists($file)
	{
		$pathObject = new PathWrapper;

		return is_file($pathObject->clean($file));
	}

	/**
	 * Returns the name, without any path.
	 *
	 * @param   string  $file  File path
	 *
	 * @return  string  filename
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use basename() instead.
	 */
	public static function getName($file)
	{
		Log::add(__METHOD__ . ' is deprecated. Use native basename() syntax.', Log::WARNING, 'deprecated');

		// Convert back slashes to forward slashes
		$file = str_replace('\\', '/', $file);
		$slash = strrpos($file, '/');

		if ($slash !== false)
		{
			return substr($file, $slash + 1);
		}
		else
		{
			return $file;
		}
	}
}
src/Filesystem/Wrapper/PathWrapper.php000064400000007213152177723700014063 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Wrapper;

use Joomla\CMS\Filesystem\Path;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for Path
 *
 * @package     Joomla.Platform
 * @subpackage  Filesystem
 * @since       3.4
 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
 */
class PathWrapper
{
	/**
	 * Helper wrapper method for canChmod
	 *
	 * @param   string  $path  Path to check.
	 *
	 * @return  boolean  True if path can have mode changed.
	 *
	 * @see         Path::canChmod()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function canChmod($path)
	{
		return Path::canChmod($path);
	}

	/**
	 * Helper wrapper method for setPermissions
	 *
	 * @param   string  $path        Root path to begin changing mode [without trailing slash].
	 * @param   string  $filemode    Octal representation of the value to change file mode to [null = no change].
	 * @param   string  $foldermode  Octal representation of the value to change folder mode to [null = no change].
	 *
	 * @return  boolean  True if successful [one fail means the whole operation failed].
	 *
	 * @see         Path::setPermissions()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function setPermissions($path, $filemode = '0644', $foldermode = '0755')
	{
		return Path::setPermissions($path, $filemode, $foldermode);
	}

	/**
	 * Helper wrapper method for getPermissions
	 *
	 * @param   string  $path  The path of a file/folder.
	 *
	 * @return  string  Filesystem permissions.
	 *
	 * @see         Path::getPermissions()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function getPermissions($path)
	{
		return Path::getPermissions($path);
	}

	/**
	 * Helper wrapper method for check
	 *
	 * @param   string  $path  A file system path to check.
	 *
	 * @return  string  A cleaned version of the path or exit on error.
	 *
	 * @see         Path::check()
	 * @since       3.4
	 * @throws      Exception
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function check($path)
	{
		return Path::check($path);
	}

	/**
	 * Helper wrapper method for clean
	 *
	 * @param   string  $path  The path to clean.
	 * @param   string  $ds    Directory separator (optional).
	 *
	 * @return  string  The cleaned path.
	 *
	 * @see         Path::clean()
	 * @since       3.4
	 * @throws      UnexpectedValueException
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function clean($path, $ds = DIRECTORY_SEPARATOR)
	{
		return Path::clean($path, $ds);
	}

	/**
	 * Helper wrapper method for isOwner
	 *
	 * @param   string  $path  Path to check ownership.
	 *
	 * @return  boolean  True if the php script owns the path passed.
	 *
	 * @see         Path::isOwner()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function isOwner($path)
	{
		return Path::isOwner($path);
	}

	/**
	 * Helper wrapper method for find
	 *
	 * @param   mixed   $paths  A path string or array of path strings to search in
	 * @param   string  $file   The file name to look for.
	 *
	 * @return mixed   The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
	 *
	 * @see         Path::find()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Path instead
	 */
	public function find($paths, $file)
	{
		return Path::find($paths, $file);
	}
}
src/Filesystem/Wrapper/FolderWrapper.php000064400000014546152177723700014411 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Wrapper;

use Joomla\CMS\Filesystem\Folder;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for Folder
 *
 * @package     Joomla.Platform
 * @subpackage  Filesystem
 * @since       3.4
 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
 */
class FolderWrapper
{
	/**
	 * Helper wrapper method for copy
	 *
	 * @param   string   $src          The path to the source folder.
	 * @param   string   $dest         The path to the destination folder.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $force        Force copy.
	 * @param   boolean  $use_streams  Optionally force folder/file overwrites.
	 *
	 * @return  boolean  True on success.
	 *
	 * @see         Folder::copy()
	 * @since       3.4
	 * @throws      RuntimeException
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function copy($src, $dest, $path = '', $force = false, $use_streams = false)
	{
		return Folder::copy($src, $dest, $path, $force, $use_streams);
	}

	/**
	 * Helper wrapper method for create
	 *
	 * @param   string   $path  A path to create from the base path.
	 * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @see         Folder::create()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function create($path = '', $mode = 493)
	{
		return Folder::create($path, $mode);
	}

	/**
	 * Helper wrapper method for delete
	 *
	 * @param   string  $path  The path to the folder to delete.
	 *
	 * @return  boolean  True on success.
	 *
	 * @see         Folder::delete()
	 * @since       3.4
	 * @throws      UnexpectedValueException
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function delete($path)
	{
		return Folder::delete($path);
	}

	/**
	 * Helper wrapper method for move
	 *
	 * @param   string   $src          The path to the source folder.
	 * @param   string   $dest         The path to the destination folder.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $use_streams  Optionally use streams.
	 *
	 * @return  mixed  Error message on false or boolean true on success.
	 *
	 * @see         Folder::move()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function move($src, $dest, $path = '', $use_streams = false)
	{
		return Folder::move($src, $dest, $path, $use_streams);
	}

	/**
	 * Helper wrapper method for exists
	 *
	 * @param   string  $path  Folder name relative to installation dir.
	 *
	 * @return  boolean  True if path is a folder.
	 *
	 * @see         Folder::exists()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function exists($path)
	{
		return Folder::exists($path);
	}

	/**
	 * Helper wrapper method for files
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludefilter  Array of filter to exclude.
	 * @param   boolean  $naturalSort    False for asort, true for natsort.
	 *
	 * @return  array  Files in the given folder.
	 *
	 * @see         Folder::files()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludefilter = array('^\..*', '.*~'), $naturalSort = false)
	{
		return Folder::files($path, $filter, $recurse, $full, $exclude, $excludefilter, $naturalSort);
	}

	/**
	 * Helper wrapper method for folders
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for folder names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the folders.
	 * @param   array    $exclude        Array with names of folders which should not be shown in the result.
	 * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @see         Folder::folders()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludefilter = array('^\..*'))
	{
		return Folder::folders($path, $filter, $recurse, $full, $exclude, $excludefilter);
	}

	/**
	 * Helper wrapper method for listFolderTree
	 *
	 * @param   string   $path      The path of the folder to read.
	 * @param   string   $filter    A filter for folder names.
	 * @param   integer  $maxLevel  The maximum number of levels to recursively read, defaults to three.
	 * @param   integer  $level     The current level, optional.
	 * @param   integer  $parent    Unique identifier of the parent folder, if any.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @see         Folder::listFolderTree()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
	{
		return Folder::listFolderTree($path, $filter, $maxLevel, $level, $parent);
	}

	/**
	 * Helper wrapper method for makeSafe
	 *
	 * @param   string  $path  The full path to sanitise.
	 *
	 * @return  string  The sanitised string
	 *
	 * @see         Folder::makeSafe()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\Folder instead
	 */
	public function makeSafe($path)
	{
		return Folder::makeSafe($path);
	}
}
src/Filesystem/Wrapper/FileWrapper.php000064400000012625152177723700014051 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Wrapper;

use Joomla\Filesystem\File;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for File
 *
 * @package     Joomla.Platform
 * @subpackage  Filesystem
 * @since       3.4
 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
 */
class FileWrapper
{
	/**
	 * Helper wrapper method for getExt
	 *
	 * @param   string  $file  The file name.
	 *
	 * @return  string  The file extension.
	 *
	 * @see         File::getExt()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function getExt($file)
	{
		return File::getExt($file);
	}

	/**
	 * Helper wrapper method for stripExt
	 *
	 * @param   string  $file  The file name.
	 *
	 * @return  string  The file name without the extension.
	 *
	 * @see         File::stripExt()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function stripExt($file)
	{
		return File::stripExt($file);
	}

	/**
	 * Helper wrapper method for makeSafe
	 *
	 * @param   string  $file  The name of the file [not full path].
	 *
	 * @return  string  The sanitised string.
	 *
	 * @see         File::makeSafe()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function makeSafe($file)
	{
		return File::makeSafe($file);
	}

	/**
	 * Helper wrapper method for copy
	 *
	 * @param   string   $src          The path to the source file.
	 * @param   string   $dest         The path to the destination file.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $use_streams  True to use streams.
	 *
	 * @return  boolean  True on success.
	 *
	 * @see         File::copy()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function copy($src, $dest, $path = null, $use_streams = false)
	{
		return File::copy($src, $dest, $path, $use_streams);
	}

	/**
	 * Helper wrapper method for delete
	 *
	 * @param   mixed  $file  The file name or an array of file names
	 *
	 * @return  boolean  True on success.
	 *
	 * @see         File::delete()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function delete($file)
	{
		return File::delete($file);
	}

	/**
	 * Helper wrapper method for move
	 *
	 * @param   string   $src          The path to the source file.
	 * @param   string   $dest         The path to the destination file.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $use_streams  True to use streams.
	 *
	 * @return  boolean  True on success.
	 *
	 * @see         File::move()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function move($src, $dest, $path = '', $use_streams = false)
	{
		return File::move($src, $dest, $path, $use_streams);
	}

	/**
	 * Helper wrapper method for read
	 *
	 * @param   string   $filename   The full file path.
	 * @param   boolean  $incpath    Use include path.
	 * @param   integer  $amount     Amount of file to read.
	 * @param   integer  $chunksize  Size of chunks to read.
	 * @param   integer  $offset     Offset of the file.
	 *
	 * @return mixed  Returns file contents or boolean False if failed.
	 *
	 * @see         File::read()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function read($filename, $incpath = false, $amount = 0, $chunksize = 8192, $offset = 0)
	{
		return File::read($filename, $incpath, $amount, $chunksize, $offset);
	}

	/**
	 * Helper wrapper method for write
	 *
	 * @param   string   $file         The full file path.
	 * @param   string   &$buffer      The buffer to write.
	 * @param   boolean  $use_streams  Use streams.
	 *
	 * @return boolean  True on success.
	 *
	 * @see         File::write()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function write($file, &$buffer, $use_streams = false)
	{
		return File::write($file, $buffer, $use_streams);
	}

	/**
	 * Helper wrapper method for upload
	 *
	 * @param   string   $src          The name of the php (temporary) uploaded file.
	 * @param   string   $dest         The path (including filename) to move the uploaded file to.
	 * @param   boolean  $use_streams  True to use streams.
	 *
	 * @return boolean  True on success.
	 *
	 * @see         File::upload()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function upload($src, $dest, $use_streams = false)
	{
		return File::upload($src, $dest, $use_streams);
	}

	/**
	 * Helper wrapper method for exists
	 *
	 * @param   string  $file  File path.
	 *
	 * @return boolean  True if path is a file.
	 *
	 * @see         File::exists()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function exists($file)
	{
		return File::exists($file);
	}

	/**
	 * Helper wrapper method for getName
	 *
	 * @param   string  $file  File path.
	 *
	 * @return string  filename.
	 *
	 * @see         File::getName()
	 * @since       3.4
	 * @deprecated  4.0 Use \Joomla\CMS\Filesystem\File instead
	 */
	public function getName($file)
	{
		return File::getName($file);
	}
}
src/Filesystem/Streams/StreamString.php000064400000012324152177723700014245 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Streams;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filesystem\Support\Stringcontroller;

/**
 * String Stream Wrapper
 *
 * This class allows you to use a PHP string in the same way that
 * you would normally use a regular stream wrapper
 *
 * @since  1.7.0
 */
class StreamString
{
	/**
	 * The current string
	 *
	 * @var   string
	 * @since  3.0.0
	 */
	protected $currentString;

	/**
	 * The path
	 *
	 * @var   string
	 * @since  3.0.0
	 */
	protected $path;

	/**
	 * The mode
	 *
	 * @var   string
	 * @since  3.0.0
	 */
	protected $mode;

	/**
	 * Enter description here ...
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $options;

	/**
	 * Enter description here ...
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $openedPath;

	/**
	 * Current position
	 *
	 * @var    integer
	 * @since  3.0.0
	 */
	protected $pos;

	/**
	 * Length of the string
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $len;

	/**
	 * Statistics for a file
	 *
	 * @var    array
	 * @since  3.0.0
	 *
	 * @link   http://us.php.net/manual/en/function.stat.php
	 */
	protected $stat;

	/**
	 * Method to open a file or URL.
	 *
	 * @param   string   $path          The stream path.
	 * @param   string   $mode          Not used.
	 * @param   integer  $options       Not used.
	 * @param   string   &$opened_path  Not used.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function stream_open($path, $mode, $options, &$opened_path)
	{
		$this->currentString = &StringController::getRef(str_replace('string://', '', $path));

		if ($this->currentString)
		{
			$this->len = strlen($this->currentString);
			$this->pos = 0;
			$this->stat = $this->url_stat($path, 0);

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve information from a file resource
	 *
	 * @return  array
	 *
	 * @link    https://www.php.net/manual/en/streamwrapper.stream-stat.php
	 * @since   1.7.0
	 */
	public function stream_stat()
	{
		return $this->stat;
	}

	/**
	 * Method to retrieve information about a file.
	 *
	 * @param   string   $path   File path or URL to stat
	 * @param   integer  $flags  Additional flags set by the streams API
	 *
	 * @return  array
	 *
	 * @link    https://www.php.net/manual/en/streamwrapper.url-stat.php
	 * @since   1.7.0
	 */
	public function url_stat($path, $flags = 0)
	{
		$now = time();
		$string = &StringController::getRef(str_replace('string://', '', $path));
		$stat = array(
			'dev' => 0,
			'ino' => 0,
			'mode' => 0,
			'nlink' => 1,
			'uid' => 0,
			'gid' => 0,
			'rdev' => 0,
			'size' => strlen($string),
			'atime' => $now,
			'mtime' => $now,
			'ctime' => $now,
			'blksize' => '512',
			'blocks' => ceil(strlen($string) / 512),
		);

		return $stat;
	}

	/**
	 * Method to read a given number of bytes starting at the current position
	 * and moving to the end of the string defined by the current position plus the
	 * given number.
	 *
	 * @param   integer  $count  Bytes of data from the current position should be returned.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 *
	 * @link    https://www.php.net/manual/en/streamwrapper.stream-read.php
	 */
	public function stream_read($count)
	{
		$result = substr($this->currentString, $this->pos, $count);
		$this->pos += $count;

		return $result;
	}

	/**
	 * Stream write, always returning false.
	 *
	 * @param   string  $data  The data to write.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @note    Updating the string is not supported.
	 */
	public function stream_write($data)
	{
		// We don't support updating the string.
		return false;
	}

	/**
	 * Method to get the current position
	 *
	 * @return  integer  The position
	 *
	 * @since   1.7.0
	 */
	public function stream_tell()
	{
		return $this->pos;
	}

	/**
	 * End of field check
	 *
	 * @return  boolean  True if at end of field.
	 *
	 * @since   1.7.0
	 */
	public function stream_eof()
	{
		if ($this->pos > $this->len)
		{
			return true;
		}

		return false;
	}

	/**
	 * Stream offset
	 *
	 * @param   integer  $offset  The starting offset.
	 * @param   integer  $whence  SEEK_SET, SEEK_CUR, SEEK_END
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public function stream_seek($offset, $whence)
	{
		// $whence: SEEK_SET, SEEK_CUR, SEEK_END
		if ($offset > $this->len)
		{
			// We can't seek beyond our len.
			return false;
		}

		switch ($whence)
		{
			case SEEK_SET:
				$this->pos = $offset;
				break;

			case SEEK_CUR:
				if (($this->pos + $offset) < $this->len)
				{
					$this->pos += $offset;
				}
				else
				{
					return false;
				}
				break;

			case SEEK_END:
				$this->pos = $this->len - $offset;
				break;
		}

		return true;
	}

	/**
	 * Stream flush, always returns true.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @note    Data storage is not supported
	 */
	public function stream_flush()
	{
		// We don't store data.
		return true;
	}
}

stream_wrapper_register('string', '\\Joomla\\CMS\\Filesystem\\Streams\\StreamString') or die('StreamString Wrapper Registration Failed');
src/Filesystem/Folder.php000064400000044647152177723700011435 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FileWrapper;
use Joomla\CMS\Client\ClientHelper;
use Joomla\CMS\Client\FtpClient;
use Joomla\CMS\Language\Text;

/**
 * A Folder handling class
 *
 * @since  1.7.0
 */
abstract class Folder
{
	/**
	 * Copy a folder.
	 *
	 * @param   string   $src          The path to the source folder.
	 * @param   string   $dest         The path to the destination folder.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $force        Force copy.
	 * @param   boolean  $use_streams  Optionally force folder/file overwrites.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public static function copy($src, $dest, $path = '', $force = false, $use_streams = false)
	{
		@set_time_limit(ini_get('max_execution_time'));

		$FTPOptions = ClientHelper::getCredentials('ftp');
		$pathObject = new PathWrapper;

		if ($path)
		{
			$src  = $pathObject->clean($path . '/' . $src);
			$dest = $pathObject->clean($path . '/' . $dest);
		}

		// Eliminate trailing directory separators, if any
		$src = rtrim($src, DIRECTORY_SEPARATOR);
		$dest = rtrim($dest, DIRECTORY_SEPARATOR);

		if (!self::exists($src))
		{
			throw new \RuntimeException('Source folder not found', -1);
		}

		if (self::exists($dest) && !$force)
		{
			throw new \RuntimeException('Destination folder already exists', -1);
		}

		// Make sure the destination exists
		if (!self::create($dest))
		{
			throw new \RuntimeException('Cannot create destination folder', -1);
		}

		// If we're using ftp and don't have streams enabled
		if ($FTPOptions['enabled'] == 1 && !$use_streams)
		{
			// Connect the FTP client
			$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

			if (!($dh = @opendir($src)))
			{
				throw new \RuntimeException('Cannot open source folder', -1);
			}
			// Walk through the directory copying files and recursing into folders.
			while (($file = readdir($dh)) !== false)
			{
				$sfid = $src . '/' . $file;
				$dfid = $dest . '/' . $file;

				switch (filetype($sfid))
				{
					case 'dir':
						if ($file != '.' && $file != '..')
						{
							$ret = self::copy($sfid, $dfid, null, $force);

							if ($ret !== true)
							{
								return $ret;
							}
						}
						break;

					case 'file':
						// Translate path for the FTP account
						$dfid = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dfid), '/');

						if (!$ftp->store($sfid, $dfid))
						{
							throw new \RuntimeException('Copy file failed', -1);
						}
						break;
				}
			}
		}
		else
		{
			if (!($dh = @opendir($src)))
			{
				throw new \RuntimeException('Cannot open source folder', -1);
			}
			// Walk through the directory copying files and recursing into folders.
			while (($file = readdir($dh)) !== false)
			{
				$sfid = $src . '/' . $file;
				$dfid = $dest . '/' . $file;

				switch (filetype($sfid))
				{
					case 'dir':
						if ($file != '.' && $file != '..')
						{
							$ret = self::copy($sfid, $dfid, null, $force, $use_streams);

							if ($ret !== true)
							{
								return $ret;
							}
						}
						break;

					case 'file':
						if ($use_streams)
						{
							$stream = Factory::getStream();

							if (!$stream->copy($sfid, $dfid))
							{
								throw new \RuntimeException('Cannot copy file: ' . $stream->getError(), -1);
							}
						}
						else
						{
							if (!@copy($sfid, $dfid))
							{
								throw new \RuntimeException('Copy file failed', -1);
							}
						}
						break;
				}
			}
		}

		return true;
	}

	/**
	 * Create a folder -- and all necessary parent folders.
	 *
	 * @param   string   $path  A path to create from the base path.
	 * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   1.7.0
	 */
	public static function create($path = '', $mode = 0755)
	{
		$FTPOptions = ClientHelper::getCredentials('ftp');
		static $nested = 0;

		// Check to make sure the path valid and clean
		$pathObject = new PathWrapper;
		$path = $pathObject->clean($path);

		// Check if parent dir exists
		$parent = dirname($path);

		if (!self::exists($parent))
		{
			// Prevent infinite loops!
			$nested++;

			if (($nested > 20) || ($parent == $path))
			{
				Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_LOOP'), Log::WARNING, 'jerror');
				$nested--;

				return false;
			}

			// Create the parent directory
			if (self::create($parent, $mode) !== true)
			{
				// JFolder::create throws an error
				$nested--;

				return false;
			}

			// OK, parent directory has been created
			$nested--;
		}

		// Check if dir already exists
		if (self::exists($path))
		{
			return true;
		}

		// Check for safe mode
		if ($FTPOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

			// Translate path to FTP path
			$path = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');
			$ret = $ftp->mkdir($path);
			$ftp->chmod($path, $mode);
		}
		else
		{
			// We need to get and explode the open_basedir paths
			$obd = ini_get('open_basedir');

			// If open_basedir is set we need to get the open_basedir that the path is in
			if ($obd != null)
			{
				if (IS_WIN)
				{
					$obdSeparator = ';';
				}
				else
				{
					$obdSeparator = ':';
				}

				// Create the array of open_basedir paths
				$obdArray = explode($obdSeparator, $obd);
				$inBaseDir = false;

				// Iterate through open_basedir paths looking for a match
				foreach ($obdArray as $test)
				{
					$test = $pathObject->clean($test);

					if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0)
					{
						$inBaseDir = true;
						break;
					}
				}

				if ($inBaseDir == false)
				{
					// Return false for JFolder::create because the path to be created is not in open_basedir
					Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_PATH'), Log::WARNING, 'jerror');

					return false;
				}
			}

			// First set umask
			$origmask = @umask(0);

			// Create the path
			if (!$ret = @mkdir($path, $mode))
			{
				@umask($origmask);
				Log::add(
					__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_COULD_NOT_CREATE_DIRECTORY') . 'Path: ' . $path, Log::WARNING, 'jerror'
				);

				return false;
			}

			// Reset umask
			@umask($origmask);
		}

		return $ret;
	}

	/**
	 * Delete a folder.
	 *
	 * @param   string  $path  The path to the folder to delete.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	public static function delete($path)
	{
		@set_time_limit(ini_get('max_execution_time'));
		$pathObject = new PathWrapper;

		// Sanity check
		if (!$path)
		{
			// Bad programmer! Bad Bad programmer!
			Log::add(__METHOD__ . ': ' . Text::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');

			return false;
		}

		$FTPOptions = ClientHelper::getCredentials('ftp');

		// Check to make sure the path valid and clean
		$path = $pathObject->clean($path);

		// Is this really a folder?
		if (!is_dir($path))
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Remove all the files in folder if they exist; disable all filtering
		$files = self::files($path, '.', false, true, array(), array());

		if (!empty($files))
		{
			$file = new FileWrapper;

			if ($file->delete($files) !== true)
			{
				// JFile::delete throws an error
				return false;
			}
		}

		// Remove sub-folders of folder; disable all filtering
		$folders = self::folders($path, '.', false, true, array(), array());

		foreach ($folders as $folder)
		{
			if (is_link($folder))
			{
				// Don't descend into linked directories, just delete the link.
				$file = new FileWrapper;

				if ($file->delete($folder) !== true)
				{
					// JFile::delete throws an error
					return false;
				}
			}
			elseif (self::delete($folder) !== true)
			{
				// JFolder::delete throws an error
				return false;
			}
		}

		if ($FTPOptions['enabled'] == 1)
		{
			// Connect the FTP client
			$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);
		}

		// In case of restricted permissions we zap it one way or the other
		// as long as the owner is either the webserver or the ftp.
		if (@rmdir($path))
		{
			$ret = true;
		}
		elseif ($FTPOptions['enabled'] == 1)
		{
			// Translate path and delete
			$path = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $path), '/');

			// FTP connector throws an error
			$ret = $ftp->delete($path);
		}
		else
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');
			$ret = false;
		}

		return $ret;
	}

	/**
	 * Moves a folder.
	 *
	 * @param   string   $src          The path to the source folder.
	 * @param   string   $dest         The path to the destination folder.
	 * @param   string   $path         An optional base path to prefix to the file names.
	 * @param   boolean  $use_streams  Optionally use streams.
	 *
	 * @return  mixed  Error message on false or boolean true on success.
	 *
	 * @since   1.7.0
	 */
	public static function move($src, $dest, $path = '', $use_streams = false)
	{
		$FTPOptions = ClientHelper::getCredentials('ftp');
		$pathObject = new PathWrapper;

		if ($path)
		{
			$src = $pathObject->clean($path . '/' . $src);
			$dest = $pathObject->clean($path . '/' . $dest);
		}

		if (!self::exists($src))
		{
			return Text::_('JLIB_FILESYSTEM_ERROR_FIND_SOURCE_FOLDER');
		}

		if (self::exists($dest))
		{
			return Text::_('JLIB_FILESYSTEM_ERROR_FOLDER_EXISTS');
		}

		if ($use_streams)
		{
			$stream = Factory::getStream();

			if (!$stream->move($src, $dest))
			{
				return Text::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_RENAME', $stream->getError());
			}

			$ret = true;
		}
		else
		{
			if ($FTPOptions['enabled'] == 1)
			{
				// Connect the FTP client
				$ftp = FtpClient::getInstance($FTPOptions['host'], $FTPOptions['port'], array(), $FTPOptions['user'], $FTPOptions['pass']);

				// Translate path for the FTP account
				$src = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $src), '/');
				$dest = $pathObject->clean(str_replace(JPATH_ROOT, $FTPOptions['root'], $dest), '/');

				// Use FTP rename to simulate move
				if (!$ftp->rename($src, $dest))
				{
					return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
				}

				$ret = true;
			}
			else
			{
				if (!@rename($src, $dest))
				{
					return Text::_('JLIB_FILESYSTEM_ERROR_RENAME_FILE');
				}

				$ret = true;
			}
		}

		return $ret;
	}

	/**
	 * Wrapper for the standard file_exists function
	 *
	 * @param   string  $path  Folder name relative to installation dir
	 *
	 * @return  boolean  True if path is a folder
	 *
	 * @since   1.7.0
	 */
	public static function exists($path)
	{
		$pathObject = new PathWrapper;

		return is_dir($pathObject->clean($path));
	}

	/**
	 * Utility function to read the files in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludefilter  Array of filter to exclude
	 * @param   boolean  $naturalSort    False for asort, true for natsort
	 *
	 * @return  array  Files in the given folder.
	 *
	 * @since   1.7.0
	 */
	public static function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludefilter = array('^\..*', '.*~'), $naturalSort = false)
	{
		// Check to make sure the path valid and clean
		$pathObject = new PathWrapper;
		$path = $pathObject->clean($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FILES', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Compute the excludefilter string
		if (count($excludefilter))
		{
			$excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
		}
		else
		{
			$excludefilter_string = '';
		}

		// Get the files
		$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, true);

		// Sort the files based on either natural or alpha method
		if ($naturalSort)
		{
			natsort($arr);
		}
		else
		{
			asort($arr);
		}

		return array_values($arr);
	}

	/**
	 * Utility function to read the folders in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for folder names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the folders.
	 * @param   array    $exclude        Array with names of folders which should not be shown in the result.
	 * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @since   1.7.0
	 */
	public static function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludefilter = array('^\..*'))
	{
		// Check to make sure the path valid and clean
		$pathObject = new PathWrapper;
		$path = $pathObject->clean($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			Log::add(Text::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER_FOLDER', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Compute the excludefilter string
		if (count($excludefilter))
		{
			$excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
		}
		else
		{
			$excludefilter_string = '';
		}

		// Get the folders
		$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, false);

		// Sort the folders
		asort($arr);

		return array_values($arr);
	}

	/**
	 * Function to read the files/folders in a folder.
	 *
	 * @param   string   $path                  The path of the folder to read.
	 * @param   string   $filter                A filter for file names.
	 * @param   mixed    $recurse               True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full                  True to return the full path to the file.
	 * @param   array    $exclude               Array with names of files which should not be shown in the result.
	 * @param   string   $excludefilter_string  Regexp of files to exclude
	 * @param   boolean  $findfiles             True to read the files, false to read the folders
	 *
	 * @return  array  Files.
	 *
	 * @since   1.7.0
	 */
	protected static function _items($path, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles)
	{
		@set_time_limit(ini_get('max_execution_time'));

		$arr = array();

		// Read the source directory
		if (!($handle = @opendir($path)))
		{
			return $arr;
		}

		while (($file = readdir($handle)) !== false)
		{
			if ($file != '.' && $file != '..' && !in_array($file, $exclude)
				&& (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
			{
				// Compute the fullpath
				$fullpath = $path . '/' . $file;

				// Compute the isDir flag
				$isDir = is_dir($fullpath);

				if (($isDir xor $findfiles) && preg_match("/$filter/", $file))
				{
					// (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
					if ($full)
					{
						// Full path is requested
						$arr[] = $fullpath;
					}
					else
					{
						// Filename is requested
						$arr[] = $file;
					}
				}

				if ($isDir && $recurse)
				{
					// Search recursively
					if (is_int($recurse))
					{
						// Until depth 0 is reached
						$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludefilter_string, $findfiles));
					}
					else
					{
						$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludefilter_string, $findfiles));
					}
				}
			}
		}

		closedir($handle);

		return $arr;
	}

	/**
	 * Lists folder in format suitable for tree display.
	 *
	 * @param   string   $path      The path of the folder to read.
	 * @param   string   $filter    A filter for folder names.
	 * @param   integer  $maxLevel  The maximum number of levels to recursively read, defaults to three.
	 * @param   integer  $level     The current level, optional.
	 * @param   integer  $parent    Unique identifier of the parent folder, if any.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @since   1.7.0
	 */
	public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
	{
		$dirs = array();

		if ($level == 0)
		{
			$GLOBALS['_JFolder_folder_tree_index'] = 0;
		}

		if ($level < $maxLevel)
		{
			$folders    = self::folders($path, $filter);
			$pathObject = new PathWrapper;

			// First path, index foldernames
			foreach ($folders as $name)
			{
				$id = ++$GLOBALS['_JFolder_folder_tree_index'];
				$fullName = $pathObject->clean($path . '/' . $name);
				$dirs[] = array(
					'id' => $id,
					'parent' => $parent,
					'name' => $name,
					'fullname' => $fullName,
					'relname' => str_replace(JPATH_ROOT, '', $fullName),
				);
				$dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
				$dirs = array_merge($dirs, $dirs2);
			}
		}

		return $dirs;
	}

	/**
	 * Makes path name safe to use.
	 *
	 * @param   string  $path  The full path to sanitise.
	 *
	 * @return  string  The sanitised string.
	 *
	 * @since   1.7.0
	 */
	public static function makeSafe($path)
	{
		$regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');

		return preg_replace($regex, '', $path);
	}
}
src/Filesystem/Meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini000064400000001331152177723700022502 0ustar00; Joomla! Project
; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8

JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of file %s at line %d"
JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block"
JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input"
JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE="Unexisting source file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE="Unexpected add line at line %d'"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF="Unexpected end of file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE="Unexpected remove line at line %d"

src/Filesystem/Path.php000064400000016004152177723700011100 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Filesystem\Wrapper\PathWrapper;
use Joomla\CMS\Filesystem\Wrapper\FileWrapper;
use Joomla\CMS\Crypt\Crypt;

if (!defined('JPATH_ROOT'))
{
	// Define a string constant for the root directory of the file system in native format
	$pathHelper = new PathWrapper;
	define('JPATH_ROOT', $pathHelper->clean(JPATH_SITE));
}

/**
 * A Path handling class
 *
 * @since  1.7.0
 */
class Path
{
	/**
	 * Checks if a path's permissions can be changed.
	 *
	 * @param   string  $path  Path to check.
	 *
	 * @return  boolean  True if path can have mode changed.
	 *
	 * @since   1.7.0
	 */
	public static function canChmod($path)
	{
		$perms = fileperms($path);

		if ($perms !== false)
		{
			if (@chmod($path, $perms ^ 0001))
			{
				@chmod($path, $perms);

				return true;
			}
		}

		return false;
	}

	/**
	 * Chmods files and directories recursively to given permissions.
	 *
	 * @param   string  $path        Root path to begin changing mode [without trailing slash].
	 * @param   string  $filemode    Octal representation of the value to change file mode to [null = no change].
	 * @param   string  $foldermode  Octal representation of the value to change folder mode to [null = no change].
	 *
	 * @return  boolean  True if successful [one fail means the whole operation failed].
	 *
	 * @since   1.7.0
	 */
	public static function setPermissions($path, $filemode = '0644', $foldermode = '0755')
	{
		// Initialise return value
		$ret = true;

		if (is_dir($path))
		{
			$dh = opendir($path);

			while ($file = readdir($dh))
			{
				if ($file != '.' && $file != '..')
				{
					$fullpath = $path . '/' . $file;

					if (is_dir($fullpath))
					{
						if (!self::setPermissions($fullpath, $filemode, $foldermode))
						{
							$ret = false;
						}
					}
					else
					{
						if (isset($filemode))
						{
							if (!@ chmod($fullpath, octdec($filemode)))
							{
								$ret = false;
							}
						}
					}
				}
			}

			closedir($dh);

			if (isset($foldermode))
			{
				if (!@ chmod($path, octdec($foldermode)))
				{
					$ret = false;
				}
			}
		}
		else
		{
			if (isset($filemode))
			{
				$ret = @ chmod($path, octdec($filemode));
			}
		}

		return $ret;
	}

	/**
	 * Get the permissions of the file/folder at a given path.
	 *
	 * @param   string  $path  The path of a file/folder.
	 *
	 * @return  string  Filesystem permissions.
	 *
	 * @since   1.7.0
	 */
	public static function getPermissions($path)
	{
		$path = self::clean($path);
		$mode = @ decoct(@ fileperms($path) & 0777);

		if (strlen($mode) < 3)
		{
			return '---------';
		}

		$parsed_mode = '';

		for ($i = 0; $i < 3; $i++)
		{
			// Read
			$parsed_mode .= ($mode{$i} & 04) ? 'r' : '-';

			// Write
			$parsed_mode .= ($mode{$i} & 02) ? 'w' : '-';

			// Execute
			$parsed_mode .= ($mode{$i} & 01) ? 'x' : '-';
		}

		return $parsed_mode;
	}

	/**
	 * Checks for snooping outside of the file system root.
	 *
	 * @param   string  $path  A file system path to check.
	 *
	 * @return  string  A cleaned version of the path or exit on error.
	 *
	 * @since   1.7.0
	 * @throws  Exception
	 */
	public static function check($path)
	{
		if (strpos($path, '..') !== false)
		{
			// Don't translate
			throw new \Exception(
				sprintf(
					'%s() - Use of relative paths not permitted',
					__METHOD__
				),
				20
			);
		}

		$path = self::clean($path);

		if ((JPATH_ROOT != '') && strpos($path, self::clean(JPATH_ROOT)) !== 0)
		{
			throw new \Exception(
				sprintf(
					'%1$s() - Snooping out of bounds @ %2$s',
					__METHOD__,
					$path
				),
				20
			);
		}

		return $path;
	}

	/**
	 * Function to strip additional / or \ in a path name.
	 *
	 * @param   string  $path  The path to clean.
	 * @param   string  $ds    Directory separator (optional).
	 *
	 * @return  string  The cleaned path.
	 *
	 * @since   1.7.0
	 * @throws  UnexpectedValueException
	 */
	public static function clean($path, $ds = DIRECTORY_SEPARATOR)
	{
		if (!is_string($path) && !empty($path))
		{
			throw new \UnexpectedValueException(
				sprintf(
					'%s() - $path is not a string',
					__METHOD__
				),
				20
			);
		}

		$path = trim($path);

		if (empty($path))
		{
			$path = JPATH_ROOT;
		}
		// Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
		// If dealing with a UNC path don't forget to prepend the path with a backslash.
		elseif (($ds == '\\') && substr($path, 0, 2) == '\\\\')
		{
			$path = "\\" . preg_replace('#[/\\\\]+#', $ds, $path);
		}
		else
		{
			$path = preg_replace('#[/\\\\]+#', $ds, $path);
		}

		return $path;
	}

	/**
	 * Method to determine if script owns the path.
	 *
	 * @param   string  $path  Path to check ownership.
	 *
	 * @return  boolean  True if the php script owns the path passed.
	 *
	 * @since   1.7.0
	 */
	public static function isOwner($path)
	{
		$tmp = md5(Crypt::genRandomBytes());
		$ssp = ini_get('session.save_path');
		$jtp = JPATH_SITE . '/tmp';

		// Try to find a writable directory
		$dir = false;

		foreach (array($jtp, $ssp, '/tmp') as $currentDir)
		{
			if (is_writable($currentDir))
			{
				$dir = $currentDir;

				break;
			}
		}

		if ($dir)
		{
			$fileObject = new FileWrapper;
			$test       = $dir . '/' . $tmp;

			// Create the test file
			$blank = '';
			$fileObject->write($test, $blank, false);

			// Test ownership
			$return = (fileowner($test) == fileowner($path));

			// Delete the test file
			$fileObject->delete($test);

			return $return;
		}

		return false;
	}

	/**
	 * Searches the directory paths for a given file.
	 *
	 * @param   mixed   $paths  An path string or array of path strings to search in
	 * @param   string  $file   The file name to look for.
	 *
	 * @return  mixed   The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
	 *
	 * @since   1.7.0
	 */
	public static function find($paths, $file)
	{
		// Force to array
		if (!is_array($paths) && !($paths instanceof \Iterator))
		{
			settype($paths, 'array');
		}

		// Start looping through the path set
		foreach ($paths as $path)
		{
			// Get the path to the file
			$fullname = $path . '/' . $file;

			// Is the path based on a stream?
			if (strpos($path, '://') === false)
			{
				// Not a stream, so do a realpath() to avoid directory
				// traversal attempts on the local file system.

				// Needed for substr() later
				$path = realpath($path);
				$fullname = realpath($fullname);
			}

			/*
			 * The substr() check added to make sure that the realpath()
			 * results in a directory registered so that
			 * non-registered directories are not accessible via directory
			 * traversal attempts.
			 */
			if (file_exists($fullname) && substr($fullname, 0, strlen($path)) == $path)
			{
				return $fullname;
			}
		}

		// Could not find the file in the set of paths
		return false;
	}
}
src/Filesystem/Patcher.php000064400000026270152177723700011600 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;
use Joomla\CMS\Language\Text;

/**
 * A Unified Diff Format Patcher class
 *
 * @link   http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta
 * @since  3.0.0
 */
class Patcher
{
	/**
	 * Regular expression for searching source files
	 */
	const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';

	/**
	 * Regular expression for searching destination files
	 */
	const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';

	/**
	 * Regular expression for searching hunks of differences
	 */
	const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';

	/**
	 * Regular expression for splitting lines
	 */
	const SPLIT = '/(\r\n)|(\r)|(\n)/';

	/**
	 * @var    array  sources files
	 * @since  3.0.0
	 */
	protected $sources = array();

	/**
	 * @var    array  destination files
	 * @since  3.0.0
	 */
	protected $destinations = array();

	/**
	 * @var    array  removal files
	 * @since  3.0.0
	 */
	protected $removals = array();

	/**
	 * @var    array  patches
	 * @since  3.0.0
	 */
	protected $patches = array();

	/**
	 * @var    array  instance of this class
	 * @since  3.0.0
	 */
	protected static $instance;

	/**
	 * Constructor
	 *
	 * The constructor is protected to force the use of FilesystemPatcher::getInstance()
	 *
	 * @since   3.0.0
	 */
	protected function __construct()
	{
	}

	/**
	 * Method to get a patcher
	 *
	 * @return  FilesystemPatcher  an instance of the patcher
	 *
	 * @since   3.0.0
	 */
	public static function getInstance()
	{
		if (!isset(static::$instance))
		{
			static::$instance = new static;
		}

		return static::$instance;
	}

	/**
	 * Reset the pacher
	 *
	 * @return  FilesystemPatcher  This object for chaining
	 *
	 * @since   3.0.0
	 */
	public function reset()
	{
		$this->sources = array();
		$this->destinations = array();
		$this->removals = array();
		$this->patches = array();

		return $this;
	}

	/**
	 * Apply the patches
	 *
	 * @return  integer  The number of files patched
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	public function apply()
	{
		foreach ($this->patches as $patch)
		{
			// Separate the input into lines
			$lines = self::splitLines($patch['udiff']);

			// Loop for each header
			while (self::findHeader($lines, $src, $dst))
			{
				$done = false;

				$regex = '#^([^/]*/)*#';
				if ($patch['strip'] !== null)
				{
					$regex = '#^([^/]*/){' . (int) $patch['strip'] . '}#';
				}

				$src = $patch['root'] . preg_replace($regex, '', $src);
				$dst = $patch['root'] . preg_replace($regex, '', $dst);

				// Loop for each hunk of differences
				while (self::findHunk($lines, $src_line, $src_size, $dst_line, $dst_size))
				{
					$done = true;

					// Apply the hunk of differences
					$this->applyHunk($lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size);
				}

				// If no modifications were found, throw an exception
				if (!$done)
				{
					throw new \RuntimeException('Invalid Diff');
				}
			}
		}

		// Initialize the counter
		$done = 0;

		// Patch each destination file
		foreach ($this->destinations as $file => $content)
		{
			$buffer = implode("\n", $content);

			if (File::write($file, $buffer))
			{
				if (isset($this->sources[$file]))
				{
					$this->sources[$file] = $content;
				}

				$done++;
			}
		}

		// Remove each removed file
		foreach ($this->removals as $file)
		{
			if (File::delete($file))
			{
				if (isset($this->sources[$file]))
				{
					unset($this->sources[$file]);
				}

				$done++;
			}
		}

		// Clear the destinations cache
		$this->destinations = array();

		// Clear the removals
		$this->removals = array();

		// Clear the patches
		$this->patches = array();

		return $done;
	}

	/**
	 * Add a unified diff file to the patcher
	 *
	 * @param   string  $filename  Path to the unified diff file
	 * @param   string  $root      The files root path
	 * @param   string  $strip     The number of '/' to strip
	 *
	 * @return	FilesystemPatcher  $this for chaining
	 *
	 * @since   3.0.0
	 */
	public function addFile($filename, $root = JPATH_BASE, $strip = 0)
	{
		return $this->add(file_get_contents($filename), $root, $strip);
	}

	/**
	 * Add a unified diff string to the patcher
	 *
	 * @param   string  $udiff  Unified diff input string
	 * @param   string  $root   The files root path
	 * @param   string  $strip  The number of '/' to strip
	 *
	 * @return	FilesystemPatcher  $this for chaining
	 *
	 * @since   3.0.0
	 */
	public function add($udiff, $root = JPATH_BASE, $strip = 0)
	{
		$this->patches[] = array(
			'udiff' => $udiff,
			'root' => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '',
			'strip' => $strip,
		);

		return $this;
	}

	/**
	 * Separate CR or CRLF lines
	 *
	 * @param   string  $data  Input string
	 *
	 * @return  array  The lines of the inputdestination file
	 *
	 * @since   3.0.0
	 */
	protected static function splitLines($data)
	{
		return preg_split(self::SPLIT, $data);
	}

	/**
	 * Find the diff header
	 *
	 * The internal array pointer of $lines is on the next line after the finding
	 *
	 * @param   array   &$lines  The udiff array of lines
	 * @param   string  &$src    The source file
	 * @param   string  &$dst    The destination file
	 *
	 * @return  boolean  TRUE in case of success, FALSE in case of failure
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	protected static function findHeader(&$lines, &$src, &$dst)
	{
		// Get the current line
		$line = current($lines);

		// Search for the header
		while ($line !== false && !preg_match(self::SRC_FILE, $line, $m))
		{
			$line = next($lines);
		}

		if ($line === false)
		{
			// No header found, return false
			return false;
		}

		// Set the source file
		$src = $m[1];

		// Advance to the next line
		$line = next($lines);

		if ($line === false)
		{
			throw new \RuntimeException('Unexpected EOF');
		}

		// Search the destination file
		if (!preg_match(self::DST_FILE, $line, $m))
		{
			throw new \RuntimeException('Invalid Diff file');
		}

		// Set the destination file
		$dst = $m[1];

		// Advance to the next line
		if (next($lines) === false)
		{
			throw new \RuntimeException('Unexpected EOF');
		}

		return true;
	}

	/**
	 * Find the next hunk of difference
	 *
	 * The internal array pointer of $lines is on the next line after the finding
	 *
	 * @param   array   &$lines     The udiff array of lines
	 * @param   string  &$src_line  The beginning of the patch for the source file
	 * @param   string  &$src_size  The size of the patch for the source file
	 * @param   string  &$dst_line  The beginning of the patch for the destination file
	 * @param   string  &$dst_size  The size of the patch for the destination file
	 *
	 * @return  boolean  TRUE in case of success, false in case of failure
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	protected static function findHunk(&$lines, &$src_line, &$src_size, &$dst_line, &$dst_size)
	{
		$line = current($lines);

		if (preg_match(self::HUNK, $line, $m))
		{
			$src_line = (int) $m[1];

			$src_size = 1;
			if ($m[3] !== '')
			{
				$src_size = (int) $m[3];
			}

			$dst_line = (int) $m[4];

			$dst_size = 1;
			if ($m[6] !== '')
			{
				$dst_size = (int) $m[6];
			}

			if (next($lines) === false)
			{
				throw new \RuntimeException('Unexpected EOF');
			}

			return true;
		}

		return false;
	}

	/**
	 * Apply the patch
	 *
	 * @param   array   &$lines    The udiff array of lines
	 * @param   string  $src       The source file
	 * @param   string  $dst       The destination file
	 * @param   string  $src_line  The beginning of the patch for the source file
	 * @param   string  $src_size  The size of the patch for the source file
	 * @param   string  $dst_line  The beginning of the patch for the destination file
	 * @param   string  $dst_size  The size of the patch for the destination file
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	protected function applyHunk(&$lines, $src, $dst, $src_line, $src_size, $dst_line, $dst_size)
	{
		$src_line--;
		$dst_line--;
		$line = current($lines);

		// Source lines (old file)
		$source = array();

		// New lines (new file)
		$destin = array();
		$src_left = $src_size;
		$dst_left = $dst_size;

		do
		{
			if (!isset($line[0]))
			{
				$source[] = '';
				$destin[] = '';
				$src_left--;
				$dst_left--;
			}
			elseif ($line[0] == '-')
			{
				if ($src_left == 0)
				{
					throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE', key($lines)));
				}

				$source[] = substr($line, 1);
				$src_left--;
			}
			elseif ($line[0] == '+')
			{
				if ($dst_left == 0)
				{
					throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE', key($lines)));
				}

				$destin[] = substr($line, 1);
				$dst_left--;
			}
			elseif ($line != '\\ No newline at end of file')
			{
				$line = substr($line, 1);
				$source[] = $line;
				$destin[] = $line;
				$src_left--;
				$dst_left--;
			}

			if ($src_left == 0 && $dst_left == 0)
			{
				// Now apply the patch, finally!
				if ($src_size > 0)
				{
					$src_lines = & $this->getSource($src);

					if (!isset($src_lines))
					{
						throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE', $src));
					}
				}

				if ($dst_size > 0)
				{
					if ($src_size > 0)
					{
						$dst_lines = & $this->getDestination($dst, $src);
						$src_bottom = $src_line + count($source);

						for ($l = $src_line;$l < $src_bottom;$l++)
						{
							if ($src_lines[$l] != $source[$l - $src_line])
							{
								throw new \RuntimeException(Text::sprintf('JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY', $src, $l));
							}
						}

						array_splice($dst_lines, $dst_line, count($source), $destin);
					}
					else
					{
						$this->destinations[$dst] = $destin;
					}
				}
				else
				{
					$this->removals[] = $src;
				}

				next($lines);

				return;
			}

			$line = next($lines);
		}

		while ($line !== false);
		throw new \RuntimeException('Unexpected EOF');
	}

	/**
	 * Get the lines of a source file
	 *
	 * @param   string  $src  The path of a file
	 *
	 * @return  array  The lines of the source file
	 *
	 * @since   3.0.0
	 */
	protected function &getSource($src)
	{
		if (!isset($this->sources[$src]))
		{
			$this->sources[$src] = null;
			if (is_readable($src))
			{
				$this->sources[$src] = self::splitLines(file_get_contents($src));
			}
		}

		return $this->sources[$src];
	}

	/**
	 * Get the lines of a destination file
	 *
	 * @param   string  $dst  The path of a destination file
	 * @param   string  $src  The path of a source file
	 *
	 * @return  array  The lines of the destination file
	 *
	 * @since   3.0.0
	 */
	protected function &getDestination($dst, $src)
	{
		if (!isset($this->destinations[$dst]))
		{
			$this->destinations[$dst] = $this->getSource($src);
		}

		return $this->destinations[$dst];
	}
}
src/Filesystem/Support/Stringcontroller.php000064400000002240152177723700015227 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Filesystem\Support;

defined('JPATH_PLATFORM') or die;

/**
 * String Controller
 *
 * @since  1.7.0
 */
class StringController
{
	/**
	 * Defines a variable as an array
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function _getArray()
	{
		static $strings = array();

		return $strings;
	}

	/**
	 * Create a reference
	 *
	 * @param   string  $reference  The key
	 * @param   string  &$string    The value
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function createRef($reference, &$string)
	{
		$ref = &self::_getArray();
		$ref[$reference] = & $string;
	}

	/**
	 * Get reference
	 *
	 * @param   string  $reference  The key for the reference.
	 *
	 * @return  mixed  False if not set, reference if it exists
	 *
	 * @since   1.7.0
	 */
	public function getRef($reference)
	{
		$ref = &self::_getArray();

		if (isset($ref[$reference]))
		{
			return $ref[$reference];
		}
		else
		{
			return false;
		}
	}
}
src/Cache/Controller/OutputController.php000064400000010712152177723700014552 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheController;
use Joomla\CMS\Log\Log;

/**
 * Joomla Cache output type object
 *
 * @since  1.7.0
 */
class OutputController extends CacheController
{
	/**
	 * Cache data ID
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_id;

	/**
	 * Cache data group
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_group;

	/**
	 * Object to test locked state
	 *
	 * @var    \stdClass
	 * @since  1.7.0
	 * @deprecated  4.0
	 */
	protected $_locktest = null;

	/**
	 * Start the cache
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public function start($id, $group = null)
	{
		Log::add(
			__METHOD__ . '() is deprecated.',
			Log::WARNING,
			'deprecated'
		);

		// If we have data in cache use that.
		$data = $this->cache->get($id, $group);

		$this->_locktest             = new \stdClass;
		$this->_locktest->locked     = null;
		$this->_locktest->locklooped = null;

		if ($data === false)
		{
			$this->_locktest = $this->cache->lock($id, $group);

			if ($this->_locktest->locked == true && $this->_locktest->locklooped == true)
			{
				$data = $this->cache->get($id, $group);
			}
		}

		if ($data !== false)
		{
			$data = unserialize(trim($data));
			echo $data;

			if ($this->_locktest->locked == true)
			{
				$this->cache->unlock($id, $group);
			}

			return true;
		}

		// Nothing in cache... let's start the output buffer and start collecting data for next time.
		if ($this->_locktest->locked == false)
		{
			$this->_locktest = $this->cache->lock($id, $group);
		}

		ob_start();
		ob_implicit_flush(false);

		// Set id and group placeholders
		$this->_id = $id;
		$this->_group = $group;

		return false;
	}

	/**
	 * Stop the cache buffer and store the cached data
	 *
	 * @return  boolean  True if the cache data was stored
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public function end()
	{
		Log::add(
			__METHOD__ . '() is deprecated.',
			Log::WARNING,
			'deprecated'
		);

		// Get data from output buffer and echo it
		$data = ob_get_clean();
		echo $data;

		// Get the ID and group and reset the placeholders
		$id           = $this->_id;
		$group        = $this->_group;
		$this->_id    = null;
		$this->_group = null;

		// Get the storage handler and store the cached data
		$ret = $this->cache->store(serialize($data), $id, $group);

		if ($this->_locktest->locked == true)
		{
			$this->cache->unlock($id, $group);
		}

		return $ret;
	}

	/**
	 * Get stored cached data by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  mixed  Boolean false on no result, cached object otherwise
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group = null)
	{
		$data = $this->cache->get($id, $group);

		if ($data === false)
		{
			$locktest = $this->cache->lock($id, $group);

			// If locklooped is true try to get the cached data again; it could exist now.
			if ($locktest->locked === true && $locktest->locklooped === true)
			{
				$data = $this->cache->get($id, $group);
			}

			if ($locktest->locked === true)
			{
				$this->cache->unlock($id, $group);
			}
		}

		// Check again because we might get it from second attempt
		if ($data !== false)
		{
			// Trim to fix unserialize errors
			$data = unserialize(trim($data));
		}

		return $data;
	}

	/**
	 * Store data to cache by ID and group
	 *
	 * @param   mixed    $data        The data to store
	 * @param   string   $id          The cache data ID
	 * @param   string   $group       The cache data group
	 * @param   boolean  $wrkarounds  True to use wrkarounds
	 *
	 * @return  boolean  True if cache stored
	 *
	 * @since   1.7.0
	 */
	public function store($data, $id, $group = null, $wrkarounds = true)
	{
		$locktest = $this->cache->lock($id, $group);

		if ($locktest->locked === false && $locktest->locklooped === true)
		{
			// We can not store data because another process is in the middle of saving
			return false;
		}

		$result = $this->cache->store(serialize($data), $id, $group);

		if ($locktest->locked === true)
		{
			$this->cache->unlock($id, $group);
		}

		return $result;
	}
}
src/Cache/Controller/ViewController.php000064400000006431152177723700014167 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;

/**
 * Joomla! Cache view type object
 *
 * @since  1.7.0
 */
class ViewController extends CacheController
{
	/**
	 * Get the cached view data
	 *
	 * @param   object   $view        The view object to cache output for
	 * @param   string   $method      The method name of the view method to cache output for
	 * @param   mixed    $id          The cache data ID
	 * @param   boolean  $wrkarounds  True to enable workarounds.
	 *
	 * @return  boolean  True if the cache is hit (false else)
	 *
	 * @since   1.7.0
	 */
	public function get($view, $method = 'display', $id = false, $wrkarounds = true)
	{
		// If an id is not given generate it from the request
		if (!$id)
		{
			$id = $this->_makeId($view, $method);
		}

		$data = $this->cache->get($id);

		$locktest = (object) array('locked' => null, 'locklooped' => null);

		if ($data === false)
		{
			$locktest = $this->cache->lock($id);

			/*
			 * If the loop is completed and returned true it means the lock has been set.
			 * If looped is true try to get the cached data again; it could exist now.
			 */
			if ($locktest->locked === true && $locktest->locklooped === true)
			{
				$data = $this->cache->get($id);
			}

			// False means that locking is either turned off or maxtime has been exceeded. Execute the view.
		}

		if ($data !== false)
		{
			if ($locktest->locked === true)
			{
				$this->cache->unlock($id);
			}

			$data = unserialize(trim($data));

			if ($wrkarounds)
			{
				echo Cache::getWorkarounds($data);
			}
			else
			{
				// No workarounds, so all data is stored in one piece
				echo $data;
			}

			return true;
		}

		// No hit so we have to execute the view
		if (!method_exists($view, $method))
		{
			return false;
		}

		if ($locktest->locked === false && $locktest->locklooped === true)
		{
			// We can not store data because another process is in the middle of saving
			$view->$method();

			return false;
		}

		// Capture and echo output
		ob_start();
		ob_implicit_flush(false);
		$view->$method();
		$data = ob_get_clean();
		echo $data;

		/*
		 * For a view we have a special case.  We need to cache not only the output from the view, but the state
		 * of the document head after the view has been rendered.  This will allow us to properly cache any attached
		 * scripts or stylesheets or links or any other modifications that the view has made to the document object
		 */
		if ($wrkarounds)
		{
			$data = Cache::setWorkarounds($data);
		}

		// Store the cache data
		$this->cache->store(serialize($data), $id);

		if ($locktest->locked === true)
		{
			$this->cache->unlock($id);
		}

		return false;
	}

	/**
	 * Generate a view cache ID.
	 *
	 * @param   object  $view    The view object to cache output for
	 * @param   string  $method  The method name to cache for the view object
	 *
	 * @return  string  MD5 Hash
	 *
	 * @since   1.7.0
	 */
	protected function _makeId($view, $method)
	{
		return md5(serialize(array(Cache::makeId(), get_class($view), $method)));
	}
}
src/Cache/Controller/PageController.php000064400000011103152177723700014121 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;

/**
 * Joomla! Cache page type object
 *
 * @since  1.7.0
 */
class PageController extends CacheController
{
	/**
	 * ID property for the cache page object.
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $_id;

	/**
	 * Cache group
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_group;

	/**
	 * Cache lock test
	 *
	 * @var    \stdClass
	 * @since  1.7.0
	 */
	protected $_locktest = null;

	/**
	 * Get the cached page data
	 *
	 * @param   boolean  $id     The cache data ID
	 * @param   string   $group  The cache data group
	 *
	 * @return  mixed  Boolean false on no result, cached object otherwise
	 *
	 * @since   1.7.0
	 */
	public function get($id = false, $group = 'page')
	{
		// If an id is not given, generate it from the request
		if (!$id)
		{
			$id = $this->_makeId();
		}

		// If the etag matches the page id ... set a no change header and exit : utilize browser cache
		if (!headers_sent() && isset($_SERVER['HTTP_IF_NONE_MATCH']))
		{
			$etag = stripslashes($_SERVER['HTTP_IF_NONE_MATCH']);

			if ($etag == $id)
			{
				$browserCache = isset($this->options['browsercache']) ? $this->options['browsercache'] : false;

				if ($browserCache)
				{
					$this->_noChange();
				}
			}
		}

		// We got a cache hit... set the etag header and echo the page data
		$data = $this->cache->get($id, $group);

		$this->_locktest = (object) array('locked' => null, 'locklooped' => null);

		if ($data === false)
		{
			$this->_locktest = $this->cache->lock($id, $group);

			// If locklooped is true try to get the cached data again; it could exist now.
			if ($this->_locktest->locked === true && $this->_locktest->locklooped === true)
			{
				$data = $this->cache->get($id, $group);
			}
		}

		if ($data !== false)
		{
			if ($this->_locktest->locked === true)
			{
				$this->cache->unlock($id, $group);
			}

			$data = unserialize(trim($data));
			$data = Cache::getWorkarounds($data);

			$this->_setEtag($id);

			return $data;
		}

		// Set ID and group placeholders
		$this->_id    = $id;
		$this->_group = $group;

		return false;
	}

	/**
	 * Stop the cache buffer and store the cached data
	 *
	 * @param   mixed    $data        The data to store
	 * @param   string   $id          The cache data ID
	 * @param   string   $group       The cache data group
	 * @param   boolean  $wrkarounds  True to use wrkarounds
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($data, $id, $group = null, $wrkarounds = true)
	{
		if ($this->_locktest->locked === false && $this->_locktest->locklooped === true)
		{
			// We can not store data because another process is in the middle of saving
			return false;
		}

		// Get page data from the application object
		if (!$data)
		{
			$data = \JFactory::getApplication()->getBody();

			// Only attempt to store if page data exists.
			if (!$data)
			{
				return false;
			}
		}

		// Get id and group and reset the placeholders
		if (!$id)
		{
			$id = $this->_id;
		}

		if (!$group)
		{
			$group = $this->_group;
		}

		if ($wrkarounds)
		{
			$data = Cache::setWorkarounds(
				$data,
				array(
					'nopathway' => 1,
					'nohead'    => 1,
					'nomodules' => 1,
					'headers'   => true,
				)
			);
		}

		$result = $this->cache->store(serialize($data), $id, $group);

		if ($this->_locktest->locked === true)
		{
			$this->cache->unlock($id, $group);
		}

		return $result;
	}

	/**
	 * Generate a page cache id
	 *
	 * @return  string  MD5 Hash
	 *
	 * @since   1.7.0
	 * @todo    Discuss whether this should be coupled to a data hash or a request hash ... perhaps hashed with a serialized request
	 */
	protected function _makeId()
	{
		return Cache::makeId();
	}

	/**
	 * There is no change in page data so send an unmodified header and die gracefully
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _noChange()
	{
		$app = \JFactory::getApplication();

		// Send not modified header and exit gracefully
		$app->setHeader('Status', 304, true);
		$app->sendHeaders();
		$app->close();
	}

	/**
	 * Set the ETag header in the response
	 *
	 * @param   string  $etag  The entity tag (etag) to set
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	protected function _setEtag($etag)
	{
		\JFactory::getApplication()->setHeader('ETag', '"' . $etag . '"', true);
	}
}
src/Cache/Controller/CallbackController.php000064400000013421152177723700014746 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Controller;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheController;

/**
 * Joomla! Cache callback type object
 *
 * @since  1.7.0
 */
class CallbackController extends CacheController
{
	/**
	 * Executes a cacheable callback if not found in cache else returns cached output and result
	 *
	 * Since arguments to this function are read with func_get_args you can pass any number of arguments to this method
	 * as long as the first argument passed is the callback definition.
	 *
	 * The callback definition can be in several forms:
	 * - Standard PHP Callback array see <https://www.php.net/callback> [recommended]
	 * - Function name as a string eg. 'foo' for function foo()
	 * - Static method name as a string eg. 'MyClass::myMethod' for method myMethod() of class MyClass
	 *
	 * @return  mixed  Result of the callback
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public function call()
	{
		// Get callback and arguments
		$args     = func_get_args();
		$callback = array_shift($args);

		return $this->get($callback, $args);
	}

	/**
	 * Executes a cacheable callback if not found in cache else returns cached output and result
	 *
	 * @param   mixed    $callback    Callback or string shorthand for a callback
	 * @param   array    $args        Callback arguments
	 * @param   mixed    $id          Cache ID
	 * @param   boolean  $wrkarounds  True to use wrkarounds
	 * @param   array    $woptions    Workaround options
	 *
	 * @return  mixed  Result of the callback
	 *
	 * @since   1.7.0
	 */
	public function get($callback, $args = array(), $id = false, $wrkarounds = false, $woptions = array())
	{
		// Normalize callback
		if (is_array($callback) || is_callable($callback))
		{
			// We have a standard php callback array -- do nothing
		}
		elseif (strstr($callback, '::'))
		{
			// This is shorthand for a static method callback classname::methodname
			list ($class, $method) = explode('::', $callback);
			$callback = array(trim($class), trim($method));
		}
		elseif (strstr($callback, '->'))
		{
			/*
			 * This is a really not so smart way of doing this... we provide this for backward compatability but this
			 * WILL! disappear in a future version.  If you are using this syntax change your code to use the standard
			 * PHP callback array syntax: <https://www.php.net/callback>
			 *
			 * We have to use some silly global notation to pull it off and this is very unreliable
			 */
			list ($object_123456789, $method) = explode('->', $callback);
			global $$object_123456789;
			$callback = array($$object_123456789, $method);
		}

		if (!$id)
		{
			// Generate an ID
			$id = $this->_makeId($callback, $args);
		}

		$data = $this->cache->get($id);

		$locktest = (object) array('locked' => null, 'locklooped' => null);

		if ($data === false)
		{
			$locktest = $this->cache->lock($id);

			// If locklooped is true try to get the cached data again; it could exist now.
			if ($locktest->locked === true && $locktest->locklooped === true)
			{
				$data = $this->cache->get($id);
			}
		}

		if ($data !== false)
		{
			if ($locktest->locked === true)
			{
				$this->cache->unlock($id);
			}

			$data = unserialize(trim($data));

			if ($wrkarounds)
			{
				echo Cache::getWorkarounds(
					$data['output'],
					array('mergehead' => isset($woptions['mergehead']) ? $woptions['mergehead'] : 0)
				);
			}
			else
			{
				echo $data['output'];
			}

			return $data['result'];
		}

		if (!is_array($args))
		{
			$referenceArgs = !empty($args) ? array(&$args) : array();
		}
		else
		{
			$referenceArgs = &$args;
		}

		if ($locktest->locked === false && $locktest->locklooped === true)
		{
			// We can not store data because another process is in the middle of saving
			return call_user_func_array($callback, $referenceArgs);
		}

		$coptions = array();

		if (isset($woptions['modulemode']) && $woptions['modulemode'] == 1)
		{
			$document = \JFactory::getDocument();

			if (method_exists($document, 'getHeadData'))
			{
				$coptions['headerbefore'] = $document->getHeadData();
			}

			$coptions['modulemode'] = 1;
		}
		else
		{
			$coptions['modulemode'] = 0;
		}

		$coptions['nopathway'] = isset($woptions['nopathway']) ? $woptions['nopathway'] : 1;
		$coptions['nohead']    = isset($woptions['nohead'])    ? $woptions['nohead'] : 1;
		$coptions['nomodules'] = isset($woptions['nomodules']) ? $woptions['nomodules'] : 1;

		ob_start();
		ob_implicit_flush(false);

		$result = call_user_func_array($callback, $referenceArgs);
		$output = ob_get_clean();

		$data = array('result' => $result);

		if ($wrkarounds)
		{
			$data['output'] = Cache::setWorkarounds($output, $coptions);
		}
		else
		{
			$data['output'] = $output;
		}

		// Store the cache data
		$this->cache->store(serialize($data), $id);

		if ($locktest->locked === true)
		{
			$this->cache->unlock($id);
		}

		echo $output;

		return $result;
	}

	/**
	 * Generate a callback cache ID
	 *
	 * @param   callback  $callback  Callback to cache
	 * @param   array     $args      Arguments to the callback method to cache
	 *
	 * @return  string  MD5 Hash
	 *
	 * @since   1.7.0
	 */
	protected function _makeId($callback, $args)
	{
		if (is_array($callback) && is_object($callback[0]))
		{
			$vars        = get_object_vars($callback[0]);
			$vars[]      = strtolower(get_class($callback[0]));
			$callback[0] = $vars;
		}

		// A Closure can't be serialized, so to generate the ID we'll need to get its hash
		if (is_a($callback, 'closure'))
		{
			$hash = spl_object_hash($callback);

			return md5($hash . serialize(array($args)));
		}

		return md5(serialize(array($callback, $args)));
	}
}
src/Cache/Storage/WincacheStorage.php000064400000010221152177723700013530 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;

/**
 * WinCache cache storage handler
 *
 * @link   https://www.php.net/manual/en/book.wincache.php
 * @since  1.7.0
 */
class WincacheStorage extends CacheStorage
{
	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return wincache_ucache_exists($this->_getCacheId($id, $group));
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		return wincache_ucache_get($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		$allinfo = wincache_ucache_info();
		$keys    = $allinfo['ucache_entries'];
		$secret  = $this->_hash;
		$data    = array();

		foreach ($keys as $key)
		{
			$name    = $key['key_name'];
			$namearr = explode('-', $name);

			if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
			{
				$group = $namearr[2];

				if (!isset($data[$group]))
				{
					$item = new CacheStorageHelper($group);
				}
				else
				{
					$item = $data[$group];
				}

				if (isset($key['value_size']))
				{
					$item->updateSize($key['value_size']);
				}
				else
				{
					// Dummy, WINCACHE version is too low.
					$item->updateSize(1);
				}

				$data[$group] = $item;
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		return wincache_ucache_set($this->_getCacheId($id, $group), $data, $this->_lifetime);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		return wincache_ucache_delete($this->_getCacheId($id, $group));
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		$allinfo = wincache_ucache_info();
		$keys    = $allinfo['ucache_entries'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (strpos($key['key_name'], $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
			{
				wincache_ucache_delete($key['key_name']);
			}
		}

		return true;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		$allinfo = wincache_ucache_info();
		$keys    = $allinfo['ucache_entries'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (strpos($key['key_name'], $secret . '-cache-'))
			{
				wincache_ucache_get($key['key_name']);
			}
		}

		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('wincache') && function_exists('wincache_ucache_get') && !strcmp(ini_get('wincache.ucenabled'), '1');
	}
}
src/Cache/Storage/ApcStorage.php000064400000015415152177723700012524 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;

/**
 * APC cache storage handler
 *
 * @link        https://www.php.net/manual/en/book.apc.php
 * @since       1.7.0
 * @deprecated  4.0  Use the APCu handler instead
 */
class ApcStorage extends CacheStorage
{
	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return apc_exists($this->_getCacheId($id, $group));
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		return apc_fetch($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		$allinfo = apc_cache_info('user');
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		$data = array();

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
				$name = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
				$name = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$name = $key['key'];
			}

			$namearr = explode('-', $name);

			if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
			{
				$group = $namearr[2];

				if (!isset($data[$group]))
				{
					$item = new CacheStorageHelper($group);
				}
				else
				{
					$item = $data[$group];
				}

				$item->updateSize($key['mem_size']);

				$data[$group] = $item;
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		return apc_store($this->_getCacheId($id, $group), $data, $this->_lifetime);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		return apc_delete($this->_getCacheId($id, $group));
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		$allinfo = apc_cache_info('user');
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
				$internalKey = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
				$internalKey = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$internalKey = $key['key'];
			}

			if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
			{
				apc_delete($internalKey);
			}
		}

		return true;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		$allinfo = apc_cache_info('user');
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
				$internalKey = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
				$internalKey = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$internalKey = $key['key'];
			}

			if (strpos($internalKey, $secret . '-cache-'))
			{
				apc_fetch($internalKey);
			}
		}
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		$supported = extension_loaded('apc') && ini_get('apc.enabled');

		// If on the CLI interface, the `apc.enable_cli` option must also be enabled
		if ($supported && php_sapi_name() === 'cli')
		{
			$supported = ini_get('apc.enable_cli');
		}

		return (bool) $supported;
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   1.7.0
	 */
	public function lock($id, $group, $locktime)
	{
		$returning             = new \stdClass;
		$returning->locklooped = false;

		$looptime = $locktime * 10;

		$cache_id = $this->_getCacheId($id, $group) . '_lock';

		$data_lock = apc_add($cache_id, 1, $locktime);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released. That implies that data get from other thread has finished
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					$returning->locked     = false;
					$returning->locklooped = true;
					break;
				}

				usleep(100);
				$data_lock = apc_add($cache_id, 1, $locktime);
				$lock_counter++;
			}
		}

		$returning->locked = $data_lock;

		return $returning;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function unlock($id, $group = null)
	{
		return apc_delete($this->_getCacheId($id, $group) . '_lock');
	}
}
src/Cache/Storage/CacheliteStorage.php000064400000017161152177723700013702 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;

/**
 * Cache lite storage handler
 *
 * @link   http://pear.php.net/package/Cache_Lite/
 * @since  1.7.0
 * @deprecated  4.0 Deprecated without replacement
 */
class CacheliteStorage extends CacheStorage
{
	/**
	 * Singleton Cache_Lite instance
	 *
	 * @var    \Cache_Lite
	 * @since  1.7.0
	 */
	protected static $CacheLiteInstance = null;

	/**
	 * Root path
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_root;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		$this->_root = $options['cachebase'];

		$cloptions = array(
			'cacheDir'                => $this->_root . '/',
			'lifeTime'                => $this->_lifetime,
			'fileLocking'             => $this->_locking,
			'automaticCleaningFactor' => isset($options['autoclean']) ? $options['autoclean'] : 200,
			'fileNameProtection'      => false,
			'hashedDirectoryLevel'    => 0,
			'caching'                 => $options['caching'],
		);

		if (static::$CacheLiteInstance === null)
		{
			$this->initCache($cloptions);
		}
	}

	/**
	 * Instantiates the Cache_Lite object. Only initializes the engine if it does not already exist.
	 *
	 * @param   array  $cloptions  optional parameters
	 *
	 * @return  \Cache_Lite
	 *
	 * @since   1.7.0
	 */
	protected function initCache($cloptions)
	{
		if (!class_exists('\\Cache_Lite'))
		{
			require_once 'Cache/Lite.php';
		}

		static::$CacheLiteInstance = new \Cache_Lite($cloptions);

		return static::$CacheLiteInstance;
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return $this->get($id, $group) !== false;
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		static::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');

		// This call is needed to ensure $this->rawname is set
		$this->_getCacheId($id, $group);

		return static::$CacheLiteInstance->get($this->rawname, $group);
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		$path    = $this->_root;
		$folders = new \DirectoryIterator($path);
		$data    = array();

		foreach ($folders as $folder)
		{
			if (!$folder->isDir() || $folder->isDot())
			{
				continue;
			}

			$foldername = $folder->getFilename();

			$files = new \DirectoryIterator($path . '/' . $foldername);
			$item  = new CacheStorageHelper($foldername);

			foreach ($files as $file)
			{
				if (!$file->isFile())
				{
					continue;
				}

				$filename = $file->getFilename();

				$item->updateSize(filesize($path . '/' . $foldername . '/' . $filename));
			}

			$data[$foldername] = $item;
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		$dir = $this->_root . '/' . $group;

		// If the folder doesn't exist try to create it
		if (!is_dir($dir))
		{
			// Make sure the index file is there
			$indexFile = $dir . '/index.html';
			@mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
		}

		// Make sure the folder exists
		if (!is_dir($dir))
		{
			return false;
		}

		static::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');

		// This call is needed to ensure $this->rawname is set
		$this->_getCacheId($id, $group);

		return static::$CacheLiteInstance->save($data, $this->rawname, $group);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		static::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');

		// This call is needed to ensure $this->rawname is set
		$this->_getCacheId($id, $group);

		return static::$CacheLiteInstance->remove($this->rawname, $group);
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		\JLoader::import('joomla.filesystem.folder');
		\JLoader::import('joomla.filesystem.file');

		switch ($mode)
		{
			case 'notgroup':
				$clmode  = 'notingroup';
				$success = static::$CacheLiteInstance->clean($group, $clmode);
				break;

			case 'group':
				if (is_dir($this->_root . '/' . $group))
				{
					$clmode = $group;
					static::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
					$success = static::$CacheLiteInstance->clean($group, $clmode);

					// Remove sub-folders of folder; disable all filtering
					$folders = \JFolder::folders($this->_root . '/' . $group, '.', false, true, array(), array());

					foreach ($folders as $folder)
					{
						if (is_link($folder))
						{
							if (\JFile::delete($folder) !== true)
							{
								return false;
							}
						}
						elseif (\JFolder::delete($folder) !== true)
						{
							return false;
						}
					}
				}
				else
				{
					$success = true;
				}

				break;

			default:
				if (is_dir($this->_root . '/' . $group))
				{
					$clmode = $group;
					static::$CacheLiteInstance->setOption('cacheDir', $this->_root . '/' . $group . '/');
					$success = static::$CacheLiteInstance->clean($group, $clmode);
				}
				else
				{
					$success = true;
				}

				break;
		}

		return $success;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		$result = true;
		static::$CacheLiteInstance->setOption('automaticCleaningFactor', 1);
		static::$CacheLiteInstance->setOption('hashedDirectoryLevel', 1);
		$success1 = static::$CacheLiteInstance->_cleanDir($this->_root . '/', false, 'old');

		if (!($dh = opendir($this->_root . '/')))
		{
			return false;
		}

		while ($file = readdir($dh))
		{
			if (($file != '.') && ($file != '..') && ($file != '.svn'))
			{
				$file2 = $this->_root . '/' . $file;

				if (is_dir($file2))
				{
					$result = ($result && (static::$CacheLiteInstance->_cleanDir($file2 . '/', false, 'old')));
				}
			}
		}

		$success = ($success1 && $result);

		return $success;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		@include_once 'Cache/Lite.php';

		return class_exists('\Cache_Lite');
	}
}
src/Cache/Storage/ApcuStorage.php000064400000015462152177723700012713 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;

/**
 * APCu cache storage handler
 *
 * @link   https://www.php.net/manual/en/ref.apcu.php
 * @since  3.5
 */
class ApcuStorage extends CacheStorage
{
	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return apcu_exists($this->_getCacheId($id, $group));
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.5
	 */
	public function get($id, $group, $checkTime = true)
	{
		return apcu_fetch($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.5
	 */
	public function getAll()
	{
		$allinfo = apcu_cache_info();
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		$data = array();

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// The internal key name changed with APCu 4.0.7 from key to info
				$name = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APCu modules changed the internal key name from key to entry_name
				$name = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$name = $key['key'];
			}

			$namearr = explode('-', $name);

			if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
			{
				$group = $namearr[2];

				if (!isset($data[$group]))
				{
					$item = new CacheStorageHelper($group);
				}
				else
				{
					$item = $data[$group];
				}

				$item->updateSize($key['mem_size']);

				$data[$group] = $item;
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function store($id, $group, $data)
	{
		return apcu_store($this->_getCacheId($id, $group), $data, $this->_lifetime);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function remove($id, $group)
	{
		$cache_id = $this->_getCacheId($id, $group);

		// The apcu_delete function returns false if the ID does not exist
		if (apcu_exists($cache_id))
		{
			return apcu_delete($cache_id);
		}

		return true;
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function clean($group, $mode = null)
	{
		$allinfo = apcu_cache_info();
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// The internal key name changed with APCu 4.0.7 from key to info
				$internalKey = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APCu modules changed the internal key name from key to entry_name
				$internalKey = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$internalKey = $key['key'];
			}

			if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
			{
				apcu_delete($internalKey);
			}
		}

		return true;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function gc()
	{
		$allinfo = apcu_cache_info();
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (isset($key['info']))
			{
				// The internal key name changed with APCu 4.0.7 from key to info
				$internalKey = $key['info'];
			}
			elseif (isset($key['entry_name']))
			{
				// Some APCu modules changed the internal key name from key to entry_name
				$internalKey = $key['entry_name'];
			}
			else
			{
				// A fall back for the old internal key name
				$internalKey = $key['key'];
			}

			if (strpos($internalKey, $secret . '-cache-'))
			{
				apcu_fetch($internalKey);
			}
		}

		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public static function isSupported()
	{
		$supported = extension_loaded('apcu') && ini_get('apc.enabled');

		// If on the CLI interface, the `apc.enable_cli` option must also be enabled
		if ($supported && php_sapi_name() === 'cli')
		{
			$supported = ini_get('apc.enable_cli');
		}

		return (bool) $supported;
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   3.5
	 */
	public function lock($id, $group, $locktime)
	{
		$returning = new \stdClass;
		$returning->locklooped = false;

		$looptime = $locktime * 10;

		$cache_id = $this->_getCacheId($id, $group) . '_lock';

		$data_lock = apcu_add($cache_id, 1, $locktime);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.
			// That implies that data get from other thread has finished
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					$returning->locked = false;
					$returning->locklooped = true;
					break;
				}

				usleep(100);
				$data_lock = apcu_add($cache_id, 1, $locktime);
				$lock_counter++;
			}
		}

		$returning->locked = $data_lock;

		return $returning;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.5
	 */
	public function unlock($id, $group = null)
	{
		$cache_id = $this->_getCacheId($id, $group) . '_lock';

		// The apcu_delete function returns false if the ID does not exist
		if (apcu_exists($cache_id))
		{
			return apcu_delete($cache_id);
		}

		return true;
	}
}
src/Cache/Storage/RedisStorage.php000064400000016721152177723700013070 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Log\Log;

/**
 * Redis cache storage handler for PECL
 *
 * @since  3.4
 */
class RedisStorage extends CacheStorage
{
	/**
	 * Redis connection object
	 *
	 * @var    \Redis
	 * @since  3.4
	 */
	protected static $_redis = null;

	/**
	 * Persistent session flag
	 *
	 * @var    boolean
	 * @since  3.4
	 */
	protected $_persistent = false;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   3.4
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		if (static::$_redis === null)
		{
			$this->getConnection();
		}
	}

	/**
	 * Create the Redis connection
	 *
	 * @return  \Redis|boolean  Redis connection object on success, boolean on failure
	 *
	 * @since   3.4
	 * @note    As of 4.0 this method will throw a JCacheExceptionConnecting object on connection failure
	 */
	protected function getConnection()
	{
		if (static::isSupported() == false)
		{
			return false;
		}

		$config = \JFactory::getConfig();

		$this->_persistent = $config->get('redis_persist', true);

		$server = array(
			'host' => $config->get('redis_server_host', 'localhost'),
			'port' => $config->get('redis_server_port', 6379),
			'auth' => $config->get('redis_server_auth', null),
			'db'   => (int) $config->get('redis_server_db', null),
		);

		// If you are trying to connect to a socket file, ignore the supplied port
		if ($server['host'][0] === '/')
		{
			$server['port'] = 0;
		}

		static::$_redis = new \Redis;

		try
		{
			if ($this->_persistent)
			{
				$connection = static::$_redis->pconnect($server['host'], $server['port']);
			}
			else
			{
				$connection = static::$_redis->connect($server['host'], $server['port']);
			}
		}
		catch (\RedisException $e)
		{
			Log::add($e->getMessage(), Log::DEBUG);
		}

		if ($connection == false)
		{
			static::$_redis = null;

			// Because the application instance may not be available on cli script, use it only if needed
			if (\JFactory::getApplication()->isClient('administrator'))
			{
				\JError::raiseWarning(500, 'Redis connection failed');
			}

			return false;
		}

		try
		{
			$auth = $server['auth'] ? static::$_redis->auth($server['auth']) : true;
		}
		catch (\RedisException $e)
		{
			$auth = false;
			Log::add($e->getMessage(), Log::DEBUG);
		}

		if ($auth === false)
		{
			static::$_redis = null;

			// Because the application instance may not be available on cli script, use it only if needed
			if (\JFactory::getApplication()->isClient('administrator'))
			{
				\JError::raiseWarning(500, 'Redis authentication failed');
			}

			return false;
		}

		$select = static::$_redis->select($server['db']);

		if ($select == false)
		{
			static::$_redis = null;

			// Because the application instance may not be available on cli script, use it only if needed
			if (\JFactory::getApplication()->isClient('administrator'))
			{
				\JError::raiseWarning(500, 'Redis failed to select database');
			}

			return false;
		}

		try
		{
			static::$_redis->ping();
		}
		catch (\RedisException $e)
		{
			static::$_redis = null;

			// Because the application instance may not be available on cli script, use it only if needed
			if (\JFactory::getApplication()->isClient('administrator'))
			{
				\JError::raiseWarning(500, 'Redis ping failed');
			}

			return false;
		}

		return static::$_redis;
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		// Redis exists returns integer values lets convert that to boolean see: https://redis.io/commands/exists
		return (bool) static::$_redis->exists($this->_getCacheId($id, $group));
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.4
	 */
	public function get($id, $group, $checkTime = true)
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		return static::$_redis->get($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.4
	 */
	public function getAll()
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		$allKeys = static::$_redis->keys('*');
		$data    = array();
		$secret  = $this->_hash;

		if (!empty($allKeys))
		{
			foreach ($allKeys as $key)
			{
				$namearr = explode('-', $key);

				if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
				{
					$group = $namearr[2];

					if (!isset($data[$group]))
					{
						$item = new CacheStorageHelper($group);
					}
					else
					{
						$item = $data[$group];
					}

					$item->updateSize(strlen($key)*8);
					$data[$group] = $item;
				}
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public function store($id, $group, $data)
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		static::$_redis->setex($this->_getCacheId($id, $group), $this->_lifetime, $data);

		return true;
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public function remove($id, $group)
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		return (bool) static::$_redis->delete($this->_getCacheId($id, $group));
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public function clean($group, $mode = null)
	{
		if (static::isConnected() == false)
		{
			return false;
		}

		$allKeys = static::$_redis->keys('*');

		if ($allKeys === false)
		{
			$allKeys = array();
		}

		$secret = $this->_hash;

		foreach ($allKeys as $key)
		{
			if (strpos($key, $secret . '-cache-' . $group . '-') === 0 && $mode == 'group')
			{
				static::$_redis->delete($key);
			}

			if (strpos($key, $secret . '-cache-' . $group . '-') !== 0 && $mode != 'group')
			{
				static::$_redis->delete($key);
			}
		}

		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public static function isSupported()
	{
		return class_exists('\\Redis');
	}

	/**
	 * Test to see if the Redis connection is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.4
	 */
	public static function isConnected()
	{
		return static::$_redis instanceof \Redis;
	}
}
src/Cache/Storage/CacheStorageHelper.php000064400000002027152177723700014157 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

/**
 * Cache storage helper functions.
 *
 * @since  1.7.0
 */
class CacheStorageHelper
{
	/**
	 * Cache data group
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $group = '';

	/**
	 * Cached item size
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $size = 0;

	/**
	 * Counter
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $count = 0;

	/**
	 * Constructor
	 *
	 * @param   string  $group  The cache data group
	 *
	 * @since   1.7.0
	 */
	public function __construct($group)
	{
		$this->group = $group;
	}

	/**
	 * Increase cache items count.
	 *
	 * @param   string  $size  Cached item size
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function updateSize($size)
	{
		$this->size += $size;
		$this->count++;
	}
}
src/Cache/Storage/FileStorage.php000064400000042040152177723700012672 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Log\Log;

/**
 * File cache storage handler
 *
 * @since  1.7.0
 * @note   For performance reasons this class does not use the Filesystem package's API
 */
class FileStorage extends CacheStorage
{
	/**
	 * Root path
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $_root;

	/**
	 * Locked resources
	 *
	 * @var    array
	 * @since  3.7.0
	 *
	 */
	protected $_locked_files = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);
		$this->_root = $options['cachebase'];

		// Workaround for php 5.3
		$locked_files = &$this->_locked_files;

		// Remove empty locked files at script shutdown.
		$clearAtShutdown = function () use (&$locked_files)
		{
			foreach ($locked_files as $path => $handle)
			{
				if (is_resource($handle))
				{
					@flock($handle, LOCK_UN);
					@fclose($handle);
				}

				// Delete only the existing file if it is empty.
				if (@filesize($path) === 0)
				{
					@unlink($path);
				}

				unset($locked_files[$path]);
			}
		};

		register_shutdown_function($clearAtShutdown);
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return $this->_checkExpire($id, $group);
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		$path  = $this->_getFilePath($id, $group);
		$close = false;

		if ($checkTime == false || ($checkTime == true && $this->_checkExpire($id, $group) === true))
		{
			if (file_exists($path))
			{
				if (isset($this->_locked_files[$path]))
				{
					$_fileopen = $this->_locked_files[$path];
				}
				else
				{
					$_fileopen = @fopen($path, 'rb');

					// There is no lock, we have to close file after store data
					$close = true;
				}

				if ($_fileopen)
				{
					// On Windows system we can not use file_get_contents on the file locked by yourself
					$data = stream_get_contents($_fileopen);

					if ($close)
					{
						@fclose($_fileopen);
					}

					if ($data !== false)
					{
						// Remove the initial die() statement
						return str_replace('<?php die("Access Denied"); ?>#x#', '', $data);
					}
				}
			}
		}

		return false;
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		$path    = $this->_root;
		$folders = $this->_folders($path);
		$data    = array();

		foreach ($folders as $folder)
		{
			$files = $this->_filesInFolder($path . '/' . $folder);
			$item  = new CacheStorageHelper($folder);

			foreach ($files as $file)
			{
				$item->updateSize(filesize($path . '/' . $folder . '/' . $file));
			}

			$data[$folder] = $item;
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		$path  = $this->_getFilePath($id, $group);
		$close = false;

		// Prepend a die string
		$data = '<?php die("Access Denied"); ?>#x#' . $data;

		if (isset($this->_locked_files[$path]))
		{
			$_fileopen = $this->_locked_files[$path];

			// Because lock method uses flag c+b we have to truncate it manually
			@ftruncate($_fileopen, 0);
		}
		else
		{
			$_fileopen = @fopen($path, 'wb');

			// There is no lock, we have to close file after store data
			$close = true;
		}

		if ($_fileopen)
		{
			$length = strlen($data);
			$result = @fwrite($_fileopen, $data, $length);

			if ($close)
			{
				@fclose($_fileopen);
			}

			return $result === $length;
		}

		return false;
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		$path = $this->_getFilePath($id, $group);

		if (!@unlink($path))
		{
			return false;
		}

		return true;
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		$return = true;
		$folder = $group;

		if (trim($folder) == '')
		{
			$mode = 'notgroup';
		}

		switch ($mode)
		{
			case 'notgroup' :
				$folders = $this->_folders($this->_root);

				for ($i = 0, $n = count($folders); $i < $n; $i++)
				{
					if ($folders[$i] != $folder)
					{
						$return |= $this->_deleteFolder($this->_root . '/' . $folders[$i]);
					}
				}

				break;

			case 'group' :
			default :
				if (is_dir($this->_root . '/' . $folder))
				{
					$return = $this->_deleteFolder($this->_root . '/' . $folder);
				}

				break;
		}

		return (bool) $return;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		$result = true;

		// Files older than lifeTime get deleted from cache
		$files = $this->_filesInFolder($this->_root, '', true, true, array('.svn', 'CVS', '.DS_Store', '__MACOSX', 'index.html'));

		foreach ($files as $file)
		{
			$time = @filemtime($file);

			if (($time + $this->_lifetime) < $this->_now || empty($time))
			{
				$result |= @unlink($file);
			}
		}

		return (bool) $result;
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   1.7.0
	 */
	public function lock($id, $group, $locktime)
	{
		$returning             = new \stdClass;
		$returning->locklooped = false;

		$looptime  = $locktime * 10;
		$path      = $this->_getFilePath($id, $group);
		$_fileopen = @fopen($path, 'c+b');

		if (!$_fileopen)
		{
			$returning->locked = false;

			return $returning;
		}

		$data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.
			// That implies that data get from other thread has finished
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					break;
				}

				usleep(100);
				$data_lock = (bool) @flock($_fileopen, LOCK_EX|LOCK_NB);
				$lock_counter++;
			}

			$returning->locklooped = true;
		}

		if ($data_lock === true)
		{
			// Remember resource, flock release lock if you unset/close resource
			$this->_locked_files[$path] = $_fileopen;
		}

		$returning->locked = $data_lock;

		return $returning;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function unlock($id, $group = null)
	{
		$path = $this->_getFilePath($id, $group);

		if (isset($this->_locked_files[$path]))
		{
			$ret = (bool) @flock($this->_locked_files[$path], LOCK_UN);
			@fclose($this->_locked_files[$path]);
			unset($this->_locked_files[$path]);

			return $ret;
		}

		return true;
	}

	/**
	 * Check if a cache object has expired
	 *
	 * Using @ error suppressor here because between if we did a file_exists() and then filemsize() there will
	 * be a little time space when another process can delete the file and then you get PHP Warning
	 *
	 * @param   string  $id     Cache ID to check
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean  True if the cache ID is valid
	 *
	 * @since   1.7.0
	 */
	protected function _checkExpire($id, $group)
	{
		$path = $this->_getFilePath($id, $group);

		// Check prune period
		if (file_exists($path))
		{
			$time = @filemtime($path);

			if (($time + $this->_lifetime) < $this->_now || empty($time))
			{
				@unlink($path);

				return false;
			}

			// If, right now, the file does not exist then return false
			if (@filesize($path) == 0)
			{
				return false;
			}

			return true;
		}

		return false;
	}

	/**
	 * Get a cache file path from an ID/group pair
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean|string  The path to the data object or boolean false if the cache directory does not exist
	 *
	 * @since   1.7.0
	 */
	protected function _getFilePath($id, $group)
	{
		$name = $this->_getCacheId($id, $group);
		$dir  = $this->_root . '/' . $group;

		// If the folder doesn't exist try to create it
		if (!is_dir($dir))
		{
			// Make sure the index file is there
			$indexFile = $dir . '/index.html';
			@mkdir($dir) && file_put_contents($indexFile, '<!DOCTYPE html><title></title>');
		}

		// Make sure the folder exists
		if (!is_dir($dir))
		{
			return false;
		}

		return $dir . '/' . $name . '.php';
	}

	/**
	 * Quickly delete a folder of files
	 *
	 * @param   string  $path  The path to the folder to delete.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	protected function _deleteFolder($path)
	{
		// Sanity check
		if (!$path || !is_dir($path) || empty($this->_root))
		{
			// Bad programmer! Bad, bad programmer!
			Log::add(__METHOD__ . ' ' . \JText::_('JLIB_FILESYSTEM_ERROR_DELETE_BASE_DIRECTORY'), Log::WARNING, 'jerror');

			return false;
		}

		$path = $this->_cleanPath($path);

		// Check to make sure path is inside cache folder, we do not want to delete Joomla root!
		$pos = strpos($path, $this->_cleanPath($this->_root));

		if ($pos === false || $pos > 0)
		{
			Log::add(__METHOD__ . ' ' . \JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Remove all the files in folder if they exist; disable all filtering
		$files = $this->_filesInFolder($path, '.', false, true, array(), array());

		if (!empty($files) && !is_array($files))
		{
			if (@unlink($files) !== true)
			{
				return false;
			}
		}
		elseif (!empty($files) && is_array($files))
		{
			foreach ($files as $file)
			{
				$file = $this->_cleanPath($file);

				// In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp
				if (@unlink($file) !== true)
				{
					Log::add(__METHOD__ . ' ' . \JText::sprintf('JLIB_FILESYSTEM_DELETE_FAILED', basename($file)), Log::WARNING, 'jerror');

					return false;
				}
			}
		}

		// Remove sub-folders of folder; disable all filtering
		$folders = $this->_folders($path, '.', false, true, array(), array());

		foreach ($folders as $folder)
		{
			if (is_link($folder))
			{
				// Don't descend into linked directories, just delete the link.
				if (@unlink($folder) !== true)
				{
					return false;
				}
			}
			elseif ($this->_deleteFolder($folder) !== true)
			{
				return false;
			}
		}

		// In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp
		if (@rmdir($path))
		{
			return true;
		}

		Log::add(__METHOD__ . ' ' . \JText::sprintf('JLIB_FILESYSTEM_ERROR_FOLDER_DELETE', $path), Log::WARNING, 'jerror');

		return false;
	}

	/**
	 * Function to strip additional / or \ in a path name
	 *
	 * @param   string  $path  The path to clean
	 * @param   string  $ds    Directory separator (optional)
	 *
	 * @return  string  The cleaned path
	 *
	 * @since   1.7.0
	 */
	protected function _cleanPath($path, $ds = DIRECTORY_SEPARATOR)
	{
		$path = trim($path);

		if (empty($path))
		{
			return $this->_root;
		}

		// Remove double slashes and backslahses and convert all slashes and backslashes to DIRECTORY_SEPARATOR
		$path = preg_replace('#[/\\\\]+#', $ds, $path);

		return $path;
	}

	/**
	 * Utility function to quickly read the files in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $fullpath       True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludefilter  Array of folder names to exclude
	 *
	 * @return  array  Files in the given folder.
	 *
	 * @since   1.7.0
	 */
	protected function _filesInFolder($path, $filter = '.', $recurse = false, $fullpath = false,
		$exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'), $excludefilter = array('^\..*', '.*~'))
	{
		$arr = array();

		// Check to make sure the path valid and clean
		$path = $this->_cleanPath($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			Log::add(__METHOD__ . ' ' . \JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Read the source directory.
		if (!($handle = @opendir($path)))
		{
			return $arr;
		}

		if (count($excludefilter))
		{
			$excludefilter = '/(' . implode('|', $excludefilter) . ')/';
		}
		else
		{
			$excludefilter = '';
		}

		while (($file = readdir($handle)) !== false)
		{
			if (($file != '.') && ($file != '..') && (!in_array($file, $exclude)) && (!$excludefilter || !preg_match($excludefilter, $file)))
			{
				$dir   = $path . '/' . $file;
				$isDir = is_dir($dir);

				if ($isDir)
				{
					if ($recurse)
					{
						if (is_int($recurse))
						{
							$arr2 = $this->_filesInFolder($dir, $filter, $recurse - 1, $fullpath);
						}
						else
						{
							$arr2 = $this->_filesInFolder($dir, $filter, $recurse, $fullpath);
						}

						$arr = array_merge($arr, $arr2);
					}
				}
				else
				{
					if (preg_match("/$filter/", $file))
					{
						if ($fullpath)
						{
							$arr[] = $path . '/' . $file;
						}
						else
						{
							$arr[] = $file;
						}
					}
				}
			}
		}

		closedir($handle);

		return $arr;
	}

	/**
	 * Utility function to read the folders in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for folder names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $fullpath       True to return the full path to the folders.
	 * @param   array    $exclude        Array with names of folders which should not be shown in the result.
	 * @param   array    $excludefilter  Array with regular expressions matching folders which should not be shown in the result.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @since   1.7.0
	 */
	protected function _folders($path, $filter = '.', $recurse = false, $fullpath = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludefilter = array('^\..*'))
	{
		$arr = array();

		// Check to make sure the path valid and clean
		$path = $this->_cleanPath($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			Log::add(__METHOD__ . ' ' . \JText::sprintf('JLIB_FILESYSTEM_ERROR_PATH_IS_NOT_A_FOLDER', $path), Log::WARNING, 'jerror');

			return false;
		}

		// Read the source directory
		if (!($handle = @opendir($path)))
		{
			return $arr;
		}

		if (count($excludefilter))
		{
			$excludefilter_string = '/(' . implode('|', $excludefilter) . ')/';
		}
		else
		{
			$excludefilter_string = '';
		}

		while (($file = readdir($handle)) !== false)
		{
			if (($file != '.') && ($file != '..')
				&& (!in_array($file, $exclude))
				&& (empty($excludefilter_string) || !preg_match($excludefilter_string, $file)))
			{
				$dir   = $path . '/' . $file;
				$isDir = is_dir($dir);

				if ($isDir)
				{
					// Removes filtered directories
					if (preg_match("/$filter/", $file))
					{
						if ($fullpath)
						{
							$arr[] = $dir;
						}
						else
						{
							$arr[] = $file;
						}
					}

					if ($recurse)
					{
						if (is_int($recurse))
						{
							$arr2 = $this->_folders($dir, $filter, $recurse - 1, $fullpath, $exclude, $excludefilter);
						}
						else
						{
							$arr2 = $this->_folders($dir, $filter, $recurse, $fullpath, $exclude, $excludefilter);
						}

						$arr = array_merge($arr, $arr2);
					}
				}
			}
		}

		closedir($handle);

		return $arr;
	}
}
src/Cache/Storage/MemcacheStorage.php000064400000022155152177723700013522 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Cache\Exception\CacheConnectingException;

/**
 * Memcache cache storage handler
 *
 * @link        https://www.php.net/manual/en/book.memcache.php
 * @since       1.7.0
 * @deprecated  4.0  Use the Memcached handler instead
 */
class MemcacheStorage extends CacheStorage
{
	/**
	 * Memcache connection object
	 *
	 * @var    \Memcache
	 * @since  1.7.0
	 */
	protected static $_db = null;

	/**
	 * Payload compression level
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	protected $_compress = 0;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		$this->_compress = \JFactory::getConfig()->get('memcache_compress', false) ? MEMCACHE_COMPRESSED : 0;

		if (static::$_db === null)
		{
			$this->getConnection();
		}
	}

	/**
	 * Create the Memcache connection
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	protected function getConnection()
	{
		if (!static::isSupported())
		{
			throw new \RuntimeException('Memcache Extension is not available');
		}

		$config = \JFactory::getConfig();

		$host = $config->get('memcache_server_host', 'localhost');
		$port = $config->get('memcache_server_port', 11211);

		// Create the memcache connection
		static::$_db = new \Memcache;

		if ($config->get('memcache_persist', true))
		{
			$result = @static::$_db->pconnect($host, $port);
		}
		else
		{
			$result = @static::$_db->connect($host, $port);
		}

		if (!$result)
		{
			// Null out the connection to inform the constructor it will need to attempt to connect if this class is instantiated again
			static::$_db = null;

			throw new CacheConnectingException('Could not connect to memcache server');
		}
	}

	/**
	 * Get a cache_id string from an id/group pair
	 *
	 * @param   string  $id     The cache data id
	 * @param   string  $group  The cache data group
	 *
	 * @return  string   The cache_id string
	 *
	 * @since   1.7.0
	 */
	protected function _getCacheId($id, $group)
	{
		$prefix   = Cache::getPlatformPrefix();
		$length   = strlen($prefix);
		$cache_id = parent::_getCacheId($id, $group);

		if ($length)
		{
			// Memcache use suffix instead of prefix
			$cache_id = substr($cache_id, $length) . strrev($prefix);
		}

		return $cache_id;
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return $this->get($id, $group) !== false;
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		return static::$_db->get($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		$keys   = static::$_db->get($this->_hash . '-index');
		$secret = $this->_hash;

		$data = array();

		if (is_array($keys))
		{
			foreach ($keys as $key)
			{
				if (empty($key))
				{
					continue;
				}

				$namearr = explode('-', $key->name);

				if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
				{
					$group = $namearr[2];

					if (!isset($data[$group]))
					{
						$item = new CacheStorageHelper($group);
					}
					else
					{
						$item = $data[$group];
					}

					$item->updateSize($key->size);

					$data[$group] = $item;
				}
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		$cache_id = $this->_getCacheId($id, $group);

		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (!is_array($index))
		{
			$index = array();
		}

		$tmparr       = new \stdClass;
		$tmparr->name = $cache_id;
		$tmparr->size = strlen($data);

		$index[] = $tmparr;
		static::$_db->set($this->_hash . '-index', $index, 0, 0);
		$this->unlockindex();

		static::$_db->set($cache_id, $data, $this->_compress, $this->_lifetime);

		return true;
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		$cache_id = $this->_getCacheId($id, $group);

		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (is_array($index))
		{
			foreach ($index as $key => $value)
			{
				if ($value->name == $cache_id)
				{
					unset($index[$key]);
					static::$_db->set($this->_hash . '-index', $index, 0, 0);
					break;
				}
			}
		}

		$this->unlockindex();

		return static::$_db->delete($cache_id);
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (is_array($index))
		{
			$prefix = $this->_hash . '-cache-' . $group . '-';

			foreach ($index as $key => $value)
			{
				if (strpos($value->name, $prefix) === 0 xor $mode != 'group')
				{
					static::$_db->delete($value->name);
					unset($index[$key]);
				}
			}

			static::$_db->set($this->_hash . '-index', $index, 0, 0);
		}

		$this->unlockindex();

		return true;
	}

	/**
	 * Flush all existing items in storage.
	 *
	 * @return  boolean
	 *
	 * @since   3.6.3
	 */
	public function flush()
	{
		if (!$this->lockindex())
		{
			return false;
		}

		return static::$_db->flush();
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('memcache') && class_exists('\\Memcache');
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   1.7.0
	 */
	public function lock($id, $group, $locktime)
	{
		$returning = new \stdClass;
		$returning->locklooped = false;

		$looptime = $locktime * 10;

		$cache_id = $this->_getCacheId($id, $group);

		$data_lock = static::$_db->add($cache_id . '_lock', 1, 0, $locktime);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.
			// That implies that data get from other thread has finished.
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					break;
				}

				usleep(100);
				$data_lock = static::$_db->add($cache_id . '_lock', 1, 0, $locktime);
				$lock_counter++;
			}

			$returning->locklooped = true;
		}

		$returning->locked = $data_lock;

		return $returning;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function unlock($id, $group = null)
	{
		$cache_id = $this->_getCacheId($id, $group) . '_lock';
		return static::$_db->delete($cache_id);
	}

	/**
	 * Lock cache index
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	protected function lockindex()
	{
		$looptime  = 300;
		$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 0, 30);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.  that implies that data get from other thread has finished
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					return false;
				}

				usleep(100);
				$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 0, 30);
				$lock_counter++;
			}
		}

		return true;
	}

	/**
	 * Unlock cache index
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	protected function unlockindex()
	{
		return static::$_db->delete($this->_hash . '-index_lock');
	}
}
src/Cache/Storage/XcacheStorage.php000064400000011745152177723700013216 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\CacheStorage;

/**
 * XCache cache storage handler
 *
 * @link        https://xcache.lighttpd.net/
 * @since       1.7.0
 * @deprecated  4.0  The XCache PHP extension is not compatible with PHP 7
 */
class XcacheStorage extends CacheStorage
{
	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return xcache_isset($this->_getCacheId($id, $group));
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		// Make sure XCache is configured properly
		if (static::isSupported() == false)
		{
			return false;
		}

		$cache_id = $this->_getCacheId($id, $group);
		$cache_content = xcache_get($cache_id);

		if ($cache_content === null)
		{
			return false;
		}

		return $cache_content;
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 * @note    This requires the php.ini setting xcache.admin.enable_auth = Off.
	 */
	public function getAll()
	{
		// Make sure XCache is configured properly
		if (static::isSupported() == false)
		{
			return array();
		}

		$allinfo = xcache_list(XC_TYPE_VAR, 0);
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		$data = array();

		foreach ($keys as $key)
		{
			$namearr = explode('-', $key['name']);

			if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
			{
				$group = $namearr[2];

				if (!isset($data[$group]))
				{
					$item = new CacheStorageHelper($group);
				}
				else
				{
					$item = $data[$group];
				}

				$item->updateSize($key['size']);

				$data[$group] = $item;
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		// Make sure XCache is configured properly
		if (static::isSupported() == false)
		{
			return false;
		}

		return xcache_set($this->_getCacheId($id, $group), $data, $this->_lifetime);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		// Make sure XCache is configured properly
		if (static::isSupported() == false)
		{
			return false;
		}

		$cache_id = $this->_getCacheId($id, $group);

		if (!xcache_isset($cache_id))
		{
			return true;
		}

		return xcache_unset($cache_id);
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		// Make sure XCache is configured properly
		if (static::isSupported() == false)
		{
			return true;
		}

		$allinfo = xcache_list(XC_TYPE_VAR, 0);
		$keys    = $allinfo['cache_list'];
		$secret  = $this->_hash;

		foreach ($keys as $key)
		{
			if (strpos($key['name'], $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
			{
				xcache_unset($key['name']);
			}
		}

		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		if (extension_loaded('xcache'))
		{
			// XCache Admin must be disabled for Joomla to use XCache
			$xcache_admin_enable_auth = ini_get('xcache.admin.enable_auth');

			// Some extensions ini variables are reported as strings
			if ($xcache_admin_enable_auth == 'Off')
			{
				return true;
			}

			// We require a string with contents 0, not a null value because it is not set since that then defaults to On/True
			if ($xcache_admin_enable_auth === '0')
			{
				return true;
			}

			// In some enviorments empty is equivalent to Off; See JC: #34044 && Github: #4083
			if ($xcache_admin_enable_auth === '')
			{
				return true;
			}
		}

		// If the settings are not correct, give up
		return false;
	}
}
src/Cache/Storage/MemcachedStorage.php000064400000023273152177723700013670 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Storage;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Cache\CacheStorage;
use Joomla\CMS\Cache\Exception\CacheConnectingException;

/**
 * Memcached cache storage handler
 *
 * @link   https://www.php.net/manual/en/book.memcached.php
 * @since  3.0.0
 */
class MemcachedStorage extends CacheStorage
{
	/**
	 * Memcached connection object
	 *
	 * @var    \Memcached
	 * @since  3.0.0
	 */
	protected static $_db = null;

	/**
	 * Payload compression level
	 *
	 * @var    integer
	 * @since  3.0.0
	 */
	protected $_compress = 0;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   3.0.0
	 */
	public function __construct($options = array())
	{
		parent::__construct($options);

		$this->_compress = \JFactory::getConfig()->get('memcached_compress', false) ? \Memcached::OPT_COMPRESSION : 0;

		if (static::$_db === null)
		{
			$this->getConnection();
		}
	}

	/**
	 * Create the Memcached connection
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  \RuntimeException
	 */
	protected function getConnection()
	{
		if (!static::isSupported())
		{
			throw new \RuntimeException('Memcached Extension is not available');
		}

		$config = \JFactory::getConfig();

		$host = $config->get('memcached_server_host', 'localhost');
		$port = $config->get('memcached_server_port', 11211);


		// Create the memcached connection
		if ($config->get('memcached_persist', true))
		{
			static::$_db = new \Memcached($this->_hash);
			$servers = static::$_db->getServerList();

			if ($servers && ($servers[0]['host'] != $host || $servers[0]['port'] != $port))
			{
				static::$_db->resetServerList();
				$servers = array();
			}

			if (!$servers)
			{
				static::$_db->addServer($host, $port);
			}
		}
		else
		{
			static::$_db = new \Memcached;
			static::$_db->addServer($host, $port);
		}

		static::$_db->setOption(\Memcached::OPT_COMPRESSION, $this->_compress);

		$stats  = static::$_db->getStats();
		$result = !empty($stats["$host:$port"]) && $stats["$host:$port"]['pid'] > 0;

		if (!$result)
		{
			// Null out the connection to inform the constructor it will need to attempt to connect if this class is instantiated again
			static::$_db = null;

			throw new CacheConnectingException('Could not connect to memcached server');
		}
	}

	/**
	 * Get a cache_id string from an id/group pair
	 *
	 * @param   string  $id     The cache data id
	 * @param   string  $group  The cache data group
	 *
	 * @return  string   The cache_id string
	 *
	 * @since   1.7.0
	 */
	protected function _getCacheId($id, $group)
	{
		$prefix   = Cache::getPlatformPrefix();
		$length   = strlen($prefix);
		$cache_id = parent::_getCacheId($id, $group);

		if ($length)
		{
			// Memcached use suffix instead of prefix
			$cache_id = substr($cache_id, $length) . strrev($prefix);
		}

		return $cache_id;
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		static::$_db->get($this->_getCacheId($id, $group));

		return static::$_db->getResultCode() !== \Memcached::RES_NOTFOUND;
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.0.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		return static::$_db->get($this->_getCacheId($id, $group));
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   3.0.0
	 */
	public function getAll()
	{
		$keys   = static::$_db->get($this->_hash . '-index');
		$secret = $this->_hash;

		$data = array();

		if (is_array($keys))
		{
			foreach ($keys as $key)
			{
				if (empty($key))
				{
					continue;
				}

				$namearr = explode('-', $key->name);

				if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
				{
					$group = $namearr[2];

					if (!isset($data[$group]))
					{
						$item = new CacheStorageHelper($group);
					}
					else
					{
						$item = $data[$group];
					}

					$item->updateSize($key->size);

					$data[$group] = $item;
				}
			}
		}

		return $data;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public function store($id, $group, $data)
	{
		$cache_id = $this->_getCacheId($id, $group);

		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (!is_array($index))
		{
			$index = array();
		}

		$tmparr       = new \stdClass;
		$tmparr->name = $cache_id;
		$tmparr->size = strlen($data);

		$index[] = $tmparr;
		static::$_db->set($this->_hash . '-index', $index, 0);
		$this->unlockindex();

		static::$_db->set($cache_id, $data, $this->_lifetime);

		return true;
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public function remove($id, $group)
	{
		$cache_id = $this->_getCacheId($id, $group);

		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (is_array($index))
		{
			foreach ($index as $key => $value)
			{
				if ($value->name == $cache_id)
				{
					unset($index[$key]);
					static::$_db->set($this->_hash . '-index', $index, 0);
					break;
				}
			}
		}

		$this->unlockindex();

		return static::$_db->delete($cache_id);
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public function clean($group, $mode = null)
	{
		if (!$this->lockindex())
		{
			return false;
		}

		$index = static::$_db->get($this->_hash . '-index');

		if (is_array($index))
		{
			$prefix = $this->_hash . '-cache-' . $group . '-';

			foreach ($index as $key => $value)
			{
				if (strpos($value->name, $prefix) === 0 xor $mode != 'group')
				{
					static::$_db->delete($value->name);
					unset($index[$key]);
				}
			}

			static::$_db->set($this->_hash . '-index', $index, 0);
		}

		$this->unlockindex();

		return true;
	}

	/**
	 * Flush all existing items in storage.
	 *
	 * @return  boolean
	 *
	 * @since   3.6.3
	 */
	public function flush()
	{
		if (!$this->lockindex())
		{
			return false;
		}

		return static::$_db->flush();
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		/*
		 * GAE and HHVM have both had instances where Memcached the class was defined but no extension was loaded.
		 * If the class is there, we can assume support.
		 */
		return class_exists('Memcached');
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   3.0.0
	 */
	public function lock($id, $group, $locktime)
	{
		$returning = new \stdClass;
		$returning->locklooped = false;

		$looptime = $locktime * 10;

		$cache_id = $this->_getCacheId($id, $group);

		$data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.
			// That implies that data get from other thread has finished.
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					break;
				}

				usleep(100);
				$data_lock = static::$_db->add($cache_id . '_lock', 1, $locktime);
				$lock_counter++;
			}

			$returning->locklooped = true;
		}

		$returning->locked = $data_lock;

		return $returning;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public function unlock($id, $group = null)
	{
		$cache_id = $this->_getCacheId($id, $group) . '_lock';
		return static::$_db->delete($cache_id);
	}

	/**
	 * Lock cache index
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	protected function lockindex()
	{
		$looptime  = 300;
		$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30);

		if ($data_lock === false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released.  that implies that data get from other thread has finished
			while ($data_lock === false)
			{
				if ($lock_counter > $looptime)
				{
					return false;
				}

				usleep(100);
				$data_lock = static::$_db->add($this->_hash . '-index_lock', 1, 30);
				$lock_counter++;
			}
		}

		return true;
	}

	/**
	 * Unlock cache index
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	protected function unlockindex()
	{
		return static::$_db->delete($this->_hash . '-index_lock');
	}
}
src/Cache/Cache.php000064400000044504152177723700010074 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

defined('JPATH_PLATFORM') or die;

use Joomla\Application\Web\WebClient;
use Joomla\CMS\Cache\Exception\CacheExceptionInterface;
use Joomla\String\StringHelper;

/**
 * Joomla! Cache base object
 *
 * @since  1.7.0
 */
class Cache
{
	/**
	 * Storage handler
	 *
	 * @var    CacheStorage[]
	 * @since  1.7.0
	 */
	public static $_handler = array();

	/**
	 * Cache options
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $_options;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Cache options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options)
	{
		$conf = \JFactory::getConfig();

		$this->_options = array(
			'cachebase'    => $conf->get('cache_path', JPATH_CACHE),
			'lifetime'     => (int) $conf->get('cachetime'),
			'language'     => $conf->get('language', 'en-GB'),
			'storage'      => $conf->get('cache_handler', ''),
			'defaultgroup' => 'default',
			'locking'      => true,
			'locktime'     => 15,
			'checkTime'    => true,
			'caching'      => ($conf->get('caching') >= 1) ? true : false,
		);

		// Overwrite default options with given options
		foreach ($options as $option => $value)
		{
			if (isset($options[$option]) && $options[$option] !== '')
			{
				$this->_options[$option] = $options[$option];
			}
		}

		if (empty($this->_options['storage']))
		{
			$this->setCaching(false);
		}
	}

	/**
	 * Returns a reference to a cache adapter object, always creating it
	 *
	 * @param   string  $type     The cache object type to instantiate
	 * @param   array   $options  The array of options
	 *
	 * @return  CacheController
	 *
	 * @since   1.7.0
	 */
	public static function getInstance($type = 'output', $options = array())
	{
		return CacheController::getInstance($type, $options);
	}

	/**
	 * Get the storage handlers
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public static function getStores()
	{
		$handlers = array();

		// Get an iterator and loop trough the driver classes.
		$iterator = new \DirectoryIterator(__DIR__ . '/Storage');

		/** @type  $file  \DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php' || $fileName == 'CacheStorageHelper.php')
			{
				continue;
			}

			// Derive the class name from the type.
			$class = str_ireplace('.php', '', __NAMESPACE__ . '\\Storage\\' . ucfirst(trim($fileName)));

			// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
			if (!class_exists($class))
			{
				continue;
			}

			// Sweet!  Our class exists, so now we just need to know if it passes its test method.
			if ($class::isSupported())
			{
				// Connector names should not have file extensions.
				$handler = str_ireplace('Storage.php', '', $fileName);
				$handler = str_ireplace('.php', '', $handler);
				$handlers[] = strtolower($handler);
			}
		}

		return $handlers;
	}

	/**
	 * Set caching enabled state
	 *
	 * @param   boolean  $enabled  True to enable caching
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setCaching($enabled)
	{
		$this->_options['caching'] = $enabled;
	}

	/**
	 * Get caching state
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function getCaching()
	{
		return $this->_options['caching'];
	}

	/**
	 * Set cache lifetime
	 *
	 * @param   integer  $lt  Cache lifetime
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function setLifeTime($lt)
	{
		$this->_options['lifetime'] = $lt;
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group = null)
	{
		if (!$this->getCaching())
		{
			return false;
		}

		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		return $this->_getStorage()->contains($id, $group);
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group = null)
	{
		if (!$this->getCaching())
		{
			return false;
		}

		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		return $this->_getStorage()->get($id, $group, $this->_options['checkTime']);
	}

	/**
	 * Get a list of all cached data
	 *
	 * @return  mixed  Boolean false on failure or an object with a list of cache groups and data
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		if (!$this->getCaching())
		{
			return false;
		}

		return $this->_getStorage()->getAll();
	}

	/**
	 * Store the cached data by ID and group
	 *
	 * @param   mixed   $data   The data to store
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($data, $id, $group = null)
	{
		if (!$this->getCaching())
		{
			return false;
		}

		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		// Get the storage and store the cached data
		return $this->_getStorage()->store($id, $group, $data);
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group = null)
	{
		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		try
		{
			return $this->_getStorage()->remove($id, $group);
		}
		catch (CacheExceptionInterface $e)
		{
			if (!$this->getCaching())
			{
				return false;
			}

			throw $e;
		}
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean  True on success, false otherwise
	 *
	 * @since   1.7.0
	 */
	public function clean($group = null, $mode = 'group')
	{
		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		try
		{
			return $this->_getStorage()->clean($group, $mode);
		}
		catch (CacheExceptionInterface $e)
		{
			if (!$this->getCaching())
			{
				return false;
			}

			throw $e;
		}
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		try
		{
			return $this->_getStorage()->gc();
		}
		catch (CacheExceptionInterface $e)
		{
			if (!$this->getCaching())
			{
				return false;
			}

			throw $e;
		}
	}

	/**
	 * Set lock flag on cached item
	 *
	 * @param   string  $id        The cache data ID
	 * @param   string  $group     The cache data group
	 * @param   string  $locktime  The default locktime for locking the cache.
	 *
	 * @return  \stdClass  Object with properties of lock and locklooped
	 *
	 * @since   1.7.0
	 */
	public function lock($id, $group = null, $locktime = null)
	{
		$returning = new \stdClass;
		$returning->locklooped = false;

		if (!$this->getCaching())
		{
			$returning->locked = false;

			return $returning;
		}

		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		// Get the default locktime
		$locktime = $locktime ?: $this->_options['locktime'];

		/*
		 * Allow storage handlers to perform locking on their own
		 * NOTE drivers with lock need also unlock or unlocking will fail because of false $id
		 */
		$handler = $this->_getStorage();

		if ($this->_options['locking'] == true)
		{
			$locked = $handler->lock($id, $group, $locktime);

			if ($locked !== false)
			{
				return $locked;
			}
		}

		// Fallback
		$curentlifetime = $this->_options['lifetime'];

		// Set lifetime to locktime for storing in children
		$this->_options['lifetime'] = $locktime;

		$looptime = $locktime * 10;
		$id2      = $id . '_lock';

		if ($this->_options['locking'] == true)
		{
			$data_lock = $handler->get($id2, $group, $this->_options['checkTime']);
		}
		else
		{
			$data_lock         = false;
			$returning->locked = false;
		}

		if ($data_lock !== false)
		{
			$lock_counter = 0;

			// Loop until you find that the lock has been released. That implies that data get from other thread has finished
			while ($data_lock !== false)
			{
				if ($lock_counter > $looptime)
				{
					$returning->locked = false;
					$returning->locklooped = true;
					break;
				}

				usleep(100);
				$data_lock = $handler->get($id2, $group, $this->_options['checkTime']);
				$lock_counter++;
			}
		}

		if ($this->_options['locking'] == true)
		{
			$returning->locked = $handler->store($id2, $group, 1);
		}

		// Revert lifetime to previous one
		$this->_options['lifetime'] = $curentlifetime;

		return $returning;
	}

	/**
	 * Unset lock flag on cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function unlock($id, $group = null)
	{
		if (!$this->getCaching())
		{
			return false;
		}

		// Get the default group
		$group = $group ?: $this->_options['defaultgroup'];

		// Allow handlers to perform unlocking on their own
		$handler = $this->_getStorage();

		$unlocked = $handler->unlock($id, $group);

		if ($unlocked !== false)
		{
			return $unlocked;
		}

		// Fallback
		return $handler->remove($id . '_lock', $group);
	}

	/**
	 * Get the cache storage handler
	 *
	 * @return  CacheStorage
	 *
	 * @since   1.7.0
	 */
	public function &_getStorage()
	{
		$hash = md5(serialize($this->_options));

		if (isset(self::$_handler[$hash]))
		{
			return self::$_handler[$hash];
		}

		self::$_handler[$hash] = CacheStorage::getInstance($this->_options['storage'], $this->_options);

		return self::$_handler[$hash];
	}

	/**
	 * Perform workarounds on retrieved cached data
	 *
	 * @param   string  $data     Cached data
	 * @param   array   $options  Array of options
	 *
	 * @return  string  Body of cached data
	 *
	 * @since   1.7.0
	 */
	public static function getWorkarounds($data, $options = array())
	{
		$app      = \JFactory::getApplication();
		$document = \JFactory::getDocument();
		$body     = null;

		// Get the document head out of the cache.
		if (isset($options['mergehead']) && $options['mergehead'] == 1 && isset($data['head']) && !empty($data['head'])
			&& method_exists($document, 'mergeHeadData'))
		{
			$document->mergeHeadData($data['head']);
		}
		elseif (isset($data['head']) && method_exists($document, 'setHeadData'))
		{
			$document->setHeadData($data['head']);
		}

		// Get the document MIME encoding out of the cache
		if (isset($data['mime_encoding']))
		{
			$document->setMimeEncoding($data['mime_encoding'], true);
		}

		// If the pathway buffer is set in the cache data, get it.
		if (isset($data['pathway']) && is_array($data['pathway']))
		{
			// Push the pathway data into the pathway object.
			$app->getPathway()->setPathway($data['pathway']);
		}

		// @todo check if the following is needed, seems like it should be in page cache
		// If a module buffer is set in the cache data, get it.
		if (isset($data['module']) && is_array($data['module']))
		{
			// Iterate through the module positions and push them into the document buffer.
			foreach ($data['module'] as $name => $contents)
			{
				$document->setBuffer($contents, 'module', $name);
			}
		}

		// Set cached headers.
		if (isset($data['headers']) && $data['headers'])
		{
			foreach ($data['headers'] as $header)
			{
				$app->setHeader($header['name'], $header['value']);
			}
		}

		// The following code searches for a token in the cached page and replaces it with the proper token.
		if (isset($data['body']))
		{
			$token       = \JSession::getFormToken();
			$search      = '#<input type="hidden" name="[0-9a-f]{32}" value="1" />#';
			$replacement = '<input type="hidden" name="' . $token . '" value="1" />';

			$data['body'] = preg_replace($search, $replacement, $data['body']);
			$body         = $data['body'];
		}

		// Get the document body out of the cache.
		return $body;
	}

	/**
	 * Create workarounds for data to be cached
	 *
	 * @param   string  $data     Cached data
	 * @param   array   $options  Array of options
	 *
	 * @return  string  Data to be cached
	 *
	 * @since   1.7.0
	 */
	public static function setWorkarounds($data, $options = array())
	{
		$loptions = array(
			'nopathway'  => 0,
			'nohead'     => 0,
			'nomodules'  => 0,
			'modulemode' => 0,
		);

		if (isset($options['nopathway']))
		{
			$loptions['nopathway'] = $options['nopathway'];
		}

		if (isset($options['nohead']))
		{
			$loptions['nohead'] = $options['nohead'];
		}

		if (isset($options['nomodules']))
		{
			$loptions['nomodules'] = $options['nomodules'];
		}

		if (isset($options['modulemode']))
		{
			$loptions['modulemode'] = $options['modulemode'];
		}

		$app      = \JFactory::getApplication();
		$document = \JFactory::getDocument();

		if ($loptions['nomodules'] != 1)
		{
			// Get the modules buffer before component execution.
			$buffer1 = $document->getBuffer();

			if (!is_array($buffer1))
			{
				$buffer1 = array();
			}

			// Make sure the module buffer is an array.
			if (!isset($buffer1['module']) || !is_array($buffer1['module']))
			{
				$buffer1['module'] = array();
			}
		}

		// View body data
		$cached['body'] = $data;

		// Document head data
		if ($loptions['nohead'] != 1 && method_exists($document, 'getHeadData'))
		{
			if ($loptions['modulemode'] == 1)
			{
				$headnow = $document->getHeadData();
				$unset   = array('title', 'description', 'link', 'links', 'metaTags');

				foreach ($unset as $un)
				{
					unset($headnow[$un]);
					unset($options['headerbefore'][$un]);
				}

				$cached['head'] = array();

				// Only store what this module has added
				foreach ($headnow as $now => $value)
				{
					if (isset($options['headerbefore'][$now]))
					{
						// We have to serialize the content of the arrays because the may contain other arrays which is a notice in PHP 5.4 and newer
						$nowvalue    = array_map('serialize', $headnow[$now]);
						$beforevalue = array_map('serialize', $options['headerbefore'][$now]);

						$newvalue = array_diff_assoc($nowvalue, $beforevalue);
						$newvalue = array_map('unserialize', $newvalue);

						// Special treatment for script and style declarations.
						if (($now == 'script' || $now == 'style') && is_array($newvalue) && is_array($options['headerbefore'][$now]))
						{
							foreach ($newvalue as $type => $currentScriptStr)
							{
								if (isset($options['headerbefore'][$now][strtolower($type)]))
								{
									$oldScriptStr = $options['headerbefore'][$now][strtolower($type)];

									if ($oldScriptStr != $currentScriptStr)
									{
										// Save only the appended declaration.
										$newvalue[strtolower($type)] = StringHelper::substr($currentScriptStr, StringHelper::strlen($oldScriptStr));
									}
								}
							}
						}
					}
					else
					{
						$newvalue = $headnow[$now];
					}

					if (!empty($newvalue))
					{
						$cached['head'][$now] = $newvalue;
					}
				}
			}
			else
			{
				$cached['head'] = $document->getHeadData();
			}
		}

		// Document MIME encoding
		$cached['mime_encoding'] = $document->getMimeEncoding();

		// Pathway data
		if ($app->isClient('site') && $loptions['nopathway'] != 1)
		{
			$cached['pathway'] = is_array($data) && isset($data['pathway']) ? $data['pathway'] : $app->getPathway()->getPathway();
		}

		if ($loptions['nomodules'] != 1)
		{
			// @todo Check if the following is needed, seems like it should be in page cache
			// Get the module buffer after component execution.
			$buffer2 = $document->getBuffer();

			if (!is_array($buffer2))
			{
				$buffer2 = array();
			}

			// Make sure the module buffer is an array.
			if (!isset($buffer2['module']) || !is_array($buffer2['module']))
			{
				$buffer2['module'] = array();
			}

			// Compare the second module buffer against the first buffer.
			$cached['module'] = array_diff_assoc($buffer2['module'], $buffer1['module']);
		}

		// Headers data
		if (isset($options['headers']) && $options['headers'])
		{
			$cached['headers'] = $app->getHeaders();
		}

		return $cached;
	}

	/**
	 * Create a safe ID for cached data from URL parameters
	 *
	 * @return  string  MD5 encoded cache ID
	 *
	 * @since   1.7.0
	 */
	public static function makeId()
	{
		$app = \JFactory::getApplication();

		$registeredurlparams = new \stdClass;

		// Get url parameters set by plugins
		if (!empty($app->registeredurlparams))
		{
			$registeredurlparams = $app->registeredurlparams;
		}

		// Platform defaults
		$defaulturlparams = array(
			'format' => 'WORD',
			'option' => 'WORD',
			'view'   => 'WORD',
			'layout' => 'WORD',
			'tpl'    => 'CMD',
			'id'     => 'INT',
		);

		// Use platform defaults if parameter doesn't already exist.
		foreach ($defaulturlparams as $param => $type)
		{
			if (!property_exists($registeredurlparams, $param))
			{
				$registeredurlparams->$param = $type;
			}
		}

		$safeuriaddon = new \stdClass;

		foreach ($registeredurlparams as $key => $value)
		{
			$safeuriaddon->$key = $app->input->get($key, null, $value);
		}

		return md5(serialize($safeuriaddon));
	}

	/**
	 * Set a prefix cache key if device calls for separate caching
	 *
	 * @return  string
	 *
	 * @since   3.5
	 */
	public static function getPlatformPrefix()
	{
		// No prefix when Global Config is set to no platfom specific prefix
		if (!\JFactory::getConfig()->get('cache_platformprefix', '0'))
		{
			return '';
		}

		$webclient = new WebClient;

		if ($webclient->mobile)
		{
			return 'M-';
		}

		return '';
	}

	/**
	 * Add a directory where Cache should search for handlers. You may either pass a string or an array of directories.
	 *
	 * @param   array|string  $path  A path to search.
	 *
	 * @return  array   An array with directory elements
	 *
	 * @since   1.7.0
	 */
	public static function addIncludePath($path = '')
	{
		static $paths;

		if (!isset($paths))
		{
			$paths = array();
		}

		if (!empty($path) && !in_array($path, $paths))
		{
			\JLoader::import('joomla.filesystem.path');
			array_unshift($paths, \JPath::clean($path));
		}

		return $paths;
	}
}
src/Cache/CacheStorage.php000064400000020627152177723700011421 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Cache\Exception\UnsupportedCacheException;
use Joomla\CMS\Log\Log;

/**
 * Abstract cache storage handler
 *
 * @since  1.7.0
 * @note   As of 4.0 this class will be abstract
 */
class CacheStorage
{
	/**
	 * The raw object name
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $rawname;

	/**
	 * Time that the cache storage handler was instantiated
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $_now;

	/**
	 * Cache lifetime
	 *
	 * @var    integer
	 * @since  1.7.0
	 */
	public $_lifetime;

	/**
	 * Flag if locking is enabled
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	public $_locking;

	/**
	 * Language code
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_language;

	/**
	 * Application name
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_application;

	/**
	 * Object hash
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $_hash;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		$config = \JFactory::getConfig();

		$this->_hash        = md5($config->get('secret'));
		$this->_application = (isset($options['application'])) ? $options['application'] : md5(JPATH_CONFIGURATION);
		$this->_language    = (isset($options['language'])) ? $options['language'] : 'en-GB';
		$this->_locking     = (isset($options['locking'])) ? $options['locking'] : true;
		$this->_lifetime    = (isset($options['lifetime'])) ? $options['lifetime'] * 60 : $config->get('cachetime') * 60;
		$this->_now         = (isset($options['now'])) ? $options['now'] : time();

		// Set time threshold value.  If the lifetime is not set, default to 60 (0 is BAD)
		// _threshold is now available ONLY as a legacy (it's deprecated).  It's no longer used in the core.
		if (empty($this->_lifetime))
		{
			$this->_threshold = $this->_now - 60;
			$this->_lifetime = 60;
		}
		else
		{
			$this->_threshold = $this->_now - $this->_lifetime;
		}
	}

	/**
	 * Returns a cache storage handler object.
	 *
	 * @param   string  $handler  The cache storage handler to instantiate
	 * @param   array   $options  Array of handler options
	 *
	 * @return  CacheStorage
	 *
	 * @since   1.7.0
	 * @throws  \UnexpectedValueException
	 * @throws  UnsupportedCacheException
	 */
	public static function getInstance($handler = null, $options = array())
	{
		static $now = null;

		// @deprecated  4.0  This class path is autoloaded, manual inclusion is no longer necessary
		self::addIncludePath(__DIR__ . '/Storage');

		if (!isset($handler))
		{
			$handler = \JFactory::getConfig()->get('cache_handler');

			if (empty($handler))
			{
				throw new \UnexpectedValueException('Cache Storage Handler not set.');
			}
		}

		if (is_null($now))
		{
			$now = time();
		}

		$options['now'] = $now;

		// We can't cache this since options may change...
		$handler = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $handler));

		/** @var CacheStorage $class */
		$class = __NAMESPACE__ . '\\Storage\\' . ucfirst($handler) . 'Storage';

		if (!class_exists($class))
		{
			$class = 'JCacheStorage' . ucfirst($handler);
		}

		if (!class_exists($class))
		{
			// Search for the class file in the JCacheStorage include paths.
			\JLoader::import('joomla.filesystem.path');

			$path = \JPath::find(self::addIncludePath(), strtolower($handler) . '.php');

			if ($path === false)
			{
				throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler));
			}

			\JLoader::register($class, $path);

			// The class should now be loaded
			if (!class_exists($class))
			{
				throw new UnsupportedCacheException(sprintf('Unable to load Cache Storage: %s', $handler));
			}
		}

		// Validate the cache storage is supported on this platform
		if (!$class::isSupported())
		{
			throw new UnsupportedCacheException(sprintf('The %s Cache Storage is not supported on this platform.', $handler));
		}

		return new $class($options);
	}

	/**
	 * Check if the cache contains data stored by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   3.7.0
	 */
	public function contains($id, $group)
	{
		return false;
	}

	/**
	 * Get cached data by ID and group
	 *
	 * @param   string   $id         The cache data ID
	 * @param   string   $group      The cache data group
	 * @param   boolean  $checkTime  True to verify cache time expiration threshold
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function get($id, $group, $checkTime = true)
	{
		return false;
	}

	/**
	 * Get all cached data
	 *
	 * @return  mixed  Boolean false on failure or a cached data object
	 *
	 * @since   1.7.0
	 */
	public function getAll()
	{
		return false;
	}

	/**
	 * Store the data to cache by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 * @param   string  $data   The data to store in cache
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function store($id, $group, $data)
	{
		return true;
	}

	/**
	 * Remove a cached data entry by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function remove($id, $group)
	{
		return true;
	}

	/**
	 * Clean cache for a group given a mode.
	 *
	 * group mode    : cleans all cache in the group
	 * notgroup mode : cleans all cache not in the group
	 *
	 * @param   string  $group  The cache data group
	 * @param   string  $mode   The mode for cleaning cache [group|notgroup]
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function clean($group, $mode = null)
	{
		return true;
	}

	/**
	 * Flush all existing items in storage.
	 *
	 * @return  boolean
	 *
	 * @since   3.6.3
	 */
	public function flush()
	{
		return true;
	}

	/**
	 * Garbage collect expired cache data
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function gc()
	{
		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return true;
	}

	/**
	 * Test to see if the storage handler is available.
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public static function test()
	{
		Log::add(__METHOD__ . '() is deprecated. Use CacheStorage::isSupported() instead.', Log::WARNING, 'deprecated');

		return static::isSupported();
	}

	/**
	 * Lock cached item
	 *
	 * @param   string   $id        The cache data ID
	 * @param   string   $group     The cache data group
	 * @param   integer  $locktime  Cached item max lock time
	 *
	 * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
	 *
	 * @since   1.7.0
	 */
	public function lock($id, $group, $locktime)
	{
		return false;
	}

	/**
	 * Unlock cached item
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  boolean
	 *
	 * @since   1.7.0
	 */
	public function unlock($id, $group = null)
	{
		return false;
	}

	/**
	 * Get a cache ID string from an ID/group pair
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function _getCacheId($id, $group)
	{
		$name          = md5($this->_application . '-' . $id . '-' . $this->_language);
		$this->rawname = $this->_hash . '-' . $name;

		return Cache::getPlatformPrefix() . $this->_hash . '-cache-' . $group . '-' . $name;
	}

	/**
	 * Add a directory where CacheStorage should search for handlers. You may either pass a string or an array of directories.
	 *
	 * @param   array|string  $path  A path to search.
	 *
	 * @return  array  An array with directory elements
	 *
	 * @since   1.7.0
	 */
	public static function addIncludePath($path = '')
	{
		static $paths;

		if (!isset($paths))
		{
			$paths = array();
		}

		if (!empty($path) && !in_array($path, $paths))
		{
			\JLoader::import('joomla.filesystem.path');
			array_unshift($paths, \JPath::clean($path));
		}

		return $paths;
	}
}
src/Cache/Exception/CacheExceptionInterface.php000064400000000637152177723700015531 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception interface defining a cache storage error
 *
 * @since  3.7.0
 */
interface CacheExceptionInterface
{
}
src/Cache/Exception/UnsupportedCacheException.php000064400000000744152177723700016160 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an unsupported cache storage object
 *
 * @since  3.6.3
 */
class UnsupportedCacheException extends \RuntimeException implements CacheExceptionInterface
{
}
src/Cache/Exception/CacheConnectingException.php000064400000000757152177723700015723 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an error connecting to the cache storage engine
 *
 * @since  3.6.3
 */
class CacheConnectingException extends \RuntimeException implements CacheExceptionInterface
{
}
src/Cache/CacheController.php000064400000011462152177723700012135 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Cache;

defined('JPATH_PLATFORM') or die;

/**
 * Public cache handler
 *
 * @since  1.7.0
 * @note   As of 4.0 this class will be abstract
 */
class CacheController
{
	/**
	 * Cache object
	 *
	 * @var    Cache
	 * @since  1.7.0
	 */
	public $cache;

	/**
	 * Array of options
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	public $options;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Array of options
	 *
	 * @since   1.7.0
	 */
	public function __construct($options)
	{
		$this->cache = new Cache($options);
		$this->options = & $this->cache->_options;

		// Overwrite default options with given options
		foreach ($options as $option => $value)
		{
			if (isset($options[$option]))
			{
				$this->options[$option] = $options[$option];
			}
		}
	}

	/**
	 * Magic method to proxy CacheController method calls to Cache
	 *
	 * @param   string  $name       Name of the function
	 * @param   array   $arguments  Array of arguments for the function
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function __call($name, $arguments)
	{
		return call_user_func_array(array($this->cache, $name), $arguments);
	}

	/**
	 * Returns a reference to a cache adapter object, always creating it
	 *
	 * @param   string  $type     The cache object type to instantiate; default is output.
	 * @param   array   $options  Array of options
	 *
	 * @return  CacheController
	 *
	 * @since   1.7.0
	 * @throws  \RuntimeException
	 */
	public static function getInstance($type = 'output', $options = array())
	{
		self::addIncludePath(__DIR__ . '/Controller');

		$type = strtolower(preg_replace('/[^A-Z0-9_\.-]/i', '', $type));

		$class = __NAMESPACE__ . '\\Controller\\' . ucfirst($type) . 'Controller';

		if (!class_exists($class))
		{
			$class = 'JCacheController' . ucfirst($type);
		}

		if (!class_exists($class))
		{
			// Search for the class file in the Cache include paths.
			\JLoader::import('joomla.filesystem.path');

			$path = \JPath::find(self::addIncludePath(), strtolower($type) . '.php');

			if ($path !== false)
			{
				\JLoader::register($class, $path);
			}

			// The class should now be loaded
			if (!class_exists($class))
			{
				throw new \RuntimeException('Unable to load Cache Controller: ' . $type, 500);
			}
		}

		return new $class($options);
	}

	/**
	 * Add a directory where Cache should search for controllers. You may either pass a string or an array of directories.
	 *
	 * @param   array|string  $path  A path to search.
	 *
	 * @return  array  An array with directory elements
	 *
	 * @since   1.7.0
	 */
	public static function addIncludePath($path = '')
	{
		static $paths;

		if (!isset($paths))
		{
			$paths = array();
		}

		if (!empty($path) && !in_array($path, $paths))
		{
			\JLoader::import('joomla.filesystem.path');
			array_unshift($paths, \JPath::clean($path));
		}

		return $paths;
	}

	/**
	 * Get stored cached data by ID and group
	 *
	 * @param   string  $id     The cache data ID
	 * @param   string  $group  The cache data group
	 *
	 * @return  mixed  Boolean false on no result, cached object otherwise
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Implement own method in subclass
	 */
	public function get($id, $group = null)
	{
		$data = $this->cache->get($id, $group);

		if ($data === false)
		{
			$locktest = $this->cache->lock($id, $group);

			// If locklooped is true try to get the cached data again; it could exist now.
			if ($locktest->locked === true && $locktest->locklooped === true)
			{
				$data = $this->cache->get($id, $group);
			}

			if ($locktest->locked === true)
			{
				$this->cache->unlock($id, $group);
			}
		}

		// Check again because we might get it from second attempt
		if ($data !== false)
		{
			// Trim to fix unserialize errors
			$data = unserialize(trim($data));
		}

		return $data;
	}

	/**
	 * Store data to cache by ID and group
	 *
	 * @param   mixed    $data        The data to store
	 * @param   string   $id          The cache data ID
	 * @param   string   $group       The cache data group
	 * @param   boolean  $wrkarounds  True to use wrkarounds
	 *
	 * @return  boolean  True if cache stored
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Implement own method in subclass
	 */
	public function store($data, $id, $group = null, $wrkarounds = true)
	{
		$locktest = $this->cache->lock($id, $group);

		if ($locktest->locked === false && $locktest->locklooped === true)
		{
			// We can not store data because another process is in the middle of saving
			return false;
		}

		$result = $this->cache->store(serialize($data), $id, $group);

		if ($locktest->locked === true)
		{
			$this->cache->unlock($id, $group);
		}

		return $result;
	}
}
src/Exception/ExceptionHandler.php000064400000007426152177723700013262 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Displays the custom error page when an uncaught exception occurs.
 *
 * @since  3.0
 */
class ExceptionHandler
{
	/**
	 * Render the error page based on an exception.
	 *
	 * @param   \Exception|\Throwable  $error  An Exception or Throwable (PHP 7+) object for which to render the error page.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function render($error)
	{
		$expectedClass = PHP_MAJOR_VERSION >= 7 ? '\Throwable' : '\Exception';
		$isException   = $error instanceof $expectedClass;

		// In PHP 5, the $error object should be an instance of \Exception; PHP 7 should be a Throwable implementation
		if ($isException)
		{
			try
			{
				// Try to log the error, but don't let the logging cause a fatal error
				try
				{
					\JLog::add(
						sprintf(
							'Uncaught %1$s of type %2$s thrown. Stack trace: %3$s',
							$expectedClass,
							get_class($error),
							$error->getTraceAsString()
						),
						\JLog::CRITICAL,
						'error'
					);
				}
				catch (\Throwable $e)
				{
					// Logging failed, don't make a stink about it though
				}
				catch (\Exception $e)
				{
					// Logging failed, don't make a stink about it though
				}

				$app = \JFactory::getApplication();

				// If site is offline and it's a 404 error, just go to index (to see offline message, instead of 404)
				if ($error->getCode() == '404' && $app->get('offline') == 1)
				{
					$app->redirect('index.php');
				}

				$attributes = array(
					'charset'   => 'utf-8',
					'lineend'   => 'unix',
					'tab'       => "\t",
					'language'  => 'en-GB',
					'direction' => 'ltr',
				);

				// If there is a \JLanguage instance in \JFactory then let's pull the language and direction from its metadata
				if (\JFactory::$language)
				{
					$attributes['language']  = \JFactory::getLanguage()->getTag();
					$attributes['direction'] = \JFactory::getLanguage()->isRtl() ? 'rtl' : 'ltr';
				}

				$document = \JDocument::getInstance('error', $attributes);

				if (!$document)
				{
					// We're probably in an CLI environment
					jexit($error->getMessage());
				}

				// Get the current template from the application
				$template = $app->getTemplate();

				// Push the error object into the document
				$document->setError($error);

				if (ob_get_contents())
				{
					ob_end_clean();
				}

				$document->setTitle(\JText::_('ERROR') . ': ' . $error->getCode());

				$data = $document->render(
					false,
					array(
						'template'  => $template,
						'directory' => JPATH_THEMES,
						'debug'     => JDEBUG,
					)
				);

				// Do not allow cache
				$app->allowCache(false);

				// If nothing was rendered, just use the message from the Exception
				if (empty($data))
				{
					$data = $error->getMessage();
				}

				$app->setBody($data);

				echo $app->toString();

				$app->close(0);

				// This return is needed to ensure the test suite does not trigger the non-Exception handling below
				return;
			}
			catch (\Throwable $e)
			{
				// Pass the error down
			}
			catch (\Exception $e)
			{
				// Pass the error down
			}
		}

		// This isn't an Exception, we can't handle it.
		if (!headers_sent())
		{
			header('HTTP/1.1 500 Internal Server Error');
		}

		$message = 'Error';

		if ($isException)
		{
			// Make sure we do not display sensitive data in production environments
			if (ini_get('display_errors'))
			{
				$message .= ': ';

				if (isset($e))
				{
					$message .= $e->getMessage() . ': ';
				}

				$message .= $error->getMessage();
			}
		}

		echo $message;

		jexit(1);
	}
}
src/Router/SiteRouter.php000064400000044474152177723700011461 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Component\Router\RouterInterface;
use Joomla\CMS\Component\Router\RouterLegacy;
use Joomla\String\StringHelper;

/**
 * Class to create and parse routes for the site application
 *
 * @since  1.5
 */
class SiteRouter extends Router
{
	/**
	 * Component-router objects
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $componentRouters = array();

	/**
	 * Current Application-Object
	 *
	 * @var    CMSApplication
	 * @since  3.4
	 */
	protected $app;

	/**
	 * Current \JMenu-Object
	 *
	 * @var    \JMenu
	 * @since  3.4
	 */
	protected $menu;

	/**
	 * Class constructor
	 *
	 * @param   array           $options  Array of options
	 * @param   CMSApplication  $app      CMSApplication Object
	 * @param   \JMenu          $menu     \JMenu object
	 *
	 * @since   3.4
	 */
	public function __construct($options = array(), CMSApplication $app = null, \JMenu $menu = null)
	{
		parent::__construct($options);

		$this->app  = $app ?: CMSApplication::getInstance('site');
		$this->menu = $menu ?: $this->app->getMenu();
	}

	/**
	 * Function to convert a route to an internal URI
	 *
	 * @param   \JUri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		$vars = array();

		if ($this->app->get('force_ssl') == 2 && strtolower($uri->getScheme()) !== 'https')
		{
			// Forward to https
			$uri->setScheme('https');
			$this->app->redirect((string) $uri, 301);
		}

		// Get the path
		// Decode URL to convert percent-encoding to unicode so that strings match when routing.
		$path = urldecode($uri->getPath());

		// Remove the base URI path.
		$path = substr_replace($path, '', 0, strlen(\JUri::base(true)));

		// Check to see if a request to a specific entry point has been made.
		if (preg_match("#.*?\.php#u", $path, $matches))
		{
			// Get the current entry point path relative to the site path.
			$scriptPath         = realpath($_SERVER['SCRIPT_FILENAME'] ?: str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']));
			$relativeScriptPath = str_replace('\\', '/', str_replace(JPATH_SITE, '', $scriptPath));

			// If a php file has been found in the request path, check to see if it is a valid file.
			// Also verify that it represents the same file from the server variable for entry script.
			if (file_exists(JPATH_SITE . $matches[0]) && ($matches[0] === $relativeScriptPath))
			{
				// Remove the entry point segments from the request path for proper routing.
				$path = str_replace($matches[0], '', $path);
			}
		}

		// Identify format
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			if ($this->app->get('sef_suffix') && !(substr($path, -9) === 'index.php' || substr($path, -1) === '/'))
			{
				if ($suffix = pathinfo($path, PATHINFO_EXTENSION))
				{
					$vars['format'] = $suffix;
				}
			}
		}

		// Set the route
		$uri->setPath(trim($path, '/'));

		// Set the parsepreprocess components methods
		$components = ComponentHelper::getComponents();

		foreach ($components as $component)
		{
			$componentRouter = $this->getComponentRouter($component->option);

			if (method_exists($componentRouter, 'parsepreprocess'))
			{
				$this->attachParseRule(array($componentRouter, 'parsepreprocess'), static::PROCESS_BEFORE);
			}
		}

		$vars += parent::parse($uri);

		return $vars;
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL
	 *
	 * @return  string  The absolute search engine friendly URL
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		$uri = parent::build($url);

		// Get the path data
		$route = $uri->getPath();

		// Add the suffix to the uri
		if ($this->_mode == JROUTER_MODE_SEF && $route)
		{
			if ($this->app->get('sef_suffix') && !(substr($route, -9) === 'index.php' || substr($route, -1) === '/'))
			{
				if ($format = $uri->getVar('format', 'html'))
				{
					$route .= '.' . $format;
					$uri->delVar('format');
				}
			}

			if ($this->app->get('sef_rewrite'))
			{
				// Transform the route
				if ($route === 'index.php')
				{
					$route = '';
				}
				else
				{
					$route = str_replace('index.php/', '', $route);
				}
			}
		}

		// Add frontend basepath to the uri
		$uri->setPath(\JUri::root(true) . '/' . $route);

		return $uri;
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  array
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseRawRoute(&$uri)
	{
		$vars = array();

		// Handle an empty URL (special case)
		if (!$uri->getVar('Itemid') && !$uri->getVar('option'))
		{
			$item = $this->menu->getDefault($this->app->getLanguage()->getTag());

			if (!is_object($item))
			{
				// No default item set
				return $vars;
			}

			// Set the information in the request
			$vars = $item->query;

			// Get the itemid
			$vars['Itemid'] = $item->id;

			// Set the active menu item
			$this->menu->setActive($vars['Itemid']);

			return $vars;
		}

		// Get the variables from the uri
		$this->setVars($uri->getQuery(true));

		// Get the itemid, if it hasn't been set force it to null
		$this->setVar('Itemid', $this->app->input->getInt('Itemid', null));

		// Only an Itemid  OR if filter language plugin set? Get the full information from the itemid
		if (count($this->getVars()) === 1 || ($this->app->getLanguageFilter() && count($this->getVars()) === 2))
		{
			$item = $this->menu->getItem($this->getVar('Itemid'));

			if ($item && $item->type == 'alias')
			{
				$newItem = $this->menu->getItem($item->params->get('aliasoptions'));

				if ($newItem)
				{
					$item->query     = array_merge($item->query, $newItem->query);
					$item->component = $newItem->component;
				}
			}

			if ($item !== null && is_array($item->query))
			{
				$vars += $item->query;
			}
		}

		// Set the active menu item
		$this->menu->setActive($this->getVar('Itemid'));

		return $vars;
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  string  Internal URI
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseSefRoute(&$uri)
	{
		$route = $uri->getPath();

		// Remove the suffix
		if ($this->app->get('sef_suffix'))
		{
			if ($suffix = pathinfo($route, PATHINFO_EXTENSION))
			{
				$route = str_replace('.' . $suffix, '', $route);
			}
		}

		// Get the variables from the uri
		$vars = $uri->getQuery(true);

		// Handle an empty URL (special case)
		if (empty($route))
		{
			// If route is empty AND option is set in the query, assume it's non-sef url, and parse apropriately
			if (isset($vars['option']) || isset($vars['Itemid']))
			{
				return $this->parseRawRoute($uri);
			}

			$item = $this->menu->getDefault($this->app->getLanguage()->getTag());

			// If user not allowed to see default menu item then avoid notices
			if (is_object($item))
			{
				// Set query variables of default menu item into the request, but keep existing request variables
				$vars = array_merge($vars, $item->query);

				// Get the itemid
				$vars['Itemid'] = $item->id;

				// Set the active menu item
				$this->menu->setActive($vars['Itemid']);

				$this->setVars($vars);
			}

			return $vars;
		}

		// Parse the application route
		$segments = explode('/', $route);

		if (count($segments) > 1 && $segments[0] === 'component')
		{
			$vars['option'] = 'com_' . $segments[1];
			$vars['Itemid'] = null;
			$route = implode('/', array_slice($segments, 2));
		}
		else
		{
			// Get menu items.
			$items = $this->menu->getMenu();

			$found           = false;
			$route_lowercase = StringHelper::strtolower($route);
			$lang_tag        = $this->app->getLanguage()->getTag();

			// Iterate through all items and check route matches.
			foreach ($items as $item)
			{
				if ($item->route && StringHelper::strpos($route_lowercase . '/', $item->route . '/') === 0 && $item->type !== 'menulink')
				{
					// Usual method for non-multilingual site.
					if (!$this->app->getLanguageFilter())
					{
						// Exact route match. We can break iteration because exact item was found.
						if ($item->route === $route_lowercase)
						{
							$found = $item;
							break;
						}

						// Partial route match. Item with highest level takes priority.
						if (!$found || $found->level < $item->level)
						{
							$found = $item;
						}
					}
					// Multilingual site.
					elseif ($item->language === '*' || $item->language === $lang_tag)
					{
						// Exact route match.
						if ($item->route === $route_lowercase)
						{
							$found = $item;

							// Break iteration only if language is matched.
							if ($item->language === $lang_tag)
							{
								break;
							}
						}

						// Partial route match. Item with highest level or same language takes priority.
						if (!$found || $found->level < $item->level || $item->language === $lang_tag)
						{
							$found = $item;
						}
					}
				}
			}

			if (!$found)
			{
				$found = $this->menu->getDefault($lang_tag);
			}
			else
			{
				$route = substr($route, strlen($found->route));

				if ($route)
				{
					$route = substr($route, 1);
				}
			}

			if ($found)
			{
				if ($found->type == 'alias')
				{
					$newItem = $this->menu->getItem($found->params->get('aliasoptions'));

					if ($newItem)
					{
						$found->query     = array_merge($found->query, $newItem->query);
						$found->component = $newItem->component;
					}
				}

				$vars['Itemid'] = $found->id;
				$vars['option'] = $found->component;
			}
		}

		// Set the active menu item
		if (isset($vars['Itemid']))
		{
			$this->menu->setActive($vars['Itemid']);
		}

		// Set the variables
		$this->setVars($vars);

		// Parse the component route
		if (!empty($route) && isset($this->_vars['option']))
		{
			$segments = explode('/', $route);

			if (empty($segments[0]))
			{
				array_shift($segments);
			}

			// Handle component route
			$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $this->_vars['option']);

			if (count($segments))
			{
				$crouter = $this->getComponentRouter($component);
				$vars = $crouter->parse($segments);

				$this->setVars($vars);
			}

			$route = implode('/', $segments);
		}
		else
		{
			// Set active menu item
			if ($item = $this->menu->getActive())
			{
				$vars = $item->query;
			}
		}

		$uri->setPath($route);

		return $vars;
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildRawRoute(&$uri)
	{
		// Get the query data
		$query = $uri->getQuery(true);

		if (!isset($query['option']))
		{
			return;
		}

		$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']);
		$crouter   = $this->getComponentRouter($component);
		$query     = $crouter->preprocess($query);

		$uri->setQuery($query);
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 * @codeCoverageIgnore
	 */
	protected function _buildSefRoute(&$uri)
	{
		$this->buildSefRoute($uri);
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildSefRoute(&$uri)
	{
		// Get the route
		$route = $uri->getPath();

		// Get the query data
		$query = $uri->getQuery(true);

		if (!isset($query['option']))
		{
			return;
		}

		// Build the component route
		$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']);
		$itemID    = !empty($query['Itemid']) ? $query['Itemid'] : null;
		$crouter   = $this->getComponentRouter($component);
		$parts     = $crouter->build($query);
		$result    = implode('/', $parts);
		$tmp       = ($result !== '') ? $result : '';

		// Build the application route
		$built = false;

		if (!empty($query['Itemid']))
		{
			$item = $this->menu->getItem($query['Itemid']);

			if (is_object($item) && $query['option'] === $item->component)
			{
				if (!$item->home)
				{
					$tmp = !empty($tmp) ? $item->route . '/' . $tmp : $item->route;
				}

				$built = true;
			}
		}

		if (empty($query['Itemid']) && !empty($itemID))
		{
			$query['Itemid'] = $itemID;
		}

		if (!$built)
		{
			$tmp = 'component/' . substr($query['option'], 4) . '/' . $tmp;
		}

		if ($tmp)
		{
			$route .= '/' . $tmp;
		}

		// Unset unneeded query information
		if (isset($item) && $query['option'] === $item->component)
		{
			unset($query['Itemid']);
		}

		unset($query['option']);

		// Set query again in the URI
		$uri->setQuery($query);
		$uri->setPath($route);
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI to parse
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess', 'postprocess'
	 *                          and '' for the main parse stage
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   3.2
	 */
	protected function processParseRules(&$uri, $stage = self::PROCESS_DURING)
	{
		// Process the attached parse rules
		$vars = parent::processParseRules($uri, $stage);

		if ($stage === self::PROCESS_DURING)
		{
			// Process the pagination support
			if ($this->_mode == JROUTER_MODE_SEF)
			{
				$start = $uri->getVar('start');

				if ($start !== null)
				{
					$uri->delVar('start');
					$vars['limitstart'] = $start;
				}
			}
		}

		return $vars;
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess', 'postprocess'
	 *                          and '' for the main build stage
	 *
	 * @return  void
	 *
	 * @since   3.2
	 * @deprecated  4.0  The special logic should be implemented as rule
	 */
	protected function processBuildRules(&$uri, $stage = self::PROCESS_DURING)
	{
		if ($stage === self::PROCESS_DURING)
		{
			// Make sure any menu vars are used if no others are specified
			$query = $uri->getQuery(true);

			if ($this->_mode != 1
				&& isset($query['Itemid'])
				&& (count($query) === 2 || (count($query) === 3 && isset($query['lang']))))
			{
				// Get the active menu item
				$itemid = $uri->getVar('Itemid');
				$lang = $uri->getVar('lang');
				$item = $this->menu->getItem($itemid);

				if ($item)
				{
					$uri->setQuery($item->query);
				}

				$uri->setVar('Itemid', $itemid);

				if ($lang)
				{
					$uri->setVar('lang', $lang);
				}
			}
		}

		// Process the attached build rules
		parent::processBuildRules($uri, $stage);

		if ($stage === self::PROCESS_BEFORE)
		{
			// Get the query data
			$query = $uri->getQuery(true);

			if (!isset($query['option']))
			{
				return;
			}

			// Build the component route
			$component = preg_replace('/[^A-Z0-9_\.-]/i', '', $query['option']);
			$router   = $this->getComponentRouter($component);
			$query     = $router->preprocess($query);
			$uri->setQuery($query);
		}

		if ($stage === self::PROCESS_DURING)
		{
			// Get the path data
			$route = $uri->getPath();

			if ($this->_mode == JROUTER_MODE_SEF && $route)
			{
				$limitstart = $uri->getVar('limitstart');

				if ($limitstart !== null)
				{
					$uri->setVar('start', (int) $limitstart);
					$uri->delVar('limitstart');
				}
			}

			$uri->setPath($route);
		}
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI
	 *
	 * @return  \JUri
	 *
	 * @since   3.2
	 */
	protected function createUri($url)
	{
		// Create the URI
		$uri = parent::createUri($url);

		// Get the itemid form the URI
		$itemid = $uri->getVar('Itemid');

		if ($itemid === null)
		{
			if ($option = $uri->getVar('option'))
			{
				$item = $this->menu->getItem($this->getVar('Itemid'));

				if ($item !== null && $item->component === $option)
				{
					$uri->setVar('Itemid', $item->id);
				}
			}
			else
			{
				if ($option = $this->getVar('option'))
				{
					$uri->setVar('option', $option);
				}

				if ($itemid = $this->getVar('Itemid'))
				{
					$uri->setVar('Itemid', $itemid);
				}
			}
		}
		else
		{
			if (!$uri->getVar('option'))
			{
				if ($item = $this->menu->getItem($itemid))
				{
					$uri->setVar('option', $item->component);
				}
			}
		}

		return $uri;
	}

	/**
	 * Get component router
	 *
	 * @param   string  $component  Name of the component including com_ prefix
	 *
	 * @return  RouterInterface  Component router
	 *
	 * @since   3.3
	 */
	public function getComponentRouter($component)
	{
		if (!isset($this->componentRouters[$component]))
		{
			$compname = ucfirst(substr($component, 4));
			$class = $compname . 'Router';

			if (!class_exists($class))
			{
				// Use the component routing handler if it exists
				$path = JPATH_SITE . '/components/' . $component . '/router.php';

				// Use the custom routing handler if it exists
				if (file_exists($path))
				{
					require_once $path;
				}
			}

			if (class_exists($class))
			{
				$reflection = new \ReflectionClass($class);

				if (in_array('Joomla\\CMS\\Component\\Router\\RouterInterface', $reflection->getInterfaceNames()))
				{
					$this->componentRouters[$component] = new $class($this->app, $this->menu);
				}
			}

			if (!isset($this->componentRouters[$component]))
			{
				$this->componentRouters[$component] = new RouterLegacy($compname);
			}
		}

		return $this->componentRouters[$component];
	}

	/**
	 * Set a router for a component
	 *
	 * @param   string  $component  Component name with com_ prefix
	 * @param   object  $router     Component router
	 *
	 * @return  boolean  True if the router was accepted, false if not
	 *
	 * @since   3.3
	 */
	public function setComponentRouter($component, $router)
	{
		$reflection = new \ReflectionClass($router);

		if (in_array('Joomla\\CMS\\Component\\Router\\RouterInterface', $reflection->getInterfaceNames()))
		{
			$this->componentRouters[$component] = $router;

			return true;
		}
		else
		{
			return false;
		}
	}
}
src/Router/Route.php000064400000012051152177723700010434 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;

/**
 * Route handling class
 *
 * @since  1.7.0
 */
class Route
{
	/**
	 * No change, use the protocol currently used.
	 *
	 * @since  3.9.7
	 */
	const TLS_IGNORE = 0;

	/**
	 * Make URI secure using http over TLS (https).
	 *
	 * @since  3.9.7
	 */
	const TLS_FORCE = 1;

	/**
	 * Make URI unsecure using plain http (http).
	 *
	 * @since  3.9.7
	 */
	const TLS_DISABLE = 2;

	/**
	 * The route object so we don't have to keep fetching it.
	 *
	 * @var    Router[]
	 * @since  3.0.1
	 */
	private static $_router = array();

	/**
	 * Translates an internal Joomla URL to a humanly readable URL. This method builds links for the current active client.
	 *
	 * @param   string   $url       Absolute or Relative URI to Joomla resource.
	 * @param   boolean  $xhtml     Replace & by &amp; for XML compliance.
	 * @param   integer  $tls       Secure state for the resolved URI. Use Route::TLS_* constants
	 *                                0: (default) No change, use the protocol currently used in the request
	 *                                1: Make URI secure using global secure site URI.
	 *                                2: Make URI unsecure using the global unsecure site URI.
	 * @param   boolean  $absolute  Return an absolute URL
	 *
	 * @return  string  The translated humanly readable URL.
	 *
	 * @since   1.7.0
	 */
	public static function _($url, $xhtml = true, $tls = self::TLS_IGNORE, $absolute = false)
	{
		try
		{
			// @todo  Deprecate in 4.0 Before 3.9.7 this method accepted -1.
			if ($tls == -1)
			{
				$tls = self::TLS_DISABLE;
			}

			$app    = Factory::getApplication();
			$client = $app->getName();

			return static::link($client, $url, $xhtml, $tls, $absolute);
		}
		catch (\RuntimeException $e)
		{
			// @deprecated  4.0 Before 3.9.0 this method failed silently on router error. This B/C will be removed in Joomla 4.0.
			return null;
		}
	}

	/**
	 * Translates an internal Joomla URL to a humanly readable URL.
	 * NOTE: To build link for active client instead of a specific client, you can use <var>JRoute::_()</var>
	 *
	 * @param   string   $client    The client name for which to build the link.
	 * @param   string   $url       Absolute or Relative URI to Joomla resource.
	 * @param   boolean  $xhtml     Replace & by &amp; for XML compliance.
	 * @param   integer  $tls       Secure state for the resolved URI. Use Route::TLS_* constants
	 *                                0: (default) No change, use the protocol currently used in the request
	 *                                1: Make URI secure using global secure site URI.
	 *                                2: Make URI unsecure using the global unsecure site URI.
	 * @param   boolean  $absolute  Return an absolute URL
	 *
	 * @return  string  The translated humanly readable URL.
	 *
	 * @throws  \RuntimeException
	 *
	 * @since   3.9.0
	 */
	public static function link($client, $url, $xhtml = true, $tls = self::TLS_IGNORE, $absolute = false)
	{
		// If we cannot process this $url exit early.
		if (!is_array($url) && (strpos($url, '&') !== 0) && (strpos($url, 'index.php') !== 0))
		{
			return $url;
		}

		// Get the router instance, only attempt when a client name is given.
		if ($client && !isset(self::$_router[$client]))
		{
			$app = Factory::getApplication();

			self::$_router[$client] = $app->getRouter($client);
		}

		// Make sure that we have our router
		if (!isset(self::$_router[$client]))
		{
			throw new \RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD', $client), 500);
		}

		// Build route.
		$uri    = self::$_router[$client]->build($url);
		$scheme = array('path', 'query', 'fragment');

		/*
		 * Get the secure/unsecure URLs.
		 *
		 * If the first 5 characters of the BASE are 'https', then we are on an ssl connection over
		 * https and need to set our secure URL to the current request URL, if not, and the scheme is
		 * 'http', then we need to do a quick string manipulation to switch schemes.
		 */
		if ($tls === self::TLS_FORCE)
		{
			$uri->setScheme('https');
		}
		elseif ($tls === self::TLS_DISABLE)
		{
			$uri->setScheme('http');
		}

		// Set scheme if requested or
		if ($absolute || $tls > 0)
		{
			static $scheme_host_port;

			if (!is_array($scheme_host_port))
			{
				$uri2             = Uri::getInstance();
				$scheme_host_port = array($uri2->getScheme(), $uri2->getHost(), $uri2->getPort());
			}

			if (is_null($uri->getScheme()))
			{
				$uri->setScheme($scheme_host_port[0]);
			}

			$uri->setHost($scheme_host_port[1]);
			$uri->setPort($scheme_host_port[2]);

			$scheme = array_merge($scheme, array('host', 'port', 'scheme'));
		}

		$url = $uri->toString($scheme);

		// Replace spaces.
		$url = preg_replace('/\s/u', '%20', $url);

		if ($xhtml)
		{
			$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
		}

		return $url;
	}
}
src/Router/AdministratorRouter.php000064400000002076152177723700013365 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Uri\Uri;

/**
 * Class to create and parse routes
 *
 * @since  1.5
 */
class AdministratorRouter extends Router
{
	/**
	 * Function to convert a route to an internal URI.
	 *
	 * @param   Uri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		return array();
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL
	 *
	 * @return  Uri  The absolute search engine friendly URL
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		// Create the URI object
		$uri = parent::build($url);

		// Get the path data
		$route = $uri->getPath();

		// Add basepath to the uri
		$uri->setPath(Uri::root(true) . '/' . basename(JPATH_ADMINISTRATOR) . '/' . $route);

		return $uri;
	}
}
src/Router/Router.php000064400000040262152177723700010623 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Router;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\ApplicationHelper;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Router\Exception\RouteNotFoundException;

/**
 * Class to create and parse routes
 *
 * @since  1.5
 */
class Router
{
	/**
	 * Mask for the before process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_BEFORE = 'preprocess';

	/**
	 * Mask for the during process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_DURING = '';

	/**
	 * Mask for the after process stage
	 *
	 * @var    string
	 * @since  3.4
	 */
	const PROCESS_AFTER = 'postprocess';

	/**
	 * The rewrite mode
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0
	 */
	protected $mode = null;

	/**
	 * The rewrite mode
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0
	 */
	protected $_mode = null;

	/**
	 * An array of variables
	 *
	 * @var     array
	 * @since   1.5
	 */
	protected $vars = array();

	/**
	 * An array of variables
	 *
	 * @var     array
	 * @since  1.5
	 * @deprecated  4.0 Will convert to $vars
	 */
	protected $_vars = array();

	/**
	 * An array of rules
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected $rules = array(
		'buildpreprocess' => array(),
		'build' => array(),
		'buildpostprocess' => array(),
		'parsepreprocess' => array(),
		'parse' => array(),
		'parsepostprocess' => array(),
	);

	/**
	 * An array of rules
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  4.0 Will convert to $rules
	 */
	protected $_rules = array(
		'buildpreprocess' => array(),
		'build' => array(),
		'buildpostprocess' => array(),
		'parsepreprocess' => array(),
		'parse' => array(),
		'parsepostprocess' => array(),
	);

	/**
	 * Caching of processed URIs
	 *
	 * @var    array
	 * @since  3.3
	 */
	protected $cache = array();

	/**
	 * Router instances container.
	 *
	 * @var    Router[]
	 * @since  1.7
	 */
	protected static $instances = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $options  Array of options
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
		if (array_key_exists('mode', $options))
		{
			$this->_mode = $options['mode'];
		}
		else
		{
			$this->_mode = JROUTER_MODE_RAW;
		}
	}

	/**
	 * Returns the global Router object, only creating it if it
	 * doesn't already exist.
	 *
	 * @param   string  $client   The name of the client
	 * @param   array   $options  An associative array of options
	 *
	 * @return  Router  A Router object.
	 *
	 * @since   1.5
	 * @throws  \RuntimeException
	 */
	public static function getInstance($client, $options = array())
	{
		if (empty(self::$instances[$client]))
		{
			// Create a Router object
			$classname = 'JRouter' . ucfirst($client);

			if (!class_exists($classname))
			{
				// @deprecated 4.0 Everything in this block is deprecated but the warning is only logged after the file_exists
				// Load the router object
				$info = ApplicationHelper::getClientInfo($client, true);

				if (is_object($info))
				{
					$path = $info->path . '/includes/router.php';

					\JLoader::register($classname, $path);

					if (class_exists($classname))
					{
						\JLog::add('Non-autoloadable Router subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
					}
				}
			}

			if (class_exists($classname))
			{
				self::$instances[$client] = new $classname($options);
			}
			else
			{
				throw new \RuntimeException(\JText::sprintf('JLIB_APPLICATION_ERROR_ROUTER_LOAD', $client), 500);
			}
		}

		return self::$instances[$client];
	}

	/**
	 * Function to convert a route to an internal URI
	 *
	 * @param   \JUri  &$uri  The uri.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function parse(&$uri)
	{
		// Do the preprocess stage of the URL build process
		$vars = $this->processParseRules($uri, self::PROCESS_BEFORE);

		// Process the parsed variables based on custom defined rules
		// This is the main parse stage
		$vars += $this->_processParseRules($uri);

		// Parse RAW URL
		if ($this->_mode == JROUTER_MODE_RAW)
		{
			$vars += $this->_parseRawRoute($uri);
		}

		// Parse SEF URL
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			$vars += $this->_parseSefRoute($uri);
		}

		// Do the postprocess stage of the URL build process
		$vars += $this->processParseRules($uri, self::PROCESS_AFTER);

		// Check if all parts of the URL have been parsed.
		// Otherwise we have an invalid URL
		if (strlen($uri->getPath()) > 0 && array_key_exists('option', $vars)
			&& ComponentHelper::getParams($vars['option'])->get('sef_advanced', 0))
		{
			throw new RouteNotFoundException('URL invalid');
		}

		return array_merge($this->getVars(), $vars);
	}

	/**
	 * Function to convert an internal URI to a route
	 *
	 * @param   string  $url  The internal URL or an associative array
	 *
	 * @return  \JUri  The absolute search engine friendly URL object
	 *
	 * @since   1.5
	 */
	public function build($url)
	{
		$key = md5(serialize($url));

		if (isset($this->cache[$key]))
		{
			return clone $this->cache[$key];
		}

		// Create the URI object
		$uri = $this->createUri($url);

		// Do the preprocess stage of the URL build process
		$this->processBuildRules($uri, self::PROCESS_BEFORE);

		// Process the uri information based on custom defined rules.
		// This is the main build stage
		$this->_processBuildRules($uri);

		// Build RAW URL
		if ($this->_mode == JROUTER_MODE_RAW)
		{
			$this->_buildRawRoute($uri);
		}

		// Build SEF URL : mysite/route/index.php?var=x
		if ($this->_mode == JROUTER_MODE_SEF)
		{
			$this->_buildSefRoute($uri);
		}

		// Do the postprocess stage of the URL build process
		$this->processBuildRules($uri, self::PROCESS_AFTER);

		$this->cache[$key] = clone $uri;

		return $uri;
	}

	/**
	 * Get the router mode
	 *
	 * @return  integer
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public function getMode()
	{
		return $this->_mode;
	}

	/**
	 * Set the router mode
	 *
	 * @param   integer  $mode  The routing mode.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public function setMode($mode)
	{
		$this->_mode = $mode;
	}

	/**
	 * Set a router variable, creating it if it doesn't exist
	 *
	 * @param   string   $key     The name of the variable
	 * @param   mixed    $value   The value of the variable
	 * @param   boolean  $create  If True, the variable will be created if it doesn't exist yet
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function setVar($key, $value, $create = true)
	{
		if ($create || array_key_exists($key, $this->_vars))
		{
			$this->_vars[$key] = $value;
		}
	}

	/**
	 * Set the router variable array
	 *
	 * @param   array    $vars   An associative array with variables
	 * @param   boolean  $merge  If True, the array will be merged instead of overwritten
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function setVars($vars = array(), $merge = true)
	{
		if ($merge)
		{
			$this->_vars = array_merge($this->_vars, $vars);
		}
		else
		{
			$this->_vars = $vars;
		}
	}

	/**
	 * Get a router variable
	 *
	 * @param   string  $key  The name of the variable
	 *
	 * @return  mixed  Value of the variable
	 *
	 * @since   1.5
	 */
	public function getVar($key)
	{
		$result = null;

		if (isset($this->_vars[$key]))
		{
			$result = $this->_vars[$key];
		}

		return $result;
	}

	/**
	 * Get the router variable array
	 *
	 * @return  array  An associative array of router variables
	 *
	 * @since   1.5
	 */
	public function getVars()
	{
		return $this->_vars;
	}

	/**
	 * Attach a build rule
	 *
	 * @param   callable  $callback  The function to be called
	 * @param   string    $stage     The stage of the build process that
	 *                               this should be added to. Possible values:
	 *                               'preprocess', '' for the main build process,
	 *                               'postprocess'
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function attachBuildRule($callback, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('build' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not registered. (%s)', $stage, __METHOD__));
		}

		$this->_rules['build' . $stage][] = $callback;
	}

	/**
	 * Attach a parse rule
	 *
	 * @param   callable  $callback  The function to be called.
	 * @param   string    $stage     The stage of the parse process that
	 *                               this should be added to. Possible values:
	 *                               'preprocess', '' for the main parse process,
	 *                               'postprocess'
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public function attachParseRule($callback, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('parse' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not registered. (%s)', $stage, __METHOD__));
		}

		$this->_rules['parse' . $stage][] = $callback;
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function _parseRawRoute(&$uri)
	{
		return $this->parseRawRoute($uri);
	}

	/**
	 * Function to convert a raw route to an internal URI
	 *
	 * @param   \JUri  &$uri  The raw route
	 *
	 * @return  array  Array of variables
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseRawRoute(&$uri)
	{
		return array();
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  string  Internal URI
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function _parseSefRoute(&$uri)
	{
		return $this->parseSefRoute($uri);
	}

	/**
	 * Function to convert a sef route to an internal URI
	 *
	 * @param   \JUri  &$uri  The sef URI
	 *
	 * @return  array  Array of variables
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main parse stage
	 */
	protected function parseSefRoute(&$uri)
	{
		return array();
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function _buildRawRoute(&$uri)
	{
		return $this->buildRawRoute($uri);
	}

	/**
	 * Function to build a raw route
	 *
	 * @param   \JUri  &$uri  The internal URL
	 *
	 * @return  string  Raw Route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildRawRoute(&$uri)
	{
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  string  The SEF route
	 *
	 * @since   1.5
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function _buildSefRoute(&$uri)
	{
		return $this->buildSefRoute($uri);
	}

	/**
	 * Function to build a sef route
	 *
	 * @param   \JUri  &$uri  The uri
	 *
	 * @return  string  The SEF route
	 *
	 * @since   3.2
	 * @deprecated  4.0  Attach your logic as rule to the main build stage
	 */
	protected function buildSefRoute(&$uri)
	{
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri  &$uri  The URI to parse
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use processParseRules() instead
	 */
	protected function _processParseRules(&$uri)
	{
		return $this->processParseRules($uri);
	}

	/**
	 * Process the parsed router variables based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI to parse
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess', 'postprocess'
	 *                          and '' for the main parse stage
	 *
	 * @return  array  The array of processed URI variables
	 *
	 * @since   3.2
	 */
	protected function processParseRules(&$uri, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('parse' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not registered. (%s)', $stage, __METHOD__));
		}

		$vars = array();

		foreach ($this->_rules['parse' . $stage] as $rule)
		{
			$vars += (array) call_user_func_array($rule, array(&$this, &$uri));
		}

		return $vars;
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri  &$uri  The URI
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use processBuildRules() instead
	 */
	protected function _processBuildRules(&$uri)
	{
		$this->processBuildRules($uri);
	}

	/**
	 * Process the build uri query data based on custom defined rules
	 *
	 * @param   \JUri   &$uri   The URI
	 * @param   string  $stage  The stage that should be processed.
	 *                          Possible values: 'preprocess', 'postprocess'
	 *                          and '' for the main build stage
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected function processBuildRules(&$uri, $stage = self::PROCESS_DURING)
	{
		if (!array_key_exists('build' . $stage, $this->_rules))
		{
			throw new \InvalidArgumentException(sprintf('The %s stage is not registered. (%s)', $stage, __METHOD__));
		}

		foreach ($this->_rules['build' . $stage] as $rule)
		{
			call_user_func_array($rule, array(&$this, &$uri));
		}
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI
	 *
	 * @return  \JUri
	 *
	 * @since   1.5
	 * @deprecated  4.0  Use createUri() instead
	 * @codeCoverageIgnore
	 */
	protected function _createUri($url)
	{
		return $this->createUri($url);
	}

	/**
	 * Create a uri based on a full or partial URL string
	 *
	 * @param   string  $url  The URI or an associative array
	 *
	 * @return  \JUri
	 *
	 * @since   3.2
	 */
	protected function createUri($url)
	{
		if (!is_array($url) && substr($url, 0, 1) !== '&')
		{
			return new \JUri($url);
		}

		$uri = new \JUri('index.php');

		if (is_string($url))
		{
			$vars = array();

			if (strpos($url, '&amp;') !== false)
			{
				$url = str_replace('&amp;', '&', $url);
			}

			parse_str($url, $vars);
		}
		else
		{
			$vars = $url;
		}

		$vars = array_merge($this->getVars(), $vars);

		foreach ($vars as $key => $var)
		{
			if ($var == '')
			{
				unset($vars[$key]);
			}
		}

		$uri->setQuery($vars);

		return $uri;
	}

	/**
	 * Encode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of encoded route segments
	 *
	 * @since   1.5
	 * @deprecated  4.0  This should be performed in the component router instead
	 * @codeCoverageIgnore
	 */
	protected function _encodeSegments($segments)
	{
		return $this->encodeSegments($segments);
	}

	/**
	 * Encode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of encoded route segments
	 *
	 * @since   3.2
	 * @deprecated  4.0  This should be performed in the component router instead
	 */
	protected function encodeSegments($segments)
	{
		$total = count($segments);

		for ($i = 0; $i < $total; $i++)
		{
			$segments[$i] = str_replace(':', '-', $segments[$i]);
		}

		return $segments;
	}

	/**
	 * Decode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of decoded route segments
	 *
	 * @since   1.5
	 * @deprecated  4.0  This should be performed in the component router instead
	 * @codeCoverageIgnore
	 */
	protected function _decodeSegments($segments)
	{
		return $this->decodeSegments($segments);
	}

	/**
	 * Decode route segments
	 *
	 * @param   array  $segments  An array of route segments
	 *
	 * @return  array  Array of decoded route segments
	 *
	 * @since   3.2
	 * @deprecated  4.0  This should be performed in the component router instead
	 */
	protected function decodeSegments($segments)
	{
		$total = count($segments);

		for ($i = 0; $i < $total; $i++)
		{
			$segments[$i] = preg_replace('/-/', ':', $segments[$i], 1);
		}

		return $segments;
	}
}
src/Router/Exception/RouteNotFoundException.php000064400000001637152177723700015736 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Router\Exception;

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an error for a missing route
 *
 * @since  3.8.0
 */
class RouteNotFoundException extends \InvalidArgumentException
{
	/**
	 * Constructor
	 *
	 * @param   string      $message   The Exception message to throw.
	 * @param   integer     $code      The Exception code.
	 * @param   \Exception  $previous  The previous exception used for the exception chaining.
	 *
	 * @since   3.8.0
	 */
	public function __construct($message = '', $code = 404, \Exception $previous = null)
	{
		if (empty($message))
		{
			$message = 'URL was not found';
		}

		parent::__construct($message, $code, $previous);
	}
}
src/Client/FtpClient.php000064400000130536152177723700011175 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Client;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Log\Log;
use Joomla\CMS\Utility\BufferStreamHandler;

/** Error Codes:
 * - 30 : Unable to connect to host
 * - 31 : Not connected
 * - 32 : Unable to send command to server
 * - 33 : Bad username
 * - 34 : Bad password
 * - 35 : Bad response
 * - 36 : Passive mode failed
 * - 37 : Data transfer error
 * - 38 : Local filesystem error
 */

if (!defined('CRLF'))
{
	/**
	 * Constant defining a line break
	 *
	 * @var    string
	 * @since  1.5
	 */
	define('CRLF', "\r\n");
}

if (!defined('FTP_AUTOASCII'))
{
	/**
	 * Constant defining whether the FTP connection type will automatically determine ASCII support based on a file extension
	 *
	 * @var    integer
	 * @since  1.5
	 */
	define('FTP_AUTOASCII', -1);
}

if (!defined('FTP_BINARY'))
{
	/**
	 * Stub of the native FTP_BINARY constant if PHP is running without the ftp extension enabled
	 *
	 * @var    integer
	 * @since  1.5
	 */
	define('FTP_BINARY', 1);
}

if (!defined('FTP_ASCII'))
{
	/**
	 * Stub of the native FTP_ASCII constant if PHP is running without the ftp extension enabled
	 *
	 * @var    integer
	 * @since  1.5
	 */
	define('FTP_ASCII', 0);
}

if (!defined('FTP_NATIVE'))
{
	/**
	 * Constant defining whether native FTP support is available on the platform
	 *
	 * @var    integer
	 * @since  1.5
	 */
	define('FTP_NATIVE', function_exists('ftp_connect') ? 1 : 0);
}

/**
 * FTP client class
 *
 * @since  1.5
 */
class FtpClient
{
	/**
	 * @var    resource  Socket resource
	 * @since  1.5
	 */
	protected $_conn = null;

	/**
	 * @var    resource  Data port connection resource
	 * @since  1.5
	 */
	protected $_dataconn = null;

	/**
	 * @var    array  Passive connection information
	 * @since  1.5
	 */
	protected $_pasv = null;

	/**
	 * @var    string  Response Message
	 * @since  1.5
	 */
	protected $_response = null;

	/**
	 * @var    integer  Timeout limit
	 * @since  1.5
	 */
	protected $_timeout = 15;

	/**
	 * @var    integer  Transfer Type
	 * @since  1.5
	 */
	protected $_type = null;

	/**
	 * @var    array  Array to hold ascii format file extensions
	 * @since  1.5
	 */
	protected $_autoAscii = array(
		'asp',
		'bat',
		'c',
		'cpp',
		'csv',
		'h',
		'htm',
		'html',
		'shtml',
		'ini',
		'inc',
		'log',
		'php',
		'php3',
		'pl',
		'perl',
		'sh',
		'sql',
		'txt',
		'xhtml',
		'xml',
	);

	/**
	 * Array to hold native line ending characters
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected $_lineEndings = array('UNIX' => "\n", 'WIN' => "\r\n");

	/**
	 * @var    array  FtpClient instances container.
	 * @since  2.5
	 */
	protected static $instances = array();

	/**
	 * FtpClient object constructor
	 *
	 * @param   array  $options  Associative array of options to set
	 *
	 * @since   1.5
	 */
	public function __construct(array $options = array())
	{
		// If default transfer type is not set, set it to autoascii detect
		if (!isset($options['type']))
		{
			$options['type'] = FTP_BINARY;
		}

		$this->setOptions($options);

		if (FTP_NATIVE)
		{
			BufferStreamHandler::stream_register();
		}
	}

	/**
	 * FtpClient object destructor
	 *
	 * Closes an existing connection, if we have one
	 *
	 * @since   1.5
	 */
	public function __destruct()
	{
		if (is_resource($this->_conn))
		{
			$this->quit();
		}
	}

	/**
	 * Returns the global FTP connector object, only creating it
	 * if it doesn't already exist.
	 *
	 * You may optionally specify a username and password in the parameters. If you do so,
	 * you may not login() again with different credentials using the same object.
	 * If you do not use this option, you must quit() the current connection when you
	 * are done, to free it for use by others.
	 *
	 * @param   string  $host     Host to connect to
	 * @param   string  $port     Port to connect to
	 * @param   array   $options  Array with any of these options: type=>[FTP_AUTOASCII|FTP_ASCII|FTP_BINARY], timeout=>(int)
	 * @param   string  $user     Username to use for a connection
	 * @param   string  $pass     Password to use for a connection
	 *
	 * @return  FtpClient        The FTP Client object.
	 *
	 * @since   1.5
	 */
	public static function getInstance($host = '127.0.0.1', $port = '21', array $options = array(), $user = null, $pass = null)
	{
		$signature = $user . ':' . $pass . '@' . $host . ':' . $port;

		// Create a new instance, or set the options of an existing one
		if (!isset(static::$instances[$signature]) || !is_object(static::$instances[$signature]))
		{
			static::$instances[$signature] = new static($options);
		}
		else
		{
			static::$instances[$signature]->setOptions($options);
		}

		// Connect to the server, and login, if requested
		if (!static::$instances[$signature]->isConnected())
		{
			$return = static::$instances[$signature]->connect($host, $port);

			if ($return && $user !== null && $pass !== null)
			{
				static::$instances[$signature]->login($user, $pass);
			}
		}

		return static::$instances[$signature];
	}

	/**
	 * Set client options
	 *
	 * @param   array  $options  Associative array of options to set
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function setOptions(array $options)
	{
		if (isset($options['type']))
		{
			$this->_type = $options['type'];
		}

		if (isset($options['timeout']))
		{
			$this->_timeout = $options['timeout'];
		}

		return true;
	}

	/**
	 * Method to connect to a FTP server
	 *
	 * @param   string  $host  Host to connect to [Default: 127.0.0.1]
	 * @param   string  $port  Port to connect on [Default: port 21]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.0.0
	 */
	public function connect($host = '127.0.0.1', $port = 21)
	{
		$errno = null;
		$err = null;

		// If already connected, return
		if (is_resource($this->_conn))
		{
			return true;
		}

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			$this->_conn = @ftp_connect($host, $port, $this->_timeout);

			if ($this->_conn === false)
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_NO_CONNECT', $host, $port), Log::WARNING, 'jerror');

				return false;
			}

			// Set the timeout for this connection
			ftp_set_option($this->_conn, FTP_TIMEOUT_SEC, $this->_timeout);

			return true;
		}

		// Connect to the FTP server.
		$this->_conn = @ fsockopen($host, $port, $errno, $err, $this->_timeout);

		if (!$this->_conn)
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_NO_CONNECT_SOCKET', $host, $port, $errno, $err), Log::WARNING, 'jerror');

			return false;
		}

		// Set the timeout for this connection
		socket_set_timeout($this->_conn, $this->_timeout, 0);

		// Check for welcome response code
		if (!$this->_verifyResponse(220))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_RESPONSE', $this->_response), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to determine if the object is connected to an FTP server
	 *
	 * @return  boolean  True if connected
	 *
	 * @since   1.5
	 */
	public function isConnected()
	{
		return is_resource($this->_conn);
	}

	/**
	 * Method to login to a server once connected
	 *
	 * @param   string  $user  Username to login to the server
	 * @param   string  $pass  Password to login to the server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function login($user = 'anonymous', $pass = 'jftp@joomla.org')
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_login($this->_conn, $user, $pass) === false)
			{
				Log::add('JFtp::login: Unable to login', Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send the username
		if (!$this->_putCmd('USER ' . $user, array(331, 503)))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_USERNAME', $this->_response, $user), Log::WARNING, 'jerror');

			return false;
		}

		// If we are already logged in, continue :)
		if ($this->_responseCode == 503)
		{
			return true;
		}

		// Send the password
		if (!$this->_putCmd('PASS ' . $pass, 230))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_BAD_PASSWORD', $this->_response, str_repeat('*', strlen($pass))), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to quit and close the connection
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function quit()
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			@ftp_close($this->_conn);

			return true;
		}

		// Logout and close connection
		@fwrite($this->_conn, "QUIT\r\n");
		@fclose($this->_conn);

		return true;
	}

	/**
	 * Method to retrieve the current working directory on the FTP server
	 *
	 * @return  string   Current working directory
	 *
	 * @since   1.5
	 */
	public function pwd()
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (($ret = @ftp_pwd($this->_conn)) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PWD_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return $ret;
		}

		$match = array(null);

		// Send print working directory command and verify success
		if (!$this->_putCmd('PWD', 257))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PWD_BAD_RESPONSE', $this->_response), Log::WARNING, 'jerror');

			return false;
		}

		// Match just the path
		preg_match('/"[^"\r\n]*"/', $this->_response, $match);

		// Return the cleaned path
		return preg_replace("/\"/", '', $match[0]);
	}

	/**
	 * Method to system string from the FTP server
	 *
	 * @return  string   System identifier string
	 *
	 * @since   1.5
	 */
	public function syst()
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			if (($ret = @ftp_systype($this->_conn)) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_SYST_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}
		}
		else
		{
			// Send print working directory command and verify success
			if (!$this->_putCmd('SYST', 215))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_SYST_BAD_RESPONSE', $this->_response), Log::WARNING, 'jerror');

				return false;
			}

			$ret = $this->_response;
		}

		// Match the system string to an OS
		if (strpos(strtoupper($ret), 'MAC') !== false)
		{
			$ret = 'MAC';
		}
		elseif (strpos(strtoupper($ret), 'WIN') !== false)
		{
			$ret = 'WIN';
		}
		else
		{
			$ret = 'UNIX';
		}

		// Return the os type
		return $ret;
	}

	/**
	 * Method to change the current working directory on the FTP server
	 *
	 * @param   string  $path  Path to change into on the server
	 *
	 * @return  boolean True if successful
	 *
	 * @since   1.5
	 */
	public function chdir($path)
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			if (@ftp_chdir($this->_conn, $path) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CHDIR_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send change directory command and verify success
		if (!$this->_putCmd('CWD ' . $path, 250))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CHDIR_BAD_RESPONSE', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to reinitialise the server, ie. need to login again
	 *
	 * NOTE: This command not available on all servers
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function reinit()
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->_conn, 'REIN') === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_REINIT_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send reinitialise command to the server
		if (!$this->_putCmd('REIN', 220))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_REINIT_BAD_RESPONSE', $this->_response), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to rename a file/folder on the FTP server
	 *
	 * @param   string  $from  Path to change file/folder from
	 * @param   string  $to    Path to change file/folder to
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function rename($from, $to)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_rename($this->_conn, $from, $to) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send rename from command to the server
		if (!$this->_putCmd('RNFR ' . $from, 350))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_FROM', $this->_response, $from), Log::WARNING, 'jerror');

			return false;
		}

		// Send rename to command to the server
		if (!$this->_putCmd('RNTO ' . $to, 250))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RENAME_BAD_RESPONSE_TO', $this->_response, $to), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to change mode for a path on the FTP server
	 *
	 * @param   string  $path  Path to change mode on
	 * @param   mixed   $mode  Octal value to change mode to, e.g. '0777', 0777 or 511 (string or integer)
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function chmod($path, $mode)
	{
		// If no filename is given, we assume the current directory is the target
		if ($path == '')
		{
			$path = '.';
		}

		// Convert the mode to a string
		if (is_int($mode))
		{
			$mode = decoct($mode);
		}

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->_conn, 'CHMOD ' . $mode . ' ' . $path) === false)
			{
				if (!IS_WIN)
				{
					Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CHMOD_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');
				}

				return false;
			}

			return true;
		}

		// Send change mode command and verify success [must convert mode from octal]
		if (!$this->_putCmd('SITE CHMOD ' . $mode . ' ' . $path, array(200, 250)))
		{
			if (!IS_WIN)
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CHMOD_BAD_RESPONSE', $this->_response, $path, $mode), Log::WARNING, 'jerror');
			}

			return false;
		}

		return true;
	}

	/**
	 * Method to delete a path [file/folder] on the FTP server
	 *
	 * @param   string  $path  Path to delete
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function delete($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_delete($this->_conn, $path) === false)
			{
				if (@ftp_rmdir($this->_conn, $path) === false)
				{
					Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_DELETE_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

					return false;
				}
			}

			return true;
		}

		// Send delete file command and if that doesn't work, try to remove a directory
		if (!$this->_putCmd('DELE ' . $path, 250))
		{
			if (!$this->_putCmd('RMD ' . $path, 250))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_DELETE_BAD_RESPONSE', $this->_response, $path), Log::WARNING, 'jerror');

				return false;
			}
		}

		return true;
	}

	/**
	 * Method to create a directory on the FTP server
	 *
	 * @param   string  $path  Directory to create
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function mkdir($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_mkdir($this->_conn, $path) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_MKDIR_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send change directory command and verify success
		if (!$this->_putCmd('MKD ' . $path, 257))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MKDIR_BAD_RESPONSE', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to restart data transfer at a given byte
	 *
	 * @param   integer  $point  Byte to restart transfer at
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function restart($point)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->_conn, 'REST ' . $point) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_RESTART_BAD_RESPONSE_NATIVE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		// Send restart command and verify success
		if (!$this->_putCmd('REST ' . $point, 350))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_RESTART_BAD_RESPONSE', $this->_response, $point), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to create an empty file on the FTP server
	 *
	 * @param   string  $path  Path local file to store on the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function create($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			$buffer = fopen('buffer://tmp', 'r');

			if (@ftp_fput($this->_conn, $path, $buffer, FTP_ASCII) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_BUFFER'), Log::WARNING, 'jerror');
				fclose($buffer);

				return false;
			}

			fclose($buffer);

			return true;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		if (!$this->_putCmd('STOR ' . $path, array(150, 125)))
		{
			@ fclose($this->_dataconn);
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		// To create a zero byte upload close the data port connection
		fclose($this->_dataconn);

		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_CREATE_BAD_RESPONSE_TRANSFER', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to read a file from the FTP server's contents into a buffer
	 *
	 * @param   string  $remote   Path to remote file to read on the FTP server
	 * @param   string  &$buffer  Buffer variable to read file contents into
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function read($remote, &$buffer)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			$tmp = fopen('buffer://tmp', 'br+');

			if (@ftp_fget($this->_conn, $tmp, $remote, $mode) === false)
			{
				fclose($tmp);
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_BUFFER'), Log::WARNING, 'jerror');

				return false;
			}

			// Read tmp buffer contents
			rewind($tmp);
			$buffer = '';

			while (!feof($tmp))
			{
				$buffer .= fread($tmp, 8192);
			}

			fclose($tmp);

			return true;
		}

		$this->_mode($mode);

		// Start passive mode
		if (!$this->_passive())
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
		{
			@ fclose($this->_dataconn);
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		// Read data from data port connection and add to the buffer
		$buffer = '';

		while (!feof($this->_dataconn))
		{
			$buffer .= fread($this->_dataconn, 4096);
		}

		// Close the data port connection
		fclose($this->_dataconn);

		// Let's try to cleanup some line endings if it is ascii
		if ($mode == FTP_ASCII)
		{
			$os = 'UNIX';

			if (IS_WIN)
			{
				$os = 'WIN';
			}

			$buffer = preg_replace('/' . CRLF . '/', $this->_lineEndings[$os], $buffer);
		}

		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_READ_BAD_RESPONSE_TRANSFER', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to get a file from the FTP server and save it to a local file
	 *
	 * @param   string  $local   Local path to save remote file to
	 * @param   string  $remote  Path to remote file to get on the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function get($local, $remote)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			if (@ftp_get($this->_conn, $local, $remote, $mode) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		$this->_mode($mode);

		// Check to see if the local file can be opened for writing
		$fp = fopen($local, 'wb');

		if (!$fp)
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_WRITING_LOCAL', $local), Log::WARNING, 'jerror');

			return false;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_GET_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
		{
			@ fclose($this->_dataconn);
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE_RETR', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		// Read data from data port connection and add to the buffer
		while (!feof($this->_dataconn))
		{
			$buffer = fread($this->_dataconn, 4096);
			fwrite($fp, $buffer, 4096);
		}

		// Close the data port connection and file pointer
		fclose($this->_dataconn);
		fclose($fp);

		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_GET_BAD_RESPONSE_TRANSFER', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to store a file to the FTP server
	 *
	 * @param   string  $local   Path to local file to store on the FTP server
	 * @param   string  $remote  FTP path to file to create
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function store($local, $remote = null)
	{
		// If remote file is not given, use the filename of the local file in the current
		// working directory.
		if ($remote == null)
		{
			$remote = basename($local);
		}

		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			if (@ftp_put($this->_conn, $remote, $local, $mode) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE'), Log::WARNING, 'jerror');

				return false;
			}

			return true;
		}

		$this->_mode($mode);

		// Check to see if the local file exists and if so open it for reading
		if (@ file_exists($local))
		{
			$fp = fopen($local, 'rb');

			if (!$fp)
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_READING_LOCAL', $local), Log::WARNING, 'jerror');

				return false;
			}
		}
		else
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_FIND_LOCAL', $local), Log::WARNING, 'jerror');

			return false;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			@ fclose($fp);
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		// Send store command to the FTP server
		if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
		{
			@ fclose($fp);
			@ fclose($this->_dataconn);
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE_STOR', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		// Do actual file transfer, read local file and write to data port connection
		while (!feof($fp))
		{
			$line = fread($fp, 4096);

			do
			{
				if (($result = @ fwrite($this->_dataconn, $line)) === false)
				{
					Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_STORE_DATA_PORT'), Log::WARNING, 'jerror');

					return false;
				}

				$line = substr($line, $result);
			}
			while ($line != '');
		}

		fclose($fp);
		fclose($this->_dataconn);

		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_STORE_BAD_RESPONSE_TRANSFER', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to write a string to the FTP server
	 *
	 * @param   string  $remote  FTP path to file to write to
	 * @param   string  $buffer  Contents to write to the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	public function write($remote, $buffer)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			$tmp = fopen('buffer://tmp', 'br+');
			fwrite($tmp, $buffer);
			rewind($tmp);

			if (@ftp_fput($this->_conn, $remote, $tmp, $mode) === false)
			{
				fclose($tmp);
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE'), Log::WARNING, 'jerror');

				return false;
			}

			fclose($tmp);

			return true;
		}

		// First we need to set the transfer mode
		$this->_mode($mode);

		// Start passive mode
		if (!$this->_passive())
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		// Send store command to the FTP server
		if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE_STOR', $this->_response, $remote), Log::WARNING, 'jerror');
			@ fclose($this->_dataconn);

			return false;
		}

		// Write buffer to the data connection port
		do
		{
			if (($result = @ fwrite($this->_dataconn, $buffer)) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_WRITE_DATA_PORT'), Log::WARNING, 'jerror');

				return false;
			}

			$buffer = substr($buffer, $result);
		}
		while ($buffer != '');

		// Close the data connection port [Data transfer complete]
		fclose($this->_dataconn);

		// Verify that the server received the transfer
		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_WRITE_BAD_RESPONSE_TRANSFER', $this->_response, $remote), Log::WARNING, 'jerror');

			return false;
		}

		return true;
	}

	/**
	 * Method to append a string to the FTP server
	 *
	 * @param   string  $remote  FTP path to file to append to
	 * @param   string  $buffer  Contents to append to the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.6.0
	 */
	public function append($remote, $buffer)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_PASSIVE'), 36);
			}

			$tmp = fopen('buffer://tmp', 'bw+');
			fwrite($tmp, $buffer);
			rewind($tmp);

			$size = $this->size($remote);

			if ($size === false)
			{
			}

			if (@ftp_fput($this->_conn, $remote, $tmp, $mode, $size) === false)
			{
				fclose($tmp);

				throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE'), 35);
			}

			fclose($tmp);

			return true;
		}

		// First we need to set the transfer mode
		$this->_mode($mode);

		// Start passive mode
		if (!$this->_passive())
		{
			throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_PASSIVE'), 36);
		}

		// Send store command to the FTP server
		if (!$this->_putCmd('APPE ' . $remote, array(150, 125)))
		{
			@fclose($this->_dataconn);

			throw new \RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE_APPE', $this->_response, $remote), 35);
		}

		// Write buffer to the data connection port
		do
		{
			if (($result = @ fwrite($this->_dataconn, $buffer)) === false)
			{
				throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_APPEND_DATA_PORT'), 37);
			}

			$buffer = substr($buffer, $result);
		}
		while ($buffer != '');

		// Close the data connection port [Data transfer complete]
		fclose($this->_dataconn);

		// Verify that the server received the transfer
		if (!$this->_verifyResponse(226))
		{
			throw new \RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_APPEND_BAD_RESPONSE_TRANSFER', $this->_response, $remote), 37);
		}

		return true;
	}

	/**
	 * Get the size of the remote file.
	 *
	 * @param   string  $remote  FTP path to file whose size to get
	 *
	 * @return  mixed  number of bytes or false on error
	 *
	 * @since   3.6.0
	 */
	public function size($remote)
	{
		if (FTP_NATIVE)
		{
			$size = ftp_size($this->_conn, $remote);

			// In case ftp_size fails, try the SIZE command directly.
			if ($size === -1)
			{
				$response = ftp_raw($this->_conn, 'SIZE ' . $remote);
				$responseCode = substr($response[0], 0, 3);
				$responseMessage = substr($response[0], 4);

				if ($responseCode != '213')
				{
					throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_SIZE_BAD_RESPONSE'), 35);
				}

				$size = (int) $responseMessage;
			}

			return $size;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			throw new \RuntimeException(\JText::_('JLIB_CLIENT_ERROR_JFTP_SIZE_PASSIVE'), 36);
		}

		// Send size command to the FTP server
		if (!$this->_putCmd('SIZE ' . $remote, array(213)))
		{
			@fclose($this->_dataconn);

			throw new \RuntimeException(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_SIZE_BAD_RESPONSE', $this->_response, $remote), 35);
		}

		return (int) substr($this->_responseMsg, 4);
	}

	/**
	 * Method to list the filenames of the contents of a directory on the FTP server
	 *
	 * Note: Some servers also return folder names. However, to be sure to list folders on all
	 * servers, you should use listDetails() instead if you also need to deal with folders
	 *
	 * @param   string  $path  Path local file to store on the FTP server
	 *
	 * @return  string  Directory listing
	 *
	 * @since   1.5
	 */
	public function listNames($path = null)
	{
		$data = null;

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			if (($list = @ftp_nlist($this->_conn, $path)) === false)
			{
				// Workaround for empty directories on some servers
				if ($this->listDetails($path, 'files') === array())
				{
					return array();
				}

				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE'), Log::WARNING, 'jerror');

				return false;
			}

			$list = preg_replace('#^' . preg_quote($path, '#') . '[/\\\\]?#', '', $list);

			if ($keys = array_merge(array_keys($list, '.'), array_keys($list, '..')))
			{
				foreach ($keys as $key)
				{
					unset($list[$key]);
				}
			}

			return $list;
		}

		// If a path exists, prepend a space
		if ($path != null)
		{
			$path = ' ' . $path;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_PASSIVE'), Log::WARNING, 'jerror');

			return false;
		}

		if (!$this->_putCmd('NLST' . $path, array(150, 125)))
		{
			@ fclose($this->_dataconn);

			// Workaround for empty directories on some servers
			if ($this->listDetails($path, 'files') === array())
			{
				return array();
			}

			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE_NLST', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		// Read in the file listing.
		while (!feof($this->_dataconn))
		{
			$data .= fread($this->_dataconn, 4096);
		}

		fclose($this->_dataconn);

		// Everything go okay?
		if (!$this->_verifyResponse(226))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTNAMES_BAD_RESPONSE_TRANSFER', $this->_response, $path), Log::WARNING, 'jerror');

			return false;
		}

		$data = preg_split('/[' . CRLF . ']+/', $data, -1, PREG_SPLIT_NO_EMPTY);
		$data = preg_replace('#^' . preg_quote(substr($path, 1), '#') . '[/\\\\]?#', '', $data);

		if ($keys = array_merge(array_keys($data, '.'), array_keys($data, '..')))
		{
			foreach ($keys as $key)
			{
				unset($data[$key]);
			}
		}

		return $data;
	}

	/**
	 * Method to list the contents of a directory on the FTP server
	 *
	 * @param   string  $path  Path to the local file to be stored on the FTP server
	 * @param   string  $type  Return type [raw|all|folders|files]
	 *
	 * @return  mixed  If $type is raw: string Directory listing, otherwise array of string with file-names
	 *
	 * @since   1.5
	 */
	public function listDetails($path = null, $type = 'all')
	{
		$dir_list = array();
		$data = null;
		$regs = null;

		// TODO: Deal with recurse -- nightmare
		// For now we will just set it to false
		$recurse = false;

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->_conn, true) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			if (($contents = @ftp_rawlist($this->_conn, $path)) === false)
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE'), Log::WARNING, 'jerror');

				return false;
			}
		}
		else
		{
			// Non Native mode

			// Start passive mode
			if (!$this->_passive())
			{
				Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_PASSIVE'), Log::WARNING, 'jerror');

				return false;
			}

			// If a path exists, prepend a space
			if ($path != null)
			{
				$path = ' ' . $path;
			}

			// Request the file listing
			if (!$this->_putCmd(($recurse == true) ? 'LIST -R' : 'LIST' . $path, array(150, 125)))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE_LIST', $this->_response, $path), Log::WARNING, 'jerror');
				@ fclose($this->_dataconn);

				return false;
			}

			// Read in the file listing.
			while (!feof($this->_dataconn))
			{
				$data .= fread($this->_dataconn, 4096);
			}

			fclose($this->_dataconn);

			// Everything go okay?
			if (!$this->_verifyResponse(226))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_BAD_RESPONSE_TRANSFER', $this->_response, $path), Log::WARNING, 'jerror');

				return false;
			}

			$contents = explode(CRLF, $data);
		}

		// If only raw output is requested we are done
		if ($type == 'raw')
		{
			return $data;
		}

		// If we received the listing of an empty directory, we are done as well
		if (empty($contents[0]))
		{
			return $dir_list;
		}

		// If the server returned the number of results in the first response, let's dump it
		if (strtolower(substr($contents[0], 0, 6)) == 'total ')
		{
			array_shift($contents);

			if (!isset($contents[0]) || empty($contents[0]))
			{
				return $dir_list;
			}
		}

		// Regular expressions for the directory listing parsing.
		$regexps = array(
			'UNIX' => '#([-dl][rwxstST-]+).* ([0-9]*) ([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
				. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{1,2}:[0-9]{2})|[0-9]{4}) (.+)#',
			'MAC' => '#([-dl][rwxstST-]+).* ?([0-9 ]*)?([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
				. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{2}:[0-9]{2})|[0-9]{4}) (.+)#',
			'WIN' => '#([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)#',
		);

		// Find out the format of the directory listing by matching one of the regexps
		$osType = null;

		foreach ($regexps as $k => $v)
		{
			if (@preg_match($v, $contents[0]))
			{
				$osType = $k;
				$regexp = $v;
				break;
			}
		}

		if (!$osType)
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_LISTDETAILS_UNRECOGNISED'), Log::WARNING, 'jerror');

			return false;
		}

		// Here is where it is going to get dirty....
		if ($osType == 'UNIX' || $osType == 'MAC')
		{
			foreach ($contents as $file)
			{
				$tmp_array = null;

				if (@preg_match($regexp, $file, $regs))
				{
					$fType = (int) strpos('-dl', $regs[1]{0});

					// $tmp_array['line'] = $regs[0];
					$tmp_array['type']   = $fType;
					$tmp_array['rights'] = $regs[1];

					// $tmp_array['number'] = $regs[2];
					$tmp_array['user']  = $regs[3];
					$tmp_array['group'] = $regs[4];
					$tmp_array['size']  = $regs[5];
					$tmp_array['date']  = @date('m-d', strtotime($regs[6]));
					$tmp_array['time']  = $regs[7];
					$tmp_array['name']  = $regs[9];
				}

				// If we just want files, do not add a folder
				if ($type == 'files' && $tmp_array['type'] == 1)
				{
					continue;
				}

				// If we just want folders, do not add a file
				if ($type == 'folders' && $tmp_array['type'] == 0)
				{
					continue;
				}

				if (is_array($tmp_array) && $tmp_array['name'] != '.' && $tmp_array['name'] != '..')
				{
					$dir_list[] = $tmp_array;
				}
			}
		}
		else
		{
			foreach ($contents as $file)
			{
				$tmp_array = null;

				if (@preg_match($regexp, $file, $regs))
				{
					$fType = (int) ($regs[7] == '<DIR>');
					$timestamp = strtotime("$regs[3]-$regs[1]-$regs[2] $regs[4]:$regs[5]$regs[6]");

					// $tmp_array['line'] = $regs[0];
					$tmp_array['type'] = $fType;
					$tmp_array['rights'] = '';

					// $tmp_array['number'] = 0;
					$tmp_array['user'] = '';
					$tmp_array['group'] = '';
					$tmp_array['size'] = (int) $regs[7];
					$tmp_array['date'] = date('m-d', $timestamp);
					$tmp_array['time'] = date('H:i', $timestamp);
					$tmp_array['name'] = $regs[8];
				}

				// If we just want files, do not add a folder
				if ($type == 'files' && $tmp_array['type'] == 1)
				{
					continue;
				}

				// If we just want folders, do not add a file
				if ($type == 'folders' && $tmp_array['type'] == 0)
				{
					continue;
				}

				if (is_array($tmp_array) && $tmp_array['name'] != '.' && $tmp_array['name'] != '..')
				{
					$dir_list[] = $tmp_array;
				}
			}
		}

		return $dir_list;
	}

	/**
	 * Send command to the FTP server and validate an expected response code
	 *
	 * @param   string  $cmd               Command to send to the FTP server
	 * @param   mixed   $expectedResponse  Integer response code or array of integer response codes
	 *
	 * @return  boolean  True if command executed successfully
	 *
	 * @since   1.5
	 */
	protected function _putCmd($cmd, $expectedResponse)
	{
		// Make sure we have a connection to the server
		if (!is_resource($this->_conn))
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PUTCMD_UNCONNECTED'), Log::WARNING, 'jerror');

			return false;
		}

		// Send the command to the server
		if (!fwrite($this->_conn, $cmd . "\r\n"))
		{
			Log::add(\JText::sprintf('DDD', \JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PUTCMD_SEND', $cmd)), Log::WARNING, 'jerror');
		}

		return $this->_verifyResponse($expectedResponse);
	}

	/**
	 * Verify the response code from the server and log response if flag is set
	 *
	 * @param   mixed  $expected  Integer response code or array of integer response codes
	 *
	 * @return  boolean  True if response code from the server is expected
	 *
	 * @since   1.5
	 */
	protected function _verifyResponse($expected)
	{
		$parts = null;

		// Wait for a response from the server, but timeout after the set time limit
		$endTime = time() + $this->_timeout;
		$this->_response = '';

		do
		{
			$this->_response .= fgets($this->_conn, 4096);
		}
		while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\1)? [^' . CRLF . ']+' . CRLF . "$/", $this->_response, $parts) && time() < $endTime);

		// Catch a timeout or bad response
		if (!isset($parts[1]))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_VERIFYRESPONSE', $this->_response), Log::WARNING, 'jerror');

			return false;
		}

		// Separate the code from the message
		$this->_responseCode = $parts[1];
		$this->_responseMsg = $parts[0];

		// Did the server respond with the code we wanted?
		if (is_array($expected))
		{
			if (in_array($this->_responseCode, $expected))
			{
				$retval = true;
			}
			else
			{
				$retval = false;
			}
		}
		else
		{
			if ($this->_responseCode == $expected)
			{
				$retval = true;
			}
			else
			{
				$retval = false;
			}
		}

		return $retval;
	}

	/**
	 * Set server to passive mode and open a data port connection
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	protected function _passive()
	{
		$match = array();
		$parts = array();
		$errno = null;
		$err = null;

		// Make sure we have a connection to the server
		if (!is_resource($this->_conn))
		{
			Log::add(\JText::_('JLIB_CLIENT_ERROR_JFTP_PASSIVE_CONNECT_PORT'), Log::WARNING, 'jerror');

			return false;
		}

		// Request a passive connection - this means, we'll talk to you, you don't talk to us.
		@ fwrite($this->_conn, "PASV\r\n");

		// Wait for a response from the server, but timeout after the set time limit
		$endTime = time() + $this->_timeout;
		$this->_response = '';

		do
		{
			$this->_response .= fgets($this->_conn, 4096);
		}
		while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\1)? [^' . CRLF . ']+' . CRLF . "$/", $this->_response, $parts) && time() < $endTime);

		// Catch a timeout or bad response
		if (!isset($parts[1]))
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_RESPONSE', $this->_response), Log::WARNING, 'jerror');

			return false;
		}

		// Separate the code from the message
		$this->_responseCode = $parts[1];
		$this->_responseMsg = $parts[0];

		// If it's not 227, we weren't given an IP and port, which means it failed.
		if ($this->_responseCode != '227')
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_IP_OBTAIN', $this->_responseMsg), Log::WARNING, 'jerror');

			return false;
		}

		// Snatch the IP and port information, or die horribly trying...
		if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $this->_responseMsg, $match) == 0)
		{
			Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_IP_VALID', $this->_responseMsg), Log::WARNING, 'jerror');

			return false;
		}

		// This is pretty simple - store it for later use ;).
		$this->_pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]);

		// Connect, assuming we've got a connection.
		$this->_dataconn = @fsockopen($this->_pasv['ip'], $this->_pasv['port'], $errno, $err, $this->_timeout);

		if (!$this->_dataconn)
		{
			Log::add(
				\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_PASSIVE_CONNECT', $this->_pasv['ip'], $this->_pasv['port'], $errno, $err),
				Log::WARNING,
				'jerror'
			);

			return false;
		}

		// Set the timeout for this connection
		socket_set_timeout($this->_conn, $this->_timeout, 0);

		return true;
	}

	/**
	 * Method to find out the correct transfer mode for a specific file
	 *
	 * @param   string  $fileName  Name of the file
	 *
	 * @return  integer Transfer-mode for this filetype [FTP_ASCII|FTP_BINARY]
	 *
	 * @since   1.5
	 */
	protected function _findMode($fileName)
	{
		if ($this->_type == FTP_AUTOASCII)
		{
			$dot = strrpos($fileName, '.') + 1;
			$ext = substr($fileName, $dot);

			if (in_array($ext, $this->_autoAscii))
			{
				$mode = FTP_ASCII;
			}
			else
			{
				$mode = FTP_BINARY;
			}
		}
		elseif ($this->_type == FTP_ASCII)
		{
			$mode = FTP_ASCII;
		}
		else
		{
			$mode = FTP_BINARY;
		}

		return $mode;
	}

	/**
	 * Set transfer mode
	 *
	 * @param   integer  $mode  Integer representation of data transfer mode [1:Binary|0:Ascii]
	 * Defined constants can also be used [FTP_BINARY|FTP_ASCII]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 */
	protected function _mode($mode)
	{
		if ($mode == FTP_BINARY)
		{
			if (!$this->_putCmd('TYPE I', 200))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MODE_BINARY', $this->_response), Log::WARNING, 'jerror');

				return false;
			}
		}
		else
		{
			if (!$this->_putCmd('TYPE A', 200))
			{
				Log::add(\JText::sprintf('JLIB_CLIENT_ERROR_JFTP_MODE_ASCII', $this->_response), Log::WARNING, 'jerror');

				return false;
			}
		}

		return true;
	}
}
src/Client/ClientHelper.php000064400000014552152177723700011662 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Client;

defined('JPATH_PLATFORM') or die;

/**
 * Client helper class
 *
 * @since  1.7.0
 */
class ClientHelper
{
	/**
	 * Method to return the array of client layer configuration options
	 *
	 * @param   string   $client  Client name, currently only 'ftp' is supported
	 * @param   boolean  $force   Forces re-creation of the login credentials. Set this to
	 *                            true if login credentials in the session storage have changed
	 *
	 * @return  array    Client layer configuration options, consisting of at least
	 *                   these fields: enabled, host, port, user, pass, root
	 *
	 * @since   1.7.0
	 */
	public static function getCredentials($client, $force = false)
	{
		static $credentials = array();

		$client = strtolower($client);

		if (!isset($credentials[$client]) || $force)
		{
			$config = \JFactory::getConfig();

			// Fetch the client layer configuration options for the specific client
			switch ($client)
			{
				case 'ftp':
					$options = array(
						'enabled' => $config->get('ftp_enable'),
						'host' => $config->get('ftp_host'),
						'port' => $config->get('ftp_port'),
						'user' => $config->get('ftp_user'),
						'pass' => $config->get('ftp_pass'),
						'root' => $config->get('ftp_root'),
					);
					break;

				default:
					$options = array('enabled' => false, 'host' => '', 'port' => '', 'user' => '', 'pass' => '', 'root' => '');
					break;
			}

			// If user and pass are not set in global config lets see if they are in the session
			if ($options['enabled'] == true && ($options['user'] == '' || $options['pass'] == ''))
			{
				$session = \JFactory::getSession();
				$options['user'] = $session->get($client . '.user', null, 'JClientHelper');
				$options['pass'] = $session->get($client . '.pass', null, 'JClientHelper');
			}

			// If user or pass are missing, disable this client
			if ($options['user'] == '' || $options['pass'] == '')
			{
				$options['enabled'] = false;
			}

			// Save the credentials for later use
			$credentials[$client] = $options;
		}

		return $credentials[$client];
	}

	/**
	 * Method to set client login credentials
	 *
	 * @param   string  $client  Client name, currently only 'ftp' is supported
	 * @param   string  $user    Username
	 * @param   string  $pass    Password
	 *
	 * @return  boolean  True if the given login credentials have been set and are valid
	 *
	 * @since   1.7.0
	 */
	public static function setCredentials($client, $user, $pass)
	{
		$return = false;
		$client = strtolower($client);

		// Test if the given credentials are valid
		switch ($client)
		{
			case 'ftp':
				$config = \JFactory::getConfig();
				$options = array('enabled' => $config->get('ftp_enable'), 'host' => $config->get('ftp_host'), 'port' => $config->get('ftp_port'));

				if ($options['enabled'])
				{
					$ftp = FtpClient::getInstance($options['host'], $options['port']);

					// Test the connection and try to log in
					if ($ftp->isConnected())
					{
						if ($ftp->login($user, $pass))
						{
							$return = true;
						}

						$ftp->quit();
					}
				}
				break;

			default:
				break;
		}

		if ($return)
		{
			// Save valid credentials to the session
			$session = \JFactory::getSession();
			$session->set($client . '.user', $user, 'JClientHelper');
			$session->set($client . '.pass', $pass, 'JClientHelper');

			// Force re-creation of the data saved within JClientHelper::getCredentials()
			self::getCredentials($client, true);
		}

		return $return;
	}

	/**
	 * Method to determine if client login credentials are present
	 *
	 * @param   string  $client  Client name, currently only 'ftp' is supported
	 *
	 * @return  boolean  True if login credentials are available
	 *
	 * @since   1.7.0
	 */
	public static function hasCredentials($client)
	{
		$return = false;
		$client = strtolower($client);

		// Get (unmodified) credentials for this client
		switch ($client)
		{
			case 'ftp':
				$config = \JFactory::getConfig();
				$options = array('enabled' => $config->get('ftp_enable'), 'user' => $config->get('ftp_user'), 'pass' => $config->get('ftp_pass'));
				break;

			default:
				$options = array('enabled' => false, 'user' => '', 'pass' => '');
				break;
		}

		if ($options['enabled'] == false)
		{
			// The client is disabled in global config, so let's pretend we are OK
			$return = true;
		}
		elseif ($options['user'] != '' && $options['pass'] != '')
		{
			// Login credentials are available in global config
			$return = true;
		}
		else
		{
			// Check if login credentials are available in the session
			$session = \JFactory::getSession();
			$user = $session->get($client . '.user', null, 'JClientHelper');
			$pass = $session->get($client . '.pass', null, 'JClientHelper');

			if ($user != '' && $pass != '')
			{
				$return = true;
			}
		}

		return $return;
	}

	/**
	 * Determine whether input fields for client settings need to be shown
	 *
	 * If valid credentials were passed along with the request, they are saved to the session.
	 * This functions returns an exception if invalid credentials have been given or if the
	 * connection to the server failed for some other reason.
	 *
	 * @param   string  $client  The name of the client.
	 *
	 * @return  mixed  True, if FTP settings; JError if using legacy tree.
	 *
	 * @since   1.7.0
	 * @throws  \InvalidArgumentException if credentials invalid
	 */
	public static function setCredentialsFromRequest($client)
	{
		// Determine whether FTP credentials have been passed along with the current request
		$input = \JFactory::getApplication()->input;
		$user = $input->post->getString('username', null);
		$pass = $input->post->getString('password', null);

		if ($user != '' && $pass != '')
		{
			// Add credentials to the session
			if (self::setCredentials($client, $user, $pass))
			{
				$return = false;
			}
			else
			{
				if (class_exists('JError'))
				{
					$return = \JError::raiseWarning(500, \JText::_('JLIB_CLIENT_ERROR_HELPER_SETCREDENTIALSFROMREQUEST_FAILED'));
				}
				else
				{
					throw new \InvalidArgumentException('Invalid user credentials');
				}
			}
		}
		else
		{
			// Just determine if the FTP input fields need to be shown
			$return = !self::hasCredentials('ftp');
		}

		return $return;
	}
}
src/Client/ClientWrapper.php000064400000004357152177723700012065 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Client;

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for ClientHelper
 *
 * @package     Joomla.Platform
 * @subpackage  Client
 * @since       3.4
 * @deprecated  4.0 Will be removed without replacement
 */
class ClientWrapper
{
	/**
	 * Helper wrapper method for getCredentials
	 *
	 * @param   string   $client  Client name, currently only 'ftp' is supported
	 * @param   boolean  $force   Forces re-creation of the login credentials. Set this to
	 *
	 * @return  array    Client layer configuration options, consisting of at least
	 *
	 * @see     ClientHelper::getCredentials()
	 * @since   3.4
	 */
	public function getCredentials($client, $force = false)
	{
		return ClientHelper::getCredentials($client, $force);
	}

	/**
	 * Helper wrapper method for setCredentials
	 *
	 * @param   string  $client  Client name, currently only 'ftp' is supported
	 * @param   string  $user    Username
	 * @param   string  $pass    Password
	 *
	 * @return boolean  True if the given login credentials have been set and are valid
	 *
	 * @see     ClientHelper::setCredentials()
	 * @since   3.4
	 */
	public function setCredentials($client, $user, $pass)
	{
		return ClientHelper::setCredentials($client, $user, $pass);
	}

	/**
	 * Helper wrapper method for hasCredentials
	 *
	 * @param   string  $client  Client name, currently only 'ftp' is supported
	 *
	 * @return boolean  True if login credentials are available
	 *
	 * @see     ClientHelper::hasCredentials()
	 * @since   3.4
	 */
	public function hasCredentials($client)
	{
		return ClientHelper::hasCredentials($client);
	}

	/**
	 * Helper wrapper method for setCredentialsFromRequest
	 *
	 * @param   string  $client  The name of the client.
	 *
	 * @return  mixed  True, if FTP settings; JError if using legacy tree
	 *
	 * @see     UserHelper::setCredentialsFromRequest()
	 * @since   3.4
	 * @throws  \InvalidArgumentException if credentials invalid
	 */
	public function setCredentialsFromRequest($client)
	{
		return ClientHelper::setCredentialsFromRequest($client);
	}
}
src/Microdata/Microdata.php000064400000047076152177723700011703 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Microdata;

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform class for interacting with Microdata semantics.
 *
 * @since  3.2
 */
class Microdata
{
	/**
	 * Array with all available Types and Properties from the http://schema.org vocabulary
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $types = null;

	/**
	 * The Type
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = null;

	/**
	 * The Property
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $property = null;

	/**
	 * The Human content
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $content = null;

	/**
	 * The Machine content
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $machineContent = null;

	/**
	 * The Fallback Type
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $fallbackType = null;

	/**
	 * The Fallback Property
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $fallbackProperty = null;

	/**
	 * Used for checking if the library output is enabled or disabled
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $enabled = true;

	/**
	 * Initialize the class and setup the default $Type
	 *
	 * @param   string   $type  Optional, fallback to 'Thing' Type
	 * @param   boolean  $flag  Enable or disable the library output
	 *
	 * @since   3.2
	 */
	public function __construct($type = '', $flag = true)
	{
		if ($this->enabled = (boolean) $flag)
		{
			// Fallback to 'Thing' Type
			if (!$type)
			{
				$type = 'Thing';
			}

			$this->setType($type);
		}
	}

	/**
	 * Load all available Types and Properties from the http://schema.org vocabulary contained in the types.json file
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	protected static function loadTypes()
	{
		// Load the JSON
		if (!static::$types)
		{
			$path = __DIR__ . '/types.json';
			static::$types = json_decode(file_get_contents($path), true);
		}
	}

	/**
	 * Reset all params
	 *
	 * @return void
	 *
	 * @since   3.2
	 */
	protected function resetParams()
	{
		$this->content          = null;
		$this->machineContent   = null;
		$this->property         = null;
		$this->fallbackProperty = null;
		$this->fallbackType     = null;
	}

	/**
	 * Enable or Disable the library output
	 *
	 * @param   boolean  $flag  Enable or disable the library output
	 *
	 * @return  Microdata  Instance of $this
	 *
	 * @since   3.2
	 */
	public function enable($flag = true)
	{
		$this->enabled = (boolean) $flag;

		return $this;
	}

	/**
	 * Return 'true' if the library output is enabled
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public function isEnabled()
	{
		return $this->enabled;
	}

	/**
	 * Set a new http://schema.org Type
	 *
	 * @param   string  $type  The $Type to be setup
	 *
	 * @return  Microdata  Instance of $this
	 *
	 * @since   3.2
	 */
	public function setType($type)
	{
		if (!$this->enabled)
		{
			return $this;
		}

		// Sanitize the Type
		$this->type = static::sanitizeType($type);

		// If the given $Type isn't available, fallback to 'Thing' Type
		if (!static::isTypeAvailable($this->type))
		{
			$this->type = 'Thing';
		}

		return $this;
	}

	/**
	 * Return the current $Type name
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getType()
	{
		return $this->type;
	}

	/**
	 * Setup a $Property
	 *
	 * @param   string  $property  The Property
	 *
	 * @return  Microdata  Instance of $this
	 *
	 * @since   3.2
	 */
	public function property($property)
	{
		if (!$this->enabled)
		{
			return $this;
		}

		// Sanitize the $Property
		$property = static::sanitizeProperty($property);

		// Control if the $Property exists in the given $Type and setup it, otherwise leave it 'NULL'
		if (static::isPropertyInType($this->type, $property))
		{
			$this->property = $property;
		}

		return $this;
	}

	/**
	 * Return the current $Property name
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getProperty()
	{
		return $this->property;
	}

	/**
	 * Setup a Human content or content for the Machines
	 *
	 * @param   string  $content         The human content or machine content to be used
	 * @param   string  $machineContent  The machine content
	 *
	 * @return  Microdata  Instance of $this
	 *
	 * @since   3.2
	 */
	public function content($content, $machineContent = null)
	{
		$this->content = $content;
		$this->machineContent = $machineContent;

		return $this;
	}

	/**
	 * Return the current $content
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getContent()
	{
		return $this->content;
	}

	/**
	 * Return the current $machineContent
	 *
	 * @return  string
	 *
	 * @since   3.3
	 */
	public function getMachineContent()
	{
		return $this->machineContent;
	}

	/**
	 * Setup a Fallback Type and Property
	 *
	 * @param   string  $type      The Fallback Type
	 * @param   string  $property  The Fallback Property
	 *
	 * @return  Microdata  Instance of $this
	 *
	 * @since   3.2
	 */
	public function fallback($type, $property)
	{
		if (!$this->enabled)
		{
			return $this;
		}

		// Sanitize the $Type
		$this->fallbackType = static::sanitizeType($type);

		// If the given $Type isn't available, fallback to 'Thing' Type
		if (!static::isTypeAvailable($this->fallbackType))
		{
			$this->fallbackType = 'Thing';
		}

		// Control if the $Property exist in the given $Type and setup it, otherwise leave it 'NULL'
		if (static::isPropertyInType($this->fallbackType, $property))
		{
			$this->fallbackProperty = $property;
		}
		else
		{
			$this->fallbackProperty = null;
		}

		return $this;
	}

	/**
	 * Return the current $fallbackType
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getFallbackType()
	{
		return $this->fallbackType;
	}

	/**
	 * Return the current $fallbackProperty
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function getFallbackProperty()
	{
		return $this->fallbackProperty;
	}

	/**
	 * This function handles the display logic.
	 * It checks if the Type, Property are available, if not check for a Fallback,
	 * then reset all params for the next use and return the HTML.
	 *
	 * @param   string   $displayType  Optional, 'inline', available options ['inline'|'span'|'div'|meta]
	 * @param   boolean  $emptyOutput  Return an empty string if the library output is disabled and there is a $content value
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function display($displayType = '', $emptyOutput = false)
	{
		// Initialize the HTML to output
		$html = ($this->content !== null && !$emptyOutput) ? $this->content : '';

		// Control if the library output is enabled, otherwise return the $content or an empty string
		if (!$this->enabled)
		{
			// Reset params
			$this->resetParams();

			return $html;
		}

		// If the $property is wrong for the current $Type check if a Fallback is available, otherwise return an empty HTML
		if ($this->property)
		{
			// Process and return the HTML the way the user expects to
			if ($displayType)
			{
				switch ($displayType)
				{
					case 'span':
						$html = static::htmlSpan($html, $this->property);
						break;

					case 'div':
						$html = static::htmlDiv($html, $this->property);
						break;

					case 'meta':
						$html = ($this->machineContent !== null) ? $this->machineContent : $html;
						$html = static::htmlMeta($html, $this->property);
						break;

					default:
						// Default $displayType = 'inline'
						$html = static::htmlProperty($this->property);
						break;
				}
			}
			else
			{
				/*
				 * Process and return the HTML in an automatic way,
				 * with the $Property expected Types and display everything in the right way,
				 * check if the $Property is 'normal', 'nested' or must be rendered in a metadata tag
				 */
				switch (static::getExpectedDisplayType($this->type, $this->property))
				{
					case 'nested':
						// Retrieve the expected 'nested' Type of the $Property
						$nestedType = static::getExpectedTypes($this->type, $this->property);
						$nestedProperty = '';

						// If there is a Fallback Type then probably it could be the expectedType
						if (in_array($this->fallbackType, $nestedType))
						{
							$nestedType = $this->fallbackType;

							if ($this->fallbackProperty)
							{
								$nestedProperty = $this->fallbackProperty;
							}
						}
						else
						{
							$nestedType = $nestedType[0];
						}

						// Check if a $content is available, otherwise fallback to an 'inline' display type
						if ($this->content !== null)
						{
							if ($nestedProperty)
							{
								$html = static::htmlSpan(
									$this->content,
									$nestedProperty
								);
							}

							$html = static::htmlSpan(
								$html,
								$this->property,
								$nestedType,
								true
							);
						}
						else
						{
							$html = static::htmlProperty($this->property) . ' ' . static::htmlScope($nestedType);

							if ($nestedProperty)
							{
								$html .= ' ' . static::htmlProperty($nestedProperty);
							}
						}

						break;

					case 'meta':
						// Check if a $content is available, otherwise fallback to an 'inline' display type
						if ($this->content !== null)
						{
							$html = ($this->machineContent !== null) ? $this->machineContent : $this->content;
							$html = static::htmlMeta($html, $this->property) . $this->content;
						}
						else
						{
							$html = static::htmlProperty($this->property);
						}

						break;

					default:
						/*
						 * Default expected display type = 'normal'
						 * Check if a $content is available,
						 * otherwise fallback to an 'inline' display type
						 */
						if ($this->content !== null)
						{
							$html = static::htmlSpan($this->content, $this->property);
						}
						else
						{
							$html = static::htmlProperty($this->property);
						}

						break;
				}
			}
		}
		elseif ($this->fallbackProperty)
		{
			// Process and return the HTML the way the user expects to
			if ($displayType)
			{
				switch ($displayType)
				{
					case 'span':
						$html = static::htmlSpan($html, $this->fallbackProperty, $this->fallbackType);
						break;

					case 'div':
						$html = static::htmlDiv($html, $this->fallbackProperty, $this->fallbackType);
						break;

					case 'meta':
						$html = ($this->machineContent !== null) ? $this->machineContent : $html;
						$html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
						break;

					default:
						// Default $displayType = 'inline'
						$html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
						break;
				}
			}
			else
			{
				/*
				 * Process and return the HTML in an automatic way,
				 * with the $Property expected Types an display everything in the right way,
				 * check if the Property is 'nested' or must be rendered in a metadata tag
				 */
				switch (static::getExpectedDisplayType($this->fallbackType, $this->fallbackProperty))
				{
					case 'meta':
						// Check if a $content is available, otherwise fallback to an 'inline' display Type
						if ($this->content !== null)
						{
							$html = ($this->machineContent !== null) ? $this->machineContent : $this->content;
							$html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
						}
						else
						{
							$html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
						}

						break;

					default:
						/*
						 * Default expected display type = 'normal'
						 * Check if a $content is available,
						 * otherwise fallback to an 'inline' display Type
						 */
						if ($this->content !== null)
						{
							$html = static::htmlSpan($this->content, $this->fallbackProperty);
							$html = static::htmlSpan($html, '', $this->fallbackType);
						}
						else
						{
							$html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
						}

						break;
				}
			}
		}
		elseif (!$this->fallbackProperty && $this->fallbackType !== null)
		{
			$html = static::htmlScope($this->fallbackType);
		}

		// Reset params
		$this->resetParams();

		return $html;
	}

	/**
	 * Return the HTML of the current Scope
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public function displayScope()
	{
		// Control if the library output is enabled, otherwise return the $content or empty string
		if (!$this->enabled)
		{
			return '';
		}

		return static::htmlScope($this->type);
	}

	/**
	 * Return the sanitized $Type
	 *
	 * @param   string  $type  The Type to sanitize
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function sanitizeType($type)
	{
		return ucfirst(trim($type));
	}

	/**
	 * Return the sanitized $Property
	 *
	 * @param   string  $property  The Property to sanitize
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function sanitizeProperty($property)
	{
		return lcfirst(trim($property));
	}

	/**
	 * Return an array with all available Types and Properties from the http://schema.org vocabulary
	 *
	 * @return  array
	 *
	 * @since   3.2
	 */
	public static function getTypes()
	{
		static::loadTypes();

		return static::$types;
	}

	/**
	 * Return an array with all available Types from the http://schema.org vocabulary
	 *
	 * @return  array
	 *
	 * @since   3.2
	 */
	public static function getAvailableTypes()
	{
		static::loadTypes();

		return array_keys(static::$types);
	}

	/**
	 * Return the expected Types of the given Property
	 *
	 * @param   string  $type      The Type to process
	 * @param   string  $property  The Property to process
	 *
	 * @return  array
	 *
	 * @since   3.2
	 */
	public static function getExpectedTypes($type, $property)
	{
		static::loadTypes();

		$tmp = static::$types[$type]['properties'];

		// Check if the $Property is in the $Type
		if (isset($tmp[$property]))
		{
			return $tmp[$property]['expectedTypes'];
		}

		// Check if the $Property is inherit
		$extendedType = static::$types[$type]['extends'];

		// Recursive
		if (!empty($extendedType))
		{
			return static::getExpectedTypes($extendedType, $property);
		}

		return array();
	}

	/**
	 * Return the expected display type: [normal|nested|meta]
	 * In which way to display the Property:
	 * normal -> itemprop="name"
	 * nested -> itemprop="director" itemscope itemtype="https://schema.org/Person"
	 * meta   -> `<meta itemprop="datePublished" content="1991-05-01">`
	 *
	 * @param   string  $type      The Type where to find the Property
	 * @param   string  $property  The Property to process
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	protected static function getExpectedDisplayType($type, $property)
	{
		$expectedTypes = static::getExpectedTypes($type, $property);

		// Retrieve the first expected type
		$type = $expectedTypes[0];

		// Check if it's a 'meta' display
		if ($type === 'Date' || $type === 'DateTime' || $property === 'interactionCount')
		{
			return 'meta';
		}

		// Check if it's a 'normal' display
		if ($type === 'Text' || $type === 'URL' || $type === 'Boolean' || $type === 'Number')
		{
			return 'normal';
		}

		// Otherwise it's a 'nested' display
		return 'nested';
	}

	/**
	 * Recursive function, control if the given Type has the given Property
	 *
	 * @param   string  $type      The Type where to check
	 * @param   string  $property  The Property to check
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public static function isPropertyInType($type, $property)
	{
		if (!static::isTypeAvailable($type))
		{
			return false;
		}

		// Control if the $Property exists, and return 'true'
		if (array_key_exists($property, static::$types[$type]['properties']))
		{
			return true;
		}

		// Recursive: Check if the $Property is inherit
		$extendedType = static::$types[$type]['extends'];

		if (!empty($extendedType))
		{
			return static::isPropertyInType($extendedType, $property);
		}

		return false;
	}

	/**
	 * Control if the given Type class is available
	 *
	 * @param   string  $type  The Type to check
	 *
	 * @return  boolean
	 *
	 * @since   3.2
	 */
	public static function isTypeAvailable($type)
	{
		static::loadTypes();

		return (array_key_exists($type, static::$types)) ? true : false;
	}

	/**
	 * Return Microdata semantics in a `<meta>` tag with content for machines.
	 *
	 * @param   string   $content   The machine content to display
	 * @param   string   $property  The Property
	 * @param   string   $scope     Optional, the Type scope to display
	 * @param   boolean  $invert    Optional, default = false, invert the $scope with the $property
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function htmlMeta($content, $property, $scope = '', $invert = false)
	{
		return static::htmlTag('meta', $content, $property, $scope, $invert);
	}

	/**
	 * Return Microdata semantics in a `<span>` tag.
	 *
	 * @param   string   $content   The human content
	 * @param   string   $property  Optional, the human content to display
	 * @param   string   $scope     Optional, the Type scope to display
	 * @param   boolean  $invert    Optional, default = false, invert the $scope with the $property
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function htmlSpan($content, $property = '', $scope = '', $invert = false)
	{
		return static::htmlTag('span', $content, $property, $scope, $invert);
	}

	/**
	 * Return Microdata semantics in a `<div>` tag.
	 *
	 * @param   string   $content   The human content
	 * @param   string   $property  Optional, the human content to display
	 * @param   string   $scope     Optional, the Type scope to display
	 * @param   boolean  $invert    Optional, default = false, invert the $scope with the $property
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function htmlDiv($content, $property = '', $scope = '', $invert = false)
	{
		return static::htmlTag('div', $content, $property, $scope, $invert);
	}

	/**
	 * Return Microdata semantics in a specified tag.
	 *
	 * @param   string   $tag       The HTML tag
	 * @param   string   $content   The human content
	 * @param   string   $property  Optional, the human content to display
	 * @param   string   $scope     Optional, the Type scope to display
	 * @param   boolean  $invert    Optional, default = false, invert the $scope with the $property
	 *
	 * @return  string
	 *
	 * @since   3.3
	 */
	public static function htmlTag($tag, $content, $property = '', $scope = '', $invert = false)
	{
		// Control if the $Property has already the 'itemprop' prefix
		if (!empty($property) && stripos($property, 'itemprop') !== 0)
		{
			$property = static::htmlProperty($property);
		}

		// Control if the $Scope have already the 'itemscope' prefix
		if (!empty($scope) && stripos($scope, 'itemscope') !== 0)
		{
			$scope = static::htmlScope($scope);
		}

		// Depending on the case, the $scope must precede the $property, or otherwise
		if ($invert)
		{
			$tmp = implode(' ', array($property, $scope));
		}
		else
		{
			$tmp = implode(' ', array($scope, $property));
		}

		$tmp = trim($tmp);
		$tmp = ($tmp) ? ' ' . $tmp : '';

		// Control if it is an empty element without a closing tag
		if ($tag === 'meta')
		{
			return "<meta$tmp content='$content'/>";
		}

		return '<' . $tag . $tmp . '>' . $content . '</' . $tag . '>';
	}

	/**
	 * Return the HTML Scope
	 *
	 * @param   string  $scope  The Scope to process
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function htmlScope($scope)
	{
		return "itemscope itemtype='https://schema.org/" . static::sanitizeType($scope) . "'";
	}

	/**
	 * Return the HTML Property
	 *
	 * @param   string  $property  The Property to process
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function htmlProperty($property)
	{
		return "itemprop='$property'";
	}
}
src/Microdata/types.json000064400000262330152177723700011316 0ustar00{"DataType":{"extends":"","properties":[]},"Boolean":{"extends":"DataType","properties":[]},"False":{"extends":"Boolean","properties":[]},"True":{"extends":"Boolean","properties":[]},"Date":{"extends":"DataType","properties":[]},"DateTime":{"extends":"DataType","properties":[]},"Number":{"extends":"DataType","properties":[]},"Float":{"extends":"Number","properties":[]},"Integer":{"extends":"Number","properties":[]},"Text":{"extends":"DataType","properties":[]},"URL":{"extends":"Text","properties":[]},"Time":{"extends":"DataType","properties":[]},"Thing":{"extends":"","properties":{"additionalType":{"expectedTypes":["URL"]},"alternateName":{"expectedTypes":["Text"]},"description":{"expectedTypes":["Text"]},"image":{"expectedTypes":["URL","ImageObject"]},"name":{"expectedTypes":["Text"]},"potentialAction":{"expectedTypes":["Action"]},"sameAs":{"expectedTypes":["URL"]},"url":{"expectedTypes":["URL"]}}},"Action":{"extends":"Thing","properties":{"actionStatus":{"expectedTypes":["ActionStatusType"]},"agent":{"expectedTypes":["Organization","Person"]},"endTime":{"expectedTypes":["DateTime"]},"error":{"expectedTypes":["Thing"]},"instrument":{"expectedTypes":["Thing"]},"location":{"expectedTypes":["PostalAddress","Place"]},"object":{"expectedTypes":["Thing"]},"participant":{"expectedTypes":["Organization","Person"]},"result":{"expectedTypes":["Thing"]},"startTime":{"expectedTypes":["DateTime"]},"target":{"expectedTypes":["EntryPoint"]}}},"AchieveAction":{"extends":"Action","properties":[]},"LoseAction":{"extends":"AchieveAction","properties":{"winner":{"expectedTypes":["Person"]}}},"TieAction":{"extends":"AchieveAction","properties":[]},"WinAction":{"extends":"AchieveAction","properties":{"loser":{"expectedTypes":["Person"]}}},"AssessAction":{"extends":"Action","properties":[]},"ChooseAction":{"extends":"AssessAction","properties":{"option":{"expectedTypes":["Text","Thing"]}}},"VoteAction":{"extends":"ChooseAction","properties":{"candidate":{"expectedTypes":["Person"]}}},"IgnoreAction":{"extends":"AssessAction","properties":[]},"ReactAction":{"extends":"AssessAction","properties":[]},"AgreeAction":{"extends":"ReactAction","properties":[]},"DisagreeAction":{"extends":"ReactAction","properties":[]},"DislikeAction":{"extends":"ReactAction","properties":[]},"EndorseAction":{"extends":"ReactAction","properties":{"endorsee":{"expectedTypes":["Organization","Person"]}}},"LikeAction":{"extends":"ReactAction","properties":[]},"WantAction":{"extends":"ReactAction","properties":[]},"ReviewAction":{"extends":"AssessAction","properties":{"resultReview":{"expectedTypes":["Review"]}}},"ConsumeAction":{"extends":"Action","properties":{"expectsAcceptanceOf":{"expectedTypes":["Offer"]}}},"DrinkAction":{"extends":"ConsumeAction","properties":[]},"EatAction":{"extends":"ConsumeAction","properties":[]},"InstallAction":{"extends":"ConsumeAction","properties":[]},"ListenAction":{"extends":"ConsumeAction","properties":[]},"ReadAction":{"extends":"ConsumeAction","properties":[]},"UseAction":{"extends":"ConsumeAction","properties":[]},"WearAction":{"extends":"UseAction","properties":[]},"ViewAction":{"extends":"ConsumeAction","properties":[]},"WatchAction":{"extends":"ConsumeAction","properties":[]},"ControlAction":{"extends":"Action","properties":[]},"ActivateAction":{"extends":"ControlAction","properties":[]},"DeactivateAction":{"extends":"ControlAction","properties":[]},"ResumeAction":{"extends":"ControlAction","properties":[]},"SuspendAction":{"extends":"ControlAction","properties":[]},"CreateAction":{"extends":"Action","properties":[]},"CookAction":{"extends":"CreateAction","properties":{"foodEstablishment":{"expectedTypes":["FoodEstablishment","Place"]},"foodEvent":{"expectedTypes":["FoodEvent"]},"recipe":{"expectedTypes":["Recipe"]}}},"DrawAction":{"extends":"CreateAction","properties":[]},"FilmAction":{"extends":"CreateAction","properties":[]},"PaintAction":{"extends":"CreateAction","properties":[]},"PhotographAction":{"extends":"CreateAction","properties":[]},"WriteAction":{"extends":"CreateAction","properties":{"language":{"expectedTypes":["Language"]}}},"FindAction":{"extends":"Action","properties":[]},"CheckAction":{"extends":"FindAction","properties":[]},"DiscoverAction":{"extends":"FindAction","properties":[]},"TrackAction":{"extends":"FindAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"InteractAction":{"extends":"Action","properties":[]},"BefriendAction":{"extends":"InteractAction","properties":[]},"CommunicateAction":{"extends":"InteractAction","properties":{"about":{"expectedTypes":["Thing"]},"language":{"expectedTypes":["Language"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"AskAction":{"extends":"CommunicateAction","properties":{"question":{"expectedTypes":["Text"]}}},"CheckInAction":{"extends":"CommunicateAction","properties":[]},"CheckOutAction":{"extends":"CommunicateAction","properties":[]},"CommentAction":{"extends":"CommunicateAction","properties":[]},"InformAction":{"extends":"CommunicateAction","properties":{"event":{"expectedTypes":["Event"]}}},"ConfirmAction":{"extends":"InformAction","properties":[]},"RsvpAction":{"extends":"InformAction","properties":{"additionalNumberOfGuests":{"expectedTypes":["Number"]},"rsvpResponse":{"expectedTypes":["RsvpResponseType"]}}},"InviteAction":{"extends":"CommunicateAction","properties":{"event":{"expectedTypes":["Event"]}}},"ReplyAction":{"extends":"CommunicateAction","properties":[]},"ShareAction":{"extends":"CommunicateAction","properties":[]},"FollowAction":{"extends":"InteractAction","properties":{"followee":{"expectedTypes":["Organization","Person"]}}},"JoinAction":{"extends":"InteractAction","properties":{"event":{"expectedTypes":["Event"]}}},"LeaveAction":{"extends":"InteractAction","properties":{"event":{"expectedTypes":["Event"]}}},"MarryAction":{"extends":"InteractAction","properties":[]},"RegisterAction":{"extends":"InteractAction","properties":[]},"SubscribeAction":{"extends":"InteractAction","properties":[]},"UnRegisterAction":{"extends":"InteractAction","properties":[]},"MoveAction":{"extends":"Action","properties":{"fromLocation":{"expectedTypes":["Place"]},"toLocation":{"expectedTypes":["Place"]}}},"ArriveAction":{"extends":"MoveAction","properties":[]},"DepartAction":{"extends":"MoveAction","properties":[]},"TravelAction":{"extends":"MoveAction","properties":{"distance":{"expectedTypes":["Distance"]}}},"OrganizeAction":{"extends":"Action","properties":[]},"AllocateAction":{"extends":"OrganizeAction","properties":{"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]}}},"AcceptAction":{"extends":"AllocateAction","properties":[]},"AssignAction":{"extends":"AllocateAction","properties":[]},"AuthorizeAction":{"extends":"AllocateAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"RejectAction":{"extends":"AllocateAction","properties":[]},"ApplyAction":{"extends":"OrganizeAction","properties":[]},"BookmarkAction":{"extends":"OrganizeAction","properties":[]},"PlanAction":{"extends":"OrganizeAction","properties":{"scheduledTime":{"expectedTypes":["DateTime"]}}},"CancelAction":{"extends":"PlanAction","properties":[]},"ReserveAction":{"extends":"PlanAction","properties":[]},"ScheduleAction":{"extends":"PlanAction","properties":[]},"PlayAction":{"extends":"Action","properties":{"audience":{"expectedTypes":["Audience"]},"event":{"expectedTypes":["Event"]}}},"ExerciseAction":{"extends":"PlayAction","properties":{"course":{"expectedTypes":["Place"]},"diet":{"expectedTypes":["Diet"]},"distance":{"expectedTypes":["Distance"]},"exercisePlan":{"expectedTypes":["ExercisePlan"]},"exerciseType":{"expectedTypes":["Text"]},"fromLocation":{"expectedTypes":["Place"]},"opponent":{"expectedTypes":["Person"]},"sportsActivityLocation":{"expectedTypes":["SportsActivityLocation"]},"sportsEvent":{"expectedTypes":["SportsEvent"]},"sportsTeam":{"expectedTypes":["SportsTeam"]},"toLocation":{"expectedTypes":["Place"]}}},"PerformAction":{"extends":"PlayAction","properties":{"entertainmentBusiness":{"expectedTypes":["EntertainmentBusiness"]}}},"SearchAction":{"extends":"Action","properties":{"query":{"expectedTypes":["Text","Class"]}}},"TradeAction":{"extends":"Action","properties":{"price":{"expectedTypes":["Text","Number"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]}}},"BuyAction":{"extends":"TradeAction","properties":{"seller":{"expectedTypes":["Organization","Person"]},"warrantyPromise":{"expectedTypes":["WarrantyPromise"]}}},"DonateAction":{"extends":"TradeAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"OrderAction":{"extends":"TradeAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"PayAction":{"extends":"TradeAction","properties":{"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"QuoteAction":{"extends":"TradeAction","properties":[]},"RentAction":{"extends":"TradeAction","properties":{"landlord":{"expectedTypes":["Organization","Person"]},"realEstateAgent":{"expectedTypes":["RealEstateAgent"]}}},"SellAction":{"extends":"TradeAction","properties":{"buyer":{"expectedTypes":["Person"]},"warrantyPromise":{"expectedTypes":["WarrantyPromise"]}}},"TipAction":{"extends":"TradeAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"TransferAction":{"extends":"Action","properties":{"fromLocation":{"expectedTypes":["Place"]},"toLocation":{"expectedTypes":["Place"]}}},"BorrowAction":{"extends":"TransferAction","properties":{"lender":{"expectedTypes":["Person"]}}},"DownloadAction":{"extends":"TransferAction","properties":[]},"GiveAction":{"extends":"TransferAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"LendAction":{"extends":"TransferAction","properties":{"borrower":{"expectedTypes":["Person"]}}},"ReceiveAction":{"extends":"TransferAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]},"sender":{"expectedTypes":["Organization","Person","Audience"]}}},"ReturnAction":{"extends":"TransferAction","properties":{"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"SendAction":{"extends":"TransferAction","properties":{"deliveryMethod":{"expectedTypes":["DeliveryMethod"]},"recipient":{"expectedTypes":["Organization","Person","Audience"]}}},"TakeAction":{"extends":"TransferAction","properties":[]},"UpdateAction":{"extends":"Action","properties":{"collection":{"expectedTypes":["Thing"]}}},"AddAction":{"extends":"UpdateAction","properties":[]},"InsertAction":{"extends":"AddAction","properties":{"toLocation":{"expectedTypes":["Place"]}}},"AppendAction":{"extends":"InsertAction","properties":[]},"PrependAction":{"extends":"InsertAction","properties":[]},"DeleteAction":{"extends":"UpdateAction","properties":[]},"ReplaceAction":{"extends":"UpdateAction","properties":{"replacee":{"expectedTypes":["Thing"]},"replacer":{"expectedTypes":["Thing"]}}},"BroadcastService":{"extends":"Thing","properties":{"area":{"expectedTypes":["Place"]},"broadcaster":{"expectedTypes":["Organization"]},"parentService":{"expectedTypes":["BroadcastService"]}}},"CreativeWork":{"extends":"Thing","properties":{"about":{"expectedTypes":["Thing"]},"accessibilityAPI":{"expectedTypes":["Text"]},"accessibilityControl":{"expectedTypes":["Text"]},"accessibilityFeature":{"expectedTypes":["Text"]},"accessibilityHazard":{"expectedTypes":["Text"]},"accountablePerson":{"expectedTypes":["Person"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"alternativeHeadline":{"expectedTypes":["Text"]},"associatedMedia":{"expectedTypes":["MediaObject"]},"audience":{"expectedTypes":["Audience"]},"audio":{"expectedTypes":["AudioObject"]},"author":{"expectedTypes":["Organization","Person"]},"award":{"expectedTypes":["Text"]},"character":{"expectedTypes":["Person"]},"citation":{"expectedTypes":["CreativeWork","Text"]},"comment":{"expectedTypes":["UserComments","Comment"]},"commentCount":{"expectedTypes":["Integer"]},"contentLocation":{"expectedTypes":["Place"]},"contentRating":{"expectedTypes":["Text"]},"contributor":{"expectedTypes":["Organization","Person"]},"copyrightHolder":{"expectedTypes":["Organization","Person"]},"copyrightYear":{"expectedTypes":["Number"]},"creator":{"expectedTypes":["Organization","Person"]},"dateCreated":{"expectedTypes":["Date"]},"dateModified":{"expectedTypes":["Date"]},"datePublished":{"expectedTypes":["Date"]},"discussionUrl":{"expectedTypes":["URL"]},"editor":{"expectedTypes":["Person"]},"educationalAlignment":{"expectedTypes":["AlignmentObject"]},"educationalUse":{"expectedTypes":["Text"]},"encoding":{"expectedTypes":["MediaObject"]},"exampleOfWork":{"expectedTypes":["CreativeWork"]},"genre":{"expectedTypes":["Text"]},"hasPart":{"expectedTypes":["CreativeWork"]},"headline":{"expectedTypes":["Text"]},"inLanguage":{"expectedTypes":["Text"]},"interactionCount":{"expectedTypes":["Text"]},"interactivityType":{"expectedTypes":["Text"]},"isBasedOnUrl":{"expectedTypes":["URL"]},"isFamilyFriendly":{"expectedTypes":["Boolean"]},"isPartOf":{"expectedTypes":["CreativeWork"]},"keywords":{"expectedTypes":["Text"]},"learningResourceType":{"expectedTypes":["Text"]},"license":{"expectedTypes":["CreativeWork","URL"]},"mentions":{"expectedTypes":["Thing"]},"offers":{"expectedTypes":["Offer"]},"position":{"expectedTypes":["Integer","Text"]},"producer":{"expectedTypes":["Organization","Person"]},"provider":{"expectedTypes":["Organization","Person"]},"publisher":{"expectedTypes":["Organization"]},"publishingPrinciples":{"expectedTypes":["URL"]},"recordedAt":{"expectedTypes":["Event"]},"releasedEvent":{"expectedTypes":["PublicationEvent"]},"review":{"expectedTypes":["Review"]},"sourceOrganization":{"expectedTypes":["Organization"]},"text":{"expectedTypes":["Text"]},"thumbnailUrl":{"expectedTypes":["URL"]},"timeRequired":{"expectedTypes":["Duration"]},"translator":{"expectedTypes":["Organization","Person"]},"typicalAgeRange":{"expectedTypes":["Text"]},"version":{"expectedTypes":["Number"]},"video":{"expectedTypes":["VideoObject"]},"workExample":{"expectedTypes":["CreativeWork"]}}},"Answer":{"extends":"CreativeWork","properties":{"downvoteCount":{"expectedTypes":["Integer"]},"parentItem":{"expectedTypes":["Question"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"Article":{"extends":"CreativeWork","properties":{"articleBody":{"expectedTypes":["Text"]},"articleSection":{"expectedTypes":["Text"]},"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]},"wordCount":{"expectedTypes":["Integer"]}}},"BlogPosting":{"extends":"Article","properties":[]},"NewsArticle":{"extends":"Article","properties":{"dateline":{"expectedTypes":["Text"]},"printColumn":{"expectedTypes":["Text"]},"printEdition":{"expectedTypes":["Text"]},"printPage":{"expectedTypes":["Text"]},"printSection":{"expectedTypes":["Text"]}}},"ScholarlyArticle":{"extends":"Article","properties":[]},"MedicalScholarlyArticle":{"extends":"ScholarlyArticle","properties":{"publicationType":{"expectedTypes":["Text"]}}},"TechArticle":{"extends":"Article","properties":{"dependencies":{"expectedTypes":["Text"]},"proficiencyLevel":{"expectedTypes":["Text"]}}},"APIReference":{"extends":"TechArticle","properties":{"assembly":{"expectedTypes":["Text"]},"assemblyVersion":{"expectedTypes":["Text"]},"programmingModel":{"expectedTypes":["Text"]},"targetPlatform":{"expectedTypes":["Text"]}}},"Blog":{"extends":"CreativeWork","properties":{"blogPost":{"expectedTypes":["BlogPosting"]}}},"Book":{"extends":"CreativeWork","properties":{"bookEdition":{"expectedTypes":["Text"]},"bookFormat":{"expectedTypes":["BookFormatType"]},"illustrator":{"expectedTypes":["Person"]},"isbn":{"expectedTypes":["Text"]},"numberOfPages":{"expectedTypes":["Integer"]}}},"Clip":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"clipNumber":{"expectedTypes":["Integer","Text"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"partOfEpisode":{"expectedTypes":["Episode"]},"partOfSeason":{"expectedTypes":["Season"]},"partOfSeries":{"expectedTypes":["Series"]},"publication":{"expectedTypes":["PublicationEvent"]}}},"RadioClip":{"extends":"Clip","properties":[]},"TVClip":{"extends":"Clip","properties":[]},"Code":{"extends":"CreativeWork","properties":{"codeRepository":{"expectedTypes":["URL"]},"programmingLanguage":{"expectedTypes":["Thing"]},"runtime":{"expectedTypes":["Text"]},"sampleType":{"expectedTypes":["Text"]},"targetProduct":{"expectedTypes":["SoftwareApplication"]}}},"Comment":{"extends":"CreativeWork","properties":{"downvoteCount":{"expectedTypes":["Integer"]},"parentItem":{"expectedTypes":["Question"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"DataCatalog":{"extends":"CreativeWork","properties":{"dataset":{"expectedTypes":["Dataset"]}}},"Dataset":{"extends":"CreativeWork","properties":{"catalog":{"expectedTypes":["DataCatalog"]},"distribution":{"expectedTypes":["DataDownload"]},"spatial":{"expectedTypes":["Place"]},"temporal":{"expectedTypes":["DateTime"]}}},"Diet":{"extends":"CreativeWork","properties":{"dietFeatures":{"expectedTypes":["Text"]},"endorsers":{"expectedTypes":["Organization","Person"]},"expertConsiderations":{"expectedTypes":["Text"]},"overview":{"expectedTypes":["Text"]},"physiologicalBenefits":{"expectedTypes":["Text"]},"proprietaryName":{"expectedTypes":["Text"]},"risks":{"expectedTypes":["Text"]}}},"EmailMessage":{"extends":"CreativeWork","properties":[]},"Episode":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episodeNumber":{"expectedTypes":["Integer","Text"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"partOfSeason":{"expectedTypes":["Season"]},"partOfSeries":{"expectedTypes":["Series"]},"productionCompany":{"expectedTypes":["Organization"]},"publication":{"expectedTypes":["PublicationEvent"]},"trailer":{"expectedTypes":["VideoObject"]}}},"RadioEpisode":{"extends":"Episode","properties":[]},"TVEpisode":{"extends":"Episode","properties":[]},"ExercisePlan":{"extends":"CreativeWork","properties":{"activityDuration":{"expectedTypes":["Duration"]},"activityFrequency":{"expectedTypes":["Text"]},"additionalVariable":{"expectedTypes":["Text"]},"exerciseType":{"expectedTypes":["Text"]},"intensity":{"expectedTypes":["Text"]},"repetitions":{"expectedTypes":["Number"]},"restPeriods":{"expectedTypes":["Text"]},"workload":{"expectedTypes":["Energy"]}}},"Game":{"extends":"CreativeWork","properties":{"characterAttribute":{"expectedTypes":["Thing"]},"gameItem":{"expectedTypes":["Thing"]},"gameLocation":{"expectedTypes":["PostalAddress","URL","Place"]},"numberOfPlayers":{"expectedTypes":["QuantitativeValue"]},"quest":{"expectedTypes":["Thing"]}}},"VideoGame":{"extends":"Game","properties":{"actor":{"expectedTypes":["Person"]},"cheatCode":{"expectedTypes":["CreativeWork"]},"director":{"expectedTypes":["Person"]},"gamePlatform":{"expectedTypes":["Thing","Text","URL"]},"gameServer":{"expectedTypes":["GameServer"]},"gameTip":{"expectedTypes":["CreativeWork"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"playMode":{"expectedTypes":["GamePlayMode"]},"trailer":{"expectedTypes":["VideoObject"]}}},"Map":{"extends":"CreativeWork","properties":{"mapType":{"expectedTypes":["MapCategoryType"]}}},"MediaObject":{"extends":"CreativeWork","properties":{"associatedArticle":{"expectedTypes":["NewsArticle"]},"bitrate":{"expectedTypes":["Text"]},"contentSize":{"expectedTypes":["Text"]},"contentUrl":{"expectedTypes":["URL"]},"duration":{"expectedTypes":["Duration"]},"embedUrl":{"expectedTypes":["URL"]},"encodesCreativeWork":{"expectedTypes":["CreativeWork"]},"encodingFormat":{"expectedTypes":["Text"]},"expires":{"expectedTypes":["Date"]},"height":{"expectedTypes":["QuantitativeValue","Distance"]},"playerType":{"expectedTypes":["Text"]},"productionCompany":{"expectedTypes":["Organization"]},"publication":{"expectedTypes":["PublicationEvent"]},"regionsAllowed":{"expectedTypes":["Place"]},"requiresSubscription":{"expectedTypes":["Boolean"]},"uploadDate":{"expectedTypes":["Date"]},"width":{"expectedTypes":["QuantitativeValue","Distance"]}}},"AudioObject":{"extends":"MediaObject","properties":{"transcript":{"expectedTypes":["Text"]}}},"DataDownload":{"extends":"MediaObject","properties":[]},"ImageObject":{"extends":"MediaObject","properties":{"caption":{"expectedTypes":["Text"]},"exifData":{"expectedTypes":["Text"]},"representativeOfPage":{"expectedTypes":["Boolean"]},"thumbnail":{"expectedTypes":["ImageObject"]}}},"MusicVideoObject":{"extends":"MediaObject","properties":[]},"VideoObject":{"extends":"MediaObject","properties":{"actor":{"expectedTypes":["Person"]},"caption":{"expectedTypes":["Text"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"thumbnail":{"expectedTypes":["ImageObject"]},"transcript":{"expectedTypes":["Text"]},"videoFrameSize":{"expectedTypes":["Text"]},"videoQuality":{"expectedTypes":["Text"]}}},"Movie":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"duration":{"expectedTypes":["Duration"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"productionCompany":{"expectedTypes":["Organization"]},"trailer":{"expectedTypes":["VideoObject"]}}},"MusicComposition":{"extends":"CreativeWork","properties":{"composer":{"expectedTypes":["Person","Organization"]},"firstPerformance":{"expectedTypes":["Event"]},"includedComposition":{"expectedTypes":["MusicComposition"]},"iswcCode":{"expectedTypes":["Text"]},"lyricist":{"expectedTypes":["Person"]},"musicArrangement":{"expectedTypes":["MusicComposition"]},"musicCompositionForm":{"expectedTypes":["Text"]},"musicalKey":{"expectedTypes":["Text"]},"recordedAs":{"expectedTypes":["MusicRecording"]}}},"MusicPlaylist":{"extends":"CreativeWork","properties":{"numTracks":{"expectedTypes":["Integer"]},"track":{"expectedTypes":["MusicRecording","ItemList"]}}},"MusicAlbum":{"extends":"MusicPlaylist","properties":{"albumProductionType":{"expectedTypes":["MusicAlbumProductionType"]},"albumRelease":{"expectedTypes":["MusicRelease"]},"albumReleaseType":{"expectedTypes":["MusicAlbumReleaseType"]},"byArtist":{"expectedTypes":["MusicGroup"]}}},"MusicRelease":{"extends":"MusicPlaylist","properties":{"catalogNumber":{"expectedTypes":["Text"]},"creditedTo":{"expectedTypes":["Organization","Person"]},"duration":{"expectedTypes":["Duration"]},"musicReleaseFormat":{"expectedTypes":["MusicReleaseFormatType"]},"recordLabel":{"expectedTypes":["Organization"]},"releaseOf":{"expectedTypes":["MusicAlbum"]}}},"MusicRecording":{"extends":"CreativeWork","properties":{"byArtist":{"expectedTypes":["MusicGroup"]},"duration":{"expectedTypes":["Duration"]},"inAlbum":{"expectedTypes":["MusicAlbum"]},"inPlaylist":{"expectedTypes":["MusicPlaylist"]},"isrcCode":{"expectedTypes":["Text"]},"recordingOf":{"expectedTypes":["MusicComposition"]}}},"Painting":{"extends":"CreativeWork","properties":[]},"Photograph":{"extends":"CreativeWork","properties":[]},"PublicationIssue":{"extends":"CreativeWork","properties":{"issueNumber":{"expectedTypes":["Integer","Text"]},"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]}}},"PublicationVolume":{"extends":"CreativeWork","properties":{"pageEnd":{"expectedTypes":["Integer","Text"]},"pageStart":{"expectedTypes":["Integer","Text"]},"pagination":{"expectedTypes":["Text"]},"volumeNumber":{"expectedTypes":["Integer","Text"]}}},"Question":{"extends":"CreativeWork","properties":{"acceptedAnswer":{"expectedTypes":["Answer"]},"answerCount":{"expectedTypes":["Integer"]},"downvoteCount":{"expectedTypes":["Integer"]},"suggestedAnswer":{"expectedTypes":["Answer"]},"upvoteCount":{"expectedTypes":["Integer"]}}},"Recipe":{"extends":"CreativeWork","properties":{"cookTime":{"expectedTypes":["Duration"]},"cookingMethod":{"expectedTypes":["Text"]},"ingredients":{"expectedTypes":["Text"]},"nutrition":{"expectedTypes":["NutritionInformation"]},"prepTime":{"expectedTypes":["Duration"]},"recipeCategory":{"expectedTypes":["Text"]},"recipeCuisine":{"expectedTypes":["Text"]},"recipeInstructions":{"expectedTypes":["Text"]},"recipeYield":{"expectedTypes":["Text"]},"totalTime":{"expectedTypes":["Duration"]}}},"Review":{"extends":"CreativeWork","properties":{"itemReviewed":{"expectedTypes":["Thing"]},"reviewBody":{"expectedTypes":["Text"]},"reviewRating":{"expectedTypes":["Rating"]}}},"Sculpture":{"extends":"CreativeWork","properties":[]},"Season":{"extends":"CreativeWork","properties":{"endDate":{"expectedTypes":["Date"]},"episode":{"expectedTypes":["Episode"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"partOfSeries":{"expectedTypes":["Series"]},"productionCompany":{"expectedTypes":["Organization"]},"seasonNumber":{"expectedTypes":["Integer","Text"]},"startDate":{"expectedTypes":["Date"]},"trailer":{"expectedTypes":["VideoObject"]}}},"RadioSeason":{"extends":"Season","properties":[]},"TVSeason":{"extends":"CreativeWork","properties":[]},"Series":{"extends":"CreativeWork","properties":{"endDate":{"expectedTypes":["Date"]},"startDate":{"expectedTypes":["Date"]}}},"BookSeries":{"extends":"Series","properties":[]},"MovieSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"productionCompany":{"expectedTypes":["Organization"]},"trailer":{"expectedTypes":["VideoObject"]}}},"Periodical":{"extends":"Series","properties":{"issn":{"expectedTypes":["Text"]}}},"RadioSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfSeasons":{"expectedTypes":["Number"]},"productionCompany":{"expectedTypes":["Organization"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"TVSeries":{"extends":"CreativeWork","properties":{"actor":{"expectedTypes":["Person"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"musicBy":{"expectedTypes":["MusicGroup","Person"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfSeasons":{"expectedTypes":["Number"]},"productionCompany":{"expectedTypes":["Organization"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"VideoGameSeries":{"extends":"Series","properties":{"actor":{"expectedTypes":["Person"]},"characterAttribute":{"expectedTypes":["Thing"]},"cheatCode":{"expectedTypes":["CreativeWork"]},"director":{"expectedTypes":["Person"]},"episode":{"expectedTypes":["Episode"]},"gameItem":{"expectedTypes":["Thing"]},"gamePlatform":{"expectedTypes":["Text","Thing","URL"]},"musicBy":{"expectedTypes":["Person","MusicGroup"]},"numberOfEpisodes":{"expectedTypes":["Number"]},"numberOfPlayers":{"expectedTypes":["QuantitativeValue"]},"numberOfSeasons":{"expectedTypes":["Number"]},"playMode":{"expectedTypes":["GamePlayMode"]},"productionCompany":{"expectedTypes":["Organization"]},"quest":{"expectedTypes":["Thing"]},"season":{"expectedTypes":["Season"]},"trailer":{"expectedTypes":["VideoObject"]}}},"SoftwareApplication":{"extends":"CreativeWork","properties":{"applicationCategory":{"expectedTypes":["Text","URL"]},"applicationSubCategory":{"expectedTypes":["Text","URL"]},"applicationSuite":{"expectedTypes":["Text"]},"countriesNotSupported":{"expectedTypes":["Text"]},"countriesSupported":{"expectedTypes":["Text"]},"device":{"expectedTypes":["Text"]},"downloadUrl":{"expectedTypes":["URL"]},"featureList":{"expectedTypes":["Text","URL"]},"fileFormat":{"expectedTypes":["Text"]},"fileSize":{"expectedTypes":["Integer"]},"installUrl":{"expectedTypes":["URL"]},"memoryRequirements":{"expectedTypes":["Text","URL"]},"operatingSystem":{"expectedTypes":["Text"]},"permissions":{"expectedTypes":["Text"]},"processorRequirements":{"expectedTypes":["Text"]},"releaseNotes":{"expectedTypes":["Text","URL"]},"requirements":{"expectedTypes":["Text","URL"]},"screenshot":{"expectedTypes":["ImageObject","URL"]},"softwareAddOn":{"expectedTypes":["SoftwareApplication"]},"softwareHelp":{"expectedTypes":["CreativeWork"]},"softwareVersion":{"expectedTypes":["Text"]},"storageRequirements":{"expectedTypes":["Text","URL"]}}},"MobileApplication":{"extends":"SoftwareApplication","properties":{"carrierRequirements":{"expectedTypes":["Text"]}}},"WebApplication":{"extends":"SoftwareApplication","properties":{"browserRequirements":{"expectedTypes":["Text"]}}},"VisualArtwork":{"extends":"CreativeWork","properties":{"artEdition":{"expectedTypes":["Integer","Text"]},"artform":{"expectedTypes":["Text","URL"]},"depth":{"expectedTypes":["Distance","QuantitativeValue"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"material":{"expectedTypes":["Text","URL"]},"surface":{"expectedTypes":["Text","URL"]},"width":{"expectedTypes":["Distance","QuantitativeValue"]}}},"WebPage":{"extends":"CreativeWork","properties":{"breadcrumb":{"expectedTypes":["Text","BreadcrumbList"]},"lastReviewed":{"expectedTypes":["Date"]},"mainContentOfPage":{"expectedTypes":["WebPageElement"]},"primaryImageOfPage":{"expectedTypes":["ImageObject"]},"relatedLink":{"expectedTypes":["URL"]},"reviewedBy":{"expectedTypes":["Person","Organization"]},"significantLink":{"expectedTypes":["URL"]},"specialty":{"expectedTypes":["Specialty"]}}},"AboutPage":{"extends":"WebPage","properties":[]},"CheckoutPage":{"extends":"WebPage","properties":[]},"CollectionPage":{"extends":"WebPage","properties":[]},"ImageGallery":{"extends":"CollectionPage","properties":[]},"VideoGallery":{"extends":"CollectionPage","properties":[]},"ContactPage":{"extends":"WebPage","properties":[]},"ItemPage":{"extends":"WebPage","properties":[]},"MedicalWebPage":{"extends":"WebPage","properties":{"aspect":{"expectedTypes":["Text"]}}},"ProfilePage":{"extends":"WebPage","properties":[]},"QAPage":{"extends":"WebPage","properties":[]},"SearchResultsPage":{"extends":"WebPage","properties":[]},"WebPageElement":{"extends":"CreativeWork","properties":[]},"SiteNavigationElement":{"extends":"WebPageElement","properties":[]},"Table":{"extends":"WebPageElement","properties":[]},"WPAdBlock":{"extends":"WebPageElement","properties":[]},"WPFooter":{"extends":"WebPageElement","properties":[]},"WPHeader":{"extends":"WebPageElement","properties":[]},"WPSideBar":{"extends":"WebPageElement","properties":[]},"WebSite":{"extends":"CreativeWork","properties":[]},"Event":{"extends":"Thing","properties":{"attendee":{"expectedTypes":["Organization","Person"]},"doorTime":{"expectedTypes":["DateTime"]},"duration":{"expectedTypes":["Duration"]},"endDate":{"expectedTypes":["Date"]},"eventStatus":{"expectedTypes":["EventStatusType"]},"location":{"expectedTypes":["PostalAddress","Place"]},"offers":{"expectedTypes":["Offer"]},"organizer":{"expectedTypes":["Organization","Person"]},"performer":{"expectedTypes":["Organization","Person"]},"previousStartDate":{"expectedTypes":["Date"]},"recordedIn":{"expectedTypes":["CreativeWork"]},"startDate":{"expectedTypes":["Date"]},"subEvent":{"expectedTypes":["Event"]},"superEvent":{"expectedTypes":["Event"]},"typicalAgeRange":{"expectedTypes":["Text"]},"workPerformed":{"expectedTypes":["CreativeWork"]}}},"BusinessEvent":{"extends":"Event","properties":[]},"ChildrensEvent":{"extends":"Event","properties":[]},"ComedyEvent":{"extends":"Event","properties":[]},"DanceEvent":{"extends":"Event","properties":[]},"DeliveryEvent":{"extends":"Event","properties":{"accessCode":{"expectedTypes":["Text"]},"availableFrom":{"expectedTypes":["DateTime"]},"availableThrough":{"expectedTypes":["DateTime"]},"hasDeliveryMethod":{"expectedTypes":["DeliveryMethod"]}}},"EducationEvent":{"extends":"Event","properties":[]},"Festival":{"extends":"Event","properties":[]},"FoodEvent":{"extends":"Event","properties":[]},"LiteraryEvent":{"extends":"Event","properties":[]},"MusicEvent":{"extends":"Event","properties":[]},"PublicationEvent":{"extends":"Event","properties":{"free":{"expectedTypes":["Boolean"]},"publishedOn":{"expectedTypes":["BroadcastService"]}}},"BroadcastEvent":{"extends":"PublicationEvent","properties":[]},"OnDemandEvent":{"extends":"PublicationEvent","properties":[]},"SaleEvent":{"extends":"Event","properties":[]},"SocialEvent":{"extends":"Event","properties":[]},"SportsEvent":{"extends":"Event","properties":{"awayTeam":{"expectedTypes":["Person","SportsTeam"]},"competitor":{"expectedTypes":["Person","SportsTeam"]},"homeTeam":{"expectedTypes":["Person","SportsTeam"]}}},"TheaterEvent":{"extends":"Event","properties":[]},"UserInteraction":{"extends":"Event","properties":[]},"UserBlocks":{"extends":"UserInteraction","properties":[]},"UserCheckins":{"extends":"UserInteraction","properties":[]},"UserComments":{"extends":"UserInteraction","properties":{"commentText":{"expectedTypes":["Text"]},"commentTime":{"expectedTypes":["Date"]},"creator":{"expectedTypes":["Organization","Person"]},"discusses":{"expectedTypes":["CreativeWork"]},"replyToUrl":{"expectedTypes":["URL"]}}},"UserDownloads":{"extends":"UserInteraction","properties":[]},"UserLikes":{"extends":"UserInteraction","properties":[]},"UserPageVisits":{"extends":"UserInteraction","properties":[]},"UserPlays":{"extends":"UserInteraction","properties":[]},"UserPlusOnes":{"extends":"UserInteraction","properties":[]},"UserTweets":{"extends":"UserInteraction","properties":[]},"VisualArtsEvent":{"extends":"Event","properties":[]},"Intangible":{"extends":"Thing","properties":[]},"AlignmentObject":{"extends":"Intangible","properties":{"alignmentType":{"expectedTypes":["Text"]},"educationalFramework":{"expectedTypes":["Text"]},"targetDescription":{"expectedTypes":["Text"]},"targetName":{"expectedTypes":["Text"]},"targetUrl":{"expectedTypes":["URL"]}}},"Audience":{"extends":"Intangible","properties":{"audienceType":{"expectedTypes":["Text"]},"geographicArea":{"expectedTypes":["AdministrativeArea"]}}},"BusinessAudience":{"extends":"Audience","properties":{"numberOfEmployees":{"expectedTypes":["QuantitativeValue"]},"yearlyRevenue":{"expectedTypes":["QuantitativeValue"]},"yearsInOperation":{"expectedTypes":["QuantitativeValue"]}}},"EducationalAudience":{"extends":"Audience","properties":{"educationalRole":{"expectedTypes":["Text"]}}},"MedicalAudience":{"extends":"Audience","properties":[]},"PeopleAudience":{"extends":"Audience","properties":{"healthCondition":{"expectedTypes":["MedicalCondition"]},"requiredGender":{"expectedTypes":["Text"]},"requiredMaxAge":{"expectedTypes":["Integer"]},"requiredMinAge":{"expectedTypes":["Integer"]},"suggestedGender":{"expectedTypes":["Text"]},"suggestedMaxAge":{"expectedTypes":["Number"]},"suggestedMinAge":{"expectedTypes":["Number"]}}},"ParentAudience":{"extends":"PeopleAudience","properties":{"childMaxAge":{"expectedTypes":["Number"]},"childMinAge":{"expectedTypes":["Number"]}}},"Brand":{"extends":"Intangible","properties":{"logo":{"expectedTypes":["ImageObject","URL"]}}},"BusTrip":{"extends":"Intangible","properties":{"arrivalBusStop":{"expectedTypes":["BusStation","BusStop"]},"arrivalTime":{"expectedTypes":["DateTime"]},"busName":{"expectedTypes":["Text"]},"busNumber":{"expectedTypes":["Text"]},"departureBusStop":{"expectedTypes":["BusStation","BusStop"]},"departureTime":{"expectedTypes":["DateTime"]},"provider":{"expectedTypes":["Person","Organization"]}}},"Class":{"extends":"Intangible","properties":[]},"Demand":{"extends":"Intangible","properties":{"acceptedPaymentMethod":{"expectedTypes":["PaymentMethod"]},"advanceBookingRequirement":{"expectedTypes":["QuantitativeValue"]},"availability":{"expectedTypes":["ItemAvailability"]},"availabilityEnds":{"expectedTypes":["DateTime"]},"availabilityStarts":{"expectedTypes":["DateTime"]},"availableAtOrFrom":{"expectedTypes":["Place"]},"availableDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"deliveryLeadTime":{"expectedTypes":["QuantitativeValue"]},"eligibleCustomerType":{"expectedTypes":["BusinessEntityType"]},"eligibleDuration":{"expectedTypes":["QuantitativeValue"]},"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleRegion":{"expectedTypes":["GeoShape","Text"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"includesObject":{"expectedTypes":["TypeAndQuantityNode"]},"inventoryLevel":{"expectedTypes":["QuantitativeValue"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"itemOffered":{"expectedTypes":["Product"]},"mpn":{"expectedTypes":["Text"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]},"seller":{"expectedTypes":["Organization","Person"]},"serialNumber":{"expectedTypes":["Text"]},"sku":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"warranty":{"expectedTypes":["WarrantyPromise"]}}},"EntryPoint":{"extends":"Intangible","properties":{"application":{"expectedTypes":["SoftwareApplication"]},"contentType":{"expectedTypes":["Text"]},"encodingType":{"expectedTypes":["Text"]},"httpMethod":{"expectedTypes":["Text"]},"urlTemplate":{"expectedTypes":["Text"]}}},"Enumeration":{"extends":"Intangible","properties":[]},"ActionStatusType":{"extends":"Enumeration","properties":[]},"BookFormatType":{"extends":"Enumeration","properties":[]},"BusinessEntityType":{"extends":"Enumeration","properties":[]},"BusinessFunction":{"extends":"Enumeration","properties":[]},"ContactPointOption":{"extends":"Enumeration","properties":[]},"DayOfWeek":{"extends":"Enumeration","properties":[]},"DeliveryMethod":{"extends":"Enumeration","properties":[]},"LockerDelivery":{"extends":"DeliveryMethod","properties":[]},"ParcelService":{"extends":"DeliveryMethod","properties":[]},"DrugCostCategory":{"extends":"Enumeration","properties":[]},"DrugPregnancyCategory":{"extends":"MedicalEnumeration","properties":[]},"DrugPrescriptionStatus":{"extends":"Enumeration","properties":[]},"EventStatusType":{"extends":"Enumeration","properties":[]},"GamePlayMode":{"extends":"Enumeration","properties":[]},"GameServerStatus":{"extends":"Enumeration","properties":[]},"InfectiousAgentClass":{"extends":"MedicalEnumeration","properties":[]},"ItemAvailability":{"extends":"Enumeration","properties":[]},"ItemListOrderType":{"extends":"Enumeration","properties":[]},"MapCategoryType":{"extends":"Enumeration","properties":[]},"MedicalDevicePurpose":{"extends":"Enumeration","properties":[]},"MedicalEnumeration":{"extends":"Enumeration","properties":[]},"MedicalEvidenceLevel":{"extends":"Enumeration","properties":[]},"MedicalImagingTechnique":{"extends":"Enumeration","properties":[]},"MedicalObservationalStudyDesign":{"extends":"Enumeration","properties":[]},"MedicalProcedureType":{"extends":"MedicalEnumeration","properties":[]},"MedicalSpecialty":{"extends":"Enumeration","properties":[]},"MedicalStudyStatus":{"extends":"Enumeration","properties":[]},"MedicalTrialDesign":{"extends":"Enumeration","properties":[]},"MedicineSystem":{"extends":"Enumeration","properties":[]},"PhysicalActivityCategory":{"extends":"MedicalEnumeration","properties":[]},"PhysicalExam":{"extends":"Enumeration","properties":[]},"MusicAlbumProductionType":{"extends":"Enumeration","properties":[]},"MusicAlbumReleaseType":{"extends":"Enumeration","properties":[]},"MusicReleaseFormatType":{"extends":"Enumeration","properties":[]},"OfferItemCondition":{"extends":"Enumeration","properties":[]},"OrderStatus":{"extends":"Enumeration","properties":[]},"PaymentMethod":{"extends":"Enumeration","properties":[]},"CreditCard":{"extends":"PaymentMethod","properties":[]},"QualitativeValue":{"extends":"Enumeration","properties":{"equal":{"expectedTypes":["QualitativeValue"]},"greater":{"expectedTypes":["QualitativeValue"]},"greaterOrEqual":{"expectedTypes":["QualitativeValue"]},"lesser":{"expectedTypes":["QualitativeValue"]},"lesserOrEqual":{"expectedTypes":["QualitativeValue"]},"nonEqual":{"expectedTypes":["QualitativeValue"]},"valueReference":{"expectedTypes":["Enumeration","StructuredValue"]}}},"ReservationStatusType":{"extends":"Enumeration","properties":[]},"RsvpResponseType":{"extends":"Enumeration","properties":[]},"Specialty":{"extends":"Enumeration","properties":[]},"WarrantyScope":{"extends":"Enumeration","properties":[]},"Flight":{"extends":"Intangible","properties":{"aircraft":{"expectedTypes":["Vehicle","Text"]},"arrivalAirport":{"expectedTypes":["Airport"]},"arrivalGate":{"expectedTypes":["Text"]},"arrivalTerminal":{"expectedTypes":["Text"]},"arrivalTime":{"expectedTypes":["DateTime"]},"departureAirport":{"expectedTypes":["Airport"]},"departureGate":{"expectedTypes":["Text"]},"departureTerminal":{"expectedTypes":["Text"]},"departureTime":{"expectedTypes":["DateTime"]},"estimatedFlightDuration":{"expectedTypes":["Duration","Text"]},"flightDistance":{"expectedTypes":["Text","Distance"]},"flightNumber":{"expectedTypes":["Text"]},"mealService":{"expectedTypes":["Text"]},"provider":{"expectedTypes":["Organization","Person"]},"seller":{"expectedTypes":["Organization","Person"]},"webCheckinTime":{"expectedTypes":["DateTime"]}}},"GameServer":{"extends":"Intangible","properties":{"game":{"expectedTypes":["VideoGame"]},"playersOnline":{"expectedTypes":["Number"]},"serverStatus":{"expectedTypes":["GameServerStatus"]}}},"Invoice":{"extends":"Intangible","properties":{"accountId":{"expectedTypes":["Text"]},"billingPeriod":{"expectedTypes":["Duration"]},"broker":{"expectedTypes":["Person","Organization"]},"category":{"expectedTypes":["Text","PhysicalActivityCategory","Thing"]},"confirmationNumber":{"expectedTypes":["Text"]},"customer":{"expectedTypes":["Person","Organization"]},"minimumPaymentDue":{"expectedTypes":["PriceSpecification"]},"paymentDue":{"expectedTypes":["DateTime"]},"paymentMethod":{"expectedTypes":["PaymentMethod"]},"paymentMethodId":{"expectedTypes":["Text"]},"paymentStatus":{"expectedTypes":["Text"]},"provider":{"expectedTypes":["Person","Organization"]},"referencesOrder":{"expectedTypes":["Order"]},"scheduledPaymentDate":{"expectedTypes":["Date"]},"totalPaymentDue":{"expectedTypes":["PriceSpecification"]}}},"ItemList":{"extends":"Intangible","properties":{"itemListElement":{"expectedTypes":["Text","ListItem","Thing"]},"itemListOrder":{"expectedTypes":["Text","ItemListOrderType"]},"numberOfItems":{"expectedTypes":["Number"]}}},"BreadcrumbList":{"extends":"ItemList","properties":[]},"JobPosting":{"extends":"Intangible","properties":{"baseSalary":{"expectedTypes":["Number","PriceSpecification"]},"benefits":{"expectedTypes":["Text"]},"datePosted":{"expectedTypes":["Date"]},"educationRequirements":{"expectedTypes":["Text"]},"employmentType":{"expectedTypes":["Text"]},"experienceRequirements":{"expectedTypes":["Text"]},"hiringOrganization":{"expectedTypes":["Organization"]},"incentives":{"expectedTypes":["Text"]},"industry":{"expectedTypes":["Text"]},"jobLocation":{"expectedTypes":["Place"]},"occupationalCategory":{"expectedTypes":["Text"]},"qualifications":{"expectedTypes":["Text"]},"responsibilities":{"expectedTypes":["Text"]},"salaryCurrency":{"expectedTypes":["Text"]},"skills":{"expectedTypes":["Text"]},"specialCommitments":{"expectedTypes":["Text"]},"title":{"expectedTypes":["Text"]},"workHours":{"expectedTypes":["Text"]}}},"Language":{"extends":"Intangible","properties":[]},"ListItem":{"extends":"Intangible","properties":{"item":{"expectedTypes":["Thing"]},"nextItem":{"expectedTypes":["ListItem"]},"position":{"expectedTypes":["Text","Integer"]},"previousItem":{"expectedTypes":["ListItem"]}}},"Offer":{"extends":"Intangible","properties":{"acceptedPaymentMethod":{"expectedTypes":["PaymentMethod"]},"addOn":{"expectedTypes":["Offer"]},"advanceBookingRequirement":{"expectedTypes":["QuantitativeValue"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"availability":{"expectedTypes":["ItemAvailability"]},"availabilityEnds":{"expectedTypes":["DateTime"]},"availabilityStarts":{"expectedTypes":["DateTime"]},"availableAtOrFrom":{"expectedTypes":["Place"]},"availableDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"category":{"expectedTypes":["PhysicalActivityCategory","Thing","Text"]},"deliveryLeadTime":{"expectedTypes":["QuantitativeValue"]},"eligibleCustomerType":{"expectedTypes":["BusinessEntityType"]},"eligibleDuration":{"expectedTypes":["QuantitativeValue"]},"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleRegion":{"expectedTypes":["GeoShape","Text"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"includesObject":{"expectedTypes":["TypeAndQuantityNode"]},"ineligibleRegion":{"expectedTypes":["Place"]},"inventoryLevel":{"expectedTypes":["QuantitativeValue"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"itemOffered":{"expectedTypes":["Product"]},"mpn":{"expectedTypes":["Text"]},"price":{"expectedTypes":["Number","Text"]},"priceCurrency":{"expectedTypes":["Text"]},"priceSpecification":{"expectedTypes":["PriceSpecification"]},"priceValidUntil":{"expectedTypes":["Date"]},"review":{"expectedTypes":["Review"]},"seller":{"expectedTypes":["Organization","Person"]},"serialNumber":{"expectedTypes":["Text"]},"sku":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"warranty":{"expectedTypes":["WarrantyPromise"]}}},"AggregateOffer":{"extends":"Offer","properties":{"highPrice":{"expectedTypes":["Text","Number"]},"lowPrice":{"expectedTypes":["Text","Number"]},"offerCount":{"expectedTypes":["Integer"]},"offers":{"expectedTypes":["Offer"]}}},"Order":{"extends":"Intangible","properties":{"acceptedOffer":{"expectedTypes":["Offer"]},"billingAddress":{"expectedTypes":["PostalAddress"]},"broker":{"expectedTypes":["Person","Organization"]},"confirmationNumber":{"expectedTypes":["Text"]},"customer":{"expectedTypes":["Person","Organization"]},"discount":{"expectedTypes":["Text","Number"]},"discountCode":{"expectedTypes":["Text"]},"discountCurrency":{"expectedTypes":["Text"]},"isGift":{"expectedTypes":["Boolean"]},"orderDate":{"expectedTypes":["DateTime"]},"orderNumber":{"expectedTypes":["Text"]},"orderStatus":{"expectedTypes":["OrderStatus"]},"orderedItem":{"expectedTypes":["Product"]},"partOfInvoice":{"expectedTypes":["Invoice"]},"paymentDue":{"expectedTypes":["DateTime"]},"paymentMethod":{"expectedTypes":["PaymentMethod"]},"paymentMethodId":{"expectedTypes":["Text"]},"paymentUrl":{"expectedTypes":["URL"]},"seller":{"expectedTypes":["Person","Organization"]}}},"ParcelDelivery":{"extends":"Intangible","properties":{"deliveryAddress":{"expectedTypes":["PostalAddress"]},"deliveryStatus":{"expectedTypes":["DeliveryEvent"]},"expectedArrivalFrom":{"expectedTypes":["DateTime"]},"expectedArrivalUntil":{"expectedTypes":["DateTime"]},"hasDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"itemShipped":{"expectedTypes":["Product"]},"originAddress":{"expectedTypes":["PostalAddress"]},"partOfOrder":{"expectedTypes":["Order"]},"provider":{"expectedTypes":["Person","Organization"]},"trackingNumber":{"expectedTypes":["Text"]},"trackingUrl":{"expectedTypes":["URL"]}}},"Permit":{"extends":"Intangible","properties":{"issuedBy":{"expectedTypes":["Organization"]},"issuedThrough":{"expectedTypes":["Service"]},"permitAudience":{"expectedTypes":["Audience"]},"validFor":{"expectedTypes":["Duration"]},"validFrom":{"expectedTypes":["DateTime"]},"validIn":{"expectedTypes":["AdministrativeArea"]},"validUntil":{"expectedTypes":["Date"]}}},"GovernmentPermit":{"extends":"Permit","properties":[]},"ProgramMembership":{"extends":"Intangible","properties":{"hostingOrganization":{"expectedTypes":["Organization"]},"member":{"expectedTypes":["Person","Organization"]},"membershipNumber":{"expectedTypes":["Text"]},"programName":{"expectedTypes":["Text"]}}},"Property":{"extends":"Intangible","properties":{"domainIncludes":{"expectedTypes":["Class"]},"inverseOf":{"expectedTypes":["Property"]},"rangeIncludes":{"expectedTypes":["Class"]},"supersededBy":{"expectedTypes":["Property"]}}},"PropertyValueSpecification":{"extends":"Intangible","properties":{"defaultValue":{"expectedTypes":["Text","Thing"]},"maxValue":{"expectedTypes":["Number"]},"minValue":{"expectedTypes":["Number"]},"multipleValues":{"expectedTypes":["Boolean"]},"readonlyValue":{"expectedTypes":["Boolean"]},"stepValue":{"expectedTypes":["Number"]},"valueMaxLength":{"expectedTypes":["Number"]},"valueMinLength":{"expectedTypes":["Number"]},"valueName":{"expectedTypes":["Text"]},"valuePattern":{"expectedTypes":["Text"]},"valueRequired":{"expectedTypes":["Boolean"]}}},"Quantity":{"extends":"Intangible","properties":[]},"Distance":{"extends":"Quantity","properties":[]},"Duration":{"extends":"Quantity","properties":[]},"Energy":{"extends":"Quantity","properties":[]},"Mass":{"extends":"Quantity","properties":[]},"Rating":{"extends":"Intangible","properties":{"bestRating":{"expectedTypes":["Number","Text"]},"ratingValue":{"expectedTypes":["Text"]},"worstRating":{"expectedTypes":["Number","Text"]}}},"AggregateRating":{"extends":"Rating","properties":{"itemReviewed":{"expectedTypes":["Thing"]},"ratingCount":{"expectedTypes":["Number"]},"reviewCount":{"expectedTypes":["Number"]}}},"Reservation":{"extends":"Intangible","properties":{"bookingTime":{"expectedTypes":["DateTime"]},"broker":{"expectedTypes":["Organization","Person"]},"modifiedTime":{"expectedTypes":["DateTime"]},"priceCurrency":{"expectedTypes":["Text"]},"programMembershipUsed":{"expectedTypes":["ProgramMembership"]},"provider":{"expectedTypes":["Organization","Person"]},"reservationFor":{"expectedTypes":["Thing"]},"reservationId":{"expectedTypes":["Text"]},"reservationStatus":{"expectedTypes":["ReservationStatusType"]},"reservedTicket":{"expectedTypes":["Ticket"]},"totalPrice":{"expectedTypes":["Number","PriceSpecification","Text"]},"underName":{"expectedTypes":["Organization","Person"]}}},"BusReservation":{"extends":"Reservation","properties":[]},"EventReservation":{"extends":"Reservation","properties":[]},"FlightReservation":{"extends":"Reservation","properties":{"boardingGroup":{"expectedTypes":["Text"]}}},"FoodEstablishmentReservation":{"extends":"Reservation","properties":{"endTime":{"expectedTypes":["DateTime"]},"partySize":{"expectedTypes":["Number","QuantitativeValue"]},"startTime":{"expectedTypes":["DateTime"]}}},"LodgingReservation":{"extends":"Reservation","properties":{"checkinTime":{"expectedTypes":["DateTime"]},"checkoutTime":{"expectedTypes":["DateTime"]},"lodgingUnitDescription":{"expectedTypes":["Text"]},"lodgingUnitType":{"expectedTypes":["Text","QualitativeValue"]},"numAdults":{"expectedTypes":["QuantitativeValue","Number"]},"numChildren":{"expectedTypes":["QuantitativeValue","Number"]}}},"RentalCarReservation":{"extends":"Reservation","properties":{"dropoffLocation":{"expectedTypes":["Place"]},"dropoffTime":{"expectedTypes":["DateTime"]},"pickupLocation":{"expectedTypes":["Place"]},"pickupTime":{"expectedTypes":["DateTime"]}}},"ReservationPackage":{"extends":"Reservation","properties":{"subReservation":{"expectedTypes":["Reservation"]}}},"TaxiReservation":{"extends":"Reservation","properties":{"partySize":{"expectedTypes":["Number","QuantitativeValue"]},"pickupLocation":{"expectedTypes":["Place"]},"pickupTime":{"expectedTypes":["DateTime"]}}},"TrainReservation":{"extends":"Reservation","properties":[]},"Role":{"extends":"Intangible","properties":{"endDate":{"expectedTypes":["Date"]},"roleName":{"expectedTypes":["Text","URL"]},"startDate":{"expectedTypes":["Date"]}}},"OrganizationRole":{"extends":"Role","properties":{"numberedPosition":{"expectedTypes":["Number"]}}},"EmployeeRole":{"extends":"OrganizationRole","properties":{"baseSalary":{"expectedTypes":["Number","PriceSpecification"]},"salaryCurrency":{"expectedTypes":["Text"]}}},"PerformanceRole":{"extends":"Role","properties":{"characterName":{"expectedTypes":["Text"]}}},"Seat":{"extends":"Intangible","properties":{"seatNumber":{"expectedTypes":["Text"]},"seatRow":{"expectedTypes":["Text"]},"seatSection":{"expectedTypes":["Text"]},"seatingType":{"expectedTypes":["Text","QualitativeValue"]}}},"Service":{"extends":"Intangible","properties":{"availableChannel":{"expectedTypes":["ServiceChannel"]},"produces":{"expectedTypes":["Thing"]},"provider":{"expectedTypes":["Person","Organization"]},"serviceArea":{"expectedTypes":["AdministrativeArea"]},"serviceAudience":{"expectedTypes":["Audience"]},"serviceType":{"expectedTypes":["Text"]}}},"GovernmentService":{"extends":"Service","properties":{"serviceOperator":{"expectedTypes":["Organization"]}}},"Taxi":{"extends":"Service","properties":[]},"ServiceChannel":{"extends":"Intangible","properties":{"availableLanguage":{"expectedTypes":["Language"]},"processingTime":{"expectedTypes":["Duration"]},"providesService":{"expectedTypes":["Service"]},"serviceLocation":{"expectedTypes":["Place"]},"servicePhone":{"expectedTypes":["ContactPoint"]},"servicePostalAddress":{"expectedTypes":["PostalAddress"]},"serviceSmsNumber":{"expectedTypes":["ContactPoint"]},"serviceUrl":{"expectedTypes":["URL"]}}},"StructuredValue":{"extends":"Intangible","properties":[]},"ContactPoint":{"extends":"StructuredValue","properties":{"areaServed":{"expectedTypes":["AdministrativeArea"]},"availableLanguage":{"expectedTypes":["Language"]},"contactOption":{"expectedTypes":["ContactPointOption"]},"contactType":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"faxNumber":{"expectedTypes":["Text"]},"hoursAvailable":{"expectedTypes":["OpeningHoursSpecification"]},"productSupported":{"expectedTypes":["Product","Text"]},"telephone":{"expectedTypes":["Text"]}}},"PostalAddress":{"extends":"ContactPoint","properties":{"addressCountry":{"expectedTypes":["Country"]},"addressLocality":{"expectedTypes":["Text"]},"addressRegion":{"expectedTypes":["Text"]},"postOfficeBoxNumber":{"expectedTypes":["Text"]},"postalCode":{"expectedTypes":["Text"]},"streetAddress":{"expectedTypes":["Text"]}}},"DatedMoneySpecification":{"extends":"StructuredValue","properties":{"amount":{"expectedTypes":["Number"]},"currency":{"expectedTypes":["Text"]},"endDate":{"expectedTypes":["Date"]},"startDate":{"expectedTypes":["Date"]}}},"GeoCoordinates":{"extends":"StructuredValue","properties":{"elevation":{"expectedTypes":["Text","Number"]},"latitude":{"expectedTypes":["Text","Number"]},"longitude":{"expectedTypes":["Text","Number"]}}},"GeoShape":{"extends":"StructuredValue","properties":{"box":{"expectedTypes":["Text"]},"circle":{"expectedTypes":["Text"]},"elevation":{"expectedTypes":["Number","Text"]},"line":{"expectedTypes":["Text"]},"polygon":{"expectedTypes":["Text"]}}},"NutritionInformation":{"extends":"StructuredValue","properties":{"calories":{"expectedTypes":["Energy"]},"carbohydrateContent":{"expectedTypes":["Mass"]},"cholesterolContent":{"expectedTypes":["Mass"]},"fatContent":{"expectedTypes":["Mass"]},"fiberContent":{"expectedTypes":["Mass"]},"proteinContent":{"expectedTypes":["Mass"]},"saturatedFatContent":{"expectedTypes":["Mass"]},"servingSize":{"expectedTypes":["Text"]},"sodiumContent":{"expectedTypes":["Mass"]},"sugarContent":{"expectedTypes":["Mass"]},"transFatContent":{"expectedTypes":["Mass"]},"unsaturatedFatContent":{"expectedTypes":["Mass"]}}},"OpeningHoursSpecification":{"extends":"StructuredValue","properties":{"closes":{"expectedTypes":["Time"]},"dayOfWeek":{"expectedTypes":["DayOfWeek"]},"opens":{"expectedTypes":["Time"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]}}},"OwnershipInfo":{"extends":"StructuredValue","properties":{"acquiredFrom":{"expectedTypes":["Organization","Person"]},"ownedFrom":{"expectedTypes":["DateTime"]},"ownedThrough":{"expectedTypes":["DateTime"]},"typeOfGood":{"expectedTypes":["Product"]}}},"PriceSpecification":{"extends":"StructuredValue","properties":{"eligibleQuantity":{"expectedTypes":["QuantitativeValue"]},"eligibleTransactionVolume":{"expectedTypes":["PriceSpecification"]},"maxPrice":{"expectedTypes":["Number"]},"minPrice":{"expectedTypes":["Number"]},"price":{"expectedTypes":["Number","Text"]},"priceCurrency":{"expectedTypes":["Text"]},"validFrom":{"expectedTypes":["DateTime"]},"validThrough":{"expectedTypes":["DateTime"]},"valueAddedTaxIncluded":{"expectedTypes":["Boolean"]}}},"DeliveryChargeSpecification":{"extends":"PriceSpecification","properties":{"appliesToDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"eligibleRegion":{"expectedTypes":["Text","GeoShape"]}}},"PaymentChargeSpecification":{"extends":"PriceSpecification","properties":{"appliesToDeliveryMethod":{"expectedTypes":["DeliveryMethod"]},"appliesToPaymentMethod":{"expectedTypes":["PaymentMethod"]}}},"UnitPriceSpecification":{"extends":"PriceSpecification","properties":{"billingIncrement":{"expectedTypes":["Number"]},"priceType":{"expectedTypes":["Text"]},"unitCode":{"expectedTypes":["Text"]}}},"QuantitativeValue":{"extends":"StructuredValue","properties":{"maxValue":{"expectedTypes":["Number"]},"minValue":{"expectedTypes":["Number"]},"unitCode":{"expectedTypes":["Text"]},"value":{"expectedTypes":["Number"]},"valueReference":{"expectedTypes":["StructuredValue","Enumeration"]}}},"TypeAndQuantityNode":{"extends":"StructuredValue","properties":{"amountOfThisGood":{"expectedTypes":["Number"]},"businessFunction":{"expectedTypes":["BusinessFunction"]},"typeOfGood":{"expectedTypes":["Product"]},"unitCode":{"expectedTypes":["Text"]}}},"WarrantyPromise":{"extends":"StructuredValue","properties":{"durationOfWarranty":{"expectedTypes":["QuantitativeValue"]},"warrantyScope":{"expectedTypes":["WarrantyScope"]}}},"Ticket":{"extends":"Intangible","properties":{"dateIssued":{"expectedTypes":["DateTime"]},"issuedBy":{"expectedTypes":["Organization"]},"priceCurrency":{"expectedTypes":["Text"]},"ticketNumber":{"expectedTypes":["Text"]},"ticketToken":{"expectedTypes":["Text","URL"]},"ticketedSeat":{"expectedTypes":["Seat"]},"totalPrice":{"expectedTypes":["Text","Number","PriceSpecification"]},"underName":{"expectedTypes":["Person","Organization"]}}},"TrainTrip":{"extends":"Intangible","properties":{"arrivalPlatform":{"expectedTypes":["Text"]},"arrivalStation":{"expectedTypes":["TrainStation"]},"arrivalTime":{"expectedTypes":["DateTime"]},"departurePlatform":{"expectedTypes":["Text"]},"departureStation":{"expectedTypes":["TrainStation"]},"departureTime":{"expectedTypes":["DateTime"]},"provider":{"expectedTypes":["Person","Organization"]},"trainName":{"expectedTypes":["Text"]},"trainNumber":{"expectedTypes":["Text"]}}},"MedicalEntity":{"extends":"Thing","properties":{"code":{"expectedTypes":["MedicalCode"]},"guideline":{"expectedTypes":["MedicalGuideline"]},"medicineSystem":{"expectedTypes":["MedicineSystem"]},"recognizingAuthority":{"expectedTypes":["Organization"]},"relevantSpecialty":{"expectedTypes":["MedicalSpecialty"]},"study":{"expectedTypes":["MedicalStudy"]}}},"AnatomicalStructure":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"bodyLocation":{"expectedTypes":["Text"]},"connectedTo":{"expectedTypes":["AnatomicalStructure"]},"diagram":{"expectedTypes":["ImageObject"]},"function":{"expectedTypes":["Text"]},"partOfSystem":{"expectedTypes":["AnatomicalSystem"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]},"subStructure":{"expectedTypes":["AnatomicalStructure"]}}},"Bone":{"extends":"AnatomicalStructure","properties":[]},"BrainStructure":{"extends":"AnatomicalStructure","properties":[]},"Joint":{"extends":"AnatomicalStructure","properties":{"biomechnicalClass":{"expectedTypes":["Text"]},"functionalClass":{"expectedTypes":["Text"]},"structuralClass":{"expectedTypes":["Text"]}}},"Ligament":{"extends":"AnatomicalStructure","properties":[]},"Muscle":{"extends":"AnatomicalStructure","properties":{"antagonist":{"expectedTypes":["Muscle"]},"bloodSupply":{"expectedTypes":["Vessel"]},"insertion":{"expectedTypes":["AnatomicalStructure"]},"muscleAction":{"expectedTypes":["Text"]},"nerve":{"expectedTypes":["Nerve"]},"origin":{"expectedTypes":["AnatomicalStructure"]}}},"Nerve":{"extends":"AnatomicalStructure","properties":{"branch":{"expectedTypes":["AnatomicalStructure"]},"nerveMotor":{"expectedTypes":["Muscle"]},"sensoryUnit":{"expectedTypes":["SuperficialAnatomy","AnatomicalStructure"]},"sourcedFrom":{"expectedTypes":["BrainStructure"]}}},"Vessel":{"extends":"AnatomicalStructure","properties":[]},"Artery":{"extends":"Vessel","properties":{"arterialBranch":{"expectedTypes":["AnatomicalStructure"]},"source":{"expectedTypes":["AnatomicalStructure"]},"supplyTo":{"expectedTypes":["AnatomicalStructure"]}}},"LymphaticVessel":{"extends":"Vessel","properties":{"originatesFrom":{"expectedTypes":["Vessel"]},"regionDrained":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"runsTo":{"expectedTypes":["Vessel"]}}},"Vein":{"extends":"Vessel","properties":{"drainsTo":{"expectedTypes":["Vessel"]},"regionDrained":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"tributary":{"expectedTypes":["AnatomicalStructure"]}}},"AnatomicalSystem":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"comprisedOf":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedStructure":{"expectedTypes":["AnatomicalStructure"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]}}},"MedicalCause":{"extends":"MedicalEntity","properties":{"causeOf":{"expectedTypes":["MedicalEntity"]}}},"MedicalCondition":{"extends":"MedicalEntity","properties":{"associatedAnatomy":{"expectedTypes":["AnatomicalSystem","SuperficialAnatomy","AnatomicalStructure"]},"cause":{"expectedTypes":["MedicalCause"]},"differentialDiagnosis":{"expectedTypes":["DDxElement"]},"epidemiology":{"expectedTypes":["Text"]},"expectedPrognosis":{"expectedTypes":["Text"]},"naturalProgression":{"expectedTypes":["Text"]},"pathophysiology":{"expectedTypes":["Text"]},"possibleComplication":{"expectedTypes":["Text"]},"possibleTreatment":{"expectedTypes":["MedicalTherapy"]},"primaryPrevention":{"expectedTypes":["MedicalTherapy"]},"riskFactor":{"expectedTypes":["MedicalRiskFactor"]},"secondaryPrevention":{"expectedTypes":["MedicalTherapy"]},"signOrSymptom":{"expectedTypes":["MedicalSignOrSymptom"]},"stage":{"expectedTypes":["MedicalConditionStage"]},"subtype":{"expectedTypes":["Text"]},"typicalTest":{"expectedTypes":["MedicalTest"]}}},"InfectiousDisease":{"extends":"MedicalCondition","properties":{"infectiousAgent":{"expectedTypes":["Text"]},"infectiousAgentClass":{"expectedTypes":["InfectiousAgentClass"]},"transmissionMethod":{"expectedTypes":["Text"]}}},"MedicalContraindication":{"extends":"MedicalEntity","properties":[]},"MedicalDevice":{"extends":"MedicalEntity","properties":{"adverseOutcome":{"expectedTypes":["MedicalEntity"]},"contraindication":{"expectedTypes":["MedicalContraindication"]},"indication":{"expectedTypes":["MedicalIndication"]},"postOp":{"expectedTypes":["Text"]},"preOp":{"expectedTypes":["Text"]},"procedure":{"expectedTypes":["Text"]},"purpose":{"expectedTypes":["MedicalDevicePurpose","Thing"]},"seriousAdverseOutcome":{"expectedTypes":["MedicalEntity"]}}},"MedicalGuideline":{"extends":"MedicalEntity","properties":{"evidenceLevel":{"expectedTypes":["MedicalEvidenceLevel"]},"evidenceOrigin":{"expectedTypes":["Text"]},"guidelineDate":{"expectedTypes":["Date"]},"guidelineSubject":{"expectedTypes":["MedicalEntity"]}}},"MedicalGuidelineContraindication":{"extends":"MedicalGuideline","properties":[]},"MedicalGuidelineRecommendation":{"extends":"MedicalGuideline","properties":{"recommendationStrength":{"expectedTypes":["Text"]}}},"MedicalIndication":{"extends":"MedicalEntity","properties":[]},"ApprovedIndication":{"extends":"MedicalIndication","properties":[]},"PreventionIndication":{"extends":"MedicalIndication","properties":[]},"TreatmentIndication":{"extends":"MedicalIndication","properties":[]},"MedicalIntangible":{"extends":"MedicalEntity","properties":[]},"DDxElement":{"extends":"MedicalIntangible","properties":{"diagnosis":{"expectedTypes":["MedicalCondition"]},"distinguishingSign":{"expectedTypes":["MedicalSignOrSymptom"]}}},"DoseSchedule":{"extends":"MedicalIntangible","properties":{"doseUnit":{"expectedTypes":["Text"]},"doseValue":{"expectedTypes":["Number"]},"frequency":{"expectedTypes":["Text"]},"targetPopulation":{"expectedTypes":["Text"]}}},"MaximumDoseSchedule":{"extends":"DoseSchedule","properties":[]},"RecommendedDoseSchedule":{"extends":"DoseSchedule","properties":[]},"ReportedDoseSchedule":{"extends":"DoseSchedule","properties":[]},"DrugCost":{"extends":"MedicalIntangible","properties":{"applicableLocation":{"expectedTypes":["AdministrativeArea"]},"costCategory":{"expectedTypes":["DrugCostCategory"]},"costCurrency":{"expectedTypes":["Text"]},"costOrigin":{"expectedTypes":["Text"]},"costPerUnit":{"expectedTypes":["Text","Number"]},"drugUnit":{"expectedTypes":["Text"]}}},"DrugLegalStatus":{"extends":"MedicalIntangible","properties":{"applicableLocation":{"expectedTypes":["AdministrativeArea"]}}},"DrugStrength":{"extends":"MedicalIntangible","properties":{"activeIngredient":{"expectedTypes":["Text"]},"availableIn":{"expectedTypes":["AdministrativeArea"]},"strengthUnit":{"expectedTypes":["Text"]},"strengthValue":{"expectedTypes":["Number"]}}},"MedicalCode":{"extends":"MedicalIntangible","properties":{"codeValue":{"expectedTypes":["Text"]},"codingSystem":{"expectedTypes":["Text"]}}},"MedicalConditionStage":{"extends":"MedicalIntangible","properties":{"stageAsNumber":{"expectedTypes":["Number"]},"subStageSuffix":{"expectedTypes":["Text"]}}},"":{"extends":"","properties":[]},"MedicalProcedure":{"extends":"MedicalEntity","properties":{"followup":{"expectedTypes":["Text"]},"howPerformed":{"expectedTypes":["Text"]},"preparation":{"expectedTypes":["Text"]},"procedureType":{"expectedTypes":["MedicalProcedureType"]}}},"DiagnosticProcedure":{"extends":"MedicalProcedure","properties":[]},"PalliativeProcedure":{"extends":"MedicalTherapy","properties":[]},"TherapeuticProcedure":{"extends":"MedicalTherapy","properties":[]},"MedicalRiskEstimator":{"extends":"MedicalEntity","properties":{"estimatesRiskOf":{"expectedTypes":["MedicalEntity"]},"includedRiskFactor":{"expectedTypes":["MedicalRiskFactor"]}}},"MedicalRiskCalculator":{"extends":"MedicalRiskEstimator","properties":[]},"MedicalRiskScore":{"extends":"MedicalRiskEstimator","properties":{"algorithm":{"expectedTypes":["Text"]}}},"MedicalRiskFactor":{"extends":"MedicalEntity","properties":{"increasesRiskOf":{"expectedTypes":["MedicalEntity"]}}},"MedicalSignOrSymptom":{"extends":"MedicalEntity","properties":{"cause":{"expectedTypes":["MedicalCause"]},"possibleTreatment":{"expectedTypes":["MedicalTherapy"]}}},"MedicalSign":{"extends":"MedicalSignOrSymptom","properties":{"identifyingExam":{"expectedTypes":["PhysicalExam"]},"identifyingTest":{"expectedTypes":["MedicalTest"]}}},"MedicalSymptom":{"extends":"MedicalSignOrSymptom","properties":[]},"MedicalStudy":{"extends":"MedicalEntity","properties":{"outcome":{"expectedTypes":["Text"]},"population":{"expectedTypes":["Text"]},"sponsor":{"expectedTypes":["Organization"]},"status":{"expectedTypes":["MedicalStudyStatus"]},"studyLocation":{"expectedTypes":["AdministrativeArea"]},"studySubject":{"expectedTypes":["MedicalEntity"]}}},"MedicalObservationalStudy":{"extends":"MedicalStudy","properties":{"studyDesign":{"expectedTypes":["MedicalObservationalStudyDesign"]}}},"MedicalTrial":{"extends":"MedicalStudy","properties":{"phase":{"expectedTypes":["Text"]},"trialDesign":{"expectedTypes":["MedicalTrialDesign"]}}},"MedicalTest":{"extends":"MedicalEntity","properties":{"affectedBy":{"expectedTypes":["Drug"]},"normalRange":{"expectedTypes":["Text"]},"signDetected":{"expectedTypes":["MedicalSign"]},"usedToDiagnose":{"expectedTypes":["MedicalCondition"]},"usesDevice":{"expectedTypes":["MedicalDevice"]}}},"BloodTest":{"extends":"MedicalTest","properties":[]},"ImagingTest":{"extends":"MedicalTest","properties":{"imagingTechnique":{"expectedTypes":["MedicalImagingTechnique"]}}},"MedicalTestPanel":{"extends":"MedicalTest","properties":{"subTest":{"expectedTypes":["MedicalTest"]}}},"PathologyTest":{"extends":"MedicalTest","properties":{"tissueSample":{"expectedTypes":["Text"]}}},"MedicalTherapy":{"extends":"MedicalEntity","properties":{"adverseOutcome":{"expectedTypes":["MedicalEntity"]},"contraindication":{"expectedTypes":["MedicalContraindication"]},"duplicateTherapy":{"expectedTypes":["MedicalTherapy"]},"indication":{"expectedTypes":["MedicalIndication"]},"seriousAdverseOutcome":{"expectedTypes":["MedicalEntity"]}}},"DietarySupplement":{"extends":"MedicalTherapy","properties":{"activeIngredient":{"expectedTypes":["Text"]},"background":{"expectedTypes":["Text"]},"dosageForm":{"expectedTypes":["Text"]},"isProprietary":{"expectedTypes":["Boolean"]},"legalStatus":{"expectedTypes":["DrugLegalStatus"]},"manufacturer":{"expectedTypes":["Organization"]},"maximumIntake":{"expectedTypes":["MaximumDoseSchedule"]},"mechanismOfAction":{"expectedTypes":["Text"]},"nonProprietaryName":{"expectedTypes":["Text"]},"recommendedIntake":{"expectedTypes":["RecommendedDoseSchedule"]},"safetyConsideration":{"expectedTypes":["Text"]},"targetPopulation":{"expectedTypes":["Text"]}}},"Drug":{"extends":"MedicalTherapy","properties":{"activeIngredient":{"expectedTypes":["Text"]},"administrationRoute":{"expectedTypes":["Text"]},"alcoholWarning":{"expectedTypes":["Text"]},"availableStrength":{"expectedTypes":["DrugStrength"]},"breastfeedingWarning":{"expectedTypes":["Text"]},"clinicalPharmacology":{"expectedTypes":["Text"]},"cost":{"expectedTypes":["DrugCost"]},"dosageForm":{"expectedTypes":["Text"]},"doseSchedule":{"expectedTypes":["DoseSchedule"]},"drugClass":{"expectedTypes":["DrugClass"]},"foodWarning":{"expectedTypes":["Text"]},"interactingDrug":{"expectedTypes":["Drug"]},"isAvailableGenerically":{"expectedTypes":["Boolean"]},"isProprietary":{"expectedTypes":["Boolean"]},"labelDetails":{"expectedTypes":["URL"]},"legalStatus":{"expectedTypes":["DrugLegalStatus"]},"manufacturer":{"expectedTypes":["Organization"]},"mechanismOfAction":{"expectedTypes":["Text"]},"nonProprietaryName":{"expectedTypes":["Text"]},"overdosage":{"expectedTypes":["Text"]},"pregnancyCategory":{"expectedTypes":["DrugPregnancyCategory"]},"pregnancyWarning":{"expectedTypes":["Text"]},"prescribingInfo":{"expectedTypes":["URL"]},"prescriptionStatus":{"expectedTypes":["DrugPrescriptionStatus"]},"relatedDrug":{"expectedTypes":["Drug"]},"warning":{"expectedTypes":["Text","URL"]}}},"DrugClass":{"extends":"MedicalTherapy","properties":{"drug":{"expectedTypes":["Drug"]}}},"LifestyleModification":{"extends":"MedicalTherapy","properties":[]},"PhysicalActivity":{"extends":"LifestyleModification","properties":{"associatedAnatomy":{"expectedTypes":["AnatomicalSystem","SuperficialAnatomy","AnatomicalStructure"]},"category":{"expectedTypes":["PhysicalActivityCategory","Thing","Text"]},"epidemiology":{"expectedTypes":["Text"]},"pathophysiology":{"expectedTypes":["Text"]}}},"PhysicalTherapy":{"extends":"MedicalTherapy","properties":[]},"PsychologicalTreatment":{"extends":"MedicalTherapy","properties":[]},"RadiationTherapy":{"extends":"MedicalTherapy","properties":[]},"SuperficialAnatomy":{"extends":"MedicalEntity","properties":{"associatedPathophysiology":{"expectedTypes":["Text"]},"relatedAnatomy":{"expectedTypes":["AnatomicalSystem","AnatomicalStructure"]},"relatedCondition":{"expectedTypes":["MedicalCondition"]},"relatedTherapy":{"expectedTypes":["MedicalTherapy"]},"significance":{"expectedTypes":["Text"]}}},"Organization":{"extends":"Thing","properties":{"address":{"expectedTypes":["PostalAddress"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"brand":{"expectedTypes":["Brand","Organization"]},"contactPoint":{"expectedTypes":["ContactPoint"]},"department":{"expectedTypes":["Organization"]},"dissolutionDate":{"expectedTypes":["Date"]},"duns":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"employee":{"expectedTypes":["Person"]},"event":{"expectedTypes":["Event"]},"faxNumber":{"expectedTypes":["Text"]},"founder":{"expectedTypes":["Person"]},"foundingDate":{"expectedTypes":["Date"]},"foundingLocation":{"expectedTypes":["Place"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasPOS":{"expectedTypes":["Place"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"legalName":{"expectedTypes":["Text"]},"location":{"expectedTypes":["Place","PostalAddress"]},"logo":{"expectedTypes":["ImageObject","URL"]},"makesOffer":{"expectedTypes":["Offer"]},"member":{"expectedTypes":["Person","Organization"]},"memberOf":{"expectedTypes":["ProgramMembership","Organization"]},"naics":{"expectedTypes":["Text"]},"owns":{"expectedTypes":["Product","OwnershipInfo"]},"review":{"expectedTypes":["Review"]},"seeks":{"expectedTypes":["Demand"]},"subOrganization":{"expectedTypes":["Organization"]},"taxID":{"expectedTypes":["Text"]},"telephone":{"expectedTypes":["Text"]},"vatID":{"expectedTypes":["Text"]}}},"Airline":{"extends":"Organization","properties":{"iataCode":{"expectedTypes":["Text"]}}},"Corporation":{"extends":"Organization","properties":{"tickerSymbol":{"expectedTypes":["Text"]}}},"EducationalOrganization":{"extends":"Organization","properties":{"alumni":{"expectedTypes":["Person"]}}},"CollegeOrUniversity":{"extends":"EducationalOrganization","properties":[]},"ElementarySchool":{"extends":"EducationalOrganization","properties":[]},"HighSchool":{"extends":"EducationalOrganization","properties":[]},"MiddleSchool":{"extends":"EducationalOrganization","properties":[]},"Preschool":{"extends":"EducationalOrganization","properties":[]},"School":{"extends":"EducationalOrganization","properties":[]},"GovernmentOrganization":{"extends":"Organization","properties":[]},"LocalBusiness":{"extends":"Organization","properties":{"branchOf":{"expectedTypes":["Organization"]},"currenciesAccepted":{"expectedTypes":["Text"]},"openingHours":{"expectedTypes":["Duration"]},"paymentAccepted":{"expectedTypes":["Text"]},"priceRange":{"expectedTypes":["Text"]}}},"AnimalShelter":{"extends":"LocalBusiness","properties":[]},"AutomotiveBusiness":{"extends":"LocalBusiness","properties":[]},"AutoBodyShop":{"extends":"AutomotiveBusiness","properties":[]},"AutoDealer":{"extends":"AutomotiveBusiness","properties":[]},"AutoPartsStore":{"extends":"Store","properties":[]},"AutoRental":{"extends":"AutomotiveBusiness","properties":[]},"AutoRepair":{"extends":"AutomotiveBusiness","properties":[]},"AutoWash":{"extends":"AutomotiveBusiness","properties":[]},"GasStation":{"extends":"AutomotiveBusiness","properties":[]},"MotorcycleDealer":{"extends":"AutomotiveBusiness","properties":[]},"MotorcycleRepair":{"extends":"AutomotiveBusiness","properties":[]},"ChildCare":{"extends":"LocalBusiness","properties":[]},"DryCleaningOrLaundry":{"extends":"LocalBusiness","properties":[]},"EmergencyService":{"extends":"LocalBusiness","properties":[]},"FireStation":{"extends":"CivicStructure","properties":[]},"Hospital":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTest","MedicalTherapy","MedicalProcedure"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"PoliceStation":{"extends":"CivicStructure","properties":[]},"EmploymentAgency":{"extends":"LocalBusiness","properties":[]},"EntertainmentBusiness":{"extends":"LocalBusiness","properties":[]},"AdultEntertainment":{"extends":"EntertainmentBusiness","properties":[]},"AmusementPark":{"extends":"EntertainmentBusiness","properties":[]},"ArtGallery":{"extends":"EntertainmentBusiness","properties":[]},"Casino":{"extends":"EntertainmentBusiness","properties":[]},"ComedyClub":{"extends":"EntertainmentBusiness","properties":[]},"MovieTheater":{"extends":"EntertainmentBusiness","properties":[]},"NightClub":{"extends":"EntertainmentBusiness","properties":[]},"FinancialService":{"extends":"LocalBusiness","properties":[]},"AccountingService":{"extends":"FinancialService","properties":[]},"AutomatedTeller":{"extends":"FinancialService","properties":[]},"BankOrCreditUnion":{"extends":"FinancialService","properties":[]},"InsuranceAgency":{"extends":"FinancialService","properties":[]},"FoodEstablishment":{"extends":"LocalBusiness","properties":{"acceptsReservations":{"expectedTypes":["Text","Boolean","URL"]},"menu":{"expectedTypes":["Text","URL"]},"servesCuisine":{"expectedTypes":["Text"]}}},"Bakery":{"extends":"FoodEstablishment","properties":[]},"BarOrPub":{"extends":"FoodEstablishment","properties":[]},"Brewery":{"extends":"FoodEstablishment","properties":[]},"CafeOrCoffeeShop":{"extends":"FoodEstablishment","properties":[]},"FastFoodRestaurant":{"extends":"FoodEstablishment","properties":[]},"IceCreamShop":{"extends":"FoodEstablishment","properties":[]},"Restaurant":{"extends":"FoodEstablishment","properties":[]},"Winery":{"extends":"FoodEstablishment","properties":[]},"GovernmentOffice":{"extends":"LocalBusiness","properties":[]},"PostOffice":{"extends":"GovernmentOffice","properties":[]},"HealthAndBeautyBusiness":{"extends":"LocalBusiness","properties":[]},"BeautySalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"DaySpa":{"extends":"HealthAndBeautyBusiness","properties":[]},"HairSalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"HealthClub":{"extends":"HealthAndBeautyBusiness","properties":[]},"NailSalon":{"extends":"HealthAndBeautyBusiness","properties":[]},"TattooParlor":{"extends":"HealthAndBeautyBusiness","properties":[]},"HomeAndConstructionBusiness":{"extends":"LocalBusiness","properties":[]},"Electrician":{"extends":"HomeAndConstructionBusiness","properties":[]},"GeneralContractor":{"extends":"HomeAndConstructionBusiness","properties":[]},"HVACBusiness":{"extends":"HomeAndConstructionBusiness","properties":[]},"HousePainter":{"extends":"HomeAndConstructionBusiness","properties":[]},"Locksmith":{"extends":"HomeAndConstructionBusiness","properties":[]},"MovingCompany":{"extends":"HomeAndConstructionBusiness","properties":[]},"Plumber":{"extends":"HomeAndConstructionBusiness","properties":[]},"RoofingContractor":{"extends":"HomeAndConstructionBusiness","properties":[]},"InternetCafe":{"extends":"LocalBusiness","properties":[]},"Library":{"extends":"LocalBusiness","properties":[]},"LodgingBusiness":{"extends":"LocalBusiness","properties":[]},"BedAndBreakfast":{"extends":"LodgingBusiness","properties":[]},"Hostel":{"extends":"LodgingBusiness","properties":[]},"Hotel":{"extends":"LodgingBusiness","properties":[]},"Motel":{"extends":"LodgingBusiness","properties":[]},"MedicalOrganization":{"extends":"LocalBusiness","properties":[]},"Dentist":{"extends":"MedicalOrganization","properties":[]},"DiagnosticLab":{"extends":"MedicalOrganization","properties":{"availableTest":{"expectedTypes":["MedicalTest"]}}},"MedicalClinic":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTherapy","MedicalProcedure","MedicalTest"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"Optician":{"extends":"MedicalOrganization","properties":[]},"Pharmacy":{"extends":"MedicalOrganization","properties":[]},"Physician":{"extends":"MedicalOrganization","properties":{"availableService":{"expectedTypes":["MedicalTherapy","MedicalProcedure","MedicalTest"]},"hospitalAffiliation":{"expectedTypes":["Hospital"]},"medicalSpecialty":{"expectedTypes":["MedicalSpecialty"]}}},"VeterinaryCare":{"extends":"MedicalOrganization","properties":[]},"ProfessionalService":{"extends":"LocalBusiness","properties":[]},"Attorney":{"extends":"ProfessionalService","properties":[]},"Notary":{"extends":"ProfessionalService","properties":[]},"RadioStation":{"extends":"LocalBusiness","properties":[]},"RealEstateAgent":{"extends":"LocalBusiness","properties":[]},"RecyclingCenter":{"extends":"LocalBusiness","properties":[]},"SelfStorage":{"extends":"LocalBusiness","properties":[]},"ShoppingCenter":{"extends":"LocalBusiness","properties":[]},"SportsActivityLocation":{"extends":"LocalBusiness","properties":[]},"BowlingAlley":{"extends":"SportsActivityLocation","properties":[]},"ExerciseGym":{"extends":"SportsActivityLocation","properties":[]},"GolfCourse":{"extends":"SportsActivityLocation","properties":[]},"PublicSwimmingPool":{"extends":"SportsActivityLocation","properties":[]},"SkiResort":{"extends":"SportsActivityLocation","properties":[]},"SportsClub":{"extends":"SportsActivityLocation","properties":[]},"StadiumOrArena":{"extends":"CivicStructure","properties":[]},"TennisComplex":{"extends":"SportsActivityLocation","properties":[]},"Store":{"extends":"LocalBusiness","properties":[]},"BikeStore":{"extends":"Store","properties":[]},"BookStore":{"extends":"Store","properties":[]},"ClothingStore":{"extends":"Store","properties":[]},"ComputerStore":{"extends":"Store","properties":[]},"ConvenienceStore":{"extends":"Store","properties":[]},"DepartmentStore":{"extends":"Store","properties":[]},"ElectronicsStore":{"extends":"Store","properties":[]},"Florist":{"extends":"Store","properties":[]},"FurnitureStore":{"extends":"Store","properties":[]},"GardenStore":{"extends":"Store","properties":[]},"GroceryStore":{"extends":"Store","properties":[]},"HardwareStore":{"extends":"Store","properties":[]},"HobbyShop":{"extends":"Store","properties":[]},"HomeGoodsStore":{"extends":"Store","properties":[]},"JewelryStore":{"extends":"Store","properties":[]},"LiquorStore":{"extends":"Store","properties":[]},"MensClothingStore":{"extends":"Store","properties":[]},"MobilePhoneStore":{"extends":"Store","properties":[]},"MovieRentalStore":{"extends":"Store","properties":[]},"MusicStore":{"extends":"Store","properties":[]},"OfficeEquipmentStore":{"extends":"Store","properties":[]},"OutletStore":{"extends":"Store","properties":[]},"PawnShop":{"extends":"Store","properties":[]},"PetStore":{"extends":"Store","properties":[]},"ShoeStore":{"extends":"Store","properties":[]},"SportingGoodsStore":{"extends":"Store","properties":[]},"TireShop":{"extends":"Store","properties":[]},"ToyStore":{"extends":"Store","properties":[]},"WholesaleStore":{"extends":"Store","properties":[]},"TelevisionStation":{"extends":"LocalBusiness","properties":[]},"TouristInformationCenter":{"extends":"LocalBusiness","properties":[]},"TravelAgency":{"extends":"LocalBusiness","properties":[]},"NGO":{"extends":"Organization","properties":[]},"PerformingGroup":{"extends":"Organization","properties":[]},"DanceGroup":{"extends":"PerformingGroup","properties":[]},"MusicGroup":{"extends":"PerformingGroup","properties":{"album":{"expectedTypes":["MusicAlbum"]},"genre":{"expectedTypes":["Text"]},"track":{"expectedTypes":["ItemList","MusicRecording"]}}},"TheaterGroup":{"extends":"PerformingGroup","properties":[]},"SportsOrganization":{"extends":"Organization","properties":{"sport":{"expectedTypes":["Text","URL"]}}},"SportsTeam":{"extends":"SportsOrganization","properties":{"athlete":{"expectedTypes":["Person"]},"coach":{"expectedTypes":["Person"]}}},"Person":{"extends":"Thing","properties":{"additionalName":{"expectedTypes":["Text"]},"address":{"expectedTypes":["PostalAddress"]},"affiliation":{"expectedTypes":["Organization"]},"alumniOf":{"expectedTypes":["EducationalOrganization"]},"award":{"expectedTypes":["Text"]},"birthDate":{"expectedTypes":["Date"]},"birthPlace":{"expectedTypes":["Place"]},"brand":{"expectedTypes":["Brand","Organization"]},"children":{"expectedTypes":["Person"]},"colleague":{"expectedTypes":["Person"]},"contactPoint":{"expectedTypes":["ContactPoint"]},"deathDate":{"expectedTypes":["Date"]},"deathPlace":{"expectedTypes":["Place"]},"duns":{"expectedTypes":["Text"]},"email":{"expectedTypes":["Text"]},"familyName":{"expectedTypes":["Text"]},"faxNumber":{"expectedTypes":["Text"]},"follows":{"expectedTypes":["Person"]},"gender":{"expectedTypes":["Text"]},"givenName":{"expectedTypes":["Text"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasPOS":{"expectedTypes":["Place"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"homeLocation":{"expectedTypes":["ContactPoint","Place"]},"honorificPrefix":{"expectedTypes":["Text"]},"honorificSuffix":{"expectedTypes":["Text"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"jobTitle":{"expectedTypes":["Text"]},"knows":{"expectedTypes":["Person"]},"makesOffer":{"expectedTypes":["Offer"]},"memberOf":{"expectedTypes":["ProgramMembership","Organization"]},"naics":{"expectedTypes":["Text"]},"nationality":{"expectedTypes":["Country"]},"netWorth":{"expectedTypes":["PriceSpecification"]},"owns":{"expectedTypes":["Product","OwnershipInfo"]},"parent":{"expectedTypes":["Person"]},"performerIn":{"expectedTypes":["Event"]},"relatedTo":{"expectedTypes":["Person"]},"seeks":{"expectedTypes":["Demand"]},"sibling":{"expectedTypes":["Person"]},"spouse":{"expectedTypes":["Person"]},"taxID":{"expectedTypes":["Text"]},"telephone":{"expectedTypes":["Text"]},"vatID":{"expectedTypes":["Text"]},"weight":{"expectedTypes":["QuantitativeValue"]},"workLocation":{"expectedTypes":["ContactPoint","Place"]},"worksFor":{"expectedTypes":["Organization"]}}},"Place":{"extends":"Thing","properties":{"address":{"expectedTypes":["PostalAddress"]},"aggregateRating":{"expectedTypes":["AggregateRating"]},"containedIn":{"expectedTypes":["Place"]},"event":{"expectedTypes":["Event"]},"faxNumber":{"expectedTypes":["Text"]},"geo":{"expectedTypes":["GeoCoordinates","GeoShape"]},"globalLocationNumber":{"expectedTypes":["Text"]},"hasMap":{"expectedTypes":["Map","URL"]},"interactionCount":{"expectedTypes":["Text"]},"isicV4":{"expectedTypes":["Text"]},"logo":{"expectedTypes":["ImageObject","URL"]},"openingHoursSpecification":{"expectedTypes":["OpeningHoursSpecification"]},"photo":{"expectedTypes":["ImageObject","Photograph"]},"review":{"expectedTypes":["Review"]},"telephone":{"expectedTypes":["Text"]}}},"AdministrativeArea":{"extends":"Place","properties":[]},"City":{"extends":"AdministrativeArea","properties":[]},"Country":{"extends":"AdministrativeArea","properties":[]},"State":{"extends":"AdministrativeArea","properties":[]},"CivicStructure":{"extends":"Place","properties":{"openingHours":{"expectedTypes":["Duration"]}}},"Airport":{"extends":"CivicStructure","properties":{"iataCode":{"expectedTypes":["Text"]},"icaoCode":{"expectedTypes":["Text"]}}},"Aquarium":{"extends":"CivicStructure","properties":[]},"Beach":{"extends":"CivicStructure","properties":[]},"BusStation":{"extends":"CivicStructure","properties":[]},"BusStop":{"extends":"CivicStructure","properties":[]},"Campground":{"extends":"CivicStructure","properties":[]},"Cemetery":{"extends":"CivicStructure","properties":[]},"Crematorium":{"extends":"CivicStructure","properties":[]},"EventVenue":{"extends":"CivicStructure","properties":[]},"GovernmentBuilding":{"extends":"CivicStructure","properties":[]},"CityHall":{"extends":"GovernmentBuilding","properties":[]},"Courthouse":{"extends":"GovernmentBuilding","properties":[]},"DefenceEstablishment":{"extends":"GovernmentBuilding","properties":[]},"Embassy":{"extends":"GovernmentBuilding","properties":[]},"LegislativeBuilding":{"extends":"GovernmentBuilding","properties":[]},"Museum":{"extends":"CivicStructure","properties":[]},"MusicVenue":{"extends":"CivicStructure","properties":[]},"Park":{"extends":"CivicStructure","properties":[]},"ParkingFacility":{"extends":"CivicStructure","properties":[]},"PerformingArtsTheater":{"extends":"CivicStructure","properties":[]},"PlaceOfWorship":{"extends":"CivicStructure","properties":[]},"BuddhistTemple":{"extends":"PlaceOfWorship","properties":[]},"CatholicChurch":{"extends":"PlaceOfWorship","properties":[]},"Church":{"extends":"PlaceOfWorship","properties":[]},"HinduTemple":{"extends":"PlaceOfWorship","properties":[]},"Mosque":{"extends":"PlaceOfWorship","properties":[]},"Synagogue":{"extends":"PlaceOfWorship","properties":[]},"Playground":{"extends":"CivicStructure","properties":[]},"RVPark":{"extends":"CivicStructure","properties":[]},"SubwayStation":{"extends":"CivicStructure","properties":[]},"TaxiStand":{"extends":"CivicStructure","properties":[]},"TrainStation":{"extends":"CivicStructure","properties":[]},"Zoo":{"extends":"CivicStructure","properties":[]},"Landform":{"extends":"Place","properties":[]},"BodyOfWater":{"extends":"Landform","properties":[]},"Canal":{"extends":"BodyOfWater","properties":[]},"LakeBodyOfWater":{"extends":"BodyOfWater","properties":[]},"OceanBodyOfWater":{"extends":"BodyOfWater","properties":[]},"Pond":{"extends":"BodyOfWater","properties":[]},"Reservoir":{"extends":"BodyOfWater","properties":[]},"RiverBodyOfWater":{"extends":"BodyOfWater","properties":[]},"SeaBodyOfWater":{"extends":"BodyOfWater","properties":[]},"Waterfall":{"extends":"BodyOfWater","properties":[]},"Continent":{"extends":"Landform","properties":[]},"Mountain":{"extends":"Landform","properties":[]},"Volcano":{"extends":"Landform","properties":[]},"LandmarksOrHistoricalBuildings":{"extends":"Place","properties":[]},"Residence":{"extends":"Place","properties":[]},"ApartmentComplex":{"extends":"Residence","properties":[]},"GatedResidenceCommunity":{"extends":"Residence","properties":[]},"SingleFamilyResidence":{"extends":"Residence","properties":[]},"TouristAttraction":{"extends":"Place","properties":[]},"Product":{"extends":"Thing","properties":{"aggregateRating":{"expectedTypes":["AggregateRating"]},"audience":{"expectedTypes":["Audience"]},"brand":{"expectedTypes":["Brand","Organization"]},"color":{"expectedTypes":["Text"]},"depth":{"expectedTypes":["Distance","QuantitativeValue"]},"gtin13":{"expectedTypes":["Text"]},"gtin14":{"expectedTypes":["Text"]},"gtin8":{"expectedTypes":["Text"]},"height":{"expectedTypes":["Distance","QuantitativeValue"]},"isAccessoryOrSparePartFor":{"expectedTypes":["Product"]},"isConsumableFor":{"expectedTypes":["Product"]},"isRelatedTo":{"expectedTypes":["Product"]},"isSimilarTo":{"expectedTypes":["Product"]},"itemCondition":{"expectedTypes":["OfferItemCondition"]},"logo":{"expectedTypes":["ImageObject","URL"]},"manufacturer":{"expectedTypes":["Organization"]},"model":{"expectedTypes":["Text","ProductModel"]},"mpn":{"expectedTypes":["Text"]},"offers":{"expectedTypes":["Offer"]},"productID":{"expectedTypes":["Text"]},"releaseDate":{"expectedTypes":["Date"]},"review":{"expectedTypes":["Review"]},"sku":{"expectedTypes":["Text"]},"weight":{"expectedTypes":["QuantitativeValue"]},"width":{"expectedTypes":["Distance","QuantitativeValue"]}}},"IndividualProduct":{"extends":"Product","properties":{"serialNumber":{"expectedTypes":["Text"]}}},"ProductModel":{"extends":"Product","properties":{"isVariantOf":{"expectedTypes":["ProductModel"]},"predecessorOf":{"expectedTypes":["ProductModel"]},"successorOf":{"expectedTypes":["ProductModel"]}}},"SomeProducts":{"extends":"Product","properties":{"inventoryLevel":{"expectedTypes":["QuantitativeValue"]}}},"Vehicle":{"extends":"Product","properties":[]},"Car":{"extends":"Vehicle","properties":[]}}src/Factory.php000064400000045213152177723700007473 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Access\Access;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Cache\Cache;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Editor\Editor;
use Joomla\CMS\Input\Input;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Mail\Mail;
use Joomla\CMS\Mail\MailHelper;
use Joomla\CMS\Session\Session;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\Registry\Registry;

/**
 * Joomla Platform Factory class.
 *
 * @since  1.7.0
 */
abstract class Factory
{
	/**
	 * Global application object
	 *
	 * @var    CMSApplication
	 * @since  1.7.0
	 */
	public static $application = null;

	/**
	 * Global cache object
	 *
	 * @var    Cache
	 * @since  1.7.0
	 */
	public static $cache = null;

	/**
	 * Global configuraiton object
	 *
	 * @var    \JConfig
	 * @since  1.7.0
	 */
	public static $config = null;

	/**
	 * Container for Date instances
	 *
	 * @var    array
	 * @since  1.7.3
	 */
	public static $dates = array();

	/**
	 * Global session object
	 *
	 * @var    Session
	 * @since  1.7.0
	 */
	public static $session = null;

	/**
	 * Global language object
	 *
	 * @var   Language
	 * @since  1.7.0
	 */
	public static $language = null;

	/**
	 * Global document object
	 *
	 * @var    \JDocument
	 * @since  1.7.0
	 */
	public static $document = null;

	/**
	 * Global ACL object
	 *
	 * @var    Access
	 * @since  1.7.0
	 * @deprecated  4.0
	 */
	public static $acl = null;

	/**
	 * Global database object
	 *
	 * @var    \JDatabaseDriver
	 * @since  1.7.0
	 */
	public static $database = null;

	/**
	 * Global mailer object
	 *
	 * @var    Mail
	 * @since  1.7.0
	 */
	public static $mailer = null;

	/**
	 * Get an application object.
	 *
	 * Returns the global {@link CMSApplication} object, only creating it if it doesn't already exist.
	 *
	 * @param   mixed   $id      A client identifier or name.
	 * @param   array   $config  An optional associative array of configuration settings.
	 * @param   string  $prefix  Application prefix
	 *
	 * @return  CMSApplication object
	 *
	 * @see     JApplication
	 * @since   1.7.0
	 * @throws  \Exception
	 */
	public static function getApplication($id = null, array $config = array(), $prefix = 'J')
	{
		if (!self::$application)
		{
			if (!$id)
			{
				throw new \Exception('Failed to start application', 500);
			}

			self::$application = CMSApplication::getInstance($id);

			// Attach a delegated JLog object to the application
			self::$application->setLogger(Log::createDelegatedLogger());
		}

		return self::$application;
	}

	/**
	 * Get a configuration object
	 *
	 * Returns the global {@link \JConfig} object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $file       The path to the configuration file
	 * @param   string  $type       The type of the configuration file
	 * @param   string  $namespace  The namespace of the configuration file
	 *
	 * @return  Registry
	 *
	 * @see     Registry
	 * @since   1.7.0
	 */
	public static function getConfig($file = null, $type = 'PHP', $namespace = '')
	{
		if (!self::$config)
		{
			if ($file === null)
			{
				$file = JPATH_CONFIGURATION . '/configuration.php';
			}

			self::$config = self::createConfig($file, $type, $namespace);
		}

		return self::$config;
	}

	/**
	 * Get a session object.
	 *
	 * Returns the global {@link Session} object, only creating it if it doesn't already exist.
	 *
	 * @param   array  $options  An array containing session options
	 *
	 * @return  Session object
	 *
	 * @see     Session
	 * @since   1.7.0
	 */
	public static function getSession(array $options = array())
	{
		if (!self::$session)
		{
			self::$session = self::createSession($options);
		}

		return self::$session;
	}

	/**
	 * Get a language object.
	 *
	 * Returns the global {@link Language} object, only creating it if it doesn't already exist.
	 *
	 * @return  Language object
	 *
	 * @see     Language
	 * @since   1.7.0
	 */
	public static function getLanguage()
	{
		if (!self::$language)
		{
			self::$language = self::createLanguage();
		}

		return self::$language;
	}

	/**
	 * Get a document object.
	 *
	 * Returns the global {@link \JDocument} object, only creating it if it doesn't already exist.
	 *
	 * @return  \JDocument object
	 *
	 * @see     \JDocument
	 * @since   1.7.0
	 */
	public static function getDocument()
	{
		if (!self::$document)
		{
			self::$document = self::createDocument();
		}

		return self::$document;
	}

	/**
	 * Get a user object.
	 *
	 * Returns the global {@link User} object, only creating it if it doesn't already exist.
	 *
	 * @param   integer  $id  The user to load - Can be an integer or string - If string, it is converted to ID automatically.
	 *
	 * @return  User object
	 *
	 * @see     User
	 * @since   1.7.0
	 */
	public static function getUser($id = null)
	{
		$instance = self::getSession()->get('user');

		if (is_null($id))
		{
			if (!($instance instanceof User))
			{
				$instance = User::getInstance();
			}
		}
		// Check if we have a string as the id or if the numeric id is the current instance
		elseif (!($instance instanceof User) || is_string($id) || $instance->id !== $id)
		{
			$instance = User::getInstance($id);
		}

		return $instance;
	}

	/**
	 * Get a cache object
	 *
	 * Returns the global {@link CacheController} object
	 *
	 * @param   string  $group    The cache group name
	 * @param   string  $handler  The handler to use
	 * @param   string  $storage  The storage method
	 *
	 * @return  \Joomla\CMS\Cache\CacheController object
	 *
	 * @see     JCache
	 * @since   1.7.0
	 */
	public static function getCache($group = '', $handler = 'callback', $storage = null)
	{
		$hash = md5($group . $handler . $storage);

		if (isset(self::$cache[$hash]))
		{
			return self::$cache[$hash];
		}

		$handler = ($handler == 'function') ? 'callback' : $handler;

		$options = array('defaultgroup' => $group);

		if (isset($storage))
		{
			$options['storage'] = $storage;
		}

		$cache = Cache::getInstance($handler, $options);

		self::$cache[$hash] = $cache;

		return self::$cache[$hash];
	}

	/**
	 * Get an authorization object
	 *
	 * Returns the global {@link Access} object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return  Access object
	 *
	 * @deprecated  4.0 - Use JAccess directly.
	 */
	public static function getAcl()
	{
		Log::add(__METHOD__ . ' is deprecated. Use Access directly.', Log::WARNING, 'deprecated');

		if (!self::$acl)
		{
			self::$acl = new Access;
		}

		return self::$acl;
	}

	/**
	 * Get a database object.
	 *
	 * Returns the global {@link \JDatabaseDriver} object, only creating it if it doesn't already exist.
	 *
	 * @return  \JDatabaseDriver
	 *
	 * @see     \JDatabaseDriver
	 * @since   1.7.0
	 */
	public static function getDbo()
	{
		if (!self::$database)
		{
			self::$database = self::createDbo();
		}

		return self::$database;
	}

	/**
	 * Get a mailer object.
	 *
	 * Returns the global {@link \JMail} object, only creating it if it doesn't already exist.
	 *
	 * @return  \JMail object
	 *
	 * @see     JMail
	 * @since   1.7.0
	 */
	public static function getMailer()
	{
		if (!self::$mailer)
		{
			self::$mailer = self::createMailer();
		}

		$copy = clone self::$mailer;

		return $copy;
	}

	/**
	 * Get a parsed XML Feed Source
	 *
	 * @param   string   $url         Url for feed source.
	 * @param   integer  $cache_time  Time to cache feed for (using internal cache mechanism).
	 *
	 * @return  mixed  SimplePie parsed object on success, false on failure.
	 *
	 * @since   1.7.0
	 * @throws  \BadMethodCallException
	 * @deprecated  4.0  Use directly JFeedFactory or supply SimplePie instead. Mehod will be proxied to JFeedFactory beginning in 3.2
	 */
	public static function getFeedParser($url, $cache_time = 0)
	{
		if (!class_exists('JSimplepieFactory'))
		{
			throw new \BadMethodCallException('JSimplepieFactory not found');
		}

		Log::add(__METHOD__ . ' is deprecated.   Use JFeedFactory() or supply SimplePie instead.', Log::WARNING, 'deprecated');

		return \JSimplepieFactory::getFeedParser($url, $cache_time);
	}

	/**
	 * Reads a XML file.
	 *
	 * @param   string   $data    Full path and file name.
	 * @param   boolean  $isFile  true to load a file or false to load a string.
	 *
	 * @return  mixed    JXMLElement or SimpleXMLElement on success or false on error.
	 *
	 * @see     JXMLElement
	 * @since   1.7.0
	 * @note    When JXMLElement is not present a SimpleXMLElement will be returned.
	 * @deprecated  4.0 - Use SimpleXML directly.
	 */
	public static function getXml($data, $isFile = true)
	{
		Log::add(__METHOD__ . ' is deprecated. Use SimpleXML directly.', Log::WARNING, 'deprecated');

		$class = 'SimpleXMLElement';

		if (class_exists('JXMLElement'))
		{
			$class = 'JXMLElement';
		}

		// Disable libxml errors and allow to fetch error information as needed
		libxml_use_internal_errors(true);

		if ($isFile)
		{
			// Try to load the XML file
			$xml = simplexml_load_file($data, $class);
		}
		else
		{
			// Try to load the XML string
			$xml = simplexml_load_string($data, $class);
		}

		if ($xml === false)
		{
			Log::add(\JText::_('JLIB_UTIL_ERROR_XML_LOAD'), Log::WARNING, 'jerror');

			if ($isFile)
			{
				Log::add($data, Log::WARNING, 'jerror');
			}

			foreach (libxml_get_errors() as $error)
			{
				Log::add($error->message, Log::WARNING, 'jerror');
			}
		}

		return $xml;
	}

	/**
	 * Get an editor object.
	 *
	 * @param   string  $editor  The editor to load, depends on the editor plugins that are installed
	 *
	 * @return  Editor instance of Editor
	 *
	 * @since   1.7.0
	 * @throws  \BadMethodCallException
	 * @deprecated 4.0 - Use Editor directly
	 */
	public static function getEditor($editor = null)
	{
		Log::add(__METHOD__ . ' is deprecated. Use JEditor directly.', Log::WARNING, 'deprecated');

		if (!class_exists('JEditor'))
		{
			throw new \BadMethodCallException('JEditor not found');
		}

		// Get the editor configuration setting
		if (is_null($editor))
		{
			$conf = self::getConfig();
			$editor = $conf->get('editor');
		}

		return Editor::getInstance($editor);
	}

	/**
	 * Return a reference to the {@link Uri} object
	 *
	 * @param   string  $uri  Uri name.
	 *
	 * @return  Uri object
	 *
	 * @see     Uri
	 * @since   1.7.0
	 * @deprecated  4.0 - Use JUri directly.
	 */
	public static function getUri($uri = 'SERVER')
	{
		Log::add(__METHOD__ . ' is deprecated. Use JUri directly.', Log::WARNING, 'deprecated');

		return Uri::getInstance($uri);
	}

	/**
	 * Return the {@link Date} object
	 *
	 * @param   mixed  $time      The initial time for the JDate object
	 * @param   mixed  $tzOffset  The timezone offset.
	 *
	 * @return  Date object
	 *
	 * @see     Date
	 * @since   1.7.0
	 */
	public static function getDate($time = 'now', $tzOffset = null)
	{
		static $classname;
		static $mainLocale;

		$language = self::getLanguage();
		$locale = $language->getTag();

		if (!isset($classname) || $locale != $mainLocale)
		{
			// Store the locale for future reference
			$mainLocale = $locale;

			if ($mainLocale !== false)
			{
				$classname = str_replace('-', '_', $mainLocale) . 'Date';

				if (!class_exists($classname))
				{
					// The class does not exist, default to Date
					$classname = 'Joomla\\CMS\\Date\\Date';
				}
			}
			else
			{
				// No tag, so default to Date
				$classname = 'Joomla\\CMS\\Date\\Date';
			}
		}

		$key = $time . '-' . ($tzOffset instanceof \DateTimeZone ? $tzOffset->getName() : (string) $tzOffset);

		if (!isset(self::$dates[$classname][$key]))
		{
			self::$dates[$classname][$key] = new $classname($time, $tzOffset);
		}

		$date = clone self::$dates[$classname][$key];

		return $date;
	}

	/**
	 * Create a configuration object
	 *
	 * @param   string  $file       The path to the configuration file.
	 * @param   string  $type       The type of the configuration file.
	 * @param   string  $namespace  The namespace of the configuration file.
	 *
	 * @return  Registry
	 *
	 * @see     Registry
	 * @since   1.7.0
	 */
	protected static function createConfig($file, $type = 'PHP', $namespace = '')
	{
		if (is_file($file))
		{
			include_once $file;
		}

		// Create the registry with a default namespace of config
		$registry = new Registry;

		// Sanitize the namespace.
		$namespace = ucfirst((string) preg_replace('/[^A-Z_]/i', '', $namespace));

		// Build the config name.
		$name = 'JConfig' . $namespace;

		// Handle the PHP configuration type.
		if ($type == 'PHP' && class_exists($name))
		{
			// Create the JConfig object
			$config = new $name;

			// Load the configuration values into the registry
			$registry->loadObject($config);
		}

		return $registry;
	}

	/**
	 * Create a session object
	 *
	 * @param   array  $options  An array containing session options
	 *
	 * @return  Session object
	 *
	 * @since   1.7.0
	 */
	protected static function createSession(array $options = array())
	{
		// Get the Joomla configuration settings
		$conf    = self::getConfig();
		$handler = $conf->get('session_handler', 'none');

		// Config time is in minutes
		$options['expire'] = ($conf->get('lifetime')) ? $conf->get('lifetime') * 60 : 900;

		// The session handler needs a JInput object, we can inject it without having a hard dependency to an application instance
		$input = self::$application ? self::getApplication()->input : new Input;

		$sessionHandler = new \JSessionHandlerJoomla($options);
		$sessionHandler->input = $input;

		$session = Session::getInstance($handler, $options, $sessionHandler);

		if ($session->getState() == 'expired')
		{
			$session->restart();
		}

		return $session;
	}

	/**
	 * Create a database object
	 *
	 * @return  \JDatabaseDriver
	 *
	 * @see     \JDatabaseDriver
	 * @since   1.7.0
	 */
	protected static function createDbo()
	{
		$conf = self::getConfig();

		$host = $conf->get('host');
		$user = $conf->get('user');
		$password = $conf->get('password');
		$database = $conf->get('db');
		$prefix = $conf->get('dbprefix');
		$driver = $conf->get('dbtype');
		$debug = $conf->get('debug');

		$options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database, 'prefix' => $prefix);

		try
		{
			$db = \JDatabaseDriver::getInstance($options);
		}
		catch (\RuntimeException $e)
		{
			if (!headers_sent())
			{
				header('HTTP/1.1 500 Internal Server Error');
			}

			jexit('Database Error: ' . $e->getMessage());
		}

		$db->setDebug($debug);

		return $db;
	}

	/**
	 * Create a mailer object
	 *
	 * @return  \JMail object
	 *
	 * @see     \JMail
	 * @since   1.7.0
	 */
	protected static function createMailer()
	{
		$conf = self::getConfig();

		$smtpauth = ($conf->get('smtpauth') == 0) ? null : 1;
		$smtpuser = $conf->get('smtpuser');
		$smtppass = $conf->get('smtppass');
		$smtphost = $conf->get('smtphost');
		$smtpsecure = $conf->get('smtpsecure');
		$smtpport = $conf->get('smtpport');
		$mailfrom = $conf->get('mailfrom');
		$fromname = $conf->get('fromname');
		$mailer = $conf->get('mailer');

		// Create a Mail object
		$mail = Mail::getInstance();

		// Clean the email address
		$mailfrom = MailHelper::cleanLine($mailfrom);

		// Set default sender without Reply-to if the mailfrom is a valid address
		if (MailHelper::isEmailAddress($mailfrom))
		{
			// Wrap in try/catch to catch phpmailerExceptions if it is throwing them
			try
			{
				// Check for a false return value if exception throwing is disabled
				if ($mail->setFrom($mailfrom, MailHelper::cleanLine($fromname), false) === false)
				{
					Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
				}
			}
			catch (\phpmailerException $e)
			{
				Log::add(__METHOD__ . '() could not set the sender data.', Log::WARNING, 'mail');
			}
		}

		// Default mailer is to use PHP's mail function
		switch ($mailer)
		{
			case 'smtp':
				$mail->useSmtp($smtpauth, $smtphost, $smtpuser, $smtppass, $smtpsecure, $smtpport);
				break;

			case 'sendmail':
				$mail->isSendmail();
				break;

			default:
				$mail->isMail();
				break;
		}

		return $mail;
	}

	/**
	 * Create a language object
	 *
	 * @return  Language object
	 *
	 * @see     Language
	 * @since   1.7.0
	 */
	protected static function createLanguage()
	{
		$conf = self::getConfig();
		$locale = $conf->get('language');
		$debug = $conf->get('debug_lang');
		$lang = Language::getInstance($locale, $debug);

		return $lang;
	}

	/**
	 * Create a document object
	 *
	 * @return  \JDocument object
	 *
	 * @see     \JDocument
	 * @since   1.7.0
	 */
	protected static function createDocument()
	{
		$lang = self::getLanguage();

		$input = self::getApplication()->input;
		$type = $input->get('format', 'html', 'cmd');

		$version = new Version;

		$attributes = array(
			'charset'      => 'utf-8',
			'lineend'      => 'unix',
			'tab'          => "\t",
			'language'     => $lang->getTag(),
			'direction'    => $lang->isRtl() ? 'rtl' : 'ltr',
			'mediaversion' => $version->getMediaVersion(),
		);

		return \JDocument::getInstance($type, $attributes);
	}

	/**
	 * Creates a new stream object with appropriate prefix
	 *
	 * @param   boolean  $use_prefix   Prefix the connections for writing
	 * @param   boolean  $use_network  Use network if available for writing; use false to disable (e.g. FTP, SCP)
	 * @param   string   $ua           UA User agent to use
	 * @param   boolean  $uamask       User agent masking (prefix Mozilla)
	 *
	 * @return  \JStream
	 *
	 * @see     \JStream
	 * @since   1.7.0
	 */
	public static function getStream($use_prefix = true, $use_network = true, $ua = null, $uamask = false)
	{
		\JLoader::import('joomla.filesystem.stream');

		// Setup the context; Joomla! UA and overwrite
		$context = array();
		$version = new Version;

		// Set the UA for HTTP and overwrite for FTP
		$context['http']['user_agent'] = $version->getUserAgent($ua, $uamask);
		$context['ftp']['overwrite'] = true;

		if ($use_prefix)
		{
			$FTPOptions = \JClientHelper::getCredentials('ftp');
			$SCPOptions = \JClientHelper::getCredentials('scp');

			if ($FTPOptions['enabled'] == 1 && $use_network)
			{
				$prefix = 'ftp://' . $FTPOptions['user'] . ':' . $FTPOptions['pass'] . '@' . $FTPOptions['host'];
				$prefix .= $FTPOptions['port'] ? ':' . $FTPOptions['port'] : '';
				$prefix .= $FTPOptions['root'];
			}
			elseif ($SCPOptions['enabled'] == 1 && $use_network)
			{
				$prefix = 'ssh2.sftp://' . $SCPOptions['user'] . ':' . $SCPOptions['pass'] . '@' . $SCPOptions['host'];
				$prefix .= $SCPOptions['port'] ? ':' . $SCPOptions['port'] : '';
				$prefix .= $SCPOptions['root'];
			}
			else
			{
				$prefix = JPATH_ROOT . '/';
			}

			$retval = new \JStream($prefix, JPATH_ROOT, $context);
		}
		else
		{
			$retval = new \JStream('', '', $context);
		}

		return $retval;
	}
}
src/Menu/MenuHelper.php000064400000022673152177723700011041 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Menu Helper utility
 *
 * @since  3.8.0
 */
class MenuHelper
{
	/**
	 * List of preset include paths
	 *
	 * @var  array
	 *
	 * @since   3.8.0
	 */
	protected static $presets = null;

	/**
	 * Private constructor
	 *
	 * @since   3.8.0
	 */
	private function __construct()
	{
	}

	/**
	 * Add a custom preset externally via plugin or any other means.
	 * WARNING: Presets with same name will replace previously added preset *except* Joomla's default preset (joomla)
	 *
	 * @param   string  $name     The unique identifier for the preset.
	 * @param   string  $title    The display label for the preset.
	 * @param   string  $path     The path to the preset file.
	 * @param   bool    $replace  Whether to replace the preset with the same name if any (except 'joomla').
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public static function addPreset($name, $title, $path, $replace = true)
	{
		if (static::$presets === null)
		{
			static::getPresets();
		}

		if ($name == 'joomla')
		{
			$replace = false;
		}

		if (($replace || !array_key_exists($name, static::$presets)) && is_file($path))
		{
			$preset = new \stdClass;

			$preset->name  = $name;
			$preset->title = $title;
			$preset->path  = $path;

			static::$presets[$name] = $preset;
		}
	}

	/**
	 * Get a list of available presets.
	 *
	 * @return  \stdClass[]
	 *
	 * @since   3.8.0
	 */
	public static function getPresets()
	{
		if (static::$presets === null)
		{
			// Important: 'null' will cause infinite recursion.
			static::$presets = array();

			static::addPreset('joomla', 'JLIB_MENUS_PRESET_JOOMLA', JPATH_ADMINISTRATOR . '/components/com_menus/presets/joomla.xml');
			static::addPreset('modern', 'JLIB_MENUS_PRESET_MODERN', JPATH_ADMINISTRATOR . '/components/com_menus/presets/modern.xml');

			// Load from template folder automatically
			$app = \JFactory::getApplication();
			$tpl = JPATH_THEMES . '/' . $app->getTemplate() . '/html/com_menus/presets';

			if (is_dir($tpl))
			{
				jimport('joomla.filesystem.folder');

				$files = \JFolder::files($tpl, '\.xml$');

				foreach ($files as $file)
				{
					$name  = substr($file, 0, -4);
					$title = str_replace('-', ' ', $name);

					static::addPreset(strtolower($name), ucwords($title), $tpl . '/' . $file);
				}
			}
		}

		return static::$presets;
	}

	/**
	 * Load the menu items from a preset file into a hierarchical list of objects
	 *
	 * @param   string  $name      The preset name
	 * @param   bool    $fallback  Fallback to default (joomla) preset if the specified one could not be loaded?
	 *
	 * @return  \stdClass[]
	 *
	 * @since   3.8.0
	 */
	public static function loadPreset($name, $fallback = true)
	{
		$items   = array();
		$presets = static::getPresets();

		if (isset($presets[$name]) && ($xml = simplexml_load_file($presets[$name]->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement)
		{
			static::loadXml($xml, $items);
		}
		elseif ($fallback && isset($presets['joomla']))
		{
			if (($xml = simplexml_load_file($presets['joomla']->path, null, LIBXML_NOCDATA)) && $xml instanceof \SimpleXMLElement)
			{
				static::loadXml($xml, $items);
			}
		}

		return $items;
	}

	/**
	 * Method to resolve the menu item alias type menu item
	 *
	 * @param   \stdClass  &$item  The alias object
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public static function resolveAlias(&$item)
	{
		$obj = $item;

		while ($obj->type == 'alias')
		{
			$params  = new Registry($obj->params);
			$aliasTo = $params->get('aliasoptions');

			$db = \JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('a.id, a.link, a.type, e.element')
				->from('#__menu a')
				->where('a.id = ' . (int) $aliasTo)
				->join('left', '#__extensions e ON e.id = a.component_id = e.id');

			try
			{
				$obj = $db->setQuery($query)->loadObject();

				if (!$obj)
				{
					$item->link = '';

					return;
				}
			}
			catch (\Exception $e)
			{
				$item->link = '';

				return;
			}
		}

		$item->id      = $obj->id;
		$item->link    = $obj->link;
		$item->type    = $obj->type;
		$item->element = $obj->element;
	}

	/**
	 * Parse the flat list of menu items and prepare the hierarchy of them using parent-child relationship.
	 *
	 * @param   \stdClass[]  $menuItems  List of menu items loaded from database
	 *
	 * @return  \stdClass[]
	 *
	 * @since   3.8.0
	 */
	public static function createLevels($menuItems)
	{
		$result    = array();
		$result[1] = array();

		foreach ($menuItems as $i => &$item)
		{
			// Resolve the alias item to get the original item
			if ($item->type == 'alias')
			{
				static::resolveAlias($item);
			}

			if ($item->link = in_array($item->type, array('separator', 'heading', 'container')) ? '#' : trim($item->link))
			{
				$item->submenu    = array();
				$item->class      = isset($item->img) ? $item->img : '';
				$item->scope      = isset($item->scope) ? $item->scope : null;
				$item->browserNav = $item->browserNav ? '_blank' : '';

				$result[$item->parent_id][$item->id] = $item;
			}
		}

		// Move each of the items under respective parent menu items.
		if (count($result[1]))
		{
			foreach ($result as $parentId => &$mItems)
			{
				foreach ($mItems as &$mItem)
				{
					if (isset($result[$mItem->id]))
					{
						$mItem->submenu = &$result[$mItem->id];
					}
				}
			}
		}

		// Return only top level items, subtree follows
		return $result[1];
	}

	/**
	 * Load a menu tree from an XML file
	 *
	 * @param   \SimpleXMLElement[]  $elements  The xml menuitem nodes
	 * @param   \stdClass[]          &$items    The menu hierarchy list to be populated
	 * @param   string[]             $replace   The substring replacements for iterator type items
	 *
	 * @return  void
	 *
	 * @since  3.8.0
	 */
	protected static function loadXml($elements, &$items, $replace = array())
	{
		foreach ($elements as $element)
		{
			if ($element->getName() != 'menuitem')
			{
				continue;
			}

			$select = (string) $element['sql_select'];
			$from   = (string) $element['sql_from'];

			/**
			 * Following is a repeatable group based on simple database query. This requires sql_* attributes (sql_select and sql_from are required)
			 * The values can be used like - "{sql:columnName}" in any attribute of repeated elements.
			 * The repeated elements are place inside this xml node but they will be populated in the same level in the rendered menu
			 */
			if ($select && $from)
			{
				$hidden = $element['hidden'] == 'true';
				$where  = (string) $element['sql_where'];
				$order  = (string) $element['sql_order'];
				$group  = (string) $element['sql_group'];
				$lJoin  = (string) $element['sql_leftjoin'];
				$iJoin  = (string) $element['sql_innerjoin'];

				$db    = \JFactory::getDbo();
				$query = $db->getQuery(true);
				$query->select($select)->from($from);

				if ($where)
				{
					$query->where($where);
				}

				if ($order)
				{
					$query->order($order);
				}

				if ($group)
				{
					$query->group($group);
				}

				if ($lJoin)
				{
					$query->leftJoin($lJoin);
				}

				if ($iJoin)
				{
					$query->innerJoin($iJoin);
				}

				$results = $db->setQuery($query)->loadObjectList();

				// Skip the entire group if no items to iterate over.
				if ($results)
				{
					// Show the repeatable group heading node only if not set as hidden.
					if (!$hidden)
					{
						$items[] = static::parseXmlNode($element, $replace);
					}

					// Iterate over the matching records, items goes in the same level (not $item->submenu) as this node.
					foreach ($results as $result)
					{
						static::loadXml($element->menuitem, $items, $result);
					}
				}
			}
			else
			{
				$item = static::parseXmlNode($element, $replace);

				// Process the child nodes
				static::loadXml($element->menuitem, $item->submenu, $replace);

				$items[] = $item;
			}
		}
	}

	/**
	 * Create a menu item node from an xml element
	 *
	 * @param   \SimpleXMLElement  $node     A menuitem element from preset xml
	 * @param   string[]           $replace  The values to substitute in the title, link and element texts
	 *
	 * @return  \stdClass
	 *
	 * @since   3.8.0
	 */
	protected static function parseXmlNode($node, $replace = array())
	{
		$item = new \stdClass;

		$item->id         = null;
		$item->type       = (string) $node['type'];
		$item->title      = (string) $node['title'];
		$item->link       = (string) $node['link'];
		$item->element    = (string) $node['element'];
		$item->class      = (string) $node['class'];
		$item->icon       = (string) $node['icon'];
		$item->browserNav = (string) $node['target'];
		$item->access     = (int) $node['access'];
		$item->params     = new Registry(trim($node->params));
		$item->scope      = (string) $node['scope'] ?: 'default';
		$item->submenu    = array();

		if ($item->type == 'separator' && trim($item->title, '- '))
		{
			$item->params->set('text_separator', 1);
		}

		// Translate attributes for iterator values
		foreach ($replace as $var => $val)
		{
			$item->title   = str_replace("{sql:$var}", $val, $item->title);
			$item->element = str_replace("{sql:$var}", $val, $item->element);
			$item->link    = str_replace("{sql:$var}", $val, $item->link);
			$item->class   = str_replace("{sql:$var}", $val, $item->class);
			$item->icon    = str_replace("{sql:$var}", $val, $item->icon);
		}

		return $item;
	}
}
src/Menu/AbstractMenu.php000064400000015532152177723700011361 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Menu class
 *
 * @since  1.5
 * @note   Will become abstract in Joomla 4
 */
class AbstractMenu
{
	/**
	 * Array to hold the menu items
	 *
	 * @var    MenuItem[]
	 * @since  1.5
	 * @deprecated  4.0  Will convert to $items
	 */
	protected $_items = array();

	/**
	 * Identifier of the default menu item
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0  Will convert to $default
	 */
	protected $_default = array();

	/**
	 * Identifier of the active menu item
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  4.0  Will convert to $active
	 */
	protected $_active = 0;

	/**
	 * Menu instances container.
	 *
	 * @var    AbstractMenu[]
	 * @since  1.7
	 */
	protected static $instances = array();

	/**
	 * User object to check access levels for
	 *
	 * @var    \JUser
	 * @since  3.5
	 */
	protected $user;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  An array of configuration options.
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
		// Load the menu items
		$this->load();

		foreach ($this->_items as $item)
		{
			if ($item->home)
			{
				$this->_default[trim($item->language)] = $item->id;
			}
		}

		$this->user = isset($options['user']) && $options['user'] instanceof \JUser ? $options['user'] : \JFactory::getUser();
	}

	/**
	 * Returns a Menu object
	 *
	 * @param   string  $client   The name of the client
	 * @param   array   $options  An associative array of options
	 *
	 * @return  AbstractMenu  A menu object.
	 *
	 * @since   1.5
	 * @throws  \Exception
	 */
	public static function getInstance($client, $options = array())
	{
		if (empty(self::$instances[$client]))
		{
			// Create a Menu object
			$classname = 'JMenu' . ucfirst($client);

			if (!class_exists($classname))
			{
				// @deprecated 4.0 Everything in this block is deprecated but the warning is only logged after the file_exists
				// Load the menu object
				$info = \JApplicationHelper::getClientInfo($client, true);

				if (is_object($info))
				{
					$path = $info->path . '/includes/menu.php';

					\JLoader::register($classname, $path);

					if (class_exists($classname))
					{
						\JLog::add('Non-autoloadable Menu subclasses are deprecated, support will be removed in 4.0.', \JLog::WARNING, 'deprecated');
					}
				}
			}

			if (!class_exists($classname))
			{
				throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_MENU_LOAD', $client), 500);
			}

			self::$instances[$client] = new $classname($options);
		}

		return self::$instances[$client];
	}

	/**
	 * Get menu item by id
	 *
	 * @param   integer  $id  The item id
	 *
	 * @return  MenuItem|null  The item object if the ID exists or null if not found
	 *
	 * @since   1.5
	 */
	public function getItem($id)
	{
		$result = null;

		if (isset($this->_items[$id]))
		{
			$result = &$this->_items[$id];
		}

		return $result;
	}

	/**
	 * Set the default item by id and language code.
	 *
	 * @param   integer  $id        The menu item id.
	 * @param   string   $language  The language code (since 1.6).
	 *
	 * @return  boolean  True if a menu item with the given ID exists
	 *
	 * @since   1.5
	 */
	public function setDefault($id, $language = '*')
	{
		if (isset($this->_items[$id]))
		{
			$this->_default[$language] = $id;

			return true;
		}

		return false;
	}

	/**
	 * Get the default item by language code.
	 *
	 * @param   string  $language  The language code, default value of * means all.
	 *
	 * @return  MenuItem|null  The item object or null when not found for given language
	 *
	 * @since   1.5
	 */
	public function getDefault($language = '*')
	{
		if (array_key_exists($language, $this->_default))
		{
			return $this->_items[$this->_default[$language]];
		}

		if (array_key_exists('*', $this->_default))
		{
			return $this->_items[$this->_default['*']];
		}

		return;
	}

	/**
	 * Set the default item by id
	 *
	 * @param   integer  $id  The item id
	 *
	 * @return  MenuItem|null  The menu item representing the given ID if present or null otherwise
	 *
	 * @since   1.5
	 */
	public function setActive($id)
	{
		if (isset($this->_items[$id]))
		{
			$this->_active = $id;

			return $this->_items[$id];
		}

		return;
	}

	/**
	 * Get menu item by id.
	 *
	 * @return  MenuItem|null  The item object if an active menu item has been set or null
	 *
	 * @since   1.5
	 */
	public function getActive()
	{
		if ($this->_active)
		{
			return $this->_items[$this->_active];
		}

		return;
	}

	/**
	 * Gets menu items by attribute
	 *
	 * @param   mixed    $attributes  The field name(s).
	 * @param   mixed    $values      The value(s) of the field. If an array, need to match field names
	 *                                each attribute may have multiple values to lookup for.
	 * @param   boolean  $firstonly   If true, only returns the first item found
	 *
	 * @return  MenuItem|MenuItem[]  An array of menu item objects or a single object if the $firstonly parameter is true
	 *
	 * @since   1.5
	 */
	public function getItems($attributes, $values, $firstonly = false)
	{
		$items = array();
		$attributes = (array) $attributes;
		$values = (array) $values;
		$count = count($attributes);

		foreach ($this->_items as $item)
		{
			if (!is_object($item))
			{
				continue;
			}

			$test = true;

			for ($i = 0; $i < $count; $i++)
			{
				if (is_array($values[$i]))
				{
					if (!in_array($item->{$attributes[$i]}, $values[$i]))
					{
						$test = false;
						break;
					}
				}
				else
				{
					if ($item->{$attributes[$i]} != $values[$i])
					{
						$test = false;
						break;
					}
				}
			}

			if ($test)
			{
				if ($firstonly)
				{
					return $item;
				}

				$items[] = $item;
			}
		}

		return $items;
	}

	/**
	 * Gets the parameter object for a certain menu item
	 *
	 * @param   integer  $id  The item id
	 *
	 * @return  Registry
	 *
	 * @since   1.5
	 */
	public function getParams($id)
	{
		if ($menu = $this->getItem($id))
		{
			return $menu->params;
		}

		return new Registry;
	}

	/**
	 * Getter for the menu array
	 *
	 * @return  MenuItem[]
	 *
	 * @since   1.5
	 */
	public function getMenu()
	{
		return $this->_items;
	}

	/**
	 * Method to check Menu object authorization against an access control object and optionally an access extension object
	 *
	 * @param   integer  $id  The menu id
	 *
	 * @return  boolean
	 *
	 * @since   1.5
	 */
	public function authorise($id)
	{
		$menu = $this->getItem($id);

		if ($menu)
		{
			return in_array((int) $menu->access, $this->user->getAuthorisedViewLevels());
		}

		return true;
	}

	/**
	 * Loads the menu items
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public function load()
	{
		return array();
	}
}
src/Menu/Tree.php000064400000007317152177723700007672 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

/**
 * Menu Tree class to represent a menu tree hierarchy
 *
 * @since  3.8.0
 */
class Tree
{
	/**
	 * The root menu node
	 *
	 * @var  Node
	 *
	 * @since   3.8.0
	 */
	protected $root = null;

	/**
	 * The current working menu node
	 *
	 * @var  Node
	 *
	 * @since   3.8.0
	 */
	protected $current = null;

	/**
	 * The CSS style array
	 *
	 * @var  string[]
	 *
	 * @since   3.8.0
	 */
	protected $css = array();

	/**
	 * Constructor
	 *
	 * @since   3.8.0
	 */
	public function __construct()
	{
		$this->root    = new Node;
		$this->current = $this->root;
	}

	/**
	 * Get the root node
	 *
	 * @return  Node
	 *
	 * @since   3.8.0
	 */
	public function getRoot()
	{
		return $this->root;
	}

	/**
	 * Get the current node
	 *
	 * @return  Node
	 *
	 * @since   3.8.0
	 */
	public function getCurrent()
	{
		return $this->current;
	}

	/**
	 * Get the current node
	 *
	 * @param   Node  $node  The node to be set as current
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function setCurrent($node)
	{
		if ($node)
		{
			$this->current = $node;
		}
	}

	/**
	 * Method to get the parent and set it as active optionally
	 *
	 * @param   bool  $setCurrent  Set that parent as the current node for further working
	 *
	 * @return  Node
	 *
	 * @since   3.8.0
	 */
	public function getParent($setCurrent = true)
	{
		$parent = $this->current->getParent();

		if ($setCurrent)
		{
			$this->setCurrent($parent);
		}

		return $parent;
	}

	/**
	 * Method to reset the working pointer to the root node and optionally clear all menu nodes
	 *
	 * @param   bool  $clear  Whether to clear the existing menu items or just reset the pointer to root element
	 *
	 * @return  Node  The root node
	 *
	 * @since   3.8.0
	 */
	public function reset($clear = false)
	{
		if ($clear)
		{
			$this->root = new Node;
			$this->css  = array();
		}

		$this->current = $this->root;

		return  $this->current;
	}

	/**
	 * Method to add a child
	 *
	 * @param   Node  $node        The node to process
	 * @param   bool  $setCurrent  Set this new child as the current node for further working
	 *
	 * @return  Node  The newly added node
	 *
	 * @since   3.8.0
	 */
	public function addChild(Node $node, $setCurrent = false)
	{
		$this->current->addChild($node);

		if ($setCurrent)
		{
			$this->setCurrent($node);
		}

		return $node;
	}

	/**
	 * Method to get the CSS class name for an icon identifier or create one if
	 * a custom image path is passed as the identifier
	 *
	 * @return  string	CSS class name
	 *
	 * @since   3.8.0
	 */
	public function getIconClass()
	{
		static $classes = array();

		$identifier = $this->current->get('class');

		// Top level is special
		if (trim($identifier) == '' || !$this->current->hasParent())
		{
			return null;
		}

		if (!isset($classes[$identifier]))
		{
			// We were passed a class name
			if (substr($identifier, 0, 6) == 'class:')
			{
				$class = substr($identifier, 6);
			}
			// We were passed background icon url. Build the CSS class for the icon
			else
			{
				$class = preg_replace('#\.[^.]*$#', '', basename($identifier));
				$class = preg_replace('#\.\.[^A-Za-z0-9\.\_\- ]#', '', $class);

				if ($class)
				{
					$this->css[] = ".menu-$class {background: url($identifier) no-repeat;}";
				}
			}

			$classes[$identifier] = "menu-$class";
		}

		return $classes[$identifier];
	}

	/**
	 * Get the CSS declarations for this tree
	 *
	 * @return  string[]
	 *
	 * @since   3.8.0
	 */
	public function getCss()
	{
		return $this->css;
	}
}
src/Menu/SiteMenu.php000064400000012627152177723700010524 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Language;
use Joomla\CMS\Language\Multilanguage;

/**
 * Menu class
 *
 * @since  1.5
 */
class SiteMenu extends AbstractMenu
{
	/**
	 * Application object
	 *
	 * @var    CMSApplication
	 * @since  3.5
	 */
	protected $app;

	/**
	 * Database driver
	 *
	 * @var    \JDatabaseDriver
	 * @since  3.5
	 */
	protected $db;

	/**
	 * Language object
	 *
	 * @var    Language
	 * @since  3.5
	 */
	protected $language;

	/**
	 * Class constructor
	 *
	 * @param   array  $options  An array of configuration options.
	 *
	 * @since   1.5
	 */
	public function __construct($options = array())
	{
		// Extract the internal dependencies before calling the parent constructor since it calls $this->load()
		$this->app      = isset($options['app']) && $options['app'] instanceof CMSApplication ? $options['app'] : \JFactory::getApplication();
		$this->db       = isset($options['db']) && $options['db'] instanceof \JDatabaseDriver ? $options['db'] : \JFactory::getDbo();
		$this->language = isset($options['language']) && $options['language'] instanceof Language ? $options['language'] : \JFactory::getLanguage();

		parent::__construct($options);
	}

	/**
	 * Loads the entire menu table into memory.
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @since   1.5
	 */
	public function load()
	{
		// For PHP 5.3 compat we can't use $this in the lambda function below
		$db = $this->db;

		$loader = function () use ($db)
		{
			$query = $db->getQuery(true)
				->select('m.id, m.menutype, m.title, m.alias, m.note, m.path AS route, m.link, m.type, m.level, m.language')
				->select($db->quoteName('m.browserNav') . ', m.access, m.params, m.home, m.img, m.template_style_id, m.component_id, m.parent_id')
				->select('e.element as component')
				->from('#__menu AS m')
				->join('LEFT', '#__extensions AS e ON m.component_id = e.extension_id')
				->where('m.published = 1')
				->where('m.parent_id > 0')
				->where('m.client_id = 0')
				->order('m.lft');

			// Set the query
			$db->setQuery($query);

			return $db->loadObjectList('id', 'Joomla\\CMS\\Menu\\MenuItem');
		};

		try
		{
			/** @var \JCacheControllerCallback $cache */
			$cache = \JFactory::getCache('com_menus', 'callback');

			$this->_items = $cache->get($loader, array(), md5(get_class($this)), false);
		}
		catch (\JCacheException $e)
		{
			try
			{
				$this->_items = $loader();
			}
			catch (\JDatabaseExceptionExecuting $databaseException)
			{
				\JError::raiseWarning(500, \JText::sprintf('JERROR_LOADING_MENUS', $databaseException->getMessage()));

				return false;
			}
		}
		catch (\JDatabaseExceptionExecuting $e)
		{
			\JError::raiseWarning(500, \JText::sprintf('JERROR_LOADING_MENUS', $e->getMessage()));

			return false;
		}

		foreach ($this->_items as &$item)
		{
			// Get parent information.
			$parent_tree = array();

			if (isset($this->_items[$item->parent_id]))
			{
				$parent_tree  = $this->_items[$item->parent_id]->tree;
			}

			// Create tree.
			$parent_tree[] = $item->id;
			$item->tree = $parent_tree;

			// Create the query array.
			$url = str_replace('index.php?', '', $item->link);
			$url = str_replace('&amp;', '&', $url);

			parse_str($url, $item->query);
		}

		return true;
	}

	/**
	 * Gets menu items by attribute
	 *
	 * @param   string   $attributes  The field name
	 * @param   string   $values      The value of the field
	 * @param   boolean  $firstonly   If true, only returns the first item found
	 *
	 * @return  MenuItem|MenuItem[]  An array of menu item objects or a single object if the $firstonly parameter is true
	 *
	 * @since   1.6
	 */
	public function getItems($attributes, $values, $firstonly = false)
	{
		$attributes = (array) $attributes;
		$values     = (array) $values;

		if ($this->app->isClient('site'))
		{
			// Filter by language if not set
			if (($key = array_search('language', $attributes)) === false)
			{
				if (Multilanguage::isEnabled())
				{
					$attributes[] = 'language';
					$values[]     = array(\JFactory::getLanguage()->getTag(), '*');
				}
			}
			elseif ($values[$key] === null)
			{
				unset($attributes[$key], $values[$key]);
			}

			// Filter by access level if not set
			if (($key = array_search('access', $attributes)) === false)
			{
				$attributes[] = 'access';
				$values[] = $this->user->getAuthorisedViewLevels();
			}
			elseif ($values[$key] === null)
			{
				unset($attributes[$key], $values[$key]);
			}
		}

		// Reset arrays or we get a notice if some values were unset
		$attributes = array_values($attributes);
		$values = array_values($values);

		return parent::getItems($attributes, $values, $firstonly);
	}

	/**
	 * Get menu item by id
	 *
	 * @param   string  $language  The language code.
	 *
	 * @return  MenuItem|null  The item object or null when not found for given language
	 *
	 * @since   1.6
	 */
	public function getDefault($language = '*')
	{
		if (array_key_exists($language, $this->_default) && $this->app->isClient('site') && $this->app->getLanguageFilter())
		{
			return $this->_items[$this->_default[$language]];
		}

		if (array_key_exists('*', $this->_default))
		{
			return $this->_items[$this->_default['*']];
		}

		return;
	}
}
src/Menu/Node.php000064400000010143152177723700007647 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * A Node for MenuTree
 *
 * @see    Tree
 *
 * @since  3.8.0
 */
class Node
{
	/**
	 * Node Id
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $id = null;

	/**
	 * CSS Class for node
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $class = null;

	/**
	 * Whether this node is active
	 *
	 * @var  bool
	 *
	 * @since   3.8.0
	 */
	protected $active = false;

	/**
	 * Additional custom node params
	 *
	 * @var  Registry
	 *
	 * @since   3.8.0
	 */
	protected $params;

	/**
	 * Parent node object
	 *
	 * @var  Node
	 *
	 * @since   3.8.0
	 */
	protected $parent = null;

	/**
	 * Array of Children node objects
	 *
	 * @var  Node[]
	 *
	 * @since   3.8.0
	 */
	protected $children = array();

	/**
	 * Constructor
	 *
	 * @since   3.8.0
	 */
	public function __construct()
	{
		$this->params = new Registry;
	}

	/**
	 * Add child to this node
	 *
	 * If the child already has a parent, the link is unset
	 *
	 * @param   Node  $child  The child to be added
	 *
	 * @return  Node  The new added child
	 *
	 * @since   3.8.0
	 */
	public function addChild(Node $child)
	{
		$hash = spl_object_hash($child);

		if (isset($child->parent))
		{
			$child->parent->removeChild($child);
		}

		$child->parent         = $this;
		$this->children[$hash] = $child;

		return $child;
	}

	/**
	 * Remove a child from this node
	 *
	 * If the child exists it is unset
	 *
	 * @param   Node  $child  The child to be added
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function removeChild(Node $child)
	{
		$hash = spl_object_hash($child);

		if (isset($this->children[$hash]))
		{
			$child->parent = null;

			unset($this->children[$hash]);
		}
	}

	/**
	 * Test if this node has a parent
	 *
	 * @return  boolean  True if there is a parent
	 *
	 * @since   3.8.0
	 */
	public function hasParent()
	{
		return isset($this->parent);
	}

	/**
	 * Get the parent of this node
	 *
	 * @return  Node  The Node object's parent or null for no parent
	 *
	 * @since   3.8.0
	 */
	public function getParent()
	{
		return $this->parent;
	}

	/**
	 * Test if this node has children
	 *
	 * @return  boolean
	 *
	 * @since   3.8.0
	 */
	public function hasChildren()
	{
		return count($this->children) > 0;
	}

	/**
	 * Get the children of this node
	 *
	 * @return  Node[]  The children
	 *
	 * @since   3.8.0
	 */
	public function getChildren()
	{
		return $this->children;
	}

	/**
	 * Find the current node depth in the tree hierarchy
	 *
	 * @return  integer  The node level in the hierarchy, where ROOT == 0, First level menu item == 1, and so on.
	 *
	 * @since   3.8.0
	 */
	public function getLevel()
	{
		return $this->hasParent() ? $this->getParent()->getLevel() + 1 : 0;
	}

	/**
	 * Check whether the object instance node is the root node
	 *
	 * @return  boolean
	 *
	 * @since   3.8.0
	 */
	public function isRoot()
	{
		return !$this->hasParent();
	}

	/**
	 * Set the active state on or off
	 *
	 * @param   bool  $active  The new active state
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function setActive($active)
	{
		$this->active = (bool) $active;
	}

	/**
	 * set the params array
	 *
	 * @param   Registry  $params  The params attributes
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function setParams(Registry $params)
	{
		$this->params = $params;
	}

	/**
	 * Get the param value from the node params
	 *
	 * @param   string  $key  The param name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function getParam($key)
	{
		return isset($this->params[$key]) ? $this->params[$key] : null;
	}

	/**
	 * Get an attribute value
	 *
	 * @param   string  $name  The attribute name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'id':
			case 'class':
			case 'active':
			case 'params':
				return $this->$name;
		}

		return null;
	}
}
src/Menu/Node/Separator.php000064400000002026152177723700011610 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Menu\Node;

/**
 * A Separator type of node for MenuTree
 *
 * @see    Node
 *
 * @since  3.8.0
 */
class Separator extends Node
{
	/**
	 * Node Title
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $title = null;

	/**
	 * Constructor for the class.
	 *
	 * @param   string  $title  The title of the node
	 *
	 * @since   3.8.0
	 */
	public function __construct($title = null)
	{
		$this->title = trim($title, '- ') ? $title : null;

		parent::__construct();
	}

	/**
	 * Get an attribute value
	 *
	 * @param   string  $name  The attribute name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
				return $this->$name;
		}

		return parent::get($name);
	}
}
src/Menu/Node/Component.php000064400000004072152177723700011615 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Menu\Node;

/**
 * A Component type of node for MenuTree
 *
 * @see    Node
 *
 * @since  3.8.0
 */
class Component extends Node
{
	/**
	 * Node Title
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $title = null;

	/**
	 * The component name for this node link
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $element = null;

	/**
	 * Node Link
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $link = null;

	/**
	 * Link Target
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $target = null;

	/**
	 * Link title icon
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $icon = null;

	/**
	 * Constructor for the class.
	 *
	 * @param   string  $title    The title of the node
	 * @param   string  $element  The component name
	 * @param   string  $link     The node link
	 * @param   string  $target   The link target
	 * @param   string  $class    The CSS class for the node
	 * @param   string  $id       The node id
	 * @param   string  $icon     The title icon for the node
	 *
	 * @since   3.8.0
	 */
	public function __construct($title, $element, $link, $target = null, $class = null, $id = null, $icon = null)
	{
		$this->title   = $title;
		$this->element = $element;
		$this->link    = $link ? \JFilterOutput::ampReplace($link) : 'index.php?option=' . $element;
		$this->target  = $target;
		$this->class   = $class;
		$this->id      = $id;
		$this->icon    = $icon;

		parent::__construct();
	}

	/**
	 * Get an attribute value
	 *
	 * @param   string  $name  The attribute name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'element':
			case 'link':
			case 'target':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
src/Menu/Node/Url.php000064400000003432152177723700010414 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Menu\Node;

/**
 * An external Url type of node for MenuTree
 *
 * @see    Node
 *
 * @since  3.8.0
 */
class Url extends Node
{
	/**
	 * Node Title
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $title = null;

	/**
	 * Node Link
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $link = null;

	/**
	 * Link Target
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $target = null;

	/**
	 * Link title icon
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $icon = null;

	/**
	 * Constructor for the class.
	 *
	 * @param   string  $title   The title of the node
	 * @param   string  $link    The node link
	 * @param   string  $target  The link target
	 * @param   string  $class   The CSS class for the node
	 * @param   string  $id      The node id
	 * @param   string  $icon    The title icon for the node
	 *
	 * @since   3.8.0
	 */
	public function __construct($title, $link, $target = null, $class = null, $id = null, $icon = null)
	{
		$this->title  = $title;
		$this->link   =	\JFilterOutput::ampReplace($link);
		$this->target = $target;
		$this->class  = $class;
		$this->id     = $id;
		$this->icon   = $icon;

		parent::__construct();
	}

	/**
	 * Get an attribute value
	 *
	 * @param   string  $name  The attribute name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'link':
			case 'target':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
src/Menu/Node/Container.php000064400000000634152177723700011575 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

/**
 * A Container type of node for MenuTree
 *
 * @see    Node
 *
 * @since  3.8.0
 */
class Container extends Heading
{
}
src/Menu/Node/Heading.php000064400000002732152177723700011213 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */
namespace Joomla\CMS\Menu\Node;

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Menu\Node;

/**
 * A Heading type of node for MenuTree
 *
 * @see    Node
 *
 * @since  3.8.0
 */
class Heading extends Node
{
	/**
	 * Node Title
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $title = null;

	/**
	 * Node Link
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $link = '#';

	/**
	 * Link title icon
	 *
	 * @var  string
	 *
	 * @since   3.8.0
	 */
	protected $icon = null;

	/**
	 * Constructor for the class.
	 *
	 * @param   string  $title  The title of the node
	 * @param   string  $class  The CSS class for the node
	 * @param   string  $id     The node id
	 * @param   string  $icon   The title icon for the node
	 *
	 * @since   3.8.0
	 */
	public function __construct($title, $class = null, $id = null, $icon = null)
	{
		$this->title = $title;
		$this->class = $class;
		$this->id    = $id;
		$this->icon  = $icon;

		parent::__construct();
	}

	/**
	 * Get an attribute value
	 *
	 * @param   string  $name  The attribute name
	 *
	 * @return  mixed
	 *
	 * @since   3.8.0
	 */
	public function get($name)
	{
		switch ($name)
		{
			case 'title':
			case 'link':
			case 'icon':
				return $this->$name;
		}

		return parent::get($name);
	}
}
src/Menu/MenuItem.php000064400000014502152177723700010510 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Object representing a menu item
 *
 * @since  3.7.0
 * @note   This class will no longer extend stdClass in Joomla 4
 */
class MenuItem extends \stdClass
{
	/**
	 * Primary key
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $id;

	/**
	 * The type of menu this item belongs to
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $menutype;

	/**
	 * The display title of the menu item
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $title;

	/**
	 * The SEF alias of the menu item
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $alias;

	/**
	 * A note associated with the menu item
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $note;

	/**
	 * The computed path of the menu item based on the alias field, this is populated from the `path` field in the `#__menu` table
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $route;

	/**
	 * The actual link the menu item refers to
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $link;

	/**
	 * The type of link
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $type;

	/**
	 * The relative level in the tree
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $level;

	/**
	 * The assigned language for this item
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $language;

	/**
	 * The click behaviour of the link
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $browserNav;

	/**
	 * The access level required to view the menu item
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $access;

	/**
	 * The menu item parameters
	 *
	 * @var    string|Registry
	 * @since  3.7.0
	 * @note   This field is protected to require reading this field to proxy through the getter to convert the params to a Registry instance
	 */
	protected $params;

	/**
	 * Indicates if this menu item is the home or default page
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $home;

	/**
	 * The image of the menu item
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $img;

	/**
	 * The optional template style applied to this menu item
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $template_style_id;

	/**
	 * The extension ID of the component this menu item is for
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $component_id;

	/**
	 * The parent menu item in the menu tree
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	public $parent_id;

	/**
	 * The name of the component this menu item is for
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	public $component;

	/**
	 * The tree of parent menu items
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	public $tree = array();

	/**
	 * An array of the query string values for this item
	 *
	 * @var    array
	 * @since  3.7.0
	 */
	public $query = array();

	/**
	 * Class constructor
	 *
	 * @param   array  $data  The menu item data to load
	 *
	 * @since   3.7.0
	 */
	public function __construct($data = array())
	{
		foreach ((array) $data as $key => $value)
		{
			$this->$key = $value;
		}
	}

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.7.0
	 * @deprecated  4.0  Access the item parameters through the `getParams()` method
	 */
	public function __get($name)
	{
		if ($name === 'params')
		{
			return $this->getParams();
		}

		return $this->get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 * @deprecated  4.0  Set the item parameters through the `setParams()` method
	 */
	public function __set($name, $value)
	{
		if ($name === 'params')
		{
			$this->setParams($value);

			return;
		}

		$this->set($name, $value);
	}

	/**
	 * Method check if a certain otherwise inaccessible properties of the form field object is set.
	 *
	 * @param   string  $name  The property name to check.
	 *
	 * @return  boolean
	 *
	 * @since   3.7.1
	 * @deprecated  4.0 Deprecated without replacement
	 */
	public function __isset($name)
	{
		if ($name === 'params')
		{
			return !($this->params instanceof Registry);
		}

		return $this->get($name) !== null;
	}

	/**
	 * Returns the menu item parameters
	 *
	 * @return  Registry
	 *
	 * @since   3.7.0
	 */
	public function getParams()
	{
		if (!($this->params instanceof Registry))
		{
			try
			{
				$this->params = new Registry($this->params);
			}
			catch (\RuntimeException $e)
			{
				/*
				 * Joomla shipped with a broken sample json string for 4 years which caused fatals with new
				 * error checks. So for now we catch the exception here - but one day we should remove it and require
				 * valid JSON.
				 */
				$this->params = new Registry;
			}
		}

		return $this->params;
	}

	/**
	 * Sets the menu item parameters
	 *
	 * @param   Registry|string  $params  The data to be stored as the parameters
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public function setParams($params)
	{
		$this->params = $params;
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed    The value of the property.
	 *
	 * @since   3.7.0
	 * @deprecated  4.0
	 */
	public function get($property, $default = null)
	{
		if (isset($this->$property))
		{
			return $this->$property;
		}

		return $default;
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set.
	 *
	 * @return  mixed  Previous value of the property.
	 *
	 * @since   3.7.0
	 * @deprecated  4.0
	 */
	public function set($property, $value = null)
	{
		$previous = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;

		return $previous;
	}
}
src/Menu/AdministratorMenu.php000064400000000566152177723700012437 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Menu;

defined('JPATH_PLATFORM') or die;

/**
 * Menu class.
 *
 * @since  1.5
 */
class AdministratorMenu extends AbstractMenu
{
}
src/Date/Date.php000064400000032107152177723700007614 0ustar00<?php
/**
 * Joomla! Content Management System
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

namespace Joomla\CMS\Date;

defined('JPATH_PLATFORM') or die;

/**
 * JDate is a class that stores a date and provides logic to manipulate
 * and render that date in a variety of formats.
 *
 * @method  Date|bool  add(\DateInterval $interval)  Adds an amount of days, months, years, hours, minutes and seconds to a JDate object.
 * @method  Date|bool  sub(\DateInterval $interval)  Subtracts an amount of days, months, years, hours, minutes and seconds from a JDate object.
 * @method  Date|bool  modify(string $modify)       Alter the timestamp of this object by incre/decre-menting in a format accepted by strtotime().
 *
 * @property-read  string   $daysinmonth   t - Number of days in the given month.
 * @property-read  string   $dayofweek     N - ISO-8601 numeric representation of the day of the week.
 * @property-read  string   $dayofyear     z - The day of the year (starting from 0).
 * @property-read  boolean  $isleapyear    L - Whether it's a leap year.
 * @property-read  string   $day           d - Day of the month, 2 digits with leading zeros.
 * @property-read  string   $hour          H - 24-hour format of an hour with leading zeros.
 * @property-read  string   $minute        i - Minutes with leading zeros.
 * @property-read  string   $second        s - Seconds with leading zeros.
 * @property-read  string   $microsecond   u - Microseconds with leading zeros.
 * @property-read  string   $month         m - Numeric representation of a month, with leading zeros.
 * @property-read  string   $ordinal       S - English ordinal suffix for the day of the month, 2 characters.
 * @property-read  string   $week          W - ISO-8601 week number of year, weeks starting on Monday.
 * @property-read  string   $year          Y - A full numeric representation of a year, 4 digits.
 *
 * @since  1.7.0
 */
class Date extends \DateTime
{
	const DAY_ABBR = "\x021\x03";
	const DAY_NAME = "\x022\x03";
	const MONTH_ABBR = "\x023\x03";
	const MONTH_NAME = "\x024\x03";

	/**
	 * The format string to be applied when using the __toString() magic method.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public static $format = 'Y-m-d H:i:s';

	/**
	 * Placeholder for a \DateTimeZone object with GMT as the time zone.
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	protected static $gmt;

	/**
	 * Placeholder for a \DateTimeZone object with the default server
	 * time zone as the time zone.
	 *
	 * @var    object
	 * @since  1.7.0
	 */
	protected static $stz;

	/**
	 * The \DateTimeZone object for usage in rending dates as strings.
	 *
	 * @var    \DateTimeZone
	 * @since  3.0.0
	 */
	protected $tz;

	/**
	 * Constructor.
	 *
	 * @param   string  $date  String in a format accepted by strtotime(), defaults to "now".
	 * @param   mixed   $tz    Time zone to be used for the date. Might be a string or a DateTimeZone object.
	 *
	 * @since   1.7.0
	 */
	public function __construct($date = 'now', $tz = null)
	{
		// Create the base GMT and server time zone objects.
		if (empty(self::$gmt) || empty(self::$stz))
		{
			self::$gmt = new \DateTimeZone('GMT');
			self::$stz = new \DateTimeZone(@date_default_timezone_get());
		}

		// If the time zone object is not set, attempt to build it.
		if (!($tz instanceof \DateTimeZone))
		{
			if ($tz === null)
			{
				$tz = self::$gmt;
			}
			elseif (is_string($tz))
			{
				$tz = new \DateTimeZone($tz);
			}
		}

		// If the date is numeric assume a unix timestamp and convert it.
		date_default_timezone_set('UTC');
		$date = is_numeric($date) ? date('c', $date) : $date;

		// Call the DateTime constructor.
		parent::__construct($date, $tz);

		// Reset the timezone for 3rd party libraries/extension that does not use JDate
		date_default_timezone_set(self::$stz->getName());

		// Set the timezone object for access later.
		$this->tz = $tz;
	}

	/**
	 * Magic method to access properties of the date given by class to the format method.
	 *
	 * @param   string  $name  The name of the property.
	 *
	 * @return  mixed   A value if the property name is valid, null otherwise.
	 *
	 * @since   1.7.0
	 */
	public function __get($name)
	{
		$value = null;

		switch ($name)
		{
			case 'daysinmonth':
				$value = $this->format('t', true);
				break;

			case 'dayofweek':
				$value = $this->format('N', true);
				break;

			case 'dayofyear':
				$value = $this->format('z', true);
				break;

			case 'isleapyear':
				$value = (boolean) $this->format('L', true);
				break;

			case 'day':
				$value = $this->format('d', true);
				break;

			case 'hour':
				$value = $this->format('H', true);
				break;

			case 'minute':
				$value = $this->format('i', true);
				break;

			case 'second':
				$value = $this->format('s', true);
				break;

			case 'month':
				$value = $this->format('m', true);
				break;

			case 'ordinal':
				$value = $this->format('S', true);
				break;

			case 'week':
				$value = $this->format('W', true);
				break;

			case 'year':
				$value = $this->format('Y', true);
				break;

			default:
				$trace = debug_backtrace();
				trigger_error(
					'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'],
					E_USER_NOTICE
				);
		}

		return $value;
	}

	/**
	 * Magic method to render the date object in the format specified in the public
	 * static member Date::$format.
	 *
	 * @return  string  The date as a formatted string.
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		return (string) parent::format(self::$format);
	}

	/**
	 * Proxy for new JDate().
	 *
	 * @param   string  $date  String in a format accepted by strtotime(), defaults to "now".
	 * @param   mixed   $tz    Time zone to be used for the date.
	 *
	 * @return  Date
	 *
	 * @since   1.7.3
	 */
	public static function getInstance($date = 'now', $tz = null)
	{
		return new Date($date, $tz);
	}

	/**
	 * Translates day of week number to a string.
	 *
	 * @param   integer  $day   The numeric day of the week.
	 * @param   boolean  $abbr  Return the abbreviated day string?
	 *
	 * @return  string  The day of the week.
	 *
	 * @since   1.7.0
	 */
	public function dayToString($day, $abbr = false)
	{
		switch ($day)
		{
			case 0:
				return $abbr ? \JText::_('SUN') : \JText::_('SUNDAY');
			case 1:
				return $abbr ? \JText::_('MON') : \JText::_('MONDAY');
			case 2:
				return $abbr ? \JText::_('TUE') : \JText::_('TUESDAY');
			case 3:
				return $abbr ? \JText::_('WED') : \JText::_('WEDNESDAY');
			case 4:
				return $abbr ? \JText::_('THU') : \JText::_('THURSDAY');
			case 5:
				return $abbr ? \JText::_('FRI') : \JText::_('FRIDAY');
			case 6:
				return $abbr ? \JText::_('SAT') : \JText::_('SATURDAY');
		}
	}

	/**
	 * Gets the date as a formatted string in a local calendar.
	 *
	 * @param   string   $format     The date format specification string (see {@link PHP_MANUAL#date})
	 * @param   boolean  $local      True to return the date string in the local time zone, false to return it in GMT.
	 * @param   boolean  $translate  True to translate localised strings
	 *
	 * @return  string   The date string in the specified format format.
	 *
	 * @since   1.7.0
	 */
	public function calendar($format, $local = false, $translate = true)
	{
		return $this->format($format, $local, $translate);
	}

	/**
	 * Gets the date as a formatted string.
	 *
	 * @param   string   $format     The date format specification string (see {@link PHP_MANUAL#date})
	 * @param   boolean  $local      True to return the date string in the local time zone, false to return it in GMT.
	 * @param   boolean  $translate  True to translate localised strings
	 *
	 * @return  string   The date string in the specified format format.
	 *
	 * @since   1.7.0
	 */
	public function format($format, $local = false, $translate = true)
	{
		if ($translate)
		{
			// Do string replacements for date format options that can be translated.
			$format = preg_replace('/(^|[^\\\])D/', "\\1" . self::DAY_ABBR, $format);
			$format = preg_replace('/(^|[^\\\])l/', "\\1" . self::DAY_NAME, $format);
			$format = preg_replace('/(^|[^\\\])M/', "\\1" . self::MONTH_ABBR, $format);
			$format = preg_replace('/(^|[^\\\])F/', "\\1" . self::MONTH_NAME, $format);
		}

		// If the returned time should not be local use GMT.
		if ($local == false && !empty(self::$gmt))
		{
			parent::setTimezone(self::$gmt);
		}

		// Format the date.
		$return = parent::format($format);

		if ($translate)
		{
			// Manually modify the month and day strings in the formatted time.
			if (strpos($return, self::DAY_ABBR) !== false)
			{
				$return = str_replace(self::DAY_ABBR, $this->dayToString(parent::format('w'), true), $return);
			}

			if (strpos($return, self::DAY_NAME) !== false)
			{
				$return = str_replace(self::DAY_NAME, $this->dayToString(parent::format('w')), $return);
			}

			if (strpos($return, self::MONTH_ABBR) !== false)
			{
				$return = str_replace(self::MONTH_ABBR, $this->monthToString(parent::format('n'), true), $return);
			}

			if (strpos($return, self::MONTH_NAME) !== false)
			{
				$return = str_replace(self::MONTH_NAME, $this->monthToString(parent::format('n')), $return);
			}
		}

		if ($local == false && !empty($this->tz))
		{
			parent::setTimezone($this->tz);
		}

		return $return;
	}

	/**
	 * Get the time offset from GMT in hours or seconds.
	 *
	 * @param   boolean  $hours  True to return the value in hours.
	 *
	 * @return  float  The time offset from GMT either in hours or in seconds.
	 *
	 * @since   1.7.0
	 */
	public function getOffsetFromGmt($hours = false)
	{
		return (float) $hours ? ($this->tz->getOffset($this) / 3600) : $this->tz->getOffset($this);
	}

	/**
	 * Translates month number to a string.
	 *
	 * @param   integer  $month  The numeric month of the year.
	 * @param   boolean  $abbr   If true, return the abbreviated month string
	 *
	 * @return  string  The month of the year.
	 *
	 * @since   1.7.0
	 */
	public function monthToString($month, $abbr = false)
	{
		switch ($month)
		{
			case 1:
				return $abbr ? \JText::_('JANUARY_SHORT') : \JText::_('JANUARY');
			case 2:
				return $abbr ? \JText::_('FEBRUARY_SHORT') : \JText::_('FEBRUARY');
			case 3:
				return $abbr ? \JText::_('MARCH_SHORT') : \JText::_('MARCH');
			case 4:
				return $abbr ? \JText::_('APRIL_SHORT') : \JText::_('APRIL');
			case 5:
				return $abbr ? \JText::_('MAY_SHORT') : \JText::_('MAY');
			case 6:
				return $abbr ? \JText::_('JUNE_SHORT') : \JText::_('JUNE');
			case 7:
				return $abbr ? \JText::_('JULY_SHORT') : \JText::_('JULY');
			case 8:
				return $abbr ? \JText::_('AUGUST_SHORT') : \JText::_('AUGUST');
			case 9:
				return $abbr ? \JText::_('SEPTEMBER_SHORT') : \JText::_('SEPTEMBER');
			case 10:
				return $abbr ? \JText::_('OCTOBER_SHORT') : \JText::_('OCTOBER');
			case 11:
				return $abbr ? \JText::_('NOVEMBER_SHORT') : \JText::_('NOVEMBER');
			case 12:
				return $abbr ? \JText::_('DECEMBER_SHORT') : \JText::_('DECEMBER');
		}
	}

	/**
	 * Method to wrap the setTimezone() function and set the internal time zone object.
	 *
	 * @param   \DateTimeZone  $tz  The new \DateTimeZone object.
	 *
	 * @return  Date
	 *
	 * @since   1.7.0
	 * @note    This method can't be type hinted due to a PHP bug: https://bugs.php.net/bug.php?id=61483
	 */
	public function setTimezone($tz)
	{
		$this->tz = $tz;

		return parent::setTimezone($tz);
	}

	/**
	 * Gets the date as an ISO 8601 string.  IETF RFC 3339 defines the ISO 8601 format
	 * and it can be found at the IETF Web site.
	 *
	 * @param   boolean  $local  True to return the date string in the local time zone, false to return it in GMT.
	 *
	 * @return  string  The date string in ISO 8601 format.
	 *
	 * @link    http://www.ietf.org/rfc/rfc3339.txt
	 * @since   1.7.0
	 */
	public function toISO8601($local = false)
	{
		return $this->format(\DateTime::RFC3339, $local, false);
	}

	/**
	 * Gets the date as an SQL datetime string.
	 *
	 * @param   boolean           $local  True to return the date string in the local time zone, false to return it in GMT.
	 * @param   \JDatabaseDriver  $db     The database driver or null to use \JFactory::getDbo()
	 *
	 * @return  string     The date string in SQL datetime format.
	 *
	 * @link    http://dev.mysql.com/doc/refman/5.0/en/datetime.html
	 * @since   2.5.0
	 */
	public function toSql($local = false, \JDatabaseDriver $db = null)
	{
		if ($db === null)
		{
			$db = \JFactory::getDbo();
		}

		return $this->format($db->getDateFormat(), $local, false);
	}

	/**
	 * Gets the date as an RFC 822 string.  IETF RFC 2822 supercedes RFC 822 and its definition
	 * can be found at the IETF Web site.
	 *
	 * @param   boolean  $local  True to return the date string in the local time zone, false to return it in GMT.
	 *
	 * @return  string   The date string in RFC 822 format.
	 *
	 * @link    http://www.ietf.org/rfc/rfc2822.txt
	 * @since   1.7.0
	 */
	public function toRFC822($local = false)
	{
		return $this->format(\DateTime::RFC2822, $local, false);
	}

	/**
	 * Gets the date as UNIX time stamp.
	 *
	 * @return  integer  The date as a UNIX timestamp.
	 *
	 * @since   1.7.0
	 */
	public function toUnix()
	{
		return (int) parent::format('U');
	}
}
legacy/log/logexception.php000064400000001056152177723700012017 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Log
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('LogException is deprecated, use SPL Exceptions instead.', JLog::WARNING, 'deprecated');

/**
 * Exception class definition for the Log subpackage.
 *
 * @since       1.7
 * @deprecated  2.5.5 Use semantic exceptions instead
 */
class LogException extends RuntimeException
{
}
legacy/response/response.php000064400000014470152177723700012236 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Response
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JResponse is deprecated.', JLog::WARNING, 'deprecated');

/**
 * JResponse Class.
 *
 * This class serves to provide the Joomla Platform with a common interface to access
 * response variables.  This includes header and body.
 *
 * @since       1.7.0
 * @deprecated  1.5  Use JApplicationWeb instead
 */
class JResponse
{
	/**
	 * Response body
	 *
	 * @var    array
	 * @since  1.6
	 * @deprecated  3.2
	 */
	protected static $body = array();

	/**
	 * Flag if the response is cachable
	 *
	 * @var    boolean
	 * @since  1.6
	 * @deprecated  3.2
	 */
	protected static $cachable = false;

	/**
	 * Response headers
	 *
	 * @var    array
	 * @since  1.6
	 * @deprecated  3.2
	 */
	protected static $headers = array();

	/**
	 * Set/get cachable state for the response.
	 *
	 * If $allow is set, sets the cachable state of the response.  Always returns current state.
	 *
	 * @param   boolean  $allow  True to allow browser caching.
	 *
	 * @return  boolean  True if browser caching should be allowed
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::allowCache() instead
	 */
	public static function allowCache($allow = null)
	{
		return JFactory::getApplication()->allowCache($allow);
	}

	/**
	 * Set a header.
	 *
	 * If $replace is true, replaces any headers already defined with that $name.
	 *
	 * @param   string   $name     The name of the header to set.
	 * @param   string   $value    The value of the header to set.
	 * @param   boolean  $replace  True to replace any existing headers by name.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::setHeader() instead
	 */
	public static function setHeader($name, $value, $replace = false)
	{
		JFactory::getApplication()->setHeader($name, $value, $replace);
	}

	/**
	 * Return array of headers.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::getHeaders() instead
	 */
	public static function getHeaders()
	{
		return JFactory::getApplication()->getHeaders();
	}

	/**
	 * Clear headers.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::clearHeaders() instead
	 */
	public static function clearHeaders()
	{
		JFactory::getApplication()->clearHeaders();
	}

	/**
	 * Send all headers.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::sendHeaders() instead
	 */
	public static function sendHeaders()
	{
		JFactory::getApplication()->sendHeaders();
	}

	/**
	 * Set body content.
	 *
	 * If body content already defined, this will replace it.
	 *
	 * @param   string  $content  The content to set to the response body.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::setBody() instead
	 */
	public static function setBody($content)
	{
		JFactory::getApplication()->setBody($content);
	}

	/**
	 * Prepend content to the body content
	 *
	 * @param   string  $content  The content to prepend to the response body.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::prependBody() instead
	 */
	public static function prependBody($content)
	{
		JFactory::getApplication()->prependBody($content);
	}

	/**
	 * Append content to the body content
	 *
	 * @param   string  $content  The content to append to the response body.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::appendBody() instead
	 */
	public static function appendBody($content)
	{
		JFactory::getApplication()->appendBody($content);
	}

	/**
	 * Return the body content
	 *
	 * @param   boolean  $toArray  Whether or not to return the body content as an array of strings or as a single string; defaults to false.
	 *
	 * @return  string  array
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationWeb::getBody() instead
	 */
	public static function getBody($toArray = false)
	{
		return JFactory::getApplication()->getBody($toArray);
	}

	/**
	 * Sends all headers prior to returning the string
	 *
	 * @param   boolean  $compress  If true, compress the data
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationCms::toString() instead
	 */
	public static function toString($compress = false)
	{
		return JFactory::getApplication()->toString($compress);
	}

	/**
	 * Compress the data
	 *
	 * Checks the accept encoding of the browser and compresses the data before
	 * sending it to the client.
	 *
	 * @param   string  $data  Content to compress for output.
	 *
	 * @return  string  compressed data
	 *
	 * @note    Replaces _compress method from 1.5
	 * @since   1.7
	 * @deprecated  3.2  Use JApplicationWeb::compress() instead
	 */
	protected static function compress($data)
	{
		$encoding = self::clientEncoding();

		if (!$encoding)
		{
			return $data;
		}

		if (!extension_loaded('zlib') || ini_get('zlib.output_compression'))
		{
			return $data;
		}

		if (headers_sent())
		{
			return $data;
		}

		if (connection_status() !== 0)
		{
			return $data;
		}

		// Ideal level
		$level = 4;

		/*
		$size    = strlen($data);
		$crc     = crc32($data);
		$gzdata  = "\x1f\x8b\x08\x00\x00\x00\x00\x00";
		$gzdata .= gzcompress($data, $level);
		$gzdata  = substr($gzdata, 0, strlen($gzdata) - 4);
		$gzdata .= pack("V",$crc) . pack("V", $size);
		*/

		$gzdata = gzencode($data, $level);

		self::setHeader('Content-Encoding', $encoding);

		// Header will be removed at 4.0
		if (defined('JVERSION') && JFactory::getConfig()->get('MetaVersion', 0))
		{
			self::setHeader('X-Content-Encoded-By', 'Joomla! ' . JVERSION);
		}

		return $gzdata;
	}

	/**
	 * Check, whether client supports compressed data
	 *
	 * @return  boolean
	 *
	 * @since   1.7
	 * @note    Replaces _clientEncoding method from 1.5
	 * @deprecated  3.2  Use JApplicationWebClient instead
	 */
	protected static function clientEncoding()
	{
		if (!isset($_SERVER['HTTP_ACCEPT_ENCODING']))
		{
			return false;
		}

		$encoding = false;

		if (false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip'))
		{
			$encoding = 'gzip';
		}

		if (false !== strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'x-gzip'))
		{
			$encoding = 'x-gzip';
		}

		return $encoding;
	}
}
legacy/utilities/xmlelement.php000064400000005474152177723700012733 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Utilities
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JXMLElement is deprecated. Use SimpleXMLElement.', JLog::WARNING, 'deprecated');

/**
 * Wrapper class for php SimpleXMLElement.
 *
 * @since       1.6
 * @deprecated  3.0  Use SimpleXMLElement instead.
 */
class JXMLElement extends SimpleXMLElement
{
	/**
	 * Get the name of the element.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.0  Use SimpleXMLElement::getName() instead.
	 */
	public function name()
	{
		JLog::add('JXMLElement::name() is deprecated, use SimpleXMLElement::getName() instead.', JLog::WARNING, 'deprecated');

		return (string) $this->getName();
	}

	/**
	 * Return a well-formed XML string based on SimpleXML element
	 *
	 * @param   boolean  $compressed  Should we use indentation and newlines ?
	 * @param   string   $indent      Indention character.
	 * @param   integer  $level       The level within the document which informs the indentation.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.0  Use SimpleXMLElement::asXml() instead.
	 */
	public function asFormattedXml($compressed = false, $indent = "\t", $level = 0)
	{
		JLog::add('JXMLElement::asFormattedXml() is deprecated, use SimpleXMLElement::asXml() instead.', JLog::WARNING, 'deprecated');
		$out = '';

		// Start a new line, indent by the number indicated in $level
		$out .= $compressed ? '' : "\n" . str_repeat($indent, $level);

		// Add a <, and add the name of the tag
		$out .= '<' . $this->getName();

		// For each attribute, add attr="value"
		foreach ($this->attributes() as $attr)
		{
			$out .= ' ' . $attr->getName() . '="' . htmlspecialchars((string) $attr, ENT_COMPAT, 'UTF-8') . '"';
		}

		// If there are no children and it contains no data, end it off with a />
		if (!(string) $this && !count($this->children()))
		{
			$out .= ' />';
		}
		else
		{
			// If there are children
			if (count($this->children()))
			{
				// Close off the start tag
				$out .= '>';

				$level++;

				// For each child, call the asFormattedXML function (this will ensure that all children are added recursively)
				foreach ($this->children() as $child)
				{
					$out .= $child->asFormattedXml($compressed, $indent, $level);
				}

				$level--;

				// Add the newline and indentation to go along with the close tag
				$out .= $compressed ? '' : "\n" . str_repeat($indent, $level);
			}
			elseif ((string) $this)
			{
				// If there is data, close off the start tag and add the data
				$out .= '>' . htmlspecialchars((string) $this, ENT_COMPAT, 'UTF-8');
			}

			// Add the end tag
			$out .= '</' . $this->getName() . '>';
		}

		return $out;
	}
}
legacy/dispatcher/dispatcher.php000064400000001227152177723700013012 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Dispatcher
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Deprecated class placeholder.  You should use JEventDispatcher instead.
 *
 * @since       1.5
 * @deprecated  3.0
 */
class JDispatcher extends JEventDispatcher
{
	/**
	 * Constructor.
	 *
	 * @since   1.5
	 */
	public function __construct()
	{
		JLog::add('JDispatcher is deprecated. Use JEventDispatcher instead.', JLog::WARNING, 'deprecated');
		parent::__construct();
	}
}
legacy/exception/exception.php000064400000020261152177723700012531 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Exception
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla! Exception object.
 *
 * @since       1.5
 * @deprecated  1.7
 */
class JException extends Exception
{
	/**
	 * Error level.
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $level = null;

	/**
	 * Error code.
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $code = null;

	/**
	 * Error message.
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $message = null;

	/**
	 * Additional info about the error relevant to the developer,
	 * for example, if a database connect fails, the dsn used
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $info = '';

	/**
	 * Name of the file the error occurred in [Available if backtrace is enabled]
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $file = null;

	/**
	 * Line number the error occurred in [Available if backtrace is enabled]
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $line = 0;

	/**
	 * Name of the method the error occurred in [Available if backtrace is enabled]
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $function = null;

	/**
	 * Name of the class the error occurred in [Available if backtrace is enabled]
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $class = null;

	/**
	 * @var    string  Error type.
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $type = null;

	/**
	 * Arguments received by the method the error occurred in [Available if backtrace is enabled]
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $args = array();

	/**
	 * Backtrace information.
	 *
	 * @var    mixed
	 * @since  1.5
	 * @deprecated  1.7
	 */
	protected $backtrace = null;

	/**
	 * Container holding the error messages
	 *
	 * @var    string[]
	 * @since  1.6
	 * @deprecated  1.7
	 */
	protected $_errors = array();

	/**
	 * Constructor
	 * - used to set up the error with all needed error details.
	 *
	 * @param   string   $msg        The error message
	 * @param   integer  $code       The error code from the application
	 * @param   integer  $level      The error level (use the PHP constants E_ALL, E_NOTICE etc.).
	 * @param   string   $info       Optional: The additional error information.
	 * @param   boolean  $backtrace  True if backtrace information is to be collected
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public function __construct($msg, $code = 0, $level = null, $info = null, $backtrace = false)
	{
		JLog::add('JException is deprecated.', JLog::WARNING, 'deprecated');

		$this->level = $level;
		$this->code = $code;
		$this->message = $msg;

		if ($info != null)
		{
			$this->info = $info;
		}

		if ($backtrace && function_exists('debug_backtrace'))
		{
			$this->backtrace = debug_backtrace();

			for ($i = count($this->backtrace) - 1; $i >= 0; --$i)
			{
				++$i;

				if (isset($this->backtrace[$i]['file']))
				{
					$this->file = $this->backtrace[$i]['file'];
				}

				if (isset($this->backtrace[$i]['line']))
				{
					$this->line = $this->backtrace[$i]['line'];
				}

				if (isset($this->backtrace[$i]['class']))
				{
					$this->class = $this->backtrace[$i]['class'];
				}

				if (isset($this->backtrace[$i]['function']))
				{
					$this->function = $this->backtrace[$i]['function'];
				}

				if (isset($this->backtrace[$i]['type']))
				{
					$this->type = $this->backtrace[$i]['type'];
				}

				$this->args = false;

				if (isset($this->backtrace[$i]['args']))
				{
					$this->args = $this->backtrace[$i]['args'];
				}

				break;
			}
		}

		// Store exception for debugging purposes!
		JError::addToStack($this);

		parent::__construct($msg, (int) $code);
	}

	/**
	 * Returns to error message
	 *
	 * @return  string  Error message
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 */
	public function __toString()
	{
		JLog::add('JException::__toString is deprecated.', JLog::WARNING, 'deprecated');

		return $this->message;
	}

	/**
	 * Returns to error message
	 *
	 * @return  string   Error message
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public function toString()
	{
		JLog::add('JException::toString is deprecated.', JLog::WARNING, 'deprecated');

		return (string) $this;
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $property  The name of the property
	 * @param   mixed   $default   The default value
	 *
	 * @return  mixed  The value of the property or null
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 * @see     JException::getProperties()
	 */
	public function get($property, $default = null)
	{
		JLog::add('JException::get is deprecated.', JLog::WARNING, 'deprecated');

		if (isset($this->$property))
		{
			return $this->$property;
		}

		return $default;
	}

	/**
	 * Returns an associative array of object properties
	 *
	 * @param   boolean  $public  If true, returns only the public properties
	 *
	 * @return  array  Object properties
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 * @see     JException::get()
	 */
	public function getProperties($public = true)
	{
		JLog::add('JException::getProperties is deprecated.', JLog::WARNING, 'deprecated');

		$vars = get_object_vars($this);

		if ($public)
		{
			foreach ($vars as $key => $value)
			{
				if (strpos($key, '_') === 0)
				{
					unset($vars[$key]);
				}
			}
		}

		return $vars;
	}

	/**
	 * Get the most recent error message
	 *
	 * @param   integer  $i         Option error index
	 * @param   boolean  $toString  Indicates if JError objects should return their error message
	 *
	 * @return  string  Error message
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 */
	public function getError($i = null, $toString = true)
	{
		JLog::add('JException::getError is deprecated.', JLog::WARNING, 'deprecated');

		// Find the error
		if ($i === null)
		{
			// Default, return the last message
			$error = end($this->_errors);
		}
		elseif (!array_key_exists($i, $this->_errors))
		{
			// If $i has been specified but does not exist, return false
			return false;
		}
		else
		{
			$error = $this->_errors[$i];
		}

		// Check if only the string is requested
		if ($error instanceof Exception && $toString)
		{
			return (string) $error;
		}

		return $error;
	}

	/**
	 * Return all errors, if any
	 *
	 * @return  array  Array of error messages or JErrors
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 */
	public function getErrors()
	{
		JLog::add('JException::getErrors is deprecated.', JLog::WARNING, 'deprecated');

		return $this->_errors;
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $property  The name of the property
	 * @param   mixed   $value     The value of the property to set
	 *
	 * @return  mixed  Previous value of the property
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 * @see     JException::setProperties()
	 */
	public function set($property, $value = null)
	{
		JLog::add('JException::set is deprecated.', JLog::WARNING, 'deprecated');

		$previous = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;

		return $previous;
	}

	/**
	 * Set the object properties based on a named array/hash
	 *
	 * @param   mixed  $properties  Either and associative array or another object
	 *
	 * @return  boolean
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 * @see     JException::set()
	 */
	public function setProperties($properties)
	{
		JLog::add('JException::setProperties is deprecated.', JLog::WARNING, 'deprecated');

		// Cast to an array
		$properties = (array) $properties;

		if (is_array($properties))
		{
			foreach ($properties as $k => $v)
			{
				$this->$k = $v;
			}

			return true;
		}

		return false;
	}

	/**
	 * Add an error message
	 *
	 * @param   string  $error  Error message
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated  1.7
	 */
	public function setError($error)
	{
		JLog::add('JException::setErrors is deprecated.', JLog::WARNING, 'deprecated');

		$this->_errors[] = $error;
	}
}
legacy/base/node.php000064400000005760152177723700010403 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Tree Node Class.
 *
 * @since       1.5
 * @deprecated  3.0
 */
class JNode extends JObject
{
	/**
	 * Parent node
	 *
	 * @var    JNode
	 * @since  1.5
	 * @deprecated  3.0
	 */
	protected $_parent = null;

	/**
	 * Array of Children
	 *
	 * @var    JNode[]
	 * @since  1.5
	 * @deprecated  3.0
	 */
	protected $_children = array();

	/**
	 * Constructor
	 *
	 * @since  1.5
	 * @deprecated  3.0
	 */
	public function __construct()
	{
		JLog::add('JNode::__construct() is deprecated.', JLog::WARNING, 'deprecated');

		return true;
	}

	/**
	 * Add child to this node
	 *
	 * If the child already has a parent, the link is unset
	 *
	 * @param   JNode  &$child  The child to be added
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function addChild(&$child)
	{
		JLog::add('JNode::addChild() is deprecated.', JLog::WARNING, 'deprecated');

		if ($child instanceof Jnode)
		{
			$child->setParent($this);
		}
	}

	/**
	 * Set the parent of a this node
	 *
	 * If the node already has a parent, the link is unset
	 *
	 * @param   JNode|null  &$parent  The JNode for parent to be set or null
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function setParent(&$parent)
	{
		JLog::add('JNode::setParent() is deprecated.', JLog::WARNING, 'deprecated');

		if ($parent instanceof JNode || $parent === null)
		{
			$hash = spl_object_hash($this);

			if ($this->_parent !== null)
			{
				unset($this->_parent->children[$hash]);
			}

			if ($parent !== null)
			{
				$parent->_children[$hash] = & $this;
			}

			$this->_parent = & $parent;
		}
	}

	/**
	 * Get the children of this node
	 *
	 * @return  JNode[]  The children
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function &getChildren()
	{
		JLog::add('JNode::getChildren() is deprecated.', JLog::WARNING, 'deprecated');

		return $this->_children;
	}

	/**
	 * Get the parent of this node
	 *
	 * @return  JNode|null  JNode object with the parent or null for no parent
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function &getParent()
	{
		JLog::add('JNode::getParent() is deprecated.', JLog::WARNING, 'deprecated');

		return $this->_parent;
	}

	/**
	 * Test if this node has children
	 *
	 * @return  boolean  True if there are children
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function hasChildren()
	{
		JLog::add('JNode::hasChildren() is deprecated.', JLog::WARNING, 'deprecated');

		return (bool) count($this->_children);
	}

	/**
	 * Test if this node has a parent
	 *
	 * @return  boolean  True if there is a parent
	 *
	 * @since   1.6
	 * @deprecated  3.0
	 */
	public function hasParent()
	{
		JLog::add('JNode::hasParent() is deprecated.', JLog::WARNING, 'deprecated');

		return $this->getParent() != null;
	}
}
legacy/base/observable.php000064400000007070152177723700011576 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Abstract observable class to implement the observer design pattern
 *
 * @since       1.5
 * @deprecated  2.5
 */
class JObservable extends JObject
{
	/**
	 * An array of Observer objects to notify
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  2.5
	 */
	protected $_observers = array();

	/**
	 * The state of the observable object
	 *
	 * @var    mixed
	 * @since  1.5
	 * @deprecated  2.5
	 */
	protected $_state = null;

	/**
	 * A multi dimensional array of [function][] = key for observers
	 *
	 * @var    array
	 * @since  1.6
	 * @deprecated  2.5
	 */
	protected $_methods = array();

	/**
	 * Constructor
	 *
	 * Note: Make Sure it's not directly instantiated
	 *
	 * @since  1.5
	 * @deprecated  2.5
	 */
	public function __construct()
	{
		$this->_observers = array();
	}

	/**
	 * Get the state of the JObservable object
	 *
	 * @return  mixed  The state of the object.
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	public function getState()
	{
		return $this->_state;
	}

	/**
	 * Update each attached observer object and return an array of their return values
	 *
	 * @return  array  Array of return values from the observers
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	public function notify()
	{
		// Iterate through the _observers array
		foreach ($this->_observers as $observer)
		{
			$return[] = $observer->update();
		}

		return $return;
	}

	/**
	 * Attach an observer object
	 *
	 * @param   object  $observer  An observer object to attach
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	public function attach($observer)
	{
		if (is_array($observer))
		{
			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
			{
				return;
			}

			// Make sure we haven't already attached this array as an observer
			foreach ($this->_observers as $check)
			{
				if (is_array($check) && $check['event'] == $observer['event'] && $check['handler'] == $observer['handler'])
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			end($this->_observers);
			$methods = array($observer['event']);
		}
		else
		{
			if (!($observer instanceof JObserver))
			{
				return;
			}

			// Make sure we haven't already attached this object as an observer
			$class = get_class($observer);

			foreach ($this->_observers as $check)
			{
				if ($check instanceof $class)
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			$methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
		}

		$key = key($this->_observers);

		foreach ($methods as $method)
		{
			$method = strtolower($method);

			if (!isset($this->_methods[$method]))
			{
				$this->_methods[$method] = array();
			}

			$this->_methods[$method][] = $key;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @param   object  $observer  An observer object to detach.
	 *
	 * @return  boolean  True if the observer object was detached.
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	public function detach($observer)
	{
		$retval = false;

		$key = array_search($observer, $this->_observers);

		if ($key !== false)
		{
			unset($this->_observers[$key]);
			$retval = true;

			foreach ($this->_methods as &$method)
			{
				$k = array_search($key, $method);

				if ($k !== false)
				{
					unset($method[$k]);
				}
			}
		}

		return $retval;
	}
}
legacy/base/observer.php000064400000002160152177723700011274 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Abstract observer class to implement the observer design pattern
 *
 * @since       1.5
 * @deprecated  2.5
 */
abstract class JObserver extends JObject
{
	/**
	 * Event object to observe.
	 *
	 * @var    object
	 * @since  1.5
	 * @deprecated  2.5
	 */
	protected $_subject = null;

	/**
	 * Constructor
	 *
	 * @param   object  &$subject  The object to observe.
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	public function __construct(&$subject)
	{
		// Register the observer ($this) so we can be notified
		$subject->attach($this);

		// Set the subject to observe
		$this->_subject = &$subject;
	}

	/**
	 * Method to update the state of observable objects
	 *
	 * @param   array  &$args  An array of arguments to pass to the listener.
	 *
	 * @return  mixed
	 *
	 * @since   1.5
	 * @deprecated  2.5
	 */
	abstract public function update(&$args);
}
legacy/base/tree.php000064400000003463152177723700010413 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Tree Class.
 *
 * @since       1.5
 * @deprecated  3.0
 */
class JTree extends JObject
{
	/**
	 * Root node
	 *
	 * @var    JNode
	 * @since  1.5
	 * @deprecated  3.0
	 */
	protected $_root = null;

	/**
	 * Current working node
	 *
	 * @var    JNode
	 * @since  1.5
	 * @deprecated  3.0
	 */
	protected $_current = null;

	/**
	 * Constructor
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function __construct()
	{
		JLog::add('JTree::__construct() is deprecated.', JLog::WARNING, 'deprecated');

		$this->_root = new JNode('ROOT');
		$this->_current = & $this->_root;
	}

	/**
	 * Method to add a child
	 *
	 * @param   array    &$node       The node to process
	 * @param   boolean  $setCurrent  True to set as current working node
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function addChild(&$node, $setCurrent = false)
	{
		JLog::add('JTree::addChild() is deprecated.', JLog::WARNING, 'deprecated');

		$this->_current->addChild($node);

		if ($setCurrent)
		{
			$this->_current = &$node;
		}
	}

	/**
	 * Method to get the parent
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function getParent()
	{
		JLog::add('JTree::getParent() is deprecated.', JLog::WARNING, 'deprecated');

		$this->_current = &$this->_current->getParent();
	}

	/**
	 * Method to get the parent
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.0
	 */
	public function reset()
	{
		JLog::add('JTree::reset() is deprecated.', JLog::WARNING, 'deprecated');

		$this->_current = &$this->_root;
	}
}
legacy/table/session.php000064400000011617152177723700011314 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Table
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Session table
 *
 * @since       1.5
 * @deprecated  3.2  Use SQL queries to interact with the session table.
 */
class JTableSession extends JTable
{
	/**
	 * Constructor
	 *
	 * @param   JDatabaseDriver  $db  Database driver object.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function __construct(JDatabaseDriver $db)
	{
		JLog::add('JTableSession is deprecated. Use SQL queries directly to interact with the session table.', JLog::WARNING, 'deprecated');
		parent::__construct('#__session', 'session_id', $db);

		$this->guest = 1;
		$this->username = '';
	}

	/**
	 * Insert a session
	 *
	 * @param   string   $sessionId  The session id
	 * @param   integer  $clientId   The id of the client application
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function insert($sessionId, $clientId)
	{
		$this->session_id = $sessionId;
		$this->client_id = $clientId;

		$this->time = time();
		$ret = $this->_db->insertObject($this->_tbl, $this, 'session_id');

		if (!$ret)
		{
			$this->setError(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', strtolower(get_class($this)), $this->_db->stderr()));

			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Updates the session
	 *
	 * @param   boolean  $updateNulls  True to update fields even if they are null.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function update($updateNulls = false)
	{
		$this->time = time();
		$ret = $this->_db->updateObject($this->_tbl, $this, 'session_id', $updateNulls);

		if (!$ret)
		{
			$this->setError(JText::sprintf('JLIB_DATABASE_ERROR_STORE_FAILED', strtolower(get_class($this)), $this->_db->stderr()));

			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Destroys the pre-existing session
	 *
	 * @param   integer  $userId     Identifier of the user for this session.
	 * @param   array    $clientIds  Array of client ids for which session(s) will be destroyed
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function destroy($userId, $clientIds = array())
	{
		$clientIds = implode(',', $clientIds);

		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('userid') . ' = ' . $this->_db->quote($userId))
			->where($this->_db->quoteName('client_id') . ' IN (' . $clientIds . ')');
		$this->_db->setQuery($query);

		if (!$this->_db->execute())
		{
			$this->setError($this->_db->stderr());

			return false;
		}

		return true;
	}

	/**
	 * Purge old sessions
	 *
	 * @param   integer  $maxLifetime  Session age in seconds
	 *
	 * @return  mixed  Resource on success, null on fail
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function purge($maxLifetime = 1440)
	{
		$past = time() - $maxLifetime;
		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('time') . ' < ' . (int) $past);
		$this->_db->setQuery($query);

		return $this->_db->execute();
	}

	/**
	 * Find out if a user has one or more active sessions
	 *
	 * @param   integer  $userid  The identifier of the user
	 *
	 * @return  boolean  True if a session for this user exists
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function exists($userid)
	{
		$query = $this->_db->getQuery(true)
			->select('COUNT(userid)')
			->from($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName('userid') . ' = ' . $this->_db->quote($userid));
		$this->_db->setQuery($query);

		if (!$result = $this->_db->loadResult())
		{
			$this->setError($this->_db->stderr());

			return false;
		}

		return (boolean) $result;
	}

	/**
	 * Overloaded delete method
	 *
	 * We must override it because of the non-integer primary key
	 *
	 * @param   integer  $oid  The object id (optional).
	 *
	 * @return  mixed  True if successful otherwise an error message
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use SQL queries to interact with the session table.
	 */
	public function delete($oid = null)
	{
		$k = $this->_tbl_key;

		if ($oid)
		{
			$this->$k = $oid;
		}

		$query = $this->_db->getQuery(true)
			->delete($this->_db->quoteName($this->_tbl))
			->where($this->_db->quoteName($this->_tbl_key) . ' = ' . $this->_db->quote($this->$k));
		$this->_db->setQuery($query);

		$this->_db->execute();

		return true;
	}
}
legacy/request/request.php000064400000034761152177723700011727 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Request
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Create the request global object
 */
$GLOBALS['_JREQUEST'] = array();

/**
 * Set the available masks for cleaning variables
 */
const JREQUEST_NOTRIM    = 1;
const JREQUEST_ALLOWRAW  = 2;
const JREQUEST_ALLOWHTML = 4;

JLog::add('JRequest is deprecated.', JLog::WARNING, 'deprecated');

/**
 * JRequest Class
 *
 * This class serves to provide the Joomla Platform with a common interface to access
 * request variables.  This includes $_POST, $_GET, and naturally $_REQUEST.  Variables
 * can be passed through an input filter to avoid injection or returned raw.
 *
 * @since       1.5
 * @deprecated  1.7 Get the JInput object from the application instead
 */
class JRequest
{
	/**
	 * Gets the full request path.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getUri()
	{
		$uri = JUri::getInstance();

		return $uri->toString(array('path', 'query'));
	}

	/**
	 * Gets the request method.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @deprecated  1.7 Use JInput::getMethod() instead
	 */
	public static function getMethod()
	{
		return strtoupper($_SERVER['REQUEST_METHOD']);
	}

	/**
	 * Fetches and returns a given variable.
	 *
	 * The default behaviour is fetching variables depending on the
	 * current request method: GET and HEAD will result in returning
	 * an entry from $_GET, POST and PUT will result in returning an
	 * entry from $_POST.
	 *
	 * You can force the source by setting the $hash parameter:
	 *
	 * post    $_POST
	 * get     $_GET
	 * files   $_FILES
	 * cookie  $_COOKIE
	 * env     $_ENV
	 * server  $_SERVER
	 * method  via current $_SERVER['REQUEST_METHOD']
	 * default $_REQUEST
	 *
	 * @param   string   $name     Variable name.
	 * @param   mixed    $default  Default value if the variable does not exist.
	 * @param   string   $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 * @param   string   $type     Return type for the variable, for valid values see {@link JFilterInput::clean()}.
	 * @param   integer  $mask     Filter mask for the variable.
	 *
	 * @return  mixed  Requested variable.
	 *
	 * @since   1.5
	 * @deprecated  1.7  Use JInput::get()
	 */
	public static function getVar($name, $default = null, $hash = 'default', $type = 'none', $mask = 0)
	{
		// Ensure hash and type are uppercase
		$hash = strtoupper($hash);

		if ($hash === 'METHOD')
		{
			$hash = strtoupper($_SERVER['REQUEST_METHOD']);
		}

		$type = strtoupper($type);
		$sig = $hash . $type . $mask;

		// Get the input hash
		switch ($hash)
		{
			case 'GET':
				$input = &$_GET;
				break;
			case 'POST':
				$input = &$_POST;
				break;
			case 'FILES':
				$input = &$_FILES;
				break;
			case 'COOKIE':
				$input = &$_COOKIE;
				break;
			case 'ENV':
				$input = &$_ENV;
				break;
			case 'SERVER':
				$input = &$_SERVER;
				break;
			default:
				$input = &$_REQUEST;
				$hash = 'REQUEST';
				break;
		}

		if (isset($GLOBALS['_JREQUEST'][$name]['SET.' . $hash]) && ($GLOBALS['_JREQUEST'][$name]['SET.' . $hash] === true))
		{
			// Get the variable from the input hash
			$var = (isset($input[$name]) && $input[$name] !== null) ? $input[$name] : $default;
			$var = self::_cleanVar($var, $mask, $type);
		}
		elseif (!isset($GLOBALS['_JREQUEST'][$name][$sig]))
		{
			if (isset($input[$name]))
			{
				// Get the variable from the input hash and clean it
				$var = self::_cleanVar($input[$name], $mask, $type);

				$GLOBALS['_JREQUEST'][$name][$sig] = $var;
			}
			elseif ($default !== null)
			{
				// Clean the default value
				$var = self::_cleanVar($default, $mask, $type);
			}
			else
			{
				$var = $default;
			}
		}
		else
		{
			$var = $GLOBALS['_JREQUEST'][$name][$sig];
		}

		return $var;
	}

	/**
	 * Fetches and returns a given filtered variable. The integer
	 * filter will allow only digits and the - sign to be returned. This is currently
	 * only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string   $name     Variable name.
	 * @param   integer  $default  Default value if the variable does not exist.
	 * @param   string   $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 *
	 * @return  integer  Requested variable.
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getInt($name, $default = 0, $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'int');
	}

	/**
	 * Fetches and returns a given filtered variable. The unsigned integer
	 * filter will allow only digits to be returned. This is currently
	 * only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string   $name     Variable name.
	 * @param   integer  $default  Default value if the variable does not exist.
	 * @param   string   $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 *
	 * @return  integer  Requested variable.
	 *
	 * @since   1.7
	 * @deprecated  1.7
	 */
	public static function getUInt($name, $default = 0, $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'uint');
	}

	/**
	 * Fetches and returns a given filtered variable.  The float
	 * filter only allows digits and periods.  This is currently
	 * only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string  $name     Variable name.
	 * @param   float   $default  Default value if the variable does not exist.
	 * @param   string  $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 *
	 * @return  float  Requested variable.
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getFloat($name, $default = 0.0, $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'float');
	}

	/**
	 * Fetches and returns a given filtered variable. The bool
	 * filter will only return true/false bool values. This is
	 * currently only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string   $name     Variable name.
	 * @param   boolean  $default  Default value if the variable does not exist.
	 * @param   string   $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 *
	 * @return  boolean  Requested variable.
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getBool($name, $default = false, $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'bool');
	}

	/**
	 * Fetches and returns a given filtered variable. The word
	 * filter only allows the characters [A-Za-z_]. This is currently
	 * only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string  $name     Variable name.
	 * @param   string  $default  Default value if the variable does not exist.
	 * @param   string  $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD).
	 *
	 * @return  string  Requested variable.
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getWord($name, $default = '', $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'word');
	}

	/**
	 * Cmd (Word and Integer) filter
	 *
	 * Fetches and returns a given filtered variable. The cmd
	 * filter only allows the characters [A-Za-z0-9.-_]. This is
	 * currently only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string  $name     Variable name
	 * @param   string  $default  Default value if the variable does not exist
	 * @param   string  $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD)
	 *
	 * @return  string  Requested variable
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getCmd($name, $default = '', $hash = 'default')
	{
		return self::getVar($name, $default, $hash, 'cmd');
	}

	/**
	 * Fetches and returns a given filtered variable. The string
	 * filter deletes 'bad' HTML code, if not overridden by the mask.
	 * This is currently only a proxy function for getVar().
	 *
	 * See getVar() for more in-depth documentation on the parameters.
	 *
	 * @param   string   $name     Variable name
	 * @param   string   $default  Default value if the variable does not exist
	 * @param   string   $hash     Where the var should come from (POST, GET, FILES, COOKIE, METHOD)
	 * @param   integer  $mask     Filter mask for the variable
	 *
	 * @return  string   Requested variable
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function getString($name, $default = '', $hash = 'default', $mask = 0)
	{
		// Cast to string, in case JREQUEST_ALLOWRAW was specified for mask
		return (string) self::getVar($name, $default, $hash, 'string', $mask);
	}

	/**
	 * Set a variable in one of the request variables.
	 *
	 * @param   string   $name       Name
	 * @param   string   $value      Value
	 * @param   string   $hash       Hash
	 * @param   boolean  $overwrite  Boolean
	 *
	 * @return  string   Previous value
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	public static function setVar($name, $value = null, $hash = 'method', $overwrite = true)
	{
		// If overwrite is true, makes sure the variable hasn't been set yet
		if (!$overwrite && array_key_exists($name, $_REQUEST))
		{
			return $_REQUEST[$name];
		}

		// Clean global request var
		$GLOBALS['_JREQUEST'][$name] = array();

		// Get the request hash value
		$hash = strtoupper($hash);

		if ($hash === 'METHOD')
		{
			$hash = strtoupper($_SERVER['REQUEST_METHOD']);
		}

		$previous = array_key_exists($name, $_REQUEST) ? $_REQUEST[$name] : null;

		switch ($hash)
		{
			case 'GET':
				$_GET[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'POST':
				$_POST[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'COOKIE':
				$_COOKIE[$name] = $value;
				$_REQUEST[$name] = $value;
				break;
			case 'FILES':
				$_FILES[$name] = $value;
				break;
			case 'ENV':
				$_ENV[$name] = $value;
				break;
			case 'SERVER':
				$_SERVER[$name] = $value;
				break;
		}

		// Mark this variable as 'SET'
		$GLOBALS['_JREQUEST'][$name]['SET.' . $hash] = true;
		$GLOBALS['_JREQUEST'][$name]['SET.REQUEST'] = true;

		return $previous;
	}

	/**
	 * Fetches and returns a request array.
	 *
	 * The default behaviour is fetching variables depending on the
	 * current request method: GET and HEAD will result in returning
	 * $_GET, POST and PUT will result in returning $_POST.
	 *
	 * You can force the source by setting the $hash parameter:
	 *
	 * post     $_POST
	 * get      $_GET
	 * files    $_FILES
	 * cookie   $_COOKIE
	 * env      $_ENV
	 * server   $_SERVER
	 * method   via current $_SERVER['REQUEST_METHOD']
	 * default  $_REQUEST
	 *
	 * @param   string   $hash  to get (POST, GET, FILES, METHOD).
	 * @param   integer  $mask  Filter mask for the variable.
	 *
	 * @return  mixed    Request hash.
	 *
	 * @since   1.5
	 * @deprecated  1.7  Use JInput::get()
	 * @see     JInput
	 */
	public static function get($hash = 'default', $mask = 0)
	{
		$hash = strtoupper($hash);

		if ($hash === 'METHOD')
		{
			$hash = strtoupper($_SERVER['REQUEST_METHOD']);
		}

		switch ($hash)
		{
			case 'GET':
				$input = $_GET;
				break;

			case 'POST':
				$input = $_POST;
				break;

			case 'FILES':
				$input = $_FILES;
				break;

			case 'COOKIE':
				$input = $_COOKIE;
				break;

			case 'ENV':
				$input = &$_ENV;
				break;

			case 'SERVER':
				$input = &$_SERVER;
				break;

			default:
				$input = $_REQUEST;
				break;
		}

		return self::_cleanVar($input, $mask);
	}

	/**
	 * Sets a request variable.
	 *
	 * @param   array    $array      An associative array of key-value pairs.
	 * @param   string   $hash       The request variable to set (POST, GET, FILES, METHOD).
	 * @param   boolean  $overwrite  If true and an existing key is found, the value is overwritten, otherwise it is ignored.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  1.7  Use JInput::set()
	 */
	public static function set($array, $hash = 'default', $overwrite = true)
	{
		foreach ($array as $key => $value)
		{
			self::setVar($key, $value, $hash, $overwrite);
		}
	}

	/**
	 * Checks for a form token in the request.
	 *
	 * Use in conjunction with JHtml::_('form.token').
	 *
	 * @param   string  $method  The request method in which to look for the token key.
	 *
	 * @return  boolean  True if found and valid, false otherwise.
	 *
	 * @since   1.5
	 * @deprecated  1.7 Use JSession::checkToken() instead. Note that 'default' has to become 'request'.
	 */
	public static function checkToken($method = 'post')
	{
		if ($method === 'default')
		{
			$method = 'request';
		}

		return JSession::checkToken($method);
	}

	/**
	 * Clean up an input variable.
	 *
	 * @param   mixed    $var   The input variable.
	 * @param   integer  $mask  Filter bit mask.
	 *                           1 = no trim: If this flag is cleared and the input is a string, the string will have leading and trailing
	 *                               whitespace trimmed.
	 *                           2 = allow_raw: If set, no more filtering is performed, higher bits are ignored.
	 *                           4 = allow_html: HTML is allowed, but passed through a safe HTML filter first. If set, no more filtering
	 *                               is performed. If no bits other than the 1 bit is set, a strict filter is applied.
	 * @param   string   $type  The variable type {@see JFilterInput::clean()}.
	 *
	 * @return  mixed  Same as $var
	 *
	 * @since   1.5
	 * @deprecated  1.7
	 */
	protected static function _cleanVar($var, $mask = 0, $type = null)
	{
		$mask = (int) $mask;

		// If the no trim flag is not set, trim the variable
		if (!($mask & 1) && is_string($var))
		{
			$var = trim($var);
		}

		// Now we handle input filtering
		if ($mask & 2)
		{
			// If the allow raw flag is set, do not modify the variable
		}
		elseif ($mask & 4)
		{
			// If the allow HTML flag is set, apply a safe HTML filter to the variable
			$safeHtmlFilter = JFilterInput::getInstance(null, null, 1, 1);
			$var = $safeHtmlFilter->clean($var, $type);
		}
		else
		{
			// Since no allow flags were set, we will apply the most strict filter to the variable
			// $tags, $attr, $tag_method, $attr_method, $xss_auto use defaults.
			$noHtmlFilter = JFilterInput::getInstance();
			$var = $noHtmlFilter->clean($var, $type);
		}

		return $var;
	}
}
legacy/database/mysql.php000064400000001116152177723700011444 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JDatabaseMysql is deprecated, use JDatabaseDriverMysql instead.', JLog::WARNING, 'deprecated');

/**
 * MySQL database driver
 *
 * @link        http://dev.mysql.com/doc/
 * @since       1.5
 * @deprecated  3.0 Use JDatabaseDriverMysql instead.
 */
class JDatabaseMysql extends JDatabaseDriverMysql
{
}
legacy/database/sqlsrv.php000064400000001175152177723700011636 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JDatabaseSqlsrv is deprecated, use JDatabaseDriverSqlsrv instead.', JLog::WARNING, 'deprecated');

/**
 * SQL Server database driver
 *
 * @link        https://msdn.microsoft.com/en-us/library/cc296152(SQL.90).aspx
 * @since       1.7
 * @deprecated  3.0 Use JDatabaseDriverSqlsrv instead.
 */
class JDatabaseSqlsrv extends JDatabaseDriverSqlsrv
{
}
legacy/database/exception.php000064400000001044152177723700012275 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JDatabaseException is deprecated, use SPL Exceptions instead.', JLog::WARNING, 'deprecated');

/**
 * Exception class definition for the Database subpackage.
 *
 * @since       1.7
 * @deprecated  2.5.5
 */
class JDatabaseException extends RuntimeException
{
}
legacy/database/sqlazure.php000064400000001217152177723700012147 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JDatabaseSqlazure is deprecated, use JDatabaseDriverSqlazure instead.', JLog::WARNING, 'deprecated');

/**
 * SQL Server database driver
 *
 * @link        https://azure.microsoft.com/en-us/documentation/services/sql-database/
 * @since       1.7
 * @deprecated  3.0 Use JDatabaseDriverSqlazure instead.
 */
class JDatabaseSqlazure extends JDatabaseDriverSqlazure
{
}
legacy/database/mysqli.php000064400000001150152177723700011613 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JDatabaseMysqli is deprecated, use JDatabaseDriverMysqli instead.', JLog::WARNING, 'deprecated');

/**
 * MySQLi database driver
 *
 * @link        https://www.php.net/manual/en/book.mysqli.php
 * @since       1.5
 * @deprecated  3.0 Use JDatabaseDriverMysqli instead.
 */
class JDatabaseMysqli extends JDatabaseDriverMysqli
{
}
legacy/form/field/modulelayout.php000064400000013306152177723700013310 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');

/**
 * Form Field to display a list of the layouts for module display from the module or template overrides.
 *
 * @since  1.6
 */
class JFormFieldModulelayout extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $type = 'ModuleLayout';

	/**
	 * Method to get the field input for module layouts.
	 *
	 * @return  string  The field input.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		// Get the client id.
		$clientId = $this->element['client_id'];

		if ($clientId === null && $this->form instanceof JForm)
		{
			$clientId = $this->form->getValue('client_id');
		}

		$clientId = (int) $clientId;

		$client = JApplicationHelper::getClientInfo($clientId);

		// Get the module.
		$module = (string) $this->element['module'];

		if (empty($module) && ($this->form instanceof JForm))
		{
			$module = $this->form->getValue('module');
		}

		$module = preg_replace('#\W#', '', $module);

		// Get the template.
		$template = (string) $this->element['template'];
		$template = preg_replace('#\W#', '', $template);

		// Get the style.
		$template_style_id = '';
		if ($this->form instanceof JForm)
		{
			$template_style_id = $this->form->getValue('template_style_id');
			$template_style_id = preg_replace('#\W#', '', $template_style_id);
		}

		// If an extension and view are present build the options.
		if ($module && $client)
		{
			// Load language file
			$lang = JFactory::getLanguage();
			$lang->load($module . '.sys', $client->path, null, false, true)
				|| $lang->load($module . '.sys', $client->path . '/modules/' . $module, null, false, true);

			// Get the database object and a new query object.
			$db = JFactory::getDbo();
			$query = $db->getQuery(true);

			// Build the query.
			$query->select('element, name')
				->from('#__extensions as e')
				->where('e.client_id = ' . (int) $clientId)
				->where('e.type = ' . $db->quote('template'))
				->where('e.enabled = 1');

			if ($template)
			{
				$query->where('e.element = ' . $db->quote($template));
			}

			if ($template_style_id)
			{
				$query->join('LEFT', '#__template_styles as s on s.template=e.element')
					->where('s.id=' . (int) $template_style_id);
			}

			// Set the query and load the templates.
			$db->setQuery($query);
			$templates = $db->loadObjectList('element');

			// Build the search paths for module layouts.
			$module_path = JPath::clean($client->path . '/modules/' . $module . '/tmpl');

			// Prepare array of component layouts
			$module_layouts = array();

			// Prepare the grouped list
			$groups = array();

			// Add the layout options from the module path.
			if (is_dir($module_path) && ($module_layouts = JFolder::files($module_path, '^[^_]*\.php$')))
			{
				// Create the group for the module
				$groups['_'] = array();
				$groups['_']['id'] = $this->id . '__';
				$groups['_']['text'] = JText::sprintf('JOPTION_FROM_MODULE');
				$groups['_']['items'] = array();

				foreach ($module_layouts as $file)
				{
					// Add an option to the module group
					$value = basename($file, '.php');
					$text = $lang->hasKey($key = strtoupper($module . '_LAYOUT_' . $value)) ? JText::_($key) : $value;
					$groups['_']['items'][] = JHtml::_('select.option', '_:' . $value, $text);
				}
			}

			// Loop on all templates
			if ($templates)
			{
				foreach ($templates as $template)
				{
					// Load language file
					$lang->load('tpl_' . $template->element . '.sys', $client->path, null, false, true)
						|| $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element, null, false, true);

					$template_path = JPath::clean($client->path . '/templates/' . $template->element . '/html/' . $module);

					// Add the layout options from the template path.
					if (is_dir($template_path) && ($files = JFolder::files($template_path, '^[^_]*\.php$')))
					{
						foreach ($files as $i => $file)
						{
							// Remove layout that already exist in component ones
							if (in_array($file, $module_layouts))
							{
								unset($files[$i]);
							}
						}

						if (count($files))
						{
							// Create the group for the template
							$groups[$template->element] = array();
							$groups[$template->element]['id'] = $this->id . '_' . $template->element;
							$groups[$template->element]['text'] = JText::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
							$groups[$template->element]['items'] = array();

							foreach ($files as $file)
							{
								// Add an option to the template group
								$value = basename($file, '.php');
								$text = $lang->hasKey($key = strtoupper('TPL_' . $template->element . '_' . $module . '_LAYOUT_' . $value))
									? JText::_($key) : $value;
								$groups[$template->element]['items'][] = JHtml::_('select.option', $template->element . ':' . $value, $text);
							}
						}
					}
				}
			}
			// Compute attributes for the grouped list
			$attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
			$attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

			// Prepare HTML code
			$html = array();

			// Compute the current selected values
			$selected = array($this->value);

			// Add a grouped list
			$html[] = JHtml::_(
				'select.groupedlist', $groups, $this->name,
				array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
			);

			return implode($html);
		}
		else
		{
			return '';
		}
	}
}
legacy/form/field/category.php000064400000005510152177723700012400 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Supports an HTML select list of categories
 *
 * @since  1.6
 */
class JFormFieldCategory extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	public $type = 'Category';

	/**
	 * Method to get the field options for category
	 * Use the extension attribute in a form to specify the.specific extension for
	 * which categories should be displayed.
	 * Use the show_root attribute to specify whether to show the global category root in the list.
	 *
	 * @return  array    The field option objects.
	 *
	 * @since   1.6
	 */
	protected function getOptions()
	{
		$options = array();
		$extension = $this->element['extension'] ? (string) $this->element['extension'] : (string) $this->element['scope'];
		$published = (string) $this->element['published'];
		$language  = (string) $this->element['language'];

		// Load the category options for a given extension.
		if (!empty($extension))
		{
			// Filter over published state or not depending upon if it is present.
			$filters = array();
			if ($published)
			{
				$filters['filter.published'] = explode(',', $published);
			}

			// Filter over language depending upon if it is present.
			if ($language)
			{
				$filters['filter.language'] = explode(',', $language);
			}

			if ($filters === array())
			{
				$options = JHtml::_('category.options', $extension);
			}
			else
			{
				$options = JHtml::_('category.options', $extension, $filters);
			}

			// Verify permissions.  If the action attribute is set, then we scan the options.
			if ((string) $this->element['action'])
			{
				// Get the current user object.
				$user = JFactory::getUser();

				foreach ($options as $i => $option)
				{
					/*
					 * To take save or create in a category you need to have create rights for that category
					 * unless the item is already in that category.
					 * Unset the option if the user isn't authorised for it. In this field assets are always categories.
					 */
					if ($user->authorise('core.create', $extension . '.category.' . $option->value) === false)
					{
						unset($options[$i]);
					}
				}
			}

			if (isset($this->element['show_root']))
			{
				array_unshift($options, JHtml::_('select.option', '0', JText::_('JGLOBAL_ROOT')));
			}
		}
		else
		{
			JLog::add(JText::_('JLIB_FORM_ERROR_FIELDS_CATEGORY_ERROR_EXTENSION_EMPTY'), JLog::WARNING, 'jerror');
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
legacy/form/field/componentlayout.php000064400000015436152177723700014033 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');

/**
 * Form Field to display a list of the layouts for a component view from
 * the extension or template overrides.
 *
 * @since  1.6
 */
class JFormFieldComponentlayout extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $type = 'ComponentLayout';

	/**
	 * Method to get the field input for a component layout field.
	 *
	 * @return  string   The field input.
	 *
	 * @since   1.6
	 */
	protected function getInput()
	{
		// Get the client id.
		$clientId = $this->element['client_id'];

		if ($clientId === null && $this->form instanceof JForm)
		{
			$clientId = $this->form->getValue('client_id');
		}

		$clientId = (int) $clientId;

		$client = JApplicationHelper::getClientInfo($clientId);

		// Get the extension.
		$extension = (string) $this->element['extension'];

		if (empty($extension) && ($this->form instanceof JForm))
		{
			$extension = $this->form->getValue('extension');
		}

		$extension = preg_replace('#\W#', '', $extension);

		$template = (string) $this->element['template'];
		$template = preg_replace('#\W#', '', $template);

		$template_style_id = '';
		if ($this->form instanceof JForm)
		{
			$template_style_id = $this->form->getValue('template_style_id');
			$template_style_id = preg_replace('#\W#', '', $template_style_id);
		}

		$view = (string) $this->element['view'];
		$view = preg_replace('#\W#', '', $view);

		// If a template, extension and view are present build the options.
		if ($extension && $view && $client)
		{
			// Load language file
			$lang = JFactory::getLanguage();
			$lang->load($extension . '.sys', JPATH_ADMINISTRATOR, null, false, true)
			|| $lang->load($extension . '.sys', JPATH_ADMINISTRATOR . '/components/' . $extension, null, false, true);

			// Get the database object and a new query object.
			$db = JFactory::getDbo();
			$query = $db->getQuery(true);

			// Build the query.
			$query->select('e.element, e.name')
				->from('#__extensions as e')
				->where('e.client_id = ' . (int) $clientId)
				->where('e.type = ' . $db->quote('template'))
				->where('e.enabled = 1');

			if ($template)
			{
				$query->where('e.element = ' . $db->quote($template));
			}

			if ($template_style_id)
			{
				$query->join('LEFT', '#__template_styles as s on s.template=e.element')
					->where('s.id=' . (int) $template_style_id);
			}

			// Set the query and load the templates.
			$db->setQuery($query);
			$templates = $db->loadObjectList('element');

			// Build the search paths for component layouts.
			$component_path = JPath::clean($client->path . '/components/' . $extension . '/views/' . $view . '/tmpl');

			// Prepare array of component layouts
			$component_layouts = array();

			// Prepare the grouped list
			$groups = array();

			// Add a Use Global option if useglobal="true" in XML file
			if ((string) $this->element['useglobal'] === 'true')
			{
				$groups[JText::_('JOPTION_FROM_STANDARD')]['items'][] = JHtml::_('select.option', '', JText::_('JGLOBAL_USE_GLOBAL'));
			}

			// Add the layout options from the component path.
			if (is_dir($component_path) && ($component_layouts = JFolder::files($component_path, '^[^_]*\.xml$', false, true)))
			{
				// Create the group for the component
				$groups['_'] = array();
				$groups['_']['id'] = $this->id . '__';
				$groups['_']['text'] = JText::sprintf('JOPTION_FROM_COMPONENT');
				$groups['_']['items'] = array();

				foreach ($component_layouts as $i => $file)
				{
					// Attempt to load the XML file.
					if (!$xml = simplexml_load_file($file))
					{
						unset($component_layouts[$i]);

						continue;
					}

					// Get the help data from the XML file if present.
					if (!$menu = $xml->xpath('layout[1]'))
					{
						unset($component_layouts[$i]);

						continue;
					}

					$menu = $menu[0];

					// Add an option to the component group
					$value = basename($file, '.xml');
					$component_layouts[$i] = $value;
					$text = isset($menu['option']) ? JText::_($menu['option']) : (isset($menu['title']) ? JText::_($menu['title']) : $value);
					$groups['_']['items'][] = JHtml::_('select.option', '_:' . $value, $text);
				}
			}

			// Loop on all templates
			if ($templates)
			{
				foreach ($templates as $template)
				{
					// Load language file
					$lang->load('tpl_' . $template->element . '.sys', $client->path, null, false, true)
						|| $lang->load('tpl_' . $template->element . '.sys', $client->path . '/templates/' . $template->element, null, false, true);

					$template_path = JPath::clean(
						$client->path
						. '/templates/'
						. $template->element
						. '/html/'
						. $extension
						. '/'
						. $view
					);

					// Add the layout options from the template path.
					if (is_dir($template_path) && ($files = JFolder::files($template_path, '^[^_]*\.php$', false, true)))
					{
						foreach ($files as $i => $file)
						{
							// Remove layout files that exist in the component folder
							if (in_array(basename($file, '.php'), $component_layouts))
							{
								unset($files[$i]);
							}
						}

						if (count($files))
						{
							// Create the group for the template
							$groups[$template->name] = array();
							$groups[$template->name]['id'] = $this->id . '_' . $template->element;
							$groups[$template->name]['text'] = JText::sprintf('JOPTION_FROM_TEMPLATE', $template->name);
							$groups[$template->name]['items'] = array();

							foreach ($files as $file)
							{
								// Add an option to the template group
								$value = basename($file, '.php');
								$text = $lang
									->hasKey(
										$key = strtoupper(
											'TPL_'
											. $template->name
											. '_'
											. $extension
											. '_'
											. $view
											. '_LAYOUT_'
											. $value
										)
									)
									? JText::_($key) : $value;
								$groups[$template->name]['items'][] = JHtml::_('select.option', $template->element . ':' . $value, $text);
							}
						}
					}
				}
			}

			// Compute attributes for the grouped list
			$attr = $this->element['size'] ? ' size="' . (int) $this->element['size'] . '"' : '';
			$attr .= $this->element['class'] ? ' class="' . (string) $this->element['class'] . '"' : '';

			// Prepare HTML code
			$html = array();

			// Compute the current selected values
			$selected = array($this->value);

			// Add a grouped list
			$html[] = JHtml::_(
				'select.groupedlist', $groups, $this->name,
				array('id' => $this->id, 'group.id' => 'id', 'list.attr' => $attr, 'list.select' => $selected)
			);

			return implode($html);
		}
		else
		{
			return '';
		}
	}
}
legacy/simplepie/factory.php000064400000003040152177723700012167 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Simplepie
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

jimport('simplepie.simplepie');

/**
 * Class to maintain a pathway.
 *
 * The user's navigated path within the application.
 *
 * @since       3.0
 * @deprecated  3.0 Use JFeed or supply your own methods
 */
class JSimplepieFactory
{
	/**
	 * Get a parsed XML Feed Source
	 *
	 * @param   string   $url         URL for feed source.
	 * @param   integer  $cache_time  Time to cache feed for (using internal cache mechanism).
	 *
	 * @return  SimplePie|boolean  SimplePie parsed object on success, false on failure.
	 *
	 * @since   3.0
	 * @deprecated  3.0  Use JFeedFactory($url) instead.
	 */
	public static function getFeedParser($url, $cache_time = 0)
	{
		JLog::add(__METHOD__ . ' is deprecated.   Use JFeedFactory() or supply Simple Pie instead.', JLog::WARNING, 'deprecated');

		$cache = JFactory::getCache('feed_parser', 'callback');

		if ($cache_time > 0)
		{
			$cache->setLifeTime($cache_time);
		}

		$simplepie = new SimplePie(null, null, 0);

		$simplepie->enable_cache(false);
		$simplepie->set_feed_url($url);
		$simplepie->force_feed(true);

		$contents = $cache->get(array($simplepie, 'init'), null, false, false);

		if ($contents)
		{
			return $simplepie;
		}

		JLog::add(JText::_('JLIB_UTIL_ERROR_LOADING_FEED_DATA'), JLog::WARNING, 'jerror');

		return false;
	}
}
legacy/error/error.php000064400000053310152177723700011020 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Error
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Error Definition: Illegal Options
 *
 * @var    integer
 * @since  1.5
 * @deprecated  4.0
 */
const JERROR_ILLEGAL_OPTIONS = 1;

/**
 * Error Definition: Callback does not exist
 *
 * @var    integer
 * @since  1.5
 * @deprecated  4.0
 */
const JERROR_CALLBACK_NOT_CALLABLE = 2;

/**
 * Error Definition: Illegal Handler
 *
 * @var    integer
 * @since  1.5
 * @deprecated  4.0
 */
const JERROR_ILLEGAL_MODE = 3;

/**
 * Error Handling Class
 *
 * This class is inspired in design and concept by patErrorManager <http://www.php-tools.net>
 *
 * patErrorManager contributors include:
 * - gERD Schaufelberger	<gerd@php-tools.net>
 * - Sebastian Mordziol	<argh@php-tools.net>
 * - Stephan Schmidt		<scst@php-tools.net>
 *
 * @since       1.5
 * @deprecated  4.0 Will be removed without replacement
 */
abstract class JError
{
	/**
	 * Legacy error handling marker
	 *
	 * @var    boolean  True to enable legacy error handling using JError, false to use exception handling.  This flag
	 *                  is present to allow an easy transition into exception handling for code written against the
	 *                  existing JError API in Joomla.
	 * @since  1.7
	 * @deprecated  4.0
	 */
	public static $legacy = false;

	/**
	 * Array of message levels
	 *
	 * @var    array
	 * @since  1.6
	 * @deprecated  4.0
	 */
	protected static $levels = array(E_NOTICE => 'Notice', E_WARNING => 'Warning', E_ERROR => 'Error');

	/**
	 * Array of message handlers
	 *
	 * @var    array
	 * @since  1.6
	 * @deprecated  4.0
	 */
	protected static $handlers = array(
		E_NOTICE => array('mode' => 'ignore'),
		E_WARNING => array('mode' => 'ignore'),
		E_ERROR => array('mode' => 'ignore'),
	);

	/**
	 * Array containing the error stack
	 *
	 * @var    JException[]
	 * @since  1.6
	 * @deprecated  4.0
	 */
	protected static $stack = array();

	/**
	 * Method to determine if a value is an exception object.
	 *
	 * @param   mixed  $object  Object to check.
	 *
	 * @return  boolean  True if argument is an exception, false otherwise.
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function isError($object)
	{
		JLog::add('JError::isError() is deprecated.', JLog::WARNING, 'deprecated');

		return $object instanceof Exception;
	}

	/**
	 * Method for retrieving the last exception object in the error stack
	 *
	 * @param   boolean  $unset  True to remove the error from the stack.
	 *
	 * @return  JException|boolean  Last JException object in the error stack or boolean false if none exist
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function getError($unset = false)
	{
		JLog::add('JError::getError() is deprecated.', JLog::WARNING, 'deprecated');

		if (!isset(self::$stack[0]))
		{
			return false;
		}

		if ($unset)
		{
			$error = array_shift(self::$stack);
		}
		else
		{
			$error = &self::$stack[0];
		}

		return $error;
	}

	/**
	 * Method for retrieving the exception stack
	 *
	 * @return  JException[]  Chronological array of errors that have been stored during script execution
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function getErrors()
	{
		JLog::add('JError::getErrors() is deprecated.', JLog::WARNING, 'deprecated');

		return self::$stack;
	}

	/**
	 * Method to add non-JError thrown JExceptions to the JError stack for debugging purposes
	 *
	 * @param   JException  $e  Add an exception to the stack.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated  4.0
	 */
	public static function addToStack(JException $e)
	{
		JLog::add('JError::addToStack() is deprecated.', JLog::WARNING, 'deprecated');

		self::$stack[] = &$e;
	}

	/**
	 * Create a new JException object given the passed arguments
	 *
	 * @param   integer  $level      The error level - use any of PHP's own error levels for
	 *                               this: E_ERROR, E_WARNING, E_NOTICE, E_USER_ERROR,
	 *                               E_USER_WARNING, E_USER_NOTICE.
	 * @param   string   $code       The application-internal error code for this error
	 * @param   string   $msg        The error message, which may also be shown the user if need be.
	 * @param   mixed    $info       Optional: Additional error information (usually only
	 *                               developer-relevant information that the user should never see,
	 *                               like a database DSN).
	 * @param   boolean  $backtrace  Add a stack backtrace to the exception.
	 *
	 * @return  JException
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see         JException
	 */
	public static function raise($level, $code, $msg, $info = null, $backtrace = false)
	{
		JLog::add('JError::raise() is deprecated.', JLog::WARNING, 'deprecated');

		// Build error object
		$exception = new JException($msg, $code, $level, $info, $backtrace);

		return self::throwError($exception);
	}

	/**
	 * Throw an error
	 *
	 * @param   JException  &$exception  An exception to throw.
	 *
	 * @return  JException  A reference to the handled JException object
	 *
	 * @since   1.6
	 * @deprecated  4.0 Just throw an Exception
	 * @see     JException
	 */
	public static function throwError(&$exception)
	{
		JLog::add('JError::throwError() is deprecated.', JLog::WARNING, 'deprecated');

		static $thrown = false;

		// If thrown is hit again, we've come back to JError in the middle of throwing another JError, so die!
		if ($thrown)
		{
			self::handleEcho($exception, array());

			// Inifite loop.
			jexit();
		}

		$thrown = true;
		$level = $exception->get('level');

		// See what to do with this kind of error
		$handler = self::getErrorHandling($level);

		$function = 'handle' . ucfirst($handler['mode']);

		if (is_callable(array('JError', $function)))
		{
			$reference = call_user_func_array(array('JError', $function), array(&$exception, isset($handler['options']) ? $handler['options'] : array()));
		}
		else
		{
			// This is required to prevent a very unhelpful white-screen-of-death
			jexit(
				'JError::raise -> Static method JError::' . $function . ' does not exist. Contact a developer to debug' .
				'<br /><strong>Error was</strong> <br />' . $exception->getMessage()
			);
		}
		// We don't need to store the error, since JException already does that for us!
		// Remove loop check
		$thrown = false;

		return $reference;
	}

	/**
	 * Wrapper method for the raise() method with predefined error level of E_ERROR and backtrace set to true.
	 *
	 * @param   string  $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 * @param   mixed   $info  Optional: Additional error information (usually only
	 *                         developer-relevant information that the user should
	 *                         never see, like a database DSN).
	 *
	 * @return  JException  $error  The thrown JException object
	 *
	 * @since   1.5
	 * @deprecated  4.0 Just throw an Exception
	 * @see     JError::raise()
	 */
	public static function raiseError($code, $msg, $info = null)
	{
		JLog::add('JError::raiseError() is deprecated.', JLog::WARNING, 'deprecated');

		return self::raise(E_ERROR, $code, $msg, $info, true);
	}

	/**
	 * Wrapper method for the {@link raise()} method with predefined error level of E_WARNING and backtrace set to false.
	 *
	 * @param   string  $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 * @param   mixed   $info  Optional: Additional error information (usually only
	 *                         developer-relevant information that
	 *                         the user should never see, like a database DSN).
	 *
	 * @return  JException  $error  The thrown JException object
	 *
	 * @since   1.5
	 * @deprecated  4.0 Use \Joomla\CMS\Factory::getApplication()->enqueueMessage($msg, 'warning') when wou want to notify the UI
	 * @see     JError::raise()
	 */
	public static function raiseWarning($code, $msg, $info = null)
	{
		JLog::add('JError::raiseWarning() is deprecated.', JLog::WARNING, 'deprecated');

		return self::raise(E_WARNING, $code, $msg, $info);
	}

	/**
	 * Wrapper method for the {@link raise()} method with predefined error level of E_NOTICE and backtrace set to false.
	 *
	 * @param   string  $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 * @param   mixed   $info  Optional: Additional error information (usually only
	 *                         developer-relevant information that the user
	 *                         should never see, like a database DSN).
	 *
	 * @return  JException  $error  The thrown JException object
	 *
	 * @since   1.5
	 * @deprecated  4.0 Use \Joomla\CMS\Factory::getApplication()->enqueueMessage($msg, 'notice') when wou want to notify the UI
	 * @see     JError::raise()
	 */
	public static function raiseNotice($code, $msg, $info = null)
	{
		JLog::add('JError::raiseNotice() is deprecated.', JLog::WARNING, 'deprecated');

		return self::raise(E_NOTICE, $code, $msg, $info);
	}

	/**
	 * Method to get the current error handler settings for a specified error level.
	 *
	 * @param   integer  $level  The error level to retrieve. This can be any of PHP's
	 *                           own error levels, e.g. E_ALL, E_NOTICE...
	 *
	 * @return  array    All error handling details
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function getErrorHandling($level)
	{
		JLog::add('JError::getErrorHandling() is deprecated.', JLog::WARNING, 'deprecated');

		return self::$handlers[$level];
	}

	/**
	 * Method to set the way the JError will handle different error levels. Use this if you want to override the default settings.
	 *
	 * Error handling modes:
	 * - ignore
	 * - echo
	 * - verbose
	 * - die
	 * - message
	 * - log
	 * - callback
	 *
	 * You may also set the error handling for several modes at once using PHP's bit operations.
	 * Examples:
	 * - E_ALL = Set the handling for all levels
	 * - E_ERROR | E_WARNING = Set the handling for errors and warnings
	 * - E_ALL ^ E_ERROR = Set the handling for all levels except errors
	 *
	 * @param   integer  $level    The error level for which to set the error handling
	 * @param   string   $mode     The mode to use for the error handling.
	 * @param   mixed    $options  Optional: Any options needed for the given mode.
	 *
	 * @return  boolean|JException  True on success or a JException object if failed.
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function setErrorHandling($level, $mode, $options = null)
	{
		JLog::add('JError::setErrorHandling() is deprecated.', JLog::WARNING, 'deprecated');

		$levels = self::$levels;

		$function = 'handle' . ucfirst($mode);

		if (!is_callable(array('JError', $function)))
		{
			return self::raiseError(E_ERROR, 'JError:' . JERROR_ILLEGAL_MODE, 'Error Handling mode is not known', 'Mode: ' . $mode . ' is not implemented.');
		}

		foreach ($levels as $eLevel => $eTitle)
		{
			if (($level & $eLevel) !== $eLevel)
			{
				continue;
			}

			// Set callback options
			if ($mode === 'callback')
			{
				if (!is_array($options))
				{
					return self::raiseError(E_ERROR, 'JError:' . JERROR_ILLEGAL_OPTIONS, 'Options for callback not valid');
				}

				if (!is_callable($options))
				{
					$tmp = array('GLOBAL');

					if (is_array($options))
					{
						$tmp[0] = $options[0];
						$tmp[1] = $options[1];
					}
					else
					{
						$tmp[1] = $options;
					}

					return self::raiseError(
						E_ERROR,
						'JError:' . JERROR_CALLBACK_NOT_CALLABLE,
						'Function is not callable',
						'Function:' . $tmp[1] . ' scope ' . $tmp[0] . '.'
					);
				}
			}

			// Save settings
			self::$handlers[$eLevel] = array('mode' => $mode);

			if ($options != null)
			{
				self::$handlers[$eLevel]['options'] = $options;
			}
		}

		return true;
	}

	/**
	 * Method that attaches the error handler to JError
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see     set_error_handler
	 */
	public static function attachHandler()
	{
		JLog::add('JError::getErrorHandling() is deprecated.', JLog::WARNING, 'deprecated');

		set_error_handler(array('JError', 'customErrorHandler'));
	}

	/**
	 * Method that detaches the error handler from JError
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see     restore_error_handler
	 */
	public static function detachHandler()
	{
		JLog::add('JError::detachHandler() is deprecated.', JLog::WARNING, 'deprecated');

		restore_error_handler();
	}

	/**
	 * Method to register a new error level for handling errors
	 *
	 * This allows you to add custom error levels to the built-in
	 * - E_NOTICE
	 * - E_WARNING
	 * - E_NOTICE
	 *
	 * @param   integer  $level    Error level to register
	 * @param   string   $name     Human readable name for the error level
	 * @param   string   $handler  Error handler to set for the new error level [optional]
	 *
	 * @return  boolean  True on success; false if the level already has been registered
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function registerErrorLevel($level, $name, $handler = 'ignore')
	{
		JLog::add('JError::registerErrorLevel() is deprecated.', JLog::WARNING, 'deprecated');

		if (isset(self::$levels[$level]))
		{
			return false;
		}

		self::$levels[$level] = $name;
		self::setErrorHandling($level, $handler);

		return true;
	}

	/**
	 * Translate an error level integer to a human readable string
	 * e.g. E_ERROR will be translated to 'Error'
	 *
	 * @param   integer  $level  Error level to translate
	 *
	 * @return  string|boolean  Human readable error level name or boolean false if it doesn't exist
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 */
	public static function translateErrorLevel($level)
	{
		JLog::add('JError::translateErrorLevel() is deprecated.', JLog::WARNING, 'deprecated');

		if (isset(self::$levels[$level]))
		{
			return self::$levels[$level];
		}

		return false;
	}

	/**
	 * Ignore error handler
	 * - Ignores the error
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException   The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see     JError::raise()
	 */
	public static function handleIgnore(&$error, $options)
	{
		JLog::add('JError::handleIgnore() is deprecated.', JLog::WARNING, 'deprecated');

		return $error;
	}

	/**
	 * Echo error handler
	 * - Echos the error message to output
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException  The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleEcho(&$error, $options)
	{
		JLog::add('JError::handleEcho() is deprecated.', JLog::WARNING, 'deprecated');

		$level_human = self::translateErrorLevel($error->get('level'));

		// If system debug is set, then output some more information.
		if (JDEBUG)
		{
			$backtrace = $error->getTrace();
			$trace = '';

			for ($i = count($backtrace) - 1; $i >= 0; $i--)
			{
				if (isset($backtrace[$i]['class']))
				{
					$trace .= sprintf("\n%s %s %s()", $backtrace[$i]['class'], $backtrace[$i]['type'], $backtrace[$i]['function']);
				}
				else
				{
					$trace .= sprintf("\n%s()", $backtrace[$i]['function']);
				}

				if (isset($backtrace[$i]['file']))
				{
					$trace .= sprintf(' @ %s:%d', $backtrace[$i]['file'], $backtrace[$i]['line']);
				}
			}
		}

		if (isset($_SERVER['HTTP_HOST']))
		{
			// Output as html
			echo "<br /><b>jos-$level_human</b>: "
				. $error->get('message') . "<br />\n"
				. (JDEBUG ? nl2br($trace) : '');
		}
		else
		{
			// Output as simple text
			if (defined('STDERR'))
			{
				fwrite(STDERR, "J$level_human: " . $error->get('message') . "\n");

				if (JDEBUG)
				{
					fwrite(STDERR, $trace);
				}
			}
			else
			{
				echo "J$level_human: " . $error->get('message') . "\n";

				if (JDEBUG)
				{
					echo $trace;
				}
			}
		}

		return $error;
	}

	/**
	 * Verbose error handler
	 * - Echos the error message to output as well as related info
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException  The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleVerbose(&$error, $options)
	{
		JLog::add('JError::handleVerbose() is deprecated.', JLog::WARNING, 'deprecated');

		$level_human = self::translateErrorLevel($error->get('level'));
		$info = $error->get('info');

		if (isset($_SERVER['HTTP_HOST']))
		{
			// Output as html
			echo "<br /><b>J$level_human</b>: " . $error->get('message') . "<br />\n";

			if ($info != null)
			{
				echo '&#160;&#160;&#160;' . $info . "<br />\n";
			}

			echo $error->getBacktrace(true);
		}
		else
		{
			// Output as simple text
			echo "J$level_human: " . $error->get('message') . "\n";

			if ($info != null)
			{
				echo "\t" . $info . "\n";
			}
		}

		return $error;
	}

	/**
	 * Die error handler
	 * - Echos the error message to output and then dies
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  void  Calls die()
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleDie(&$error, $options)
	{
		JLog::add('JError::handleDie() is deprecated.', JLog::WARNING, 'deprecated');

		$level_human = self::translateErrorLevel($error->get('level'));

		if (isset($_SERVER['HTTP_HOST']))
		{
			// Output as html
			jexit("<br /><b>J$level_human</b>: " . $error->get('message') . "<br />\n");
		}
		else
		{
			// Output as simple text
			if (defined('STDERR'))
			{
				fwrite(STDERR, "J$level_human: " . $error->get('message') . "\n");
				jexit();
			}
			else
			{
				jexit("J$level_human: " . $error->get('message') . "\n");
			}
		}

		return $error;
	}

	/**
	 * Message error handler
	 * Enqueues the error message into the system queue
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException  The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleMessage(&$error, $options)
	{
		JLog::add('JError::hanleMessage() is deprecated.', JLog::WARNING, 'deprecated');

		$appl = JFactory::getApplication();
		$type = ($error->get('level') == E_NOTICE) ? 'notice' : 'error';
		$appl->enqueueMessage($error->get('message'), $type);

		return $error;
	}

	/**
	 * Log error handler
	 * Logs the error message to a system log file
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException  The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleLog(&$error, $options)
	{
		JLog::add('JError::handleLog() is deprecated.', JLog::WARNING, 'deprecated');

		static $log;

		if ($log == null)
		{
			$options['text_file'] = date('Y-m-d') . '.error.log';
			$options['format'] = "{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}";
			JLog::addLogger($options, JLog::ALL, array('error'));
		}

		$entry = new JLogEntry(
			str_replace(array("\r", "\n"), array('', '\\n'), $error->get('message')),
			$error->get('level'),
			'error'
		);
		$entry->code = $error->get('code');
		JLog::add($entry);

		return $error;
	}

	/**
	 * Callback error handler
	 * - Send the error object to a callback method for error handling
	 *
	 * @param   JException  &$error   Exception object to handle
	 * @param   array       $options  Handler options
	 *
	 * @return  JException  The exception object
	 *
	 * @since   1.5
	 * @deprecated  4.0
	 * @see    JError::raise()
	 */
	public static function handleCallback(&$error, $options)
	{
		JLog::add('JError::handleCallback() is deprecated.', JLog::WARNING, 'deprecated');

		return call_user_func_array($options, array(&$error));
	}

	/**
	 * Display a custom error page and exit gracefully
	 *
	 * @param   JException  $error  Exception object
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0 Use \Joomla\CMS\Exception\ExceptionHandler::render() instead
	 */
	public static function customErrorPage($error)
	{
		JLog::add('JError::customErrorPage() is deprecated, use JErrorPage::render() instead.', JLog::WARNING, 'deprecated');

		\Joomla\CMS\Exception\ExceptionHandler::render($error);
	}

	/**
	 * Display a message to the user
	 *
	 * @param   integer  $level  The error level - use any of PHP's own error levels
	 *                   for this: E_ERROR, E_WARNING, E_NOTICE, E_USER_ERROR,
	 *                   E_USER_WARNING, E_USER_NOTICE.
	 * @param   string   $msg    Error message, shown to user if need be.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  4.0 Throw an Exception or enqueue the message to the application, eg. \Joomla\CMS\Factory::getApplication()->enqueueMessage($msg)
	 */
	public static function customErrorHandler($level, $msg)
	{
		JLog::add('JError::customErrorHandler() is deprecated.', JLog::WARNING, 'deprecated');

		self::raise($level, '', $msg);
	}

	/**
	 * Render the backtrace
	 *
	 * @param   Exception  $error  The error
	 *
	 * @return  string  Contents of the backtrace
	 *
	 * @since   1.6
	 * @deprecated  4.0 Use JLayoutHelper::render('joomla.error.backtrace', array('backtrace' => $error->getTrace())) instead
	 */
	public static function renderBacktrace($error)
	{
		JLog::add('JError::renderBacktrace() is deprecated.', JLog::WARNING, 'deprecated');

		return \Joomla\CMS\Layout\LayoutHelper::render('joomla.error.backtrace', array('backtrace' => $error->getTrace()));
	}
}
legacy/simplecrypt/simplecrypt.php000064400000003444152177723700013467 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Simplecrypt
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * JSimpleCrypt is a very simple encryption algorithm for encrypting/decrypting strings
 *
 * @since       1.5
 * @deprecated  2.5.5 Use JCrypt instead.
 */
class JSimplecrypt
{
	/**
	 * Encryption/Decryption Key
	 *
	 * @var         JCrypt
	 * @since       3.0
	 * @deprecated  3.0  Use JCrypt instead.
	 */
	private $_crypt;

	/**
	 * Object Constructor takes an optional key to be used for encryption/decryption. If no key is given then the
	 * secret word from the configuration object is used.
	 *
	 * @param   string  $privateKey  Optional encryption key
	 *
	 * @since       1.5
	 * @deprecated  2.5.5  Use JCrypt instead.
	 */
	public function __construct($privateKey = null)
	{
		JLog::add('JSimpleCrypt is deprecated. Use JCrypt instead.', JLog::WARNING, 'deprecated');

		if (empty($privateKey))
		{
			$privateKey = md5(JFactory::getConfig()->get('secret'));
		}

		// Build the JCryptKey object.
		$key = new JCryptKey('simple', $privateKey, $privateKey);

		// Setup the JCrypt object.
		$this->_crypt = new JCrypt(new JCryptCipherSimple, $key);
	}

	/**
	 * Decrypt a string
	 *
	 * @param   string  $s  String to decrypt
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @deprecated  2.5.5  Use JCrypt instead.
	 */
	public function decrypt($s)
	{
		return $this->_crypt->decrypt($s);
	}

	/**
	 * Encrypt a string
	 *
	 * @param   string  $s  String to encrypt
	 *
	 * @return  string
	 *
	 * @since   1.5
	 * @deprecated  2.5.5  Use JCrypt instead.
	 */
	public function encrypt($s)
	{
		return $this->_crypt->encrypt($s);
	}
}
legacy/application/application.php000064400000072721152177723700013353 0ustar00<?php
/**
 * @package     Joomla.Legacy
 * @subpackage  Application
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Application\BaseApplication;
use Joomla\Registry\Registry;

JLog::add('JApplication is deprecated.', JLog::WARNING, 'deprecated');

/**
 * Base class for a Joomla! application.
 *
 * Acts as a Factory class for application specific objects and provides many
 * supporting API functions. Derived clases should supply the route(), dispatch()
 * and render() functions.
 *
 * @since       1.5
 * @deprecated  3.2  Use CMSApplication instead unless specified otherwise
 */
class JApplication extends BaseApplication
{
	/**
	 * The client identifier.
	 *
	 * @var    integer
	 * @since  1.5
	 * @deprecated  3.2
	 */
	protected $_clientId = null;

	/**
	 * The application message queue.
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  3.2
	 */
	protected $_messageQueue = array();

	/**
	 * The name of the application.
	 *
	 * @var    array
	 * @since  1.5
	 * @deprecated  3.2
	 */
	protected $_name = null;

	/**
	 * The scope of the application.
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  3.2
	 */
	public $scope = null;

	/**
	 * The time the request was made.
	 *
	 * @var    string
	 * @since  1.5
	 * @deprecated  3.2
	 */
	public $requestTime = null;

	/**
	 * The time the request was made as Unix timestamp.
	 *
	 * @var    integer
	 * @since  1.6
	 * @deprecated  3.2
	 */
	public $startTime = null;

	/**
	 * The application client object.
	 *
	 * @var    JApplicationWebClient
	 * @since  3.0
	 * @deprecated  3.2
	 */
	public $client;

	/**
	 * JApplication instances container.
	 *
	 * @var    JApplication[]
	 * @since  2.5
	 * @deprecated  3.2
	 */
	protected static $instances = array();

	/**
	 * Class constructor.
	 *
	 * @param   array  $config  A configuration array including optional elements such as session
	 * session_name, clientId and others. This is not exhaustive.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function __construct($config = array())
	{
		// Set the view name.
		$this->_name = $this->getName();

		// Only set the clientId if available.
		if (isset($config['clientId']))
		{
			$this->_clientId = $config['clientId'];
		}

		// Enable sessions by default.
		if (!isset($config['session']))
		{
			$config['session'] = true;
		}

		// Create the input object
		$this->input = new JInput;

		$this->client = new JApplicationWebClient;

		$this->loadDispatcher();

		// Set the session default name.
		if (!isset($config['session_name']))
		{
			$config['session_name'] = $this->_name;
		}

		// Set the default configuration file.
		if (!isset($config['config_file']))
		{
			$config['config_file'] = 'configuration.php';
		}

		// Create the configuration object.
		if (file_exists(JPATH_CONFIGURATION . '/' . $config['config_file']))
		{
			$this->_createConfiguration(JPATH_CONFIGURATION . '/' . $config['config_file']);
		}

		// Create the session if a session name is passed.
		if ($config['session'] !== false)
		{
			$this->_createSession(JApplicationHelper::getHash($config['session_name']));
		}

		$this->requestTime = gmdate('Y-m-d H:i');

		// Used by task system to ensure that the system doesn't go over time.
		$this->startTime = JProfiler::getmicrotime();
	}

	/**
	 * Returns the global JApplicationCms object, only creating it if it
	 * doesn't already exist.
	 *
	 * @param   mixed   $client  A client identifier or name.
	 * @param   array   $config  An optional associative array of configuration settings.
	 * @param   string  $prefix  A prefix for class names
	 *
	 * @return  JApplicationCms  A JApplicationCms object.
	 *
	 * @since   1.5
	 * @deprecated  3.2  Use JApplicationCms::getInstance() instead
	 * @note    As of 3.2, this proxies to JApplicationCms::getInstance()
	 */
	public static function getInstance($client, $config = array(), $prefix = 'J')
	{
		return JApplicationCms::getInstance($client);
	}

	/**
	 * Initialise the application.
	 *
	 * @param   array  $options  An optional associative array of configuration settings.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function initialise($options = array())
	{
		// Set the language in the class.
		$config = JFactory::getConfig();

		// Check that we were given a language in the array (since by default may be blank).
		if (isset($options['language']))
		{
			$config->set('language', $options['language']);
		}

		// Set user specific editor.
		$user = JFactory::getUser();
		$editor = $user->getParam('editor', $this->get('editor'));

		if (!JPluginHelper::isEnabled('editors', $editor))
		{
			$editor = $this->get('editor');

			if (!JPluginHelper::isEnabled('editors', $editor))
			{
				$editor = 'none';
			}
		}

		$config->set('editor', $editor);

		// Trigger the onAfterInitialise event.
		JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterInitialise');
	}

	/**
	 * Route the application.
	 *
	 * Routing is the process of examining the request environment to determine which
	 * component should receive the request. The component optional parameters
	 * are then set in the request object to be processed when the application is being
	 * dispatched.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function route()
	{
		// Get the full request URI.
		$uri = clone JUri::getInstance();

		$router = $this->getRouter();
		$result = $router->parse($uri);

		foreach ($result as $key => $value)
		{
			$this->input->def($key, $value);
		}

		// Trigger the onAfterRoute event.
		JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterRoute');
	}

	/**
	 * Dispatch the application.
	 *
	 * Dispatching is the process of pulling the option from the request object and
	 * mapping them to a component. If the component does not exist, it handles
	 * determining a default component to dispatch.
	 *
	 * @param   string  $component  The component to dispatch.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function dispatch($component = null)
	{
		$document = JFactory::getDocument();

		$contents = JComponentHelper::renderComponent($component);
		$document->setBuffer($contents, 'component');

		// Trigger the onAfterDispatch event.
		JPluginHelper::importPlugin('system');
		$this->triggerEvent('onAfterDispatch');
	}

	/**
	 * Render the application.
	 *
	 * Rendering is the process of pushing the document buffers into the template
	 * placeholders, retrieving data from the document and pushing it into
	 * the JResponse buffer.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function render()
	{
		$template = $this->getTemplate(true);

		$params = array('template' => $template->template, 'file' => 'index.php', 'directory' => JPATH_THEMES, 'params' => $template->params);

		// Parse the document.
		$document = JFactory::getDocument();
		$document->parse($params);

		// Trigger the onBeforeRender event.
		JPluginHelper::importPlugin('system');
		$this->triggerEvent('onBeforeRender');

		// Render the document.
		$caching = ($this->get('caching') >= 2);
		JResponse::setBody($document->render($caching, $params));

		// Trigger the onAfterRender event.
		$this->triggerEvent('onAfterRender');
	}

	/**
	 * Redirect to another URL.
	 *
	 * Optionally enqueues a message in the system message queue (which will be displayed
	 * the next time a page is loaded) using the enqueueMessage method. If the headers have
	 * not been sent the redirect will be accomplished using a "301 Moved Permanently"
	 * code in the header pointing to the new location. If the headers have already been
	 * sent this will be accomplished using a JavaScript statement.
	 *
	 * @param   string   $url      The URL to redirect to. Can only be http/https URL
	 * @param   string   $msg      An optional message to display on redirect.
	 * @param   string   $msgType  An optional message type. Defaults to message.
	 * @param   boolean  $moved    True if the page is 301 Permanently Moved, otherwise 303 See Other is assumed.
	 *
	 * @return  void  Calls exit().
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 *
	 * @see     JApplication::enqueueMessage()
	 */
	public function redirect($url, $msg = '', $msgType = 'message', $moved = false)
	{
		// Check for relative internal links.
		if (preg_match('#^index2?\.php#', $url))
		{
			$url = JUri::base() . $url;
		}

		// Strip out any line breaks.
		$url = preg_split("/[\r\n]/", $url);
		$url = $url[0];

		/*
		 * If we don't start with a http we need to fix this before we proceed.
		 * We could validly start with something else (e.g. ftp), though this would
		 * be unlikely and isn't supported by this API.
		 */
		if (stripos($url, 'http') !== 0)
		{
			$uri = JUri::getInstance();
			$prefix = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port'));

			if ($url[0] === '/')
			{
				// We just need the prefix since we have a path relative to the root.
				$url = $prefix . $url;
			}
			else
			{
				// It's relative to where we are now, so lets add that.
				$parts = explode('/', $uri->toString(array('path')));
				array_pop($parts);
				$path = implode('/', $parts) . '/';
				$url = $prefix . $path . $url;
			}
		}

		// If the message exists, enqueue it.
		if (trim($msg))
		{
			$this->enqueueMessage($msg, $msgType);
		}

		// Persist messages if they exist.
		if (count($this->_messageQueue))
		{
			$session = JFactory::getSession();
			$session->set('application.queue', $this->_messageQueue);
		}

		// If the headers have been sent, then we cannot send an additional location header
		// so we will output a javascript redirect statement.
		if (headers_sent())
		{
			echo "<script>document.location.href=" . json_encode(str_replace("'", '&apos;', $url)) . ";</script>\n";
		}
		else
		{
			$document = JFactory::getDocument();

			jimport('phputf8.utils.ascii');

			if (($this->client->engine == JApplicationWebClient::TRIDENT) && !utf8_is_ascii($url))
			{
				// MSIE type browser and/or server cause issues when URL contains utf8 character,so use a javascript redirect method
				echo '<html><head><meta http-equiv="content-type" content="text/html; charset=' . $document->getCharset() . '" />'
					. '<script>document.location.href=' . json_encode(str_replace("'", '&apos;', $url)) . ';</script></head></html>';
			}
			else
			{
				// All other browsers, use the more efficient HTTP header method
				header($moved ? 'HTTP/1.1 301 Moved Permanently' : 'HTTP/1.1 303 See other');
				header('Location: ' . $url);
				header('Content-Type: text/html; charset=' . $document->getCharset());
			}
		}

		$this->close();
	}

	/**
	 * Enqueue a system message.
	 *
	 * @param   string  $msg   The message to enqueue.
	 * @param   string  $type  The message type. Default is message.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function enqueueMessage($msg, $type = 'message')
	{
		// For empty queue, if messages exists in the session, enqueue them first.
		if (!count($this->_messageQueue))
		{
			$session = JFactory::getSession();
			$sessionQueue = $session->get('application.queue');

			if (count($sessionQueue))
			{
				$this->_messageQueue = $sessionQueue;
				$session->set('application.queue', null);
			}
		}

		// Enqueue the message.
		$this->_messageQueue[] = array('message' => $msg, 'type' => strtolower($type));
	}

	/**
	 * Get the system message queue.
	 *
	 * @return  array  The system message queue.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getMessageQueue()
	{
		// For empty queue, if messages exists in the session, enqueue them.
		if (!count($this->_messageQueue))
		{
			$session = JFactory::getSession();
			$sessionQueue = $session->get('application.queue');

			if (count($sessionQueue))
			{
				$this->_messageQueue = $sessionQueue;
				$session->set('application.queue', null);
			}
		}

		return $this->_messageQueue;
	}

	/**
	 * Gets a configuration value.
	 *
	 * An example is in application/japplication-getcfg.php Getting a configuration
	 *
	 * @param   string  $varname  The name of the value to get.
	 * @param   string  $default  Default value to return
	 *
	 * @return  mixed  The user state.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getCfg($varname, $default = null)
	{
		$config = JFactory::getConfig();

		return $config->get('' . $varname, $default);
	}

	/**
	 * Method to get the application name.
	 *
	 * The dispatcher name is by default parsed using the classname, or it can be set
	 * by passing a $config['name'] in the class constructor.
	 *
	 * @return  string  The name of the dispatcher.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getName()
	{
		$name = $this->_name;

		if (empty($name))
		{
			$r = null;

			if (!preg_match('/J(.*)/i', get_class($this), $r))
			{
				JLog::add(JText::_('JLIB_APPLICATION_ERROR_APPLICATION_GET_NAME'), JLog::WARNING, 'jerror');
			}

			$name = strtolower($r[1]);
		}

		return $name;
	}

	/**
	 * Gets a user state.
	 *
	 * @param   string  $key      The path of the state.
	 * @param   mixed   $default  Optional default value, returned if the internal value is null.
	 *
	 * @return  mixed  The user state or null.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getUserState($key, $default = null)
	{
		$session = JFactory::getSession();
		$registry = $session->get('registry');

		if ($registry !== null)
		{
			return $registry->get($key, $default);
		}

		return $default;
	}

	/**
	 * Sets the value of a user state variable.
	 *
	 * @param   string  $key    The path of the state.
	 * @param   string  $value  The value of the variable.
	 *
	 * @return  mixed  The previous state, if one existed.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function setUserState($key, $value)
	{
		$session = JFactory::getSession();
		$registry = $session->get('registry');

		if ($registry !== null)
		{
			return $registry->set($key, $value);
		}
	}

	/**
	 * Gets the value of a user state variable.
	 *
	 * @param   string  $key      The key of the user state variable.
	 * @param   string  $request  The name of the variable passed in a request.
	 * @param   string  $default  The default value for the variable if not found. Optional.
	 * @param   string  $type     Filter for the variable, for valid values see {@link JFilterInput::clean()}. Optional.
	 *
	 * @return  mixed  The request user state.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getUserStateFromRequest($key, $request, $default = null, $type = 'none')
	{
		$cur_state = $this->getUserState($key, $default);
		$new_state = $this->input->get($request, null, $type);

		// Save the new value only if it was set in this request.
		if ($new_state !== null)
		{
			$this->setUserState($key, $new_state);
		}
		else
		{
			$new_state = $cur_state;
		}

		return $new_state;
	}

	/**
	 * Login authentication function.
	 *
	 * Username and encoded password are passed the onUserLogin event which
	 * is responsible for the user validation. A successful validation updates
	 * the current session record with the user's details.
	 *
	 * Username and encoded password are sent as credentials (along with other
	 * possibilities) to each observer (authentication plugin) for user
	 * validation.  Successful validation will update the current session with
	 * the user details.
	 *
	 * @param   array  $credentials  Array('username' => string, 'password' => string)
	 * @param   array  $options      Array('remember' => boolean)
	 *
	 * @return  boolean|JException  True on success, false if failed or silent handling is configured, or a JException object on authentication error.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function login($credentials, $options = array())
	{
		JPluginHelper::importPlugin('user');

		// Get the global JAuthentication object.
		$authenticate = JAuthentication::getInstance();
		$response = $authenticate->authenticate($credentials, $options);

		if ($response->status === JAuthentication::STATUS_SUCCESS)
		{
			// Validate that the user should be able to login (different to being authenticated).
			// This permits authentication plugins blocking the user
			$authorisations = $authenticate->authorise($response, $options);

			foreach ($authorisations as $authorisation)
			{
				$denied_states = array(JAuthentication::STATUS_EXPIRED, JAuthentication::STATUS_DENIED);

				if (in_array($authorisation->status, $denied_states))
				{
					// Trigger onUserAuthorisationFailure Event.
					$this->triggerEvent('onUserAuthorisationFailure', array((array) $authorisation));

					// If silent is set, just return false.
					if (isset($options['silent']) && $options['silent'])
					{
						return false;
					}

					// Return the error.
					switch ($authorisation->status)
					{
						case JAuthentication::STATUS_EXPIRED:
							return JError::raiseWarning('102002', JText::_('JLIB_LOGIN_EXPIRED'));
							break;

						case JAuthentication::STATUS_DENIED:
							return JError::raiseWarning('102003', JText::_('JLIB_LOGIN_DENIED'));
							break;

						default:
							return JError::raiseWarning('102004', JText::_('JLIB_LOGIN_AUTHORISATION'));
							break;
					}
				}
			}

			// Import the user plugin group.
			JPluginHelper::importPlugin('user');

			// OK, the credentials are authenticated and user is authorised.  Let's fire the onLogin event.
			$results = $this->triggerEvent('onUserLogin', array((array) $response, $options));

			/*
			 * If any of the user plugins did not successfully complete the login routine
			 * then the whole method fails.
			 *
			 * Any errors raised should be done in the plugin as this provides the ability
			 * to provide much more information about why the routine may have failed.
			 */
			$user = JFactory::getUser();

			if ($response->type === 'Cookie')
			{
				$user->set('cookieLogin', true);
			}

			if (in_array(false, $results, true) == false)
			{
				$options['user'] = $user;
				$options['responseType'] = $response->type;

				if (isset($response->length, $response->secure, $response->lifetime))
				{
					$options['length'] = $response->length;
					$options['secure'] = $response->secure;
					$options['lifetime'] = $response->lifetime;
				}

				// The user is successfully logged in. Run the after login events
				$this->triggerEvent('onUserAfterLogin', array($options));
			}

			return true;
		}

		// Trigger onUserLoginFailure Event.
		$this->triggerEvent('onUserLoginFailure', array((array) $response));

		// If silent is set, just return false.
		if (isset($options['silent']) && $options['silent'])
		{
			return false;
		}

		// If status is success, any error will have been raised by the user plugin
		if ($response->status !== JAuthentication::STATUS_SUCCESS)
		{
			JLog::add($response->error_message, JLog::WARNING, 'jerror');
		}

		return false;
	}

	/**
	 * Logout authentication function.
	 *
	 * Passed the current user information to the onUserLogout event and reverts the current
	 * session record back to 'anonymous' parameters.
	 * If any of the authentication plugins did not successfully complete
	 * the logout routine then the whole method fails. Any errors raised
	 * should be done in the plugin as this provides the ability to give
	 * much more information about why the routine may have failed.
	 *
	 * @param   integer  $userid   The user to load - Can be an integer or string - If string, it is converted to ID automatically
	 * @param   array    $options  Array('clientid' => array of client id's)
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function logout($userid = null, $options = array())
	{
		// Get a user object from the JApplication.
		$user = JFactory::getUser($userid);

		// Build the credentials array.
		$parameters['username'] = $user->get('username');
		$parameters['id'] = $user->get('id');

		// Set clientid in the options array if it hasn't been set already.
		if (!isset($options['clientid']))
		{
			$options['clientid'] = $this->getClientId();
		}

		// Import the user plugin group.
		JPluginHelper::importPlugin('user');

		// OK, the credentials are built. Lets fire the onLogout event.
		$results = $this->triggerEvent('onUserLogout', array($parameters, $options));

		if (!in_array(false, $results, true))
		{
				$options['username'] = $user->get('username');
				$this->triggerEvent('onUserAfterLogout', array($options));

			return true;
		}

		// Trigger onUserLoginFailure Event.
		$this->triggerEvent('onUserLogoutFailure', array($parameters));

		return false;
	}

	/**
	 * Gets the name of the current template.
	 *
	 * @param   boolean  $params  An optional associative array of configuration settings
	 *
	 * @return  mixed  System is the fallback.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getTemplate($params = false)
	{
		$template = new stdClass;

		$template->template = 'system';
		$template->params   = new Registry;

		if ($params)
		{
			return $template;
		}

		return $template->template;
	}

	/**
	 * Returns the application JRouter object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  JRouter|null  A JRouter object
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public static function getRouter($name = null, array $options = array())
	{
		if (!isset($name))
		{
			$app = JFactory::getApplication();
			$name = $app->getName();
		}

		try
		{
			$router = JRouter::getInstance($name, $options);
		}
		catch (Exception $e)
		{
			return;
		}

		return $router;
	}

	/**
	 * This method transliterates a string into a URL
	 * safe string or returns a URL safe UTF-8 string
	 * based on the global configuration
	 *
	 * @param   string  $string  String to process
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.6
	 * @deprecated  3.2  Use JApplicationHelper::stringURLSafe instead
	 */
	public static function stringURLSafe($string)
	{
		return JApplicationHelper::stringURLSafe($string);
	}

	/**
	 * Returns the application JPathway object.
	 *
	 * @param   string  $name     The name of the application.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  JPathway|null  A JPathway object
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getPathway($name = null, $options = array())
	{
		if (!isset($name))
		{
			$name = $this->_name;
		}

		try
		{
			$pathway = JPathway::getInstance($name, $options);
		}
		catch (Exception $e)
		{
			return;
		}

		return $pathway;
	}

	/**
	 * Returns the application JPathway object.
	 *
	 * @param   string  $name     The name of the application/client.
	 * @param   array   $options  An optional associative array of configuration settings.
	 *
	 * @return  JMenu|null  JMenu object.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getMenu($name = null, $options = array())
	{
		if (!isset($name))
		{
			$name = $this->_name;
		}

		try
		{
			$menu = JMenu::getInstance($name, $options);
		}
		catch (Exception $e)
		{
			return;
		}

		return $menu;
	}

	/**
	 * Provides a secure hash based on a seed
	 *
	 * @param   string  $seed  Seed string.
	 *
	 * @return  string  A secure hash
	 *
	 * @since   1.6
	 * @deprecated  3.2  Use JApplicationHelper::getHash instead
	 */
	public static function getHash($seed)
	{
		return JApplicationHelper::getHash($seed);
	}

	/**
	 * Create the configuration registry.
	 *
	 * @param   string  $file  The path to the configuration file
	 *
	 * @return  JConfig  A JConfig object
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	protected function _createConfiguration($file)
	{
		JLoader::register('JConfig', $file);

		// Create the JConfig object.
		$config = new JConfig;

		// Get the global configuration object.
		$registry = JFactory::getConfig();

		// Load the configuration values into the registry.
		$registry->loadObject($config);

		return $config;
	}

	/**
	 * Create the user session.
	 *
	 * Old sessions are flushed based on the configuration value for the cookie
	 * lifetime. If an existing session, then the last access time is updated.
	 * If a new session, a session id is generated and a record is created in
	 * the #__sessions table.
	 *
	 * @param   string  $name  The sessions name.
	 *
	 * @return  JSession  JSession on success. May call exit() on database error.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	protected function _createSession($name)
	{
		$options = array();
		$options['name'] = $name;

		switch ($this->_clientId)
		{
			case 0:
				if ($this->get('force_ssl') == 2)
				{
					$options['force_ssl'] = true;
				}
				break;

			case 1:
				if ($this->get('force_ssl') >= 1)
				{
					$options['force_ssl'] = true;
				}
				break;
		}

		$this->registerEvent('onAfterSessionStart', array($this, 'afterSessionStart'));

		$session = JFactory::getSession($options);
		$session->initialise($this->input, $this->dispatcher);
		$session->start();

		// TODO: At some point we need to get away from having session data always in the db.

		$db = JFactory::getDbo();

		// Remove expired sessions from the database.
		$time = time();

		if ($time % 2)
		{
			// The modulus introduces a little entropy, making the flushing less accurate
			// but fires the query less than half the time.
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__session'))
				->where($db->quoteName('time') . ' < ' . (int) ($time - $session->getExpire()));

			$db->setQuery($query);
			$db->execute();
		}

		// Check to see the the session already exists.
		$handler = $this->get('session_handler');

		if (($handler !== 'database' && ($time % 2 || $session->isNew())) || ($handler === 'database' && $session->isNew()))
		{
			$this->checkSession();
		}

		return $session;
	}

	/**
	 * Checks the user session.
	 *
	 * If the session record doesn't exist, initialise it.
	 * If session is new, create session variables
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated  3.2
	 */
	public function checkSession()
	{
		$db = JFactory::getDbo();
		$session = JFactory::getSession();
		$user = JFactory::getUser();

		$query = $db->getQuery(true)
			->select($db->quoteName('session_id'))
			->from($db->quoteName('#__session'))
			->where($db->quoteName('session_id') . ' = ' . $db->quote($session->getId()));

		$db->setQuery($query, 0, 1);
		$exists = $db->loadResult();

		// If the session record doesn't exist initialise it.
		if (!$exists)
		{
			$query->clear();

			if ($session->isNew())
			{
				$query->insert($db->quoteName('#__session'))
					->columns($db->quoteName('session_id') . ', ' . $db->quoteName('client_id') . ', ' . $db->quoteName('time'))
					->values($db->quote($session->getId()) . ', ' . (int) $this->getClientId() . ', ' . time());
				$db->setQuery($query);
			}
			else
			{
				$query->insert($db->quoteName('#__session'))
					->columns(
						$db->quoteName('session_id') . ', ' . $db->quoteName('client_id') . ', ' . $db->quoteName('guest') . ', ' .
						$db->quoteName('time') . ', ' . $db->quoteName('userid') . ', ' . $db->quoteName('username')
					)
					->values(
						$db->quote($session->getId()) . ', ' . (int) $this->getClientId() . ', ' . (int) $user->get('guest') . ', ' .
						(int) $session->get('session.timer.start') . ', ' . (int) $user->get('id') . ', ' . $db->quote($user->get('username'))
					);

				$db->setQuery($query);
			}

			// If the insert failed, exit the application.
			try
			{
				$db->execute();
			}
			catch (RuntimeException $e)
			{
				jexit($e->getMessage());
			}
		}
	}

	/**
	 * After the session has been started we need to populate it with some default values.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 * @deprecated  3.2
	 */
	public function afterSessionStart()
	{
		$session = JFactory::getSession();

		if ($session->isNew())
		{
			$session->set('registry', new Registry);
			$session->set('user', new JUser);
		}
	}

	/**
	 * Gets the client id of the current running application.
	 *
	 * @return  integer  A client identifier.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function getClientId()
	{
		return $this->_clientId;
	}

	/**
	 * Is admin interface?
	 *
	 * @return  boolean  True if this application is administrator.
	 *
	 * @since   1.0.2
	 * @deprecated  3.2
	 */
	public function isAdmin()
	{
		return $this->isClient('administrator');
	}

	/**
	 * Is site interface?
	 *
	 * @return  boolean  True if this application is site.
	 *
	 * @since   1.5
	 * @deprecated  3.2
	 */
	public function isSite()
	{
		return $this->isClient('site');
	}

	/**
	 * Check the client interface by name.
	 *
	 * @param   string  $identifier  String identifier for the application interface
	 *
	 * @return  boolean  True if this application is of the given type client interface.
	 *
	 * @since   3.7.0
	 */
	public function isClient($identifier)
	{
		return $this->getName() == $identifier;
	}

	/**
	 * Method to determine if the host OS is  Windows
	 *
	 * @return  boolean  True if Windows OS
	 *
	 * @since   1.6
	 * @deprecated  4.0 Use the IS_WIN constant instead.
	 */
	public static function isWinOs()
	{
		JLog::add('JApplication::isWinOS() is deprecated. Use the IS_WIN constant instead.', JLog::WARNING, 'deprecated');

		return IS_WIN;
	}

	/**
	 * Determine if we are using a secure (SSL) connection.
	 *
	 * @return  boolean  True if using SSL, false if not.
	 *
	 * @since   3.0
	 * @deprecated  3.2
	 */
	public function isSSLConnection()
	{
		return (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || getenv('SSL_PROTOCOL_VERSION');
	}

	/**
	 * Returns the response as a string.
	 *
	 * @return  string  The response
	 *
	 * @since   1.6
	 * @deprecated  3.2
	 */
	public function __toString()
	{
		$compress = $this->get('gzip', false);

		return JResponse::toString($compress);
	}
}
cms/less/less.php000064400000002625152177723700007773 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  LESS
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for lessc
 *
 * @package     Joomla.Libraries
 * @subpackage  Less
 * @since       3.4
 * @deprecated  4.0  without replacement
 */
class JLess extends lessc
{
	/**
	 * Constructor
	 *
	 * @param   string                 $fname      Filename to process
	 * @param   \JLessFormatterJoomla  $formatter  Formatter object
	 *
	 * @since   3.4
	 */
	public function __construct($fname = null, $formatter = null)
	{
		parent::__construct($fname);

		if ($formatter === null)
		{
			$formatter = new JLessFormatterJoomla;
		}

		$this->setFormatter($formatter);
	}

	/**
	 * Override compile to reset $this->allParsedFiles array to allow
	 * parsing multiple files/strings using same imports.
	 * PR: https://github.com/leafo/lessphp/pull/607
	 *
	 * For documentation on this please see /vendor/leafo/lessc.inc.php
	 *
	 * @param   string  $string  LESS string to parse.
	 * @param   string  $name    The sourceName used for error messages.
	 *
	 * @return  string  $out     The compiled css output.
	 */
	public function compile($string, $name = null)
	{
		$this->allParsedFiles = array();

		return parent::compile($string, $name);
	}
}
cms/less/formatter/joomla.php000064400000001263152177723700012306 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  Less
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Formatter ruleset for Joomla formatted CSS generated via LESS
 *
 * @package     Joomla.Libraries
 * @subpackage  Less
 * @since       3.4
 * @deprecated  4.0  without replacement
 */
class JLessFormatterJoomla extends lessc_formatter_classic
{
	public $disableSingle = true;

	public $breakSelectors = true;

	public $assignSeparator = ': ';

	public $selectorSeparator = ',';

	public $indentChar = "\t";
}
cms/html/sortablelist.php000064400000005741152177723700011534 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * HTML utility class for creating a sortable table list
 *
 * @since  3.0
 */
abstract class JHtmlSortablelist
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.0
	 */
	protected static $loaded = array();

	/**
	 * Method to load the Sortable script and make table sortable
	 *
	 * @param   string   $tableId                 DOM id of the table
	 * @param   string   $formId                  DOM id of the form
	 * @param   string   $sortDir                 Sort direction
	 * @param   string   $saveOrderingUrl         Save ordering url, ajax-load after an item dropped
	 * @param   boolean  $proceedSaveOrderButton  Set whether a save order button is displayed
	 * @param   boolean  $nestedList              Set whether the list is a nested list
	 *
	 * @return  void
	 *
	 * @since   3.0
	 *
	 * @throws  InvalidArgumentException
	 */
	public static function sortable($tableId, $formId, $sortDir = 'asc', $saveOrderingUrl = null, $proceedSaveOrderButton = true, $nestedList = false)
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Note: $i is required but has to be an optional argument in the function call due to argument order
		if ($saveOrderingUrl === null)
		{
			throw new InvalidArgumentException(sprintf('$saveOrderingUrl is a required argument in %s()', __METHOD__));
		}

		$displayData = array(
			'tableId'                => $tableId,
			'formId'                 => $formId,
			'sortDir'                => $sortDir,
			'saveOrderingUrl'        => $saveOrderingUrl,
			'nestedList'             => $nestedList,
			'proceedSaveOrderButton' => $proceedSaveOrderButton,
		);

		JLayoutHelper::render('joomla.html.sortablelist', $displayData);

		// Set static array
		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Method to inject script for enabled and disable Save order button
	 * when changing value of ordering input boxes
	 *
	 * @return  void
	 *
	 * @since   3.0
	 *
	 * @deprecated 4.0 The logic is merged in the JLayout file
	 */
	public static function _proceedSaveOrderButton()
	{
		JFactory::getDocument()->addScriptDeclaration(
			"(function ($){
				$(document).ready(function (){
					var saveOrderButton = $('.saveorder');
					saveOrderButton.css({'opacity':'0.2', 'cursor':'default'}).attr('onclick','return false;');
					var oldOrderingValue = '';
					$('.text-area-order').focus(function ()
					{
						oldOrderingValue = $(this).attr('value');
					})
					.keyup(function (){
						var newOrderingValue = $(this).attr('value');
						if (oldOrderingValue != newOrderingValue)
						{
							saveOrderButton.css({'opacity':'1', 'cursor':'pointer'}).removeAttr('onclick')
						}
					});
				});
			})(jQuery);"
		);

		return;
	}
}
cms/html/content.php000064400000003675152177723700010503 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class to fire onContentPrepare for non-article based content.
 *
 * @since  1.5
 */
abstract class JHtmlContent
{
	/**
	 * Fire onContentPrepare for content that isn't part of an article.
	 *
	 * @param   string  $text     The content to be transformed.
	 * @param   array   $params   The content params.
	 * @param   string  $context  The context of the content to be transformed.
	 *
	 * @return  string   The content after transformation.
	 *
	 * @since   1.5
	 */
	public static function prepare($text, $params = null, $context = 'text')
	{
		if ($params === null)
		{
			$params = new JObject;
		}

		$article = new stdClass;
		$article->text = $text;
		JPluginHelper::importPlugin('content');
		$dispatcher = JEventDispatcher::getInstance();
		$dispatcher->trigger('onContentPrepare', array($context, &$article, &$params, 0));

		return $article->text;
	}

	/**
	 * Returns an array of months.
	 *
	 * @param   Registry  $state  The state object.
	 *
	 * @return  array
	 *
	 * @since   3.9.0
	 */
	public static function months($state)
	{
		$model = JModelLegacy::getInstance('Articles', 'ContentModel', array('ignore_request' => true));

		foreach ($state as $key => $value) 
		{
			$model->setState($key, $value);
		}

		$model->setState('filter.category_id', $state->get('category.id'));
		$model->setState('list.start', 0);
		$model->setState('list.limit', -1);
		$model->setState('list.direction', 'asc');
		$model->setState('list.filter', '');

		$items = array();

		foreach ($model->countItemsByMonth() as $item)
		{
			$date    = new JDate($item->d);
			$items[] = JHtml::_('select.option', $item->d, $date->format('F Y') . ' [' . $item->c . ']');
		}

		return $items;
	}
}
cms/html/user.php000064400000003221152177723700007772 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class working with users
 *
 * @since  2.5
 */
abstract class JHtmlUser
{
	/**
	 * Displays a list of user groups.
	 *
	 * @param   boolean  $includeSuperAdmin  true to include super admin groups, false to exclude them
	 *
	 * @return  array  An array containing a list of user groups.
	 *
	 * @since   2.5
	 */
	public static function groups($includeSuperAdmin = false)
	{
		$options = array_values(JHelperUsergroups::getInstance()->getAll());

		for ($i = 0, $n = count($options); $i < $n; $i++)
		{
			$options[$i]->value = $options[$i]->id;
			$options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
			$groups[] = JHtml::_('select.option', $options[$i]->value, $options[$i]->text);
		}

		// Exclude super admin groups if requested
		if (!$includeSuperAdmin)
		{
			$filteredGroups = array();

			foreach ($groups as $group)
			{
				if (!JAccess::checkGroup($group->value, 'core.admin'))
				{
					$filteredGroups[] = $group;
				}
			}

			$groups = $filteredGroups;
		}

		return $groups;
	}

	/**
	 * Get a list of users.
	 *
	 * @return  string
	 *
	 * @since   2.5
	 */
	public static function userlist()
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('a.id AS value, a.name AS text')
			->from('#__users AS a')
			->where('a.block = 0')
			->order('a.name');
		$db->setQuery($query);

		return $db->loadObjectList();
	}
}
cms/html/jgrid.php000064400000040407152177723700010122 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Utility class for creating HTML Grids
 *
 * @since  1.6
 */
abstract class JHtmlJGrid
{
	/**
	 * Returns an action on a grid
	 *
	 * @param   integer       $i               The row index
	 * @param   string        $task            The task to fire
	 * @param   string|array  $prefix          An optional task prefix or an array of options
	 * @param   string        $text            An optional text to display [unused - @deprecated 4.0]
	 * @param   string        $active_title    An optional active tooltip to display if $enable is true
	 * @param   string        $inactive_title  An optional inactive tooltip to display if $enable is true
	 * @param   boolean       $tip             An optional setting for tooltip
	 * @param   string        $active_class    An optional active HTML class
	 * @param   string        $inactive_class  An optional inactive HTML class
	 * @param   boolean       $enabled         An optional setting for access control on the action.
	 * @param   boolean       $translate       An optional setting for translation.
	 * @param   string        $checkbox	       An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function action($i, $task, $prefix = '', $text = '', $active_title = '', $inactive_title = '', $tip = false, $active_class = '',
		$inactive_class = '', $enabled = true, $translate = true, $checkbox = 'cb')
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$active_title = array_key_exists('active_title', $options) ? $options['active_title'] : $active_title;
			$inactive_title = array_key_exists('inactive_title', $options) ? $options['inactive_title'] : $inactive_title;
			$tip = array_key_exists('tip', $options) ? $options['tip'] : $tip;
			$active_class = array_key_exists('active_class', $options) ? $options['active_class'] : $active_class;
			$inactive_class = array_key_exists('inactive_class', $options) ? $options['inactive_class'] : $inactive_class;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$translate = array_key_exists('translate', $options) ? $options['translate'] : $translate;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		if ($tip)
		{
			JHtml::_('bootstrap.tooltip');

			$title = $enabled ? $active_title : $inactive_title;
			$title = $translate ? JText::_($title) : $title;
			$title = JHtml::_('tooltipText', $title, '', 0);
		}

		if ($enabled)
		{
			$html[] = '<a class="btn btn-micro' . ($active_class === 'publish' ? ' active' : '') . ($tip ? ' hasTooltip' : '') . '"';
			$html[] = ' href="javascript:void(0);" onclick="return listItemTask(\'' . $checkbox . $i . '\',\'' . $prefix . $task . '\')"';
			$html[] = $tip ? ' title="' . $title . '"' : '';
			$html[] = '>';
			$html[] = '<span class="icon-' . $active_class . '" aria-hidden="true"></span>';
			$html[] = '</a>';
		}
		else
		{
			$html[] = '<a class="btn btn-micro disabled jgrid' . ($tip ? ' hasTooltip' : '') . '"';
			$html[] = $tip ? ' title="' . $title . '"' : '';
			$html[] = '>';

			if ($active_class === 'protected')
			{
				$html[] = '<span class="icon-lock"></span>';
			}
			else
			{
				$html[] = '<span class="icon-' . $inactive_class . '"></span>';
			}

			$html[] = '</a>';
		}

		return implode($html);
	}

	/**
	 * Returns a state on a grid
	 *
	 * @param   array         $states     array of value/state. Each state is an array of the form
	 *                                    (task, text, active title, inactive title, tip (boolean), HTML active class, HTML inactive class)
	 *                                    or ('task'=>task, 'text'=>text, 'active_title'=>active title,
	 *                                    'inactive_title'=>inactive title, 'tip'=>boolean, 'active_class'=>html active class,
	 *                                    'inactive_class'=>html inactive class)
	 * @param   integer       $value      The state value.
	 * @param   integer       $i          The row index
	 * @param   string|array  $prefix     An optional task prefix or an array of options
	 * @param   boolean       $enabled    An optional setting for access control on the action.
	 * @param   boolean       $translate  An optional setting for translation.
	 * @param   string        $checkbox   An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function state($states, $value, $i, $prefix = '', $enabled = true, $translate = true, $checkbox = 'cb')
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$translate = array_key_exists('translate', $options) ? $options['translate'] : $translate;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		$state = ArrayHelper::getValue($states, (int) $value, $states[0]);
		$task = array_key_exists('task', $state) ? $state['task'] : $state[0];
		$text = array_key_exists('text', $state) ? $state['text'] : (array_key_exists(1, $state) ? $state[1] : '');
		$active_title = array_key_exists('active_title', $state) ? $state['active_title'] : (array_key_exists(2, $state) ? $state[2] : '');
		$inactive_title = array_key_exists('inactive_title', $state) ? $state['inactive_title'] : (array_key_exists(3, $state) ? $state[3] : '');
		$tip = array_key_exists('tip', $state) ? $state['tip'] : (array_key_exists(4, $state) ? $state[4] : false);
		$active_class = array_key_exists('active_class', $state) ? $state['active_class'] : (array_key_exists(5, $state) ? $state[5] : '');
		$inactive_class = array_key_exists('inactive_class', $state) ? $state['inactive_class'] : (array_key_exists(6, $state) ? $state[6] : '');

		return static::action(
			$i, $task, $prefix, $text, $active_title, $inactive_title, $tip,
			$active_class, $inactive_class, $enabled, $translate, $checkbox
		);
	}

	/**
	 * Returns a published state on a grid
	 *
	 * @param   integer       $value         The state value.
	 * @param   integer       $i             The row index
	 * @param   string|array  $prefix        An optional task prefix or an array of options
	 * @param   boolean       $enabled       An optional setting for access control on the action.
	 * @param   string        $checkbox      An optional prefix for checkboxes.
	 * @param   string        $publish_up    An optional start publishing date.
	 * @param   string        $publish_down  An optional finish publishing date.
	 *
	 * @return  string  The HTML markup
	 *
	 * @see     JHtmlJGrid::state()
	 * @since   1.6
	 */
	public static function published($value, $i, $prefix = '', $enabled = true, $checkbox = 'cb', $publish_up = null, $publish_down = null)
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		$states = array(
			1 => array('unpublish', 'JPUBLISHED', 'JLIB_HTML_UNPUBLISH_ITEM', 'JPUBLISHED', true, 'publish', 'publish'),
			0 => array('publish', 'JUNPUBLISHED', 'JLIB_HTML_PUBLISH_ITEM', 'JUNPUBLISHED', true, 'unpublish', 'unpublish'),
			2 => array('unpublish', 'JARCHIVED', 'JLIB_HTML_UNPUBLISH_ITEM', 'JARCHIVED', true, 'archive', 'archive'),
			-2 => array('publish', 'JTRASHED', 'JLIB_HTML_PUBLISH_ITEM', 'JTRASHED', true, 'trash', 'trash'),
		);

		// Special state for dates
		if ($publish_up || $publish_down)
		{
			$nullDate = JFactory::getDbo()->getNullDate();
			$nowDate = JFactory::getDate()->toUnix();

			$tz = JFactory::getUser()->getTimezone();

			$publish_up = ($publish_up != $nullDate) ? JFactory::getDate($publish_up, 'UTC')->setTimeZone($tz) : false;
			$publish_down = ($publish_down != $nullDate) ? JFactory::getDate($publish_down, 'UTC')->setTimeZone($tz) : false;

			// Create tip text, only we have publish up or down settings
			$tips = array();

			if ($publish_up)
			{
				$tips[] = JText::sprintf('JLIB_HTML_PUBLISHED_START', JHtml::_('date', $publish_up, JText::_('DATE_FORMAT_LC5'), 'UTC'));
			}

			if ($publish_down)
			{
				$tips[] = JText::sprintf('JLIB_HTML_PUBLISHED_FINISHED', JHtml::_('date', $publish_down, JText::_('DATE_FORMAT_LC5'), 'UTC'));
			}

			$tip = empty($tips) ? false : implode('<br />', $tips);

			// Add tips and special titles
			foreach ($states as $key => $state)
			{
				// Create special titles for published items
				if ($key == 1)
				{
					$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_ITEM';

					if ($publish_up > $nullDate && $nowDate < $publish_up->toUnix())
					{
						$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_PENDING_ITEM';
						$states[$key][5] = $states[$key][6] = 'pending';
					}

					if ($publish_down > $nullDate && $nowDate > $publish_down->toUnix())
					{
						$states[$key][2] = $states[$key][3] = 'JLIB_HTML_PUBLISHED_EXPIRED_ITEM';
						$states[$key][5] = $states[$key][6] = 'expired';
					}
				}

				// Add tips to titles
				if ($tip)
				{
					$states[$key][1] = JText::_($states[$key][1]);
					$states[$key][2] = JText::_($states[$key][2]) . '<br />' . $tip;
					$states[$key][3] = JText::_($states[$key][3]) . '<br />' . $tip;
					$states[$key][4] = true;
				}
			}

			return static::state($states, $value, $i, array('prefix' => $prefix, 'translate' => !$tip), $enabled, true, $checkbox);
		}

		return static::state($states, $value, $i, $prefix, $enabled, true, $checkbox);
	}

	/**
	 * Returns an isDefault state on a grid
	 *
	 * @param   integer       $value     The state value.
	 * @param   integer       $i         The row index
	 * @param   string|array  $prefix    An optional task prefix or an array of options
	 * @param   boolean       $enabled   An optional setting for access control on the action.
	 * @param   string        $checkbox  An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @see     JHtmlJGrid::state()
	 * @since   1.6
	 */
	public static function isdefault($value, $i, $prefix = '', $enabled = true, $checkbox = 'cb')
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		$states = array(
			0 => array('setDefault', '', 'JLIB_HTML_SETDEFAULT_ITEM', '', 1, 'unfeatured', 'unfeatured'),
			1 => array('unsetDefault', 'JDEFAULT', 'JLIB_HTML_UNSETDEFAULT_ITEM', 'JDEFAULT', 1, 'featured', 'featured'),
		);

		return static::state($states, $value, $i, $prefix, $enabled, true, $checkbox);
	}

	/**
	 * Returns an array of standard published state filter options.
	 *
	 * @param   array  $config  An array of configuration options.
	 *                          This array can contain a list of key/value pairs where values are boolean
	 *                          and keys can be taken from 'published', 'unpublished', 'archived', 'trash', 'all'.
	 *                          These pairs determine which values are displayed.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function publishedOptions($config = array())
	{
		// Build the active state filter options.
		$options = array();

		if (!array_key_exists('published', $config) || $config['published'])
		{
			$options[] = JHtml::_('select.option', '1', 'JPUBLISHED');
		}

		if (!array_key_exists('unpublished', $config) || $config['unpublished'])
		{
			$options[] = JHtml::_('select.option', '0', 'JUNPUBLISHED');
		}

		if (!array_key_exists('archived', $config) || $config['archived'])
		{
			$options[] = JHtml::_('select.option', '2', 'JARCHIVED');
		}

		if (!array_key_exists('trash', $config) || $config['trash'])
		{
			$options[] = JHtml::_('select.option', '-2', 'JTRASHED');
		}

		if (!array_key_exists('all', $config) || $config['all'])
		{
			$options[] = JHtml::_('select.option', '*', 'JALL');
		}

		return $options;
	}

	/**
	 * Returns a checked-out icon
	 *
	 * @param   integer       $i           The row index.
	 * @param   string        $editorName  The name of the editor.
	 * @param   string        $time        The time that the object was checked out.
	 * @param   string|array  $prefix      An optional task prefix or an array of options
	 * @param   boolean       $enabled     True to enable the action.
	 * @param   string        $checkbox    An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function checkedout($i, $editorName, $time, $prefix = '', $enabled = false, $checkbox = 'cb')
	{
		JHtml::_('bootstrap.tooltip');

		if (is_array($prefix))
		{
			$options = $prefix;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		$text = $editorName . '<br />' . JHtml::_('date', $time, JText::_('DATE_FORMAT_LC')) . '<br />' . JHtml::_('date', $time, 'H:i');
		$active_title = JHtml::_('tooltipText', JText::_('JLIB_HTML_CHECKIN'), $text, 0);
		$inactive_title = JHtml::_('tooltipText', JText::_('JLIB_HTML_CHECKED_OUT'), $text, 0);

		return static::action(
			$i, 'checkin', $prefix, JText::_('JLIB_HTML_CHECKED_OUT'), html_entity_decode($active_title, ENT_QUOTES, 'UTF-8'),
			html_entity_decode($inactive_title, ENT_QUOTES, 'UTF-8'), true, 'checkedout', 'checkedout', $enabled, false, $checkbox
		);
	}

	/**
	 * Creates an order-up action icon.
	 *
	 * @param   integer       $i         The row index.
	 * @param   string        $task      An optional task to fire.
	 * @param   string|array  $prefix    An optional task prefix or an array of options
	 * @param   string        $text      An optional text to display
	 * @param   boolean       $enabled   An optional setting for access control on the action.
	 * @param   string        $checkbox  An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function orderUp($i, $task = 'orderup', $prefix = '', $text = 'JLIB_HTML_MOVE_UP', $enabled = true, $checkbox = 'cb')
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$text = array_key_exists('text', $options) ? $options['text'] : $text;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		return static::action($i, $task, $prefix, $text, $text, $text, false, 'uparrow', 'uparrow_disabled', $enabled, true, $checkbox);
	}

	/**
	 * Creates an order-down action icon.
	 *
	 * @param   integer       $i         The row index.
	 * @param   string        $task      An optional task to fire.
	 * @param   string|array  $prefix    An optional task prefix or an array of options
	 * @param   string        $text      An optional text to display
	 * @param   boolean       $enabled   An optional setting for access control on the action.
	 * @param   string        $checkbox  An optional prefix for checkboxes.
	 *
	 * @return  string  The HTML markup
	 *
	 * @since   1.6
	 */
	public static function orderDown($i, $task = 'orderdown', $prefix = '', $text = 'JLIB_HTML_MOVE_DOWN', $enabled = true, $checkbox = 'cb')
	{
		if (is_array($prefix))
		{
			$options = $prefix;
			$text = array_key_exists('text', $options) ? $options['text'] : $text;
			$enabled = array_key_exists('enabled', $options) ? $options['enabled'] : $enabled;
			$checkbox = array_key_exists('checkbox', $options) ? $options['checkbox'] : $checkbox;
			$prefix = array_key_exists('prefix', $options) ? $options['prefix'] : '';
		}

		return static::action($i, $task, $prefix, $text, $text, $text, false, 'downarrow', 'downarrow_disabled', $enabled, true, $checkbox);
	}
}
cms/html/category.php000064400000012671152177723700010642 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Utility class for categories
 *
 * @since  1.5
 */
abstract class JHtmlCategory
{
	/**
	 * Cached array of the category items.
	 *
	 * @var    array
	 * @since  1.5
	 */
	protected static $items = array();

	/**
	 * Returns an array of categories for the given extension.
	 *
	 * @param   string  $extension  The extension option e.g. com_something.
	 * @param   array   $config     An array of configuration options. By default, only
	 *                              published and unpublished categories are returned.
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public static function options($extension, $config = array('filter.published' => array(0, 1)))
	{
		$hash = md5($extension . '.' . serialize($config));

		if (!isset(static::$items[$hash]))
		{
			$config = (array) $config;
			$db     = JFactory::getDbo();
			$user   = JFactory::getUser();
			$groups = implode(',', $user->getAuthorisedViewLevels());

			$query = $db->getQuery(true)
				->select('a.id, a.title, a.level, a.language')
				->from('#__categories AS a')
				->where('a.parent_id > 0');

			// Filter on extension.
			$query->where('extension = ' . $db->quote($extension));

			// Filter on user access level
			$query->where('a.access IN (' . $groups . ')');

			// Filter on the published state
			if (isset($config['filter.published']))
			{
				if (is_numeric($config['filter.published']))
				{
					$query->where('a.published = ' . (int) $config['filter.published']);
				}
				elseif (is_array($config['filter.published']))
				{
					$config['filter.published'] = ArrayHelper::toInteger($config['filter.published']);
					$query->where('a.published IN (' . implode(',', $config['filter.published']) . ')');
				}
			}

			// Filter on the language
			if (isset($config['filter.language']))
			{
				if (is_string($config['filter.language']))
				{
					$query->where('a.language = ' . $db->quote($config['filter.language']));
				}
				elseif (is_array($config['filter.language']))
				{
					foreach ($config['filter.language'] as &$language)
					{
						$language = $db->quote($language);
					}

					$query->where('a.language IN (' . implode(',', $config['filter.language']) . ')');
				}
			}

			// Filter on the access
			if (isset($config['filter.access']))
			{
				if (is_string($config['filter.access']))
				{
					$query->where('a.access = ' . $db->quote($config['filter.access']));
				}
				elseif (is_array($config['filter.access']))
				{
					foreach ($config['filter.access'] as &$access)
					{
						$access = $db->quote($access);
					}

					$query->where('a.access IN (' . implode(',', $config['filter.access']) . ')');
				}
			}

			$query->order('a.lft');

			$db->setQuery($query);
			$items = $db->loadObjectList();

			// Assemble the list options.
			static::$items[$hash] = array();

			foreach ($items as &$item)
			{
				$repeat = ($item->level - 1 >= 0) ? $item->level - 1 : 0;
				$item->title = str_repeat('- ', $repeat) . $item->title;

				if ($item->language !== '*')
				{
					$item->title .= ' (' . $item->language . ')';
				}

				static::$items[$hash][] = JHtml::_('select.option', $item->id, $item->title);
			}
		}

		return static::$items[$hash];
	}

	/**
	 * Returns an array of categories for the given extension.
	 *
	 * @param   string  $extension  The extension option.
	 * @param   array   $config     An array of configuration options. By default, only published and unpublished categories are returned.
	 *
	 * @return  array   Categories for the extension
	 *
	 * @since   1.6
	 */
	public static function categories($extension, $config = array('filter.published' => array(0, 1)))
	{
		$hash = md5($extension . '.' . serialize($config));

		if (!isset(static::$items[$hash]))
		{
			$config = (array) $config;
			$user = JFactory::getUser();
			$db = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('a.id, a.title, a.level, a.parent_id')
				->from('#__categories AS a')
				->where('a.parent_id > 0');

			// Filter on extension.
			$query->where('extension = ' . $db->quote($extension));

			// Filter on user level.
			$groups = implode(',', $user->getAuthorisedViewLevels());
			$query->where('a.access IN (' . $groups . ')');

			// Filter on the published state
			if (isset($config['filter.published']))
			{
				if (is_numeric($config['filter.published']))
				{
					$query->where('a.published = ' . (int) $config['filter.published']);
				}
				elseif (is_array($config['filter.published']))
				{
					$config['filter.published'] = ArrayHelper::toInteger($config['filter.published']);
					$query->where('a.published IN (' . implode(',', $config['filter.published']) . ')');
				}
			}

			$query->order('a.lft');

			$db->setQuery($query);
			$items = $db->loadObjectList();

			// Assemble the list options.
			static::$items[$hash] = array();

			foreach ($items as &$item)
			{
				$repeat = ($item->level - 1 >= 0) ? $item->level - 1 : 0;
				$item->title = str_repeat('- ', $repeat) . $item->title;
				static::$items[$hash][] = JHtml::_('select.option', $item->id, $item->title);
			}
			// Special "Add to root" option:
			static::$items[$hash][] = JHtml::_('select.option', '1', JText::_('JLIB_HTML_ADD_TO_ROOT'));
		}

		return static::$items[$hash];
	}
}
cms/html/tag.php000064400000011116152177723700007571 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Utility class for tags
 *
 * @since  3.1
 */
abstract class JHtmlTag
{
	/**
	 * Cached array of the tag items.
	 *
	 * @var    array
	 * @since  3.1
	 */
	protected static $items = array();

	/**
	 * Returns an array of tags.
	 *
	 * @param   array  $config  An array of configuration options. By default, only
	 *                          published and unpublished categories are returned.
	 *
	 * @return  array
	 *
	 * @since   3.1
	 */
	public static function options($config = array('filter.published' => array(0, 1)))
	{
		$hash = md5(serialize($config));

		if (!isset(static::$items[$hash]))
		{
			$config = (array) $config;
			$db = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('a.id, a.title, a.level')
				->from('#__tags AS a')
				->where('a.parent_id > 0');

			// Filter on the published state
			if (isset($config['filter.published']))
			{
				if (is_numeric($config['filter.published']))
				{
					$query->where('a.published = ' . (int) $config['filter.published']);
				}
				elseif (is_array($config['filter.published']))
				{
					$config['filter.published'] = ArrayHelper::toInteger($config['filter.published']);
					$query->where('a.published IN (' . implode(',', $config['filter.published']) . ')');
				}
			}

			// Filter on the language
			if (isset($config['filter.language']))
			{
				if (is_string($config['filter.language']))
				{
					$query->where('a.language = ' . $db->quote($config['filter.language']));
				}
				elseif (is_array($config['filter.language']))
				{
					foreach ($config['filter.language'] as &$language)
					{
						$language = $db->quote($language);
					}

					$query->where('a.language IN (' . implode(',', $config['filter.language']) . ')');
				}
			}

			$query->order('a.lft');

			$db->setQuery($query);
			$items = $db->loadObjectList();

			// Assemble the list options.
			static::$items[$hash] = array();

			foreach ($items as &$item)
			{
				$repeat = ($item->level - 1 >= 0) ? $item->level - 1 : 0;
				$item->title = str_repeat('- ', $repeat) . $item->title;
				static::$items[$hash][] = JHtml::_('select.option', $item->id, $item->title);
			}
		}

		return static::$items[$hash];
	}

	/**
	 * Returns an array of tags.
	 *
	 * @param   array  $config  An array of configuration options. By default, only published and unpublished tags are returned.
	 *
	 * @return  array  Tag data
	 *
	 * @since   3.1
	 */
	public static function tags($config = array('filter.published' => array(0, 1)))
	{
		$hash = md5(serialize($config));
		$config = (array) $config;
		$db = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('a.id, a.title, a.level, a.parent_id')
			->from('#__tags AS a')
			->where('a.parent_id > 0');

		// Filter on the published state
		if (isset($config['filter.published']))
		{
			if (is_numeric($config['filter.published']))
			{
				$query->where('a.published = ' . (int) $config['filter.published']);
			}
			elseif (is_array($config['filter.published']))
			{
				$config['filter.published'] = ArrayHelper::toInteger($config['filter.published']);
				$query->where('a.published IN (' . implode(',', $config['filter.published']) . ')');
			}
		}

		$query->order('a.lft');

		$db->setQuery($query);
		$items = $db->loadObjectList();

		// Assemble the list options.
		static::$items[$hash] = array();

		foreach ($items as &$item)
		{
			$repeat = ($item->level - 1 >= 0) ? $item->level - 1 : 0;
			$item->title = str_repeat('- ', $repeat) . $item->title;
			static::$items[$hash][] = JHtml::_('select.option', $item->id, $item->title);
		}

		return static::$items[$hash];
	}

	/**
	 * This is just a proxy for the formbehavior.ajaxchosen method
	 *
	 * @param   string   $selector     DOM id of the tag field
	 * @param   boolean  $allowCustom  Flag to allow custom values
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public static function ajaxfield($selector = '#jform_tags', $allowCustom = true)
	{
		// Get the component parameters
		$params = JComponentHelper::getParams('com_tags');
		$minTermLength = (int) $params->get('min_term_length', 3);

		$displayData = array(
			'minTermLength' => $minTermLength,
			'selector'      => $selector,
			'allowCustom'   => JFactory::getUser()->authorise('core.create', 'com_tags') ? $allowCustom : false,
		);

		JLayoutHelper::render('joomla.html.tag', $displayData);

		return;
	}
}
cms/html/form.php000064400000003302152177723700007757 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Utility class for form elements
 *
 * @since  1.5
 */
abstract class JHtmlForm
{
	/**
	 * Array containing information for loaded files.
	 *
	 * @var    array
	 *
	 * @since  3.8.0
	 */
	protected static $loaded = array();

	/**
	 * Displays a hidden token field to reduce the risk of CSRF exploits
	 *
	 * Use in conjunction with JSession::checkToken()
	 *
	 * @param   array  $attribs  Input element attributes.
	 *
	 * @return  string  A hidden input field with a token
	 *
	 * @see     JSession::checkToken()
	 * @since   1.5
	 */
	public static function token(array $attribs = array())
	{
		$attributes = '';

		if ($attribs !== array())
		{
			$attributes .= ' ' . ArrayHelper::toString($attribs);
		}

		return '<input type="hidden" name="' . JSession::getFormToken() . '" value="1"' . $attributes . ' />';
	}

	/**
	 * Add CSRF form token to Joomla script options that developers can get it by Javascript.
	 *
	 * @param   string  $name  The script option key name.
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public static function csrf($name = 'csrf.token')
	{
		if (isset(static::$loaded[__METHOD__][$name]))
		{
			return;
		}

		/** @var JDocumentHtml $doc */
		$doc = JFactory::getDocument();

		if (!$doc instanceof JDocumentHtml || $doc->getType() !== 'html')
		{
			return;
		}

		$doc->addScriptOptions($name, JSession::getFormToken());

		static::$loaded[__METHOD__][$name] = true;
	}
}
cms/html/behavior.php000064400000075601152177723700010626 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for JavaScript behaviors
 *
 * @since  1.5
 */
abstract class JHtmlBehavior
{
	/**
	 * Array containing information for loaded files
	 *
	 * @var    array
	 * @since  2.5
	 */
	protected static $loaded = array();

	/**
	 * Method to load the MooTools framework into the document head
	 *
	 * If debugging mode is on an uncompressed version of MooTools is included for easier debugging.
	 *
	 * @param   boolean  $extras  Flag to determine whether to load MooTools More in addition to Core
	 * @param   mixed    $debug   Is debugging mode on? [optional]
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated 4.0 Update scripts to jquery
	 */
	public static function framework($extras = false, $debug = null)
	{
		$type = $extras ? 'more' : 'core';

		// Only load once
		if (!empty(static::$loaded[__METHOD__][$type]))
		{
			return;
		}

		JLog::add('JHtmlBehavior::framework is deprecated. Update to jquery scripts.', JLog::WARNING, 'deprecated');

		// If no debugging value is set, use the configuration setting
		if ($debug === null)
		{
			$debug = JDEBUG;
		}

		if ($type !== 'core' && empty(static::$loaded[__METHOD__]['core']))
		{
			static::framework(false, $debug);
		}

		JHtml::_('script', 'system/mootools-' . $type . '.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));

		// Keep loading core.js for BC reasons
		static::core();

		static::$loaded[__METHOD__][$type] = true;

		return;
	}

	/**
	 * Method to load core.js into the document head.
	 *
	 * Core.js defines the 'Joomla' namespace and contains functions which are used across extensions
	 *
	 * @return  void
	 *
	 * @since   3.3
	 */
	public static function core()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		JHtml::_('form.csrf');
		JHtml::_('script', 'system/core.js', array('version' => 'auto', 'relative' => true));

		// Add core and base uri paths so javascript scripts can use them.
		JFactory::getDocument()->addScriptOptions('system.paths', array('root' => JUri::root(true), 'base' => JUri::base(true)));

		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for image captions.
	 *
	 * @param   string  $selector  The selector for which a caption behaviour is to be applied.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @Deprecated 4.0 Use native HTML figure tags.
	 */
	public static function caption($selector = 'img.caption')
	{
		JLog::add('JHtmlBehavior::caption is deprecated. Use native HTML figure tags.', JLog::WARNING, 'deprecated');

		// Only load once
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'system/caption.js', array('version' => 'auto', 'relative' => true));

		// Attach caption to document
		JFactory::getDocument()->addScriptDeclaration(
			"jQuery(window).on('load',  function() {
				new JCaption('" . $selector . "');
			});"
		);

		// Set static array
		static::$loaded[__METHOD__][$selector] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for form validation.
	 *
	 * To enable form validation the form tag must have class="form-validate".
	 * Each field that needs to be validated needs to have class="validate".
	 * Additional handlers can be added to the handler for username, password,
	 * numeric and email. To use these add class="validate-email" and so on.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @Deprecated 3.4 Use formvalidator instead
	 */
	public static function formvalidation()
	{
		JLog::add('The use of formvalidation is deprecated use formvalidator instead.', JLog::WARNING, 'deprecated');

		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include MooTools framework
		static::framework();

		// Load the new jQuery code
		static::formvalidator();
	}

	/**
	 * Add unobtrusive JavaScript support for form validation.
	 *
	 * To enable form validation the form tag must have class="form-validate".
	 * Each field that needs to be validated needs to have class="validate".
	 * Additional handlers can be added to the handler for username, password,
	 * numeric and email. To use these add class="validate-email" and so on.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 */
	public static function formvalidator()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include core
		static::core();

		// Include jQuery
		JHtml::_('jquery.framework');

		// Add validate.js language strings
		JText::script('JLIB_FORM_FIELD_INVALID');

		JHtml::_('script', 'system/punycode.js', array('version' => 'auto', 'relative' => true));
		JHtml::_('script', 'system/validate.js', array('version' => 'auto', 'relative' => true));
		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for submenu switcher support
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function switcher()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'system/switcher.js', array('framework' => true, 'version' => 'auto', 'relative' => true));

		$script = "
			document.switcher = null;
			jQuery(function($){
				var toggler = document.getElementById('submenu');
				var element = document.getElementById('config-document');
				if (element) {
					document.switcher = new JSwitcher(toggler, element);
				}
			});";

		JFactory::getDocument()->addScriptDeclaration($script);
		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for a combobox effect.
	 *
	 * Note that this control is only reliable in absolutely positioned elements.
	 * Avoid using a combobox in a slider or dynamic pane.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function combobox()
	{
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}
		// Include core
		static::core();

		JHtml::_('script', 'system/combobox.js', array('version' => 'auto', 'relative' => true));
		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for a hover tooltips.
	 *
	 * Add a title attribute to any element in the form
	 * title="title::text"
	 *
	 * Uses the core Tips class in MooTools.
	 *
	 * @param   string  $selector  The class selector for the tooltip.
	 * @param   array   $params    An array of options for the tooltip.
	 *                             Options for the tooltip can be:
	 *                             - maxTitleChars  integer   The maximum number of characters in the tooltip title (defaults to 50).
	 *                             - offsets        object    The distance of your tooltip from the mouse (defaults to {'x': 16, 'y': 16}).
	 *                             - showDelay      integer   The millisecond delay the show event is fired (defaults to 100).
	 *                             - hideDelay      integer   The millisecond delay the hide hide is fired (defaults to 100).
	 *                             - className      string    The className your tooltip container will get.
	 *                             - fixed          boolean   If set to true, the toolTip will not follow the mouse.
	 *                             - onShow         function  The default function for the show event, passes the tip element
	 *                               and the currently hovered element.
	 *                             - onHide         function  The default function for the hide event, passes the currently
	 *                               hovered element.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function tooltip($selector = '.hasTip', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (isset(static::$loaded[__METHOD__][$sig]))
		{
			return;
		}

		// Include MooTools framework
		static::framework(true);

		// Setup options object
		$opt['maxTitleChars'] = isset($params['maxTitleChars']) && $params['maxTitleChars'] ? (int) $params['maxTitleChars'] : 50;

		// Offsets needs an array in the format: array('x'=>20, 'y'=>30)
		$opt['offset']    = isset($params['offset']) && is_array($params['offset']) ? $params['offset'] : null;
		$opt['showDelay'] = isset($params['showDelay']) ? (int) $params['showDelay'] : null;
		$opt['hideDelay'] = isset($params['hideDelay']) ? (int) $params['hideDelay'] : null;
		$opt['className'] = isset($params['className']) ? $params['className'] : null;
		$opt['fixed']     = isset($params['fixed']) && $params['fixed'];
		$opt['onShow']    = isset($params['onShow']) ? '\\' . $params['onShow'] : null;
		$opt['onHide']    = isset($params['onHide']) ? '\\' . $params['onHide'] : null;

		$options = JHtml::getJSObject($opt);

		// Include jQuery
		JHtml::_('jquery.framework');

		// Attach tooltips to document
		JFactory::getDocument()->addScriptDeclaration(
			"jQuery(function($) {
			 $('$selector').each(function() {
				var title = $(this).attr('title');
				if (title) {
					var parts = title.split('::', 2);
					var mtelement = document.id(this);
					mtelement.store('tip:title', parts[0]);
					mtelement.store('tip:text', parts[1]);
				}
			});
			var JTooltips = new Tips($('$selector').get(), $options);
		});"
		);

		// Set static array
		static::$loaded[__METHOD__][$sig] = true;

		return;
	}

	/**
	 * Add unobtrusive JavaScript support for modal links.
	 *
	 * @param   string  $selector  The selector for which a modal behaviour is to be applied.
	 * @param   array   $params    An array of parameters for the modal behaviour.
	 *                             Options for the modal behaviour can be:
	 *                            - ajaxOptions
	 *                            - size
	 *                            - shadow
	 *                            - overlay
	 *                            - onOpen
	 *                            - onClose
	 *                            - onUpdate
	 *                            - onResize
	 *                            - onShow
	 *                            - onHide
	 *
	 * @return  void
	 *
	 * @since   1.5
	 * @deprecated 4.0  Use the modal equivalent from bootstrap
	 */
	public static function modal($selector = 'a.modal', $params = array())
	{
		$document = JFactory::getDocument();

		// Load the necessary files if they haven't yet been loaded
		if (!isset(static::$loaded[__METHOD__]))
		{
			// Include MooTools framework
			static::framework(true);

			// Load the JavaScript and css
			JHtml::_('script', 'system/modal.js', array('framework' => true, 'version' => 'auto', 'relative' => true));
			JHtml::_('stylesheet', 'system/modal.css', array('version' => 'auto', 'relative' => true));
		}

		$sig = md5(serialize(array($selector, $params)));

		if (isset(static::$loaded[__METHOD__][$sig]))
		{
			return;
		}

		JLog::add('JHtmlBehavior::modal is deprecated. Use the modal equivalent from bootstrap.', JLog::WARNING, 'deprecated');

		// Setup options object
		$opt['ajaxOptions']   = isset($params['ajaxOptions']) && is_array($params['ajaxOptions']) ? $params['ajaxOptions'] : null;
		$opt['handler']       = isset($params['handler']) ? $params['handler'] : null;
		$opt['parseSecure']   = isset($params['parseSecure']) ? (bool) $params['parseSecure'] : null;
		$opt['closable']      = isset($params['closable']) ? (bool) $params['closable'] : null;
		$opt['closeBtn']      = isset($params['closeBtn']) ? (bool) $params['closeBtn'] : null;
		$opt['iframePreload'] = isset($params['iframePreload']) ? (bool) $params['iframePreload'] : null;
		$opt['iframeOptions'] = isset($params['iframeOptions']) && is_array($params['iframeOptions']) ? $params['iframeOptions'] : null;
		$opt['size']          = isset($params['size']) && is_array($params['size']) ? $params['size'] : null;
		$opt['shadow']        = isset($params['shadow']) ? $params['shadow'] : null;
		$opt['overlay']       = isset($params['overlay']) ? $params['overlay'] : null;
		$opt['onOpen']        = isset($params['onOpen']) ? $params['onOpen'] : null;
		$opt['onClose']       = isset($params['onClose']) ? $params['onClose'] : null;
		$opt['onUpdate']      = isset($params['onUpdate']) ? $params['onUpdate'] : null;
		$opt['onResize']      = isset($params['onResize']) ? $params['onResize'] : null;
		$opt['onMove']        = isset($params['onMove']) ? $params['onMove'] : null;
		$opt['onShow']        = isset($params['onShow']) ? $params['onShow'] : null;
		$opt['onHide']        = isset($params['onHide']) ? $params['onHide'] : null;

		// Include jQuery
		JHtml::_('jquery.framework');

		if (isset($params['fullScreen']) && (bool) $params['fullScreen'])
		{
			$opt['size']      = array('x' => '\\jQuery(window).width() - 80', 'y' => '\\jQuery(window).height() - 80');
		}

		$options = JHtml::getJSObject($opt);

		// Attach modal behavior to document
		$document
			->addScriptDeclaration(
			"
		jQuery(function($) {
			SqueezeBox.initialize(" . $options . ");
			initSqueezeBox();
			$(document).on('subform-row-add', initSqueezeBox);

			function initSqueezeBox(event, container)
			{
				SqueezeBox.assign($(container || document).find('" . $selector . "').get(), {
					parse: 'rel'
				});
			}
		});

		window.jModalClose = function () {
			SqueezeBox.close();
		};

		// Add extra modal close functionality for tinyMCE-based editors
		document.onreadystatechange = function () {
			if (document.readyState == 'interactive' && typeof tinyMCE != 'undefined' && tinyMCE)
			{
				if (typeof window.jModalClose_no_tinyMCE === 'undefined')
				{
					window.jModalClose_no_tinyMCE = typeof(jModalClose) == 'function'  ?  jModalClose  :  false;

					jModalClose = function () {
						if (window.jModalClose_no_tinyMCE) window.jModalClose_no_tinyMCE.apply(this, arguments);
						tinyMCE.activeEditor.windowManager.close();
					};
				}

				if (typeof window.SqueezeBoxClose_no_tinyMCE === 'undefined')
				{
					if (typeof(SqueezeBox) == 'undefined')  SqueezeBox = {};
					window.SqueezeBoxClose_no_tinyMCE = typeof(SqueezeBox.close) == 'function'  ?  SqueezeBox.close  :  false;

					SqueezeBox.close = function () {
						if (window.SqueezeBoxClose_no_tinyMCE)  window.SqueezeBoxClose_no_tinyMCE.apply(this, arguments);
						tinyMCE.activeEditor.windowManager.close();
					};
				}
			}
		};
		"
		);

		// Set static array
		static::$loaded[__METHOD__][$sig] = true;

		return;
	}

	/**
	 * JavaScript behavior to allow shift select in grids
	 *
	 * @param   string  $id  The id of the form for which a multiselect behaviour is to be applied.
	 *
	 * @return  void
	 *
	 * @since   1.7
	 */
	public static function multiselect($id = 'adminForm')
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__][$id]))
		{
			return;
		}

		// Include core
		static::core();

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'system/multiselect.js', array('version' => 'auto', 'relative' => true));

		// Attach multiselect to document
		JFactory::getDocument()->addScriptDeclaration(
			"jQuery(document).ready(function() {
				Joomla.JMultiSelect('" . $id . "');
			});"
		);

		// Set static array
		static::$loaded[__METHOD__][$id] = true;

		return;
	}

	/**
	 * Add unobtrusive javascript support for a collapsible tree.
	 *
	 * @param   string  $id      An index
	 * @param   array   $params  An array of options.
	 * @param   array   $root    The root node
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function tree($id, $params = array(), $root = array())
	{
		// Include MooTools framework
		static::framework();

		JHtml::_('script', 'system/mootree.js', array('framework' => true, 'version' => 'auto', 'relative' => true));
		JHtml::_('stylesheet', 'system/mootree.css', array('version' => 'auto', 'relative' => true));

		if (isset(static::$loaded[__METHOD__][$id]))
		{
			return;
		}

		// Include jQuery
		JHtml::_('jquery.framework');

		// Setup options object
		$opt['div']   = array_key_exists('div', $params) ? $params['div'] : $id . '_tree';
		$opt['mode']  = array_key_exists('mode', $params) ? $params['mode'] : 'folders';
		$opt['grid']  = array_key_exists('grid', $params) ? '\\' . $params['grid'] : true;
		$opt['theme'] = array_key_exists('theme', $params) ? $params['theme'] : JHtml::_('image', 'system/mootree.gif', '', array(), true, true);

		// Event handlers
		$opt['onExpand'] = array_key_exists('onExpand', $params) ? '\\' . $params['onExpand'] : null;
		$opt['onSelect'] = array_key_exists('onSelect', $params) ? '\\' . $params['onSelect'] : null;
		$opt['onClick']  = array_key_exists('onClick', $params) ? '\\' . $params['onClick']
		: '\\function(node){  window.open(node.data.url, node.data.target != null ? node.data.target : \'_self\'); }';

		$options = JHtml::getJSObject($opt);

		// Setup root node
		$rt['text']     = array_key_exists('text', $root) ? $root['text'] : 'Root';
		$rt['id']       = array_key_exists('id', $root) ? $root['id'] : null;
		$rt['color']    = array_key_exists('color', $root) ? $root['color'] : null;
		$rt['open']     = array_key_exists('open', $root) ? '\\' . $root['open'] : true;
		$rt['icon']     = array_key_exists('icon', $root) ? $root['icon'] : null;
		$rt['openicon'] = array_key_exists('openicon', $root) ? $root['openicon'] : null;
		$rt['data']     = array_key_exists('data', $root) ? $root['data'] : null;
		$rootNode = JHtml::getJSObject($rt);

		$treeName = array_key_exists('treeName', $params) ? $params['treeName'] : '';

		$js = '		jQuery(function(){
			tree' . $treeName . ' = new MooTreeControl(' . $options . ',' . $rootNode . ');
			tree' . $treeName . '.adopt(\'' . $id . '\');})';

		// Attach tooltips to document
		$document = JFactory::getDocument();
		$document->addScriptDeclaration($js);

		// Set static array
		static::$loaded[__METHOD__][$id] = true;

		return;
	}

	/**
	 * Add unobtrusive JavaScript support for a calendar control.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @deprecated 4.0
	 */
	public static function calendar()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		JLog::add('JHtmlBehavior::calendar is deprecated as the static assets are being loaded in the relative layout.', JLog::WARNING, 'deprecated');

		$document = JFactory::getDocument();
		$tag      = JFactory::getLanguage()->getTag();
		$attribs  = array('title' => JText::_('JLIB_HTML_BEHAVIOR_GREEN'), 'media' => 'all');

		JHtml::_('stylesheet', 'system/calendar-jos.css', array('version' => 'auto', 'relative' => true), $attribs);
		JHtml::_('script', $tag . '/calendar.js', array('version' => 'auto', 'relative' => true));
		JHtml::_('script', $tag . '/calendar-setup.js', array('version' => 'auto', 'relative' => true));

		$translation = static::calendartranslation();

		if ($translation)
		{
			$document->addScriptDeclaration($translation);
		}

		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for a color picker.
	 *
	 * @return  void
	 *
	 * @since   1.7
	 *
	 * @deprecated 4.0 Use directly the field or the layout
	 */
	public static function colorpicker()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'jui/jquery.minicolors.min.js', array('version' => 'auto', 'relative' => true));
		JHtml::_('stylesheet', 'jui/jquery.minicolors.css', array('version' => 'auto', 'relative' => true));
		JFactory::getDocument()->addScriptDeclaration("
				jQuery(document).ready(function (){
					jQuery('.minicolors').each(function() {
						jQuery(this).minicolors({
							control: jQuery(this).attr('data-control') || 'hue',
							format: jQuery(this).attr('data-validate') === 'color'
								? 'hex'
								: (jQuery(this).attr('data-format') === 'rgba'
									? 'rgb'
									: jQuery(this).attr('data-format'))
								|| 'hex',
							keywords: jQuery(this).attr('data-keywords') || '',
							opacity: jQuery(this).attr('data-format') === 'rgba' ? true : false || false,
							position: jQuery(this).attr('data-position') || 'default',
							theme: 'bootstrap'
						});
					});
				});
			"
		);

		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Add unobtrusive JavaScript support for a simple color picker.
	 *
	 * @return  void
	 *
	 * @since   3.1
	 *
	 * @deprecated 4.0 Use directly the field or the layout
	 */
	public static function simplecolorpicker()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'jui/jquery.simplecolors.min.js', array('version' => 'auto', 'relative' => true));
		JHtml::_('stylesheet', 'jui/jquery.simplecolors.css', array('version' => 'auto', 'relative' => true));
		JFactory::getDocument()->addScriptDeclaration("
				jQuery(document).ready(function (){
					jQuery('select.simplecolors').simplecolors();
				});
			"
		);

		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Keep session alive, for example, while editing or creating an article.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 */
	public static function keepalive()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		$session = JFactory::getSession();

		// If the handler is not 'Database', we set a fixed, small refresh value (here: 5 min)
		$refreshTime = 300;

		if ($session->storeName === 'database')
		{
			$lifeTime    = $session->getExpire();
			$refreshTime = $lifeTime <= 60 ? 45 : $lifeTime - 60;

			// The longest refresh period is one hour to prevent integer overflow.
			if ($refreshTime > 3600 || $refreshTime <= 0)
			{
				$refreshTime = 3600;
			}
		}

		// If we are in the frontend or logged in as a user, we can use the ajax component to reduce the load
		$uri = 'index.php' . (JFactory::getApplication()->isClient('site') || !JFactory::getUser()->guest ? '?option=com_ajax&format=json' : '');

		// Include core and polyfill for browsers lower than IE 9.
		static::core();
		static::polyfill('event', 'lt IE 9');

		// Add keepalive script options.
		JFactory::getDocument()->addScriptOptions('system.keepalive', array('interval' => $refreshTime * 1000, 'uri' => JRoute::_($uri)));

		// Add script.
		JHtml::_('script', 'system/keepalive.js', array('version' => 'auto', 'relative' => true));

		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Highlight some words via Javascript.
	 *
	 * @param   array   $terms      Array of words that should be highlighted.
	 * @param   string  $start      ID of the element that marks the begin of the section in which words
	 *                              should be highlighted. Note this element will be removed from the DOM.
	 * @param   string  $end        ID of the element that end this section.
	 *                              Note this element will be removed from the DOM.
	 * @param   string  $className  Class name of the element highlights are wrapped in.
	 * @param   string  $tag        Tag that will be used to wrap the highlighted words.
	 *
	 * @return  void
	 *
	 * @since   2.5
	 */
	public static function highlighter(array $terms, $start = 'highlighter-start', $end = 'highlighter-end', $className = 'highlight', $tag = 'span')
	{
		$sig = md5(serialize(array($terms, $start, $end)));

		if (isset(static::$loaded[__METHOD__][$sig]))
		{
			return;
		}

		$terms = array_filter($terms, 'strlen');

		// Nothing to Highlight
		if (empty($terms))
		{
			static::$loaded[__METHOD__][$sig] = true;

			return;
		}

		// Include core
		static::core();

		// Include jQuery
		JHtml::_('jquery.framework');

		JHtml::_('script', 'system/highlighter.js', array('version' => 'auto', 'relative' => true));

		foreach ($terms as $i => $term)
		{
			$terms[$i] = JFilterOutput::stringJSSafe($term);
		}

		$document = JFactory::getDocument();
		$document->addScriptDeclaration("
			jQuery(function ($) {
				var start = document.getElementById('" . $start . "');
				var end = document.getElementById('" . $end . "');
				if (!start || !end || !Joomla.Highlighter) {
					return true;
				}
				highlighter = new Joomla.Highlighter({
					startElement: start,
					endElement: end,
					className: '" . $className . "',
					onlyWords: false,
					tag: '" . $tag . "'
				}).highlight([\"" . implode('","', $terms) . "\"]);
				$(start).remove();
				$(end).remove();
			});
		");

		static::$loaded[__METHOD__][$sig] = true;

		return;
	}

	/**
	 * Break us out of any containing iframes
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @deprecated  4.0  Add a X-Frame-Options HTTP Header with the SAMEORIGIN value instead.
	 */
	public static function noframes()
	{
		JLog::add(__METHOD__ . ' is deprecated, add a X-Frame-Options HTTP Header with the SAMEORIGIN value instead.', JLog::WARNING, 'deprecated');

		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Include core
		static::core();

		// Include jQuery
		JHtml::_('jquery.framework');

		$js = 'jQuery(function () {
			if (top == self) {
				document.documentElement.style.display = "block";
			}
			else
			{
				top.location = self.location;
			}

			// Firefox fix
			jQuery("input[autofocus]").focus();
		})';
		$document = JFactory::getDocument();
		$document->addStyleDeclaration('html { display:none }');
		$document->addScriptDeclaration($js);

		JFactory::getApplication()->setHeader('X-Frame-Options', 'SAMEORIGIN');

		static::$loaded[__METHOD__] = true;
	}

	/**
	 * Internal method to get a JavaScript object notation string from an array
	 *
	 * @param   array  $array  The array to convert to JavaScript object notation
	 *
	 * @return  string  JavaScript object notation representation of the array
	 *
	 * @since       1.5
	 * @deprecated  4.0 - Use JHtml::getJSObject() instead.
	 */
	protected static function _getJSObject($array = array())
	{
		JLog::add('JHtmlBehavior::_getJSObject() is deprecated. JHtml::getJSObject() instead..', JLog::WARNING, 'deprecated');

		return JHtml::getJSObject($array);
	}

	/**
	 * Add unobtrusive JavaScript support to keep a tab state.
	 *
	 * Note that keeping tab state only works for inner tabs if in accordance with the following example:
	 *
	 * ```
	 * parent tab = permissions
	 * child tab = permission-<identifier>
	 * ```
	 *
	 * Each tab header `<a>` tag also should have a unique href attribute
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function tabstate()
	{
		if (isset(self::$loaded[__METHOD__]))
		{
			return;
		}
		// Include jQuery
		JHtml::_('jquery.framework');
		JHtml::_('behavior.polyfill', array('filter','xpath'));
		JHtml::_('script', 'system/tabs-state.js', array('version' => 'auto', 'relative' => true));
		self::$loaded[__METHOD__] = true;
	}

	/**
	 * Add javascript polyfills.
	 *
	 * @param   string|array  $polyfillTypes       The polyfill type(s). Examples: event, array('event', 'classlist').
	 * @param   string        $conditionalBrowser  An IE conditional expression. Example: lt IE 9 (lower than IE 9).
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 */
	public static function polyfill($polyfillTypes = null, $conditionalBrowser = null)
	{
		if ($polyfillTypes === null)
		{
			return;
		}

		foreach ((array) $polyfillTypes as $polyfillType)
		{
			$sig = md5(serialize(array($polyfillType, $conditionalBrowser)));

			// Only load once
			if (isset(static::$loaded[__METHOD__][$sig]))
			{
				continue;
			}

			// If include according to browser.
			$scriptOptions = array('version' => 'auto', 'relative' => true);
			$scriptOptions = $conditionalBrowser !== null ? array_replace($scriptOptions, array('conditional' => $conditionalBrowser)) : $scriptOptions;

			JHtml::_('script', 'system/polyfill.' . $polyfillType . '.js', $scriptOptions);

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
		}
	}

	/**
	 * Internal method to translate the JavaScript Calendar
	 *
	 * @return  string  JavaScript that translates the object
	 *
	 * @since   1.5
	 */
	protected static function calendartranslation()
	{
		static $jsscript = 0;

		// Guard clause, avoids unnecessary nesting
		if ($jsscript)
		{
			return false;
		}

		$jsscript = 1;

		// To keep the code simple here, run strings through JText::_() using array_map()
		$callback = array('JText', '_');
		$weekdays_full = array_map(
			$callback, array(
				'SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY',
			)
		);
		$weekdays_short = array_map(
			$callback,
			array(
				'SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT', 'SUN',
			)
		);
		$months_long = array_map(
			$callback, array(
				'JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE',
				'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER',
			)
		);
		$months_short = array_map(
			$callback, array(
				'JANUARY_SHORT', 'FEBRUARY_SHORT', 'MARCH_SHORT', 'APRIL_SHORT', 'MAY_SHORT', 'JUNE_SHORT',
				'JULY_SHORT', 'AUGUST_SHORT', 'SEPTEMBER_SHORT', 'OCTOBER_SHORT', 'NOVEMBER_SHORT', 'DECEMBER_SHORT',
			)
		);

		// This will become an object in Javascript but define it first in PHP for readability
		$today = " " . JText::_('JLIB_HTML_BEHAVIOR_TODAY') . " ";
		$text = array(
			'INFO'           => JText::_('JLIB_HTML_BEHAVIOR_ABOUT_THE_CALENDAR'),
			'ABOUT'          => "DHTML Date/Time Selector\n"
				. "(c) dynarch.com 20022005 / Author: Mihai Bazon\n"
				. "For latest version visit: http://www.dynarch.com/projects/calendar/\n"
				. "Distributed under GNU LGPL.  See http://gnu.org/licenses/lgpl.html for details."
				. "\n\n"
				. JText::_('JLIB_HTML_BEHAVIOR_DATE_SELECTION')
				. JText::_('JLIB_HTML_BEHAVIOR_YEAR_SELECT')
				. JText::_('JLIB_HTML_BEHAVIOR_MONTH_SELECT')
				. JText::_('JLIB_HTML_BEHAVIOR_HOLD_MOUSE'),
			'ABOUT_TIME'      => "\n\n"
				. "Time selection:\n"
				. " Click on any of the time parts to increase it\n"
				. " or Shiftclick to decrease it\n"
				. " or click and drag for faster selection.",
			'PREV_YEAR'       => JText::_('JLIB_HTML_BEHAVIOR_PREV_YEAR_HOLD_FOR_MENU'),
			'PREV_MONTH'      => JText::_('JLIB_HTML_BEHAVIOR_PREV_MONTH_HOLD_FOR_MENU'),
			'GO_TODAY'        => JText::_('JLIB_HTML_BEHAVIOR_GO_TODAY'),
			'NEXT_MONTH'      => JText::_('JLIB_HTML_BEHAVIOR_NEXT_MONTH_HOLD_FOR_MENU'),
			'SEL_DATE'        => JText::_('JLIB_HTML_BEHAVIOR_SELECT_DATE'),
			'DRAG_TO_MOVE'    => JText::_('JLIB_HTML_BEHAVIOR_DRAG_TO_MOVE'),
			'PART_TODAY'      => $today,
			'DAY_FIRST'       => JText::_('JLIB_HTML_BEHAVIOR_DISPLAY_S_FIRST'),
			'WEEKEND'         => JFactory::getLanguage()->getWeekEnd(),
			'CLOSE'           => JText::_('JLIB_HTML_BEHAVIOR_CLOSE'),
			'TODAY'           => JText::_('JLIB_HTML_BEHAVIOR_TODAY'),
			'TIME_PART'       => JText::_('JLIB_HTML_BEHAVIOR_SHIFT_CLICK_OR_DRAG_TO_CHANGE_VALUE'),
			'DEF_DATE_FORMAT' => "%Y%m%d",
			'TT_DATE_FORMAT'  => JText::_('JLIB_HTML_BEHAVIOR_TT_DATE_FORMAT'),
			'WK'              => JText::_('JLIB_HTML_BEHAVIOR_WK'),
			'TIME'            => JText::_('JLIB_HTML_BEHAVIOR_TIME'),
		);

		return 'Calendar._DN = ' . json_encode($weekdays_full) . ';'
			. ' Calendar._SDN = ' . json_encode($weekdays_short) . ';'
			. ' Calendar._FD = 0;'
			. ' Calendar._MN = ' . json_encode($months_long) . ';'
			. ' Calendar._SMN = ' . json_encode($months_short) . ';'
			. ' Calendar._TT = ' . json_encode($text) . ';';
	}
}
cms/html/rules.php000064400000020101152177723700010142 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

JLog::add('JHtmlRules is deprecated.', JLog::WARNING, 'deprecated');

/**
 * Extended Utility class for all HTML drawing classes.
 *
 * @since       1.6
 * @deprecated  4.0
 */
abstract class JHtmlRules
{
	/**
	 * Creates the HTML for the permissions widget
	 *
	 * @param   array    $actions   Array of action objects
	 * @param   integer  $assetId   Id of a specific asset to  create a widget for.
	 * @param   integer  $parent    Id of the parent of the asset
	 * @param   string   $control   The form control
	 * @param   string   $idPrefix  Prefix for the ids assigned to specific action-group pairs
	 *
	 * @return  string   HTML for the permissions widget
	 *
	 * @see     JAccess
	 * @see     JFormFieldRules
	 * @since   1.6
	 * @deprecated  4.0
	 */
	public static function assetFormWidget($actions, $assetId = null, $parent = null, $control = 'jform[rules]', $idPrefix = 'jform_rules')
	{
		$images = static::_getImagesArray();

		// Get the user groups.
		$groups = static::_getUserGroups();

		// Get the incoming inherited rules as well as the asset specific rules.
		$inheriting = JAccess::getAssetRules($parent ?: static::_getParentAssetId($assetId), true);
		$inherited = JAccess::getAssetRules($assetId, true);
		$rules = JAccess::getAssetRules($assetId);

		$html = array();

		$html[] = '<div class="acl-options">';
		$html[] = JHtml::_('tabs.start', 'acl-rules-' . $assetId, array('useCookie' => 1));
		$html[] = JHtml::_('tabs.panel', JText::_('JLIB_HTML_ACCESS_SUMMARY'), 'summary');
		$html[] = '			<p>' . JText::_('JLIB_HTML_ACCESS_SUMMARY_DESC') . '</p>';
		$html[] = '			<table class="aclsummary-table" summary="' . JText::_('JLIB_HTML_ACCESS_SUMMARY_DESC') . '">';
		$html[] = '			<caption>' . JText::_('JLIB_HTML_ACCESS_SUMMARY_DESC_CAPTION') . '</caption>';
		$html[] = '			<tr>';
		$html[] = '				<th class="col1 hidelabeltxt">' . JText::_('JLIB_RULES_GROUPS') . '</th>';

		foreach ($actions as $i => $action)
		{
			$html[] = '				<th class="col' . ($i + 2) . '">' . JText::_($action->title) . '</th>';
		}

		$html[] = '			</tr>';

		foreach ($groups as $i => $group)
		{
			$html[] = '			<tr class="row' . ($i % 2) . '">';
			$html[] = '				<td class="col1">' . $group->text . '</td>';

			foreach ($actions as $j => $action)
			{
				$html[] = '				<td class="col' . ($j + 2) . '">'
					. ($assetId ? ($inherited->allow($action->name, $group->identities) ? $images['allow'] : $images['deny'])
					: ($inheriting->allow($action->name, $group->identities) ? $images['allow'] : $images['deny'])) . '</td>';
			}

			$html[] = '			</tr>';
		}

		$html[] = ' 		</table>';

		foreach ($actions as $action)
		{
			$actionTitle = JText::_($action->title);
			$actionDesc = JText::_($action->description);
			$html[] = JHtml::_('tabs.panel', $actionTitle, $action->name);
			$html[] = '			<p>' . $actionDesc . '</p>';
			$html[] = '			<table class="aclmodify-table" summary="' . strip_tags($actionDesc) . '">';
			$html[] = '			<caption>' . JText::_('JLIB_HTML_ACCESS_MODIFY_DESC_CAPTION_ACL') . ' ' . $actionTitle . ' '
				. JText::_('JLIB_HTML_ACCESS_MODIFY_DESC_CAPTION_TABLE') . '</caption>';
			$html[] = '			<tr>';
			$html[] = '				<th class="col1 hidelabeltxt">' . JText::_('JLIB_RULES_GROUP') . '</th>';
			$html[] = '				<th class="col2">' . JText::_('JLIB_RULES_INHERIT') . '</th>';
			$html[] = '				<th class="col3 hidelabeltxt">' . JText::_('JMODIFY') . '</th>';
			$html[] = '				<th class="col4">' . JText::_('JCURRENT') . '</th>';
			$html[] = '			</tr>';

			foreach ($groups as $i => $group)
			{
				$selected = $rules->allow($action->name, $group->value);

				$html[] = '			<tr class="row' . ($i % 2) . '">';
				$html[] = '				<td class="col1">' . $group->text . '</td>';
				$html[] = '				<td class="col2">'
					. ($inheriting->allow($action->name, $group->identities) ? $images['allow-i'] : $images['deny-i']) . '</td>';
				$html[] = '				<td class="col3">';
				$html[] = '					<select id="' . $idPrefix . '_' . $action->name . '_' . $group->value
					. '" class="inputbox" size="1" name="' . $control . '[' . $action->name . '][' . $group->value . ']" title="'
					. JText::sprintf('JLIB_RULES_SELECT_ALLOW_DENY_GROUP', $actionTitle, $group->text) . '">';
				$html[] = '						<option value=""' . ($selected === null ? ' selected="selected"' : '') . '>'
					. JText::_('JLIB_RULES_INHERIT') . '</option>';
				$html[] = '						<option value="1"' . ($selected === true ? ' selected="selected"' : '') . '>'
					. JText::_('JLIB_RULES_ALLOWED') . '</option>';
				$html[] = '						<option value="0"' . ($selected === false ? ' selected="selected"' : '') . '>'
					. JText::_('JLIB_RULES_DENIED') . '</option>';
				$html[] = '					</select>';
				$html[] = '				</td>';
				$html[] = '				<td class="col4">'
					. ($assetId ? ($inherited->allow($action->name, $group->identities) ? $images['allow'] : $images['deny'])
					: ($inheriting->allow($action->name, $group->identities) ? $images['allow'] : $images['deny'])) . '</td>';
				$html[] = '			</tr>';
			}

			$html[] = '			</table>';
		}

		$html[] = JHtml::_('tabs.end');

		// Build the footer with legend and special purpose buttons.
		$html[] = '	<div class="clr"></div>';
		$html[] = '	<ul class="acllegend fltlft">';
		$html[] = '		<li class="acl-allowed">' . JText::_('JLIB_RULES_ALLOWED') . '</li>';
		$html[] = '		<li class="acl-denied">' . JText::_('JLIB_RULES_DENIED') . '</li>';
		$html[] = '	</ul>';
		$html[] = '</div>';

		return implode("\n", $html);
	}

	/**
	 * Get the id of the parent asset
	 *
	 * @param   integer  $assetId  The asset for which the parentid will be returned
	 *
	 * @return  integer  The id of the parent asset
	 *
	 * @since   1.6
	 * @deprecated  4.0
	 */
	protected static function _getParentAssetId($assetId)
	{
		// Get a database object.
		$db = JFactory::getDbo();
		$query = $db->getQuery(true);

		// Get the user groups from the database.
		$query->select($db->quoteName('parent_id'))
			->from($db->quoteName('#__assets'))
			->where($db->quoteName('id') . ' = ' . (int) $assetId);
		$db->setQuery($query);

		return (int) $db->loadResult();
	}

	/**
	 * Get the user groups
	 *
	 * @return  array  Array of user groups
	 *
	 * @since   1.6
	 * @deprecated  4.0
	 */
	protected static function _getUserGroups()
	{
		// Get a database object.
		$db = JFactory::getDbo();

		// Get the user groups from the database.
		$db->setQuery(
			'SELECT a.id AS value, a.title AS text, b.id as parent'
			. ' FROM #__usergroups AS a LEFT JOIN #__usergroups AS b ON a.lft >= b.lft AND a.rgt <= b.rgt'
			. ' ORDER BY a.lft ASC, b.lft ASC'
		);
		$result = $db->loadObjectList();
		$options = array();

		// Pre-compute additional values.
		foreach ($result as $option)
		{
			$end = end($options);

			if ($end === false || $end->value != $option->value)
			{
				$end = $option;
				$end->level = 0;
				$options[] = $end;
			}
			else
			{
				$end->level++;
			}

			$end->identities[] = $option->parent;
		}

		return $options;
	}

	/**
	 * Get the array of images associate with specific permissions
	 *
	 * @return  array  An associative  array of permissions and images
	 *
	 * @since   1.6
	 * @deprecated  4.0
	 */
	protected static function _getImagesArray()
	{
		$images['allow-l'] = '<label class="icon-16-allow" title="' . JText::_('JLIB_RULES_ALLOWED') . '">' . JText::_('JLIB_RULES_ALLOWED')
			. '</label>';
		$images['deny-l'] = '<label class="icon-16-deny" title="' . JText::_('JLIB_RULES_DENIED') . '">' . JText::_('JLIB_RULES_DENIED') . '</label>';
		$images['allow'] = '<a class="icon-16-allow" title="' . JText::_('JLIB_RULES_ALLOWED') . '"> </a>';
		$images['deny'] = '<a class="icon-16-deny" title="' . JText::_('JLIB_RULES_DENIED') . '"> </a>';
		$images['allow-i'] = '<a class="icon-16-allowinactive" title="' . JText::_('JRULE_ALLOWED_INHERITED') . '"> </a>';
		$images['deny-i'] = '<a class="icon-16-denyinactive" title="' . JText::_('JRULE_DENIED_INHERITED') . '"> </a>';

		return $images;
	}
}
cms/html/batch.php000064400000006220152177723700010077 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Extended Utility class for batch processing widgets.
 *
 * @since       1.7
 *
 * @deprecated  4.0 Use JLayout directly
 */
abstract class JHtmlBatch
{
	/**
	 * Display a batch widget for the access level selector.
	 *
	 * @return  string  The necessary HTML for the widget.
	 *
	 * @since       1.7
	 *
	 * @deprecated  4.0 instead of JHtml::_('batch.access'); use JLayoutHelper::render('joomla.html.batch.access', array());
	 */
	public static function access()
	{
		JLog::add('The use of JHtml::_("batch.access") is deprecated use JLayout instead.', JLog::WARNING, 'deprecated');

		return JLayoutHelper::render('joomla.html.batch.access', array());
	}

	/**
	 * Displays a batch widget for moving or copying items.
	 *
	 * @param   string  $extension  The extension that owns the category.
	 *
	 * @return  string  The necessary HTML for the widget.
	 *
	 * @since       1.7
	 *
	 * @deprecated  4.0 instead of JHtml::_('batch.item'); use JLayoutHelper::render('joomla.html.batch.item', array('extension' => 'com_XXX'));
	 */
	public static function item($extension)
	{
		$displayData = array('extension' => $extension);

		JLog::add('The use of JHtml::_("batch.item") is deprecated use JLayout instead.', JLog::WARNING, 'deprecated');

		return JLayoutHelper::render('joomla.html.batch.item', $displayData);
	}

	/**
	 * Display a batch widget for the language selector.
	 *
	 * @return  string  The necessary HTML for the widget.
	 *
	 * @since       2.5
	 *
	 * @deprecated  4.0 instead of JHtml::_('batch.language'); use JLayoutHelper::render('joomla.html.batch.language', array());
	 */
	public static function language()
	{
		JLog::add('The use of JHtml::_("batch.language") is deprecated use JLayout instead.', JLog::WARNING, 'deprecated');

		return JLayoutHelper::render('joomla.html.batch.language', array());
	}

	/**
	 * Display a batch widget for the user selector.
	 *
	 * @param   boolean  $noUser  Choose to display a "no user" option
	 *
	 * @return  string  The necessary HTML for the widget.
	 *
	 * @since       2.5
	 *
	 * @deprecated  4.0 instead of JHtml::_('batch.user'); use JLayoutHelper::render('joomla.html.batch.user', array());
	 */
	public static function user($noUser = true)
	{
		$displayData = array('noUser' => $noUser);

		JLog::add('The use of JHtml::_("batch.user") is deprecated use JLayout instead.', JLog::WARNING, 'deprecated');

		return JLayoutHelper::render('joomla.html.batch.user', $displayData);
	}

	/**
	 * Display a batch widget for the tag selector.
	 *
	 * @return  string  The necessary HTML for the widget.
	 *
	 * @since       3.1
	 *
	 * @deprecated  4.0 instead of JHtml::_('batch.tag'); use JLayoutHelper::render('joomla.html.batch.tag', array());
	 */
	public static function tag()
	{
		JLog::add('The use of JHtml::_("batch.tag") is deprecated use JLayout instead.', JLog::WARNING, 'deprecated');

		return JLayoutHelper::render('joomla.html.batch.tag', array());
	}
}
cms/html/string.php000064400000022340152177723700010325 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\String\StringHelper;

/**
 * HTML helper class for rendering manipulated strings.
 *
 * @since  1.6
 */
abstract class JHtmlString
{
	/**
	 * Truncates text blocks over the specified character limit and closes
	 * all open HTML tags. The method will optionally not truncate an individual
	 * word, it will find the first space that is within the limit and
	 * truncate at that point. This method is UTF-8 safe.
	 *
	 * @param   string   $text       The text to truncate.
	 * @param   integer  $length     The maximum length of the text.
	 * @param   boolean  $noSplit    Don't split a word if that is where the cutoff occurs (default: true).
	 * @param   boolean  $allowHtml  Allow HTML tags in the output, and close any open tags (default: true).
	 *
	 * @return  string   The truncated text.
	 *
	 * @since   1.6
	 */
	public static function truncate($text, $length = 0, $noSplit = true, $allowHtml = true)
	{
		// Assume a lone open tag is invalid HTML.
		if ($length === 1 && $text[0] === '<')
		{
			return '...';
		}

		// Check if HTML tags are allowed.
		if (!$allowHtml)
		{
			// Deal with spacing issues in the input.
			$text = str_replace('>', '> ', $text);
			$text = str_replace(array('&nbsp;', '&#160;'), ' ', $text);
			$text = StringHelper::trim(preg_replace('#\s+#mui', ' ', $text));

			// Strip the tags from the input and decode entities.
			$text = strip_tags($text);
			$text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');

			// Remove remaining extra spaces.
			$text = str_replace('&nbsp;', ' ', $text);
			$text = StringHelper::trim(preg_replace('#\s+#mui', ' ', $text));
		}

		// Whether or not allowing HTML, truncate the item text if it is too long.
		if ($length > 0 && StringHelper::strlen($text) > $length)
		{
			$tmp = trim(StringHelper::substr($text, 0, $length));

			if ($tmp[0] === '<' && strpos($tmp, '>') === false)
			{
				return '...';
			}

			// $noSplit true means that we do not allow splitting of words.
			if ($noSplit)
			{
				// Find the position of the last space within the allowed length.
				$offset = StringHelper::strrpos($tmp, ' ');
				$tmp = StringHelper::substr($tmp, 0, $offset + 1);

				// If there are no spaces and the string is longer than the maximum
				// we need to just use the ellipsis. In that case we are done.
				if ($offset === false && strlen($text) > $length)
				{
					return '...';
				}

				if (StringHelper::strlen($tmp) > $length - 3)
				{
					$tmp = trim(StringHelper::substr($tmp, 0, StringHelper::strrpos($tmp, ' ')));
				}
			}

			if ($allowHtml)
			{
				// Put all opened tags into an array
				preg_match_all("#<([a-z][a-z0-9]*)\b.*?(?!/)>#i", $tmp, $result);
				$openedTags = $result[1];

				// Some tags self close so they do not need a separate close tag.
				$openedTags = array_diff($openedTags, array('img', 'hr', 'br'));
				$openedTags = array_values($openedTags);

				// Put all closed tags into an array
				preg_match_all("#</([a-z][a-z0-9]*)\b(?:[^>]*?)>#iU", $tmp, $result);
				$closedTags = $result[1];

				$numOpened = count($openedTags);

				// Not all tags are closed so trim the text and finish.
				if (count($closedTags) !== $numOpened)
				{
					// Closing tags need to be in the reverse order of opening tags.
					$openedTags = array_reverse($openedTags);

					// Close tags
					for ($i = 0; $i < $numOpened; $i++)
					{
						if (!in_array($openedTags[$i], $closedTags))
						{
							$tmp .= '</' . $openedTags[$i] . '>';
						}
						else
						{
							unset($closedTags[array_search($openedTags[$i], $closedTags)]);
						}
					}
				}

				// Check if we are within a tag
				if (StringHelper::strrpos($tmp, '<') > StringHelper::strrpos($tmp, '>'))
				{
					$offset = StringHelper::strrpos($tmp, '<');
					$tmp = StringHelper::trim(StringHelper::substr($tmp, 0, $offset));
				}
			}

			if ($tmp === false || strlen($text) > strlen($tmp))
			{
				$text = trim($tmp) . '...';
			}
		}

		// Clean up any internal spaces created by the processing.
		$text = str_replace(' </', '</', $text);
		$text = str_replace(' ...', '...', $text);

		return $text;
	}

	/**
	 * Method to extend the truncate method to more complex situations
	 *
	 * The goal is to get the proper length plain text string with as much of
	 * the html intact as possible with all tags properly closed.
	 *
	 * @param   string   $html       The content of the introtext to be truncated
	 * @param   integer  $maxLength  The maximum number of characters to render
	 * @param   boolean  $noSplit    Don't split a word if that is where the cutoff occurs (default: true).
	 *
	 * @return  string  The truncated string. If the string is truncated an ellipsis
	 *                  (...) will be appended.
	 *
	 * @note    If a maximum length of 3 or less is selected and the text has more than
	 *          that number of characters an ellipsis will be displayed.
	 *          This method will not create valid HTML from malformed HTML.
	 *
	 * @since   3.1
	 */
	public static function truncateComplex($html, $maxLength = 0, $noSplit = true)
	{
		// Start with some basic rules.
		$baseLength = strlen($html);

		// If the original HTML string is shorter than the $maxLength do nothing and return that.
		if ($baseLength <= $maxLength || $maxLength === 0)
		{
			return $html;
		}

		// Take care of short simple cases.
		if ($maxLength <= 3 && $html[0] !== '<' && strpos(substr($html, 0, $maxLength - 1), '<') === false && $baseLength > $maxLength)
		{
			return '...';
		}

		// Deal with maximum length of 1 where the string starts with a tag.
		if ($maxLength === 1 && $html[0] === '<')
		{
			$endTagPos = strlen(strstr($html, '>', true));
			$tag = substr($html, 1, $endTagPos);

			$l = $endTagPos + 1;

			if ($noSplit)
			{
				return substr($html, 0, $l) . '</' . $tag . '...';
			}

			// TODO: $character doesn't seem to be used...
			$character = substr(strip_tags($html), 0, 1);

			return substr($html, 0, $l) . '</' . $tag . '...';
		}

		// First get the truncated plain text string. This is the rendered text we want to end up with.
		$ptString = JHtml::_('string.truncate', $html, $maxLength, $noSplit, $allowHtml = false);

		// It's all HTML, just return it.
		if ($ptString === '')
		{
				return $html;
		}

		// If the plain text is shorter than the max length the variable will not end in ...
		// In that case we use the whole string.
		if (substr($ptString, -3) !== '...')
		{
				return $html;
		}

		// Regular truncate gives us the ellipsis but we want to go back for text and tags.
		if ($ptString === '...')
		{
			$stripped = substr(strip_tags($html), 0, $maxLength);
			$ptString = JHtml::_('string.truncate', $stripped, $maxLength, $noSplit, $allowHtml = false);
		}

		// We need to trim the ellipsis that truncate adds.
		$ptString = rtrim($ptString, '.');

		// Now deal with more complex truncation.
		while ($maxLength <= $baseLength)
		{
			// Get the truncated string assuming HTML is allowed.
			$htmlString = JHtml::_('string.truncate', $html, $maxLength, $noSplit, $allowHtml = true);

			if ($htmlString === '...' && strlen($ptString) + 3 > $maxLength)
			{
				return $htmlString;
			}

			$htmlString = rtrim($htmlString, '.');

			// Now get the plain text from the HTML string and trim it.
			$htmlStringToPtString = JHtml::_('string.truncate', $htmlString, $maxLength, $noSplit, $allowHtml = false);
			$htmlStringToPtString = rtrim($htmlStringToPtString, '.');

			// If the new plain text string matches the original plain text string we are done.
			if ($ptString === $htmlStringToPtString)
			{
				return $htmlString . '...';
			}

			// Get the number of HTML tag characters in the first $maxLength characters
			$diffLength = strlen($ptString) - strlen($htmlStringToPtString);

			if ($diffLength <= 0)
			{
				return $htmlString . '...';
			}

			// Set new $maxlength that adjusts for the HTML tags
			$maxLength += $diffLength;
		}
	}

	/**
	 * Abridges text strings over the specified character limit. The
	 * behavior will insert an ellipsis into the text replacing a section
	 * of variable size to ensure the string does not exceed the defined
	 * maximum length. This method is UTF-8 safe.
	 *
	 * For example, it transforms "Really long title" to "Really...title".
	 *
	 * Note that this method does not scan for HTML tags so will potentially break them.
	 *
	 * @param   string   $text    The text to abridge.
	 * @param   integer  $length  The maximum length of the text (default is 50).
	 * @param   integer  $intro   The maximum length of the intro text (default is 30).
	 *
	 * @return  string   The abridged text.
	 *
	 * @since   1.6
	 */
	public static function abridge($text, $length = 50, $intro = 30)
	{
		// Abridge the item text if it is too long.
		if (StringHelper::strlen($text) > $length)
		{
			// Determine the remaining text length.
			$remainder = $length - ($intro + 3);

			// Extract the beginning and ending text sections.
			$beg = StringHelper::substr($text, 0, $intro);
			$end = StringHelper::substr($text, StringHelper::strlen($text) - $remainder);

			// Build the resulting string.
			$text = $beg . '...' . $end;
		}

		return $text;
	}
}
cms/html/sliders.php000064400000010406152177723700010464 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for Sliders elements
 *
 * @since       1.6
 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
 */
abstract class JHtmlSliders
{
	/**
	 * Creates a panes and loads the javascript behavior for it.
	 *
	 * @param   string  $group   The pane identifier.
	 * @param   array   $params  An array of options.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function start($group = 'sliders', $params = array())
	{
		static::loadBehavior($group, $params);

		return '<div id="' . $group . '" class="pane-sliders"><div style="display:none;"><div>';
	}

	/**
	 * Close the current pane.
	 *
	 * @return  string  hTML to close the pane
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function end()
	{
		return '</div></div></div>';
	}

	/**
	 * Begins the display of a new panel.
	 *
	 * @param   string  $text  Text to display.
	 * @param   string  $id    Identifier of the panel.
	 *
	 * @return  string  HTML to start a panel
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function panel($text, $id)
	{
		return '</div></div><div class="panel"><h3 class="pane-toggler title" id="' . $id . '"><a href="javascript:void(0);"><span>' . $text
			. '</span></a></h3><div class="pane-slider content">';
	}

	/**
	 * Load the JavaScript behavior.
	 *
	 * @param   string  $group   The pane identifier.
	 * @param   array   $params  Array of options.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	protected static function loadBehavior($group, $params = array())
	{
		static $loaded = array();

		if (!array_key_exists($group, $loaded))
		{
			// Get the JInput object
			$input = JFactory::getApplication()->input;

			$loaded[$group] = true;

			// Include mootools framework.
			JHtml::_('behavior.framework', true);

			$document = JFactory::getDocument();

			$display = (isset($params['startOffset']) && isset($params['startTransition']) && $params['startTransition'])
				? (int) $params['startOffset'] : null;
			$show = (isset($params['startOffset']) && !(isset($params['startTransition']) && $params['startTransition']))
				? (int) $params['startOffset'] : null;

			$opt['onActive'] = "\\function(toggler, i) {toggler.addClass('pane-toggler-down');" .
				"toggler.removeClass('pane-toggler');i.addClass('pane-down');i.removeClass('pane-hide');Cookie.write('jpanesliders_"
				. $group . "',$$('div#" . $group . ".pane-sliders > .panel > h3').indexOf(toggler));}";
			$opt['onBackground'] = "\\function(toggler, i) {toggler.addClass('pane-toggler');" .
				"toggler.removeClass('pane-toggler-down');i.addClass('pane-hide');i.removeClass('pane-down');if($$('div#"
				. $group . ".pane-sliders > .panel > h3').length==$$('div#" . $group
				. ".pane-sliders > .panel > h3.pane-toggler').length) Cookie.write('jpanesliders_" . $group . "',-1);}";
			$opt['duration'] = isset($params['duration']) ? (int) $params['duration'] : 300;
			$opt['display'] = (isset($params['useCookie']) && $params['useCookie']) ? $input->cookie->get('jpanesliders_' . $group, $display, 'integer')
				: $display;
			$opt['show'] = (isset($params['useCookie']) && $params['useCookie']) ? $input->cookie->get('jpanesliders_' . $group, $show, 'integer') : $show;
			$opt['opacity'] = (isset($params['opacityTransition']) && $params['opacityTransition']) ? 'true' : 'false';
			$opt['alwaysHide'] = (isset($params['allowAllClose']) && (!$params['allowAllClose'])) ? 'false' : 'true';

			$options = JHtml::getJSObject($opt);

			$js = "window.addEvent('domready', function(){ new Fx.Accordion($$('div#" . $group
				. ".pane-sliders > .panel > h3.pane-toggler'), $$('div#" . $group . ".pane-sliders > .panel > div.pane-slider'), " . $options
				. "); });";

			$document->addScriptDeclaration($js);
		}
	}
}
cms/html/menu.php000064400000023160152177723700007764 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class working with menu select lists
 *
 * @since  1.5
 */
abstract class JHtmlMenu
{
	/**
	 * Cached array of the menus.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected static $menus = array();

	/**
	 * Cached array of the menus items.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected static $items = array();

	/**
	 * Get a list of the available menus.
	 *
	 * @param   int  $clientId  The client id
	 *
	 * @return  array
	 *
	 * @since   1.6
	 */
	public static function menus($clientId = 0)
	{
		$key = serialize($clientId);

		if (!isset(static::$menus[$key]))
		{
			$db = JFactory::getDbo();

			$query = $db->getQuery(true)
				->select($db->qn(array('id', 'menutype', 'title', 'client_id'), array('id', 'value', 'text', 'client_id')))
				->from($db->quoteName('#__menu_types'))
				->order('client_id, title');

			if (isset($clientId))
			{
				$query->where('client_id = ' . (int) $clientId);
			}

			static::$menus[$key] = $db->setQuery($query)->loadObjectList();
		}

		return static::$menus[$key];
	}

	/**
	 * Returns an array of menu items grouped by menu.
	 *
	 * @param   array  $config  An array of configuration options [published, checkacl, clientid].
	 *
	 * @return  array
	 *
	 * @since   1.6
	 */
	public static function menuItems($config = array())
	{
		$key = serialize($config);

		if (empty(static::$items[$key]))
		{
			// B/C - not passed  = 0, null can be passed for both clients
			$clientId = array_key_exists('clientid', $config) ? $config['clientid'] : 0;
			$menus    = static::menus($clientId);

			$db    = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('a.id AS value, a.title AS text, a.level, a.menutype, a.client_id')
				->from('#__menu AS a')
				->where('a.parent_id > 0');

			// Filter on the client id
			if (isset($clientId))
			{
				$query->where('a.client_id = ' . (int) $clientId);
			}

			// Filter on the published state
			if (isset($config['published']))
			{
				if (is_numeric($config['published']))
				{
					$query->where('a.published = ' . (int) $config['published']);
				}
				elseif ($config['published'] === '')
				{
					$query->where('a.published IN (0,1)');
				}
			}

			$query->order('a.lft');

			$db->setQuery($query);
			$items = $db->loadObjectList();

			// Collate menu items based on menutype
			$lookup = array();

			foreach ($items as &$item)
			{
				if (!isset($lookup[$item->menutype]))
				{
					$lookup[$item->menutype] = array();
				}

				$lookup[$item->menutype][] = &$item;

				// Translate the menu item title when client is administrator
				if ($clientId === 1)
				{
					$item->text = JText::_($item->text);
				}

				$item->text = str_repeat('- ', $item->level) . $item->text;
			}

			static::$items[$key] = array();

			$user = JFactory::getUser();

			$aclcheck = !empty($config['checkacl']) ? (int) $config['checkacl'] : 0;

			foreach ($menus as &$menu)
			{
				if ($aclcheck)
				{
					$action = $aclcheck == $menu->id ? 'edit' : 'create';

					if (!$user->authorise('core.' . $action, 'com_menus.menu.' . $menu->id))
					{
						continue;
					}
				}

				// Start group:
				static::$items[$key][] = JHtml::_('select.optgroup', $menu->text);

				// Special "Add to this Menu" option:
				static::$items[$key][] = JHtml::_('select.option', $menu->value . '.1', JText::_('JLIB_HTML_ADD_TO_THIS_MENU'));

				// Menu items:
				if (isset($lookup[$menu->value]))
				{
					foreach ($lookup[$menu->value] as &$item)
					{
						static::$items[$key][] = JHtml::_('select.option', $menu->value . '.' . $item->value, $item->text);
					}
				}

				// Finish group:
				static::$items[$key][] = JHtml::_('select.optgroup', $menu->text);
			}
		}

		return static::$items[$key];
	}

	/**
	 * Displays an HTML select list of menu items.
	 *
	 * @param   string  $name      The name of the control.
	 * @param   string  $selected  The value of the selected option.
	 * @param   string  $attribs   Attributes for the control.
	 * @param   array   $config    An array of options for the control [id, published, checkacl, clientid].
	 *
	 * @return  string
	 *
	 * @since   1.6
	 */
	public static function menuItemList($name, $selected = null, $attribs = null, $config = array())
	{
		static $count;

		$options = static::menuItems($config);

		return JHtml::_(
			'select.genericlist', $options, $name,
			array(
				'id'             => isset($config['id']) ? $config['id'] : 'assetgroups_' . (++$count),
				'list.attr'      => $attribs === null ? 'class="inputbox" size="1"' : $attribs,
				'list.select'    => (int) $selected,
				'list.translate' => false,
			)
		);
	}

	/**
	 * Build the select list for Menu Ordering
	 *
	 * @param   object   &$row  The row object
	 * @param   integer  $id    The id for the row. Must exist to enable menu ordering
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function ordering(&$row, $id)
	{
		if ($id)
		{
			$db = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('ordering AS value, title AS text')
				->from($db->quoteName('#__menu'))
				->where($db->quoteName('menutype') . ' = ' . $db->quote($row->menutype))
				->where($db->quoteName('parent_id') . ' = ' . (int) $row->parent_id)
				->where($db->quoteName('published') . ' != -2')
				->order('ordering');
			$order = JHtml::_('list.genericordering', $query);
			$ordering = JHtml::_(
				'select.genericlist', $order, 'ordering',
				array('list.attr' => 'class="inputbox" size="1"', 'list.select' => (int) $row->ordering)
			);
		}
		else
		{
			$ordering = '<input type="hidden" name="ordering" value="' . $row->ordering . '" />' . JText::_('JGLOBAL_NEWITEMSLAST_DESC');
		}

		return $ordering;
	}

	/**
	 * Build the multiple select list for Menu Links/Pages
	 *
	 * @param   boolean  $all         True if all can be selected
	 * @param   boolean  $unassigned  True if unassigned can be selected
	 * @param   int      $clientId    The client id
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function linkOptions($all = false, $unassigned = false, $clientId = 0)
	{
		$db = JFactory::getDbo();

		// Get a list of the menu items
		$query = $db->getQuery(true)
			->select('m.id, m.parent_id, m.title, m.menutype, m.client_id')
			->from($db->quoteName('#__menu') . ' AS m')
			->where($db->quoteName('m.published') . ' = 1')
			->order('m.client_id, m.menutype, m.parent_id');

		if (isset($clientId))
		{
			$query->where('m.client_id = ' . (int) $clientId);
		}

		$db->setQuery($query);

		$mitems = $db->loadObjectList();

		if (!$mitems)
		{
			$mitems = array();
		}

		// Establish the hierarchy of the menu
		$children = array();

		// First pass - collect children
		foreach ($mitems as $v)
		{
			$pt            = $v->parent_id;
			$list          = @$children[$pt] ? $children[$pt] : array();
			$list[]        = $v;
			$children[$pt] = $list;
		}

		// Second pass - get an indent list of the items
		$list = static::treerecurse((int) $mitems[0]->parent_id, '', array(), $children, 9999, 0, 0);

		// Code that adds menu name to Display of Page(s)
		$mitems = array();

		if ($all | $unassigned)
		{
			$mitems[] = JHtml::_('select.option', '<OPTGROUP>', JText::_('JOPTION_MENUS'));

			if ($all)
			{
				$mitems[] = JHtml::_('select.option', 0, JText::_('JALL'));
			}

			if ($unassigned)
			{
				$mitems[] = JHtml::_('select.option', -1, JText::_('JOPTION_UNASSIGNED'));
			}

			$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
		}

		$lastMenuType = null;
		$tmpMenuType  = null;

		foreach ($list as $list_a)
		{
			if ($list_a->menutype != $lastMenuType)
			{
				if ($tmpMenuType)
				{
					$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
				}

				$mitems[]     = JHtml::_('select.option', '<OPTGROUP>', $list_a->menutype);
				$lastMenuType = $list_a->menutype;
				$tmpMenuType  = $list_a->menutype;
			}

			$mitems[] = JHtml::_('select.option', $list_a->id, $list_a->title);
		}

		if ($lastMenuType !== null)
		{
			$mitems[] = JHtml::_('select.option', '</OPTGROUP>');
		}

		return $mitems;
	}

	/**
	 * Build the list representing the menu tree
	 *
	 * @param   integer  $id         Id of the menu item
	 * @param   string   $indent     The indentation string
	 * @param   array    $list       The list to process
	 * @param   array    &$children  The children of the current item
	 * @param   integer  $maxlevel   The maximum number of levels in the tree
	 * @param   integer  $level      The starting level
	 * @param   int      $type       Set the type of spacer to use. Use 1 for |_ or 0 for -
	 *
	 * @return  array
	 *
	 * @since   1.5
	 */
	public static function treerecurse($id, $indent, $list, &$children, $maxlevel = 9999, $level = 0, $type = 1)
	{
		if ($level <= $maxlevel && isset($children[$id]) && is_array($children[$id]))
		{
			if ($type)
			{
				$pre    = '<sup>|_</sup>&#160;';
				$spacer = '.&#160;&#160;&#160;&#160;&#160;&#160;';
			}
			else
			{
				$pre    = '- ';
				$spacer = '&#160;&#160;';
			}

			foreach ($children[$id] as $v)
			{
				$id = $v->id;

				if ($v->parent_id == 0)
				{
					$txt = $v->title;
				}
				else
				{
					$txt = $pre . $v->title;
				}

				$list[$id]           = $v;
				$list[$id]->treename = $indent . $txt;

				if (isset($children[$id]) && is_array($children[$id]))
				{
					$list[$id]->children = count($children[$id]);
					$list                = static::treerecurse($id, $indent . $spacer, $list, $children, $maxlevel, $level + 1, $type);
				}
				else
				{
					$list[$id]->children = 0;
				}
			}
		}

		return $list;
	}
}
cms/html/jquery.php000064400000007251152177723700010342 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for jQuery JavaScript behaviors
 *
 * @since  3.0
 */
abstract class JHtmlJquery
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.0
	 */
	protected static $loaded = array();

	/**
	 * Method to load the jQuery JavaScript framework into the document head
	 *
	 * If debugging mode is on an uncompressed version of jQuery is included for easier debugging.
	 *
	 * @param   boolean  $noConflict  True to load jQuery in noConflict mode [optional]
	 * @param   mixed    $debug       Is debugging mode on? [optional]
	 * @param   boolean  $migrate     True to enable the jQuery Migrate plugin
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function framework($noConflict = true, $debug = null, $migrate = true)
	{
		// Only load once
		if (!empty(static::$loaded[__METHOD__]))
		{
			return;
		}

		// If no debugging value is set, use the configuration setting
		if ($debug === null)
		{
			$debug = (boolean) JFactory::getConfig()->get('debug');
		}

		JHtml::_('script', 'jui/jquery.min.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));

		// Check if we are loading in noConflict
		if ($noConflict)
		{
			JHtml::_('script', 'jui/jquery-noconflict.js', array('version' => 'auto', 'relative' => true));
		}

		// Check if we are loading Migrate
		if ($migrate)
		{
			JHtml::_('script', 'jui/jquery-migrate.min.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));
		}

		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Method to load the jQuery UI JavaScript framework into the document head
	 *
	 * If debugging mode is on an uncompressed version of jQuery UI is included for easier debugging.
	 *
	 * @param   array  $components  The jQuery UI components to load [optional]
	 * @param   mixed  $debug       Is debugging mode on? [optional]
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function ui(array $components = array('core'), $debug = null)
	{
		// Set an array containing the supported jQuery UI components handled by this method
		$supported = array('core', 'sortable');

		// Include jQuery
		static::framework();

		// If no debugging value is set, use the configuration setting
		if ($debug === null)
		{
			$debug = JDEBUG;
		}

		// Load each of the requested components
		foreach ($components as $component)
		{
			// Only attempt to load the component if it's supported in core and hasn't already been loaded
			if (in_array($component, $supported) && empty(static::$loaded[__METHOD__][$component]))
			{
				JHtml::_('script', 'jui/jquery.ui.' . $component . '.min.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));
				static::$loaded[__METHOD__][$component] = true;
			}
		}

		return;
	}

	/**
	 * Auto set CSRF token to ajaxSetup so all jQuery ajax call will contains CSRF token.
	 *
	 * @param   string  $name  The CSRF meta tag name.
	 *
	 * @return  void
	 *
	 * @throws  \InvalidArgumentException
	 *
	 * @since   3.8.0
	 */
	public static function token($name = 'csrf.token')
	{
		// Only load once
		if (!empty(static::$loaded[__METHOD__][$name]))
		{
			return;
		}

		static::framework();
		JHtml::_('form.csrf', $name);

		$doc = JFactory::getDocument();

		$doc->addScriptDeclaration(
<<<JS
;(function ($) {
	$.ajaxSetup({
		headers: {
			'X-CSRF-Token': Joomla.getOptions('$name')
		}
	});
})(jQuery);
JS
		);

		static::$loaded[__METHOD__][$name] = true;
	}
}
cms/html/bootstrap.php000064400000102166152177723700011041 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for Bootstrap elements.
 *
 * @since  3.0
 */
abstract class JHtmlBootstrap
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.0
	 */
	protected static $loaded = array();

	/**
	 * Add javascript support for the Bootstrap affix plugin
	 *
	 * @param   string  $selector  Unique selector for the element to be affixed.
	 * @param   array   $params    An array of options.
	 *                             Options for the affix plugin can be:
	 *                             - offset  number|function|object  Pixels to offset from screen when calculating position of scroll.
	 *                                                               If a single number is provided, the offset will be applied in both top
	 *                                                               and left directions. To listen for a single direction, or multiple
	 *                                                               unique offsets, just provide an object offset: { x: 10 }.
	 *                                                               Use a function when you need to dynamically provide an offset
	 *                                                               (useful for some responsive designs).
	 *
	 * @return  void
	 *
	 * @since   3.1
	 *
	 * @deprecated  4.0  Bootstrap 4.0 dropped this so will Joomla.
	 */
	public static function affix($selector = 'affix', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['offset'] = isset($params['offset']) ? $params['offset'] : 10;

			$options = JHtml::getJSObject($opt);

			// Attach affix to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){ $(' . json_encode('#' . $selector) . ').affix(' . $options . '); });'
			);

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
		}

		return;
	}

	/**
	 * Add javascript support for Bootstrap alerts
	 *
	 * @param   string  $selector  Common class for the alerts
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function alert($selector = 'alert')
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// Include Bootstrap framework
		JHtml::_('bootstrap.framework');

		// Attach the alerts to the document
		JFactory::getDocument()->addScriptDeclaration(
			'jQuery(function($){ $(' . json_encode('.' . $selector) . ').alert(); });'
		);

		static::$loaded[__METHOD__][$selector] = true;

		return;
	}

	/**
	 * Add javascript support for Bootstrap buttons
	 *
	 * @param   string  $selector  Common class for the buttons
	 *
	 * @return  void
	 *
	 * @since   3.1
	 */
	public static function button($selector = 'button')
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// Include Bootstrap framework
		JHtml::_('bootstrap.framework');

		// Attach the button to the document
		JFactory::getDocument()->addScriptDeclaration(
			'jQuery(function($){ $(' . json_encode('.' . $selector) . ').button(); });'
		);

		static::$loaded[__METHOD__][$selector] = true;

		return;
	}

	/**
	 * Add javascript support for Bootstrap carousels
	 *
	 * @param   string  $selector  Common class for the carousels.
	 * @param   array   $params    An array of options for the carousel.
	 *                             Options for the carousel can be:
	 *                             - interval  number  The amount of time to delay between automatically cycling an item.
	 *                                                 If false, carousel will not automatically cycle.
	 *                             - pause     string  Pauses the cycling of the carousel on mouseenter and resumes the cycling
	 *                                                 of the carousel on mouseleave.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function carousel($selector = 'carousel', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['interval'] = isset($params['interval']) ? (int) $params['interval'] : 5000;
			$opt['pause']    = isset($params['pause']) ? $params['pause'] : 'hover';

			$options = JHtml::getJSObject($opt);

			// Attach the carousel to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){ $(' . json_encode('.' . $selector) . ').carousel(' . $options . '); });'
			);

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
		}

		return;
	}

	/**
	 * Add javascript support for Bootstrap dropdowns
	 *
	 * @param   string  $selector  Common class for the dropdowns
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function dropdown($selector = 'dropdown-toggle')
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// Include Bootstrap framework
		JHtml::_('bootstrap.framework');

		// Attach the dropdown to the document
		JFactory::getDocument()->addScriptDeclaration(
			'jQuery(function($){ $(' . json_encode('.' . $selector) . ').dropdown(); });'
		);

		static::$loaded[__METHOD__][$selector] = true;

		return;
	}

	/**
	 * Method to load the Bootstrap JavaScript framework into the document head
	 *
	 * If debugging mode is on an uncompressed version of Bootstrap is included for easier debugging.
	 *
	 * @param   mixed  $debug  Is debugging mode on? [optional]
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function framework($debug = null)
	{
		// Only load once
		if (!empty(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Load jQuery
		JHtml::_('jquery.framework');

		// If no debugging value is set, use the configuration setting
		if ($debug === null)
		{
			$debug = JDEBUG;
		}

		JHtml::_('script', 'jui/bootstrap.min.js', array('version' => 'auto', 'relative' => true, 'detectDebug' => $debug));
		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Add javascript support for Bootstrap modals
	 *
	 * @param   string  $selector  The ID selector for the modal.
	 * @param   array   $params    An array of options for the modal.
	 *                             Options for the modal can be:
	 *                             - backdrop  boolean  Includes a modal-backdrop element.
	 *                             - keyboard  boolean  Closes the modal when escape key is pressed.
	 *                             - show      boolean  Shows the modal when initialized.
	 *                             - remote    string   An optional remote URL to load
	 *
	 * @return  void
	 *
	 * @since   3.0
	 * @deprecated  4.0  This method was used by the old renderModal() implementation.
	 *                   Since the new implementation it is unneeded and the broken JS it was injecting could create issues
	 *                   As a case, please see: https://github.com/joomla/joomla-cms/pull/6918
	 */
	public static function modal($selector = 'modal', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['backdrop'] = isset($params['backdrop']) ? (boolean) $params['backdrop'] : true;
			$opt['keyboard'] = isset($params['keyboard']) ? (boolean) $params['keyboard'] : true;
			$opt['show']     = isset($params['show']) ? (boolean) $params['show'] : false;
			$opt['remote']   = isset($params['remote']) ? $params['remote'] : '';

			$options = JHtml::getJSObject($opt);

			// Attach the modal to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){ $(' . json_encode('#' . $selector) . ').modal(' . $options . '); });'
			);

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
		}

		return;
	}

	/**
	 * Method to render a Bootstrap modal
	 *
	 * @param   string  $selector  The ID selector for the modal.
	 * @param   array   $params    An array of options for the modal.
	 *                             Options for the modal can be:
	 *                             - title        string   The modal title
	 *                             - backdrop     mixed    A boolean select if a modal-backdrop element should be included (default = true)
	 *                                                     The string 'static' includes a backdrop which doesn't close the modal on click.
	 *                             - keyboard     boolean  Closes the modal when escape key is pressed (default = true)
	 *                             - closeButton  boolean  Display modal close button (default = true)
	 *                             - animation    boolean  Fade in from the top of the page (default = true)
	 *                             - footer       string   Optional markup for the modal footer
	 *                             - url          string   URL of a resource to be inserted as an `<iframe>` inside the modal body
	 *                             - height       string   height of the `<iframe>` containing the remote resource
	 *                             - width        string   width of the `<iframe>` containing the remote resource
	 * @param   string  $body      Markup for the modal body. Appended after the `<iframe>` if the URL option is set
	 *
	 * @return  string  HTML markup for a modal
	 *
	 * @since   3.0
	 */
	public static function renderModal($selector = 'modal', $params = array(), $body = '')
	{
		// Include Bootstrap framework
		JHtml::_('bootstrap.framework');

		$layoutData = array(
			'selector' => $selector,
			'params'   => $params,
			'body'     => $body,
		);

		return JLayoutHelper::render('joomla.modal.main', $layoutData);
	}

	/**
	 * Add javascript support for Bootstrap popovers
	 *
	 * Use element's Title as popover content
	 *
	 * @param   string  $selector  Selector for the popover
	 * @param   array   $params    An array of options for the popover.
	 *                  Options for the popover can be:
	 *                      animation  boolean          apply a css fade transition to the popover
	 *                      html       boolean          Insert HTML into the popover. If false, jQuery's text method will be used to insert
	 *                                                  content into the dom.
	 *                      placement  string|function  how to position the popover - top | bottom | left | right
	 *                      selector   string           If a selector is provided, popover objects will be delegated to the specified targets.
	 *                      trigger    string           how popover is triggered - hover | focus | manual
	 *                      title      string|function  default title value if `title` tag isn't present
	 *                      content    string|function  default content value if `data-content` attribute isn't present
	 *                      delay      number|object    delay showing and hiding the popover (ms) - does not apply to manual trigger type
	 *                                                  If a number is supplied, delay is applied to both hide/show
	 *                                                  Object structure is: delay: { show: 500, hide: 100 }
	 *                      container  string|boolean   Appends the popover to a specific element: { container: 'body' }
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function popover($selector = '.hasPopover', $params = array())
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// Include Bootstrap framework
		JHtml::_('bootstrap.framework');

		$opt['animation'] = isset($params['animation']) ? $params['animation'] : null;
		$opt['html']      = isset($params['html']) ? $params['html'] : true;
		$opt['placement'] = isset($params['placement']) ? $params['placement'] : null;
		$opt['selector']  = isset($params['selector']) ? $params['selector'] : null;
		$opt['title']     = isset($params['title']) ? $params['title'] : null;
		$opt['trigger']   = isset($params['trigger']) ? $params['trigger'] : 'hover focus';
		$opt['content']   = isset($params['content']) ? $params['content'] : null;
		$opt['delay']     = isset($params['delay']) ? $params['delay'] : null;
		$opt['container'] = isset($params['container']) ? $params['container'] : 'body';

		$options = JHtml::getJSObject($opt);

		$initFunction = 'function initPopovers (event, container) { ' .
				'$(container || document).find(' . json_encode($selector) . ').popover(' . $options . ');' .
			'}';

		// Attach the popover to the document
		JFactory::getDocument()->addScriptDeclaration(
			'jQuery(function($){ initPopovers(); $("body").on("subform-row-add", initPopovers); ' . $initFunction . ' });'
		);

		static::$loaded[__METHOD__][$selector] = true;

		return;
	}

	/**
	 * Add javascript support for Bootstrap ScrollSpy
	 *
	 * @param   string  $selector  The ID selector for the ScrollSpy element.
	 * @param   array   $params    An array of options for the ScrollSpy.
	 *                             Options for the ScrollSpy can be:
	 *                             - offset  number  Pixels to offset from top when calculating position of scroll.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function scrollspy($selector = 'navbar', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['offset'] = isset($params['offset']) ? (int) $params['offset'] : 10;

			$options = JHtml::getJSObject($opt);

			// Attach ScrollSpy to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){ $(' . json_encode('#' . $selector) . ').scrollspy(' . $options . '); });'
			);

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
		}

		return;
	}

	/**
	 * Add javascript support for Bootstrap tooltips
	 *
	 * Add a title attribute to any element in the form
	 * title="title::text"
	 *
	 * @param   string  $selector  The ID selector for the tooltip.
	 * @param   array   $params    An array of options for the tooltip.
	 *                             Options for the tooltip can be:
	 *                             - animation  boolean          Apply a CSS fade transition to the tooltip
	 *                             - html       boolean          Insert HTML into the tooltip. If false, jQuery's text method will be used to insert
	 *                                                           content into the dom.
	 *                             - placement  string|function  How to position the tooltip - top | bottom | left | right
	 *                             - selector   string           If a selector is provided, tooltip objects will be delegated to the specified targets.
	 *                             - title      string|function  Default title value if `title` tag isn't present
	 *                             - trigger    string           How tooltip is triggered - hover | focus | manual
	 *                             - delay      integer          Delay showing and hiding the tooltip (ms) - does not apply to manual trigger type
	 *                                                           If a number is supplied, delay is applied to both hide/show
	 *                                                           Object structure is: delay: { show: 500, hide: 100 }
	 *                             - container  string|boolean   Appends the popover to a specific element: { container: 'body' }
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function tooltip($selector = '.hasTooltip', $params = array())
	{
		if (!isset(static::$loaded[__METHOD__][$selector]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['animation'] = isset($params['animation']) ? (boolean) $params['animation'] : null;
			$opt['html']      = isset($params['html']) ? (boolean) $params['html'] : true;
			$opt['placement'] = isset($params['placement']) ? (string) $params['placement'] : null;
			$opt['selector']  = isset($params['selector']) ? (string) $params['selector'] : null;
			$opt['title']     = isset($params['title']) ? (string) $params['title'] : null;
			$opt['trigger']   = isset($params['trigger']) ? (string) $params['trigger'] : null;
			$opt['delay']     = isset($params['delay']) ? (is_array($params['delay']) ? $params['delay'] : (int) $params['delay']) : null;
			$opt['container'] = isset($params['container']) ? $params['container'] : 'body';
			$opt['template']  = isset($params['template']) ? (string) $params['template'] : null;
			$onShow           = isset($params['onShow']) ? (string) $params['onShow'] : null;
			$onShown          = isset($params['onShown']) ? (string) $params['onShown'] : null;
			$onHide           = isset($params['onHide']) ? (string) $params['onHide'] : null;
			$onHidden         = isset($params['onHidden']) ? (string) $params['onHidden'] : null;

			$options = JHtml::getJSObject($opt);

			// Build the script.
			$script = array('$(container).find(' . json_encode($selector) . ').tooltip(' . $options . ')');

			if ($onShow)
			{
				$script[] = 'on("show.bs.tooltip", ' . $onShow . ')';
			}

			if ($onShown)
			{
				$script[] = 'on("shown.bs.tooltip", ' . $onShown . ')';
			}

			if ($onHide)
			{
				$script[] = 'on("hide.bs.tooltip", ' . $onHide . ')';
			}

			if ($onHidden)
			{
				$script[] = 'on("hidden.bs.tooltip", ' . $onHidden . ')';
			}

			$initFunction = 'function initTooltips (event, container) { ' .
				'container = container || document;' .
				implode('.', $script) . ';' .
				'}';

			// Attach tooltips to document
			JFactory::getDocument()
				->addScriptDeclaration('jQuery(function($){ initTooltips(); $("body").on("subform-row-add", initTooltips); ' . $initFunction . ' });');

			// Set static array
			static::$loaded[__METHOD__][$selector] = true;
		}

		return;
	}

	/**
	 * Loads js and css files needed by Bootstrap Tooltip Extended plugin
	 *
	 * @param   boolean  $extended  If true, bootstrap-tooltip-extended.js and .css files are loaded
	 *
	 * @return  void
	 *
	 * @since   3.6
	 *
	 * @deprecated  4.0 No replacement, use Bootstrap tooltips.
	 */
	public static function tooltipExtended($extended = true)
	{
		if ($extended)
		{
			JHtml::_('script', 'jui/bootstrap-tooltip-extended.min.js', array('version' => 'auto', 'relative' => true));
			JHtml::_('stylesheet', 'jui/bootstrap-tooltip-extended.css', array('version' => 'auto', 'relative' => true));
		}
	}

	/**
	 * Add javascript support for Bootstrap typeahead
	 *
	 * @param   string  $selector  The selector for the typeahead element.
	 * @param   array   $params    An array of options for the typeahead element.
	 *                             Options for the tooltip can be:
	 *                             - source       array, function  The data source to query against. May be an array of strings or a function.
	 *                                                             The function is passed two arguments, the query value in the input field and the
	 *                                                             process callback. The function may be used synchronously by returning the data
	 *                                                             source directly or asynchronously via the process callback's single argument.
	 *                             - items        number           The max number of items to display in the dropdown.
	 *                             - minLength    number           The minimum character length needed before triggering autocomplete suggestions
	 *                             - matcher      function         The method used to determine if a query matches an item. Accepts a single argument,
	 *                                                             the item against which to test the query. Access the current query with this.query.
	 *                                                             Return a boolean true if query is a match.
	 *                             - sorter       function         Method used to sort autocomplete results. Accepts a single argument items and has
	 *                                                             the scope of the typeahead instance. Reference the current query with this.query.
	 *                             - updater      function         The method used to return selected item. Accepts a single argument, the item and
	 *                                                             has the scope of the typeahead instance.
	 *                             - highlighter  function         Method used to highlight autocomplete results. Accepts a single argument item and
	 *                                                             has the scope of the typeahead instance. Should return html.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 *
	 * @deprecated  4.0  Bootstrap 4.0 dropped this so will Joomla.
	 */
	public static function typeahead($selector = '.typeahead', $params = array())
	{
		if (!isset(static::$loaded[__METHOD__][$selector]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['source']      = isset($params['source']) ? $params['source'] : null;
			$opt['items']       = isset($params['items']) ? (int) $params['items'] : 8;
			$opt['minLength']   = isset($params['minLength']) ? (int) $params['minLength'] : 1;
			$opt['matcher']     = isset($params['matcher']) ? (string) $params['matcher'] : null;
			$opt['sorter']      = isset($params['sorter']) ? (string) $params['sorter'] : null;
			$opt['updater']     = isset($params['updater']) ? (string) $params['updater'] : null;
			$opt['highlighter'] = isset($params['highlighter']) ? (int) $params['highlighter'] : null;

			$options = JHtml::getJSObject($opt);

			// Attach typehead to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){ $(' . json_encode($selector) . ').typeahead(' . $options . '); });'
			);

			// Set static array
			static::$loaded[__METHOD__][$selector] = true;
		}

		return;
	}

	/**
	 * Add javascript support for Bootstrap accordians and insert the accordian
	 *
	 * @param   string  $selector  The ID selector for the tooltip.
	 * @param   array   $params    An array of options for the tooltip.
	 *                             Options for the tooltip can be:
	 *                             - parent  selector  If selector then all collapsible elements under the specified parent will be closed when this
	 *                                                 collapsible item is shown. (similar to traditional accordion behavior)
	 *                             - toggle  boolean   Toggles the collapsible element on invocation
	 *                             - active  string    Sets the active slide during load
	 *
	 *                             - onShow    function  This event fires immediately when the show instance method is called.
	 *                             - onShown   function  This event is fired when a collapse element has been made visible to the user
	 *                                                   (will wait for css transitions to complete).
	 *                             - onHide    function  This event is fired immediately when the hide method has been called.
	 *                             - onHidden  function  This event is fired when a collapse element has been hidden from the user
	 *                                                   (will wait for css transitions to complete).
	 *
	 * @return  string  HTML for the accordian
	 *
	 * @since   3.0
	 */
	public static function startAccordion($selector = 'myAccordian', $params = array())
	{
		if (!isset(static::$loaded[__METHOD__][$selector]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['parent'] = isset($params['parent']) ? ($params['parent'] == true ? '#' . $selector : $params['parent']) : false;
			$opt['toggle'] = isset($params['toggle']) ? (boolean) $params['toggle'] : !($opt['parent'] === false || isset($params['active']));
			$onShow = isset($params['onShow']) ? (string) $params['onShow'] : null;
			$onShown = isset($params['onShown']) ? (string) $params['onShown'] : null;
			$onHide = isset($params['onHide']) ? (string) $params['onHide'] : null;
			$onHidden = isset($params['onHidden']) ? (string) $params['onHidden'] : null;

			$options = JHtml::getJSObject($opt);

			$opt['active'] = isset($params['active']) ? (string) $params['active'] : '';

			// Build the script.
			$script = array();
			$script[] = "jQuery(function($){";
			$script[] = "\t$('#" . $selector . "').collapse(" . $options . ")";

			if ($onShow)
			{
				$script[] = "\t.on('show', " . $onShow . ")";
			}

			if ($onShown)
			{
				$script[] = "\t.on('shown', " . $onShown . ")";
			}

			if ($onHide)
			{
				$script[] = "\t.on('hideme', " . $onHide . ")";
			}

			if ($onHidden)
			{
				$script[] = "\t.on('hidden', " . $onHidden . ")";
			}

			$parents = array_key_exists(__METHOD__, static::$loaded) ? array_filter(array_column(static::$loaded[__METHOD__], 'parent')) : array();

			if ($opt['parent'] && empty($parents))
			{
				$script[] = "
					$(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
						var \$this   = $(this), href
						var parent  = \$this.attr('data-parent')
						var \$parent = parent && $(parent)

						if (\$parent) \$parent.find('[data-toggle=collapse][data-parent=' + parent + ']').not(\$this).addClass('collapsed')
					})";
			}

			$script[] = "});";

			// Attach accordion to document
			JFactory::getDocument()->addScriptDeclaration(implode("\n", $script));

			// Set static array
			static::$loaded[__METHOD__][$selector] = $opt;

			return '<div id="' . $selector . '" class="accordion">';
		}
	}

	/**
	 * Close the current accordion
	 *
	 * @return  string  HTML to close the accordian
	 *
	 * @since   3.0
	 */
	public static function endAccordion()
	{
		return '</div>';
	}

	/**
	 * Begins the display of a new accordion slide.
	 *
	 * @param   string  $selector  Identifier of the accordion group.
	 * @param   string  $text      Text to display.
	 * @param   string  $id        Identifier of the slide.
	 * @param   string  $class     Class of the accordion group.
	 *
	 * @return  string  HTML to add the slide
	 *
	 * @since   3.0
	 */
	public static function addSlide($selector, $text, $id, $class = '')
	{
		$in = (static::$loaded[__CLASS__ . '::startAccordion'][$selector]['active'] == $id) ? ' in' : '';
		$collapsed = (static::$loaded[__CLASS__ . '::startAccordion'][$selector]['active'] == $id) ? '' : ' collapsed';
		$parent = static::$loaded[__CLASS__ . '::startAccordion'][$selector]['parent'] ?
			' data-parent="' . static::$loaded[__CLASS__ . '::startAccordion'][$selector]['parent'] . '"' : '';
		$class = (!empty($class)) ? ' ' . $class : '';

		$html = '<div class="accordion-group' . $class . '">'
			. '<div class="accordion-heading">'
			. '<strong><a href="#' . $id . '" data-toggle="collapse"' . $parent . ' class="accordion-toggle' . $collapsed . '">'
			. $text
			. '</a></strong>'
			. '</div>'
			. '<div class="accordion-body collapse' . $in . '" id="' . $id . '">'
			. '<div class="accordion-inner">';

		return $html;
	}

	/**
	 * Close the current slide
	 *
	 * @return  string  HTML to close the slide
	 *
	 * @since   3.0
	 */
	public static function endSlide()
	{
		return '</div></div></div>';
	}

	/**
	 * Creates a tab pane
	 *
	 * @param   string  $selector  The pane identifier.
	 * @param   array   $params    The parameters for the pane
	 *
	 * @return  string
	 *
	 * @since   3.1
	 */
	public static function startTabSet($selector = 'myTab', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['active'] = (isset($params['active']) && $params['active']) ? (string) $params['active'] : '';

			// Attach tabs to document
			JFactory::getDocument()
				->addScriptDeclaration(JLayoutHelper::render('libraries.cms.html.bootstrap.starttabsetscript', array('selector' => $selector)));

			// Set static array
			static::$loaded[__METHOD__][$sig] = true;
			static::$loaded[__METHOD__][$selector]['active'] = $opt['active'];
		}

		return JLayoutHelper::render('libraries.cms.html.bootstrap.starttabset', array('selector' => $selector));
	}

	/**
	 * Close the current tab pane
	 *
	 * @return  string  HTML to close the pane
	 *
	 * @since   3.1
	 */
	public static function endTabSet()
	{
		return JLayoutHelper::render('libraries.cms.html.bootstrap.endtabset');
	}

	/**
	 * Begins the display of a new tab content panel.
	 *
	 * @param   string  $selector  Identifier of the panel.
	 * @param   string  $id        The ID of the div element
	 * @param   string  $title     The title text for the new UL tab
	 *
	 * @return  string  HTML to start a new panel
	 *
	 * @since   3.1
	 */
	public static function addTab($selector, $id, $title)
	{
		static $tabScriptLayout = null;
		static $tabLayout = null;

		$tabScriptLayout = $tabScriptLayout === null ? new JLayoutFile('libraries.cms.html.bootstrap.addtabscript') : $tabScriptLayout;
		$tabLayout = $tabLayout === null ? new JLayoutFile('libraries.cms.html.bootstrap.addtab') : $tabLayout;

		$active = (static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] == $id) ? ' active' : '';

		// Inject tab into UL
		JFactory::getDocument()
			->addScriptDeclaration($tabScriptLayout->render(array('selector' => $selector, 'id' => $id, 'active' => $active, 'title' => $title)));

		return $tabLayout->render(array('id' => $id, 'active' => $active));
	}

	/**
	 * Close the current tab content panel
	 *
	 * @return  string  HTML to close the pane
	 *
	 * @since   3.1
	 */
	public static function endTab()
	{
		return JLayoutHelper::render('libraries.cms.html.bootstrap.endtab');
	}

	/**
	 * Creates a tab pane
	 *
	 * @param   string  $selector  The pane identifier.
	 * @param   array   $params    The parameters for the pane
	 *
	 * @return  string
	 *
	 * @since   3.0
	 * @deprecated  4.0	Use JHtml::_('bootstrap.startTabSet') instead.
	 */
	public static function startPane($selector = 'myTab', $params = array())
	{
		$sig = md5(serialize(array($selector, $params)));

		if (!isset(static::$loaded['JHtmlBootstrap::startTabSet'][$sig]))
		{
			// Include Bootstrap framework
			JHtml::_('bootstrap.framework');

			// Setup options object
			$opt['active'] = isset($params['active']) ? (string) $params['active'] : '';

			// Attach tab to document
			JFactory::getDocument()->addScriptDeclaration(
				'jQuery(function($){
					$(' . json_encode('#' . $selector . ' a') . ').click(function (e) {
						e.preventDefault();
						$(this).tab("show");
					});
				});'
			);

			// Set static array
			static::$loaded['JHtmlBootstrap::startTabSet'][$sig] = true;
			static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] = $opt['active'];
		}

		return '<div class="tab-content" id="' . $selector . 'Content">';
	}

	/**
	 * Close the current tab pane
	 *
	 * @return  string  HTML to close the pane
	 *
	 * @since   3.0
	 * @deprecated  4.0	Use JHtml::_('bootstrap.endTabSet') instead.
	 */
	public static function endPane()
	{
		return '</div>';
	}

	/**
	 * Begins the display of a new tab content panel.
	 *
	 * @param   string  $selector  Identifier of the panel.
	 * @param   string  $id        The ID of the div element
	 *
	 * @return  string  HTML to start a new panel
	 *
	 * @since   3.0
	 * @deprecated  4.0 Use JHtml::_('bootstrap.addTab') instead.
	 */
	public static function addPanel($selector, $id)
	{
		$active = (static::$loaded['JHtmlBootstrap::startTabSet'][$selector]['active'] == $id) ? ' active' : '';

		return '<div id="' . $id . '" class="tab-pane' . $active . '">';
	}

	/**
	 * Close the current tab content panel
	 *
	 * @return  string  HTML to close the pane
	 *
	 * @since   3.0
	 * @deprecated  4.0 Use JHtml::_('bootstrap.endTab') instead.
	 */
	public static function endPanel()
	{
		return '</div>';
	}

	/**
	 * Loads CSS files needed by Bootstrap
	 *
	 * @param   boolean  $includeMainCss  If true, main bootstrap.css files are loaded
	 * @param   string   $direction       rtl or ltr direction. If empty, ltr is assumed
	 * @param   array    $attribs         Optional array of attributes to be passed to JHtml::_('stylesheet')
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function loadCss($includeMainCss = true, $direction = 'ltr', $attribs = array())
	{
		// Load Bootstrap main CSS
		if ($includeMainCss)
		{
			JHtml::_('stylesheet', 'jui/bootstrap.min.css', array('version' => 'auto', 'relative' => true), $attribs);
			JHtml::_('stylesheet', 'jui/bootstrap-responsive.min.css', array('version' => 'auto', 'relative' => true), $attribs);
			JHtml::_('stylesheet', 'jui/bootstrap-extended.css', array('version' => 'auto', 'relative' => true), $attribs);
		}

		// Load Bootstrap RTL CSS
		if ($direction === 'rtl')
		{
			JHtml::_('stylesheet', 'jui/bootstrap-rtl.css', array('version' => 'auto', 'relative' => true), $attribs);
		}
	}
}
cms/html/date.php000064400000004044152177723700007735 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Extended Utility class for handling date display.
 *
 * @since  2.5
 */
abstract class JHtmlDate
{
	/**
	 * Function to convert a static time into a relative measurement
	 *
	 * @param   string  $date    The date to convert
	 * @param   string  $unit    The optional unit of measurement to return
	 *                           if the value of the diff is greater than one
	 * @param   string  $time    An optional time to compare to, defaults to now
	 * @param   string  $format  An optional format for the JHtml::date output
	 *
	 * @return  string  The converted time string
	 *
	 * @since   2.5
	 */
	public static function relative($date, $unit = null, $time = null, $format = null)
	{
		if ($time === null)
		{
			// Get now
			$time = new JDate('now');
		}

		// Get the difference in seconds between now and the time
		$diff = strtotime($time) - strtotime($date);

		// Less than a minute
		if ($diff < 60)
		{
			return JText::_('JLIB_HTML_DATE_RELATIVE_LESSTHANAMINUTE');
		}

		// Round to minutes
		$diff = round($diff / 60);

		// 1 to 59 minutes
		if ($diff < 60 || $unit === 'minute')
		{
			return JText::plural('JLIB_HTML_DATE_RELATIVE_MINUTES', $diff);
		}

		// Round to hours
		$diff = round($diff / 60);

		// 1 to 23 hours
		if ($diff < 24 || $unit === 'hour')
		{
			return JText::plural('JLIB_HTML_DATE_RELATIVE_HOURS', $diff);
		}

		// Round to days
		$diff = round($diff / 24);

		// 1 to 6 days
		if ($diff < 7 || $unit === 'day')
		{
			return JText::plural('JLIB_HTML_DATE_RELATIVE_DAYS', $diff);
		}

		// Round to weeks
		$diff = round($diff / 7);

		// 1 to 4 weeks
		if ($diff <= 4 || $unit === 'week')
		{
			return JText::plural('JLIB_HTML_DATE_RELATIVE_WEEKS', $diff);
		}

		// Over a month, return the absolute time
		return JHtml::_('date', $date, $format);
	}
}
cms/html/access.php000064400000017745152177723700010275 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Extended Utility class for all HTML drawing classes.
 *
 * @since  1.6
 */
abstract class JHtmlAccess
{
	/**
	 * A cached array of the asset groups
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected static $asset_groups = null;

	/**
	 * Displays a list of the available access view levels
	 *
	 * @param   string  $name      The form field name.
	 * @param   string  $selected  The name of the selected section.
	 * @param   string  $attribs   Additional attributes to add to the select field.
	 * @param   mixed   $params    True to add "All Sections" option or an array of options
	 * @param   mixed   $id        The form field id or false if not used
	 *
	 * @return  string  The required HTML for the SELECT tag.
	 *
	 * @see    JFormFieldAccessLevel
	 * @since  1.6
	 */
	public static function level($name, $selected, $attribs = '', $params = true, $id = false)
	{
		$db = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select($db->quoteName('a.id', 'value') . ', ' . $db->quoteName('a.title', 'text'))
			->from($db->quoteName('#__viewlevels', 'a'))
			->group($db->quoteName(array('a.id', 'a.title', 'a.ordering')))
			->order($db->quoteName('a.ordering') . ' ASC')
			->order($db->quoteName('title') . ' ASC');

		// Get the options.
		$db->setQuery($query);
		$options = $db->loadObjectList();

		// If params is an array, push these options to the array
		if (is_array($params))
		{
			$options = array_merge($params, $options);
		}

		// If all levels is allowed, push it into the array.
		elseif ($params)
		{
			array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_LEVELS')));
		}

		return JHtml::_(
			'select.genericlist',
			$options,
			$name,
			array(
				'list.attr' => $attribs,
				'list.select' => $selected,
				'id' => $id,
			)
		);
	}

	/**
	 * Displays a list of the available user groups.
	 *
	 * @param   string   $name      The form field name.
	 * @param   string   $selected  The name of the selected section.
	 * @param   string   $attribs   Additional attributes to add to the select field.
	 * @param   boolean  $allowAll  True to add "All Groups" option.
	 * @param   mixed    $id        The form field id
	 *
	 * @return  string   The required HTML for the SELECT tag.
	 *
	 * @see     JFormFieldUsergroup
	 * @since   1.6
	 */
	public static function usergroup($name, $selected, $attribs = '', $allowAll = true, $id = false)
	{
		$options = array_values(JHelperUsergroups::getInstance()->getAll());

		for ($i = 0, $n = count($options); $i < $n; $i++)
		{
			$options[$i]->value = $options[$i]->id;
			$options[$i]->text = str_repeat('- ', $options[$i]->level) . $options[$i]->title;
		}

		// If all usergroups is allowed, push it into the array.
		if ($allowAll)
		{
			array_unshift($options, JHtml::_('select.option', '', JText::_('JOPTION_ACCESS_SHOW_ALL_GROUPS')));
		}

		return JHtml::_('select.genericlist', $options, $name, array('list.attr' => $attribs, 'list.select' => $selected, 'id' => $id));
	}

	/**
	 * Returns a UL list of user groups with checkboxes
	 *
	 * @param   string   $name             The name of the checkbox controls array
	 * @param   array    $selected         An array of the checked boxes
	 * @param   boolean  $checkSuperAdmin  If false only super admins can add to super admin groups
	 *
	 * @return  string
	 *
	 * @since   1.6
	 */
	public static function usergroups($name, $selected, $checkSuperAdmin = false)
	{
		static $count;

		$count++;

		$isSuperAdmin = JFactory::getUser()->authorise('core.admin');

		$groups = array_values(JHelperUsergroups::getInstance()->getAll());

		$html = array();

		for ($i = 0, $n = count($groups); $i < $n; $i++)
		{
			$item = &$groups[$i];

			// If checkSuperAdmin is true, only add item if the user is superadmin or the group is not super admin
			if ((!$checkSuperAdmin) || $isSuperAdmin || (!JAccess::checkGroup($item->id, 'core.admin')))
			{
				// Setup  the variable attributes.
				$eid = $count . 'group_' . $item->id;

				// Don't call in_array unless something is selected
				$checked = '';

				if ($selected)
				{
					$checked = in_array($item->id, $selected) ? ' checked="checked"' : '';
				}

				$rel = ($item->parent_id > 0) ? ' rel="' . $count . 'group_' . $item->parent_id . '"' : '';

				// Build the HTML for the item.
				$html[] = '	<div class="control-group">';
				$html[] = '		<div class="controls">';
				$html[] = '			<label class="checkbox" for="' . $eid . '">';
				$html[] = '			<input type="checkbox" name="' . $name . '[]" value="' . $item->id . '" id="' . $eid . '"';
				$html[] = '					' . $checked . $rel . ' />';
				$html[] = '			' . JLayoutHelper::render('joomla.html.treeprefix', array('level' => $item->level + 1)) . $item->title;
				$html[] = '			</label>';
				$html[] = '		</div>';
				$html[] = '	</div>';
			}
		}

		return implode("\n", $html);
	}

	/**
	 * Returns a UL list of actions with checkboxes
	 *
	 * @param   string  $name       The name of the checkbox controls array
	 * @param   array   $selected   An array of the checked boxes
	 * @param   string  $component  The component the permissions apply to
	 * @param   string  $section    The section (within a component) the permissions apply to
	 *
	 * @return  string
	 *
	 * @see     JAccess
	 * @since   1.6
	 */
	public static function actions($name, $selected, $component, $section = 'global')
	{
		static $count;

		$count++;

		$actions = JAccess::getActionsFromFile(
			JPATH_ADMINISTRATOR . '/components/' . $component . '/access.xml',
			"/access/section[@name='" . $section . "']/"
		);

		$html = array();
		$html[] = '<ul class="checklist access-actions">';

		for ($i = 0, $n = count($actions); $i < $n; $i++)
		{
			$item = &$actions[$i];

			// Setup  the variable attributes.
			$eid = $count . 'action_' . $item->id;
			$checked = in_array($item->id, $selected) ? ' checked="checked"' : '';

			// Build the HTML for the item.
			$html[] = '	<li>';
			$html[] = '		<input type="checkbox" name="' . $name . '[]" value="' . $item->id . '" id="' . $eid . '"';
			$html[] = '			' . $checked . ' />';
			$html[] = '		<label for="' . $eid . '">';
			$html[] = '			' . JText::_($item->title);
			$html[] = '		</label>';
			$html[] = '	</li>';
		}

		$html[] = '</ul>';

		return implode("\n", $html);
	}

	/**
	 * Gets a list of the asset groups as an array of JHtml compatible options.
	 *
	 * @return  mixed  An array or false if an error occurs
	 *
	 * @since   1.6
	 */
	public static function assetgroups()
	{
		if (empty(static::$asset_groups))
		{
			$db = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('a.id AS value, a.title AS text')
				->from($db->quoteName('#__viewlevels') . ' AS a')
				->group('a.id, a.title, a.ordering')
				->order('a.ordering ASC');

			$db->setQuery($query);
			static::$asset_groups = $db->loadObjectList();
		}

		return static::$asset_groups;
	}

	/**
	 * Displays a Select list of the available asset groups
	 *
	 * @param   string  $name      The name of the select element
	 * @param   mixed   $selected  The selected asset group id
	 * @param   string  $attribs   Optional attributes for the select field
	 * @param   array   $config    An array of options for the control
	 *
	 * @return  mixed  An HTML string or null if an error occurs
	 *
	 * @since   1.6
	 */
	public static function assetgrouplist($name, $selected, $attribs = null, $config = array())
	{
		static $count;

		$options = static::assetgroups();

		if (isset($config['title']))
		{
			array_unshift($options, JHtml::_('select.option', '', $config['title']));
		}

		return JHtml::_(
			'select.genericlist',
			$options,
			$name,
			array(
				'id' => isset($config['id']) ? $config['id'] : 'assetgroups_' . (++$count),
				'list.attr' => $attribs === null ? 'class="inputbox" size="3"' : $attribs,
				'list.select' => (int) $selected,
			)
		);
	}
}
cms/html/searchtools.php000064400000010244152177723700011345 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Searchtools elements.
 *
 * @since  3.2
 */
abstract class JHtmlSearchtools
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.2
	 */
	protected static $loaded = array();

	/**
	 * Load the main Searchtools libraries
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function main()
	{
		// Only load once
		if (empty(static::$loaded[__METHOD__]))
		{
			// Requires jQuery but allows to skip its loading
			if ($loadJquery = (!isset($options['loadJquery']) || $options['loadJquery'] != 0))
			{
				JHtml::_('jquery.framework');
			}

			// Load the jQuery plugin && CSS
			JHtml::_('script', 'jui/jquery.searchtools.min.js', array('version' => 'auto', 'relative' => true));
			JHtml::_('stylesheet', 'jui/jquery.searchtools.css', array('version' => 'auto', 'relative' => true));

			static::$loaded[__METHOD__] = true;
		}

		return;
	}

	/**
	 * Load searchtools for a specific form
	 *
	 * @param   mixed  $selector  Is debugging mode on? [optional]
	 * @param   array  $options   Optional array of parameters for search tools
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function form($selector = '.js-stools-form', $options = array())
	{
		$sig = md5(serialize(array($selector, $options)));

		// Only load once
		if (!isset(static::$loaded[__METHOD__][$sig]))
		{
			// Include Bootstrap framework
			static::main();

			// Add the form selector to the search tools options
			$options['formSelector'] = $selector;

			// Generate options with default values
			$options = static::optionsToRegistry($options);

			$doc = JFactory::getDocument();
			$script = "
				(function($){
					$(document).ready(function() {
						$('" . $selector . "').searchtools(
							" . $options->toString() . "
						);
					});
				})(jQuery);
			";
			$doc->addScriptDeclaration($script);

			static::$loaded[__METHOD__][$sig] = true;
		}

		return;
	}

	/**
	 * Function to receive & pre-process javascript options
	 *
	 * @param   mixed  $options  Associative array/Registry object with options
	 *
	 * @return  Registry         Options converted to Registry object
	 */
	private static function optionsToRegistry($options)
	{
		// Support options array
		if (is_array($options))
		{
			$options = new Registry($options);
		}

		if (!($options instanceof Registry))
		{
			$options = new Registry;
		}

		return $options;
	}

	/**
	 * Method to sort a column in a grid
	 *
	 * @param   string  $title          The link title
	 * @param   string  $order          The order field for the column
	 * @param   string  $direction      The current direction
	 * @param   mixed   $selected       The selected ordering
	 * @param   string  $task           An optional task override
	 * @param   string  $new_direction  An optional direction for the new column
	 * @param   string  $tip            An optional text shown as tooltip title instead of $title
	 * @param   string  $icon           Icon to show
	 * @param   string  $formName       Name of the form to submit
	 *
	 * @return  string
	 */
	public static function sort($title, $order, $direction = 'asc', $selected = 0, $task = null, $new_direction = 'asc', $tip = '', $icon = null,
		$formName = 'adminForm')
	{
		$direction = strtolower($direction);
		$orderIcons = array('icon-arrow-up-3', 'icon-arrow-down-3');
		$index = (int) ($direction === 'desc');

		if ($order !== $selected)
		{
			$direction = $new_direction;
		}
		else
		{
			$direction = $direction === 'desc' ? 'asc' : 'desc';
		}

		// Create an object to pass it to the layouts
		$data            = new stdClass;
		$data->order     = $order;
		$data->direction = $direction;
		$data->selected  = $selected;
		$data->task      = $task;
		$data->tip       = $tip;
		$data->title     = $title;
		$data->orderIcon = $orderIcons[$index];
		$data->icon      = $icon;
		$data->formName  = $formName;

		return JLayoutHelper::render('joomla.searchtools.grid.sort', $data);
	}
}
cms/html/icons.php000064400000003026152177723700010132 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for icons.
 *
 * @since  2.5
 */
abstract class JHtmlIcons
{
	/**
	 * Method to generate html code for a list of buttons
	 *
	 * @param   array  $buttons  Array of buttons
	 *
	 * @return  string
	 *
	 * @since   2.5
	 */
	public static function buttons($buttons)
	{
		$html = array();

		foreach ($buttons as $button)
		{
			$html[] = JHtml::_('icons.button', $button);
		}

		return implode($html);
	}

	/**
	 * Method to generate html code for a list of buttons
	 *
	 * @param   array  $button  Button properties
	 *
	 * @return  string
	 *
	 * @since   2.5
	 */
	public static function button($button)
	{
		if (isset($button['access']))
		{
			if (is_bool($button['access']))
			{
				if ($button['access'] == false)
				{
					return '';
				}
			}
			else
			{
				// Get the user object to verify permissions
				$user = JFactory::getUser();

				// Take each pair of permission, context values.
				for ($i = 0, $n = count($button['access']); $i < $n; $i += 2)
				{
					if (!$user->authorise($button['access'][$i], $button['access'][$i + 1]))
					{
						return '';
					}
				}
			}
		}

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new JLayoutFile('joomla.quickicons.icon');

		return $layout->render($button);
	}
}
cms/html/number.php000064400000007000152177723700010303 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * HTML helper class for rendering numbers.
 *
 * @since  1.6
 */
abstract class JHtmlNumber
{
	/**
	 * Converts bytes to more distinguishable formats such as:
	 * kilobytes, megabytes, etc.
	 *
	 * By default, the proper format will automatically be chosen.
	 * However, one of the allowed unit types (viz. 'b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB') may also be used instead.
	 * IEC standard unit types ('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB') can be used as well.
	 *
	 * @param   string   $bytes      The number of bytes. Can be either numeric or suffixed format: 32M, 60K, 12G or 812b
	 * @param   string   $unit       The type of unit to return, few special values are:
	 *                               Blank string '' for no unit,
	 *                               'auto' to choose automatically (default)
	 *                               'binary' to choose automatically but use binary unit prefix
	 * @param   integer  $precision  The number of digits to be used after the decimal place.
	 * @param   bool     $iec        Whether to be aware of IEC standards. IEC prefixes are always acceptable in input.
	 *                               When IEC is ON:  KiB = 1024 B, KB = 1000 B
	 *                               When IEC is OFF: KiB = 1024 B, KB = 1024 B
	 *
	 * @return  string   The number of bytes in the proper units.
	 *
	 * @since   1.6
	 * @link    https://en.wikipedia.org/wiki/Binary_prefix
	 */
	public static function bytes($bytes, $unit = 'auto', $precision = 2, $iec = false)
	{
		/*
		 * Allowed 123.45, 123.45 M, 123.45 Mi, 123.45 MB, 123.45 MiB, 1.2345E+12MB, 1.2345E+12 MB , 1.2345E+12 MiB etc.
		 * i.e. – Any number in decimal digits or in sci. notation, optional space, optional 1-3 letter unit suffix
		 */
		if (is_numeric($bytes))
		{
			$oBytes = $bytes;
		}
		else
		{
			preg_match('/(.*?)\s?((?:[KMGTPEZY]i?)?B?)$/i', trim($bytes), $matches);
			list(, $oBytes, $oUnit) = $matches;

			if ($oUnit && is_numeric($oBytes))
			{
				$oBase  = $iec && strpos($oUnit, 'i') === false ? 1000 : 1024;
				$factor = pow($oBase, stripos('BKMGTPEZY', $oUnit[0]));
				$oBytes *= $factor;
			}
		}

		if (empty($oBytes) || !is_numeric($oBytes))
		{
			return 0;
		}

		$oBytes = round($oBytes);

		// If no unit is requested return early
		if ($unit === '')
		{
			return (string) $oBytes;
		}

		$stdSuffixes = array('b', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
		$iecSuffixes = array('b', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB');

		// User supplied method
		if (in_array($unit, $iecSuffixes))
		{
			$base   = 1024;
			$i      = array_search($unit, $iecSuffixes, true);
			$suffix = $unit;
		}
		elseif (in_array($unit, $stdSuffixes))
		{
			$base   = $iec ? 1000 : 1024;
			$i      = array_search($unit, $stdSuffixes, true);
			$suffix = $unit;
		}
		elseif ($unit === 'binary')
		{
			$base   = 1024;
			$i      = (int) floor(log($oBytes, $base));
			$suffix = $iecSuffixes[$i];
		}
		else
		{
			// Default method
			$base   = $iec ? 1000 : 1024;
			$i      = (int) floor(log($oBytes, $base));
			$suffix = $stdSuffixes[$i];
		}

		return number_format(
			round($oBytes / pow($base, $i), (int) $precision), (int) $precision, JText::_('DECIMALS_SEPARATOR'), JText::_('THOUSANDS_SEPARATOR')
		) . ' ' . $suffix;
	}
}
cms/html/grid.php000064400000024522152177723700007750 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for creating HTML Grids
 *
 * @since  1.5
 */
abstract class JHtmlGrid
{
	/**
	 * Display a boolean setting widget.
	 *
	 * @param   integer  $i        The row index.
	 * @param   integer  $value    The value of the boolean field.
	 * @param   string   $taskOn   Task to turn the boolean setting on.
	 * @param   string   $taskOff  Task to turn the boolean setting off.
	 *
	 * @return  string   The boolean setting widget.
	 *
	 * @since   1.6
	 *
	 * @deprecated  4.0 This is only used in hathor and will be removed without replacement
	 */
	public static function boolean($i, $value, $taskOn = null, $taskOff = null)
	{
		// Load the behavior.
		static::behavior();
		JHtml::_('bootstrap.tooltip');

		// Build the title.
		$title = $value ? JText::_('JYES') : JText::_('JNO');
		$title = JHtml::_('tooltipText', $title, JText::_('JGLOBAL_CLICK_TO_TOGGLE_STATE'), 0);

		// Build the <a> tag.
		$bool = $value ? 'true' : 'false';
		$task = $value ? $taskOff : $taskOn;
		$toggle = (!$task) ? false : true;

		if ($toggle)
		{
			return '<a class="grid_' . $bool . ' hasTooltip" title="' . $title . '" rel="{id:\'cb' . $i . '\', task:\'' . $task
				. '\'}" href="#toggle"></a>';
		}
		else
		{
			return '<a class="grid_' . $bool . '"></a>';
		}
	}

	/**
	 * Method to sort a column in a grid
	 *
	 * @param   string  $title          The link title
	 * @param   string  $order          The order field for the column
	 * @param   string  $direction      The current direction
	 * @param   string  $selected       The selected ordering
	 * @param   string  $task           An optional task override
	 * @param   string  $new_direction  An optional direction for the new column
	 * @param   string  $tip            An optional text shown as tooltip title instead of $title
	 * @param   string  $form           An optional form selector
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function sort($title, $order, $direction = 'asc', $selected = '', $task = null, $new_direction = 'asc', $tip = '', $form = null)
	{
		JHtml::_('behavior.core');
		JHtml::_('bootstrap.popover');

		$direction = strtolower($direction);
		$icon = array('arrow-up-3', 'arrow-down-3');
		$index = (int) ($direction === 'desc');

		if ($order != $selected)
		{
			$direction = $new_direction;
		}
		else
		{
			$direction = $direction === 'desc' ? 'asc' : 'desc';
		}

		if ($form)
		{
			$form = ', document.getElementById(\'' . $form . '\')';
		}

		$html = '<a href="#" onclick="Joomla.tableOrdering(\'' . $order . '\',\'' . $direction . '\',\'' . $task . '\'' . $form . ');return false;"'
			. ' class="hasPopover" title="' . htmlspecialchars(JText::_($tip ?: $title)) . '"'
			. ' data-content="' . htmlspecialchars(JText::_('JGLOBAL_CLICK_TO_SORT_THIS_COLUMN')) . '" data-placement="top">';

		if (isset($title['0']) && $title['0'] === '<')
		{
			$html .= $title;
		}
		else
		{
			$html .= JText::_($title);
		}

		if ($order == $selected)
		{
			$html .= '<span class="icon-' . $icon[$index] . '"></span>';
		}

		$html .= '</a>';

		return $html;
	}

	/**
	 * Method to check all checkboxes in a grid
	 *
	 * @param   string  $name    The name of the form element
	 * @param   string  $tip     The text shown as tooltip title instead of $tip
	 * @param   string  $action  The action to perform on clicking the checkbox
	 *
	 * @return  string
	 *
	 * @since   3.1.2
	 */
	public static function checkall($name = 'checkall-toggle', $tip = 'JGLOBAL_CHECK_ALL', $action = 'Joomla.checkAll(this)')
	{
		JHtml::_('behavior.core');
		JHtml::_('bootstrap.tooltip');

		return '<input type="checkbox" name="' . $name . '" value="" class="hasTooltip" title="' . JHtml::_('tooltipText', $tip)
			. '" onclick="' . $action . '" />';
	}

	/**
	 * Method to create a checkbox for a grid row.
	 *
	 * @param   integer  $rowNum      The row index
	 * @param   integer  $recId       The record id
	 * @param   boolean  $checkedOut  True if item is checked out
	 * @param   string   $name        The name of the form element
	 * @param   string   $stub        The name of stub identifier
	 *
	 * @return  mixed    String of html with a checkbox if item is not checked out, null if checked out.
	 *
	 * @since   1.5
	 */
	public static function id($rowNum, $recId, $checkedOut = false, $name = 'cid', $stub = 'cb')
	{
		return $checkedOut ? '' : '<input type="checkbox" id="' . $stub . $rowNum . '" name="' . $name . '[]" value="' . $recId
			. '" onclick="Joomla.isChecked(this.checked);" />';
	}

	/**
	 * Displays a checked out icon.
	 *
	 * @param   object   &$row        A data object (must contain checkedout as a property).
	 * @param   integer  $i           The index of the row.
	 * @param   string   $identifier  The property name of the primary key or index of the row.
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function checkedOut(&$row, $i, $identifier = 'id')
	{
		$user = JFactory::getUser();
		$userid = $user->get('id');

		if ($row instanceof JTable)
		{
			$result = $row->isCheckedOut($userid);
		}
		else
		{
			$result = false;
		}

		if ($result)
		{
			return static::_checkedOut($row);
		}
		else
		{
			if ($identifier === 'id')
			{
				return JHtml::_('grid.id', $i, $row->$identifier);
			}
			else
			{
				return JHtml::_('grid.id', $i, $row->$identifier, $result, $identifier);
			}
		}
	}

	/**
	 * Method to create a clickable icon to change the state of an item
	 *
	 * @param   mixed    $value   Either the scalar value or an object (for backward compatibility, deprecated)
	 * @param   integer  $i       The index
	 * @param   string   $img1    Image for a positive or on value
	 * @param   string   $img0    Image for the empty or off value
	 * @param   string   $prefix  An optional prefix for the task
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function published($value, $i, $img1 = 'tick.png', $img0 = 'publish_x.png', $prefix = '')
	{
		if (is_object($value))
		{
			$value = $value->published;
		}

		$img = $value ? $img1 : $img0;
		$task = $value ? 'unpublish' : 'publish';
		$alt = $value ? JText::_('JPUBLISHED') : JText::_('JUNPUBLISHED');
		$action = $value ? JText::_('JLIB_HTML_UNPUBLISH_ITEM') : JText::_('JLIB_HTML_PUBLISH_ITEM');

		return '<a href="#" onclick="return listItemTask(\'cb' . $i . '\',\'' . $prefix . $task . '\')" title="' . $action . '">'
			. JHtml::_('image', 'admin/' . $img, $alt, null, true) . '</a>';
	}

	/**
	 * Method to create a select list of states for filtering
	 * By default the filter shows only published and unpublished items
	 *
	 * @param   string  $filter_state  The initial filter state
	 * @param   string  $published     The JText string for published
	 * @param   string  $unpublished   The JText string for Unpublished
	 * @param   string  $archived      The JText string for Archived
	 * @param   string  $trashed       The JText string for Trashed
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function state($filter_state = '*', $published = 'JPUBLISHED', $unpublished = 'JUNPUBLISHED', $archived = null, $trashed = null)
	{
		$state = array('' => '- ' . JText::_('JLIB_HTML_SELECT_STATE') . ' -', 'P' => JText::_($published), 'U' => JText::_($unpublished));

		if ($archived)
		{
			$state['A'] = JText::_($archived);
		}

		if ($trashed)
		{
			$state['T'] = JText::_($trashed);
		}

		return JHtml::_(
			'select.genericlist',
			$state,
			'filter_state',
			array(
				'list.attr' => 'class="inputbox" size="1" onchange="Joomla.submitform();"',
				'list.select' => $filter_state,
				'option.key' => null,
			)
		);
	}

	/**
	 * Method to create an icon for saving a new ordering in a grid
	 *
	 * @param   array   $rows   The array of rows of rows
	 * @param   string  $image  The image [UNUSED]
	 * @param   string  $task   The task to use, defaults to save order
	 *
	 * @return  string
	 *
	 * @since   1.5
	 */
	public static function order($rows, $image = 'filesave.png', $task = 'saveorder')
	{
		return '<a href="javascript:saveorder('
			. (count($rows) - 1) . ', \'' . $task . '\')" rel="tooltip" class="saveorder btn btn-micro pull-right" title="'
			. JText::_('JLIB_HTML_SAVE_ORDER') . '"><span class="icon-menu-2"></span></a>';
	}

	/**
	 * Method to create a checked out icon with optional overlib in a grid.
	 *
	 * @param   object   &$row     The row object
	 * @param   boolean  $overlib  True if an overlib with checkout information should be created.
	 *
	 * @return  string   HTMl for the icon and overlib
	 *
	 * @since   1.5
	 */
	protected static function _checkedOut(&$row, $overlib = true)
	{
		$hover = '';

		if ($overlib)
		{
			JHtml::_('bootstrap.tooltip');

			$date = JHtml::_('date', $row->checked_out_time, JText::_('DATE_FORMAT_LC1'));
			$time = JHtml::_('date', $row->checked_out_time, 'H:i');

			$hover = '<span class="editlinktip hasTooltip" title="' . JHtml::_('tooltipText', 'JLIB_HTML_CHECKED_OUT', $row->editor)
				. '<br />' . $date . '<br />' . $time . '">';
		}

		return $hover . JHtml::_('image', 'admin/checked_out.png', null, null, true) . '</span>';
	}

	/**
	 * Method to build the behavior script and add it to the document head.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 *
	 * @deprecated  4.0 This is only used in hathor and will be removed without replacement
	 */
	public static function behavior()
	{
		static $loaded;

		if (!$loaded)
		{
			// Include jQuery
			JHtml::_('jquery.framework');

			// Build the behavior script.
			$js = '
		jQuery(function($){
			$actions = $(\'a.move_up, a.move_down, a.grid_true, a.grid_false, a.grid_trash\');
			$actions.each(function(){
				$(this).on(\'click\', function(){
					args = JSON.decode(this.rel);
					listItemTask(args.id, args.task);
				});
			});
			$(\'input.check-all-toggle\').each(function(){
				$(this).on(\'click\', function(){
					if (this.checked) {
						$(this).closest(\'form\').find(\'input[type="checkbox"]\').each(function(){
							this.checked = true;
						})
					}
					else {
						$(this).closest(\'form\').find(\'input[type="checkbox"]\').each(function(){
							this.checked = false;
						})
					}
				});
			});
		});';

			// Add the behavior to the document head.
			$document = JFactory::getDocument();
			$document->addScriptDeclaration($js);

			$loaded = true;
		}
	}
}
cms/html/select.php000064400000066761152177723700010315 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

/**
 * Utility class for creating HTML select lists
 *
 * @since  1.5
 */
abstract class JHtmlSelect
{
	/**
	 * Default values for options. Organized by option group.
	 *
	 * @var     array
	 * @since   1.5
	 */
	protected static $optionDefaults = array(
		'option' => array(
			'option.attr' => null,
			'option.disable' => 'disable',
			'option.id' => null,
			'option.key' => 'value',
			'option.key.toHtml' => true,
			'option.label' => null,
			'option.label.toHtml' => true,
			'option.text' => 'text',
			'option.text.toHtml' => true,
			'option.class' => 'class',
			'option.onclick' => 'onclick',
		),
	);

	/**
	 * Generates a yes/no radio list.
	 *
	 * @param   string  $name      The value of the HTML name attribute
	 * @param   array   $attribs   Additional HTML attributes for the `<select>` tag
	 * @param   string  $selected  The key that is selected
	 * @param   string  $yes       Language key for Yes
	 * @param   string  $no        Language key for no
	 * @param   mixed   $id        The id for the field or false for no id
	 *
	 * @return  string  HTML for the radio list
	 *
	 * @since   1.5
	 * @see     JFormFieldRadio
	 */
	public static function booleanlist($name, $attribs = array(), $selected = null, $yes = 'JYES', $no = 'JNO', $id = false)
	{
		$arr = array(JHtml::_('select.option', '0', JText::_($no)), JHtml::_('select.option', '1', JText::_($yes)));

		return JHtml::_('select.radiolist', $arr, $name, $attribs, 'value', 'text', (int) $selected, $id);
	}

	/**
	 * Generates an HTML selection list.
	 *
	 * @param   array    $data       An array of objects, arrays, or scalars.
	 * @param   string   $name       The value of the HTML name attribute.
	 * @param   mixed    $attribs    Additional HTML attributes for the `<select>` tag. This
	 *                               can be an array of attributes, or an array of options. Treated as options
	 *                               if it is the last argument passed. Valid options are:
	 *                               Format options, see {@see JHtml::$formatOptions}.
	 *                               Selection options, see {@see JHtmlSelect::options()}.
	 *                               list.attr, string|array: Additional attributes for the select
	 *                               element.
	 *                               id, string: Value to use as the select element id attribute.
	 *                               Defaults to the same as the name.
	 *                               list.select, string|array: Identifies one or more option elements
	 *                               to be selected, based on the option key values.
	 * @param   string   $optKey     The name of the object variable for the option value. If
	 *                               set to null, the index of the value array is used.
	 * @param   string   $optText    The name of the object variable for the option text.
	 * @param   mixed    $selected   The key that is selected (accepts an array or a string).
	 * @param   mixed    $idtag      Value of the field id or null by default
	 * @param   boolean  $translate  True to translate
	 *
	 * @return  string  HTML for the select list.
	 *
	 * @since   1.5
	 */
	public static function genericlist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false,
		$translate = false)
	{
		// Set default options
		$options = array_merge(JHtml::$formatOptions, array('format.depth' => 0, 'id' => false));

		if (is_array($attribs) && func_num_args() === 3)
		{
			// Assume we have an options array
			$options = array_merge($options, $attribs);
		}
		else
		{
			// Get options from the parameters
			$options['id'] = $idtag;
			$options['list.attr'] = $attribs;
			$options['list.translate'] = $translate;
			$options['option.key'] = $optKey;
			$options['option.text'] = $optText;
			$options['list.select'] = $selected;
		}

		$attribs = '';

		if (isset($options['list.attr']))
		{
			if (is_array($options['list.attr']))
			{
				$attribs = ArrayHelper::toString($options['list.attr']);
			}
			else
			{
				$attribs = $options['list.attr'];
			}

			if ($attribs !== '')
			{
				$attribs = ' ' . $attribs;
			}
		}

		$id = $options['id'] !== false ? $options['id'] : $name;
		$id = str_replace(array('[', ']', ' '), '', $id);

		$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
		$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '"' . $attribs . '>' . $options['format.eol']
			. static::options($data, $options) . $baseIndent . '</select>' . $options['format.eol'];

		return $html;
	}

	/**
	 * Method to build a list with suggestions
	 *
	 * @param   array    $data       An array of objects, arrays, or values.
	 * @param   string   $optKey     The name of the object variable for the option value. If
	 *                               set to null, the index of the value array is used.
	 * @param   string   $optText    The name of the object variable for the option text.
	 * @param   mixed    $idtag      Value of the field id or null by default
	 * @param   boolean  $translate  True to translate
	 *
	 * @return  string  HTML for the select list
	 *
	 * @since       3.2
	 * @deprecated  4.0  Just create the `<datalist>` directly instead
	 */
	public static function suggestionlist($data, $optKey = 'value', $optText = 'text', $idtag = null, $translate = false)
	{
		// Log deprecated message
		JLog::add(
			sprintf(
				'%s() is deprecated. Create the <datalist> tag directly instead.',
				__METHOD__
			),
			JLog::WARNING,
			'deprecated'
		);

		// Note: $idtag is requried but has to be an optional argument in the funtion call due to argument order
		if (!$idtag)
		{
			throw new InvalidArgumentException('$idtag is a required argument in deprecated JHtmlSelect::suggestionlist');
		}

		// Set default options
		$options = array_merge(JHtml::$formatOptions, array('format.depth' => 0, 'id' => false));

		// Get options from the parameters
		$options['id'] = $idtag;
		$options['list.attr'] = null;
		$options['list.translate'] = $translate;
		$options['option.key'] = $optKey;
		$options['option.text'] = $optText;
		$options['list.select'] = null;

		$id = ' id="' . $idtag . '"';

		$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
		$html = $baseIndent . '<datalist' . $id . '>' . $options['format.eol']
			. static::options($data, $options) . $baseIndent . '</datalist>' . $options['format.eol'];

		return $html;
	}

	/**
	 * Generates a grouped HTML selection list from nested arrays.
	 *
	 * @param   array   $data     An array of groups, each of which is an array of options.
	 * @param   string  $name     The value of the HTML name attribute
	 * @param   array   $options  Options, an array of key/value pairs. Valid options are:
	 *                            Format options, {@see JHtml::$formatOptions}.
	 *                            Selection options. See {@see JHtmlSelect::options()}.
	 *                            group.id: The property in each group to use as the group id
	 *                            attribute. Defaults to none.
	 *                            group.label: The property in each group to use as the group
	 *                            label. Defaults to "text". If set to null, the data array index key is
	 *                            used.
	 *                            group.items: The property in each group to use as the array of
	 *                            items in the group. Defaults to "items". If set to null, group.id and
	 *                            group. label are forced to null and the data element is assumed to be a
	 *                            list of selections.
	 *                            id: Value to use as the select element id attribute. Defaults to
	 *                            the same as the name.
	 *                            list.attr: Attributes for the select element. Can be a string or
	 *                            an array of key/value pairs. Defaults to none.
	 *                            list.select: either the value of one selected option or an array
	 *                            of selected options. Default: none.
	 *                            list.translate: Boolean. If set, text and labels are translated via
	 *                            JText::_().
	 *
	 * @return  string  HTML for the select list
	 *
	 * @since   1.5
	 * @throws  RuntimeException If a group has contents that cannot be processed.
	 */
	public static function groupedlist($data, $name, $options = array())
	{
		// Set default options and overwrite with anything passed in
		$options = array_merge(
			JHtml::$formatOptions,
			array('format.depth' => 0, 'group.items' => 'items', 'group.label' => 'text', 'group.label.toHtml' => true, 'id' => false),
			$options
		);

		// Apply option rules
		if ($options['group.items'] === null)
		{
			$options['group.label'] = null;
		}

		$attribs = '';

		if (isset($options['list.attr']))
		{
			if (is_array($options['list.attr']))
			{
				$attribs = ArrayHelper::toString($options['list.attr']);
			}
			else
			{
				$attribs = $options['list.attr'];
			}

			if ($attribs !== '')
			{
				$attribs = ' ' . $attribs;
			}
		}

		$id = $options['id'] !== false ? $options['id'] : $name;
		$id = str_replace(array('[', ']', ' '), '', $id);

		// Disable groups in the options.
		$options['groups'] = false;

		$baseIndent = str_repeat($options['format.indent'], $options['format.depth']++);
		$html = $baseIndent . '<select' . ($id !== '' ? ' id="' . $id . '"' : '') . ' name="' . $name . '"' . $attribs . '>' . $options['format.eol'];
		$groupIndent = str_repeat($options['format.indent'], $options['format.depth']++);

		foreach ($data as $dataKey => $group)
		{
			$label = $dataKey;
			$id = '';
			$noGroup = is_int($dataKey);

			if ($options['group.items'] == null)
			{
				// Sub-list is an associative array
				$subList = $group;
			}
			elseif (is_array($group))
			{
				// Sub-list is in an element of an array.
				$subList = $group[$options['group.items']];

				if (isset($group[$options['group.label']]))
				{
					$label = $group[$options['group.label']];
					$noGroup = false;
				}

				if (isset($options['group.id']) && isset($group[$options['group.id']]))
				{
					$id = $group[$options['group.id']];
					$noGroup = false;
				}
			}
			elseif (is_object($group))
			{
				// Sub-list is in a property of an object
				$subList = $group->{$options['group.items']};

				if (isset($group->{$options['group.label']}))
				{
					$label = $group->{$options['group.label']};
					$noGroup = false;
				}

				if (isset($options['group.id']) && isset($group->{$options['group.id']}))
				{
					$id = $group->{$options['group.id']};
					$noGroup = false;
				}
			}
			else
			{
				throw new RuntimeException('Invalid group contents.', 1);
			}

			if ($noGroup)
			{
				$html .= static::options($subList, $options);
			}
			else
			{
				$html .= $groupIndent . '<optgroup' . (empty($id) ? '' : ' id="' . $id . '"') . ' label="'
					. ($options['group.label.toHtml'] ? htmlspecialchars($label, ENT_COMPAT, 'UTF-8') : $label) . '">' . $options['format.eol']
					. static::options($subList, $options) . $groupIndent . '</optgroup>' . $options['format.eol'];
			}
		}

		$html .= $baseIndent . '</select>' . $options['format.eol'];

		return $html;
	}

	/**
	 * Generates a selection list of integers.
	 *
	 * @param   integer  $start     The start integer
	 * @param   integer  $end       The end integer
	 * @param   integer  $inc       The increment
	 * @param   string   $name      The value of the HTML name attribute
	 * @param   mixed    $attribs   Additional HTML attributes for the `<select>` tag, an array of
	 *                              attributes, or an array of options. Treated as options if it is the last
	 *                              argument passed.
	 * @param   mixed    $selected  The key that is selected
	 * @param   string   $format    The printf format to be applied to the number
	 *
	 * @return  string   HTML for the select list
	 *
	 * @since   1.5
	 */
	public static function integerlist($start, $end, $inc, $name, $attribs = null, $selected = null, $format = '')
	{
		// Set default options
		$options = array_merge(JHtml::$formatOptions, array('format.depth' => 0, 'option.format' => '', 'id' => null));

		if (is_array($attribs) && func_num_args() === 5)
		{
			// Assume we have an options array
			$options = array_merge($options, $attribs);

			// Extract the format and remove it from downstream options
			$format = $options['option.format'];
			unset($options['option.format']);
		}
		else
		{
			// Get options from the parameters
			$options['list.attr'] = $attribs;
			$options['list.select'] = $selected;
		}

		$start = (int) $start;
		$end   = (int) $end;
		$inc   = (int) $inc;

		$data = array();

		for ($i = $start; $i <= $end; $i += $inc)
		{
			$data[$i] = $format ? sprintf($format, $i) : $i;
		}

		// Tell genericlist() to use array keys
		$options['option.key'] = null;

		return JHtml::_('select.genericlist', $data, $name, $options);
	}

	/**
	 * Create a placeholder for an option group.
	 *
	 * @param   string  $text     The text for the option
	 * @param   string  $optKey   The returned object property name for the value
	 * @param   string  $optText  The returned object property name for the text
	 *
	 * @return  stdClass
	 *
	 * @deprecated  4.0  Use JHtmlSelect::groupedList()
	 * @see     JHtmlSelect::groupedList()
	 * @since   1.5
	 */
	public static function optgroup($text, $optKey = 'value', $optText = 'text')
	{
		JLog::add('JHtmlSelect::optgroup() is deprecated, use JHtmlSelect::groupedList() instead.', JLog::WARNING, 'deprecated');

		// Set initial state
		static $state = 'open';

		// Toggle between open and close states:
		switch ($state)
		{
			case 'open':
				$obj = new stdClass;
				$obj->$optKey = '<OPTGROUP>';
				$obj->$optText = $text;
				$state = 'close';
				break;
			case 'close':
				$obj = new stdClass;
				$obj->$optKey = '</OPTGROUP>';
				$obj->$optText = $text;
				$state = 'open';
				break;
		}

		return $obj;
	}

	/**
	 * Create an object that represents an option in an option list.
	 *
	 * @param   string   $value    The value of the option
	 * @param   string   $text     The text for the option
	 * @param   mixed    $optKey   If a string, the returned object property name for
	 *                             the value. If an array, options. Valid options are:
	 *                             attr: String|array. Additional attributes for this option.
	 *                             Defaults to none.
	 *                             disable: Boolean. If set, this option is disabled.
	 *                             label: String. The value for the option label.
	 *                             option.attr: The property in each option array to use for
	 *                             additional selection attributes. Defaults to none.
	 *                             option.disable: The property that will hold the disabled state.
	 *                             Defaults to "disable".
	 *                             option.key: The property that will hold the selection value.
	 *                             Defaults to "value".
	 *                             option.label: The property in each option array to use as the
	 *                             selection label attribute. If a "label" option is provided, defaults to
	 *                             "label", if no label is given, defaults to null (none).
	 *                             option.text: The property that will hold the the displayed text.
	 *                             Defaults to "text". If set to null, the option array is assumed to be a
	 *                             list of displayable scalars.
	 * @param   string   $optText  The property that will hold the the displayed text. This
	 *                             parameter is ignored if an options array is passed.
	 * @param   boolean  $disable  Not used.
	 *
	 * @return  stdClass
	 *
	 * @since   1.5
	 */
	public static function option($value, $text = '', $optKey = 'value', $optText = 'text', $disable = false)
	{
		$options = array(
			'attr' => null,
			'disable' => false,
			'option.attr' => null,
			'option.disable' => 'disable',
			'option.key' => 'value',
			'option.label' => null,
			'option.text' => 'text',
		);

		if (is_array($optKey))
		{
			// Merge in caller's options
			$options = array_merge($options, $optKey);
		}
		else
		{
			// Get options from the parameters
			$options['option.key'] = $optKey;
			$options['option.text'] = $optText;
			$options['disable'] = $disable;
		}

		$obj = new stdClass;
		$obj->{$options['option.key']}  = $value;
		$obj->{$options['option.text']} = trim($text) ? $text : $value;

		/*
		 * If a label is provided, save it. If no label is provided and there is
		 * a label name, initialise to an empty string.
		 */
		$hasProperty = $options['option.label'] !== null;

		if (isset($options['label']))
		{
			$labelProperty = $hasProperty ? $options['option.label'] : 'label';
			$obj->$labelProperty = $options['label'];
		}
		elseif ($hasProperty)
		{
			$obj->{$options['option.label']} = '';
		}

		// Set attributes only if there is a property and a value
		if ($options['attr'] !== null)
		{
			$obj->{$options['option.attr']} = $options['attr'];
		}

		// Set disable only if it has a property and a value
		if ($options['disable'] !== null)
		{
			$obj->{$options['option.disable']} = $options['disable'];
		}

		return $obj;
	}

	/**
	 * Generates the option tags for an HTML select list (with no select tag
	 * surrounding the options).
	 *
	 * @param   array    $arr        An array of objects, arrays, or values.
	 * @param   mixed    $optKey     If a string, this is the name of the object variable for
	 *                               the option value. If null, the index of the array of objects is used. If
	 *                               an array, this is a set of options, as key/value pairs. Valid options are:
	 *                               -Format options, {@see JHtml::$formatOptions}.
	 *                               -groups: Boolean. If set, looks for keys with the value
	 *                                "&lt;optgroup>" and synthesizes groups from them. Deprecated. Defaults
	 *                                true for backwards compatibility.
	 *                               -list.select: either the value of one selected option or an array
	 *                                of selected options. Default: none.
	 *                               -list.translate: Boolean. If set, text and labels are translated via
	 *                                JText::_(). Default is false.
	 *                               -option.id: The property in each option array to use as the
	 *                                selection id attribute. Defaults to none.
	 *                               -option.key: The property in each option array to use as the
	 *                                selection value. Defaults to "value". If set to null, the index of the
	 *                                option array is used.
	 *                               -option.label: The property in each option array to use as the
	 *                                selection label attribute. Defaults to null (none).
	 *                               -option.text: The property in each option array to use as the
	 *                               displayed text. Defaults to "text". If set to null, the option array is
	 *                               assumed to be a list of displayable scalars.
	 *                               -option.attr: The property in each option array to use for
	 *                                additional selection attributes. Defaults to none.
	 *                               -option.disable: The property that will hold the disabled state.
	 *                                Defaults to "disable".
	 *                               -option.key: The property that will hold the selection value.
	 *                                Defaults to "value".
	 *                               -option.text: The property that will hold the the displayed text.
	 *                               Defaults to "text". If set to null, the option array is assumed to be a
	 *                               list of displayable scalars.
	 * @param   string   $optText    The name of the object variable for the option text.
	 * @param   mixed    $selected   The key that is selected (accepts an array or a string)
	 * @param   boolean  $translate  Translate the option values.
	 *
	 * @return  string  HTML for the select list
	 *
	 * @since   1.5
	 */
	public static function options($arr, $optKey = 'value', $optText = 'text', $selected = null, $translate = false)
	{
		$options = array_merge(
			JHtml::$formatOptions,
			static::$optionDefaults['option'],
			array('format.depth' => 0, 'groups' => true, 'list.select' => null, 'list.translate' => false)
		);

		if (is_array($optKey))
		{
			// Set default options and overwrite with anything passed in
			$options = array_merge($options, $optKey);
		}
		else
		{
			// Get options from the parameters
			$options['option.key'] = $optKey;
			$options['option.text'] = $optText;
			$options['list.select'] = $selected;
			$options['list.translate'] = $translate;
		}

		$html = '';
		$baseIndent = str_repeat($options['format.indent'], $options['format.depth']);

		foreach ($arr as $elementKey => &$element)
		{
			$attr = '';
			$extra = '';
			$label = '';
			$id = '';

			if (is_array($element))
			{
				$key = $options['option.key'] === null ? $elementKey : $element[$options['option.key']];
				$text = $element[$options['option.text']];

				if (isset($element[$options['option.attr']]))
				{
					$attr = $element[$options['option.attr']];
				}

				if (isset($element[$options['option.id']]))
				{
					$id = $element[$options['option.id']];
				}

				if (isset($element[$options['option.label']]))
				{
					$label = $element[$options['option.label']];
				}

				if (isset($element[$options['option.disable']]) && $element[$options['option.disable']])
				{
					$extra .= ' disabled="disabled"';
				}
			}
			elseif (is_object($element))
			{
				$key = $options['option.key'] === null ? $elementKey : $element->{$options['option.key']};
				$text = $element->{$options['option.text']};

				if (isset($element->{$options['option.attr']}))
				{
					$attr = $element->{$options['option.attr']};
				}

				if (isset($element->{$options['option.id']}))
				{
					$id = $element->{$options['option.id']};
				}

				if (isset($element->{$options['option.label']}))
				{
					$label = $element->{$options['option.label']};
				}

				if (isset($element->{$options['option.disable']}) && $element->{$options['option.disable']})
				{
					$extra .= ' disabled="disabled"';
				}

				if (isset($element->{$options['option.class']}) && $element->{$options['option.class']})
				{
					$extra .= ' class="' . $element->{$options['option.class']} . '"';
				}

				if (isset($element->{$options['option.onclick']}) && $element->{$options['option.onclick']})
				{
					$extra .= ' onclick="' . $element->{$options['option.onclick']} . '"';
				}
			}
			else
			{
				// This is a simple associative array
				$key = $elementKey;
				$text = $element;
			}

			/*
			 * The use of options that contain optgroup HTML elements was
			 * somewhat hacked for J1.5. J1.6 introduces the grouplist() method
			 * to handle this better. The old solution is retained through the
			 * "groups" option, which defaults true in J1.6, but should be
			 * deprecated at some point in the future.
			 */

			$key = (string) $key;

			if ($key === '<OPTGROUP>' && $options['groups'])
			{
				$html .= $baseIndent . '<optgroup label="' . ($options['list.translate'] ? JText::_($text) : $text) . '">' . $options['format.eol'];
				$baseIndent = str_repeat($options['format.indent'], ++$options['format.depth']);
			}
			elseif ($key === '</OPTGROUP>' && $options['groups'])
			{
				$baseIndent = str_repeat($options['format.indent'], --$options['format.depth']);
				$html .= $baseIndent . '</optgroup>' . $options['format.eol'];
			}
			else
			{
				// If no string after hyphen - take hyphen out
				$splitText = explode(' - ', $text, 2);
				$text = $splitText[0];

				if (isset($splitText[1]) && $splitText[1] !== '' && !preg_match('/^[\s]+$/', $splitText[1]))
				{
					$text .= ' - ' . $splitText[1];
				}

				if (!empty($label) && $options['list.translate'])
				{
					$label = JText::_($label);
				}

				if ($options['option.label.toHtml'])
				{
					$label = htmlentities($label);
				}

				if (is_array($attr))
				{
					$attr = ArrayHelper::toString($attr);
				}
				else
				{
					$attr = trim($attr);
				}

				$extra = ($id ? ' id="' . $id . '"' : '') . ($label ? ' label="' . $label . '"' : '') . ($attr ? ' ' . $attr : '') . $extra;

				if (is_array($options['list.select']))
				{
					foreach ($options['list.select'] as $val)
					{
						$key2 = is_object($val) ? $val->{$options['option.key']} : $val;

						if ($key == $key2)
						{
							$extra .= ' selected="selected"';
							break;
						}
					}
				}
				elseif ((string) $key === (string) $options['list.select'])
				{
					$extra .= ' selected="selected"';
				}

				if ($options['list.translate'])
				{
					$text = JText::_($text);
				}

				// Generate the option, encoding as required
				$html .= $baseIndent . '<option value="' . ($options['option.key.toHtml'] ? htmlspecialchars($key, ENT_COMPAT, 'UTF-8') : $key) . '"'
					. $extra . '>';
				$html .= $options['option.text.toHtml'] ? htmlentities(html_entity_decode($text, ENT_COMPAT, 'UTF-8'), ENT_COMPAT, 'UTF-8') : $text;
				$html .= '</option>' . $options['format.eol'];
			}
		}

		return $html;
	}

	/**
	 * Generates an HTML radio list.
	 *
	 * @param   array    $data       An array of objects
	 * @param   string   $name       The value of the HTML name attribute
	 * @param   string   $attribs    Additional HTML attributes for the `<select>` tag
	 * @param   mixed    $optKey     The key that is selected
	 * @param   string   $optText    The name of the object variable for the option value
	 * @param   string   $selected   The name of the object variable for the option text
	 * @param   boolean  $idtag      Value of the field id or null by default
	 * @param   boolean  $translate  True if options will be translated
	 *
	 * @return  string  HTML for the select list
	 *
	 * @since   1.5
	 */
	public static function radiolist($data, $name, $attribs = null, $optKey = 'value', $optText = 'text', $selected = null, $idtag = false,
		$translate = false)
	{

		if (is_array($attribs))
		{
			$attribs = ArrayHelper::toString($attribs);
		}

		$id_text = $idtag ?: $name;

		$html = '<div class="controls">';

		foreach ($data as $obj)
		{
			$k = $obj->$optKey;
			$t = $translate ? JText::_($obj->$optText) : $obj->$optText;
			$id = (isset($obj->id) ? $obj->id : null);

			$extra = '';
			$id = $id ? $obj->id : $id_text . $k;

			if (is_array($selected))
			{
				foreach ($selected as $val)
				{
					$k2 = is_object($val) ? $val->$optKey : $val;

					if ($k == $k2)
					{
						$extra .= ' selected="selected" ';
						break;
					}
				}
			}
			else
			{
				$extra .= ((string) $k === (string) $selected ? ' checked="checked" ' : '');
			}

			$html .= "\n\t" . '<label for="' . $id . '" id="' . $id . '-lbl" class="radio">';
			$html .= "\n\t\n\t" . '<input type="radio" name="' . $name . '" id="' . $id . '" value="' . $k . '" ' . $extra
				. $attribs . ' />' . $t;
			$html .= "\n\t" . '</label>';
		}

		$html .= "\n";
		$html .= '</div>';
		$html .= "\n";

		return $html;
	}
}
cms/html/language/en-GB/en-GB.jhtmldate.ini000064400000001523152177723700014325 0ustar00; Joomla! Project
; Copyright (C) 2005 - 2019 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8

JLIB_HTML_DATE_RELATIVE_DAYS="%s days ago"
JLIB_HTML_DATE_RELATIVE_DAYS_1="%s day ago"
JLIB_HTML_DATE_RELATIVE_DAYS_0="%s days ago"
JLIB_HTML_DATE_RELATIVE_HOURS="%s hours ago"
JLIB_HTML_DATE_RELATIVE_HOURS_1="%s hour ago"
JLIB_HTML_DATE_RELATIVE_HOURS_0="%s hours ago"
JLIB_HTML_DATE_RELATIVE_LESSTHANAMINUTE="Less than a minute ago"
JLIB_HTML_DATE_RELATIVE_MINUTES="%s minutes ago"
JLIB_HTML_DATE_RELATIVE_MINUTES_1="%s minute ago"
JLIB_HTML_DATE_RELATIVE_MINUTES_0="%s minutes ago"
JLIB_HTML_DATE_RELATIVE_WEEKS="%s weeks ago"
JLIB_HTML_DATE_RELATIVE_WEEKS_1="%s week ago"
JLIB_HTML_DATE_RELATIVE_WEEKS_0="%s weeks ago"
cms/html/tabs.php000064400000006236152177723700007756 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for Tabs elements.
 *
 * @since       1.6
 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
 */
abstract class JHtmlTabs
{
	/**
	 * Creates a panes and creates the JavaScript object for it.
	 *
	 * @param   string  $group   The pane identifier.
	 * @param   array   $params  An array of option.
	 *
	 * @return  string
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function start($group = 'tabs', $params = array())
	{
		static::loadBehavior($group, $params);

		return '<dl class="tabs" id="' . $group . '"><dt style="display:none;"></dt><dd style="display:none;">';
	}

	/**
	 * Close the current pane
	 *
	 * @return  string  HTML to close the pane
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function end()
	{
		return '</dd></dl>';
	}

	/**
	 * Begins the display of a new panel.
	 *
	 * @param   string  $text  Text to display.
	 * @param   string  $id    Identifier of the panel.
	 *
	 * @return  string  HTML to start a new panel
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	public static function panel($text, $id)
	{
		return '</dd><dt class="tabs ' . $id . '"><span><h3><a href="javascript:void(0);">' . $text . '</a></h3></span></dt><dd class="tabs">';
	}

	/**
	 * Load the JavaScript behavior.
	 *
	 * @param   string  $group   The pane identifier.
	 * @param   array   $params  Array of options.
	 *
	 * @return  void
	 *
	 * @since   1.6
	 * @deprecated  3.7.0 These helpers are dependent on the deprecated MooTools support
	 */
	protected static function loadBehavior($group, $params = array())
	{
		static $loaded = array();

		if (!array_key_exists((string) $group, $loaded))
		{
			// Include MooTools framework
			JHtml::_('behavior.framework', true);

			$opt['onActive']            = isset($params['onActive']) ? '\\' . $params['onActive'] : null;
			$opt['onBackground']        = isset($params['onBackground']) ? '\\' . $params['onBackground'] : null;
			$opt['display']             = isset($params['startOffset']) ? (int) $params['startOffset'] : null;
			$opt['titleSelector']       = 'dt.tabs';
			$opt['descriptionSelector'] = 'dd.tabs';

			// When use storage is set and value is false - By default we allow to use storage
			$opt['useStorage'] = !(isset($params['useCookie']) && !$params['useCookie']);

			$options = JHtml::getJSObject($opt);

			$js = '	window.addEvent(\'domready\', function(){
						$$(\'dl#' . $group . '.tabs\').each(function(tabs){
							new JTabs(tabs, ' . $options . ');
						});
					});';

			$document = JFactory::getDocument();
			$document->addScriptDeclaration($js);
			JHtml::_('script', 'system/tabs.js', array('version' => 'auto', 'relative' => true));

			$loaded[(string) $group] = true;
		}
	}
}
cms/html/contentlanguage.php000064400000003047152177723700012200 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class working with content language select lists
 *
 * @since  1.6
 */
abstract class JHtmlContentLanguage
{
	/**
	 * Cached array of the content language items.
	 *
	 * @var    array
	 * @since  1.6
	 */
	protected static $items = null;

	/**
	 * Get a list of the available content language items.
	 *
	 * @param   boolean  $all        True to include All (*)
	 * @param   boolean  $translate  True to translate All
	 *
	 * @return  string
	 *
	 * @see     JFormFieldContentLanguage
	 * @since   1.6
	 */
	public static function existing($all = false, $translate = false)
	{
		if (empty(static::$items))
		{
			// Get the database object and a new query object.
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true);

			// Build the query.
			$query->select('a.lang_code AS value, a.title AS text, a.title_native')
				->from('#__languages AS a')
				->where('a.published >= 0')
				->order('a.title');

			// Set the query and load the options.
			$db->setQuery($query);
			static::$items = $db->loadObjectList();
		}

		if ($all)
		{
			$all_option = array(new JObject(array('value' => '*', 'text' => $translate ? JText::alt('JALL', 'language') : 'JALL_LANGUAGE')));

			return array_merge($all_option, static::$items);
		}
		else
		{
			return static::$items;
		}
	}
}
cms/html/list.php000064400000015306152177723700007776 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\String\StringHelper;

/**
 * Utility class for creating different select lists
 *
 * @since  1.5
 */
abstract class JHtmlList
{
	/**
	 * Build the select list to choose an image
	 *
	 * @param   string  $name        The name of the field
	 * @param   string  $active      The selected item
	 * @param   string  $javascript  Alternative javascript
	 * @param   string  $directory   Directory the images are stored in
	 * @param   string  $extensions  Allowed extensions
	 *
	 * @return  array  Image names
	 *
	 * @since   1.5
	 */
	public static function images($name, $active = null, $javascript = null, $directory = null, $extensions = 'bmp|gif|jpg|png')
	{
		if (!$directory)
		{
			$directory = '/images/';
		}

		if (!$javascript)
		{
			$javascript = "onchange=\"if (document.forms.adminForm." . $name
				. ".options[selectedIndex].value!='') {document.imagelib.src='..$directory' + document.forms.adminForm." . $name
				. ".options[selectedIndex].value} else {document.imagelib.src='media/system/images/blank.png'}\"";
		}

		$imageFiles = new DirectoryIterator(JPATH_SITE . '/' . $directory);
		$images = array(JHtml::_('select.option', '', JText::_('JOPTION_SELECT_IMAGE')));

		foreach ($imageFiles as $file)
		{
			$fileName = $file->getFilename();

			if (!$file->isFile())
			{
				continue;
			}

			if (preg_match('#(' . $extensions . ')$#', $fileName))
			{
				$images[] = JHtml::_('select.option', $fileName);
			}
		}

		$images = JHtml::_(
			'select.genericlist',
			$images,
			$name,
			array(
				'list.attr' => 'class="inputbox" size="1" ' . $javascript,
				'list.select' => $active,
			)
		);

		return $images;
	}

	/**
	 * Returns an array of options
	 *
	 * @param   string   $query  SQL with 'ordering' AS value and 'name field' AS text
	 * @param   integer  $chop   The length of the truncated headline
	 *
	 * @return  array  An array of objects formatted for JHtml list processing
	 *
	 * @since   1.5
	 */
	public static function genericordering($query, $chop = 30)
	{
		$db = JFactory::getDbo();
		$options = array();
		$db->setQuery($query);

		$items = $db->loadObjectList();

		if (empty($items))
		{
			$options[] = JHtml::_('select.option', 1, JText::_('JOPTION_ORDER_FIRST'));

			return $options;
		}

		$options[] = JHtml::_('select.option', 0, '0 ' . JText::_('JOPTION_ORDER_FIRST'));

		for ($i = 0, $n = count($items); $i < $n; $i++)
		{
			$items[$i]->text = JText::_($items[$i]->text);

			if (StringHelper::strlen($items[$i]->text) > $chop)
			{
				$text = StringHelper::substr($items[$i]->text, 0, $chop) . '...';
			}
			else
			{
				$text = $items[$i]->text;
			}

			$options[] = JHtml::_('select.option', $items[$i]->value, $items[$i]->value . '. ' . $text);
		}

		$options[] = JHtml::_('select.option', $items[$i - 1]->value + 1, ($items[$i - 1]->value + 1) . ' ' . JText::_('JOPTION_ORDER_LAST'));

		return $options;
	}

	/**
	 * Build the select list for Ordering derived from a query
	 *
	 * @param   integer  $name      The scalar value
	 * @param   string   $query     The query
	 * @param   string   $attribs   HTML tag attributes
	 * @param   string   $selected  The selected item
	 * @param   integer  $neworder  1 if new and first, -1 if new and last, 0  or null if existing item
	 *
	 * @return  string   HTML markup for the select list
	 *
	 * @since   1.6
	 */
	public static function ordering($name, $query, $attribs = null, $selected = null, $neworder = null)
	{
		if (empty($attribs))
		{
			$attribs = 'class="inputbox" size="1"';
		}

		if (empty($neworder))
		{
			$orders = JHtml::_('list.genericordering', $query);
			$html = JHtml::_('select.genericlist', $orders, $name, array('list.attr' => $attribs, 'list.select' => (int) $selected));
		}
		else
		{
			if ($neworder > 0)
			{
				$text = JText::_('JGLOBAL_NEWITEMSLAST_DESC');
			}
			elseif ($neworder <= 0)
			{
				$text = JText::_('JGLOBAL_NEWITEMSFIRST_DESC');
			}

			$html = '<input type="hidden" name="' . $name . '" value="' . (int) $selected . '" /><span class="readonly">' . $text . '</span>';
		}

		return $html;
	}

	/**
	 * Select list of active users
	 *
	 * @param   string   $name        The name of the field
	 * @param   string   $active      The active user
	 * @param   integer  $nouser      If set include an option to select no user
	 * @param   string   $javascript  Custom javascript
	 * @param   string   $order       Specify a field to order by
	 *
	 * @return  string   The HTML for a list of users list of users
	 *
	 * @since   1.5
	 */
	public static function users($name, $active, $nouser = 0, $javascript = null, $order = 'name')
	{
		$db = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('u.id AS value, u.name AS text')
			->from('#__users AS u')
			->join('LEFT', '#__user_usergroup_map AS m ON m.user_id = u.id')
			->where('u.block = 0')
			->order($order)
			->group('u.id');
		$db->setQuery($query);

		if ($nouser)
		{
			$users[] = JHtml::_('select.option', '0', JText::_('JOPTION_NO_USER'));
			$users = array_merge($users, $db->loadObjectList());
		}
		else
		{
			$users = $db->loadObjectList();
		}

		$users = JHtml::_(
			'select.genericlist',
			$users,
			$name,
			array(
				'list.attr' => 'class="inputbox" size="1" ' . $javascript,
				'list.select' => $active,
			)
		);

		return $users;
	}

	/**
	 * Select list of positions - generally used for location of images
	 *
	 * @param   string   $name        Name of the field
	 * @param   string   $active      The active value
	 * @param   string   $javascript  Alternative javascript
	 * @param   boolean  $none        Null if not assigned
	 * @param   boolean  $center      Null if not assigned
	 * @param   boolean  $left        Null if not assigned
	 * @param   boolean  $right       Null if not assigned
	 * @param   boolean  $id          Null if not assigned
	 *
	 * @return  array  The positions
	 *
	 * @since   1.5
	 */
	public static function positions($name, $active = null, $javascript = null, $none = true, $center = true, $left = true, $right = true,
		$id = false)
	{
		$pos = array();

		if ($none)
		{
			$pos[''] = JText::_('JNONE');
		}

		if ($center)
		{
			$pos['center'] = JText::_('JGLOBAL_CENTER');
		}

		if ($left)
		{
			$pos['left'] = JText::_('JGLOBAL_LEFT');
		}

		if ($right)
		{
			$pos['right'] = JText::_('JGLOBAL_RIGHT');
		}

		$positions = JHtml::_(
			'select.genericlist', $pos, $name,
			array(
				'id' => $id,
				'list.attr' => 'class="inputbox" size="1"' . $javascript,
				'list.select' => $active,
				'option.key' => null,
			)
		);

		return $positions;
	}
}
cms/html/dropdown.php000064400000021451152177723700010655 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * HTML utility class for building a dropdown menu
 *
 * @since  3.0
 */
abstract class JHtmlDropdown
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.0
	 */
	protected static $loaded = array();

	/**
	 * @var    string  HTML markup for the dropdown list
	 * @since  3.0
	 */
	protected static $dropDownList = null;

	/**
	 * Method to inject needed script
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function init()
	{
		// Only load once
		if (isset(static::$loaded[__METHOD__]))
		{
			return;
		}

		// Depends on Bootstrap
		JHtml::_('bootstrap.framework');

		JFactory::getDocument()->addScriptDeclaration("
			(function($){
				$(document).ready(function (){
					$('.has-context')
					.mouseenter(function (){
						$('.btn-group',$(this)).show();
					})
					.mouseleave(function (){
						$('.btn-group',$(this)).hide();
						$('.btn-group',$(this)).removeClass('open');
					});

					contextAction =function (cbId, task)
					{
						$('input[name=\"cid[]\"]').removeAttr('checked');
						$('#' + cbId).attr('checked','checked');
						Joomla.submitbutton(task);
					}
				});
			})(jQuery);
			"
		);

		// Set static array
		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Method to start a new dropdown menu
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function start()
	{
		// Only start once
		if (isset(static::$loaded[__METHOD__]) && static::$loaded[__METHOD__] == true)
		{
			return;
		}

		$dropDownList = '<div class="btn-group" style="margin-left:6px;display:none">
							<a href="#" data-toggle="dropdown" class="dropdown-toggle btn btn-mini">
								<span class="caret"></span>
							</a>
							<ul class="dropdown-menu">';
		static::$dropDownList = $dropDownList;
		static::$loaded[__METHOD__] = true;

		return;
	}

	/**
	 * Method to render current dropdown menu
	 *
	 * @return  string  HTML markup for the dropdown list
	 *
	 * @since   3.0
	 */
	public static function render()
	{
		$dropDownList  = static::$dropDownList;
		$dropDownList .= '</ul></div>';

		static::$dropDownList = null;
		static::$loaded['JHtmlDropdown::start'] = false;

		return $dropDownList;
	}

	/**
	 * Append an edit item to the current dropdown menu
	 *
	 * @param   integer  $id          Record ID
	 * @param   string   $prefix      Task prefix
	 * @param   string   $customLink  The custom link if dont use default Joomla action format
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function edit($id, $prefix = '', $customLink = '')
	{
		static::start();

		if (!$customLink)
		{
			$option = JFactory::getApplication()->input->getCmd('option');
			$link = 'index.php?option=' . $option;
		}
		else
		{
			$link = $customLink;
		}

		$link .= '&task=' . $prefix . 'edit&id=' . $id;
		$link = JRoute::_($link);

		static::addCustomItem(JText::_('JACTION_EDIT'), $link);

		return;
	}

	/**
	 * Append a publish item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function publish($checkboxId, $prefix = '')
	{
		$task = $prefix . 'publish';
		static::addCustomItem(JText::_('JTOOLBAR_PUBLISH'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append an unpublish item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function unpublish($checkboxId, $prefix = '')
	{
		$task = $prefix . 'unpublish';
		static::addCustomItem(JText::_('JTOOLBAR_UNPUBLISH'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append a featured item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function featured($checkboxId, $prefix = '')
	{
		$task = $prefix . 'featured';
		static::addCustomItem(JText::_('JFEATURED'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append an unfeatured item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function unfeatured($checkboxId, $prefix = '')
	{
		$task = $prefix . 'unfeatured';
		static::addCustomItem(JText::_('JUNFEATURED'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append an archive item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function archive($checkboxId, $prefix = '')
	{
		$task = $prefix . 'archive';
		static::addCustomItem(JText::_('JTOOLBAR_ARCHIVE'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append an unarchive item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function unarchive($checkboxId, $prefix = '')
	{
		$task = $prefix . 'unpublish';
		static::addCustomItem(JText::_('JTOOLBAR_UNARCHIVE'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append a trash item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function trash($checkboxId, $prefix = '')
	{
		$task = $prefix . 'trash';
		static::addCustomItem(JText::_('JTOOLBAR_TRASH'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append an untrash item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function untrash($checkboxId, $prefix = '')
	{
		$task = $prefix . 'publish';
		static::addCustomItem(JText::_('JTOOLBAR_UNTRASH'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Append a checkin item to the current dropdown menu
	 *
	 * @param   string  $checkboxId  ID of corresponding checkbox of the record
	 * @param   string  $prefix      The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function checkin($checkboxId, $prefix = '')
	{
		$task = $prefix . 'checkin';
		static::addCustomItem(JText::_('JTOOLBAR_CHECKIN'), 'javascript:void(0)', 'onclick="contextAction(\'' . $checkboxId . '\', \'' . $task . '\')"');

		return;
	}

	/**
	 * Writes a divider between dropdown items
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function divider()
	{
		static::$dropDownList .= '<li class="divider"></li>';

		return;
	}

	/**
	 * Append a custom item to current dropdown menu
	 *
	 * @param   string   $label           The label of item
	 * @param   string   $link            The link of item
	 * @param   string   $linkAttributes  Custom link attributes
	 * @param   string   $className       Class name of item
	 * @param   boolean  $ajaxLoad        True if using ajax load when item clicked
	 * @param   string   $jsCallBackFunc  Javascript function name, called when ajax load successfully
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function addCustomItem($label, $link = 'javascript:void(0)', $linkAttributes = '', $className = '', $ajaxLoad = false,
		$jsCallBackFunc = null)
	{
		static::start();

		if ($ajaxLoad)
		{
			$href = ' href = "javascript:void(0)" onclick="loadAjax(\'' . $link . '\', \'' . $jsCallBackFunc . '\')"';
		}
		else
		{
			$href = ' href = "' . $link . '" ';
		}

		$dropDownList = static::$dropDownList;
		$dropDownList .= '<li class="' . $className . '"><a ' . $linkAttributes . $href . ' >';
		$dropDownList .= $label;
		$dropDownList .= '</a></li>';
		static::$dropDownList = $dropDownList;

		return;
	}
}
cms/html/actionsdropdown.php000064400000013157152177723700012242 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * HTML utility class for building a dropdown menu
 *
 * @since  3.2
 */
abstract class JHtmlActionsDropdown
{
	/**
	 * @var    string  HTML markup for the dropdown list
	 * @since  3.2
	 */
	protected static $dropDownList = array();

	/**
	 * Method to render current dropdown menu
	 *
	 * @param   string  $item  An item to render.
	 *
	 * @return  string  HTML markup for the dropdown list
	 *
	 * @since   3.2
	 */
	public static function render($item = '')
	{
		$html = array();

		$html[] = '<button data-toggle="dropdown" class="dropdown-toggle btn btn-micro">';
		$html[] = '<span class="caret"></span>';

		if ($item)
		{
			$html[] = '<span class="element-invisible">' . JText::sprintf('JACTIONS', $item) . '</span>';
		}

		$html[] = '</button>';
		$html[] = '<ul class="dropdown-menu">';
		$html[] = implode('', static::$dropDownList);
		$html[] = '</ul>';

		static::$dropDownList = null;

		return implode('', $html);
	}

	/**
	 * Append a publish item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function publish($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'publish';
		static::addCustomItem(JText::_('JTOOLBAR_PUBLISH'), 'publish', $id, $task);
	}

	/**
	 * Append an unpublish item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function unpublish($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'unpublish';
		static::addCustomItem(JText::_('JTOOLBAR_UNPUBLISH'), 'unpublish', $id, $task);
	}

	/**
	 * Append a feature item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function feature($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'featured';
		static::addCustomItem(JText::_('JFEATURE'), 'featured', $id, $task);
	}

	/**
	 * Append an unfeature item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function unfeature($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'unfeatured';
		static::addCustomItem(JText::_('JUNFEATURE'), 'unfeatured', $id, $task);
	}

	/**
	 * Append an archive item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function archive($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'archive';
		static::addCustomItem(JText::_('JTOOLBAR_ARCHIVE'), 'archive', $id, $task);
	}

	/**
	 * Append an unarchive item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function unarchive($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'unpublish';
		static::addCustomItem(JText::_('JTOOLBAR_UNARCHIVE'), 'unarchive', $id, $task);
	}

	/**
	 * Append a duplicate item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function duplicate($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'duplicate';
		static::addCustomItem(JText::_('JTOOLBAR_DUPLICATE'), 'copy', $id, $task);
	}

	/**
	 * Append a trash item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function trash($id, $prefix = '')
	{
		$task = ($prefix ? $prefix . '.' : '') . 'trash';
		static::addCustomItem(JText::_('JTOOLBAR_TRASH'), 'trash', $id, $task);
	}

	/**
	 * Append an untrash item to the current dropdown menu
	 *
	 * @param   string  $id      ID of corresponding checkbox of the record
	 * @param   string  $prefix  The task prefix
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function untrash($id, $prefix = '')
	{
		self::publish($id, $prefix);
	}

	/**
	 * Writes a divider between dropdown items
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function divider()
	{
		static::$dropDownList[] = '<li class="divider"></li>';
	}

	/**
	 * Append a custom item to current dropdown menu.
	 *
	 * @param   string  $label  The label of the item.
	 * @param   string  $icon   The icon classname.
	 * @param   string  $id     The item id.
	 * @param   string  $task   The task.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public static function addCustomItem($label, $icon = '', $id = '', $task = '')
	{
		static::$dropDownList[] = '<li>'
			. '<a href = "javascript://" onclick="listItemTask(\'' . $id . '\', \'' . $task . '\')">'
			. ($icon ? '<span class="icon-' . $icon . '" aria-hidden="true"></span> ' : '')
			. $label
			. '</a>'
			. '</li>';
	}
}
cms/html/tel.php000064400000004215152177723700007604 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * HTML helper class for rendering telephone numbers.
 *
 * @since  1.6
 */
abstract class JHtmlTel
{
	/**
	 * Converts strings of integers into more readable telephone format
	 *
	 * By default, the ITU-T format will automatically be used.
	 * However, one of the allowed unit types may also be used instead.
	 *
	 * @param   integer  $number       The integers in a phone number with dot separated country code
	 *                                 ccc.nnnnnnn where ccc represents country code and nnn represents the local number.
	 * @param   string   $displayplan  The numbering plan used to display the numbers.
	 *
	 * @return  string  The formatted telephone number.
	 *
	 * @see     JFormRuleTel
	 * @since   1.6
	 */
	public static function tel($number, $displayplan)
	{
		$number = explode('.', $number);
		$countrycode = $number[0];
		$number = $number[1];

		if ($displayplan === 'ITU-T' || $displayplan === 'International' || $displayplan === 'int' || $displayplan === 'missdn' || $displayplan == null)
		{
			$display[0] = '+';
			$display[1] = $countrycode;
			$display[2] = ' ';
			$display[3] = implode(str_split($number, 2), ' ');
		}
		elseif ($displayplan === 'NANP' || $displayplan === 'northamerica' || $displayplan === 'US')
		{
			$display[0] = '(';
			$display[1] = substr($number, 0, 3);
			$display[2] = ') ';
			$display[3] = substr($number, 3, 3);
			$display[4] = '-';
			$display[5] = substr($number, 6, 4);
		}
		elseif ($displayplan === 'EPP' || $displayplan === 'IETF')
		{
			$display[0] = '+';
			$display[1] = $countrycode;
			$display[2] = '.';
			$display[3] = $number;
		}
		elseif ($displayplan === 'ARPA' || $displayplan === 'ENUM')
		{
			$number = implode(str_split(strrev($number), 1), '.');
			$display[0] = '+';
			$display[1] = $number;
			$display[2] = '.';
			$display[3] = $countrycode;
			$display[4] = '.e164.arpa';
		}

		return implode($display, '');
	}
}
cms/html/debug.php000064400000002710152177723700010104 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Extended Utility class for render debug information.
 *
 * @since  3.7.0
 */
abstract class JHtmlDebug
{
	/**
	 * xdebug.file_link_format from the php.ini.
	 *
	 * Make this property public to support test.
	 *
	 * @var    string
	 *
	 * @since  3.7.0
	 */
	public static $xdebugLinkFormat;

	/**
	 * Replaces the Joomla! root with "JROOT" to improve readability.
	 * Formats a link with a special value xdebug.file_link_format
	 * from the php.ini file.
	 *
	 * @param   string  $file  The full path to the file.
	 * @param   string  $line  The line number.
	 *
	 * @return  string
	 *
	 * @throws  \InvalidArgumentException
	 *
	 * @since   3.7.0
	 */
	public static function xdebuglink($file, $line = '')
	{
		if (static::$xdebugLinkFormat === null)
		{
			static::$xdebugLinkFormat = ini_get('xdebug.file_link_format');
		}

		$link = str_replace(JPATH_ROOT, 'JROOT', JPath::clean($file));
		$link .= $line ? ':' . $line : '';

		if (static::$xdebugLinkFormat)
		{
			$href = static::$xdebugLinkFormat;
			$href = str_replace('%f', $file, $href);
			$href = str_replace('%l', $line, $href);

			$html = JHtml::_('link', $href, $link);
		}
		else
		{
			$html = $link;
		}

		return $html;
	}
}
cms/html/formbehavior.php000064400000007415152177723700011510 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Utility class for form related behaviors
 *
 * @since  3.0
 */
abstract class JHtmlFormbehavior
{
	/**
	 * @var    array  Array containing information for loaded files
	 * @since  3.0
	 */
	protected static $loaded = array();

	/**
	 * Method to load the Chosen JavaScript framework and supporting CSS into the document head
	 *
	 * If debugging mode is on an uncompressed version of Chosen is included for easier debugging.
	 *
	 * @param   string  $selector  Class for Chosen elements.
	 * @param   mixed   $debug     Is debugging mode on? [optional]
	 * @param   array   $options   the possible Chosen options as name => value [optional]
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function chosen($selector = '.advancedSelect', $debug = null, $options = array())
	{
		if (isset(static::$loaded[__METHOD__][$selector]))
		{
			return;
		}

		// If no debugging value is set, use the configuration setting
		if ($debug === null)
		{
			$debug = JDEBUG;
		}

		// Default settings
		if (!isset($options['disable_search_threshold']))
		{
			$options['disable_search_threshold'] = 10;
		}

		// Allow searching contains space in query
		if (!isset($options['search_contains']))
		{
			$options['search_contains'] = true;
		}

		if (!isset($options['allow_single_deselect']))
		{
			$options['allow_single_deselect'] = true;
		}

		if (!isset($options['placeholder_text_multiple']))
		{
			$options['placeholder_text_multiple'] = JText::_('JGLOBAL_TYPE_OR_SELECT_SOME_OPTIONS');
		}

		if (!isset($options['placeholder_text_single']))
		{
			$options['placeholder_text_single'] = JText::_('JGLOBAL_SELECT_AN_OPTION');
		}

		if (!isset($options['no_results_text']))
		{
			$options['no_results_text'] = JText::_('JGLOBAL_SELECT_NO_RESULTS_MATCH');
		}

		$displayData = array(
			'debug'     => $debug,
			'options'  => $options,
			'selector' => $selector,
		);

		JLayoutHelper::render('joomla.html.formbehavior.chosen', $displayData);

		static::$loaded[__METHOD__][$selector] = true;

		return;
	}

	/**
	 * Method to load the AJAX Chosen library
	 *
	 * If debugging mode is on an uncompressed version of AJAX Chosen is included for easier debugging.
	 *
	 * @param   Registry  $options  Options in a Registry object
	 * @param   mixed     $debug    Is debugging mode on? [optional]
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function ajaxchosen(Registry $options, $debug = null)
	{
		// Retrieve options/defaults
		$selector       = $options->get('selector', '.tagfield');
		$type           = $options->get('type', 'GET');
		$url            = $options->get('url', null);
		$dataType       = $options->get('dataType', 'json');
		$jsonTermKey    = $options->get('jsonTermKey', 'term');
		$afterTypeDelay = $options->get('afterTypeDelay', '500');
		$minTermLength  = $options->get('minTermLength', '3');

		// Ajax URL is mandatory
		if (!empty($url))
		{
			if (isset(static::$loaded[__METHOD__][$selector]))
			{
				return;
			}

			// Requires chosen to work
			static::chosen($selector, $debug);

			$displayData = array(
				'url'            => $url,
				'debug'          => $debug,
				'options'        => $options,
				'selector'       => $selector,
				'type'           => $type,
				'dataType'       => $dataType,
				'jsonTermKey'    => $jsonTermKey,
				'afterTypeDelay' => $afterTypeDelay,
				'minTermLength'  => $minTermLength,
			);

			JLayoutHelper::render('joomla.html.formbehavior.ajaxchosen', $displayData);

			static::$loaded[__METHOD__][$selector] = true;
		}

		return;
	}
}
cms/html/adminlanguage.php000064400000002610152177723700011611 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class working with administrator language select lists
 *
 * @since  3.8.0
 */
abstract class JHtmlAdminLanguage
{
	/**
	 * Cached array of the administrator language items.
	 *
	 * @var    array
	 * @since  3.8.0
	 */
	protected static $items = null;

	/**
	 * Get a list of the available administrator language items.
	 *
	 * @param   boolean  $all        True to include All (*)
	 * @param   boolean  $translate  True to translate All
	 *
	 * @return  string
	 *
	 * @since   3.8.0
	 */
	public static function existing($all = false, $translate = false)
	{
		if (empty(static::$items))
		{
			$languages       = array();
			$admin_languages = JLanguageHelper::getKnownLanguages(JPATH_ADMINISTRATOR);

			foreach ($admin_languages as $tag => $language)
			{
				$languages[$tag] = $language['nativeName'];
			}

			ksort($languages);

			static::$items = $languages;
		}

		if ($all)
		{
			$all_option = array(new JObject(array('value' => '*', 'text' => $translate ? JText::alt('JALL', 'language') : 'JALL_LANGUAGE')));

			return array_merge($all_option, static::$items);
		}
		else
		{
			return static::$items;
		}
	}
}
cms/html/sidebar.php000064400000006142152177723700010432 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class to render a list view sidebar
 *
 * @since  3.0
 */
abstract class JHtmlSidebar
{
	/**
	 * Menu entries
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected static $entries = array();

	/**
	 * Filters
	 *
	 * @var    array
	 * @since  3.0
	 */
	protected static $filters = array();

	/**
	 * Value for the action attribute of the form.
	 *
	 * @var    string
	 * @since  3.0
	 */
	protected static $action = '';

	/**
	 * Render the sidebar.
	 *
	 * @return  string  The necessary HTML to display the sidebar
	 *
	 * @since   3.0
	 */
	public static function render()
	{
		// Collect display data
		$data                 = new stdClass;
		$data->list           = static::getEntries();
		$data->filters        = static::getFilters();
		$data->action         = static::getAction();
		$data->displayMenu    = count($data->list);
		$data->displayFilters = count($data->filters);
		$data->hide           = JFactory::getApplication()->input->getBool('hidemainmenu');

		// Create a layout object and ask it to render the sidebar
		$layout      = new JLayoutFile('joomla.sidebars.submenu');

		return $layout->render($data);
	}

	/**
	 * Method to add a menu item to submenu.
	 *
	 * @param   string  $name    Name of the menu item.
	 * @param   string  $link    URL of the menu item.
	 * @param   bool    $active  True if the item is active, false otherwise.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function addEntry($name, $link = '', $active = false)
	{
		static::$entries[] = array($name, $link, $active);
	}

	/**
	 * Returns an array of all submenu entries
	 *
	 * @return  array
	 *
	 * @since   3.0
	 */
	public static function getEntries()
	{
		return static::$entries;
	}

	/**
	 * Method to add a filter to the submenu
	 *
	 * @param   string  $label      Label for the menu item.
	 * @param   string  $name       Name for the filter. Also used as id.
	 * @param   string  $options    Options for the select field.
	 * @param   bool    $noDefault  Don't the label as the empty option
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function addFilter($label, $name, $options, $noDefault = false)
	{
		static::$filters[] = array('label' => $label, 'name' => $name, 'options' => $options, 'noDefault' => $noDefault);
	}

	/**
	 * Returns an array of all filters
	 *
	 * @return  array
	 *
	 * @since   3.0
	 */
	public static function getFilters()
	{
		return static::$filters;
	}

	/**
	 * Set value for the action attribute of the filter form
	 *
	 * @param   string  $action  Value for the action attribute of the form
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public static function setAction($action)
	{
		static::$action = $action;
	}

	/**
	 * Get value for the action attribute of the filter form
	 *
	 * @return  string
	 *
	 * @since   3.0
	 */
	public static function getAction()
	{
		return static::$action;
	}
}
cms/html/email.php000064400000007244152177723700010114 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for cloaking email addresses
 *
 * @since  1.5
 */
abstract class JHtmlEmail
{
	/**
	 * Simple JavaScript email cloaker
	 *
	 * By default replaces an email with a mailto link with email cloaked
	 *
	 * @param   string   $mail    The -mail address to cloak.
	 * @param   boolean  $mailto  True if text and mailing address differ
	 * @param   string   $text    Text for the link
	 * @param   boolean  $email   True if text is an email address
	 *
	 * @return  string  The cloaked email.
	 *
	 * @since   1.5
	 */
	public static function cloak($mail, $mailto = true, $text = '', $email = true)
	{
		// Handle IDN addresses: punycode for href but utf-8 for text displayed.
		if ($mailto && (empty($text) || $email))
		{
			// Use dedicated $text whereas $mail is used as href and must be punycoded.
			$text = JStringPunycode::emailToUTF8($text ?: $mail);
		}
		elseif (!$mailto)
		{
			// In that case we don't use link - so convert $mail back to utf-8.
			$mail = JStringPunycode::emailToUTF8($mail);
		}

		// Convert mail
		$mail = static::convertEncoding($mail);

		// Random hash
		$rand = md5($mail . mt_rand(1, 100000));

		// Split email by @ symbol
		$mail       = explode('@', $mail);
		$mail_parts = explode('.', $mail[1]);

		if ($mailto)
		{
			// Special handling when mail text is different from mail address
			if ($text)
			{
				// Convert text - here is the right place
				$text = static::convertEncoding($text);

				if ($email)
				{
					// Split email by @ symbol
					$text = explode('@', $text);
					$text_parts = explode('.', $text[1]);
					$tmpScript = "var addy_text" . $rand . " = '" . @$text[0] . "' + '&#64;' + '" . implode("' + '&#46;' + '", @$text_parts)
						. "';";
				}
				else
				{
					$tmpScript = "var addy_text" . $rand . " = '" . $text . "';";
				}

				$tmpScript .= "document.getElementById('cloak" . $rand . "').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy"
					. $rand . " + '\'>'+addy_text" . $rand . "+'<\/a>';";
			}
			else
			{
				$tmpScript = "document.getElementById('cloak" . $rand . "').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy"
					. $rand . " + '\'>' +addy" . $rand . "+'<\/a>';";
			}
		}
		else
		{
			$tmpScript = "document.getElementById('cloak" . $rand . "').innerHTML += addy" . $rand . ";";
		}

		$script       = "
				document.getElementById('cloak" . $rand . "').innerHTML = '';
				var prefix = '&#109;a' + 'i&#108;' + '&#116;o';
				var path = 'hr' + 'ef' + '=';
				var addy" . $rand . " = '" . @$mail[0] . "' + '&#64;';
				addy" . $rand . " = addy" . $rand . " + '" . implode("' + '&#46;' + '", $mail_parts) . "';
				$tmpScript
		";

		// TODO: Use inline script for now
		$inlineScript = "<script type='text/javascript'>" . $script . "</script>";

		return '<span id="cloak' . $rand . '">' . JText::_('JLIB_HTML_CLOAKING') . '</span>' . $inlineScript;
	}

	/**
	 * Convert encoded text
	 *
	 * @param   string  $text  Text to convert
	 *
	 * @return  string  The converted text.
	 *
	 * @since   1.5
	 */
	protected static function convertEncoding($text)
	{
		$text = html_entity_decode($text);

		// Replace vowels with character encoding
		$text = str_replace('a', '&#97;', $text);
		$text = str_replace('e', '&#101;', $text);
		$text = str_replace('i', '&#105;', $text);
		$text = str_replace('o', '&#111;', $text);
		$text = str_replace('u', '&#117;', $text);
		$text = htmlentities($text, ENT_QUOTES, 'UTF-8', false);

		return $text;
	}
}
cms/html/links.php000064400000005013152177723700010135 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  HTML
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * Utility class for icons.
 *
 * @since  3.2
 */
abstract class JHtmlLinks
{
	/**
	 * Method to generate html code for groups of lists of links
	 *
	 * @param   array  $groupsOfLinks  Array of links
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function linksgroups($groupsOfLinks)
	{
		$html = array();

		if (count($groupsOfLinks) > 0)
		{
			$layout = new JLayoutFile('joomla.links.groupsopen');
			$html[] = $layout->render('');

			foreach ($groupsOfLinks as $title => $links)
			{
				if (isset($links[0]['separategroup']))
				{
					$layout = new JLayoutFile('joomla.links.groupseparator');
					$html[] = $layout->render($title);
				}

				$layout = new JLayoutFile('joomla.links.groupopen');
				$htmlHeader = $layout->render($title);

				$htmlLinks  = JHtml::_('links.links', $links);

				if ($htmlLinks != '')
				{
					$html[] = $htmlHeader;
					$html[] = $htmlLinks;

					$layout = new JLayoutFile('joomla.links.groupclose');
					$html[] = $layout->render('');
				}
			}

			$layout = new JLayoutFile('joomla.links.groupsclose');
			$html[] = $layout->render('');
		}

		return implode($html);
	}

	/**
	 * Method to generate html code for a list of links
	 *
	 * @param   array  $links  Array of links
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function links($links)
	{
		$html = array();

		foreach ($links as $link)
		{
			$html[] = JHtml::_('links.link', $link);
		}

		return implode($html);
	}

	/**
	 * Method to generate html code for a single link
	 *
	 * @param   array  $link  link properties
	 *
	 * @return  string
	 *
	 * @since   3.2
	 */
	public static function link($link)
	{
		if (isset($link['access']))
		{
			if (is_bool($link['access']))
			{
				if ($link['access'] == false)
				{
					return '';
				}
			}
			else
			{
				// Get the user object to verify permissions
				$user = JFactory::getUser();

				// Take each pair of permission, context values.
				for ($i = 0, $n = count($link['access']); $i < $n; $i += 2)
				{
					if (!$user->authorise($link['access'][$i], $link['access'][$i + 1]))
					{
						return '';
					}
				}
			}
		}

		// Instantiate a new JLayoutFile instance and render the layout
		$layout = new JLayoutFile('joomla.links.link');

		return $layout->render($link);
	}
}
cms/class/loader.php000064400000002237152177723700010431 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  Class
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

use Composer\Autoload\ClassLoader;

/**
 * Decorate Composer ClassLoader for Joomla!
 *
 * For backward compatibility due to class aliasing in the CMS, the loadClass() method was modified to call
 * the JLoader::applyAliasFor() method.
 *
 * @since  3.4
 */
class JClassLoader
{
	/**
	 * The composer class loader
	 *
	 * @var    ClassLoader
	 * @since  3.4
	 */
	private $loader;

	/**
	 * Constructor
	 *
	 * @param   ClassLoader  $loader  Composer autoloader
	 *
	 * @since   3.4
	 */
	public function __construct(ClassLoader $loader)
	{
		$this->loader = $loader;
	}

	/**
	 * Loads the given class or interface.
	 *
	 * @param   string  $class  The name of the class
	 *
	 * @return  boolean|null  True if loaded, null otherwise
	 *
	 * @since   3.4
	 */
	public function loadClass($class)
	{
		if ($result = $this->loader->loadClass($class))
		{
			JLoader::applyAliasFor($class);
		}

		return $result;
	}
}
php-encryption/Crypto.php000064400000060307152177723700011535 0ustar00<?php

/*
 * PHP Encryption Library
 * Copyright (c) 2014, Taylor Hornby
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Web: https://defuse.ca/secure-php-encryption.htm
 * GitHub: https://github.com/defuse/php-encryption 
 *
 * WARNING: This encryption library is not a silver bullet. It only provides
 * symmetric encryption given a uniformly random key. This means you MUST NOT
 * use an ASCII string like a password as the key parameter, it MUST be
 * a uniformly random key generated by CreateNewRandomKey(). If you want to
 * encrypt something with a password, apply a password key derivation function
 * like PBKDF2 or scrypt with a random salt to generate a key.
 *
 * WARNING: Error handling is very important, especially for crypto code! 
 *
 * How to use this code:
 *
 *     Generating a Key
 *     ----------------
 *       try {
 *           $key = Crypto::CreateNewRandomKey();
 *           // WARNING: Do NOT encode $key with bin2hex() or base64_encode(),
 *           // they may leak the key to the attacker through side channels.
 *       } catch (CryptoTestFailedException $ex) {
 *           die('Cannot safely create a key');
 *       } catch (CannotPerformOperationException $ex) {
 *           die('Cannot safely create a key');
 *       }
 *
 *     Encrypting a Message
 *     --------------------
 *       $message = "ATTACK AT DAWN";
 *       try {
 *           $ciphertext = Crypto::Encrypt($message, $key);
 *       } catch (CryptoTestFailedException $ex) {
 *           die('Cannot safely perform encryption');
 *       } catch (CannotPerformOperationException $ex) {
 *           die('Cannot safely perform decryption');
 *       }
 *
 *     Decrypting a Message
 *     --------------------
 *       try {
 *           $decrypted = Crypto::Decrypt($ciphertext, $key);
 *       } catch (InvalidCiphertextException $ex) { // VERY IMPORTANT
 *           // Either:
 *           //   1. The ciphertext was modified by the attacker,
 *           //   2. The key is wrong, or
 *           //   3. $ciphertext is not a valid ciphertext or was corrupted.
 *           // Assume the worst.
 *           die('DANGER! DANGER! The ciphertext has been tampered with!');
 *       } catch (CryptoTestFailedException $ex) {
 *           die('Cannot safely perform encryption');
 *       } catch (CannotPerformOperationException $ex) {
 *           die('Cannot safely perform decryption');
 *       }
 */

/* 
 * Raised by Decrypt() when one of the following conditions are met:
 *  - The key is wrong.
 *  - The ciphertext is invalid or not in the correct format.
 *  - The attacker modified the ciphertext.
 */
class InvalidCiphertextException extends Exception {}
/* If you see these, it means it is NOT SAFE to do encryption on your system. */
class CannotPerformOperationException extends Exception {}
class CryptoTestFailedException extends Exception {}

class Crypto
{
    // Ciphertext format: [____HMAC____][____IV____][____CIPHERTEXT____].

    /* Do not change these constants! */
    const CIPHER = MCRYPT_RIJNDAEL_128;
    const KEY_BYTE_SIZE = 16;
    const CIPHER_MODE = 'cbc';
    const HASH_FUNCTION = 'sha256';
    const MAC_BYTE_SIZE = 32;
    const ENCRYPTION_INFO = 'DefusePHP|KeyForEncryption';
    const AUTHENTICATION_INFO = 'DefusePHP|KeyForAuthentication';

    /*
     * Use this to generate a random encryption key.
     */
    public static function CreateNewRandomKey()
    {
        Crypto::RuntimeTest();
        return self::SecureRandom(self::KEY_BYTE_SIZE);
    }

    /*
     * Encrypts a message.
     * $plaintext is the message to encrypt.
     * $key is the encryption key, a value generated by CreateNewRandomKey().
     * You MUST catch exceptions thrown by this function. See docs above.
     */
    public static function Encrypt($plaintext, $key)
    {
        Crypto::RuntimeTest();

        if (self::our_strlen($key) !== self::KEY_BYTE_SIZE)
        {
            throw new CannotPerformOperationException("Bad key.");
        }

        // Generate a sub-key for encryption.
        $keysize = self::KEY_BYTE_SIZE;
        $ekey = self::HKDF(self::HASH_FUNCTION, $key, $keysize, self::ENCRYPTION_INFO);

        // Generate a random initialization vector.
        self::EnsureFunctionExists("mcrypt_get_iv_size");
        $ivsize = mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE);
        if ($ivsize === FALSE || $ivsize <= 0) {
            throw new CannotPerformOperationException();
        }
        $iv = self::SecureRandom($ivsize);

        $ciphertext = $iv . self::PlainEncrypt($plaintext, $ekey, $iv);

        // Generate a sub-key for authentication and apply the HMAC.
        $akey = self::HKDF(self::HASH_FUNCTION, $key, self::KEY_BYTE_SIZE, self::AUTHENTICATION_INFO);
        $auth = hash_hmac(self::HASH_FUNCTION, $ciphertext, $akey, true);
        $ciphertext = $auth . $ciphertext;

        return $ciphertext;
    }

    /*
     * Decrypts a ciphertext.
     * $ciphertext is the ciphertext to decrypt.
     * $key is the key that the ciphertext was encrypted with.
     * You MUST catch exceptions thrown by this function. See docs above.
     */
    public static function Decrypt($ciphertext, $key)
    {
        Crypto::RuntimeTest();

        // Extract the HMAC from the front of the ciphertext.
        if (self::our_strlen($ciphertext) <= self::MAC_BYTE_SIZE) {
            throw new InvalidCiphertextException();
        }
        $hmac = self::our_substr($ciphertext, 0, self::MAC_BYTE_SIZE);
        if ($hmac === FALSE) {
            throw new CannotPerformOperationException();
        }
        $ciphertext = self::our_substr($ciphertext, self::MAC_BYTE_SIZE);
        if ($ciphertext === FALSE) {
            throw new CannotPerformOperationException();
        }

        // Regenerate the same authentication sub-key.
        $akey = self::HKDF(self::HASH_FUNCTION, $key, self::KEY_BYTE_SIZE, self::AUTHENTICATION_INFO);

        if (self::VerifyHMAC($hmac, $ciphertext, $akey))
        {
            // Regenerate the same encryption sub-key.
            $keysize = self::KEY_BYTE_SIZE;
            $ekey = self::HKDF(self::HASH_FUNCTION, $key, $keysize, self::ENCRYPTION_INFO);

            // Extract the initialization vector from the ciphertext.
            self::EnsureFunctionExists("mcrypt_get_iv_size");
            $ivsize = mcrypt_get_iv_size(self::CIPHER, self::CIPHER_MODE);
            if ($ivsize === FALSE || $ivsize <= 0) {
                throw new CannotPerformOperationException();
            }
            if (self::our_strlen($ciphertext) <= $ivsize) {
                throw new InvalidCiphertextException();
            }
            $iv = self::our_substr($ciphertext, 0, $ivsize);
            if ($iv === FALSE) {
                throw new CannotPerformOperationException();
            }
            $ciphertext = self::our_substr($ciphertext, $ivsize);
            if ($ciphertext === FALSE) {
                throw new CannotPerformOperationException();
            }
            
            $plaintext = self::PlainDecrypt($ciphertext, $ekey, $iv);

            return $plaintext;
        }
        else
        {
            /*
             * We throw an exception instead of returning FALSE because we want
             * a script that doesn't handle this condition to CRASH, instead
             * of thinking the ciphertext decrypted to the value FALSE.
             */
             throw new InvalidCiphertextException();
        }
    }

    /*
     * Runs tests.
     * Raises CannotPerformOperationException or CryptoTestFailedException if
     * one of the tests fail. If any tests fails, your system is not capable of
     * performing encryption, so make sure you fail safe in that case.
     */
    public static function RuntimeTest()
    {
        // 0: Tests haven't been run yet.
        // 1: Tests have passed.
        // 2: Tests are running right now.
        // 3: Tests have failed.
        static $test_state = 0;

        if ($test_state === 1 || $test_state === 2) {
            return;
        }

        try {
            $test_state = 2;
            self::AESTestVector();
            self::HMACTestVector();
            self::HKDFTestVector();

            self::TestEncryptDecrypt();
            if (self::our_strlen(Crypto::CreateNewRandomKey()) != self::KEY_BYTE_SIZE) {
                throw new CryptoTestFailedException();
            }

            if (self::ENCRYPTION_INFO == self::AUTHENTICATION_INFO) {
                throw new CryptoTestFailedException();
            }
        } catch (CryptoTestFailedException $ex) {
            // Do this, otherwise it will stay in the "tests are running" state.
            $test_state = 3;
            throw $ex;
        }

        // Change this to '0' make the tests always re-run (for benchmarking).
        $test_state = 1;
    }

    /*
     * Never call this method directly!
     */
    private static function PlainEncrypt($plaintext, $key, $iv)
    {
        self::EnsureFunctionExists("mcrypt_module_open");
        $crypt = mcrypt_module_open(self::CIPHER, "", self::CIPHER_MODE, "");
        if ($crypt === FALSE) {
            throw new CannotPerformOperationException();
        }

        // Pad the plaintext to a multiple of the block size.
        self::EnsureFunctionExists("mcrypt_enc_get_block_size");
        $block = mcrypt_enc_get_block_size($crypt);
        $pad = $block - (self::our_strlen($plaintext) % $block);
        $plaintext .= str_repeat(chr($pad), $pad);

        self::EnsureFunctionExists("mcrypt_generic_init");
        $ret = mcrypt_generic_init($crypt, $key, $iv);
        if ($ret !== 0) {
            throw new CannotPerformOperationException();
        }
        self::EnsureFunctionExists("mcrypt_generic");
        $ciphertext = mcrypt_generic($crypt, $plaintext);
        self::EnsureFunctionExists("mcrypt_generic_deinit");
        $ret = mcrypt_generic_deinit($crypt);
        if ($ret !== TRUE) {
            throw new CannotPerformOperationException();
        }
        self::EnsureFunctionExists("mcrypt_module_close");
        $ret = mcrypt_module_close($crypt);
        if ($ret !== TRUE) {
            throw new CannotPerformOperationException();
        }

        return $ciphertext;
    }

    /*
     * Never call this method directly!
     */
    private static function PlainDecrypt($ciphertext, $key, $iv)
    {
        self::EnsureFunctionExists("mcrypt_module_open");
        $crypt = mcrypt_module_open(self::CIPHER, "", self::CIPHER_MODE, "");
        if ($crypt === FALSE) {
            throw new CannotPerformOperationException();
        }

        self::EnsureFunctionExists("mcrypt_enc_get_block_size");
        $block = mcrypt_enc_get_block_size($crypt);
        self::EnsureFunctionExists("mcrypt_generic_init");
        $ret = mcrypt_generic_init($crypt, $key, $iv);
        if ($ret !== 0) {
            throw new CannotPerformOperationException();
        }
        self::EnsureFunctionExists("mdecrypt_generic");
        $plaintext = mdecrypt_generic($crypt, $ciphertext);
        self::EnsureFunctionExists("mcrypt_generic_deinit");
        $ret = mcrypt_generic_deinit($crypt);
        if ($ret !== TRUE) {
            throw new CannotPerformOperationException();
        }
        self::EnsureFunctionExists("mcrypt_module_close");
        $ret = mcrypt_module_close($crypt);
        if ($ret !== TRUE) {
            throw new CannotPerformOperationException();
        }

        // Remove the padding.
        $pad = ord($plaintext[self::our_strlen($plaintext) - 1]);
        if ($pad <= 0 || $pad > $block) {
            throw new CannotPerformOperationException();
        }
        $plaintext = self::our_substr($plaintext, 0, self::our_strlen($plaintext) - $pad);
        if ($plaintext === FALSE) {
            throw new CannotPerformOperationException();
        }

        return $plaintext;
    }

    /*
     * Returns a random binary string of length $octets bytes.
     */
    private static function SecureRandom($octets)
    {
        self::EnsureFunctionExists("mcrypt_create_iv");
        $random = mcrypt_create_iv($octets, MCRYPT_DEV_URANDOM);
        if ($random === FALSE) {
            throw new CannotPerformOperationException();
        } else {
            return $random;
        }
    }

    /*
     * Use HKDF to derive multiple keys from one.
     * http://tools.ietf.org/html/rfc5869
     */
    private static function HKDF($hash, $ikm, $length, $info = '', $salt = NULL)
    {
        // Find the correct digest length as quickly as we can.
        $digest_length = self::MAC_BYTE_SIZE;
        if ($hash != self::HASH_FUNCTION) {
            $digest_length = self::our_strlen(hash_hmac($hash, '', '', true));
        }

        // Sanity-check the desired output length.
        if (empty($length) || !is_int($length) ||
            $length < 0 || $length > 255 * $digest_length) {
            throw new CannotPerformOperationException();
        }

        // "if [salt] not provided, is set to a string of HashLen zeroes."
        if (is_null($salt)) {
            $salt = str_repeat("\x00", $digest_length);
        }

        // HKDF-Extract:
        // PRK = HMAC-Hash(salt, IKM)
        // The salt is the HMAC key.
        $prk = hash_hmac($hash, $ikm, $salt, true);

        // HKDF-Expand:

        // This check is useless, but it serves as a reminder to the spec.
        if (self::our_strlen($prk) < $digest_length) {
            throw new CannotPerformOperationException();
        }

        // T(0) = ''
        $t = '';
        $last_block = '';
        for ($block_index = 1; self::our_strlen($t) < $length; $block_index++) {
            // T(i) = HMAC-Hash(PRK, T(i-1) | info | 0x??)
            $last_block = hash_hmac(
                $hash,
                $last_block . $info . chr($block_index),
                $prk,
                true
            );
            // T = T(1) | T(2) | T(3) | ... | T(N)
            $t .= $last_block;
        }

        // ORM = first L octets of T
        $orm = self::our_substr($t, 0, $length);
        if ($orm === FALSE) {
            throw new CannotPerformOperationException();
        }
        return $orm;
    }

    private static function VerifyHMAC($correct_hmac, $message, $key)
    {
        $message_hmac = hash_hmac(self::HASH_FUNCTION, $message, $key, true);

        // We can't just compare the strings with '==', since it would make
        // timing attacks possible. We could use the XOR-OR constant-time
        // comparison algorithm, but I'm not sure if that's good enough way up
        // here in an interpreted language. So we use the method of HMACing the 
        // strings we want to compare with a random key, then comparing those.

        // NOTE: This leaks information when the strings are not the same
        // length, but they should always be the same length here. Enforce it:
        if (self::our_strlen($correct_hmac) !== self::our_strlen($message_hmac)) {
            throw new CannotPerformOperationException();
        }

        $blind = self::CreateNewRandomKey();
        $message_compare = hash_hmac(self::HASH_FUNCTION, $message_hmac, $blind);
        $correct_compare = hash_hmac(self::HASH_FUNCTION, $correct_hmac, $blind);
        return $correct_compare === $message_compare;
    }

    private static function TestEncryptDecrypt()
    {
        $key = Crypto::CreateNewRandomKey();
        $data = "EnCrYpT EvErYThInG\x00\x00";

        // Make sure encrypting then decrypting doesn't change the message.
        $ciphertext = Crypto::Encrypt($data, $key);
        try {
            $decrypted = Crypto::Decrypt($ciphertext, $key);
        } catch (InvalidCiphertextException $ex) {
            // It's important to catch this and change it into a 
            // CryptoTestFailedException, otherwise a test failure could trick
            // the user into thinking it's just an invalid ciphertext!
            throw new CryptoTestFailedException();
        }
        if($decrypted !== $data)
        {
            throw new CryptoTestFailedException();
        }

        // Modifying the ciphertext: Appending a string.
        try {
            Crypto::Decrypt($ciphertext . "a", $key);
            throw new CryptoTestFailedException();
        } catch (InvalidCiphertextException $e) { /* expected */ }

        // Modifying the ciphertext: Changing an IV byte.
        try {
            $ciphertext[0] = chr((ord($ciphertext[0]) + 1) % 256);
            Crypto::Decrypt($ciphertext, $key);
            throw new CryptoTestFailedException();
        } catch (InvalidCiphertextException $e) { /* expected */ }

        // Decrypting with the wrong key.
        $key = Crypto::CreateNewRandomKey();
        $data = "abcdef";
        $ciphertext = Crypto::Encrypt($data, $key);
        $wrong_key = Crypto::CreateNewRandomKey();
        try {
            Crypto::Decrypt($ciphertext, $wrong_key);
            throw new CryptoTestFailedException();
        } catch (InvalidCiphertextException $e) { /* expected */ }

        // Ciphertext too small (shorter than HMAC).
        $key = Crypto::CreateNewRandomKey();
        $ciphertext = str_repeat("A", self::MAC_BYTE_SIZE - 1);
        try {
            Crypto::Decrypt($ciphertext, $key);
            throw new CryptoTestFailedException();
        } catch (InvalidCiphertextException $e) { /* expected */ }
    }

    private static function HKDFTestVector()
    {
        // HKDF test vectors from RFC 5869

        // Test Case 1
        $ikm = str_repeat("\x0b", 22);
        $salt = self::hexToBytes("000102030405060708090a0b0c");
        $info = self::hexToBytes("f0f1f2f3f4f5f6f7f8f9");
        $length = 42;
        $okm = self::hexToBytes(
            "3cb25f25faacd57a90434f64d0362f2a" .
            "2d2d0a90cf1a5a4c5db02d56ecc4c5bf" .
            "34007208d5b887185865"
        );
        $computed_okm = self::HKDF("sha256", $ikm, $length, $info, $salt);
        if ($computed_okm !== $okm) {
            throw new CryptoTestFailedException();
        }

        // Test Case 7
        $ikm = str_repeat("\x0c", 22);
        $length = 42;
        $okm = self::hexToBytes(
            "2c91117204d745f3500d636a62f64f0a" .
            "b3bae548aa53d423b0d1f27ebba6f5e5" .
            "673a081d70cce7acfc48"
        );
        $computed_okm = self::HKDF("sha1", $ikm, $length);
        if ($computed_okm !== $okm) {
            throw new CryptoTestFailedException();
        }

    }

    private static function HMACTestVector()
    {
        // HMAC test vector From RFC 4231 (Test Case 1)
        $key = str_repeat("\x0b", 20);
        $data = "Hi There";
        $correct = "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7";
        if (hash_hmac(self::HASH_FUNCTION, $data, $key) != $correct) {
            throw new CryptoTestFailedException();
        }
    }

    private static function AESTestVector()
    {
        // AES CBC mode test vector from NIST SP 800-38A
        $key = self::hexToBytes("2b7e151628aed2a6abf7158809cf4f3c");
        $iv = self::hexToBytes("000102030405060708090a0b0c0d0e0f");
        $plaintext = self::hexToBytes(
            "6bc1bee22e409f96e93d7e117393172a" . 
            "ae2d8a571e03ac9c9eb76fac45af8e51" .
            "30c81c46a35ce411e5fbc1191a0a52ef" .
            "f69f2445df4f9b17ad2b417be66c3710"
        );
        $ciphertext = self::hexToBytes(
            "7649abac8119b246cee98e9b12e9197d" .
            "5086cb9b507219ee95db113a917678b2" .
            "73bed6b8e3c1743b7116e69e22229516" .
            "3ff1caa1681fac09120eca307586e1a7" .
            /* Block due to padding. Not from NIST test vector. 
                Padding Block: 10101010101010101010101010101010
                Ciphertext:    3ff1caa1681fac09120eca307586e1a7
                           (+) 2fe1dab1780fbc19021eda206596f1b7 
                           AES 8cb82807230e1321d3fae00d18cc2012
             
             */
            "8cb82807230e1321d3fae00d18cc2012"
        );

        $computed_ciphertext = self::PlainEncrypt($plaintext, $key, $iv);
        if ($computed_ciphertext !== $ciphertext) {
            throw new CryptoTestFailedException();
        }

        $computed_plaintext = self::PlainDecrypt($ciphertext, $key, $iv);
        if ($computed_plaintext !== $plaintext) {
            throw new CryptoTestFailedException();
        }
    }

    /* WARNING: Do not call this function on secrets. It creates side channels. */
    private static function hexToBytes($hex_string)
    {
        return pack("H*", $hex_string);
    }

    private static function EnsureFunctionExists($name)
    {
        if (!function_exists($name)) {
            throw new CannotPerformOperationException();
        }
    }

    /*
     * We need these strlen() and substr() functions because when
     * 'mbstring.func_overload' is set in php.ini, the standard strlen() and
     * substr() are replaced by mb_strlen() and mb_substr().
     */

    private static function our_strlen($str)
    {
        if (function_exists('mb_strlen')) {
            $length = mb_strlen($str, '8bit');
            if ($length === FALSE) {
                throw new CannotPerformOperationException();
            }
            return $length;
        } else {
            return strlen($str);
        }
    }

    private static function our_substr($str, $start, $length = NULL)
    {
        if (function_exists('mb_substr'))
        {
            // mb_substr($str, 0, NULL, '8bit') returns an empty string on PHP
            // 5.3, so we have to find the length ourselves.
            if (!isset($length)) {
                if ($start >= 0) {
                    $length = self::our_strlen($str) - $start;
                } else {
                    $length = -$start;
                }
            }

            return mb_substr($str, $start, $length, '8bit');
        }

        // Unlike mb_substr(), substr() doesn't accept NULL for length
        if (isset($length)) {
            return substr($str, $start, $length);
        } else {
            return substr($str, $start);
        }
    }

}

/*
 * We want to catch all uncaught exceptions that come from the Crypto class,
 * since by default, PHP will leak the key in the stack trace from an uncaught
 * exception. This is a really ugly hack, but I think it's justified.
 *
 * Everything up to handler() getting called should be reliable, so this should
 * reliably suppress the stack traces. The rest is just a bonus so that we don't
 * make it impossible to debug other exceptions.
 *
 * This bit of code was adapted from: http://stackoverflow.com/a/7939492
 */

class CryptoExceptionHandler
{
    private $rethrow = NULL;

    public function __construct()
    {
        set_exception_handler(array($this, "handler"));
    }

    public function handler($ex)
    {
        if (
            $ex instanceof InvalidCiphertextException ||
            $ex instanceof CannotPerformOperationException ||
            $ex instanceof CryptoTestFailedException
        ) {
            echo "FATAL ERROR: Uncaught crypto exception. Suppresssing output.\n";
        } else {
            /* Re-throw the exception in the destructor. */
            $this->rethrow = $ex;
        }
    }

    public function __destruct() {
        if ($this->rethrow) {
            throw $this->rethrow;
        }
    }
}

$crypto_exception_handler_object_dont_touch_me = new CryptoExceptionHandler();

joomla/oauth2/client.php000064400000022156152177723700011237 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  OAuth2
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with an OAuth 2.0 server.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/oauth2` framework package that will be bundled instead
 */
class JOAuth2Client
{
	/**
	 * @var    Registry  Options for the JOAuth2Client object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $http;

	/**
	 * @var    JInput  The input object to use in retrieving GET/POST data.
	 * @since  3.1.4
	 */
	protected $input;

	/**
	 * @var    JApplicationWeb  The application object to send HTTP headers for redirects.
	 * @since  3.1.4
	 */
	protected $application;

	/**
	 * Constructor.
	 *
	 * @param   Registry         $options      JOAuth2Client options object
	 * @param   JHttp            $http         The HTTP client object
	 * @param   JInput           $input        The input object
	 * @param   JApplicationWeb  $application  The application object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JHttp $http = null, JInput $input = null, JApplicationWeb $application = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->http = isset($http) ? $http : new JHttp($this->options);
		$this->application = isset($application) ? $application : new JApplicationWeb;
		$this->input = isset($input) ? $input : $this->application->input;
	}

	/**
	 * Get the access token or redict to the authentication URL.
	 *
	 * @return  string  The access token
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function authenticate()
	{
		if ($data['code'] = $this->input->get('code', false, 'raw'))
		{
			$data['grant_type'] = 'authorization_code';
			$data['redirect_uri'] = $this->getOption('redirecturi');
			$data['client_id'] = $this->getOption('clientid');
			$data['client_secret'] = $this->getOption('clientsecret');
			$response = $this->http->post($this->getOption('tokenurl'), $data);

			if ($response->code >= 200 && $response->code < 400)
			{
				if (strpos($response->headers['Content-Type'], 'application/json') === 0)
				{
					$token = array_merge(json_decode($response->body, true), array('created' => time()));
				}
				else
				{
					parse_str($response->body, $token);
					$token = array_merge($token, array('created' => time()));
				}

				$this->setToken($token);

				return $token;
			}
			else
			{
				throw new RuntimeException('Error code ' . $response->code . ' received requesting access token: ' . $response->body . '.');
			}
		}

		if ($this->getOption('sendheaders'))
		{
			$this->application->redirect($this->createUrl());
		}

		return false;
	}

	/**
	 * Verify if the client has been authenticated
	 *
	 * @return  boolean  Is authenticated
	 *
	 * @since   3.1.4
	 */
	public function isAuthenticated()
	{
		$token = $this->getToken();

		if (!$token || !array_key_exists('access_token', $token))
		{
			return false;
		}
		elseif (array_key_exists('expires_in', $token) && $token['created'] + $token['expires_in'] < time() + 20)
		{
			return false;
		}
		else
		{
			return true;
		}
	}

	/**
	 * Create the URL for authentication.
	 *
	 * @return  JHttpResponse  The HTTP response
	 *
	 * @since   3.1.4
	 * @throws  InvalidArgumentException
	 */
	public function createUrl()
	{
		if (!$this->getOption('authurl') || !$this->getOption('clientid'))
		{
			throw new InvalidArgumentException('Authorization URL and client_id are required');
		}

		$url = $this->getOption('authurl');

		if (strpos($url, '?'))
		{
			$url .= '&';
		}
		else
		{
			$url .= '?';
		}

		$url .= 'response_type=code';
		$url .= '&client_id=' . urlencode($this->getOption('clientid'));

		if ($this->getOption('redirecturi'))
		{
			$url .= '&redirect_uri=' . urlencode($this->getOption('redirecturi'));
		}

		if ($this->getOption('scope'))
		{
			$scope = is_array($this->getOption('scope')) ? implode(' ', $this->getOption('scope')) : $this->getOption('scope');
			$url .= '&scope=' . urlencode($scope);
		}

		if ($this->getOption('state'))
		{
			$url .= '&state=' . urlencode($this->getOption('state'));
		}

		if (is_array($this->getOption('requestparams')))
		{
			foreach ($this->getOption('requestparams') as $key => $value)
			{
				$url .= '&' . $key . '=' . urlencode($value);
			}
		}

		return $url;
	}

	/**
	 * Send a signed Oauth request.
	 *
	 * @param   string  $url      The URL for the request.
	 * @param   mixed   $data     The data to include in the request
	 * @param   array   $headers  The headers to send with the request
	 * @param   string  $method   The method with which to send the request
	 * @param   int     $timeout  The timeout for the request
	 *
	 * @return  string  The URL.
	 *
	 * @since   3.1.4
	 * @throws  InvalidArgumentException
	 * @throws  RuntimeException
	 */
	public function query($url, $data = null, $headers = array(), $method = 'get', $timeout = null)
	{
		$token = $this->getToken();

		if (array_key_exists('expires_in', $token) && $token['created'] + $token['expires_in'] < time() + 20)
		{
			if (!$this->getOption('userefresh'))
			{
				return false;
			}

			$token = $this->refreshToken($token['refresh_token']);
		}

		if (!$this->getOption('authmethod') || $this->getOption('authmethod') == 'bearer')
		{
			$headers['Authorization'] = 'Bearer ' . $token['access_token'];
		}
		elseif ($this->getOption('authmethod') == 'get')
		{
			if (strpos($url, '?'))
			{
				$url .= '&';
			}
			else
			{
				$url .= '?';
			}

			$url .= $this->getOption('getparam') ? $this->getOption('getparam') : 'access_token';
			$url .= '=' . $token['access_token'];
		}

		switch ($method)
		{
			case 'head':
			case 'get':
			case 'delete':
			case 'trace':
			$response = $this->http->$method($url, $headers, $timeout);
			break;
			case 'post':
			case 'put':
			case 'patch':
			$response = $this->http->$method($url, $data, $headers, $timeout);
			break;
			default:
			throw new InvalidArgumentException('Unknown HTTP request method: ' . $method . '.');
		}

		if ($response->code < 200 || $response->code >= 400)
		{
			throw new RuntimeException('Error code ' . $response->code . ' received requesting data: ' . $response->body . '.');
		}

		return $response;
	}

	/**
	 * Get an option from the JOAuth2Client instance.
	 *
	 * @param   string  $key  The name of the option to get
	 *
	 * @return  mixed  The option value
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JOAuth2Client instance.
	 *
	 * @param   string  $key    The name of the option to set
	 * @param   mixed   $value  The option value to set
	 *
	 * @return  JOAuth2Client  This object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}

	/**
	 * Get the access token from the JOAuth2Client instance.
	 *
	 * @return  array  The access token
	 *
	 * @since   3.1.4
	 */
	public function getToken()
	{
		return $this->getOption('accesstoken');
	}

	/**
	 * Set an option for the JOAuth2Client instance.
	 *
	 * @param   array  $value  The access token
	 *
	 * @return  JOAuth2Client  This object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setToken($value)
	{
		if (is_array($value) && !array_key_exists('expires_in', $value) && array_key_exists('expires', $value))
		{
			$value['expires_in'] = $value['expires'];
			unset($value['expires']);
		}

		$this->setOption('accesstoken', $value);

		return $this;
	}

	/**
	 * Refresh the access token instance.
	 *
	 * @param   string  $token  The refresh token
	 *
	 * @return  array  The new access token
	 *
	 * @since   3.1.4
	 * @throws  Exception
	 * @throws  RuntimeException
	 */
	public function refreshToken($token = null)
	{
		if (!$this->getOption('userefresh'))
		{
			throw new RuntimeException('Refresh token is not supported for this OAuth instance.');
		}

		if (!$token)
		{
			$token = $this->getToken();

			if (!array_key_exists('refresh_token', $token))
			{
				throw new RuntimeException('No refresh token is available.');
			}

			$token = $token['refresh_token'];
		}

		$data['grant_type'] = 'refresh_token';
		$data['refresh_token'] = $token;
		$data['client_id'] = $this->getOption('clientid');
		$data['client_secret'] = $this->getOption('clientsecret');
		$response = $this->http->post($this->getOption('tokenurl'), $data);

		if ($response->code >= 200 || $response->code < 400)
		{
			if (strpos($response->headers['Content-Type'], 'application/json') === 0)
			{
				$token = array_merge(json_decode($response->body, true), array('created' => time()));
			}
			else
			{
				parse_str($response->body, $token);
				$token = array_merge($token, array('created' => time()));
			}

			$this->setToken($token);

			return $token;
		}
		else
		{
			throw new Exception('Error code ' . $response->code . ' received refreshing token: ' . $response->body . '.');
		}
	}
}
joomla/keychain/keychain.php000064400000012614152177723700012143 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Keychain
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Keychain Class
 *
 * @since       3.1.4
 * @deprecated  4.0  Deprecated without replacement
 */
class JKeychain extends \Joomla\Registry\Registry
{
	/**
	 * @var    string  Method to use for encryption.
	 * @since  3.1.4
	 */
	public $method = 'aes-128-cbc';

	/**
	 * @var    string  Initialisation vector for encryption method.
	 * @since  3.1.4
	 */
	public $iv = '1234567890123456';

	/**
	 * Create a passphrase file
	 *
	 * @param   string  $passphrase            The passphrase to store in the passphrase file.
	 * @param   string  $passphraseFile        Path to the passphrase file to create.
	 * @param   string  $privateKeyFile        Path to the private key file to encrypt the passphrase file.
	 * @param   string  $privateKeyPassphrase  The passphrase for the private key.
	 *
	 * @return  boolean  Result of writing the passphrase file to disk.
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function createPassphraseFile($passphrase, $passphraseFile, $privateKeyFile, $privateKeyPassphrase)
	{
		$privateKey = openssl_get_privatekey(file_get_contents($privateKeyFile), $privateKeyPassphrase);

		if (!$privateKey)
		{
			throw new RuntimeException('Failed to load private key.');
		}

		$crypted = '';

		if (!openssl_private_encrypt($passphrase, $crypted, $privateKey))
		{
			throw new RuntimeException('Failed to encrypt data using private key.');
		}

		return file_put_contents($passphraseFile, $crypted);
	}

	/**
	 * Delete a registry value (very simple method)
	 *
	 * @param   string  $path  Registry Path (e.g. joomla.content.showauthor)
	 *
	 * @return  mixed  Value of old value or boolean false if operation failed
	 *
	 * @since   3.1.4
	 */
	public function deleteValue($path)
	{
		$result = null;

		// Explode the registry path into an array
		$nodes = explode('.', $path);

		if ($nodes)
		{
			// Initialize the current node to be the registry root.
			$node = $this->data;

			// Traverse the registry to find the correct node for the result.
			for ($i = 0, $n = count($nodes) - 1; $i < $n; $i++)
			{
				if (!isset($node->{$nodes[$i]}) && ($i != $n))
				{
					$node->{$nodes[$i]} = new stdClass;
				}

				$node = $node->{$nodes[$i]};
			}

			// Get the old value if exists so we can return it
			$result = $node->{$nodes[$i]};
			unset($node->{$nodes[$i]});
		}

		return $result;
	}

	/**
	 * Load a keychain file into this object.
	 *
	 * @param   string  $keychainFile    Path to the keychain file.
	 * @param   string  $passphraseFile  The path to the passphrase file to decript the keychain.
	 * @param   string  $publicKeyFile   The file containing the public key to decrypt the passphrase file.
	 *
	 * @return  boolean  Result of loading the object.
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function loadKeychain($keychainFile, $passphraseFile, $publicKeyFile)
	{
		if (!file_exists($keychainFile))
		{
			throw new RuntimeException('Attempting to load non-existent keychain file');
		}

		$passphrase = $this->getPassphraseFromFile($passphraseFile, $publicKeyFile);

		$cleartext = openssl_decrypt(file_get_contents($keychainFile), $this->method, $passphrase, true, $this->iv);

		if ($cleartext === false)
		{
			throw new RuntimeException('Failed to decrypt keychain file');
		}

		return $this->loadObject(json_decode($cleartext));
	}

	/**
	 * Save this keychain to a file.
	 *
	 * @param   string  $keychainFile    The path to the keychain file.
	 * @param   string  $passphraseFile  The path to the passphrase file to encrypt the keychain.
	 * @param   string  $publicKeyFile   The file containing the public key to decrypt the passphrase file.
	 *
	 * @return  boolean  Result of storing the file.
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function saveKeychain($keychainFile, $passphraseFile, $publicKeyFile)
	{
		$passphrase = $this->getPassphraseFromFile($passphraseFile, $publicKeyFile);
		$data = $this->toString('JSON');

		$encrypted = @openssl_encrypt($data, $this->method, $passphrase, true, $this->iv);

		if ($encrypted === false)
		{
			throw new RuntimeException('Unable to encrypt keychain');
		}

		return file_put_contents($keychainFile, $encrypted);
	}

	/**
	 * Get the passphrase for this keychain
	 *
	 * @param   string  $passphraseFile  The file containing the passphrase to encrypt and decrypt.
	 * @param   string  $publicKeyFile   The file containing the public key to decrypt the passphrase file.
	 *
	 * @return  string  The passphrase in from passphraseFile
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	protected function getPassphraseFromFile($passphraseFile, $publicKeyFile)
	{
		if (!file_exists($publicKeyFile))
		{
			throw new RuntimeException('Missing public key file');
		}

		$publicKey = openssl_get_publickey(file_get_contents($publicKeyFile));

		if (!$publicKey)
		{
			throw new RuntimeException('Failed to load public key.');
		}

		if (!file_exists($passphraseFile))
		{
			throw new RuntimeException('Missing passphrase file');
		}

		$passphrase = '';

		if (!openssl_public_decrypt(file_get_contents($passphraseFile), $passphrase, $publicKey))
		{
			throw new RuntimeException('Failed to decrypt passphrase file');
		}

		return $passphrase;
	}
}
joomla/github/meta.php000064400000002707152177723700010767 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Meta class.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubMeta extends JGithubObject
{
	/**
	 * Method to get the authorized IP addresses for services
	 *
	 * @return  array  Authorized IP addresses
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function getMeta()
	{
		// Build the request path.
		$path = '/meta';

		$githubIps = $this->processResponse($this->client->get($this->fetchUrl($path)), 200);

		/*
		 * The response body returns the IP addresses in CIDR format
		 * Decode the response body and strip the subnet mask information prior to
		 * returning the data to the user.  We're assuming quite a bit here that all
		 * masks will be /32 as they are as of the time of development.
		 */

		$authorizedIps = array();

		foreach ($githubIps as $key => $serviceIps)
		{
			// The first level contains an array of IPs based on the service
			$authorizedIps[$key] = array();

			foreach ($serviceIps as $serviceIp)
			{
				// The second level is each individual IP address, strip the mask here
				$authorizedIps[$key][] = substr($serviceIp, 0, -3);
			}
		}

		return $authorizedIps;
	}
}
joomla/github/github.php000064400000011773152177723700011326 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with a GitHub server instance.
 *
 * @property-read  JGithubPackageActivity       $activity       GitHub API object for activity.
 * @property-read  JGithubPackageAuthorization  $authorization  GitHub API object for authorizations.
 * @property-read  JGithubPackageData           $data           GitHub API object for data.
 * @property-read  JGithubPackageGists          $gists          GitHub API object for gists.
 * @property-read  JGithubPackageGitignore      $gitignore      GitHub API object for gitignore.
 * @property-read  JGithubPackageIssues         $issues         GitHub API object for issues.
 * @property-read  JGithubPackageMarkdown       $markdown       GitHub API object for markdown.
 * @property-read  JGithubPackageOrgs           $orgs           GitHub API object for orgs.
 * @property-read  JGithubPackagePulls          $pulls          GitHub API object for pulls.
 * @property-read  JGithubPackageRepositories   $repositories   GitHub API object for repositories.
 * @property-read  JGithubPackageSearch         $search         GitHub API object for search.
 * @property-read  JGithubPackageUsers          $users          GitHub API object for users.
 *
 * @property-read  JGithubRefs        $refs        Deprecated GitHub API object for referencess.
 * @property-read  JGithubForks       $forks       Deprecated GitHub API object for forks.
 * @property-read  JGithubCommits     $commits     Deprecated GitHub API object for commits.
 * @property-read  JGithubMilestones  $milestones  Deprecated GitHub API object for commits.
 * @property-read  JGithubStatuses    $statuses    Deprecated GitHub API object for commits.
 * @property-read  JGithubAccount     $account     Deprecated GitHub API object for account references.
 * @property-read  JGithubHooks       $hooks       Deprecated GitHub API object for hooks.
 * @property-read  JGithubMeta        $meta        Deprecated GitHub API object for meta.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithub
{
	/**
	 * @var    Registry  Options for the GitHub object.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * @var    JGithubHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  1.7.3
	 */
	protected $client;

	/**
	 * @var    array  List of known packages.
	 * @since  3.3 (CMS)
	 */
	protected $packages = array(
		'activity',
		'authorization',
		'data',
		'gists',
		'gitignore',
		'issues',
		'markdown',
		'orgs',
		'pulls',
		'repositories',
		'users',
	);

	/**
	 * @var    array  List of known legacy packages.
	 * @since  3.3 (CMS)
	 */
	protected $legacyPackages = array('refs', 'forks', 'commits', 'milestones', 'statuses', 'account', 'hooks', 'meta');

	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  GitHub options object.
	 * @param   JGithubHttp  $client   The HTTP client object.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Registry $options = null, JGithubHttp $client = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JGithubHttp($this->options);

		// Setup the default API url if not already set.
		$this->options->def('api.url', 'https://api.github.com');
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @throws RuntimeException
	 *
	 * @since   1.7.3
	 * @return  JGithubObject  GitHub API object (gists, issues, pulls, etc).
	 */
	public function __get($name)
	{
		if (false == in_array($name, $this->packages))
		{
			// Check for a legacy class
			if (in_array($name, $this->legacyPackages))
			{
				if (false == isset($this->$name))
				{
					$className = 'JGithub' . ucfirst($name);

					$this->$name = new $className($this->options, $this->client);
				}

				return $this->$name;
			}

			throw new RuntimeException(sprintf('%1$s - Unknown package %2$s', __METHOD__, $name));
		}

		if (false == isset($this->$name))
		{
			$className = 'JGithubPackage' . ucfirst($name);

			$this->$name = new $className($this->options, $this->client);
		}

		return $this->$name;
	}

	/**
	 * Get an option from the JGitHub instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   1.7.3
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JGitHub instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JGitHub  This object for method chaining.
	 *
	 * @since   1.7.3
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/github/http.php000064400000002704152177723700011015 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * HTTP client class for connecting to a GitHub instance.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubHttp extends JHttp
{
	/**
	 * Use no authentication for HTTP connections.
	 *
	 * @var    integer
	 * @since  1.7.3
	 */
	const AUTHENTICATION_NONE = 0;

	/**
	 * Use basic authentication for HTTP connections.
	 *
	 * @var    integer
	 * @since  1.7.3
	 */
	const AUTHENTICATION_BASIC = 1;

	/**
	 * Use OAuth authentication for HTTP connections.
	 *
	 * @var    integer
	 * @since  1.7.3
	 */
	const AUTHENTICATION_OAUTH = 2;

	/**
	 * Constructor.
	 *
	 * @param   Registry        $options    Client options object.
	 * @param   JHttpTransport  $transport  The HTTP transport object.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Registry $options = null, JHttpTransport $transport = null)
	{
		// Call the JHttp constructor to setup the object.
		parent::__construct($options, $transport);

		// Make sure the user agent string is defined.
		$this->options->def('userAgent', 'JGitHub/2.0');

		// Set the default timeout to 120 seconds.
		$this->options->def('timeout', 120);
	}
}
joomla/github/commits.php000064400000025610152177723700011512 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Commits class for the Joomla Platform.
 *
 * @since       3.0.0
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubCommits extends JGithubObject
{
	/**
	 * Method to create a commit.
	 *
	 * @param   string  $user     The name of the owner of the GitHub repository.
	 * @param   string  $repo     The name of the GitHub repository.
	 * @param   string  $message  The commit message.
	 * @param   string  $tree     SHA of the tree object this commit points to.
	 * @param   array   $parents  Array of the SHAs of the commits that were the parents of this commit.
	 *                            If omitted or empty, the commit will be written as a root commit.
	 *                            For a single parent, an array of one SHA should be provided.
	 *                            For a merge commit, an array of more than one should be provided.
	 *
	 * @deprecated  use data->commits->create()
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function create($user, $repo, $message, $tree, array $parents = array())
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/commits';

		$data = json_encode(
			array('message' => $message, 'tree' => $tree, 'parents' => $parents)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to create a comment on a commit.
	 *
	 * @param   string   $user      The name of the owner of the GitHub repository.
	 * @param   string   $repo      The name of the GitHub repository.
	 * @param   string   $sha       The SHA of the commit to comment on.
	 * @param   string   $comment   The text of the comment.
	 * @param   integer  $line      The line number of the commit to comment on.
	 * @param   string   $filepath  A relative path to the file to comment on within the commit.
	 * @param   integer  $position  Line index in the diff to comment on.
	 *
	 * @deprecated  use repositories->comments->create()
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function createCommitComment($user, $repo, $sha, $comment, $line, $filepath, $position)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha . '/comments';

		$data = json_encode(
			array(
				'body' => $comment,
				'commit_id' => $sha,
				'line' => (int) $line,
				'path' => $filepath,
				'position' => (int) $position,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete a comment on a commit.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $id    The ID of the comment to edit.
	 *
	 * @deprecated  use repositories->comments->delete()
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function deleteCommitComment($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . $id;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to edit a comment on a commit.
	 *
	 * @param   string  $user     The name of the owner of the GitHub repository.
	 * @param   string  $repo     The name of the GitHub repository.
	 * @param   string  $id       The ID of the comment to edit.
	 * @param   string  $comment  The text of the comment.
	 *
	 * @deprecated  use repositories->comments->edit()
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function editCommitComment($user, $repo, $id, $comment)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . $id;

		$data = json_encode(
			array(
				'body' => $comment,
			)
		);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single commit for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   string   $sha    The SHA of the commit to retrieve.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->commits->get()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getCommit($user, $repo, $sha, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single comment on a commit.
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the comment to retrieve
	 *
	 * @deprecated  use repositories->comments->get()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getCommitComment($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . $id;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of comments for a single commit for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   string   $sha    The SHA of the commit to retrieve.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->comments->getList()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getCommitComments($user, $repo, $sha, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha . '/comments';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a diff for two commits.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $base  The base of the diff, either a commit SHA or branch.
	 * @param   string  $head  The head of the diff, either a commit SHA or branch.
	 *
	 * @deprecated  use repositories->commits->compare()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getDiff($user, $repo, $base, $head)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/compare/' . $base . '...' . $head;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list commits for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->commits->getList()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getList($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of commit comments for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->comments->getListRepository()
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getListComments($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/object.php000064400000006677152177723700011321 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * GitHub API object class for the Joomla Platform.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
abstract class JGithubObject
{
	/**
	 * @var    Registry  Options for the GitHub object.
	 * @since  1.7.3
	 */
	protected $options;

	/**
	 * @var    JGithubHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  1.7.3
	 */
	protected $client;

	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  GitHub options object.
	 * @param   JGithubHttp  $client   The HTTP client object.
	 *
	 * @since   1.7.3
	 */
	public function __construct(Registry $options = null, JGithubHttp $client = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JGithubHttp($this->options);
	}

	/**
	 * Method to build and return a full request URL for the request.  This method will
	 * add appropriate pagination details if necessary and also prepend the API url
	 * to have a complete URL for the request.
	 *
	 * @param   string   $path   URL to inflect
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @return  string   The request URL.
	 *
	 * @since   1.7.3
	 */
	protected function fetchUrl($path, $page = 0, $limit = 0)
	{
		// Get a new JUri object fousing the api url and given path.
		$uri = new JUri($this->options->get('api.url') . $path);

		if ($this->options->get('gh.token', false))
		{
			// Use oAuth authentication - @todo set in request header ?
			$uri->setVar('access_token', $this->options->get('gh.token'));
		}
		else
		{
			// Use basic authentication
			if ($this->options->get('api.username', false))
			{
				$username = $this->options->get('api.username');
				$username = str_replace('@', '%40', $username);
				$uri->setUser($username);
			}

			if ($this->options->get('api.password', false))
			{
				$password = $this->options->get('api.password');
				$password = str_replace('@', '%40', $password);
				$uri->setPass($password);
			}
		}

		// If we have a defined page number add it to the JUri object.
		if ($page > 0)
		{
			$uri->setVar('page', (int) $page);
		}

		// If we have a defined items per page add it to the JUri object.
		if ($limit > 0)
		{
			$uri->setVar('per_page', (int) $limit);
		}

		return (string) $uri;
	}

	/**
	 * Process the response and decode it.
	 *
	 * @param   JHttpResponse  $response      The response.
	 * @param   integer        $expectedCode  The expected "good" code.
	 * @param   boolean        $decode        If the should be response be JSON decoded.
	 *
	 * @throws DomainException
	 * @since  3.3.0
	 *
	 * @return mixed
	 */
	protected function processResponse(JHttpResponse $response, $expectedCode = 200, $decode = true)
	{
		// Validate the response code.
		if ($response->code == $expectedCode)
		{
			return ($decode) ? json_decode($response->body) : $response->body;
		}

		// Decode the error response and throw an exception.
		$error   = json_decode($response->body);
		$message = (isset($error->message)) ? $error->message : 'Error: ' . $response->code;

		throw new DomainException($message, $response->code);
	}
}
joomla/github/account.php000064400000014367152177723700011502 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Account class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubAccount extends JGithubObject
{
	/**
	 * Method to create an authorisation.
	 *
	 * @param   array   $scopes  A list of scopes that this authorisation is in.
	 * @param   string  $note    A note to remind you what the OAuth token is for.
	 * @param   string  $url     A URL to remind you what app the OAuth token is for.
	 *
	 * @deprecated  use authorization->create()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function createAuthorisation(array $scopes = array(), $note = '', $url = '')
	{
		// Build the request path.
		$path = '/authorizations';

		$data = json_encode(
			array('scopes' => $scopes, 'note' => $note, 'note_url' => $url)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete an authorisation
	 *
	 * @param   integer  $id  ID of the authorisation to delete
	 *
	 * @deprecated  use authorization->delete()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function deleteAuthorisation($id)
	{
		// Build the request path.
		$path = '/authorizations/' . $id;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to edit an authorisation.
	 *
	 * @param   integer  $id            ID of the authorisation to edit
	 * @param   array    $scopes        Replaces the authorisation scopes with these.
	 * @param   array    $addScopes     A list of scopes to add to this authorisation.
	 * @param   array    $removeScopes  A list of scopes to remove from this authorisation.
	 * @param   string   $note          A note to remind you what the OAuth token is for.
	 * @param   string   $url           A URL to remind you what app the OAuth token is for.
	 *
	 * @deprecated  use authorization->edit()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @throws  RuntimeException
	 */
	public function editAuthorisation($id, array $scopes = array(), array $addScopes = array(), array $removeScopes = array(), $note = '', $url = '')
	{
		// Check if more than one scopes array contains data
		$scopesCount = 0;

		if (!empty($scopes))
		{
			$scope = 'scopes';
			$scopeData = $scopes;
			$scopesCount++;
		}

		if (!empty($addScopes))
		{
			$scope = 'add_scopes';
			$scopeData = $addScopes;
			$scopesCount++;
		}

		if (!empty($removeScopes))
		{
			$scope = 'remove_scopes';
			$scopeData = $removeScopes;
			$scopesCount++;
		}

		// Only allowed to send data for one scope parameter
		if ($scopesCount >= 2)
		{
			throw new RuntimeException('You can only send one scope key in this request.');
		}

		// Build the request path.
		$path = '/authorizations/' . $id;

		$data = json_encode(
			array(
				$scope => $scopeData,
				'note' => $note,
				'note_url' => $url,
			)
		);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get details about an authorised application for the authenticated user.
	 *
	 * @param   integer  $id  ID of the authorisation to retrieve
	 *
	 * @deprecated  use authorization->get()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @note    This method will only accept Basic Authentication
	 * @throws  DomainException
	 */
	public function getAuthorisation($id)
	{
		// Build the request path.
		$path = '/authorizations/' . $id;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get the authorised applications for the authenticated user.
	 *
	 * @deprecated  use authorization->getList()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @note    This method will only accept Basic Authentication
	 */
	public function getAuthorisations()
	{
		// Build the request path.
		$path = '/authorizations';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get the rate limit for the authenticated user.
	 *
	 * @deprecated  use authorization->getRateLimit()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function getRateLimit()
	{
		// Build the request path.
		$path = '/rate_limit';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/package.php000064400000002374152177723700011434 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API package class for the Joomla Platform.
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
abstract class JGithubPackage extends JGithubObject
{
	/**
	 * @var    string
	 * @since  3.3 (CMS)
	 */
	protected $name = '';

	/**
	 * @var    array
	 * @since  3.3 (CMS)
	 */
	protected $packages = array();

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JGithubPackage  GitHub API package object.
	 *
	 * @since   3.3 (CMS)
	 * @throws  RuntimeException
	 */
	public function __get($name)
	{
		if (false == in_array($name, $this->packages))
		{
			throw new RuntimeException(sprintf('%1$s - Unknown package %2$s', __METHOD__, $name));
		}

		if (false == isset($this->$name))
		{
			$className = 'JGithubPackage' . ucfirst($this->name) . ucfirst($name);

			$this->$name = new $className($this->options, $this->client);
		}

		return $this->$name;
	}
}
joomla/github/statuses.php000064400000005546152177723700011720 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubStatuses extends JGithubObject
{
	/**
	 * Method to create a status.
	 *
	 * @param   string  $user         The name of the owner of the GitHub repository.
	 * @param   string  $repo         The name of the GitHub repository.
	 * @param   string  $sha          The SHA1 value for which to set the status.
	 * @param   string  $state        The state (pending, success, error or failure).
	 * @param   string  $targetUrl    Optional target URL.
	 * @param   string  $description  Optional description for the status.
	 *
	 * @deprecated  use repositories->statuses->create()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function create($user, $repo, $sha, $state, $targetUrl = null, $description = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/statuses/' . $sha;

		if (!in_array($state, array('pending', 'success', 'error', 'failure')))
		{
			throw new InvalidArgumentException('State must be one of pending, success, error or failure.');
		}

		// Build the request data.
		$data = array(
			'state' => $state,
		);

		if (!is_null($targetUrl))
		{
			$data['target_url'] = $targetUrl;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), json_encode($data));

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list statuses for an SHA.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $sha   SHA1 for which to get the statuses.
	 *
	 * @deprecated  use repositories->statuses->getList()
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function getList($user, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/statuses/' . $sha;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/package/users.php000064400000010010152177723700012557 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/users
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageUsers extends JGithubPackage
{
	protected $name = 'Users';

	protected $packages = array('emails', 'followers', 'keys');

	/**
	 * Get a single user.
	 *
	 * @param   string  $user  The users login name.
	 *
	 * @throws DomainException
	 *
	 * @return object
	 */
	public function get($user)
	{
		// Build the request path.
		$path = '/users/' . $user;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get the current authenticated user.
	 *
	 * @throws DomainException
	 *
	 * @return mixed
	 */
	public function getAuthenticatedUser()
	{
		// Build the request path.
		$path = '/user';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Update a user.
	 *
	 * @param   string  $name      The full name
	 * @param   string  $email     The email
	 * @param   string  $blog      The blog
	 * @param   string  $company   The company
	 * @param   string  $location  The location
	 * @param   string  $hireable  If he is unemplayed :P
	 * @param   string  $bio       The biometrical DNA fingerprint (or smthng...)
	 *
	 * @throws DomainException
	 *
	 * @return mixed
	 */
	public function edit($name = '', $email = '', $blog = '', $company = '', $location = '', $hireable = '', $bio = '')
	{
		$data = array(
			'name'     => $name,
			'email'    => $email,
			'blog'     => $blog,
			'company'  => $company,
			'location' => $location,
			'hireable' => $hireable,
			'bio'      => $bio,
		);

		// Build the request path.
		$path = '/user';

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * Get all users.
	 *
	 * This provides a dump of every user, in the order that they signed up for GitHub.
	 *
	 * @param   integer  $since  The integer ID of the last User that you’ve seen.
	 *
	 * @throws DomainException
	 * @return mixed
	 */
	public function getList($since = 0)
	{
		// Build the request path.
		$path = '/users';

		$path .= ($since) ? '?since=' . $since : '';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/*
	 * Legacy methods
	 */

	/**
	 * Get a single user.
	 *
	 * @param   string  $user  The users login name.
	 *
	 * @deprecated use users->get()
	 *
	 * @throws DomainException
	 *
	 * @return mixed
	 */
	public function getUser($user)
	{
		return $this->get($user);
	}

	/**
	 * Update a user.
	 *
	 * @param   string  $name      The full name
	 * @param   string  $email     The email
	 * @param   string  $blog      The blog
	 * @param   string  $company   The company
	 * @param   string  $location  The location
	 * @param   string  $hireable  If he is unemplayed :P
	 * @param   string  $bio       The biometrical DNA fingerprint (or smthng...)
	 *
	 * @deprecated use users->edit()
	 *
	 * @throws DomainException
	 *
	 * @return mixed
	 */
	public function updateUser($name = '', $email = '', $blog = '', $company = '', $location = '', $hireable = '', $bio = '')
	{
		return $this->edit($name = '', $email = '', $blog = '', $company = '', $location = '', $hireable = '', $bio = '');
	}

	/**
	 * Get all users.
	 *
	 * This provides a dump of every user, in the order that they signed up for GitHub.
	 *
	 * @param   integer  $since  The integer ID of the last User that you’ve seen.
	 *
	 * @deprecated use users->getList()
	 *
	 * @throws DomainException
	 * @return mixed
	 */
	public function getUsers($since = 0)
	{
		return $this->getList($since);
	}
}
joomla/github/package/search.php000064400000006533152177723700012702 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Search class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/search
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageSearch extends JGithubPackage
{
	/**
	 * Search issues.
	 *
	 * @param   string  $owner    The name of the owner of the repository.
	 * @param   string  $repo     The name of the repository.
	 * @param   string  $state    The state - open or closed.
	 * @param   string  $keyword  The search term.
	 *
	 * @throws UnexpectedValueException
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function issues($owner, $repo, $state, $keyword)
	{
		if (false == in_array($state, array('open', 'close')))
		{
			throw new UnexpectedValueException('State must be either "open" or "closed"');
		}

		// Build the request path.
		$path = '/legacy/issues/search/' . $owner . '/' . $repo . '/' . $state . '/' . $keyword;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Search repositories.
	 *
	 * Find repositories by keyword. Note, this legacy method does not follow
	 * the v3 pagination pattern.
	 * This method returns up to 100 results per page and pages can be fetched
	 * using the start_page parameter.
	 *
	 * @param   string   $keyword     The search term.
	 * @param   string   $language    Filter results by language https://github.com/languages
	 * @param   integer  $start_page  Page number to fetch
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function repositories($keyword, $language = '', $start_page = 0)
	{
		// Build the request path.
		$path = '/legacy/repos/search/' . $keyword . '?';

		$path .= ($language) ? '&language=' . $language : '';
		$path .= ($start_page) ? '&start_page=' . $start_page : '';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Search users.
	 *
	 * Find users by keyword.
	 *
	 * @param   string   $keyword     The search term.
	 * @param   integer  $start_page  Page number to fetch
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function users($keyword, $start_page = 0)
	{
		// Build the request path.
		$path = '/legacy/user/search/' . $keyword . '?';

		$path .= ($start_page) ? '&start_page=' . $start_page : '';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Email search.
	 *
	 * This API call is added for compatibility reasons only. There’s no guarantee
	 * that full email searches will always be available. The @ character in the
	 * address must be left unencoded. Searches only against public email addresses
	 * (as configured on the user’s GitHub profile).
	 *
	 * @param   string  $email  The email address(es).
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function email($email)
	{
		// Build the request path.
		$path = '/legacy/user/email/' . $email;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}
}
joomla/github/package/data/blobs.php000064400000003465152177723700013450 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Data Blobs class for the Joomla Platform.
 *
 * Since blobs can be any arbitrary binary data, the input and responses for the blob API
 * takes an encoding parameter that can be either utf-8 or base64. If your data cannot be
 * losslessly sent as a UTF-8 string, you can base64 encode it.
 *
 * @documentation https://developer.github.com/v3/git/blobs/
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageDataBlobs extends JGithubPackage
{
	/**
	 * Get a Blob.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 * @param   string  $sha    The commit SHA.
	 *
	 * @return object
	 */
	public function get($owner, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/blobs/' . $sha;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a Blob.
	 *
	 * @param   string  $owner     Repository owner.
	 * @param   string  $repo      Repository name.
	 * @param   string  $content   The content of the blob.
	 * @param   string  $encoding  The encoding to use.
	 *
	 * @return object
	 */
	public function create($owner, $repo, $content, $encoding = 'utf-8')
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/blobs';

		$data = array(
			'content'  => $content,
			'encoding' => $encoding,
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}
}
joomla/github/package/data/commits.php000064400000004276152177723700014023 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Data Commits class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/git/commits/
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageDataCommits extends JGithubPackage
{
	/**
	 * Get a single commit.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $sha    The commit SHA.
	 *
	 * @return object
	 */
	public function get($owner, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/commits/' . $sha;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to create a commit.
	 *
	 * @param   string  $owner    The name of the owner of the GitHub repository.
	 * @param   string  $repo     The name of the GitHub repository.
	 * @param   string  $message  The commit message.
	 * @param   string  $tree     SHA of the tree object this commit points to.
	 * @param   array   $parents  Array of the SHAs of the commits that were the parents of this commit.
	 *                            If omitted or empty, the commit will be written as a root commit.
	 *                            For a single parent, an array of one SHA should be provided.
	 *                            For a merge commit, an array of more than one should be provided.
	 *
	 * @throws DomainException
	 * @since   3.0.0
	 *
	 * @return  object
	 */
	public function create($owner, $repo, $message, $tree, array $parents = array())
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/commits';

		$data = json_encode(
			array('message' => $message, 'tree' => $tree, 'parents' => $parents)
		);

		// Send the request.
		return $this->processResponse(
			$response = $this->client->post($this->fetchUrl($path), $data),
			201
		);
	}
}
joomla/github/package/data/tags.php000064400000005553152177723700013305 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Data Tags class for the Joomla Platform.
 *
 * This tags API only deals with tag objects - so only annotated tags, not lightweight tags.
 *
 * @documentation https://developer.github.com/v3/git/tags/
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageDataTags extends JGithubPackage
{
	/**
	 * Get a Tag.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $sha    The SHA1 value to set the reference to.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($owner, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/tags/' . $sha;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a Tag Object
	 *
	 * Note that creating a tag object does not create the reference that makes a tag in Git.
	 * If you want to create an annotated tag in Git, you have to do this call to create the tag object,
	 * and then create the refs/tags/[tag] reference. If you want to create a lightweight tag,
	 * you simply have to create the reference - this call would be unnecessary.
	 *
	 * @param   string  $owner         The name of the owner of the GitHub repository.
	 * @param   string  $repo          The name of the GitHub repository.
	 * @param   string  $tag           The tag string.
	 * @param   string  $message       The tag message.
	 * @param   string  $object        The SHA of the git object this is tagging.
	 * @param   string  $type          The type of the object we’re tagging. Normally this is a commit
	 *                                 but it can also be a tree or a blob.
	 * @param   string  $tagger_name   The name of the author of the tag.
	 * @param   string  $tagger_email  The email of the author of the tag.
	 * @param   string  $tagger_date   Timestamp of when this object was tagged.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return object
	 */
	public function create($owner, $repo, $tag, $message, $object, $type, $tagger_name, $tagger_email, $tagger_date)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/tags';

		$data = array(
			'tag'          => $tag,
			'message'      => $message,
			'object'       => $object,
			'type'         => $type,
			'tagger_name'  => $tagger_name,
			'tagger_email' => $tagger_email,
			'tagger_date'  => $tagger_date,
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}
}
joomla/github/package/data/trees.php000064400000006441152177723700013466 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Data Trees class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/git/trees/
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageDataTrees extends JGithubPackage
{
	/**
	 * Get a Tree
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $sha    The SHA1 value to set the reference to.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($owner, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/trees/' . $sha;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a Tree Recursively
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $sha    The SHA1 value to set the reference to.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return object
	 */
	public function getRecursively($owner, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/trees/' . $sha . '?recursive=1';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a Tree.
	 *
	 * The tree creation API will take nested entries as well. If both a tree and a nested path
	 * modifying that tree are specified, it will overwrite the contents of that tree with the
	 * new path contents and write a new tree out.
	 *
	 * Parameters fir the tree:
	 *
	 * tree.path
	 *     String of the file referenced in the tree
	 * tree.mode
	 *     String of the file mode - one of 100644 for file (blob), 100755 for executable (blob),
	 *     040000 for subdirectory (tree), 160000 for submodule (commit) or 120000 for a blob
	 *     that specifies the path of a symlink
	 * tree.type
	 *     String of blob, tree, commit
	 * tree.sha
	 *     String of SHA1 checksum ID of the object in the tree
	 * tree.content
	 *     String of content you want this file to have - GitHub will write this blob out and use
	 *     that SHA for this entry. Use either this or tree.sha
	 *
	 * @param   string  $owner      The name of the owner of the GitHub repository.
	 * @param   string  $repo       The name of the GitHub repository.
	 * @param   array   $tree       Array of Hash objects (of path, mode, type and sha) specifying
	 *                              a tree structure
	 * @param   string  $base_tree  The SHA1 of the tree you want to update with new data.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return object
	 */
	public function create($owner, $repo, $tree, $base_tree = '')
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/trees';

		$data = array();

		$data['tree'] = $tree;

		if ($base_tree)
		{
			$data['base_tree'] = $base_tree;
		}

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}
}
joomla/github/package/data/refs.php000064400000012016152177723700013276 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/git/refs/
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageDataRefs extends JGithubPackage
{
	/**
	 * Method to get a reference.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $ref   The reference to get.
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function get($user, $repo, $ref)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs/' . $ref;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list references for a repository.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $namespace  Optional sub-namespace to limit the returned references.
	 * @param   integer  $page       Page to request
	 * @param   integer  $limit      Number of results to return per page
	 *
	 * @return  array
	 *
	 * @since   1.7.3
	 */
	public function getList($user, $repo, $namespace = '', $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs' . $namespace;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to create a ref.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $ref   The name of the fully qualified reference.
	 * @param   string  $sha   The SHA1 value to set this reference to.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($user, $repo, $ref, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs';

		// Build the request data.
		$data = json_encode(
			array(
				'ref' => $ref,
				'sha' => $sha,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a reference.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   string   $ref    The reference to update.
	 * @param   string   $sha    The SHA1 value to set the reference to.
	 * @param   boolean  $force  Whether the update should be forced. Default to false.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($user, $repo, $ref, $sha, $force = false)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs/' . $ref;

		// Craete the data object.
		$data = new stdClass;

		// If a title is set add it to the data object.
		if ($force)
		{
			$data->force = true;
		}

		$data->sha = $sha;

		// Encode the request data.
		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Delete a Reference
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $ref    The reference to update.
	 *
	 * @since   3.3 (CMS)
	 * @return object
	 */
	public function delete($owner, $repo, $ref)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/git/refs/' . $ref;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/gists.php000064400000031443152177723700012564 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Gists class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/gists
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @property-read  JGithubPackageGistsComments  $comments  GitHub API object for gist comments.
 */
class JGithubPackageGists extends JGithubPackage
{
	protected $name = 'Gists';

	protected $packages = array(
		'comments',
	);

	/**
	 * Method to create a gist.
	 *
	 * @param   mixed    $files        Either an array of file paths or a single file path as a string.
	 * @param   boolean  $public       True if the gist should be public.
	 * @param   string   $description  The optional description of the gist.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($files, $public = false, $description = null)
	{
		// Build the request path.
		$path = '/gists';

		// Build the request data.
		$data = json_encode(
			array(
				'files' => $this->buildFileData((array) $files),
				'public' => (bool) $public,
				'description' => $description,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  void
	 */
	public function delete($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to update a gist.
	 *
	 * @param   integer  $gistId       The gist number.
	 * @param   mixed    $files        Either an array of file paths or a single file path as a string.
	 * @param   boolean  $public       True if the gist should be public.
	 * @param   string   $description  The description of the gist.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($gistId, $files = null, $public = null, $description = null)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId;

		// Craete the data object.
		$data = new stdClass;

		// If a description is set add it to the data object.
		if (isset($description))
		{
			$data->description = $description;
		}

		// If the public flag is set add it to the data object.
		if (isset($public))
		{
			$data->public = $public;
		}

		// If a state is set add it to the data object.
		if (isset($files))
		{
			$data->files = $this->buildFileData((array) $files);
		}

		// Encode the request data.
		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to fork a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function fork($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/fork';

		// Send the request.
		// TODO: Verify change
		$response = $this->client->post($this->fetchUrl($path), '');

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function get($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list gists.  If a user is authenticated it will return the user's gists, otherwise
	 * it will return all public gists.
	 *
	 * @param   integer  $page   The page number from which to get items.
	 * @param   integer  $limit  The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/gists';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of gists belonging to a given user.
	 *
	 * @param   string   $user   The name of the GitHub user from which to list gists.
	 * @param   integer  $page   The page number from which to get items.
	 * @param   integer  $limit  The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getListByUser($user, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/users/' . $user . '/gists';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of all public gists.
	 *
	 * @param   integer  $page   The page number from which to get items.
	 * @param   integer  $limit  The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getListPublic($page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/gists/public';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of the authenticated users' starred gists.
	 *
	 * @param   integer  $page   The page number from which to get items.
	 * @param   integer  $limit  The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getListStarred($page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/gists/starred';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to check if a gist has been starred.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  boolean  True if the gist is starred.
	 */
	public function isStarred($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/star';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code == 204)
		{
			return true;
		}
		elseif ($response->code == 404)
		{
			return false;
		}
		else
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to star a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  void
	 */
	public function star($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/star';

		// Send the request.
		$response = $this->client->put($this->fetchUrl($path), '');

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to star a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  void
	 */
	public function unstar($gistId)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/star';

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to fetch a data array for transmitting to the GitHub API for a list of files based on
	 * an input array of file paths or filename and content pairs.
	 *
	 * @param   array  $files  The list of file paths or filenames and content.
	 *
	 * @throws InvalidArgumentException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	protected function buildFileData(array $files)
	{
		$data = array();

		foreach ($files as $key => $file)
		{
			// If the key isn't numeric, then we are dealing with a file whose content has been supplied
			if (!is_numeric($key))
			{
				$data[$key] = array('content' => $file);
			}

			// Otherwise, we have been given a path and we have to load the content
			// Verify that the each file exists.
			elseif (!file_exists($file))
			{
				throw new InvalidArgumentException('The file ' . $file . ' does not exist.');
			}
			else
			{
				$data[basename($file)] = array('content' => file_get_contents($file));
			}
		}

		return $data;
	}

	/*
	 * Deprecated methods
	 */

	/**
	 * Method to create a comment on a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 * @param   string   $body    The comment body text.
	 *
	 * @deprecated use gists->comments->create()
	 *
	 * @return  object
	 *
	 * @since      1.7.3
	 */
	public function createComment($gistId, $body)
	{
		return $this->comments->create($gistId, $body);
	}

	/**
	 * Method to delete a comment on a gist.
	 *
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @deprecated use gists->comments->delete()
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function deleteComment($commentId)
	{
		$this->comments->delete($commentId);
	}

	/**
	 * Method to update a comment on a gist.
	 *
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @deprecated use gists->comments->edit()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function editComment($commentId, $body)
	{
		return $this->comments->edit($commentId, $body);
	}

	/**
	 * Method to get a specific comment on a gist.
	 *
	 * @param   integer  $commentId  The comment id to get.
	 *
	 * @deprecated use gists->comments->get()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function getComment($commentId)
	{
		return $this->comments->get($commentId);
	}

	/**
	 * Method to get the list of comments on a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @deprecated use gists->comments->getList()
	 *
	 * @return  array
	 *
	 * @since   1.7.3
	 */
	public function getComments($gistId, $page = 0, $limit = 0)
	{
		return $this->comments->getList($gistId, $page, $limit);
	}
}
joomla/github/package/repositories.php000064400000032454152177723700014165 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity class for the Joomla Platform.
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @documentation  https://developer.github.com/v3/repos
 *
 * @property-read  JGithubPackageRepositoriesCollaborators  $collaborators  GitHub API object for collaborators.
 * @property-read  JGithubPackageRepositoriesComments       $comments       GitHub API object for comments.
 * @property-read  JGithubPackageRepositoriesCommits        $commits        GitHub API object for commits.
 * @property-read  JGithubPackageRepositoriesContents       $contents       GitHub API object for contents.
 * @property-read  JGithubPackageRepositoriesDownloads      $downloads      GitHub API object for downloads.
 * @property-read  JGithubPackageRepositoriesForks          $forks          GitHub API object for forks.
 * @property-read  JGithubPackageRepositoriesHooks          $hooks          GitHub API object for hooks.
 * @property-read  JGithubPackageRepositoriesKeys           $keys           GitHub API object for keys.
 * @property-read  JGithubPackageRepositoriesMerging        $merging        GitHub API object for merging.
 * @property-read  JGithubPackageRepositoriesStatuses       $statuses       GitHub API object for statuses.
 */
class JGithubPackageRepositories extends JGithubPackage
{
	protected $name = 'Repositories';

	protected $packages = array('collaborators', 'comments', 'commits', 'contents', 'downloads', 'forks', 'hooks', 'keys', 'merging', 'statuses');

	/**
	 * List your repositories.
	 *
	 * List repositories for the authenticated user.
	 *
	 * @param   string  $type       Sort type. all, owner, public, private, member. Default: all.
	 * @param   string  $sort       Sort field. created, updated, pushed, full_name, default: full_name.
	 * @param   string  $direction  Sort direction. asc or desc, default: when using full_name: asc, otherwise desc.
	 *
	 * @throws RuntimeException
	 *
	 * @return object
	 */
	public function getListOwn($type = 'all', $sort = 'full_name', $direction = '')
	{
		if (false == in_array($type, array('all', 'owner', 'public', 'private', 'member')))
		{
			throw new RuntimeException('Invalid type');
		}

		if (false == in_array($sort, array('created', 'updated', 'pushed', 'full_name')))
		{
			throw new RuntimeException('Invalid sort field');
		}

		// Sort direction default: when using full_name: asc, otherwise desc.
		$direction = ($direction) ? : (('full_name' == $sort) ? 'asc' : 'desc');

		if (false == in_array($direction, array('asc', 'desc')))
		{
			throw new RuntimeException('Invalid sort order');
		}

		// Build the request path.
		$path = '/user/repos'
			. '?type=' . $type
			. '&sort=' . $sort
			. '&direction=' . $direction;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List user repositories.
	 *
	 * List public repositories for the specified user.
	 *
	 * @param   string  $user       The user name.
	 * @param   string  $type       Sort type. all, owner, member. Default: all.
	 * @param   string  $sort       Sort field. created, updated, pushed, full_name, default: full_name.
	 * @param   string  $direction  Sort direction. asc or desc, default: when using full_name: asc, otherwise desc.
	 *
	 * @throws RuntimeException
	 *
	 * @return object
	 */
	public function getListUser($user, $type = 'all', $sort = 'full_name', $direction = '')
	{
		if (false == in_array($type, array('all', 'owner', 'member')))
		{
			throw new RuntimeException('Invalid type');
		}

		if (false == in_array($sort, array('created', 'updated', 'pushed', 'full_name')))
		{
			throw new RuntimeException('Invalid sort field');
		}

		// Sort direction default: when using full_name: asc, otherwise desc.
		$direction = ($direction) ? : (('full_name' == $sort) ? 'asc' : 'desc');

		if (false == in_array($direction, array('asc', 'desc')))
		{
			throw new RuntimeException('Invalid sort order');
		}

		// Build the request path.
		$path = '/users/' . $user . '/repos'
			. '?type=' . $type
			. '&sort=' . $sort
			. '&direction=' . $direction;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List organization repositories.
	 *
	 * List repositories for the specified org.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $type  Sort type. all, public, private, forks, sources, member. Default: all.
	 *
	 * @throws RuntimeException
	 *
	 * @return object
	 */
	public function getListOrg($org, $type = 'all')
	{
		if (false == in_array($type, array('all', 'public', 'private', 'forks', 'sources', 'member')))
		{
			throw new RuntimeException('Invalid type');
		}

		// Build the request path.
		$path = '/orgs/' . $org . '/repos'
			. '?type=' . $type;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List all repositories.
	 *
	 * This provides a dump of every repository, in the order that they were created.
	 *
	 * @param   integer  $id  The integer ID of the last Repository that you’ve seen.
	 *
	 * @throws RuntimeException
	 *
	 * @return object
	 */
	public function getList($id = 0)
	{
		// Build the request path.
		$path = '/repositories';
		$path .= ($id) ? '?since=' . (int) $id : '';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a new repository for the authenticated user or an organization.
	 * OAuth users must supply repo scope.
	 *
	 * @param   string   $name                The repository name.
	 * @param   string   $org                 The organization name (if needed).
	 * @param   string   $description         The repository description.
	 * @param   string   $homepage            The repository homepage.
	 * @param   boolean  $private             Set true to create a private repository, false to create a public one.
	 *                                          Creating private repositories requires a paid GitHub account.
	 * @param   boolean  $has_issues          Set true to enable issues for this repository, false to disable them.
	 * @param   boolean  $has_wiki            Set true to enable the wiki for this repository, false to disable it.
	 * @param   boolean  $has_downloads       Set true to enable downloads for this repository, false to disable them.
	 * @param   integer  $team_id             The id of the team that will be granted access to this repository.
	 *                                        This is only valid when creating a repo in an organization.
	 * @param   boolean  $auto_init           true to create an initial commit with empty README.
	 * @param   string   $gitignore_template  Desired language or platform .gitignore template to apply.
	 *                                         Use the name of the template without the extension. For example,
	 *                                        “Haskell” Ignored if auto_init parameter is not provided.
	 *
	 * @return object
	 */
	public function create($name, $org = '', $description = '', $homepage = '', $private = false, $has_issues = false,
		$has_wiki = false, $has_downloads = false, $team_id = 0, $auto_init = false, $gitignore_template = '')
	{
		$path = ($org)
			// Create a repository for an organization
			? '/orgs/' . $org . '/repos'
			// Create a repository for a user
			: '/user/repos';

		$data = array(
			'name'               => $name,
			'description'        => $description,
			'homepage'           => $homepage,
			'private'            => $private,
			'has_issues'         => $has_issues,
			'has_wiki'           => $has_wiki,
			'has_downloads'      => $has_downloads,
			'team_id'            => $team_id,
			'auto_init'          => $auto_init,
			'gitignore_template' => $gitignore_template,
		);

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}

	/**
	 * Get a repository.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function get($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Edit a repository.
	 *
	 * @param   string   $owner           Repository owner.
	 * @param   string   $repo            Repository name.
	 * @param   string   $name            The repository name.
	 * @param   string   $description     The repository description.
	 * @param   string   $homepage        The repository homepage.
	 * @param   boolean  $private         Set true to create a private repository, false to create a public one.
	 *                                    Creating private repositories requires a paid GitHub account.
	 * @param   boolean  $has_issues      Set true to enable issues for this repository, false to disable them.
	 * @param   boolean  $has_wiki        Set true to enable the wiki for this repository, false to disable it.
	 * @param   boolean  $has_downloads   Set true to enable downloads for this repository, false to disable them.
	 * @param   string   $default_branch  Update the default branch for this repository
	 *
	 * @return object
	 */
	public function edit($owner, $repo, $name, $description = '', $homepage = '', $private = false, $has_issues = false,
		$has_wiki = false, $has_downloads = false, $default_branch = '')
	{
		$path = '/repos/' . $owner . '/' . $repo;

		$data = array(
			'name'           => $name,
			'description'    => $description,
			'homepage'       => $homepage,
			'private'        => $private,
			'has_issues'     => $has_issues,
			'has_wiki'       => $has_wiki,
			'has_downloads'  => $has_downloads,
			'default_branch' => $default_branch,
		);

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * List contributors.
	 *
	 * @param   string   $owner  Repository owner.
	 * @param   string   $repo   Repository name.
	 * @param   boolean  $anon   Set to 1 or true to include anonymous contributors in results.
	 *
	 * @return object
	 */
	public function getListContributors($owner, $repo, $anon = false)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/contributors';

		$path .= ($anon) ? '?anon=true' : '';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List languages.
	 *
	 * List languages for the specified repository. The value on the right of a language is the number of bytes of code
	 * written in that language.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function getListLanguages($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/languages';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List Teams
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function getListTeams($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/teams';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List Tags.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function getListTags($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/tags';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List Branches.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function getListBranches($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/branches';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a Branch.
	 *
	 * @param   string  $owner   Repository owner.
	 * @param   string  $repo    Repository name.
	 * @param   string  $branch  Branch name.
	 *
	 * @return object
	 */
	public function getBranch($owner, $repo, $branch)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/branches/' . $branch;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Delete a Repository.
	 *
	 * Deleting a repository requires admin access. If OAuth is used, the delete_repo scope is required.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @return object
	 */
	public function delete($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo;

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path))
		);
	}
}
joomla/github/package/activity.php000064400000001373152177723700013266 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity class for the Joomla Platform.
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @documentation  https://developer.github.com/v3/activity/
 *
 * @property-read  JGithubPackageActivityEvents  $events  GitHub API object for markdown.
 */
class JGithubPackageActivity extends JGithubPackage
{
	protected $name = 'Activity';

	protected $packages = array('events', 'notifications', 'starring', 'watching');
}
joomla/github/package/issues.php000064400000034607152177723700012753 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Issues class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/issues
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @property-read  JGithubPackageIssuesAssignees   $assignees   GitHub API object for assignees.
 * @property-read  JGithubPackageIssuesComments    $comments    GitHub API object for comments.
 * @property-read  JGithubPackageIssuesEvents      $events      GitHub API object for events.
 * @property-read  JGithubPackageIssuesLabels      $labels      GitHub API object for labels.
 * @property-read  JGithubPackageIssuesMilestones  $milestones  GitHub API object for milestones.
 */
class JGithubPackageIssues extends JGithubPackage
{
	protected $name = 'Issues';

	protected $packages = array('assignees', 'comments', 'events', 'labels', 'milestones');

	/**
	 * Method to create an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $title      The title of the new issue.
	 * @param   string   $body       The body text for the new issue.
	 * @param   string   $assignee   The login for the GitHub user that this issue should be assigned to.
	 * @param   integer  $milestone  The milestone to associate this issue with.
	 * @param   array    $labels     The labels to associate with this issue.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($user, $repo, $title, $body = null, $assignee = null, $milestone = null, array $labels = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues';

		// Ensure that we have a non-associative array.
		if (isset($labels))
		{
			$labels = array_values($labels);
		}

		// Build the request data.
		$data = json_encode(
			array(
				'title'     => $title,
				'assignee'  => $assignee,
				'milestone' => $milestone,
				'labels'    => $labels,
				'body'      => $body,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $issueId    The issue number.
	 * @param   string   $state      The optional new state for the issue. [open, closed]
	 * @param   string   $title      The title of the new issue.
	 * @param   string   $body       The body text for the new issue.
	 * @param   string   $assignee   The login for the GitHub user that this issue should be assigned to.
	 * @param   integer  $milestone  The milestone to associate this issue with.
	 * @param   array    $labels     The labels to associate with this issue.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($user, $repo, $issueId, $state = null, $title = null, $body = null, $assignee = null, $milestone = null, array $labels = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues/' . (int) $issueId;

		// Craete the data object.
		$data = new stdClass;

		// If a title is set add it to the data object.
		if (isset($title))
		{
			$data->title = $title;
		}

		// If a body is set add it to the data object.
		if (isset($body))
		{
			$data->body = $body;
		}

		// If a state is set add it to the data object.
		if (isset($state))
		{
			$data->state = $state;
		}

		// If an assignee is set add it to the data object.
		if (isset($assignee))
		{
			$data->assignee = $assignee;
		}

		// If a milestone is set add it to the data object.
		if (isset($milestone))
		{
			$data->milestone = $milestone;
		}

		// If labels are set add them to the data object.
		if (isset($labels))
		{
			// Ensure that we have a non-associative array.
			if (isset($labels))
			{
				$labels = array_values($labels);
			}

			$data->labels = $labels;
		}

		// Encode the request data.
		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single issue.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function get($user, $repo, $issueId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues/' . (int) $issueId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list an authenticated user's issues.
	 *
	 * @param   string   $filter     The filter type: assigned, created, mentioned, subscribed.
	 * @param   string   $state      The optional state to filter requests by. [open, closed]
	 * @param   string   $labels     The list of comma separated Label names. Example: bug,ui,@high.
	 * @param   string   $sort       The sort order: created, updated, comments, default: created.
	 * @param   string   $direction  The list direction: asc or desc, default: desc.
	 * @param   JDate    $since      The date/time since when issues should be returned.
	 * @param   integer  $page       The page number from which to get items.
	 * @param   integer  $limit      The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($filter = null, $state = null, $labels = null, $sort = null,
		$direction = null, JDate $since = null, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/issues';

		// TODO Implement the filtering options.

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list issues.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $milestone  The milestone number, 'none', or *.
	 * @param   string   $state      The optional state to filter requests by. [open, closed]
	 * @param   string   $assignee   The assignee name, 'none', or *.
	 * @param   string   $mentioned  The GitHub user name.
	 * @param   string   $labels     The list of comma separated Label names. Example: bug,ui,@high.
	 * @param   string   $sort       The sort order: created, updated, comments, default: created.
	 * @param   string   $direction  The list direction: asc or desc, default: desc.
	 * @param   JDate    $since      The date/time since when issues should be returned.
	 * @param   integer  $page       The page number from which to get items.
	 * @param   integer  $limit      The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getListByRepository($user, $repo, $milestone = null, $state = null, $assignee = null, $mentioned = null, $labels = null,
		$sort = null, $direction = null, JDate $since = null, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues';

		$uri = new JUri($this->fetchUrl($path, $page, $limit));

		if ($milestone)
		{
			$uri->setVar('milestone', $milestone);
		}

		if ($state)
		{
			$uri->setVar('state', $state);
		}

		if ($assignee)
		{
			$uri->setVar('assignee', $assignee);
		}

		if ($mentioned)
		{
			$uri->setVar('mentioned', $mentioned);
		}

		if ($labels)
		{
			$uri->setVar('labels', $labels);
		}

		if ($sort)
		{
			$uri->setVar('sort', $sort);
		}

		if ($direction)
		{
			$uri->setVar('direction', $direction);
		}

		if ($since)
		{
			$uri->setVar('since', $since->toISO8601());
		}

		// Send the request.
		$response = $this->client->get((string) $uri);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/*
	 * Deprecated methods
	 */

	/**
	 * Method to create a comment on an issue.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 * @param   string   $body     The comment body text.
	 *
	 * @deprecated use issues->comments->create()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function createComment($user, $repo, $issueId, $body)
	{
		return $this->comments->create($user, $repo, $issueId, $body);
	}

	/**
	 * Method to create a label on a repo.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $name   The label name.
	 * @param   string  $color  The label color.
	 *
	 * @deprecated use issues->labels->create()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function createLabel($user, $repo, $name, $color)
	{
		return $this->labels->create($user, $repo, $name, $color);
	}

	/**
	 * Method to delete a comment on an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @deprecated use issues->comments->delete()
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function deleteComment($user, $repo, $commentId)
	{
		$this->comments->delete($user, $repo, $commentId);
	}

	/**
	 * Method to delete a label on a repo.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $label  The label name.
	 *
	 * @deprecated use issues->labels->delete()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function deleteLabel($user, $repo, $label)
	{
		return $this->labels->delete($user, $repo, $label);
	}

	/**
	 * Method to update a comment on an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @deprecated use issues->comments->edit()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function editComment($user, $repo, $commentId, $body)
	{
		return $this->comments->edit($user, $repo, $commentId, $body);
	}

	/**
	 * Method to update a label on a repo.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $label  The label name.
	 * @param   string  $name   The label name.
	 * @param   string  $color  The label color.
	 *
	 * @deprecated use issues->labels->update()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function editLabel($user, $repo, $label, $name, $color)
	{
		return $this->labels->update($user, $repo, $label, $name, $color);
	}

	/**
	 * Method to get a specific comment on an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The comment id to get.
	 *
	 * @deprecated use issues->comments->get()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function getComment($user, $repo, $commentId)
	{
		return $this->comments->get($user, $repo, $commentId);
	}

	/**
	 * Method to get the list of comments on an issue.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 * @param   integer  $page     The page number from which to get items.
	 * @param   integer  $limit    The number of items on a page.
	 *
	 * @deprecated use issues->comments->getList()
	 *
	 * @return  array
	 *
	 * @since   1.7.3
	 */
	public function getComments($user, $repo, $issueId, $page = 0, $limit = 0)
	{
		return $this->comments->getList($user, $repo, $issueId, $page, $limit);
	}

	/**
	 * Method to get a specific label on a repo.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $name  The label name to get.
	 *
	 * @deprecated use issues->labels->get()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getLabel($user, $repo, $name)
	{
		return $this->labels->get($user, $repo, $name);
	}

	/**
	 * Method to get the list of labels on a repo.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 *
	 * @deprecated use issues->labels->getList()
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function getLabels($user, $repo)
	{
		return $this->labels->getList($user, $repo);
	}
}
joomla/github/package/gists/comments.php000064400000010435152177723700014407 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Gists Comments class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/gists/comments/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageGistsComments extends JGithubPackage
{
	/**
	 * Method to create a comment on a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 * @param   string   $body    The comment body text.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($gistId, $body)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/comments';

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete a comment on a gist.
	 *
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  void
	 */
	public function delete($commentId)
	{
		// Build the request path.
		$path = '/gists/comments/' . (int) $commentId;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to update a comment on a gist.
	 *
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($commentId, $body)
	{
		// Build the request path.
		$path = '/gists/comments/' . (int) $commentId;

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
			)
		);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a specific comment on a gist.
	 *
	 * @param   integer  $commentId  The comment id to get.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function get($commentId)
	{
		// Build the request path.
		$path = '/gists/comments/' . (int) $commentId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get the list of comments on a gist.
	 *
	 * @param   integer  $gistId  The gist number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($gistId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/gists/' . (int) $gistId . '/comments';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/package/data.php000064400000004437152177723700012347 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API DB class for the Joomla Platform.
 *
 * https://developer.github.com/v3/git/
 * Git DB API
 *
 * The Git Database API gives you access to read and write raw Git objects to your Git database on GitHub and to list
 *  * and update your references (branch heads and tags).
 *
 * This basically allows you to reimplement a lot of Git functionality over our API - by creating raw objects
 *  * directly into the database and updating branch references you could technically do just about anything that
 *  * Git can do without having Git installed.
 *
 * Git DB API functions will return a 409 if the git repo for a Repository is empty or unavailable.
 *  * This typically means it is being created still. Contact Support if this response status persists.
 *
 * git db
 *
 * For more information on the Git object database, please read the Git Internals chapter of the Pro Git book.
 *
 * As an example, if you wanted to commit a change to a file in your repository, you would:
 *
 *     get the current commit object
 *     retrieve the tree it points to
 *     retrieve the content of the blob object that tree has for that particular file path
 *     change the content somehow and post a new blob object with that new content, getting a blob SHA back
 *     post a new tree object with that file path pointer replaced with your new blob SHA getting a tree SHA back
 *     create a new commit object with the current commit SHA as the parent and the new tree SHA, getting a commit SHA back
 *     update the reference of your branch to point to the new commit SHA
 *
 * It might seem complex, but it’s actually pretty simple when you understand the model and it opens up a ton of
 * things you could potentially do with the API.
 *
 * @documentation https://developer.github.com/v3/git/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageData extends JGithubPackage
{
	protected $name = 'Data';

	protected $packages = array('blobs', 'commits', 'refs', 'tags', 'trees');
}
joomla/github/package/markdown.php000064400000003652152177723700013256 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2012 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Markdown class.
 *
 * @documentation https://developer.github.com/v3/markdown
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageMarkdown extends JGithubPackage
{
	/**
	 * Method to render a markdown document.
	 *
	 * @param   string  $text     The text object being parsed.
	 * @param   string  $mode     The parsing mode; valid options are 'markdown' or 'gfm'.
	 * @param   string  $context  An optional repository context, only used in 'gfm' mode.
	 *
	 * @since   3.3 (CMS)
	 * @throws  DomainException
	 * @throws  InvalidArgumentException
	 *
	 * @return  string  Formatted HTML
	 */
	public function render($text, $mode = 'gfm', $context = null)
	{
		// The valid modes
		$validModes = array('gfm', 'markdown');

		// Make sure the scope is valid
		if (!in_array($mode, $validModes))
		{
			throw new InvalidArgumentException(sprintf('The %s mode is not valid. Valid modes are "gfm" or "markdown".', $mode));
		}

		// Build the request path.
		$path = '/markdown';

		// Build the request data.
		$data = str_replace('\\/', '/', json_encode(
				array(
					'text'    => $text,
					'mode'    => $mode,
					'context' => $context,
				)
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			$message = (isset($error->message)) ? $error->message : 'Error: ' . $response->code;
			throw new DomainException($message, $response->code);
		}

		return $response->body;
	}
}
joomla/github/package/orgs/teams.php000064400000022624152177723700013517 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Orgs Teams class for the Joomla Platform.
 *
 * All actions against teams require at a minimum an authenticated user who is a member
 * of the owner’s team in the :org being managed. Additionally, OAuth users require “user” scope.
 *
 * @documentation https://developer.github.com/v3/orgs/teams/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageOrgsTeams extends JGithubPackage
{
	/**
	 * List teams.
	 *
	 * @param   string  $org  The name of the organization.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList($org)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/teams';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get team.
	 *
	 * @param   integer  $id  The team id.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($id)
	{
		// Build the request path.
		$path = '/teams/' . (int) $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create team.
	 *
	 * In order to create a team, the authenticated user must be an owner of the organization.
	 *
	 * @param   string  $org         The name of the organization.
	 * @param   string  $name        The name of the team.
	 * @param   array   $repoNames   Repository names.
	 * @param   string  $permission  The permission.
	 *                               pull - team members can pull, but not push to or administer these repositories. Default
	 *                               push - team members can pull and push, but not administer these repositories.
	 *                               admin - team members can pull, push and administer these repositories.
	 *
	 * @throws UnexpectedValueException
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function create($org, $name, array $repoNames = array(), $permission = '')
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/teams';

		$data = array(
			'name' => $name,
		);

		if ($repoNames)
		{
			$data['repo_names'] = $repoNames;
		}

		if ($permission)
		{
			if (false == in_array($permission, array('pull', 'push', 'admin')))
			{
				throw new UnexpectedValueException('Permissions must be either "pull", "push", or "admin".');
			}

			$data['permission'] = $permission;
		}

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Edit team.
	 *
	 * In order to edit a team, the authenticated user must be an owner of the org that the team is associated with.
	 *
	 * @param   integer  $id          The team id.
	 * @param   string   $name        The name of the team.
	 * @param   string   $permission  The permission.
	 *                                pull - team members can pull, but not push to or administer these repositories. Default
	 *                                push - team members can pull and push, but not administer these repositories.
	 *                                admin - team members can pull, push and administer these repositories.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function edit($id, $name, $permission = '')
	{
		// Build the request path.
		$path = '/teams/' . (int) $id;

		$data = array(
			'name' => $name,
		);

		if ($permission)
		{
			if (false == in_array($permission, array('pull', 'push', 'admin')))
			{
				throw new UnexpectedValueException('Permissions must be either "pull", "push", or "admin".');
			}

			$data['permission'] = $permission;
		}

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Delete team.
	 *
	 * In order to delete a team, the authenticated user must be an owner of the org that the team is associated with.
	 *
	 * @param   integer  $id  The team id.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function delete($id)
	{
		// Build the request path.
		$path = '/teams/' . $id;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * List team members.
	 *
	 * In order to list members in a team, the authenticated user must be a member of the team.
	 *
	 * @param   integer  $id  The team id.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListMembers($id)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/members';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get team member.
	 *
	 * In order to get if a user is a member of a team, the authenticated user must be a member of the team.
	 *
	 * @param   integer  $id    The team id.
	 * @param   string   $user  The name of the user.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function isMember($id, $user)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/members/' . $user;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case 204 :
				// Response if user is a member
				return true;
				break;

			case 404 :
				// Response if user is not a member
				return false;
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Add team member.
	 *
	 * In order to add a user to a team, the authenticated user must have ‘admin’ permissions
	 * to the team or be an owner of the org that the team is associated with.
	 *
	 * @param   integer  $id    The team id.
	 * @param   string   $user  The name of the user.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function addMember($id, $user)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/members/' . $user;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Remove team member.
	 *
	 * In order to remove a user from a team, the authenticated user must have ‘admin’ permissions
	 * to the team or be an owner of the org that the team is associated with.
	 * NOTE: This does not delete the user, it just remove them from the team.
	 *
	 * @param   integer  $id    The team id.
	 * @param   string   $user  The name of the user.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function removeMember($id, $user)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/members/' . $user;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * List team repos.
	 *
	 * @param   integer  $id  The team id.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListRepos($id)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/repos';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Check if the repo is managed by this team.
	 *
	 * @param   integer  $id    The team id.
	 * @param   string   $repo  The name of the GitHub repository.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function checkRepo($id, $repo)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/repos/' . $repo;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case 204 :
				// Response if repo is managed by this team.
				return true;
				break;

			case 404 :
				// Response if repo is not managed by this team.
				return false;
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Add team repo.
	 *
	 * In order to add a repo to a team, the authenticated user must be an owner of the
	 * org that the team is associated with. Also, the repo must be owned by the organization,
	 * or a direct form of a repo owned by the organization.
	 *
	 * If you attempt to add a repo to a team that is not owned by the organization, you get:
	 * Status: 422 Unprocessable Entity
	 *
	 * @param   integer  $id     The team id.
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function addRepo($id, $owner, $repo)
	{
		// Build the request path.
		$path = '/teams/' . $id . '/repos/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Remove team repo.
	 *
	 * In order to remove a repo from a team, the authenticated user must be an owner
	 * of the org that the team is associated with. NOTE: This does not delete the
	 * repo, it just removes it from the team.
	 *
	 * @param   integer  $id     The team id.
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function removeRepo($id, $owner, $repo)
	{
		// Build the request path.
		$path = '/teams/' . (int) $id . '/repos/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/orgs/members.php000064400000012405152177723700014034 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Orgs Members class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/orgs/members/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageOrgsMembers extends JGithubPackage
{
	/**
	 * Members list.
	 *
	 * List all users who are members of an organization.
	 * A member is a user that belongs to at least 1 team in the organization.
	 * If the authenticated user is also a member of this organization then
	 * both concealed and public members will be returned.
	 * If the requester is not a member of the organization the query will be
	 * redirected to the public members list.
	 *
	 * @param   string  $org  The name of the organization.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return boolean|mixed
	 */
	public function getList($org)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/members';

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case 302 :
				// Requester is not an organization member.
				return false;
				break;

			case 200 :
				return json_decode($response->body);
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Check membership.
	 *
	 * Check if a user is, publicly or privately, a member of the organization.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $user  The name of the user.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return boolean
	 */
	public function check($org, $user)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/members/' . $user;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case 204 :
				// Requester is an organization member and user is a member.
				return true;
				break;

			case 404 :
				// Requester is an organization member and user is not a member.
				// Requester is not an organization member and is inquiring about themselves.
				return false;
				break;

			case 302 :
				// Requester is not an organization member.
				return false;
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Add a member.
	 *
	 * To add someone as a member to an org, you must add them to a team.
	 */

	/**
	 * Remove a member.
	 *
	 * Removing a user from this list will remove them from all teams and they will no longer have
	 * any access to the organization’s repositories.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $user  The name of the user.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function remove($org, $user)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/members/' . $user;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Public members list.
	 *
	 * Members of an organization can choose to have their membership publicized or not.
	 *
	 * @param   string  $org  The name of the organization.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListPublic($org)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/public_members';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Check public membership.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $user  The name of the user.
	 *
	 * @throws UnexpectedValueException
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function checkPublic($org, $user)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/public_members/' . $user;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case 204 :
				// Response if user is a public member.
				return true;
				break;

			case 404 :
				// Response if user is not a public member.
				return false;
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Publicize a user’s membership.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $user  The name of the user.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function publicize($org, $user)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/public_members/' . $user;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Conceal a user’s membership.
	 *
	 * @param   string  $org   The name of the organization.
	 * @param   string  $user  The name of the user.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function conceal($org, $user)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/public_members/' . $user;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/repositories/collaborators.php000064400000006073152177723700017031 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Collaborators class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/collaborators
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesCollaborators extends JGithubPackage
{
	/**
	 * List.
	 *
	 * When authenticating as an organization owner of an organization-owned repository, all organization
	 * owners are included in the list of collaborators. Otherwise, only users with access to the repository
	 * are returned in the collaborators list.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/collaborators';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Test if a user is a collaborator.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $user   The name of the GitHub user.
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return boolean
	 */
	public function get($owner, $repo, $user)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/collaborators/' . $user;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case '204';

				return true;
				break;
			case '404';

				return false;
				break;
			default;
				throw new UnexpectedValueException('Unexpected code: ' . $response->code);
				break;
		}
	}

	/**
	 * Add collaborator.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $user   The name of the GitHub user.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function add($owner, $repo, $user)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/collaborators/' . $user;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Remove collaborator.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $user   The name of the GitHub user.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function remove($owner, $repo, $user)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/collaborators/' . $user;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/repositories/keys.php000064400000006367152177723700015144 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Forks class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/keys
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesKeys extends JGithubPackage
{
	/**
	 * List keys in a repository.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 *
	 * @since 3.3.0
	 *
	 * @return object
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/keys';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a key.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The id of the key.
	 *
	 * @since 3.3.0
	 *
	 * @return object
	 */
	public function get($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/keys/' . (int) $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a key.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $title  The key title.
	 * @param   string  $key    The key.
	 *
	 * @since 3.3.0
	 *
	 * @return object
	 */
	public function create($owner, $repo, $title, $key)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/keys';

		$data = array(
			'title' => $title,
			'key'   => $key,
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}

	/**
	 * Edit a key.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The id of the key.
	 * @param   string   $title  The key title.
	 * @param   string   $key    The key.
	 *
	 * @since 3.3.0
	 *
	 * @return object
	 */
	public function edit($owner, $repo, $id, $title, $key)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/keys/' . (int) $id;

		$data = array(
			'title' => $title,
			'key'   => $key,
		);

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * Delete a key.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The id of the key.
	 *
	 * @since 3.3.0
	 *
	 * @return boolean
	 */
	public function delete($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/keys/' . (int) $id;

		$this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);

		return true;
	}
}
joomla/github/package/repositories/commits.php000064400000007474152177723700015644 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Commits class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/commits
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesCommits extends JGithubPackage
{
	/**
	 * Method to list commits for a repository.
	 *
	 * A special note on pagination: Due to the way Git works, commits are paginated based on SHA
	 * instead of page number.
	 * Please follow the link headers as outlined in the pagination overview instead of constructing
	 * page links yourself.
	 *
	 * @param   string  $user    The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $sha     Sha or branch to start listing commits from.
	 * @param   string  $path    Only commits containing this file path will be returned.
	 * @param   string  $author  GitHub login, name, or email by which to filter by commit author.
	 * @param   JDate   $since   ISO 8601 Date - Only commits after this date will be returned.
	 * @param   JDate   $until   ISO 8601 Date - Only commits before this date will be returned.
	 *
	 * @throws DomainException
	 * @since    3.0.0
	 *
	 * @return  array
	 */
	public function getList($user, $repo, $sha = '', $path = '', $author = '', JDate $since = null, JDate $until = null)
	{
		// Build the request path.
		$rPath = '/repos/' . $user . '/' . $repo . '/commits?';

		$rPath .= ($sha) ? '&sha=' . $sha : '';
		$rPath .= ($path) ? '&path=' . $path : '';
		$rPath .= ($author) ? '&author=' . $author : '';
		$rPath .= ($since) ? '&since=' . $since->toISO8601() : '';
		$rPath .= ($until) ? '&until=' . $until->toISO8601() : '';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($rPath));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single commit for a repository.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $sha   The SHA of the commit to retrieve.
	 *
	 * @throws DomainException
	 * @since   3.0.0
	 *
	 * @return  array
	 */
	public function get($user, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a diff for two commits.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $base  The base of the diff, either a commit SHA or branch.
	 * @param   string  $head  The head of the diff, either a commit SHA or branch.
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function compare($user, $repo, $base, $head)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/compare/' . $base . '...' . $head;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}
}
joomla/github/package/repositories/statistics.php000064400000011526152177723700016354 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API class for the Joomla Platform.
 *
 * The Repository Statistics API allows you to fetch the data that GitHub uses for
 * visualizing different types of repository activity.
 *
 * @documentation https://developer.github.com/v3/repos/statistics
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesStatistics  extends JGithubPackage
{
	/**
	 * Get contributors list with additions, deletions, and commit counts.
	 *
	 * Response include:
	 * total - The Total number of commits authored by the contributor.
	 *
	 * Weekly Hash
	 *
	 * w - Start of the week
	 * a - Number of additions
	 * d - Number of deletions
	 * c - Number of commits
	 *
	 * @param   string  $owner  The owner of the repository.
	 * @param   string  $repo   The repository name.
	 *
	 * @since   1.0
	 *
	 * @return  object
	 */
	public function getListContributors($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stats/contributors';

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Get the last year of commit activity data.
	 *
	 * Returns the last year of commit activity grouped by week.
	 * The days array is a group of commits per day, starting on Sunday.
	 *
	 * @param   string  $owner  The owner of the repository.
	 * @param   string  $repo   The repository name.
	 *
	 * @since   1.0
	 *
	 * @return  object
	 */
	public function getActivityData($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stats/commit_activity';

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Get the number of additions and deletions per week.
	 *
	 * Response returns a weekly aggregate of the number of additions and deletions pushed to a repository.
	 *
	 * @param   string  $owner  The owner of the repository.
	 * @param   string  $repo   The repository name.
	 *
	 * @since   1.0
	 *
	 * @return  object
	 */
	public function getCodeFrequency($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stats/code_frequency';

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Get the weekly commit count for the repo owner and everyone else.
	 *
	 * Returns the total commit counts for the "owner" and total commit counts in "all". "all" is everyone combined,
	 * including the owner in the last 52 weeks.
	 * If you’d like to get the commit counts for non-owners, you can subtract all from owner.
	 *
	 * The array order is oldest week (index 0) to most recent week.
	 *
	 * @param   string  $owner  The owner of the repository.
	 * @param   string  $repo   The repository name.
	 *
	 * @since   1.0
	 *
	 * @return  object
	 */
	public function getParticipation($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stats/participation';

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Get the number of commits per hour in each day.
	 *
	 * Response
	 * Each array contains the day number, hour number, and number of commits:
	 *
	 * 0-6: Sunday - Saturday
	 * 0-23: Hour of day
	 * Number of commits
	 *
	 * For example, [2, 14, 25] indicates that there were 25 total commits, during the 2:00pm hour on Tuesdays.
	 * All times are based on the time zone of individual commits.
	 *
	 * @param   string  $owner  The owner of the repository.
	 * @param   string  $repo   The repository name.
	 *
	 * @since   1.0
	 *
	 * @return  object
	 */
	public function getPunchCard($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stats/punch_card';

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Process the response and decode it.
	 *
	 * @param   JHttpResponse  $response      The response.
	 * @param   integer        $expectedCode  The expected "good" code.
	 * @param   boolean        $decode        If the should be response be JSON decoded.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  \DomainException
	 */
	protected function processResponse(JHttpResponse $response, $expectedCode = 200, $decode = true)
	{
		if (202 == $response->code)
		{
			throw new \DomainException(
				'GitHub is building the statistics data. Please try again in a few moments.',
				$response->code
			);
		}

		return parent::processResponse($response, $expectedCode, $decode);
	}
}
joomla/github/package/repositories/statuses.php000064400000005616152177723700016040 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/statuses
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesStatuses extends JGithubPackage
{
	/**
	 * Method to create a status.
	 *
	 * @param   string  $user         The name of the owner of the GitHub repository.
	 * @param   string  $repo         The name of the GitHub repository.
	 * @param   string  $sha          The SHA1 value for which to set the status.
	 * @param   string  $state        The state (pending, success, error or failure).
	 * @param   string  $targetUrl    Optional target URL.
	 * @param   string  $description  Optional description for the status.
	 *
	 * @throws InvalidArgumentException
	 * @throws DomainException
	 *
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function create($user, $repo, $sha, $state, $targetUrl = null, $description = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/statuses/' . $sha;

		if (!in_array($state, array('pending', 'success', 'error', 'failure')))
		{
			throw new InvalidArgumentException('State must be one of pending, success, error or failure.');
		}

		// Build the request data.
		$data = array(
			'state' => $state,
		);

		if (!is_null($targetUrl))
		{
			$data['target_url'] = $targetUrl;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), json_encode($data));

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list statuses for an SHA.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $sha   SHA1 for which to get the statuses.
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function getList($user, $repo, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/statuses/' . $sha;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/package/repositories/contents.php000064400000012767152177723700016027 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Contents class for the Joomla Platform.
 *
 * These API methods let you retrieve the contents of files within a repository as Base64 encoded content.
 * See media types for requesting raw or other formats.
 *
 * @documentation https://developer.github.com/v3/repos/contents
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesContents extends JGithubPackage
{
	/**
	 * Get the README
	 *
	 * This method returns the preferred README for a repository.
	 *
	 * GET /repos/:owner/:repo/readme
	 *
	 * Parameters
	 *
	 * ref
	 * Optional string - The String name of the Commit/Branch/Tag. Defaults to master.
	 *
	 * Response
	 *
	 * Status: 200 OK
	 * X-RateLimit-Limit: 5000
	 * X-RateLimit-Remaining: 4999
	 *
	 * {
	 * "type": "file",
	 * "encoding": "base64",
	 * "_links": {
	 * "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
	 * "self": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md",
	 * "html": "https://github.com/octokit/octokit.rb/blob/master/README.md"
	 * },
	 * "size": 5362,
	 * "name": "README.md",
	 * "path": "README.md",
	 * "content": "encoded content ...",
	 * "sha": "3d21ec53a331a6f037a91c368710b99387d012c1"
	 * }
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $ref    The String name of the Commit/Branch/Tag. Defaults to master.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getReadme($owner, $repo, $ref = '')
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/readme';

		if ($ref)
		{
			$path .= '?ref=' . $ref;
		}

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get contents
	 *
	 * This method returns the contents of any file or directory in a repository.
	 *
	 * GET /repos/:owner/:repo/contents/:path
	 *
	 * Parameters
	 *
	 * path
	 * Optional string - The content path.
	 * ref
	 * Optional string - The String name of the Commit/Branch/Tag. Defaults to master.
	 *
	 * Response
	 *
	 * Status: 200 OK
	 * X-RateLimit-Limit: 5000
	 * X-RateLimit-Remaining: 4999
	 *
	 * {
	 * "type": "file",
	 * "encoding": "base64",
	 * "_links": {
	 * "git": "https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1",
	 * "self": "https://api.github.com/repos/octokit/octokit.rb/contents/README.md",
	 * "html": "https://github.com/octokit/octokit.rb/blob/master/README.md"
	 * },
	 * "size": 5362,
	 * "name": "README.md",
	 * "path": "README.md",
	 * "content": "encoded content ...",
	 * "sha": "3d21ec53a331a6f037a91c368710b99387d012c1"
	 * }
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $path   The content path.
	 * @param   string  $ref    The String name of the Commit/Branch/Tag. Defaults to master.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($owner, $repo, $path, $ref = '')
	{
		// Build the request path.
		$rPath = '/repos/' . $owner . '/' . $repo . '/contents/' . $path;

		if ($ref)
		{
			$rPath .= '?ref=' . $ref;
		}

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($rPath))
		);
	}

	/**
	 * Get archive link
	 *
	 * This method will return a 302 to a URL to download a tarball or zipball archive for a repository.
	 * Please make sure your HTTP framework is configured to follow redirects or you will need to use the Location header to make a second GET request.
	 *
	 * Note: For private repositories, these links are temporary and expire quickly.
	 *
	 * GET /repos/:owner/:repo/:archive_format/:ref
	 *
	 * Parameters
	 *
	 * archive_format
	 * Either tarball or zipball
	 * ref
	 * Optional string - valid Git reference, defaults to master
	 *
	 * Response
	 *
	 * Status: 302 Found
	 * Location: http://github.com/me/myprivate/tarball/master?SSO=thistokenexpires
	 * X-RateLimit-Limit: 5000
	 * X-RateLimit-Remaining: 4999
	 *
	 * To follow redirects with curl, use the -L switch:
	 *
	 * curl -L https://api.github.com/repos/octokit/octokit.rb/tarball > octokit.tar.gz
	 *
	 * @param   string  $owner           The name of the owner of the GitHub repository.
	 * @param   string  $repo            The name of the GitHub repository.
	 * @param   string  $archive_format  Either tarball or zipball.
	 * @param   string  $ref             The String name of the Commit/Branch/Tag. Defaults to master.
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getArchiveLink($owner, $repo, $archive_format = 'zipball', $ref = '')
	{
		if (false == in_array($archive_format, array('tarball', 'zipball')))
		{
			throw new UnexpectedValueException('Archive format must be either "tarball" or "zipball".');
		}

		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/' . $archive_format;

		if ($ref)
		{
			$path .= '?ref=' . $ref;
		}

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path)),
			302
		);
	}
}
joomla/github/package/repositories/forks.php000064400000004664152177723700015313 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Forks class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/forks
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesForks extends JGithubPackage
{
	/**
	 * Method to fork a repository.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $org   The organization to fork the repo into. By default it is forked to the current user.
	 *
	 * @return  object
	 *
	 * @since   2.5.0
	 * @throws  DomainException
	 */
	public function create($user, $repo, $org = '')
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/forks';

		if (strlen($org) > 0)
		{
			$data = json_encode(
				array('org' => $org)
			);
		}
		else
		{
			$data = json_encode(array());
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 202)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list forks for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @return  array
	 *
	 * @since   2.5.0
	 * @throws  DomainException
	 */
	public function getList($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/forks';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/package/repositories/merging.php000064400000004772152177723700015617 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Merging class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/merging
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesMerging extends JGithubPackage
{
	/**
	 * Perform a merge.
	 *
	 * @param   string  $owner           The name of the owner of the GitHub repository.
	 * @param   string  $repo            The name of the GitHub repository.
	 * @param   string  $base            The name of the base branch that the head will be merged into.
	 * @param   string  $head            The head to merge. This can be a branch name or a commit SHA1.
	 * @param   string  $commit_message  Commit message to use for the merge commit.
	 *                                   If omitted, a default message will be used.
	 *
	 * @throws UnexpectedValueException
	 * @since   3.3.0
	 *
	 * @return  boolean
	 */
	public function perform($owner, $repo, $base, $head, $commit_message = '')
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/merges';

		$data = new stdClass;

		$data->base = $base;
		$data->head = $head;

		if ($commit_message)
		{
			$data->commit_message = $commit_message;
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), json_encode($data));

		switch ($response->code)
		{
			case '201':
				// Success
				return json_decode($response->body);
				break;

			case '204':
				// No-op response (base already contains the head, nothing to merge)
				throw new UnexpectedValueException('Nothing to merge');
				break;

			case '404':
				// Missing base or Missing head response
				$error = json_decode($response->body);

				$message = (isset($error->message)) ? $error->message : 'Missing base or head: ' . $response->code;

				throw new UnexpectedValueException($message);
				break;

			case '409':
				// Merge conflict response
				$error = json_decode($response->body);

				$message = (isset($error->message)) ? $error->message : 'Merge conflict ' . $response->code;

				throw new UnexpectedValueException($message);
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}
}
joomla/github/package/repositories/hooks.php000064400000014347152177723700015311 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Hooks class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/hooks
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesHooks extends JGithubPackage
{
	/**
	 * Array containing the allowed hook events
	 *
	 * @var    array
	 * @since  3.1.4
	 */
	protected $events = array(
		'push',
		'issues',
		'issue_comment',
		'commit_comment',
		'pull_request',
		'gollum',
		'watch',
		'download',
		'fork',
		'fork_apply',
		'member',
		'public',
		'status',
	);

	/**
	 * Method to create a hook on a repository.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   string   $name    The name of the service being called.
	 * @param   array    $config  Array containing the config for the service.
	 * @param   array    $events  The events the hook will be triggered for.
	 * @param   boolean  $active  Flag to determine if the hook is active
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @throws  RuntimeException
	 */
	public function create($user, $repo, $name, $config, array $events = array('push'), $active = true)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks';

		// Check to ensure all events are in the allowed list
		foreach ($events as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your events array contains an unauthorized event.');
			}
		}

		$data = json_encode(
			array('name' => $name, 'config' => $config, 'events' => $events, 'active' => $active)
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Method to delete a hook
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to delete.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function delete($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Method to edit a hook.
	 *
	 * @param   string   $user          The name of the owner of the GitHub repository.
	 * @param   string   $repo          The name of the GitHub repository.
	 * @param   integer  $id            ID of the hook to edit.
	 * @param   string   $name          The name of the service being called.
	 * @param   array    $config        Array containing the config for the service.
	 * @param   array    $events        The events the hook will be triggered for.  This resets the currently set list
	 * @param   array    $addEvents     Events to add to the hook.
	 * @param   array    $removeEvents  Events to remove from the hook.
	 * @param   boolean  $active        Flag to determine if the hook is active
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @throws  RuntimeException
	 */
	public function edit($user, $repo, $id, $name, $config, array $events = array('push'), array $addEvents = array(),
		array $removeEvents = array(), $active = true)
	{
		// Check to ensure all events are in the allowed list
		foreach ($events as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your events array contains an unauthorized event.');
			}
		}

		foreach ($addEvents as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your active_events array contains an unauthorized event.');
			}
		}

		foreach ($removeEvents as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your remove_events array contains an unauthorized event.');
			}
		}

		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		$data = json_encode(
			array(
				'name'          => $name,
				'config'        => $config,
				'events'        => $events,
				'add_events'    => $addEvents,
				'remove_events' => $removeEvents,
				'active'        => $active,
			)
		);

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to get details about a single hook for the repository.
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to retrieve
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function get($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to list hooks for a repository.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function getList($user, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to test a hook against the latest repository commit
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to delete
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function test($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id . '/test';

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode('')),
			204
		);
	}
}
joomla/github/package/repositories/comments.php000064400000011512152177723700016002 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Comments class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/repos/comments
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesComments extends JGithubPackage
{
	/**
	 * Method to get a list of commit comments for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getListRepository($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}

	/**
	 * Method to get a list of comments for a single commit for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   string   $sha    The SHA of the commit to retrieve.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function getList($user, $repo, $sha, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha . '/comments';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}

	/**
	 * Method to get a single comment on a commit.
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the comment to retrieve
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	public function get($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . (int) $id;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to edit a comment on a commit.
	 *
	 * @param   string  $user     The name of the owner of the GitHub repository.
	 * @param   string  $repo     The name of the GitHub repository.
	 * @param   string  $id       The ID of the comment to edit.
	 * @param   string  $comment  The text of the comment.
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function edit($user, $repo, $id, $comment)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . $id;

		$data = json_encode(
			array(
				'body' => $comment,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to delete a comment on a commit.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $id    The ID of the comment to edit.
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function delete($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/comments/' . $id;

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Method to create a comment on a commit.
	 *
	 * @param   string   $user      The name of the owner of the GitHub repository.
	 * @param   string   $repo      The name of the GitHub repository.
	 * @param   string   $sha       The SHA of the commit to comment on.
	 * @param   string   $comment   The text of the comment.
	 * @param   integer  $line      The line number of the commit to comment on.
	 * @param   string   $filepath  A relative path to the file to comment on within the commit.
	 * @param   integer  $position  Line index in the diff to comment on.
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function create($user, $repo, $sha, $comment, $line, $filepath, $position)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/commits/' . $sha . '/comments';

		$data = json_encode(
			array(
				'body' => $comment,
				'path' => $filepath,
				'position' => (int) $position,
				'line' => (int) $line,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}
}
joomla/github/package/repositories/downloads.php000064400000014247152177723700016157 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Repositories Downloads class for the Joomla Platform.
 *
 * The downloads API is for package downloads only.
 * If you want to get source tarballs you should use
 * https://developer.github.com/v3/repos/contents/#get-archive-link instead.
 *
 * @documentation https://developer.github.com/v3/repos/downloads
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageRepositoriesDownloads extends JGithubPackage
{
	/**
	 * List downloads for a repository.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/downloads';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a single download.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The id of the download.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/downloads/' . $id;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a new download (Part 1: Create the resource).
	 *
	 * Creating a new download is a two step process. You must first create a new download resource.
	 *
	 * @param   string  $owner         The name of the owner of the GitHub repository.
	 * @param   string  $repo          The name of the GitHub repository.
	 * @param   string  $name          The name.
	 * @param   string  $size          Size of file in bytes.
	 * @param   string  $description   The description.
	 * @param   string  $content_type  The content type.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function create($owner, $repo, $name, $size, $description = '', $content_type = '')
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/downloads';

		$data = array(
			'name' => $name,
			'size' => $size,
		);

		if ($description)
		{
			$data['description'] = $description;
		}

		if ($content_type)
		{
			$data['content_type'] = $content_type;
		}

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Create a new download (Part 2: Upload file to s3).
	 *
	 * Now that you have created the download resource, you can use the information
	 * in the response to upload your file to s3. This can be done with a POST to
	 * the s3_url you got in the create response. Here is a brief example using curl:
	 *
	 * curl \
	 *     -F "key=downloads/octocat/Hello-World/new_file.jpg" \
	 *     -F "acl=public-read" \
	 *     -F "success_action_status=201" \
	 *     -F "Filename=new_file.jpg" \
	 *     -F "AWSAccessKeyId=1ABCDEF..." \
	 *     -F "Policy=ewogIC..." \
	 *     -F "Signature=mwnF..." \
	 *     -F "Content-Type=image/jpeg" \
	 *     -F "file=@new_file.jpg" \
	 *           https://github.s3.amazonaws.com/
	 *
	 * NOTES
	 * The order in which you pass these fields matters! Follow the order shown above exactly.
	 * All parameters shown are required and if you excluded or modify them your upload will
	 * fail because the values are hashed and signed by the policy.
	 *
	 * More information about using the REST API to interact with s3 can be found here:
	 * http://docs.amazonwebservices.com/AmazonS3/latest/API/
	 *
	 * @param   string  $key                    Value of path field in the response.
	 * @param   string  $acl                    Value of acl field in the response.
	 * @param   string  $success_action_status  201, or whatever you want to get back.
	 * @param   string  $filename               Value of name field in the response.
	 * @param   string  $awsAccessKeyId         Value of accesskeyid field in the response.
	 * @param   string  $policy                 Value of policy field in the response.
	 * @param   string  $signature              Value of signature field in the response.
	 * @param   string  $content_type           Value of mime_type field in the response.
	 * @param   string  $file                   Local file. Example assumes the file existing in the directory
	 *                                          where you are running the curl command. Yes, the @ matters.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return boolean
	 */
	public function upload($key, $acl, $success_action_status, $filename, $awsAccessKeyId, $policy, $signature, $content_type, $file)
	{
		// Build the request path.
		$url = 'https://github.s3.amazonaws.com/';

		$data = array(
			'key'                   => $key,
			'acl'                   => $acl,
			'success_action_status' => (int) $success_action_status,
			'Filename'              => $filename,
			'AWSAccessKeyId'        => $awsAccessKeyId,
			'Policy'                => $policy,
			'Signature'             => $signature,
			'Content-Type'          => $content_type,
			'file'                  => $file,
		);

		// Send the request.
		$response = $this->client->post($url, $data);

		// @todo Process the response..

		return (201 == $response->code) ? true : false;
	}

	/**
	 * Delete a download.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The id of the download.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function delete($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/downloads/' . (int) $id;

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/authorization.php000064400000020421152177723700014325 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Authorization class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/oauth/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageAuthorization extends JGithubPackage
{
	/**
	 * Method to create an authorization.
	 *
	 * @param   array   $scopes  A list of scopes that this authorization is in.
	 * @param   string  $note    A note to remind you what the OAuth token is for.
	 * @param   string  $url     A URL to remind you what app the OAuth token is for.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function create(array $scopes = array(), $note = '', $url = '')
	{
		// Build the request path.
		$path = '/authorizations';

		$data = json_encode(
			array('scopes' => $scopes, 'note' => $note, 'note_url' => $url)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete an authorization
	 *
	 * @param   integer  $id  ID of the authorization to delete
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function delete($id)
	{
		// Build the request path.
		$path = '/authorizations/' . $id;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to edit an authorization.
	 *
	 * @param   integer  $id            ID of the authorization to edit
	 * @param   array    $scopes        Replaces the authorization scopes with these.
	 * @param   array    $addScopes     A list of scopes to add to this authorization.
	 * @param   array    $removeScopes  A list of scopes to remove from this authorization.
	 * @param   string   $note          A note to remind you what the OAuth token is for.
	 * @param   string   $url           A URL to remind you what app the OAuth token is for.
	 *
	 * @throws RuntimeException
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function edit($id, array $scopes = array(), array $addScopes = array(), array $removeScopes = array(), $note = '', $url = '')
	{
		// Check if more than one scopes array contains data
		$scopesCount = 0;

		if (!empty($scopes))
		{
			$scope     = 'scopes';
			$scopeData = $scopes;
			$scopesCount++;
		}

		if (!empty($addScopes))
		{
			$scope     = 'add_scopes';
			$scopeData = $addScopes;
			$scopesCount++;
		}

		if (!empty($removeScopes))
		{
			$scope     = 'remove_scopes';
			$scopeData = $removeScopes;
			$scopesCount++;
		}

		// Only allowed to send data for one scope parameter
		if ($scopesCount >= 2)
		{
			throw new RuntimeException('You can only send one scope key in this request.');
		}

		// Build the request path.
		$path = '/authorizations/' . $id;

		$data = json_encode(
			array(
				$scope     => $scopeData,
				'note'     => $note,
				'note_url' => $url,
			)
		);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get details about an authorised application for the authenticated user.
	 *
	 * @param   integer  $id  ID of the authorization to retrieve
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 * @note    This method will only accept Basic Authentication
	 *
	 * @return  object
	 */
	public function get($id)
	{
		// Build the request path.
		$path = '/authorizations/' . $id;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get the authorised applications for the authenticated user.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 * @note    This method will only accept Basic Authentication
	 *
	 * @return  object
	 */
	public function getList()
	{
		// Build the request path.
		$path = '/authorizations';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get the rate limit for the authenticated user.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function getRateLimit()
	{
		// Build the request path.
		$path = '/rate_limit';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * 1. Request authorization on GitHub.
	 *
	 * @param   string  $client_id     The client ID you received from GitHub when you registered.
	 * @param   string  $redirect_uri  URL in your app where users will be sent after authorization.
	 * @param   string  $scope         Comma separated list of scopes.
	 * @param   string  $state         An unguessable random string. It is used to protect against
	 *                                 cross-site request forgery attacks.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return JUri
	 */
	public function getAuthorizationLink($client_id, $redirect_uri = '', $scope = '', $state = '')
	{
		$uri = new JUri('https://github.com/login/oauth/authorize');

		$uri->setVar('client_id', $client_id);

		if ($redirect_uri)
		{
			$uri->setVar('redirect_uri', urlencode($redirect_uri));
		}

		if ($scope)
		{
			$uri->setVar('scope', $scope);
		}

		if ($state)
		{
			$uri->setVar('state', $state);
		}

		return (string) $uri;
	}

	/**
	 * 2. Request the access token.
	 *
	 * @param   string  $client_id      The client ID you received from GitHub when you registered.
	 * @param   string  $client_secret  The client secret you received from GitHub when you registered.
	 * @param   string  $code           The code you received as a response to Step 1.
	 * @param   string  $redirect_uri   URL in your app where users will be sent after authorization.
	 * @param   string  $format         The response format (json, xml, ).
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return string
	 */
	public function requestToken($client_id, $client_secret, $code, $redirect_uri = '', $format = '')
	{
		$uri = 'https://github.com/login/oauth/access_token';

		$data = array(
			'client_id'     => $client_id,
			'client_secret' => $client_secret,
			'code'          => $code,
		);

		if ($redirect_uri)
		{
			$data['redirect_uri'] = $redirect_uri;
		}

		$headers = array();

		switch ($format)
		{
			case 'json' :
				$headers['Accept'] = 'application/json';
				break;
			case 'xml' :
				$headers['Accept'] = 'application/xml';
				break;
			default :
				if ($format)
				{
					throw new UnexpectedValueException('Invalid format');
				}
				break;
		}

		// Send the request.
		return $this->processResponse(
			$this->client->post($uri, $data, $headers),
			200, false
		);
	}
}
joomla/github/package/issues/events.php000064400000005326152177723700014253 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Issues Events class for the Joomla Platform.
 *
 * Records various events that occur around an Issue or Pull Request.
 * This is useful both for display on issue/pull request information pages and also
 * to determine who should be notified of comments.
 *
 * @documentation https://developer.github.com/v3/issues/events/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageIssuesEvents extends JGithubPackage
{
	/**
	 * List events for an issue.
	 *
	 * @param   string   $owner         The name of the owner of the GitHub repository.
	 * @param   string   $repo          The name of the GitHub repository.
	 * @param   integer  $issue_number  The issue number.
	 * @param   integer  $page          The page number from which to get items.
	 * @param   integer  $limit         The number of items on a page.
	 *
	 * @return object
	 */
	public function getList($owner, $repo, $issue_number, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . (int) $issue_number . '/events';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}

	/**
	 * List events for a repository.
	 *
	 * @param   string   $owner    The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 * @param   integer  $page     The page number from which to get items.
	 * @param   integer  $limit    The number of items on a page.
	 *
	 * @return object
	 */
	public function getListRepository($owner, $repo, $issueId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . (int) $issueId . '/comments';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}

	/**
	 * Get a single event.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The event number.
	 *
	 * @return object
	 */
	public function get($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/events/' . (int) $id;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}
}
joomla/github/package/issues/assignees.php000064400000004134152177723700014724 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Assignees class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/issues/assignees/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageIssuesAssignees extends JGithubPackage
{
	/**
	 * List assignees.
	 *
	 * This call lists all the available assignees (owner + collaborators) to which issues may be assigned.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 *
	 * @return object
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/assignees';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Check assignee.
	 *
	 * You may check to see if a particular user is an assignee for a repository.
	 * If the given assignee login belongs to an assignee for the repository, a 204 header
	 * with no content is returned.
	 * Otherwise a 404 status code is returned.
	 *
	 * @param   string  $owner     The name of the owner of the GitHub repository.
	 * @param   string  $repo      The name of the GitHub repository.
	 * @param   string  $assignee  The assinees login name.
	 *
	 * @throws DomainException|Exception
	 * @return boolean
	 */
	public function check($owner, $repo, $assignee)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/assignees/' . $assignee;

		try
		{
			$response = $this->client->get($this->fetchUrl($path));

			if (204 == $response->code)
			{
				return true;
			}

			throw new DomainException('Invalid response: ' . $response->code);
		}
		catch (DomainException $e)
		{
			if (isset($response->code) && 404 == $response->code)
			{
				return false;
			}

			throw $e;
		}
	}
}
joomla/github/package/issues/labels.php000064400000017214152177723700014210 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Milestones class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/issues/labels/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageIssuesLabels extends JGithubPackage
{
	/**
	 * Method to get the list of labels on a repo.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  array
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/labels';

		// Send the request.
		return $this->processResponse(
			$response = $this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to get a specific label on a repo.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $name  The label name to get.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function get($user, $repo, $name)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/labels/' . $name;

		// Send the request.
		return $this->processResponse(
			$response = $this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to create a label on a repo.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $name   The label name.
	 * @param   string  $color  The label color.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function create($owner, $repo, $name, $color)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/labels';

		// Build the request data.
		$data = json_encode(
			array(
				'name'  => $name,
				'color' => $color,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a label on a repo.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $label  The label name.
	 * @param   string  $name   The new label name.
	 * @param   string  $color  The new label color.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function update($user, $repo, $label, $name, $color)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/labels/' . $label;

		// Build the request data.
		$data = json_encode(
			array(
				'name'  => $name,
				'color' => $color,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to delete a label on a repo.
	 *
	 * @param   string  $owner  The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $name   The label name.
	 *
	 * @throws DomainException
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function delete($owner, $repo, $name)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/labels/' . $name;

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * List labels on an issue.
	 *
	 * @param   string   $owner   The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $number  The issue number.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function getListByIssue($owner, $repo, $number)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . $number . '/labels';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Add labels to an issue.
	 *
	 * @param   string  $owner   The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $number  The issue number.
	 * @param   array   $labels  An array of labels to add.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function add($owner, $repo, $number, array $labels)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . $number . '/labels';

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($labels))
		);
	}

	/**
	 * Remove a label from an issue.
	 *
	 * @param   string  $owner   The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $number  The issue number.
	 * @param   string  $name    The name of the label to remove.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function removeFromIssue($owner, $repo, $number, $name)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . $number . '/labels/' . $name;

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path))
		);
	}

	/**
	 * Replace all labels for an issue.
	 *
	 * Sending an empty array ([]) will remove all Labels from the Issue.
	 *
	 * @param   string  $owner   The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $number  The issue number.
	 * @param   array   $labels  New labels
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function replace($owner, $repo, $number, array $labels)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . $number . '/labels';

		// Send the request.
		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), json_encode($labels))
		);
	}

	/**
	.* Remove all labels from an issue.
	 *
	 * @param   string  $owner   The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $number  The issue number.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function removeAllFromIssue($owner, $repo, $number)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . $number . '/labels';

		// Send the request.
		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Get labels for every issue in a milestone.
	 *
	 * @param   string  $owner   The name of the owner of the GitHub repository.
	 * @param   string  $repo    The name of the GitHub repository.
	 * @param   string  $number  The issue number.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function getListByMilestone($owner, $repo, $number)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/milestones/' . $number . '/labels';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}
}
joomla/github/package/issues/comments.php000064400000013015152177723700014566 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Comments class for the Joomla Platform.
 *
 * The Issue Comments API supports listing, viewing, editing, and creating comments
 * on issues and pull requests.
 *
 * @documentation https://developer.github.com/v3/issues/comments/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageIssuesComments extends JGithubPackage
{
	/**
	 * Method to get the list of comments on an issue.
	 *
	 * @param   string   $owner    The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 * @param   integer  $page     The page number from which to get items.
	 * @param   integer  $limit    The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($owner, $repo, $issueId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/' . (int) $issueId . '/comments';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}

	/**
	 * Method to get the list of comments in a repository.
	 *
	 * @param   string  $owner      The name of the owner of the GitHub repository.
	 * @param   string  $repo       The name of the GitHub repository.
	 * @param   string  $sort       The sort field - created or updated.
	 * @param   string  $direction  The sort order- asc or desc. Ignored without sort parameter.
	 * @param   JDate   $since      A timestamp in ISO 8601 format.
	 *
	 * @throws UnexpectedValueException
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getRepositoryList($owner, $repo, $sort = 'created', $direction = 'asc', JDate $since = null)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/comments';

		if (false == in_array($sort, array('created', 'updated')))
		{
			throw new UnexpectedValueException(
				sprintf(
					'%1$s - sort field must be "created" or "updated"', __METHOD__
				)
			);
		}

		if (false == in_array($direction, array('asc', 'desc')))
		{
			throw new UnexpectedValueException(
				sprintf(
					'%1$s - direction field must be "asc" or "desc"', __METHOD__
				)
			);
		}

		$path .= '?sort=' . $sort;
		$path .= '&direction=' . $direction;

		if ($since)
		{
			$path .= '&since=' . $since->toISO8601();
		}

		// Send the request.
		return $this->processResponse($this->client->get($this->fetchUrl($path)));
	}

	/**
	 * Method to get a single comment.
	 *
	 * @param   string   $owner  The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $id     The comment id.
	 *
	 * @return mixed
	 */
	public function get($owner, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/comments/' . (int) $id;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to update a comment on an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @since   1.7.3
	 * @throws DomainException
	 *
	 * @return  object
	 */
	public function edit($user, $repo, $commentId, $body)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues/comments/' . (int) $commentId;

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to create a comment on an issue.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number.
	 * @param   string   $body     The comment body text.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($user, $repo, $issueId, $body)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues/' . (int) $issueId . '/comments';

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Method to delete a comment on an issue.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  boolean
	 */
	public function delete($user, $repo, $commentId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/issues/comments/' . (int) $commentId;

		// Send the request.
		$this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);

		return true;
	}
}
joomla/github/package/issues/milestones.php000064400000014740152177723700015131 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Milestones class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/issues/milestones/
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageIssuesMilestones extends JGithubPackage
{
	/**
	 * Method to get the list of milestones for a repo.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $state      The milestone state to retrieved.  Open (default) or closed.
	 * @param   string   $sort       Sort can be due_date (default) or completeness.
	 * @param   string   $direction  Direction is asc or desc (default).
	 * @param   integer  $page       The page number from which to get items.
	 * @param   integer  $limit      The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   3.1.4
	 *
	 * @return  array
	 */
	public function getList($user, $repo, $state = 'open', $sort = 'due_date', $direction = 'desc', $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones?';

		$path .= 'state=' . $state;
		$path .= '&sort=' . $sort;
		$path .= '&direction=' . $direction;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a specific milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The milestone id to get.
	 *
	 * @throws DomainException
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function get($user, $repo, $milestoneId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to create a milestone for a repository.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $title        The title of the milestone.
	 * @param   string   $state        Can be open (default) or closed.
	 * @param   string   $description  Optional description for milestone.
	 * @param   string   $due_on       Optional ISO 8601 time.
	 *
	 * @throws DomainException
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function create($user, $repo, $title, $state = null, $description = null, $due_on = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones';

		// Build the request data.
		$data = array(
			'title' => $title,
		);

		if (!is_null($state))
		{
			$data['state'] = $state;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		if (!is_null($due_on))
		{
			$data['due_on'] = $due_on;
		}

		$data = json_encode($data);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The id of the comment to update.
	 * @param   integer  $title        Optional title of the milestone.
	 * @param   string   $state        Can be open (default) or closed.
	 * @param   string   $description  Optional description for milestone.
	 * @param   string   $due_on       Optional ISO 8601 time.
	 *
	 * @throws DomainException
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function edit($user, $repo, $milestoneId, $title = null, $state = null, $description = null, $due_on = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Build the request data.
		$data = array();

		if (!is_null($title))
		{
			$data['title'] = $title;
		}

		if (!is_null($state))
		{
			$data['state'] = $state;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		if (!is_null($due_on))
		{
			$data['due_on'] = $due_on;
		}

		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete a milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The id of the milestone to delete.
	 *
	 * @throws DomainException
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function delete($user, $repo, $milestoneId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}
}
joomla/github/package/gitignore.php000064400000003642152177723700013422 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Gitignore class for the Joomla Platform.
 *
 * The .gitignore Templates API lists and fetches templates from the GitHub .gitignore repository.
 *
 * @documentation https://developer.github.com/v3/gitignore/
 * @documentation https://github.com/github/gitignore
 *
 * @since       3.3.0
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageGitignore extends JGithubPackage
{
	/**
	 * Listing available templates
	 *
	 * List all templates available to pass as an option when creating a repository.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList()
	{
		// Build the request path.
		$path = '/gitignore/templates';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a single template
	 *
	 * @param   string   $name  The name of the template
	 * @param   boolean  $raw   Raw output
	 *
	 * @throws DomainException
	 * @since  3.3 (CMS)
	 *
	 * @return mixed|string
	 */
	public function get($name, $raw = false)
	{
		// Build the request path.
		$path = '/gitignore/templates/' . $name;

		$headers = array();

		if ($raw)
		{
			$headers['Accept'] = 'application/vnd.github.raw+json';
		}

		$response = $this->client->get($this->fetchUrl($path), $headers);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error   = json_decode($response->body);
			$message = (isset($error->message)) ? $error->message : 'Invalid response';

			throw new DomainException($message, $response->code);
		}

		return ($raw) ? $response->body : json_decode($response->body);
	}
}
joomla/github/package/pulls.php000064400000035506152177723700012576 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Pull Requests class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/pulls
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @property-read  JGithubPackagePullsComments  $comments  GitHub API object for comments.
 */
class JGithubPackagePulls extends JGithubPackage
{
	protected $name = 'Pulls';

	protected $packages = array(
		'comments',
	);

	/**
	 * Method to create a pull request.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $title  The title of the new pull request.
	 * @param   string  $base   The branch (or git ref) you want your changes pulled into. This
	 *                          should be an existing branch on the current repository. You cannot
	 *                          submit a pull request to one repo that requests a merge to a base
	 *                          of another repo.
	 * @param   string  $head   The branch (or git ref) where your changes are implemented.
	 * @param   string  $body   The body text for the new pull request.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($user, $repo, $title, $base, $head, $body = '')
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls';

		// Build the request data.
		$data = json_encode(
			array(
				'title' => $title,
				'base' => $base,
				'head' => $head,
				'body' => $body,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to create a pull request from an existing issue.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $issueId  The issue number for which to attach the new pull request.
	 * @param   string   $base     The branch (or git ref) you want your changes pulled into. This
	 *                             should be an existing branch on the current repository. You cannot
	 *                             submit a pull request to one repo that requests a merge to a base
	 *                             of another repo.
	 * @param   string   $head     The branch (or git ref) where your changes are implemented.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function createFromIssue($user, $repo, $issueId, $base, $head)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls';

		// Build the request data.
		$data = json_encode(
			array(
				'issue' => (int) $issueId,
				'base' => $base,
				'head' => $head,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 * @param   string   $title   The optional new title for the pull request.
	 * @param   string   $body    The optional new body text for the pull request.
	 * @param   string   $state   The optional new state for the pull request. [open, closed]
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($user, $repo, $pullId, $title = null, $body = null, $state = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId;

		// Craete the data object.
		$data = new stdClass;

		// If a title is set add it to the data object.
		if (isset($title))
		{
			$data->title = $title;
		}

		// If a body is set add it to the data object.
		if (isset($body))
		{
			$data->body = $body;
		}

		// If a state is set add it to the data object.
		if (isset($state))
		{
			$data->state = $state;
		}

		// Encode the request data.
		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a single pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function get($user, $repo, $pullId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of commits for a pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getCommits($user, $repo, $pullId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/commits';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of files for a pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getFiles($user, $repo, $pullId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/files';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list pull requests.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   string   $state  The optional state to filter requests by. [open, closed]
	 * @param   integer  $page   The page number from which to get items.
	 * @param   integer  $limit  The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($user, $repo, $state = 'open', $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls';

		// If a state exists append it as an option.
		if ($state != 'open')
		{
			$path .= '?state=' . $state;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to check if a pull request has been merged.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.  The pull request number.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  boolean  True if the pull request has been merged.
	 */
	public function isMerged($user, $repo, $pullId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/merge';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code == 204)
		{
			return true;
		}
		elseif ($response->code == 404)
		{
			return false;
		}
		else
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}

	/**
	 * Method to merge a pull request.
	 *
	 * @param   string   $user     The name of the owner of the GitHub repository.
	 * @param   string   $repo     The name of the GitHub repository.
	 * @param   integer  $pullId   The pull request number.
	 * @param   string   $message  The message that will be used for the merge commit.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function merge($user, $repo, $pullId, $message = '')
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/merge';

		// Build the request data.
		$data = json_encode(
			array(
				'commit_message' => $message,
			)
		);

		// Send the request.
		$response = $this->client->put($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/*
	 * Legacy methods
	 */

	/**
	 * Method to create a comment on a pull request.
	 *
	 * @param   string   $user      The name of the owner of the GitHub repository.
	 * @param   string   $repo      The name of the GitHub repository.
	 * @param   integer  $pullId    The pull request number.
	 * @param   string   $body      The comment body text.
	 * @param   string   $commitId  The SHA1 hash of the commit to comment on.
	 * @param   string   $filePath  The Relative path of the file to comment on.
	 * @param   string   $position  The line index in the diff to comment on.
	 *
	 * @deprecated  use pulls->comments->create()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function createComment($user, $repo, $pullId, $body, $commitId, $filePath, $position)
	{
		return $this->comments->create($user, $repo, $pullId, $body, $commitId, $filePath, $position);
	}

	/**
	 * Method to create a comment in reply to another comment.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $pullId     The pull request number.
	 * @param   string   $body       The comment body text.
	 * @param   integer  $inReplyTo  The id of the comment to reply to.
	 *
	 * @deprecated  use pulls->comments->createReply()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function createCommentReply($user, $repo, $pullId, $body, $inReplyTo)
	{
		return $this->comments->createReply($user, $repo, $pullId, $body, $inReplyTo);
	}

	/**
	 * Method to delete a comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @deprecated  use pulls->comments->delete()
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function deleteComment($user, $repo, $commentId)
	{
		$this->comments->delete($user, $repo, $commentId);
	}

	/**
	 * Method to update a comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @deprecated  use pulls->comments->edit()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function editComment($user, $repo, $commentId, $body)
	{
		return $this->comments->edit($user, $repo, $commentId, $body);
	}

	/**
	 * Method to get a specific comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The comment id to get.
	 *
	 * @deprecated  use pulls->comments->get()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function getComment($user, $repo, $commentId)
	{
		return $this->comments->get($user, $repo, $commentId);
	}

	/**
	 * Method to get the list of comments on a pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @deprecated  use pulls->comments->getList()
	 *
	 * @return  array
	 *
	 * @since   1.7.3
	 */
	public function getComments($user, $repo, $pullId, $page = 0, $limit = 0)
	{
		return $this->comments->getList($user, $repo, $pullId, $page, $limit);
	}
}
joomla/github/package/users/keys.php000064400000005722152177723700013550 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/users/keys
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageUsersKeys extends JGithubPackage
{
	/**
	 * List public keys for a user.
	 *
	 * Lists the verified public keys for a user. This is accessible by anyone.
	 *
	 * @param   string  $user  The name of the user.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListUser($user)
	{
		// Build the request path.
		$path = '/users/' . $user . '/keys';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List your public keys.
	 *
	 * Lists the current user’s keys.
	 * Management of public keys via the API requires that you are authenticated
	 * through basic auth, or OAuth with the ‘user’ scope.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList()
	{
		// Build the request path.
		$path = '/users/keys';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a single public key.
	 *
	 * @param   integer  $id  The id of the key.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function get($id)
	{
		// Build the request path.
		$path = '/users/keys/' . $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Create a public key
	 *
	 * @param   string  $title  The title of the key.
	 * @param   string  $key    The key.
	 *
	 * @since    3.3 (CMS)
	 *
	 * @return object
	 */
	public function create($title, $key)
	{
		// Build the request path.
		$path = '/users/keys';

		$data = array(
			'title' => $title,
			'key'   => $key,
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($data)),
			201
		);
	}

	/**
	 * Update a public key.
	 *
	 * @param   integer  $id     The id of the key.
	 * @param   string   $title  The title of the key.
	 * @param   string   $key    The key.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function edit($id, $title, $key)
	{
		// Build the request path.
		$path = '/users/keys/' . $id;

		$data = array(
			'title' => $title,
			'key'   => $key,
		);

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * Delete a public key.
	 *
	 * @param   integer  $id  The id of the key.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function delete($id)
	{
		// Build the request path.
		$path = '/users/keys/' . (int) $id;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/users/emails.php000064400000004057152177723700014047 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * Management of email addresses via the API requires that you are authenticated
 * through basic auth or OAuth with the user scope.
 *
 * @documentation https://developer.github.com/v3/users/emails
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageUsersEmails extends JGithubPackage
{
	/**
	 * List email addresses for a user.
	 *
	 * Future response:
	 * In the final version of the API, this method will return an array of hashes
	 * with extended information for each email address indicating if the address
	 * has been verified and if it’s the user’s primary email address for GitHub.
	 *
	 * Until API v3 is finalized, use the application/vnd.github.v3 media type
	 * to get this response format.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList()
	{
		// Build the request path.
		$path = '/user/emails';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Add email address(es).
	 *
	 * @param   string|array  $email  The email address(es).
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function add($email)
	{
		// Build the request path.
		$path = '/user/emails';

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode($email)),
			201
		);
	}

	/**
	 * Delete email address(es).
	 *
	 * @param   string|array  $email  The email address(es).
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function delete($email)
	{
		// Build the request path.
		$path = '/user/emails';

		$this->client->setOption('body', json_encode($email));

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/users/followers.php000064400000005766152177723700014621 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/users/followers
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageUsersFollowers extends JGithubPackage
{
	/**
	 * List followers of a user.
	 *
	 * @param   string  $user  The name of the user. If not set the current authenticated user will be used.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList($user = '')
	{
		// Build the request path.
		$path = ($user)
			? '/users/' . $user . '/followers'
			: '/user/followers';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List users followed by another user.
	 *
	 * @param   string  $user  The name of the user. If not set the current authenticated user will be used.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListFollowedBy($user = '')
	{
		// Build the request path.
		$path = ($user)
			? '/users/' . $user . '/following'
			: '/user/following';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Check if you are following a user.
	 *
	 * @param   string  $user  The name of the user.
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return boolean
	 */
	public function check($user)
	{
		// Build the request path.
		$path = '/user/following/' . $user;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case '204' :
				// You are following this user
				return true;
				break;

			case '404' :
				// You are not following this user
				return false;
				break;

			default :
				throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
				break;
		}
	}

	/**
	 * Follow a user.
	 *
	 * Following a user requires the user to be logged in and authenticated with
	 * basic auth or OAuth with the user:follow scope.
	 *
	 * @param   string  $user  The name of the user.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function follow($user)
	{
		// Build the request path.
		$path = '/user/following/' . $user;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Unfollow a user.
	 *
	 * Unfollowing a user requires the user to be logged in and authenticated with
	 * basic auth or OAuth with the user:follow scope.
	 *
	 * @param   string  $user  The name of the user.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function unfollow($user)
	{
		// Build the request path.
		$path = '/user/following/' . $user;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/pulls/comments.php000064400000012615152177723700014417 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Pulls Comments class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/pulls/comments/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackagePullsComments extends JGithubPackage
{
	/**
	 * Method to create a comment on a pull request.
	 *
	 * @param   string   $user      The name of the owner of the GitHub repository.
	 * @param   string   $repo      The name of the GitHub repository.
	 * @param   integer  $pullId    The pull request number.
	 * @param   string   $body      The comment body text.
	 * @param   string   $commitId  The SHA1 hash of the commit to comment on.
	 * @param   string   $filePath  The Relative path of the file to comment on.
	 * @param   string   $position  The line index in the diff to comment on.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function create($user, $repo, $pullId, $body, $commitId, $filePath, $position)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/comments';

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
				'commit_id' => $commitId,
				'path' => $filePath,
				'position' => $position,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Method to create a comment in reply to another comment.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $pullId     The pull request number.
	 * @param   string   $body       The comment body text.
	 * @param   integer  $inReplyTo  The id of the comment to reply to.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function createReply($user, $repo, $pullId, $body, $inReplyTo)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/comments';

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
				'in_reply_to' => (int) $inReplyTo,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Method to delete a comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to delete.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  void
	 */
	public function delete($user, $repo, $commentId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/comments/' . (int) $commentId;

		// Send the request.
		$this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Method to update a comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The id of the comment to update.
	 * @param   string   $body       The new body text for the comment.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function edit($user, $repo, $commentId, $body)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/comments/' . (int) $commentId;

		// Build the request data.
		$data = json_encode(
			array(
				'body' => $body,
			)
		);

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to get a specific comment on a pull request.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   integer  $commentId  The comment id to get.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  object
	 */
	public function get($user, $repo, $commentId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/comments/' . (int) $commentId;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to get the list of comments on a pull request.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   integer  $pullId  The pull request number.
	 * @param   integer  $page    The page number from which to get items.
	 * @param   integer  $limit   The number of items on a page.
	 *
	 * @throws DomainException
	 * @since   1.7.3
	 *
	 * @return  array
	 */
	public function getList($user, $repo, $pullId, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/pulls/' . (int) $pullId . '/comments';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path, $page, $limit))
		);
	}
}
joomla/github/package/orgs.php000064400000005120152177723700012376 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity class for the Joomla Platform.
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 *
 * @documentation  https://developer.github.com/v3/orgs/
 *
 * @property-read  JGithubPackageOrgsMembers  $members  GitHub API object for members.
 * @property-read  JGithubPackageOrgsTeams    $teams    GitHub API object for teams.
 */
class JGithubPackageOrgs extends JGithubPackage
{
	protected $name = 'Orgs';

	protected $packages = array('members', 'teams');

	/**
	 * List User Organizations.
	 *
	 * If a user name is given, public and private organizations for the authenticated user will be listed.
	 *
	 * @param   string  $user  The user name.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function getList($user = '')
	{
		// Build the request path.
		$path = ($user)
			? '/users/' . $user . '/orgs'
			: '/user/orgs';

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get an Organization.
	 *
	 * @param   string  $org  The organization name.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function get($org)
	{
		// Build the request path.
		$path = '/orgs/' . $org;

		// Send the request.
		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Edit an Organization.
	 *
	 * @param   string  $org           The organization name.
	 * @param   string  $billingEmail  Billing email address. This address is not publicized.
	 * @param   string  $company       The company name.
	 * @param   string  $email         The email address.
	 * @param   string  $location      The location name.
	 * @param   string  $name          The name.
	 *
	 * @since   3.3 (CMS)
	 *
	 * @return  object
	 */
	public function edit($org, $billingEmail = '', $company = '', $email = '', $location = '', $name = '')
	{
		// Build the request path.
		$path = '/orgs/' . $org;

		$args = array('billing_email', 'company', 'email', 'location', 'name');

		$data = array();

		$fArgs = func_get_args();

		foreach ($args as $i => $arg)
		{
			if (array_key_exists($i + 1, $fArgs) && $fArgs[$i + 1])
			{
				$data[$arg] = $fArgs[$i + 1];
			}
		}

		// Send the request.
		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}
}
joomla/github/package/activity/events.php000064400000011024152177723700014564 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity Events class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/activity/events/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageActivityEvents extends JGithubPackage
{
	/**
	 * List public events.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getPublic()
	{
		// Build the request path.
		$path = '/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List repository events.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since   3.1.4
	 *
	 * @return  object
	 */
	public function getRepository($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List issue events for a repository.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getIssue($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/issues/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List public events for a network of repositories.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getNetwork($owner, $repo)
	{
		// Build the request path.
		$path = '/networks/' . $owner . '/' . $repo . '/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List public events for an organization.
	 *
	 * @param   string  $org  Organisation.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getOrg($org)
	{
		// Build the request path.
		$path = '/orgs/' . $org . '/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List events that a user has received.
	 *
	 * These are events that you’ve received by watching repos and following users.
	 * If you are authenticated as the given user, you will see private events.
	 * Otherwise, you’ll only see public events.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getUser($user)
	{
		// Build the request path.
		$path = '/users/' . $user . '/received_events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List public events that a user has received.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getUserPublic($user)
	{
		// Build the request path.
		$path = '/users/' . $user . '/received_events/public';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List events performed by a user.
	 *
	 * If you are authenticated as the given user, you will see your private events.
	 * Otherwise, you’ll only see public events.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getByUser($user)
	{
		// Build the request path.
		$path = '/users/' . $user . '/events';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List public events performed by a user.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getByUserPublic($user)
	{
		// Build the request path.
		$path = '/users/' . $user . '/events/public';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List events for an organization.
	 *
	 * This is the user’s organization dashboard.
	 * You must be authenticated as the user to view this.
	 *
	 * @param   string  $user  User name.
	 * @param   string  $org   Organisation.
	 *
	 * @since   3.1.4
	 * @return  object
	 */
	public function getUserOrg($user, $org)
	{
		// Build the request path.
		$path = '/users/' . $user . '/events/orgs/' . $org;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}
}
joomla/github/package/activity/notifications.php000064400000016653152177723700016146 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity Events class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/activity/notifications/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageActivityNotifications extends JGithubPackage
{
	/**
	 * List your notifications.
	 *
	 * List all notifications for the current user, grouped by repository.
	 *
	 * @param   boolean  $all            True to show notifications marked as read.
	 * @param   boolean  $participating  True to show only notifications in which the user is directly participating or
	 *                                   mentioned.
	 * @param   JDate    $since          filters out any notifications updated before the given time. The time should be passed in
	 *                                   as UTC in the ISO 8601 format.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getList($all = true, $participating = true, JDate $since = null)
	{
		// Build the request path.
		$path = '/notifications?';

		$path .= ($all) ? '&all=1' : '';
		$path .= ($participating) ? '&participating=1' : '';
		$path .= ($since) ? '&since=' . $since->toISO8601() : '';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List your notifications in a repository.
	 *
	 * List all notifications for the current user.
	 *
	 * @param   string   $owner          Repository owner.
	 * @param   string   $repo           Repository name.
	 * @param   boolean  $all            True to show notifications marked as read.
	 * @param   boolean  $participating  True to show only notifications in which the user is directly participating or
	 *                                   mentioned.
	 * @param   JDate    $since          filters out any notifications updated before the given time. The time should be passed in
	 *                                   as UTC in the ISO 8601 format.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getListRepository($owner, $repo, $all = true, $participating = true, JDate $since = null)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/notifications?';

		$path .= ($all) ? '&all=1' : '';
		$path .= ($participating) ? '&participating=1' : '';
		$path .= ($since) ? '&since=' . $since->toISO8601() : '';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Mark as read.
	 *
	 * Marking a notification as “read” removes it from the default view on GitHub.com.
	 *
	 * @param   boolean  $unread        Changes the unread status of the threads.
	 * @param   boolean  $read          Inverse of “unread”.
	 * @param   JDate    $last_read_at  Describes the last point that notifications were checked.
	 *                                  Anything updated since this time will not be updated. Default: Now. Expected in ISO 8601 format.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function markRead($unread = true, $read = true, JDate $last_read_at = null)
	{
		// Build the request path.
		$path = '/notifications';

		$data = array(
			'unread' => $unread,
			'read'   => $read,
		);

		if ($last_read_at)
		{
			$data['last_read_at'] = $last_read_at->toISO8601();
		}

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), json_encode($data)),
			205
		);
	}

	/**
	 * Mark notifications as read in a repository.
	 *
	 * Marking all notifications in a repository as “read” removes them from the default view on GitHub.com.
	 *
	 * @param   string   $owner         Repository owner.
	 * @param   string   $repo          Repository name.
	 * @param   boolean  $unread        Changes the unread status of the threads.
	 * @param   boolean  $read          Inverse of “unread”.
	 * @param   JDate    $last_read_at  Describes the last point that notifications were checked.
	 *                                  Anything updated since this time will not be updated. Default: Now. Expected in ISO 8601 format.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function markReadRepository($owner, $repo, $unread, $read, JDate $last_read_at = null)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/notifications';

		$data = array(
			'unread' => $unread,
			'read'   => $read,
		);

		if ($last_read_at)
		{
			$data['last_read_at'] = $last_read_at->toISO8601();
		}

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), json_encode($data)),
			205
		);
	}

	/**
	 * View a single thread.
	 *
	 * @param   integer  $id  The thread id.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function viewThread($id)
	{
		// Build the request path.
		$path = '/notifications/threads/' . $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Mark a thread as read.
	 *
	 * @param   integer  $id      The thread id.
	 * @param   boolean  $unread  Changes the unread status of the threads.
	 * @param   boolean  $read    Inverse of “unread”.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function markReadThread($id, $unread = true, $read = true)
	{
		// Build the request path.
		$path = '/notifications/threads/' . $id;

		$data = array(
			'unread' => $unread,
			'read'   => $read,
		);

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), json_encode($data)),
			205
		);
	}

	/**
	 * Get a Thread Subscription.
	 *
	 * This checks to see if the current user is subscribed to a thread.
	 * You can also get a Repository subscription.
	 *
	 * @param   integer  $id  The thread id.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getThreadSubscription($id)
	{
		// Build the request path.
		$path = '/notifications/threads/' . $id . '/subscription';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Set a Thread Subscription.
	 *
	 * This lets you subscribe to a thread, or ignore it. Subscribing to a thread is unnecessary
	 * if the user is already subscribed to the repository. Ignoring a thread will mute all
	 * future notifications (until you comment or get @mentioned).
	 *
	 * @param   integer  $id          The thread id.
	 * @param   boolean  $subscribed  Determines if notifications should be received from this thread.
	 * @param   boolean  $ignored     Determines if all notifications should be blocked from this thread.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function setThreadSubscription($id, $subscribed, $ignored)
	{
		// Build the request path.
		$path = '/notifications/threads/' . $id . '/subscription';

		$data = array(
			'subscribed' => $subscribed,
			'ignored'    => $ignored,
		);

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * Delete a Thread Subscription.
	 *
	 * @param   integer  $id  The thread id.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function deleteThreadSubscription($id)
	{
		// Build the request path.
		$path = '/notifications/threads/' . $id . '/subscription';

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/activity/watching.php000064400000011247152177723700015073 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity Watching Events class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/activity/watching/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageActivityWatching extends JGithubPackage
{
	/**
	 * List watchers
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return mixed
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/subscribers';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List repositories being watched.
	 *
	 * List repositories being watched by a user.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return mixed
	 */
	public function getRepositories($user = '')
	{
		// Build the request path.
		$path = ($user)
			? '/users/' . $user . '/subscriptions'
			: '/user/subscriptions';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Get a Repository Subscription.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return mixed
	 */
	public function getSubscription($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/subscription';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Set a Repository Subscription.
	 *
	 * @param   string   $owner       Repository owner.
	 * @param   string   $repo        Repository name.
	 * @param   boolean  $subscribed  Determines if notifications should be received from this thread.
	 * @param   boolean  $ignored     Determines if all notifications should be blocked from this thread.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function setSubscription($owner, $repo, $subscribed, $ignored)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/subscription';

		$data = array(
			'subscribed' => $subscribed,
			'ignored'    => $ignored,
		);

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), json_encode($data))
		);
	}

	/**
	 * Delete a Repository Subscription.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function deleteSubscription($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/subscription';

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Check if you are watching a repository (LEGACY).
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function check($owner, $repo)
	{
		// Build the request path.
		$path = '/user/subscriptions/' . $owner . '/' . $repo;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case '204' :
				// This repository is watched by you.
				return true;
				break;

			case '404' :
				// This repository is not watched by you.
				return false;
				break;
		}

		throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
	}

	/**
	 * Watch a repository (LEGACY).
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function watch($owner, $repo)
	{
		// Build the request path.
		$path = '/user/subscriptions/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Stop watching a repository (LEGACY).
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function unwatch($owner, $repo)
	{
		// Build the request path.
		$path = '/user/subscriptions/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/package/activity/starring.php000064400000006010152177723700015110 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Activity Events class for the Joomla Platform.
 *
 * @documentation https://developer.github.com/v3/activity/starring/
 *
 * @since       3.3 (CMS)
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubPackageActivityStarring extends JGithubPackage
{
	/**
	 * List Stargazers.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return mixed
	 */
	public function getList($owner, $repo)
	{
		// Build the request path.
		$path = '/repos/' . $owner . '/' . $repo . '/stargazers';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * List repositories being starred.
	 *
	 * List repositories being starred by a user.
	 *
	 * @param   string  $user  User name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function getRepositories($user = '')
	{
		// Build the request path.
		$path = ($user)
			? '/users' . $user . '/starred'
			: '/user/starred';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Check if you are starring a repository.
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @throws UnexpectedValueException
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function check($owner, $repo)
	{
		// Build the request path.
		$path = '/user/starred/' . $owner . '/' . $repo;

		$response = $this->client->get($this->fetchUrl($path));

		switch ($response->code)
		{
			case '204' :
				// This repository is watched by you.
				return true;
				break;

			case '404' :
				// This repository is not watched by you.
				return false;
				break;
		}

		throw new UnexpectedValueException('Unexpected response code: ' . $response->code);
	}

	/**
	 * Star a repository.
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function star($owner, $repo)
	{
		// Build the request path.
		$path = '/user/starred/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->put($this->fetchUrl($path), ''),
			204
		);
	}

	/**
	 * Unstar a repository.
	 *
	 * Requires for the user to be authenticated.
	 *
	 * @param   string  $owner  Repository owner.
	 * @param   string  $repo   Repository name.
	 *
	 * @since 3.3 (CMS)
	 *
	 * @return object
	 */
	public function unstar($owner, $repo)
	{
		// Build the request path.
		$path = '/user/starred/' . $owner . '/' . $repo;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}
}
joomla/github/forks.php000064400000004716152177723700011167 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Forks class for the Joomla Platform.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubForks extends JGithubObject
{
	/**
	 * Method to fork a repository.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $org   The organization to fork the repo into. By default it is forked to the current user.
	 *
	 * @deprecated  use repositories->forks->create()
	 *
	 * @return  object
	 *
	 * @since   2.5.0
	 * @throws  DomainException
	 */
	public function create($user, $repo, $org = '')
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/forks';

		if (strlen($org) > 0)
		{
			$data = json_encode(
				array('org' => $org)
			);
		}
		else
		{
			$data = json_encode(array());
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 202)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list forks for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->forks->getList()
	 *
	 * @return  array
	 *
	 * @since   2.5.0
	 * @throws  DomainException
	 */
	public function getList($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/forks';

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/hooks.php000064400000015115152177723700011161 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Hooks class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubHooks extends JGithubObject
{
	/**
	 * Array containing the allowed hook events
	 *
	 * @var    array
	 * @since  3.1.4
	 */
	protected $events = array(
		'push',
		'issues',
		'issue_comment',
		'commit_comment',
		'pull_request',
		'gollum',
		'watch',
		'download',
		'fork',
		'fork_apply',
		'member',
		'public',
		'status',
	);

	/**
	 * Method to create a hook on a repository.
	 *
	 * @param   string   $user    The name of the owner of the GitHub repository.
	 * @param   string   $repo    The name of the GitHub repository.
	 * @param   string   $name    The name of the service being called.
	 * @param   array    $config  Array containing the config for the service.
	 * @param   array    $events  The events the hook will be triggered for.
	 * @param   boolean  $active  Flag to determine if the hook is active
	 *
	 * @deprecated  use repositories->hooks->create()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @throws  RuntimeException
	 */
	public function create($user, $repo, $name, array $config, array $events = array('push'), $active = true)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks';

		// Check to ensure all events are in the allowed list
		foreach ($events as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your events array contains an unauthorized event.');
			}
		}

		$data = json_encode(
			array('name' => $name, 'config' => $config, 'events' => $events, 'active' => $active)
		);

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), $data),
			201
		);
	}

	/**
	 * Method to delete a hook
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to delete.
	 *
	 * @deprecated  use repositories->hooks->delete()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function delete($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		return $this->processResponse(
			$this->client->delete($this->fetchUrl($path)),
			204
		);
	}

	/**
	 * Method to edit a hook.
	 *
	 * @param   string   $user          The name of the owner of the GitHub repository.
	 * @param   string   $repo          The name of the GitHub repository.
	 * @param   integer  $id            ID of the hook to edit.
	 * @param   string   $name          The name of the service being called.
	 * @param   array    $config        Array containing the config for the service.
	 * @param   array    $events        The events the hook will be triggered for.  This resets the currently set list
	 * @param   array    $addEvents     Events to add to the hook.
	 * @param   array    $removeEvents  Events to remove from the hook.
	 * @param   boolean  $active        Flag to determine if the hook is active
	 *
	 * @deprecated  use repositories->hooks->edit()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 * @throws  RuntimeException
	 */
	public function edit($user, $repo, $id, $name, array $config, array $events = array('push'), array $addEvents = array(),
		array $removeEvents = array(), $active = true)
	{
		// Check to ensure all events are in the allowed list
		foreach ($events as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your events array contains an unauthorized event.');
			}
		}

		foreach ($addEvents as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your active_events array contains an unauthorized event.');
			}
		}

		foreach ($removeEvents as $event)
		{
			if (!in_array($event, $this->events))
			{
				throw new RuntimeException('Your remove_events array contains an unauthorized event.');
			}
		}

		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		$data = json_encode(
			array(
				'name' => $name,
				'config' => $config,
				'events' => $events,
				'add_events' => $addEvents,
				'remove_events' => $removeEvents,
				'active' => $active,
			)
		);

		return $this->processResponse(
			$this->client->patch($this->fetchUrl($path), $data)
		);
	}

	/**
	 * Method to get details about a single hook for the repository.
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to retrieve
	 *
	 * @deprecated  use repositories->hooks->get()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function get($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id;

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to list hooks for a repository.
	 *
	 * @param   string   $user   The name of the owner of the GitHub repository.
	 * @param   string   $repo   The name of the GitHub repository.
	 * @param   integer  $page   Page to request
	 * @param   integer  $limit  Number of results to return per page
	 *
	 * @deprecated  use repositories->hooks->getList()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function getList($user, $repo, $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks';

		return $this->processResponse(
			$this->client->get($this->fetchUrl($path))
		);
	}

	/**
	 * Method to test a hook against the latest repository commit
	 *
	 * @param   string   $user  The name of the owner of the GitHub repository.
	 * @param   string   $repo  The name of the GitHub repository.
	 * @param   integer  $id    ID of the hook to delete
	 *
	 * @deprecated  use repositories->hooks->test()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 * @throws  DomainException
	 */
	public function test($user, $repo, $id)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/hooks/' . $id . '/test';

		return $this->processResponse(
			$this->client->post($this->fetchUrl($path), json_encode('')),
			204
		);
	}
}
joomla/github/refs.php000064400000011064152177723700010774 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API References class for the Joomla Platform.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubRefs extends JGithubObject
{
	/**
	 * Method to create an issue.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $ref   The name of the fully qualified reference.
	 * @param   string  $sha   The SHA1 value to set this reference to.
	 *
	 * @deprecated  use data->refs->create()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function create($user, $repo, $ref, $sha)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs';

		// Build the request data.
		$data = json_encode(
			array(
				'ref' => $ref,
				'sha' => $sha,
			)
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a reference.
	 *
	 * @param   string  $user   The name of the owner of the GitHub repository.
	 * @param   string  $repo   The name of the GitHub repository.
	 * @param   string  $ref    The reference to update.
	 * @param   string  $sha    The SHA1 value to set the reference to.
	 * @param   string  $force  Whether the update should be forced. Default to false.
	 *
	 * @deprecated  use data->refs->edit()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function edit($user, $repo, $ref, $sha, $force = false)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs/' . $ref;

		// Craete the data object.
		$data = new stdClass;

		// If a title is set add it to the data object.
		if ($force)
		{
			$data->force = true;
		}

		$data->sha = $sha;

		// Encode the request data.
		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a reference.
	 *
	 * @param   string  $user  The name of the owner of the GitHub repository.
	 * @param   string  $repo  The name of the GitHub repository.
	 * @param   string  $ref   The reference to get.
	 *
	 * @deprecated  use data->refs->get()
	 *
	 * @return  object
	 *
	 * @since   1.7.3
	 */
	public function get($user, $repo, $ref)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs/' . $ref;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to list references for a repository.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $namespace  Optional sub-namespace to limit the returned references.
	 * @param   integer  $page       Page to request
	 * @param   integer  $limit      Number of results to return per page
	 *
	 * @deprecated  use data->refs->getList()
	 *
	 * @return  array
	 *
	 * @since   1.7.3
	 */
	public function getList($user, $repo, $namespace = '', $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/git/refs' . $namespace;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}
}
joomla/github/milestones.php000064400000015010152177723700012212 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  GitHub
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * GitHub API Milestones class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/github` package via Composer instead
 */
class JGithubMilestones extends JGithubObject
{
	/**
	 * Method to get the list of milestones for a repo.
	 *
	 * @param   string   $user       The name of the owner of the GitHub repository.
	 * @param   string   $repo       The name of the GitHub repository.
	 * @param   string   $state      The milestone state to retrieved.  Open (default) or closed.
	 * @param   string   $sort       Sort can be due_date (default) or completeness.
	 * @param   string   $direction  Direction is asc or desc (default).
	 * @param   integer  $page       The page number from which to get items.
	 * @param   integer  $limit      The number of items on a page.
	 *
	 * @deprecated  use issues->milestones->getList()
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function getList($user, $repo, $state = 'open', $sort = 'due_date', $direction = 'desc', $page = 0, $limit = 0)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones?';

		$path .= 'state=' . $state;
		$path .= '&sort=' . $sort;
		$path .= '&direction=' . $direction;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $page, $limit));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to get a specific milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The milestone id to get.
	 *
	 * @deprecated  use issues->milestones->get()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function get($user, $repo, $milestoneId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to create a milestone for a repository.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $title        The title of the milestone.
	 * @param   string   $state        Can be open (default) or closed.
	 * @param   string   $description  Optional description for milestone.
	 * @param   string   $due_on       Optional ISO 8601 time.
	 *
	 * @deprecated  use issues->milestones->create()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function create($user, $repo, $title, $state = null, $description = null, $due_on = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones';

		// Build the request data.
		$data = array(
			'title' => $title,
		);

		if (!is_null($state))
		{
			$data['state'] = $state;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		if (!is_null($due_on))
		{
			$data['due_on'] = $due_on;
		}

		$data = json_encode($data);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 201)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to update a milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The id of the comment to update.
	 * @param   integer  $title        Optional title of the milestone.
	 * @param   string   $state        Can be open (default) or closed.
	 * @param   string   $description  Optional description for milestone.
	 * @param   string   $due_on       Optional ISO 8601 time.
	 *
	 * @deprecated  use issues->milestones->edit()
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function edit($user, $repo, $milestoneId, $title = null, $state = null, $description = null, $due_on = null)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Build the request data.
		$data = array();

		if (!is_null($title))
		{
			$data['title'] = $title;
		}

		if (!is_null($state))
		{
			$data['state'] = $state;
		}

		if (!is_null($description))
		{
			$data['description'] = $description;
		}

		if (!is_null($due_on))
		{
			$data['due_on'] = $due_on;
		}

		$data = json_encode($data);

		// Send the request.
		$response = $this->client->patch($this->fetchUrl($path), $data);

		// Validate the response code.
		if ($response->code != 200)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}

		return json_decode($response->body);
	}

	/**
	 * Method to delete a milestone.
	 *
	 * @param   string   $user         The name of the owner of the GitHub repository.
	 * @param   string   $repo         The name of the GitHub repository.
	 * @param   integer  $milestoneId  The id of the milestone to delete.
	 *
	 * @deprecated  use issues->milestones->delete()
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 */
	public function delete($user, $repo, $milestoneId)
	{
		// Build the request path.
		$path = '/repos/' . $user . '/' . $repo . '/milestones/' . (int) $milestoneId;

		// Send the request.
		$response = $this->client->delete($this->fetchUrl($path));

		// Validate the response code.
		if ($response->code != 204)
		{
			// Decode the error response and throw an exception.
			$error = json_decode($response->body);
			throw new DomainException($error->message, $response->code);
		}
	}
}
joomla/linkedin/jobs.php000064400000021271152177723700011306 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API Jobs class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinJobs extends JLinkedinObject
{
	/**
	 * Method to retrieve detailed information about a job.
	 *
	 * @param   integer  $id      The unique identifier for a job.
	 * @param   string   $fields  Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getJob($id, $fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/jobs/' . $id;

		// Set request parameters.
		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of bookmarked jobs for the current member.
	 *
	 * @param   string  $fields  Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getBookmarked($fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/job-bookmarks';

		// Set request parameters.
		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to bookmark a job to the current user's account.
	 *
	 * @param   integer  $id  The unique identifier for a job.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function bookmark($id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/job-bookmarks';

		// Build xml.
		$xml = '<job-bookmark><job><id>' . $id . '</id></job></job-bookmark>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to delete a bookmark.
	 *
	 * @param   integer  $id  The unique identifier for a job.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function deleteBookmark($id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/people/~/job-bookmarks/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}

	/**
	 * Method to retrieve job suggestions for the current user.
	 *
	 * @param   string   $fields  Request fields beyond the default ones.
	 * @param   integer  $start   Starting location within the result set for paginated returns.
	 * @param   integer  $count   The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getSuggested($fields = null, $start = 0, $count = 0)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/suggestions/job-suggestions';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to search across LinkedIn's job postings.
	 *
	 * @param   string   $fields        Request fields beyond the default ones.
	 * @param   string   $keywords      Members who have all the keywords anywhere in their profile.
	 * @param   string   $company_name  Jobs with a matching company name.
	 * @param   string   $job_title     Matches jobs with the same job title.
	 * @param   string   $country_code  Matches members with a location in a specific country. Values are defined in by ISO 3166 standard.
	 * 									Country codes must be in all lower case.
	 * @param   integer  $postal_code   Matches members centered around a Postal Code. Must be combined with the country-code parameter.
	 * 									Not supported for all countries.
	 * @param   integer  $distance      Matches members within a distance from a central point. This is measured in miles.
	 * @param   string   $facets        Facet buckets to return, e.g. location.
	 * @param   array    $facet         Array of facet values to search over. Contains values for company, date-posted, location, job-function,
	 * 									industry, and salary, in exactly this order, null must be specified for an element if no value.
	 * @param   integer  $start         Starting location within the result set for paginated returns.
	 * @param   integer  $count         The number of results returned.
	 * @param   string   $sort          Controls the search result order. There are four options: R (relationship), DA (date-posted-asc),
	 * 									DD (date-posted-desc).
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function search($fields = null, $keywords = null, $company_name = null, $job_title = null, $country_code = null, $postal_code = null,
		$distance = null, $facets = null, $facet = null, $start = 0, $count = 0, $sort = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/job-search';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if keywords is specified.
		if ($keywords)
		{
			$data['keywords'] = $keywords;
		}

		// Check if company-name is specified.
		if ($company_name)
		{
			$data['company-name'] = $company_name;
		}

		// Check if job-title is specified.
		if ($job_title)
		{
			$data['job-title'] = $job_title;
		}

		// Check if country_code is specified.
		if ($country_code)
		{
			$data['country-code'] = $country_code;
		}

		// Check if postal_code is specified.
		if ($postal_code)
		{
			$data['postal-code'] = $postal_code;
		}

		// Check if distance is specified.
		if ($distance)
		{
			$data['distance'] = $distance;
		}

		// Check if facets is specified.
		if ($facets)
		{
			$data['facets'] = $facets;
		}

		// Check if facet is specified.
		if ($facet)
		{
			$data['facet'] = array();

			for ($i = 0, $iMax = count($facet); $i < $iMax; $i++)
			{
				if ($facet[$i])
				{
					if ($i == 0)
					{
						$data['facet'][] = 'company,' . $this->oauth->safeEncode($facet[$i]);
					}

					if ($i == 1)
					{
						$data['facet'][] = 'date-posted,' . $facet[$i];
					}

					if ($i == 2)
					{
						$data['facet'][] = 'location,' . $facet[$i];
					}

					if ($i == 3)
					{
						$data['facet'][] = 'job-function,' . $this->oauth->safeEncode($facet[$i]);
					}

					if ($i == 4)
					{
						$data['facet'][] = 'industry,' . $facet[$i];
					}

					if ($i == 5)
					{
						$data['facet'][] = 'salary,' . $facet[$i];
					}
				}
			}
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if sort is specified.
		if ($sort)
		{
			$data['sort'] = $sort;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}
}
joomla/linkedin/companies.php000064400000025612152177723700012332 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API Companies class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinCompanies extends JLinkedinObject
{
	/**
	 * Method to retrieve companies using a company ID, a universal name, or an email domain.
	 *
	 * @param   integer  $id      The unique internal numeric company identifier.
	 * @param   string   $name    The unique string identifier for a company.
	 * @param   string   $domain  Company email domains.
	 * @param   string   $fields  Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 * @throws  RuntimeException
	 */
	public function getCompanies($id = null, $name = null, $domain = null, $fields = null)
	{
		// At least one value is needed to retrieve data.
		if ($id == null && $name == null && $domain == null)
		{
			// We don't have a valid entry
			throw new RuntimeException('You must specify a company ID, a universal name, or an email domain.');
		}

		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/companies';

		if ($id && $name)
		{
			$base .= '::(' . $id . ',universal-name=' . $name . ')';
		}
		elseif ($id)
		{
			$base .= '/' . $id;
		}
		elseif ($name)
		{
			$base .= '/universal-name=' . $name;
		}

		// Set request parameters.
		$data['format'] = 'json';

		if ($domain)
		{
			$data['email-domain'] = $domain;
		}

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to read shares for a particular company .
	 *
	 * @param   string   $id     The unique company identifier.
	 * @param   string   $type   Any valid Company Update Type from the table: https://developer.linkedin.com/reading-company-updates.
	 * @param   integer  $count  Maximum number of updates to return.
	 * @param   integer  $start  The offset by which to start Network Update pagination.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getUpdates($id, $type = null, $count = 0, $start = 0)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/companies/' . $id . '/updates';

		// Set request parameters.
		$data['format'] = 'json';

		// Check if type is specified.
		if ($type)
		{
			$data['event-type'] = $type;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to search across company pages.
	 *
	 * @param   string   $fields    Request fields beyond the default ones.
	 * @param   string   $keywords  Members who have all the keywords anywhere in their profile.
	 * @param   boolean  $hq        Matching companies by the headquarters location. When this is set to "true" and a location facet is used,
	 * 								this restricts returned companies to only those whose headquarters resides in the specified location.
	 * @param   string   $facets    Facet buckets to return, e.g. location.
	 * @param   array    $facet     Array of facet values to search over. Contains values for location, industry, network, company-size,
	 * 								num-followers-range and fortune, in exactly this order, null must be specified for an element if no value.
	 * @param   integer  $start     Starting location within the result set for paginated returns.
	 * @param   integer  $count     The number of results returned.
	 * @param   string   $sort      Controls the search result order. There are four options: relevance, relationship,
	 * 								followers and company-size.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function search($fields = null, $keywords = null, $hq = false, $facets = null, $facet = null, $start = 0, $count = 0, $sort = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/company-search';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if keywords is specified.
		if ($keywords)
		{
			$data['keywords'] = $keywords;
		}

		// Check if hq is true.
		if ($hq)
		{
			$data['hq-only'] = $hq;
		}

		// Check if facets is specified.
		if ($facets)
		{
			$data['facets'] = $facets;
		}

		// Check if facet is specified.
		if ($facet)
		{
			$data['facet'] = array();

			for ($i = 0, $iMax = count($facet); $i < $iMax; $i++)
			{
				if ($facet[$i])
				{
					if ($i == 0)
					{
						$data['facet'][] = 'location,' . $facet[$i];
					}

					if ($i == 1)
					{
						$data['facet'][] = 'industry,' . $facet[$i];
					}

					if ($i == 2)
					{
						$data['facet'][] = 'network,' . $facet[$i];
					}

					if ($i == 3)
					{
						$data['facet'][] = 'company-size,' . $facet[$i];
					}

					if ($i == 4)
					{
						$data['facet'][] = 'num-followers-range,' . $facet[$i];
					}

					if ($i == 5)
					{
						$data['facet'][] = 'fortune,' . $facet[$i];
					}
				}
			}
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if sort is specified.
		if ($sort)
		{
			$data['sort'] = $sort;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of companies the current member is following.
	 *
	 * @param   string  $fields  Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getFollowed($fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/following/companies';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to follow a company.
	 *
	 * @param   string  $id  The unique identifier for a company.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function follow($id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/following/companies';

		// Build xml.
		$xml = '<company><id>' . $id . '</id></company>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to unfollow a company.
	 *
	 * @param   string  $id  The unique identifier for a company.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function unfollow($id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/people/~/following/companies/id=' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}

	/**
	 * Method to get a collection of suggested companies for the current user.
	 *
	 * @param   string   $fields  Request fields beyond the default ones.
	 * @param   integer  $start   Starting location within the result set for paginated returns.
	 * @param   integer  $count   The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getSuggested($fields = null, $start = 0, $count = 0)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/suggestions/to-follow/companies';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get a collection of suggested companies for the current user.
	 *
	 * @param   string   $id      The unique identifier for a company.
	 * @param   string   $fields  Request fields beyond the default ones.
	 * @param   integer  $start   Starting location within the result set for paginated returns.
	 * @param   integer  $count   The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getProducts($id, $fields = null, $start = 0, $count = 0)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/companies/' . $id . '/products';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}
}
joomla/linkedin/groups.php000064400000063620152177723700011674 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API Groups class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinGroups extends JLinkedinObject
{
	/**
	 * Method to get a group.
	 *
	 * @param   string   $id      The unique identifier for a group.
	 * @param   string   $fields  Request fields beyond the default ones.
	 * @param   integer  $start   Starting location within the result set for paginated returns.
	 * @param   integer  $count   The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getGroup($id, $fields = null, $start = 0, $count = 5)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/groups/' . $id;

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count != 5)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to find the groups a member belongs to.
	 *
	 * @param   string   $id                The unique identifier for a user.
	 * @param   string   $fields            Request fields beyond the default ones.
	 * @param   integer  $start             Starting location within the result set for paginated returns.
	 * @param   integer  $count             The number of results returned.
	 * @param   string   $membership_state  The state of the caller’s membership to the specified group.
	 * 										Values are: non-member, awaiting-confirmation, awaiting-parent-group-confirmation, member, moderator, manager, owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getMemberships($id = null, $fields = null, $start = 0, $count = 5, $membership_state = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if id is specified.
		if ($id)
		{
			$base .= $id . '/group-memberships';
		}
		else
		{
			$base .= '~/group-memberships';
		}

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count != 5)
		{
			$data['count'] = $count;
		}

		// Check if membership_state is specified.
		if ($membership_state)
		{
			$data['membership-state'] = $membership_state;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to find the groups a member belongs to.
	 *
	 * @param   string   $person_id  The unique identifier for a user.
	 * @param   string   $group_id   The unique identifier for a group.
	 * @param   string   $fields     Request fields beyond the default ones.
	 * @param   integer  $start      Starting location within the result set for paginated returns.
	 * @param   integer  $count      The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getSettings($person_id = null, $group_id = null, $fields = null, $start = 0, $count = 5)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if person_id is specified.
		if ($person_id)
		{
			$base .= $person_id . '/group-memberships';
		}
		else
		{
			$base .= '~/group-memberships';
		}

		// Check if group_id is specified.
		if ($group_id)
		{
			$base .= '/' . $group_id;
		}

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count != 5)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to change a groups settings.
	 *
	 * @param   string   $group_id          The unique identifier for a group.
	 * @param   boolean  $show_logo         Show group logo in profile.
	 * @param   string   $digest_frequency  Email digest frequency.
	 * @param   boolean  $announcements     Email announcements from managers.
	 * @param   boolean  $allow_messages    Allow messages from members.
	 * @param   boolean  $new_post          Email for every new post.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function changeSettings($group_id, $show_logo = null, $digest_frequency = null, $announcements = null,
		$allow_messages = null, $new_post = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/group-memberships/' . $group_id;

		// Build xml.
		$xml = '<group-membership>';

		if (!is_null($show_logo))
		{
			$xml .= '<show-group-logo-in-profile>' . $this->booleanToString($show_logo) . '</show-group-logo-in-profile>';
		}

		if ($digest_frequency)
		{
			$xml .= '<email-digest-frequency><code>' . $digest_frequency . '</code></email-digest-frequency>';
		}

		if (!is_null($announcements))
		{
			$xml .= '<email-announcements-from-managers>' . $this->booleanToString($announcements) . '</email-announcements-from-managers>';
		}

		if (!is_null($allow_messages))
		{
			$xml .= '<allow-messages-from-members>' . $this->booleanToString($allow_messages) . '</allow-messages-from-members>';
		}

		if (!is_null($new_post))
		{
			$xml .= '<email-for-every-new-post>' . $this->booleanToString($new_post) . '</email-for-every-new-post>';
		}

		$xml .= '</group-membership>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to join a group.
	 *
	 * @param   string   $group_id          The unique identifier for a group.
	 * @param   boolean  $show_logo         Show group logo in profile.
	 * @param   string   $digest_frequency  Email digest frequency.
	 * @param   boolean  $announcements     Email announcements from managers.
	 * @param   boolean  $allow_messages    Allow messages from members.
	 * @param   boolean  $new_post          Email for every new post.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function joinGroup($group_id, $show_logo = null, $digest_frequency = null, $announcements = null,
		$allow_messages = null, $new_post = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/group-memberships';

		// Build xml.
		$xml = '<group-membership><group><id>' . $group_id . '</id></group>';

		if (!is_null($show_logo))
		{
			$xml .= '<show-group-logo-in-profile>' . $this->booleanToString($show_logo) . '</show-group-logo-in-profile>';
		}

		if ($digest_frequency)
		{
			$xml .= '<email-digest-frequency><code>' . $digest_frequency . '</code></email-digest-frequency>';
		}

		if (!is_null($announcements))
		{
			$xml .= '<email-announcements-from-managers>' . $this->booleanToString($announcements) . '</email-announcements-from-managers>';
		}

		if (!is_null($allow_messages))
		{
			$xml .= '<allow-messages-from-members>' . $this->booleanToString($allow_messages) . '</allow-messages-from-members>';
		}

		if (!is_null($new_post))
		{
			$xml .= '<email-for-every-new-post>' . $this->booleanToString($new_post) . '</email-for-every-new-post>';
		}

		$xml .= '<membership-state><code>member</code></membership-state></group-membership>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to leave a group.
	 *
	 * @param   string  $group_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function leaveGroup($group_id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/people/~/group-memberships/' . $group_id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}

	/**
	 * Method to get dicussions for a group.
	 *
	 * @param   string   $id              The unique identifier for a group.
	 * @param   string   $fields          Request fields beyond the default ones.
	 * @param   integer  $start           Starting location within the result set for paginated returns.
	 * @param   integer  $count           The number of results returned.
	 * @param   string   $order           Sort order for posts. Valid for: recency, popularity.
	 * @param   string   $category        Category of posts. Valid for: discussion
	 * @param   string   $modified_since  Timestamp filter for posts created after the specified value.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getDiscussions($id, $fields = null, $start = 0, $count = 0, $order = null, $category = 'discussion', $modified_since = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/groups/' . $id . '/posts';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if order is specified.
		if ($order)
		{
			$data['order'] = $order;
		}

		// Check if category is specified.
		if ($category)
		{
			$data['category'] = $category;
		}

		// Check if modified_since is specified.
		if ($modified_since)
		{
			$data['modified-since'] = $modified_since;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get posts a user started / participated in / follows for a group.
	 *
	 * @param   string   $group_id        The unique identifier for a group.
	 * @param   string   $role            Filter for posts related to the caller. Valid for: creator, commenter, follower.
	 * @param   string   $person_id       The unique identifier for a user.
	 * @param   string   $fields          Request fields beyond the default ones.
	 * @param   integer  $start           Starting location within the result set for paginated returns.
	 * @param   integer  $count           The number of results returned.
	 * @param   string   $order           Sort order for posts. Valid for: recency, popularity.
	 * @param   string   $category        Category of posts. Valid for: discussion
	 * @param   string   $modified_since  Timestamp filter for posts created after the specified value.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getUserPosts($group_id, $role, $person_id = null, $fields = null, $start = 0, $count = 0,
		$order = null, $category = 'discussion', $modified_since = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if person_id is specified.
		if ($person_id)
		{
			$base .= $person_id;
		}
		else
		{
			$base .= '~';
		}

		$base .= '/group-memberships/' . $group_id . '/posts';

		$data['format'] = 'json';
		$data['role'] = $role;

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if order is specified.
		if ($order)
		{
			$data['order'] = $order;
		}

		// Check if category is specified.
		if ($category)
		{
			$data['category'] = $category;
		}

		// Check if modified_since is specified.
		if ($modified_since)
		{
			$data['modified-since'] = $modified_since;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to retrieve details about a post.
	 *
	 * @param   string  $post_id  The unique identifier for a post.
	 * @param   string  $fields   Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getPost($post_id, $fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/posts/' . $post_id;

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to retrieve all comments of a post.
	 *
	 * @param   string   $post_id  The unique identifier for a post.
	 * @param   string   $fields   Request fields beyond the default ones.
	 * @param   integer  $start    Starting location within the result set for paginated returns.
	 * @param   integer  $count    The number of results returned.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getPostComments($post_id, $fields = null, $start = 0, $count = 0)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/posts/' . $post_id . '/comments';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to retrieve all comments of a post.
	 *
	 * @param   string  $group_id  The unique identifier for a group.
	 * @param   string  $title     Post title.
	 * @param   string  $summary   Post summary.
	 *
	 * @return  string  The created post's id.
	 *
	 * @since   3.2.0
	 */
	public function createPost($group_id, $title, $summary)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/groups/' . $group_id . '/posts';

		// Build xml.
		$xml = '<post><title>' . $title . '</title><summary>' . $summary . '</summary></post>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		// Return the post id.
		$response = explode('posts/', $response->headers['Location']);

		return $response[1];
	}

	/**
	 * Method to like or unlike a post.
	 *
	 * @param   string   $post_id  The unique identifier for a group.
	 * @param   boolean  $like     True to like post, false otherwise.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	private function _likeUnlike($post_id, $like)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/posts/' . $post_id . '/relation-to-viewer/is-liked';

		// Build xml.
		$xml = '<is-liked>' . $this->booleanToString($like) . '</is-liked>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method used to like a post.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function likePost($post_id)
	{
		return $this->_likeUnlike($post_id, true);
	}

	/**
	 * Method used to unlike a post.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function unlikePost($post_id)
	{
		return $this->_likeUnlike($post_id, false);
	}

	/**
	 * Method to follow or unfollow a post.
	 *
	 * @param   string   $post_id  The unique identifier for a group.
	 * @param   boolean  $follow   True to like post, false otherwise.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	private function _followUnfollow($post_id, $follow)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/posts/' . $post_id . '/relation-to-viewer/is-following';

		// Build xml.
		$xml = '<is-following>' . $this->booleanToString($follow) . '</is-following>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method used to follow a post.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function followPost($post_id)
	{
		return $this->_followUnfollow($post_id, true);
	}

	/**
	 * Method used to unfollow a post.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function unfollowPost($post_id)
	{
		return $this->_followUnfollow($post_id, false);
	}

	/**
	 * Method to flag a post as a Promotion or Job.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 * @param   string  $flag     Flag as a 'promotion' or 'job'.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function flagPost($post_id, $flag)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/posts/' . $post_id . '/category/code';

		// Build xml.
		$xml = '<code>' . $flag . '</code>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to delete a post if the current user is the creator or flag it as inappropriate otherwise.
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function deletePost($post_id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/posts/' . $post_id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}

	/**
	 * Method to access the comments resource.
	 *
	 * @param   string  $comment_id  The unique identifier for a comment.
	 * @param   string  $fields      Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getComment($comment_id, $fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/comments/' . $comment_id;

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to add a comment to a post
	 *
	 * @param   string  $post_id  The unique identifier for a group.
	 * @param   string  $comment  The post comment's text.
	 *
	 * @return  string   The created comment's id.
	 *
	 * @since   3.2.0
	 */
	public function addComment($post_id, $comment)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/posts/' . $post_id . '/comments';

		// Build xml.
		$xml = '<comment><text>' . $comment . '</text></comment>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		// Return the comment id.
		$response = explode('comments/', $response->headers['Location']);

		return $response[1];
	}

	/**
	 * Method to delete a comment if the current user is the creator or flag it as inappropriate otherwise.
	 *
	 * @param   string  $comment_id  The unique identifier for a group.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment_id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/comments/' . $comment_id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}

	/**
	 * Method to get suggested groups for a user.
	 *
	 * @param   string  $person_id  The unique identifier for a user.
	 * @param   string  $fields     Request fields beyond the default ones.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getSuggested($person_id = null, $fields = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if person_id is specified.
		if ($person_id)
		{
			$base .= $person_id . '/suggestions/groups';
		}
		else
		{
			$base .= '~/suggestions/groups';
		}

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to delete a group suggestion for a user.
	 *
	 * @param   string  $suggestion_id  The unique identifier for a suggestion.
	 * @param   string  $person_id      The unique identifier for a user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function deleteSuggestion($suggestion_id, $person_id = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/people/';

		// Check if person_id is specified.
		if ($person_id)
		{
			$base .= $person_id . '/suggestions/groups/' . $suggestion_id;
		}
		else
		{
			$base .= '~/suggestions/groups/' . $suggestion_id;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters);

		return $response;
	}
}
joomla/linkedin/stream.php000064400000034437152177723700011654 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API Social Stream class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinStream extends JLinkedinObject
{
	/**
	 * Method to add a new share. Note: post must contain comment and/or (title and url).
	 *
	 * @param   string   $visibility   One of anyone: all members or connections-only: connections only.
	 * @param   string   $comment      Text of member's comment.
	 * @param   string   $title        Title of shared document.
	 * @param   string   $url          URL for shared content.
	 * @param   string   $image        URL for image of shared content.
	 * @param   string   $description  Description of shared content.
	 * @param   boolean  $twitter      True to have LinkedIn pass the status message along to a member's tethered Twitter account.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 * @throws  RuntimeException
	 */
	public function share($visibility, $comment = null, $title = null, $url = null, $image = null, $description = null, $twitter = false)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/shares';

		// Check if twitter is true.
		if ($twitter)
		{
			$base .= '?twitter-post=true';
		}

		// Build xml.
		$xml = '<share>
				  <visibility>
					 <code>' . $visibility . '</code>
				  </visibility>';

		// Check if comment specified.
		if ($comment)
		{
			$xml .= '<comment>' . $comment . '</comment>';
		}

		// Check if title and url are specified.
		if ($title && $url)
		{
			$xml .= '<content>
					   <title>' . $title . '</title>
					   <submitted-url>' . $url . '</submitted-url>';

			// Check if image is specified.
			if ($image)
			{
				$xml .= '<submitted-image-url>' . $image . '</submitted-image-url>';
			}

			// Check if descrption id specified.
			if ($description)
			{
				$xml .= '<description>' . $description . '</description>';
			}

			$xml .= '</content>';
		}
		elseif (!$comment)
		{
			throw new RuntimeException('Post must contain comment and/or (title and url).');
		}

		$xml .= '</share>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to reshare an existing share.
	 *
	 * @param   string   $visibility  One of anyone: all members or connections-only: connections only.
	 * @param   string   $id          The unique identifier for a share.
	 * @param   string   $comment     Text of member's comment.
	 * @param   boolean  $twitter     True to have LinkedIn pass the status message along to a member's tethered Twitter account.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 * @throws  RuntimeException
	 */
	public function reshare($visibility, $id, $comment = null, $twitter = false)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/shares';

		// Check if twitter is true.
		if ($twitter)
		{
			$base .= '?twitter-post=true';
		}

		// Build xml.
		$xml = '<share>
				  <visibility>
					 <code>' . $visibility . '</code>
				  </visibility>';

		// Check if comment specified.
		if ($comment)
		{
			$xml .= '<comment>' . $comment . '</comment>';
		}

		$xml .= '   <attribution>
					   <share>
					   	  <id>' . $id . '</id>
					   </share>
					</attribution>
				 </share>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to get a particular member's current share.
	 *
	 * @param   string  $id   Member id of the profile you want.
	 * @param   string  $url  The public profile URL.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getCurrentShare($id = null, $url = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if a member id is specified.
		if ($id)
		{
			$base .= 'id=' . $id;
		}
		elseif (!$url)
		{
			$base .= '~';
		}

		// Check if profile url is specified.
		if ($url)
		{
			$base .= 'url=' . $this->oauth->safeEncode($url);
		}

		$base .= ':(current-share)';

		// Set request parameters.
		$data['format'] = 'json';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get a particular member's current share.
	 *
	 * @param   string   $id    Member id of the profile you want.
	 * @param   string   $url   The public profile URL.
	 * @param   boolean  $self  Used to return member's feed. Omitted to return aggregated network feed.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getShareStream($id = null, $url = null, $self = true)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if a member id is specified.
		if ($id)
		{
			$base .= $id;
		}
		elseif (!$url)
		{
			$base .= '~';
		}

		// Check if profile url is specified.
		if ($url)
		{
			$base .= 'url=' . $this->oauth->safeEncode($url);
		}

		$base .= '/network';

		// Set request parameters.
		$data['format'] = 'json';
		$data['type'] = 'SHAR';

		// Check if self is true
		if ($self)
		{
			$data['scope'] = 'self';
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get the users network updates.
	 *
	 * @param   string   $id      Member id.
	 * @param   boolean  $self    Used to return member's feed. Omitted to return aggregated network feed.
	 * @param   mixed    $type    String containing any valid Network Update Type from the table or an array of strings
	 * 							  to specify more than one Network Update type.
	 * @param   integer  $count   Number of updates to return, with a maximum of 250.
	 * @param   integer  $start   The offset by which to start Network Update pagination.
	 * @param   string   $after   Timestamp after which to retrieve updates.
	 * @param   string   $before  Timestamp before which to retrieve updates.
	 * @param   boolean  $hidden  Whether to display updates from people the member has chosen to "hide" from their update stream.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getNetworkUpdates($id = null, $self = true, $type = null, $count = 0, $start = 0, $after = null, $before = null,
		$hidden = false)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		// Check if a member id is specified.
		if ($id)
		{
			$base .= $id;
		}
		else
		{
			$base .= '~';
		}

		$base .= '/network/updates';

		// Set request parameters.
		$data['format'] = 'json';

		// Check if self is true.
		if ($self)
		{
			$data['scope'] = 'self';
		}

		// Check if type is specified.
		if ($type)
		{
			$data['type'] = $type;
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if after is specified.
		if ($after)
		{
			$data['after'] = $after;
		}

		// Check if before is specified.
		if ($before > 0)
		{
			$data['before'] = $before;
		}

		// Check if hidden is true.
		if ($hidden)
		{
			$data['hidden'] = $hidden;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get information about the current member's network.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getNetworkStats()
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/network/network-stats';

		// Set request parameters.
		$data['format'] = 'json';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get the users network updates.
	 *
	 * @param   string  $body  The actual content of the update. You can use HTML to include links to the user name and the content the user
	 *                         created. Other HTML tags are not supported. All body text should be HTML entity escaped and UTF-8 compliant.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function postNetworkUpdate($body)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/person-activities';

		// Build the xml.
		$xml = '<activity locale="en_US">
					<content-type>linkedin-html</content-type>
				    <body>' . $body . '</body>
				</activity>';

		$header['Content-Type'] = 'text/xml';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to retrieve all comments for a given network update.
	 *
	 * @param   string  $key  update/update-key representing an update.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getComments($key)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/network/updates/key=' . $key . '/update-comments';

		// Set request parameters.
		$data['format'] = 'json';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to post a new comment to an existing update.
	 *
	 * @param   string  $key      update/update-key representing an update.
	 * @param   string  $comment  Maximum length of 700 characters
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function postComment($key, $comment)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base
		$base = '/v1/people/~/network/updates/key=' . $key . '/update-comments';

		// Build the xml.
		$xml = '<update-comment>
				  <comment>' . $comment . '</comment>
				</update-comment>';

		$header['Content-Type'] = 'text/xml';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method to retrieve the complete list of people who liked an update.
	 *
	 * @param   string  $key  update/update-key representing an update.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getLikes($key)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/network/updates/key=' . $key . '/likes';

		// Set request parameters.
		$data['format'] = 'json';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to like or unlike an update.
	 *
	 * @param   string   $key   Update/update-key representing an update.
	 * @param   boolean  $like  True to like update, false otherwise.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	private function _likeUnlike($key, $like)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 204);

		// Set the API base
		$base = '/v1/people/~/network/updates/key=' . $key . '/is-liked';

		// Build xml.
		$xml = '<is-liked>' . $this->booleanToString($like) . '</is-liked>';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method used to like an update.
	 *
	 * @param   string  $key  Update/update-key representing an update.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function like($key)
	{
		return $this->_likeUnlike($key, true);
	}

	/**
	 * Method used to unlike an update.
	 *
	 * @param   string  $key  Update/update-key representing an update.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function unlike($key)
	{
		return $this->_likeUnlike($key, false);
	}
}
joomla/linkedin/object.php000064400000004240152177723700011614 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Linkedin API object class for the Joomla Platform.
 *
 * @since  3.2.0
 */
abstract class JLinkedinObject
{
	/**
	 * @var    Registry  Options for the Linkedin object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * @var   JLinkedinOAuth The OAuth client.
	 * @since  3.2.0
	 */
	protected $oauth;

	/**
	 * Constructor.
	 *
	 * @param   Registry        $options  Linkedin options object.
	 * @param   JHttp           $client   The HTTP client object.
	 * @param   JLinkedinOAuth  $oauth    The OAuth client.
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JLinkedinOAuth $oauth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JHttp($this->options);
		$this->oauth = $oauth;
	}

	/**
	 * Method to convert boolean to string.
	 *
	 * @param   boolean  $bool  The boolean value to convert.
	 *
	 * @return  string  String with the converted boolean.
	 *
	 * @since 3.2.0
	 */
	public function booleanToString($bool)
	{
		if ($bool)
		{
			return 'true';
		}
		else
		{
			return 'false';
		}
	}

	/**
	 * Get an option from the JLinkedinObject instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JLinkedinObject instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JLinkedinObject  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/linkedin/communications.php000064400000013721152177723700013402 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API Social Communications class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinCommunications extends JLinkedinObject
{
	/**
	 * Method used to invite people.
	 *
	 * @param   string  $email       A string containing email of the recipient.
	 * @param   string  $first_name  A string containing frist name of the recipient.
	 * @param   string  $last_name   A string containing last name of the recipient.
	 * @param   string  $subject     The subject of the message that will be sent to the recipient
	 * @param   string  $body        A text of the message.
	 * @param   string  $connection  Only connecting as a 'friend' is supported presently.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function inviteByEmail($email, $first_name, $last_name, $subject, $body, $connection = 'friend')
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base.
		$base = '/v1/people/~/mailbox';

		// Build the xml.
		$xml = '<mailbox-item>
				  <recipients>
				  	<recipient>
						<person path="/people/email=' . $email . '">
							<first-name>' . $first_name . '</first-name>
							<last-name>' . $last_name . '</last-name>
						</person>
					</recipient>
				</recipients>
				<subject>' . $subject . '</subject>
				<body>' . $body . '</body>
				<item-content>
				    <invitation-request>
				      <connect-type>' . $connection . '</connect-type>
				    </invitation-request>
				</item-content>
			 </mailbox-item>';

		$header['Content-Type'] = 'text/xml';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method used to invite people.
	 *
	 * @param   string  $id          Member id.
	 * @param   string  $first_name  A string containing frist name of the recipient.
	 * @param   string  $last_name   A string containing last name of the recipient.
	 * @param   string  $subject     The subject of the message that will be sent to the recipient
	 * @param   string  $body        A text of the message.
	 * @param   string  $connection  Only connecting as a 'friend' is supported presently.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function inviteById($id, $first_name, $last_name, $subject, $body, $connection = 'friend')
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base for people search.
		$base = '/v1/people-search:(people:(api-standard-profile-request))';

		$data['format'] = 'json';
		$data['first-name'] = $first_name;
		$data['last-name'] = $last_name;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		if (strpos($response->body, 'apiStandardProfileRequest') === false)
		{
			throw new RuntimeException($response->body);
		}

		// Get header value.
		$value = explode('"value": "', $response->body);
		$value = explode('"', $value[1]);
		$value = $value[0];

		// Split on the colon character.
		$value = explode(':', $value);
		$name = $value[0];
		$value = $value[1];

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base.
		$base = '/v1/people/~/mailbox';

		// Build the xml.
		$xml = '<mailbox-item>
				  <recipients>
				  	<recipient>
						<person path="/people/id=' . $id . '">
						</person>
					</recipient>
				</recipients>
				<subject>' . $subject . '</subject>
				<body>' . $body . '</body>
				<item-content>
				    <invitation-request>
				      <connect-type>' . $connection . '</connect-type>
				      <authorization>
				      	<name>' . $name . '</name>
				        <value>' . $value . '</value>
				      </authorization>
				    </invitation-request>
				</item-content>
			 </mailbox-item>';

		$header['Content-Type'] = 'text/xml';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}

	/**
	 * Method used to send messages via LinkedIn between two or more individuals connected to the member sending the message..
	 *
	 * @param   mixed   $recipient  A string containing the member id or an array of ids.
	 * @param   string  $subject    The subject of the message that will be sent to the recipient
	 * @param   string  $body       A text of the message.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function sendMessage($recipient, $subject, $body)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the success response code.
		$this->oauth->setOption('success_code', 201);

		// Set the API base.
		$base = '/v1/people/~/mailbox';

		// Build the xml.
		$xml = '<mailbox-item>
				  <recipients>';

		if (is_array($recipient))
		{
			foreach ($recipient as $r)
			{
				$xml .= '<recipient>
							<person path="/people/' . $r . '"/>
						</recipient>';
			}
		}

		$xml .= '</recipients>
				 <subject>' . $subject . '</subject>
				 <body>' . $body . '</body>
				</mailbox-item>';

		$header['Content-Type'] = 'text/xml';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		return $response;
	}
}
joomla/linkedin/linkedin.php000064400000006371152177723700012152 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with a Linkedin API instance.
 *
 * @since  3.2.0
 */
class JLinkedin
{
	/**
	 * @var    Registry  Options for the Linkedin object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * @var JLinkedinOAuth The OAuth client.
	 * @since 3.2.0
	 */
	protected $oauth;

	/**
	 * @var    JLinkedinPeople  Linkedin API object for people.
	 * @since  3.2.0
	 */
	protected $people;

	/**
	 * @var    JLinkedinGroups  Linkedin API object for groups.
	 * @since  3.2.0
	 */
	protected $groups;

	/**
	 * @var    JLinkedinCompanies  Linkedin API object for companies.
	 * @since  3.2.0
	 */
	protected $companies;

	/**
	 * @var    JLinkedinJobs  Linkedin API object for jobs.
	 * @since  3.2.0
	 */
	protected $jobs;

	/**
	 * @var    JLinkedinStream  Linkedin API object for social stream.
	 * @since  3.2.0
	 */
	protected $stream;

	/**
	 * @var    JLinkedinCommunications  Linkedin API object for communications.
	 * @since  3.2.0
	 */
	protected $communications;

	/**
	 * Constructor.
	 *
	 * @param   JLinkedinOauth  $oauth    OAuth object
	 * @param   Registry        $options  Linkedin options object.
	 * @param   JHttp           $client   The HTTP client object.
	 *
	 * @since   3.2.0
	 */
	public function __construct(JLinkedinOauth $oauth = null, Registry $options = null, JHttp $client = null)
	{
		$this->oauth = $oauth;
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JHttp($this->options);

		// Setup the default API url if not already set.
		$this->options->def('api.url', 'https://api.linkedin.com');
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JLinkedinObject  Linkedin API object (statuses, users, favorites, etc.).
	 *
	 * @since   3.2.0
	 * @throws  InvalidArgumentException
	 */
	public function __get($name)
	{
		$class = 'JLinkedin' . ucfirst($name);

		if (class_exists($class))
		{
			if (false == isset($this->$name))
			{
				$this->$name = new $class($this->options, $this->client, $this->oauth);
			}

			return $this->$name;
		}

		throw new InvalidArgumentException(sprintf('Argument %s produced an invalid class name: %s', $name, $class));
	}

	/**
	 * Get an option from the JLinkedin instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the Linkedin instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JLinkedin  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/linkedin/oauth.php000064400000006511152177723700011471 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for generating Linkedin API access token.
 *
 * @since  3.2.0
 */
class JLinkedinOauth extends JOAuth1Client
{
	/**
	* @var    Registry  Options for the JLinkedinOauth object.
	* @since  3.2.0
	*/
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  JLinkedinOauth options object.
	 * @param   JHttp     $client   The HTTP client object.
	 * @param   JInput    $input    The input object
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null)
	{
		$this->options = isset($options) ? $options : new Registry;

		$this->options->def('accessTokenURL', 'https://www.linkedin.com/uas/oauth/accessToken');
		$this->options->def('authenticateURL', 'https://www.linkedin.com/uas/oauth/authenticate');
		$this->options->def('authoriseURL', 'https://www.linkedin.com/uas/oauth/authorize');
		$this->options->def('requestTokenURL', 'https://www.linkedin.com/uas/oauth/requestToken');

		// Call the JOauthV1aclient constructor to setup the object.
		parent::__construct($this->options, $client, $input);
	}

	/**
	 * Method to verify if the access token is valid by making a request to an API endpoint.
	 *
	 * @return  boolean  Returns true if the access token is valid and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function verifyCredentials()
	{
		$token = $this->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		$data['format'] = 'json';

		// Set the API url.
		$path = 'https://api.linkedin.com/v1/people::(~)';

		// Send the request.
		$response = $this->oauthRequest($path, 'GET', $parameters, $data);

		// Verify response
		if ($response->code == 200)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to validate a response.
	 *
	 * @param   string         $url       The request URL.
	 * @param   JHttpResponse  $response  The response to validate.
	 *
	 * @return  void
	 *
	 * @since  3.2.0
	 * @throws DomainException
	 */
	public function validateResponse($url, $response)
	{
		if (!$code = $this->getOption('success_code'))
		{
			$code = 200;
		}

		if (strpos($url, '::(~)') === false && $response->code != $code)
		{
			if ($error = json_decode($response->body))
			{
				throw new DomainException('Error code ' . $error->errorCode . ' received with message: ' . $error->message . '.');
			}
			else
			{
				throw new DomainException($response->body);
			}
		}
	}

	/**
	 * Method used to set permissions.
	 *
	 * @param   mixed  $scope  String or an array of string containing permissions.
	 *
	 * @return  JLinkedinOauth  This object for method chaining
	 *
	 * @link    https://developer.linkedin.com/documents/authentication
	 * @since   3.2.0
	 */
	public function setScope($scope)
	{
		$this->setOption('scope', $scope);

		return $this;
	}

	/**
	 * Method to get the current scope
	 *
	 * @return  string String or an array of string containing permissions.
	 *
	 * @since   3.2.0
	 */
	public function getScope()
	{
		return $this->getOption('scope');
	}
}
joomla/linkedin/people.php000064400000023636152177723700011644 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Linkedin
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Linkedin API People class for the Joomla Platform.
 *
 * @since  3.2.0
 */
class JLinkedinPeople extends JLinkedinObject
{
	/**
	 * Method to get a member's profile.
	 *
	 * @param   string  $id        Member id of the profile you want.
	 * @param   string  $url       The public profile URL.
	 * @param   string  $fields    Request fields beyond the default ones.
	 * @param   string  $type      Choosing public or standard profile.
	 * @param   string  $language  A comma separated list of locales ordered from highest to lowest preference.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getProfile($id = null, $url = null, $fields = null, $type = 'standard', $language = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/';

		$data['format'] = 'json';

		// Check if a member id is specified.
		if ($id)
		{
			$base .= 'id=' . $id;
		}
		elseif (!$url)
		{
			$base .= '~';
		}

		// Check if profile url is specified.
		if ($url)
		{
			$base .= 'url=' . $this->oauth->safeEncode($url);

			// Choose public profile
			if (!strcmp($type, 'public'))
			{
				$base .= ':public';
			}
		}

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if language is specified.
		$header = array();

		if ($language)
		{
			$header = array('Accept-Language' => $language);
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data, $header);

		return json_decode($response->body);
	}

	/**
	 * Method to get a list of connections for a user who has granted access to his/her account.
	 *
	 * @param   string   $fields          Request fields beyond the default ones.
	 * @param   integer  $start           Starting location within the result set for paginated returns.
	 * @param   integer  $count           The number of results returned.
	 * @param   string   $modified        Values are updated or new.
	 * @param   string   $modified_since  Value as a Unix time stamp of milliseconds since epoch.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function getConnections($fields = null, $start = 0, $count = 500, $modified = null, $modified_since = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people/~/connections';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}
		// Check if count is specified.
		if ($count != 500)
		{
			$data['count'] = $count;
		}

		// Check if modified is specified.
		if ($modified)
		{
			$data['modified'] = $modified;
		}

		// Check if modified_since is specified.
		if ($modified_since)
		{
			$data['modified-since'] = $modified_since;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		return json_decode($response->body);
	}

	/**
	 * Method to get information about people.
	 *
	 * @param   string   $fields           Request fields beyond the default ones. provide 'api-standard-profile-request'
	 * 									   field for out of network profiles.
	 * @param   string   $keywords         Members who have all the keywords anywhere in their profile.
	 * @param   string   $first_name       Members with a matching first name. Matches must be exact.
	 * @param   string   $last_name        Members with a matching last name. Matches must be exactly.
	 * @param   string   $company_name     Members who have a matching company name on their profile.
	 * @param   boolean  $current_company  A value of true matches members who currently work at the company specified in the company-name
	 * 									   parameter.
	 * @param   string   $title            Matches members with that title on their profile.
	 * @param   boolean  $current_title    A value of true matches members whose title is currently the one specified in the title-name parameter.
	 * @param   string   $school_name      Members who have a matching school name on their profile.
	 * @param   string   $current_school   A value of true matches members who currently attend the school specified in the school-name parameter.
	 * @param   string   $country_code     Matches members with a location in a specific country. Values are defined in by ISO 3166 standard.
	 * 									   Country codes must be in all lower case.
	 * @param   integer  $postal_code      Matches members centered around a Postal Code. Must be combined with the country-code parameter.
	 * 									   Not supported for all countries.
	 * @param   integer  $distance         Matches members within a distance from a central point. This is measured in miles.
	 * @param   string   $facets           Facet buckets to return, e.g. location.
	 * @param   array    $facet            Array of facet values to search over. Contains values for location, industry, network, language,
	 * 									   current-company, past-company and school, in exactly this order, null must be specified for an element if no value.
	 * @param   integer  $start            Starting location within the result set for paginated returns.
	 * @param   integer  $count            The number of results returned.
	 * @param   string   $sort             Controls the search result order. There are four options: connections, recommenders,
	 * 									   distance and relevance.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	public function search($fields = null, $keywords = null, $first_name = null, $last_name = null, $company_name = null,
		$current_company = null, $title = null, $current_title = null, $school_name = null, $current_school = null, $country_code = null,
		$postal_code = null, $distance = null, $facets = null, $facet = null, $start = 0, $count = 10, $sort = null)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = '/v1/people-search';

		$data['format'] = 'json';

		// Check if fields is specified.
		if ($fields)
		{
			$base .= ':' . $fields;
		}

		// Check if keywords is specified.
		if ($keywords)
		{
			$data['keywords'] = $keywords;
		}

		// Check if first_name is specified.
		if ($first_name)
		{
			$data['first-name'] = $first_name;
		}

		// Check if last_name is specified.
		if ($last_name)
		{
			$data['last-name'] = $last_name;
		}

		// Check if company-name is specified.
		if ($company_name)
		{
			$data['company-name'] = $company_name;
		}

		// Check if current_company is specified.
		if ($current_company)
		{
			$data['current-company'] = $current_company;
		}

		// Check if title is specified.
		if ($title)
		{
			$data['title'] = $title;
		}

		// Check if current_title is specified.
		if ($current_title)
		{
			$data['current-title'] = $current_title;
		}

		// Check if school_name is specified.
		if ($school_name)
		{
			$data['school-name'] = $school_name;
		}

		// Check if current_school is specified.
		if ($current_school)
		{
			$data['current-school'] = $current_school;
		}

		// Check if country_code is specified.
		if ($country_code)
		{
			$data['country-code'] = $country_code;
		}

		// Check if postal_code is specified.
		if ($postal_code)
		{
			$data['postal-code'] = $postal_code;
		}

		// Check if distance is specified.
		if ($distance)
		{
			$data['distance'] = $distance;
		}

		// Check if facets is specified.
		if ($facets)
		{
			$data['facets'] = $facets;
		}

		// Check if facet is specified.
		if ($facet)
		{
			$data['facet'] = array();

			for ($i = 0, $iMax = count($facet); $i < $iMax; $i++)
			{
				if ($facet[$i])
				{
					if ($i == 0)
					{
						$data['facet'][] = 'location,' . $facet[$i];
					}

					if ($i == 1)
					{
						$data['facet'][] = 'industry,' . $facet[$i];
					}

					if ($i == 2)
					{
						$data['facet'][] = 'network,' . $facet[$i];
					}

					if ($i == 3)
					{
						$data['facet'][] = 'language,' . $facet[$i];
					}

					if ($i == 4)
					{
						$data['facet'][] = 'current-company,' . $facet[$i];
					}

					if ($i == 5)
					{
						$data['facet'][] = 'past-company,' . $facet[$i];
					}

					if ($i == 6)
					{
						$data['facet'][] = 'school,' . $facet[$i];
					}
				}
			}
		}

		// Check if start is specified.
		if ($start > 0)
		{
			$data['start'] = $start;
		}

		// Check if count is specified.
		if ($count != 10)
		{
			$data['count'] = $count;
		}

		// Check if sort is specified.
		if ($sort)
		{
			$data['sort'] = $sort;
		}

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters, $data);

		if (strpos($fields, 'api-standard-profile-request') === false)
		{
			return json_decode($response->body);
		}

		// Get header name.
		$name = explode('"name": "', $response->body);
		$name = explode('"', $name[1]);
		$name = $name[0];

		// Get header value.
		$value = explode('"value": "', $response->body);
		$value = explode('"', $value[1]);
		$value = $value[0];

		// Get request url.
		$url = explode('"url": "', $response->body);
		$url = explode('"', $url[1]);
		$url = $url[0];

		// Build header for out of network profile.
		$header[$name] = $value;

		// Send the request.
		$response = $this->oauth->oauthRequest($url, 'GET', $parameters, $data, $header);

		return json_decode($response->body);
	}
}
joomla/google/data/picasa/photo.php000064400000016233152177723700013334 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Picasa data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPicasaPhoto extends JGoogleData
{
	/**
	 * @var    SimpleXMLElement  The photo's XML
	 * @since  3.1.4
	 */
	protected $xml;

	/**
	 * Constructor.
	 *
	 * @param   SimpleXMLElement  $xml      XML from Google
	 * @param   Registry          $options  Google options object
	 * @param   JGoogleAuth       $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(SimpleXMLElement $xml, Registry $options = null, JGoogleAuth $auth = null)
	{
		$this->xml = $xml;

		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://picasaweb.google.com/data/');
		}
	}

	/**
	 * Method to delete a Picasa photo
	 *
	 * @param   mixed  $match  Check for most up to date photo
	 *
	 * @return  boolean  Success or failure.
	 *
	 * @since   3.1.4
	 * @throws  Exception
	 * @throws  RuntimeException
	 * @throws  UnexpectedValueException
	 */
	public function delete($match = '*')
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();

			if ($match === true)
			{
				$match = $this->xml->xpath('./@gd:etag');
				$match = $match[0];
			}

			try
			{
				$jdata = $this->query($url, null, array('GData-Version' => 2, 'If-Match' => $match), 'delete');
			}
			catch (Exception $e)
			{
				if (strpos($e->getMessage(), 'Error code 412 received requesting data: Mismatch: etags') === 0)
				{
					throw new RuntimeException("Etag match failed: `$match`.", $e->getCode(), $e);
				}

				throw $e;
			}

			if ($jdata->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}

			$this->xml = null;

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the photo link
	 *
	 * @param   string  $type  Type of link to return
	 *
	 * @return  string  Link or false on failure
	 *
	 * @since   3.1.4
	 */
	public function getLink($type = 'edit')
	{
		$links = $this->xml->link;

		foreach ($links as $link)
		{
			if ($link->attributes()->rel == $type)
			{
				return (string) $link->attributes()->href;
			}
		}

		return false;
	}

	/**
	 * Method to get the photo's URL
	 *
	 * @return  string  Link
	 *
	 * @since   3.1.4
	 */
	public function getUrl()
	{
		return (string) $this->xml->children()->content->attributes()->src;
	}

	/**
	 * Method to get the photo's thumbnails
	 *
	 * @return  array  An array of thumbnails
	 *
	 * @since   3.1.4
	 */
	public function getThumbnails()
	{
		$thumbs = array();

		foreach ($this->xml->children('media', true)->group->thumbnail as $item)
		{
			$url = (string) $item->attributes()->url;
			$width = (int) $item->attributes()->width;
			$height = (int) $item->attributes()->height;
			$thumbs[$width] = array('url' => $url, 'w' => $width, 'h' => $height);
		}

		return $thumbs;
	}

	/**
	 * Method to get the title of the photo
	 *
	 * @return  string  Photo title
	 *
	 * @since   3.1.4
	 */
	public function getTitle()
	{
		return (string) $this->xml->children()->title;
	}

	/**
	 * Method to get the summary of the photo
	 *
	 * @return  string  Photo description
	 *
	 * @since   3.1.4
	 */
	public function getSummary()
	{
		return (string) $this->xml->children()->summary;
	}

	/**
	 * Method to get the access level of the photo
	 *
	 * @return  string  Photo access level
	 *
	 * @since   3.1.4
	 */
	public function getAccess()
	{
		return (string) $this->xml->children('gphoto', true)->access;
	}

	/**
	 * Method to get the time of the photo
	 *
	 * @return  double  Photo time
	 *
	 * @since   3.1.4
	 */
	public function getTime()
	{
		return (double) $this->xml->children('gphoto', true)->timestamp / 1000;
	}

	/**
	 * Method to get the size of the photo
	 *
	 * @return  int  Photo size
	 *
	 * @since   3.1.4
	 */
	public function getSize()
	{
		return (int) $this->xml->children('gphoto', true)->size;
	}

	/**
	 * Method to get the height of the photo
	 *
	 * @return  int  Photo height
	 *
	 * @since   3.1.4
	 */
	public function getHeight()
	{
		return (int) $this->xml->children('gphoto', true)->height;
	}

	/**
	 * Method to get the width of the photo
	 *
	 * @return  int  Photo width
	 *
	 * @since   3.1.4
	 */
	public function getWidth()
	{
		return (int) $this->xml->children('gphoto', true)->width;
	}

	/**
	 * Method to set the title of the photo
	 *
	 * @param   string  $title  New photo title
	 *
	 * @return  JGoogleDataPicasaPhoto  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setTitle($title)
	{
		$this->xml->children()->title = $title;

		return $this;
	}

	/**
	 * Method to set the summary of the photo
	 *
	 * @param   string  $summary  New photo description
	 *
	 * @return  JGoogleDataPicasaPhoto  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setSummary($summary)
	{
		$this->xml->children()->summary = $summary;

		return $this;
	}

	/**
	 * Method to set the access level of the photo
	 *
	 * @param   string  $access  New photo access level
	 *
	 * @return  JGoogleDataPicasaPhoto  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAccess($access)
	{
		$this->xml->children('gphoto', true)->access = $access;

		return $this;
	}

	/**
	 * Method to set the time of the photo
	 *
	 * @param   int  $time  New photo time
	 *
	 * @return  JGoogleDataPicasaPhoto  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setTime($time)
	{
		$this->xml->children('gphoto', true)->timestamp = $time * 1000;

		return $this;
	}

	/**
	 * Method to modify a Picasa Photo
	 *
	 * @param   string  $match  Optional eTag matching parameter
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 */
	public function save($match = '*')
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();

			if ($match === true)
			{
				$match = $this->xml->xpath('./@gd:etag');
				$match = $match[0];
			}

			try
			{
				$headers = array('GData-Version' => 2, 'Content-type' => 'application/atom+xml', 'If-Match' => $match);
				$jdata = $this->query($url, $this->xml->asXml(), $headers, 'put');
			}
			catch (Exception $e)
			{
				if (strpos($e->getMessage(), 'Error code 412 received requesting data: Mismatch: etags') === 0)
				{
					throw new RuntimeException("Etag match failed: `$match`.", $e->getCode(), $e);
				}

				throw $e;
			}

			$this->xml = $this->safeXml($jdata->body);

			return $this;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Refresh photo data
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function refresh()
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();
			$jdata = $this->query($url, null, array('GData-Version' => 2));
			$this->xml = $this->safeXml($jdata->body);

			return $this;
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/picasa/album.php000064400000023555152177723700013310 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Picasa data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPicasaAlbum extends JGoogleData
{
	/**
	 * @var    SimpleXMLElement  The album's XML
	 * @since  3.1.4
	 */
	protected $xml;

	/**
	 * Constructor.
	 *
	 * @param   SimpleXMLElement  $xml      XML from Google
	 * @param   Registry          $options  Google options object
	 * @param   JGoogleAuth       $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(SimpleXMLElement $xml, Registry $options = null, JGoogleAuth $auth = null)
	{
		$this->xml = $xml;

		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://picasaweb.google.com/data/');
		}
	}

	/**
	 * Method to delete a Picasa album
	 *
	 * @param   mixed  $match  Check for most up to date album
	 *
	 * @return  boolean  Success or failure.
	 *
	 * @since   3.1.4
	 * @throws  Exception
	 * @throws  RuntimeException
	 * @throws  UnexpectedValueException
	 */
	public function delete($match = '*')
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();

			if ($match === true)
			{
				$match = $this->xml->xpath('./@gd:etag');
				$match = $match[0];
			}

			try
			{
				$jdata = $this->query($url, null, array('GData-Version' => 2, 'If-Match' => $match), 'delete');
			}
			catch (Exception $e)
			{
				if (strpos($e->getMessage(), 'Error code 412 received requesting data: Mismatch: etags') === 0)
				{
					throw new RuntimeException("Etag match failed: `$match`.", $e->getCode(), $e);
				}

				throw $e;
			}

			if ($jdata->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}

			$this->xml = null;

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the album link
	 *
	 * @param   string  $type  Type of link to return
	 *
	 * @return  string  Link or false on failure
	 *
	 * @since   3.1.4
	 */
	public function getLink($type = 'edit')
	{
		$links = $this->xml->link;

		foreach ($links as $link)
		{
			if ($link->attributes()->rel == $type)
			{
				return (string) $link->attributes()->href;
			}
		}

		return false;
	}

	/**
	 * Method to get the title of the album
	 *
	 * @return  string  Album title
	 *
	 * @since   3.1.4
	 */
	public function getTitle()
	{
		return (string) $this->xml->children()->title;
	}

	/**
	 * Method to get the summary of the album
	 *
	 * @return  string  Album summary
	 *
	 * @since   3.1.4
	 */
	public function getSummary()
	{
		return (string) $this->xml->children()->summary;
	}

	/**
	 * Method to get the location of the album
	 *
	 * @return  string  Album location
	 *
	 * @since   3.1.4
	 */
	public function getLocation()
	{
		return (string) $this->xml->children('gphoto', true)->location;
	}

	/**
	 * Method to get the access level of the album
	 *
	 * @return  string  Album access level
	 *
	 * @since   3.1.4
	 */
	public function getAccess()
	{
		return (string) $this->xml->children('gphoto', true)->access;
	}

	/**
	 * Method to get the time of the album
	 *
	 * @return  double  Album time
	 *
	 * @since   3.1.4
	 */
	public function getTime()
	{
		return (double) $this->xml->children('gphoto', true)->timestamp / 1000;
	}

	/**
	 * Method to set the title of the album
	 *
	 * @param   string  $title  New album title
	 *
	 * @return  JGoogleDataPicasaAlbum  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setTitle($title)
	{
		$this->xml->children()->title = $title;

		return $this;
	}

	/**
	 * Method to set the summary of the album
	 *
	 * @param   string  $summary  New album summary
	 *
	 * @return  JGoogleDataPicasaAlbum  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setSummary($summary)
	{
		$this->xml->children()->summary = $summary;

		return $this;
	}

	/**
	 * Method to set the location of the album
	 *
	 * @param   string  $location  New album location
	 *
	 * @return  JGoogleDataPicasaAlbum  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setLocation($location)
	{
		$this->xml->children('gphoto', true)->location = $location;

		return $this;
	}

	/**
	 * Method to set the access level of the album
	 *
	 * @param   string  $access  New album access
	 *
	 * @return  JGoogleDataPicasaAlbum  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAccess($access)
	{
		$this->xml->children('gphoto', true)->access = $access;

		return $this;
	}

	/**
	 * Method to set the time of the album
	 *
	 * @param   int  $time  New album time
	 *
	 * @return  JGoogleDataPicasaAlbum  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setTime($time)
	{
		$this->xml->children('gphoto', true)->timestamp = $time * 1000;

		return $this;
	}

	/**
	 * Method to modify a Picasa Album
	 *
	 * @param   string  $match  Optional eTag matching parameter
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws  Exception
	 */
	public function save($match = '*')
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();

			if ($match === true)
			{
				$match = $this->xml->xpath('./@gd:etag');
				$match = $match[0];
			}

			try
			{
				$headers = array('GData-Version' => 2, 'Content-type' => 'application/atom+xml', 'If-Match' => $match);
				$jdata = $this->query($url, $this->xml->asXml(), $headers, 'put');
			}
			catch (Exception $e)
			{
				if (strpos($e->getMessage(), 'Error code 412 received requesting data: Mismatch: etags') === 0)
				{
					throw new RuntimeException("Etag match failed: `$match`.", $e->getCode(), $e);
				}

				throw $e;
			}

			$this->xml = $this->safeXml($jdata->body);

			return $this;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Refresh Picasa Album
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function refresh()
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink();
			$jdata = $this->query($url, null, array('GData-Version' => 2));
			$this->xml = $this->safeXml($jdata->body);

			return $this;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of Picasa Photos
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function listPhotos()
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getLink('http://schemas.google.com/g/2005#feed');
			$jdata = $this->query($url, null, array('GData-Version' => 2));
			$xml = $this->safeXml($jdata->body);

			if (isset($xml->children()->entry))
			{
				$items = array();

				foreach ($xml->children()->entry as $item)
				{
					$items[] = new JGoogleDataPicasaPhoto($item, $this->options, $this->auth);
				}

				return $items;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Add photo
	 *
	 * @param   string  $file     Path of file to upload
	 * @param   string  $title    Title to give to file (defaults to filename)
	 * @param   string  $summary  Description of the file
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function upload($file, $title = '', $summary = '')
	{
		if ($this->isAuthenticated())
		{
			jimport('joomla.filesystem.file');
			$title = $title != '' ? $title : JFile::getName($file);

			if (!($type = $this->getMime($file)))
			{
				throw new RuntimeException('Inappropriate file type.');
			}

			if (!($data = file_get_contents($file)))
			{
				throw new RuntimeException("Cannot access file: `$file`");
			}

			$xml = new SimpleXMLElement('<entry></entry>');
			$xml->addAttribute('xmlns', 'http://www.w3.org/2005/Atom');
			$xml->addChild('title', $title);
			$xml->addChild('summary', $summary);
			$cat = $xml->addChild('category', '');
			$cat->addAttribute('scheme', 'http://schemas.google.com/g/2005#kind');
			$cat->addAttribute('term', 'http://schemas.google.com/photos/2007#photo');

			$post = "Media multipart posting\n";
			$post .= "--END_OF_PART\n";
			$post .= "Content-Type: application/atom+xml\n\n";
			$post .= $xml->asXml() . "\n";
			$post .= "--END_OF_PART\n";
			$post .= "Content-Type: {$type}\n\n";
			$post .= $data;

			$jdata = $this->query($this->getLink(), $post, array('GData-Version' => 2, 'Content-Type: multipart/related'), 'post');

			return new JGoogleDataPicasaPhoto($this->safeXml($jdata->body), $this->options, $this->auth);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Add photo
	 *
	 * @param   string  $file  Filename
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	protected function getMime($file)
	{
		switch (strtolower(JFile::getExt($file)))
		{
			case 'bmp':
			case 'bm':
			return 'image/bmp';
			case 'gif':
			return 'image/gif';
			case 'jpg':
			case 'jpeg':
			case 'jpe':
			case 'jif':
			case 'jfif':
			case 'jfi':
			return 'image/jpeg';
			case 'png':
			return 'image/png';
			case '3gp':
			return 'video/3gpp';
			case 'avi':
			return 'video/avi';
			case 'mov':
			case 'moov':
			case 'qt':
			return 'video/quicktime';
			case 'mp4':
			case 'm4a':
			case 'm4p':
			case 'm4b':
			case 'm4r':
			case 'm4v':
			return 'video/mp4';
			case 'mpg':
			case 'mpeg':
			case 'mp1':
			case 'mp2':
			case 'mp3':
			case 'm1v':
			case 'm1a':
			case 'm2a':
			case 'mpa':
			case 'mpv':
			return 'video/mpeg';
			case 'asf':
			return 'video/x-ms-asf';
			case 'wmv':
			return 'video/x-ms-wmv';
			default:
			return false;
		}
	}
}
joomla/google/data/calendar.php000064400000036744152177723700012525 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Calendar data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataCalendar extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/calendar');
		}
	}

	/**
	 * Method to remove a calendar from a user's calendar list
	 *
	 * @param   string  $calendarID  ID of calendar to delete
	 *
	 * @return  boolean  Success or failure
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function removeCalendar($calendarID)
	{
		if ($this->isAuthenticated())
		{
			$jdata = $this->query('https://www.googleapis.com/calendar/v3/users/me/calendarList/' . urlencode($calendarID), null, null, 'delete');

			if ($jdata->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get a calendar's settings from Google
	 *
	 * @param   string  $calendarID  ID of calendar to get.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function getCalendar($calendarID)
	{
		if ($this->isAuthenticated())
		{
			$jdata = $this->query('https://www.googleapis.com/calendar/v3/users/me/calendarList/' . urlencode($calendarID));

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to add a calendar to a user's Google Calendar list
	 *
	 * @param   string  $calendarID  New calendar ID
	 * @param   array   $options     New calendar settings
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function addCalendar($calendarID, $options = array())
	{
		if ($this->isAuthenticated())
		{
			$options['id'] = $calendarID;
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendarList';
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'post');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve calendar list from Google
	 *
	 * @param   array  $options   Search settings
	 * @param   int    $maxpages  Maximum number of pages of calendars to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function listCalendars($options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendarList?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to edit a Google Calendar's settings
	 *
	 * @param   string  $calendarID  Calendar ID
	 * @param   array   $options     Calendar settings
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function editCalendarSettings($calendarID, $options)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendarList/' . urlencode($calendarID);
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'put');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to clear a Google Calendar
	 *
	 * @param   string  $calendarID  ID of calendar to clear
	 *
	 * @return  boolean  Success or failure
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function clearCalendar($calendarID)
	{
		if ($this->isAuthenticated())
		{
			$data = $this->query('https://www.googleapis.com/calendar/v3/users/me/calendars/' . urlencode($calendarID) . '/clear', null, null, 'post');

			if ($data->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$data->body}`.");
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to delete a calendar from Google
	 *
	 * @param   string  $calendarID  ID of calendar to delete.
	 *
	 * @return  boolean  Success or failure
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function deleteCalendar($calendarID)
	{
		if ($this->isAuthenticated())
		{
			$data = $this->query('https://www.googleapis.com/calendar/v3/users/me/calendars/' . urlencode($calendarID), null, null, 'delete');

			if ($data->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$data->body}`.");
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to create a Google Calendar
	 *
	 * @param   string  $title    New calendar title
	 * @param   array   $options  New calendar settings
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function createCalendar($title, $options = array())
	{
		if ($this->isAuthenticated())
		{
			$options['summary'] = $title;
			$url = 'https://www.googleapis.com/calendar/v3/calendars';
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'post');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to edit a Google Calendar
	 *
	 * @param   string  $calendarID  Calendar ID.
	 * @param   array   $options     Calendar settings.
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function editCalendar($calendarID, $options)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendars/' . urlencode($calendarID);
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'put');
			$data = json_decode($jdata->body, true);

			if ($data && array_key_exists('items', $data))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to delete an event from a Google Calendar
	 *
	 * @param   string  $calendarID  ID of calendar to delete from
	 * @param   string  $eventID     ID of event to delete.
	 *
	 * @return  boolean  Success or failure.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function deleteEvent($calendarID, $eventID)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendars/' . urlencode($calendarID) . '/events/' . urlencode($eventID);
			$data = $this->query($url, null, null, 'delete');

			if ($data->body != '')
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$data->body}`.");
			}

			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get an event from a Google Calendar
	 *
	 * @param   string  $calendarID  ID of calendar
	 * @param   string  $eventID     ID of event to get
	 * @param   array   $options     Options to send to Google
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function getEvent($calendarID, $eventID, $options = array())
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendarList/';
			$url .= urlencode($calendarID) . '/events/' . urlencode($eventID) . '?' . http_build_query($options);
			$jdata = $this->query($url);

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to create a Google Calendar event
	 *
	 * @param   string   $calendarID  ID of calendar
	 * @param   mixed    $start       Event start time
	 * @param   mixed    $end         Event end time
	 * @param   array    $options     New event settings
	 * @param   mixed    $timezone    Timezone for event
	 * @param   boolean  $allday      Treat event as an all-day event
	 * @param   boolean  $notify      Notify participants
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws InvalidArgumentException
	 * @throws UnexpectedValueException
	 */
	public function createEvent($calendarID, $start, $end = false, $options = array(), $timezone = false, $allday = false, $notify = false)
	{
		if ($this->isAuthenticated())
		{
			if (!$start)
			{
				$startobj = new DateTime;
			}
			elseif (is_int($start))
			{
				$startobj = new DateTime;
				$startobj->setTimestamp($start);
			}
			elseif (is_string($start))
			{
				$startobj = new DateTime($start);
			}
			elseif (is_a($start, 'DateTime'))
			{
				$startobj = $start;
			}
			else
			{
				throw new InvalidArgumentException('Invalid event start time.');
			}

			if (!$end)
			{
				$endobj = $startobj;
			}
			elseif (is_int($end))
			{
				$endobj = new DateTime;
				$endobj->setTimestamp($end);
			}
			elseif (is_string($end))
			{
				$endobj = new DateTime($end);
			}
			elseif (is_a($end, 'DateTime'))
			{
				$endobj = $end;
			}
			else
			{
				throw new InvalidArgumentException('Invalid event end time.');
			}

			if ($allday)
			{
				$options['start'] = array('date' => $startobj->format('Y-m-d'));
				$options['end'] = array('date' => $endobj->format('Y-m-d'));
			}
			else
			{
				$options['start'] = array('dateTime' => $startobj->format(DateTime::RFC3339));
				$options['end'] = array('dateTime' => $endobj->format(DateTime::RFC3339));
			}

			if ($timezone === true)
			{
				$options['start']['timeZone'] = $startobj->getTimezone()->getName();
				$options['end']['timeZone'] = $endobj->getTimezone()->getName();
			}
			elseif (is_a($timezone, 'DateTimeZone'))
			{
				$options['start']['timeZone'] = $timezone->getName();
				$options['end']['timeZone'] = $timezone->getName();
			}
			elseif (is_string($timezone))
			{
				$options['start']['timeZone'] = $timezone;
				$options['end']['timeZone'] = $timezone;
			}

			$url = 'https://www.googleapis.com/calendar/v3/calendars/' . urlencode($calendarID) . '/events' . ($notify ? '?sendNotifications=true' : '');
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'post');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of events on a Google calendar
	 *
	 * @param   string  $calendarID  Calendar ID
	 * @param   string  $eventID     ID of the event to change
	 * @param   array   $options     Search settings
	 * @param   int     $maxpages    Minimum number of events to retrieve (more may be retrieved depending on page size)
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function listRecurrences($calendarID, $eventID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/calendar/v3/users/me/calendars/' . urlencode($calendarID) . '/events/' . urlencode($eventID) . '/instances';
			$url .= '?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of events on a Google calendar
	 *
	 * @param   string  $calendarID  Calendar ID
	 * @param   array   $options     Calendar settings
	 * @param   int     $maxpages    Cycle through pages of data to generate a complete list
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function listEvents($calendarID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/calendar/v3/calendars/' . urlencode($calendarID) . '/events?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to move an event from one calendar to another
	 *
	 * @param   string   $calendarID  Calendar ID
	 * @param   string   $eventID     ID of the event to change
	 * @param   string   $destID      Calendar ID
	 * @param   boolean  $notify      Notify participants of changes
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function moveEvent($calendarID, $eventID, $destID, $notify = false)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/calendars/' . urlencode($calendarID) . '/events/' . urlencode($eventID) . '/move';
			$url .= '?destination=' . $destID . ($notify ? '&sendNotifications=true' : '');
			$jdata = $this->query($url, null, null, 'post');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to edit a Google Calendar event
	 *
	 * @param   string   $calendarID  Calendar ID
	 * @param   string   $eventID     ID of the event to change
	 * @param   array    $options     Event settings
	 * @param   boolean  $notify      Notify participants of changes
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function editEvent($calendarID, $eventID, $options, $notify = false)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/calendar/v3/calendars/';
			$url .= urlencode($calendarID) . '/events/' . urlencode($eventID) . ($notify ? '?sendNotifications=true' : '');
			$jdata = $this->query($url, json_encode($options), array('Content-type' => 'application/json'), 'put');

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/adsense.php000064400000026057152177723700012372 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Adsense data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataAdsense extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/adsense');
		}
	}

	/**
	 * Method to get an Adsense account's settings from Google
	 *
	 * @param   string   $accountID    ID of account to get
	 * @param   boolean  $subaccounts  Include list of subaccounts
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function getAccount($accountID, $subaccounts = true)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . ($subaccounts ? '?tree=true' : '');
			$jdata = $this->query($url);

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense accounts from Google
	 *
	 * @param   array  $options   Search settings
	 * @param   int    $maxpages  Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listAccounts($options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense clients from Google
	 *
	 * @param   string  $accountID  ID of account to list the clients from
	 * @param   array   $options    Search settings
	 * @param   int     $maxpages   Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listClients($accountID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . '/adclients?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get an AdSense AdUnit
	 *
	 * @param   string  $accountID   ID of account to get
	 * @param   string  $adclientID  ID of client to get
	 * @param   string  $adunitID    ID of adunit to get
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function getUnit($accountID, $adclientID, $adunitID)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID);
			$url .= '/adclients/' . urlencode($adclientID) . '/adunits/' . urlencode($adunitID);
			$jdata = $this->query($url);

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense Custom Channels for a specific Adunit
	 *
	 * @param   string  $accountID   ID of account
	 * @param   string  $adclientID  ID of client
	 * @param   string  $adunitID    ID of adunit to list channels from
	 * @param   array   $options     Search settings
	 * @param   int     $maxpages    Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listUnitChannels($accountID, $adclientID, $adunitID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID);
			$url .= '/adclients/' . urlencode($adclientID) . '/adunits/' . urlencode($adunitID) . '/customchannels?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get an Adsense Channel
	 *
	 * @param   string  $accountID   ID of account to get
	 * @param   string  $adclientID  ID of client to get
	 * @param   string  $channelID   ID of channel to get
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function getChannel($accountID, $adclientID, $channelID)
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . '/adclients/';
			$url .= urlencode($adclientID) . '/customchannels/' . urlencode($channelID);
			$jdata = $this->query($url);

			if ($data = json_decode($jdata->body, true))
			{
				return $data;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense Custom Channels
	 *
	 * @param   string  $accountID   ID of account
	 * @param   string  $adclientID  ID of client to list channels from
	 * @param   array   $options     Search settings
	 * @param   int     $maxpages    Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listChannels($accountID, $adclientID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . '/adclients/' . urlencode($adclientID);
			$url .= '/customchannels?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense Adunits for a specific Custom Channel
	 *
	 * @param   string  $accountID   ID of account
	 * @param   string  $adclientID  ID of client
	 * @param   string  $channelID   ID of channel to list units from
	 * @param   array   $options     Search settings
	 * @param   int     $maxpages    Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listChannelUnits($accountID, $adclientID, $channelID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . '/adclients/' . urlencode($adclientID);
			$url .= '/customchannels/' . urlencode($channelID) . '/adunits?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to generate a report from Google AdSense
	 *
	 * @param   string  $accountID   ID of account
	 * @param   string  $adclientID  ID of client
	 * @param   array   $options     Search settings
	 * @param   int     $maxpages    Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  UnexpectedValueException
	 */
	public function listUrlChannels($accountID, $adclientID, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			$next = array_key_exists('nextPageToken', $options) ? $options['nextPage'] : null;
			unset($options['nextPageToken']);
			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID);
			$url .= '/adclients/' . urlencode($adclientID) . '/urlchannels?' . http_build_query($options);

			return $this->listGetData($url, $maxpages, $next);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to retrieve a list of AdSense Channel URLs
	 *
	 * @param   string  $accountID  ID of account
	 * @param   mixed   $start      Start day
	 * @param   mixed   $end        End day
	 * @param   array   $options    Search settings
	 * @param   int     $maxpages   Maximum number of pages of accounts to return
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws  InvalidArgumentException
	 * @throws  UnexpectedValueException
	 */
	public function generateReport($accountID, $start, $end = false, $options = array(), $maxpages = 1)
	{
		if ($this->isAuthenticated())
		{
			if (is_int($start))
			{
				$startobj = new DateTime;
				$startobj->setTimestamp($start);
			}
			elseif (is_string($start))
			{
				$startobj = new DateTime($start);
			}
			elseif (is_a($start, 'DateTime'))
			{
				$startobj = $start;
			}
			else
			{
				throw new InvalidArgumentException('Invalid start time.');
			}

			if (!$end)
			{
				$endobj = new DateTime;
			}
			elseif (is_int($end))
			{
				$endobj = new DateTime;
				$endobj->setTimestamp($end);
			}
			elseif (is_string($end))
			{
				$endobj = new DateTime($end);
			}
			elseif (is_a($end, 'DateTime'))
			{
				$endobj = $end;
			}
			else
			{
				throw new InvalidArgumentException('Invalid end time.');
			}

			$options['startDate'] = $startobj->format('Y-m-d');
			$options['endDate'] = $endobj->format('Y-m-d');

			unset($options['startIndex']);

			$url = 'https://www.googleapis.com/adsense/v1.1/accounts/' . urlencode($accountID) . '/reports?' . http_build_query($options);

			if (strpos($url, '&'))
			{
				$url .= '&';
			}

			$i = 0;
			$data['rows'] = array();

			do
			{
				$jdata = $this->query($url . 'startIndex=' . count($data['rows']));
				$newdata = json_decode($jdata->body, true);

				if ($newdata && array_key_exists('rows', $newdata))
				{
					$newdata['rows'] = array_merge($data['rows'], $newdata['rows']);
					$data = $newdata;
				}
				else
				{
					throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
				}

				$i++;
			}
			while (count($data['rows']) < $data['totalMatchedRows'] && $i < $maxpages);

			return $data;
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/plus.php000064400000004310152177723700011717 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google+ data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPlus extends JGoogleData
{
	/**
	 * @var    JGoogleDataPlusPeople  Google+ API object for people.
	 * @since  3.1.4
	 */
	protected $people;

	/**
	 * @var    JGoogleDataPlusActivities  Google+ API object for people.
	 * @since  3.1.4
	 */
	protected $activities;

	/**
	 * @var    JGoogleDataPlusComments  Google+ API object for people.
	 * @since  3.1.4
	 */
	protected $comments;

	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		// Setup the default API url if not already set.
		$options->def('api.url', 'https://www.googleapis.com/plus/v1/');

		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/plus.me');
		}
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JGoogleDataPlus  Google+ API object (people, activities, comments).
	 *
	 * @since   3.1.4
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'people':
				if ($this->people == null)
				{
					$this->people = new JGoogleDataPlusPeople($this->options, $this->auth);
				}

				return $this->people;

			case 'activities':
				if ($this->activities == null)
				{
					$this->activities = new JGoogleDataPlusActivities($this->options, $this->auth);
				}

				return $this->activities;

			case 'comments':
				if ($this->comments == null)
				{
					$this->comments = new JGoogleDataPlusComments($this->options, $this->auth);
				}

				return $this->comments;
		}
	}
}
joomla/google/data/picasa.php000064400000010036152177723700012176 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Picasa data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPicasa extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://picasaweb.google.com/data/');
		}
	}

	/**
	 * Method to retrieve a list of Picasa Albums
	 *
	 * @param   string  $userID  ID of user
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function listAlbums($userID = 'default')
	{
		if ($this->isAuthenticated())
		{
			$url = 'https://picasaweb.google.com/data/feed/api/user/' . urlencode($userID);
			$jdata = $this->query($url, null, array('GData-Version' => 2));
			$xml = $this->safeXml($jdata->body);

			if (isset($xml->children()->entry))
			{
				$items = array();

				foreach ($xml->children()->entry as $item)
				{
					$items[] = new JGoogleDataPicasaAlbum($item, $this->options, $this->auth);
				}

				return $items;
			}
			else
			{
				throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
			}
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to create a Picasa Album
	 *
	 * @param   string  $userID    ID of user
	 * @param   string  $title     New album title
	 * @param   string  $access    New album access settings
	 * @param   string  $summary   New album summary
	 * @param   string  $location  New album location
	 * @param   int     $time      New album timestamp
	 * @param   array   $keywords  New album keywords
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 */
	public function createAlbum($userID = 'default', $title = '', $access = 'private', $summary = '', $location = '', $time = false, $keywords = array())
	{
		if ($this->isAuthenticated())
		{
			$time = $time ? $time : time();
			$title = $title != '' ? $title : date('F j, Y');
			$xml = new SimpleXMLElement('<entry></entry>');
			$xml->addAttribute('xmlns', 'http://www.w3.org/2005/Atom');
			$xml->addChild('title', $title);
			$xml->addChild('summary', $summary);
			$xml->addChild('gphoto:location', $location, 'http://schemas.google.com/photos/2007');
			$xml->addChild('gphoto:access', $access);
			$xml->addChild('gphoto:timestamp', $time);
			$media = $xml->addChild('media:group', '', 'http://search.yahoo.com/mrss/');
			$media->addChild('media:keywords', implode($keywords, ', '));
			$cat = $xml->addChild('category', '');
			$cat->addAttribute('scheme', 'http://schemas.google.com/g/2005#kind');
			$cat->addAttribute('term', 'http://schemas.google.com/photos/2007#album');

			$url = 'https://picasaweb.google.com/data/feed/api/user/' . urlencode($userID);
			$jdata = $this->query($url, $xml->asXml(), array('GData-Version' => 2, 'Content-type' => 'application/atom+xml'), 'post');

			$xml = $this->safeXml($jdata->body);

			return new JGoogleDataPicasaAlbum($xml, $this->options, $this->auth);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get Picasa Album
	 *
	 * @param   string  $url  URL of album to get
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	public function getAlbum($url)
	{
		if ($this->isAuthenticated())
		{
			$jdata = $this->query($url, null, array('GData-Version' => 2));
			$xml = $this->safeXml($jdata->body);

			return new JGoogleDataPicasaAlbum($xml, $this->options, $this->auth);
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/plus/activities.php000064400000012556152177723700014076 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google+ data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPlusActivities extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
	parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/plus.me');
		}
	}

	/**
	 * List all of the activities in the specified collection for a particular user.
	 *
	 * @param   string   $userId      The ID of the user to get activities for. The special value "me" can be used to indicate the authenticated user.
	 * @param   string   $collection  The collection of activities to list. Acceptable values are: "public".
	 * @param   string   $fields      Used to specify the fields you want returned.
	 * @param   integer  $max         The maximum number of people to include in the response, used for paging.
	 * @param   string   $token       The continuation token, used to page through large result sets. To get the next page of results, set this
	 *								  parameter to the value of "nextPageToken" from the previous response. This token may be of any length.
	 * @param   string   $alt         Specifies an alternative representation type. Acceptable values are: "json" - Use JSON format (default)
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function listActivities($userId, $collection, $fields = null, $max = 10, $token = null, $alt = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'people/' . $userId . '/activities/' . $collection;

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			// Check if max is specified.
			if ($max != 10)
			{
				$url .= (strpos($url, '?') === false) ? '?maxResults=' : '&maxResults=';
				$url .= $max;
			}

			// Check if token is specified.
			if ($token)
			{
				$url .= (strpos($url, '?') === false) ? '?pageToken=' : '&pageToken=';
				$url .= $token;
			}

			// Check if alt is specified.
			if ($alt)
			{
				$url .= (strpos($url, '?') === false) ? '?alt=' : '&alt=';
				$url .= $alt;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get an activity.
	 *
	 * @param   string  $id      The ID of the activity to get.
	 * @param   string  $fields  Used to specify the fields you want returned.
	 * @param   string  $alt     Specifies an alternative representation type. Acceptable values are: "json" - Use JSON format (default)
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function getActivity($id, $fields = null, $alt = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'activities/' . $id;

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			// Check if alt is specified.
			if ($alt)
			{
				$url .= (strpos($url, '?') === false) ? '?alt=' : '&alt=';
				$url .= $alt;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Search all public activities.
	 *
	 * @param   string   $query     Full-text search query string.
	 * @param   string   $fields    Used to specify the fields you want returned.
	 * @param   string   $language  Specify the preferred language to search with. https://developers.google.com/+/api/search#available-languages
	 * @param   integer  $max       The maximum number of people to include in the response, used for paging.
	 * @param   string   $order     Specifies how to order search results. Acceptable values are "best" and "recent".
	 * @param   string   $token     The continuation token, used to page through large result sets. To get the next page of results, set this
	 * 								parameter to the value of "nextPageToken" from the previous response. This token may be of any length.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function search($query, $fields = null, $language = null, $max = 10, $order = null, $token = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'activities?query=' . urlencode($query);

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '&fields=' . $fields;
			}

			// Check if language is specified.
			if ($language)
			{
				$url .= '&language=' . $language;
			}

			// Check if max is specified.
			if ($max != 10)
			{
				$url .= '&maxResults=' . $max;
			}

			// Check if order is specified.
			if ($order)
			{
				$url .= '&orderBy=' . $order;
			}

			// Check of token is specified.
			if ($token)
			{
				$url .= '&pageToken=' . $token;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/plus/comments.php000064400000006737152177723700013563 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google+ data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPlusComments extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/plus.me');
		}
	}

	/**
	 * List all of the comments for an activity.
	 *
	 * @param   string   $activityId  The ID of the activity to get comments for.
	 * @param   string   $fields      Used to specify the fields you want returned.
	 * @param   integer  $max         The maximum number of people to include in the response, used for paging.
	 * @param   string   $order       The order in which to sort the list of comments. Acceptable values are "ascending" and "descending".
	 * @param   string   $token       The continuation token, used to page through large result sets. To get the next page of results, set this
	 * 								  parameter to the value of "nextPageToken" from the previous response. This token may be of any length.
	 * @param   string   $alt         Specifies an alternative representation type. Acceptable values are: "json" - Use JSON format (default)
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function listComments($activityId, $fields = null, $max = 20, $order = null, $token = null, $alt = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'activities/' . $activityId . '/comments';

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			// Check if max is specified.
			if ($max != 20)
			{
				$url .= (strpos($url, '?') === false) ? '?maxResults=' : '&maxResults=';
				$url .= $max;
			}

			// Check if order is specified.
			if ($order)
			{
				$url .= (strpos($url, '?') === false) ? '?orderBy=' : '&orderBy=';
				$url .= $order;
			}

			// Check of token is specified.
			if ($token)
			{
				$url .= (strpos($url, '?') === false) ? '?pageToken=' : '&pageToken=';
				$url .= $token;
			}

			// Check if alt is specified.
			if ($alt)
			{
				$url .= (strpos($url, '?') === false) ? '?alt=' : '&alt=';
				$url .= $alt;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get a comment.
	 *
	 * @param   string  $id      The ID of the comment to get.
	 * @param   string  $fields  Used to specify the fields you want returned.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function getComment($id, $fields = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'comments/' . $id;

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}
}
joomla/google/data/plus/people.php000064400000011244152177723700013207 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google+ data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleDataPlusPeople extends JGoogleData
{
	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object
	 * @param   JGoogleAuth  $auth     Google data http client object
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		parent::__construct($options, $auth);

		if (isset($this->auth) && !$this->auth->getOption('scope'))
		{
			$this->auth->setOption('scope', 'https://www.googleapis.com/auth/plus.me');
		}
	}

	/**
	 * Get a person's profile.
	 *
	 * @param   string  $id      The ID of the person to get the profile for. The special value "me" can be used to indicate the authenticated user.
	 * @param   string  $fields  Used to specify the fields you want returned.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function getPeople($id, $fields = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'people/' . $id;

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Search all public profiles.
	 *
	 * @param   string   $query     Specify a query string for full text search of public text in all profiles.
	 * @param   string   $fields    Used to specify the fields you want returned.
	 * @param   string   $language  Specify the preferred language to search with. https://developers.google.com/+/api/search#available-languages
	 * @param   integer  $max       The maximum number of people to include in the response, used for paging.
	 * @param   string   $token     The continuation token, used to page through large result sets. To get the next page of results, set this
	 * 								parameter to the value of "nextPageToken" from the previous response. This token may be of any length.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function search($query, $fields = null, $language = null, $max = 10, $token = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'people?query=' . urlencode($query);

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '&fields=' . $fields;
			}

			// Check if language is specified.
			if ($language)
			{
				$url .= '&language=' . $language;
			}

			// Check if max is specified.
			if ($max != 10)
			{
				$url .= '&maxResults=' . $max;
			}

			// Check of token is specified.
			if ($token)
			{
				$url .= '&pageToken=' . $token;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}

	/**
	 * List all of the people in the specified collection for a particular activity.
	 *
	 * @param   string   $activityId  The ID of the activity to get the list of people for.
	 * @param   string   $collection  The collection of people to list. Acceptable values are "plusoners" and "resharers".
	 * @param   string   $fields      Used to specify the fields you want returned.
	 * @param   integer  $max         The maximum number of people to include in the response, used for paging.
	 * @param   string   $token       The continuation token, used to page through large result sets. To get the next page of results, set this
	 * 								  parameter to the value of "nextPageToken" from the previous response. This token may be of any length.
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 */
	public function listByActivity($activityId, $collection, $fields = null, $max = 10, $token = null)
	{
		if ($this->isAuthenticated())
		{
			$url = $this->getOption('api.url') . 'activities/' . $activityId . '/people/' . $collection;

			// Check if fields is specified.
			if ($fields)
			{
				$url .= '?fields=' . $fields;
			}

			// Check if max is specified.
			if ($max != 10)
			{
				$url .= (strpos($url, '?') === false) ? '?maxResults=' : '&maxResults=';
				$url .= $max;
			}

			// Check of token is specified.
			if ($token)
			{
				$url .= (strpos($url, '?') === false) ? '?pageToken=' : '&pageToken=';
				$url .= $token;
			}

			$jdata = $this->auth->query($url);

			return json_decode($jdata->body, true);
		}
		else
		{
			return false;
		}
	}
}
joomla/google/google.php000064400000007131152177723700011303 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with the Google APIs.
 *
 * @property-read  JGoogleData    $data    Google API object for data.
 * @property-read  JGoogleEmbed   $embed   Google API object for embed generation.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogle
{
	/**
	 * @var    Registry  Options for the Google object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JGoogleAuth  The authentication client object to use in sending authenticated HTTP requests.
	 * @since  3.1.4
	 */
	protected $auth;

	/**
	 * @var    JGoogleData  Google API object for data request.
	 * @since  3.1.4
	 */
	protected $data;

	/**
	 * @var    JGoogleEmbed  Google API object for embed generation.
	 * @since  3.1.4
	 */
	protected $embed;

	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object.
	 * @param   JGoogleAuth  $auth     The authentication client object.
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->auth  = isset($auth) ? $auth : new JGoogleAuthOauth2($this->options);
	}

	/**
	 * Method to create JGoogleData objects
	 *
	 * @param   string       $name     Name of property to retrieve
	 * @param   Registry     $options  Google options object.
	 * @param   JGoogleAuth  $auth     The authentication client object.
	 *
	 * @return  JGoogleData  Google data API object.
	 *
	 * @since   3.1.4
	 */
	public function data($name, $options = null, $auth = null)
	{
		if ($this->options && !$options)
		{
			$options = $this->options;
		}

		if ($this->auth && !$auth)
		{
			$auth = $this->auth;
		}

		switch ($name)
		{
			case 'plus':
			case 'Plus':
				return new JGoogleDataPlus($options, $auth);
			case 'picasa':
			case 'Picasa':
				return new JGoogleDataPicasa($options, $auth);
			case 'adsense':
			case 'Adsense':
				return new JGoogleDataAdsense($options, $auth);
			case 'calendar':
			case 'Calendar':
				return new JGoogleDataCalendar($options, $auth);
			default:
				return;
		}
	}

	/**
	 * Method to create JGoogleEmbed objects
	 *
	 * @param   string    $name     Name of property to retrieve
	 * @param   Registry  $options  Google options object.
	 *
	 * @return  JGoogleEmbed  Google embed API object.
	 *
	 * @since   3.1.4
	 */
	public function embed($name, $options = null)
	{
		if ($this->options && !$options)
		{
			$options = $this->options;
		}

		switch ($name)
		{
			case 'maps':
			case 'Maps':
				return new JGoogleEmbedMaps($options);
			case 'analytics':
			case 'Analytics':
				return new JGoogleEmbedAnalytics($options);
			default:
				return;
		}
	}

	/**
	 * Get an option from the JGoogle instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JGoogle instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JGoogle  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/google/data.php000064400000010164152177723700010740 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google API data class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
abstract class JGoogleData
{
	/**
	 * @var    Registry  Options for the Google data object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JGoogleAuth  Authentication client for the Google data object.
	 * @since  3.1.4
	 */
	protected $auth;

	/**
	 * Constructor.
	 *
	 * @param   Registry     $options  Google options object.
	 * @param   JGoogleAuth  $auth     Google data http client object.
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JGoogleAuth $auth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->auth = isset($auth) ? $auth : new JGoogleAuthOauth2($this->options);
	}

	/**
	 * Method to authenticate to Google
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1.4
	 */
	public function authenticate()
	{
		return $this->auth->authenticate();
	}

	/**
	 * Check authentication
	 *
	 * @return  boolean  True if authenticated.
	 *
	 * @since   3.1.4
	 */
	public function isAuthenticated()
	{
		return $this->auth->isAuthenticated();
	}

	/**
	 * Method to validate XML
	 *
	 * @param   string  $data  XML data to be parsed
	 *
	 * @return  SimpleXMLElement  XMLElement of parsed data
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	protected static function safeXml($data)
	{
		try
		{
			return new SimpleXMLElement($data, LIBXML_NOWARNING | LIBXML_NOERROR);
		}
		catch (Exception $e)
		{
			throw new UnexpectedValueException("Unexpected data received from Google: `$data`.", $e->getCode(), $e);
		}
	}

	/**
	 * Method to retrieve a list of data
	 *
	 * @param   array   $url       URL to GET
	 * @param   int     $maxpages  Maximum number of pages to return
	 * @param   string  $token     Next page token
	 *
	 * @return  mixed  Data from Google
	 *
	 * @since   3.1.4
	 * @throws UnexpectedValueException
	 */
	protected function listGetData($url, $maxpages = 1, $token = null)
	{
		$qurl = $url;

		if (strpos($url, '&') && isset($token))
		{
			$qurl .= '&pageToken=' . $token;
		}
		elseif (isset($token))
		{
			$qurl .= 'pageToken=' . $token;
		}

		$jdata = $this->query($qurl);
		$data = json_decode($jdata->body, true);

		if ($data && array_key_exists('items', $data))
		{
			if ($maxpages != 1 && array_key_exists('nextPageToken', $data))
			{
				$data['items'] = array_merge($data['items'], $this->listGetData($url, $maxpages - 1, $data['nextPageToken']));
			}

			return $data['items'];
		}
		elseif ($data)
		{
			return array();
		}
		else
		{
			throw new UnexpectedValueException("Unexpected data received from Google: `{$jdata->body}`.");
		}
	}

	/**
	 * Method to retrieve data from Google
	 *
	 * @param   string  $url      The URL for the request.
	 * @param   mixed   $data     The data to include in the request.
	 * @param   array   $headers  The headers to send with the request.
	 * @param   string  $method   The type of http request to send.
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 */
	protected function query($url, $data = null, $headers = null, $method = 'get')
	{
		return $this->auth->query($url, $data, $headers, $method);
	}

	/**
	 * Get an option from the JGoogleData instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JGoogleData instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JGoogleData  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/google/embed.php000064400000004750152177723700011107 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google API object class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
abstract class JGoogleEmbed
{
	/**
	 * @var    Registry  Options for the Google data object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JUri  URI of the page being rendered.
	 * @since  3.1.4
	 */
	protected $uri;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  Google options object
	 * @param   JUri      $uri      URL of the page being rendered
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JUri $uri = null)
	{
		$this->options = $options ? $options : new Registry;
		$this->uri = $uri ? $uri : JUri::getInstance();
	}

	/**
	 * Method to retrieve the javascript header for the embed API
	 *
	 * @return  string  The header
	 *
	 * @since   3.1.4
	 */
	public function isSecure()
	{
		return $this->uri->getScheme() == 'https';
	}

	/**
	 * Method to retrieve the header for the API
	 *
	 * @return  string  The header
	 *
	 * @since   3.1.4
	 */
	abstract public function getHeader();

	/**
	 * Method to retrieve the body for the API
	 *
	 * @return  string  The body
	 *
	 * @since   3.1.4
	 */
	abstract public function getBody();

	/**
	 * Method to output the javascript header for the embed API
	 *
	 * @return  null
	 *
	 * @since   3.1.4
	 */
	public function echoHeader()
	{
		echo $this->getHeader();
	}

	/**
	 * Method to output the body for the API
	 *
	 * @return  null
	 *
	 * @since   3.1.4
	 */
	public function echoBody()
	{
		echo $this->getBody();
	}

	/**
	 * Get an option from the JGoogleEmbed instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JGoogleEmbed instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JGoogleEmbed  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/google/auth/oauth2.php000064400000006020152177723700012166 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google OAuth authentication class
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleAuthOauth2 extends JGoogleAuth
{
	/**
	 * @var    JOAuth2Client  OAuth client for the Google authentication object.
	 * @since  3.1.4
	 */
	protected $client;

	/**
	 * Constructor.
	 *
	 * @param   Registry       $options  JGoogleAuth options object.
	 * @param   JOAuth2Client  $client   OAuth client for Google authentication.
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JOAuth2Client $client = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JOAuth2Client($this->options);
	}

	/**
	 * Method to authenticate to Google
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1.4
	 */
	public function authenticate()
	{
		$this->googlize();

		return $this->client->authenticate();
	}

	/**
	 * Verify if the client has been authenticated
	 *
	 * @return  boolean  Is authenticated
	 *
	 * @since   3.1.4
	 */
	public function isAuthenticated()
	{
		return $this->client->isAuthenticated();
	}

	/**
	 * Method to retrieve data from Google
	 *
	 * @param   string  $url      The URL for the request.
	 * @param   mixed   $data     The data to include in the request.
	 * @param   array   $headers  The headers to send with the request.
	 * @param   string  $method   The type of http request to send.
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 */
	public function query($url, $data = null, $headers = null, $method = 'get')
	{
		$this->googlize();

		return $this->client->query($url, $data, $headers, $method);
	}

	/**
	 * Method to fill in Google-specific OAuth settings
	 *
	 * @return  JOAuth2Client  Google-configured Oauth2 client.
	 *
	 * @since   3.1.4
	 */
	protected function googlize()
	{
		if (!$this->client->getOption('authurl'))
		{
			$this->client->setOption('authurl', 'https://accounts.google.com/o/oauth2/auth');
		}

		if (!$this->client->getOption('tokenurl'))
		{
			$this->client->setOption('tokenurl', 'https://accounts.google.com/o/oauth2/token');
		}

		if (!$this->client->getOption('requestparams'))
		{
			$this->client->setOption('requestparams', array());
		}

		$params = $this->client->getOption('requestparams');

		if (!array_key_exists('access_type', $params))
		{
			$params['access_type'] = 'offline';
		}

		if ($params['access_type'] == 'offline' && $this->client->getOption('userefresh') === null)
		{
			$this->client->setOption('userefresh', true);
		}

		if (!array_key_exists('approval_prompt', $params))
		{
			$params['approval_prompt'] = 'auto';
		}

		$this->client->setOption('requestparams', $params);

		return $this->client;
	}
}
joomla/google/auth.php000064400000003735152177723700010776 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Google authentication class abstract
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
abstract class JGoogleAuth
{
	/**
	 * @var    \Joomla\Registry\Registry  Options for the Google authentication object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * Abstract method to authenticate to Google
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.1.4
	 */
	abstract public function authenticate();

	/**
	 * Verify if the client has been authenticated
	 *
	 * @return  boolean  Is authenticated
	 *
	 * @since   3.1.4
	 */
	abstract public function isAuthenticated();

	/**
	 * Abstract method to retrieve data from Google
	 *
	 * @param   string  $url      The URL for the request.
	 * @param   mixed   $data     The data to include in the request.
	 * @param   array   $headers  The headers to send with the request.
	 * @param   string  $method   The type of http request to send.
	 *
	 * @return  mixed  Data from Google.
	 *
	 * @since   3.1.4
	 */
	abstract public function query($url, $data = null, $headers = null, $method = 'get');

	/**
	 * Get an option from the JGoogleAuth object.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JGoogleAuth object.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JGoogleAuth  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/google/embed/maps.php000064400000041726152177723700012053 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Google Maps embed class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleEmbedMaps extends JGoogleEmbed
{
	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $http;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  Google options object
	 * @param   JUri      $uri      URL of the page being rendered
	 * @param   JHttp     $http     Http client for geocoding requests
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JUri $uri = null, JHttp $http = null)
	{
		parent::__construct($options, $uri);
		$this->http = $http ? $http : new JHttp($this->options);
	}

	/**
	 * Method to get the API key
	 *
	 * @return  string  The Google Maps API key
	 *
	 * @since   3.1.4
	 */
	public function getKey()
	{
		return $this->getOption('key');
	}

	/**
	 * Method to set the API key
	 *
	 * @param   string  $key  The Google Maps API key
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setKey($key)
	{
		$this->setOption('key', $key);

		return $this;
	}

	/**
	 * Method to get the id of the map div
	 *
	 * @return  string  The ID
	 *
	 * @since   3.1.4
	 */
	public function getMapId()
	{
		return $this->getOption('mapid') ? $this->getOption('mapid') : 'map_canvas';
	}

	/**
	 * Method to set the map div id
	 *
	 * @param   string  $id  The ID
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setMapId($id)
	{
		$this->setOption('mapid', $id);

		return $this;
	}

	/**
	 * Method to get the class of the map div
	 *
	 * @return  string  The class
	 *
	 * @since   3.1.4
	 */
	public function getMapClass()
	{
		return $this->getOption('mapclass') ? $this->getOption('mapclass') : '';
	}

	/**
	 * Method to set the map div class
	 *
	 * @param   string  $class  The class
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setMapClass($class)
	{
		$this->setOption('mapclass', $class);

		return $this;
	}

	/**
	 * Method to get the style of the map div
	 *
	 * @return  string  The style
	 *
	 * @since   3.1.4
	 */
	public function getMapStyle()
	{
		return $this->getOption('mapstyle') ? $this->getOption('mapstyle') : '';
	}

	/**
	 * Method to set the map div style
	 *
	 * @param   string  $style  The style
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setMapStyle($style)
	{
		$this->setOption('mapstyle', $style);

		return $this;
	}

	/**
	 * Method to get the map type setting
	 *
	 * @return  string  The class
	 *
	 * @since   3.1.4
	 */
	public function getMapType()
	{
		return $this->getOption('maptype') ? $this->getOption('maptype') : 'ROADMAP';
	}

	/**
	 * Method to set the map type ()
	 *
	 * @param   string  $type  Valid types are ROADMAP, SATELLITE, HYBRID, and TERRAIN
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setMapType($type)
	{
		$this->setOption('maptype', strtoupper($type));

		return $this;
	}

	/**
	 * Method to get additional map options
	 *
	 * @return  string  The options
	 *
	 * @since   3.1.4
	 */
	public function getAdditionalMapOptions()
	{
		return $this->getOption('mapoptions') ? $this->getOption('mapoptions') : array();
	}

	/**
	 * Method to add additional map options
	 *
	 * @param   array  $options  Additional map options
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAdditionalMapOptions($options)
	{
		$this->setOption('mapoptions', $options);

		return $this;
	}

	/**
	 * Method to get additional map options
	 *
	 * @return  string  The options
	 *
	 * @since   3.1.4
	 */
	public function getAdditionalJavascript()
	{
		return $this->getOption('extrascript') ? $this->getOption('extrascript') : '';
	}

	/**
	 * Method to add additional javascript
	 *
	 * @param   array  $script  Additional javascript
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAdditionalJavascript($script)
	{
		$this->setOption('extrascript', $script);

		return $this;
	}

	/**
	 * Method to get the zoom
	 *
	 * @return  int  The zoom level
	 *
	 * @since   3.1.4
	 */
	public function getZoom()
	{
		return $this->getOption('zoom') ? $this->getOption('zoom') : 0;
	}

	/**
	 * Method to set the map zoom
	 *
	 * @param   int  $zoom  Zoom level (0 is whole world)
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setZoom($zoom)
	{
		$this->setOption('zoom', $zoom);

		return $this;
	}

	/**
	 * Method to set the center of the map
	 *
	 * @return  mixed  A latitude longitude array or an address string
	 *
	 * @since   3.1.4
	 */
	public function getCenter()
	{
		return $this->getOption('mapcenter') ? $this->getOption('mapcenter') : array(0, 0);
	}

	/**
	 * Method to set the center of the map
	 *
	 * @param   mixed  $location       A latitude/longitude array or an address string
	 * @param   mixed  $title          Title of marker or false for no marker
	 * @param   array  $markeroptions  Options for marker
	 * @param   array  $markerevents   Events for marker
	 *
	 * @example with events call:
	 *		$map->setCenter(
	 *			array(0, 0),
	 *			'Map Center',
	 *			array(),
	 *			array(
	 *				'click' => 'function() { // code goes here }
	 *			)
	 *		)
	 *
	 * @return  JGoogleEmbedMaps  The latitude/longitude of the center or false on failure
	 *
	 * @since   3.1.4
	 */
	public function setCenter($location, $title = true, $markeroptions = array(), $markerevents = array())
	{
		if ($title)
		{
			$title = is_string($title) ? $title : null;

			if (!$marker = $this->addMarker($location, $title, $markeroptions, $markerevents))
			{
				return false;
			}

			$location = $marker['loc'];
		}
		elseif (is_string($location))
		{
			$geocode = $this->geocodeAddress($location);

			if (!$geocode)
			{
				return false;
			}

			$location = $geocode['geometry']['location'];
			$location = array_values($location);
		}

		$this->setOption('mapcenter', $location);

		return $this;
	}

	/**
	 * Method to add an event handler to the map.
	 * Event handlers must be passed in either as callback name or fully qualified function declaration
	 *
	 * @param   string  $type      The event name
	 * @param   string  $function  The event handling function body
	 *
	 * @example to add an event call:
	 *		$map->addEventHandler('click', 'function(){ alert("map click event"); }');
	 *
	 * @return  JGoogleEmbedMaps   The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function addEventHandler($type, $function)
	{
		$events = $this->listEventHandlers();

		$events[$type] = $function;

		$this->setOption('events', $events);

		return $this;
	}

	/**
	 * Method to remove an event handler from the map
	 *
	 * @param   string  $type  The event name
	 *
	 * @example to delete an event call:
	 *		$map->deleteEventHandler('click');
	 *
	 * @return  string  The event handler content
	 *
	 * @since   3.1.4
	 */
	public function deleteEventHandler($type = null)
	{
		$events = $this->listEventHandlers();

		if ($type === null || !isset($events[$type]))
		{
			return;
		}

		$event = $events[$type];
		unset($events[$type]);
		$this->setOption('events', $events);

		return $event;
	}

	/**
	 * List the events added to the map
	 *
	 * @return  array  A list of events
	 *
	 * @since   3.1.4
	 */
	public function listEventHandlers()
	{
		return $this->getOption('events') ? $this->getOption('events') : array();
	}

	/**
	 * Add a marker to the map
	 *
	 * @param   mixed  $location  A latitude/longitude array or an address string
	 * @param   mixed  $title     The hover-text for the marker
	 * @param   array  $options   Options for marker
	 * @param   array  $events    Events for marker
	 *
	 * @example with events call:
	 *		$map->addMarker(
	 *			array(0, 0),
	 *			'My Marker',
	 *			array(),
	 *			array(
	 *				'click' => 'function() { // code goes here }
	 *			)
	 *		)
	 *
	 * @return  mixed  The marker or false on failure
	 *
	 * @since   3.1.4
	 */
	public function addMarker($location, $title = null, $options = array(), $events = array())
	{
		if (is_string($location))
		{
			if (!$title)
			{
				$title = $location;
			}

			$geocode = $this->geocodeAddress($location);

			if (!$geocode)
			{
				return false;
			}

			$location = $geocode['geometry']['location'];
		}
		elseif (!$title)
		{
			$title = implode(', ', $location);
		}

		$location = array_values($location);
		$marker = array('loc' => $location, 'title' => $title, 'options' => $options, 'events' => $events);

		$markers = $this->listMarkers();
		$markers[] = $marker;
		$this->setOption('markers', $markers);

		return $marker;
	}

	/**
	 * List the markers added to the map
	 *
	 * @return  array  A list of markers
	 *
	 * @since   3.1.4
	 */
	public function listMarkers()
	{
		return $this->getOption('markers') ? $this->getOption('markers') : array();
	}

	/**
	 * Delete a marker from the map
	 *
	 * @param   int  $index  Index of marker to delete (defaults to last added marker)
	 *
	 * @return  array The latitude/longitude of the deleted marker
	 *
	 * @since   3.1.4
	 */
	public function deleteMarker($index = null)
	{
		$markers = $this->listMarkers();

		if ($index === null)
		{
			$index = count($markers) - 1;
		}

		if ($index >= count($markers) || $index < 0)
		{
			throw new OutOfBoundsException('Marker index out of bounds.');
		}

		$marker = $markers[$index];
		unset($markers[$index]);
		$markers = array_values($markers);
		$this->setOption('markers', $markers);

		return $marker;
	}

	/**
	 * Checks if the javascript is set to be asynchronous
	 *
	 * @return  boolean  True if asynchronous
	 *
	 * @since   3.1.4
	 */
	public function isAsync()
	{
		return $this->getOption('async') === null ? true : $this->getOption('async');
	}

	/**
	 * Load javascript asynchronously
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function useAsync()
	{
		$this->setOption('async', true);

		return $this;
	}

	/**
	 * Load javascript synchronously
	 *
	 * @return  JGoogleEmbedAMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function useSync()
	{
		$this->setOption('async', false);

		return $this;
	}

	/**
	 * Method to get callback function for async javascript loading
	 *
	 * @return  string  The ID
	 *
	 * @since   3.1.4
	 */
	public function getAsyncCallback()
	{
		return $this->getOption('callback') ? $this->getOption('callback') : 'initialize';
	}

	/**
	 * Method to set the callback function for async javascript loading
	 *
	 * @param   string  $callback  The callback function name
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAsyncCallback($callback)
	{
		$this->setOption('callback', $callback);

		return $this;
	}

	/**
	 * Checks if a sensor is set to be required
	 *
	 * @return  boolean  True if asynchronous
	 *
	 * @since   3.1.4
	 */
	public function hasSensor()
	{
		return $this->getOption('sensor') === null ? false : $this->getOption('sensor');
	}

	/**
	 * Require access to sensor data
	 *
	 * @return  JGoogleEmbedMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function useSensor()
	{
		$this->setOption('sensor', true);

		return $this;
	}

	/**
	 * Don't require access to sensor data
	 *
	 * @return  JGoogleEmbedAMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function noSensor()
	{
		$this->setOption('sensor', false);

		return $this;
	}

	/**
	 * Checks how the script should be loaded
	 *
	 * @return  string  Autoload type (onload, jquery, mootools, or false)
	 *
	 * @since   3.1.4
	 */
	public function getAutoload()
	{
		return $this->getOption('autoload') ? $this->getOption('autoload') : 'false';
	}

	/**
	 * Automatically add the callback to the window
	 *
	 * @param   string  $type  The method to add the callback (options are onload, jquery, mootools, and false)
	 *
	 * @return  JGoogleEmbedAMaps  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setAutoload($type = 'onload')
	{
		$this->setOption('autoload', $type);

		return $this;
	}

	/**
	 * Get code to load Google Maps javascript
	 *
	 * @return  string  Javascript code
	 *
	 * @since   3.1.4
	 */
	public function getHeader()
	{
		$zoom = $this->getZoom();
		$center = $this->getCenter();
		$maptype = $this->getMapType();
		$id = $this->getMapId();
		$scheme = $this->isSecure() ? 'https' : 'http';
		$key = $this->getKey();
		$sensor = $this->hasSensor() ? 'true' : 'false';

		$setup = 'var mapOptions = {';
		$setup .= "zoom: {$zoom},";
		$setup .= "center: new google.maps.LatLng({$center[0]},{$center[1]}),";
		$setup .= "mapTypeId: google.maps.MapTypeId.{$maptype},";
		$setup .= substr(json_encode($this->getAdditionalMapOptions()), 1, -1);
		$setup .= '};';
		$setup .= "var map = new google.maps.Map(document.getElementById('{$id}'), mapOptions);";

		$events = $this->listEventHandlers();

		if (isset($events) && count($events))
		{
			foreach ($events as $type => $handler)
			{
				$setup .= "google.maps.event.addListener(map, '{$type}', {$handler});";
			}
		}

		$markers = $this->listMarkers();

		if (isset($markers) && count($markers))
		{
			$setup .= 'var marker;';

			foreach ($markers as $marker)
			{
				$loc = $marker['loc'];
				$title = $marker['title'];
				$options = $marker['options'];

				$setup .= 'marker = new google.maps.Marker({';
				$setup .= "position: new google.maps.LatLng({$loc[0]},{$loc[1]}),";
				$setup .= 'map: map,';
				$setup .= "title:'{$title}',";
				$setup .= substr(json_encode($options), 1, -1);
				$setup .= '});';

				if (isset($marker['events']) && is_array($marker['events']))
				{
					foreach ($marker['events'] as $type => $handler)
					{
						$setup .= 'google.maps.event.addListener(marker, "' . $type . '", ' . $handler . ');';
					}
				}
			}
		}

		$setup .= $this->getAdditionalJavascript();

		if ($this->isAsync())
		{
			$asynccallback = $this->getAsyncCallback();

			$output = '<script type="text/javascript">';
			$output .= "function {$asynccallback}() {";
			$output .= $setup;
			$output .= '}';

			$onload = 'function() {';
			$onload .= 'var script = document.createElement("script");';
			$onload .= 'script.type = "text/javascript";';
			$onload .= "script.src = '{$scheme}://maps.googleapis.com/maps/api/js?" . ($key ? "key={$key}&" : '')
				. "sensor={$sensor}&callback={$asynccallback}';";
			$onload .= 'document.body.appendChild(script);';
			$onload .= '}';
		}
		else
		{
			$output = "<script type='text/javascript' src='{$scheme}://maps.googleapis.com/maps/api/js?" . ($key ? "key={$key}&" : '') . "sensor={$sensor}'>";
			$output .= '</script>';
			$output .= '<script type="text/javascript">';

			$onload = 'function() {';
			$onload .= $setup;
			$onload .= '}';
		}

		switch ($this->getAutoload())
		{
			case 'onload':
			$output .= "window.onload={$onload};";
			break;

			case 'jquery':
			$output .= "jQuery(document).ready({$onload});";
			break;

			case 'mootools':
			$output .= "window.addEvent('domready',{$onload});";
			break;
		}

		$output .= '</script>';

		return $output;
	}

	/**
	 * Method to retrieve the div that the map is loaded into
	 *
	 * @return  string  The body
	 *
	 * @since   3.1.4
	 */
	public function getBody()
	{
		$id = $this->getMapId();
		$class = $this->getMapClass();
		$style = $this->getMapStyle();

		$output = "<div id='{$id}'";

		if (!empty($class))
		{
			$output .= " class='{$class}'";
		}

		if (!empty($style))
		{
			$output .= " style='{$style}'";
		}

		$output .= '></div>';

		return $output;
	}

	/**
	 * Method to get the location information back from an address
	 *
	 * @param   string  $address  The address to geocode
	 *
	 * @return  array  An array containing Google's geocode data
	 *
	 * @since   3.1.4
	 */
	public function geocodeAddress($address)
	{
		$uri = JUri::getInstance('https://maps.googleapis.com/maps/api/geocode/json');

		$uri->setVar('address', urlencode($address));

		if (($key = $this->getKey()))
		{
			$uri->setVar('key', $key);
		}

		$response = $this->http->get($uri->toString());

		if ($response->code < 200 || $response->code >= 300)
		{
			throw new RuntimeException('Error code ' . $response->code . ' received geocoding address: ' . $response->body . '.');
		}

		$data = json_decode($response->body, true);

		if (!$data)
		{
			throw new RuntimeException('Invalid json received geocoding address: ' . $response->body . '.');
		}

		if ($data['status'] != 'OK')
		{
			if (!empty($data['error_message']))
			{
				throw new RuntimeException($data['error_message']);
			}

			return;
		}

		return $data['results'][0];
	}
}
joomla/google/embed/analytics.php000064400000017524152177723700013101 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Google
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Google Analytics embed class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/google` package via Composer instead
 */
class JGoogleEmbedAnalytics extends JGoogleEmbed
{
	/**
	 * Method to get the tracking code
	 *
	 * @return  string  The Google Analytics tracking code
	 *
	 * @since   3.1.4
	 */
	public function getCode()
	{
		return $this->getOption('code');
	}

	/**
	 * Method to set the tracking code
	 *
	 * @param   string  $code  The Google Analytics tracking code
	 *
	 * @return  JGoogleEmbedAnalytics  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function setCode($code)
	{
		$this->setOption('code', $code);

		return $this;
	}

	/**
	 * Checks if the javascript is set to be asynchronous
	 *
	 * @return  boolean  True if asynchronous
	 *
	 * @since   3.1.4
	 */
	public function isAsync()
	{
		return $this->getOption('async') === null ? true : $this->getOption('async');
	}

	/**
	 * Load javascript asynchronously
	 *
	 * @return  JGoogleEmbedAnalytics  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function useAsync()
	{
		$this->setOption('async', true);

		return $this;
	}

	/**
	 * Load javascript synchronously
	 *
	 * @return  JGoogleEmbedAnalytics  The object for method chaining
	 *
	 * @since   3.1.4
	 */
	public function useSync()
	{
		$this->setOption('async', false);

		return $this;
	}

	/**
	 * Add an analytics call
	 *
	 * @param   string  $method  The name of the function
	 * @param   array   $params  The parameters for the call
	 *
	 * @return  array  The added call
	 *
	 * @since   3.1.4
	 */
	public function addCall($method, $params = array())
	{
		$call = array('name' => $method, 'params' => $params);

		$calls = $this->listCalls();
		$calls[] = $call;
		$this->setOption('calls', $calls);

		return $call;
	}

	/**
	 * List the analytics calls to be executed
	 *
	 * @return  array  A list of calls
	 *
	 * @since   3.1.4
	 */
	public function listCalls()
	{
		return $this->getOption('calls') ? $this->getOption('calls') : array();
	}

	/**
	 * Delete a call from the stack
	 *
	 * @param   int  $index  Index of call to delete (defaults to last added call)
	 *
	 * @return  array  The deleted call
	 *
	 * @since   3.1.4
	 */
	public function deleteCall($index = null)
	{
		$calls = $this->listCalls();

		if ($index === null)
		{
			$index = count($calls) - 1;
		}

		$call = $calls[$index];
		unset($calls[$index]);
		$calls = array_values($calls);
		$this->setOption('calls', $calls);

		return $call;
	}

	/**
	 * Create a javascript function from the call parameters
	 *
	 * @param   string  $method  The name of the function
	 * @param   array   $params  The parameters for the call
	 *
	 * @return  string  The created call
	 *
	 * @since   3.1.4
	 */
	public function createCall($method, $params = array())
	{
		$params = array_values($params);

		if ($this->isAsync())
		{
			$output = "_gaq.push(['{$method}',";
			$output .= substr(json_encode($params), 1, -1);
			$output .= ']);';
		}
		else
		{
			$output = "pageTracker.{$method}(";
			$output .= substr(json_encode($params), 1, -1);
			$output .= ');';
		}

		return $output;
	}

	/**
	 * Add a custom variable to the analytics
	 *
	 * @param   int     $slot   The slot to store the variable in (1-5)
	 * @param   string  $name   The variable name
	 * @param   string  $value  The variable value
	 * @param   int     $scope  The scope of the variable (1: visitor level, 2: session level, 3: page level)
	 *
	 * @return  array  The added call
	 *
	 * @since   3.1.4
	 */
	public function addCustomVar($slot, $name, $value, $scope = 3)
	{
		return $this->addCall('_setCustomVar', array($slot, $name, $value, $scope));
	}

	/**
	 * Get the code to create a custom analytics variable
	 *
	 * @param   int     $slot   The slot to store the variable in (1-5)
	 * @param   string  $name   The variable name
	 * @param   string  $value  The variable value
	 * @param   int     $scope  The scope of the variable (1: visitor level, 2: session level, 3: page level)
	 *
	 * @return  string  The created call
	 *
	 * @since   3.1.4
	 */
	public function createCustomVar($slot, $name, $value, $scope = 3)
	{
		return $this->createCall('_setCustomVar', array($slot, $name, $value, $scope));
	}

	/**
	 * Track an analytics event
	 *
	 * @param   string   $category     The general event category
	 * @param   string   $action       The event action
	 * @param   string   $label        The event description
	 * @param   string   $value        The value of the event
	 * @param   boolean  $noninteract  Don't allow this event to impact bounce statistics
	 *
	 * @return  array  The added call
	 *
	 * @since   3.1.4
	 */
	public function addEvent($category, $action, $label = null, $value = null, $noninteract = false)
	{
		return $this->addCall('_trackEvent', array($category, $action, $label, $value, $noninteract));
	}

	/**
	 * Get the code to track an analytics event
	 *
	 * @param   string   $category     The general event category
	 * @param   string   $action       The event action
	 * @param   string   $label        The event description
	 * @param   string   $value        The value of the event
	 * @param   boolean  $noninteract  Don't allow this event to impact bounce statistics
	 *
	 * @return  string  The created call
	 *
	 * @since   3.1.4
	 */
	public function createEvent($category, $action, $label = null, $value = null, $noninteract = false)
	{
		return $this->createCall('_trackEvent', array($category, $action, $label, $value, $noninteract));
	}

	/**
	 * Get code to load Google Analytics javascript
	 *
	 * @return  string  Javascript code
	 *
	 * @since   3.1.4
	 */
	public function getHeader()
	{
		if (!$this->isAsync())
		{
			// Synchronous code is included only in the body
			return '';
		}

		if (!$this->getOption('code'))
		{
			throw new UnexpectedValueException('A Google Analytics tracking code is required.');
		}

		$code = $this->getOption('code');

		$output = '<script type="text/javascript">';
		$output .= 'var _gaq = _gaq || [];';
		$output .= "_gaq.push(['_setAccount', '{$code}']);";

		foreach ($this->listCalls() as $call)
		{
			$output .= $this->createCall($call['name'], $call['params']);
		}

		$output .= '_gaq.push(["_trackPageview"]);';
		$output .= '</script>';

		return $output;
	}

	/**
	 * Google Analytics only needs to be included in the header
	 *
	 * @return  null
	 *
	 * @since   3.1.4
	 */
	public function getBody()
	{
		if (!$this->getOption('code'))
		{
			throw new UnexpectedValueException('A Google Analytics tracking code is required.');
		}

		$prefix = $this->isSecure() ? 'https://ssl' : 'http://www';
		$code = $this->getOption('code');

		if ($this->isAsync())
		{
			$output = '<script type="text/javascript">';
			$output .= '(function() {';
			$output .= 'var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;';
			$output .= "ga.src = '{$prefix}.google-analytics.com/ga.js';";
			$output .= 'var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);';
			$output .= '})();';
			$output .= '</script>';
		}
		else
		{
			$output = '<script type="text/javascript">';
			$output .= "document.write(unescape(\"%3Cscript src='{$prefix}.google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E\"));";
			$output .= '</script>';
			$output .= '<script type="text/javascript">';
			$output .= 'try{';
			$output .= "var pageTracker = _gat._getTracker('{$code}');";

			foreach ($this->listCalls() as $call)
			{
				$output .= $this->createCall($call['name'], $call['params']);
			}

			$output .= 'pageTracker._trackPageview();';
			$output .= '} catch(err) {}</script>';
		}

		return $output;
	}
}
joomla/oauth1/client.php000064400000033642152177723700011240 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  OAuth1
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with an OAuth 1.0 and 1.0a server.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/oauth1` framework package that will be bundled instead
 */
abstract class JOAuth1Client
{
	/**
	 * @var    Registry  Options for the JOAuth1Client object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * @var    array  Contains access token key, secret and verifier.
	 * @since  3.2.0
	 */
	protected $token = array();

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * @var    JInput The input object to use in retrieving GET/POST data.
	 * @since  3.2.0
	 */
	protected $input;

	/**
	 * @var    JApplicationWeb  The application object to send HTTP headers for redirects.
	 * @since  3.2.0
	 */
	protected $application;

	/**
	 * @var   string  Selects which version of OAuth to use: 1.0 or 1.0a.
	 * @since 3.2.0
	 */
	protected $version;

	/**
	 * Constructor.
	 *
	 * @param   Registry         $options      OAuth1Client options object.
	 * @param   JHttp            $client       The HTTP client object.
	 * @param   JInput           $input        The input object
	 * @param   JApplicationWeb  $application  The application object
	 * @param   string           $version      Specify the OAuth version. By default we are using 1.0a.
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null, JApplicationWeb $application = null,
		$version = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : JHttpFactory::getHttp($this->options);
		$this->input = isset($input) ? $input : JFactory::getApplication()->input;
		$this->application = isset($application) ? $application : new JApplicationWeb;
		$this->version = isset($version) ? $version : '1.0a';
	}

	/**
	 * Method to for the oauth flow.
	 *
	 * @return  array  Contains access token key, secret and verifier.
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function authenticate()
	{
		// Already got some credentials stored?
		if ($this->token)
		{
			$response = $this->verifyCredentials();

			if ($response)
			{
				return $this->token;
			}
			else
			{
				$this->token = null;
			}
		}

		// Check for callback.
		if (strcmp($this->version, '1.0a') === 0)
		{
			$verifier = $this->input->get('oauth_verifier');
		}
		else
		{
			$verifier = $this->input->get('oauth_token');
		}

		if (empty($verifier))
		{
			// Generate a request token.
			$this->_generateRequestToken();

			// Authenticate the user and authorise the app.
			$this->_authorise();
		}

		// Callback
		else
		{
			$session = JFactory::getSession();

			// Get token form session.
			$this->token = array('key' => $session->get('key', null, 'oauth_token'), 'secret' => $session->get('secret', null, 'oauth_token'));

			// Verify the returned request token.
			if (strcmp($this->token['key'], $this->input->get('oauth_token')) !== 0)
			{
				throw new DomainException('Bad session!');
			}

			// Set token verifier for 1.0a.
			if (strcmp($this->version, '1.0a') === 0)
			{
				$this->token['verifier'] = $this->input->get('oauth_verifier');
			}

			// Generate access token.
			$this->_generateAccessToken();

			// Return the access token.
			return $this->token;
		}
	}

	/**
	 * Method used to get a request token.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	private function _generateRequestToken()
	{
		// Set the callback URL.
		if ($this->getOption('callback'))
		{
			$parameters = array(
				'oauth_callback' => $this->getOption('callback'),
			);
		}
		else
		{
			$parameters = array();
		}

		// Make an OAuth request for the Request Token.
		$response = $this->oauthRequest($this->getOption('requestTokenURL'), 'POST', $parameters);

		parse_str($response->body, $params);

		if (strcmp($this->version, '1.0a') === 0 && strcmp($params['oauth_callback_confirmed'], 'true') !== 0)
		{
			throw new DomainException('Bad request token!');
		}

		// Save the request token.
		$this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);

		// Save the request token in session
		$session = JFactory::getSession();
		$session->set('key', $this->token['key'], 'oauth_token');
		$session->set('secret', $this->token['secret'], 'oauth_token');
	}

	/**
	 * Method used to authorise the application.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 */
	private function _authorise()
	{
		$url = $this->getOption('authoriseURL') . '?oauth_token=' . $this->token['key'];

		if ($this->getOption('scope'))
		{
			$scope = is_array($this->getOption('scope')) ? implode(' ', $this->getOption('scope')) : $this->getOption('scope');
			$url .= '&scope=' . urlencode($scope);
		}

		if ($this->getOption('sendheaders'))
		{
			$this->application->redirect($url);
		}
	}

	/**
	 * Method used to get an access token.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 */
	private function _generateAccessToken()
	{
		// Set the parameters.
		$parameters = array(
			'oauth_token' => $this->token['key'],
		);

		if (strcmp($this->version, '1.0a') === 0)
		{
			$parameters = array_merge($parameters, array('oauth_verifier' => $this->token['verifier']));
		}

		// Make an OAuth request for the Access Token.
		$response = $this->oauthRequest($this->getOption('accessTokenURL'), 'POST', $parameters);

		parse_str($response->body, $params);

		// Save the access token.
		$this->token = array('key' => $params['oauth_token'], 'secret' => $params['oauth_token_secret']);
	}

	/**
	 * Method used to make an OAuth request.
	 *
	 * @param   string  $url         The request URL.
	 * @param   string  $method      The request method.
	 * @param   array   $parameters  Array containing request parameters.
	 * @param   mixed   $data        The POST request data.
	 * @param   array   $headers     An array of name-value pairs to include in the header of the request
	 *
	 * @return  JHttpResponse
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function oauthRequest($url, $method, $parameters, $data = array(), $headers = array())
	{
		// Set the parameters.
		$defaults = array(
			'oauth_consumer_key' => $this->getOption('consumer_key'),
			'oauth_signature_method' => 'HMAC-SHA1',
			'oauth_version' => '1.0',
			'oauth_nonce' => $this->generateNonce(),
			'oauth_timestamp' => time(),
		);

		$parameters = array_merge($parameters, $defaults);

		// Do not encode multipart parameters. Do not include $data in the signature if $data is not array.
		if (isset($headers['Content-Type']) && strpos($headers['Content-Type'], 'multipart/form-data') !== false || !is_array($data))
		{
			$oauth_headers = $parameters;
		}
		else
		{
			// Use all parameters for the signature.
			$oauth_headers = array_merge($parameters, $data);
		}

		// Sign the request.
		$oauth_headers = $this->_signRequest($url, $method, $oauth_headers);

		// Get parameters for the Authorisation header.
		if (is_array($data))
		{
			$oauth_headers = array_diff_key($oauth_headers, $data);
		}

		// Send the request.
		switch ($method)
		{
			case 'GET':
				$url = $this->toUrl($url, $data);
				$response = $this->client->get($url, array('Authorization' => $this->_createHeader($oauth_headers)));
				break;
			case 'POST':
				$headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
				$response = $this->client->post($url, $data, $headers);
				break;
			case 'PUT':
				$headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
				$response = $this->client->put($url, $data, $headers);
				break;
			case 'DELETE':
				$headers = array_merge($headers, array('Authorization' => $this->_createHeader($oauth_headers)));
				$response = $this->client->delete($url, $headers);
				break;
		}

		// Validate the response code.
		$this->validateResponse($url, $response);

		return $response;
	}

	/**
	 * Method to validate a response.
	 *
	 * @param   string         $url       The request URL.
	 * @param   JHttpResponse  $response  The response to validate.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	abstract public function validateResponse($url, $response);

	/**
	 * Method used to create the header for the POST request.
	 *
	 * @param   array  $parameters  Array containing request parameters.
	 *
	 * @return  string  The header.
	 *
	 * @since   3.2.0
	 */
	private function _createHeader($parameters)
	{
		$header = 'OAuth ';

		foreach ($parameters as $key => $value)
		{
			if (!strcmp($header, 'OAuth '))
			{
				$header .= $key . '="' . $this->safeEncode($value) . '"';
			}
			else
			{
				$header .= ', ' . $key . '="' . $value . '"';
			}
		}

		return $header;
	}

	/**
	 * Method to create the URL formed string with the parameters.
	 *
	 * @param   string  $url         The request URL.
	 * @param   array   $parameters  Array containing request parameters.
	 *
	 * @return  string  The formed URL.
	 *
	 * @since   3.2.0
	 */
	public function toUrl($url, $parameters)
	{
		foreach ($parameters as $key => $value)
		{
			if (is_array($value))
			{
				foreach ($value as $v)
				{
					if (strpos($url, '?') === false)
					{
						$url .= '?' . $key . '=' . $v;
					}
					else
					{
						$url .= '&' . $key . '=' . $v;
					}
				}
			}
			else
			{
				if (strpos($value, ' ') !== false)
				{
					$value = $this->safeEncode($value);
				}

				if (strpos($url, '?') === false)
				{
					$url .= '?' . $key . '=' . $value;
				}
				else
				{
					$url .= '&' . $key . '=' . $value;
				}
			}
		}

		return $url;
	}

	/**
	 * Method used to sign requests.
	 *
	 * @param   string  $url         The URL to sign.
	 * @param   string  $method      The request method.
	 * @param   array   $parameters  Array containing request parameters.
	 *
	 * @return  array
	 *
	 * @since   3.2.0
	 */
	private function _signRequest($url, $method, $parameters)
	{
		// Create the signature base string.
		$base = $this->_baseString($url, $method, $parameters);

		$parameters['oauth_signature'] = $this->safeEncode(
			base64_encode(
				hash_hmac('sha1', $base, $this->_prepareSigningKey(), true)
				)
			);

		return $parameters;
	}

	/**
	 * Prepare the signature base string.
	 *
	 * @param   string  $url         The URL to sign.
	 * @param   string  $method      The request method.
	 * @param   array   $parameters  Array containing request parameters.
	 *
	 * @return  string  The base string.
	 *
	 * @since   3.2.0
	 */
	private function _baseString($url, $method, $parameters)
	{
		// Sort the parameters alphabetically
		uksort($parameters, 'strcmp');

		// Encode parameters.
		foreach ($parameters as $key => $value)
		{
			$key = $this->safeEncode($key);

			if (is_array($value))
			{
				foreach ($value as $v)
				{
					$v = $this->safeEncode($v);
					$kv[] = "{$key}={$v}";
				}
			}
			else
			{
				$value = $this->safeEncode($value);
				$kv[] = "{$key}={$value}";
			}
		}
		// Form the parameter string.
		$params = implode('&', $kv);

		// Signature base string elements.
		$base = array(
			$method,
			$url,
			$params,
		);

		// Return the base string.
		return implode('&', $this->safeEncode($base));
	}

	/**
	 * Encodes the string or array passed in a way compatible with OAuth.
	 * If an array is passed each array value will will be encoded.
	 *
	 * @param   mixed  $data  The scalar or array to encode.
	 *
	 * @return  string  $data encoded in a way compatible with OAuth.
	 *
	 * @since   3.2.0
	 */
	public function safeEncode($data)
	{
		if (is_array($data))
		{
			return array_map(array($this, 'safeEncode'), $data);
		}
		elseif (is_scalar($data))
		{
			return str_ireplace(
				array('+', '%7E'),
				array(' ', '~'),
				rawurlencode($data)
				);
		}
		else
		{
			return '';
		}
	}

	/**
	 * Method used to generate the current nonce.
	 *
	 * @return  string  The current nonce.
	 *
	 * @since   3.2.0
	 */
	public static function generateNonce()
	{
		$mt = microtime();
		$rand = JCrypt::genRandomBytes();

		// The md5s look nicer than numbers.
		return md5($mt . $rand);
	}

	/**
	 * Prepares the OAuth signing key.
	 *
	 * @return  string  The prepared signing key.
	 *
	 * @since   3.2.0
	 */
	private function _prepareSigningKey()
	{
		return $this->safeEncode($this->getOption('consumer_secret')) . '&' . $this->safeEncode(($this->token) ? $this->token['secret'] : '');
	}

	/**
	 * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
	 * returns a 401 status code and an error message if not.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.2.0
	 */
	abstract public function verifyCredentials();

	/**
	 * Get an option from the JOauth1aClient instance.
	 *
	 * @param   string  $key  The name of the option to get
	 *
	 * @return  mixed  The option value
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JOauth1aClient instance.
	 *
	 * @param   string  $key    The name of the option to set
	 * @param   mixed   $value  The option value to set
	 *
	 * @return  JOAuth1Client  This object for method chaining
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}

	/**
	 * Get the oauth token key or secret.
	 *
	 * @return  array  The oauth token key and secret.
	 *
	 * @since   3.2.0
	 */
	public function getToken()
	{
		return $this->token;
	}

	/**
	 * Set the oauth token.
	 *
	 * @param   array  $token  The access token key and secret.
	 *
	 * @return  JOAuth1Client  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setToken($token)
	{
		$this->token = $token;

		return $this;
	}
}
joomla/controller/base.php000064400000005161152177723700011651 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Controller
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Application\AbstractApplication;

/**
 * Joomla Platform Base Controller Class
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
abstract class JControllerBase implements JController
{
	/**
	 * The application object.
	 *
	 * @var    AbstractApplication
	 * @since  3.0.0
	 */
	protected $app;

	/**
	 * The input object.
	 *
	 * @var    JInput
	 * @since  3.0.0
	 */
	protected $input;

	/**
	 * Instantiate the controller.
	 *
	 * @param   JInput               $input  The input object.
	 * @param   AbstractApplication  $app    The application object.
	 *
	 * @since  3.0.0
	 */
	public function __construct(JInput $input = null, AbstractApplication $app = null)
	{
		// Setup dependencies.
		$this->app = isset($app) ? $app : $this->loadApplication();
		$this->input = isset($input) ? $input : $this->loadInput();
	}

	/**
	 * Get the application object.
	 *
	 * @return  AbstractApplication  The application object.
	 *
	 * @since   3.0.0
	 */
	public function getApplication()
	{
		return $this->app;
	}

	/**
	 * Get the input object.
	 *
	 * @return  JInput  The input object.
	 *
	 * @since   3.0.0
	 */
	public function getInput()
	{
		return $this->input;
	}

	/**
	 * Serialize the controller.
	 *
	 * @return  string  The serialized controller.
	 *
	 * @since   3.0.0
	 */
	public function serialize()
	{
		return serialize($this->input);
	}

	/**
	 * Unserialize the controller.
	 *
	 * @param   string  $input  The serialized controller.
	 *
	 * @return  JController  Supports chaining.
	 *
	 * @since   3.0.0
	 * @throws  UnexpectedValueException if input is not the right class.
	 */
	public function unserialize($input)
	{
		// Setup dependencies.
		$this->app = $this->loadApplication();

		// Unserialize the input.
		$this->input = unserialize($input);

		if (!($this->input instanceof JInput))
		{
			throw new UnexpectedValueException(sprintf('%s::unserialize would not accept a `%s`.', get_class($this), gettype($this->input)));
		}

		return $this;
	}

	/**
	 * Load the application object.
	 *
	 * @return  AbstractApplication  The application object.
	 *
	 * @since   3.0.0
	 */
	protected function loadApplication()
	{
		return JFactory::getApplication();
	}

	/**
	 * Load the input object.
	 *
	 * @return  JInput  The input object.
	 *
	 * @since   3.0.0
	 */
	protected function loadInput()
	{
		return $this->app->input;
	}
}
joomla/controller/controller.php000064400000002251152177723700013117 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Controller
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Application\AbstractApplication;

/**
 * Joomla Platform Controller Interface
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
interface JController extends Serializable
{
	/**
	 * Execute the controller.
	 *
	 * @return  boolean  True if controller finished execution, false if the controller did not
	 *                   finish execution. A controller might return false if some precondition for
	 *                   the controller to run has not been satisfied.
	 *
	 * @since   3.0.0
	 * @throws  LogicException
	 * @throws  RuntimeException
	 */
	public function execute();

	/**
	 * Get the application object.
	 *
	 * @return  AbstractApplication  The application object.
	 *
	 * @since   3.0.0
	 */
	public function getApplication();

	/**
	 * Get the input object.
	 *
	 * @return  JInput  The input object.
	 *
	 * @since   3.0.0
	 */
	public function getInput();
}
joomla/utilities/arrayhelper.php000064400000026013152177723700013104 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Utilities
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\String\StringHelper;
use Joomla\Utilities\ArrayHelper;

/**
 * JArrayHelper is an array utility class for doing all sorts of odds and ends with arrays.
 *
 * @since       1.7.0
 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper instead
 */
abstract class JArrayHelper
{
	/**
	 * Option to perform case-sensitive sorts.
	 *
	 * @var    mixed  Boolean or array of booleans.
	 * @since  1.7.3
	 */
	protected static $sortCase;

	/**
	 * Option to set the sort direction.
	 *
	 * @var    mixed  Integer or array of integers.
	 * @since  1.7.3
	 */
	protected static $sortDirection;

	/**
	 * Option to set the object key to sort on.
	 *
	 * @var    string
	 * @since  1.7.3
	 */
	protected static $sortKey;

	/**
	 * Option to perform a language aware sort.
	 *
	 * @var    mixed  Boolean or array of booleans.
	 * @since  1.7.3
	 */
	protected static $sortLocale;

	/**
	 * Function to convert array to integer values
	 *
	 * @param   array  &$array   The source array to convert
	 * @param   mixed  $default  A default value (int|array) to assign if $array is not an array
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::toInteger instead
	 */
	public static function toInteger(&$array, $default = null)
	{
		$array = ArrayHelper::toInteger($array, $default);
	}

	/**
	 * Utility function to map an array to a stdClass object.
	 *
	 * @param   array    &$array     The array to map.
	 * @param   string   $class      Name of the class to create
	 * @param   boolean  $recursive  Convert also any array inside the main array
	 *
	 * @return  object   The object mapped from the given array
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::toObject instead
	 */
	public static function toObject(&$array, $class = 'stdClass', $recursive = true)
	{
		$obj = null;

		if (is_array($array))
		{
			$obj = ArrayHelper::toObject($array, $class, $recursive);
		}
		else
		{
			JLog::add('This method is typehinted to be an array in \Joomla\Utilities\ArrayHelper::toObject.', JLog::WARNING, 'deprecated');
		}

		return $obj;
	}

	/**
	 * Utility function to map an array to a string.
	 *
	 * @param   array    $array         The array to map.
	 * @param   string   $inner_glue    The glue (optional, defaults to '=') between the key and the value.
	 * @param   string   $outer_glue    The glue (optional, defaults to ' ') between array elements.
	 * @param   boolean  $keepOuterKey  True if final key should be kept.
	 *
	 * @return  string   The string mapped from the given array
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::toString instead
	 */
	public static function toString($array = null, $inner_glue = '=', $outer_glue = ' ', $keepOuterKey = false)
	{
		$output = array();

		if (is_array($array))
		{
			$output[] = ArrayHelper::toString($array, $inner_glue, $outer_glue, $keepOuterKey);
		}
		else
		{
			JLog::add('This method is typehinted to be an array in \Joomla\Utilities\ArrayHelper::toString.', JLog::WARNING, 'deprecated');
		}

		return implode($outer_glue, $output);
	}

	/**
	 * Utility function to map an object to an array
	 *
	 * @param   object   $p_obj    The source object
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array    The array mapped from the given object
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::fromObject instead
	 */
	public static function fromObject($p_obj, $recurse = true, $regex = null)
	{
		if (is_object($p_obj))
		{
			return self::_fromObject($p_obj, $recurse, $regex);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Utility function to map an object or array to an array
	 *
	 * @param   mixed    $item     The source object or array
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array  The array mapped from the given object
	 *
	 * @since   1.7.0
	 */
	protected static function _fromObject($item, $recurse, $regex)
	{
		if (is_object($item))
		{
			$result = array();

			foreach (get_object_vars($item) as $k => $v)
			{
				if (!$regex || preg_match($regex, $k))
				{
					if ($recurse)
					{
						$result[$k] = self::_fromObject($v, $recurse, $regex);
					}
					else
					{
						$result[$k] = $v;
					}
				}
			}
		}
		elseif (is_array($item))
		{
			$result = array();

			foreach ($item as $k => $v)
			{
				$result[$k] = self::_fromObject($v, $recurse, $regex);
			}
		}
		else
		{
			$result = $item;
		}

		return $result;
	}

	/**
	 * Extracts a column from an array of arrays or objects
	 *
	 * @param   array   &$array  The source array
	 * @param   string  $index   The index of the column or name of object property
	 *
	 * @return  array  Column of values from the source array
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::getColumn instead
	 */
	public static function getColumn(&$array, $index)
	{
		$result = array();

		if (is_array($array))
		{
			$result = ArrayHelper::getColumn($array, $index);
		}
		else
		{
			JLog::add('This method is typehinted to be an array in \Joomla\Utilities\ArrayHelper::getColumn.', JLog::WARNING, 'deprecated');
		}

		return $result;
	}

	/**
	 * Utility function to return a value from a named array or a specified default
	 *
	 * @param   array   &$array   A named array
	 * @param   string  $name     The key to search for
	 * @param   mixed   $default  The default value to give if no key found
	 * @param   string  $type     Return type for the variable (INT, FLOAT, STRING, WORD, BOOLEAN, ARRAY)
	 *
	 * @return  mixed  The value from the source array
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::getValue instead
	 */
	public static function getValue(&$array, $name, $default = null, $type = '')
	{
		// Previously we didn't typehint an array. So force any object to be an array
		return ArrayHelper::getValue((array) $array, $name, $default, $type);
	}

	/**
	 * Takes an associative array of arrays and inverts the array keys to values using the array values as keys.
	 *
	 * Example:
	 * $input = array(
	 *     'New' => array('1000', '1500', '1750'),
	 *     'Used' => array('3000', '4000', '5000', '6000')
	 * );
	 * $output = JArrayHelper::invert($input);
	 *
	 * Output would be equal to:
	 * $output = array(
	 *     '1000' => 'New',
	 *     '1500' => 'New',
	 *     '1750' => 'New',
	 *     '3000' => 'Used',
	 *     '4000' => 'Used',
	 *     '5000' => 'Used',
	 *     '6000' => 'Used'
	 * );
	 *
	 * @param   array  $array  The source array.
	 *
	 * @return  array  The inverted array.
	 *
	 * @since   3.1.4
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::invert instead
	 */
	public static function invert($array)
	{
		return ArrayHelper::invert($array);
	}

	/**
	 * Method to determine if an array is an associative array.
	 *
	 * @param   array  $array  An array to test.
	 *
	 * @return  boolean  True if the array is an associative array.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::isAssociative instead
	 */
	public static function isAssociative($array)
	{
		return ArrayHelper::isAssociative($array);
	}

	/**
	 * Pivots an array to create a reverse lookup of an array of scalars, arrays or objects.
	 *
	 * @param   array   $source  The source array.
	 * @param   string  $key     Where the elements of the source array are objects or arrays, the key to pivot on.
	 *
	 * @return  array  An array of arrays pivoted either on the value of the keys, or an individual key of an object or array.
	 *
	 * @since   1.7.3
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::pivot instead
	 */
	public static function pivot($source, $key = null)
	{
		$result = array();

		if (is_array($source))
		{
			$result = ArrayHelper::pivot($source, $key);
		}
		else
		{
			JLog::add('This method is typehinted to be an array in \Joomla\Utilities\ArrayHelper::pivot.', JLog::WARNING, 'deprecated');
		}

		return $result;
	}

	/**
	 * Utility function to sort an array of objects on a given field
	 *
	 * @param   array  &$a             An array of objects
	 * @param   mixed  $k              The key (string) or an array of keys to sort on
	 * @param   mixed  $direction      Direction (integer) or an array of direction to sort in [1 = Ascending] [-1 = Descending]
	 * @param   mixed  $caseSensitive  Boolean or array of booleans to let sort occur case sensitive or insensitive
	 * @param   mixed  $locale         Boolean or array of booleans to let sort occur using the locale language or not
	 *
	 * @return  array  The sorted array of objects
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::sortObjects instead
	 */
	public static function sortObjects(&$a, $k, $direction = 1, $caseSensitive = true, $locale = false)
	{
		if (!is_array($locale) || !is_array($locale[0]))
		{
			$locale = array($locale);
		}

		self::$sortCase = (array) $caseSensitive;
		self::$sortDirection = (array) $direction;
		self::$sortKey = (array) $k;
		self::$sortLocale = $locale;

		usort($a, array(__CLASS__, '_sortObjects'));

		self::$sortCase = null;
		self::$sortDirection = null;
		self::$sortKey = null;
		self::$sortLocale = null;

		return $a;
	}

	/**
	 * Callback function for sorting an array of objects on a key
	 *
	 * @param   array  &$a  An array of objects
	 * @param   array  &$b  An array of objects
	 *
	 * @return  integer  Comparison status
	 *
	 * @see     JArrayHelper::sortObjects()
	 * @since   1.7.0
	 */
	protected static function _sortObjects(&$a, &$b)
	{
		$key = self::$sortKey;

		for ($i = 0, $count = count($key); $i < $count; $i++)
		{
			if (isset(self::$sortDirection[$i]))
			{
				$direction = self::$sortDirection[$i];
			}

			if (isset(self::$sortCase[$i]))
			{
				$caseSensitive = self::$sortCase[$i];
			}

			if (isset(self::$sortLocale[$i]))
			{
				$locale = self::$sortLocale[$i];
			}

			$va = $a->{$key[$i]};
			$vb = $b->{$key[$i]};

			if ((is_bool($va) || is_numeric($va)) && (is_bool($vb) || is_numeric($vb)))
			{
				$cmp = $va - $vb;
			}
			elseif ($caseSensitive)
			{
				$cmp = StringHelper::strcmp($va, $vb, $locale);
			}
			else
			{
				$cmp = StringHelper::strcasecmp($va, $vb, $locale);
			}

			if ($cmp > 0)
			{
				return $direction;
			}

			if ($cmp < 0)
			{
				return -$direction;
			}
		}

		return 0;
	}

	/**
	 * Multidimensional array safe unique test
	 *
	 * @param   array  $myArray  The array to make unique.
	 *
	 * @return  array
	 *
	 * @link    https://www.php.net/manual/en/function.array-unique.php
	 * @since   1.7.0
	 * @deprecated  4.0 Use Joomla\Utilities\ArrayHelper::arrayUnique instead
	 */
	public static function arrayUnique($myArray)
	{
		return is_array($myArray) ? ArrayHelper::arrayUnique($myArray) : $myArray;
	}
}
joomla/twitter/users.php000064400000024073152177723700011422 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Users class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterUsers extends JTwitterObject
{
	/**
	 * Method to get up to 100 users worth of extended information, specified by either ID, screen name, or combination of the two.
	 *
	 * @param   string   $screen_name  A comma separated list of screen names, up to 100 are allowed in a single request.
	 * @param   string   $id           A comma separated list of user IDs, up to 100 are allowed in a single request.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a variety of
	 * 								   metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getUsersLookup($screen_name = null, $id = null, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'lookup');

		// Set user IDs and screen names.
		if ($id)
		{
			$data['user_id'] = $id;
		}

		if ($screen_name)
		{
			$data['screen_name'] = $screen_name;
		}

		if ($id == null && $screen_name == null)
		{
			// We don't have a valid entry
			throw new RuntimeException('You must specify either a comma separated list of screen names, user IDs, or a combination of the two');
		}

		// Set the API path
		$path = '/users/lookup.json';

		// Check if string_ids is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to access the profile banner in various sizes for the user with the indicated screen_name.
	 *
	 * @param   mixed  $user  Either an integer containing the user ID or a string containing the screen name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getUserProfileBanner($user)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'profile_banner');

		// Set the API path
		$path = '/users/profile_banner.json';

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method used to search for users
	 *
	 * @param   string   $query     The search query to run against people search.
	 * @param   integer  $page      Specifies the page of results to retrieve.
	 * @param   integer  $count     The number of people to retrieve. Maximum of 20 allowed per page.
	 * @param   boolean  $entities  When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function searchUsers($query, $page = 0, $count = 0, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'search');

		$data['q'] = rawurlencode($query);

		// Check if page is specified.
		if ($page > 0)
		{
			$data['page'] = $page;
		}

		// Check if per_page is specified
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Set the API path
		$path = '/users/search.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get extended information of a given user, specified by ID or screen name as per the required id parameter.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities  Set to true to return IDs as strings, false to return as integers.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getUser($user, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'show/:id');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/users/show.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get an array of users that the specified user can contribute to.
	 *
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities     Set to true to return IDs as strings, false to return as integers.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getContributees($user, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'contributees');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/users/contributees.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get an array of users who can contribute to the specified account.
	 *
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities     Set to true to return IDs as strings, false to return as integers.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getContributors($user, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'contributors');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/users/contributors.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method access to Twitter's suggested user list.
	 *
	 * @param   boolean  $lang  Restricts the suggested categories to the requested language.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSuggestions($lang = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'suggestions');

		// Set the API path
		$path = '/users/suggestions.json';

		$data = array();

		// Check if entities is true
		if ($lang)
		{
			$data['lang'] = $lang;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * method to access the users in a given category of the Twitter suggested user list.
	 *
	 * @param   string   $slug  The short name of list or a category.
	 * @param   boolean  $lang  Restricts the suggested categories to the requested language.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSuggestionsSlug($slug, $lang = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'suggestions/:slug');

		// Set the API path
		$path = '/users/suggestions/' . $slug . '.json';

		$data = array();

		// Check if entities is true
		if ($lang)
		{
			$data['lang'] = $lang;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to access the users in a given category of the Twitter suggested user list and return
	 * their most recent status if they are not a protected user.
	 *
	 * @param   string  $slug  The short name of list or a category.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSuggestionsSlugMembers($slug)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('users', 'suggestions/:slug/members');

		// Set the API path
		$path = '/users/suggestions/' . $slug . '/members.json';

		// Send the request.
		return $this->sendRequest($path);
	}
}
joomla/twitter/search.php000064400000013121152177723700011516 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Search class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwittersearch extends JTwitterObject
{
	/**
	 * Method to get tweets that match a specified query.
	 *
	 * @param   string   $query        Search query. Should be URL encoded. Queries will be limited by complexity.
	 * @param   string   $callback     If supplied, the response will use the JSONP format with a callback of the given name
	 * @param   string   $geocode      Returns tweets by users located within a given radius of the given latitude/longitude. The parameter value is
	 * 								   specified by "latitude,longitude,radius", where radius units must be specified as either "mi" (miles) or "km" (kilometers).
	 * @param   string   $lang         Restricts tweets to the given language, given by an ISO 639-1 code.
	 * @param   string   $locale       Specify the language of the query you are sending (only ja is currently effective). This is intended for
	 * 								   language-specific clients and the default should work in the majority of cases.
	 * @param   string   $result_type  Specifies what type of search results you would prefer to receive. The current default is "mixed."
	 * @param   integer  $count        The number of tweets to return per page, up to a maximum of 100. Defaults to 15.
	 * @param   string   $until        Returns tweets generated before the given date. Date should be formatted as YYYY-MM-DD.
	 * @param   integer  $since_id     Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id       Returns results with an ID less than (that is, older than) or equal to the specified ID.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discrete structure, including: urls, media and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function search($query, $callback = null, $geocode = null, $lang = null, $locale = null, $result_type = null, $count = 15,
		$until = null, $since_id = 0, $max_id = 0, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('search', 'tweets');

		// Set the API path
		$path = '/search/tweets.json';

		// Set query parameter.
		$data['q'] = rawurlencode($query);

		// Check if callback is specified.
		if ($callback)
		{
			$data['callback'] = $callback;
		}

		// Check if geocode is specified.
		if ($geocode)
		{
			$data['geocode'] = $geocode;
		}

		// Check if lang is specified.
		if ($lang)
		{
			$data['lang'] = $lang;
		}

		// Check if locale is specified.
		if ($locale)
		{
			$data['locale'] = $locale;
		}

		// Check if result_type is specified.
		if ($result_type)
		{
			$data['result_type'] = $result_type;
		}

		// Check if count is specified.
		if ($count != 15)
		{
			$data['count'] = $count;
		}

		// Check if until is specified.
		if ($until)
		{
			$data['until'] = $until;
		}

		// Check if since_id is specified.
		if ($since_id > 0)
		{
			$data['since_id'] = $since_id;
		}

		// Check if max_id is specified.
		if ($max_id > 0)
		{
			$data['max_id'] = $max_id;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the authenticated user's saved search queries.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSavedSearches()
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('saved_searches', 'list');

		// Set the API path
		$path = '/saved_searches/list.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to get the information for the saved search represented by the given id.
	 *
	 * @param   integer  $id  The ID of the saved search.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSavedSearchesById($id)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('saved_searches', 'show/:id');

		// Set the API path
		$path = '/saved_searches/show/' . $id . '.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to create a new saved search for the authenticated user.
	 *
	 * @param   string  $query  The query of the search the user would like to save.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function createSavedSearch($query)
	{
		// Set the API path
		$path = '/saved_searches/create.json';

		// Set POST request data
		$data['query'] = rawurlencode($query);

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to delete a saved search for the authenticating user.
	 *
	 * @param   integer  $id  The ID of the saved search.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function deleteSavedSearch($id)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('saved_searches', 'destroy/:id');

		// Set the API path
		$path = '/saved_searches/destroy/' . $id . '.json';

		// Send the request.
		return $this->sendRequest($path, 'POST');
	}
}
joomla/twitter/directmessages.php000064400000014267152177723700013267 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Direct Messages class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterDirectmessages extends JTwitterObject
{
	/**
	 * Method to get the most recent direct messages sent to the authenticating user.
	 *
	 * @param   integer  $since_id     Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id       Returns results with an ID less than (that is, older than) or equal to the specified ID.
	 * @param   integer  $count        Specifies the number of direct messages to try and retrieve, up to a maximum of 200.
	 * @param   boolean  $entities     When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                                 about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getDirectMessages($since_id = 0, $max_id =  0, $count = 20, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('direct_messages');

		// Set the API path
		$path = '/direct_messages.json';

		// Check if since_id is specified.
		if ($since_id)
		{
			$data['since_id'] = $since_id;
		}

		// Check if max_id is specified.
		if ($max_id)
		{
			$data['max_id'] = $max_id;
		}

		// Check if count is specified.
		if ($count)
		{
			$data['count'] = $count;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified.
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the most recent direct messages sent by the authenticating user.
	 *
	 * @param   integer  $since_id  Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id    Returns results with an ID less than (that is, older than) or equal to the specified ID.
	 * @param   integer  $count     Specifies the number of direct messages to try and retrieve, up to a maximum of 200.
	 * @param   integer  $page      Specifies the page of results to retrieve.
	 * @param   boolean  $entities  When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                              about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSentDirectMessages($since_id = 0, $max_id =  0, $count = 20, $page = 0, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('direct_messages', 'sent');

		// Set the API path
		$path = '/direct_messages/sent.json';

		// Check if since_id is specified.
		if ($since_id)
		{
			$data['since_id'] = $since_id;
		}

		// Check if max_id is specified.
		if ($max_id)
		{
			$data['max_id'] = $max_id;
		}

		// Check if count is specified.
		if ($count)
		{
			$data['count'] = $count;
		}

		// Check if page is specified.
		if ($page)
		{
			$data['page'] = $page;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to send a new direct message to the specified user from the authenticating user.
	 *
	 * @param   mixed   $user  Either an integer containing the user ID or a string containing the screen name.
	 * @param   string  $text  The text of your direct message. Be sure to keep the message under 140 characters.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function sendDirectMessages($user, $text)
	{
		// Set the API path
		$path = '/direct_messages/new.json';

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		$data['text'] = $text;

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to get a single direct message, specified by an id parameter.
	 *
	 * @param   integer  $id  The ID of the direct message.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getDirectMessagesById($id)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('direct_messages', 'show');

		// Set the API path
		$path = '/direct_messages/show.json';

		$data['id'] = $id;

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to delete the direct message specified in the required ID parameter.
	 *
	 * @param   integer  $id        The ID of the direct message.
	 * @param   boolean  $entities  When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                              about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function deleteDirectMessages($id, $entities = null)
	{
		// Set the API path
		$path = '/direct_messages/destroy.json';

		$data['id'] = $id;

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/twitter/lists.php000064400000066565152177723700011433 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Lists class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterLists extends JTwitterObject
{
	/**
	 * Method to get all lists the authenticating or specified user subscribes to, including their own.
	 *
	 * @param   mixed    $user     Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $reverse  Set this to true if you would like owned lists to be returned first. See description
	 * 					 above for information on how this parameter works.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getLists($user, $reverse = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'list');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if reverse is specified.
		if (!is_null($reverse))
		{
			$data['reverse'] = $reverse;
		}

		// Set the API path
		$path = '/lists/list.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get tweet timeline for members of the specified list
	 *
	 * @param   mixed    $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed    $owner        Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $since_id     Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id       Returns results with an ID less than (that is, older than) or equal to the specified ID.
	 * @param   integer  $count        Specifies the number of results to retrieve per "page."
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities". This node offers a variety
	 * 								   of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $include_rts  When set to either true, t or 1, the list timeline will contain native retweets (if they exist) in addition
	 * 								   to the standard stream of tweets.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getStatuses($list, $owner = null, $since_id = 0, $max_id = 0, $count = 0, $entities = null, $include_rts = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'statuses');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/statuses.json';

		// Check if since_id is specified
		if ($since_id > 0)
		{
			$data['since_id'] = $since_id;
		}

		// Check if max_id is specified
		if ($max_id > 0)
		{
			$data['max_id'] = $max_id;
		}

		// Check if count is specified
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if include_rts is specified
		if (!is_null($include_rts))
		{
			$data['include_rts'] = $include_rts;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the subscribers of the specified list.
	 *
	 * @param   mixed    $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed    $owner        Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $cursor       Breaks the results into pages. A single page contains 20 lists. Provide a value of -1 to begin paging.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities". This node offers a variety
	 * 								   of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getSubscribers($list, $owner = null, $cursor = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'subscribers');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/subscribers.json';

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to remove multiple members from a list, by specifying a comma-separated list of member ids or screen names.
	 *
	 * @param   mixed   $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   string  $user_id      A comma separated list of user IDs, up to 100 are allowed in a single request.
	 * @param   string  $screen_name  A comma separated list of screen names, up to 100 are allowed in a single request.
	 * @param   mixed   $owner        Either an integer containing the user ID or a string containing the screen name of the owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function deleteMembers($list, $user_id = null, $screen_name = null, $owner = null)
	{
		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username for owner is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		if ($user_id)
		{
			$data['user_id'] = $user_id;
		}

		if ($screen_name)
		{
			$data['screen_name'] = $screen_name;
		}

		if ($user_id == null && $screen_name == null)
		{
			// We don't have a valid entry
			throw new RuntimeException('You must specify either a comma separated list of screen names, user IDs, or a combination of the two');
		}

		// Set the API path
		$path = '/lists/members/destroy_all.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to subscribe the authenticated user to the specified list.
	 *
	 * @param   mixed  $list   Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed  $owner  Either an integer containing the user ID or a string containing the screen name of the owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function subscribe($list, $owner = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'subscribers/create');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username for owner is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/subscribers/create.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to check if the specified user is a member of the specified list.
	 *
	 * @param   mixed    $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name of the user to remove.
	 * @param   mixed    $owner        Either an integer containing the user ID or a string containing the screen name of the owner.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function isMember($list, $user, $owner = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'members/show');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/members/show.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to check if the specified user is a subscriber of the specified list.
	 *
	 * @param   mixed    $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name of the user to remove.
	 * @param   mixed    $owner        Either an integer containing the user ID or a string containing the screen name of the owner.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function isSubscriber($list, $user, $owner = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'subscribers/show');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/subscribers/show.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to unsubscribe the authenticated user from the specified list.
	 *
	 * @param   mixed  $list   Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed  $owner  Either an integer containing the user ID or a string containing the screen name of the owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function unsubscribe($list, $owner = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'subscribers/destroy');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/subscribers/destroy.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to add multiple members to a list, by specifying a comma-separated list of member ids or screen names.
	 *
	 * @param   mixed   $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   string  $user_id      A comma separated list of user IDs, up to 100 are allowed in a single request.
	 * @param   string  $screen_name  A comma separated list of screen names, up to 100 are allowed in a single request.
	 * @param   mixed   $owner        Either an integer containing the user ID or a string containing the screen name of the owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function addMembers($list, $user_id = null, $screen_name = null, $owner = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'members/create_all');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		if ($user_id)
		{
			$data['user_id'] = $user_id;
		}

		if ($screen_name)
		{
			$data['screen_name'] = $screen_name;
		}

		if ($user_id == null && $screen_name == null)
		{
			// We don't have a valid entry
			throw new RuntimeException('You must specify either a comma separated list of screen names, user IDs, or a combination of the two');
		}

		// Set the API path
		$path = '/lists/members/create_all.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to get the members of the specified list.
	 *
	 * @param   mixed    $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed    $owner        Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities". This node offers a variety
	 * 								   of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getMembers($list, $owner = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'members');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/members.json';

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the specified list.
	 *
	 * @param   mixed  $list   Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed  $owner  Either an integer containing the user ID or a string containing the screen name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getListById($list, $owner = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'show');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/show.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get a collection of the lists the specified user is subscribed to, 20 lists per page by default. Does not include the user's own lists.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $count   The amount of results to return per page. Defaults to 20. Maximum of 1,000 when using cursors.
	 * @param   integer  $cursor  Breaks the results into pages. Provide a value of -1 to begin paging.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getSubscriptions($user, $count = 0, $cursor = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'subscriptions');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if count is specified.
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Check if cursor is specified.
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Set the API path
		$path = '/lists/subscriptions.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to update the specified list
	 *
	 * @param   mixed   $list         Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed   $owner        Either an integer containing the user ID or a string containing the screen name of the owner.
	 * @param   string  $name         The name of the list.
	 * @param   string  $mode         Whether your list is public or private. Values can be public or private. If no mode is
	 * 								  specified the list will be public.
	 * @param   string  $description  The description to give the list.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function update($list, $owner = null, $name = null, $mode = null, $description = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'update');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Check if name is specified.
		if ($name)
		{
			$data['name'] = $name;
		}

		// Check if mode is specified.
		if ($mode)
		{
			$data['mode'] = $mode;
		}

		// Check if description is specified.
		if ($description)
		{
			$data['description'] = $description;
		}

		// Set the API path
		$path = '/lists/update.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to create a new list for the authenticated user.
	 *
	 * @param   string  $name         The name of the list.
	 * @param   string  $mode         Whether your list is public or private. Values can be public or private. If no mode is
	 * 								  specified the list will be public.
	 * @param   string  $description  The description to give the list.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function create($name, $mode = null, $description = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'create');

		// Check if name is specified.
		if ($name)
		{
			$data['name'] = $name;
		}

		// Check if mode is specified.
		if ($mode)
		{
			$data['mode'] = $mode;
		}

		// Check if description is specified.
		if ($description)
		{
			$data['description'] = $description;
		}

		// Set the API path
		$path = '/lists/create.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to delete a specified list.
	 *
	 * @param   mixed  $list   Either an integer containing the list ID or a string containing the list slug.
	 * @param   mixed  $owner  Either an integer containing the user ID or a string containing the screen name of the owner.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function delete($list, $owner = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('lists', 'destroy');

		// Determine which type of data was passed for $list
		if (is_numeric($list))
		{
			$data['list_id'] = $list;
		}
		elseif (is_string($list))
		{
			$data['slug'] = $list;

			// In this case the owner is required.
			if (is_numeric($owner))
			{
				$data['owner_id'] = $owner;
			}
			elseif (is_string($owner))
			{
				$data['owner_screen_name'] = $owner;
			}
			else
			{
				// We don't have a valid entry
				throw new RuntimeException('The specified username for owner is not in the correct format; must use integer or string');
			}
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified list is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/lists/destroy.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/twitter/twitter.php000064400000007706152177723700011767 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with a Twitter API instance.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitter
{
	/**
	 * @var    Registry  Options for the JTwitter object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $client;

	/**
	 * @var    JTwitterOAuth The OAuth client.
	 * @since  3.1.4
	 */
	protected $oauth;

	/**
	 * @var    JTwitterFriends  Twitter API object for friends.
	 * @since  3.1.4
	 */
	protected $friends;

	/**
	 * @var    JTwitterUsers  Twitter API object for users.
	 * @since  3.1.4
	 */
	protected $users;

	/**
	 * @var    JTwitterHelp  Twitter API object for help.
	 * @since  3.1.4
	 */
	protected $help;

	/**
	 * @var    JTwitterStatuses  Twitter API object for statuses.
	 * @since  3.1.4
	 */
	protected $statuses;

	/**
	 * @var    JTwitterSearch  Twitter API object for search.
	 * @since  3.1.4
	 */
	protected $search;

	/**
	 * @var    JTwitterFavorites  Twitter API object for favorites.
	 * @since  3.1.4
	 */
	protected $favorites;

	/**
	 * @var    JTwitterDirectMessages  Twitter API object for direct messages.
	 * @since  3.1.4
	 */
	protected $directMessages;

	/**
	 * @var    JTwitterLists  Twitter API object for lists.
	 * @since  3.1.4
	 */
	protected $lists;

	/**
	 * @var    JTwitterPlaces  Twitter API object for places & geo.
	 * @since  3.1.4
	 */
	protected $places;

	/**
	 * @var    JTwitterTrends  Twitter API object for trends.
	 * @since  3.1.4
	 */
	protected $trends;

	/**
	 * @var    JTwitterBlock  Twitter API object for block.
	 * @since  3.1.4
	 */
	protected $block;

	/**
	 * @var    JTwitterProfile  Twitter API object for profile.
	 * @since  3.1.4
	 */
	protected $profile;

	/**
	 * Constructor.
	 *
	 * @param   JTwitterOauth  $oauth    The oauth client.
	 * @param   Registry       $options  Twitter options object.
	 * @param   JHttp          $client   The HTTP client object.
	 *
	 * @since   3.1.4
	 */
	public function __construct(JTwitterOAuth $oauth = null, Registry $options = null, JHttp $client = null)
	{
		$this->oauth = $oauth;
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JHttp($this->options);

		// Setup the default API url if not already set.
		$this->options->def('api.url', 'https://api.twitter.com/1.1');
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JTwitterObject  Twitter API object (statuses, users, favorites, etc.).
	 *
	 * @since   3.1.4
	 * @throws  InvalidArgumentException
	 */
	public function __get($name)
	{
		$class = 'JTwitter' . ucfirst($name);

		if (class_exists($class))
		{
			if (false == isset($this->$name))
			{
				$this->$name = new $class($this->options, $this->client, $this->oauth);
			}

			return $this->$name;
		}

		throw new InvalidArgumentException(sprintf('Argument %s produced an invalid class name: %s', $name, $class));
	}

	/**
	 * Get an option from the JTwitter instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JTwitter instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JTwitter  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/twitter/trends.php000064400000004775152177723700011567 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Trends class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterTrends extends JTwitterObject
{
	/**
	 * Method to get the top 10 trending topics for a specific WOEID, if trending information is available for it.
	 *
	 * @param   integer  $id       The Yahoo! Where On Earth ID of the location to return trending information for.
	 * 							   Global information is available by using 1 as the WOEID.
	 * @param   string   $exclude  Setting this equal to hashtags will remove all hashtags from the trends list.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getTrends($id, $exclude = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('trends', 'place');

		// Set the API path
		$path = '/trends/place.json';

		$data['id'] = $id;

		// Check if exclude is specified
		if ($exclude)
		{
			$data['exclude'] = $exclude;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the locations that Twitter has trending topic information for.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getLocations()
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('trends', 'available');

		// Set the API path
		$path = '/trends/available.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to get the locations that Twitter has trending topic information for, closest to a specified location.
	 *
	 * @param   float  $lat   The latitude to search around.
	 * @param   float  $long  The longitude to search around.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getClosest($lat = null, $long = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('trends', 'closest');

		// Set the API path
		$path = '/trends/closest.json';

		$data = array();

		// Check if lat is specified
		if ($lat)
		{
			$data['lat'] = $lat;
		}

		// Check if long is specified
		if ($long)
		{
			$data['long'] = $long;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}
}
joomla/twitter/friends.php000064400000031431152177723700011707 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Friends class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterFriends extends JTwitterObject
{
	/**
	 * Method to get an array of user IDs the specified user follows.
	 *
	 * @param   mixed    $user        Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $cursor      Causes the list of connections to be broken into pages of no more than 5000 IDs at a time.
	 * 								  The number of IDs returned is not guaranteed to be 5000 as suspended users are filtered out
	 * 								  after connections are queried. If no cursor is provided, a value of -1 will be assumed, which is the first "page."
	 * @param   boolean  $string_ids  Set to true to return IDs as strings, false to return as integers.
	 * @param   integer  $count       Specifies the number of IDs attempt retrieval of, up to a maximum of 5,000 per distinct request.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getFriendIds($user, $cursor = null, $string_ids = null, $count = 0)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friends', 'ids');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if string_ids is true
		if ($string_ids)
		{
			$data['stringify_ids'] = $string_ids;
		}

		// Check if count is specified
		if ($count > 0)
		{
			$data['count'] = $count;
		}

		// Set the API path
		$path = '/friends/ids.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to display detailed friend information between two users.
	 *
	 * @param   mixed  $user_a  Either an integer containing the user ID or a string containing the screen name of the first user.
	 * @param   mixed  $user_b  Either an integer containing the user ID or a string containing the screen name of the second user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getFriendshipDetails($user_a, $user_b)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friendships', 'show');

		// Determine which type of data was passed for $user_a
		if (is_numeric($user_a))
		{
			$data['source_id'] = $user_a;
		}
		elseif (is_string($user_a))
		{
			$data['source_screen_name'] = $user_a;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The first specified username is not in the correct format; must use integer or string');
		}

		// Determine which type of data was passed for $user_b
		if (is_numeric($user_b))
		{
			$data['target_id'] = $user_b;
		}
		elseif (is_string($user_b))
		{
			$data['target_screen_name'] = $user_b;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The second specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/friendships/show.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get an array of user IDs the specified user is followed by.
	 *
	 * @param   mixed    $user        Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $cursor      Causes the list of IDs to be broken into pages of no more than 5000 IDs at a time. The number of IDs returned
	 * 								  is not guaranteed to be 5000 as suspended users are filtered out after connections are queried. If no cursor
	 * 								  is provided, a value of -1 will be assumed, which is the first "page."
	 * @param   boolean  $string_ids  Set to true to return IDs as strings, false to return as integers.
	 * @param   integer  $count       Specifies the number of IDs attempt retrieval of, up to a maximum of 5,000 per distinct request.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getFollowerIds($user, $cursor = null, $string_ids = null, $count = 0)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('followers', 'ids');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/followers/ids.json';

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if string_ids is specified
		if (!is_null($string_ids))
		{
			$data['stringify_ids'] = $string_ids;
		}

		// Check if count is specified
		if (!is_null($count))
		{
			$data['count'] = $count;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to determine pending requests to follow the authenticating user.
	 *
	 * @param   integer  $cursor      Causes the list of IDs to be broken into pages of no more than 5000 IDs at a time. The number of IDs returned
	 * 								  is not guaranteed to be 5000 as suspended users are filtered out after connections are queried. If no cursor
	 * 								  is provided, a value of -1 will be assumed, which is the first "page."
	 * @param   boolean  $string_ids  Set to true to return IDs as strings, false to return as integers.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getFriendshipsIncoming($cursor = null, $string_ids = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friendships', 'incoming');

		$data = array();

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if string_ids is specified
		if (!is_null($string_ids))
		{
			$data['stringify_ids'] = $string_ids;
		}

		// Set the API path
		$path = '/friendships/incoming.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to determine every protected user for whom the authenticating user has a pending follow request.
	 *
	 * @param   integer  $cursor      Causes the list of IDs to be broken into pages of no more than 5000 IDs at a time. The number of IDs returned
	 * 								  is not guaranteed to be 5000 as suspended users are filtered out after connections are queried. If no cursor
	 * 								  is provided, a value of -1 will be assumed, which is the first "page."
	 * @param   boolean  $string_ids  Set to true to return IDs as strings, false to return as integers.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getFriendshipsOutgoing($cursor = null, $string_ids = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friendships', 'outgoing');

		$data = array();

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if string_ids is specified
		if (!is_null($string_ids))
		{
			$data['stringify_ids'] = $string_ids;
		}

		// Set the API path
		$path = '/friendships/outgoing.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Allows the authenticating users to follow the user specified in the ID parameter.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $follow  Enable notifications for the target user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function follow($user, $follow = false)
	{
		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if follow is true
		if ($follow)
		{
			$data['follow'] = $follow;
		}

		// Set the API path
		$path = '/friendships/create.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Allows the authenticating users to unfollow the user specified in the ID parameter.
	 *
	 * @param   mixed  $user  Either an integer containing the user ID or a string containing the screen name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function unfollow($user)
	{
		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API path
		$path = '/friendships/destroy.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to get the relationship of the authenticating user to the comma separated list of up to 100 screen_names or user_ids provided.
	 *
	 * @param   string  $screen_name  A comma separated list of screen names, up to 100 are allowed in a single request.
	 * @param   string  $id           A comma separated list of user IDs, up to 100 are allowed in a single request.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getFriendshipsLookup($screen_name = null, $id = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friendships', 'lookup');

		// Set user IDs and screen names.
		if ($id)
		{
			$data['user_id'] = $id;
		}

		if ($screen_name)
		{
			$data['screen_name'] = $screen_name;
		}

		if ($id == null && $screen_name == null)
		{
			// We don't have a valid entry
			throw new RuntimeException('You must specify either a comma separated list of screen names, user IDs, or a combination of the two');
		}

		// Set the API path
		$path = '/friendships/lookup.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Allows one to enable or disable retweets and device notifications from the specified user.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $device    Enable/disable device notifications from the target user.
	 * @param   boolean  $retweets  Enable/disable retweets from the target user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function updateFriendship($user, $device = null, $retweets = null)
	{
		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if device is specified.
		if (!is_null($device))
		{
			$data['device'] = $device;
		}

		// Check if retweets is specified.
		if (!is_null($retweets))
		{
			$data['retweets'] = $retweets;
		}

		// Set the API path
		$path = '/friendships/update.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to get the user ids that currently authenticated user does not want to see retweets from.
	 *
	 * @param   boolean  $string_ids  Set to true to return IDs as strings, false to return as integers.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getFriendshipNoRetweetIds($string_ids = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('friendships', 'no_retweets/ids');

		$data = array();

		// Check if string_ids is specified
		if (!is_null($string_ids))
		{
			$data['stringify_ids'] = $string_ids;
		}

		// Set the API path
		$path = '/friendships/no_retweets/ids.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}
}
joomla/twitter/object.php000064400000013501152177723700011521 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Twitter API object class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
abstract class JTwitterObject
{
	/**
	 * @var    Registry  Options for the Twitter object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $client;

	/**
	 * @var    JTwitterOAuth The OAuth client.
	 * @since  3.1.4
	 */
	protected $oauth;

	/**
	 * Constructor.
	 *
	 * @param   Registry       &$options  Twitter options object.
	 * @param   JHttp          $client    The HTTP client object.
	 * @param   JTwitterOAuth  $oauth     The OAuth client.
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry &$options = null, JHttp $client = null, JTwitterOAuth $oauth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JHttp($this->options);
		$this->oauth = $oauth;
	}

	/**
	 * Method to check the rate limit for the requesting IP address
	 *
	 * @param   string  $resource  A resource or a comma-separated list of resource families you want to know the current rate limit disposition for.
	 * @param   string  $action    An action for the specified resource, if only one resource is specified.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function checkRateLimit($resource = null, $action = null)
	{
		// Check the rate limit for remaining hits
		$rate_limit = $this->getRateLimit($resource);

		$property = '/' . $resource;

		if (!is_null($action))
		{
			$property .= '/' . $action;
		}

		if ($rate_limit->resources->$resource->$property->remaining == 0)
		{
			// The IP has exceeded the Twitter API rate limit
			throw new RuntimeException('This server has exceed the Twitter API rate limit for the given period.  The limit will reset at '
				. $rate_limit->resources->$resource->$property->reset
			);
		}
	}

	/**
	 * Method to build and return a full request URL for the request.  This method will
	 * add appropriate pagination details if necessary and also prepend the API url
	 * to have a complete URL for the request.
	 *
	 * @param   string  $path        URL to inflect
	 * @param   array   $parameters  The parameters passed in the URL.
	 *
	 * @return  string  The request URL.
	 *
	 * @since   3.1.4
	 */
	public function fetchUrl($path, $parameters = null)
	{
		if ($parameters)
		{
			foreach ($parameters as $key => $value)
			{
				if (strpos($path, '?') === false)
				{
					$path .= '?' . $key . '=' . $value;
				}
				else
				{
					$path .= '&' . $key . '=' . $value;
				}
			}
		}

		// Get a new JUri object fousing the api url and given path.
		if (strpos($path, 'http://search.twitter.com/search.json') === false)
		{
			$uri = new JUri($this->options->get('api.url') . $path);
		}
		else
		{
			$uri = new JUri($path);
		}

		return (string) $uri;
	}

	/**
	 * Method to retrieve the rate limit for the requesting IP address
	 *
	 * @param   string  $resource  A resource or a comma-separated list of resource families you want to know the current rate limit disposition for.
	 *
	 * @return  array  The JSON response decoded
	 *
	 * @since   3.1.4
	 */
	public function getRateLimit($resource)
	{
		// Build the request path.
		$path = '/application/rate_limit_status.json';

		if (!is_null($resource))
		{
			return $this->sendRequest($path, 'GET',  array('resources' => $resource));
		}

		return $this->sendRequest($path);
	}

	/**
	 * Method to send the request.
	 *
	 * @param   string  $path     The path of the request to make
	 * @param   string  $method   The request method.
	 * @param   mixed   $data     Either an associative array or a string to be sent with the post request.
	 * @param   array   $headers  An array of name-value pairs to include in the header of the request
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function sendRequest($path, $method = 'GET', $data = array(), $headers = array())
	{
		// Get the access token.
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters['oauth_token'] = $token['key'];

		// Send the request.
		$response = $this->oauth->oauthRequest($this->fetchUrl($path), $method, $parameters, $data, $headers);

		if (strpos($path, 'update_with_media') !== false)
		{
			// Check Media Rate Limit.
			$response_headers = $response->headers;

			if ($response_headers['x-mediaratelimit-remaining'] == 0)
			{
				// The IP has exceeded the Twitter API media rate limit
				throw new RuntimeException('This server has exceed the Twitter API media rate limit for the given period.  The limit will reset in '
					. $response_headers['x-mediaratelimit-reset'] . 'seconds.'
				);
			}
		}

		if (strpos($response->body, 'redirected') !== false)
		{
			return $response->headers['Location'];
		}

		return json_decode($response->body);
	}

	/**
	 * Get an option from the JTwitterObject instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JTwitterObject instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JTwitterObject  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/twitter/help.php000064400000002535152177723700011210 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Help class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterHelp extends JTwitterObject
{
	/**
	 * Method to get the supported languages from the API.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getLanguages()
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('help', 'languages');

		// Set the API path
		$path = '/help/languages.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to get the current configuration used by Twitter including twitter.com slugs which are not usernames,
	 * maximum photo resolutions, and t.co URL lengths.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getConfiguration()
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('help', 'configuration');

		// Set the API path
		$path = '/help/configuration.json';

		// Send the request.
		return $this->sendRequest($path);
	}
}
joomla/twitter/block.php000064400000011303152177723700011343 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Block class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterBlock extends JTwitterObject
{
	/**
	 * Method to get the user ids the authenticating user is blocking.
	 *
	 * @param   boolean  $stringify_ids  Provide this option to have ids returned as strings instead.
	 * @param   integer  $cursor         Causes the list of IDs to be broken into pages of no more than 5000 IDs at a time. The number of IDs returned
	 * 									 is not guaranteed to be 5000 as suspended users are filtered out after connections are queried. If no cursor
	 * 									 is provided, a value of -1 will be assumed, which is the first "page."
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getBlocking($stringify_ids = null, $cursor = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('blocks', 'ids');

		$data = array();

		// Check if stringify_ids is specified
		if (!is_null($stringify_ids))
		{
			$data['stringify_ids'] = $stringify_ids;
		}

		// Check if cursor is specified
		if (!is_null($stringify_ids))
		{
			$data['cursor'] = $cursor;
		}

		// Set the API path
		$path = '/blocks/ids.json';

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to block the specified user from following the authenticating user.
	 *
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function block($user, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('blocks', 'create');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_statuses is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Set the API path
		$path = '/blocks/create.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to unblock the specified user from following the authenticating user.
	 *
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function unblock($user, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('blocks', 'destroy');

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_statuses is specified
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Set the API path
		$path = '/blocks/destroy.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/twitter/statuses.php000064400000052445152177723700012140 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Statuses class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterStatuses extends JTwitterObject
{
	/**
	 * Method to get a single tweet with the given ID.
	 *
	 * @param   integer  $id          The ID of the tweet to retrieve.
	 * @param   boolean  $trim_user   When set to true, each tweet returned in a timeline will include a user object including only
	 *                                the status author's numerical ID.
	 * @param   boolean  $entities    When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                                about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $my_retweet  When set to either true, t or 1, any statuses returned that have been retweeted by the authenticating user will
	 *                                include an additional current_user_retweet node, containing the ID of the source status for the retweet.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getTweetById($id, $trim_user = null, $entities = null, $my_retweet = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'show/:id');

		// Set the API base
		$path = '/statuses/show/' . $id . '.json';

		$data = array();

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if my_retweet is specified
		if (!is_null($my_retweet))
		{
			$data['include_my_retweet'] = $my_retweet;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to retrieve the latest statuses from the specified user timeline.
	 *
	 * @param   mixed    $user         Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $count        Specifies the number of tweets to try and retrieve, up to a maximum of 200.  Retweets are always included
	 *                                 in the count, so it is always suggested to set $include_rts to true
	 * @param   boolean  $include_rts  When set to true, the timeline will contain native retweets in addition to the standard stream of tweets.
	 * @param   boolean  $no_replies   This parameter will prevent replies from appearing in the returned timeline. This parameter is only supported
	 *                                 for JSON and XML responses.
	 * @param   integer  $since_id     Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id       Returns results with an ID less than (that is, older than) the specified ID.
	 * @param   boolean  $trim_user    When set to true, each tweet returned in a timeline will include a user object including only
	 *                                 the status author's numerical ID.
	 * @param   boolean  $contributor  This parameter enhances the contributors element of the status response to include the screen_name of the
	 *                                 contributor. By default only the user_id of the contributor is included.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getUserTimeline($user, $count = 20, $include_rts = null, $no_replies = null, $since_id = 0, $max_id = 0, $trim_user = null,
		$contributor = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'user_timeline');

		$data = array();

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}
		else
		{
			// We don't have a valid entry
			throw new RuntimeException('The specified username is not in the correct format; must use integer or string');
		}

		// Set the API base
		$path = '/statuses/user_timeline.json';

		// Set the count string
		$data['count'] = $count;

		// Check if include_rts is specified
		if (!is_null($include_rts))
		{
			$data['include_rts'] = $include_rts;
		}

		// Check if no_replies is specified
		if (!is_null($no_replies))
		{
			$data['exclude_replies'] = $no_replies;
		}

		// Check if a since_id is specified
		if ($since_id > 0)
		{
			$data['since_id'] = (int) $since_id;
		}

		// Check if a max_id is specified
		if ($max_id > 0)
		{
			$data['max_id'] = (int) $max_id;
		}

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Check if contributor details is specified
		if (!is_null($contributor))
		{
			$data['contributor_details'] = $contributor;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to post a tweet.
	 *
	 * @param   string   $status                 The text of the tweet.
	 * @param   integer  $in_reply_to_status_id  The ID of an existing status that the update is in reply to.
	 * @param   float    $lat                    The latitude of the location this tweet refers to.
	 * @param   float    $long                   The longitude of the location this tweet refers to.
	 * @param   string   $place_id               A place in the world.
	 * @param   boolean  $display_coordinates    Whether or not to put a pin on the exact coordinates a tweet has been sent from.
	 * @param   boolean  $trim_user              When set to true, each tweet returned in a timeline will include a user object including only
	 *                                           the status author's numerical ID.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function tweet($status, $in_reply_to_status_id = null, $lat = null, $long = null, $place_id = null, $display_coordinates = null,
		$trim_user = null)
	{
		// Set the API base.
		$path = '/statuses/update.json';

		// Set POST data.
		$data = array('status' => utf8_encode($status));

		// Check if in_reply_to_status_id is specified.
		if ($in_reply_to_status_id)
		{
			$data['in_reply_to_status_id'] = $in_reply_to_status_id;
		}

		// Check if lat is specified.
		if ($lat)
		{
			$data['lat'] = $lat;
		}

		// Check if long is specified.
		if ($long)
		{
			$data['long'] = $long;
		}

		// Check if place_id is specified.
		if ($place_id)
		{
			$data['place_id'] = $place_id;
		}

		// Check if display_coordinates is specified.
		if (!is_null($display_coordinates))
		{
			$data['display_coordinates'] = $display_coordinates;
		}

		// Check if trim_user is specified.
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to retrieve the most recent mentions for the authenticating user.
	 *
	 * @param   integer  $count        Specifies the number of tweets to try and retrieve, up to a maximum of 200.  Retweets are always included
	 *                                 in the count, so it is always suggested to set $include_rts to true
	 * @param   boolean  $include_rts  When set to true, the timeline will contain native retweets in addition to the standard stream of tweets.
	 * @param   boolean  $entities     When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                                 about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   integer  $since_id     Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id       Returns results with an ID less than (that is, older than) the specified ID.
	 * @param   boolean  $trim_user    When set to true, each tweet returned in a timeline will include a user object including only
	 *                                 the status author's numerical ID.
	 * @param   string   $contributor  This parameter enhances the contributors element of the status response to include the screen_name
	 *                                 of the contributor.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getMentions($count = 20, $include_rts = null, $entities = null, $since_id = 0, $max_id = 0,
		$trim_user = null, $contributor = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'mentions_timeline');

		// Set the API base
		$path = '/statuses/mentions_timeline.json';

		// Set the count string
		$data['count'] = $count;

		// Check if include_rts is specified
		if (!is_null($include_rts))
		{
			$data['include_rts'] = $include_rts;
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if a since_id is specified
		if ($since_id > 0)
		{
			$data['since_id'] = (int) $since_id;
		}

		// Check if a max_id is specified
		if ($max_id > 0)
		{
			$data['max_id'] = (int) $max_id;
		}

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Check if contributor is specified
		if (!is_null($contributor))
		{
			$data['contributor_details'] = $contributor;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get the most recent tweets of the authenticated user that have been retweeted by others.
	 *
	 * @param   integer  $count          Specifies the number of tweets to try and retrieve, up to a maximum of 200.  Retweets are always included
	 *                                   in the count, so it is always suggested to set $include_rts to true
	 * @param   integer  $since_id       Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   boolean  $entities       When set to true,  each tweet will include a node called "entities,". This node offers a variety of metadata
	 *                                   about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $user_entities  The user entities node will be disincluded when set to false.
	 * @param   integer  $max_id         Returns results with an ID less than (that is, older than) the specified ID.
	 * @param   boolean  $trim_user      When set to true, each tweet returned in a timeline will include a user object including only
	 *                                   the status author's numerical ID.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getRetweetsOfMe($count = 20, $since_id = 0, $entities = null, $user_entities = null, $max_id = 0, $trim_user = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'retweets_of_me');

		// Set the API path
		$path = '/statuses/retweets_of_me.json';

		// Set the count string
		$data['count'] = $count;

		// Check if a since_id is specified
		if ($since_id > 0)
		{
			$data['since_id'] = (int) $since_id;
		}

		// Check if a max_id is specified
		if ($max_id > 0)
		{
			$data['max_id'] = (int) $max_id;
		}

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Check if entities is specified
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if entities is specified
		if (!is_null($user_entities))
		{
			$data['include_user_entities'] = $user_entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to show user objects of up to 100 members who retweeted the status.
	 *
	 * @param   integer  $id             The numerical ID of the desired status.
	 * @param   integer  $count          Specifies the number of retweets to try and retrieve, up to a maximum of 100.
	 * @param   integer  $cursor         Causes the list of IDs to be broken into pages of no more than 100 IDs at a time.
	 *                                   The number of IDs returned is not guaranteed to be 100 as suspended users are
	 *                                   filtered out after connections are queried. If no cursor is provided, a value of
	 *                                   -1 will be assumed, which is the first "page."
	 * @param   boolean  $stringify_ids  Set to true to return IDs as strings, false to return as integers.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getRetweeters($id, $count = 20, $cursor = null, $stringify_ids = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'retweeters/ids');

		// Set the API path
		$path = '/statuses/retweeters/ids.json';

		// Set the status id.
		$data['id'] = $id;

		// Set the count string
		$data['count'] = $count;

		// Check if cursor is specified
		if (!is_null($cursor))
		{
			$data['cursor'] = $cursor;
		}

		// Check if entities is specified
		if (!is_null($stringify_ids))
		{
			$data['stringify_ids'] = $stringify_ids;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to get up to 100 of the first retweets of a given tweet.
	 *
	 * @param   integer  $id         The numerical ID of the desired status.
	 * @param   integer  $count      Specifies the number of tweets to try and retrieve, up to a maximum of 200.  Retweets are always included
	 *                               in the count, so it is always suggested to set $include_rts to true
	 * @param   boolean  $trim_user  When set to true, each tweet returned in a timeline will include a user object including only
	 *                               the status author's numerical ID.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getRetweetsById($id, $count = 20, $trim_user = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('statuses', 'retweets/:id');

		// Set the API path
		$path = '/statuses/retweets/' . $id . '.json';

		// Set the count string
		$data['count'] = $count;

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to delete the status specified by the required ID parameter.
	 *
	 * @param   integer  $id         The numerical ID of the desired status.
	 * @param   boolean  $trim_user  When set to true, each tweet returned in a timeline will include a user object including only
	 *                               the status author's numerical ID.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function deleteTweet($id, $trim_user = null)
	{
		// Set the API path
		$path = '/statuses/destroy/' . $id . '.json';

		$data = array();

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to retweet a tweet.
	 *
	 * @param   integer  $id         The numerical ID of the desired status.
	 * @param   boolean  $trim_user  When set to true, each tweet returned in a timeline will include a user object including only
	 *                               the status author's numerical ID.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function retweet($id, $trim_user = null)
	{
		// Set the API path
		$path = '/statuses/retweet/' . $id . '.json';

		$data = array();

		// Check if trim_user is specified
		if (!is_null($trim_user))
		{
			$data['trim_user'] = $trim_user;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to post a tweet with media.
	 *
	 * @param   string   $status                 The text of the tweet.
	 * @param   string   $media                  File to upload
	 * @param   integer  $in_reply_to_status_id  The ID of an existing status that the update is in reply to.
	 * @param   float    $lat                    The latitude of the location this tweet refers to.
	 * @param   float    $long                   The longitude of the location this tweet refers to.
	 * @param   string   $place_id               A place in the world.
	 * @param   boolean  $display_coordinates    Whether or not to put a pin on the exact coordinates a tweet has been sent from.
	 * @param   boolean  $sensitive              Set to true for content which may not be suitable for every audience.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function tweetWithMedia($status, $media, $in_reply_to_status_id = null, $lat = null, $long = null, $place_id = null,
		$display_coordinates = null, $sensitive = null)
	{
		// Set the API request path.
		$path = '/statuses/update_with_media.json';

		// Set POST data.
		$data = array(
			'status' => utf8_encode($status),
			'media[]' => "@{$media}",
		);

		$header = array('Content-Type' => 'multipart/form-data');

		// Check if in_reply_to_status_id is specified.
		if (!is_null($in_reply_to_status_id))
		{
			$data['in_reply_to_status_id'] = $in_reply_to_status_id;
		}

		// Check if lat is specified.
		if ($lat)
		{
			$data['lat'] = $lat;
		}

		// Check if long is specified.
		if ($long)
		{
			$data['long'] = $long;
		}

		// Check if place_id is specified.
		if ($place_id)
		{
			$data['place_id'] = $place_id;
		}

		// Check if display_coordinates is specified.
		if (!is_null($display_coordinates))
		{
			$data['display_coordinates'] = $display_coordinates;
		}

		// Check if sensitive is specified.
		if (!is_null($sensitive))
		{
			$data['possibly_sensitive'] = $sensitive;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data, $header);
	}

	/**
	 * Method to get information allowing the creation of an embedded representation of a Tweet on third party sites.
	 * Note: either the id or url parameters must be specified in a request. It is not necessary to include both.
	 *
	 * @param   integer  $id           The Tweet/status ID to return embed code for.
	 * @param   string   $url          The URL of the Tweet/status to be embedded.
	 * @param   integer  $maxwidth     The maximum width in pixels that the embed should be rendered at. This value is constrained to be
	 *                                 between 250 and 550 pixels.
	 * @param   boolean  $hide_media   Specifies whether the embedded Tweet should automatically expand images which were uploaded via
	 *                                 POST statuses/update_with_media.
	 * @param   boolean  $hide_thread  Specifies whether the embedded Tweet should automatically show the original message in the case that
	 *                                 the embedded Tweet is a reply.
	 * @param   boolean  $omit_script  Specifies whether the embedded Tweet HTML should include a `<script>` element pointing to widgets.js.
	 *                                 In cases where a page already includes widgets.js, setting this value to true will prevent a redundant
	 *                                 script element from being included.
	 * @param   string   $align        Specifies whether the embedded Tweet should be left aligned, right aligned, or centered in the page.
	 *                                 Valid values are left, right, center, and none.
	 * @param   string   $related      A value for the TWT related parameter, as described in Web Intents. This value will be forwarded to all
	 *                                 Web Intents calls.
	 * @param   string   $lang         Language code for the rendered embed. This will affect the text and localization of the rendered HTML.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function getOembed($id = null, $url = null, $maxwidth = null, $hide_media = null, $hide_thread = null, $omit_script = null,
		$align = null, $related = null, $lang = null)
	{
		// Check the rate limit for remaining hits.
		$this->checkRateLimit('statuses', 'oembed');

		// Set the API request path.
		$path = '/statuses/oembed.json';

		// Determine which of $id and $url is specified.
		if ($id)
		{
			$data['id'] = $id;
		}
		elseif ($url)
		{
			$data['url'] = rawurlencode($url);
		}
		else
		{
			// We don't have a valid entry.
			throw new RuntimeException('Either the id or url parameters must be specified in a request.');
		}

		// Check if maxwidth is specified.
		if ($maxwidth)
		{
			$data['maxwidth'] = $maxwidth;
		}

		// Check if hide_media is specified.
		if (!is_null($hide_media))
		{
			$data['hide_media'] = $hide_media;
		}

		// Check if hide_thread is specified.
		if (!is_null($hide_thread))
		{
			$data['hide_thread'] = $hide_thread;
		}

		// Check if omit_script is specified.
		if (!is_null($omit_script))
		{
			$data['omit_script'] = $omit_script;
		}

		// Check if align is specified.
		if ($align)
		{
			$data['align'] = $align;
		}

		// Check if related is specified.
		if ($related)
		{
			$data['related'] = $related;
		}

		// Check if lang is specified.
		if ($lang)
		{
			$data['lang'] = $lang;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}
}
joomla/twitter/favorites.php000064400000007703152177723700012264 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Favorites class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterFavorites extends JTwitterObject
{
	/**
	 * Method to get the most recent favorite statuses for the authenticating or specified user.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the screen name.
	 * @param   integer  $count     Specifies the number of tweets to try and retrieve, up to a maximum of 200.  Retweets are always included
	 *                              in the count, so it is always suggested to set $include_rts to true
	 * @param   integer  $since_id  Returns results with an ID greater than (that is, more recent than) the specified ID.
	 * @param   integer  $max_id    Returns results with an ID less than (that is, older than) the specified ID.
	 * @param   boolean  $entities  When set to true,  each tweet will include a node called "entities,". This node offers a variety
	 * 								of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getFavorites($user = null, $count = 20, $since_id = 0, $max_id = 0, $entities = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('favorites', 'list');

		// Set the API path.
		$path = '/favorites/list.json';

		// Determine which type of data was passed for $user
		if (is_numeric($user))
		{
			$data['user_id'] = $user;
		}
		elseif (is_string($user))
		{
			$data['screen_name'] = $user;
		}

		// Set the count string
		$data['count'] = $count;

		// Check if since_id is specified.
		if ($since_id > 0)
		{
			$data['since_id'] = $since_id;
		}

		// Check if max_id is specified.
		if ($max_id > 0)
		{
			$data['max_id'] = $max_id;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to favorite the status specified in the ID parameter as the authenticating user
	 *
	 * @param   integer  $id        The numerical ID of the desired status.
	 * @param   boolean  $entities  When set to true,  each tweet will include a node called "entities,". This node offers a variety
	 * 								of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function createFavorites($id, $entities = null)
	{
		// Set the API path.
		$path = '/favorites/create.json';

		$data['id'] = $id;

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to un-favorites the status specified in the ID parameter as the authenticating user.
	 *
	 * @param   integer  $id        The numerical ID of the desired status.
	 * @param   boolean  $entities  When set to true,  each tweet will include a node called "entities,". This node offers a variety
	 * 								of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function deleteFavorites($id, $entities = null)
	{
		// Set the API path.
		$path = '/favorites/destroy.json';

		$data['id'] = $id;

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/twitter/profile.php000064400000024014152177723700011714 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Profile class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterProfile extends JTwitterObject
{
	/**
	 * Method to et values that users are able to set under the "Account" tab of their settings page.
	 *
	 * @param   string   $name         Full name associated with the profile. Maximum of 20 characters.
	 * @param   string   $url          URL associated with the profile. Will be prepended with "http://" if not present. Maximum of 100 characters.
	 * @param   string   $location     The city or country describing where the user of the account is located. The contents are not normalized
	 * 								   or geocoded in any way. Maximum of 30 characters.
	 * @param   string   $description  A description of the user owning the account. Maximum of 160 characters.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function updateProfile($name = null, $url = null, $location = null, $description = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('account', 'update_profile');

		$data = array();

		// Check if name is specified.
		if ($name)
		{
			$data['name'] = $name;
		}

		// Check if url is specified.
		if ($url)
		{
			$data['url'] = $url;
		}

		// Check if location is specified.
		if ($location)
		{
			$data['location'] = $location;
		}

		// Check if description is specified.
		if ($description)
		{
			$data['description'] = $description;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified.
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Set the API path
		$path = '/account/update_profile.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to update the authenticating user's profile background image. This method can also be used to enable or disable the profile
	 * background image.
	 *
	 * @param   string   $image        The background image for the profile.
	 * @param   boolean  $tile         Whether or not to tile the background image.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 * @param   boolean  $use          Determines whether to display the profile background image or not.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function updateProfileBackgroundImage($image = null, $tile = false, $entities = null, $skip_status = null, $use = false)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('account', 'update_profile_background_image');

		$data = array();

		// Check if image is specified.
		if ($image)
		{
			$data['image'] = "@{$image}";
		}

		// Check if url is true.
		if ($tile)
		{
			$data['tile'] = $tile;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified.
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Check if use is true.
		if ($use)
		{
			$data['use'] = $use;
		}

		// Set the API path
		$path = '/account/update_profile_background_image.json';

		$header = array('Content-Type' => 'multipart/form-data', 'Expect' => '');

		// Send the request.
		return $this->sendRequest($path, 'POST', $data, $header);
	}

	/**
	 * Method to update the authenticating user's profile image.
	 *
	 * @param   string   $image        The background image for the profile.
	 * @param   boolean  $entities     When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 								   variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status  When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function updateProfileImage($image = null, $entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('account', 'update_profile_image');

		$data = array();

		// Check if image is specified.
		if ($image)
		{
			$data['image'] = "@{$image}";
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is specified.
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Set the API path
		$path = '/account/update_profile_image.json';

		$header = array('Content-Type' => 'multipart/form-data', 'Expect' => '');

		// Send the request.
		return $this->sendRequest($path, 'POST', $data, $header);
	}

	/**
	 * Method to set one or more hex values that control the color scheme of the authenticating user's profile page on twitter.com.
	 *
	 * @param   string   $background      Profile background color.
	 * @param   string   $link            Profile link color.
	 * @param   string   $sidebar_border  Profile sidebar's border color.
	 * @param   string   $sidebar_fill    Profile sidebar's fill color.
	 * @param   string   $text            Profile text color.
	 * @param   boolean  $entities        When set to either true, t or 1, each tweet will include a node called "entities,". This node offers a
	 * 									  variety of metadata about the tweet in a discreet structure, including: user_mentions, urls, and hashtags.
	 * @param   boolean  $skip_status     When set to either true, t or 1 statuses will not be included in the returned user objects.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function updateProfileColors($background = null, $link = null, $sidebar_border = null, $sidebar_fill = null, $text = null,
		$entities = null, $skip_status = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('account', 'update_profile_colors');

		$data = array();

		// Check if background is specified.
		if ($background)
		{
			$data['profile_background_color'] = $background;
		}

		// Check if link is specified.
		if ($link)
		{
			$data['profile_link_color'] = $link;
		}

		// Check if sidebar_border is specified.
		if ($sidebar_border)
		{
			$data['profile_sidebar_border_color'] = $sidebar_border;
		}

		// Check if sidebar_fill is specified.
		if ($sidebar_fill)
		{
			$data['profile_sidebar_fill_color'] = $sidebar_fill;
		}

		// Check if text is specified.
		if ($text)
		{
			$data['profile_text_color'] = $text;
		}

		// Check if entities is specified.
		if (!is_null($entities))
		{
			$data['include_entities'] = $entities;
		}

		// Check if skip_status is true.
		if (!is_null($skip_status))
		{
			$data['skip_status'] = $skip_status;
		}

		// Set the API path
		$path = '/account/update_profile_colors.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}

	/**
	 * Method to get the settings (including current trend, geo and sleep time information) for the authenticating user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSettings()
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('account', 'settings');

		// Set the API path
		$path = '/account/settings.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to update the authenticating user's settings.
	 *
	 * @param   integer  $location     The Yahoo! Where On Earth ID to use as the user's default trend location.
	 * @param   boolean  $sleep_time   When set to true, t or 1, will enable sleep time for the user.
	 * @param   integer  $start_sleep  The hour that sleep time should begin if it is enabled.
	 * @param   integer  $end_sleep    The hour that sleep time should end if it is enabled.
	 * @param   string   $time_zone    The timezone dates and times should be displayed in for the user. The timezone must be one of the
	 * 								   Rails TimeZone names.
	 * @param   string   $lang         The language which Twitter should render in for this user.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function updateSettings($location = null, $sleep_time = false, $start_sleep = null, $end_sleep = null,
		$time_zone = null, $lang = null)
	{
		$data = array();

		// Check if location is specified.
		if ($location)
		{
			$data['trend_location_woeid '] = $location;
		}

		// Check if sleep_time is true.
		if ($sleep_time)
		{
			$data['sleep_time_enabled'] = $sleep_time;
		}

		// Check if start_sleep is specified.
		if ($start_sleep)
		{
			$data['start_sleep_time'] = $start_sleep;
		}

		// Check if end_sleep is specified.
		if ($end_sleep)
		{
			$data['end_sleep_time'] = $end_sleep;
		}

		// Check if time_zone is specified.
		if ($time_zone)
		{
			$data['time_zone'] = $time_zone;
		}

		// Check if lang is specified.
		if ($lang)
		{
			$data['lang'] = $lang;
		}

		// Set the API path
		$path = '/account/settings.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/twitter/oauth.php000064400000006556152177723700011407 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for generating Twitter API access token.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterOAuth extends JOAuth1Client
{
	/**
	* @var    Registry  Options for the JTwitterOauth object.
	* @since  3.1.4
	*/
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry         $options      JTwitterOauth options object.
	 * @param   JHttp            $client       The HTTP client object.
	 * @param   JInput           $input        The input object.
	 * @param   JApplicationWeb  $application  The application object.
	 *
	 * @since   3.1.4
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null, JApplicationWeb $application = null)
	{
		$this->options = isset($options) ? $options : new Registry;

		$this->options->def('accessTokenURL', 'https://api.twitter.com/oauth/access_token');
		$this->options->def('authenticateURL', 'https://api.twitter.com/oauth/authenticate');
		$this->options->def('authoriseURL', 'https://api.twitter.com/oauth/authorize');
		$this->options->def('requestTokenURL', 'https://api.twitter.com/oauth/request_token');

		// Call the JOAuth1Client constructor to setup the object.
		parent::__construct($this->options, $client, $input, $application);
	}

	/**
	 * Method to verify if the access token is valid by making a request.
	 *
	 * @return  boolean  Returns true if the access token is valid and false otherwise.
	 *
	 * @since   3.1.4
	 */
	public function verifyCredentials()
	{
		$token = $this->getToken();

		// Set the parameters.
		$parameters = array('oauth_token' => $token['key']);

		// Set the API base
		$path = 'https://api.twitter.com/1.1/account/verify_credentials.json';

		// Send the request.
		$response = $this->oauthRequest($path, 'GET', $parameters);

		// Verify response
		if ($response->code == 200)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Ends the session of the authenticating user, returning a null cookie.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function endSession()
	{
		$token = $this->getToken();

		// Set parameters.
		$parameters = array('oauth_token' => $token['key']);

		// Set the API base
		$path = 'https://api.twitter.com/1.1/account/end_session.json';

		// Send the request.
		$response = $this->oauthRequest($path, 'POST', $parameters);

		return json_decode($response->body);
	}

	/**
	 * Method to validate a response.
	 *
	 * @param   string         $url       The request URL.
	 * @param   JHttpResponse  $response  The response to validate.
	 *
	 * @return  void
	 *
	 * @since  3.1.4
	 * @throws DomainException
	 */
	public function validateResponse($url, $response)
	{
		if (strpos($url, 'verify_credentials') === false && $response->code != 200)
		{
			$error = json_decode($response->body);

			if (property_exists($error, 'error'))
			{
				throw new DomainException($error->error);
			}
			else
			{
				$error = $error->errors;
				throw new DomainException($error[0]->message, $error[0]->code);
			}
		}
	}
}
joomla/twitter/places.php000064400000020755152177723700011533 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Twitter
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Twitter API Places & Geo class for the Joomla Platform.
 *
 * @since       3.1.4
 * @deprecated  4.0  Use the `joomla/twitter` package via Composer instead
 */
class JTwitterPlaces extends JTwitterObject
{
	/**
	 * Method to get all the information about a known place.
	 *
	 * @param   string  $id  A place in the world. These IDs can be retrieved using getGeocode.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getPlace($id)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('geo', 'id/:place_id');

		// Set the API path
		$path = '/geo/id/' . $id . '.json';

		// Send the request.
		return $this->sendRequest($path);
	}

	/**
	 * Method to get up to 20 places that can be used as a place_id when updating a status.
	 *
	 * @param   float    $lat          The latitude to search around.
	 * @param   float    $long         The longitude to search around.
	 * @param   string   $accuracy     A hint on the "region" in which to search. If a number, then this is a radius in meters,
	 * 								   but it can also take a string that is suffixed with ft to specify feet.
	 * @param   string   $granularity  This is the minimal granularity of place types to return and must be one of: poi, neighborhood,
	 * 								   city, admin or country.
	 * @param   integer  $max_results  A hint as to the number of results to return.
	 * @param   string   $callback     If supplied, the response will use the JSONP format with a callback of the given name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getGeocode($lat, $long, $accuracy = null, $granularity = null, $max_results = 0, $callback = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('geo', 'reverse_geocode');

		// Set the API path
		$path = '/geo/reverse_geocode.json';

		// Set the request parameters
		$data['lat'] = $lat;
		$data['long'] = $long;

		// Check if accuracy is specified
		if ($accuracy)
		{
			$data['accuracy'] = $accuracy;
		}

		// Check if granularity is specified
		if ($granularity)
		{
			$data['granularity'] = $granularity;
		}

		// Check if max_results is specified
		if ($max_results)
		{
			$data['max_results'] = $max_results;
		}

		// Check if callback is specified
		if ($callback)
		{
			$data['callback'] = $callback;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to search for places that can be attached to a statuses/update.
	 *
	 * @param   float    $lat          The latitude to search around.
	 * @param   float    $long         The longitude to search around.
	 * @param   string   $query        Free-form text to match against while executing a geo-based query, best suited for finding nearby
	 * 								   locations by name.
	 * @param   string   $ip           An IP address.
	 * @param   string   $granularity  This is the minimal granularity of place types to return and must be one of: poi, neighborhood, city,
	 * 								   admin or country.
	 * @param   string   $accuracy     A hint on the "region" in which to search. If a number, then this is a radius in meters, but it can
	 * 								   also take a string that is suffixed with ft to specify feet.
	 * @param   integer  $max_results  A hint as to the number of results to return.
	 * @param   string   $within       This is the place_id which you would like to restrict the search results to.
	 * @param   string   $attribute    This parameter searches for places which have this given street address.
	 * @param   string   $callback     If supplied, the response will use the JSONP format with a callback of the given name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function search($lat = null, $long = null, $query = null, $ip = null, $granularity = null, $accuracy = null, $max_results = 0,
		$within = null, $attribute = null, $callback = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('geo', 'search');

		// Set the API path
		$path = '/geo/search.json';

		// At least one of the following parameters must be provided: lat, long, ip, or query.
		if ($lat == null && $long == null && $ip == null && $query == null)
		{
			throw new RuntimeException('At least one of the following parameters must be provided: lat, long, ip, or query.');
		}

		// Check if lat is specified.
		if ($lat)
		{
			$data['lat'] = $lat;
		}

		// Check if long is specified.
		if ($long)
		{
			$data['long'] = $long;
		}

		// Check if query is specified.
		if ($query)
		{
			$data['query'] = rawurlencode($query);
		}

		// Check if ip is specified.
		if ($ip)
		{
			$data['ip'] = $ip;
		}

		// Check if granularity is specified
		if ($granularity)
		{
			$data['granularity'] = $granularity;
		}

		// Check if accuracy is specified
		if ($accuracy)
		{
			$data['accuracy'] = $accuracy;
		}

		// Check if max_results is specified
		if ($max_results)
		{
			$data['max_results'] = $max_results;
		}

		// Check if within is specified
		if ($within)
		{
			$data['contained_within'] = $within;
		}

		// Check if attribute is specified
		if ($attribute)
		{
			$data['attribute:street_address'] = rawurlencode($attribute);
		}

		// Check if callback is specified
		if ($callback)
		{
			$data['callback'] = $callback;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to locate places near the given coordinates which are similar in name.
	 *
	 * @param   float   $lat        The latitude to search around.
	 * @param   float   $long       The longitude to search around.
	 * @param   string  $name       The name a place is known as.
	 * @param   string  $within     This is the place_id which you would like to restrict the search results to.
	 * @param   string  $attribute  This parameter searches for places which have this given street address.
	 * @param   string  $callback   If supplied, the response will use the JSONP format with a callback of the given name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function getSimilarPlaces($lat, $long, $name, $within = null, $attribute = null, $callback = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('geo', 'similar_places');

		// Set the API path
		$path = '/geo/similar_places.json';

		$data['lat'] = $lat;
		$data['long'] = $long;
		$data['name'] = rawurlencode($name);

		// Check if within is specified
		if ($within)
		{
			$data['contained_within'] = $within;
		}

		// Check if attribute is specified
		if ($attribute)
		{
			$data['attribute:street_address'] = rawurlencode($attribute);
		}

		// Check if callback is specified
		if ($callback)
		{
			$data['callback'] = $callback;
		}

		// Send the request.
		return $this->sendRequest($path, 'GET', $data);
	}

	/**
	 * Method to create a new place object at the given latitude and longitude.
	 *
	 * @param   float   $lat        The latitude to search around.
	 * @param   float   $long       The longitude to search around.
	 * @param   string  $name       The name a place is known as.
	 * @param   string  $geo_token  The token found in the response from geo/similar_places.
	 * @param   string  $within     This is the place_id which you would like to restrict the search results to.
	 * @param   string  $attribute  This parameter searches for places which have this given street address.
	 * @param   string  $callback   If supplied, the response will use the JSONP format with a callback of the given name.
	 *
	 * @return  array  The decoded JSON response
	 *
	 * @since   3.1.4
	 */
	public function createPlace($lat, $long, $name, $geo_token, $within, $attribute = null, $callback = null)
	{
		// Check the rate limit for remaining hits
		$this->checkRateLimit('geo', 'place');

		$data['lat'] = $lat;
		$data['long'] = $long;
		$data['name'] = rawurlencode($name);
		$data['token'] = $geo_token;
		$data['contained_within'] = $within;

		// Check if attribute is specified
		if ($attribute)
		{
			$data['attribute:street_address'] = rawurlencode($attribute);
		}

		// Check if callback is specified
		if ($callback)
		{
			$data['callback'] = $callback;
		}

		// Set the API path
		$path = '/geo/place.json';

		// Send the request.
		return $this->sendRequest($path, 'POST', $data);
	}
}
joomla/view/html.php000064400000007136152177723700010476 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  View
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.path');

/**
 * Joomla Platform HTML View Class
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
abstract class JViewHtml extends JViewBase
{
	/**
	 * The view layout.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $layout = 'default';

	/**
	 * The paths queue.
	 *
	 * @var    SplPriorityQueue
	 * @since  3.0.0
	 */
	protected $paths;

	/**
	 * Method to instantiate the view.
	 *
	 * @param   JModel            $model  The model object.
	 * @param   SplPriorityQueue  $paths  The paths queue.
	 *
	 * @since   3.0.0
	 */
	public function __construct(JModel $model, SplPriorityQueue $paths = null)
	{
		parent::__construct($model);

		// Setup dependencies.
		$this->paths = isset($paths) ? $paths : $this->loadPaths();
	}

	/**
	 * Magic toString method that is a proxy for the render method.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function __toString()
	{
		return $this->render();
	}

	/**
	 * Method to escape output.
	 *
	 * @param   string  $output  The output to escape.
	 *
	 * @return  string  The escaped output.
	 *
	 * @note the ENT_COMPAT flag will be replaced by ENT_QUOTES in Joomla 4.0 to also escape single quotes
	 *
	 * @see     JView::escape()
	 * @since   3.0.0
	 */
	public function escape($output)
	{
		// Escape the output.
		return htmlspecialchars($output, ENT_COMPAT, 'UTF-8');
	}

	/**
	 * Method to get the view layout.
	 *
	 * @return  string  The layout name.
	 *
	 * @since   3.0.0
	 */
	public function getLayout()
	{
		return $this->layout;
	}

	/**
	 * Method to get the layout path.
	 *
	 * @param   string  $layout  The layout name.
	 *
	 * @return  mixed  The layout file name if found, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public function getPath($layout)
	{
		// Get the layout file name.
		$file = JPath::clean($layout . '.php');

		// Find the layout file path.
		$path = JPath::find(clone $this->paths, $file);

		return $path;
	}

	/**
	 * Method to get the view paths.
	 *
	 * @return  SplPriorityQueue  The paths queue.
	 *
	 * @since   3.0.0
	 */
	public function getPaths()
	{
		return $this->paths;
	}

	/**
	 * Method to render the view.
	 *
	 * @return  string  The rendered view.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function render()
	{
		// Get the layout path.
		$path = $this->getPath($this->getLayout());

		// Check if the layout path was found.
		if (!$path)
		{
			throw new RuntimeException('Layout Path Not Found');
		}

		// Start an output buffer.
		ob_start();

		// Load the layout.
		include $path;

		// Get the layout contents.
		$output = ob_get_clean();

		return $output;
	}

	/**
	 * Method to set the view layout.
	 *
	 * @param   string  $layout  The layout name.
	 *
	 * @return  JViewHtml  Method supports chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLayout($layout)
	{
		$this->layout = $layout;

		return $this;
	}

	/**
	 * Method to set the view paths.
	 *
	 * @param   SplPriorityQueue  $paths  The paths queue.
	 *
	 * @return  JViewHtml  Method supports chaining.
	 *
	 * @since   3.0.0
	 */
	public function setPaths(SplPriorityQueue $paths)
	{
		$this->paths = $paths;

		return $this;
	}

	/**
	 * Method to load the paths queue.
	 *
	 * @return  SplPriorityQueue  The paths queue.
	 *
	 * @since   3.0.0
	 */
	protected function loadPaths()
	{
		return new SplPriorityQueue;
	}
}
joomla/view/view.php000064400000001400152177723700010470 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  View
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform View Interface
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
interface JView
{
	/**
	 * Method to escape output.
	 *
	 * @param   string  $output  The output to escape.
	 *
	 * @return  string  The escaped output.
	 *
	 * @since   3.0.0
	 */
	public function escape($output);

	/**
	 * Method to render the view.
	 *
	 * @return  string  The rendered view.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function render();
}
joomla/view/base.php000064400000001742152177723700010441 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  View
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Base View Class
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
abstract class JViewBase implements JView
{
	/**
	 * The model object.
	 *
	 * @var    JModel
	 * @since  3.0.0
	 */
	protected $model;

	/**
	 * Method to instantiate the view.
	 *
	 * @param   JModel  $model  The model object.
	 *
	 * @since  3.0.0
	 */
	public function __construct(JModel $model)
	{
		// Setup dependencies.
		$this->model = $model;
	}

	/**
	 * Method to escape output.
	 *
	 * @param   string  $output  The output to escape.
	 *
	 * @return  string  The escaped output.
	 *
	 * @see     JView::escape()
	 * @since   3.0.0
	 */
	public function escape($output)
	{
		return $output;
	}
}
joomla/facebook/user.php000064400000116375152177723700011315 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API User class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/user/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookUser extends JFacebookObject
{
	/**
	 * Method to get the specified user's details. Authentication is required only for some fields.
	 *
	 * @param   mixed  $user  Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getUser($user)
	{
		return $this->get($user);
	}

	/**
	 * Method to get the specified user's friends. Requires authentication.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFriends($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'friends', '', $limit, $offset);
	}

	/**
	 * Method to get the user's incoming friend requests. Requires authentication and read_requests permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFriendRequests($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'friendrequests', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the user's friend lists. Requires authentication and read_friendlists permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFriendLists($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'friendlists', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the user's wall. Requires authentication and read_stream permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFeed($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'feed', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the user's news feed. Requires authentication and read_stream permission.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the username.
	 * @param   string   $filter    User's stream filter.
	 * @param   boolean  $location  Retreive only posts with a location attached.
	 * @param   integer  $limit     The number of objects per page.
	 * @param   integer  $offset    The object's number on the page.
	 * @param   string   $until     A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since     A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getHome($user, $filter = null, $location = false, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		$extra_fields = '';

		if ($filter != null)
		{
			$extra_fields = '?filter=' . $filter;
		}

		if ($location == true)
		{
			$extra_fields .= (strpos($extra_fields, '?') === false) ? '?with=location' : '&with=location';
		}

		return $this->getConnection($user, 'home', $extra_fields, $limit, $offset, $until, $since);
	}

	/**
	 * Method to see if a user is a friend of the current user. Requires authentication.
	 *
	 * @param   mixed  $current_user  Either an integer containing the user ID or a string containing the username for the current user.
	 * @param   mixed  $user          Either an integer containing the user ID or a string containing the username for the user.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function hasFriend($current_user, $user)
	{
		return $this->getConnection($current_user, 'friends/' . $user);
	}

	/**
	 * Method to get mutual friends of one user and the current user. Requires authentication.
	 *
	 * @param   mixed    $current_user  Either an integer containing the user ID or a string containing the username for the current user.
	 * @param   mixed    $user          Either an integer containing the user ID or a string containing the username for the user.
	 * @param   integer  $limit         The number of objects per page.
	 * @param   integer  $offset        The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getMutualFriends($current_user, $user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($current_user, 'mutualfriends/' . $user, '', $limit, $offset);
	}

	/**
	 * Method to get the user's profile picture. Requires authentication.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the username.
	 * @param   boolean  $redirect  If false this will return the URL of the profile picture without a 302 redirect.
	 * @param   string   $type      To request a different photo use square | small | normal | large.
	 *
	 * @return  string   The URL to the user's profile picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($user, $redirect = true, $type = null)
	{
		$extra_fields = '';

		if ($redirect == false)
		{
			$extra_fields = '?redirect=false';
		}

		if ($type != null)
		{
			$extra_fields .= (strpos($extra_fields, '?') === false) ? '?type=' . $type : '&type=' . $type;
		}

		return $this->getConnection($user, 'picture', $extra_fields);
	}

	/**
	 * Method to get the user's family relationships. Requires authentication and user_relationships permission..
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFamily($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'family', '', $limit, $offset);
	}

	/**
	 * Method to get the user's notifications. Requires authentication and manage_notifications permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   boolean  $read    Enables you to see notifications that the user has already read.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getNotifications($user, $read = null, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		if ($read == true)
		{
			$read = '?include_read=1';
		}

		// Send the request.
		return $this->getConnection($user, 'notifications', $read, $limit, $offset, $until, $since);
	}

	/**
	 * Method to mark a notification as read. Requires authentication and manage_notifications permission.
	 *
	 * @param   string  $notification  The notification id.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function updateNotification($notification)
	{
		$data['unread'] = 0;

		return $this->createConnection($notification, null, $data);
	}

	/**
	 * Method to get the user's permissions. Requires authentication.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPermissions($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'permissions', '', $limit, $offset);
	}

	/**
	 * Method to revoke a specific permission on behalf of a user. Requires authentication.
	 *
	 * @param   mixed   $user        Either an integer containing the user ID or a string containing the username.
	 * @param   string  $permission  The permission to revoke. If none specified, then this will de-authorize the application completely.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deletePermission($user, $permission = '')
	{
		return $this->deleteConnection($user, 'permissions', '?permission=' . $permission);
	}

	/**
	 * Method to get the user's albums. Requires authentication and user_photos or friends_photos permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getAlbums($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'albums', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to create an album for a user.  Requires authentication and publish_stream permission.
	 *
	 * @param   mixed   $user         Either an integer containing the user ID or a string containing the username.
	 * @param   string  $name         Album name.
	 * @param   string  $description  Album description.
	 * @param   string  $privacy      A JSON-encoded object that defines the privacy setting for the album.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createAlbum($user, $name, $description = null, $privacy = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['name'] = $name;
		$data['description'] = $description;
		$data['privacy'] = $privacy;

		return $this->createConnection($user, 'albums', $data);
	}

	/**
	 * Method to get the user's checkins. Requires authentication and user_checkins or friends_checkins permission
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getCheckins($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'checkins', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to create a checkin for a user. Requires authentication and publish_checkins permission.
	 *
	 * @param   mixed   $user         Either an integer containing the user ID or a string containing the username.
	 * @param   string  $place        Id of the Place Page.
	 * @param   string  $coordinates  A JSON-encoded string containing latitute and longitude.
	 * @param   string  $tags         Comma separated list of USER_IDs.
	 * @param   string  $message      A message to add to the checkin.
	 * @param   string  $link         A link to add to the checkin.
	 * @param   string  $picture      A picture to add to the checkin.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createCheckin($user, $place, $coordinates, $tags = null, $message = null, $link = null, $picture = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['place'] = $place;
		$data['coordinates'] = $coordinates;
		$data['tags'] = $tags;
		$data['message'] = $message;
		$data['link'] = $link;
		$data['picture'] = $picture;

		return $this->createConnection($user, 'checkins', $data);
	}

	/**
	 * Method to get the user's likes. Requires authentication and user_likes or friends_likes permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to see if a user likes a specific Page. Requires authentication.
	 *
	 * @param   mixed   $user  Either an integer containing the user ID or a string containing the username.
	 * @param   string  $page  Facebook ID of the Page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function likesPage($user, $page)
	{
		return $this->getConnection($user, 'likes/' . $page);
	}

	/**
	 * Method to get the current user's events. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getEvents($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'events', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to create an event for a user. Requires authentication create_event permission.
	 *
	 * @param   mixed   $user          Either an integer containing the user ID or a string containing the username.
	 * @param   string  $name          Event name.
	 * @param   string  $start_time    Event start time as UNIX timestamp.
	 * @param   string  $end_time      Event end time as UNIX timestamp.
	 * @param   string  $description   Event description.
	 * @param   string  $location      Event location.
	 * @param   string  $location_id   Facebook Place ID of the place the Event is taking place.
	 * @param   string  $privacy_type  Event privacy setting, a string containing 'OPEN' (default), 'CLOSED', or 'SECRET'.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createEvent($user, $name, $start_time, $end_time = null, $description = null,
		$location = null, $location_id = null, $privacy_type = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['start_time'] = $start_time;
		$data['name'] = $name;
		$data['end_time'] = $end_time;
		$data['description'] = $description;
		$data['location'] = $location;
		$data['location_id'] = $location_id;
		$data['privacy_type'] = $privacy_type;

		return $this->createConnection($user, 'events', $data);
	}

	/**
	 * Method to edit an event. Requires authentication create_event permission.
	 *
	 * @param   mixed   $event         Event ID.
	 * @param   string  $name          Event name.
	 * @param   string  $start_time    Event start time as UNIX timestamp.
	 * @param   string  $end_time      Event end time as UNIX timestamp.
	 * @param   string  $description   Event description.
	 * @param   string  $location      Event location.
	 * @param   string  $location_id   Facebook Place ID of the place the Event is taking place.
	 * @param   string  $privacy_type  Event privacy setting, a string containing 'OPEN' (default), 'CLOSED', or 'SECRET'.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function editEvent($event, $name = null, $start_time = null, $end_time = null, $description = null,
		$location = null, $location_id = null, $privacy_type = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['start_time'] = $start_time;
		$data['name'] = $name;
		$data['end_time'] = $end_time;
		$data['description'] = $description;
		$data['location'] = $location;
		$data['location_id'] = $location_id;
		$data['privacy_type'] = $privacy_type;

		return $this->createConnection($event, null, $data);
	}

	/**
	 * Method to delete an event. Note: you can only delete the event if it was created by the same app. Requires authentication create_event permission.
	 *
	 * @param   string  $event  Event ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteEvent($event)
	{
		return $this->deleteConnection($event);
	}

	/**
	 * Method to get the groups that the user belongs to. Requires authentication and user_groups or friends_groups permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getGroups($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'groups', '', $limit, $offset);
	}

	/**
	 * Method to get the user's posted links. Requires authentication and user_groups or friends_groups permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLinks($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'links', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a link on user's feed. Requires authentication and publish_stream permission.
	 *
	 * @param   mixed   $user     Either an integer containing the user ID or a string containing the username.
	 * @param   string  $link     Link URL.
	 * @param   string  $message  Link message.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createLink($user, $link, $message = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['link'] = $link;
		$data['message'] = $message;

		return $this->createConnection($user, 'feed', $data);
	}

	/**
	 * Method to delete a link. Requires authentication and publish_stream permission.
	 *
	 * @param   mixed  $link  The Link ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLink($link)
	{
		return $this->deleteConnection($link);
	}

	/**
	 * Method to get the user's notes. Requires authentication and user_groups or friends_groups permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getNotes($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'notes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to create a note on the behalf of the user.
	 * Requires authentication and publish_stream permission, user_groups or friends_groups permission.
	 *
	 * @param   mixed   $user     Either an integer containing the user ID or a string containing the username.
	 * @param   string  $subject  The subject of the note.
	 * @param   string  $message  Note content.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createNote($user, $subject, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['subject'] = $subject;
		$data['message'] = $message;

		return $this->createConnection($user, 'notes', $data);
	}

	/**
	 * Method to get the user's photos. Requires authentication and user_groups or friends_groups permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPhotos($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'photos', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a photo on user's wall. Requires authentication and publish_stream permission, user_groups or friends_groups permission.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the username.
	 * @param   string   $source    Path to photo.
	 * @param   string   $message   Photo description.
	 * @param   string   $place     Facebook ID of the place associated with the photo.
	 * @param   boolean  $no_story  If set to 1, optionally suppresses the feed story that is automatically
	 * 								generated on a user’s profile when they upload a photo using your application.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPhoto($user, $source, $message = null, $place = null, $no_story = null)
	{
		// Set POST request parameters.
		$data = array();
		$data[basename($source)] = '@' . realpath($source);
		$data['message'] = $message;
		$data['place'] = $place;
		$data['no_story'] = $no_story;

		return $this->createConnection($user, 'photos', $data, array('Content-Type' => 'multipart/form-data'));
	}

	/**
	 * Method to get the user's posts. Requires authentication and read_stream permission for non-public posts.
	 *
	 * @param   mixed    $user      Either an integer containing the user ID or a string containing the username.
	 * @param   boolean  $location  Retreive only posts with a location attached.
	 * @param   integer  $limit     The number of objects per page.
	 * @param   integer  $offset    The object's number on the page.
	 * @param   string   $until     A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since     A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPosts($user, $location = false, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		if ($location == true)
		{
			$location = '?with=location';
		}

		// Send the request.
		return $this->getConnection($user, 'posts', $location, $limit, $offset, $until, $since);
	}

	/**
	 * Method to post on a user's wall. Message or link parameter is required. Requires authentication and publish_stream permission.
	 *
	 * @param   mixed   $user               Either an integer containing the user ID or a string containing the username.
	 * @param   string  $message            Post message.
	 * @param   string  $link               Post URL.
	 * @param   string  $picture            Post thumbnail image (can only be used if link is specified)
	 * @param   string  $name               Post name (can only be used if link is specified).
	 * @param   string  $caption            Post caption (can only be used if link is specified).
	 * @param   string  $description        Post description (can only be used if link is specified).
	 * @param   array   $actions            Post actions array of objects containing name and link.
	 * @param   string  $place              Facebook Page ID of the location associated with this Post.
	 * @param   string  $tags               Comma-separated list of Facebook IDs of people tagged in this Post.
	 * 										For example: 1207059,701732. You cannot specify this field without also specifying a place.
	 * @param   string  $privacy            Post privacy settings (can only be specified if the Timeline being posted
	 * 										on belongs to the User creating the Post).
	 * @param   string  $object_attachment  Facebook ID for an existing picture in the User's photo albums to use as the thumbnail image.
	 *                                      The User must be the owner of the photo, and the photo cannot be part of a message attachment.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPost($user, $message = null, $link = null, $picture = null, $name = null, $caption = null,
		$description = null, $actions = null, $place = null, $tags = null, $privacy = null, $object_attachment = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;
		$data['link'] = $link;
		$data['name'] = $name;
		$data['caption'] = $caption;
		$data['description'] = $description;
		$data['actions'] = $actions;
		$data['place'] = $place;
		$data['tags'] = $tags;
		$data['privacy'] = $privacy;
		$data['object_attachment'] = $object_attachment;
		$data['picture'] = $picture;

		return $this->createConnection($user, 'feed', $data);
	}

	/**
	 * Method to delete a post. Note: you can only delete the post if it was created by the current user. Requires authentication
	 *
	 * @param   string  $post  The Post ID.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deletePost($post)
	{
		return $this->deleteConnection($post);
	}

	/**
	 * Method to get the user's statuses. Requires authentication read_stream permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getStatuses($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'statuses', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a status message on behalf of the user. Requires authentication publish_stream permission.
	 *
	 * @param   mixed   $user     Either an integer containing the user ID or a string containing the username.
	 * @param   string  $message  Status message content.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createStatus($user, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($user, 'feed', $data);
	}

	/**
	 * Method to delete a status. Note: you can only delete the post if it was created by the current user.
	 * Requires authentication publish_stream permission.
	 *
	 * @param   string  $status  The Status ID.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteStatus($status)
	{
		return $this->deleteConnection($status);
	}

	/**
	 * Method to get the videos the user has been tagged in. Requires authentication and user_videos or friends_videos permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getVideos($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'videos', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a video on behalf of the user. Requires authentication and publish_stream permission.
	 *
	 * @param   mixed   $user         Either an integer containing the user ID or a string containing the username.
	 * @param   string  $source       Path to video.
	 * @param   string  $title        Video title.
	 * @param   string  $description  Video description.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createVideo($user, $source, $title = null, $description = null)
	{
		// Set POST request parameters.
		$data = array();
		$data[basename($source)] = '@' . realpath($source);
		$data['title'] = $title;
		$data['description'] = $description;

		return $this->createConnection($user, 'videos', $data, array('Content-Type' => 'multipart/form-data'));
	}

	/**
	 * Method to get the posts the user has been tagged in. Requires authentication and user_videos or friends_videos permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getTagged($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'tagged', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the activities listed on the user's profile. Requires authentication and user_activities or friends_activities permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getActivities($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'activities', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the books listed on the user's profile. Requires authentication and user_likes or friends_likes permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getBooks($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'books', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the interests listed on the user's profile. Requires authentication.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getInterests($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'interests', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the movies listed on the user's profile. Requires authentication and user_likes or friends_likes permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getMovies($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'movies', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the television listed on the user's profile. Requires authentication and user_likes or friends_likes permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getTelevision($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'television', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the music listed on the user's profile. Requires authentication user_likes or friends_likes permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getMusic($user, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($user, 'music', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the user's subscribers. Requires authentication and user_subscriptions or friends_subscriptions permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getSubscribers($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'subscribers', '', $limit, $offset);
	}

	/**
	 * Method to get the people the user is subscribed to. Requires authentication and user_subscriptions or friends_subscriptions permission.
	 *
	 * @param   mixed    $user    Either an integer containing the user ID or a string containing the username.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getSubscribedTo($user, $limit = 0, $offset = 0)
	{
		return $this->getConnection($user, 'subscribedto', '', $limit, $offset);
	}
}
joomla/facebook/photo.php000064400000016744152177723700011467 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Photo class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/photo/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookPhoto extends JFacebookObject
{
	/**
	 * Method to get a photo. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $photo  The photo id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPhoto($photo)
	{
		return $this->get($photo);
	}

	/**
	 * Method to get a photo's comments. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $photo   The photo id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($photo, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($photo, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a photo. Requires authentication and publish_stream permission, user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $photo    The photo id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($photo, $message)
	{
		// Set POST request parameters.
		$data['message'] = $message;

		return $this->createConnection($photo, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission, user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get photo's likes. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $photo   The photo id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($photo, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($photo, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a photo. Requires authentication and publish_stream permission, user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $photo  The photo id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($photo)
	{
		return $this->createConnection($photo, 'likes');
	}

	/**
	 * Method to unlike a photo. Requires authentication and publish_stream permission, user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $photo  The photo id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($photo)
	{
		return $this->deleteConnection($photo, 'likes');
	}

	/**
	 * Method to get the Users tagged in the photo. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $photo   The photo id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getTags($photo, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($photo, 'tags', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to tag one or more Users in a photo. $to or $tag_text required.
	 * Requires authentication and publish_stream permission, user_photos permission for private photos.
	 *
	 * @param   string   $photo     The photo id.
	 * @param   mixed    $to        ID of the User or an array of Users to tag in the photo: [{"id":"1234"}, {"id":"12345"}].
	 * @param   string   $tag_text  A text string to tag.
	 * @param   integer  $x         x coordinate of tag, as a percentage offset from the left edge of the picture.
	 * @param   integer  $y         y coordinate of tag, as a percentage offset from the top edge of the picture.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createTag($photo, $to = null, $tag_text = null, $x = null, $y = null)
	{
		// Set POST request parameters.
		if (is_array($to))
		{
			$data['tags'] = $to;
		}
		else
		{
			$data['to'] = $to;
		}

		if ($tag_text)
		{
			$data['tag_text'] = $tag_text;
		}

		if ($x)
		{
			$data['x'] = $x;
		}

		if ($y)
		{
			$data['y'] = $y;
		}

		return $this->createConnection($photo, 'tags', $data);
	}

	/**
	 * Method to update the position of the tag for a particular Users in a photo.
	 * Requires authentication and publish_stream permission, user_photos permission for private photos.
	 *
	 * @param   string   $photo  The photo id.
	 * @param   string   $to     ID of the User to update tag in the photo.
	 * @param   integer  $x      x coordinate of tag, as a percentage offset from the left edge of the picture.
	 * @param   integer  $y      y coordinate of tag, as a percentage offset from the top edge of the picture.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function updateTag($photo, $to, $x = null, $y = null)
	{
		// Set POST request parameters.
		$data['to'] = $to;

		if ($x)
		{
			$data['x'] = $x;
		}

		if ($y)
		{
			$data['y'] = $y;
		}

		return $this->createConnection($photo, 'tags', $data);
	}

	/**
	 * Method to get the album-sized view of the photo. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $photo     The photo id.
	 * @param   boolean  $redirect  If false this will return the URL of the picture without a 302 redirect.
	 *
	 * @return  string  URL of the picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($photo, $redirect = true)
	{
		$extra_fields = '';

		if ($redirect == false)
		{
			$extra_fields = '?redirect=false';
		}

		return $this->getConnection($photo, 'picture', $extra_fields);
	}
}
joomla/facebook/object.php000064400000017143152177723700011576 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Facebook API object class for the Joomla Platform.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
abstract class JFacebookObject
{
	/**
	 * @var    Registry  Options for the Facebook object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * @var    JFacebookOAuth  The OAuth client.
	 * @since  3.2.0
	 */
	protected $oauth;

	/**
	 * Constructor.
	 *
	 * @param   Registry        $options  Facebook options object.
	 * @param   JHttp           $client   The HTTP client object.
	 * @param   JFacebookOAuth  $oauth    The OAuth client.
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JFacebookOAuth $oauth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JHttp($this->options);
		$this->oauth = $oauth;
	}

	/**
	 * Method to build and return a full request URL for the request.  This method will
	 * add appropriate pagination details if necessary and also prepend the API url
	 * to have a complete URL for the request.
	 *
	 * @param   string   $path    URL to inflect.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   integer  $until   A unix timestamp or any date accepted by strtotime.
	 * @param   integer  $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  string  The request URL.
	 *
	 * @since   3.2.0
	 */
	protected function fetchUrl($path, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		// Get a new JUri object fousing the api url and given path.
		$uri = new JUri($this->options->get('api.url') . $path);

		if ($limit > 0)
		{
			$uri->setVar('limit', (int) $limit);
		}

		if ($offset > 0)
		{
			$uri->setVar('offset', (int) $offset);
		}

		if ($until != null)
		{
			$uri->setVar('until', $until);
		}

		if ($since != null)
		{
			$uri->setVar('since', $since);
		}

		return (string) $uri;
	}

	/**
	 * Method to send the request.
	 *
	 * @param   string   $path     The path of the request to make.
	 * @param   mixed    $data     Either an associative array or a string to be sent with the post request.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request
	 * @param   integer  $limit    The number of objects per page.
	 * @param   integer  $offset   The object's number on the page.
	 * @param   string   $until    A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since    A unix timestamp or any date accepted by strtotime.
	 *
	 * @return   mixed  The request response.
	 *
	 * @since    3.2.0
	 * @throws   DomainException
	 */
	public function sendRequest($path, $data = '', array $headers = null, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		// Send the request.
		$response = $this->client->get($this->fetchUrl($path, $limit, $offset, $until, $since), $headers);

		$response = json_decode($response->body);

		// Validate the response.
		if (property_exists($response, 'error'))
		{
			throw new RuntimeException($response->error->message);
		}

		return $response;
	}

	/**
	 * Method to get an object.
	 *
	 * @param   string  $object  The object id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function get($object)
	{
		if ($this->oauth != null)
		{
			if ($this->oauth->isAuthenticated())
			{
				$response = $this->oauth->query($this->fetchUrl($object));

				return json_decode($response->body);
			}
			else
			{
				return false;
			}
		}

		// Send the request.
		return $this->sendRequest($object);
	}

	/**
	 * Method to get object's connection.
	 *
	 * @param   string   $object        The object id.
	 * @param   string   $connection    The object's connection name.
	 * @param   string   $extra_fields  URL fields.
	 * @param   integer  $limit         The number of objects per page.
	 * @param   integer  $offset        The object's number on the page.
	 * @param   string   $until         A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since         A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getConnection($object, $connection = null, $extra_fields = '', $limit = 0, $offset = 0, $until = null, $since = null)
	{
		$path = $object . '/' . $connection . $extra_fields;

		if ($this->oauth != null)
		{
			if ($this->oauth->isAuthenticated())
			{
				$response = $this->oauth->query($this->fetchUrl($path, $limit, $offset, $until, $since));

				if (strcmp($response->body, ''))
				{
					return json_decode($response->body);
				}
				else
				{
					return $response->headers['Location'];
				}
			}
			else
			{
				return false;
			}
		}

		// Send the request.
		return $this->sendRequest($path, '', null, $limit, $offset, $until, $since);
	}

	/**
	 * Method to create a connection.
	 *
	 * @param   string  $object      The object id.
	 * @param   string  $connection  The object's connection name.
	 * @param   array   $parameters  The POST request parameters.
	 * @param   array   $headers     An array of name-value pairs to include in the header of the request
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createConnection($object, $connection = null, $parameters = null, array $headers = null)
	{
		if ($this->oauth->isAuthenticated())
		{
			// Build the request path.
			if ($connection != null)
			{
				$path = $object . '/' . $connection;
			}
			else
			{
				$path = $object;
			}

			// Send the post request.
			$response = $this->oauth->query($this->fetchUrl($path), $parameters, $headers, 'post');

			return json_decode($response->body);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to delete a connection.
	 *
	 * @param   string  $object        The object id.
	 * @param   string  $connection    The object's connection name.
	 * @param   string  $extra_fields  URL fields.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteConnection($object, $connection = null, $extra_fields = '')
	{
		if ($this->oauth->isAuthenticated())
		{
			// Build the request path.
			if ($connection != null)
			{
				$path = $object . '/' . $connection . $extra_fields;
			}
			else
			{
				$path = $object . $extra_fields;
			}

			// Send the delete request.
			$response = $this->oauth->query($this->fetchUrl($path), null, array(), 'delete');

			return json_decode($response->body);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method used to set the OAuth client.
	 *
	 * @param   JFacebookOAuth  $oauth  The OAuth client object.
	 *
	 * @return  JFacebookObject  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOAuth($oauth)
	{
		$this->oauth = $oauth;

		return $this;
	}

	/**
	 * Method used to get the OAuth client.
	 *
	 * @return  JFacebookOAuth  The OAuth client
	 *
	 * @since   3.2.0
	 */
	public function getOAuth()
	{
		return $this->oauth;
	}
}
joomla/facebook/album.php000064400000014121152177723700011421 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Album class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/album/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookAlbum extends JFacebookObject
{
	/**
	 * Method to get an album. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string  $album  The album id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getAlbum($album)
	{
		return $this->get($album);
	}

	/**
	 * Method to get the photos contained in this album. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $album   The album id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPhotos($album, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($album, 'photos', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to add photos to an album. Note: check can_upload flag first. Requires authentication and publish_stream  permission.
	 *
	 * @param   string  $album    The album id.
	 * @param   string  $source   Path to photo.
	 * @param   string  $message  Photo description.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPhoto($album, $source, $message = null)
	{
		// Set POST request parameters.
		$data = array();
		$data[basename($source)] = '@' . realpath($source);

		if ($message)
		{
			$data['message'] = $message;
		}

		return $this->createConnection($album, 'photos', $data, array('Content-Type' => 'multipart/form-data'));
	}

	/**
	 * Method to get an album's comments. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $album   The album id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($album, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($album, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on an album. Requires authentication and publish_stream  permission.
	 *
	 * @param   string  $album    The album id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($album, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($album, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream  permission.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get album's likes. Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $album   The album id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($album, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($album, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like an album. Requires authentication and publish_stream  permission.
	 *
	 * @param   string  $album  The album id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($album)
	{
		return $this->createConnection($album, 'likes');
	}

	/**
	 * Method to unlike an album. Requires authentication and publish_stream  permission.
	 *
	 * @param   string  $album  The album id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($album)
	{
		return $this->deleteConnection($album, 'likes');
	}

	/**
	 * Method to get the album's cover photo, the first picture uploaded to an album becomes the cover photo for the album.
	 * Requires authentication and user_photos or friends_photos permission for private photos.
	 *
	 * @param   string   $album     The album id.
	 * @param   boolean  $redirect  If false this will return the URL of the picture without a 302 redirect.
	 *
	 * @return  string  URL of the picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($album, $redirect = true)
	{
		$extra_fields = '';

		if ($redirect == false)
		{
			$extra_fields = '?redirect=false';
		}

		return $this->getConnection($album, 'picture', $extra_fields);
	}
}
joomla/facebook/group.php000064400000016171152177723700011464 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Group class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/group/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookGroup extends JFacebookObject
{
	/**
	 * Method to read a group. Requires authentication and user_groups or friends_groups permission for non-public groups.
	 *
	 * @param   string  $group  The group id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getGroup($group)
	{
		return $this->get($group);
	}

	/**
	 * Method to get the group's wall. Requires authentication and user_groups or friends_groups permission for non-public groups.
	 *
	 * @param   string   $group   The group id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFeed($group, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($group, 'feed', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the group's members. Requires authentication and user_groups or friends_groups permission for non-public groups.
	 *
	 * @param   string   $group   The group id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getMembers($group, $limit = 0, $offset = 0)
	{
		return $this->getConnection($group, 'members', '', $limit, $offset);
	}

	/**
	 * Method to get the group's docs. Requires authentication and user_groups or friends_groups permission for non-public groups.
	 *
	 * @param   string   $group   The group id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getDocs($group, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($group, 'docs', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to get the groups's picture. Requires authentication and user_groups or friends_groups permission.
	 *
	 * @param   string  $group  The group id.
	 * @param   string  $type   To request a different photo use square | small | normal | large.
	 *
	 * @return  string   The URL to the group's picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($group, $type = null)
	{
		if ($type)
		{
			$type = '?type=' . $type;
		}

		return $this->getConnection($group, 'picture', $type);
	}

	/**
	 * Method to post a link on group's wall. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $group    The group id.
	 * @param   string  $link     Link URL.
	 * @param   string  $message  Link message.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createLink($group, $link, $message = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['link'] = $link;

		if ($message)
		{
			$data['message'] = $message;
		}

		return $this->createConnection($group, 'feed', $data);
	}

	/**
	 * Method to delete a link. Requires authentication.
	 *
	 * @param   mixed  $link  The Link ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLink($link)
	{
		return $this->deleteConnection($link);
	}

	/**
	 * Method to post on group's wall. Message or link parameter is required. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $group        The group id.
	 * @param   string  $message      Post message.
	 * @param   string  $link         Post URL.
	 * @param   string  $picture      Post thumbnail image (can only be used if link is specified)
	 * @param   string  $name         Post name (can only be used if link is specified).
	 * @param   string  $caption      Post caption (can only be used if link is specified).
	 * @param   string  $description  Post description (can only be used if link is specified).
	 * @param   array   $actions      Post actions array of objects containing name and link.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPost($group, $message = null, $link = null, $picture = null, $name = null, $caption = null,
		$description = null, $actions = null)
	{
		// Set POST request parameters.
		if ($message)
		{
			$data['message'] = $message;
		}

		if ($link)
		{
			$data['link'] = $link;
		}

		if ($name)
		{
			$data['name'] = $name;
		}

		if ($caption)
		{
			$data['caption'] = $caption;
		}

		if ($description)
		{
			$data['description'] = $description;
		}

		if ($actions)
		{
			$data['actions'] = $actions;
		}

		if ($picture)
		{
			$data['picture'] = $picture;
		}

		return $this->createConnection($group, 'feed', $data);
	}

	/**
	 * Method to delete a post. Note: you can only delete the post if it was created by the current user. Requires authentication.
	 *
	 * @param   string  $post  The Post ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deletePost($post)
	{
		return $this->deleteConnection($post);
	}

	/**
	 * Method to post a status message on behalf of the user on the group's wall. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $group    The group id.
	 * @param   string  $message  Status message content.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createStatus($group, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($group, 'feed', $data);
	}

	/**
	 * Method to delete a status. Note: you can only delete the status if it was created by the current user. Requires authentication.
	 *
	 * @param   string  $status  The Status ID.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteStatus($status)
	{
		return $this->deleteConnection($status);
	}
}
joomla/facebook/comment.php000064400000007430152177723700011770 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Comment class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/Comment/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookComment extends JFacebookObject
{
	/**
	 * Method to get a comment. Requires authentication.
	 *
	 * @param   string  $comment  The comment id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComment($comment)
	{
		return $this->get($comment);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $comment  The comment id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get a comment's comments. Requires authentication.
	 *
	 * @param   string   $comment  The comment id.
	 * @param   integer  $limit    The number of objects per page.
	 * @param   integer  $offset   The object's number on the page.
	 * @param   string   $until    A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since    A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($comment, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($comment, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a comment. Requires authentication with publish_stream permission.
	 *
	 * @param   string  $comment  The comment id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($comment, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($comment, 'comments', $data);
	}

	/**
	 * Method to get comment's likes. Requires authentication.
	 *
	 * @param   string   $comment  The comment id.
	 * @param   integer  $limit    The number of objects per page.
	 * @param   integer  $offset   The object's number on the page.
	 * @param   string   $until    A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since    A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($comment, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($comment, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a comment. Requires authentication and publish_stram permission.
	 *
	 * @param   string  $comment  The comment id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($comment)
	{
		return $this->createConnection($comment, 'likes');
	}

	/**
	 * Method to unlike a comment. Requires authentication and publish_stram permission.
	 *
	 * @param   string  $comment  The comment id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($comment)
	{
		return $this->deleteConnection($comment, 'likes');
	}
}
joomla/facebook/checkin.php000064400000010107152177723700011725 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Checkin class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/checkin/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookCheckin extends JFacebookObject
{
	/**
	 * Method to get a checkin. Requires authentication and user_checkins or friends_checkins permission.
	 *
	 * @param   string  $checkin  The checkin id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getCheckin($checkin)
	{
		return $this->get($checkin);
	}

	/**
	 * Method to get a checkin's comments. Requires authentication and user_checkins or friends_checkins permission.
	 *
	 * @param   string   $checkin  The checkin id.
	 * @param   integer  $limit    The number of objects per page.
	 * @param   integer  $offset   The object's number on the page.
	 * @param   string   $until    A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since    A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($checkin, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($checkin, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a comment to the checkin. Requires authentication and publish_stream and user_checkins or friends_checkins permission.
	 *
	 * @param   string  $checkin  The checkin id.
	 * @param   string  $message  The checkin's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($checkin, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($checkin, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get a checkin's likes. Requires authentication and user_checkins or friends_checkins permission.
	 *
	 * @param   string   $checkin  The checkin id.
	 * @param   integer  $limit    The number of objects per page.
	 * @param   integer  $offset   The object's number on the page.
	 * @param   string   $until    A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since    A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($checkin, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($checkin, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a checkin. Requires authentication and publish_stream and user_checkins or friends_checkins permission.
	 *
	 * @param   string  $checkin  The checkin id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createLike($checkin)
	{
		return $this->createConnection($checkin, 'likes');
	}

	/**
	 * Method to unlike a checkin. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $checkin  The checkin id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($checkin)
	{
		return $this->deleteConnection($checkin, 'likes');
	}
}
joomla/facebook/post.php000064400000010157152177723700011313 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Post class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/post/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookPost extends JFacebookObject
{
	/**
	 * Method to get a post. Requires authentication and read_stream permission for all data.
	 *
	 * @param   string  $post  The post id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPost($post)
	{
		return $this->get($post);
	}

	/**
	 * Method to delete a post if it was created by this application. Requires authentication and publish_stream permission
	 *
	 * @param   string  $post  The post id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deletePost($post)
	{
		return $this->deleteConnection($post);
	}

	/**
	 * Method to get a post's comments. Requires authentication and read_stream permission.
	 *
	 * @param   string   $post    The post id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($post, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($post, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a post. Requires authentication and publish_stream permission
	 *
	 * @param   string  $post     The post id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($post, $message)
	{
		// Set POST request parameters.
		$data['message'] = $message;

		return $this->createConnection($post, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get post's likes. Requires authentication and read_stream permission.
	 *
	 * @param   string   $post    The post id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($post, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($post, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a post. Requires authentication and publish_stream permission
	 *
	 * @param   string  $post  The post id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($post)
	{
		return $this->createConnection($post, 'likes');
	}

	/**
	 * Method to unlike a post. Requires authentication and publish_stream permission
	 *
	 * @param   string  $post  The post id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($post)
	{
		return $this->deleteConnection($post, 'likes');
	}
}
joomla/facebook/video.php000064400000010757152177723700011442 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Video class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/video/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookVideo extends JFacebookObject
{
	/**
	 * Method to get a video. Requires authentication and user_videos or friends_videos permission for private videos.
	 *
	 * @param   string  $video  The video id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getVideo($video)
	{
		return $this->get($video);
	}

	/**
	 * Method to get a video's comments. Requires authentication and user_videos or friends_videos permission for private videos.
	 *
	 * @param   string   $video   The video id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($video, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($video, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a video. Requires authentication and publish_stream permission, user_videos or friends_videos permission for private videos.
	 *
	 * @param   string  $video    The video id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($video, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($video, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission, user_videos or friends_videos permission for private videos.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get video's likes. Requires authentication and user_videos or friends_videos permission for private videos.
	 *
	 * @param   string   $video   The video id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($video, $limit=0, $offset=0, $until=null, $since=null)
	{
		return $this->getConnection($video, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a video. Requires authentication and publish_stream permission, user_videos or friends_videos permission for private videos.
	 *
	 * @param   string  $video  The video id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($video)
	{
		return $this->createConnection($video, 'likes');
	}

	/**
	 * Method to unlike a video. Requires authentication and publish_stream permission, user_videos or friends_videos permission for private videos.
	 *
	 * @param   string  $video  The video id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($video)
	{
		return $this->deleteConnection($video, 'likes');
	}

	/**
	 * Method to get the album-sized view of the video. Requires authentication and user_videos or friends_videos permission for private photos.
	 *
	 * @param   string  $video  The video id.
	 *
	 * @return  string  URL of the picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($video)
	{
		return $this->getConnection($video, 'picture');
	}
}
joomla/facebook/facebook.php000064400000007634152177723700012105 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with a Facebook API instance.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebook
{
	/**
	 * @var    Registry  Options for the Facebook object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * @var    JHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * @var    JFacebookOAuth  The OAuth client.
	 * @since  3.2.0
	 */
	protected $oauth;

	/**
	 * @var    JFacebookUser  Facebook API object for user.
	 * @since  3.2.0
	 */
	protected $user;

	/**
	 * @var    JFacebookStatus  Facebook API object for status.
	 * @since  3.2.0
	 */
	protected $status;

	/**
	 * @var    JFacebookCheckin  Facebook API object for checkin.
	 * @since  3.2.0
	 */
	protected $checkin;

	/**
	 * @var    JFacebookEvent  Facebook API object for event.
	 * @since  3.2.0
	 */
	protected $event;

	/**
	 * @var    JFacebookGroup  Facebook API object for group.
	 * @since  3.2.0
	 */
	protected $group;

	/**
	 * @var    JFacebookLink  Facebook API object for link.
	 * @since  3.2.0
	 */
	protected $link;

	/**
	 * @var    JFacebookNote  Facebook API object for note.
	 * @since  3.2.0
	 */
	protected $note;

	/**
	 * @var    JFacebookPost  Facebook API object for post.
	 * @since  3.2.0
	 */
	protected $post;

	/**
	 * @var    JFacebookComment  Facebook API object for comment.
	 * @since  3.2.0
	 */
	protected $comment;

	/**
	 * @var    JFacebookPhoto  Facebook API object for photo.
	 * @since  3.2.0
	 */
	protected $photo;

	/**
	 * @var    JFacebookVideo  Facebook API object for video.
	 * @since  3.2.0
	 */
	protected $video;

	/**
	 * @var    JFacebookAlbum  Facebook API object for album.
	 * @since  3.2.0
	 */
	protected $album;

	/**
	 * Constructor.
	 *
	 * @param   JFacebookOAuth  $oauth    OAuth client.
	 * @param   Registry        $options  Facebook options object.
	 * @param   JHttp           $client   The HTTP client object.
	 *
	 * @since   3.2.0
	 */
	public function __construct(JFacebookOAuth $oauth = null, Registry $options = null, JHttp $client = null)
	{
		$this->oauth = $oauth;
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JHttp($this->options);

		// Setup the default API url if not already set.
		$this->options->def('api.url', 'https://graph.facebook.com/');
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JFacebookObject  Facebook API object (status, user, friends etc).
	 *
	 * @since   3.2.0
	 * @throws  InvalidArgumentException
	 */
	public function __get($name)
	{
		$class = 'JFacebook' . ucfirst($name);

		if (class_exists($class))
		{
			if (false == isset($this->$name))
			{
				$this->$name = new $class($this->options, $this->client, $this->oauth);
			}

			return $this->$name;
		}

		throw new InvalidArgumentException(sprintf('Argument %s produced an invalid class name: %s', $name, $class));
	}

	/**
	 * Get an option from the JFacebook instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JFacebook instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JFacebook  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/facebook/note.php000064400000007775152177723700011307 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Note class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/note/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookNote extends JFacebookObject
{
	/**
	 * Method to get a note. Requires authentication and user_notes or friends_notes permission for non-public notes.
	 *
	 * @param   string  $note  The note id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getNote($note)
	{
		return $this->get($note);
	}

	/**
	 * Method to get a note's comments. Requires authentication and user_notes or friends_notes permission for non-public notes.
	 *
	 * @param   string   $note    The note id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($note, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($note, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a note. Requires authentication and publish_stream and user_notes or friends_notes permissions.
	 *
	 * @param   string  $note     The note id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($note, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($note, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream and user_notes or friends_notes permissions.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get note's likes. Requires authentication and user_notes or friends_notes for non-public notes.
	 *
	 * @param   string   $note    The note id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($note, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($note, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a note. Requires authentication and publish_stream and user_notes or friends_notes permissions.
	 *
	 * @param   string  $note  The note id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($note)
	{
		return $this->createConnection($note, 'likes');
	}

	/**
	 * Method to unlike a note. Requires authentication and publish_stream and user_notes or friends_notes permissions.
	 *
	 * @param   string  $note  The note id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($note)
	{
		return $this->deleteConnection($note, 'likes');
	}
}
joomla/facebook/oauth.php000064400000003521152177723700011443 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for generating Facebook API access token.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookOAuth extends JOAuth2Client
{
	/**
	 * @var    Registry Options for the JFacebookOAuth object.
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  JFacebookOauth options object.
	 * @param   JHttp     $client   The HTTP client object.
	 * @param   JInput    $input    The input object.
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null)
	{
		$this->options = isset($options) ? $options : new Registry;

		// Setup the authentication and token urls if not already set.
		$this->options->def('authurl', 'http://www.facebook.com/dialog/oauth');
		$this->options->def('tokenurl', 'https://graph.facebook.com/oauth/access_token');

		// Call the JOAuth2Client constructor to setup the object.
		parent::__construct($this->options, $client, $input);
	}

	/**
	 * Method used to set permissions.
	 *
	 * @param   string  $scope  Comma separated list of permissions.
	 *
	 * @return  JFacebookOauth  This object for method chaining
	 *
	 * @since   3.2.0
	 */
	public function setScope($scope)
	{
		$this->setOption('scope', $scope);

		return $this;
	}

	/**
	 * Method to get the current scope
	 *
	 * @return  string Comma separated list of permissions.
	 *
	 * @since   3.2.0
	 */
	public function getScope()
	{
		return $this->getOption('scope');
	}
}
joomla/facebook/link.php000064400000007552152177723700011270 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Link class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/link/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookLink extends JFacebookObject
{
	/**
	 * Method to get a link. Requires authentication and read_stream permission for non-public links.
	 *
	 * @param   string  $link  The link id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLink($link)
	{
		return $this->get($link);
	}

	/**
	 * Method to get a link's comments. Requires authentication and read_stream permission for non-public links.
	 *
	 * @param   string   $link    The link id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($link, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($link, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to comment on a link. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $link     The link id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($link, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($link, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get link's likes. Requires authentication and read_stream permission for non-public links.
	 *
	 * @param   string   $link    The link id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($link, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($link, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like a link. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $link  The link id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createLike($link)
	{
		return $this->createConnection($link, 'likes');
	}

	/**
	 * Method to unlike a link. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $link  The link id.
	 *
	 * @return  boolean Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($link)
	{
		return $this->deleteConnection($link, 'likes');
	}
}
joomla/facebook/status.php000064400000010042152177723700011642 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API Status class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/status/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookStatus extends JFacebookObject
{
	/**
	 * Method to get a status message. Requires authentication.
	 *
	 * @param   string  $status  The status message id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getStatus($status)
	{
		return $this->get($status);
	}

	/**
	 * Method to get a status message's comments. Requires authentication.
	 *
	 * @param   string   $status  The status message id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getComments($status, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($status, 'comments', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a comment to the status message. Requires authentication and publish_stream and user_status or friends_status permission.
	 *
	 * @param   string  $status   The status message id.
	 * @param   string  $message  The comment's text.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createComment($status, $message)
	{
		// Set POST request parameters.
		$data['message'] = $message;

		return $this->createConnection($status, 'comments', $data);
	}

	/**
	 * Method to delete a comment. Requires authentication and publish_stream and user_status or friends_status permission.
	 *
	 * @param   string  $comment  The comment's id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteComment($comment)
	{
		return $this->deleteConnection($comment);
	}

	/**
	 * Method to get a status message's likes. Requires authentication.
	 *
	 * @param   string   $status  The status message id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getLikes($status, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($status, 'likes', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to like status message. Requires authentication and publish_stream and user_status or friends_status permission.
	 *
	 * @param   string  $status  The status message id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createLike($status)
	{
		return $this->createConnection($status, 'likes');
	}

	/**
	 * Method to unlike a status message. Requires authentication and publish_stream and user_status or friends_status permission.
	 *
	 * @param   string  $status  The status message id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function deleteLike($status)
	{
		return $this->deleteConnection($status, 'likes');
	}
}
joomla/facebook/event.php000064400000040444152177723700011451 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Facebook
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Facebook API User class for the Joomla Platform.
 *
 * @link        http://developers.facebook.com/docs/reference/api/event/
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/facebook` package via Composer instead
 */
class JFacebookEvent extends JFacebookObject
{
	/**
	 * Method to get information about an event visible to the current user. Requires authentication.
	 *
	 * @param   string  $event  The event id.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getEvent($event)
	{
		return $this->get($event);
	}

	/**
	 * Method to get the event's wall. Requires authentication.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getFeed($event, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($event, 'feed', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a link on event's feed which the current_user is or maybe attending. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $event    The event id.
	 * @param   string  $link     Link URL.
	 * @param   string  $message  Link message.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createLink($event, $link, $message = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['link'] = $link;
		$data['message'] = $message;

		return $this->createConnection($event, 'feed', $data);
	}

	/**
	 * Method to delete a link. Requires authentication and publish_stream permission.
	 *
	 * @param   mixed  $link  The Link ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteLink($link)
	{
		return $this->deleteConnection($link);
	}

	/**
	 * Method to post on event's wall. Message or link parameter is required. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $event        The event id.
	 * @param   string  $message      Post message.
	 * @param   string  $link         Post URL.
	 * @param   string  $picture      Post thumbnail image (can only be used if link is specified)
	 * @param   string  $name         Post name (can only be used if link is specified).
	 * @param   string  $caption      Post caption (can only be used if link is specified).
	 * @param   string  $description  Post description (can only be used if link is specified).
	 * @param   array   $actions      Post actions array of objects containing name and link.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPost($event, $message = null, $link = null, $picture = null, $name = null, $caption = null,
		$description = null, $actions = null)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;
		$data['link'] = $link;
		$data['name'] = $name;
		$data['caption'] = $caption;
		$data['description'] = $description;
		$data['actions'] = $actions;
		$data['picture'] = $picture;

		return $this->createConnection($event, 'feed', $data);
	}

	/**
	 * Method to delete a post. Note: you can only delete the post if it was created by the current user.
	 * Requires authentication and publish_stream permission.
	 *
	 * @param   string  $post  The Post ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deletePost($post)
	{
		return $this->deleteConnection($post);
	}

	/**
	 * Method to post a status message on behalf of the user on the event's wall. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $event    The event id.
	 * @param   string  $message  Status message content.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createStatus($event, $message)
	{
		// Set POST request parameters.
		$data = array();
		$data['message'] = $message;

		return $this->createConnection($event, 'feed', $data);
	}

	/**
	 * Method to delete a status. Note: you can only delete the post if it was created by the current user.
	 * Requires authentication and publish_stream permission.
	 *
	 * @param   string  $status  The Status ID.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteStatus($status)
	{
		return $this->deleteConnection($status);
	}

	/**
	 * Method to get the list of invitees for the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getInvited($event, $limit = 0, $offset = 0)
	{
		return $this->getConnection($event, 'invited', '', $limit, $offset);
	}

	/**
	 * Method to check if a user is invited to the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   mixed   $user   Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  array   The decoded JSON response or an empty array if the user is not invited.
	 *
	 * @since   3.2.0
	 */
	public function isInvited($event, $user)
	{
		return $this->getConnection($event, 'invited/' . $user);
	}

	/**
	 * Method to invite users to the event. Requires authentication and create_event permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   string  $users  Comma separated list of user ids.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createInvite($event, $users)
	{
		// Set POST request parameters.
		$data = array();
		$data['users'] = $users;

		return $this->createConnection($event, 'invited', $data);
	}

	/**
	 * Method to delete an invitation. Note: you can only delete the invite if the current user is the event admin.
	 * Requires authentication and rsvp_event permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   string  $user   The user id.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function deleteInvite($event, $user)
	{
		return $this->deleteConnection($event, 'invited/' . $user);
	}

	/**
	 * Method to get the list of attending users. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getAttending($event, $limit = 0, $offset = 0)
	{
		return $this->getConnection($event, 'attending', '', $limit, $offset);
	}

	/**
	 * Method to check if a user is attending an event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   mixed   $user   Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  array   The decoded JSON response or an empty array if the user is not invited.
	 *
	 * @since   3.2.0
	 */
	public function isAttending($event, $user)
	{
		return $this->getConnection($event, 'attending/' . $user);
	}

	/**
	 * Method to set the current user as attending. Requires authentication and rsvp_event permission.
	 *
	 * @param   string  $event  The event id.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createAttending($event)
	{
		return $this->createConnection($event, 'attending');
	}

	/**
	 * Method to get the list of maybe attending users. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getMaybe($event, $limit = 0, $offset = 0)
	{
		return $this->getConnection($event, 'maybe', '', $limit, $offset);
	}

	/**
	 * Method to check if a user is maybe attending an event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   mixed   $user   Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  array   The decoded JSON response or an empty array if the user is not invited.
	 *
	 * @since   3.2.0
	 */
	public function isMaybe($event, $user)
	{
		return $this->getConnection($event, 'maybe/' . $user);
	}

	/**
	 * Method to set the current user as maybe attending. Requires authentication and rscp_event permission.
	 *
	 * @param   string  $event  The event id.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createMaybe($event)
	{
		return $this->createConnection($event, 'maybe');
	}

	/**
	 * Method to get the list of users which declined the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getDeclined($event, $limit = 0, $offset = 0)
	{
		return $this->getConnection($event, 'declined', '', $limit, $offset);
	}

	/**
	 * Method to check if a user responded 'no' to the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   mixed   $user   Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  array   The decoded JSON response or an empty array if the user is not invited.
	 *
	 * @since   3.2.0
	 */
	public function isDeclined($event, $user)
	{
		return $this->getConnection($event, 'declined/' . $user);
	}

	/**
	 * Method to set the current user as declined. Requires authentication and rscp_event permission.
	 *
	 * @param   string  $event  The event id.
	 *
	 * @return  boolean   Returns true if successful, and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function createDeclined($event)
	{
		return $this->createConnection($event, 'declined');
	}

	/**
	 * Method to get the list of users which have not replied to the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getNoreply($event, $limit = 0, $offset = 0)
	{
		return $this->getConnection($event, 'noreply', '', $limit, $offset);
	}

	/**
	 * Method to check if a user has not replied to the event. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string  $event  The event id.
	 * @param   mixed   $user   Either an integer containing the user ID or a string containing the username.
	 *
	 * @return  array   The decoded JSON response or an empty array if the user is not invited.
	 *
	 * @since   3.2.0
	 */
	public function isNoreply($event, $user)
	{
		return $this->getConnection($event, 'noreply/' . $user);
	}

	/**
	 * Method to get the event's profile picture. Requires authentication and user_events or friends_events permission.
	 *
	 * @param   string   $event     The event id.
	 * @param   boolean  $redirect  If false this will return the URL of the picture without a 302 redirect.
	 * @param   string   $type      To request a different photo use square | small | normal | large.
	 *
	 * @return  string   The URL to the event's profile picture.
	 *
	 * @since   3.2.0
	 */
	public function getPicture($event, $redirect = true, $type = null)
	{
		$extra_fields = '';

		if ($redirect == false)
		{
			$extra_fields = '?redirect=false';
		}

		if ($type)
		{
			$extra_fields .= (strpos($extra_fields, '?') === false) ? '?type=' . $type : '&type=' . $type;
		}

		return $this->getConnection($event, 'picture', $extra_fields);
	}

	/**
	 * Method to get photos published on event's wall. Requires authentication.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getPhotos($event, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($event, 'photos', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a photo on event's wall. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $event    The event id.
	 * @param   string  $source   Path to photo.
	 * @param   string  $message  Photo description.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createPhoto($event, $source, $message = null)
	{
		// Set POST request parameters.
		$data = array();
		$data[basename($source)] = '@' . realpath($source);

		if ($message)
		{
			$data['message'] = $message;
		}

		return $this->createConnection($event, 'photos', $data, array('Content-Type' => 'multipart/form-data'));
	}

	/**
	 * Method to get videos published on event's wall. Requires authentication.
	 *
	 * @param   string   $event   The event id.
	 * @param   integer  $limit   The number of objects per page.
	 * @param   integer  $offset  The object's number on the page.
	 * @param   string   $until   A unix timestamp or any date accepted by strtotime.
	 * @param   string   $since   A unix timestamp or any date accepted by strtotime.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function getVideos($event, $limit = 0, $offset = 0, $until = null, $since = null)
	{
		return $this->getConnection($event, 'videos', '', $limit, $offset, $until, $since);
	}

	/**
	 * Method to post a video on event's wall. Requires authentication and publish_stream permission.
	 *
	 * @param   string  $event        The event id.
	 * @param   string  $source       Path to photo.
	 * @param   string  $title        Video title.
	 * @param   string  $description  Video description.
	 *
	 * @return  mixed   The decoded JSON response or false if the client is not authenticated.
	 *
	 * @since   3.2.0
	 */
	public function createVideo($event, $source, $title = null, $description = null)
	{
		// Set POST request parameters.
		$data = array();
		$data[basename($source)] = '@' . realpath($source);

		if ($title)
		{
			$data['title'] = $title;
		}

		if ($description)
		{
			$data['description'] = $description;
		}

		return $this->createConnection($event, 'videos', $data, array('Content-Type' => 'multipart/form-data'));
	}
}
joomla/session/handler/interface.php000064400000005322152177723700013613 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Interface for managing HTTP sessions
 *
 * @since       3.5
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
interface JSessionHandlerInterface
{
	/**
	 * Starts the session.
	 *
	 * @return  boolean  True if started.
	 *
	 * @since   3.5
	 * @throws  RuntimeException If something goes wrong starting the session.
	 */
	public function start();

	/**
	 * Checks if the session is started.
	 *
	 * @return  boolean  True if started, false otherwise.
	 *
	 * @since   3.5
	 */
	public function isStarted();

	/**
	 * Returns the session ID
	 *
	 * @return  string  The session ID
	 *
	 * @since   3.5
	 */
	public function getId();

	/**
	 * Sets the session ID
	 *
	 * @param   string  $id  The session ID
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function setId($id);

	/**
	 * Returns the session name
	 *
	 * @return  mixed  The session name.
	 *
	 * @since   3.5
	 */
	public function getName();

	/**
	 * Sets the session name
	 *
	 * @param   string  $name  The name of the session
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function setName($name);

	/**
	 * Regenerates ID that represents this storage.
	 *
	 * Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent storage.
	 *
	 * @param   boolean  $destroy   Destroy session when regenerating?
	 * @param   integer  $lifetime  Sets the cookie lifetime for the session cookie. A null value will leave the system settings unchanged,
	 *                              0 sets the cookie to expire with browser session. Time is in seconds, and is not a Unix timestamp.
	 *
	 * @return  boolean  True if session regenerated, false if error
	 *
	 * @since   3.5
	 */
	public function regenerate($destroy = false, $lifetime = null);

	/**
	 * Force the session to be saved and closed.
	 *
	 * This method must invoke session_write_close() unless this interface is used for a storage object design for unit or functional testing where
	 * a real PHP session would interfere with testing, in which case it should actually persist the session data if required.
	 *
	 * @return  void
	 *
	 * @see     session_write_close()
	 * @since   3.5
	 * @throws  RuntimeException  If the session is saved without being started, or if the session is already closed.
	 */
	public function save();

	/**
	 * Clear all session data in memory.
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function clear();
}
joomla/session/handler/joomla.php000064400000006637152177723700013146 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Interface for managing HTTP sessions
 *
 * @since       3.5
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionHandlerJoomla extends JSessionHandlerNative
{
	/**
	 * The input object
	 *
	 * @var    JInput
	 * @since  3.5
	 */
	public $input = null;

	/**
	 * Force cookies to be SSL only
	 *
	 * @var    boolean
	 * @since  3.5
	 */
	protected $force_ssl = false;

	/**
	 * Public constructor
	 *
	 * @param   array  $options  An array of configuration options
	 *
	 * @since   3.5
	 */
	public function __construct($options = array())
	{
		if (!headers_sent())
		{
			// Disable transparent sid support
			ini_set('session.use_trans_sid', '0');

			// Only allow the session ID to come from cookies and nothing else.
			if ((int) ini_get('session.use_cookies') !== 1)
			{
				ini_set('session.use_only_cookies', 1);
			}
		}

		// Set options
		$this->setOptions($options);
		$this->setCookieParams();
	}

	/**
	 * Starts the session
	 *
	 * @return  boolean  True if started
	 *
	 * @since   3.5
	 * @throws  RuntimeException If something goes wrong starting the session.
	 */
	public function start()
	{
		$session_name = $this->getName();

		// Get the JInputCookie object
		$cookie = $this->input->cookie;

		if (is_null($cookie->get($session_name)))
		{
			$session_clean = $this->input->get($session_name, false, 'string');

			if ($session_clean)
			{
				$this->setId($session_clean);
				$cookie->set($session_name, '', 1);
			}
		}

		return parent::start();
	}

	/**
	 * Clear all session data in memory.
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function clear()
	{
		$sessionName = $this->getName();

		/*
		 * In order to kill the session altogether, such as to log the user out, the session id
		 * must also be unset. If a cookie is used to propagate the session id (default behavior),
		 * then the session cookie must be deleted.
		 * We need to use setcookie here or we will get a warning in some session handlers (ex: files).
		 */
		if (isset($_COOKIE[$sessionName]))
		{
			$cookie = session_get_cookie_params();

			setcookie($sessionName, '', 1, $cookie['path'], $cookie['domain'], $cookie['secure'], true);
		}

		parent::clear();
	}

	/**
	 * Set session cookie parameters
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	protected function setCookieParams()
	{
		if (headers_sent())
		{
			return;
		}

		$cookie = session_get_cookie_params();

		if ($this->force_ssl)
		{
			$cookie['secure'] = true;
		}

		$config = JFactory::getConfig();

		if ($config->get('cookie_domain', '') != '')
		{
			$cookie['domain'] = $config->get('cookie_domain');
		}

		if ($config->get('cookie_path', '') != '')
		{
			$cookie['path'] = $config->get('cookie_path');
		}

		session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], true);
	}

	/**
	 * Set additional session options
	 *
	 * @param   array  $options  List of parameter
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.5
	 */
	protected function setOptions(array $options)
	{
		if (isset($options['force_ssl']))
		{
			$this->force_ssl = (bool) $options['force_ssl'];
		}

		return true;
	}
}
joomla/session/handler/native.php000064400000013613152177723700013143 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Interface for managing HTTP sessions
 *
 * @since       3.5
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionHandlerNative implements JSessionHandlerInterface
{
	/**
	 * Has the session been started
	 *
	 * @var    boolean
	 * @since  3.5
	 */
	private $started = false;

	/**
	 * Has the session been closed
	 *
	 * @var    boolean
	 * @since  3.5
	 */
	private $closed = false;

	/**
	 * Starts the session
	 *
	 * @return  boolean  True if started
	 *
	 * @since   3.5
	 */
	public function start()
	{
		if ($this->isStarted())
		{
			return true;
		}

		$this->doSessionStart();

		return true;
	}

	/**
	 * Checks if the session is started.
	 *
	 * @return  boolean  True if started, false otherwise.
	 *
	 * @since   3.5
	 */
	public function isStarted()
	{
		return $this->started;
	}

	/**
	 * Returns the session ID
	 *
	 * @return  string  The session ID
	 *
	 * @since   3.5
	 */
	public function getId()
	{
		return session_id();
	}

	/**
	 * Sets the session ID
	 *
	 * @param   string  $id  The session ID
	 *
	 * @return  void
	 *
	 * @since   3.5
	 * @throws  LogicException
	 */
	public function setId($id)
	{
		if ($this->isStarted())
		{
			throw new LogicException('Cannot change the ID of an active session');
		}

		session_id($id);
	}

	/**
	 * Returns the session name
	 *
	 * @return  mixed  The session name
	 *
	 * @since   3.5
	 */
	public function getName()
	{
		return session_name();
	}

	/**
	 * Sets the session name
	 *
	 * @param   string  $name  The name of the session
	 *
	 * @return  void
	 *
	 * @since   3.5
	 * @throws  LogicException
	 */
	public function setName($name)
	{
		if ($this->isStarted())
		{
			throw new LogicException('Cannot change the name of an active session');
		}

		session_name($name);
	}

	/**
	 * Regenerates ID that represents this storage.
	 *
	 * Note regenerate+destroy should not clear the session data in memory only delete the session data from persistent storage.
	 *
	 * @param   boolean  $destroy   Destroy session when regenerating?
	 * @param   integer  $lifetime  Sets the cookie lifetime for the session cookie. A null value will leave the system settings unchanged,
	 *                              0 sets the cookie to expire with browser session. Time is in seconds, and is not a Unix timestamp.
	 *
	 * @return  boolean  True if session regenerated, false if error
	 *
	 * @since   3.5
	 */
	public function regenerate($destroy = false, $lifetime = null)
	{
		if (!headers_sent() && null !== $lifetime)
		{
			ini_set('session.cookie_lifetime', $lifetime);
		}

		$return = session_regenerate_id($destroy);

		// Workaround for https://bugs.php.net/bug.php?id=61470 as suggested by David Grudl
		session_write_close();
		$this->closed = true;

		if (isset($_SESSION))
		{
			$backup = $_SESSION;
			$this->doSessionStart();
			$_SESSION = $backup;
		}
		else
		{
			$this->doSessionStart();
		}

		return $return;
	}

	/**
	 * Force the session to be saved and closed.
	 *
	 * This method must invoke session_write_close() unless this interface is used for a storage object design for unit or functional testing where
	 * a real PHP session would interfere with testing, in which case it should actually persist the session data if required.
	 *
	 * @return  void
	 *
	 * @see     session_write_close()
	 * @since   3.5
	 */
	public function save()
	{
		// Verify if the session is active
		if ((version_compare(PHP_VERSION, '5.4', 'ge') && PHP_SESSION_ACTIVE === session_status())
			|| (version_compare(PHP_VERSION, '5.4', 'lt') && $this->started && isset($_SESSION) && $this->getId()))
		{
			$session = JFactory::getSession();
			$data    = $session->getData();

			// Before storing it, let's serialize and encode the Registry object
			$_SESSION['joomla'] = base64_encode(serialize($data));

			session_write_close();

			$this->closed  = true;
			$this->started = false;
		}
	}

	/**
	 * Clear all session data in memory.
	 *
	 * @return  void
	 *
	 * @since   3.5
	 */
	public function clear()
	{
		// Need to destroy any existing sessions started with session.auto_start
		if ($this->getId())
		{
			session_unset();
			session_destroy();
		}

		$this->closed  = true;
		$this->started = false;
	}

	/**
	 * Performs the session start mechanism
	 *
	 * @return  void
	 *
	 * @since   3.5.1
	 * @throws  RuntimeException If something goes wrong starting the session.
	 */
	private function doSessionStart()
	{
		// Register our function as shutdown method, so we can manipulate it
		register_shutdown_function(array($this, 'save'));

		// Disable the cache limiter
		session_cache_limiter('none');

		/*
		 * Extended checks to determine if the session has already been started
		 */

		// If running PHP 5.4, try to use the native API
		if (version_compare(PHP_VERSION, '5.4', 'ge') && PHP_SESSION_ACTIVE === session_status())
		{
			throw new RuntimeException('Failed to start the session: already started by PHP.');
		}

		// Fallback check for PHP 5.3
		if (version_compare(PHP_VERSION, '5.4', 'lt') && !$this->closed && isset($_SESSION) && $this->getId())
		{
			throw new RuntimeException('Failed to start the session: already started by PHP ($_SESSION is set).');
		}

		// If we are using cookies (default true) and headers have already been started (early output),
		if (ini_get('session.use_cookies') && headers_sent($file, $line))
		{
			throw new RuntimeException(sprintf('Failed to start the session because headers have already been sent by "%s" at line %d.', $file, $line));
		}

		// Ok to try and start the session
		if (!session_start())
		{
			throw new RuntimeException('Failed to start the session');
		}

		// Mark ourselves as started
		$this->started = true;
	}
}
joomla/session/storage.php000064400000011515152177723700011703 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Custom session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
abstract class JSessionStorage
{
	/**
	 * @var    JSessionStorage[]  JSessionStorage instances container.
	 * @since  1.7.3
	 */
	protected static $instances = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 */
	public function __construct($options = array())
	{
		$this->register($options);
	}

	/**
	 * Returns a session storage handler object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $name     The session store to instantiate
	 * @param   array   $options  Array of options
	 *
	 * @return  JSessionStorage
	 *
	 * @since   1.7.0
	 * @throws  JSessionExceptionUnsupported
	 */
	public static function getInstance($name = 'none', $options = array())
	{
		$name = strtolower(JFilterInput::getInstance()->clean($name, 'word'));

		if (empty(self::$instances[$name]))
		{
			/** @var JSessionStorage $class */
			$class = 'JSessionStorage' . ucfirst($name);

			if (!class_exists($class))
			{
				$path = __DIR__ . '/storage/' . $name . '.php';

				if (!file_exists($path))
				{
					throw new JSessionExceptionUnsupported('Unable to load session storage class: ' . $name);
				}

				JLoader::register($class, $path);

				// The class should now be loaded
				if (!class_exists($class))
				{
					throw new JSessionExceptionUnsupported('Unable to load session storage class: ' . $name);
				}
			}

			// Validate the session storage is supported on this platform
			if (!$class::isSupported())
			{
				throw new JSessionExceptionUnsupported(sprintf('The %s Session Storage is not supported on this platform.', $name));
			}

			self::$instances[$name] = new $class($options);
		}

		return self::$instances[$name];
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function register()
	{
		if (!headers_sent())
		{
			session_set_save_handler(
				array($this, 'open'),
				array($this, 'close'),
				array($this, 'read'),
				array($this, 'write'),
				array($this, 'destroy'),
				array($this, 'gc')
			);
		}
	}

	/**
	 * Open the SessionHandler backend.
	 *
	 * @param   string  $save_path     The path to the session object.
	 * @param   string  $session_name  The name of the session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function open($save_path, $session_name)
	{
		return true;
	}

	/**
	 * Close the SessionHandler backend.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function close()
	{
		return true;
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.7.0
	 */
	public function read($id)
	{
		return;
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function write($id, $session_data)
	{
		return true;
	}

	/**
	 * Destroy the data for a particular session identifier in the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function destroy($id)
	{
		return true;
	}

	/**
	 * Garbage collect stale sessions from the SessionHandler backend.
	 *
	 * @param   integer  $maxlifetime  The maximum age of a session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function gc($maxlifetime = null)
	{
		return true;
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return true;
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use JSessionStorage::isSupported() instead.
	 */
	public static function test()
	{
		JLog::add('JSessionStorage::test() is deprecated. Use JSessionStorage::isSupported() instead.', JLog::WARNING, 'deprecated');

		return static::isSupported();
	}
}
joomla/session/storage/redis.php000064400000005045152177723700013012 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Redis session storage handler for PHP
 *
 * @link   https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since  3.8.0
 */
class JSessionStorageRedis extends JSessionStorage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   3.8.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('Redis Extension is not available', 404);
		}

		$config = JFactory::getConfig();

		$this->_server = array(
			'host'    => $config->get('session_redis_server_host', 'localhost'),
			'port'    => $config->get('session_redis_server_port', 6379),
			'persist' => $config->get('session_redis_persist', true),
			'auth'    => $config->get('session_redis_server_auth', null),
			'db'      => (int) $config->get('session_redis_server_db', 0),
		);

		// If you are trying to connect to a socket file, ignore the supplied port
		if ($this->_server['host'][0] === '/')
		{
			$this->_server['port'] = 0;
		}

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   3.8.0
	 */
	public function register()
	{
		if (!empty($this->_server) && isset($this->_server['host'], $this->_server['port']))
		{
			if (!headers_sent())
			{
				if ($this->_server['port'] === 0)
				{
					$path = 'unix://' . $this->_server['host'];
				}
				else
				{
					$path = 'tcp://' . $this->_server['host'] . ":" . $this->_server['port'];
				}

				$persist = isset($this->_server['persist']) ? $this->_server['persist'] : false;
				$db      = isset($this->_server['db']) ? $this->_server['db'] : 0;

				$path .= '?persistent=' . (int) $persist . '&database=' . $db;

				if (!empty($this->_server['auth']))
				{
					$path .= '&auth=' . $this->_server['auth'];
				}

				ini_set('session.save_path', $path);
				ini_set('session.save_handler', 'redis');
			}

			// This is required if the configuration.php gzip is turned on
			ini_set('zlib.output_compression', 'Off');
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.8.0
	 */
	public static function isSupported()
	{
		return extension_loaded('redis') && class_exists('Redis');
	}
}
joomla/session/storage/xcache.php000064400000004325152177723700013137 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * XCache session storage handler
 *
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageXcache extends JSessionStorage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('XCache Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.7.0
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		// Check if id exists
		if (!xcache_isset($sess_id))
		{
			return;
		}

		return (string) xcache_get($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return xcache_set($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		if (!xcache_isset($sess_id))
		{
			return true;
		}

		return xcache_unset($sess_id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('xcache');
	}
}
joomla/session/storage/wincache.php000064400000002561152177723700013465 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * WINCACHE session storage handler for PHP
 *
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageWincache extends JSessionStorage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('Wincache Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function register()
	{
		if (!headers_sent())
		{
			ini_set('session.save_handler', 'wincache');
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('wincache') && function_exists('wincache_ucache_get') && !strcmp(ini_get('wincache.ucenabled'), '1');
	}
}
joomla/session/storage/apcu.php000064400000005012152177723700012626 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * APC session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       3.9
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageApcu extends JSessionStorage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   3.9
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('APCu Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   3.9
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		return (string) apcu_fetch($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.9
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return apcu_store($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.9
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		// The apcu_delete function returns false if the id does not exist
		return apcu_delete($sess_id = 'sess_' . $id) || !apcu_exists($sess_id = 'sess_' . $id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.9
	 */
	public static function isSupported()
	{
		$supported = extension_loaded('apcu') && ini_get('apc.enabled');

		// If on the CLI interface, the `apc.enable_cli` option must also be enabled
		if ($supported && php_sapi_name() === 'cli')
		{
			$supported = ini_get('apc.enable_cli');
		}

		return (bool) $supported;
	}
}
joomla/session/storage/none.php000064400000001402152177723700012634 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * File session handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageNone extends JSessionStorage
{
	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function register()
	{
		// Default session handler is `files`
	}
}
joomla/session/storage/memcached.php000064400000003612152177723700013610 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Memcached session storage handler for PHP
 *
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageMemcached extends JSessionStorage
{
	/**
	 * @var array Container for memcache server conf arrays
	 */
	private $_servers = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('Memcached Extension is not available', 404);
		}

		$config = JFactory::getConfig();

		// This will be an array of loveliness
		// @todo: multiple servers
		$this->_servers = array(
			array(
				'host' => $config->get('session_memcached_server_host', 'localhost'),
				'port' => $config->get('session_memcached_server_port', 11211),
			),
		);

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function register()
	{
		if (!empty($this->_servers) && isset($this->_servers[0]))
		{
			$serverConf = current($this->_servers);

			if (!headers_sent())
			{
				ini_set('session.save_path', "{$serverConf['host']}:{$serverConf['port']}");
				ini_set('session.save_handler', 'memcached');
			}
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('memcached') && class_exists('Memcached');
	}
}
joomla/session/storage/database.php000064400000007556152177723700013461 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Database session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageDatabase extends JSessionStorage
{
	/**
	 * Read the data for a particular session identifier from the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.7.0
	 */
	public function read($id)
	{
		// Get the database connection object and verify its connected.
		$db = JFactory::getDbo();

		try
		{
			// Get the session data from the database table.
			$query = $db->getQuery(true)
				->select($db->quoteName('data'))
			->from($db->quoteName('#__session'))
			->where($db->quoteName('session_id') . ' = ' . $db->quote($id));

			$db->setQuery($query);

			$result = (string) $db->loadResult();

			$result = str_replace('\0\0\0', chr(0) . '*' . chr(0), $result);

			return $result;
		}
		catch (RuntimeException $e)
		{
			return false;
		}
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id    The session identifier.
	 * @param   string  $data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function write($id, $data)
	{
		// Get the database connection object and verify its connected.
		$db = JFactory::getDbo();

		$data = str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);

		try
		{
			$query = $db->getQuery(true)
				->update($db->quoteName('#__session'))
				->set($db->quoteName('data') . ' = ' . $db->quote($data))
				->set($db->quoteName('time') . ' = ' . time())
				->where($db->quoteName('session_id') . ' = ' . $db->quote($id));

			// Try to update the session data in the database table.
			$db->setQuery($query);
			$db->execute();

			/*
			 * Since $db->execute did not throw an exception, so the query was successful.
			 * Either the data changed, or the data was identical.
			 * In either case we are done.
			 */
			return true;
		}
		catch (RuntimeException $e)
		{
			return false;
		}
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function destroy($id)
	{
		// Get the database connection object and verify its connected.
		$db = JFactory::getDbo();

		try
		{
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__session'))
				->where($db->quoteName('session_id') . ' = ' . $db->quote($id));

			// Remove a session from the database.
			$db->setQuery($query);

			return (boolean) $db->execute();
		}
		catch (RuntimeException $e)
		{
			return false;
		}
	}

	/**
	 * Garbage collect stale sessions from the SessionHandler backend.
	 *
	 * @param   integer  $lifetime  The maximum age of a session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function gc($lifetime = 1440)
	{
		// Get the database connection object and verify its connected.
		$db = JFactory::getDbo();

		// Determine the timestamp threshold with which to purge old sessions.
		$past = time() - $lifetime;

		try
		{
			$query = $db->getQuery(true)
				->delete($db->quoteName('#__session'))
				->where($db->quoteName('time') . ' < ' . (int) $past);

			// Remove expired sessions from the database.
			$db->setQuery($query);

			return (boolean) $db->execute();
		}
		catch (RuntimeException $e)
		{
			return false;
		}
	}
}
joomla/session/storage/memcache.php000064400000003602152177723700013443 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Memcache session storage handler for PHP
 *
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageMemcache extends JSessionStorage
{
	/**
	 * @var array Container for memcache server conf arrays
	 */
	private $_servers = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('Memcache Extension is not available', 404);
		}

		$config = JFactory::getConfig();

		// This will be an array of loveliness
		// @todo: multiple servers
		$this->_servers = array(
			array(
				'host' => $config->get('session_memcache_server_host', 'localhost'),
				'port' => $config->get('session_memcache_server_port', 11211),
			),
		);

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 */
	public function register()
	{
		if (!empty($this->_servers) && isset($this->_servers[0]))
		{
			$serverConf = current($this->_servers);

			if (!headers_sent())
			{
				ini_set('session.save_path', "{$serverConf['host']}:{$serverConf['port']}");
				ini_set('session.save_handler', 'memcache');
			}
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('memcache') && class_exists('Memcache');
	}
}
joomla/session/storage/apc.php000064400000004241152177723700012444 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Session
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * APC session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.7.0
 * @deprecated  4.0  The CMS' Session classes will be replaced with the `joomla/session` package
 */
class JSessionStorageApc extends JSessionStorage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new RuntimeException('APC Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.7.0
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		return (string) apc_fetch($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return apc_store($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		return apc_delete($sess_id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return extension_loaded('apc');
	}
}
joomla/openstreetmap/user.php000064400000006324152177723700012422 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Openstreetmap API User class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapUser extends JOpenstreetmapObject
{
	/**
	 * Method to get user details
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function getDetails()
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'user/details';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters);

		return $response->body;
	}

	/**
	 * Method to get preferences
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function getPreferences()
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'user/preferences';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', $parameters);

		return $response->body;
	}

	/**
	 * Method to replace user preferences
	 *
	 * @param   array  $preferences  Array of new preferences
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function replacePreferences($preferences)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'user/preferences';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Create a list of preferences
		$preference_list = '';

		if (!empty($preferences))
		{
			foreach ($preferences as $key => $value)
			{
				$preference_list .= '<preference k="' . $key . '" v="' . $value . '"/>';
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
			<osm version="0.6" generator="JOpenstreetmap">
				<preferences>'
				. $preference_list .
				'</preferences>
			</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to change user preferences
	 *
	 * @param   string  $key         Key of the preference
	 * @param   string  $preference  New value for preference
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function changePreference($key, $preference)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'user/preferences/' . $key;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $preference);

		return $response->body;
	}
}
joomla/openstreetmap/changesets.php000064400000015326152177723700013572 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Openstreetmap API Changesets class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapChangesets extends JOpenstreetmapObject
{
	/**
	 * Method to create a changeset
	 *
	 * @param   array  $changesets  Array which contains changeset data
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function createChangeset($changesets=array())
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
			'oauth_token_secret' => $token['secret'],
		);

		// Set the API base
		$base = 'changeset/create';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
			<osm version="0.6" generator="JOpenstreetmap">';

		if (!empty($changesets))
		{
			// Create Changeset element for every changeset
			foreach ($changesets as $tags)
			{
				$xml .= '<changeset>';

				if (!empty($tags))
				{
					// Create a list of tags for each changeset
					foreach ($tags as $key => $value)
					{
						$xml .= '<tag k="' . $key . '" v="' . $value . '"/>';
					}
				}

				$xml .= '</changeset>';
			}
		}

		$xml .= '</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to read a changeset
	 *
	 * @param   integer  $id  identifier of the changeset
	 *
	 * @return  array  The XML response about a changeset
	 *
	 * @since   3.2.0
	 */
	public function readChangeset($id)
	{
		// Set the API base
		$base = 'changeset/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->changeset;
	}

	/**
	 * Method to update a changeset
	 *
	 * @param   integer  $id    Identifier of the changeset
	 * @param   array    $tags  Array of tags to update
	 *
	 * @return  array  The XML response of updated changeset
	 *
	 * @since   3.2.0
	 */
	public function updateChangeset($id, $tags = array())
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'changeset/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Create a list of tags to update changeset
		$tag_list = '';

		if (!empty($tags))
		{
			foreach ($tags as $key => $value)
			{
				$tag_list .= '<tag k="' . $key . '" v="' . $value . '"/>';
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<changeset>'
				. $tag_list .
				'</changeset>
				</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		$xml_string = simplexml_load_string($response->body);

		return $xml_string->changeset;
	}

	/**
	 * Method to close a changeset
	 *
	 * @param   integer  $id  identifier of the changeset
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 */
	public function closeChangeset($id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'changeset/' . $id . '/close';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['format'] = 'text/xml';

		// Send the request.
		$this->oauth->oauthRequest($path, 'PUT', $parameters, $header);
	}

	/**
	 * Method to download a changeset
	 *
	 * @param   integer  $id  Identifier of the changeset
	 *
	 * @return  array  The XML response of requested changeset
	 *
	 * @since   3.2.0
	 */
	public function downloadChangeset($id)
	{
		// Set the API base
		$base = 'changeset/' . $id . '/download';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->create;
	}

	/**
	 * Method to expand the bounding box of a changeset
	 *
	 * @param   integer  $id     Identifier of the changeset
	 * @param   array    $nodes  List of lat lon about nodes
	 *
	 * @return  array  The XML response of changed changeset
	 *
	 * @since   3.2.0
	 */
	public function expandBBoxChangeset($id, $nodes)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'changeset/' . $id . '/expand_bbox';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Create a list of tags to update changeset
		$node_list = '';

		if (!empty($nodes))
		{
			foreach ($nodes as $node)
			{
				$node_list .= '<node lat="' . $node[0] . '" lon="' . $node[1] . '"/>';
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<changeset>'
				. $node_list .
				'</changeset>
			</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		$xml_string = simplexml_load_string($response->body);

		return $xml_string->changeset;
	}

	/**
	 * Method to query on changesets
	 *
	 * @param   string  $param  Parameters for query
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function queryChangeset($param)
	{
		// Set the API base
		$base = 'changesets/' . $param;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->osm;
	}

	/**
	 * Method to upload a diff to a changeset
	 *
	 * @param   string   $xml  Diff data to upload
	 * @param   integer  $id   Identifier of the changeset
	 *
	 * @return  array  The XML response of result
	 *
	 * @since   3.2.0
	 */
	public function diffUploadChangeset($xml, $id)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'changeset/' . $id . '/upload';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'POST', $parameters, $xml, $header);

		$xml_string = simplexml_load_string($response->body);

		return $xml_string->diffResult;
	}
}
joomla/openstreetmap/object.php000064400000006101152177723700012703 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Openstreetmap API object class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
abstract class JOpenstreetmapObject
{
	/**
	 * Options for the Openstreetmap object.
	 *
	 * @var    Registry
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * The HTTP client object to use in sending HTTP requests.
	 *
	 * @var    JHttp
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * The OAuth client.
	 *
	 * @var    JOpenstreetmapOauth
	 * @since  3.2.0
	 */
	protected $oauth;

	/**
	 * Constructor
	 *
	 * @param   Registry             &$options  Openstreetmap options object.
	 * @param   JHttp                $client    The HTTP client object.
	 * @param   JOpenstreetmapOauth  $oauth     Openstreetmap oauth client
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry &$options = null, JHttp $client = null, JOpenstreetmapOauth $oauth = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JHttp($this->options);
		$this->oauth = $oauth;
	}

	/**
	 * Get an option from the JOpenstreetmapObject instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JOpenstreetmapObject instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JOpenstreetmapObject  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}

	/**
	 * Method to send the request which does not require authentication.
	 *
	 * @param   string  $path     The path of the request to make
	 * @param   string  $method   The request method.
	 * @param   array   $headers  The headers passed in the request.
	 * @param   mixed   $data     Either an associative array or a string to be sent with the post request.
	 *
	 * @return  SimpleXMLElement  The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function sendRequest($path, $method = 'GET', $headers = array(), $data = '')
	{
		// Send the request.
		switch ($method)
		{
			case 'GET':
				$response = $this->client->get($path, $headers);
				break;

			case 'POST':
				$response = $this->client->post($path, $data, $headers);
				break;
		}

		// Validate the response code.
		if ($response->code != 200)
		{
			$error = htmlspecialchars($response->body, ENT_COMPAT, 'UTF-8');

			throw new DomainException($error, $response->code);
		}

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}
}
joomla/openstreetmap/info.php000064400000004131152177723700012371 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Openstreetmap API Info class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapInfo extends JOpenstreetmapObject
{
	/**
	 * Method to get capabilities of the API
	 *
	 * @return	array  The XML response
	 *
	 * @since	3.2.0
	 */
	public function getCapabilities()
	{
		// Set the API base
		$base = 'capabilities';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', array());

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}

	/**
	 * Method to retrieve map data of a bounding box
	 *
	 * @param   float  $left    Left boundary
	 * @param   float  $bottom  Bottom boundary
	 * @param   float  $right   Right boundary
	 * @param   float  $top     Top boundary
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function retrieveMapData($left, $bottom, $right, $top)
	{
		// Set the API base
		$base = 'map?bbox=' . $left . ',' . $bottom . ',' . $right . ',' . $top;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', array());

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}

	/**
	 * Method to retrieve permissions for current user
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function retrievePermissions()
	{
		// Set the API base
		$base = 'permissions';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', array());

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}
}
joomla/openstreetmap/gps.php000064400000007437152177723700012243 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Openstreetmap API GPS class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapGps extends JOpenstreetmapObject
{
	/**
	 * Method to retrieve GPS points
	 *
	 * @param   float    $left    Left boundary
	 * @param   float    $bottom  Bottom boundary
	 * @param   float    $right   Right boundary
	 * @param   float    $top     Top boundary
	 * @param   integer  $page    Page number
	 *
	 * @return	array  The XML response containing GPS points
	 *
	 * @since	3.2.0
	 */
	public function retrieveGps($left, $bottom, $right, $top, $page = 0)
	{
		// Set the API base
		$base = 'trackpoints?bbox=' . $left . ',' . $bottom . ',' . $right . ',' . $top . '&page=' . $page;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'GET', array());

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}

	/**
	 * Method to upload GPS Traces
	 *
	 * @param   string   $file         File name that contains trace points
	 * @param   string   $description  Description on trace points
	 * @param   string   $tags         Tags for trace
	 * @param   integer  $public       1 for public, 0 for private
	 * @param   string   $visibility   One of the following: private, public, trackable, identifiable
	 * @param   string   $username     Username
	 * @param   string   $password     Password
	 *
	 * @return  JHttpResponse  The response
	 *
	 * @since   3.2.0
	 */
	public function uploadTrace($file, $description, $tags, $public, $visibility, $username, $password)
	{
		// Set parameters.
		$parameters = array(
			'file' => $file,
			'description' => $description,
			'tags' => $tags,
			'public' => $public,
			'visibility' => $visibility,
		);

		// Set the API base
		$base = 'gpx/create';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'multipart/form-data';

		$header = array_merge($header, $parameters);
		$header = array_merge($header, array('Authorization' => 'Basic ' . base64_encode($username . ':' . $password)));

		// Send the request.
		$response = $this->sendRequest($path, 'POST', $header, array());

		return $response;
	}

	/**
	 * Method to download Trace details
	 *
	 * @param   integer  $id        Trace identifier
	 * @param   string   $username  Username
	 * @param   string   $password  Password
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function downloadTraceMetadetails($id, $username, $password)
	{
		// Set the API base
		$base = 'gpx/' . $id . '/details';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path, 'GET', array('Authorization' => 'Basic ' . base64_encode($username . ':' . $password)));

		return $xml_string;
	}

	/**
	 * Method to download Trace data
	 *
	 * @param   integer  $id        Trace identifier
	 * @param   string   $username  Username
	 * @param   string   $password  Password
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function downloadTraceMetadata($id, $username, $password)
	{
		// Set the API base
		$base = 'gpx/' . $id . '/data';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path, 'GET', array('Authorization' => 'Basic ' . base64_encode($username . ':' . $password)));

		return $xml_string;
	}
}
joomla/openstreetmap/oauth.php000064400000004726152177723700012570 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for generating Openstreetmap API access token.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapOauth extends JOAuth1Client
{
	/**
	 * Options for the JOpenstreetmapOauth object.
	 *
	 * @var    Registry
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * Constructor.
	 *
	 * @param   Registry  $options  JOpenstreetmapOauth options object.
	 * @param   JHttp     $client   The HTTP client object.
	 * @param   JInput    $input    The input object
	 *
	 * @since   3.2.0
	 */
	public function __construct(Registry $options = null, JHttp $client = null, JInput $input = null)
	{
		$this->options = isset($options) ? $options : new Registry;

		$this->options->def('accessTokenURL', 'https://www.openstreetmap.org/oauth/access_token');
		$this->options->def('authoriseURL', 'https://www.openstreetmap.org/oauth/authorize');
		$this->options->def('requestTokenURL', 'https://www.openstreetmap.org/oauth/request_token');

		/*
		$this->options->def('accessTokenURL', 'https://api06.dev.openstreetmap.org/oauth/access_token');
		$this->options->def('authoriseURL', 'https://api06.dev.openstreetmap.org/oauth/authorize');
		$this->options->def('requestTokenURL', 'https://api06.dev.openstreetmap.org/oauth/request_token');
		*/

		// Call the JOauth1Client constructor to setup the object.
		parent::__construct($this->options, $client, $input, null, '1.0');
	}

	/**
	 * Method to verify if the access token is valid by making a request to an API endpoint.
	 *
	 * @return  boolean  Returns true if the access token is valid and false otherwise.
	 *
	 * @since   3.2.0
	 */
	public function verifyCredentials()
	{
		return true;
	}

	/**
	 * Method to validate a response.
	 *
	 * @param   string         $url       The request URL.
	 * @param   JHttpResponse  $response  The response to validate.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function validateResponse($url, $response)
	{
		if ($response->code != 200)
		{
			$error = htmlspecialchars($response->body, ENT_COMPAT, 'UTF-8');

			throw new DomainException($error, $response->code);
		}
	}
}
joomla/openstreetmap/elements.php000064400000032333152177723700013257 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

/**
 * Openstreetmap API Elements class for the Joomla Platform
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmapElements extends JOpenstreetmapObject
{
	/**
	 * Method to create a node
	 *
	 * @param   integer  $changeset  Changeset id
	 * @param   float    $latitude   Latitude of the node
	 * @param   float    $longitude  Longitude of the node
	 * @param   array    $tags       Array of tags for a node
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function createNode($changeset, $latitude, $longitude, $tags)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'node/create';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$tag_list = '';

		// Create XML node
		if (!empty($tags))
		{
			foreach ($tags as $key => $value)
			{
				$tag_list .= '<tag k="' . $key . '" v="' . $value . '"/>';
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<node changeset="' . $changeset . '" lat="' . $latitude . '" lon="' . $longitude . '">'
				. $tag_list .
				'</node>
				</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to create a way
	 *
	 * @param   integer  $changeset  Changeset id
	 * @param   array    $tags       Array of tags for a way
	 * @param   array    $nds        Node ids to refer
	 *
	 * @return  array   The XML response
	 *
	 * @since   3.2.0
	 */
	public function createWay($changeset, $tags, $nds)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'way/create';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$tag_list = '';

		// Create XML node
		if (!empty($tags))
		{
			foreach ($tags as $key => $value)
			{
				$tag_list .= '<tag k="' . $key . '" v="' . $value . '"/>';
			}
		}

		$nd_list = '';

		if (!empty($nds))
		{
			foreach ($nds as $value)
			{
				$nd_list .= '<nd ref="' . $value . '"/>';
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<way changeset="' . $changeset . '">'
					. $tag_list
					. $nd_list .
				'</way>
			</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to create a relation
	 *
	 * @param   integer  $changeset  Changeset id
	 * @param   array    $tags       Array of tags for a relation
	 * @param   array    $members    Array of members for a relation
	 *                               eg: $members = array(array("type"=>"node", "role"=>"stop", "ref"=>"123"), array("type"=>"way", "ref"=>"123"))
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function createRelation($changeset, $tags, $members)
	{
		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = 'relation/create';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$tag_list = '';

		// Create XML node
		if (!empty($tags))
		{
			foreach ($tags as $key => $value)
			{
				$tag_list .= '<tag k="' . $key . '" v="' . $value . '"/>';
			}
		}

		// Members
		$member_list = '';

		if (!empty($members))
		{
			foreach ($members as $member)
			{
				if ($member['type'] == 'node')
				{
					$member_list .= '<member type="' . $member['type'] . '" role="' . $member['role'] . '" ref="' . $member['ref'] . '"/>';
				}
				elseif ($member['type'] == 'way')
				{
					$member_list .= '<member type="' . $member['type'] . '" ref="' . $member['ref'] . '"/>';
				}
			}
		}

		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<relation relation="' . $changeset . '" >'
					. $tag_list
					. $member_list .
				'</relation>
			</osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to read an element [node|way|relation]
	 *
	 * @param   string   $element  [node|way|relation]
	 * @param   integer  $id       Element identifier
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function readElement($element, $id)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		// Set the API base
		$base = $element . '/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->$element;
	}

	/**
	 * Method to update an Element [node|way|relation]
	 *
	 * @param   string   $element  [node|way|relation]
	 * @param   string   $xml      Full reperentation of the element with a version number
	 * @param   integer  $id       Element identifier
	 *
	 * @return  array   The xml response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function updateElement($element, $xml, $id)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = $element . '/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to delete an element [node|way|relation]
	 *
	 * @param   string   $element    [node|way|relation]
	 * @param   integer  $id         Element identifier
	 * @param   integer  $version    Element version
	 * @param   integer  $changeset  Changeset identifier
	 * @param   float    $latitude   Latitude of the element
	 * @param   float    $longitude  Longitude of the element
	 *
	 * @return  array   The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function deleteElement($element, $id, $version, $changeset, $latitude = null, $longitude = null)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = $element . '/' . $id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Create xml
		$xml = '<?xml version="1.0" encoding="UTF-8"?>
				<osm version="0.6" generator="JOpenstreetmap">
				<' . $element . ' id="' . $id . '" version="' . $version . '" changeset="' . $changeset . '"';

		if (!empty($latitude) && !empty($longitude))
		{
			$xml .= ' lat="' . $latitude . '" lon="' . $longitude . '"';
		}

		$xml .= '/></osm>';

		$header['Content-Type'] = 'text/xml';

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'DELETE', $parameters, $xml, $header);

		return $response->body;
	}

	/**
	 * Method to get history of an element [node|way|relation]
	 *
	 * @param   string   $element  [node|way|relation]
	 * @param   integer  $id       Element identifier
	 *
	 * @return  array   The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function historyOfElement($element, $id)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		// Set the API base
		$base = $element . '/' . $id . '/history';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->$element;
	}

	/**
	 * Method to get details about a version of an element [node|way|relation]
	 *
	 * @param   string   $element  [node|way|relation]
	 * @param   integer  $id       Element identifier
	 * @param   integer  $version  Element version
	 *
	 * @return  array    The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function versionOfElement($element, $id, $version)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		// Set the API base
		$base = $element . '/' . $id . '/' . $version;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->$element;
	}

	/**
	 * Method to get data about multiple ids of an element [node|way|relation]
	 *
	 * @param   string  $element  [nodes|ways|relations] - use plural word
	 * @param   string  $params   Comma separated list of ids belonging to type $element
	 *
	 * @return  array   The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function multiFetchElements($element, $params)
	{
		if ($element != 'nodes' && $element != 'ways' && $element != 'relations')
		{
			throw new DomainException('Element should be nodes, ways or relations');
		}

		// Get singular word
		$single_element = substr($element, 0, strlen($element) - 1);

		// Set the API base, $params is a string with comma separated values
		$base = $element . '?' . $element . '=' . $params;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->$single_element;
	}

	/**
	 * Method to get relations for an Element [node|way|relation]
	 *
	 * @param   string   $element  [node|way|relation]
	 * @param   integer  $id       Element identifier
	 *
	 * @return  array   The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function relationsForElement($element, $id)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		// Set the API base
		$base = $element . '/' . $id . '/relations';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->$element;
	}

	/**
	 * Method to get ways for a Node element
	 *
	 * @param   integer  $id  Node identifier
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 */
	public function waysForNode($id)
	{
		// Set the API base
		$base = 'node/' . $id . '/ways';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->way;
	}

	/**
	 * Method to get full information about an element [way|relation]
	 *
	 * @param   string   $element  [way|relation]
	 * @param   integer  $id       Identifier
	 *
	 * @return  array  The XML response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function fullElement($element, $id)
	{
		if ($element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a way or a relation');
		}

		// Set the API base
		$base = $element . '/' . $id . '/full';

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$xml_string = $this->sendRequest($path);

		return $xml_string->node;
	}

	/**
	 * Method used by the DWG to hide old versions of elements containing data privacy or copyright infringements
	 *
	 * @param   string   $element       [node|way|relation]
	 * @param   integer  $id            Element identifier
	 * @param   integer  $version       Element version
	 * @param   integer  $redaction_id  Redaction id
	 *
	 * @return  array   The xml response
	 *
	 * @since   3.2.0
	 * @throws  DomainException
	 */
	public function redaction($element, $id, $version, $redaction_id)
	{
		if ($element != 'node' && $element != 'way' && $element != 'relation')
		{
			throw new DomainException('Element should be a node, a way or a relation');
		}

		$token = $this->oauth->getToken();

		// Set parameters.
		$parameters = array(
			'oauth_token' => $token['key'],
		);

		// Set the API base
		$base = $element . '/' . $id . '/' . $version . '/redact?redaction=' . $redaction_id;

		// Build the request path.
		$path = $this->getOption('api.url') . $base;

		// Send the request.
		$response = $this->oauth->oauthRequest($path, 'PUT', $parameters);

		$xml_string = simplexml_load_string($response->body);

		return $xml_string;
	}
}
joomla/openstreetmap/openstreetmap.php000064400000006654152177723700014340 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Openstreetmap
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die();

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interact with Openstreetmap API.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use the `joomla/openstreetmap` package via Composer instead
 */
class JOpenstreetmap
{
	/**
	 * Options for the Openstreetmap object.
	 *
	 * @var    Registry
	 * @since  3.2.0
	 */
	protected $options;

	/**
	 * The HTTP client object to use in sending HTTP requests.
	 *
	 * @var    JHttp
	 * @since  3.2.0
	 */
	protected $client;

	/**
	 * The OAuth client.
	 *
	 * @var   JOpenstreetmapOauth
	 * @since 3.2.0
	 */
	protected $oauth;

	/**
	 * Openstreetmap API object for changesets.
	 *
	 * @var    JOpenstreetmapChangesets
	 * @since  3.2.0
	 */
	protected $changesets;

	/**
	 * Openstreetmap API object for elements.
	 *
	 * @var    JOpenstreetmapElements
	 * @since  3.2.0
	 */
	protected $elements;

	/**
	 * Openstreetmap API object for GPS.
	 *
	 * @var    JOpenstreetmapGps
	 * @since  3.2.0
	 */
	protected $gps;

	/**
	 * Openstreetmap API object for info.
	 *
	 * @var    JOpenstreetmapInfo
	 * @since  3.2.0
	 */
	protected $info;

	/**
	 * Openstreetmap API object for user.
	 *
	 * @var    JOpenstreetmapUser
	 * @since  3.2.0
	 */
	protected $user;

	/**
	 * Constructor.
	 *
	 * @param   JOpenstreetmapOauth  $oauth    Openstreetmap oauth client
	 * @param   Registry             $options  Openstreetmap options object
	 * @param   JHttp                $client   The HTTP client object
	 *
	 * @since   3.2.0
	 */
	public function __construct(JOpenstreetmapOauth $oauth = null, Registry $options = null, JHttp $client = null)
	{
		$this->oauth = $oauth;
		$this->options = isset($options) ? $options : new Registry;
		$this->client  = isset($client) ? $client : new JHttp($this->options);

		// Setup the default API url if not already set.
		$this->options->def('api.url', 'https://api.openstreetmap.org/api/0.6/');

		// $this->options->def('api.url', 'https://api06.dev.openstreetmap.org/api/0.6/');
	}

	/**
	 * Method to get object instances
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JOpenstreetmapObject  Openstreetmap API object
	 *
	 * @since   3.2.0
	 * @throws  InvalidArgumentException
	 */
	public function __get($name)
	{
		$class = 'JOpenstreetmap' . ucfirst($name);

		if (class_exists($class))
		{
			if (false == isset($this->$name))
			{
				$this->$name = new $class($this->options, $this->client, $this->oauth);
			}

			return $this->$name;
		}

		throw new InvalidArgumentException(sprintf('Argument %s produced an invalid class name: %s', $name, $class));
	}

	/**
	 * Get an option from the JOpenstreetmap instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.2.0
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the Openstreetmap instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JOpenstreetmap  This object for method chaining.
	 *
	 * @since   3.2.0
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/base/adapterinstance.php000064400000002554152177723700012636 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Adapter Instance Class
 *
 * @since       1.6
 * @deprecated  5.0 Will be removed without replacement
 */
class JAdapterInstance extends JObject
{
	/**
	 * Parent
	 *
	 * @var    JAdapter
	 * @since  1.6
	 */
	protected $parent = null;

	/**
	 * Database
	 *
	 * @var    JDatabaseDriver
	 * @since  1.6
	 */
	protected $db = null;

	/**
	 * Constructor
	 *
	 * @param   JAdapter         $parent   Parent object
	 * @param   JDatabaseDriver  $db       Database object
	 * @param   array            $options  Configuration Options
	 *
	 * @since   1.6
	 */
	public function __construct(JAdapter $parent, JDatabaseDriver $db, array $options = array())
	{
		// Set the properties from the options array that is passed in
		$this->setProperties($options);

		// Set the parent and db in case $options for some reason overrides it.
		$this->parent = $parent;

		// Pull in the global dbo in case something happened to it.
		$this->db = $db ?: JFactory::getDbo();
	}

	/**
	 * Retrieves the parent object
	 *
	 * @return  JAdapter
	 *
	 * @since   1.6
	 */
	public function getParent()
	{
		return $this->parent;
	}
}
joomla/base/adapter.php000064400000010615152177723700011106 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Base
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Adapter Class
 * Retains common adapter pattern functions
 * Class harvested from joomla.installer.installer
 *
 * @since       1.6
 * @deprecated  5.0 Will be removed without replacement
 */
class JAdapter extends JObject
{
	/**
	 * Associative array of adapters
	 *
	 * @var    JAdapterInstance[]
	 * @since  1.6
	 */
	protected $_adapters = array();

	/**
	 * Adapter Folder
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_adapterfolder = 'adapters';

	/**
	 * Adapter Class Prefix
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_classprefix = 'J';

	/**
	 * Base Path for the adapter instance
	 *
	 * @var    string
	 * @since  1.6
	 */
	protected $_basepath = null;

	/**
	 * Database Connector Object
	 *
	 * @var    JDatabaseDriver
	 * @since  1.6
	 */
	protected $_db;

	/**
	 * Constructor
	 *
	 * @param   string  $basepath       Base Path of the adapters
	 * @param   string  $classprefix    Class prefix of adapters
	 * @param   string  $adapterfolder  Name of folder to append to base path
	 *
	 * @since   1.6
	 */
	public function __construct($basepath, $classprefix = null, $adapterfolder = null)
	{
		$this->_basepath = $basepath;
		$this->_classprefix = $classprefix ? $classprefix : 'J';
		$this->_adapterfolder = $adapterfolder ? $adapterfolder : 'adapters';

		$this->_db = JFactory::getDbo();
	}

	/**
	 * Get the database connector object
	 *
	 * @return  JDatabaseDriver  Database connector object
	 *
	 * @since   1.6
	 */
	public function getDbo()
	{
		return $this->_db;
	}

	/**
	 * Return an adapter.
	 *
	 * @param   string  $name     Name of adapter to return
	 * @param   array   $options  Adapter options
	 *
	 * @return  JAdapterInstance|boolean  Adapter of type 'name' or false
	 *
	 * @since   1.6
	 */
	public function getAdapter($name, $options = array())
	{
		if (array_key_exists($name, $this->_adapters))
		{
			return $this->_adapters[$name];
		}

		if ($this->setAdapter($name, $options))
		{
			return $this->_adapters[$name];
		}

		return false;
	}

	/**
	 * Set an adapter by name
	 *
	 * @param   string  $name      Adapter name
	 * @param   object  &$adapter  Adapter object
	 * @param   array   $options   Adapter options
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.6
	 */
	public function setAdapter($name, &$adapter = null, $options = array())
	{
		if (is_object($adapter))
		{
			$this->_adapters[$name] = &$adapter;

			return true;
		}

		$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name);

		if (class_exists($class))
		{
			$this->_adapters[$name] = new $class($this, $this->_db, $options);

			return true;
		}

		$class = rtrim($this->_classprefix, '\\') . '\\' . ucfirst($name) . 'Adapter';

		if (class_exists($class))
		{
			$this->_adapters[$name] = new $class($this, $this->_db, $options);

			return true;
		}

		$fullpath = $this->_basepath . '/' . $this->_adapterfolder . '/' . strtolower($name) . '.php';

		if (!file_exists($fullpath))
		{
			return false;
		}

		// Try to load the adapter object
		$class = $this->_classprefix . ucfirst($name);

		JLoader::register($class, $fullpath);

		if (!class_exists($class))
		{
			return false;
		}

		$this->_adapters[$name] = new $class($this, $this->_db, $options);

		return true;
	}

	/**
	 * Loads all adapters.
	 *
	 * @param   array  $options  Adapter options
	 *
	 * @return  void
	 *
	 * @since   1.6
	 */
	public function loadAllAdapters($options = array())
	{
		$files = new DirectoryIterator($this->_basepath . '/' . $this->_adapterfolder);

		/* @type  $file  DirectoryIterator */
		foreach ($files as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Try to load the adapter object
			require_once $this->_basepath . '/' . $this->_adapterfolder . '/' . $fileName;

			// Derive the class name from the filename.
			$name = str_ireplace('.php', '', ucfirst(trim($fileName)));
			$class = $this->_classprefix . ucfirst($name);

			if (!class_exists($class))
			{
				// Skip to next one
				continue;
			}

			$adapter = new $class($this, $this->_db, $options);
			$this->_adapters[$name] = clone $adapter;
		}
	}
}
joomla/database/exporter/mysql.php000064400000002016152177723700013331 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL export driver.
 *
 * @since       1.7.0
 * @deprecated  4.0  Use MySQLi or PDO MySQL instead
 */
class JDatabaseExporterMysql extends JDatabaseExporterMysqli
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseExporterMysql  Method supports chaining.
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverMysql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/exporter/mysqli.php000064400000005613152177723700013510 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQLi export driver.
 *
 * @since  1.7.0
 */
class JDatabaseExporterMysqli extends JDatabaseExporter
{
	/**
	 * Builds the XML data for the tables to export.
	 *
	 * @return  string  An XML string
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXml()
	{
		$buffer = array();

		$buffer[] = '<?xml version="1.0"?>';
		$buffer[] = '<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
		$buffer[] = ' <database name="">';

		$buffer = array_merge($buffer, $this->buildXmlStructure());

		$buffer[] = ' </database>';
		$buffer[] = '</mysqldump>';

		return implode("\n", $buffer);
	}

	/**
	 * Builds the XML structure to export.
	 *
	 * @return  array  An array of XML lines (strings).
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXmlStructure()
	{
		$buffer = array();

		foreach ($this->from as $table)
		{
			// Replace the magic prefix if found.
			$table = $this->getGenericTableName($table);

			// Get the details columns information.
			$fields = $this->db->getTableColumns($table, false);
			$keys = $this->db->getTableKeys($table);

			$buffer[] = '  <table_structure name="' . $table . '">';

			foreach ($fields as $field)
			{
				$buffer[] = '   <field Field="' . $field->Field . '"' . ' Type="' . $field->Type . '"' . ' Null="' . $field->Null . '"' . ' Key="' .
					$field->Key . '"' . (isset($field->Default) ? ' Default="' . $field->Default . '"' : '') . ' Extra="' . $field->Extra . '"' .
					' />';
			}

			foreach ($keys as $key)
			{
				$buffer[] = '   <key Table="' . $table . '"' . ' Non_unique="' . $key->Non_unique . '"' . ' Key_name="' . $key->Key_name . '"' .
					' Seq_in_index="' . $key->Seq_in_index . '"' . ' Column_name="' . $key->Column_name . '"' . ' Collation="' . $key->Collation . '"' .
					' Null="' . $key->Null . '"' . ' Index_type="' . $key->Index_type . '"' .
					' Comment="' . htmlspecialchars($key->Comment, ENT_COMPAT, 'UTF-8') . '"' . ' />';
			}

			$buffer[] = '  </table_structure>';
		}

		return $buffer;
	}

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseExporterMysqli  Method supports chaining.
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverMysqli))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/exporter/pdomysql.php000064400000005764152177723700014051 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL export driver for the PDO based MySQL database driver.
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @since       3.4
 */
class JDatabaseExporterPdomysql extends JDatabaseExporter
{
	/**
	 * Builds the XML data for the tables to export.
	 *
	 * @return  string  An XML string
	 *
	 * @since   3.4
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXml()
	{
		$buffer   = array();

		$buffer[] = '<?xml version="1.0"?>';
		$buffer[] = '<mysqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
		$buffer[] = ' <database name="">';

		$buffer   = array_merge($buffer, $this->buildXmlStructure());

		$buffer[] = ' </database>';
		$buffer[] = '</mysqldump>';

		return implode("\n", $buffer);
	}

	/**
	 * Builds the XML structure to export.
	 *
	 * @return  array  An array of XML lines (strings).
	 *
	 * @since   3.4
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXmlStructure()
	{
		$buffer = array();

		foreach ($this->from as $table)
		{
			// Replace the magic prefix if found.
			$table = $this->getGenericTableName($table);

			// Get the details columns information.
			$fields = $this->db->getTableColumns($table, false);
			$keys   = $this->db->getTableKeys($table);

			$buffer[] = '  <table_structure name="' . $table . '">';

			foreach ($fields as $field)
			{
				$buffer[] = '   <field Field="' . $field->Field . '"' . ' Type="' . $field->Type . '"' . ' Null="' . $field->Null . '"' . ' Key="' .
					$field->Key . '"' . (isset($field->Default) ? ' Default="' . $field->Default . '"' : '') . ' Extra="' . $field->Extra . '"' .
					' />';
			}

			foreach ($keys as $key)
			{
				$buffer[] = '   <key Table="' . $table . '"' . ' Non_unique="' . $key->Non_unique . '"' . ' Key_name="' . $key->Key_name . '"' .
					' Seq_in_index="' . $key->Seq_in_index . '"' . ' Column_name="' . $key->Column_name . '"' . ' Collation="' . $key->Collation . '"' .
					' Null="' . $key->Null . '"' . ' Index_type="' . $key->Index_type . '"' .
					' Comment="' . htmlspecialchars($key->Comment, ENT_COMPAT, 'UTF-8') . '"' . ' />';
			}

			$buffer[] = '  </table_structure>';
		}

		return $buffer;
	}

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseExporterPdomysql  Method supports chaining.
	 *
	 * @since   3.4
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPdomysql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/exporter/postgresql.php000064400000007021152177723700014370 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL export driver.
 *
 * @since       3.0.0
 * @deprecated  4.0  Use PDO PostgreSQL instead
 *
 * @property-read  JDatabaseDriverPostgresql  $db  The database connector to use for exporting structure and/or data.
 */
class JDatabaseExporterPostgresql extends JDatabaseExporter
{
	/**
	 * Builds the XML data for the tables to export.
	 *
	 * @return  string  An XML string
	 *
	 * @since   3.0.0
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXml()
	{
		$buffer = array();

		$buffer[] = '<?xml version="1.0"?>';
		$buffer[] = '<postgresqldump xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">';
		$buffer[] = ' <database name="">';

		$buffer = array_merge($buffer, $this->buildXmlStructure());

		$buffer[] = ' </database>';
		$buffer[] = '</postgresqldump>';

		return implode("\n", $buffer);
	}

	/**
	 * Builds the XML structure to export.
	 *
	 * @return  array  An array of XML lines (strings).
	 *
	 * @since   3.0.0
	 * @throws  Exception if an error occurs.
	 */
	protected function buildXmlStructure()
	{
		$buffer = array();

		foreach ($this->from as $table)
		{
			// Replace the magic prefix if found.
			$table = $this->getGenericTableName($table);

			// Get the details columns information.
			$fields = $this->db->getTableColumns($table, false);
			$keys = $this->db->getTableKeys($table);
			$sequences = $this->db->getTableSequences($table);

			$buffer[] = '  <table_structure name="' . $table . '">';

			foreach ($sequences as $sequence)
			{
				if (version_compare($this->db->getVersion(), '9.1.0') < 0)
				{
					$sequence->start_value = null;
				}

				$buffer[] = '   <sequence Name="' . $sequence->sequence . '"' . ' Schema="' . $sequence->schema . '"' .
					' Table="' . $sequence->table . '"' . ' Column="' . $sequence->column . '"' . ' Type="' . $sequence->data_type . '"' .
					' Start_Value="' . $sequence->start_value . '"' . ' Min_Value="' . $sequence->minimum_value . '"' .
					' Max_Value="' . $sequence->maximum_value . '"' . ' Increment="' . $sequence->increment . '"' .
					' Cycle_option="' . $sequence->cycle_option . '"' .
					' />';
			}

			foreach ($fields as $field)
			{
				$buffer[] = '   <field Field="' . $field->column_name . '"' . ' Type="' . $field->type . '"' . ' Null="' . $field->null . '"' .
							(isset($field->default) ? ' Default="' . $field->default . '"' : '') . ' Comments="' . $field->comments . '"' .
					' />';
			}

			foreach ($keys as $key)
			{
				$buffer[] = '   <key Index="' . $key->idxName . '"' . ' is_primary="' . $key->isPrimary . '"' . ' is_unique="' . $key->isUnique . '"' .
					' Query="' . $key->Query . '" />';
			}

			$buffer[] = '  </table_structure>';
		}

		return $buffer;
	}

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseExporterPostgresql  Method supports chaining.
	 *
	 * @since   3.0.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPostgresql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/exporter/pgsql.php000064400000001746152177723700013323 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PDO PostgreSQL Database Exporter.
 *
 * @since  3.9.0
 */
class JDatabaseExporterPgsql extends JDatabaseExporterPostgresql
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseExporterPgsql  Method supports chaining.
	 *
	 * @since   3.9.0
	 * @throws  \Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPgsql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/exporter.php000064400000010645152177723700012173 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Exporter Class
 *
 * @since  3.0.0
 */
abstract class JDatabaseExporter
{
	/**
	 * The type of output format (xml).
	 *
	 * @var    string
	 * @since  3.2.0
	 */
	protected $asFormat = 'xml';

	/**
	 * An array of cached data.
	 *
	 * @var    array
	 * @since  3.2.0
	 */
	protected $cache = array();

	/**
	 * The database connector to use for exporting structure and/or data.
	 *
	 * @var    JDatabaseDriver
	 * @since  3.2.0
	 */
	protected $db = null;

	/**
	 * An array input sources (table names).
	 *
	 * @var    array
	 * @since  3.2.0
	 */
	protected $from = array();

	/**
	 * An array of options for the exporter.
	 *
	 * @var    object
	 * @since  3.2.0
	 */
	protected $options = null;

	/**
	 * Constructor.
	 *
	 * Sets up the default options for the exporter.
	 *
	 * @since   3.2.0
	 */
	public function __construct()
	{
		$this->options = new stdClass;

		$this->cache = array('columns' => array(), 'keys' => array());

		// Set up the class defaults:

		// Export with only structure
		$this->withStructure();

		// Export as xml.
		$this->asXml();

		// Default destination is a string using $output = (string) $exporter;
	}

	/**
	 * Magic function to exports the data to a string.
	 *
	 * @return  string
	 *
	 * @since   3.2.0
	 * @throws  Exception if an error is encountered.
	 */
	public function __toString()
	{
		// Check everything is ok to run first.
		$this->check();

		// Get the format.
		switch ($this->asFormat)
		{
			case 'xml':
			default:
				$buffer = $this->buildXml();
				break;
		}

		return $buffer;
	}

	/**
	 * Set the output option for the exporter to XML format.
	 *
	 * @return  JDatabaseExporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function asXml()
	{
		$this->asFormat = 'xml';

		return $this;
	}

	/**
	 * Builds the XML data for the tables to export.
	 *
	 * @return  string  An XML string
	 *
	 * @since   3.2.0
	 * @throws  Exception if an error occurs.
	 */
	abstract protected function buildXml();

	/**
	 * Builds the XML structure to export.
	 *
	 * @return  array  An array of XML lines (strings).
	 *
	 * @since   3.2.0
	 * @throws  Exception if an error occurs.
	 */
	abstract protected function buildXmlStructure();

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseDriver  Method supports chaining.
	 *
	 * @since   3.2.0
	 * @throws  Exception if an error is encountered.
	 */
	abstract public function check();

	/**
	 * Specifies a list of table names to export.
	 *
	 * @param   mixed  $from  The name of a single table, or an array of the table names to export.
	 *
	 * @return  JDatabaseExporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 * @throws  Exception if input is not a string or array.
	 */
	public function from($from)
	{
		if (is_string($from))
		{
			$this->from = array($from);
		}
		elseif (is_array($from))
		{
			$this->from = $from;
		}
		else
		{
			throw new Exception('JPLATFORM_ERROR_INPUT_REQUIRES_STRING_OR_ARRAY');
		}

		return $this;
	}

	/**
	 * Get the generic name of the table, converting the database prefix to the wildcard string.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  string  The name of the table with the database prefix replaced with #__.
	 *
	 * @since   3.2.0
	 */
	protected function getGenericTableName($table)
	{
		$prefix = $this->db->getPrefix();

		// Replace the magic prefix if found.
		$table = preg_replace("|^$prefix|", '#__', $table);

		return $table;
	}

	/**
	 * Sets the database connector to use for exporting structure and/or data from MySQL.
	 *
	 * @param   JDatabaseDriver  $db  The database connector.
	 *
	 * @return  JDatabaseExporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function setDbo(JDatabaseDriver $db)
	{
		$this->db = $db;

		return $this;
	}

	/**
	 * Sets an internal option to export the structure of the input table(s).
	 *
	 * @param   boolean  $setting  True to export the structure, false to not.
	 *
	 * @return  JDatabaseExporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function withStructure($setting = true)
	{
		$this->options->withStructure = (boolean) $setting;

		return $this;
	}
}
joomla/database/interface.php000064400000001047152177723700012257 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Interface
 *
 * @since  1.7.0
*/
interface JDatabaseInterface
{
	/**
	 * Test to see if the connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 */
	public static function isSupported();
}
joomla/database/importer/mysql.php000064400000002016152177723700013322 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL import driver.
 *
 * @since       1.7.0
 * @deprecated  4.0  Use MySQLi or PDO MySQL instead
 */
class JDatabaseImporterMysql extends JDatabaseImporterMysqli
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporterMysql  Method supports chaining.
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverMysql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/importer/mysqli.php000064400000026711152177723700013503 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQLi import driver.
 *
 * @since  1.7.0
 */
class JDatabaseImporterMysqli extends JDatabaseImporter
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporterMysqli  Method supports chaining.
	 *
	 * @since   1.7.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverMysqli))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}

	/**
	 * Get the SQL syntax to add a table.
	 *
	 * @param   SimpleXMLElement  $table  The table information.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	protected function xmlToCreate(SimpleXMLElement $table)
	{
		$existingTables = $this->db->getTableList();
		$tableName = (string) $table['name'];

		if (in_array($tableName, $existingTables))
		{
			throw new RuntimeException('The table you are trying to create already exists');
		}

		$createTableStatement = 'CREATE TABLE ' . $this->db->quoteName($tableName) . ' (';

		foreach ($table->xpath('field') as $field)
		{
			$createTableStatement .= $this->getColumnSQL($field) . ', ';
		}

		$newLookup = $this->getKeyLookup($table->xpath('key'));

		// Loop through each key in the new structure.
		foreach ($newLookup as $key)
		{
			$createTableStatement .= $this->getKeySQL($key) . ', ';
		}

		// Remove the comma after the last key
		$createTableStatement = rtrim($createTableStatement, ', ');

		$createTableStatement .= ')';

		return $createTableStatement;
	}

	/**
	 * Get the SQL syntax to add a column.
	 *
	 * @param   string            $table  The table name.
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getAddColumnSql($table, SimpleXMLElement $field)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD COLUMN ' . $this->getColumnSql($field);
	}

	/**
	 * Get the SQL syntax to add a key.
	 *
	 * @param   string  $table  The table name.
	 * @param   array   $keys   An array of the fields pertaining to this key.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getAddKeySql($table, $keys)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD ' . $this->getKeySql($keys);
	}

	/**
	 * Get alters for table if there is a difference.
	 *
	 * @param   SimpleXMLElement  $structure  The XML structure of the table.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	protected function getAlterTableSql(SimpleXMLElement $structure)
	{
		$table = $this->getRealTableName($structure['name']);
		$oldFields = $this->db->getTableColumns($table, false);
		$oldKeys = $this->db->getTableKeys($table);
		$alters = array();

		// Get the fields and keys from the XML that we are aiming for.
		$newFields = $structure->xpath('field');
		$newKeys = $structure->xpath('key');

		// Loop through each field in the new structure.
		foreach ($newFields as $field)
		{
			$fName = (string) $field['Field'];

			if (isset($oldFields[$fName]))
			{
				// The field exists, check it's the same.
				$column = $oldFields[$fName];

				// Test whether there is a change.
				$change = ((string) $field['Type'] != $column->Type) || ((string) $field['Null'] != $column->Null)
					|| ((string) $field['Default'] != $column->Default) || ((string) $field['Extra'] != $column->Extra);

				if ($change)
				{
					$alters[] = $this->getChangeColumnSql($table, $field);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldFields[$fName]);
			}
			else
			{
				// The field is new.
				$alters[] = $this->getAddColumnSql($table, $field);
			}
		}

		// Any columns left are orphans
		foreach ($oldFields as $name => $column)
		{
			// Delete the column.
			$alters[] = $this->getDropColumnSql($table, $name);
		}

		// Get the lookups for the old and new keys.
		$oldLookup = $this->getKeyLookup($oldKeys);
		$newLookup = $this->getKeyLookup($newKeys);

		// Loop through each key in the new structure.
		foreach ($newLookup as $name => $keys)
		{
			// Check if there are keys on this field in the existing table.
			if (isset($oldLookup[$name]))
			{
				$same = true;
				$newCount = count($newLookup[$name]);
				$oldCount = count($oldLookup[$name]);

				// There is a key on this field in the old and new tables. Are they the same?
				if ($newCount == $oldCount)
				{
					// Need to loop through each key and do a fine grained check.
					for ($i = 0; $i < $newCount; $i++)
					{
						$same = (((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique)
							&& ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name)
							&& ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index)
							&& ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation)
							&& ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type));

						/*
						Debug.
						echo '<pre>';
						echo '<br />Non_unique:   '.
							((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Non_unique'].' vs '.$oldLookup[$name][$i]->Non_unique;
						echo '<br />Column_name:  '.
							((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Column_name'].' vs '.$oldLookup[$name][$i]->Column_name;
						echo '<br />Seq_in_index: '.
							((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Seq_in_index'].' vs '.$oldLookup[$name][$i]->Seq_in_index;
						echo '<br />Collation:    '.
							((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Collation'].' vs '.$oldLookup[$name][$i]->Collation;
						echo '<br />Index_type:   '.
							((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Index_type'].' vs '.$oldLookup[$name][$i]->Index_type;
						echo '<br />Same = '.($same ? 'true' : 'false');
						echo '</pre>';
						 */

						if (!$same)
						{
							// Break out of the loop. No need to check further.
							break;
						}
					}
				}
				else
				{
					// Count is different, just drop and add.
					$same = false;
				}

				if (!$same)
				{
					$alters[] = $this->getDropKeySql($table, $name);
					$alters[] = $this->getAddKeySql($table, $keys);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldLookup[$name]);
			}
			else
			{
				// This is a new key.
				$alters[] = $this->getAddKeySql($table, $keys);
			}
		}

		// Any keys left are orphans.
		foreach ($oldLookup as $name => $keys)
		{
			if (strtoupper($name) == 'PRIMARY')
			{
				$alters[] = $this->getDropPrimaryKeySql($table);
			}
			else
			{
				$alters[] = $this->getDropKeySql($table, $name);
			}
		}

		return $alters;
	}

	/**
	 * Get the syntax to alter a column.
	 *
	 * @param   string            $table  The name of the database table to alter.
	 * @param   SimpleXMLElement  $field  The XML definition for the field.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getChangeColumnSql($table, SimpleXMLElement $field)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' '
			. $this->getColumnSql($field);
	}

	/**
	 * Get the SQL syntax for a single column that would be included in a table create or alter statement.
	 *
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getColumnSql(SimpleXMLElement $field)
	{
		// TODO Incorporate into parent class and use $this.
		$blobs = array('text', 'smalltext', 'mediumtext', 'largetext');

		$fName = (string) $field['Field'];
		$fType = (string) $field['Type'];
		$fNull = (string) $field['Null'];
		$fDefault = isset($field['Default']) ? (string) $field['Default'] : null;
		$fExtra = (string) $field['Extra'];

		$query = $this->db->quoteName($fName) . ' ' . $fType;

		if ($fNull == 'NO')
		{
			if (in_array($fType, $blobs) || $fDefault === null)
			{
				$query .= ' NOT NULL';
			}
			else
			{
				// TODO Don't quote numeric values.
				$query .= ' NOT NULL DEFAULT ' . $this->db->quote($fDefault);
			}
		}
		else
		{
			if ($fDefault === null)
			{
				$query .= ' DEFAULT NULL';
			}
			else
			{
				// TODO Don't quote numeric values.
				$query .= ' DEFAULT ' . $this->db->quote($fDefault);
			}
		}

		if ($fExtra)
		{
			$query .= ' ' . strtoupper($fExtra);
		}

		return $query;
	}

	/**
	 * Get the SQL syntax to drop a key.
	 *
	 * @param   string  $table  The table name.
	 * @param   string  $name   The name of the key to drop.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getDropKeySql($table, $name)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP KEY ' . $this->db->quoteName($name);
	}

	/**
	 * Get the SQL syntax to drop a key.
	 *
	 * @param   string  $table  The table name.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getDropPrimaryKeySql($table)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP PRIMARY KEY';
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   array  $keys  An array of objects that comprise the keys for the table.
	 *
	 * @return  array  The lookup array. array({key name} => array(object, ...))
	 *
	 * @since   1.7.0
	 * @throws  Exception
	 */
	protected function getKeyLookup($keys)
	{
		// First pass, create a lookup of the keys.
		$lookup = array();

		foreach ($keys as $key)
		{
			if ($key instanceof SimpleXMLElement)
			{
				$kName = (string) $key['Key_name'];
			}
			else
			{
				$kName = $key->Key_name;
			}

			if (empty($lookup[$kName]))
			{
				$lookup[$kName] = array();
			}

			$lookup[$kName][] = $key;
		}

		return $lookup;
	}

	/**
	 * Get the SQL syntax for a key.
	 *
	 * @param   array  $columns  An array of SimpleXMLElement objects comprising the key.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	protected function getKeySql($columns)
	{
		// TODO Error checking on array and element types.

		$kNonUnique = (string) $columns[0]['Non_unique'];
		$kName = (string) $columns[0]['Key_name'];
		$kColumn = (string) $columns[0]['Column_name'];

		$prefix = '';

		if ($kName == 'PRIMARY')
		{
			$prefix = 'PRIMARY ';
		}
		elseif ($kNonUnique == 0)
		{
			$prefix = 'UNIQUE ';
		}

		$nColumns = count($columns);
		$kColumns = array();

		if ($nColumns == 1)
		{
			$kColumns[] = $this->db->quoteName($kColumn);
		}
		else
		{
			foreach ($columns as $column)
			{
				$kColumns[] = (string) $column['Column_name'];
			}
		}

		$query = $prefix . 'KEY ' . ($kName != 'PRIMARY' ? $this->db->quoteName($kName) : '') . ' (' . implode(',', $kColumns) . ')';

		return $query;
	}
}
joomla/database/importer/pdomysql.php000064400000026012152177723700014027 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL import driver for the PDO based MySQL database driver.
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @since       3.4
 */
class JDatabaseImporterPdomysql extends JDatabaseImporter
{
	/**
	 * Get the SQL syntax to add a column.
	 *
	 * @param   string            $table  The table name.
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getAddColumnSql($table, SimpleXMLElement $field)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD COLUMN ' . $this->getColumnSql($field);

		return $sql;
	}

	/**
	 * Get the SQL syntax to add a key.
	 *
	 * @param   string  $table  The table name.
	 * @param   array   $keys   An array of the fields pertaining to this key.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getAddKeySql($table, $keys)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD ' . $this->getKeySql($keys);

		return $sql;
	}

	/**
	 * Get alters for table if there is a difference.
	 *
	 * @param   SimpleXMLElement  $structure  The XML structure of the table.
	 *
	 * @return  array
	 *
	 * @since   3.4
	 */
	protected function getAlterTableSql(SimpleXMLElement $structure)
	{
		// Initialise variables.
		$table     = $this->getRealTableName($structure['name']);
		$oldFields = $this->db->getTableColumns($table);
		$oldKeys   = $this->db->getTableKeys($table);
		$alters    = array();

		// Get the fields and keys from the XML that we are aiming for.
		$newFields = $structure->xpath('field');
		$newKeys   = $structure->xpath('key');

		// Loop through each field in the new structure.
		foreach ($newFields as $field)
		{
			$fName = (string) $field['Field'];

			if (isset($oldFields[$fName]))
			{
				// The field exists, check it's the same.
				$column = $oldFields[$fName];

				// Test whether there is a change.
				$change = ((string) $field['Type'] != $column->Type) || ((string) $field['Null'] != $column->Null)
					|| ((string) $field['Default'] != $column->Default) || ((string) $field['Extra'] != $column->Extra);

				if ($change)
				{
					$alters[] = $this->getChangeColumnSql($table, $field);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldFields[$fName]);
			}
			else
			{
				// The field is new.
				$alters[] = $this->getAddColumnSql($table, $field);
			}
		}

		// Any columns left are orphans
		foreach ($oldFields as $name => $column)
		{
			// Delete the column.
			$alters[] = $this->getDropColumnSql($table, $name);
		}

		// Get the lookups for the old and new keys.
		$oldLookup = $this->getKeyLookup($oldKeys);
		$newLookup = $this->getKeyLookup($newKeys);

		// Loop through each key in the new structure.
		foreach ($newLookup as $name => $keys)
		{
			// Check if there are keys on this field in the existing table.
			if (isset($oldLookup[$name]))
			{
				$same     = true;
				$newCount = count($newLookup[$name]);
				$oldCount = count($oldLookup[$name]);

				// There is a key on this field in the old and new tables. Are they the same?
				if ($newCount == $oldCount)
				{
					// Need to loop through each key and do a fine grained check.
					for ($i = 0; $i < $newCount; $i++)
					{
						$same = (((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique)
							&& ((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name)
							&& ((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index)
							&& ((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation)
							&& ((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type));

						/*
						Debug.
						echo '<pre>';
						echo '<br />Non_unique:   '.
							((string) $newLookup[$name][$i]['Non_unique'] == $oldLookup[$name][$i]->Non_unique ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Non_unique'].' vs '.$oldLookup[$name][$i]->Non_unique;
						echo '<br />Column_name:  '.
							((string) $newLookup[$name][$i]['Column_name'] == $oldLookup[$name][$i]->Column_name ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Column_name'].' vs '.$oldLookup[$name][$i]->Column_name;
						echo '<br />Seq_in_index: '.
							((string) $newLookup[$name][$i]['Seq_in_index'] == $oldLookup[$name][$i]->Seq_in_index ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Seq_in_index'].' vs '.$oldLookup[$name][$i]->Seq_in_index;
						echo '<br />Collation:    '.
							((string) $newLookup[$name][$i]['Collation'] == $oldLookup[$name][$i]->Collation ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Collation'].' vs '.$oldLookup[$name][$i]->Collation;
						echo '<br />Index_type:   '.
							((string) $newLookup[$name][$i]['Index_type'] == $oldLookup[$name][$i]->Index_type ? 'Pass' : 'Fail').' '.
							(string) $newLookup[$name][$i]['Index_type'].' vs '.$oldLookup[$name][$i]->Index_type;
						echo '<br />Same = '.($same ? 'true' : 'false');
						echo '</pre>';
						 */

						if (!$same)
						{
							// Break out of the loop. No need to check further.
							break;
						}
					}
				}
				else
				{
					// Count is different, just drop and add.
					$same = false;
				}

				if (!$same)
				{
					$alters[] = $this->getDropKeySql($table, $name);
					$alters[] = $this->getAddKeySql($table, $keys);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldLookup[$name]);
			}
			else
			{
				// This is a new key.
				$alters[] = $this->getAddKeySql($table, $keys);
			}
		}

		// Any keys left are orphans.
		foreach ($oldLookup as $name => $keys)
		{
			if (strtoupper($name) == 'PRIMARY')
			{
				$alters[] = $this->getDropPrimaryKeySql($table);
			}
			else
			{
				$alters[] = $this->getDropKeySql($table, $name);
			}
		}

		return $alters;
	}

	/**
	 * Get the syntax to alter a column.
	 *
	 * @param   string            $table  The name of the database table to alter.
	 * @param   SimpleXMLElement  $field  The XML definition for the field.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getChangeColumnSql($table, SimpleXMLElement $field)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' CHANGE COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' '
			. $this->getColumnSql($field);

		return $sql;
	}

	/**
	 * Get the SQL syntax for a single column that would be included in a table create or alter statement.
	 *
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getColumnSql(SimpleXMLElement $field)
	{
		// Initialise variables.
		// TODO Incorporate into parent class and use $this.
		$blobs = array('text', 'smalltext', 'mediumtext', 'largetext');

		$fName    = (string) $field['Field'];
		$fType    = (string) $field['Type'];
		$fNull    = (string) $field['Null'];
		$fDefault = isset($field['Default']) ? (string) $field['Default'] : null;
		$fExtra   = (string) $field['Extra'];

		$sql = $this->db->quoteName($fName) . ' ' . $fType;

		if ($fNull == 'NO')
		{
			if (in_array($fType, $blobs) || $fDefault === null)
			{
				$sql .= ' NOT NULL';
			}
			else
			{
				// TODO Don't quote numeric values.
				$sql .= ' NOT NULL DEFAULT ' . $this->db->quote($fDefault);
			}
		}
		else
		{
			if ($fDefault === null)
			{
				$sql .= ' DEFAULT NULL';
			}
			else
			{
				// TODO Don't quote numeric values.
				$sql .= ' DEFAULT ' . $this->db->quote($fDefault);
			}
		}

		if ($fExtra)
		{
			$sql .= ' ' . strtoupper($fExtra);
		}

		return $sql;
	}

	/**
	 * Get the SQL syntax to drop a column.
	 *
	 * @param   string  $table  The table name.
	 * @param   string  $name   The name of the field to drop.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getDropColumnSql($table, $name)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP COLUMN ' . $this->db->quoteName($name);

		return $sql;
	}

	/**
	 * Get the SQL syntax to drop a key.
	 *
	 * @param   string  $table  The table name.
	 * @param   string  $name   The name of the key to drop.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getDropKeySql($table, $name)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP KEY ' . $this->db->quoteName($name);

		return $sql;
	}

	/**
	 * Get the SQL syntax to drop a key.
	 *
	 * @param   string  $table  The table name.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getDropPrimaryKeySql($table)
	{
		$sql = 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP PRIMARY KEY';

		return $sql;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   array  $keys  An array of objects that comprise the keys for the table.
	 *
	 * @return  array  The lookup array. array({key name} => array(object, ...))
	 *
	 * @since   3.4
	 * @throws  Exception
	 */
	protected function getKeyLookup($keys)
	{
		// First pass, create a lookup of the keys.
		$lookup = array();

		foreach ($keys as $key)
		{
			if ($key instanceof SimpleXMLElement)
			{
				$kName = (string) $key['Key_name'];
			}
			else
			{
				$kName = $key->Key_name;
			}

			if (empty($lookup[$kName]))
			{
				$lookup[$kName] = array();
			}

			$lookup[$kName][] = $key;
		}

		return $lookup;
	}

	/**
	 * Get the SQL syntax for a key.
	 *
	 * @param   array  $columns  An array of SimpleXMLElement objects comprising the key.
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	protected function getKeySql($columns)
	{
		// TODO Error checking on array and element types.

		$kNonUnique = (string) $columns[0]['Non_unique'];
		$kName      = (string) $columns[0]['Key_name'];
		$kColumn    = (string) $columns[0]['Column_name'];
		$prefix     = '';

		if ($kName == 'PRIMARY')
		{
			$prefix = 'PRIMARY ';
		}
		elseif ($kNonUnique == 0)
		{
			$prefix = 'UNIQUE ';
		}

		$nColumns = count($columns);
		$kColumns = array();

		if ($nColumns == 1)
		{
			$kColumns[] = $this->db->quoteName($kColumn);
		}
		else
		{
			foreach ($columns as $column)
			{
				$kColumns[] = (string) $column['Column_name'];
			}
		}

		$sql = $prefix . 'KEY ' . ($kName != 'PRIMARY' ? $this->db->quoteName($kName) : '') . ' (' . implode(',', $kColumns) . ')';

		return $sql;
	}

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporterPdomysql  Method supports chaining.
	 *
	 * @since   3.4
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPdomysql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/importer/postgresql.php000064400000034470152177723700014371 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL import driver.
 *
 * @since       3.0.0
 * @deprecated  4.0  Use PDO PostgreSQL instead
 */
class JDatabaseImporterPostgresql extends JDatabaseImporter
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporterPostgresql  Method supports chaining.
	 *
	 * @since   3.0.0
	 * @throws  Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPostgresql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}

	/**
	 * Get the SQL syntax to add a column.
	 *
	 * @param   string            $table  The table name.
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getAddColumnSql($table, SimpleXMLElement $field)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ADD COLUMN ' . $this->getColumnSql($field);
	}

	/**
	 * Get the SQL syntax to add an index.
	 *
	 * @param   SimpleXMLElement  $field  The XML index definition.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getAddIndexSql(SimpleXMLElement $field)
	{
		return (string) $field['Query'];
	}

	/**
	 * Get alters for table if there is a difference.
	 *
	 * @param   SimpleXMLElement  $structure  The XML structure of the table.
	 *
	 * @return  array
	 *
	 * @since   3.0.0
	 */
	protected function getAlterTableSql(SimpleXMLElement $structure)
	{
		$table = $this->getRealTableName($structure['name']);
		$oldFields = $this->db->getTableColumns($table);
		$oldKeys = $this->db->getTableKeys($table);
		$oldSequence = $this->db->getTableSequences($table);
		$alters = array();

		// Get the fields and keys from the XML that we are aiming for.
		$newFields = $structure->xpath('field');
		$newKeys = $structure->xpath('key');
		$newSequence = $structure->xpath('sequence');

		/* Sequence section */
		$oldSeq = $this->getSeqLookup($oldSequence);
		$newSequenceLook = $this->getSeqLookup($newSequence);

		foreach ($newSequenceLook as $kSeqName => $vSeq)
		{
			if (isset($oldSeq[$kSeqName]))
			{
				// The field exists, check it's the same.
				$column = $oldSeq[$kSeqName][0];

				/* For older database version that doesn't support these fields use default values */
				if (version_compare($this->db->getVersion(), '9.1.0') < 0)
				{
					$column->Min_Value = '1';
					$column->Max_Value = '9223372036854775807';
					$column->Increment = '1';
					$column->Cycle_option = 'NO';
					$column->Start_Value = '1';
				}

				// Test whether there is a change.
				$change = ((string) $vSeq[0]['Type'] != $column->Type) || ((string) $vSeq[0]['Start_Value'] != $column->Start_Value)
					|| ((string) $vSeq[0]['Min_Value'] != $column->Min_Value) || ((string) $vSeq[0]['Max_Value'] != $column->Max_Value)
					|| ((string) $vSeq[0]['Increment'] != $column->Increment) || ((string) $vSeq[0]['Cycle_option'] != $column->Cycle_option)
					|| ((string) $vSeq[0]['Table'] != $column->Table) || ((string) $vSeq[0]['Column'] != $column->Column)
					|| ((string) $vSeq[0]['Schema'] != $column->Schema) || ((string) $vSeq[0]['Name'] != $column->Name);

				if ($change)
				{
					$alters[] = $this->getChangeSequenceSql($kSeqName, $vSeq);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldSeq[$kSeqName]);
			}
			else
			{
				// The sequence is new
				$alters[] = $this->getAddSequenceSql($newSequenceLook[$kSeqName][0]);
			}
		}

		// Any sequences left are orphans
		foreach ($oldSeq as $name => $column)
		{
			// Delete the sequence.
			$alters[] = $this->getDropSequenceSql($name);
		}

		/* Field section */
		// Loop through each field in the new structure.
		foreach ($newFields as $field)
		{
			$fName = (string) $field['Field'];

			if (isset($oldFields[$fName]))
			{
				// The field exists, check it's the same.
				$column = $oldFields[$fName];

				// Test whether there is a change.
				$change = ((string) $field['Type'] != $column->Type) || ((string) $field['Null'] != $column->Null)
					|| ((string) $field['Default'] != $column->Default);

				if ($change)
				{
					$alters[] = $this->getChangeColumnSql($table, $field);
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldFields[$fName]);
			}
			else
			{
				// The field is new.
				$alters[] = $this->getAddColumnSql($table, $field);
			}
		}

		// Any columns left are orphans
		foreach ($oldFields as $name => $column)
		{
			// Delete the column.
			$alters[] = $this->getDropColumnSql($table, $name);
		}

		/* Index section */
		// Get the lookups for the old and new keys
		$oldLookup = $this->getIdxLookup($oldKeys);
		$newLookup = $this->getIdxLookup($newKeys);

		// Loop through each key in the new structure.
		foreach ($newLookup as $name => $keys)
		{
			// Check if there are keys on this field in the existing table.
			if (isset($oldLookup[$name]))
			{
				$same = true;
				$newCount = count($newLookup[$name]);
				$oldCount = count($oldLookup[$name]);

				// There is a key on this field in the old and new tables. Are they the same?
				if ($newCount == $oldCount)
				{
					for ($i = 0; $i < $newCount; $i++)
					{
						// Check only query field -> different query means different index
						$same = ((string) $newLookup[$name][$i]['Query'] == $oldLookup[$name][$i]->Query);

						if (!$same)
						{
							// Break out of the loop. No need to check further.
							break;
						}
					}
				}
				else
				{
					// Count is different, just drop and add.
					$same = false;
				}

				if (!$same)
				{
					$alters[] = $this->getDropIndexSql($name);
					$alters[]  = (string) $newLookup[$name][0]['Query'];
				}

				// Unset this field so that what we have left are fields that need to be removed.
				unset($oldLookup[$name]);
			}
			else
			{
				// This is a new key.
				$alters[] = (string) $newLookup[$name][0]['Query'];
			}
		}

		// Any keys left are orphans.
		foreach ($oldLookup as $name => $keys)
		{
			if ($oldLookup[$name][0]->is_primary == 'TRUE')
			{
				$alters[] = $this->getDropPrimaryKeySql($table, $oldLookup[$name][0]->Index);
			}
			else
			{
				$alters[] = $this->getDropIndexSql($name);
			}
		}

		return $alters;
	}

	/**
	 * Get the SQL syntax to drop a sequence.
	 *
	 * @param   string  $name  The name of the sequence to drop.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getDropSequenceSql($name)
	{
		return 'DROP SEQUENCE ' . $this->db->quoteName($name);
	}

	/**
	 * Get the syntax to add a sequence.
	 *
	 * @param   SimpleXMLElement  $field  The XML definition for the sequence.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getAddSequenceSql($field)
	{
		/* For older database version that doesn't support these fields use default values */
		if (version_compare($this->db->getVersion(), '9.1.0') < 0)
		{
			$field['Min_Value'] = '1';
			$field['Max_Value'] = '9223372036854775807';
			$field['Increment'] = '1';
			$field['Cycle_option'] = 'NO';
			$field['Start_Value'] = '1';
		}

		return 'CREATE SEQUENCE ' . (string) $field['Name'] .
			' INCREMENT BY ' . (string) $field['Increment'] . ' MINVALUE ' . $field['Min_Value'] .
			' MAXVALUE ' . (string) $field['Max_Value'] . ' START ' . (string) $field['Start_Value'] .
			(((string) $field['Cycle_option'] == 'NO') ? ' NO' : '') . ' CYCLE' .
			' OWNED BY ' . $this->db->quoteName((string) $field['Schema'] . '.' . (string) $field['Table'] . '.' . (string) $field['Column']);
	}

	/**
	 * Get the syntax to alter a sequence.
	 *
	 * @param   SimpleXMLElement  $field  The XML definition for the sequence.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getChangeSequenceSql($field)
	{
		/* For older database version that doesn't support these fields use default values */
		if (version_compare($this->db->getVersion(), '9.1.0') < 0)
		{
			$field['Min_Value'] = '1';
			$field['Max_Value'] = '9223372036854775807';
			$field['Increment'] = '1';
			$field['Cycle_option'] = 'NO';
			$field['Start_Value'] = '1';
		}

		return 'ALTER SEQUENCE ' . (string) $field['Name'] .
			' INCREMENT BY ' . (string) $field['Increment'] . ' MINVALUE ' . (string) $field['Min_Value'] .
			' MAXVALUE ' . (string) $field['Max_Value'] . ' START ' . (string) $field['Start_Value'] .
			' OWNED BY ' . $this->db->quoteName((string) $field['Schema'] . '.' . (string) $field['Table'] . '.' . (string) $field['Column']);
	}

	/**
	 * Get the syntax to alter a column.
	 *
	 * @param   string            $table  The name of the database table to alter.
	 * @param   SimpleXMLElement  $field  The XML definition for the field.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getChangeColumnSql($table, SimpleXMLElement $field)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' ALTER COLUMN ' . $this->db->quoteName((string) $field['Field']) . ' '
			. $this->getAlterColumnSql($table, $field);
	}

	/**
	 * Get the SQL syntax for a single column that would be included in a table create statement.
	 *
	 * @param   string            $table  The name of the database table to alter.
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getAlterColumnSql($table, $field)
	{
		// TODO Incorporate into parent class and use $this.
		$blobs = array('text', 'smalltext', 'mediumtext', 'largetext');

		$fName = (string) $field['Field'];
		$fType = (string) $field['Type'];
		$fNull = (string) $field['Null'];
		$fDefault = (isset($field['Default']) && $field['Default'] != 'NULL') ?
						preg_match('/^[0-9]$/', $field['Default']) ? $field['Default'] : $this->db->quote((string) $field['Default'])
					: null;

		$query = ' TYPE ' . $fType;

		if ($fNull == 'NO')
		{
			if (in_array($fType, $blobs) || $fDefault === null)
			{
				$query .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET NOT NULL' .
						",\nALTER COLUMN " . $this->db->quoteName($fName) . ' DROP DEFAULT';
			}
			else
			{
				$query .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET NOT NULL' .
						",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET DEFAULT ' . $fDefault;
			}
		}
		else
		{
			if ($fDefault !== null)
			{
				$query .= ",\nALTER COLUMN " . $this->db->quoteName($fName) . ' DROP NOT NULL' .
						",\nALTER COLUMN " . $this->db->quoteName($fName) . ' SET DEFAULT ' . $fDefault;
			}
		}

		/* sequence was created in other function, here is associated a default value but not yet owner */
		if (strpos($fDefault, 'nextval') !== false)
		{
			$query .= ";\nALTER SEQUENCE " . $this->db->quoteName($table . '_' . $fName . '_seq') . ' OWNED BY ' . $this->db->quoteName($table . '.' . $fName);
		}

		return $query;
	}

	/**
	 * Get the SQL syntax for a single column that would be included in a table create statement.
	 *
	 * @param   SimpleXMLElement  $field  The XML field definition.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getColumnSql(SimpleXMLElement $field)
	{
		// TODO Incorporate into parent class and use $this.
		$blobs = array('text', 'smalltext', 'mediumtext', 'largetext');

		$fName = (string) $field['Field'];
		$fType = (string) $field['Type'];
		$fNull = (string) $field['Null'];
		$fDefault = (isset($field['Default']) && $field['Default'] != 'NULL') ?
						preg_match('/^[0-9]$/', $field['Default']) ? $field['Default'] : $this->db->quote((string) $field['Default'])
					: null;

		/* nextval() as default value means that type field is serial */
		if (strpos($fDefault, 'nextval') !== false)
		{
			$query = $this->db->quoteName($fName) . ' SERIAL';
		}
		else
		{
			$query = $this->db->quoteName($fName) . ' ' . $fType;

			if ($fNull == 'NO')
			{
				if (in_array($fType, $blobs) || $fDefault === null)
				{
					$query .= ' NOT NULL';
				}
				else
				{
					$query .= ' NOT NULL DEFAULT ' . $fDefault;
				}
			}
			else
			{
				if ($fDefault !== null)
				{
					$query .= ' DEFAULT ' . $fDefault;
				}
			}
		}

		return $query;
	}

	/**
	 * Get the SQL syntax to drop an index.
	 *
	 * @param   string  $name  The name of the key to drop.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getDropIndexSql($name)
	{
		return 'DROP INDEX ' . $this->db->quoteName($name);
	}

	/**
	 * Get the SQL syntax to drop a key.
	 *
	 * @param   string  $table  The table name.
	 * @param   string  $name   The constraint name.
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	protected function getDropPrimaryKeySql($table, $name)
	{
		return 'ALTER TABLE ONLY ' . $this->db->quoteName($table) . ' DROP CONSTRAINT ' . $this->db->quoteName($name);
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   array  $keys  An array of objects that comprise the keys for the table.
	 *
	 * @return  array  The lookup array. array({key name} => array(object, ...))
	 *
	 * @since   3.0.0
	 * @throws  Exception
	 */
	protected function getIdxLookup($keys)
	{
		// First pass, create a lookup of the keys.
		$lookup = array();

		foreach ($keys as $key)
		{
			if ($key instanceof SimpleXMLElement)
			{
				$kName = (string) $key['Index'];
			}
			else
			{
				$kName = $key->Index;
			}

			if (empty($lookup[$kName]))
			{
				$lookup[$kName] = array();
			}

			$lookup[$kName][] = $key;
		}

		return $lookup;
	}

	/**
	 * Get the details list of sequences for a table.
	 *
	 * @param   array  $sequences  An array of objects that comprise the sequences for the table.
	 *
	 * @return  array  The lookup array. array({key name} => array(object, ...))
	 *
	 * @since   3.0.0
	 * @throws  Exception
	 */
	protected function getSeqLookup($sequences)
	{
		// First pass, create a lookup of the keys.
		$lookup = array();

		foreach ($sequences as $seq)
		{
			if ($seq instanceof SimpleXMLElement)
			{
				$sName = (string) $seq['Name'];
			}
			else
			{
				$sName = $seq->Name;
			}

			if (empty($lookup[$sName]))
			{
				$lookup[$sName] = array();
			}

			$lookup[$sName][] = $seq;
		}

		return $lookup;
	}
}
joomla/database/importer/pgsql.php000064400000001746152177723700013314 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PDO PostgreSQL Database Importer.
 *
 * @since  3.9.0
 */
class JDatabaseImporterPgsql extends JDatabaseImporterPostgresql
{
	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporterPgsql  Method supports chaining.
	 *
	 * @since   3.9.0
	 * @throws  \Exception if an error is encountered.
	 */
	public function check()
	{
		// Check if the db connector has been set.
		if (!($this->db instanceof JDatabaseDriverPgsql))
		{
			throw new Exception('JPLATFORM_ERROR_DATABASE_CONNECTOR_WRONG_TYPE');
		}

		// Check if the tables have been specified.
		if (empty($this->from))
		{
			throw new Exception('JPLATFORM_ERROR_NO_TABLES_SPECIFIED');
		}

		return $this;
	}
}
joomla/database/query.php000064400000130200152177723700011456 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since  1.7.0
 *
 * @method      string  q()   q($text, $escape = true)  Alias for quote method
 * @method      string  qn()  qn($name, $as = null)     Alias for quoteName method
 * @method      string  e()   e($text, $extra = false)  Alias for escape method
 * @property-read   JDatabaseQueryElement  $type
 * @property-read   JDatabaseQueryElement  $select
 * @property-read   JDatabaseQueryElement  $group
 * @property-read   JDatabaseQueryElement  $having
 */
abstract class JDatabaseQuery
{
	/**
	 * @var    JDatabaseDriver  The database driver.
	 * @since  1.7.0
	 */
	protected $db = null;

	/**
	 * @var    string  The SQL query (if a direct query string was provided).
	 * @since  3.0.0
	 */
	protected $sql = null;

	/**
	 * @var    string  The query type.
	 * @since  1.7.0
	 */
	protected $type = '';

	/**
	 * @var    JDatabaseQueryElement  The query element for a generic query (type = null).
	 * @since  1.7.0
	 */
	protected $element = null;

	/**
	 * @var    JDatabaseQueryElement  The select element.
	 * @since  1.7.0
	 */
	protected $select = null;

	/**
	 * @var    JDatabaseQueryElement  The delete element.
	 * @since  1.7.0
	 */
	protected $delete = null;

	/**
	 * @var    JDatabaseQueryElement  The update element.
	 * @since  1.7.0
	 */
	protected $update = null;

	/**
	 * @var    JDatabaseQueryElement  The insert element.
	 * @since  1.7.0
	 */
	protected $insert = null;

	/**
	 * @var    JDatabaseQueryElement  The from element.
	 * @since  1.7.0
	 */
	protected $from = null;

	/**
	 * @var    JDatabaseQueryElement  The join element.
	 * @since  1.7.0
	 */
	protected $join = null;

	/**
	 * @var    JDatabaseQueryElement  The set element.
	 * @since  1.7.0
	 */
	protected $set = null;

	/**
	 * @var    JDatabaseQueryElement  The where element.
	 * @since  1.7.0
	 */
	protected $where = null;

	/**
	 * @var    JDatabaseQueryElement  The group by element.
	 * @since  1.7.0
	 */
	protected $group = null;

	/**
	 * @var    JDatabaseQueryElement  The having element.
	 * @since  1.7.0
	 */
	protected $having = null;

	/**
	 * @var    JDatabaseQueryElement  The column list for an INSERT statement.
	 * @since  1.7.0
	 */
	protected $columns = null;

	/**
	 * @var    JDatabaseQueryElement  The values list for an INSERT statement.
	 * @since  1.7.0
	 */
	protected $values = null;

	/**
	 * @var    JDatabaseQueryElement  The order element.
	 * @since  1.7.0
	 */
	protected $order = null;

	/**
	 * @var   object  The auto increment insert field element.
	 * @since 1.7.0
	 */
	protected $autoIncrementField = null;

	/**
	 * @var    JDatabaseQueryElement  The call element.
	 * @since  3.0.0
	 */
	protected $call = null;

	/**
	 * @var    JDatabaseQueryElement  The exec element.
	 * @since  3.0.0
	 */
	protected $exec = null;

	/**
	 * @var    JDatabaseQueryElement  The union element.
	 * @since  3.0.0
	 * @deprecated  4.0  Will be transformed and moved to $merge variable.
	 */
	protected $union = null;

	/**
	 * @var    JDatabaseQueryElement  The unionAll element.
	 * @since  3.2.0
	 * @deprecated  4.0  Will be transformed and moved to $merge variable.
	 */
	protected $unionAll = null;

	/**
	 * @var    array  Details of window function.
	 * @since  3.7.0
	 */
	protected $selectRowNumber = null;

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  string  The aliased method's return value or null.
	 *
	 * @since   1.7.0
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
				break;

			case 'qn':
				return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
				break;

			case 'e':
				return $this->escape($args[0], isset($args[1]) ? $args[1] : false);
				break;
		}
	}

	/**
	 * Class constructor.
	 *
	 * @param   JDatabaseDriver  $db  The database driver.
	 *
	 * @since   1.7.0
	 */
	public function __construct(JDatabaseDriver $db = null)
	{
		$this->db = $db;
	}

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string	The completed query.
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		$query = '';

		if ($this->sql)
		{
			return $this->sql;
		}

		switch ($this->type)
		{
			case 'element':
				$query .= (string) $this->element;
				break;

			case 'select':
				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->selectRowNumber === null)
				{
					if ($this->group)
					{
						$query .= (string) $this->group;
					}

					if ($this->having)
					{
						$query .= (string) $this->having;
					}

					if ($this->union)
					{
						$query .= (string) $this->union;
					}

					if ($this->unionAll)
					{
						$query .= (string) $this->unionAll;
					}
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'delete':
				$query .= (string) $this->delete;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'update':
				$query .= (string) $this->update;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				$query .= (string) $this->set;

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				// Set method
				if ($this->set)
				{
					$query .= (string) $this->set;
				}
				// Columns-Values method
				elseif ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->values->getElements();

					if (!($elements[0] instanceof $this))
					{
						$query .= ' VALUES ';
					}

					$query .= (string) $this->values;
				}

				break;

			case 'call':
				$query .= (string) $this->call;
				break;

			case 'exec':
				$query .= (string) $this->exec;
				break;
		}

		if ($this instanceof JDatabaseQueryLimitable)
		{
			$query = $this->processLimit($query, $this->limit, $this->offset);
		}

		return $query;
	}

	/**
	 * Magic function to get protected variable value
	 *
	 * @param   string  $name  The name of the variable.
	 *
	 * @return  mixed
	 *
	 * @since   1.7.0
	 */
	public function __get($name)
	{
		return isset($this->$name) ? $this->$name : null;
	}

	/**
	 * Add a single column, or array of columns to the CALL clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The call method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->call('a.*')->call('b.id');
	 * $query->call(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function call($columns)
	{
		$this->type = 'call';

		if (is_null($this->call))
		{
			$this->call = new JDatabaseQueryElement('CALL', $columns);
		}
		else
		{
			$this->call->append($columns);
		}

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   1.7.0
	 */
	public function castAsChar($value)
	{
		return $value;
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   1.7.0
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'CHAR_LENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function clear($clause = null)
	{
		$this->sql = null;

		switch ($clause)
		{
			case 'select':
				$this->select = null;
				$this->type = null;
				$this->selectRowNumber = null;
				break;

			case 'delete':
				$this->delete = null;
				$this->type = null;
				break;

			case 'update':
				$this->update = null;
				$this->type = null;
				break;

			case 'insert':
				$this->insert = null;
				$this->type = null;
				$this->autoIncrementField = null;
				break;

			case 'from':
				$this->from = null;
				break;

			case 'join':
				$this->join = null;
				break;

			case 'set':
				$this->set = null;
				break;

			case 'where':
				$this->where = null;
				break;

			case 'group':
				$this->group = null;
				break;

			case 'having':
				$this->having = null;
				break;

			case 'order':
				$this->order = null;
				break;

			case 'columns':
				$this->columns = null;
				break;

			case 'values':
				$this->values = null;
				break;

			case 'exec':
				$this->exec = null;
				$this->type = null;
				break;

			case 'call':
				$this->call = null;
				$this->type = null;
				break;

			case 'limit':
				$this->offset = 0;
				$this->limit = 0;
				break;

			case 'offset':
				$this->offset = 0;
				break;

			case 'union':
				$this->union = null;
				break;

			case 'unionAll':
				$this->unionAll = null;
				break;

			default:
				$this->type = null;
				$this->select = null;
				$this->selectRowNumber = null;
				$this->delete = null;
				$this->update = null;
				$this->insert = null;
				$this->from = null;
				$this->join = null;
				$this->set = null;
				$this->where = null;
				$this->group = null;
				$this->having = null;
				$this->order = null;
				$this->columns = null;
				$this->values = null;
				$this->autoIncrementField = null;
				$this->exec = null;
				$this->call = null;
				$this->union = null;
				$this->unionAll = null;
				$this->offset = 0;
				$this->limit = 0;
				break;
		}

		return $this;
	}

	/**
	 * Adds a column, or array of column names that would be used for an INSERT INTO statement.
	 *
	 * @param   mixed  $columns  A column name, or array of column names.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function columns($columns)
	{
		if (is_null($this->columns))
		{
			$this->columns = new JDatabaseQueryElement('()', $columns);
		}
		else
		{
			$this->columns->append($columns);
		}

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.7.0
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return 'CONCATENATE(' . implode(' || ' . $this->quote($separator) . ' || ', $values) . ')';
		}
		else
		{
			return 'CONCATENATE(' . implode(' || ', $values) . ')';
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * Usage:
	 * $query->where('published_up < '.$query->currentTimestamp());
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function currentTimestamp()
	{
		return 'CURRENT_TIMESTAMP()';
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the getDateFormat method directly.
	 *
	 * @return  string  The format string.
	 *
	 * @since   1.7.0
	 */
	public function dateFormat()
	{
		if (!($this->db instanceof JDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->getDateFormat();
	}

	/**
	 * Creates a formatted dump of the query for debugging purposes.
	 *
	 * Usage:
	 * echo $query->dump();
	 *
	 * @return  string
	 *
	 * @since   1.7.3
	 */
	public function dump()
	{
		return '<pre class="jdatabasequery">' . str_replace('#__', $this->db->getPrefix(), $this) . '</pre>';
	}

	/**
	 * Add a table name to the DELETE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->delete('#__a')->where('id = 1');
	 *
	 * @param   string  $table  The name of the table to delete from.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function delete($table = null)
	{
		$this->type = 'delete';
		$this->delete = new JDatabaseQueryElement('DELETE', null);

		if (!empty($table))
		{
			$this->from($table);
		}

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the escape method directly.
	 *
	 * Note that 'e' is an alias for this method as it is in JDatabaseDriver.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function escape($text, $extra = false)
	{
		if (!($this->db instanceof JDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->escape($text, $extra);
	}

	/**
	 * Add a single column, or array of columns to the EXEC clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The exec method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->exec('a.*')->exec('b.id');
	 * $query->exec(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function exec($columns)
	{
		$this->type = 'exec';

		if (is_null($this->exec))
		{
			$this->exec = new JDatabaseQueryElement('EXEC', $columns);
		}
		else
		{
			$this->exec->append($columns);
		}

		return $this;
	}

	/**
	 * Add a table to the FROM clause of the query.
	 *
	 * Note that while an array of tables can be provided, it is recommended you use explicit joins.
	 *
	 * Usage:
	 * $query->select('*')->from('#__a');
	 *
	 * @param   mixed   $tables         A string or array of table names.
	 *                                  This can be a JDatabaseQuery object (or a child of it) when used
	 *                                  as a subquery in FROM clause along with a value for $subQueryAlias.
	 * @param   string  $subQueryAlias  Alias used when $tables is a JDatabaseQuery.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @throws  RuntimeException
	 *
	 * @since   1.7.0
	 */
	public function from($tables, $subQueryAlias = null)
	{
		if (is_null($this->from))
		{
			if ($tables instanceof $this)
			{
				if (is_null($subQueryAlias))
				{
					throw new RuntimeException('JLIB_DATABASE_ERROR_NULL_SUBQUERY_ALIAS');
				}

				$tables = '( ' . (string) $tables . ' ) AS ' . $this->quoteName($subQueryAlias);
			}

			$this->from = new JDatabaseQueryElement('FROM', $tables);
		}
		else
		{
			$this->from->append($tables);
		}

		return $this;
	}

	/**
	 * Used to get a string to extract year from date column.
	 *
	 * Usage:
	 * $query->select($query->year($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing year to be extracted.
	 *
	 * @return  string  Returns string to extract year from a date.
	 *
	 * @since   3.0.0
	 */
	public function year($date)
	{
		return 'YEAR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract month from date column.
	 *
	 * Usage:
	 * $query->select($query->month($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing month to be extracted.
	 *
	 * @return  string  Returns string to extract month from a date.
	 *
	 * @since   3.0.0
	 */
	public function month($date)
	{
		return 'MONTH(' . $date . ')';
	}

	/**
	 * Used to get a string to extract day from date column.
	 *
	 * Usage:
	 * $query->select($query->day($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing day to be extracted.
	 *
	 * @return  string  Returns string to extract day from a date.
	 *
	 * @since   3.0.0
	 */
	public function day($date)
	{
		return 'DAY(' . $date . ')';
	}

	/**
	 * Used to get a string to extract hour from date column.
	 *
	 * Usage:
	 * $query->select($query->hour($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing hour to be extracted.
	 *
	 * @return  string  Returns string to extract hour from a date.
	 *
	 * @since   3.0.0
	 */
	public function hour($date)
	{
		return 'HOUR(' . $date . ')';
	}

	/**
	 * Used to get a string to extract minute from date column.
	 *
	 * Usage:
	 * $query->select($query->minute($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing minute to be extracted.
	 *
	 * @return  string  Returns string to extract minute from a date.
	 *
	 * @since   3.0.0
	 */
	public function minute($date)
	{
		return 'MINUTE(' . $date . ')';
	}

	/**
	 * Used to get a string to extract seconds from date column.
	 *
	 * Usage:
	 * $query->select($query->second($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing second to be extracted.
	 *
	 * @return  string  Returns string to extract second from a date.
	 *
	 * @since   3.0.0
	 */
	public function second($date)
	{
		return 'SECOND(' . $date . ')';
	}

	/**
	 * Add a grouping column to the GROUP clause of the query.
	 *
	 * Usage:
	 * $query->group('id');
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function group($columns)
	{
		if (is_null($this->group))
		{
			$this->group = new JDatabaseQueryElement('GROUP BY', $columns);
		}
		else
		{
			$this->group->append($columns);
		}

		return $this;
	}

	/**
	 * A conditions to the HAVING clause of the query.
	 *
	 * Usage:
	 * $query->group('id')->having('COUNT(id) > 5');
	 *
	 * @param   mixed   $conditions  A string or array of columns.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function having($conditions, $glue = 'AND')
	{
		if (is_null($this->having))
		{
			$glue = strtoupper($glue);
			$this->having = new JDatabaseQueryElement('HAVING', $conditions, " $glue ");
		}
		else
		{
			$this->having->append($conditions);
		}

		return $this;
	}

	/**
	 * Add an INNER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->innerJoin('b ON b.id = a.id')->innerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function innerJoin($condition)
	{
		$this->join('INNER', $condition);

		return $this;
	}

	/**
	 * Add a table name to the INSERT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->insert('#__a')->set('id = 1');
	 * $query->insert('#__a')->columns('id, title')->values('1,2')->values('3,4');
	 * $query->insert('#__a')->columns('id, title')->values(array('1,2', '3,4'));
	 *
	 * @param   mixed    $table           The name of the table to insert data into.
	 * @param   boolean  $incrementField  The name of the field to auto increment.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function insert($table, $incrementField=false)
	{
		$this->type = 'insert';
		$this->insert = new JDatabaseQueryElement('INSERT INTO', $table);
		$this->autoIncrementField = $incrementField;

		return $this;
	}

	/**
	 * Add a JOIN clause to the query.
	 *
	 * Usage:
	 * $query->join('INNER', 'b ON b.id = a.id);
	 *
	 * @param   string  $type        The type of join. This string is prepended to the JOIN keyword.
	 * @param   string  $conditions  A string or array of conditions.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function join($type, $conditions)
	{
		if (is_null($this->join))
		{
			$this->join = array();
		}

		$this->join[] = new JDatabaseQueryElement(strtoupper($type) . ' JOIN', $conditions);

		return $this;
	}

	/**
	 * Add a LEFT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->leftJoin('b ON b.id = a.id')->leftJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function leftJoin($condition)
	{
		$this->join('LEFT', $condition);

		return $this;
	}

	/**
	 * Get the length of a string in bytes.
	 *
	 * Note, use 'charLength' to find the number of characters in a string.
	 *
	 * Usage:
	 * query->where($query->length('a').' > 3');
	 *
	 * @param   string  $value  The string to measure.
	 *
	 * @return  int
	 *
	 * @since   1.7.0
	 */
	public function length($value)
	{
		return 'LENGTH(' . $value . ')';
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the nullDate method directly.
	 *
	 * Usage:
	 * $query->where('modified_date <> '.$query->nullDate());
	 *
	 * @param   boolean  $quoted  Optionally wraps the null date in database quotes (true by default).
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 *
	 * @since   1.7.0
	 */
	public function nullDate($quoted = true)
	{
		if (!($this->db instanceof JDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		$result = $this->db->getNullDate($quoted);

		if ($quoted)
		{
			return $this->db->quote($result);
		}

		return $result;
	}

	/**
	 * Add an ordering column to the ORDER clause of the query.
	 *
	 * Usage:
	 * $query->order('foo')->order('bar');
	 * $query->order(array('foo','bar'));
	 *
	 * @param   mixed  $columns  A string or array of ordering columns.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function order($columns)
	{
		if (is_null($this->order))
		{
			$this->order = new JDatabaseQueryElement('ORDER BY', $columns);
		}
		else
		{
			$this->order->append($columns);
		}

		return $this;
	}

	/**
	 * Add an OUTER JOIN clause to the query.
	 *
	 * Usage:
	 * $query->outerJoin('b ON b.id = a.id')->outerJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function outerJoin($condition)
	{
		$this->join('OUTER', $condition);

		return $this;
	}

	/**
	 * Method to quote and optionally escape a string to database requirements for insertion into the database.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quote method directly.
	 *
	 * Note that 'q' is an alias for this method as it is in JDatabaseDriver.
	 *
	 * Usage:
	 * $query->quote('fulltext');
	 * $query->q('fulltext');
	 * $query->q(array('option', 'fulltext'));
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True to escape the string, false to leave it unchanged.
	 *
	 * @return  string  The quoted input string.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function quote($text, $escape = true)
	{
		if (!($this->db instanceof JDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->quote($text, $escape);
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * This method is provided for use where the query object is passed to a function for modification.
	 * If you have direct access to the database object, it is recommended you use the quoteName method directly.
	 *
	 * Note that 'qn' is an alias for this method as it is in JDatabaseDriver.
	 *
	 * Usage:
	 * $query->quoteName('#__a');
	 * $query->qn('#__a');
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
	 *                        same length of $name; if is null there will not be any AS part for string or array element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException if the internal db property is not a valid object.
	 */
	public function quoteName($name, $as = null)
	{
		if (!($this->db instanceof JDatabaseDriver))
		{
			throw new RuntimeException('JLIB_DATABASE_ERROR_INVALID_DB_OBJECT');
		}

		return $this->db->quoteName($name, $as);
	}

	/**
	 * Add a RIGHT JOIN clause to the query.
	 *
	 * Usage:
	 * $query->rightJoin('b ON b.id = a.id')->rightJoin('c ON c.id = b.id');
	 *
	 * @param   string  $condition  The join condition.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function rightJoin($condition)
	{
		$this->join('RIGHT', $condition);

		return $this;
	}

	/**
	 * Add a single column, or array of columns to the SELECT clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 * The select method can, however, be called multiple times in the same query.
	 *
	 * Usage:
	 * $query->select('a.*')->select('b.id');
	 * $query->select(array('a.*', 'b.id'));
	 *
	 * @param   mixed  $columns  A string or an array of field names.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function select($columns)
	{
		$this->type = 'select';

		if (is_null($this->select))
		{
			$this->select = new JDatabaseQueryElement('SELECT', $columns);
		}
		else
		{
			$this->select->append($columns);
		}

		return $this;
	}

	/**
	 * Add a single condition string, or an array of strings to the SET clause of the query.
	 *
	 * Usage:
	 * $query->set('a = 1')->set('b = 2');
	 * $query->set(array('a = 1', 'b = 2');
	 *
	 * @param   mixed   $conditions  A string or array of string conditions.
	 * @param   string  $glue        The glue by which to join the condition strings. Defaults to ,.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function set($conditions, $glue = ',')
	{
		if (is_null($this->set))
		{
			$glue = strtoupper($glue);
			$this->set = new JDatabaseQueryElement('SET', $conditions, "\n\t$glue ");
		}
		else
		{
			$this->set->append($conditions);
		}

		return $this;
	}

	/**
	 * Allows a direct query to be provided to the database
	 * driver's setQuery() method, but still allow queries
	 * to have bounded variables.
	 *
	 * Usage:
	 * $query->setQuery('select * from #__users');
	 *
	 * @param   mixed  $sql  An SQL Query
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setQuery($sql)
	{
		$this->sql = $sql;

		return $this;
	}

	/**
	 * Add a table name to the UPDATE clause of the query.
	 *
	 * Note that you must not mix insert, update, delete and select method calls when building a query.
	 *
	 * Usage:
	 * $query->update('#__foo')->set(...);
	 *
	 * @param   string  $table  A table to update.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function update($table)
	{
		$this->type = 'update';
		$this->update = new JDatabaseQueryElement('UPDATE', $table);

		return $this;
	}

	/**
	 * Adds a tuple, or array of tuples that would be used as values for an INSERT INTO statement.
	 *
	 * Usage:
	 * $query->values('1,2,3')->values('4,5,6');
	 * $query->values(array('1,2,3', '4,5,6'));
	 *
	 * @param   string  $values  A single tuple, or array of tuples.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function values($values)
	{
		if (is_null($this->values))
		{
			$this->values = new JDatabaseQueryElement('()', $values, '),(');
		}
		else
		{
			$this->values->append($values);
		}

		return $this;
	}

	/**
	 * Add a single condition, or an array of conditions to the WHERE clause of the query.
	 *
	 * Usage:
	 * $query->where('a = 1')->where('b = 2');
	 * $query->where(array('a = 1', 'b = 2'));
	 *
	 * @param   mixed   $conditions  A string or array of where conditions.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *                               Note that the glue is set on first use and cannot be changed.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   1.7.0
	 */
	public function where($conditions, $glue = 'AND')
	{
		if (is_null($this->where))
		{
			$glue = strtoupper($glue);
			$this->where = new JDatabaseQueryElement('WHERE', $conditions, " $glue ");
		}
		else
		{
			$this->where->append($conditions);
		}

		return $this;
	}

	/**
	 * Extend the WHERE clause with a single condition or an array of conditions, with a potentially
	 * different logical operator from the one in the current WHERE clause.
	 *
	 * Usage:
	 * $query->where(array('a = 1', 'b = 2'))->extendWhere('XOR', array('c = 3', 'd = 4'));
	 * will produce: WHERE ((a = 1 AND b = 2) XOR (c = 3 AND d = 4)
	 *
	 * @param   string  $outerGlue   The glue by which to join the conditions to the current WHERE conditions.
	 * @param   mixed   $conditions  A string or array of WHERE conditions.
	 * @param   string  $innerGlue   The glue by which to join the conditions. Defaults to AND.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.6
	 */
	public function extendWhere($outerGlue, $conditions, $innerGlue = 'AND')
	{
		// Replace the current WHERE with a new one which has the old one as an unnamed child.
		$this->where = new JDatabaseQueryElement('WHERE', $this->where->setName('()'), " $outerGlue ");

		// Append the new conditions as a new unnamed child.
		$this->where->append(new JDatabaseQueryElement('()', $conditions, " $innerGlue "));

		return $this;
	}

	/**
	 * Extend the WHERE clause with an OR and a single condition or an array of conditions.
	 *
	 * Usage:
	 * $query->where(array('a = 1', 'b = 2'))->orWhere(array('c = 3', 'd = 4'));
	 * will produce: WHERE ((a = 1 AND b = 2) OR (c = 3 AND d = 4)
	 *
	 * @param   mixed   $conditions  A string or array of WHERE conditions.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to AND.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.6
	 */
	public function orWhere($conditions, $glue = 'AND')
	{
		return $this->extendWhere('OR', $conditions, $glue);
	}

	/**
	 * Extend the WHERE clause with an AND and a single condition or an array of conditions.
	 *
	 * Usage:
	 * $query->where(array('a = 1', 'b = 2'))->andWhere(array('c = 3', 'd = 4'));
	 * will produce: WHERE ((a = 1 AND b = 2) AND (c = 3 OR d = 4)
	 *
	 * @param   mixed   $conditions  A string or array of WHERE conditions.
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to OR.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.6
	 */
	public function andWhere($conditions, $glue = 'OR')
	{
		return $this->extendWhere('AND', $conditions, $glue);
	}

	/**
	 * Method to provide deep copy support to nested objects and
	 * arrays when cloning.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if ($k === 'db')
			{
				continue;
			}

			if (is_object($v) || is_array($v))
			{
				$this->{$k} = unserialize(serialize($v));
			}
		}
	}

	/**
	 * Add a query to UNION with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage (the $query base query MUST be a select query):
	 * $query->union('SELECT name FROM  #__foo')
	 * $query->union('SELECT name FROM  #__foo', true)
	 * $query->union($query2)->union($query3)
	 *
	 * The $query attribute as an array is deprecated and will not be supported in 4.0.
	 *
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 * $query->union(array($query2, $query3))
	 *
	 * @param   mixed    $query     The JDatabaseQuery object or string to union.
	 * @param   boolean  $distinct  True to only return distinct rows from the union.
	 * @param   string   $glue      The glue by which to join the conditions.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @link http://dev.mysql.com/doc/refman/5.0/en/union.html
	 *
	 * @since   3.0.0
	 */
	public function union($query, $distinct = false, $glue = '')
	{
		// Set up the DISTINCT flag, the name with parentheses, and the glue.
		if ($distinct)
		{
			$name = 'UNION DISTINCT ()';
			$glue = ')' . PHP_EOL . 'UNION DISTINCT (';
		}
		else
		{
			$glue = ')' . PHP_EOL . 'UNION (';
			$name = 'UNION ()';
		}

		if (is_array($query))
		{
			JLog::add('Query attribute as an array is deprecated.', JLog::WARNING, 'deprecated');
		}

		// Get the JDatabaseQueryElement if it does not exist
		if (is_null($this->union))
		{
			$this->union = new JDatabaseQueryElement($name, $query, "$glue");
		}
		// Otherwise append the second UNION.
		else
		{
			$this->union->append($query);
		}

		return $this;
	}

	/**
	 * Add a query to UNION DISTINCT with the current query. Simply a proxy to union with the DISTINCT keyword.
	 *
	 * Usage:
	 * $query->unionDistinct('SELECT name FROM  #__foo')
	 *
	 * @param   mixed   $query  The JDatabaseQuery object or string to union.
	 * @param   string  $glue   The glue by which to join the conditions.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @see     union
	 *
	 * @since   3.0.0
	 * @deprecated  4.0  Use union() instead.
	 */
	public function unionDistinct($query, $glue = '')
	{
		$distinct = true;

		// Apply the distinct flag to the union.
		return $this->union($query, $distinct, $glue);
	}

	/**
	 * Find and replace sprintf-like tokens in a format string.
	 * Each token takes one of the following forms:
	 *     %%       - A literal percent character.
	 *     %[t]     - Where [t] is a type specifier.
	 *     %[n]$[x] - Where [n] is an argument specifier and [t] is a type specifier.
	 *
	 * Types:
	 * a - Numeric: Replacement text is coerced to a numeric type but not quoted or escaped.
	 * e - Escape: Replacement text is passed to $this->escape().
	 * E - Escape (extra): Replacement text is passed to $this->escape() with true as the second argument.
	 * n - Name Quote: Replacement text is passed to $this->quoteName().
	 * q - Quote: Replacement text is passed to $this->quote().
	 * Q - Quote (no escape): Replacement text is passed to $this->quote() with false as the second argument.
	 * r - Raw: Replacement text is used as-is. (Be careful)
	 *
	 * Date Types:
	 * - Replacement text automatically quoted (use uppercase for Name Quote).
	 * - Replacement text should be a string in date format or name of a date column.
	 * y/Y - Year
	 * m/M - Month
	 * d/D - Day
	 * h/H - Hour
	 * i/I - Minute
	 * s/S - Second
	 *
	 * Invariable Types:
	 * - Takes no argument.
	 * - Argument index not incremented.
	 * t - Replacement text is the result of $this->currentTimestamp().
	 * z - Replacement text is the result of $this->nullDate(false).
	 * Z - Replacement text is the result of $this->nullDate(true).
	 *
	 * Usage:
	 * $query->format('SELECT %1$n FROM %2$n WHERE %3$n = %4$a', 'foo', '#__foo', 'bar', 1);
	 * Returns: SELECT `foo` FROM `#__foo` WHERE `bar` = 1
	 *
	 * Notes:
	 * The argument specifier is optional but recommended for clarity.
	 * The argument index used for unspecified tokens is incremented only when used.
	 *
	 * @param   string  $format  The formatting string.
	 *
	 * @return  string  Returns a string produced according to the formatting string.
	 *
	 * @since   3.1.4
	 */
	public function format($format)
	{
		$query = $this;
		$args = array_slice(func_get_args(), 1);
		array_unshift($args, null);

		$i = 1;
		$func = function ($match) use ($query, $args, &$i)
		{
			if (isset($match[6]) && $match[6] == '%')
			{
				return '%';
			}

			// No argument required, do not increment the argument index.
			switch ($match[5])
			{
				case 't':
					return $query->currentTimestamp();
					break;

				case 'z':
					return $query->nullDate(false);
					break;

				case 'Z':
					return $query->nullDate(true);
					break;
			}

			// Increment the argument index only if argument specifier not provided.
			$index = is_numeric($match[4]) ? (int) $match[4] : $i++;

			if (!$index || !isset($args[$index]))
			{
				// TODO - What to do? sprintf() throws a Warning in these cases.
				$replacement = '';
			}
			else
			{
				$replacement = $args[$index];
			}

			switch ($match[5])
			{
				case 'a':
					return 0 + $replacement;
					break;

				case 'e':
					return $query->escape($replacement);
					break;

				case 'E':
					return $query->escape($replacement, true);
					break;

				case 'n':
					return $query->quoteName($replacement);
					break;

				case 'q':
					return $query->quote($replacement);
					break;

				case 'Q':
					return $query->quote($replacement, false);
					break;

				case 'r':
					return $replacement;
					break;

				// Dates
				case 'y':
					return $query->year($query->quote($replacement));
					break;

				case 'Y':
					return $query->year($query->quoteName($replacement));
					break;

				case 'm':
					return $query->month($query->quote($replacement));
					break;

				case 'M':
					return $query->month($query->quoteName($replacement));
					break;

				case 'd':
					return $query->day($query->quote($replacement));
					break;

				case 'D':
					return $query->day($query->quoteName($replacement));
					break;

				case 'h':
					return $query->hour($query->quote($replacement));
					break;

				case 'H':
					return $query->hour($query->quoteName($replacement));
					break;

				case 'i':
					return $query->minute($query->quote($replacement));
					break;

				case 'I':
					return $query->minute($query->quoteName($replacement));
					break;

				case 's':
					return $query->second($query->quote($replacement));
					break;

				case 'S':
					return $query->second($query->quoteName($replacement));
					break;
			}

			return '';
		};

		/**
		 * Regexp to find and replace all tokens.
		 * Matched fields:
		 * 0: Full token
		 * 1: Everything following '%'
		 * 2: Everything following '%' unless '%'
		 * 3: Argument specifier and '$'
		 * 4: Argument specifier
		 * 5: Type specifier
		 * 6: '%' if full token is '%%'
		 */
		return preg_replace_callback('#%(((([\d]+)\$)?([aeEnqQryYmMdDhHiIsStzZ]))|(%))#', $func, $format);
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 * Note: Not all drivers support all units.
	 *
	 * @param   string  $date      The db quoted string representation of the date to add to. May be date or datetime
	 * @param   string  $interval  The string representation of the appropriate number of units
	 * @param   string  $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @link    http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add
	 * @since   3.2.0
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		return 'DATE_ADD(' . $date . ', INTERVAL ' . $interval . ' ' . $datePart . ')';
	}

	/**
	 * Add a query to UNION ALL with the current query.
	 * Multiple unions each require separate statements and create an array of unions.
	 *
	 * Usage:
	 * $query->union('SELECT name FROM  #__foo')
	 *
	 * The $query attribute as an array is deprecated and will not be supported in 4.0.
	 *
	 * $query->union(array('SELECT name FROM  #__foo','SELECT name FROM  #__bar'))
	 *
	 * @param   mixed    $query     The JDatabaseQuery object or string to union.
	 * @param   boolean  $distinct  Not used - ignored.
	 * @param   string   $glue      Not used - ignored.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @see     union
	 *
	 * @since   3.2.0
	 */
	public function unionAll($query, $distinct = false, $glue = '')
	{
		$glue = ')' . PHP_EOL . 'UNION ALL (';
		$name = 'UNION ALL ()';

		if (is_array($query))
		{
			JLog::add('Query attribute as an array is deprecated.', JLog::WARNING, 'deprecated');
		}

		// Get the JDatabaseQueryElement if it does not exist
		if (is_null($this->unionAll))
		{
			$this->unionAll = new JDatabaseQueryElement($name, $query, "$glue");
		}

		// Otherwise append the second UNION.
		else
		{
			$this->unionAll->append($query);
		}

		return $this;
	}

	/**
	 * Validate arguments which are passed to selectRowNumber method and set up common variables.
	 *
	 * @param   string  $orderBy           An expression of ordering for window function.
	 * @param   string  $orderColumnAlias  An alias for new ordering column.
	 *
	 * @return  void
	 *
	 * @since   3.7.0
	 * @throws  RuntimeException
	 */
	protected function validateRowNumber($orderBy, $orderColumnAlias)
	{
		if ($this->selectRowNumber)
		{
			throw new RuntimeException("Method 'selectRowNumber' can be called only once per instance.");
		}

		$this->type = 'select';

		$this->selectRowNumber = array(
			'orderBy' => $orderBy,
			'orderColumnAlias' => $orderColumnAlias,
		);
	}

	/**
	 * Return the number of the current row.
	 *
	 * Usage:
	 * $query->select('id');
	 * $query->selectRowNumber('ordering,publish_up DESC', 'new_ordering');
	 * $query->from('#__content');
	 *
	 * @param   string  $orderBy           An expression of ordering for window function.
	 * @param   string  $orderColumnAlias  An alias for new ordering column.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.7.0
	 * @throws  RuntimeException
	 */
	public function selectRowNumber($orderBy, $orderColumnAlias)
	{
		$this->validateRowNumber($orderBy, $orderColumnAlias);
		$this->select("ROW_NUMBER() OVER (ORDER BY $orderBy) AS $orderColumnAlias");

		return $this;
	}
}
joomla/database/exception/unsupported.php000064400000000650152177723700014704 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an unsupported database object
 *
 * @since  3.6
 */
class JDatabaseExceptionUnsupported extends RuntimeException
{
}
joomla/database/exception/connecting.php000064400000000665152177723700014451 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an error connecting to the database platform
 *
 * @since  3.6
 */
class JDatabaseExceptionConnecting extends RuntimeException
{
}
joomla/database/exception/executing.php000064400000002332152177723700014306 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Exception class defining an error executing a statement
 *
 * @since  3.6
 */
class JDatabaseExceptionExecuting extends RuntimeException
{
	/**
	 * The SQL statement that was executed.
	 *
	 * @var    string
	 * @since  3.6
	 */
	private $query;

	/**
	 * Construct the exception
	 *
	 * @param   string     $query     The SQL statement that was executed.
	 * @param   string     $message   The Exception message to throw. [optional]
	 * @param   integer    $code      The Exception code. [optional]
	 * @param   Exception  $previous  The previous exception used for the exception chaining. [optional]
	 *
	 * @since   3.6
	 */
	public function __construct($query, $message = '', $code = 0, Exception $previous = null)
	{
		parent::__construct($message, $code, $previous);

		$this->query = $query;
	}

	/**
	 * Get the SQL statement that was executed
	 *
	 * @return  string
	 *
	 * @since   3.6
	 */
	public function getQuery()
	{
		return $this->query;
	}
}
joomla/database/iterator/mysql.php000064400000002361152177723700013315 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL database iterator.
 *
 * @link        http://dev.mysql.com/doc/
 * @since       3.0.0
 * @deprecated  4.0  Use MySQLi or PDO MySQL instead
 */
class JDatabaseIteratorMysql extends JDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @since   3.0.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		return mysql_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject()
	{
		return mysql_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult()
	{
		mysql_free_result($this->cursor);
	}
}
joomla/database/iterator/sqlsrv.php000064400000002224152177723700013500 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQL server database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorSqlsrv extends JDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @since   3.0.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		return sqlsrv_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject()
	{
		return sqlsrv_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult()
	{
		sqlsrv_free_stmt($this->cursor);
	}
}
joomla/database/iterator/oracle.php000064400000000612152177723700013412 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Oracle database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorOracle extends JDatabaseIteratorPdo
{
}
joomla/database/iterator/sqlazure.php000064400000000622152177723700014014 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQL azure database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorSqlazure extends JDatabaseIteratorSqlsrv
{
}
joomla/database/iterator/mysqli.php000064400000002222152177723700013462 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQLi database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorMysqli extends JDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @since   3.0.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		return mysqli_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject()
	{
		return mysqli_fetch_object($this->cursor, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult()
	{
		mysqli_free_result($this->cursor);
	}
}
joomla/database/iterator/pdomysql.php000064400000001032152177723700014012 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL database iterator for the PDO based MySQL database driver.
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @link        https://dev.mysql.com/doc/
 * @since       3.4
 */
class JDatabaseIteratorPdomysql extends JDatabaseIteratorPdo
{
}
joomla/database/iterator/sqlite.php000064400000000612152177723700013446 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQLite database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorSqlite extends JDatabaseIteratorPdo
{
}
joomla/database/iterator/postgresql.php000064400000002311152177723700014346 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL database iterator.
 *
 * @since       3.2.0
 * @deprecated  4.0  Use PDO PostgreSQL instead
 */
class JDatabaseIteratorPostgresql extends JDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @since   3.2.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		return pg_num_rows($this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.2.0
	 */
	protected function fetchObject()
	{
		return pg_fetch_object($this->cursor, null, $this->class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.2.0
	 */
	protected function freeResult()
	{
		pg_free_result($this->cursor);
	}
}
joomla/database/iterator/pdo.php000064400000002643152177723700012735 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PDO database iterator.
 *
 * @since  3.0.0
 */
class JDatabaseIteratorPdo extends JDatabaseIterator
{
	/**
	 * Get the number of rows in the result set for the executed SQL given by the cursor.
	 *
	 * @return  integer  The number of rows in the result set.
	 *
	 * @since   3.0.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			return $this->cursor->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			return $this->cursor->fetchObject($this->class);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult()
	{
		if (!empty($this->cursor) && $this->cursor instanceof PDOStatement)
		{
			$this->cursor->closeCursor();
		}
	}
}
joomla/database/iterator/pgsql.php000064400000000672152177723700013301 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL database iterator for the PDO based PostgreSQL database driver.
 *
 * @since  3.9.0
 */
class JDatabaseIteratorPgsql extends JDatabaseIteratorPdo
{
}
joomla/database/query/mysql.php000064400000000674152177723700012636 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since       1.7.0
 * @deprecated  4.0  Use MySQLi or PDO MySQL instead
 */
class JDatabaseQueryMysql extends JDatabaseQueryMysqli
{
}
joomla/database/query/limitable.php000064400000003236152177723700013430 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Database Query Limitable Interface.
 * Adds bind/unbind methods as well as a getBounded() method
 * to retrieve the stored bounded variables on demand prior to
 * query execution.
 *
 * @since  3.0.0
 */
interface JDatabaseQueryLimitable
{
	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the JDatabaseQueryLimitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0);

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0);
}
joomla/database/query/sqlsrv.php000064400000056775152177723700013040 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since  1.7.0
 */
class JDatabaseQuerySqlsrv extends JDatabaseQuery implements JDatabaseQueryLimitable
{
	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $name_quotes = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $null_date = '1900-01-01 00:00:00';

	/**
	 * @var    integer  The affected row limit for the current SQL statement.
	 * @since  3.2
	 */
	protected $limit = 0;

	/**
	 * @var    integer  The affected row offset to apply for the current SQL statement.
	 * @since  3.2
	 */
	protected $offset = 0;

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string	The completed query.
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		$query = '';

		switch ($this->type)
		{
			case 'select':
				// Add required aliases for offset or fixGroupColumns method
				$columns = $this->fixSelectAliases();

				$query = (string) $this->select;

				if ($this->group)
				{
					$this->fixGroupColumns($columns);
				}

				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->selectRowNumber === null)
				{
					if ($this->group)
					{
						$query .= (string) $this->group;
					}

					if ($this->having)
					{
						$query .= (string) $this->having;
					}
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				if ($this instanceof JDatabaseQueryLimitable && ($this->limit > 0 || $this->offset > 0))
				{
					$query = $this->processLimit($query, $this->limit, $this->offset);
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				// Set method
				if ($this->set)
				{
					$query .= (string) $this->set;
				}
				// Columns-Values method
				elseif ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->insert->getElements();
					$tableName = array_shift($elements);

					$query .= 'VALUES ';
					$query .= (string) $this->values;

					if ($this->autoIncrementField)
					{
						$query = 'SET IDENTITY_INSERT ' . $tableName . ' ON;' . $query . 'SET IDENTITY_INSERT ' . $tableName . ' OFF;';
					}

					if ($this->where)
					{
						$query .= (string) $this->where;
					}
				}

				break;

			case 'delete':
				$query .= (string) $this->delete;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			case 'update':
				if ($this->join)
				{
					$tmpUpdate    = $this->update;
					$tmpFrom      = $this->from;
					$this->update = null;
					$this->from   = null;

					$updateElem  = $tmpUpdate->getElements();
					$updateArray = explode(' ', $updateElem[0]);

					// Use table alias if exists
					$this->update(end($updateArray));
					$this->from($updateElem[0]);

					$query .= (string) $this->update;
					$query .= (string) $this->set;
					$query .= (string) $this->from;

					$this->update = $tmpUpdate;
					$this->from   = $tmpFrom;

					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}
				else
				{
					$query .= (string) $this->update;
					$query .= (string) $this->set;
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				break;

			default:
				$query = parent::__toString();

				break;
		}

		return $query;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @param   string  $len    The lenght of the char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   1.7.0
	 */
	public function castAsChar($value, $len = null)
	{
		if (!$len)
		{
			return 'CAST(' . $value . ' as NVARCHAR(30))';
		}
		else
		{
			return 'CAST(' . $value . ' as NVARCHAR(' . $len . '))';
		}
	}

	/**
	 * Gets the function to determine the length of a character string.
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   1.7.0
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'DATALENGTH(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.7.0
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return '(' . implode('+' . $this->quote($separator) . '+', $values) . ')';
		}
		else
		{
			return '(' . implode('+', $values) . ')';
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function currentTimestamp()
	{
		return 'GETDATE()';
	}

	/**
	 * Get the length of a string in bytes.
	 *
	 * @param   string  $value  The string to measure.
	 *
	 * @return  integer
	 *
	 * @since   1.7.0
	 */
	public function length($value)
	{
		return 'LEN(' . $value . ')';
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   datetime  $date      The date to add to; type may be time or datetime.
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   3.2.0
	 * @note    Not all drivers support all units.
	 * @link    http://msdn.microsoft.com/en-us/library/ms186819.aspx for more information
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		return 'DATEADD(' . $datePart . ', ' . $interval . ', ' . $date . ')';
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit)
		{
			$total = $offset + $limit;

			$position = stripos($query, 'SELECT');
			$distinct = stripos($query, 'SELECT DISTINCT');

			if ($position === $distinct)
			{
				$query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $total, $position, 15);
			}
			else
			{
				$query = substr_replace($query, 'SELECT TOP ' . (int) $total, $position, 6);
			}
		}

		if (!$offset)
		{
			return $query;
		}

		return PHP_EOL
			. 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS RowNumber FROM ('
			. $query
			. PHP_EOL . ') AS A) AS A WHERE RowNumber > ' . (int) $offset;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Split a string of sql expression into an array of individual columns.
	 * Single line or line end comments and multi line comments are stripped off.
	 * Always return at least one column.
	 *
	 * @param   string  $string  Input string of sql expression like select expression.
	 *
	 * @return  array[]  The columns from the input string separated into an array.
	 *
	 * @since   3.7.0
	 */
	protected function splitSqlExpression($string)
	{
		// Append whitespace as equivalent to the last comma
		$string .= ' ';

		$colIdx    = 0;
		$start     = 0;
		$open      = false;
		$openC     = 0;
		$comment   = false;
		$endString = '';
		$length    = strlen($string);
		$columns   = array();
		$column    = array();
		$current   = '';
		$previous  = null;
		$operators = array(
			'+' => '',
			'-' => '',
			'*' => '',
			'/' => '',
			'%' => '',
			'&' => '',
			'|' => '',
			'~' => '',
			'^' => '',
		);

		$addBlock = function ($block) use (&$column, &$colIdx)
		{
			if (isset($column[$colIdx]))
			{
				$column[$colIdx] .= $block;
			}
			else
			{
				$column[$colIdx] = $block;
			}
		};

		for ($i = 0; $i < $length; $i++)
		{
			$current      = substr($string, $i, 1);
			$current2     = substr($string, $i, 2);
			$current3     = substr($string, $i, 3);
			$lenEndString = strlen($endString);
			$testEnd      = substr($string, $i, $lenEndString);

			if ($current == '[' || $current == '"' || $current == "'" || $current2 == '--'
				|| ($current2 == '/*') || ($current == '#' && $current3 != '#__')
				|| ($lenEndString && $testEnd == $endString))
			{
				if ($open)
				{
					if ($testEnd === $endString)
					{
						if ($comment)
						{
							if ($lenEndString > 1)
							{
								$i += ($lenEndString - 1);
							}

							// Move cursor after close tag of comment
							$start = $i + 1;
							$comment = false;
						}
						elseif ($current == "'" || $current == ']' || $current == '"')
						{
							// Check for escaped quote like '', ]] or ""
							$n = 1;

							while ($i + $n < $length && $string[$i + $n] == $current)
							{
								$n++;
							}

							// Jump to the last quote
							$i += $n - 1;

							if ($n % 2 === 0)
							{
								// There is only escaped quote
								continue;
							}
							elseif ($n > 2)
							{
								// The last right close quote is not escaped
								$current = $string[$i];
							}
						}

						$open = false;
						$endString = '';
					}
				}
				else
				{
					$open = true;

					if ($current == '#' || $current2 == '--')
					{
						$endString = "\n";
						$comment = true;
					}
					elseif ($current2 == '/*')
					{
						$endString = '*/';
						$comment = true;
					}
					elseif ($current == '[')
					{
						$endString = ']';
					}
					else
					{
						$endString = $current;
					}

					if ($comment && $start < $i)
					{
						// Add string exists before comment
						$addBlock(substr($string, $start, $i - $start));
						$previous = $string[$i - 1];
						$start = $i;
					}
				}
			}
			elseif (!$open)
			{
				if ($current == '(')
				{
					$openC++;
					$previous = $current;
				}
				elseif ($current == ')')
				{
					$openC--;
					$previous = $current;
				}
				elseif ($current == '.')
				{
					if ($i === $start && $colIdx > 0 && !isset($column[$colIdx]))
					{
						// Remove whitepace placed before dot
						$colIdx--;
					}

					$previous = $current;
				}
				elseif ($openC === 0)
				{
					if (ctype_space($current))
					{
						// Normalize whitepace
						$string[$i] = ' ';

						if ($start < $i)
						{
							// Add text placed before whitespace
							$addBlock(substr($string, $start, $i - $start));
							$colIdx++;
							$previous = $string[$i - 1];
						}
						elseif (isset($column[$colIdx]))
						{
							if ($colIdx > 1 || !isset($operators[$previous]))
							{
								// There was whitespace after comment
								$colIdx++;
							}
						}

						// Move cursor forward
						$start = $i + 1;
					}
					elseif (isset($operators[$current]) && ($current !== '*' || $previous !== '.'))
					{
						if ($start < $i)
						{
							// Add text before operator
							$addBlock(substr($string, $start, $i - $start));
							$colIdx++;
						}
						elseif (!isset($column[$colIdx]) && isset($operators[$previous]))
						{
							// Do not create whitespace between operators
							$colIdx--;
						}

						// Add operator
						$addBlock($current);
						$previous = $current;
						$colIdx++;

						// Move cursor forward
						$start = $i + 1;
					}
					else
					{
						$previous = $current;
					}
				}
			}

			if (($current == ',' && !$open && $openC == 0) || $i == $length - 1)
			{
				if ($start < $i && !$comment)
				{
					// Save remaining text
					$addBlock(substr($string, $start, $i - $start));
				}

				$columns[] = $column;

				// Reset values
				$column   = array();
				$colIdx   = 0;
				$previous = null;

				// Column saved, move cursor forward after comma
				$start = $i + 1;
			}
		}

		return $columns;
	}

	/**
	 * Add required aliases to columns for select statement in subquery.
	 *
	 * @return  array[]  Array of columns with added missing aliases.
	 *
	 * @since   3.7.0
	 */
	protected function fixSelectAliases()
	{
		$operators = array(
			'+' => '',
			'-' => '',
			'*' => '',
			'/' => '',
			'%' => '',
			'&' => '',
			'|' => '',
			'~' => '',
			'^' => '',
		);

		// Split into array and remove comments
		$columns = $this->splitSqlExpression(implode(',', $this->select->getElements()));

		foreach ($columns as $i => $column)
		{
			$size = count($column);

			if ($size == 0)
			{
				continue;
			}

			if ($size > 2 && strcasecmp($column[$size - 2], 'AS') === 0)
			{
				// Alias exists, replace it to uppercase
				$columns[$i][$size - 2] = 'AS';
				continue;
			}

			if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false)
			{
				// This words are reserved, they are not column names
				array_shift($column);
				$size--;
			}

			$lastWord = strtoupper($column[$size - 1]);
			$length   = strlen($lastWord);
			$lastChar = $lastWord[$length - 1];

			if ($lastChar == '*')
			{
				// Skip on wildcard
				continue;
			}

			if ($lastChar == ')'
				|| ($size == 1 && $lastChar == "'")
				|| $lastWord[0] == '@'
				|| $lastWord == 'NULL'
				|| $lastWord == 'END'
				|| is_numeric($lastWord))
			{
				/* Ends with:
				 * - SQL function
				 * - single static value like 'only '+'string'
				 * - @@var
				 * - NULL
				 * - CASE ... END
				 * - Numeric
				 */
				$columns[$i][] = 'AS';
				$columns[$i][] = $this->quoteName('columnAlias' . $i);
				continue;
			}

			if ($size == 1)
			{
				continue;
			}

			$lastChar2 = substr($column[$size - 2], -1);

			// Check if column ends with  '- a.x' or '- a. x'
			if (isset($operators[$lastChar2])
				|| ($size > 2 && $lastChar2 === '.' && isset($operators[substr($column[$size - 3], -1)])))
			{
				// Ignore plus signs if column start with them
				if ($size != 2 || ltrim($column[0], '+') !== '' || $column[1][0] === "'")
				{
					// If operator exists before last word then alias is required for subquery
					$columns[$i][] = 'AS';
					$columns[$i][] = $this->quoteName('columnAlias' . $i);
					continue;
				}
			}
			elseif ($column[$size - 1][0] !== '.' && $lastChar2 !== '.')
			{
				// If columns is like name name2 then second word is alias.
				// Add missing AS before the alias, exception for 'a. x' and 'a .x'
				array_splice($columns[$i], -1, 0, 'AS');
			}
		}

		$selectColumns = array();

		foreach ($columns as $i => $column)
		{
			$selectColumns[$i] = implode(' ', $column);
		}

		$this->select = new JDatabaseQueryElement('SELECT', $selectColumns);

		return $columns;
	}

	/**
	 * Add missing columns names to GROUP BY clause.
	 *
	 * @param   array[]  $selectColumns  Array of columns from splitSqlExpression method.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.7.0
	 */
	protected function fixGroupColumns($selectColumns)
	{
		// Cache tables columns
		static $cacheCols = array();

		// Known columns of all included tables
		$knownColumnsByAlias = array();

		$iquotes  = array('"' => '', '[' => '', "'" => '');
		$nquotes = array('"', '[', ']');

		// Aggregate functions
		$aFuncs = array(
			'AVG(',
			'CHECKSUM_AGG(',
			'COUNT(',
			'COUNT_BIG(',
			'GROUPING(',
			'GROUPING_ID(',
			'MIN(',
			'MAX(',
			'SUM(',
			'STDEV(',
			'STDEVP(',
			'VAR(',
			'VARP(',
		);

		// Aggregated columns
		$filteredColumns = array();

		// Aliases found in SELECT statement
		$knownAliases = array();
		$wildcardTables = array();

		foreach ($selectColumns as $i => $column)
		{
			$size = count($column);

			if ($size === 0)
			{
				continue;
			}

			if ($i == 0 && stripos(' DISTINCT ALL ', " $column[0] ") !== false)
			{
				// These words are reserved, they are not column names
				array_shift($selectColumns[0]);
				array_shift($column);
				$size--;
			}

			if ($size > 2 && $column[$size - 2] === 'AS')
			{
				// Save and remove AS alias
				$alias = $column[$size - 1];

				if (isset($iquotes[$alias[0]]))
				{
					$alias = substr($alias, 1, -1);
				}

				// Remove alias
				$selectColumns[$i] = $column = array_slice($column, 0, -2);

				if ($size === 3 || ($size === 4 && strpos('+-*/%&|~^', $column[0][0]) !== false))
				{
					$lastWord = $column[$size - 3];

					if ($lastWord[0] === "'" || $lastWord === 'NULL' || is_numeric($lastWord))
					{
						unset($selectColumns[$i]);

						continue;
					}
				}

				// Remember pair alias => column expression
				$knownAliases[$alias] = implode(' ', $column);
			}

			$aggregated = false;

			foreach ($column as $j => $block)
			{
				if (substr($block, -2) === '.*')
				{
					// Found column ends with .*
					if (isset($iquotes[$block[0]]))
					{
						// Quoted table
						$wildcardTables[] = substr($block, 1, -3);
					}
					else
					{
						$wildcardTables[] = substr($block, 0, -2);
					}
				}
				elseif (str_ireplace($aFuncs, '', $block) != $block)
				{
					$aggregated = true;
				}

				if ($block[0] === "'")
				{
					// Shrink static strings which could contain column name
					$column[$j] = "''";
				}
			}

			if (!$aggregated)
			{
				// Without aggregated columns and aliases
				$filteredColumns[] = implode(' ', $selectColumns[$i]);
			}

			// Without aliases and static strings
			$selectColumns[$i] = implode(' ', $column);
		}

		// If select statement use table.* expression
		if ($wildcardTables)
		{
			// Split FROM statement into list of tables
			$tables = $this->splitSqlExpression(implode(',', $this->from->getElements()));

			foreach ($tables as $i => $table)
			{
				$table = implode(' ', $table);

				// Exclude subquery from the FROM clause
				if (strpos($table, '(') === false)
				{
					// Unquote
					$table = str_replace($nquotes, '', $table);
					$table = str_replace('#__', $this->db->getPrefix(), $table);
					$table = explode(' ', $table);
					$alias = end($table);
					$table = $table[0];

					// Chek if exists a wildcard with current alias table?
					if (in_array($alias, $wildcardTables, true))
					{
						if (!isset($cacheCols[$table]))
						{
							$cacheCols[$table] = $this->db->getTableColumns($table);
						}

						if ($this->join || $table != $alias)
						{
							foreach ($cacheCols[$table] as $name => $type)
							{
								$knownColumnsByAlias[$alias][] = $alias . '.' . $name;
							}
						}
						else
						{
							foreach ($cacheCols[$table] as $name => $type)
							{
								$knownColumnsByAlias[$alias][] = $name;
							}
						}
					}
				}
			}

			// Now we need to get all tables from any joins
			// Go through all joins and add them to the tables array
			if ($this->join)
			{
				foreach ($this->join as $join)
				{
					// Unquote and replace prefix
					$joinTbl = str_replace($nquotes, '', (string) $join);
					$joinTbl = str_replace("#__", $this->db->getPrefix(), $joinTbl);

					// Exclude subquery
					if (preg_match('/JOIN\s+(\w+)(?:\s+AS)?(?:\s+(\w+))?/i', $joinTbl, $matches))
					{
						$table = $matches[1];
						$alias = isset($matches[2]) ? $matches[2] : $table;

						// Chek if exists a wildcard with current alias table?
						if (in_array($alias, $wildcardTables, true))
						{
							if (!isset($cacheCols[$table]))
							{
								$cacheCols[$table] = $this->db->getTableColumns($table);
							}

							foreach ($cacheCols[$table] as $name => $type)
							{
								$knownColumnsByAlias[$alias][] = $alias . '.' . $name;
							}
						}
					}
				}
			}
		}

		$selectExpression = implode(',', $selectColumns);

		// Split into the right columns
		$groupColumns = $this->splitSqlExpression(implode(',', $this->group->getElements()));

		// Remove column aliases from GROUP statement - SQLSRV does not support it
		foreach ($groupColumns as $i => $column)
		{
			$groupColumns[$i] = implode(' ', $column);
			$column = str_replace($nquotes, '', $groupColumns[$i]);

			if (isset($knownAliases[$column]))
			{
				// Be sure that this is not a valid column name
				if (!preg_match('/\b' . preg_quote($column, '/') . '\b/', $selectExpression))
				{
					// Replace column alias by column expression
					$groupColumns[$i] = $knownAliases[$column];
				}
			}
		}

		// Find all alias.* and fill with proper table column names
		foreach ($filteredColumns as $i => $column)
		{
			if (substr($column, -2) === '.*')
			{
				unset($filteredColumns[$i]);

				// Extract alias.* columns into GROUP BY statement
				$groupColumns = array_merge($groupColumns, $knownColumnsByAlias[substr($column, 0, -2)]);
			}
		}

		$groupColumns = array_merge($groupColumns, $filteredColumns);

		if ($this->order)
		{
			// Remove direction suffixes
			$dir = array(" DESC\v", " ASC\v");

			$orderColumns = $this->splitSqlExpression(implode(',', $this->order->getElements()));

			foreach ($orderColumns as $i => $column)
			{
				$column = implode(' ', $column);
				$orderColumns[$i] = $column = trim(str_ireplace($dir, '', "$column\v"), "\v");

				if (isset($knownAliases[str_replace($nquotes, '', $column)]))
				{
					unset($orderColumns[$i]);
				}

				if (str_ireplace($aFuncs, '', $column) != $column)
				{
					// Do not add aggregate expression
					unset($orderColumns[$i]);
				}
			}

			$groupColumns = array_merge($groupColumns, $orderColumns);
		}

		// Get a unique string of all column names that need to be included in the group statement
		$this->group = new JDatabaseQueryElement('GROUP BY', array_unique($groupColumns));

		return $this;
	}

	/**
	 * Return correct rand() function for MSSQL.
	 *
	 * Ensure that the rand() function is MSSQL compatible.
	 *
	 * Usage:
	 * $query->Rand();
	 *
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' NEWID() ';
	}
}
joomla/database/query/oracle.php000064400000012367152177723700012740 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Oracle Query Building Class.
 *
 * @since  3.0.0
 */
class JDatabaseQueryOracle extends JDatabaseQueryPdo implements JDatabaseQueryPreparable, JDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  3.0.0
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  3.0.0
	 */
	protected $limit;

	/**
	 * @var    array  Bounded object array
	 * @since  3.0.0
	 */
	protected $bounded = array();

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  JDatabaseQueryOracle
	 *
	 * @since   3.0.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array())
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = array();

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value = &$value;
		$obj->dataType = $dataType;
		$obj->length = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   3.0.0
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}
		else
		{
			if (isset($this->bounded[$key]))
			{
				return $this->bounded[$key];
			}
		}
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  JDatabaseQueryOracle  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = array();
				break;
		}

		parent::clear($clause);

		return $this;
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the JDatabaseQueryLimitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		// Check if we need to mangle the query.
		if ($limit || $offset)
		{
			$query = 'SELECT joomla2.*
		              FROM (
		                  SELECT joomla1.*, ROWNUM AS joomla_db_rownum
		                  FROM (
		                      ' . $query . '
		                  ) joomla1
		              ) joomla2';

			// Check if the limit value is greater than zero.
			if ($limit > 0)
			{
				$query .= ' WHERE joomla2.joomla_db_rownum BETWEEN ' . ($offset + 1) . ' AND ' . ($offset + $limit);
			}
			else
			{
				// Check if there is an offset and then use this.
				if ($offset)
				{
					$query .= ' WHERE joomla2.joomla_db_rownum > ' . ($offset + 1);
				}
			}
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQueryOracle  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}
}
joomla/database/query/element.php000064400000005204152177723700013114 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Element Class.
 *
 * @property-read    string  $name      The name of the element.
 * @property-read    array   $elements  An array of elements.
 * @property-read    string  $glue      Glue piece.
 *
 * @since  1.7.0
 */
class JDatabaseQueryElement
{
	/**
	 * @var    string  The name of the element.
	 * @since  1.7.0
	 */
	protected $name = null;

	/**
	 * @var    array  An array of elements.
	 * @since  1.7.0
	 */
	protected $elements = null;

	/**
	 * @var    string  Glue piece.
	 * @since  1.7.0
	 */
	protected $glue = null;

	/**
	 * Constructor.
	 *
	 * @param   string  $name      The name of the element.
	 * @param   mixed   $elements  String or array.
	 * @param   string  $glue      The glue for elements.
	 *
	 * @since   1.7.0
	 */
	public function __construct($name, $elements, $glue = ',')
	{
		$this->elements = array();
		$this->name = $name;
		$this->glue = $glue;

		$this->append($elements);
	}

	/**
	 * Magic function to convert the query element to a string.
	 *
	 * @return  string
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		if (substr($this->name, -2) == '()')
		{
			return PHP_EOL . substr($this->name, 0, -2) . '(' . implode($this->glue, $this->elements) . ')';
		}
		else
		{
			return PHP_EOL . $this->name . ' ' . implode($this->glue, $this->elements);
		}
	}

	/**
	 * Appends element parts to the internal list.
	 *
	 * @param   mixed  $elements  String or array.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	public function append($elements)
	{
		if (is_array($elements))
		{
			$this->elements = array_merge($this->elements, $elements);
		}
		else
		{
			$this->elements[] = $elements;
		}
	}

	/**
	 * Gets the elements of this element.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	public function getElements()
	{
		return $this->elements;
	}

	/**
	 * Sets the name of this element.
	 *
	 * @param   string  $name  Name of the element.
	 *
	 * @return  JDatabaseQueryElement  Returns this object to allow chaining.
	 *
	 * @since   3.6
	 */
	public function setName($name)
	{
		$this->name = $name;

		return $this;
	}

	/**
	 * Method to provide deep copy support to nested objects and arrays
	 * when cloning.
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 */
	public function __clone()
	{
		foreach ($this as $k => $v)
		{
			if (is_object($v) || is_array($v))
			{
				$this->{$k} = unserialize(serialize($v));
			}
		}
	}
}
joomla/database/query/sqlazure.php000064400000001470152177723700013332 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since  1.7.0
 */
class JDatabaseQuerySqlazure extends JDatabaseQuerySqlsrv
{
	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 *
	 * @since  1.7.0
	 */
	protected $name_quotes = '';
}
joomla/database/query/mysqli.php000064400000012315152177723700013002 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since  1.7.0
 */
class JDatabaseQueryMysqli extends JDatabaseQuery implements JDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  3.0.0
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  3.0.0
	 */
	protected $limit;

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string  The completed query.
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		switch ($this->type)
		{
			case 'select':
				if ($this->selectRowNumber)
				{
					$orderBy      = $this->selectRowNumber['orderBy'];
					$tmpOffset    = $this->offset;
					$tmpLimit     = $this->limit;
					$this->offset = 0;
					$this->limit  = 0;
					$tmpOrder     = $this->order;
					$this->order  = null;
					$query        = parent::__toString();
					$this->order  = $tmpOrder;
					$this->offset = $tmpOffset;
					$this->limit  = $tmpLimit;

					// Add support for second order by, offset and limit
					$query = PHP_EOL . 'SELECT * FROM (' . $query . PHP_EOL . "ORDER BY $orderBy" . PHP_EOL . ') w';

					if ($this->order)
					{
						$query .= (string) $this->order;
					}

					return $this->processLimit($query, $this->limit, $this->offset);
				}
		}

		return parent::__toString();
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return string
	 *
	 * @since 3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 && $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}
		elseif ($limit > 0)
		{
			$query .= ' LIMIT ' . $limit;
		}

		return $query;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.7.0
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			$concat_string = 'CONCAT_WS(' . $this->quote($separator);

			foreach ($values as $value)
			{
				$concat_string .= ', ' . $value;
			}

			return $concat_string . ')';
		}
		else
		{
			return 'CONCAT(' . implode(',', $values) . ')';
		}
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Return correct regexp operator for mysqli.
	 *
	 * Ensure that the regexp operator is mysqli compatible.
	 *
	 * Usage:
	 * $query->where('field ' . $query->regexp($search));
	 *
	 * @param   string  $value  The regex pattern.
	 *
	 * @return  string  Returns the regex operator.
	 *
	 * @since   1.7.3
	 */
	public function regexp($value)
	{
		return ' REGEXP ' . $value;
	}

	/**
	 * Return correct rand() function for Mysql.
	 *
	 * Ensure that the rand() function is Mysql compatible.
	 * 
	 * Usage:
	 * $query->Rand();
	 * 
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' RAND() ';
	}

	/**
	 * Return the number of the current row.
	 *
	 * @param   string  $orderBy           An expression of ordering for window function.
	 * @param   string  $orderColumnAlias  An alias for new ordering column.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.7.0
	 * @throws  RuntimeException
	 */
	public function selectRowNumber($orderBy, $orderColumnAlias)
	{
		$this->validateRowNumber($orderBy, $orderColumnAlias);
		$this->select("(SELECT @rownum := @rownum + 1 FROM (SELECT @rownum := 0) AS r) AS $orderColumnAlias");

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 * $query->select($query->castAsChar('a', 40));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @param   string  $len    The lenght of the char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   3.7.0
	 */
	public function castAsChar($value, $len = null)
	{
		if (!$len)
		{
			return $value;
		}
		else
		{
			return ' CAST(' . $value . ' AS CHAR(' . $len . '))';
		}
	}
}
joomla/database/query/pdomysql.php000064400000000701152177723700013330 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @package     Joomla.Platform
 * @subpackage  Database
 * @since       3.4
 */
class JDatabaseQueryPdomysql extends JDatabaseQueryMysqli
{
}
joomla/database/query/preparable.php000064400000003705152177723700013604 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Database Query Preparable Interface.
 * Adds bind/unbind methods as well as a getBounded() method
 * to retrieve the stored bounded variables on demand prior to
 * query execution.
 *
 * @since  3.0.0
 */
interface JDatabaseQueryPreparable
{
	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  JDatabaseQuery
	 *
	 * @since   3.0.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array());

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   3.0.0
	 */
	public function &getBounded($key = null);
}
joomla/database/query/sqlite.php000064400000024343152177723700012771 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQLite Query Building Class.
 *
 * @since  3.0.0
 */
class JDatabaseQuerySqlite extends JDatabaseQueryPdo implements JDatabaseQueryPreparable, JDatabaseQueryLimitable
{
	/**
	 * @var    integer  The offset for the result set.
	 * @since  3.0.0
	 */
	protected $offset;

	/**
	 * @var    integer  The limit for the result set.
	 * @since  3.0.0
	 */
	protected $limit;

	/**
	 * @var    array  Bounded object array
	 * @since  3.0.0
	 */
	protected $bounded = array();

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  JDatabaseQuerySqlite
	 *
	 * @since   3.0.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array())
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = array();

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value = &$value;
		$obj->dataType = $dataType;
		$obj->length = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   3.0.0
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}
		else
		{
			if (isset($this->bounded[$key]))
			{
				return $this->bounded[$key];
			}
		}
	}

	/**
	 * Gets the number of characters in a string.
	 *
	 * Note, use 'length' to find the number of bytes in a string.
	 *
	 * Usage:
	 * $query->select($query->charLength('a'));
	 *
	 * @param   string  $field      A value.
	 * @param   string  $operator   Comparison operator between charLength integer value and $condition
	 * @param   string  $condition  Integer value to compare charLength with.
	 *
	 * @return  string  The required char length call.
	 *
	 * @since   3.2.0
	 */
	public function charLength($field, $operator = null, $condition = null)
	{
		return 'length(' . $field . ')' . (isset($operator) && isset($condition) ? ' ' . $operator . ' ' . $condition : '');
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  JDatabaseQuerySqlite  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = array();
				break;
		}

		parent::clear($clause);

		return $this;
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.7.0
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return implode(' || ' . $this->quote($separator) . ' || ', $values);
		}
		else
		{
			return implode(' || ', $values);
		}
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset. This method is used
	 * automatically by the __toString() method if it detects that the
	 * query implements the JDatabaseQueryLimitable interface.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0 || $offset > 0)
		{
			$query .= ' LIMIT ' . $offset . ', ' . $limit;
		}

		return $query;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQuerySqlite  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Add to the current date and time.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   datetime  $date      The date or datetime to add to
	 * @param   string    $interval  The string representation of the appropriate number of units
	 * @param   string    $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   3.2.0
	 * @link    http://www.sqlite.org/lang_datefunc.html
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		// SQLite does not support microseconds as a separate unit. Convert the interval to seconds
		if (strcasecmp($datePart, 'microseconds') == 0)
		{
			// Force the dot as a decimal point
			$interval = str_replace(',', '.', .001 * $interval);

			$datePart = 'seconds';
		}

		if (substr($interval, 0, 1) != '-')
		{
			return "datetime('" . $date . "', '+" . $interval . " " . $datePart . "')";
		}
		else
		{
			return "datetime('" . $date . "', '" . $interval . " " . $datePart . "')";
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * Usage:
	 * $query->where('published_up < '.$query->currentTimestamp());
	 *
	 * @return  string
	 *
	 * @since   3.4
	 */
	public function currentTimestamp()
	{
		return 'CURRENT_TIMESTAMP';
	}

	/**
	 * Magic function to convert the query to a string.
	 *
	 * @return  string  The completed query.
	 *
	 * @since   1.7.0
	 */
	public function __toString()
	{
		switch ($this->type)
		{
			case 'select':
				if ($this->selectRowNumber)
				{
					$orderBy          = $this->selectRowNumber['orderBy'];
					$orderColumnAlias = $this->selectRowNumber['orderColumnAlias'];

					$column = "ROW_NUMBER() AS $orderColumnAlias";

					if ($this->select === null)
					{
						$query = PHP_EOL . "SELECT 1"
							. (string) $this->from
							. (string) $this->where;
					}
					else
					{
						$tmpOffset    = $this->offset;
						$tmpLimit     = $this->limit;
						$this->offset = 0;
						$this->limit  = 0;
						$tmpOrder    = $this->order;
						$this->order = null;
						$query       = parent::__toString();
						$column      = "w.*, $column";
						$this->order = $tmpOrder;
						$this->offset = $tmpOffset;
						$this->limit  = $tmpLimit;
					}

					// Special sqlite query to count ROW_NUMBER
					$query = PHP_EOL . "SELECT $column"
						. PHP_EOL . "FROM ($query" . PHP_EOL . "ORDER BY $orderBy"
						. PHP_EOL . ") AS w,(SELECT ROW_NUMBER(0)) AS r"
						// Forbid to flatten subqueries.
						. ((string) $this->order ?: PHP_EOL . 'ORDER BY NULL');

					return $this->processLimit($query, $this->limit, $this->offset);
				}

				break;

			case 'update':
				if ($this->join)
				{
					$table = $this->update->getElements();
					$table = $table[0];

					$tableName = explode(' ', $table);
					$tableName = $tableName[0];

					if ($this->columns === null)
					{
						$fields = $this->db->getTableColumns($tableName);

						foreach ($fields as $key => $value)
						{
							$fields[$key] = $key;
						}

						$this->columns = new JDatabaseQueryElement('()', $fields);
					}

					$fields   = $this->columns->getElements();
					$elements = $this->set->getElements();

					foreach ($elements as $nameValue)
					{
						$setArray = explode(' = ', $nameValue, 2);

						if ($setArray[0][0] === '`')
						{
							// Unquote column name
							$setArray[0] = substr($setArray[0], 1, -1);
						}

						$fields[$setArray[0]] = $setArray[1];
					}

					$select = new JDatabaseQuerySqlite($this->db);
					$select->select(array_values($fields))
						->from($table);

					$select->join  = $this->join;
					$select->where = $this->where;

					return 'INSERT OR REPLACE INTO ' . $tableName
						. ' (' . implode(',', array_keys($fields)) . ')'
						. (string) $select;
				}
		}

		return parent::__toString();
	}

	/**
	 * Return the number of the current row.
	 *
	 * @param   string  $orderBy           An expression of ordering for window function.
	 * @param   string  $orderColumnAlias  An alias for new ordering column.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.7.0
	 * @throws  RuntimeException
	 */
	public function selectRowNumber($orderBy, $orderColumnAlias)
	{
		$this->validateRowNumber($orderBy, $orderColumnAlias);

		return $this;
	}
}
joomla/database/query/postgresql.php000064400000041042152177723700013666 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Query Building Class.
 *
 * @since       1.7.3
 * @deprecated  4.0  Use PDO PostgreSQL instead
 */
class JDatabaseQueryPostgresql extends JDatabaseQuery implements JDatabaseQueryLimitable
{
	/**
	 * @var    object  The FOR UPDATE element used in "FOR UPDATE"  lock
	 * @since  1.7.3
	 */
	protected $forUpdate = null;

	/**
	 * @var    object  The FOR SHARE element used in "FOR SHARE"  lock
	 * @since  1.7.3
	 */
	protected $forShare = null;

	/**
	 * @var    object  The NOWAIT element used in "FOR SHARE" and "FOR UPDATE" lock
	 * @since  1.7.3
	 */
	protected $noWait = null;

	/**
	 * @var    object  The LIMIT element
	 * @since  1.7.3
	 */
	protected $limit = null;

	/**
	 * @var    object  The OFFSET element
	 * @since  1.7.3
	 */
	protected $offset = null;

	/**
	 * @var    object  The RETURNING element of INSERT INTO
	 * @since  1.7.3
	 */
	protected $returning = null;

	/**
	 * Magic function to convert the query to a string, only for postgresql specific query
	 *
	 * @return  string	The completed query.
	 *
	 * @since   1.7.3
	 */
	public function __toString()
	{
		$query = '';

		switch ($this->type)
		{
			case 'select':
				if ($this->selectRowNumber && $this->selectRowNumber['native'] === false)
				{
					// Workaround for postgresql version less than 8.4.0
					try
					{
						$this->db->setQuery('CREATE TEMP SEQUENCE ROW_NUMBER');
						$this->db->execute();
					}
					catch (JDatabaseExceptionExecuting $e)
					{
						// Do nothing, sequence exists
					}

					$orderBy          = $this->selectRowNumber['orderBy'];
					$orderColumnAlias = $this->selectRowNumber['orderColumnAlias'];

					$columns = "nextval('ROW_NUMBER') - 1 AS $orderColumnAlias";

					if ($this->select === null)
					{
						$query = PHP_EOL . "SELECT 1"
							. (string) $this->from
							. (string) $this->where;
					}
					else
					{
						$tmpOffset    = $this->offset;
						$tmpLimit     = $this->limit;
						$this->offset = 0;
						$this->limit  = 0;
						$tmpOrder     = $this->order;
						$this->order  = null;
						$query        = parent::__toString();
						$columns      = "w.*, $columns";
						$this->order  = $tmpOrder;
						$this->offset = $tmpOffset;
						$this->limit  = $tmpLimit;
					}

					// Add support for second order by, offset and limit
					$query = PHP_EOL . "SELECT $columns FROM (" . $query . PHP_EOL . "ORDER BY $orderBy"
						. PHP_EOL . ") w,(SELECT setval('ROW_NUMBER', 1)) AS r";

					if ($this->order)
					{
						$query .= (string) $this->order;
					}

					break;
				}

				$query .= (string) $this->select;
				$query .= (string) $this->from;

				if ($this->join)
				{
					// Special case for joins
					foreach ($this->join as $join)
					{
						$query .= (string) $join;
					}
				}

				if ($this->where)
				{
					$query .= (string) $this->where;
				}

				if ($this->selectRowNumber)
				{
					if ($this->order)
					{
						$query .= (string) $this->order;
					}

					break;
				}

				if ($this->group)
				{
					$query .= (string) $this->group;
				}

				if ($this->having)
				{
					$query .= (string) $this->having;
				}

				if ($this->order)
				{
					$query .= (string) $this->order;
				}

				if ($this->forUpdate)
				{
					$query .= (string) $this->forUpdate;
				}
				else
				{
					if ($this->forShare)
					{
						$query .= (string) $this->forShare;
					}
				}

				if ($this->noWait)
				{
					$query .= (string) $this->noWait;
				}

				break;

			case 'update':
				$query .= (string) $this->update;
				$query .= (string) $this->set;

				if ($this->join)
				{
					$tmpFrom     = $this->from;
					$tmpWhere    = $this->where ? clone $this->where : null;
					$this->from  = null;

					// Workaround for special case of JOIN with UPDATE
					foreach ($this->join as $join)
					{
						$joinElem = $join->getElements();

						$joinArray = preg_split('/\sON\s/i', $joinElem[0]);

						if (count($joinArray) > 2)
						{
							$condition = array_pop($joinArray);
							$joinArray = array(implode(' ON ', $joinArray), $condition);
						}

						$this->from($joinArray[0]);

						if (isset($joinArray[1]))
						{
							$this->where($joinArray[1]);
						}
					}

					$query .= (string) $this->from;

					if ($this->where)
					{
						$query .= (string) $this->where;
					}

					$this->from  = $tmpFrom;
					$this->where = $tmpWhere;
				}
				elseif ($this->where)
				{
					$query .= (string) $this->where;
				}

				break;

			case 'insert':
				$query .= (string) $this->insert;

				if ($this->values)
				{
					if ($this->columns)
					{
						$query .= (string) $this->columns;
					}

					$elements = $this->values->getElements();

					if (!($elements[0] instanceof $this))
					{
						$query .= ' VALUES ';
					}

					$query .= (string) $this->values;

					if ($this->returning)
					{
						$query .= (string) $this->returning;
					}
				}

				break;

			default:
				$query = parent::__toString();
				break;
		}

		if ($this instanceof JDatabaseQueryLimitable)
		{
			$query = $this->processLimit($query, $this->limit, $this->offset);
		}

		return $query;
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  JDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case 'limit':
				$this->limit = null;
				break;

			case 'offset':
				$this->offset = null;
				break;

			case 'forUpdate':
				$this->forUpdate = null;
				break;

			case 'forShare':
				$this->forShare = null;
				break;

			case 'noWait':
				$this->noWait = null;
				break;

			case 'returning':
				$this->returning = null;
				break;

			case 'select':
			case 'update':
			case 'delete':
			case 'insert':
			case 'from':
			case 'join':
			case 'set':
			case 'where':
			case 'group':
			case 'having':
			case 'order':
			case 'columns':
			case 'values':
				parent::clear($clause);
				break;

			default:
				$this->type = null;
				$this->limit = null;
				$this->offset = null;
				$this->forUpdate = null;
				$this->forShare = null;
				$this->noWait = null;
				$this->returning = null;
				parent::clear($clause);
				break;
		}

		return $this;
	}

	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 * $query->select($query->castAsChar('a', 40));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @param   string  $len    The lenght of the char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   1.7.3
	 */
	public function castAsChar($value, $len = null)
	{
		if (!$len)
		{
			return $value . '::text';
		}
		else
		{
			return ' CAST(' . $value . ' AS CHAR(' . $len . '))';
		}
	}

	/**
	 * Concatenates an array of column names or values.
	 *
	 * Usage:
	 * $query->select($query->concatenate(array('a', 'b')));
	 *
	 * @param   array   $values     An array of values to concatenate.
	 * @param   string  $separator  As separator to place between each value.
	 *
	 * @return  string  The concatenated values.
	 *
	 * @since   1.7.3
	 */
	public function concatenate($values, $separator = null)
	{
		if ($separator)
		{
			return implode(' || ' . $this->quote($separator) . ' || ', $values);
		}
		else
		{
			return implode(' || ', $values);
		}
	}

	/**
	 * Gets the current date and time.
	 *
	 * @return  string  Return string used in query to obtain
	 *
	 * @since   1.7.3
	 */
	public function currentTimestamp()
	{
		return 'NOW()';
	}

	/**
	 * Sets the FOR UPDATE lock on select's output row
	 *
	 * @param   string  $table_name  The table to lock
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to ',' .
	 *
	 * @return  JDatabaseQueryPostgresql  FOR UPDATE query element
	 *
	 * @since   1.7.3
	 */
	public function forUpdate($table_name, $glue = ',')
	{
		$this->type = 'forUpdate';

		if (is_null($this->forUpdate))
		{
			$glue            = strtoupper($glue);
			$this->forUpdate = new JDatabaseQueryElement('FOR UPDATE', 'OF ' . $table_name, "$glue ");
		}
		else
		{
			$this->forUpdate->append($table_name);
		}

		return $this;
	}

	/**
	 * Sets the FOR SHARE lock on select's output row
	 *
	 * @param   string  $table_name  The table to lock
	 * @param   string  $glue        The glue by which to join the conditions. Defaults to ',' .
	 *
	 * @return  JDatabaseQueryPostgresql  FOR SHARE query element
	 *
	 * @since   1.7.3
	 */
	public function forShare($table_name, $glue = ',')
	{
		$this->type = 'forShare';

		if (is_null($this->forShare))
		{
			$glue           = strtoupper($glue);
			$this->forShare = new JDatabaseQueryElement('FOR SHARE', 'OF ' . $table_name, "$glue ");
		}
		else
		{
			$this->forShare->append($table_name);
		}

		return $this;
	}

	/**
	 * Used to get a string to extract year from date column.
	 *
	 * Usage:
	 * $query->select($query->year($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing year to be extracted.
	 *
	 * @return  string  Returns string to extract year from a date.
	 *
	 * @since   3.0.0
	 */
	public function year($date)
	{
		return 'EXTRACT (YEAR FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract month from date column.
	 *
	 * Usage:
	 * $query->select($query->month($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing month to be extracted.
	 *
	 * @return  string  Returns string to extract month from a date.
	 *
	 * @since   3.0.0
	 */
	public function month($date)
	{
		return 'EXTRACT (MONTH FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract day from date column.
	 *
	 * Usage:
	 * $query->select($query->day($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing day to be extracted.
	 *
	 * @return  string  Returns string to extract day from a date.
	 *
	 * @since   3.0.0
	 */
	public function day($date)
	{
		return 'EXTRACT (DAY FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract hour from date column.
	 *
	 * Usage:
	 * $query->select($query->hour($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing hour to be extracted.
	 *
	 * @return  string  Returns string to extract hour from a date.
	 *
	 * @since   3.0.0
	 */
	public function hour($date)
	{
		return 'EXTRACT (HOUR FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract minute from date column.
	 *
	 * Usage:
	 * $query->select($query->minute($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing minute to be extracted.
	 *
	 * @return  string  Returns string to extract minute from a date.
	 *
	 * @since   3.0.0
	 */
	public function minute($date)
	{
		return 'EXTRACT (MINUTE FROM ' . $date . ')';
	}

	/**
	 * Used to get a string to extract seconds from date column.
	 *
	 * Usage:
	 * $query->select($query->second($query->quoteName('dateColumn')));
	 *
	 * @param   string  $date  Date column containing second to be extracted.
	 *
	 * @return  string  Returns string to extract second from a date.
	 *
	 * @since   3.0.0
	 */
	public function second($date)
	{
		return 'EXTRACT (SECOND FROM ' . $date . ')';
	}

	/**
	 * Sets the NOWAIT lock on select's output row
	 *
	 * @return  JDatabaseQueryPostgresql  NO WAIT query element
	 *
	 * @since   1.7.3
	 */
	public function noWait ()
	{
		$this->type = 'noWait';

		if (is_null($this->noWait))
		{
			$this->noWait = new JDatabaseQueryElement('NOWAIT', null);
		}

		return $this;
	}

	/**
	 * Set the LIMIT clause to the query
	 *
	 * @param   integer  $limit  An int of how many row will be returned
	 *
	 * @return  JDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function limit($limit = 0)
	{
		if (is_null($this->limit))
		{
			$this->limit = new JDatabaseQueryElement('LIMIT', (int) $limit);
		}

		return $this;
	}

	/**
	 * Set the OFFSET clause to the query
	 *
	 * @param   integer  $offset  An int for skipping row
	 *
	 * @return  JDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function offset($offset = 0)
	{
		if (is_null($this->offset))
		{
			$this->offset = new JDatabaseQueryElement('OFFSET', (int) $offset);
		}

		return $this;
	}

	/**
	 * Add the RETURNING element to INSERT INTO statement.
	 *
	 * @param   mixed  $pkCol  The name of the primary key column.
	 *
	 * @return  JDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   1.7.3
	 */
	public function returning($pkCol)
	{
		if (is_null($this->returning))
		{
			$this->returning = new JDatabaseQueryElement('RETURNING', $pkCol);
		}

		return $this;
	}

	/**
	 * Sets the offset and limit for the result set, if the database driver supports it.
	 *
	 * Usage:
	 * $query->setLimit(100, 0); (retrieve 100 rows, starting at first record)
	 * $query->setLimit(50, 50); (retrieve 50 rows, starting at 50th record)
	 *
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  JDatabaseQueryPostgresql  Returns this object to allow chaining.
	 *
	 * @since   3.0.0
	 */
	public function setLimit($limit = 0, $offset = 0)
	{
		$this->limit  = (int) $limit;
		$this->offset = (int) $offset;

		return $this;
	}

	/**
	 * Method to modify a query already in string format with the needed
	 * additions to make the query limited to a particular number of
	 * results, or start at a particular offset.
	 *
	 * @param   string   $query   The query in string format
	 * @param   integer  $limit   The limit for the result set
	 * @param   integer  $offset  The offset for the result set
	 *
	 * @return  string
	 *
	 * @since   3.0.0
	 */
	public function processLimit($query, $limit, $offset = 0)
	{
		if ($limit > 0)
		{
			$query .= ' LIMIT ' . $limit;
		}

		if ($offset > 0)
		{
			$query .= ' OFFSET ' . $offset;
		}

		return $query;
	}

	/**
	 * Add to the current date and time in Postgresql.
	 * Usage:
	 * $query->select($query->dateAdd());
	 * Prefixing the interval with a - (negative sign) will cause subtraction to be used.
	 *
	 * @param   string  $date      The db quoted string representation of the date to add to
	 * @param   string  $interval  The string representation of the appropriate number of units
	 * @param   string  $datePart  The part of the date to perform the addition on
	 *
	 * @return  string  The string with the appropriate sql for addition of dates
	 *
	 * @since   3.2.0
	 * @note    Not all drivers support all units. Check appropriate references
	 * @link    http://www.postgresql.org/docs/9.0/static/functions-datetime.html.
	 */
	public function dateAdd($date, $interval, $datePart)
	{
		if (substr($interval, 0, 1) != '-')
		{
			return "timestamp " . $date . " + interval '" . $interval . " " . $datePart . "'";
		}
		else
		{
			return "timestamp " . $date . " - interval '" . ltrim($interval, '-') . " " . $datePart . "'";
		}
	}

	/**
	 * Return correct regexp operator for Postgresql.
	 *
	 * Ensure that the regexp operator is Postgresql compatible.
	 *
	 * Usage:
	 * $query->where('field ' . $query->regexp($search));
	 *
	 * @param   string  $value  The regex pattern.
	 *
	 * @return  string  Returns the regex operator.
	 *
	 * @since   1.7.3
	 */
	public function regexp($value)
	{
		return ' ~* ' . $value;
	}

	/**
	 * Return correct rand() function for Postgresql.
	 *
	 * Ensure that the rand() function is Postgresql compatible.
	 *
	 * Usage:
	 * $query->Rand();
	 *
	 * @return  string  The correct rand function.
	 *
	 * @since   3.5
	 */
	public function Rand()
	{
		return ' RANDOM() ';
	}

	/**
	 * Return the number of the current row.
	 *
	 * @param   string  $orderBy           An expression of ordering for window function.
	 * @param   string  $orderColumnAlias  An alias for new ordering column.
	 *
	 * @return  JDatabaseQuery  Returns this object to allow chaining.
	 *
	 * @since   3.7.0
	 * @throws  RuntimeException
	 */
	public function selectRowNumber($orderBy, $orderColumnAlias)
	{
		$this->validateRowNumber($orderBy, $orderColumnAlias);

		if (version_compare($this->db->getVersion(), '8.4.0') >= 0)
		{
			$this->selectRowNumber['native'] = true;
			$this->select("ROW_NUMBER() OVER (ORDER BY $orderBy) AS $orderColumnAlias");
		}
		else
		{
			$this->selectRowNumber['native'] = false;
		}

		return $this;
	}
}
joomla/database/query/pdo.php000064400000001706152177723700012250 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PDO Query Building Class.
 *
 * @since  3.0.0
 */
class JDatabaseQueryPdo extends JDatabaseQuery
{
	/**
	 * Casts a value to a char.
	 *
	 * Ensure that the value is properly quoted before passing to the method.
	 *
	 * Usage:
	 * $query->select($query->castAsChar('a'));
	 * $query->select($query->castAsChar('a', 40));
	 *
	 * @param   string  $value  The value to cast as a char.
	 *
	 * @param   string  $len    The lenght of the char.
	 *
	 * @return  string  Returns the cast value.
	 *
	 * @since   1.7.0
	 */
	public function castAsChar($value, $len = null)
	{
		if (!$len)
		{
			return $value;
		}
		else
		{
			return ' CAST(' . $value . ' AS CHAR(' . $len . '))';
		}
	}
}
joomla/database/query/pgsql.php000064400000006175152177723700012621 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PDO PostgreSQL Query Building Class.
 *
 * @since  3.9.0
 */
class JDatabaseQueryPgsql extends JDatabaseQueryPostgresql implements JDatabaseQueryPreparable
{
	/**
	 * Holds key / value pair of bound objects.
	 *
	 * @var    mixed
	 * @since  3.9.0
	 */
	protected $bounded = array();

	/**
	 * Method to add a variable to an internal array that will be bound to a prepared SQL statement before query execution. Also
	 * removes a variable that has been bounded from the internal bounded array when the passed in value is null.
	 *
	 * @param   string|integer  $key            The key that will be used in your SQL query to reference the value. Usually of
	 *                                          the form ':key', but can also be an integer.
	 * @param   mixed           &$value         The value that will be bound. The value is passed by reference to support output
	 *                                          parameters such as those possible with stored procedures.
	 * @param   integer         $dataType       Constant corresponding to a SQL datatype.
	 * @param   integer         $length         The length of the variable. Usually required for OUTPUT parameters.
	 * @param   array           $driverOptions  Optional driver options to be used.
	 *
	 * @return  JDatabaseQueryPgsql
	 *
	 * @since   3.9.0
	 */
	public function bind($key = null, &$value = null, $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = array())
	{
		// Case 1: Empty Key (reset $bounded array)
		if (empty($key))
		{
			$this->bounded = array();

			return $this;
		}

		// Case 2: Key Provided, null value (unset key from $bounded array)
		if (is_null($value))
		{
			if (isset($this->bounded[$key]))
			{
				unset($this->bounded[$key]);
			}

			return $this;
		}

		$obj = new stdClass;

		$obj->value = &$value;
		$obj->dataType = $dataType;
		$obj->length = $length;
		$obj->driverOptions = $driverOptions;

		// Case 3: Simply add the Key/Value into the bounded array
		$this->bounded[$key] = $obj;

		return $this;
	}

	/**
	 * Retrieves the bound parameters array when key is null and returns it by reference. If a key is provided then that item is
	 * returned.
	 *
	 * @param   mixed  $key  The bounded variable key to retrieve.
	 *
	 * @return  mixed
	 *
	 * @since   3.9.0
	 */
	public function &getBounded($key = null)
	{
		if (empty($key))
		{
			return $this->bounded;
		}

		if (isset($this->bounded[$key]))
		{
			return $this->bounded[$key];
		}
	}

	/**
	 * Clear data from the query or a specific clause of the query.
	 *
	 * @param   string  $clause  Optionally, the name of the clause to clear, or nothing to clear the whole query.
	 *
	 * @return  JDatabaseQueryPgsql  Returns this object to allow chaining.
	 *
	 * @since   3.9.0
	 */
	public function clear($clause = null)
	{
		switch ($clause)
		{
			case null:
				$this->bounded = array();
				break;
		}

		return parent::clear($clause);
	}
}
joomla/database/driver/mysql.php000064400000033752152177723700012767 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL database driver
 *
 * @link        https://dev.mysql.com/doc/
 * @since       3.0.0
 * @deprecated  4.0  Use MySQLi or PDO MySQL instead
 */
class JDatabaseDriverMysql extends JDatabaseDriverMysqli
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'mysql';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  Array of database options with keys: host, user, password, database, select.
	 *
	 * @since   3.0.0
	 */
	public function __construct($options)
	{
		// PHP's `mysql` extension is not present in PHP 7, block instantiation in this environment
		if (PHP_MAJOR_VERSION >= 7)
		{
			throw new JDatabaseExceptionUnsupported(
				'This driver is unsupported in PHP 7, please use the MySQLi or PDO MySQL driver instead.'
			);
		}

		// Get some basic values from the options.
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the MySQL extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new JDatabaseExceptionUnsupported('Make sure the MySQL extension for PHP is installed and enabled.');
		}

		// Attempt to connect to the server.
		if (!($this->connection = @ mysql_connect($this->options['host'], $this->options['user'], $this->options['password'], true)))
		{
			throw new JDatabaseExceptionConnecting('Could not connect to MySQL server.');
		}

		// Set sql_mode to non_strict mode
		mysql_query("SET @@SESSION.sql_mode = '';", $this->connection);

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Pre-populate the UTF-8 Multibyte compatibility flag based on server version
		$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();

		// Set the character set (needed for MySQL 4.1.2+).
		$this->utf = $this->setUtf();

		// Disable query cache and turn profiling ON in debug mode.
		if ($this->debug)
		{
			mysql_query('SET query_cache_type = 0;', $this->connection);

			if ($this->hasProfiling())
			{
				mysql_query('SET profiling_history_size = 100, profiling = 1;', $this->connection);
			}
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			mysql_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$this->connect();

		$result = mysql_real_escape_string($text, $this->getConnection());

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return PHP_MAJOR_VERSION < 7 && function_exists('mysql_connect');
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   3.0.0
	 */
	public function connected()
	{
		if (is_resource($this->connection))
		{
			return @mysql_ping($this->connection);
		}

		return false;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   3.0.0
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return mysql_affected_rows($this->connection);
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   3.0.0
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		return mysql_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();

		return mysql_get_server_info($this->connection);
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   3.0.0
	 */
	public function insertid()
	{
		$this->connect();

		return mysql_insert_id($this->connection);
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		if (!is_resource($this->connection))
		{
			JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			JLog::add($query, JLog::DEBUG, 'databasequery');

			$this->timings[] = microtime(true);
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysql_query($query, $this->connection);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$this->errorNum = $this->getErrorNumber();
			$this->errorMsg = $this->getErrorMessage();

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage();

					// Throw the normal query exception.
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

					throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Throw the normal query exception.
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

				throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!mysql_select_db($database, $this->connection))
		{
			throw new JDatabaseExceptionConnecting('Could not connect to MySQL database.');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		// If UTF is not supported return false immediately
		if (!$this->utf)
		{
			return false;
		}

		// Make sure we're connected to the server
		$this->connect();

		// Which charset should I use, plain utf8 or multibyte utf8mb4?
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		$result = @mysql_set_charset($charset, $this->connection);

		/**
		 * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise.
		 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
		 * masks the server version and reports only its own we can not be sure if the server actually does support
		 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
		 * catch the error and determine that utf8mb4 is not supported!
		 */
		if (!$result && $this->utf8mb4)
		{
			$this->utf8mb4 = false;
			$result = @mysql_set_charset('utf8', $this->connection);
		}

		return $result;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchArray($cursor = null)
	{
		return mysql_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchAssoc($cursor = null)
	{
		return mysql_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysql_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult($cursor = null)
	{
		mysql_free_result($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Internal function to check if profiling is available
	 *
	 * @return  boolean
	 *
	 * @since   3.1.3
	 */
	private function hasProfiling()
	{
		try
		{
			$res = mysql_query("SHOW VARIABLES LIKE 'have_profiling'", $this->connection);
			$row = mysql_fetch_assoc($res);

			return isset($row);
		}
		catch (Exception $e)
		{
			return false;
		}
	}

	/**
	 * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * @return  boolean
	 *
	 * @since   CMS 3.5.0
	 */
	private function serverClaimsUtf8mb4Support()
	{
		$client_version = mysql_get_client_info();
		$server_version = $this->getVersion();

		if (version_compare($server_version, '5.5.3', '<'))
		{
			return false;
		}
		else
		{
			if (strpos($client_version, 'mysqlnd') !== false)
			{
				$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

				return version_compare($client_version, '5.0.9', '>=');
			}
			else
			{
				return version_compare($client_version, '5.5.3', '>=');
			}
		}
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) mysql_errno($this->connection);
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage()
	{
		$errorMessage = (string) mysql_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
		}

		return $errorMessage;
	}
}
joomla/database/driver/sqlsrv.php000064400000065312152177723700013151 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQL Server database driver
 *
 * @link   https://msdn.microsoft.com/en-us/library/cc296152(SQL.90).aspx
 * @since  3.0.0
 */
class JDatabaseDriverSqlsrv extends JDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'sqlsrv';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mssql';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nameQuote = '[]';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nullDate = '1900-01-01 00:00:00';

	/**
	 * @var    string  The minimum supported database version.
	 * @since  3.0.0
	 */
	protected static $dbMinimum = '10.50.1600.1';

	/**
	 * Test to see if the SQLSRV connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('sqlsrv_connect');
	}

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   3.0.0
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select'] = (isset($options['select'])) ? (bool) $options['select'] : true;

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Build the connection configuration array.
		$config = array(
			'Database' => $this->options['database'],
			'uid' => $this->options['user'],
			'pwd' => $this->options['password'],
			'CharacterSet' => 'UTF-8',
			'ReturnDatesAsStrings' => true,
		);

		// Make sure the SQLSRV extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new JDatabaseExceptionUnsupported('The sqlsrv extension for PHP is not installed or enabled..');
		}

		// Attempt to connect to the server.
		if (!($this->connection = @ sqlsrv_connect($this->options['host'], $config)))
		{
			throw new JDatabaseExceptionConnecting('Database sqlsrv_connect failed, ' . print_r(sqlsrv_errors(), true));
		}

		// Make sure that DB warnings are not returned as errors.
		sqlsrv_configure('WarningsReturnAsErrors', 0);

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Set charactersets.
		$this->utf = $this->setUtf();

		// Set QUOTED_IDENTIFIER always ON
		sqlsrv_query($this->connection, 'SET QUOTED_IDENTIFIER ON');
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			sqlsrv_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Get table constraints
	 *
	 * @param   string  $tableName  The name of the database table.
	 *
	 * @return  array  Any constraints available for the table.
	 *
	 * @since   3.0.0
	 */
	protected function getTableConstraints($tableName)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery(
			'SELECT CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE TABLE_NAME = ' . $query->quote($tableName)
		);

		return $this->loadColumn();
	}

	/**
	 * Rename constraints.
	 *
	 * @param   array   $constraints  Array(strings) of table constraints
	 * @param   string  $prefix       A string
	 * @param   string  $backup       A string
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function renameConstraints($constraints = array(), $prefix = null, $backup = null)
	{
		$this->connect();

		foreach ($constraints as $constraint)
		{
			$this->setQuery('sp_rename ' . $constraint . ',' . str_replace($prefix, $backup, $constraint));
			$this->execute();
		}
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * The escaping for MSSQL isn't handled in the driver though that would be nice.  Because of this we need
	 * to handle the escaping ourselves.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$result = str_replace("'", "''", $text);

		// SQL Server does not accept NULL byte in query string
		$result = str_replace("\0", "' + CHAR(0) + N'", $result);

		// Fix for SQL Server escape sequence, see https://support.microsoft.com/en-us/kb/164291
		$result = str_replace(
			array("\\\n",     "\\\r",     "\\\\\r\r\n"),
			array("\\\\\n\n", "\\\\\r\r", "\\\\\r\n\r\n"),
			$result
		);

		if ($extra)
		{
			// Escape special chars
			$result = str_replace(
				array('[',   '_',   '%'),
				array('[[]', '[_]', '[%]'),
				$result
			);
		}

		return $result;
	}

	/**
	 * Quotes and optionally escapes a string to database requirements for use in database queries.
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True (default) to escape the string, false to leave it unchanged.
	 *
	 * @return  string  The quoted input string.
	 *
	 * @note    Accepting an array of strings was added in 3.1.4.
	 * @since   1.7.0
	 */
	public function quote($text, $escape = true)
	{
		if (is_array($text))
		{
			return parent::quote($text, $escape);
		}

		// To support unicode on MSSQL we have to add prefix N
		return 'N\'' . ($escape ? $this->escape($text) : $text) . '\'';
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   3.0.0
	 */
	public function connected()
	{
		// TODO: Run a blank query here
		return true;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		if ($ifExists)
		{
			$this->setQuery(
				'IF EXISTS(SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ' . $query->quote($tableName) . ') DROP TABLE ' . $tableName
			);
		}
		else
		{
			$this->setQuery('DROP TABLE ' . $tableName);
		}

		$this->execute();

		return $this;
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   3.0.0
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return sqlsrv_rows_affected($this->cursor);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   3.0.0
	 */
	public function getCollation()
	{
		// TODO: Not fake this
		return 'MSSQL UTF-8 (UCS2)';
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		// TODO: Not fake this
		return 'MSSQL UTF-8 (UCS2)';
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   3.0.0
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		return sqlsrv_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   mixed    $table     A table name
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$result = array();

		$table_temp = $this->replacePrefix((string) $table);

		// Set the query to get the table fields statement.
		$this->setQuery(
			'SELECT column_name as Field, data_type as Type, is_nullable as \'Null\', column_default as \'Default\'' .
			' FROM information_schema.columns WHERE table_name = ' . $this->quote($table_temp)
		);
		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$field->Default = preg_replace("/(^(\(\(|\('|\(N'|\()|(('\)|(?<!\()\)\)|\))$))/i", '', $field->Default);
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * This is unsupported by MSSQL.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		return '';
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// TODO To implement.
		return array();
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SELECT name FROM ' . $this->getDatabase() . '.sys.Tables WHERE type = \'U\';');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();

		$version = sqlsrv_server_info($this->connection);

		return $version['SQLServerVersion'];
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$fields = array();
		$values = array();
		$statement = 'INSERT INTO ' . $this->quoteName($table) . ' (%s) VALUES (%s)';

		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			if (!$this->checkFieldExists($table, $k))
			{
				continue;
			}

			if ($k[0] == '_')
			{
				// Internal field
				continue;
			}

			if ($k == $key && $key == 0)
			{
				continue;
			}

			$fields[] = $this->quoteName($k);
			$values[] = $this->Quote($v);
		}
		// Set the query and execute the insert.
		$this->setQuery(sprintf($statement, implode(',', $fields), implode(',', $values)));

		if (!$this->execute())
		{
			return false;
		}

		$id = $this->insertid();

		if ($key && $id)
		{
			$object->$key = $id;
		}

		return true;
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   3.0.0
	 */
	public function insertid()
	{
		$this->connect();

		// TODO: SELECT IDENTITY
		$this->setQuery('SELECT @@IDENTITY');

		return (int) $this->loadResult();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 * @throws  Exception
	 */
	public function execute()
	{
		$this->connect();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query = $this->limit($query, $this->limit, $this->offset);
		}

		if (!is_resource($this->connection))
		{
			JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			JLog::add($query, JLog::DEBUG, 'databasequery');

			$this->timings[] = microtime(true);
		}

		// SQLSrv_num_rows requires a static or keyset cursor.
		if (strncmp(ltrim(strtoupper($query)), 'SELECT', strlen('SELECT')) == 0)
		{
			$array = array('Scrollable' => SQLSRV_CURSOR_KEYSET);
		}
		else
		{
			$array = array();
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @sqlsrv_query($this->connection, $query, array(), $array);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage();

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage();

					// Throw the normal query exception.
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

					throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

				throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   3.0.0
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$query = trim($query);

		if (strpos($query, "'"))
		{
			$parts = explode("'", $query);

			for ($nIndex = 0, $size = count($parts); $nIndex < $size; $nIndex = $nIndex + 2)
			{
				if (strpos($parts[$nIndex], $prefix) !== false)
				{
					$parts[$nIndex] = str_replace($prefix, $this->tablePrefix, $parts[$nIndex]);
				}
			}

			$query = implode("'", $parts);
		}
		else
		{
			$query = str_replace($prefix, $this->tablePrefix, $query);
		}

		return $query;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!sqlsrv_query($this->connection, 'USE ' . $database, null, array('scrollable' => SQLSRV_CURSOR_STATIC)))
		{
			throw new JDatabaseExceptionConnecting('Could not connect to SQL Server database.');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT TRANSACTION')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK TRANSACTION')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TRANSACTION ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('BEGIN TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('BEGIN TRANSACTION ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchArray($cursor = null)
	{
		return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_NUMERIC);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchAssoc($cursor = null)
	{
		return sqlsrv_fetch_array($cursor ? $cursor : $this->cursor, SQLSRV_FETCH_ASSOC);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		// Class has to be loaded for sqlsrv on windows platform
		class_exists($class);

		return sqlsrv_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult($cursor = null)
	{
		sqlsrv_free_stmt($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to check and see if a field exists in a table.
	 *
	 * @param   string  $table  The table in which to verify the field.
	 * @param   string  $field  The field to verify.
	 *
	 * @return  boolean  True if the field exists in the table.
	 *
	 * @since   3.0.0
	 */
	protected function checkFieldExists($table, $field)
	{
		$this->connect();

		$table = $this->replacePrefix((string) $table);
		$query = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' AND COLUMN_NAME = '$field' ORDER BY ORDINAL_POSITION";
		$this->setQuery($query);

		if ($this->loadResult())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to wrap an SQL statement to provide a LIMIT and OFFSET behavior for scrolling through a result set.
	 *
	 * @param   string   $query   The SQL statement to process.
	 * @param   integer  $limit   The maximum affected rows to set.
	 * @param   integer  $offset  The affected row offset to set.
	 *
	 * @return  string   The processed SQL statement.
	 *
	 * @since   3.0.0
	 */
	protected function limit($query, $limit, $offset)
	{
		if ($limit)
		{
			$total = $offset + $limit;

			$position = stripos($query, 'SELECT');
			$distinct = stripos($query, 'SELECT DISTINCT');

			if ($position === $distinct)
			{
				$query = substr_replace($query, 'SELECT DISTINCT TOP ' . (int) $total, $position, 15);
			}
			else
			{
				$query = substr_replace($query, 'SELECT TOP ' . (int) $total, $position, 6);
			}
		}

		if (!$offset)
		{
			return $query;
		}

		return PHP_EOL
			. 'SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY (SELECT 0)) AS RowNumber FROM ('
			. $query
			. PHP_EOL . ') AS A) AS A WHERE RowNumber > ' . (int) $offset;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  JDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$constraints = array();

		if (!is_null($prefix) && !is_null($backup))
		{
			$constraints = $this->getTableConstraints($oldTable);
		}

		if (!empty($constraints))
		{
			$this->renameConstraints($constraints, $prefix, $backup);
		}

		$this->setQuery("sp_rename '" . $oldTable . "', '" . $newTable . "'");

		return $this->execute();
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to lock.
	 *
	 * @return  JDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function lockTable($tableName)
	{
		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriverSqlsrv  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		return $this;
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		$errors = sqlsrv_errors();

		return $errors[0]['code'];
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage()
	{
		$errors       = sqlsrv_errors();
		$errorMessage = (string) $errors[0]['message'];

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
		}

		return $errorMessage;
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		return array();
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}
}
joomla/database/driver/oracle.php000064400000036625152177723700013071 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Oracle database driver
 *
 * @link   https://www.php.net/pdo
 * @since  3.0.0
 */
class JDatabaseDriverOracle extends JDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'oracle';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'oracle';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nameQuote = '"';

	/**
	 * Returns the current dateformat
	 *
	 * @var   string
	 * @since 3.0.0
	 */
	protected $dateformat;

	/**
	 * Returns the current character set
	 *
	 * @var   string
	 * @since 3.0.0
	 */
	protected $charset;

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   3.0.0
	 */
	public function __construct($options)
	{
		$options['driver'] = 'oci';
		$options['charset']    = (isset($options['charset'])) ? $options['charset']   : 'AL32UTF8';
		$options['dateformat'] = (isset($options['dateformat'])) ? $options['dateformat'] : 'RRRR-MM-DD HH24:MI:SS';

		$this->charset = $options['charset'];
		$this->dateformat = $options['dateformat'];

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		parent::connect();

		if (isset($this->options['schema']))
		{
			$this->setQuery('ALTER SESSION SET CURRENT_SCHEMA = ' . $this->quoteName($this->options['schema']))->execute();
		}

		$this->setDateFormat($this->dateformat);
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		// Close the connection.
		$this->freeResult();

		$this->connection = null;
	}

	/**
	 * Drops a table from the database.
	 *
	 * Note: The IF EXISTS flag is unused in the Oracle driver.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true)
			->setQuery('DROP TABLE :tableName');
		$query->bind(':tableName', $tableName);

		$this->setQuery($query);

		$this->execute();

		return $this;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   3.0.0
	 */
	public function getCollation()
	{
		return $this->charset;
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return $this->charset;
	}

	/**
	 * Get a query to run and verify the database is operational.
	 *
	 * @return  string  The query to check the health of the DB.
	 *
	 * @since   3.0.1
	 */
	public function getConnectedQuery()
	{
		return 'SELECT 1 FROM dual';
	}

	/**
	 * Returns the current date format
	 * This method should be useful in the case that
	 * somebody actually wants to use a different
	 * date format and needs to check what the current
	 * one is to see if it needs to be changed.
	 *
	 * @return string The current date format
	 *
	 * @since 3.0.0
	 */
	public function getDateFormat()
	{
		return $this->dateformat;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * Note: You must have the correct privileges before this method
	 * will return usable results!
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		$result = array();
		$query = $this->getQuery(true)
			->select('dbms_metadata.get_ddl(:type, :tableName)')
			->from('dual')
			->bind(':type', 'TABLE');

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			$query->bind(':tableName', $table);
			$this->setQuery($query);
			$statement = (string) $this->loadResult();
			$result[$table] = $statement;
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$columns = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);

		$query->select('*');
		$query->from('ALL_TAB_COLUMNS');
		$query->where('table_name = :tableName');

		$prefixedTable = str_replace('#__', strtoupper($this->tablePrefix), $table);
		$query->bind(':tableName', $prefixedTable);
		$this->setQuery($query);
		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$columns[$field->COLUMN_NAME] = $field->DATA_TYPE;
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				$columns[$field->COLUMN_NAME] = $field;
				$columns[$field->COLUMN_NAME]->Default = null;
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $columns;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);
		$query->select('*')
			->from('ALL_CONSTRAINTS')
			->where('table_name = :tableName')
			->bind(':tableName', $table);

		$this->setQuery($query);
		$keys = $this->loadObjectList();

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database (schema).
	 *
	 * @param   string   $databaseName         The database (schema) name
	 * @param   boolean  $includeDatabaseName  Whether to include the schema name in the results
	 *
	 * @return  array    An array of all the tables in the database.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableList($databaseName = null, $includeDatabaseName = false)
	{
		$this->connect();

		$query = $this->getQuery(true);

		if ($includeDatabaseName)
		{
			$query->select('owner, table_name');
		}
		else
		{
			$query->select('table_name');
		}

		$query->from('all_tables');

		if ($databaseName)
		{
			$query->where('owner = :database')
				->bind(':database', $databaseName);
		}

		$query->order('table_name');

		$this->setQuery($query);

		if ($includeDatabaseName)
		{
			$tables = $this->loadAssocList();
		}
		else
		{
			$tables = $this->loadColumn();
		}

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();

		$this->setQuery("select value from nls_database_parameters where parameter = 'NLS_RDBMS_VERSION'");

		return $this->loadResult();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Sets the Oracle Date Format for the session
	 * Default date format for Oracle is = DD-MON-RR
	 * The default date format for this driver is:
	 * 'RRRR-MM-DD HH24:MI:SS' since it is the format
	 * that matches the MySQL one used within most Joomla
	 * tables.
	 *
	 * @param   string  $dateFormat  Oracle Date Format String
	 *
	 * @return boolean
	 *
	 * @since  3.0.0
	 */
	public function setDateFormat($dateFormat = 'DD-MON-RR')
	{
		$this->connect();

		$this->setQuery("ALTER SESSION SET NLS_DATE_FORMAT = '$dateFormat'");

		if (!$this->execute())
		{
			return false;
		}

		$this->setQuery("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = '$dateFormat'");

		if (!$this->execute())
		{
			return false;
		}

		$this->dateformat = $dateFormat;

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * Returns false automatically for the Oracle driver since
	 * you can only set the character set when the connection
	 * is created.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLE ' . $this->quoteName($table) . ' IN EXCLUSIVE MODE')->execute();

		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by Oracle.
	 * @param   string  $prefix    Not used by Oracle.
	 *
	 * @return  JDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME ' . $oldTable . ' TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriverOracle  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('COMMIT')->execute();

		return $this;
	}

	/**
	 * Test to see if the PDO ODBC connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('oci', PDO::getAvailableDrivers());
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   1.7.0
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$startPos = 0;
		$quoteChar = "'";
		$literal = '';

		$query = trim($query);
		$n = strlen($query);

		while ($startPos < $n)
		{
			$ip = strpos($query, $prefix, $startPos);

			if ($ip === false)
			{
				break;
			}

			$j = strpos($query, "'", $startPos);

			if ($j === false)
			{
				$j = $n;
			}

			$literal .= str_replace($prefix, $this->tablePrefix, substr($query, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k = strpos($query, $quoteChar, $j);
				$escaped = false;

				if ($k === false)
				{
					break;
				}

				$l = $k - 1;

				while ($l >= 0 && $query{$l} == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}

				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}

				break;
			}

			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}

			$literal .= substr($query, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}

		if ($startPos < $n)
		{
			$literal .= substr($query, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			return parent::transactionStart($asSavepoint);
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		return array();
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}
}
joomla/database/driver/sqlazure.php000064400000001122152177723700013452 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQL Server database driver
 *
 * @link   https://azure.microsoft.com/en-us/documentation/services/sql-database/
 * @since  3.0.0
 */
class JDatabaseDriverSqlazure extends JDatabaseDriverSqlsrv
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'sqlazure';
}
joomla/database/driver/mysqli.php000064400000060510152177723700013130 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQLi database driver
 *
 * @link   https://www.php.net/manual/en/book.mysqli.php
 * @since  3.0.0
 */
class JDatabaseDriverMysqli extends JDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'mysqli';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mysql';

	/**
	 * @var    mysqli  The database connection resource.
	 * @since  1.7.0
	 */
	protected $connection;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.0.1
	 */
	protected $nameQuote = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.0.1
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * @var    string  The minimum supported database version.
	 * @since  3.0.1
	 */
	protected static $dbMinimum = '5.0.4';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   3.0.0
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['host']     = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user']     = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['select']   = (isset($options['select'])) ? (bool) $options['select'] : true;
		$options['port']     = (isset($options['port'])) ? (int) $options['port'] : null;
		$options['socket']   = (isset($options['socket'])) ? $options['socket'] : null;

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		/*
		 * Unlike mysql_connect(), mysqli_connect() takes the port and socket as separate arguments. Therefore, we
		 * have to extract them from the host string.
		 */
		$port = isset($this->options['port']) ? $this->options['port'] : 3306;
		$regex = '/^(?P<host>((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))(:(?P<port>.+))?$/';

		if (preg_match($regex, $this->options['host'], $matches))
		{
			// It's an IPv4 address with or without port
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^(?P<host>\[.*\])(:(?P<port>.+))?$/', $this->options['host'], $matches))
		{
			// We assume square-bracketed IPv6 address with or without port, e.g. [fe80:102::2%eth1]:3306
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^(?P<host>(\w+:\/{2,3})?[a-z0-9\.\-]+)(:(?P<port>[^:]+))?$/i', $this->options['host'], $matches))
		{
			// Named host (e.g example.com or localhost) with or without port
			$this->options['host'] = $matches['host'];

			if (!empty($matches['port']))
			{
				$port = $matches['port'];
			}
		}
		elseif (preg_match('/^:(?P<port>[^:]+)$/', $this->options['host'], $matches))
		{
			// Empty host, just port, e.g. ':3306'
			$this->options['host'] = 'localhost';
			$port = $matches['port'];
		}
		// ... else we assume normal (naked) IPv6 address, so host and port stay as they are or default

		// Get the port number or socket name
		if (is_numeric($port))
		{
			$this->options['port'] = (int) $port;
		}
		else
		{
			$this->options['socket'] = $port;
		}

		// Make sure the MySQLi extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new JDatabaseExceptionUnsupported('The MySQLi extension for PHP is not installed or enabled.');
		}

		$this->connection = @mysqli_connect(
			$this->options['host'], $this->options['user'], $this->options['password'], null, $this->options['port'], $this->options['socket']
		);

		// Attempt to connect to the server.
		if (!$this->connection)
		{
			throw new JDatabaseExceptionConnecting('Could not connect to MySQL server.');
		}

		// Set sql_mode to non_strict mode
		mysqli_query($this->connection, "SET @@SESSION.sql_mode = '';");

		// If auto-select is enabled select the given database.
		if ($this->options['select'] && !empty($this->options['database']))
		{
			$this->select($this->options['database']);
		}

		// Pre-populate the UTF-8 Multibyte compatibility flag based on server version
		$this->utf8mb4 = $this->serverClaimsUtf8mb4Support();

		// Set the character set (needed for MySQL 4.1.2+).
		$this->utf = $this->setUtf();

		// Disable query cache and turn profiling ON in debug mode.
		if ($this->debug)
		{
			mysqli_query($this->connection, 'SET query_cache_type = 0;');

			if ($this->hasProfiling())
			{
				mysqli_query($this->connection, 'SET profiling_history_size = 100, profiling = 1;');
			}
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		// Close the connection.
		if ($this->connection instanceof mysqli && $this->connection->stat() !== false)
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			mysqli_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$this->connect();

		$result = mysqli_real_escape_string($this->getConnection(), $text);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('mysqli_connect');
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   3.0.0
	 */
	public function connected()
	{
		if (is_object($this->connection))
		{
			return mysqli_ping($this->connection);
		}

		return false;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));

		$this->execute();

		return $this;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   3.0.0
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return mysqli_affected_rows($this->connection);
	}

	/**
	 * Method to get the database collation.
	 *
	 * @return  mixed  The collation in use by the database (string) or boolean false if not supported.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_database"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_connection"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   3.0.0
	 */
	public function getNumRows($cursor = null)
	{
		return mysqli_num_rows($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		$result = array();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			// Set the query to get the table CREATE statement.
			$this->setQuery('SHOW CREATE table ' . $this->quoteName($this->escape($table)));
			$row = $this->loadRow();

			// Populate the result array based on the create statements.
			$result[$table] = $row[1];
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		// Set the query to get the table fields statement.
		$this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($this->escape($table)));
		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// Get the details columns information.
		$this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));
		$keys = $this->loadObjectList();

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SHOW TABLES');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();

		return mysqli_get_server_info($this->connection);
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  mixed  The value of the auto-increment field from the last inserted row.
	 *                 If the value is greater than maximal int value, it will return a string.
	 *
	 * @since   3.0.0
	 */
	public function insertid()
	{
		$this->connect();

		return mysqli_insert_id($this->connection);
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();

		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->offset . ', ' . $this->limit;
		}

		if (!is_object($this->connection))
		{
			JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';
		$memoryBefore   = null;

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			JLog::add($query, JLog::DEBUG, 'databasequery');

			$this->timings[] = microtime(true);

			if (is_object($this->cursor))
			{
				// Avoid warning if result already freed by third-party library
				@$this->freeResult();
			}

			$memoryBefore = memory_get_usage();
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @mysqli_query($this->connection, $query);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}

			$this->callStacks[count($this->callStacks) - 1][0]['memory'] = array(
				$memoryBefore,
				memory_get_usage(),
				is_object($this->cursor) ? $this->getNumRows() : null,
			);
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$this->errorNum = $this->getErrorNumber();
			$this->errorMsg = $this->getErrorMessage();

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage();

					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

					throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

				throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
			}
		}

		return $this->cursor;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by MySQL.
	 * @param   string  $prefix    Not used by MySQL.
	 *
	 * @return  JDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME TABLE ' . $oldTable . ' TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		if (!$database)
		{
			return false;
		}

		if (!mysqli_select_db($this->connection, $database))
		{
			throw new JDatabaseExceptionConnecting('Could not connect to MySQL database.');
		}

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		// If UTF is not supported return false immediately
		if (!$this->utf)
		{
			return false;
		}

		// Make sure we're connected to the server
		$this->connect();

		// Which charset should I use, plain utf8 or multibyte utf8mb4?
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		$result = @$this->connection->set_charset($charset);

		/**
		 * If I could not set the utf8mb4 charset then the server doesn't support utf8mb4 despite claiming otherwise.
		 * This happens on old MySQL server versions (less than 5.5.3) using the mysqlnd PHP driver. Since mysqlnd
		 * masks the server version and reports only its own we can not be sure if the server actually does support
		 * UTF-8 Multibyte (i.e. it's MySQL 5.5.3 or later). Since the utf8mb4 charset is undefined in this case we
		 * catch the error and determine that utf8mb4 is not supported!
		 */
		if (!$result && $this->utf8mb4)
		{
			$this->utf8mb4 = false;
			$result = @$this->connection->set_charset('utf8');
		}

		return $result;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('START TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchArray($cursor = null)
	{
		return mysqli_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchAssoc($cursor = null)
	{
		return mysqli_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return mysqli_fetch_object($cursor ? $cursor : $this->cursor, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult($cursor = null)
	{
		mysqli_free_result($cursor ? $cursor : $this->cursor);

		if ((! $cursor) || ($cursor === $this->cursor))
		{
			$this->cursor = null;
		}
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriverMysqli  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('UNLOCK TABLES')->execute();

		return $this;
	}

	/**
	 * Internal function to check if profiling is available
	 *
	 * @return  boolean
	 *
	 * @since   3.1.3
	 */
	private function hasProfiling()
	{
		try
		{
			$res = mysqli_query($this->connection, "SHOW VARIABLES LIKE 'have_profiling'");
			$row = mysqli_fetch_assoc($res);

			return isset($row);
		}
		catch (Exception $e)
		{
			return false;
		}
	}

	/**
	 * Does the database server claim to have support for UTF-8 Multibyte (utf8mb4) collation?
	 *
	 * libmysql supports utf8mb4 since 5.5.3 (same version as the MySQL server). mysqlnd supports utf8mb4 since 5.0.9.
	 *
	 * @return  boolean
	 *
	 * @since   CMS 3.5.0
	 */
	private function serverClaimsUtf8mb4Support()
	{
		$client_version = mysqli_get_client_info();
		$server_version = $this->getVersion();

		if (version_compare($server_version, '5.5.3', '<'))
		{
			return false;
		}
		else
		{
			if (strpos($client_version, 'mysqlnd') !== false)
			{
				$client_version = preg_replace('/^\D+([\d.]+).*/', '$1', $client_version);

				return version_compare($client_version, '5.0.9', '>=');
			}
			else
			{
				return version_compare($client_version, '5.5.3', '>=');
			}
		}
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) mysqli_errno($this->connection);
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage()
	{
		$errorMessage = (string) mysqli_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
		}

		return $errorMessage;
	}
}
joomla/database/driver/pdomysql.php000064400000032521152177723700013463 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MySQL database driver supporting PDO based connections
 *
 * @link   https://www.php.net/manual/en/ref.pdo-mysql.php
 * @since  3.4
 */
class JDatabaseDriverPdomysql extends JDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.4
	 */
	public $name = 'pdomysql';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'mysql';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $nameQuote = '`';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * The minimum supported database version.
	 *
	 * @var    string
	 * @since  3.4
	 */
	protected static $dbMinimum = '5.0.4';

	/**
	 * Constructor.
	 *
	 * @param   array  $options  Array of database options with keys: host, user, password, database, select.
	 *
	 * @since   3.4
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['driver']  = 'mysql';

		if (!isset($options['charset']) || $options['charset'] == 'utf8')
		{
			$options['charset'] = 'utf8mb4';
		}

		/**
		 * Pre-populate the UTF-8 Multibyte compatibility flag. Unfortunately PDO won't report the server version
		 * unless we're connected to it, and we cannot connect to it unless we know if it supports utf8mb4, which requires
		 * us knowing the server version. Because of this chicken and egg issue, we _assume_ it's supported and we'll just
		 * catch any problems at connection time.
		 */
		$this->utf8mb4 = ($options['charset'] == 'utf8mb4');

		// Finalize initialisation.
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		try
		{
			// Try to connect to MySQL
			parent::connect();
		}
		catch (\RuntimeException $e)
		{
			// If the connection failed, but not because of the wrong character set, then bubble up the exception.
			if (!$this->utf8mb4)
			{
				throw $e;
			}

			/*
			 * Otherwise, try connecting again without using
			 * utf8mb4 and see if maybe that was the problem. If the
			 * connection succeeds, then we will have learned that the
			 * client end of the connection does not support utf8mb4.
  			 */
			$this->utf8mb4 = false;
			$this->options['charset'] = 'utf8';

			parent::connect();
		}

		if ($this->utf8mb4)
		{
			/*
 			 * At this point we know the client supports utf8mb4.  Now
 			 * we must check if the server supports utf8mb4 as well.
 			 */
			$serverVersion = $this->connection->getAttribute(PDO::ATTR_SERVER_VERSION);
			$this->utf8mb4 = version_compare($serverVersion, '5.5.3', '>=');

			if (!$this->utf8mb4)
			{
				// Reconnect with the utf8 character set.
				parent::disconnect();
				$this->options['charset'] = 'utf8';
				parent::connect();
			}
		}

		$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

		// Set sql_mode to non_strict mode
		$this->connection->query("SET @@SESSION.sql_mode = '';");

		// Disable query cache and turn profiling ON in debug mode.
		if ($this->debug)
		{
			$this->connection->query('SET query_cache_type = 0;');

			if ($this->hasProfiling())
			{
				$this->connection->query('SET profiling_history_size = 100, profiling = 1;');
			}
		}
	}

	/**
	 * Test to see if the MySQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.4
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('mysql', PDO::getAvailableDrivers());
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$query->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName));

		$this->setQuery($query);

		$this->execute();

		return $this;
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		$this->setQuery('USE ' . $this->quoteName($database));

		$this->execute();

		return $this;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database (string) or boolean false if not supported.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_database"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		$this->connect();

		// Attempt to get the database collation by accessing the server system variable.
		$this->setQuery('SHOW VARIABLES LIKE "collation_connection"');
		$result = $this->loadObject();

		if (property_exists($result, 'Value'))
		{
			return $result->Value;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		// Initialise variables.
		$result = array();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		foreach ($tables as $table)
		{
			$this->setQuery('SHOW CREATE TABLE ' . $this->quoteName($table));

			$row = $this->loadRow();

			// Populate the result array based on the create statements.
			$result[$table] = $row[1];
		}

		return $result;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		// Set the query to get the table fields statement.
		$this->setQuery('SHOW FULL COLUMNS FROM ' . $this->quoteName($table));

		$fields = $this->loadObjectList();

		// If we only want the type as the value add just that to the list.
		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = preg_replace('/[(0-9)]/', '', $field->Type);
			}
		}
		// If we want the whole field data object add that to the list.
		else
		{
			foreach ($fields as $field)
			{
				$result[$field->Field] = $field;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// Get the details columns information.
		$this->setQuery('SHOW KEYS FROM ' . $this->quoteName($table));

		$keys = $this->loadObjectList();

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		// Set the query to get the tables statement.
		$this->setQuery('SHOW TABLES');
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		$this->setQuery('LOCK TABLES ' . $this->quoteName($table) . ' WRITE')->execute();

		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by MySQL.
	 * @param   string  $prefix    Not used by MySQL.
	 *
	 * @return  JDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('RENAME TABLE ' . $this->quoteName($oldTable) . ' TO ' . $this->quoteName($newTable));

		$this->execute();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * Oracle escaping reference:
	 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
	 *
	 * SQLite escaping notes:
	 * http://www.sqlite.org/faq.html#q14
	 *
	 * Method body is as implemented by the Zend Framework
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.4
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$this->connect();

		$result = substr($this->connection->quote($text), 1, -1);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriverPdomysql  Returns this object to support chaining.
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->setQuery('UNLOCK TABLES')->execute();

		return $this;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.4
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			parent::transactionStart($asSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . $this->transactionDepth;
			$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth++;
			}
		}
	}

	/**
	 * Internal function to check if profiling is available.
	 *
	 * @return  boolean
	 *
	 * @since   3.9.1
	 */
	private function hasProfiling()
	{
		$result = $this->setQuery("SHOW VARIABLES LIKE 'have_profiling'")->loadAssoc();

		return isset($result);
	}
}
joomla/database/driver/sqlite.php000064400000027434152177723700013123 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * SQLite database driver
 *
 * @link   https://www.php.net/pdo
 * @since  3.0.0
 */
class JDatabaseDriverSqlite extends JDatabaseDriverPdo
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'sqlite';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'sqlite';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nameQuote = '`';

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		parent::connect();

		$this->connection->sqliteCreateFunction(
			'ROW_NUMBER',
			function($init = null)
			{
				static $rownum, $partition;

				if ($init !== null)
				{
					$rownum = $init;
					$partition = null;

					return $rownum;
				}

				$args = func_get_args();
				array_shift($args);

				$partitionBy = $args ? implode(',', $args) : null;

				if ($partitionBy === null || $partitionBy === $partition)
				{
					$rownum++;
				}
				else
				{
					$rownum    = 1;
					$partition = $partitionBy;
				}

				return $rownum;
			}
		);
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		$this->freeResult();

		$this->connection = null;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$query = $this->getQuery(true);

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $query->quoteName($tableName));

		$this->execute();

		return $this;
	}

	/**
	 * Method to escape a string for usage in an SQLite statement.
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		return SQLite3::escapeString($text);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   3.0.0
	 */
	public function getCollation()
	{
		return $this->charset;
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return $this->charset;
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * Note: Doesn't appear to have support in SQLite
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableCreate($tables)
	{
		$this->connect();

		// Sanitize input to an array and iterate over the list.
		settype($tables, 'array');

		return $tables;
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$columns = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);

		$query->setQuery('pragma table_info(' . $table . ')');

		$this->setQuery($query);
		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$columns[$field->NAME] = $field->TYPE;
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				// Do some dirty translation to MySQL output.
				// TODO: Come up with and implement a standard across databases.
				$columns[$field->NAME] = (object) array(
					'Field' => $field->NAME,
					'Type' => $field->TYPE,
					'Null' => ($field->NOTNULL == '1' ? 'NO' : 'YES'),
					'Default' => $field->DFLT_VALUE,
					'Key' => ($field->PK != '0' ? 'PRI' : ''),
				);
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $columns;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		$keys = array();
		$query = $this->getQuery(true);

		$fieldCasing = $this->getOption(PDO::ATTR_CASE);

		$this->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);

		$table = strtoupper($table);
		$query->setQuery('pragma table_info( ' . $table . ')');

		// $query->bind(':tableName', $table);

		$this->setQuery($query);
		$rows = $this->loadObjectList();

		foreach ($rows as $column)
		{
			if ($column->PK == 1)
			{
				$keys[$column->NAME] = $column;
			}
		}

		$this->setOption(PDO::ATTR_CASE, $fieldCasing);

		return $keys;
	}

	/**
	 * Method to get an array of all tables in the database (schema).
	 *
	 * @return  array   An array of all the tables in the database.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		$type = 'table';

		$query = $this->getQuery(true)
			->select('name')
			->from('sqlite_master')
			->where('type = :type')
			->bind(':type', $type)
			->order('name');

		$this->setQuery($query);

		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();

		$this->setQuery('SELECT sqlite_version()');

		return $this->loadResult();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * Returns false automatically for the Oracle driver since
	 * you can only set the character set when the connection
	 * is created.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		$this->connect();

		return false;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $table  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function lockTable($table)
	{
		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by Sqlite.
	 * @param   string  $prefix    Not used by Sqlite.
	 *
	 * @return  JDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->setQuery('ALTER TABLE ' . $oldTable . ' RENAME TO ' . $newTable)->execute();

		return $this;
	}

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriverSqlite  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		return $this;
	}

	/**
	 * Test to see if the PDO ODBC connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('sqlite', PDO::getAvailableDrivers());
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionCommit($toSavepoint);
		}
		else
		{
			$this->transactionDepth--;
		}
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			parent::transactionRollback($toSavepoint);
		}
		else
		{
			$savepoint = 'SP_' . ($this->transactionDepth - 1);
			$this->setQuery('ROLLBACK TO ' . $this->quoteName($savepoint));

			if ($this->execute())
			{
				$this->transactionDepth--;
			}
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.1.4
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			parent::transactionStart($asSavepoint);
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		return array();
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}
}
joomla/database/driver/postgresql.php000064400000117020152177723700014014 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL database driver
 *
 * @since       3.0.0
 * @deprecated  4.0  Use PDO PostgreSQL instead
 */
class JDatabaseDriverPostgresql extends JDatabaseDriver
{
	/**
	 * The database driver name
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'postgresql';

	/**
	 * The type of the database server family supported by this driver.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType = 'postgresql';

	/**
	 * Quote for named objects
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nameQuote = '"';

	/**
	 * The null/zero date string
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nullDate = '1970-01-01 00:00:00';

	/**
	 * The minimum supported database version.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected static $dbMinimum = '8.3.18';

	/**
	 * Operator used for concatenation
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $concat_operator = '||';

	/**
	 * JDatabaseDriverPostgresqlQuery object returned by getQuery
	 *
	 * @var    JDatabaseQueryPostgresql
	 * @since  3.0.0
	 */
	protected $queryObject = null;

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since	3.0.0
	 */
	public function __construct($options)
	{
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['port'] = (isset($options['port'])) ? $options['port'] : null;

		// Finalize initialization
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the postgresql extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new JDatabaseExceptionUnsupported('The pgsql extension for PHP is not installed or enabled.');
		}

		/*
		 * pg_connect() takes the port as separate argument. Therefore, we
		 * have to extract it from the host string (if provided).
		 */

		// Check for empty port
		if (!$this->options['port'])
		{
			// Port is empty or not set via options, check for port annotation (:) in the host string
			$tmp = substr(strstr($this->options['host'], ':'), 1);

			if (!empty($tmp))
			{
				// Get the port number
				if (is_numeric($tmp))
				{
					$this->options['port'] = $tmp;
				}

				// Extract the host name
				$this->options['host'] = substr($this->options['host'], 0, strlen($this->options['host']) - (strlen($tmp) + 1));

				// This will take care of the following notation: ":5432"
				if ($this->options['host'] === '')
				{
					$this->options['host'] = 'localhost';
				}
			}
			// No port annotation (:) found, setting port to default PostgreSQL port 5432
			else
			{
				$this->options['port'] = '5432';
			}
		}

		// Build the DSN for the connection.
		$dsn = '';

		if (!empty($this->options['host']))
		{
			$dsn .= "host={$this->options['host']} port={$this->options['port']} ";
		}

		$dsn .= "dbname={$this->options['database']} user={$this->options['user']} password={$this->options['password']}";

		// Attempt to connect to the server.
		if (!($this->connection = @pg_connect($dsn)))
		{
			throw new JDatabaseExceptionConnecting('Error connecting to PGSQL database.');
		}

		pg_set_error_verbosity($this->connection, PGSQL_ERRORS_DEFAULT);
		pg_query($this->connection, 'SET standard_conforming_strings=off');
		pg_query($this->connection, 'SET escape_string_warning=off');
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		// Close the connection.
		if (is_resource($this->connection))
		{
			foreach ($this->disconnectHandlers as $h)
			{
				call_user_func_array($h, array( &$this));
			}

			pg_close($this->connection);
		}

		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$this->connect();

		$result = pg_escape_string($this->connection, $text);

		if ($extra)
		{
			$result = addcslashes($result, '%_');
		}

		return $result;
	}

	/**
	 * Test to see if the PostgreSQL connector is available
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function test()
	{
		return function_exists('pg_connect');
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return	boolean
	 *
	 * @since	3.0.0
	 */
	public function connected()
	{
		$this->connect();

		if (is_resource($this->connection))
		{
			return pg_ping($this->connection);
		}

		return false;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  boolean
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->connect();

		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName));
		$this->execute();

		return true;
	}

	/**
	 * Get the number of affected rows by the last INSERT, UPDATE, REPLACE or DELETE for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows in the previous operation
	 *
	 * @since   3.0.0
	 */
	public function getAffectedRows()
	{
		$this->connect();

		return pg_affected_rows($this->cursor);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getCollation()
	{
		$this->connect();

		$this->setQuery('SHOW LC_COLLATE');
		$array = $this->loadAssocList();

		return $array[0]['lc_collate'];
	}

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return pg_client_encoding($this->connection);
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * This command is only valid for statements like SELECT or SHOW that return an actual result set.
	 * To retrieve the number of rows affected by an INSERT, UPDATE, REPLACE or DELETE query, use getAffectedRows().
	 *
	 * @param   resource  $cur  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   3.0.0
	 */
	public function getNumRows($cur = null)
	{
		$this->connect();

		return pg_num_rows((int) $cur ? $cur : $this->cursor);
	}

	/**
	 * Get the current or query, or new JDatabaseQuery object.
	 *
	 * @param   boolean  $new    False to return the last query set, True to return a new JDatabaseQuery object.
	 * @param   boolean  $asObj  False to return last query as string, true to get JDatabaseQueryPostgresql object.
	 *
	 * @return  JDatabaseQuery  The current query object or a new object extending the JDatabaseQuery class.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getQuery($new = false, $asObj = false)
	{
		if ($new)
		{
			$this->queryObject = new JDatabaseQueryPostgresql($this);

			return $this->queryObject;
		}
		else
		{
			if ($asObj)
			{
				return $this->queryObject;
			}
			else
			{
				return $this->sql;
			}
		}
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * This is unsuported by PostgreSQL.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  string  An empty char because this function is not supported by PostgreSQL.
	 *
	 * @since   3.0.0
	 */
	public function getTableCreate($tables)
	{
		return '';
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table. For PostgreSQL may start with a schema.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();
		$tableSub = $this->replacePrefix($table);
		$fn = explode('.', $tableSub);

		if (count($fn) === 2) 
		{
			$schema = $fn[0];
			$tableSub = $fn[1];
		} 
		else 
		{
			$schema = 'public';
		}

		$this->setQuery('
			SELECT a.attname AS "column_name",
				pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
				CASE WHEN a.attnotnull IS TRUE
					THEN \'NO\'
					ELSE \'YES\'
				END AS "null",
				CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT NULL
					THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
				END as "Default",
				CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
				THEN \'\'
				ELSE pg_catalog.col_description(a.attrelid, a.attnum)
				END  AS "comments"
			FROM pg_catalog.pg_attribute a
			LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum
			LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
			WHERE a.attrelid =
				(SELECT oid FROM pg_catalog.pg_class WHERE relname=' . $this->quote($tableSub) . '
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
					nspname = ' . $this->quote($schema) . ')
				)
			AND a.attnum > 0 AND NOT a.attisdropped
			ORDER BY a.attnum'
		);

		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->column_name] = preg_replace('/[(0-9)]/', '', $field->type);
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				if (stristr(strtolower($field->type), 'character varying'))
				{
					$field->Default = '';
				}

				if (stristr(strtolower($field->type), 'text'))
				{
					$field->Default = '';
				}
				// Do some dirty translation to MySQL output.
				// TODO: Come up with and implement a standard across databases.
				$result[$field->column_name] = (object) array(
					'column_name' => $field->column_name,
					'type' => $field->type,
					'null' => $field->null,
					'Default' => $field->Default,
					'comments' => '',
					'Field' => $field->column_name,
					'Type' => $field->type,
					'Null' => $field->null,
					// TODO: Improve query above to return primary key info as well
					// 'Key' => ($field->PK == '1' ? 'PRI' : '')
				);
			}
		}

		/* Change Postgresql's NULL::* type with PHP's null one */
		foreach ($fields as $field)
		{
			if (preg_match('/^NULL::*/', $field->Default))
			{
				$field->Default = null;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList))
		{
			// Get the details columns information.
			$this->setQuery('
				SELECT indexname AS "idxName", indisprimary AS "isPrimary", indisunique  AS "isUnique",
					CASE WHEN indisprimary = true THEN
						( SELECT \'ALTER TABLE \' || tablename || \' ADD \' || pg_catalog.pg_get_constraintdef(const.oid, true)
							FROM pg_constraint AS const WHERE const.conname= pgClassFirst.relname )
					ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
					END AS "Query"
				FROM pg_indexes
				LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
				LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
				WHERE tablename=' . $this->quote($table) . ' ORDER BY indkey'
			);

			$keys = $this->loadObjectList();

			return $keys;
		}

		return false;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableList()
	{
		$this->connect();

		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type=' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ')')
			->order('table_name ASC');

		$this->setQuery($query);
		$tables = $this->loadColumn();

		return $tables;
	}

	/**
	 * Get the details list of sequences for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of sequences specification for the table.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getTableSequences($table)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList))
		{
			$name = array(
				's.relname', 'n.nspname', 't.relname', 'a.attname', 'info.data_type', 'info.minimum_value', 'info.maximum_value',
				'info.increment', 'info.cycle_option',
			);
			$as = array('sequence', 'schema', 'table', 'column', 'data_type', 'minimum_value', 'maximum_value', 'increment', 'cycle_option');

			if (version_compare($this->getVersion(), '9.1.0') >= 0)
			{
				$name[] .= 'info.start_value';
				$as[] .= 'start_value';
			}

			// Get the details columns information.
			$query = $this->getQuery(true)
				->select($this->quoteName($name, $as))
				->from('pg_class AS s')
				->join('LEFT', "pg_depend d ON d.objid=s.oid AND d.classid='pg_class'::regclass AND d.refclassid='pg_class'::regclass")
				->join('LEFT', 'pg_class t ON t.oid=d.refobjid')
				->join('LEFT', 'pg_namespace n ON n.oid=t.relnamespace')
				->join('LEFT', 'pg_attribute a ON a.attrelid=t.oid AND a.attnum=d.refobjsubid')
				->join('LEFT', 'information_schema.sequences AS info ON info.sequence_name=s.relname')
				->where("s.relkind='S' AND d.deptype='a' AND t.relname=" . $this->quote($table));
			$this->setQuery($query);
			$seq = $this->loadObjectList();

			return $seq;
		}

		return false;
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.0.0
	 */
	public function getVersion()
	{
		$this->connect();
		$version = pg_version($this->connection);

		return $version['server'];
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 * To be called after the INSERT statement, it's MANDATORY to have a sequence on
	 * every primary key table.
	 *
	 * To get the auto incremented value it's possible to call this function after
	 * INSERT INTO query, or use INSERT INTO with RETURNING clause.
	 *
	 * @example with insertid() call:
	 *		$query = $this->getQuery(true)
	 *			->insert('jos_dbtest')
	 *			->columns('title,start_date,description')
	 *			->values("'testTitle2nd','1971-01-01','testDescription2nd'");
	 *		$this->setQuery($query);
	 *		$this->execute();
	 *		$id = $this->insertid();
	 *
	 * @example with RETURNING clause:
	 *		$query = $this->getQuery(true)
	 *			->insert('jos_dbtest')
	 *			->columns('title,start_date,description')
	 *			->values("'testTitle2nd','1971-01-01','testDescription2nd'")
	 *			->returning('id');
	 *		$this->setQuery($query);
	 *		$id = $this->loadResult();
	 *
	 * @return  integer  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   3.0.0
	 */
	public function insertid()
	{
		$this->connect();
		$insertQuery = $this->getQuery(false, true);
		$table = $insertQuery->__get('insert')->getElements();

		/* find sequence column name */
		$colNameQuery = $this->getQuery(true);
		$colNameQuery->select('column_default')
			->from('information_schema.columns')
			->where('table_name=' . $this->quote($this->replacePrefix(str_replace('"', '', $table[0]))), 'AND')
			->where("column_default LIKE '%nextval%'");

		$this->setQuery($colNameQuery);
		$colName = $this->loadRow();
		$changedColName = str_replace('nextval', 'currval', $colName);

		$insertidQuery = $this->getQuery(true);
		$insertidQuery->select($changedColName);
		$this->setQuery($insertidQuery);
		$insertVal = $this->loadRow();

		return $insertVal[0];
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function lockTable($tableName)
	{
		$this->transactionStart();
		$this->setQuery('LOCK TABLE ' . $this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE MODE')->execute();

		return $this;
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function execute()
	{
		$this->connect();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!($this->sql instanceof JDatabaseQuery) && ($this->limit > 0 || $this->offset > 0))
		{
			$query .= ' LIMIT ' . $this->limit . ' OFFSET ' . $this->offset;
		}

		if (!is_resource($this->connection))
		{
			JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			JLog::add($query, JLog::DEBUG, 'databasequery');

			$this->timings[] = microtime(true);

			if (is_object($this->cursor))
			{
				// Avoid warning if result already freed by third-party library
				@$this->freeResult();
			}

			$memoryBefore = memory_get_usage();
		}

		// Execute the query. Error suppression is used here to prevent warnings/notices that the connection has been lost.
		$this->cursor = @pg_query($this->connection, $query);

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}

			$this->callStacks[count($this->callStacks) - 1][0]['memory'] = array(
				$memoryBefore,
				memory_get_usage(),
				is_resource($this->cursor) ? $this->getNumRows($this->cursor) : null,
			);
		}

		// If an error occurred handle it.
		if (!$this->cursor)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage();

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage();

					// Throw the normal query exception.
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

					throw new JDatabaseExceptionExecuting($query, $this->errorMsg, null, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

				throw new JDatabaseExceptionExecuting($query, $this->errorMsg);
			}
		}

		return $this->cursor;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by PostgreSQL.
	 * @param   string  $prefix    Not used by PostgreSQL.
	 *
	 * @return  JDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		// Origin Table does not exist
		if (!in_array($oldTable, $tableList))
		{
			// Origin Table not found
			throw new RuntimeException('Table not found in Postgresql database.');
		}
		else
		{
			/* Rename indexes */
			$this->setQuery(
				'SELECT relname
					FROM pg_class
					WHERE oid IN (
						SELECT indexrelid
						FROM pg_index, pg_class
						WHERE pg_class.relname=' . $this->quote($oldTable, true) . '
						AND pg_class.oid=pg_index.indrelid );'
			);

			$oldIndexes = $this->loadColumn();

			foreach ($oldIndexes as $oldIndex)
			{
				$changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
				$this->setQuery('ALTER INDEX ' . $this->escape($oldIndex) . ' RENAME TO ' . $this->escape($changedIdxName));
				$this->execute();
			}

			/* Rename sequence */
			$this->setQuery(
				'SELECT relname
					FROM pg_class
					WHERE relkind = \'S\'
					AND relnamespace IN (
						SELECT oid
						FROM pg_namespace
						WHERE nspname NOT LIKE \'pg_%\'
						AND nspname != \'information_schema\'
					)
					AND relname LIKE \'%' . $oldTable . '%\' ;'
			);

			$oldSequences = $this->loadColumn();

			foreach ($oldSequences as $oldSequence)
			{
				$changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
				$this->setQuery('ALTER SEQUENCE ' . $this->escape($oldSequence) . ' RENAME TO ' . $this->escape($changedSequenceName));
				$this->execute();
			}

			/* Rename table */
			$this->setQuery('ALTER TABLE ' . $this->escape($oldTable) . ' RENAME TO ' . $this->escape($newTable));
			$this->execute();
		}

		return true;
	}

	/**
	 * Selects the database, but redundant for PostgreSQL
	 *
	 * @param   string  $database  Database name to select.
	 *
	 * @return  boolean  Always true
	 *
	 * @since   3.0.0
	 */
	public function select($database)
	{
		return true;
	}

	/**
	 * Custom settings for UTF support
	 *
	 * @return  integer  Zero on success, -1 on failure
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		$this->connect();

		if (!function_exists('pg_set_client_encoding'))
		{
			return -1;
		}

		return pg_set_client_encoding($this->connection, 'UTF8');
	}

	/**
	 * This function return a field value as a prepared string to be used in a SQL statement.
	 *
	 * @param   array   $columns      Array of table's column returned by ::getTableColumns.
	 * @param   string  $field_name   The table field's name.
	 * @param   string  $field_value  The variable value to quote and return.
	 *
	 * @return  string  The quoted string.
	 *
	 * @since   3.0.0
	 */
	public function sqlValue($columns, $field_name, $field_value)
	{
		switch ($columns[$field_name])
		{
			case 'boolean':
				$val = 'NULL';

				if ($field_value == 't')
				{
					$val = 'TRUE';
				}
				elseif ($field_value == 'f')
				{
					$val = 'FALSE';
				}

				break;

			case 'bigint':
			case 'bigserial':
			case 'integer':
			case 'money':
			case 'numeric':
			case 'real':
			case 'smallint':
			case 'serial':
			case 'numeric,':
				$val = strlen($field_value) == 0 ? 'NULL' : $field_value;
				break;

			case 'date':
			case 'timestamp without time zone':
				if (empty($field_value))
				{
					$field_value = $this->getNullDate();
				}

				$val = $this->quote($field_value);
				break;

			default:
				$val = $this->quote($field_value);
				break;
		}

		return $val;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
			$this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($savepoint))->execute();
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('START TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchArray($cursor = null)
	{
		return pg_fetch_row($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchAssoc($cursor = null)
	{
		return pg_fetch_assoc($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		return pg_fetch_object(is_null($cursor) ? $this->cursor : $cursor, null, $class);
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult($cursor = null)
	{
		pg_free_result($cursor ? $cursor : $this->cursor);
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$columns = $this->getTableColumns($table);

		$fields = array();
		$values = array();

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			// Ignore any internal fields or primary keys with value 0.
			if (($k[0] == '_') || ($k == $key && (($v === 0) || ($v === '0'))))
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->sqlValue($columns, $k, $v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true)
			->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		$retVal = false;

		if ($key)
		{
			$query->returning($key);

			// Set the query and execute the insert.
			$this->setQuery($query);

			$id = $this->loadResult();

			if ($id)
			{
				$object->$key = $id;
				$retVal = true;
			}
		}
		else
		{
			// Set the query and execute the insert.
			$this->setQuery($query);

			if ($this->execute())
			{
				$retVal = true;
			}
		}

		return $retVal;
	}

	/**
	 * Test to see if the PostgreSQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return function_exists('pg_connect');
	}

	/**
	 * Returns an array containing database's table list.
	 *
	 * @return  array  The database's table list.
	 *
	 * @since   3.0.0
	 */
	public function showTables()
	{
		$this->connect();

		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type = ' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ' )');

		$this->setQuery($query);
		$tableList = $this->loadColumn();

		return $tableList;
	}

	/**
	 * Get the substring position inside a string
	 *
	 * @param   string  $substring  The string being sought
	 * @param   string  $string     The string/column being searched
	 *
	 * @return  integer  The position of $substring in $string
	 *
	 * @since   3.0.0
	 */
	public function getStringPositionSql($substring, $string)
	{
		$this->connect();

		$query = "SELECT POSITION( $substring IN $string )";
		$this->setQuery($query);
		$position = $this->loadRow();

		return $position['position'];
	}

	/**
	 * Generate a random value
	 *
	 * @return  float  The random generated number
	 *
	 * @since   3.0.0
	 */
	public function getRandom()
	{
		$this->connect();

		$this->setQuery('SELECT RANDOM()');
		$random = $this->loadAssoc();

		return $random['random'];
	}

	/**
	 * Get the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   3.0.0
	 */
	public function getAlterDbCharacterSet($dbName)
	{
		$query = 'ALTER DATABASE ' . $this->quoteName($dbName) . ' SET CLIENT_ENCODING TO ' . $this->quote('UTF8');

		return $query;
	}

	/**
	 * Get the query string to create new Database in correct PostgreSQL syntax.
	 *
	 * @param   object   $options  object coming from "initialise" function to pass user and database name to database driver.
	 * @param   boolean  $utf      True if the database supports the UTF-8 character set, not used in PostgreSQL "CREATE DATABASE" query.
	 *
	 * @return  string	The query that creates database, owned by $options['user']
	 *
	 * @since   3.0.0
	 */
	public function getCreateDbQuery($options, $utf)
	{
		$query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' OWNER ' . $this->quoteName($options->db_user);

		if ($utf)
		{
			$query .= ' ENCODING ' . $this->quote('UTF-8');
		}

		return $query;
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $query   The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   3.0.0
	 */
	public function replacePrefix($query, $prefix = '#__')
	{
		$query = trim($query);

		if (strpos($query, '\''))
		{
			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'currval'))
			{
				$query = explode('currval', $query);

				for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('currval', $query);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'nextval'))
			{
				$query = explode('nextval', $query);

				for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('nextval', $query);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($query, 'setval'))
			{
				$query = explode('setval', $query);

				for ($nIndex = 1, $nIndexMax = count($query); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$query[$nIndex] = str_replace($prefix, $this->tablePrefix, $query[$nIndex]);
				}

				$query = implode('setval', $query);
			}

			$explodedQuery = explode('\'', $query);

			for ($nIndex = 0, $nIndexMax = count($explodedQuery); $nIndex < $nIndexMax; $nIndex += 2)
			{
				if (strpos($explodedQuery[$nIndex], $prefix))
				{
					$explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix, $explodedQuery[$nIndex]);
				}
			}

			$replacedQuery = implode('\'', $explodedQuery);
		}
		else
		{
			$replacedQuery = str_replace($prefix, $this->tablePrefix, $query);
		}

		return $replacedQuery;
	}

	/**
	 * Method to release a savepoint.
	 *
	 * @param   string  $savepointName  Savepoint's name to release
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function releaseTransactionSavepoint($savepointName)
	{
		$this->connect();
		$this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
		$this->execute();
	}

	/**
	 * Method to create a savepoint.
	 *
	 * @param   string  $savepointName  Savepoint's name to create
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function transactionSavepoint($savepointName)
	{
		$this->connect();
		$this->setQuery('SAVEPOINT ' . $this->quoteName($this->escape($savepointName)));
		$this->execute();
	}

	/**
	 * Unlocks tables in the database, this command does not exist in PostgreSQL,
	 * it is automatically done on commit or rollback.
	 *
	 * @return  JDatabaseDriverPostgresql  Returns this object to support chaining.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function unlockTables()
	{
		$this->transactionCommit();

		return $this;
	}

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string   $table    The name of the database table to update.
	 * @param   object   &$object  A reference to an object whose public properties match the table fields.
	 * @param   array    $key      The name of the primary key.
	 * @param   boolean  $nulls    True to update null fields or false to ignore them.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$columns = $this->getTableColumns($table);
		$fields  = array();
		$where   = array();

		if (is_string($key))
		{
			$key = array($key);
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process scalars that are not internal fields.
			if (is_array($v) or is_object($v) or $k[0] == '_')
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key))
			{
				$key_val = $this->sqlValue($columns, $k, $v);
				$where[] = $this->quoteName($k) . '=' . $key_val;
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we want to update nulls then set it.
				if ($nulls)
				{
					$val = 'NULL';
				}
				// If the value is null and we do not want to update nulls then ignore this field.
				else
				{
					continue;
				}
			}
			// The field is not null so we prep it for update.
			else
			{
				$val = $this->sqlValue($columns, $k, $v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)));

		return $this->execute();
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 *
	 * @throws  \JDatabaseExceptionExecuting  Thrown if the global cursor is false indicating a query failed
	 */
	protected function getErrorNumber()
	{
		if ($this->cursor === false)
		{
			$this->errorMsg = pg_last_error($this->connection);

			throw new JDatabaseExceptionExecuting($this->sql, $this->errorMsg);
		}

		return (int) pg_result_error_field($this->cursor, PGSQL_DIAG_SQLSTATE) . ' ';
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage()
	{
		$errorMessage = (string) pg_last_error($this->connection);

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorMessage = str_replace($this->tablePrefix, '#__', $errorMessage);
		}

		return $errorMessage;
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		return array();
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}
}
joomla/database/driver/pdo.php000064400000063740152177723700012404 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform PDO Database Driver Class
 *
 * @link   https://www.php.net/pdo
 * @since  3.0.0
 */
abstract class JDatabaseDriverPdo extends JDatabaseDriver
{
	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	public $name = 'pdo';

	/**
	 * @var    PDO  The database connection resource.
	 * @since  3.0.0
	 */
	protected $connection;

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc.  The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nameQuote = "'";

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $nullDate = '0000-00-00 00:00:00';

	/**
	 * @var    resource  The prepared statement.
	 * @since  3.0.0
	 */
	protected $prepared;

	/**
	 * Contains the current query execution status
	 *
	 * @var array
	 * @since 3.0.0
	 */
	protected $executed = false;

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   3.0.0
	 */
	public function __construct($options)
	{
		// Get some basic values from the options.
		$options['driver'] = (isset($options['driver'])) ? $options['driver'] : 'odbc';
		$options['dsn'] = (isset($options['dsn'])) ? $options['dsn'] : '';
		$options['host'] = (isset($options['host'])) ? $options['host'] : 'localhost';
		$options['database'] = (isset($options['database'])) ? $options['database'] : '';
		$options['user'] = (isset($options['user'])) ? $options['user'] : '';
		$options['password'] = (isset($options['password'])) ? $options['password'] : '';
		$options['driverOptions'] = (isset($options['driverOptions'])) ? $options['driverOptions'] : array();

		$hostParts = explode(':', $options['host']);

		if (!empty($hostParts[1]))
		{
			$options['host'] = $hostParts[0];
			$options['port'] = $hostParts[1];
		}

		// Finalize initialisation
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function connect()
	{
		if ($this->connection)
		{
			return;
		}

		// Make sure the PDO extension for PHP is installed and enabled.
		if (!self::isSupported())
		{
			throw new JDatabaseExceptionUnsupported('PDO Extension is not available.', 1);
		}

		$replace = array();
		$with = array();

		// Find the correct PDO DSN Format to use:
		switch ($this->options['driver'])
		{
			case 'cubrid':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 33000;

				$format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'dblib':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'firebird':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3050;

				$format = 'firebird:dbname=#DBNAME#';

				$replace = array('#DBNAME#');
				$with = array($this->options['database']);

				break;

			case 'ibm':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 56789;

				if (!empty($this->options['dsn']))
				{
					$format = 'ibm:DSN=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database']);
				}

				break;

			case 'informix':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1526;
				$this->options['protocol'] = (isset($this->options['protocol'])) ? $this->options['protocol'] : 'onsoctcp';

				if (!empty($this->options['dsn']))
				{
					$format = 'informix:DSN=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['server'], $this->options['protocol']);
				}

				break;

			case 'mssql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			// The pdomysql case is a special case within the CMS environment
			case 'pdomysql':
			case 'mysql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 3306;

				$format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#', '#CHARSET#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database'], $this->options['charset']);

				break;

			case 'oci':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1521;
				$this->options['charset'] = (isset($this->options['charset'])) ? $this->options['charset'] : 'AL32UTF8';

				if (!empty($this->options['dsn']))
				{
					$format = 'oci:dbname=#DSN#';

					$replace = array('#DSN#');
					$with = array($this->options['dsn']);
				}
				else
				{
					$format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#';

					$replace = array('#HOST#', '#PORT#', '#DBNAME#');
					$with = array($this->options['host'], $this->options['port'], $this->options['database']);
				}

				$format .= ';charset=' . $this->options['charset'];

				break;

			case 'odbc':
				$format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#';

				$replace = array('#DSN#', '#USER#', '#PASSWORD#');
				$with = array($this->options['dsn'], $this->options['user'], $this->options['password']);

				break;

			case 'pgsql':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 5432;

				$format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;

			case 'sqlite':
				if (isset($this->options['version']) && $this->options['version'] == 2)
				{
					$format = 'sqlite2:#DBNAME#';
				}
				else
				{
					$format = 'sqlite:#DBNAME#';
				}

				$replace = array('#DBNAME#');
				$with = array($this->options['database']);

				break;

			case 'sybase':
				$this->options['port'] = (isset($this->options['port'])) ? $this->options['port'] : 1433;

				$format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#';

				$replace = array('#HOST#', '#PORT#', '#DBNAME#');
				$with = array($this->options['host'], $this->options['port'], $this->options['database']);

				break;
		}

		// Create the connection string:
		$connectionString = str_replace($replace, $with, $format);

		try
		{
			$this->connection = new PDO(
				$connectionString,
				$this->options['user'],
				$this->options['password'],
				$this->options['driverOptions']
			);
		}
		catch (PDOException $e)
		{
			throw new JDatabaseExceptionConnecting('Could not connect to PDO: ' . $e->getMessage(), 2, $e);
		}
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function disconnect()
	{
		foreach ($this->disconnectHandlers as $h)
		{
			call_user_func_array($h, array( &$this));
		}

		$this->freeResult();
		$this->connection = null;
	}

	/**
	 * Method to escape a string for usage in an SQL statement.
	 *
	 * Oracle escaping reference:
	 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F
	 *
	 * SQLite escaping notes:
	 * http://www.sqlite.org/faq.html#q14
	 *
	 * Method body is as implemented by the Zend Framework
	 *
	 * Note: Using query objects with bound variables is
	 * preferable to the below.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Unused optional parameter to provide extra escaping.
	 *
	 * @return  string  The escaped string.
	 *
	 * @since   3.0.0
	 */
	public function escape($text, $extra = false)
	{
		if (is_int($text))
		{
			return $text;
		}

		if (is_float($text))
		{
			// Force the dot as a decimal point.
			return str_replace(',', '.', $text);
		}

		$text = str_replace("'", "''", $text);

		return addcslashes($text, "\000\n\r\\\032");
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 * @throws  Exception
	 */
	public function execute()
	{
		$this->connect();

		// Take a local copy so that we don't modify the original query and cause issues later
		$query = $this->replacePrefix((string) $this->sql);

		if (!is_object($this->connection))
		{
			JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database');
			throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
		}

		// Increment the query counter.
		$this->count++;

		// Reset the error values.
		$this->errorNum = 0;
		$this->errorMsg = '';

		// If debugging is enabled then let's log the query.
		if ($this->debug)
		{
			// Add the query to the object queue.
			$this->log[] = $query;

			JLog::add($query, JLog::DEBUG, 'databasequery');

			$this->timings[] = microtime(true);
		}

		// Execute the query.
		$this->executed = false;

		if ($this->prepared instanceof PDOStatement)
		{
			// Bind the variables:
			if ($this->sql instanceof JDatabaseQueryPreparable)
			{
				$bounded = $this->sql->getBounded();

				foreach ($bounded as $key => $obj)
				{
					$this->prepared->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions);
				}
			}

			$this->executed = $this->prepared->execute();
		}

		if ($this->debug)
		{
			$this->timings[] = microtime(true);

			if (defined('DEBUG_BACKTRACE_IGNORE_ARGS'))
			{
				$this->callStacks[] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
			}
			else
			{
				$this->callStacks[] = debug_backtrace();
			}
		}

		// If an error occurred handle it.
		if (!$this->executed)
		{
			// Get the error number and message before we execute any more queries.
			$errorNum = $this->getErrorNumber();
			$errorMsg = $this->getErrorMessage();

			// Check if the server was disconnected.
			if (!$this->connected())
			{
				try
				{
					// Attempt to reconnect.
					$this->connection = null;
					$this->connect();
				}
				// If connect fails, ignore that exception and throw the normal exception.
				catch (RuntimeException $e)
				{
					// Get the error number and message.
					$this->errorNum = $this->getErrorNumber();
					$this->errorMsg = $this->getErrorMessage();

					// Throw the normal query exception.
					JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

					throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum, $e);
				}

				// Since we were able to reconnect, run the query again.
				return $this->execute();
			}
			// The server was not disconnected.
			else
			{
				// Get the error number and message from before we tried to reconnect.
				$this->errorNum = $errorNum;
				$this->errorMsg = $errorMsg;

				// Throw the normal query exception.
				JLog::add(JText::sprintf('JLIB_DATABASE_QUERY_FAILED', $this->errorNum, $this->errorMsg), JLog::ERROR, 'database-error');

				throw new JDatabaseExceptionExecuting($query, $this->errorMsg, $this->errorNum);
			}
		}

		return $this->prepared;
	}

	/**
	 * Retrieve a PDO database connection attribute
	 *
	 * Usage: $db->getOption(PDO::ATTR_CASE);
	 *
	 * @param   mixed  $key  One of the PDO::ATTR_* Constants
	 *
	 * @return  mixed
	 *
	 * @link    https://www.php.net/manual/en/pdo.getattribute.php
	 * @since   3.0.0
	 */
	public function getOption($key)
	{
		$this->connect();

		return $this->connection->getAttribute($key);
	}

	/**
	 * Get a query to run and verify the database is operational.
	 *
	 * @return  string  The query to check the health of the DB.
	 *
	 * @since   3.0.1
	 */
	public function getConnectedQuery()
	{
		return 'SELECT 1';
	}

	/**
	 * Get the version of the database connector.
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   3.9.0
	 */
	public function getVersion()
	{
		$this->connect();

		return $this->getOption(PDO::ATTR_SERVER_VERSION);
	}

	/**
	 * Sets an attribute on the PDO database handle.
	 *
	 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER);
	 *
	 * @param   integer  $key    One of the PDO::ATTR_* Constants
	 * @param   mixed    $value  One of the associated PDO Constants related to the particular attribute key.
	 *
	 * @return  boolean
	 *
	 * @link   https://www.php.net/manual/en/pdo.setattribute.php
	 * @since   3.0.0
	 */
	public function setOption($key, $value)
	{
		$this->connect();

		return $this->connection->setAttribute($key, $value);
	}

	/**
	 * Test to see if the PDO extension is available.
	 * Override as needed to check for specific PDO Drivers.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.0.0
	 */
	public static function isSupported()
	{
		return defined('PDO::ATTR_DRIVER_NAME');
	}

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   3.0.0
	 */
	public function connected()
	{
		// Flag to prevent recursion into this function.
		static $checkingConnected = false;

		if ($checkingConnected)
		{
			// Reset this flag and throw an exception.
			$checkingConnected = true;
			die('Recursion trying to check if connected.');
		}

		// Backup the query state.
		$query = $this->sql;
		$limit = $this->limit;
		$offset = $this->offset;
		$prepared = $this->prepared;

		try
		{
			// Set the checking connection flag.
			$checkingConnected = true;

			// Run a simple query to check the connection.
			$this->setQuery($this->getConnectedQuery());
			$status = (bool) $this->loadResult();
		}
		// If we catch an exception here, we must not be connected.
		catch (Exception $e)
		{
			$status = false;
		}

		// Restore the query state.
		$this->sql = $query;
		$this->limit = $limit;
		$this->offset = $offset;
		$this->prepared = $prepared;
		$checkingConnected = false;

		return $status;
	}

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 * Only applicable for DELETE, INSERT, or UPDATE statements.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   3.0.0
	 */
	public function getAffectedRows()
	{
		$this->connect();

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 * Only applicable for DELETE, INSERT, or UPDATE statements.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   3.0.0
	 */
	public function getNumRows($cursor = null)
	{
		$this->connect();

		if ($cursor instanceof PDOStatement)
		{
			return $cursor->rowCount();
		}
		elseif ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->rowCount();
		}
		else
		{
			return 0;
		}
	}

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  string  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   3.0.0
	 */
	public function insertid()
	{
		$this->connect();

		// Error suppress this to prevent PDO warning us that the driver doesn't support this operation.
		return @$this->connection->lastInsertId();
	}

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function select($database)
	{
		$this->connect();

		return true;
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query          The SQL statement to set either as a JDatabaseQuery object or a string.
	 * @param   integer  $offset         The affected row offset to set.
	 * @param   integer  $limit          The maximum affected rows to set.
	 * @param   array    $driverOptions  The optional PDO driver options.
	 *
	 * @return  JDatabaseDriver  This object to support method chaining.
	 *
	 * @since   3.0.0
	 */
	public function setQuery($query, $offset = null, $limit = null, $driverOptions = array())
	{
		$this->connect();

		$this->freeResult();

		if (is_string($query))
		{
			// Allows taking advantage of bound variables in a direct query:
			$query = $this->getQuery(true)->setQuery($query);
		}

		if ($query instanceof JDatabaseQueryLimitable && !is_null($offset) && !is_null($limit))
		{
			$query = $query->processLimit($query, $limit, $offset);
		}

		// Create a stringified version of the query (with prefixes replaced):
		$sql = $this->replacePrefix((string) $query);

		// Use the stringified version in the prepare call:
		$this->prepared = $this->connection->prepare($sql, $driverOptions);

		// Store reference to the original JDatabaseQuery instance within the class.
		// This is important since binding variables depends on it within execute():
		parent::setQuery($query, $offset, $limit);

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.0.0
	 */
	public function setUtf()
	{
		return false;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth == 1)
		{
			$this->connection->commit();
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth == 1)
		{
			$this->connection->rollBack();
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			$this->connection->beginTransaction();
		}

		$this->transactionDepth++;
	}

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchArray($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_NUM);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_NUM);
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchAssoc($cursor = null)
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetch(PDO::FETCH_ASSOC);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetch(PDO::FETCH_ASSOC);
		}
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   Unused, only necessary so method signature will be the same as parent.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	protected function fetchObject($cursor = null, $class = 'stdClass')
	{
		if (!empty($cursor) && $cursor instanceof PDOStatement)
		{
			return $cursor->fetchObject($class);
		}

		if ($this->prepared instanceof PDOStatement)
		{
			return $this->prepared->fetchObject($class);
		}
	}

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	protected function freeResult($cursor = null)
	{
		$this->executed = false;

		if ($cursor instanceof PDOStatement)
		{
			$cursor->closeCursor();
			$cursor = null;
		}

		if ($this->prepared instanceof PDOStatement)
		{
			$this->prepared->closeCursor();
			$this->prepared = null;
		}
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use getIterator() instead
	 */
	public function loadNextObject($class = 'stdClass')
	{
		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject(null, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function loadNextAssoc()
	{
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchAssoc())
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use getIterator() instead
	 */
	public function loadNextRow()
	{
		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		$this->connect();

		// Execute the query and get the result set cursor.
		if (!$this->executed)
		{
			if (!($this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray())
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult();

		return false;
	}

	/**
	 * PDO does not support serialize
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function __sleep()
	{
		$serializedProperties = array();

		$reflect = new ReflectionClass($this);

		// Get properties of the current class
		$properties = $reflect->getProperties();

		foreach ($properties as $property)
		{
			// Do not serialize properties that are PDO
			if ($property->isStatic() == false && !($this->{$property->name} instanceof PDO))
			{
				$serializedProperties[] = $property->name;
			}
		}

		return $serializedProperties;
	}

	/**
	 * Wake up after serialization
	 *
	 * @return  array
	 *
	 * @since   3.1.4
	 */
	public function __wakeup()
	{
		// Get connection back
		$this->__construct($this->options);
	}

	/**
	 * Return the actual SQL Error number
	 *
	 * @return  integer  The SQL Error number
	 *
	 * @since   3.4.6
	 */
	protected function getErrorNumber()
	{
		return (int) $this->connection->errorCode();
	}

	/**
	 * Return the actual SQL Error message
	 *
	 * @return  string  The SQL Error message
	 *
	 * @since   3.4.6
	 */
	protected function getErrorMessage()
	{
		// The SQL Error Information
		$errorInfo = implode(', ', $this->connection->errorInfo());

		// Replace the Databaseprefix with `#__` if we are not in Debug
		if (!$this->debug)
		{
			$errorInfo = str_replace($this->tablePrefix, '#__', $errorInfo);
		}

		return $errorInfo;
	}
}
joomla/database/driver/pgsql.php000064400000062010152177723700012735 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * PostgreSQL PDO Database Driver
 *
 * @link   https://www.php.net/manual/en/ref.pdo-mysql.php
 * @since  3.9.0
 */
class JDatabaseDriverPgsql extends JDatabaseDriverPdo
{
	/**
	 * The database driver name
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	public $name = 'pgsql';

	/**
	 * The character(s) used to quote SQL statement names such as table names or field names,
	 * etc. The child classes should define this as necessary.  If a single character string the
	 * same character is used for both sides of the quoted name, else the first character will be
	 * used for the opening quote and the second for the closing quote.
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	protected $nameQuote = '"';

	/**
	 * The null or zero representation of a timestamp for the database driver.  This should be
	 * defined in child classes to hold the appropriate value for the engine.
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	protected $nullDate = '1970-01-01 00:00:00';

	/**
	 * The minimum supported database version.
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	protected static $dbMinimum = '8.3.18';

	/**
	 * Operator used for concatenation
	 *
	 * @var    string
	 * @since  3.9.0
	 */
	protected $concat_operator = '||';

	/**
	 * Database object constructor
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since	3.9.0
	 */
	public function __construct($options)
	{
		$options['driver']   = 'pgsql';
		$options['host']     = isset($options['host']) ? $options['host'] : 'localhost';
		$options['user']     = isset($options['user']) ? $options['user'] : '';
		$options['password'] = isset($options['password']) ? $options['password'] : '';
		$options['database'] = isset($options['database']) ? $options['database'] : '';
		$options['port']     = isset($options['port']) ? $options['port'] : null;

		// Finalize initialization
		parent::__construct($options);
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function connect()
	{
		if ($this->getConnection())
		{
			return;
		}

		parent::connect();

		$this->setQuery('SET standard_conforming_strings = off')->execute();
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $tableName  The name of the database table to drop.
	 * @param   boolean  $ifExists   Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  boolean	true
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function dropTable($tableName, $ifExists = true)
	{
		$this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($tableName))->execute();

		return true;
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getCollation()
	{
		$this->setQuery('SHOW LC_COLLATE');
		$array = $this->loadAssocList();

		return $array[0]['lc_collate'];
	}

	/**
	 * Method to get the database connection collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database connection (string) or boolean false if not supported.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getConnectionCollation()
	{
		$this->setQuery('SHOW LC_COLLATE');
		$array = $this->loadAssocList();

		return $array[0]['lc_collate'];
	}

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * This is unsuported by PostgreSQL.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  string  An empty string because this function is not supported by PostgreSQL.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getTableCreate($tables)
	{
		return '';
	}

	/**
	 * Retrieves field information about a given table.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True to only return field types.
	 *
	 * @return  array  An array of fields for the database table.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getTableColumns($table, $typeOnly = true)
	{
		$this->connect();

		$result = array();

		$tableSub = $this->replacePrefix($table);

		$this->setQuery('
			SELECT a.attname AS "column_name",
				pg_catalog.format_type(a.atttypid, a.atttypmod) as "type",
				CASE WHEN a.attnotnull IS TRUE
					THEN \'NO\'
					ELSE \'YES\'
				END AS "null",
				CASE WHEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) IS NOT NULL
					THEN pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true)
				END as "Default",
				CASE WHEN pg_catalog.col_description(a.attrelid, a.attnum) IS NULL
				THEN \'\'
				ELSE pg_catalog.col_description(a.attrelid, a.attnum)
				END  AS "comments"
			FROM pg_catalog.pg_attribute a
			LEFT JOIN pg_catalog.pg_attrdef adef ON a.attrelid=adef.adrelid AND a.attnum=adef.adnum
			LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid
			WHERE a.attrelid =
				(SELECT oid FROM pg_catalog.pg_class WHERE relname=' . $this->quote($tableSub) . '
					AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE
					nspname = \'public\')
				)
			AND a.attnum > 0 AND NOT a.attisdropped
			ORDER BY a.attnum'
		);

		$fields = $this->loadObjectList();

		if ($typeOnly)
		{
			foreach ($fields as $field)
			{
				$result[$field->column_name] = preg_replace('/[(0-9)]/', '', $field->type);
			}
		}
		else
		{
			foreach ($fields as $field)
			{
				if (stristr(strtolower($field->type), 'character varying'))
				{
					$field->Default = '';
				}

				if (stristr(strtolower($field->type), 'text'))
				{
					$field->Default = '';
				}

				// Do some dirty translation to MySQL output.
				// @todo: Come up with and implement a standard across databases.
				$result[$field->column_name] = (object) array(
					'column_name' => $field->column_name,
					'type' => $field->type,
					'null' => $field->null,
					'Default' => $field->Default,
					'comments' => '',
					'Field' => $field->column_name,
					'Type' => $field->type,
					'Null' => $field->null,
					// @todo: Improve query above to return primary key info as well
					// 'Key' => ($field->PK == '1' ? 'PRI' : '')
				);
			}
		}

		// Change Postgresql's NULL::* type with PHP's null one
		foreach ($fields as $field)
		{
			if (preg_match('/^NULL::*/', $field->Default))
			{
				$field->Default = null;
			}
		}

		return $result;
	}

	/**
	 * Get the details list of keys for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of the column specification for the table.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getTableKeys($table)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList, true))
		{
			// Get the details columns information.
			$this->setQuery('
				SELECT indexname AS "idxName", indisprimary AS "isPrimary", indisunique  AS "isUnique",
					CASE WHEN indisprimary = true THEN
						( SELECT \'ALTER TABLE \' || tablename || \' ADD \' || pg_catalog.pg_get_constraintdef(const.oid, true)
							FROM pg_constraint AS const WHERE const.conname= pgClassFirst.relname )
					ELSE pg_catalog.pg_get_indexdef(indexrelid, 0, true)
					END AS "Query"
				FROM pg_indexes
				LEFT JOIN pg_class AS pgClassFirst ON indexname=pgClassFirst.relname
				LEFT JOIN pg_index AS pgIndex ON pgClassFirst.oid=pgIndex.indexrelid
				WHERE tablename=' . $this->quote($table) . ' ORDER BY indkey'
			);

			return $this->loadObjectList();
		}

		return false;
	}

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getTableList()
	{
		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type = ' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ')')
			->order('table_name ASC');

		$this->setQuery($query);

		return $this->loadColumn();
	}

	/**
	 * Get the details list of sequences for a table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array  An array of sequences specification for the table.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function getTableSequences($table)
	{
		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		if (in_array($table, $tableList, true))
		{
			$name = array(
				's.relname', 'n.nspname', 't.relname', 'a.attname', 'info.data_type',
				'info.minimum_value', 'info.maximum_value', 'info.increment', 'info.cycle_option'
			);

			$as = array('sequence', 'schema', 'table', 'column', 'data_type', 'minimum_value', 'maximum_value', 'increment', 'cycle_option');

			if (version_compare($this->getVersion(), '9.1.0') >= 0)
			{
				$name[] .= 'info.start_value';
				$as[] .= 'start_value';
			}

			// Get the details columns information.
			$query = $this->getQuery(true)
				->select($this->quoteName($name, $as))
				->from('pg_class AS s')
				->leftJoin("pg_depend d ON d.objid = s.oid AND d.classid = 'pg_class'::regclass AND d.refclassid = 'pg_class'::regclass")
				->leftJoin('pg_class t ON t.oid = d.refobjid')
				->leftJoin('pg_namespace n ON n.oid = t.relnamespace')
				->leftJoin('pg_attribute a ON a.attrelid = t.oid AND a.attnum = d.refobjsubid')
				->leftJoin('information_schema.sequences AS info ON info.sequence_name = s.relname')
				->where('s.relkind = ' . $this->quote('S') . ' AND d.deptype = ' . $this->quote('a') . ' AND t.relname = ' . $this->quote($table));
			$this->setQuery($query);

			return $this->loadObjectList();
		}

		return false;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriverPgsql  Returns this object to support chaining.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function lockTable($tableName)
	{
		$this->transactionStart();
		$this->setQuery('LOCK TABLE ' . $this->quoteName($tableName) . ' IN ACCESS EXCLUSIVE MODE')->execute();

		return $this;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Not used by PostgreSQL.
	 * @param   string  $prefix    Not used by PostgreSQL.
	 *
	 * @return  JDatabaseDriverPgsql  Returns this object to support chaining.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function renameTable($oldTable, $newTable, $backup = null, $prefix = null)
	{
		$this->connect();

		// To check if table exists and prevent SQL injection
		$tableList = $this->getTableList();

		// Origin Table does not exist
		if (!in_array($oldTable, $tableList, true))
		{
			// Origin Table not found
			throw new \RuntimeException('Table not found in Postgresql database.');
		}

		// Rename indexes
		$subQuery = $this->getQuery(true)
			->select('indexrelid')
			->from('pg_index, pg_class')
			->where('pg_class.relname = ' . $this->quote($oldTable))
			->where('pg_class.oid = pg_index.indrelid');

		$this->setQuery(
			$this->getQuery(true)
				->select('relname')
				->from('pg_class')
				->where('oid IN (' . (string) $subQuery . ')')
		);

		$oldIndexes = $this->loadColumn();

		foreach ($oldIndexes as $oldIndex)
		{
			$changedIdxName = str_replace($oldTable, $newTable, $oldIndex);
			$this->setQuery('ALTER INDEX ' . $this->escape($oldIndex) . ' RENAME TO ' . $this->escape($changedIdxName))->execute();
		}

		// Rename sequences
		$subQuery = $this->getQuery(true)
			->select('oid')
			->from('pg_namespace')
			->where('nspname NOT LIKE ' . $this->quote('pg_%'))
			->where('nspname != ' . $this->quote('information_schema'));

		$this->setQuery(
			$this->getQuery(true)
				->select('relname')
				->from('pg_class')
				->where('relkind = ' . $this->quote('S'))
				->where('relnamespace IN (' . (string) $subQuery . ')')
				->where('relname LIKE ' . $this->quote("%$oldTable%"))
		);

		$oldSequences = $this->loadColumn();

		foreach ($oldSequences as $oldSequence)
		{
			$changedSequenceName = str_replace($oldTable, $newTable, $oldSequence);
			$this->setQuery('ALTER SEQUENCE ' . $this->escape($oldSequence) . ' RENAME TO ' . $this->escape($changedSequenceName))->execute();
		}

		// Rename table
		$this->setQuery('ALTER TABLE ' . $this->escape($oldTable) . ' RENAME TO ' . $this->escape($newTable))->execute();

		return true;
	}

	/**
	 * This function return a field value as a prepared string to be used in a SQL statement.
	 *
	 * @param   array   $columns      Array of table's column returned by ::getTableColumns.
	 * @param   string  $field_name   The table field's name.
	 * @param   string  $field_value  The variable value to quote and return.
	 *
	 * @return  string  The quoted string.
	 *
	 * @since   3.9.0
	 */
	public function sqlValue($columns, $field_name, $field_value)
	{
		switch ($columns[$field_name])
		{
			case 'boolean':
				$val = 'NULL';

				if ($field_value === 't' || $field_value === true || $field_value === 1 || $field_value === '1')
				{
					$val = 'TRUE';
				}
				elseif ($field_value === 'f' || $field_value === false || $field_value === 0 || $field_value === '0')
				{
					$val = 'FALSE';
				}

				break;

			case 'bigint':
			case 'bigserial':
			case 'integer':
			case 'money':
			case 'numeric':
			case 'real':
			case 'smallint':
			case 'serial':
			case 'numeric,':
				$val = $field_value === '' ? 'NULL' : $field_value;
				break;

			case 'date':
			case 'timestamp without time zone':
				if (empty($field_value))
				{
					$field_value = $this->getNullDate();
				}

				$val = $this->quote($field_value);

				break;

			default:
				$val = $this->quote($field_value);
				break;
		}

		return $val;
	}

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function transactionCommit($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('COMMIT')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$this->transactionDepth--;
	}

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function transactionRollback($toSavepoint = false)
	{
		$this->connect();

		if (!$toSavepoint || $this->transactionDepth <= 1)
		{
			if ($this->setQuery('ROLLBACK')->execute())
			{
				$this->transactionDepth = 0;
			}

			return;
		}

		$savepoint = 'SP_' . ($this->transactionDepth - 1);
		$this->setQuery('ROLLBACK TO SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth--;
			$this->setQuery('RELEASE SAVEPOINT ' . $this->quoteName($savepoint))->execute();
		}
	}

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function transactionStart($asSavepoint = false)
	{
		$this->connect();

		if (!$asSavepoint || !$this->transactionDepth)
		{
			if ($this->setQuery('START TRANSACTION')->execute())
			{
				$this->transactionDepth = 1;
			}

			return;
		}

		$savepoint = 'SP_' . $this->transactionDepth;
		$this->setQuery('SAVEPOINT ' . $this->quoteName($savepoint));

		if ($this->execute())
		{
			$this->transactionDepth++;
		}
	}

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$columns = $this->getTableColumns($table);

		$fields = array();
		$values = array();

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Skip columns that don't exist in the table.
			if (!array_key_exists($k, $columns))
			{
				continue;
			}

			// Only process non-null scalars.
			if (is_array($v) || is_object($v) || $v === null)
			{
				continue;
			}

			// Ignore any internal fields or primary keys with value 0.
			if (($k[0] === '_') || ($k == $key && (($v === 0) || ($v === '0'))))
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->sqlValue($columns, $k, $v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true);

		$query->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		$retVal = false;

		if ($key)
		{
			$query->returning($key);

			// Set the query and execute the insert.
			$this->setQuery($query);

			$id = $this->loadResult();

			if ($id)
			{
				$object->$key = $id;
				$retVal = true;
			}
		}
		else
		{
			// Set the query and execute the insert.
			$this->setQuery($query);

			if ($this->execute())
			{
				$retVal = true;
			}
		}

		return $retVal;
	}

	/**
	 * Test to see if the PostgreSQL connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   3.9.0
	 */
	public static function isSupported()
	{
		return class_exists('PDO') && in_array('pgsql', PDO::getAvailableDrivers(), true);
	}

	/**
	 * Returns an array containing database's table list.
	 *
	 * @return  array  The database's table list.
	 *
	 * @since   3.9.0
	 */
	public function showTables()
	{
		$query = $this->getQuery(true)
			->select('table_name')
			->from('information_schema.tables')
			->where('table_type=' . $this->quote('BASE TABLE'))
			->where('table_schema NOT IN (' . $this->quote('pg_catalog') . ', ' . $this->quote('information_schema') . ' )');

		$this->setQuery($query);

		return $this->loadColumn();
	}

	/**
	 * Get the substring position inside a string
	 *
	 * @param   string  $substring  The string being sought
	 * @param   string  $string     The string/column being searched
	 *
	 * @return  integer  The position of $substring in $string
	 *
	 * @since   3.9.0
	 */
	public function getStringPositionSql($substring, $string)
	{
		$this->setQuery("SELECT POSITION($substring IN $string)");
		$position = $this->loadRow();

		return $position['position'];
	}

	/**
	 * Generate a random value
	 *
	 * @return  float  The random generated number
	 *
	 * @since   3.9.0
	 */
	public function getRandom()
	{
		$this->setQuery('SELECT RANDOM()');
		$random = $this->loadAssoc();

		return $random['random'];
	}

	/**
	 * Get the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   3.9.0
	 */
	public function getAlterDbCharacterSet($dbName)
	{
		return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' SET CLIENT_ENCODING TO ' . $this->quote('UTF8');
	}

	/**
	 * Get the query string to create new Database in correct PostgreSQL syntax.
	 *
	 * @param   object   $options  object coming from "initialise" function to pass user and database name to database driver.
	 * @param   boolean  $utf      True if the database supports the UTF-8 character set, not used in PostgreSQL "CREATE DATABASE" query.
	 *
	 * @return  string	The query that creates database, owned by $options['user']
	 *
	 * @since   3.9.0
	 */
	public function getCreateDbQuery($options, $utf)
	{
		$query = 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' OWNER ' . $this->quoteName($options->db_user);

		if ($utf)
		{
			$query .= ' ENCODING ' . $this->quote('UTF-8');
		}

		return $query;
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $sql     The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   3.9.0
	 */
	public function replacePrefix($sql, $prefix = '#__')
	{
		$sql = trim($sql);

		if (strpos($sql, '\''))
		{
			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($sql, 'currval'))
			{
				$sql = explode('currval', $sql);

				for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]);
				}

				$sql = implode('currval', $sql);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($sql, 'nextval'))
			{
				$sql = explode('nextval', $sql);

				for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]);
				}

				$sql = implode('nextval', $sql);
			}

			// Sequence name quoted with ' ' but need to be replaced
			if (strpos($sql, 'setval'))
			{
				$sql = explode('setval', $sql);

				for ($nIndex = 1, $nIndexMax = count($sql); $nIndex < $nIndexMax; $nIndex += 2)
				{
					$sql[$nIndex] = str_replace($prefix, $this->tablePrefix, $sql[$nIndex]);
				}

				$sql = implode('setval', $sql);
			}

			$explodedQuery = explode('\'', $sql);

			for ($nIndex = 0, $nIndexMax = count($explodedQuery); $nIndex < $nIndexMax; $nIndex += 2)
			{
				if (strpos($explodedQuery[$nIndex], $prefix))
				{
					$explodedQuery[$nIndex] = str_replace($prefix, $this->tablePrefix, $explodedQuery[$nIndex]);
				}
			}

			$replacedQuery = implode('\'', $explodedQuery);
		}
		else
		{
			$replacedQuery = str_replace($prefix, $this->tablePrefix, $sql);
		}

		return $replacedQuery;
	}

	/**
	 * Unlocks tables in the database, this command does not exist in PostgreSQL, it is automatically done on commit or rollback.
	 *
	 * @return  JDatabaseDriverPgsql  Returns this object to support chaining.
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function unlockTables()
	{
		$this->transactionCommit();

		return $this;
	}

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string   $table    The name of the database table to update.
	 * @param   object   &$object  A reference to an object whose public properties match the table fields.
	 * @param   array    $key      The name of the primary key.
	 * @param   boolean  $nulls    True to update null fields or false to ignore them.
	 *
	 * @return  boolean
	 *
	 * @since   3.9.0
	 * @throws  \RuntimeException
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$columns = $this->getTableColumns($table);
		$fields  = array();
		$where   = array();

		if (is_string($key))
		{
			$key = array($key);
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Skip columns that don't exist in the table.
			if (! array_key_exists($k, $columns))
			{
				continue;
			}

			// Only process scalars that are not internal fields.
			if (is_array($v) || is_object($v) || $k[0] === '_')
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key, true))
			{
				$key_val = $this->sqlValue($columns, $k, $v);
				$where[] = $this->quoteName($k) . '=' . $key_val;
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we do not want to update nulls then ignore this field.
				if (!$nulls)
				{
					continue;
				}

				// If the value is null and we want to update nulls then set it.
				$val = 'NULL';
			}
			else
			// The field is not null so we prep it for update.
			{
				$val = $this->sqlValue($columns, $k, $v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)));

		return $this->execute();
	}
}
joomla/database/iterator.php000064400000007230152177723700012150 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Driver Class
 *
 * @since  3.0.0
 */
abstract class JDatabaseIterator implements Countable, Iterator
{
	/**
	 * The database cursor.
	 *
	 * @var    mixed
	 * @since  3.0.0
	 */
	protected $cursor;

	/**
	 * The class of object to create.
	 *
	 * @var    string
	 * @since  3.0.0
	 */
	protected $class;

	/**
	 * The name of the column to use for the key of the database record.
	 *
	 * @var    mixed
	 * @since  3.0.0
	 */
	private $_column;

	/**
	 * The current database record.
	 *
	 * @var    mixed
	 * @since  3.0.0
	 */
	private $_current;

	/**
	 * A numeric or string key for the current database record.
	 *
	 * @var    int|string
	 * @since  3.0.0
	 */
	private $_key;

	/**
	 * The number of fetched records.
	 *
	 * @var    integer
	 * @since  3.0.0
	 */
	private $_fetched = 0;

	/**
	 * Database iterator constructor.
	 *
	 * @param   mixed   $cursor  The database cursor.
	 * @param   string  $column  An option column to use as the iterator key.
	 * @param   string  $class   The class of object that is returned.
	 *
	 * @throws  InvalidArgumentException
	 */
	public function __construct($cursor, $column = null, $class = 'stdClass')
	{
		if (!class_exists($class))
		{
			throw new InvalidArgumentException(sprintf('new %s(*%s*, cursor)', get_class($this), gettype($class)));
		}

		$this->cursor = $cursor;
		$this->class = $class;
		$this->_column = $column;
		$this->_fetched = 0;
		$this->next();
	}

	/**
	 * Database iterator destructor.
	 *
	 * @since   3.0.0
	 */
	public function __destruct()
	{
		if ($this->cursor)
		{
			$this->freeResult($this->cursor);
		}
	}

	/**
	 * The current element in the iterator.
	 *
	 * @return  object
	 *
	 * @see     Iterator::current()
	 * @since   3.0.0
	 */
	public function current()
	{
		return $this->_current;
	}

	/**
	 * The key of the current element in the iterator.
	 *
	 * @return  int|string
	 *
	 * @see     Iterator::key()
	 * @since   3.0.0
	 */
	public function key()
	{
		return $this->_key;
	}

	/**
	 * Moves forward to the next result from the SQL query.
	 *
	 * @return  void
	 *
	 * @see     Iterator::next()
	 * @since   3.0.0
	 */
	public function next()
	{
		// Set the default key as being the number of fetched object
		$this->_key = $this->_fetched;

		// Try to get an object
		$this->_current = $this->fetchObject();

		// If an object has been found
		if ($this->_current)
		{
			// Set the key as being the indexed column (if it exists)
			if (isset($this->_current->{$this->_column}))
			{
				$this->_key = $this->_current->{$this->_column};
			}

			// Update the number of fetched object
			$this->_fetched++;
		}
	}

	/**
	 * Rewinds the iterator.
	 *
	 * This iterator cannot be rewound.
	 *
	 * @return  void
	 *
	 * @see     Iterator::rewind()
	 * @since   3.0.0
	 */
	public function rewind()
	{
	}

	/**
	 * Checks if the current position of the iterator is valid.
	 *
	 * @return  boolean
	 *
	 * @see     Iterator::valid()
	 * @since   3.0.0
	 */
	public function valid()
	{
		return (boolean) $this->_current;
	}

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   3.0.0
	 */
	abstract protected function fetchObject();

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	abstract protected function freeResult();
}
joomla/database/importer.php000064400000011554152177723700012164 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Importer Class
 *
 * @since  3.0.0
 */
abstract class JDatabaseImporter
{
	/**
	 * @var    array  An array of cached data.
	 * @since  3.2.0
	 */
	protected $cache = array();

	/**
	 * The database connector to use for exporting structure and/or data.
	 *
	 * @var    JDatabaseDriver
	 * @since  3.2.0
	 */
	protected $db = null;

	/**
	 * The input source.
	 *
	 * @var    mixed
	 * @since  3.2.0
	 */
	protected $from = array();

	/**
	 * The type of input format (XML).
	 *
	 * @var    string
	 * @since  3.2.0
	 */
	protected $asFormat = 'xml';

	/**
	 * An array of options for the exporter.
	 *
	 * @var    object
	 * @since  3.2.0
	 */
	protected $options = null;

	/**
	 * Constructor.
	 *
	 * Sets up the default options for the exporter.
	 *
	 * @since   3.2.0
	 */
	public function __construct()
	{
		$this->options = new stdClass;

		$this->cache = array('columns' => array(), 'keys' => array());

		// Set up the class defaults:

		// Import with only structure
		$this->withStructure();

		// Export as XML.
		$this->asXml();

		// Default destination is a string using $output = (string) $exporter;
	}

	/**
	 * Set the output option for the exporter to XML format.
	 *
	 * @return  JDatabaseImporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function asXml()
	{
		$this->asFormat = 'xml';

		return $this;
	}

	/**
	 * Checks if all data and options are in order prior to exporting.
	 *
	 * @return  JDatabaseImporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 * @throws  Exception if an error is encountered.
	 */
	abstract public function check();

	/**
	 * Specifies the data source to import.
	 *
	 * @param   mixed  $from  The data source to import.
	 *
	 * @return  JDatabaseImporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function from($from)
	{
		$this->from = $from;

		return $this;
	}

	/**
	 * Get the SQL syntax to drop a column.
	 *
	 * @param   string  $table  The table name.
	 * @param   string  $name   The name of the field to drop.
	 *
	 * @return  string
	 *
	 * @since   3.2.0
	 */
	protected function getDropColumnSql($table, $name)
	{
		return 'ALTER TABLE ' . $this->db->quoteName($table) . ' DROP COLUMN ' . $this->db->quoteName($name);
	}

	/**
	 * Get the real name of the table, converting the prefix wildcard string if present.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  string	The real name of the table.
	 *
	 * @since   3.2.0
	 */
	protected function getRealTableName($table)
	{
		$prefix = $this->db->getPrefix();

		// Replace the magic prefix if found.
		$table = preg_replace('|^#__|', $prefix, $table);

		return $table;
	}

	/**
	 * Merges the incoming structure definition with the existing structure.
	 *
	 * @return  void
	 *
	 * @note    Currently only supports XML format.
	 * @since   3.2.0
	 * @throws  RuntimeException on error.
	 */
	public function mergeStructure()
	{
		$prefix = $this->db->getPrefix();
		$tables = $this->db->getTableList();

		if ($this->from instanceof SimpleXMLElement)
		{
			$xml = $this->from;
		}
		else
		{
			$xml = new SimpleXMLElement($this->from);
		}

		// Get all the table definitions.
		$xmlTables = $xml->xpath('database/table_structure');

		foreach ($xmlTables as $table)
		{
			// Convert the magic prefix into the real table name.
			$tableName = (string) $table['name'];
			$tableName = preg_replace('|^#__|', $prefix, $tableName);

			if (in_array($tableName, $tables))
			{
				// The table already exists. Now check if there is any difference.
				if ($queries = $this->getAlterTableSql($xml->database->table_structure))
				{
					// Run the queries to upgrade the data structure.
					foreach ($queries as $query)
					{
						$this->db->setQuery((string) $query);
						$this->db->execute();
					}
				}
			}
			else
			{
				// This is a new table.
				$sql = $this->xmlToCreate($table);

				$this->db->setQuery((string) $sql);
				$this->db->execute();
			}
		}
	}

	/**
	 * Sets the database connector to use for exporting structure and/or data.
	 *
	 * @param   JDatabaseDriver  $db  The database connector.
	 *
	 * @return  JDatabaseImporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function setDbo(JDatabaseDriver $db)
	{
		$this->db = $db;

		return $this;
	}

	/**
	 * Sets an internal option to merge the structure based on the input data.
	 *
	 * @param   boolean  $setting  True to export the structure, false to not.
	 *
	 * @return  JDatabaseImporter  Method supports chaining.
	 *
	 * @since   3.2.0
	 */
	public function withStructure($setting = true)
	{
		$this->options->withStructure = (boolean) $setting;

		return $this;
	}
}
joomla/database/factory.php000064400000012552152177723700011771 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Factory class
 *
 * @since  3.0.0
 */
class JDatabaseFactory
{
	/**
	 * Contains the current JDatabaseFactory instance
	 *
	 * @var    JDatabaseFactory
	 * @since  3.0.0
	 */
	private static $_instance = null;

	/**
	 * Method to return a JDatabaseDriver instance based on the given options. There are three global options and then
	 * the rest are specific to the database driver. The 'database' option determines which database is to
	 * be used for the connection. The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   string  $name     Name of the database driver you'd like to instantiate
	 * @param   array   $options  Parameters to be passed to the database driver.
	 *
	 * @return  JDatabaseDriver  A database driver object.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getDriver($name = 'mysqli', $options = array())
	{
		// Sanitize the database connector options.
		$options['driver']   = preg_replace('/[^A-Z0-9_\.-]/i', '', $name);
		$options['database'] = (isset($options['database'])) ? $options['database'] : null;
		$options['select']   = (isset($options['select'])) ? $options['select'] : true;

		// Derive the class name from the driver.
		$class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver']));

		// If the class still doesn't exist we have nothing left to do but throw an exception.  We did our best.
		if (!class_exists($class))
		{
			throw new JDatabaseExceptionUnsupported(sprintf('Unable to load Database Driver: %s', $options['driver']));
		}

		// Create our new JDatabaseDriver connector based on the options given.
		try
		{
			$instance = new $class($options);
		}
		catch (RuntimeException $e)
		{
			throw new JDatabaseExceptionConnecting(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
		}

		return $instance;
	}

	/**
	 * Gets an exporter class object.
	 *
	 * @param   string           $name  Name of the driver you want an exporter for.
	 * @param   JDatabaseDriver  $db    Optional JDatabaseDriver instance
	 *
	 * @return  JDatabaseExporter  An exporter object.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getExporter($name, JDatabaseDriver $db = null)
	{
		// Derive the class name from the driver.
		$class = 'JDatabaseExporter' . ucfirst(strtolower($name));

		// Make sure we have an exporter class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported('Database Exporter not found.');
		}

		$o = new $class;

		if ($db instanceof JDatabaseDriver)
		{
			$o->setDbo($db);
		}

		return $o;
	}

	/**
	 * Gets an importer class object.
	 *
	 * @param   string           $name  Name of the driver you want an importer for.
	 * @param   JDatabaseDriver  $db    Optional JDatabaseDriver instance
	 *
	 * @return  JDatabaseImporter  An importer object.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getImporter($name, JDatabaseDriver $db = null)
	{
		// Derive the class name from the driver.
		$class = 'JDatabaseImporter' . ucfirst(strtolower($name));

		// Make sure we have an importer class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported('Database importer not found.');
		}

		$o = new $class;

		if ($db instanceof JDatabaseDriver)
		{
			$o->setDbo($db);
		}

		return $o;
	}

	/**
	 * Gets an instance of the factory object.
	 *
	 * @return  JDatabaseFactory
	 *
	 * @since   3.0.0
	 */
	public static function getInstance()
	{
		return self::$_instance ? self::$_instance : new JDatabaseFactory;
	}

	/**
	 * Get the current query object or a new JDatabaseQuery object.
	 *
	 * @param   string           $name  Name of the driver you want an query object for.
	 * @param   JDatabaseDriver  $db    Optional JDatabaseDriver instance
	 *
	 * @return  JDatabaseQuery  The current query object or a new object extending the JDatabaseQuery class.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getQuery($name, JDatabaseDriver $db = null)
	{
		// Derive the class name from the driver.
		$class = 'JDatabaseQuery' . ucfirst(strtolower($name));

		// Make sure we have a query class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported('Database Query class not found');
		}

		return new $class($db);
	}

	/**
	 * Gets an instance of a factory object to return on subsequent calls of getInstance.
	 *
	 * @param   JDatabaseFactory  $instance  A JDatabaseFactory object.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public static function setInstance(JDatabaseFactory $instance = null)
	{
		self::$_instance = $instance;
	}
}
joomla/database/database.php000064400000012001152177723700012053 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Database connector class.
 *
 * @since       1.7.0
 * @deprecated  4.0
 */
abstract class JDatabase
{
	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 * @deprecated  4.0
	 */
	public function query()
	{
		JLog::add('JDatabase::query() is deprecated, use JDatabaseDriver::execute() instead.', JLog::WARNING, 'deprecated');

		return $this->execute();
	}

	/**
	 * Get a list of available database connectors.  The list will only be populated with connectors that both
	 * the class exists and the static test method returns true.  This gives us the ability to have a multitude
	 * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
	 *
	 * @return  array  An array of available database connectors.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public static function getConnectors()
	{
		JLog::add('JDatabase::getConnectors() is deprecated, use JDatabaseDriver::getConnectors() instead.', JLog::WARNING, 'deprecated');

		return JDatabaseDriver::getConnectors();
	}

	/**
	 * Gets the error message from the database connection.
	 *
	 * @param   boolean  $escaped  True to escape the message string for use in JavaScript.
	 *
	 * @return  string  The error message for the most recent query.
	 *
	 * @deprecated  4.0
	 * @since   1.7.0
	 */
	public function getErrorMsg($escaped = false)
	{
		JLog::add('JDatabase::getErrorMsg() is deprecated, use exception handling instead.', JLog::WARNING, 'deprecated');

		if ($escaped)
		{
			return addslashes($this->errorMsg);
		}
		else
		{
			return $this->errorMsg;
		}
	}

	/**
	 * Gets the error number from the database connection.
	 *
	 * @return      integer  The error number for the most recent query.
	 *
	 * @since       1.7.0
	 * @deprecated  4.0
	 */
	public function getErrorNum()
	{
		JLog::add('JDatabase::getErrorNum() is deprecated, use exception handling instead.', JLog::WARNING, 'deprecated');

		return $this->errorNum;
	}

	/**
	 * Method to return a JDatabaseDriver instance based on the given options.  There are three global options and then
	 * the rest are specific to the database driver.  The 'driver' option defines which JDatabaseDriver class is
	 * used for the connection -- the default is 'mysqli'.  The 'database' option determines which database is to
	 * be used for the connection.  The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   array  $options  Parameters to be passed to the database driver.
	 *
	 * @return  JDatabaseDriver  A database object.
	 *
	 * @since       1.7.0
	 * @deprecated  4.0
	 */
	public static function getInstance($options = array())
	{
		JLog::add('JDatabase::getInstance() is deprecated, use JDatabaseDriver::getInstance() instead.', JLog::WARNING, 'deprecated');

		return JDatabaseDriver::getInstance($options);
	}

	/**
	 * Splits a string of multiple queries into an array of individual queries.
	 *
	 * @param   string  $query  Input SQL string with which to split into individual queries.
	 *
	 * @return  array  The queries from the input string separated into an array.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public static function splitSql($query)
	{
		JLog::add('JDatabase::splitSql() is deprecated, use JDatabaseDriver::splitSql() instead.', JLog::WARNING, 'deprecated');

		return JDatabaseDriver::splitSql($query);
	}

	/**
	 * Return the most recent error message for the database connector.
	 *
	 * @param   boolean  $showSQL  True to display the SQL statement sent to the database as well as the error.
	 *
	 * @return  string  The error message for the most recent query.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0
	 */
	public function stderr($showSQL = false)
	{
		JLog::add('JDatabase::stderr() is deprecated.', JLog::WARNING, 'deprecated');

		if ($this->errorNum != 0)
		{
			return JText::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $this->errorNum, $this->errorMsg)
			. ($showSQL ? "<br />SQL = <pre>$this->sql</pre>" : '');
		}
		else
		{
			return JText::_('JLIB_DATABASE_FUNCTION_NOERROR');
		}
	}

	/**
	 * Test to see if the connector is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0 - Use JDatabaseDriver::isSupported() instead.
	 */
	public static function test()
	{
		JLog::add('JDatabase::test() is deprecated. Use JDatabaseDriver::isSupported() instead.', JLog::WARNING, 'deprecated');

		return static::isSupported();
	}
}
joomla/database/driver.php000064400000156333152177723700011623 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Database
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Joomla Platform Database Driver Class
 *
 * @since  3.0.0
 *
 * @method   string|array  q()   q($text, $escape = true)  Alias for quote method
 * @method   string|array  qn()  qn($name, $as = null)     Alias for quoteName method
 */
abstract class JDatabaseDriver extends JDatabase implements JDatabaseInterface
{
	/**
	 * The name of the database.
	 *
	 * @var    string
	 * @since  2.5.0
	 */
	private $_database;

	/**
	 * The name of the database driver.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $name;

	/**
	 * The type of the database server family supported by this driver. Examples: mysql, oracle, postgresql, mssql,
	 * sqlite.
	 *
	 * @var    string
	 * @since  CMS 3.5.0
	 */
	public $serverType;

	/**
	 * @var    resource  The database connection resource.
	 * @since  1.7.0
	 */
	protected $connection;

	/**
	 * @var    integer  The number of SQL statements executed by the database driver.
	 * @since  1.7.0
	 */
	protected $count = 0;

	/**
	 * @var    resource  The database connection cursor from the last query.
	 * @since  1.7.0
	 */
	protected $cursor;

	/**
	 * @var    boolean  The database driver debugging state.
	 * @since  1.7.0
	 */
	protected $debug = false;

	/**
	 * @var    integer  The affected row limit for the current SQL statement.
	 * @since  1.7.0
	 */
	protected $limit = 0;

	/**
	 * @var    array  The log of executed SQL statements by the database driver.
	 * @since  1.7.0
	 */
	protected $log = array();

	/**
	 * @var    array  The log of executed SQL statements timings (start and stop microtimes) by the database driver.
	 * @since  CMS 3.1.2
	 */
	protected $timings = array();

	/**
	 * @var    array  The log of executed SQL statements timings (start and stop microtimes) by the database driver.
	 * @since  CMS 3.1.2
	 */
	protected $callStacks = array();

	/**
	 * @var    string  The character(s) used to quote SQL statement names such as table names or field names,
	 *                 etc.  The child classes should define this as necessary.  If a single character string the
	 *                 same character is used for both sides of the quoted name, else the first character will be
	 *                 used for the opening quote and the second for the closing quote.
	 * @since  1.7.0
	 */
	protected $nameQuote;

	/**
	 * @var    string  The null or zero representation of a timestamp for the database driver.  This should be
	 *                 defined in child classes to hold the appropriate value for the engine.
	 * @since  1.7.0
	 */
	protected $nullDate;

	/**
	 * @var    integer  The affected row offset to apply for the current SQL statement.
	 * @since  1.7.0
	 */
	protected $offset = 0;

	/**
	 * @var    array  Passed in upon instantiation and saved.
	 * @since  1.7.0
	 */
	protected $options;

	/**
	 * @var    JDatabaseQuery|string  The current SQL statement to execute.
	 * @since  1.7.0
	 */
	protected $sql;

	/**
	 * @var    string  The common database table prefix.
	 * @since  1.7.0
	 */
	protected $tablePrefix;

	/**
	 * @var    boolean  True if the database engine supports UTF-8 character encoding.
	 * @since  1.7.0
	 */
	protected $utf = true;

	/**
	 * @var    boolean  True if the database engine supports UTF-8 Multibyte (utf8mb4) character encoding.
	 * @since  CMS 3.5.0
	 */
	protected $utf8mb4 = false;

	/**
	 * @var         integer  The database error number
	 * @since       1.7.0
	 * @deprecated  3.0.0
	 */
	protected $errorNum = 0;

	/**
	 * @var         string  The database error message
	 * @since       1.7.0
	 * @deprecated  3.0.0
	 */
	protected $errorMsg;

	/**
	 * @var    array  JDatabaseDriver instances container.
	 * @since  1.7.0
	 */
	protected static $instances = array();

	/**
	 * @var    string  The minimum supported database version.
	 * @since  3.0.0
	 */
	protected static $dbMinimum;

	/**
	 * @var    integer  The depth of the current transaction.
	 * @since  3.1.4
	 */
	protected $transactionDepth = 0;

	/**
	 * @var    callable[]  List of callables to call just before disconnecting database
	 * @since  CMS 3.1.2
	 */
	protected $disconnectHandlers = array();

	/**
	 * Get a list of available database connectors.  The list will only be populated with connectors that both
	 * the class exists and the static test method returns true.  This gives us the ability to have a multitude
	 * of connector classes that are self-aware as to whether or not they are able to be used on a given system.
	 *
	 * @return  array  An array of available database connectors.
	 *
	 * @since   1.7.0
	 */
	public static function getConnectors()
	{
		$connectors = array();

		// Get an iterator and loop trough the driver classes.
		$iterator = new DirectoryIterator(__DIR__ . '/driver');

		/* @type  $file  DirectoryIterator */
		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Derive the class name from the type.
			$class = str_ireplace('.php', '', 'JDatabaseDriver' . ucfirst(trim($fileName)));

			// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
			if (!class_exists($class))
			{
				continue;
			}

			// Sweet!  Our class exists, so now we just need to know if it passes its test method.
			if ($class::isSupported())
			{
				// Connector names should not have file extensions.
				$connectors[] = str_ireplace('.php', '', $fileName);
			}
		}

		return $connectors;
	}

	/**
	 * Method to return a JDatabaseDriver instance based on the given options.  There are three global options and then
	 * the rest are specific to the database driver.  The 'driver' option defines which JDatabaseDriver class is
	 * used for the connection -- the default is 'mysqli'.  The 'database' option determines which database is to
	 * be used for the connection.  The 'select' option determines whether the connector should automatically select
	 * the chosen database.
	 *
	 * Instances are unique to the given options and new objects are only created when a unique options array is
	 * passed into the method.  This ensures that we don't end up with unnecessary database connection resources.
	 *
	 * @param   array  $options  Parameters to be passed to the database driver.
	 *
	 * @return  JDatabaseDriver  A database object.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public static function getInstance($options = array())
	{
		// Sanitize the database connector options.
		$options['driver']   = (isset($options['driver'])) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli';
		$options['database'] = (isset($options['database'])) ? $options['database'] : null;
		$options['select']   = (isset($options['select'])) ? $options['select'] : true;

		// If the selected driver is `mysql` and we are on PHP 7 or greater, switch to the `mysqli` driver.
		if ($options['driver'] === 'mysql' && PHP_MAJOR_VERSION >= 7)
		{
			// Check if we have support for the other MySQL drivers
			$mysqliSupported   = JDatabaseDriverMysqli::isSupported();
			$pdoMysqlSupported = JDatabaseDriverPdomysql::isSupported();

			// If neither is supported, then the user cannot use MySQL; throw an exception
			if (!$mysqliSupported && !$pdoMysqlSupported)
			{
				throw new JDatabaseExceptionUnsupported(
					'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.'
					. ' Also, this system does not support MySQLi or PDO MySQL.  Cannot instantiate database driver.'
				);
			}

			// Prefer MySQLi as it is a closer replacement for the removed MySQL driver, otherwise use the PDO driver
			if ($mysqliSupported)
			{
				JLog::add(
					'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.  Trying `mysqli` instead.',
					JLog::WARNING,
					'deprecated'
				);

				$options['driver'] = 'mysqli';
			}
			else
			{
				JLog::add(
					'The PHP `ext/mysql` extension is removed in PHP 7, cannot use the `mysql` driver.  Trying `pdomysql` instead.',
					JLog::WARNING,
					'deprecated'
				);

				$options['driver'] = 'pdomysql';
			}
		}

		// Get the options signature for the database connector.
		$signature = md5(serialize($options));

		// If we already have a database connector instance for these options then just use that.
		if (empty(self::$instances[$signature]))
		{
			// Derive the class name from the driver.
			$class = 'JDatabaseDriver' . ucfirst(strtolower($options['driver']));

			// If the class still doesn't exist we have nothing left to do but throw an exception.  We did our best.
			if (!class_exists($class))
			{
				throw new JDatabaseExceptionUnsupported(sprintf('Unable to load Database Driver: %s', $options['driver']));
			}

			// Create our new JDatabaseDriver connector based on the options given.
			try
			{
				$instance = new $class($options);
			}
			catch (RuntimeException $e)
			{
				throw new JDatabaseExceptionConnecting(sprintf('Unable to connect to the Database: %s', $e->getMessage()), $e->getCode(), $e);
			}

			// Set the new connector to the global instances based on signature.
			self::$instances[$signature] = $instance;
		}

		return self::$instances[$signature];
	}

	/**
	 * Splits a string of multiple queries into an array of individual queries.
	 * Single line or line end comments and multi line comments are stripped off.
	 *
	 * @param   string  $sql  Input SQL string with which to split into individual queries.
	 *
	 * @return  array  The queries from the input string separated into an array.
	 *
	 * @since   1.7.0
	 */
	public static function splitSql($sql)
	{
		$start = 0;
		$open = false;
		$comment = false;
		$endString = '';
		$end = strlen($sql);
		$queries = array();
		$query = '';

		for ($i = 0; $i < $end; $i++)
		{
			$current = substr($sql, $i, 1);
			$current2 = substr($sql, $i, 2);
			$current3 = substr($sql, $i, 3);
			$lenEndString = strlen($endString);
			$testEnd = substr($sql, $i, $lenEndString);

			if ($current == '"' || $current == "'" || $current2 == '--'
				|| ($current2 == '/*' && $current3 != '/*!' && $current3 != '/*+')
				|| ($current == '#' && $current3 != '#__')
				|| ($comment && $testEnd == $endString))
			{
				// Check if quoted with previous backslash
				$n = 2;

				while (substr($sql, $i - $n + 1, 1) == '\\' && $n < $i)
				{
					$n++;
				}

				// Not quoted
				if ($n % 2 == 0)
				{
					if ($open)
					{
						if ($testEnd == $endString)
						{
							if ($comment)
							{
								$comment = false;
								if ($lenEndString > 1)
								{
									$i += ($lenEndString - 1);
									$current = substr($sql, $i, 1);
								}
								$start = $i + 1;
							}
							$open = false;
							$endString = '';
						}
					}
					else
					{
						$open = true;
						if ($current2 == '--')
						{
							$endString = "\n";
							$comment = true;
						}
						elseif ($current2 == '/*')
						{
							$endString = '*/';
							$comment = true;
						}
						elseif ($current == '#')
						{
							$endString = "\n";
							$comment = true;
						}
						else
						{
							$endString = $current;
						}
						if ($comment && $start < $i)
						{
							$query = $query . substr($sql, $start, ($i - $start));
						}
					}
				}
			}

			if ($comment)
			{
				$start = $i + 1;
			}

			if (($current == ';' && !$open) || $i == $end - 1)
			{
				if ($start <= $i)
				{
					$query = $query . substr($sql, $start, ($i - $start + 1));
				}
				$query = trim($query);

				if ($query)
				{
					if (($i == $end - 1) && ($current != ';'))
					{
						$query = $query . ';';
					}
					$queries[] = $query;
				}

				$query = '';
				$start = $i + 1;
			}
		}

		return $queries;
	}

	/**
	 * Magic method to provide method alias support for quote() and quoteName().
	 *
	 * @param   string  $method  The called method.
	 * @param   array   $args    The array of arguments passed to the method.
	 *
	 * @return  mixed  The aliased method's return value or null.
	 *
	 * @since   1.7.0
	 */
	public function __call($method, $args)
	{
		if (empty($args))
		{
			return;
		}

		switch ($method)
		{
			case 'q':
				return $this->quote($args[0], isset($args[1]) ? $args[1] : true);
				break;
			case 'qn':
				return $this->quoteName($args[0], isset($args[1]) ? $args[1] : null);
				break;
		}
	}

	/**
	 * Constructor.
	 *
	 * @param   array  $options  List of options used to configure the connection
	 *
	 * @since   1.7.0
	 */
	public function __construct($options)
	{
		// Initialise object variables.
		$this->_database = (isset($options['database'])) ? $options['database'] : '';

		$this->tablePrefix = (isset($options['prefix'])) ? $options['prefix'] : 'jos_';
		$this->count = 0;
		$this->errorNum = 0;
		$this->log = array();

		// Set class options.
		$this->options = $options;
	}

	/**
	 * Alter database's character set, obtaining query string from protected member.
	 *
	 * @param   string  $dbName  The database name that will be altered
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function alterDbCharacterSet($dbName)
	{
		if (is_null($dbName))
		{
			throw new RuntimeException('Database name must not be null.');
		}

		$this->setQuery($this->getAlterDbCharacterSet($dbName));

		return $this->execute();
	}

	/**
	 * Alter a table's character set, obtaining an array of queries to do so from a protected method. The conversion is
	 * wrapped in a transaction, if supported by the database driver. Otherwise the table will be locked before the
	 * conversion. This prevents data corruption.
	 *
	 * @param   string   $tableName  The name of the table to alter
	 * @param   boolean  $rethrow    True to rethrow database exceptions. Default: false (exceptions are suppressed)
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   CMS 3.5.0
	 * @throws  RuntimeException  If the table name is empty
	 * @throws  Exception  Relayed from the database layer if a database error occurs and $rethrow == true
	 */
	public function alterTableCharacterSet($tableName, $rethrow = false)
	{
		if (is_null($tableName))
		{
			throw new RuntimeException('Table name must not be null.');
		}

		$queries = $this->getAlterTableCharacterSet($tableName);

		if (empty($queries))
		{
			return false;
		}

		$hasTransaction = true;

		try
		{
			$this->transactionStart();
		}
		catch (Exception $e)
		{
			$hasTransaction = false;
			$this->lockTable($tableName);
		}

		foreach ($queries as $query)
		{
			try
			{
				$this->setQuery($query)->execute();
			}
			catch (Exception $e)
			{
				if ($hasTransaction)
				{
					$this->transactionRollback();
				}
				else
				{
					$this->unlockTables();
				}

				if ($rethrow)
				{
					throw $e;
				}

				return false;
			}
		}

		if ($hasTransaction)
		{
			try
			{
				$this->transactionCommit();
			}
			catch (Exception $e)
			{
				$this->transactionRollback();

				if ($rethrow)
				{
					throw $e;
				}

				return false;
			}
		}
		else
		{
			$this->unlockTables();
		}

		return true;
	}

	/**
	 * Connects to the database if needed.
	 *
	 * @return  void  Returns void if the database connected successfully.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	abstract public function connect();

	/**
	 * Determines if the connection to the server is active.
	 *
	 * @return  boolean  True if connected to the database engine.
	 *
	 * @since   1.7.0
	 */
	abstract public function connected();

	/**
	 * Create a new database using information from $options object, obtaining query string
	 * from protected member.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 * 									This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 * @throws  RuntimeException
	 */
	public function createDatabase($options, $utf = true)
	{
		if (is_null($options))
		{
			throw new RuntimeException('$options object must not be null.');
		}
		elseif (empty($options->db_name))
		{
			throw new RuntimeException('$options object must have db_name set.');
		}
		elseif (empty($options->db_user))
		{
			throw new RuntimeException('$options object must have db_user set.');
		}

		$this->setQuery($this->getCreateDatabaseQuery($options, $utf));

		return $this->execute();
	}

	/**
	 * Destructor.
	 *
	 * @since   3.8.0
	 */
	public function __destruct()
	{
		$this->disconnect();
	}

	/**
	 * Disconnects the database.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	abstract public function disconnect();

	/**
	 * Adds a function callable just before disconnecting the database. Parameter of the callable is $this JDatabaseDriver
	 *
	 * @param   callable  $callable  Function to call in disconnect() method just before disconnecting from database
	 *
	 * @return  void
	 *
	 * @since   CMS 3.1.2
	 */
	public function addDisconnectHandler($callable)
	{
		$this->disconnectHandlers[] = $callable;
	}

	/**
	 * Drops a table from the database.
	 *
	 * @param   string   $table     The name of the database table to drop.
	 * @param   boolean  $ifExists  Optionally specify that the table must exist before it is dropped.
	 *
	 * @return  JDatabaseDriver     Returns this object to support chaining.
	 *
	 * @since   2.5.0
	 * @throws  RuntimeException
	 */
	abstract public function dropTable($table, $ifExists = true);

	/**
	 * Escapes a string for usage in an SQL statement.
	 *
	 * @param   string   $text   The string to be escaped.
	 * @param   boolean  $extra  Optional parameter to provide extra escaping.
	 *
	 * @return  string   The escaped string.
	 *
	 * @since   1.7.0
	 */
	abstract public function escape($text, $extra = false);

	/**
	 * Method to fetch a row from the result set cursor as an array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   1.7.0
	 */
	abstract protected function fetchArray($cursor = null);

	/**
	 * Method to fetch a row from the result set cursor as an associative array.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  mixed  Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   1.7.0
	 */
	abstract protected function fetchAssoc($cursor = null);

	/**
	 * Method to fetch a row from the result set cursor as an object.
	 *
	 * @param   mixed   $cursor  The optional result set cursor from which to fetch the row.
	 * @param   string  $class   The class name to use for the returned row object.
	 *
	 * @return  mixed   Either the next row from the result set or false if there are no more rows.
	 *
	 * @since   1.7.0
	 */
	abstract protected function fetchObject($cursor = null, $class = 'stdClass');

	/**
	 * Method to free up the memory used for the result set.
	 *
	 * @param   mixed  $cursor  The optional result set cursor from which to fetch the row.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 */
	abstract protected function freeResult($cursor = null);

	/**
	 * Get the number of affected rows for the previous executed SQL statement.
	 *
	 * @return  integer  The number of affected rows.
	 *
	 * @since   1.7.0
	 */
	abstract public function getAffectedRows();

	/**
	 * Return the query string to alter the database character set.
	 *
	 * @param   string  $dbName  The database name
	 *
	 * @return  string  The query that alter the database query string
	 *
	 * @since   3.0.1
	 */
	public function getAlterDbCharacterSet($dbName)
	{
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';

		return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET `' . $charset . '`';
	}

	/**
	 * Get the query strings to alter the character set and collation of a table.
	 *
	 * @param   string  $tableName  The name of the table
	 *
	 * @return  string[]  The queries required to alter the table's character set and collation
	 *
	 * @since   CMS 3.5.0
	 */
	public function getAlterTableCharacterSet($tableName)
	{
		$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
		$collation = $charset . '_unicode_ci';

		$quotedTableName = $this->quoteName($tableName);

		$queries = array();
		$queries[] = "ALTER TABLE $quotedTableName CONVERT TO CHARACTER SET $charset COLLATE $collation";

		/**
		 * We also need to convert each text column, modifying their character set and collation. This allows us to
		 * change, for example, a utf8_bin collated column to a utf8mb4_bin collated column.
		 */
		$sql = "SHOW FULL COLUMNS FROM $quotedTableName";
		$this->setQuery($sql);
		$columns = $this->loadAssocList();
		$columnMods = array();

		if (is_array($columns))
		{
			foreach ($columns as $column)
			{
				// Make sure we are redefining only columns which do support a collation
				$col = (object) $column;

				if (empty($col->Collation))
				{
					continue;
				}

				// Default new collation: utf8_unicode_ci or utf8mb4_unicode_ci
				$newCollation = $charset . '_unicode_ci';
				$collationParts = explode('_', $col->Collation);

				/**
				 * If the collation is in the form charset_collationType_ci or charset_collationType we have to change
				 * the charset but leave the collationType intact (e.g. utf8_bin must become utf8mb4_bin, NOT
				 * utf8mb4_general_ci).
				 */
				if (count($collationParts) >= 2)
				{
					$ci = array_pop($collationParts);
					$collationType = array_pop($collationParts);
					$newCollation = $charset . '_' . $collationType . '_' . $ci;

					/**
					 * When the last part of the old collation is not _ci we have a charset_collationType format,
					 * something like utf8_bin. Therefore the new collation only has *two* parts.
					 */
					if ($ci != 'ci')
					{
						$newCollation = $charset . '_' . $ci;
					}
				}

				// If the old and new collation is the same we don't have to change the collation type
				if (strtolower($newCollation) == strtolower($col->Collation))
				{
					continue;
				}

				$null = $col->Null == 'YES' ? 'NULL' : 'NOT NULL';
				$default = is_null($col->Default) ? '' : "DEFAULT '" . $this->q($col->Default) . "'";
				$columnMods[] = "MODIFY COLUMN `{$col->Field}` {$col->Type} CHARACTER SET $charset COLLATE $newCollation $null $default";
			}
		}

		if (count($columnMods))
		{
			$queries[] = "ALTER TABLE $quotedTableName " .
				implode(',', $columnMods) .
				" CHARACTER SET $charset COLLATE $collation";
		}

		return $queries;
	}

	/**
	 * Automatically downgrade a CREATE TABLE or ALTER TABLE query from utf8mb4 (UTF-8 Multibyte) to plain utf8. Used
	 * when the server doesn't support UTF-8 Multibyte.
	 *
	 * @param   string  $query  The query to convert
	 *
	 * @return  string  The converted query
	 */
	public function convertUtf8mb4QueryToUtf8($query)
	{
		if ($this->hasUTF8mb4Support())
		{
			return $query;
		}

		// If it's not an ALTER TABLE or CREATE TABLE command there's nothing to convert
		if (!preg_match('/^(ALTER|CREATE)\s+TABLE\s+/i', $query))
		{
			return $query;
		}

		// Don't do preg replacement if string does not exist
		if (stripos($query, 'utf8mb4') === false)
		{
			return $query;
		}

		// Replace utf8mb4 with utf8 if not within single or double quotes or name quotes
		return preg_replace('/[`"\'][^`"\']*[`"\'](*SKIP)(*FAIL)|utf8mb4/i', 'utf8', $query);
	}

	/**
	 * Return the query string to create new Database.
	 * Each database driver, other than MySQL, need to override this member to return correct string.
	 *
	 * @param   stdClass  $options  Object used to pass user and database name to database driver.
	 *                   This object must have "db_name" and "db_user" set.
	 * @param   boolean   $utf      True if the database supports the UTF-8 character set.
	 *
	 * @return  string  The query that creates database
	 *
	 * @since   3.0.1
	 */
	protected function getCreateDatabaseQuery($options, $utf)
	{
		if ($utf)
		{
			$charset = $this->utf8mb4 ? 'utf8mb4' : 'utf8';
			$collation = $charset . '_unicode_ci';

			return 'CREATE DATABASE ' . $this->quoteName($options->db_name) . ' CHARACTER SET `' . $charset . '` COLLATE `' . $collation . '`';
		}

		return 'CREATE DATABASE ' . $this->quoteName($options->db_name);
	}

	/**
	 * Method to get the database collation in use by sampling a text field of a table in the database.
	 *
	 * @return  mixed  The collation in use by the database or boolean false if not supported.
	 *
	 * @since   1.7.0
	 */
	abstract public function getCollation();

	/**
	 * Method to get the database connection collation, as reported by the driver. If the connector doesn't support
	 * reporting this value please return an empty string.
	 *
	 * @return  string
	 */
	public function getConnectionCollation()
	{
		return '';
	}

	/**
	 * Method that provides access to the underlying database connection. Useful for when you need to call a
	 * proprietary method such as postgresql's lo_* methods.
	 *
	 * @return  resource  The underlying database connection resource.
	 *
	 * @since   1.7.0
	 */
	public function getConnection()
	{
		return $this->connection;
	}

	/**
	 * Get the total number of SQL statements executed by the database driver.
	 *
	 * @return  integer
	 *
	 * @since   1.7.0
	 */
	public function getCount()
	{
		return $this->count;
	}

	/**
	 * Gets the name of the database used by this conneciton.
	 *
	 * @return  string
	 *
	 * @since   2.5.0
	 */
	protected function getDatabase()
	{
		return $this->_database;
	}

	/**
	 * Returns a PHP date() function compliant date format for the database driver.
	 *
	 * @return  string  The format string.
	 *
	 * @since   1.7.0
	 */
	public function getDateFormat()
	{
		return 'Y-m-d H:i:s';
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   1.7.0
	 */
	public function getLog()
	{
		return $this->log;
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   CMS 3.1.2
	 */
	public function getTimings()
	{
		return $this->timings;
	}

	/**
	 * Get the database driver SQL statement log.
	 *
	 * @return  array  SQL statements executed by the database driver.
	 *
	 * @since   CMS 3.1.2
	 */
	public function getCallStacks()
	{
		return $this->callStacks;
	}

	/**
	 * Get the minimum supported database version.
	 *
	 * @return  string  The minimum version number for the database driver.
	 *
	 * @since   3.0.0
	 */
	public function getMinimum()
	{
		return static::$dbMinimum;
	}

	/**
	 * Get the null or zero representation of a timestamp for the database driver.
	 *
	 * @return  string  Null or zero representation of a timestamp.
	 *
	 * @since   1.7.0
	 */
	public function getNullDate()
	{
		return $this->nullDate;
	}

	/**
	 * Get the number of returned rows for the previous executed SQL statement.
	 *
	 * @param   resource  $cursor  An optional database cursor resource to extract the row count from.
	 *
	 * @return  integer   The number of returned rows.
	 *
	 * @since   1.7.0
	 */
	abstract public function getNumRows($cursor = null);

	/**
	 * Get the common table prefix for the database driver.
	 *
	 * @return  string  The common database table prefix.
	 *
	 * @since   1.7.0
	 */
	public function getPrefix()
	{
		return $this->tablePrefix;
	}

	/**
	 * Gets an exporter class object.
	 *
	 * @return  JDatabaseExporter  An exporter object.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getExporter()
	{
		// Derive the class name from the driver.
		$class = 'JDatabaseExporter' . ucfirst($this->name);

		// Make sure we have an exporter class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported('Database Exporter not found.');
		}

		$o = new $class;
		$o->setDbo($this);

		return $o;
	}

	/**
	 * Gets an importer class object.
	 *
	 * @return  JDatabaseImporter  An importer object.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getImporter()
	{
		// Derive the class name from the driver.
		$class = 'JDatabaseImporter' . ucfirst($this->name);

		// Make sure we have an importer class for this driver.
		if (!class_exists($class))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported('Database Importer not found');
		}

		$o = new $class;
		$o->setDbo($this);

		return $o;
	}

	/**
	 * Get the name of the database driver. If $this->name is not set it will try guessing the driver name from the
	 * class name.
	 *
	 * @return  string
	 *
	 * @since   CMS 3.5.0
	 */
	public function getName()
	{
		if (empty($this->name))
		{
			$className = get_class($this);
			$className = str_replace('JDatabaseDriver', '', $className);
			$this->name = strtolower($className);
		}

		return $this->name;
	}

	/**
	 * Get the server family type, e.g. mysql, postgresql, oracle, sqlite, mssql. If $this->serverType is not set it
	 * will attempt guessing the server family type from the driver name. If this is not possible the driver name will
	 * be returned instead.
	 *
	 * @return  string
	 *
	 * @since   CMS 3.5.0
	 */
	public function getServerType()
	{
		if (empty($this->serverType))
		{
			$name = $this->getName();

			if (stristr($name, 'mysql') !== false)
			{
				$this->serverType = 'mysql';
			}
			elseif (stristr($name, 'postgre') !== false)
			{
				$this->serverType = 'postgresql';
			}
			elseif (stristr($name, 'pgsql') !== false)
			{
				$this->serverType = 'postgresql';
			}
			elseif (stristr($name, 'oracle') !== false)
			{
				$this->serverType = 'oracle';
			}
			elseif (stristr($name, 'sqlite') !== false)
			{
				$this->serverType = 'sqlite';
			}
			elseif (stristr($name, 'sqlsrv') !== false)
			{
				$this->serverType = 'mssql';
			}
			elseif (stristr($name, 'mssql') !== false)
			{
				$this->serverType = 'mssql';
			}
			else
			{
				$this->serverType = $name;
			}
		}

		return $this->serverType;
	}

	/**
	 * Get the current query object or a new JDatabaseQuery object.
	 *
	 * @param   boolean  $new  False to return the current query object, True to return a new JDatabaseQuery object.
	 *
	 * @return  JDatabaseQuery  The current query object or a new object extending the JDatabaseQuery class.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function getQuery($new = false)
	{
		if ($new)
		{
			// Derive the class name from the driver.
			$class = 'JDatabaseQuery' . ucfirst($this->name);

			// Make sure we have a query class for this driver.
			if (!class_exists($class))
			{
				// If it doesn't exist we are at an impasse so throw an exception.
				throw new JDatabaseExceptionUnsupported('Database Query Class not found.');
			}

			return new $class($this);
		}
		else
		{
			return $this->sql;
		}
	}

	/**
	 * Get a new iterator on the current query.
	 *
	 * @param   string  $column  An option column to use as the iterator key.
	 * @param   string  $class   The class of object that is returned.
	 *
	 * @return  JDatabaseIterator  A new database iterator.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	public function getIterator($column = null, $class = 'stdClass')
	{
		// Derive the class name from the driver.
		$iteratorClass = 'JDatabaseIterator' . ucfirst($this->name);

		// Make sure we have an iterator class for this driver.
		if (!class_exists($iteratorClass))
		{
			// If it doesn't exist we are at an impasse so throw an exception.
			throw new JDatabaseExceptionUnsupported(sprintf('class *%s* is not defined', $iteratorClass));
		}

		// Return a new iterator
		return new $iteratorClass($this->execute(), $column, $class);
	}

	/**
	 * Retrieves field information about the given tables.
	 *
	 * @param   string   $table     The name of the database table.
	 * @param   boolean  $typeOnly  True (default) to only return field types.
	 *
	 * @return  array  An array of fields by table.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function getTableColumns($table, $typeOnly = true);

	/**
	 * Shows the table CREATE statement that creates the given tables.
	 *
	 * @param   mixed  $tables  A table name or a list of table names.
	 *
	 * @return  array  A list of the create SQL for the tables.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function getTableCreate($tables);

	/**
	 * Retrieves keys information about the given table.
	 *
	 * @param   string  $table  The name of the table.
	 *
	 * @return  array   An array of keys for the table.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function getTableKeys($table);

	/**
	 * Method to get an array of all tables in the database.
	 *
	 * @return  array  An array of all the tables in the database.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function getTableList();

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 *
	 * @since   1.7.0
	 * @deprecated 4.0 - Use hasUTFSupport() instead
	 */
	public function getUTFSupport()
	{
		JLog::add('JDatabaseDriver::getUTFSupport() is deprecated. Use JDatabaseDriver::hasUTFSupport() instead.', JLog::WARNING, 'deprecated');

		return $this->hasUTFSupport();
	}

	/**
	 * Determine whether or not the database engine supports UTF-8 character encoding.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 character encoding.
	 *
	 * @since   3.0.0
	 */
	public function hasUTFSupport()
	{
		return $this->utf;
	}

	/**
	 * Determine whether the database engine support the UTF-8 Multibyte (utf8mb4) character encoding. This applies to
	 * MySQL databases.
	 *
	 * @return  boolean  True if the database engine supports UTF-8 Multibyte.
	 *
	 * @since   CMS 3.5.0
	 */
	public function hasUTF8mb4Support()
	{
		return $this->utf8mb4;
	}

	/**
	 * Get the version of the database connector
	 *
	 * @return  string  The database connector version.
	 *
	 * @since   1.7.0
	 */
	abstract public function getVersion();

	/**
	 * Method to get the auto-incremented value from the last INSERT statement.
	 *
	 * @return  mixed  The value of the auto-increment field from the last inserted row.
	 *
	 * @since   1.7.0
	 */
	abstract public function insertid();

	/**
	 * Inserts a row into a table based on an object's properties.
	 *
	 * @param   string  $table    The name of the database table to insert into.
	 * @param   object  &$object  A reference to an object whose public properties match the table fields.
	 * @param   string  $key      The name of the primary key. If provided the object property is updated.
	 *
	 * @return  boolean    True on success.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function insertObject($table, &$object, $key = null)
	{
		$fields = array();
		$values = array();

		// Iterate over the object variables to build the query fields and values.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process non-null scalars.
			if (is_array($v) or is_object($v) or $v === null)
			{
				continue;
			}

			// Ignore any internal fields.
			if ($k[0] == '_')
			{
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			$fields[] = $this->quoteName($k);
			$values[] = $this->quote($v);
		}

		// Create the base insert statement.
		$query = $this->getQuery(true)
			->insert($this->quoteName($table))
			->columns($fields)
			->values(implode(',', $values));

		// Set the query and execute the insert.
		$this->setQuery($query);

		if (!$this->execute())
		{
			return false;
		}

		// Update the primary key if it exists.
		$id = $this->insertid();

		if ($key && $id && is_string($key))
		{
			$object->$key = $id;
		}

		return true;
	}

	/**
	 * Method to check whether the installed database version is supported by the database driver
	 *
	 * @return  boolean  True if the database version is supported
	 *
	 * @since   3.0.0
	 */
	public function isMinimumVersion()
	{
		return version_compare($this->getVersion(), static::$dbMinimum) >= 0;
	}

	/**
	 * Method to get the first row of the result set from the database query as an associative array
	 * of ['field_name' => 'row_value'].
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadAssoc()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get the first row from the result set as an associative array.
		if ($array = $this->fetchAssoc($cursor))
		{
			$ret = $array;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an associative array
	 * of ['field_name' => 'row_value'].  The array of rows can optionally be keyed by a field name, but defaults to
	 * a sequential numeric array.
	 *
	 * NOTE: Chosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key     The name of a field on which to key the result array.
	 * @param   string  $column  An optional column name. Instead of the whole row, only this column value will be in
	 * the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadAssocList($key = null, $column = null)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get all of the rows from the result set.
		while ($row = $this->fetchAssoc($cursor))
		{
			$value = ($column) ? (isset($row[$column]) ? $row[$column] : $row) : $row;

			if ($key)
			{
				$array[$row[$key]] = $value;
			}
			else
			{
				$array[] = $value;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from
	 * the database query.
	 *
	 * @param   integer  $offset  The row offset to use to build the result array.
	 *
	 * @return  mixed    The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadColumn($offset = 0)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			$array[] = $row[$offset];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the next row in the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The result of the query as an array, false if there are no more rows.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 * @deprecated  4.0 - Use getIterator() instead
	 */
	public function loadNextObject($class = 'stdClass')
	{
		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		$this->connect();

		static $cursor = null;

		// Execute the query and get the result set cursor.
		if (is_null($cursor))
		{
			if (!($cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchObject($cursor, $class))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);
		$cursor = null;

		return false;
	}

	/**
	 * Method to get the next row in the result set from the database query as an array.
	 *
	 * @return  mixed  The result of the query as an array, false if there are no more rows.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 * @deprecated  4.0 (CMS)  Use JDatabaseDriver::getIterator() instead
	 */
	public function loadNextRow()
	{
		JLog::add(__METHOD__ . '() is deprecated. Use JDatabaseDriver::getIterator() instead.', JLog::WARNING, 'deprecated');
		$this->connect();

		static $cursor = null;

		// Execute the query and get the result set cursor.
		if (is_null($cursor))
		{
			if (!($cursor = $this->execute()))
			{
				return $this->errorNum ? null : false;
			}
		}

		// Get the next row from the result set as an object of type $class.
		if ($row = $this->fetchArray($cursor))
		{
			return $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);
		$cursor = null;

		return false;
	}

	/**
	 * Method to get the first row of the result set from the database query as an object.
	 *
	 * @param   string  $class  The class name to use for the returned row object.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadObject($class = 'stdClass')
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get the first row from the result set as an object of type $class.
		if ($object = $this->fetchObject($cursor, $class))
		{
			$ret = $object;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an object.  The array
	 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   string  $key    The name of a field on which to key the result array.
	 * @param   string  $class  The class name to use for the returned row objects.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadObjectList($key = '', $class = 'stdClass')
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get all of the rows from the result set as objects of type $class.
		while ($row = $this->fetchObject($cursor, $class))
		{
			if ($key)
			{
				$array[$row->$key] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Method to get the first field of the first row of the result set from the database query.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadResult()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row[0];
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get the first row of the result set from the database query as an array.  Columns are indexed
	 * numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc.
	 *
	 * @return  mixed  The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadRow()
	{
		$this->connect();

		$ret = null;

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get the first row from the result set as an array.
		if ($row = $this->fetchArray($cursor))
		{
			$ret = $row;
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $ret;
	}

	/**
	 * Method to get an array of the result set rows from the database query where each row is an array.  The array
	 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array.
	 *
	 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted
	 * behavior and should be avoided.
	 *
	 * @param   integer  $index  The index of a field on which to key the result array.
	 *
	 * @return  mixed   The return value or null if the query failed.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function loadRowList($index = null)
	{
		$this->connect();

		$array = array();

		// Execute the query and get the result set cursor.
		if (!($cursor = $this->execute()))
		{
			return;
		}

		// Get all of the rows from the result set as arrays.
		while ($row = $this->fetchArray($cursor))
		{
			if ($index !== null)
			{
				$array[$row[$index]] = $row;
			}
			else
			{
				$array[] = $row;
			}
		}

		// Free up system resources and return.
		$this->freeResult($cursor);

		return $array;
	}

	/**
	 * Locks a table in the database.
	 *
	 * @param   string  $tableName  The name of the table to unlock.
	 *
	 * @return  JDatabaseDriver     Returns this object to support chaining.
	 *
	 * @since   2.5.0
	 * @throws  RuntimeException
	 */
	abstract public function lockTable($tableName);

	/**
	 * Quotes and optionally escapes a string to database requirements for use in database queries.
	 *
	 * @param   mixed    $text    A string or an array of strings to quote.
	 * @param   boolean  $escape  True (default) to escape the string, false to leave it unchanged.
	 *
	 * @return  string|array  The quoted input.
	 *
	 * @note    Accepting an array of strings was added in 12.3.
	 * @since   1.7.0
	 */
	public function quote($text, $escape = true)
	{
		if (is_array($text))
		{
			foreach ($text as $k => $v)
			{
				$text[$k] = $this->quote($v, $escape);
			}

			return $text;
		}
		else
		{
			return '\'' . ($escape ? $this->escape($text) : $text) . '\'';
		}
	}

	/**
	 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection
	 * risks and reserved word conflicts.
	 *
	 * @param   mixed  $name  The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes.
	 *                        Each type supports dot-notation name.
	 * @param   mixed  $as    The AS query part associated to $name. It can be string or array, in latter case it has to be
	 *                        same length of $name; if is null there will not be any AS part for string or array element.
	 *
	 * @return  mixed  The quote wrapped name, same type of $name.
	 *
	 * @since   1.7.0
	 */
	public function quoteName($name, $as = null)
	{
		if (is_string($name))
		{
			$quotedName = $this->quoteNameStr(explode('.', $name));

			$quotedAs = '';

			if (!is_null($as))
			{
				settype($as, 'array');
				$quotedAs .= ' AS ' . $this->quoteNameStr($as);
			}

			return $quotedName . $quotedAs;
		}
		else
		{
			$fin = array();

			if (is_null($as))
			{
				foreach ($name as $str)
				{
					$fin[] = $this->quoteName($str);
				}
			}
			elseif (is_array($name) && (count($name) == count($as)))
			{
				$count = count($name);

				for ($i = 0; $i < $count; $i++)
				{
					$fin[] = $this->quoteName($name[$i], $as[$i]);
				}
			}

			return $fin;
		}
	}

	/**
	 * Quote strings coming from quoteName call.
	 *
	 * @param   array  $strArr  Array of strings coming from quoteName dot-explosion.
	 *
	 * @return  string  Dot-imploded string of quoted parts.
	 *
	 * @since 1.7.3
	 */
	protected function quoteNameStr($strArr)
	{
		$parts = array();
		$q = $this->nameQuote;

		foreach ($strArr as $part)
		{
			if (is_null($part))
			{
				continue;
			}

			if (strlen($q) == 1)
			{
				$parts[] = $q . str_replace($q, $q . $q, $part) . $q;
			}
			else
			{
				$parts[] = $q{0} . str_replace($q{1}, $q{1} . $q{1}, $part) . $q{1};
			}
		}

		return implode('.', $parts);
	}

	/**
	 * This function replaces a string identifier <var>$prefix</var> with the string held is the
	 * <var>tablePrefix</var> class variable.
	 *
	 * @param   string  $sql     The SQL statement to prepare.
	 * @param   string  $prefix  The common table prefix.
	 *
	 * @return  string  The processed SQL statement.
	 *
	 * @since   1.7.0
	 */
	public function replacePrefix($sql, $prefix = '#__')
	{
		$startPos = 0;
		$literal = '';

		$sql = trim($sql);
		$n = strlen($sql);

		while ($startPos < $n)
		{
			$ip = strpos($sql, $prefix, $startPos);

			if ($ip === false)
			{
				break;
			}

			$j = strpos($sql, "'", $startPos);
			$k = strpos($sql, '"', $startPos);

			if (($k !== false) && (($k < $j) || ($j === false)))
			{
				$quoteChar = '"';
				$j = $k;
			}
			else
			{
				$quoteChar = "'";
			}

			if ($j === false)
			{
				$j = $n;
			}

			$literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos));
			$startPos = $j;

			$j = $startPos + 1;

			if ($j >= $n)
			{
				break;
			}

			// Quote comes first, find end of quote
			while (true)
			{
				$k = strpos($sql, $quoteChar, $j);
				$escaped = false;

				if ($k === false)
				{
					break;
				}

				$l = $k - 1;

				while ($l >= 0 && $sql{$l} == '\\')
				{
					$l--;
					$escaped = !$escaped;
				}

				if ($escaped)
				{
					$j = $k + 1;
					continue;
				}

				break;
			}

			if ($k === false)
			{
				// Error in the query - no end quote; ignore it
				break;
			}

			$literal .= substr($sql, $startPos, $k - $startPos + 1);
			$startPos = $k + 1;
		}

		if ($startPos < $n)
		{
			$literal .= substr($sql, $startPos, $n - $startPos);
		}

		return $literal;
	}

	/**
	 * Renames a table in the database.
	 *
	 * @param   string  $oldTable  The name of the table to be renamed
	 * @param   string  $newTable  The new name for the table.
	 * @param   string  $backup    Table prefix
	 * @param   string  $prefix    For the table - used to rename constraints in non-mysql databases
	 *
	 * @return  JDatabaseDriver    Returns this object to support chaining.
	 *
	 * @since   2.5.0
	 * @throws  RuntimeException
	 */
	abstract public function renameTable($oldTable, $newTable, $backup = null, $prefix = null);

	/**
	 * Select a database for use.
	 *
	 * @param   string  $database  The name of the database to select for use.
	 *
	 * @return  boolean  True if the database was successfully selected.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function select($database);

	/**
	 * Sets the database debugging state for the driver.
	 *
	 * @param   boolean  $level  True to enable debugging.
	 *
	 * @return  boolean  The old debugging level.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  This will be removed in Joomla 4 without replacement
	 */
	public function setDebug($level)
	{
		$previous = $this->debug;
		$this->debug = (bool) $level;

		return $previous;
	}

	/**
	 * Sets the SQL statement string for later execution.
	 *
	 * @param   mixed    $query   The SQL statement to set either as a JDatabaseQuery object or a string.
	 * @param   integer  $offset  The affected row offset to set.
	 * @param   integer  $limit   The maximum affected rows to set.
	 *
	 * @return  JDatabaseDriver  This object to support method chaining.
	 *
	 * @since   1.7.0
	 */
	public function setQuery($query, $offset = 0, $limit = 0)
	{
		$this->sql = $query;

		if ($query instanceof JDatabaseQueryLimitable)
		{
			if (!$limit && $query->limit)
			{
				$limit = $query->limit;
			}

			if (!$offset && $query->offset)
			{
				$offset = $query->offset;
			}

			$query->setLimit($limit, $offset);
		}
		else
		{
			$this->limit = (int) max(0, $limit);
			$this->offset = (int) max(0, $offset);
		}

		return $this;
	}

	/**
	 * Set the connection to use UTF-8 character encoding.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 */
	abstract public function setUtf();

	/**
	 * Method to commit a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, commit to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function transactionCommit($toSavepoint = false);

	/**
	 * Method to roll back a transaction.
	 *
	 * @param   boolean  $toSavepoint  If true, rollback to the last savepoint.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function transactionRollback($toSavepoint = false);

	/**
	 * Method to initialize a transaction.
	 *
	 * @param   boolean  $asSavepoint  If true and a transaction is already active, a savepoint will be created.
	 *
	 * @return  void
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	abstract public function transactionStart($asSavepoint = false);

	/**
	 * Method to truncate a table.
	 *
	 * @param   string  $table  The table to truncate
	 *
	 * @return  void
	 *
	 * @since   1.7.3
	 * @throws  RuntimeException
	 */
	public function truncateTable($table)
	{
		$this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table));
		$this->execute();
	}

	/**
	 * Updates a row in a table based on an object's properties.
	 *
	 * @param   string        $table    The name of the database table to update.
	 * @param   object        &$object  A reference to an object whose public properties match the table fields.
	 * @param   array|string  $key      The name of the primary key.
	 * @param   boolean       $nulls    True to update null fields or false to ignore them.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.7.0
	 * @throws  RuntimeException
	 */
	public function updateObject($table, &$object, $key, $nulls = false)
	{
		$fields = array();
		$where = array();

		if (is_string($key))
		{
			$key = array($key);
		}

		if (is_object($key))
		{
			$key = (array) $key;
		}

		// Create the base update statement.
		$statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s';

		// Iterate over the object variables to build the query fields/value pairs.
		foreach (get_object_vars($object) as $k => $v)
		{
			// Only process scalars that are not internal fields.
			if (is_array($v) || is_object($v) || $k[0] === '_')
			{
				continue;
			}

			// Set the primary key to the WHERE clause instead of a field to update.
			if (in_array($k, $key))
			{
				$where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v));
				continue;
			}

			// Prepare and sanitize the fields and values for the database query.
			if ($v === null)
			{
				// If the value is null and we want to update nulls then set it.
				if ($nulls)
				{
					$val = 'NULL';
				}
				// If the value is null and we do not want to update nulls then ignore this field.
				else
				{
					continue;
				}
			}
			// The field is not null so we prep it for update.
			else
			{
				$val = $this->quote($v);
			}

			// Add the field to be updated.
			$fields[] = $this->quoteName($k) . '=' . $val;
		}

		// We don't have any fields to update.
		if (empty($fields))
		{
			return true;
		}

		// Set the query and execute the update.
		$this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)));

		return $this->execute();
	}

	/**
	 * Execute the SQL statement.
	 *
	 * @return  mixed  A database cursor resource on success, boolean false on failure.
	 *
	 * @since   3.0.0
	 * @throws  RuntimeException
	 */
	abstract public function execute();

	/**
	 * Unlocks tables in the database.
	 *
	 * @return  JDatabaseDriver  Returns this object to support chaining.
	 *
	 * @since   2.5.0
	 * @throws  RuntimeException
	 */
	abstract public function unlockTables();
}
joomla/platform.php000064400000004601152177723700010376 0ustar00<?php
/**
 * @package    Joomla.Platform
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Version information class for the Joomla Platform.
 *
 * @since       1.7.0
 * @deprecated  4.0  Deprecated without replacement
 */
final class JPlatform
{
	// Product name.
	const PRODUCT = 'Joomla Platform';

	// Release version.
	const RELEASE = '13.1';

	// Maintenance version.
	const MAINTENANCE = '0';

	// Development STATUS.
	const STATUS = 'Stable';

	// Build number.
	const BUILD = 0;

	// Code name.
	const CODE_NAME = 'Curiosity';

	// Release date.
	const RELEASE_DATE = '24-Apr-2013';

	// Release time.
	const RELEASE_TIME = '00:00';

	// Release timezone.
	const RELEASE_TIME_ZONE = 'GMT';

	// Copyright Notice.
	const COPYRIGHT = 'Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.';

	// Link text.
	const LINK_TEXT = '<a href="https://www.joomla.org">Joomla!</a> is Free Software released under the GNU General Public License.';

	/**
	 * Compares two a "PHP standardized" version number against the current Joomla Platform version.
	 *
	 * @param   string  $minimum  The minimum version of the Joomla Platform which is compatible.
	 *
	 * @return  boolean  True if the version is compatible.
	 *
	 * @link    https://www.php.net/version_compare
	 * @since   1.7.0
	 * @deprecated  4.0  Deprecated without replacement
	 */
	public static function isCompatible($minimum)
	{
		return version_compare(self::getShortVersion(), $minimum, 'eq') == 1;
	}

	/**
	 * Gets a "PHP standardized" version string for the current Joomla Platform.
	 *
	 * @return  string  Version string.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Deprecated without replacement
	 */
	public static function getShortVersion()
	{
		return self::RELEASE . '.' . self::MAINTENANCE;
	}

	/**
	 * Gets a version string for the current Joomla Platform with all release information.
	 *
	 * @return  string  Complete version string.
	 *
	 * @since   1.7.0
	 * @deprecated  4.0  Deprecated without replacement
	 */
	public static function getLongVersion()
	{
		return self::PRODUCT . ' ' . self::RELEASE . '.' . self::MAINTENANCE . ' ' . self::STATUS . ' [ ' . self::CODE_NAME . ' ] '
			. self::RELEASE_DATE . ' ' . self::RELEASE_TIME . ' ' . self::RELEASE_TIME_ZONE;
	}
}
joomla/observable/interface.php000064400000003127152177723700012640 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Observable Subject pattern interface for Joomla
 *
 * To make a class and its inheriting classes observable:
 * 1) add: implements JObservableInterface
 *    to its class
 *
 * 2) at the end of the constructor, add:
 * // Create observer updater and attaches all observers interested by $this class:
 * $this->_observers = new JObserverUpdater($this);
 * JObserverMapper::attachAllObservers($this);
 *
 * 3) add the function attachObserver below to your class to add observers using the JObserverUpdater class:
 * 	public function attachObserver(JObserverInterface $observer)
 * 	{
 * 		$this->_observers->attachObserver($observer);
 * 	}
 *
 * 4) in the methods that need to be observed, add, e.g. (name of event, params of event):
 * 		$this->_observers->update('onBeforeLoad', array($keys, $reset));
 *
 * @since  3.1.2
 */
interface JObservableInterface
{
	/**
	 * Adds an observer to this JObservableInterface instance.
	 * Ideally, this method should be called fron the constructor of JObserverInterface
	 * which should be instanciated by JObserverMapper.
	 * The implementation of this function can use JObserverUpdater
	 *
	 * @param   JObserverInterface  $observer  The observer to attach to $this observable subject
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function attachObserver(JObserverInterface $observer);
}
joomla/form/fields/url.php000064400000003446152177723700011573 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the Joomla Platform.
 * Supports a URL text field
 *
 * @link   http://www.w3.org/TR/html-markup/input.url.html#input.url
 * @see    JFormRuleUrl for validation of full urls
 * @since  1.7.0
 */
class JFormFieldUrl extends JFormFieldText
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Url';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.url';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.1.2 (CMS)
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$maxLength    = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';

		// Note that the input type "url" is suitable only for external URLs, so if internal URLs are allowed
		// we have to use the input type "text" instead.
		$inputType    = $this->element['relative'] ? 'type="text"' : 'type="url"';

		$extraData = array(
			'maxLength' => $maxLength,
			'inputType' => $inputType,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/plugins.php000064400000010344152177723700012445 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Framework.
 *
 * @since  2.5.0
 */
class JFormFieldPlugins extends JFormFieldList
{
	/**
	 * The field type.
	 *
	 * @var    string
	 * @since  2.5.0
	 */
	protected $type = 'Plugins';

	/**
	 * The path to folder for plugins.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $folder;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'folder':
				return $this->folder;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'folder':
				$this->folder = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->folder = (string) $this->element['folder'];
		}

		return $return;
	}

	/**
	 * Method to get a list of options for a list input.
	 *
	 * @return	array  An array of JHtml options.
	 *
	 * @since   2.5.0
	 */
	protected function getOptions()
	{
		$folder        = $this->folder;
		$parentOptions = parent::getOptions();

		if (!empty($folder))
		{
			// Get list of plugins
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('element AS value, name AS text')
				->from('#__extensions')
				->where('folder = ' . $db->quote($folder))
				->where('enabled = 1')
				->order('ordering, name');

			if ((string) $this->element['useaccess'] === 'true')
			{
				$groups = implode(',', JFactory::getUser()->getAuthorisedViewLevels());
				$query->where($db->quoteName('access') . ' IN (' . $groups . ')');
			}

			$options   = $db->setQuery($query)->loadObjectList();
			$lang      = JFactory::getLanguage();
			$useGlobal = $this->element['useglobal'];

			if ($useGlobal)
			{
				$globalValue = JFactory::getConfig()->get($this->fieldname);
			}

			foreach ($options as $i => $item)
			{
				$source    = JPATH_PLUGINS . '/' . $folder . '/' . $item->value;
				$extension = 'plg_' . $folder . '_' . $item->value;
				$lang->load($extension . '.sys', JPATH_ADMINISTRATOR, null, false, true) || $lang->load($extension . '.sys', $source, null, false, true);
				$options[$i]->text = JText::_($item->text);

				// If we are using useglobal update the use global value text with the plugin text.
				if ($useGlobal && isset($parentOptions[0]) && $item->value === $globalValue)
				{
					$text                   = JText::_($extension);
					$parentOptions[0]->text = JText::sprintf('JGLOBAL_USE_GLOBAL_VALUE', ($text === '' || $text === $extension ? $item->value : $text));
				}
			}
		}
		else
		{
			JLog::add(JText::_('JFRAMEWORK_FORM_FIELDS_PLUGINS_ERROR_FOLDER_EMPTY'), JLog::WARNING, 'jerror');
		}

		return array_merge($parentOptions, $options);
	}
}
joomla/form/fields/range.php000064400000002642152177723700012062 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('number');

/**
 * Form Field class for the Joomla Platform.
 * Provides a horizontal scroll bar to specify a value in a range.
 *
 * @link   http://www.w3.org/TR/html-markup/input.text.html#input.text
 * @since  3.2
 */
class JFormFieldRange extends JFormFieldNumber
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = 'Range';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.range';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$extraData = array(
			'max' => $this->max,
			'min' => $this->min,
			'step' => $this->step,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/hidden.php000064400000002340152177723700012214 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Provides a hidden field
 *
 * @link   http://www.w3.org/TR/html-markup/input.hidden.html#input.hidden
 * @since  1.7.0
 */
class JFormFieldHidden extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Hidden';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.hidden';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		return parent::getLayoutData();
	}
}
joomla/form/fields/components.php000064400000003201152177723700013143 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Utilities\ArrayHelper;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Framework.
 *
 * @since  3.7.0
 */
class JFormFieldComponents extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var     string
	 * @since   3.7.0
	 */
	protected $type = 'Components';

	/**
	 * Method to get a list of options for a list input.
	 *
	 * @return	array  An array of JHtml options.
	 *
	 * @since   2.5.0
	 */
	protected function getOptions()
	{
		$db    = JFactory::getDbo();
		$query = $db->getQuery(true)
			->select('name AS text, element AS value')
			->from('#__extensions')
			->where('enabled >= 1')
			->where('type =' . $db->quote('component'));

		$items = $db->setQuery($query)->loadObjectList();

		if ($items)
		{
			$lang = JFactory::getLanguage();

			foreach ($items as &$item)
			{
				// Load language
				$extension = $item->value;

				$lang->load("$extension.sys", JPATH_ADMINISTRATOR, null, false, true)
					|| $lang->load("$extension.sys", JPATH_ADMINISTRATOR . '/components/' . $extension, null, false, true);

				// Translate component name
				$item->text = JText::_($item->text);
			}

			// Sort by component name
			$items = ArrayHelper::sortObjects($items, 'text', 1, true, true);
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $items);

		return $options;
	}
}
joomla/form/fields/checkbox.php000064400000010433152177723700012551 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Single checkbox field.
 * This is a boolean field with null for false and the specified option for true
 *
 * @link   http://www.w3.org/TR/html-markup/input.checkbox.html#input.checkbox
 * @see    JFormFieldCheckboxes
 * @since  1.7.0
 */
class JFormFieldCheckbox extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Checkbox';

	/**
	 * The checked state of checkbox field.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $checked = false;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'checked':
				return $this->checked;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'checked':
				$value = (string) $value;
				$this->checked = ($value == 'true' || $value == $name || $value == '1');
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		// Handle the default attribute
		$default = (string) $element['default'];

		if ($default)
		{
			$test = $this->form->getValue((string) $element['name'], $group);

			$value = ($test == $default) ? $default : null;
		}

		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$checked = (string) $this->element['checked'];
			$this->checked = ($checked == 'true' || $checked == 'checked' || $checked == '1');

			empty($this->value) || $this->checked ? null : $this->checked = true;
		}

		return $return;
	}

	/**
	 * Method to get the field input markup.
	 * The checked element sets the field to selected.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		// Initialize some field attributes.
		$class     = !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$disabled  = $this->disabled ? ' disabled' : '';
		$value     = !empty($this->default) ? $this->default : '1';
		$required  = $this->required ? ' required aria-required="true"' : '';
		$autofocus = $this->autofocus ? ' autofocus' : '';
		$checked   = $this->checked || !empty($this->value) ? ' checked' : '';

		// Initialize JavaScript field attributes.
		$onclick  = !empty($this->onclick) ? ' onclick="' . $this->onclick . '"' : '';
		$onchange = !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';

		// Including fallback code for HTML5 non supported browsers.
		JHtml::_('jquery.framework');
		JHtml::_('script', 'system/html5fallback.js', array('version' => 'auto', 'relative' => true, 'conditional' => 'lt IE 9'));

		return '<input type="checkbox" name="' . $this->name . '" id="' . $this->id . '" value="'
			. htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"' . $class . $checked . $disabled . $onclick . $onchange
			. $required . $autofocus . ' />';
	}
}
joomla/form/fields/databaseconnection.php000064400000004011152177723700014602 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a list of available database connections, optionally limiting to
 * a given list.
 *
 * @see    JDatabaseDriver
 * @since  1.7.3
 */
class JFormFieldDatabaseConnection extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.3
	 */
	protected $type = 'DatabaseConnection';

	/**
	 * Method to get the list of database options.
	 *
	 * This method produces a drop down list of available databases supported
	 * by JDatabaseDriver classes that are also supported by the application.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.3
	 * @see     JDatabaseDriver::getConnectors()
	 */
	protected function getOptions()
	{
		// This gets the connectors available in the platform and supported by the server.
		$available = JDatabaseDriver::getConnectors();

		/**
		 * This gets the list of database types supported by the application.
		 * This should be entered in the form definition as a comma separated list.
		 * If no supported databases are listed, it is assumed all available databases
		 * are supported.
		 */
		$supported = $this->element['supported'];

		if (!empty($supported))
		{
			$supported = explode(',', $supported);

			foreach ($supported as $support)
			{
				if (in_array($support, $available))
				{
					$options[$support] = JText::_(ucfirst($support));
				}
			}
		}
		else
		{
			foreach ($available as $support)
			{
				$options[$support] = JText::_(ucfirst($support));
			}
		}

		// This will come into play if an application is installed that requires
		// a database that is not available on the server.
		if (empty($options))
		{
			$options[''] = JText::_('JNONE');
		}

		return $options;
	}
}
joomla/form/fields/rules.php000064400000034473152177723700012127 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Field for assigning permissions to groups for a given asset
 *
 * @see    JAccess
 * @since  1.7.0
 */
class JFormFieldRules extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Rules';

	/**
	 * The section.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $section;

	/**
	 * The component.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $component;

	/**
	 * The assetField.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $assetField;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'section':
			case 'component':
			case 'assetField':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'section':
			case 'component':
			case 'assetField':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->section    = $this->element['section'] ? (string) $this->element['section'] : '';
			$this->component  = $this->element['component'] ? (string) $this->element['component'] : '';
			$this->assetField = $this->element['asset_field'] ? (string) $this->element['asset_field'] : 'asset_id';
		}

		return $return;
	}

	/**
	 * Method to get the field input markup for Access Control Lists.
	 * Optionally can be associated with a specific component and section.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 * @todo:   Add access check.
	 */
	protected function getInput()
	{
		JHtml::_('bootstrap.tooltip');

		// Add Javascript for permission change
		JHtml::_('script', 'system/permissions.js', array('version' => 'auto', 'relative' => true));

		// Load JavaScript message titles
		JText::script('ERROR');
		JText::script('WARNING');
		JText::script('NOTICE');
		JText::script('MESSAGE');

		// Add strings for JavaScript error translations.
		JText::script('JLIB_JS_AJAX_ERROR_CONNECTION_ABORT');
		JText::script('JLIB_JS_AJAX_ERROR_NO_CONTENT');
		JText::script('JLIB_JS_AJAX_ERROR_OTHER');
		JText::script('JLIB_JS_AJAX_ERROR_PARSE');
		JText::script('JLIB_JS_AJAX_ERROR_TIMEOUT');

		// Initialise some field attributes.
		$section    = $this->section;
		$assetField = $this->assetField;
		$component  = empty($this->component) ? 'root.1' : $this->component;

		// Current view is global config?
		$isGlobalConfig = $component === 'root.1';

		// Get the actions for the asset.
		$actions = JAccess::getActions($component, $section);

		// Iterate over the children and add to the actions.
		foreach ($this->element->children() as $el)
		{
			if ($el->getName() == 'action')
			{
				$actions[] = (object) array(
					'name' => (string) $el['name'],
					'title' => (string) $el['title'],
					'description' => (string) $el['description'],
				);
			}
		}

		// Get the asset id.
		// Note that for global configuration, com_config injects asset_id = 1 into the form.
		$assetId       = $this->form->getValue($assetField);
		$newItem       = empty($assetId) && $isGlobalConfig === false && $section !== 'component';
		$parentAssetId = null;

		// If the asset id is empty (component or new item).
		if (empty($assetId))
		{
			// Get the component asset id as fallback.
			$db = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select($db->quoteName('id'))
				->from($db->quoteName('#__assets'))
				->where($db->quoteName('name') . ' = ' . $db->quote($component));

			$db->setQuery($query);

			$assetId = (int) $db->loadResult();

			/**
			 * @to do: incorrect info
			 * When creating a new item (not saving) it uses the calculated permissions from the component (item <-> component <-> global config).
			 * But if we have a section too (item <-> section(s) <-> component <-> global config) this is not correct.
			 * Also, currently it uses the component permission, but should use the calculated permissions for achild of the component/section.
			 */
		}

		// If not in global config we need the parent_id asset to calculate permissions.
		if (!$isGlobalConfig)
		{
			// In this case we need to get the component rules too.
			$db = JFactory::getDbo();

			$query = $db->getQuery(true)
				->select($db->quoteName('parent_id'))
				->from($db->quoteName('#__assets'))
				->where($db->quoteName('id') . ' = ' . $assetId);

			$db->setQuery($query);

			$parentAssetId = (int) $db->loadResult();
		}

		// Full width format.

		// Get the rules for just this asset (non-recursive).
		$assetRules = JAccess::getAssetRules($assetId, false, false);

		// Get the available user groups.
		$groups = $this->getUserGroups();

		// Ajax request data.
		$ajaxUri = JRoute::_('index.php?option=com_config&task=config.store&format=json&' . JSession::getFormToken() . '=1');

		// Prepare output
		$html = array();

		// Description
		$html[] = '<p class="rule-desc">' . JText::_('JLIB_RULES_SETTINGS_DESC') . '</p>';

		// Begin tabs
		$html[] = '<div class="tabbable tabs-left" data-ajaxuri="' . $ajaxUri . '" id="permissions-sliders">';

		// Building tab nav
		$html[] = '<ul class="nav nav-tabs">';

		foreach ($groups as $group)
		{
			// Initial Active Tab
			$active = (int) $group->value === 1 ? ' class="active"' : '';

			$html[] = '<li' . $active . '>';
			$html[] = '<a href="#permission-' . $group->value . '" data-toggle="tab">';
			$html[] = JLayoutHelper::render('joomla.html.treeprefix', array('level' => $group->level + 1)) . $group->text;
			$html[] = '</a>';
			$html[] = '</li>';
		}

		$html[] = '</ul>';

		$html[] = '<div class="tab-content">';

		// Start a row for each user group.
		foreach ($groups as $group)
		{
			// Initial Active Pane
			$active = (int) $group->value === 1 ? ' active' : '';

			$html[] = '<div class="tab-pane' . $active . '" id="permission-' . $group->value . '">';
			$html[] = '<table class="table table-striped">';
			$html[] = '<thead>';
			$html[] = '<tr>';

			$html[] = '<th class="actions" id="actions-th' . $group->value . '">';
			$html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_ACTION') . '</span>';
			$html[] = '</th>';

			$html[] = '<th class="settings" id="settings-th' . $group->value . '">';
			$html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_SELECT_SETTING') . '</span>';
			$html[] = '</th>';

			$html[] = '<th id="aclactionth' . $group->value . '">';
			$html[] = '<span class="acl-action">' . JText::_('JLIB_RULES_CALCULATED_SETTING') . '</span>';
			$html[] = '</th>';

			$html[] = '</tr>';
			$html[] = '</thead>';
			$html[] = '<tbody>';

			// Check if this group has super user permissions
			$isSuperUserGroup = JAccess::checkGroup($group->value, 'core.admin');

			foreach ($actions as $action)
			{
				$html[] = '<tr>';
				$html[] = '<td headers="actions-th' . $group->value . '">';
				$html[] = '<label for="' . $this->id . '_' . $action->name . '_' . $group->value . '" class="hasTooltip" title="'
					. JHtml::_('tooltipText', $action->title, $action->description) . '">';
				$html[] = JText::_($action->title);
				$html[] = '</label>';
				$html[] = '</td>';

				$html[] = '<td headers="settings-th' . $group->value . '">';

				$html[] = '<select onchange="sendPermissions.call(this, event)" data-chosen="true" class="input-small novalidate"'
					. ' name="' . $this->name . '[' . $action->name . '][' . $group->value . ']"'
					. ' id="' . $this->id . '_' . $action->name	. '_' . $group->value . '"'
					. ' title="' . strip_tags(JText::sprintf('JLIB_RULES_SELECT_ALLOW_DENY_GROUP', JText::_($action->title), trim($group->text))) . '">';

				/**
				 * Possible values:
				 * null = not set means inherited
				 * false = denied
				 * true = allowed
				 */

				// Get the actual setting for the action for this group.
				$assetRule = $newItem === false ? $assetRules->allow($action->name, $group->value) : null;

				// Build the dropdowns for the permissions sliders

				// The parent group has "Not Set", all children can rightly "Inherit" from that.
				$html[] = '<option value=""' . ($assetRule === null ? ' selected="selected"' : '') . '>'
					. JText::_(empty($group->parent_id) && $isGlobalConfig ? 'JLIB_RULES_NOT_SET' : 'JLIB_RULES_INHERITED') . '</option>';
				$html[] = '<option value="1"' . ($assetRule === true ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_ALLOWED')
					. '</option>';
				$html[] = '<option value="0"' . ($assetRule === false ? ' selected="selected"' : '') . '>' . JText::_('JLIB_RULES_DENIED')
					. '</option>';

				$html[] = '</select>&#160; ';

				$html[] = '<span id="icon_' . $this->id . '_' . $action->name . '_' . $group->value . '"' . '></span>';
				$html[] = '</td>';

				// Build the Calculated Settings column.
				$html[] = '<td headers="aclactionth' . $group->value . '">';

				$result = array();

				// Get the group, group parent id, and group global config recursive calculated permission for the chosen action.
				$inheritedGroupRule            = JAccess::checkGroup((int) $group->value, $action->name, $assetId);
				$inheritedGroupParentAssetRule = !empty($parentAssetId) ? JAccess::checkGroup($group->value, $action->name, $parentAssetId) : null;
				$inheritedParentGroupRule      = !empty($group->parent_id) ? JAccess::checkGroup($group->parent_id, $action->name, $assetId) : null;

				// Current group is a Super User group, so calculated setting is "Allowed (Super User)".
				if ($isSuperUserGroup)
				{
					$result['class'] = 'label label-success';
					$result['text'] = '<span class="icon-lock icon-white"></span>' . JText::_('JLIB_RULES_ALLOWED_ADMIN');
				}
				// Not super user.
				else
				{
					// First get the real recursive calculated setting and add (Inherited) to it.

					// If recursive calculated setting is "Denied" or null. Calculated permission is "Not Allowed (Inherited)".
					if ($inheritedGroupRule === null || $inheritedGroupRule === false)
					{
						$result['class'] = 'label label-important';
						$result['text']  = JText::_('JLIB_RULES_NOT_ALLOWED_INHERITED');
					}
					// If recursive calculated setting is "Allowed". Calculated permission is "Allowed (Inherited)".
					else
					{
						$result['class'] = 'label label-success';
						$result['text']  = JText::_('JLIB_RULES_ALLOWED_INHERITED');
					}

					// Second part: Overwrite the calculated permissions labels if there is an explicit permission in the current group.

					/**
					 * @to do: incorrect info
					 * If a component has a permission that doesn't exists in global config (ex: frontend editing in com_modules) by default
					 * we get "Not Allowed (Inherited)" when we should get "Not Allowed (Default)".
					 */

					// If there is an explicit permission "Not Allowed". Calculated permission is "Not Allowed".
					if ($assetRule === false)
					{
						$result['class'] = 'label label-important';
						$result['text']  = JText::_('JLIB_RULES_NOT_ALLOWED');
					}
					// If there is an explicit permission is "Allowed". Calculated permission is "Allowed".
					elseif ($assetRule === true)
					{
						$result['class'] = 'label label-success';
						$result['text']  = JText::_('JLIB_RULES_ALLOWED');
					}

					// Third part: Overwrite the calculated permissions labels for special cases.

					// Global configuration with "Not Set" permission. Calculated permission is "Not Allowed (Default)".
					if (empty($group->parent_id) && $isGlobalConfig === true && $assetRule === null)
					{
						$result['class'] = 'label label-important';
						$result['text']  = JText::_('JLIB_RULES_NOT_ALLOWED_DEFAULT');
					}

					/**
					 * Component/Item with explicit "Denied" permission at parent Asset (Category, Component or Global config) configuration.
					 * Or some parent group has an explicit "Denied".
					 * Calculated permission is "Not Allowed (Locked)".
					 */
					elseif ($inheritedGroupParentAssetRule === false || $inheritedParentGroupRule === false)
					{
						$result['class'] = 'label label-important';
						$result['text']  = '<span class="icon-lock icon-white"></span>' . JText::_('JLIB_RULES_NOT_ALLOWED_LOCKED');
					}
				}

				$html[] = '<span class="' . $result['class'] . '">' . $result['text'] . '</span>';
				$html[] = '</td>';
				$html[] = '</tr>';
			}

			$html[] = '</tbody>';
			$html[] = '</table></div>';
		}

		$html[] = '</div></div>';
		$html[] = '<div class="clr"></div>';
		$html[] = '<div class="alert">';

		if ($section === 'component' || !$section)
		{
			$html[] = JText::_('JLIB_RULES_SETTING_NOTES');
		}
		else
		{
			$html[] = JText::_('JLIB_RULES_SETTING_NOTES_ITEM');
		}

		$html[] = '</div>';

		return implode("\n", $html);
	}

	/**
	 * Get a list of the user groups.
	 *
	 * @return  array
	 *
	 * @since   1.7.0
	 */
	protected function getUserGroups()
	{
		$options = JHelperUsergroups::getInstance()->getAll();

		foreach ($options as &$option)
		{
			$option->value = $option->id;
			$option->text  = $option->title;
		}

		return array_values($options);
	}
}
joomla/form/fields/subform.php000064400000022173152177723700012444 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\CMS\Form\Form;

jimport('joomla.filesystem.path');

/**
 * The Field to load the form inside current form
 *
 * @Example with all attributes:
 * 	<field name="field-name" type="subform"
 * 		formsource="path/to/form.xml" min="1" max="3" multiple="true" buttons="add,remove,move"
 * 		layout="joomla.form.field.subform.repeatable-table" groupByFieldset="false" component="com_example" client="site"
 * 		label="Field Label" description="Field Description" />
 *
 * @since  3.6
 */
class JFormFieldSubform extends JFormField
{
	/**
	 * The form field type.
	 * @var    string
	 */
	protected $type = 'Subform';

	/**
	 * Form source
	 * @var string
	 */
	protected $formsource;

	/**
	 * Minimum items in repeat mode
	 * @var int
	 */
	protected $min = 0;

	/**
	 * Maximum items in repeat mode
	 * @var int
	 */
	protected $max = 1000;

	/**
	 * Layout to render the form
	 * @var  string
	 */
	protected $layout = 'joomla.form.field.subform.default';

	/**
	 * Whether group subform fields by it`s fieldset
	 * @var boolean
	 */
	protected $groupByFieldset = false;

	/**
	 * Which buttons to show in miltiple mode
	 * @var array $buttons
	 */
	protected $buttons = array('add' => true, 'remove' => true, 'move' => true);

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.6
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'formsource':
			case 'min':
			case 'max':
			case 'layout':
			case 'groupByFieldset':
			case 'buttons':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.6
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'formsource':
				$this->formsource = (string) $value;

				// Add root path if we have a path to XML file
				if (strrpos($this->formsource, '.xml') === strlen($this->formsource) - 4)
				{
					$this->formsource = JPath::clean(JPATH_ROOT . '/' . $this->formsource);
				}

				break;

			case 'min':
				$this->min = (int) $value;
				break;

			case 'max':
				if ($value)
				{
					$this->max = max(1, (int) $value);
				}
				break;

			case 'groupByFieldset':
				if ($value !== null)
				{
					$value = (string) $value;
					$this->groupByFieldset = !($value === 'false' || $value === 'off' || $value === '0');
				}
				break;

			case 'layout':
				$this->layout = (string) $value;

				// Make sure the layout is not empty.
				if (!$this->layout)
				{
					// Set default value depend from "multiple" mode
					$this->layout = !$this->multiple ? 'joomla.form.field.subform.default' : 'joomla.form.field.subform.repeatable';
				}

				break;

			case 'buttons':

				if (!$this->multiple)
				{
					$this->buttons = array();
					break;
				}

				if ($value && !is_array($value))
				{
					$value = explode(',', (string) $value);
					$value = array_fill_keys(array_filter($value), true);
				}

				if ($value)
				{
					$value = array_merge(array('add' => false, 'remove' => false, 'move' => false), $value);
					$this->buttons = $value;
				}

				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the <field /> tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   3.6
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		if (!parent::setup($element, $value, $group))
		{
			return false;
		}

		foreach (array('formsource', 'min', 'max', 'layout', 'groupByFieldset', 'buttons') as $attributeName)
		{
			$this->__set($attributeName, $element[$attributeName]);
		}

		if ($this->value && is_string($this->value))
		{
			// Guess here is the JSON string from 'default' attribute
			$this->value = json_decode($this->value, true);
		}

		if (!$this->formsource && $element->form)
		{
			// Set the formsource parameter from the content of the node
			$this->formsource = $element->form->saveXML();
		}

		return true;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.6
	 */
	protected function getInput()
	{
		// Prepare data for renderer
		$data    = parent::getLayoutData();
		$tmpl    = null;
		$control = $this->name;

		try
		{
			$tmpl  = $this->loadSubForm();
			$forms = $this->loadSubFormData($tmpl);
		}
		catch (Exception $e)
		{
			return $e->getMessage();
		}

		$data['tmpl']      = $tmpl;
		$data['forms']     = $forms;
		$data['min']       = $this->min;
		$data['max']       = $this->max;
		$data['control']   = $control;
		$data['buttons']   = $this->buttons;
		$data['fieldname'] = $this->fieldname;
		$data['groupByFieldset'] = $this->groupByFieldset;

		/**
		 * For each rendering process of a subform element, we want to have a
		 * separate unique subform id present to could distinguish the eventhandlers
		 * regarding adding/moving/removing rows from nested subforms from their parents.
		 */
		static $unique_subform_id = 0;
		$data['unique_subform_id'] = ('sr-' . ($unique_subform_id++));

		// Prepare renderer
		$renderer = $this->getRenderer($this->layout);

		// Allow to define some JLayout options as attribute of the element
		if ($this->element['component'])
		{
			$renderer->setComponent((string) $this->element['component']);
		}

		if ($this->element['client'])
		{
			$renderer->setClient((string) $this->element['client']);
		}

		// Render
		$html = $renderer->render($data);

		// Add hidden input on front of the subform inputs, in multiple mode
		// for allow to submit an empty value
		if ($this->multiple)
		{
			$html = '<input name="' . $this->name . '" type="hidden" value="" />' . $html;
		}

		return $html;
	}

	/**
	 * Method to get the name used for the field input tag.
	 *
	 * @param   string  $fieldName  The field element name.
	 *
	 * @return  string  The name to be used for the field input tag.
	 *
	 * @since   3.6
	 */
	protected function getName($fieldName)
	{
		$name = '';

		// If there is a form control set for the attached form add it first.
		if ($this->formControl)
		{
			$name .= $this->formControl;
		}

		// If the field is in a group add the group control to the field name.
		if ($this->group)
		{
			// If we already have a name segment add the group control as another level.
			$groups = explode('.', $this->group);

			if ($name)
			{
				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
			else
			{
				$name .= array_shift($groups);

				foreach ($groups as $group)
				{
					$name .= '[' . $group . ']';
				}
			}
		}

		// If we already have a name segment add the field name as another level.
		if ($name)
		{
			$name .= '[' . $fieldName . ']';
		}
		else
		{
			$name .= $fieldName;
		}

		return $name;
	}

	/**
	 * Loads the form instance for the subform.
	 *
	 * @return  Form  The form instance.
	 *
	 * @throws  InvalidArgumentException if no form provided.
	 * @throws  RuntimeException if the form could not be loaded.
	 *
	 * @since   3.9.7
	 */
	public function loadSubForm()
	{
		$control = $this->name;

		if ($this->multiple)
		{
			$control .= '[' . $this->fieldname . 'X]';
		}

		// Prepare the form template
		$formname = 'subform.' . str_replace(array('jform[', '[', ']'), array('', '.', ''), $this->name);
		$tmpl     = Form::getInstance($formname, $this->formsource, array('control' => $control));

		return $tmpl;
	}

	/**
	 * Binds given data to the subform and its elements.
	 *
	 * @param   Form  &$subForm  Form instance of the subform.
	 *
	 * @return  Form[]  Array of Form instances for the rows.
	 *
	 * @since   3.9.7
	 */
	private function loadSubFormData(Form &$subForm)
	{
		$value = $this->value ? (array) $this->value : array();
		
		// Simple form, just bind the data and return one row.
		if (!$this->multiple)
		{
			$subForm->bind($value);
			return array($subForm);
		}
		
		// Multiple rows possible: Construct array and bind values to their respective forms.
		$forms = array();
		$value = array_values($value);
		
		// Show as many rows as we have values, but at least min and at most max.
		$c = max($this->min, min(count($value), $this->max));

		for ($i = 0; $i < $c; $i++)
		{
			$control  = $this->name . '[' . $this->fieldname . $i . ']';
			$itemForm = Form::getInstance($subForm->getName() . $i, $this->formsource, array('control' => $control));

			if (!empty($value[$i]))
			{
				$itemForm->bind($value[$i]);
			}

			$forms[] = $itemForm;
		}

		return $forms;
	}
}
joomla/form/fields/calendar.php000064400000022150152177723700012533 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 *
 * Provides a pop up date picker linked to a button.
 * Optionally may be filtered to use user's or server's time zone.
 *
 * @since  1.7.0
 */
class JFormFieldCalendar extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Calendar';

	/**
	 * The allowable maxlength of calendar field.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $maxlength;

	/**
	 * The format of date and time.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $format;

	/**
	 * The filter.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $filter;

	/**
	 * The minimum year number to subtract/add from the current year
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	protected $minyear;

	/**
	 * The maximum year number to subtract/add from the current year
	 *
	 * @var    integer
	 * @since  3.7.0
	 */
	protected $maxyear;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	protected $layout = 'joomla.form.field.calendar';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'maxlength':
			case 'format':
			case 'filter':
			case 'timeformat':
			case 'todaybutton':
			case 'singleheader':
			case 'weeknumbers':
			case 'showtime':
			case 'filltable':
			case 'minyear':
			case 'maxyear':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'maxlength':
			case 'timeformat':
				$this->$name = (int) $value;
				break;
			case 'todaybutton':
			case 'singleheader':
			case 'weeknumbers':
			case 'showtime':
			case 'filltable':
			case 'format':
			case 'filter':
			case 'minyear':
			case 'maxyear':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->maxlength    = (int) $this->element['maxlength'] ? (int) $this->element['maxlength'] : 45;
			$this->format       = (string) $this->element['format'] ? (string) $this->element['format'] : '%Y-%m-%d';
			$this->filter       = (string) $this->element['filter'] ? (string) $this->element['filter'] : 'USER_UTC';
			$this->todaybutton  = (string) $this->element['todaybutton'] ? (string) $this->element['todaybutton'] : 'true';
			$this->weeknumbers  = (string) $this->element['weeknumbers'] ? (string) $this->element['weeknumbers'] : 'true';
			$this->showtime     = (string) $this->element['showtime'] ? (string) $this->element['showtime'] : 'false';
			$this->filltable    = (string) $this->element['filltable'] ? (string) $this->element['filltable'] : 'true';
			$this->timeformat   = (int) $this->element['timeformat'] ? (int) $this->element['timeformat'] : 24;
			$this->singleheader = (string) $this->element['singleheader'] ? (string) $this->element['singleheader'] : 'false';
			$this->minyear      = strlen((string) $this->element['minyear']) ? (string) $this->element['minyear'] : null;
			$this->maxyear      = strlen((string) $this->element['maxyear']) ? (string) $this->element['maxyear'] : null;

			if ($this->maxyear < 0 || $this->minyear > 0)
			{
				$this->todaybutton = 'false';
			}
		}

		return $return;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		$config    = JFactory::getConfig();
		$user      = JFactory::getUser();

		// Translate the format if requested
		$translateFormat = (string) $this->element['translateformat'];

		if ($translateFormat && $translateFormat != 'false')
		{
			$showTime = (string) $this->element['showtime'];

			$lang  = \JFactory::getLanguage();
			$debug = $lang->setDebug(false);

			if ($showTime && $showTime != 'false')
			{
				$this->format = JText::_('DATE_FORMAT_CALENDAR_DATETIME');
			}
			else
			{
				$this->format = JText::_('DATE_FORMAT_CALENDAR_DATE');
			}

			$lang->setDebug($debug);
		}

		// If a known filter is given use it.
		switch (strtoupper($this->filter))
		{
			case 'SERVER_UTC':
				// Convert a date to UTC based on the server timezone.
				if ($this->value && $this->value != JFactory::getDbo()->getNullDate())
				{
					// Get a date object based on the correct timezone.
					$date = JFactory::getDate($this->value, 'UTC');
					$date->setTimezone(new DateTimeZone($config->get('offset')));

					// Transform the date string.
					$this->value = $date->format('Y-m-d H:i:s', true, false);
				}
				break;
			case 'USER_UTC':
				// Convert a date to UTC based on the user timezone.
				if ($this->value && $this->value != JFactory::getDbo()->getNullDate())
				{
					// Get a date object based on the correct timezone.
					$date = JFactory::getDate($this->value, 'UTC');
					$date->setTimezone($user->getTimezone());

					// Transform the date string.
					$this->value = $date->format('Y-m-d H:i:s', true, false);
				}
				break;
		}

		// Format value when not nulldate ('0000-00-00 00:00:00'), otherwise blank it as it would result in 1970-01-01.
		if ($this->value && $this->value != JFactory::getDbo()->getNullDate() && strtotime($this->value) !== false)
		{
			$tz = date_default_timezone_get();
			date_default_timezone_set('UTC');
			$this->value = strftime($this->format, strtotime($this->value));
			date_default_timezone_set($tz);
		}
		else
		{
			$this->value = '';
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since  3.7.0
	 */
	protected function getLayoutData()
	{
		$data      = parent::getLayoutData();
		$tag       = JFactory::getLanguage()->getTag();
		$calendar  = JFactory::getLanguage()->getCalendar();
		$direction = strtolower(JFactory::getDocument()->getDirection());

		// Get the appropriate file for the current language date helper
		$helperPath = 'system/fields/calendar-locales/date/gregorian/date-helper.min.js';

		if (!empty($calendar) && is_dir(JPATH_ROOT . '/media/system/js/fields/calendar-locales/date/' . strtolower($calendar)))
		{
			$helperPath = 'system/fields/calendar-locales/date/' . strtolower($calendar) . '/date-helper.min.js';
		}

		// Get the appropriate locale file for the current language
		$localesPath = 'system/fields/calendar-locales/en.js';

		if (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower($tag) . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . strtolower($tag) . '.js';
		}
		elseif (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . $tag . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . $tag . '.js';
		}
		elseif (is_file(JPATH_ROOT . '/media/system/js/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js'))
		{
			$localesPath = 'system/fields/calendar-locales/' . strtolower(substr($tag, 0, -3)) . '.js';
		}

		$extraData = array(
			'value'        => $this->value,
			'maxLength'    => $this->maxlength,
			'format'       => $this->format,
			'filter'       => $this->filter,
			'todaybutton'  => ($this->todaybutton === 'true') ? 1 : 0,
			'weeknumbers'  => ($this->weeknumbers === 'true') ? 1 : 0,
			'showtime'     => ($this->showtime === 'true') ? 1 : 0,
			'filltable'    => ($this->filltable === 'true') ? 1 : 0,
			'timeformat'   => $this->timeformat,
			'singleheader' => ($this->singleheader === 'true') ? 1 : 0,
			'helperPath'   => $helperPath,
			'localesPath'  => $localesPath,
			'minYear'      => $this->minyear,
			'maxYear'      => $this->maxyear,
			'direction'    => $direction,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/folderlist.php000064400000012460152177723700013134 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.path');

JFormHelper::loadFieldClass('list');

/**
 * Supports an HTML select list of folder
 *
 * @since  1.7.0
 */
class JFormFieldFolderList extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'FolderList';

	/**
	 * The filter.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $filter;

	/**
	 * The exclude.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $exclude;

	/**
	 * The recursive.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $recursive;

	/**
	 * The hideNone.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $hideNone = false;

	/**
	 * The hideDefault.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $hideDefault = false;

	/**
	 * The directory.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $directory;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'filter':
			case 'exclude':
			case 'recursive':
			case 'hideNone':
			case 'hideDefault':
			case 'directory':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'filter':
			case 'directory':
			case 'exclude':
			case 'recursive':
				$this->$name = (string) $value;
				break;

			case 'hideNone':
			case 'hideDefault':
				$value = (string) $value;
				$this->$name = ($value === 'true' || $value === $name || $value === '1');
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->filter  = (string) $this->element['filter'];
			$this->exclude = (string) $this->element['exclude'];

			$recursive       = (string) $this->element['recursive'];
			$this->recursive = ($recursive == 'true' || $recursive == 'recursive' || $recursive == '1');

			$hideNone       = (string) $this->element['hide_none'];
			$this->hideNone = ($hideNone == 'true' || $hideNone == 'hideNone' || $hideNone == '1');

			$hideDefault       = (string) $this->element['hide_default'];
			$this->hideDefault = ($hideDefault == 'true' || $hideDefault == 'hideDefault' || $hideDefault == '1');

			// Get the path in which to search for file options.
			$this->directory = (string) $this->element['directory'];
		}

		return $return;
	}

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		$path = $this->directory;

		if (!is_dir($path))
		{
			$path = JPATH_ROOT . '/' . $path;
		}
		
		$path = JPath::clean($path);

		// Prepend some default options based on field attributes.
		if (!$this->hideNone)
		{
			$options[] = JHtml::_('select.option', '-1', JText::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
		}

		if (!$this->hideDefault)
		{
			$options[] = JHtml::_('select.option', '', JText::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
		}

		// Get a list of folders in the search path with the given filter.
		$folders = JFolder::folders($path, $this->filter, $this->recursive, true);

		// Build the options list from the list of folders.
		if (is_array($folders))
		{
			foreach ($folders as $folder)
			{
				// Check to see if the file is in the exclude mask.
				if ($this->exclude)
				{
					if (preg_match(chr(1) . $this->exclude . chr(1), $folder))
					{
						continue;
					}
				}

				// Remove the root part and the leading /
				$folder = trim(str_replace($path, '', $folder), '/');

				$options[] = JHtml::_('select.option', $folder, $folder);
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/sessionhandler.php000064400000002170152177723700014003 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a select list of session handler options.
 *
 * @since  1.7.0
 */
class JFormFieldSessionHandler extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'SessionHandler';

	/**
	 * Method to get the session handler field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		// Get the options from JSession.
		foreach (JSession::getStores() as $store)
		{
			$options[] = JHtml::_('select.option', $store, JText::_('JLIB_FORM_VALUE_SESSION_' . $store), 'value', 'text');
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/usergroup.php000064400000004572152177723700013025 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Supports a nested checkbox field listing user groups.
 * Multiselect is available by default.
 *
 * @since       1.7.0
 * @deprecated  3.5
 */
class JFormFieldUsergroup extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Usergroup';

	/**
	 * Method to get the user group field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		JLog::add('JFormFieldUsergroup is deprecated. Use JFormFieldUserGroupList instead.', JLog::WARNING, 'deprecated');

		$options = array();
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= $this->disabled ? ' disabled' : '';
		$attr .= $this->size ? ' size="' . $this->size . '"' : '';
		$attr .= $this->multiple ? ' multiple' : '';
		$attr .= $this->required ? ' required aria-required="true"' : '';
		$attr .= $this->autofocus ? ' autofocus' : '';

		// Initialize JavaScript field attributes.
		$attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';
		$attr .= !empty($this->onclick) ? ' onclick="' . $this->onclick . '"' : '';

		// Iterate through the children and build an array of options.
		foreach ($this->element->children() as $option)
		{
			// Only add <option /> elements.
			if ($option->getName() != 'option')
			{
				continue;
			}

			$disabled = (string) $option['disabled'];
			$disabled = ($disabled == 'true' || $disabled == 'disabled' || $disabled == '1');

			// Create a new option object based on the <option /> element.
			$tmp = JHtml::_(
				'select.option', (string) $option['value'], trim((string) $option), 'value', 'text',
				$disabled
			);

			// Set some option attributes.
			$tmp->class = (string) $option['class'];

			// Set some JavaScript option attributes.
			$tmp->onclick = (string) $option['onclick'];

			// Add the option object to the result set.
			$options[] = $tmp;
		}

		return JHtml::_('access.usergroup', $this->name, $this->value, $attr, $options, $this->id);
	}
}
joomla/form/fields/checkboxes.php000064400000007756152177723700013117 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Displays options as a list of checkboxes.
 * Multiselect may be forced to be true.
 *
 * @see    JFormFieldCheckbox
 * @since  1.7.0
 */
class JFormFieldCheckboxes extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Checkboxes';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $layout = 'joomla.form.field.checkboxes';

	/**
	 * Flag to tell the field to always be in multiple values mode.
	 *
	 * @var    boolean
	 * @since  1.7.0
	 */
	protected $forceMultiple = true;

	/**
	 * The comma seprated list of checked checkboxes value.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	public $checkedOptions;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'forceMultiple':
			case 'checkedOptions':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'checkedOptions':
				$this->checkedOptions = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to get the radio button field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->checkedOptions = (string) $this->element['checked'];
		}

		return $return;
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// True if the field has 'value' set. In other words, it has been stored, don't use the default values.
		$hasValue = (isset($this->value) && !empty($this->value));

		// If a value has been stored, use it. Otherwise, use the defaults.
		$checkedOptions = $hasValue ? $this->value : $this->checkedOptions;

		$extraData = array(
			'checkedOptions' => is_array($checkedOptions) ? $checkedOptions : explode(',', (string) $checkedOptions),
			'hasValue'       => $hasValue,
			'options'        => $this->getOptions(),
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/color.php000064400000015042152177723700012102 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Color Form Field class for the Joomla Platform.
 * This implementation is designed to be compatible with HTML5's `<input type="color">`
 *
 * @link   http://www.w3.org/TR/html-markup/input.color.html
 * @since  1.7.3
 */
class JFormFieldColor extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.3
	 */
	protected $type = 'Color';

	/**
	 * The control.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $control = 'hue';

	/**
	 * The format.
	 *
	 * @var    string
	 * @since  3.6.0
	 */
	protected $format = 'hex';

	/**
	 * The keywords (transparent,initial,inherit).
	 *
	 * @var    string
	 * @since  3.6.0
	 */
	protected $keywords = '';

	/**
	 * The position.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $position = 'default';

	/**
	 * The colors.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $colors;

	/**
	 * The split.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $split = 3;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $layout = 'joomla.form.field.color';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'control':
			case 'format':
			case 'keywords':
			case 'exclude':
			case 'colors':
			case 'split':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'split':
				$value = (int) $value;
			case 'control':
			case 'format':
				$this->$name = (string) $value;
				break;
			case 'keywords':
				$this->$name = (string) $value;
				break;
			case 'exclude':
			case 'colors':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->control  = isset($this->element['control']) ? (string) $this->element['control'] : 'hue';
			$this->format   = isset($this->element['format']) ? (string) $this->element['format'] : 'hex';
			$this->keywords = isset($this->element['keywords']) ? (string) $this->element['keywords'] : '';
			$this->position = isset($this->element['position']) ? (string) $this->element['position'] : 'default';
			$this->colors   = (string) $this->element['colors'];
			$this->split    = isset($this->element['split']) ? (int) $this->element['split'] : 3;
		}

		return $return;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.3
	 */
	protected function getInput()
	{
		// Switch the layouts
		$this->layout = $this->control === 'simple' ? $this->layout . '.simple' : $this->layout . '.advanced';

		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.5
	 */
	protected function getLayoutData()
	{
		$lang  = JFactory::getLanguage();
		$data  = parent::getLayoutData();
		$color = strtolower($this->value);
		$color = ! $color ? '' : $color;

		// Position of the panel can be: right (default), left, top or bottom (default RTL is left)
		$position = ' data-position="' . (($lang->isRTL() && $this->position == 'default') ? 'left' : $this->position) . '"';

		if (!$color || in_array($color, array('none', 'transparent')))
		{
			$color = 'none';
		}
		elseif ($color['0'] != '#' && $this->format == 'hex')
		{
			$color = '#' . $color;
		}

		// Assign data for simple/advanced mode
		$controlModeData = $this->control === 'simple' ? $this->getSimpleModeLayoutData() : $this->getAdvancedModeLayoutData($lang);

		$extraData = array(
			'color'    => $color,
			'format'   => $this->format,
			'keywords' => $this->keywords,
			'position' => $position,
			'validate' => $this->validate
		);

		return array_merge($data, $extraData, $controlModeData);
	}

	/**
	 * Method to get the data for the simple mode to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.5
	 */
	protected function getSimpleModeLayoutData()
	{
		$colors = strtolower($this->colors);

		if (empty($colors))
		{
			$colors = array(
				'none',
				'#049cdb',
				'#46a546',
				'#9d261d',
				'#ffc40d',
				'#f89406',
				'#c3325f',
				'#7a43b6',
				'#ffffff',
				'#999999',
				'#555555',
				'#000000',
			);
		}
		else
		{
			$colors = explode(',', $colors);
		}

		if (!$this->split)
		{
			$count = count($colors);
			if ($count % 5 == 0)
			{
				$split = 5;
			}
			else
			{
				if ($count % 4 == 0)
				{
					$split = 4;
				}
			}
		}

		$split = $this->split ? $this->split : 3;

		return array(
			'colors' => $colors,
			'split'  => $split,
		);
	}

	/**
	 * Method to get the data for the advanced mode to be passed to the layout for rendering.
	 *
	 * @param   object  $lang  The language object
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	protected function getAdvancedModeLayoutData($lang)
	{
		return array(
			'colors'  => $this->colors,
			'control' => $this->control,
			'lang'    => $lang,
		);
	}
}
joomla/form/fields/repeatable.php000064400000013153152177723700013071 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Display a JSON loaded window with a repeatable set of sub fields
 *
 * @since       3.2
 *
 * @deprecated  4.0  Use JFormFieldSubform
 */
class JFormFieldRepeatable extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = 'Repeatable';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		JLog::add('JFormFieldRepeatable is deprecated. Use JFormFieldSubform instead.', JLog::WARNING, 'deprecated');

		// Initialize variables.
		$subForm = new JForm($this->name, array('control' => 'jform'));
		$xml = $this->element->children()->asXml();
		$subForm->load($xml);

		// Needed for repeating modals in gmaps
		// @TODO: what and where???
		$subForm->repeatCounter = (int) @$this->form->repeatCounter;

		$children = $this->element->children();
		$subForm->setFields($children);

		// If a maximum value isn't set then we'll make the maximum amount of cells a large number
		$maximum = $this->element['maximum'] ? (int) $this->element['maximum'] : '999';

		// Build a Table
		$head_row_str = array();
		$body_row_str = array();
		$head_row_str[] = '<th></th>';
		$body_row_str[] = '<td><span class="sortable-handler " style="cursor: move;"><span class="icon-menu" aria-hidden="true"></span></span></td>';
		foreach ($subForm->getFieldset() as $field)
		{
			// Reset name to simple
			$field->name = (string) $field->element['name'];

			// Build heading
			$head_row_str[] = '<th>' . strip_tags($field->getLabel($field->name));
			$head_row_str[] = '<br /><small style="font-weight:normal">' . JText::_($field->description) . '</small>';
			$head_row_str[] = '</th>';

			// Build body
			$body_row_str[] = '<td>' . $field->getInput() . '</td>';
		}

		// Append buttons
		$head_row_str[] = '<th><div class="btn-group"><a href="#" class="add btn button btn-success" aria-label="' . JText::_('JGLOBAL_FIELD_ADD') . '">';
		$head_row_str[] = '<span class="icon-plus" aria-hidden="true"></span> </a></div></th>';
		$body_row_str[] = '<td><div class="btn-group">';
		$body_row_str[] = '<a class="add btn button btn-success" aria-label="' . JText::_('JGLOBAL_FIELD_ADD') . '">';
		$body_row_str[] = '<span class="icon-plus" aria-hidden="true"></span> </a>';
		$body_row_str[] = '<a class="remove btn button btn-danger" aria-label="' . JText::_('JGLOBAL_FIELD_REMOVE') . '">';
		$body_row_str[] = '<span class="icon-minus" aria-hidden="true"></span> </a>';
		$body_row_str[] = '</div></td>';

		// Put all table parts together
		$table = '<table id="' . $this->id . '_table" class="adminlist ' . $this->element['class'] . ' table table-striped">'
					. '<thead><tr>' . implode("\n", $head_row_str) . '</tr></thead>'
					. '<tbody><tr>' . implode("\n", $body_row_str) . '</tr></tbody>'
				. '</table>';

		// And finaly build a main container
		$str = array();
		$str[] = '<div id="' . $this->id . '_container">';

		// Add the table to modal
		$str[] = '<div id="' . $this->id . '_modal" class="modal hide">';
		$str[] = $table;
		$str[] = '<div class="modal-footer">';
		$str[] = '<button class="close-modal btn button btn-link">' . JText::_('JCANCEL') . '</button>';
		$str[] = '<button class="save-modal-data btn button btn-primary">' . JText::_('JAPPLY') . '</button>';
		$str[] = '</div>';

		// Close modal container
		$str[] = '</div>';

		// Close main container
		$str[] = '</div>';

		// Button for display the modal window
		$select = (string) $this->element['select'] ? JText::_((string) $this->element['select']) : JText::_('JLIB_FORM_BUTTON_SELECT');
		$icon = $this->element['icon'] ? '<span class="icon-' . $this->element['icon'] . '"></span> ' : '';
		$str[] = '<button class="open-modal btn" id="' . $this->id . '_button" >' . $icon . $select . '</button>';

		if (is_array($this->value))
		{
			$this->value = array_shift($this->value);
		}

		// Script params
		$data = array();
		$data[] = 'data-container="#' . $this->id . '_container"';
		$data[] = 'data-modal-element="#' . $this->id . '_modal"';
		$data[] = 'data-repeatable-element="table tbody tr"';
		$data[] = 'data-bt-add="a.add"';
		$data[] = 'data-bt-remove="a.remove"';
		$data[] = 'data-bt-modal-open="#' . $this->id . '_button"';
		$data[] = 'data-bt-modal-close="button.close-modal"';
		$data[] = 'data-bt-modal-save-data="button.save-modal-data"';
		$data[] = 'data-maximum="' . $maximum . '"';
		$data[] = 'data-input="#' . $this->id . '"';

		// Hidden input, where the main value is
		$value = htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8');
		$str[] = '<input type="hidden" name="' . $this->name . '" id="' . $this->id . '" value="' . $value
				. '"  class="form-field-repeatable" ' . implode(' ', $data) . ' />';

		// Add scripts
		JHtml::_('bootstrap.framework');

		// Depends on jQuery UI
		JHtml::_('jquery.ui', array('core', 'sortable'));

		JHtml::_('script', 'jui/sortablelist.js', array('version' => 'auto', 'relative' => true));
		JHtml::_('stylesheet', 'jui/sortablelist.css', array('version' => 'auto', 'relative' => true));
		JHtml::_('script', 'system/repeatable.js', array('framework' => true, 'version' => 'auto', 'relative' => true));

		$javascript = 'jQuery(document).ready(function($) { $("#' . $this->id . '_table tbody").sortable(); });';

		JFactory::getDocument()->addScriptDeclaration($javascript);

		return implode("\n", $str);
	}
}
joomla/form/fields/imagelist.php000064400000001706152177723700012744 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('filelist');

/**
 * Supports an HTML select list of image
 *
 * @since  1.7.0
 */
class JFormFieldImageList extends JFormFieldFileList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'ImageList';

	/**
	 * Method to get the list of images field options.
	 * Use the filter attribute to specify allowable file extensions.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		// Define the image file type filter.
		$this->filter = '\.png$|\.gif$|\.jpg$|\.bmp$|\.ico$|\.jpeg$|\.psd$|\.eps$';

		// Get the field options.
		return parent::getOptions();
	}
}
joomla/form/fields/timezone.php000064400000007531152177723700012622 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('groupedlist');

/**
 * Form Field class for the Joomla Platform.
 *
 * @since  1.7.0
 */
class JFormFieldTimezone extends JFormFieldGroupedList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Timezone';

	/**
	 * The list of available timezone groups to use.
	 *
	 * @var    array
	 * @since  1.7.0
	 */
	protected static $zones = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific');

	/**
	 * The keyField of timezone field.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $keyField;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'keyField':
				return $this->keyField;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'keyField':
				$this->keyField = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->keyField = (string) $this->element['key_field'];
		}

		return $return;
	}

	/**
	 * Method to get the time zone field option groups.
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   1.7.0
	 */
	protected function getGroups()
	{
		$groups = array();

		// Get the list of time zones from the server.
		$zones = DateTimeZone::listIdentifiers();

		// Build the group lists.
		foreach ($zones as $zone)
		{
			// Time zones not in a group we will ignore.
			if (strpos($zone, '/') === false)
			{
				continue;
			}

			// Get the group/locale from the timezone.
			list ($group, $locale) = explode('/', $zone, 2);

			// Only use known groups.
			if (in_array($group, self::$zones))
			{
				// Initialize the group if necessary.
				if (!isset($groups[$group]))
				{
					$groups[$group] = array();
				}

				// Only add options where a locale exists.
				if (!empty($locale))
				{
					$groups[$group][$zone] = JHtml::_('select.option', $zone, str_replace('_', ' ', $locale), 'value', 'text', false);
				}
			}
		}

		// Sort the group lists.
		ksort($groups);

		foreach ($groups as &$location)
		{
			sort($location);
		}

		// Merge any additional groups in the XML definition.
		$groups = array_merge(parent::getGroups(), $groups);

		return $groups;
	}
}
joomla/form/fields/password.php000064400000010102152177723700012616 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Text field for passwords
 *
 * @link   http://www.w3.org/TR/html-markup/input.password.html#input.password
 * @note   Two password fields may be validated as matching using JFormRuleEquals
 * @since  1.7.0
 */
class JFormFieldPassword extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Password';

	/**
	 * The threshold of password field.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $threshold = 66;

	/**
	 * The allowable maxlength of password.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $maxLength;

	/**
	 * Whether to attach a password strength meter or not.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $meter = false;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.password';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'threshold':
			case 'maxLength':
			case 'meter':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		$value = (string) $value;

		switch ($name)
		{
			case 'maxLength':
			case 'threshold':
				$this->$name = $value;
				break;

			case 'meter':
				$this->meter = ($value === 'true' || $value === $name || $value === '1');
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->maxLength = $this->element['maxlength'] ? (int) $this->element['maxlength'] : 99;
			$this->threshold = $this->element['threshold'] ? (int) $this->element['threshold'] : 66;

			$meter       = (string) $this->element['strengthmeter'];
			$this->meter = ($meter == 'true' || $meter == 'on' || $meter == '1');
		}

		return $return;
	}

	/**
	 * Method to get the field input markup for password.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$extraData = array(
			'maxLength' => $this->maxLength,
			'meter'     => $this->meter,
			'threshold' => $this->threshold,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/language.php000064400000004043152177723700012546 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Supports a list of installed application languages
 *
 * @see    JFormFieldContentLanguage for a select list of content languages.
 * @since  1.7.0
 */
class JFormFieldLanguage extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Language';

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		// Initialize some field attributes.
		$client = (string) $this->element['client'];

		if ($client != 'site' && $client != 'administrator')
		{
			$client = 'site';
		}

		// Make sure the languages are sorted base on locale instead of random sorting
		$languages = JLanguageHelper::createLanguageList($this->value, constant('JPATH_' . strtoupper($client)), true, true);
		if (count($languages) > 1)
		{
			usort(
				$languages,
				function ($a, $b)
				{
					return strcmp($a['value'], $b['value']);
				}
			);
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(
			parent::getOptions(),
			$languages
		);

		// Set the default value active language
		if ($langParams = JComponentHelper::getParams('com_languages'))
		{
			switch ((string) $this->value)
			{
				case 'site':
				case 'frontend':
				case '0':
					$this->value = $langParams->get('site', 'en-GB');
					break;
				case 'admin':
				case 'administrator':
				case 'backend':
				case '1':
					$this->value = $langParams->get('administrator', 'en-GB');
					break;
				case 'active':
				case 'auto':
					$lang = JFactory::getLanguage();
					$this->value = $lang->getTag();
					break;
				default:
				break;
			}
		}

		return $options;
	}
}
joomla/form/fields/number.php000064400000011463152177723700012257 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Provides a one line text box with up-down handles to set a number in the field.
 *
 * @link   http://www.w3.org/TR/html-markup/input.text.html#input.text
 * @since  3.2
 */
class JFormFieldNumber extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = 'Number';

	/**
	 * The allowable maximum value of the field.
	 *
	 * @var    float
	 * @since  3.2
	 */
	protected $max = null;

	/**
	 * The allowable minimum value of the field.
	 *
	 * @var    float
	 * @since  3.2
	 */
	protected $min = null;

	/**
	 * The step by which value of the field increased or decreased.
	 *
	 * @var    float
	 * @since  3.2
	 */
	protected $step = 0;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.number';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'max':
			case 'min':
			case 'step':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'step':
			case 'min':
			case 'max':
				$this->$name = (float) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			// It is better not to force any default limits if none is specified
			$this->max  = isset($this->element['max']) ? (float) $this->element['max'] : null;
			$this->min  = isset($this->element['min']) ? (float) $this->element['min'] : null;
			$this->step = isset($this->element['step']) ? (float) $this->element['step'] : 1;
		}

		return $return;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		if ($this->element['useglobal'])
		{
			$component = JFactory::getApplication()->input->getCmd('option');

			// Get correct component for menu items
			if ($component == 'com_menus')
			{
				$link      = $this->form->getData()->get('link');
				$uri       = new JUri($link);
				$component = $uri->getVar('option', 'com_menus');
			}

			$params = JComponentHelper::getParams($component);
			$value  = $params->get($this->fieldname);

			// Try with global configuration
			if (is_null($value))
			{
				$value = JFactory::getConfig()->get($this->fieldname);
			}

			// Try with menu configuration
			if (is_null($value) && JFactory::getApplication()->input->getCmd('option') == 'com_menus')
			{
				$value = JComponentHelper::getParams('com_menus')->get($this->fieldname);
			}

			if (!is_null($value))
			{
				$value = (string) $value;

				$this->hint = JText::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
			}
		}

		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$extraData = array(
			'max'   => $this->max,
			'min'   => $this->min,
			'step'  => $this->step,
			'value' => $this->value,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/sql.php000064400000016204152177723700011564 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Supports a custom SQL select list
 *
 * @since  1.7.0
 */
class JFormFieldSQL extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	public $type = 'SQL';

	/**
	 * The keyField.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $keyField;

	/**
	 * The valueField.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $valueField;

	/**
	 * The translate.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $translate = false;

	/**
	 * The query.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $query;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'keyField':
			case 'valueField':
			case 'translate':
			case 'query':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'keyField':
			case 'valueField':
			case 'translate':
			case 'query':
				$this->$name = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			// Check if its using the old way
			$this->query = (string) $this->element['query'];

			if (empty($this->query))
			{
				// Get the query from the form
				$query    = array();
				$defaults = array();

				$sql_select = (string) $this->element['sql_select'];
				$sql_from   = (string) $this->element['sql_from'];

				if ($sql_select && $sql_from)
				{
					$query['select'] = $sql_select;
					$query['from']   = $sql_from;
					$query['join']   = (string) $this->element['sql_join'];
					$query['where']  = (string) $this->element['sql_where'];
					$query['group']  = (string) $this->element['sql_group'];
					$query['order']  = (string) $this->element['sql_order'];

					// Get the filters
					$filters = isset($this->element['sql_filter']) ? explode(',', $this->element['sql_filter']) : '';

					// Get the default value for query if empty
					if (is_array($filters))
					{
						foreach ($filters as $filter)
						{
							$name   = "sql_default_{$filter}";
							$attrib = (string) $this->element[$name];

							if (!empty($attrib))
							{
								$defaults[$filter] = $attrib;
							}
						}
					}

					// Process the query
					$this->query = $this->processQuery($query, $filters, $defaults);
				}
			}

			$this->keyField   = (string) $this->element['key_field'] ?: 'value';
			$this->valueField = (string) $this->element['value_field'] ?: (string) $this->element['name'];
			$this->translate  = (string) $this->element['translate'] ?: false;
			$this->header     = (string) $this->element['header'] ?: false;
		}

		return $return;
	}

	/**
	 * Method to process the query from form.
	 *
	 * @param   array   $conditions  The conditions from the form.
	 * @param   string  $filters     The columns to filter.
	 * @param   array   $defaults    The defaults value to set if condition is empty.
	 *
	 * @return  JDatabaseQuery  The query object.
	 *
	 * @since   3.5
	 */
	protected function processQuery($conditions, $filters, $defaults)
	{
		// Get the database object.
		$db = JFactory::getDbo();

		// Get the query object
		$query = $db->getQuery(true);

		// Select fields
		$query->select($conditions['select']);

		// From selected table
		$query->from($conditions['from']);

		// Join over the groups
		if (!empty($conditions['join']))
		{
			$query->join('LEFT', $conditions['join']);
		}

		// Where condition
		if (!empty($conditions['where']))
		{
			$query->where($conditions['where']);
		}

		// Group by
		if (!empty($conditions['group']))
		{
			$query->group($conditions['group']);
		}

		// Process the filters
		if (is_array($filters))
		{
			$html_filters = JFactory::getApplication()->getUserStateFromRequest($this->context . '.filter', 'filter', array(), 'array');

			foreach ($filters as $k => $value)
			{
				if (!empty($html_filters[$value]))
				{
					$escape = $db->quote($db->escape($html_filters[$value]), false);

					$query->where("{$value} = {$escape}");
				}
				elseif (!empty($defaults[$value]))
				{
					$escape = $db->quote($db->escape($defaults[$value]), false);

					$query->where("{$value} = {$escape}");
				}
			}
		}

		// Add order to query
		if (!empty($conditions['order']))
		{
			$query->order($conditions['order']);
		}

		return $query;
	}

	/**
	 * Method to get the custom field options.
	 * Use the query attribute to supply a query to generate the list.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		// Initialize some field attributes.
		$key   = $this->keyField;
		$value = $this->valueField;
		$header = $this->header;

		if ($this->query)
		{
			// Get the database object.
			$db = JFactory::getDbo();

			// Set the query and get the result list.
			$db->setQuery($this->query);

			try
			{
				$items = $db->loadObjectlist();
			}
			catch (JDatabaseExceptionExecuting $e)
			{
				JFactory::getApplication()->enqueueMessage(JText::_('JERROR_AN_ERROR_HAS_OCCURRED'), 'error');
			}
		}

		// Add header.
		if (!empty($header))
		{
			$header_title = JText::_($header);
			$options[] = JHtml::_('select.option', '', $header_title);
		}

		// Build the field options.
		if (!empty($items))
		{
			foreach ($items as $item)
			{
				if ($this->translate == true)
				{
					$options[] = JHtml::_('select.option', $item->$key, JText::_($item->$value));
				}
				else
				{
					$options[] = JHtml::_('select.option', $item->$key, $item->$value);
				}
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/radio.php000064400000002761152177723700012066 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides radio button inputs
 *
 * @link   http://www.w3.org/TR/html-markup/command.radio.html#command.radio
 * @since  1.7.0
 */
class JFormFieldRadio extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Radio';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.5
	 */
	protected $layout = 'joomla.form.field.radio';

	/**
	 * Method to get the radio button field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since   3.5
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		$extraData = array(
			'options' => $this->getOptions(),
			'value'   => (string) $this->value,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/file.php000064400000006534152177723700011711 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Provides an input field for files
 *
 * @link   http://www.w3.org/TR/html-markup/input.file.html#input.file
 * @since  1.7.0
 */
class JFormFieldFile extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'File';

	/**
	 * The accepted file type list.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $accept;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $layout = 'joomla.form.field.file';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'accept':
				return $this->accept;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'accept':
				$this->accept = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->accept = (string) $this->element['accept'];
		}

		return $return;
	}

	/**
	 * Method to get the field input markup for the file field.
	 * Field attributes allow specification of a maximum file size and a string
	 * of accepted file extensions.
	 *
	 * @return  string  The field input markup.
	 *
	 * @note    The field does not include an upload mechanism.
	 * @see     JFormFieldMedia
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since   3.6
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		$extraData = array(
			'accept'   => $this->accept,
			'multiple' => $this->multiple,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/textarea.php000064400000010150152177723700012574 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Supports a multi line area for entry of plain text
 *
 * @link   http://www.w3.org/TR/html-markup/textarea.html#textarea
 * @since  1.7.0
 */
class JFormFieldTextarea extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Textarea';

	/**
	 * The number of rows in textarea.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $rows;

	/**
	 * The number of columns in textarea.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $columns;

	/**
	 * The maximum number of characters in textarea.
	 *
	 * @var    mixed
	 * @since  3.4
	 */
	protected $maxlength;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.textarea';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'rows':
			case 'columns':
			case 'maxlength':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'rows':
			case 'columns':
			case 'maxlength':
				$this->$name = (int) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->rows      = isset($this->element['rows']) ? (int) $this->element['rows'] : false;
			$this->columns   = isset($this->element['cols']) ? (int) $this->element['cols'] : false;
			$this->maxlength = isset($this->element['maxlength']) ? (int) $this->element['maxlength'] : false;
		}

		return $return;
	}

	/**
	 * Method to get the textarea field input markup.
	 * Use the rows and columns attributes to specify the dimensions of the area.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$columns      = $this->columns ? ' cols="' . $this->columns . '"' : '';
		$rows         = $this->rows ? ' rows="' . $this->rows . '"' : '';
		$maxlength    = $this->maxlength ? ' maxlength="' . $this->maxlength . '"' : '';

		$extraData = array(
			'maxlength'    => $maxlength,
			'rows'         => $rows,
			'columns'      => $columns
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/list.php000064400000016434152177723700011745 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Supports a generic list of options.
 *
 * @since  1.7.0
 */
class JFormFieldList extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'List';

	/**
	 * Method to get the field input markup for a generic list.
	 * Use the multiple attribute to enable multiselect.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.7.0
	 */
	protected function getInput()
	{
		$html = array();
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
		$attr .= $this->multiple ? ' multiple' : '';
		$attr .= $this->required ? ' required aria-required="true"' : '';
		$attr .= $this->autofocus ? ' autofocus' : '';

		// To avoid user's confusion, readonly="true" should imply disabled="true".
		if ((string) $this->readonly == '1' || (string) $this->readonly == 'true' || (string) $this->disabled == '1'|| (string) $this->disabled == 'true')
		{
			$attr .= ' disabled="disabled"';
		}

		// Initialize JavaScript field attributes.
		$attr .= $this->onchange ? ' onchange="' . $this->onchange . '"' : '';

		// Get the field options.
		$options = (array) $this->getOptions();

		// Create a read-only list (no name) with hidden input(s) to store the value(s).
		if ((string) $this->readonly == '1' || (string) $this->readonly == 'true')
		{
			$html[] = JHtml::_('select.genericlist', $options, '', trim($attr), 'value', 'text', $this->value, $this->id);

			// E.g. form field type tag sends $this->value as array
			if ($this->multiple && is_array($this->value))
			{
				if (!count($this->value))
				{
					$this->value[] = '';
				}

				foreach ($this->value as $value)
				{
					$html[] = '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"/>';
				}
			}
			else
			{
				$html[] = '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') . '"/>';
			}
		}
		else
		// Create a regular list passing the arguments in an array.
		{
			$listoptions = array();
			$listoptions['option.key'] = 'value';
			$listoptions['option.text'] = 'text';
			$listoptions['list.select'] = $this->value;
			$listoptions['id'] = $this->id;
			$listoptions['list.translate'] = false;
			$listoptions['option.attr'] = 'optionattr';
			$listoptions['list.attr'] = trim($attr);

			$html[] = JHtml::_('select.genericlist', $options, $this->name, $listoptions);
		}

		return implode($html);
	}

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.7.0
	 */
	protected function getOptions()
	{
		$fieldname = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
		$options   = array();

		foreach ($this->element->xpath('option') as $option)
		{
			// Filter requirements
			if ($requires = explode(',', (string) $option['requires']))
			{
				// Requires multilanguage
				if (in_array('multilanguage', $requires) && !JLanguageMultilang::isEnabled())
				{
					continue;
				}

				// Requires associations
				if (in_array('associations', $requires) && !JLanguageAssociations::isEnabled())
				{
					continue;
				}

				// Requires adminlanguage
				if (in_array('adminlanguage', $requires) && !JModuleHelper::isAdminMultilang())
				{
					continue;
				}

				// Requires vote plugin
				if (in_array('vote', $requires) && !JPluginHelper::isEnabled('content', 'vote'))
				{
					continue;
				}
			}

			$value = (string) $option['value'];
			$text  = trim((string) $option) != '' ? trim((string) $option) : $value;

			$disabled = (string) $option['disabled'];
			$disabled = ($disabled == 'true' || $disabled == 'disabled' || $disabled == '1');
			$disabled = $disabled || ($this->readonly && $value != $this->value);

			$checked = (string) $option['checked'];
			$checked = ($checked == 'true' || $checked == 'checked' || $checked == '1');

			$selected = (string) $option['selected'];
			$selected = ($selected == 'true' || $selected == 'selected' || $selected == '1');

			$tmp = array(
					'value'    => $value,
					'text'     => JText::alt($text, $fieldname),
					'disable'  => $disabled,
					'class'    => (string) $option['class'],
					'selected' => ($checked || $selected),
					'checked'  => ($checked || $selected),
			);

			// Set some event handler attributes. But really, should be using unobtrusive js.
			$tmp['onclick']  = (string) $option['onclick'];
			$tmp['onchange'] = (string) $option['onchange'];

			if ((string) $option['showon'])
			{
				$tmp['optionattr'] = " data-showon='" .
					json_encode(
						JFormHelper::parseShowOnConditions((string) $option['showon'], $this->formControl, $this->group)
						)
					. "'";
			}
			// Add the option object to the result set.
			$options[] = (object) $tmp;
		}

		if ($this->element['useglobal'])
		{
			$tmp        = new stdClass;
			$tmp->value = '';
			$tmp->text  = JText::_('JGLOBAL_USE_GLOBAL');
			$component  = JFactory::getApplication()->input->getCmd('option');

			// Get correct component for menu items
			if ($component == 'com_menus')
			{
				$link      = $this->form->getData()->get('link');
				$uri       = new JUri($link);
				$component = $uri->getVar('option', 'com_menus');
			}

			$params = JComponentHelper::getParams($component);
			$value  = $params->get($this->fieldname);

			// Try with global configuration
			if (is_null($value))
			{
				$value = JFactory::getConfig()->get($this->fieldname);
			}

			// Try with menu configuration
			if (is_null($value) && JFactory::getApplication()->input->getCmd('option') == 'com_menus')
			{
				$value = JComponentHelper::getParams('com_menus')->get($this->fieldname);
			}

			if (!is_null($value))
			{
				$value = (string) $value;

				foreach ($options as $option)
				{
					if ($option->value === $value)
					{
						$value = $option->text;

						break;
					}
				}

				$tmp->text = JText::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
			}

			array_unshift($options, $tmp);
		}

		reset($options);

		return $options;
	}

	/**
	 * Method to add an option to the list field.
	 *
	 * @param   string  $text        Text/Language variable of the option.
	 * @param   array   $attributes  Array of attributes ('name' => 'value' format)
	 *
	 * @return  JFormFieldList  For chaining.
	 *
	 * @since   3.7.0
	 */
	public function addOption($text, $attributes = array())
	{
		if ($text && $this->element instanceof SimpleXMLElement)
		{
			$child = $this->element->addChild('option', $text);

			foreach ($attributes as $name => $value)
			{
				$child->addAttribute($name, $value);
			}
		}

		return $this;
	}

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.7.0
	 */
	public function __get($name)
	{
		if ($name == 'options')
		{
			return $this->getOptions();
		}

		return parent::__get($name);
	}
}
joomla/form/fields/integer.php000064400000003370152177723700012422 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a select list of integers with specified first, last and step values.
 *
 * @since  1.7.0
 */
class JFormFieldInteger extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Integer';

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		// Initialize some field attributes.
		$first = (int) $this->element['first'];
		$last = (int) $this->element['last'];
		$step = (int) $this->element['step'];

		// Sanity checks.
		if ($step == 0)
		{
			// Step of 0 will create an endless loop.
			return $options;
		}
		elseif ($first < $last && $step < 0)
		{
			// A negative step will never reach the last number.
			return $options;
		}
		elseif ($first > $last && $step > 0)
		{
			// A position step will never reach the last number.
			return $options;
		}
		elseif ($step < 0)
		{
			// Build the options array backwards.
			for ($i = $first; $i >= $last; $i += $step)
			{
				$options[] = JHtml::_('select.option', $i);
			}
		}
		else
		{
			// Build the options array.
			for ($i = $first; $i <= $last; $i += $step)
			{
				$options[] = JHtml::_('select.option', $i);
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/tel.php000064400000003133152177723700011546 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the Joomla Platform.
 * Supports a text field telephone numbers.
 *
 * @link   http://www.w3.org/TR/html-markup/input.tel.html
 * @see    JFormRuleTel for telephone number validation
 * @see    JHtmlTel for rendering of telephone numbers
 * @since  1.7.0
 */
class JFormFieldTel extends JFormFieldText
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Tel';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7.0
	 */
	protected $layout = 'joomla.form.field.tel';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7.0
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$maxLength    = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';

		$extraData = array(
			'maxLength' => $maxLength,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/accesslevel.php000064400000002743152177723700013261 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a list of access levels. Access levels control what users in specific
 * groups can see.
 *
 * @see    JAccess
 * @since  1.7.0
 */
class JFormFieldAccessLevel extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'AccessLevel';

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= $this->disabled ? ' disabled' : '';
		$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
		$attr .= $this->multiple ? ' multiple' : '';
		$attr .= $this->required ? ' required aria-required="true"' : '';
		$attr .= $this->autofocus ? ' autofocus' : '';

		// Initialize JavaScript field attributes.
		$attr .= $this->onchange ? ' onchange="' . $this->onchange . '"' : '';

		// Get the field options.
		$options = $this->getOptions();

		return JHtml::_('access.level', $this->name, $this->value, $attr, $options, $this->id);
	}
}
joomla/form/fields/text.php000064400000015255152177723700011756 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Supports a one line text field.
 *
 * @link   http://www.w3.org/TR/html-markup/input.text.html#input.text
 * @since  1.7.0
 */
class JFormFieldText extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Text';

	/**
	 * The allowable maxlength of the field.
	 *
	 * @var    integer
	 * @since  3.2
	 */
	protected $maxLength;

	/**
	 * The mode of input associated with the field.
	 *
	 * @var    mixed
	 * @since  3.2
	 */
	protected $inputmode;

	/**
	 * The name of the form field direction (ltr or rtl).
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $dirname;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.text';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'maxLength':
			case 'dirname':
			case 'inputmode':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'maxLength':
				$this->maxLength = (int) $value;
				break;

			case 'dirname':
				$value = (string) $value;
				$this->dirname = ($value == $name || $value == 'true' || $value == '1');
				break;

			case 'inputmode':
				$this->inputmode = (string) $value;
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$result = parent::setup($element, $value, $group);

		if ($result == true)
		{
			$inputmode = (string) $this->element['inputmode'];
			$dirname = (string) $this->element['dirname'];

			$this->inputmode = '';
			$inputmode = preg_replace('/\s+/', ' ', trim($inputmode));
			$inputmode = explode(' ', $inputmode);

			if (!empty($inputmode))
			{
				$defaultInputmode = in_array('default', $inputmode) ? JText::_('JLIB_FORM_INPUTMODE') . ' ' : '';

				foreach (array_keys($inputmode, 'default') as $key)
				{
					unset($inputmode[$key]);
				}

				$this->inputmode = $defaultInputmode . implode(' ', $inputmode);
			}

			// Set the dirname.
			$dirname = ((string) $dirname == 'dirname' || $dirname == 'true' || $dirname == '1');
			$this->dirname = $dirname ? $this->getName($this->fieldname . '_dir') : false;

			$this->maxLength = (int) $this->element['maxlength'];
		}

		return $result;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		if ($this->element['useglobal'])
		{
			$component = JFactory::getApplication()->input->getCmd('option');

			// Get correct component for menu items
			if ($component == 'com_menus')
			{
				$link      = $this->form->getData()->get('link');
				$uri       = new JUri($link);
				$component = $uri->getVar('option', 'com_menus');
			}

			$params = JComponentHelper::getParams($component);
			$value  = $params->get($this->fieldname);

			// Try with global configuration
			if (is_null($value))
			{
				$value = JFactory::getConfig()->get($this->fieldname);
			}

			// Try with menu configuration
			if (is_null($value) && JFactory::getApplication()->input->getCmd('option') == 'com_menus')
			{
				$value = JComponentHelper::getParams('com_menus')->get($this->fieldname);
			}

			if (!is_null($value))
			{
				$value = (string) $value;

				$this->hint = JText::sprintf('JGLOBAL_USE_GLOBAL_VALUE', $value);
			}
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.4
	 */
	protected function getOptions()
	{
		$options = array();

		foreach ($this->element->children() as $option)
		{
			// Only add <option /> elements.
			if ($option->getName() != 'option')
			{
				continue;
			}

			// Create a new option object based on the <option /> element.
			$options[] = JHtml::_(
				'select.option', (string) $option['value'],
				JText::alt(trim((string) $option), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)), 'value', 'text'
			);
		}

		return $options;
	}

	/**
	 * Method to get the field suggestions.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since       3.2
	 * @deprecated  4.0  Use getOptions instead
	 */
	protected function getSuggestions()
	{
		return $this->getOptions();
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.7
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$maxLength    = !empty($this->maxLength) ? ' maxlength="' . $this->maxLength . '"' : '';
		$inputmode    = !empty($this->inputmode) ? ' inputmode="' . $this->inputmode . '"' : '';
		$dirname      = !empty($this->dirname) ? ' dirname="' . $this->dirname . '"' : '';

		/* Get the field options for the datalist.
			Note: getSuggestions() is deprecated and will be changed to getOptions() with 4.0. */
		$options  = (array) $this->getSuggestions();

		$extraData = array(
			'maxLength' => $maxLength,
			'pattern'   => $this->pattern,
			'inputmode' => $inputmode,
			'dirname'   => $dirname,
			'options'   => $options,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/predefinedlist.php000064400000003607152177723700013771 0ustar00<?php
/**
 * @package     Joomla.Libraries
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field to load a list of predefined values
 *
 * @since  3.2
 */
abstract class JFormFieldPredefinedList extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = 'PredefinedList';

	/**
	 * Cached array of the category items.
	 *
	 * @var    array
	 * @since  3.2
	 */
	protected static $options = array();

	/**
	 * Available predefined options
	 *
	 * @var  array
	 * @since  3.2
	 */
	protected $predefinedOptions = array();

	/**
	 * Translate options labels ?
	 *
	 * @var  boolean
	 * @since  3.2
	 */
	protected $translate = true;

	/**
	 * Method to get the options to populate list
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   3.2
	 */
	protected function getOptions()
	{
		// Hash for caching
		$hash = md5($this->element);
		$type = strtolower($this->type);

		if (!isset(static::$options[$type][$hash]) && !empty($this->predefinedOptions))
		{
			static::$options[$type][$hash] = parent::getOptions();

			$options = array();

			// Allow to only use specific values of the predefined list
			$filter = isset($this->element['filter']) ? explode(',', $this->element['filter']) : array();

			foreach ($this->predefinedOptions as $value => $text)
			{
				if (empty($filter) || in_array($value, $filter))
				{
					$text = $this->translate ? JText::_($text) : $text;

					$options[] = (object) array(
						'value' => $value,
						'text'  => $text,
					);
				}
			}

			static::$options[$type][$hash] = array_merge(static::$options[$type][$hash], $options);
		}

		return static::$options[$type][$hash];
	}
}
joomla/form/fields/note.php000064400000003467152177723700011741 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Supports a one line text field.
 *
 * @link   http://www.w3.org/TR/html-markup/input.text.html#input.text
 * @since  1.7.0
 */
class JFormFieldNote extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Note';

	/**
	 * Method to get the field label markup.
	 *
	 * @return  string  The field label markup.
	 *
	 * @since   1.7.0
	 */
	protected function getLabel()
	{
		if (empty($this->element['label']) && empty($this->element['description']))
		{
			return '';
		}

		$title = $this->element['label'] ? (string) $this->element['label'] : ($this->element['title'] ? (string) $this->element['title'] : '');
		$heading = $this->element['heading'] ? (string) $this->element['heading'] : 'h4';
		$description = (string) $this->element['description'];
		$class = !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$close = (string) $this->element['close'];

		$html = array();

		if ($close)
		{
			$close = $close == 'true' ? 'alert' : $close;
			$html[] = '<button type="button" class="close" data-dismiss="' . $close . '">&times;</button>';
		}

		$html[] = !empty($title) ? '<' . $heading . '>' . JText::_($title) . '</' . $heading . '>' : '';
		$html[] = !empty($description) ? JText::_($description) : '';

		return '</div><div ' . $class . '>' . implode('', $html);
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		return '';
	}
}
joomla/form/fields/meter.php000064400000011010152177723700012067 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('number');

/**
 * Form Field class for the Joomla Platform.
 * Provides a meter to show value in a range.
 *
 * @link   http://www.w3.org/TR/html-markup/input.text.html#input.text
 * @since  3.2
 */
class JFormFieldMeter extends JFormFieldNumber
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $type = 'Meter';

	/**
	 * The width of the field increased or decreased.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $width;

	/**
	 * Whether the field is active or not.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $active = false;

	/**
	 * Whether the field is animated or not.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $animated = true;

	/**
	 * The color of the field
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $color;

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.meter';

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'active':
			case 'width':
			case 'animated':
			case 'color':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'width':
			case 'color':
				$this->$name = (string) $value;
				break;

			case 'active':
				$value = (string) $value;
				$this->active = ($value === 'true' || $value === $name || $value === '1');
				break;

			case 'animated':
				$value = (string) $value;
				$this->animated = !($value === 'false' || $value === 'off' || $value === '0');
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->width = isset($this->element['width']) ? (string) $this->element['width'] : '';
			$this->color = isset($this->element['color']) ? (string) $this->element['color'] : '';

			$active       = (string) $this->element['active'];
			$this->active = ($active == 'true' || $active == 'on' || $active == '1');

			$animated       = (string) $this->element['animated'];
			$this->animated = !($animated == 'false' || $animated == 'off' || $animated == '0');
		}

		return $return;
	}

	/**
	 * Method to get the field input markup.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   3.2
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.5
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Initialize some field attributes.
		$extraData = array(
			'width'    => $this->width,
			'color'    => $this->color,
			'animated' => $this->animated,
			'active'   => $this->active,
			'max'      => $this->max,
			'min'      => $this->min,
			'step'     => $this->step,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/aliastag.php000064400000003210152177723700012543 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Framework.
 *
 * @since  2.5.0
 */
class JFormFieldAliastag extends JFormFieldList
{
	/**
	 * The field type.
	 *
	 * @var    string
	 * @since  3.6
	 */
	protected $type = 'Aliastag';

	/**
	 * Method to get a list of options for a list input.
	 *
	 * @return	array  An array of JHtml options.
	 *
	 * @since   3.6
	 */
	protected function getOptions()
	{
			// Get list of tag type alias
			$db    = JFactory::getDbo();
			$query = $db->getQuery(true)
				->select('Distinct type_alias AS value, type_alias AS text')
				->from('#__contentitem_tag_map');
			$db->setQuery($query);

			$options = $db->loadObjectList();

			$lang = JFactory::getLanguage();

			foreach ($options as $i => $item)
			{
				$parts     = explode('.', $item->value);
				$extension = $parts[0];
				$lang->load($extension . '.sys', JPATH_ADMINISTRATOR, null, false, true)
				|| $lang->load($extension, JPath::clean(JPATH_ADMINISTRATOR . '/components/' . $extension), null, false, true);
				$options[$i]->text = JText::_(strtoupper($extension) . '_TAGS_' . strtoupper($parts[1]));
			}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		// Sort by language value
		usort(
			$options,
			function($a, $b)
			{
				return $a->text > $b->text;
			}
		);

		return $options;
	}
}
joomla/form/fields/combo.php000064400000002677152177723700012075 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Implements a combo box field.
 *
 * @since  1.7.0
 */
class JFormFieldCombo extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Combo';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.8.0
	 */
	protected $layout = 'joomla.form.field.combo';

	/**
	 * Method to get the field input markup for a combo box field.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		if (empty($this->layout))
		{
			throw new UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
		}

		return $this->getRenderer($this->layout)->render($this->getLayoutData());
	}

	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since   3.8.0
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		// Get the field options.
		$options = $this->getOptions();

		$extraData = array(
			'options' => $options,
		);

		return array_merge($data, $extraData);
	}
}
joomla/form/fields/spacer.php000064400000006403152177723700012242 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Provides spacer markup to be used in form layouts.
 *
 * @since  1.7.0
 */
class JFormFieldSpacer extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Spacer';

	/**
	 * Method to get the field input markup for a spacer.
	 * The spacer does not have accept input.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		return ' ';
	}

	/**
	 * Method to get the field label markup for a spacer.
	 * Use the label text or name from the XML element as the spacer or
	 * Use a hr="true" to automatically generate plain hr markup
	 *
	 * @return  string  The field label markup.
	 *
	 * @since   1.7.0
	 */
	protected function getLabel()
	{
		$html = array();
		$class = !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$html[] = '<span class="spacer">';
		$html[] = '<span class="before"></span>';
		$html[] = '<span' . $class . '>';

		if ((string) $this->element['hr'] == 'true')
		{
			$html[] = '<hr' . $class . ' />';
		}
		else
		{
			$label = '';

			// Get the label text from the XML element, defaulting to the element name.
			$text = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
			$text = $this->translateLabel ? JText::_($text) : $text;

			// Build the class for the label.
			$class = !empty($this->description) ? 'hasPopover' : '';
			$class = $this->required == true ? $class . ' required' : $class;

			// Add the opening label tag and main attributes attributes.
			$label .= '<label id="' . $this->id . '-lbl" class="' . $class . '"';

			// If a description is specified, use it to build a tooltip.
			if (!empty($this->description))
			{
				JHtml::_('bootstrap.popover');
				$label .= ' title="' . htmlspecialchars(trim($text, ':'), ENT_COMPAT, 'UTF-8') . '"';
				$label .= ' data-content="' . htmlspecialchars(
					$this->translateDescription ? JText::_($this->description) : $this->description,
					ENT_COMPAT,
					'UTF-8'
				) . '"';

				if (JFactory::getLanguage()->isRtl())
				{
					$label .= ' data-placement="left"';
				}
			}

			// Add the label text and closing tag.
			$label .= '>' . $text . '</label>';
			$html[] = $label;
		}

		$html[] = '</span>';
		$html[] = '<span class="after"></span>';
		$html[] = '</span>';

		return implode('', $html);
	}

	/**
	 * Method to get the field title.
	 *
	 * @return  string  The field title.
	 *
	 * @since   1.7.0
	 */
	protected function getTitle()
	{
		return $this->getLabel();
	}

	/**
	 * Method to get a control group with label and input.
	 *
	 * @param   array  $options  Options to be passed into the rendering of the field
	 *
	 * @return  string  A string containing the html for the control group
	 *
	 * @since   3.7.3
	 */
	public function renderField($options = array())
	{
		$options['class'] = empty($options['class']) ? 'field-spacer' : $options['class'] . ' field-spacer';

		return parent::renderField($options);
	}
}
joomla/form/fields/filelist.php000064400000013054152177723700012600 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.file');
jimport('joomla.filesystem.path');

JFormHelper::loadFieldClass('list');

/**
 * Supports an HTML select list of files
 *
 * @since  1.7.0
 */
class JFormFieldFileList extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'FileList';

	/**
	 * The filter.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $filter;

	/**
	 * The exclude.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $exclude;

	/**
	 * The hideNone.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $hideNone = false;

	/**
	 * The hideDefault.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $hideDefault = false;

	/**
	 * The stripExt.
	 *
	 * @var    boolean
	 * @since  3.2
	 */
	protected $stripExt = false;

	/**
	 * The directory.
	 *
	 * @var    string
	 * @since  3.2
	 */
	protected $directory;

	/**
	 * Method to get certain otherwise inaccessible properties from the form field object.
	 *
	 * @param   string  $name  The property name for which to get the value.
	 *
	 * @return  mixed  The property value or null.
	 *
	 * @since   3.2
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'filter':
			case 'exclude':
			case 'hideNone':
			case 'hideDefault':
			case 'stripExt':
			case 'directory':
				return $this->$name;
		}

		return parent::__get($name);
	}

	/**
	 * Method to set certain otherwise inaccessible properties of the form field object.
	 *
	 * @param   string  $name   The property name for which to set the value.
	 * @param   mixed   $value  The value of the property.
	 *
	 * @return  void
	 *
	 * @since   3.2
	 */
	public function __set($name, $value)
	{
		switch ($name)
		{
			case 'filter':
			case 'directory':
			case 'exclude':
				$this->$name = (string) $value;
				break;

			case 'hideNone':
			case 'hideDefault':
			case 'stripExt':
				$value = (string) $value;
				$this->$name = ($value === 'true' || $value === $name || $value === '1');
				break;

			default:
				parent::__set($name, $value);
		}
	}

	/**
	 * Method to attach a JForm object to the field.
	 *
	 * @param   SimpleXMLElement  $element  The SimpleXMLElement object representing the `<field>` tag for the form field object.
	 * @param   mixed             $value    The form field value to validate.
	 * @param   string            $group    The field name group control value. This acts as an array container for the field.
	 *                                      For example if the field has name="foo" and the group value is set to "bar" then the
	 *                                      full field name would end up being "bar[foo]".
	 *
	 * @return  boolean  True on success.
	 *
	 * @see     JFormField::setup()
	 * @since   3.2
	 */
	public function setup(SimpleXMLElement $element, $value, $group = null)
	{
		$return = parent::setup($element, $value, $group);

		if ($return)
		{
			$this->filter  = (string) $this->element['filter'];
			$this->exclude = (string) $this->element['exclude'];

			$hideNone       = (string) $this->element['hide_none'];
			$this->hideNone = ($hideNone == 'true' || $hideNone == 'hideNone' || $hideNone == '1');

			$hideDefault       = (string) $this->element['hide_default'];
			$this->hideDefault = ($hideDefault == 'true' || $hideDefault == 'hideDefault' || $hideDefault == '1');

			$stripExt       = (string) $this->element['stripext'];
			$this->stripExt = ($stripExt == 'true' || $stripExt == 'stripExt' || $stripExt == '1');

			// Get the path in which to search for file options.
			$this->directory = (string) $this->element['directory'];
		}

		return $return;
	}

	/**
	 * Method to get the list of files for the field options.
	 * Specify the target directory with a directory attribute
	 * Attributes allow an exclude mask and stripping of extensions from file name.
	 * Default attribute may optionally be set to null (no file) or -1 (use a default).
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		$path = $this->directory;

		if (!is_dir($path))
		{
			$path = JPATH_ROOT . '/' . $path;
		}
		
		$path = JPath::clean($path);

		// Prepend some default options based on field attributes.
		if (!$this->hideNone)
		{
			$options[] = JHtml::_('select.option', '-1', JText::alt('JOPTION_DO_NOT_USE', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
		}

		if (!$this->hideDefault)
		{
			$options[] = JHtml::_('select.option', '', JText::alt('JOPTION_USE_DEFAULT', preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)));
		}

		// Get a list of files in the search path with the given filter.
		$files = JFolder::files($path, $this->filter);

		// Build the options list from the list of files.
		if (is_array($files))
		{
			foreach ($files as $file)
			{
				// Check to see if the file is in the exclude mask.
				if ($this->exclude)
				{
					if (preg_match(chr(1) . $this->exclude . chr(1), $file))
					{
						continue;
					}
				}

				// If the extension is to be stripped, do it.
				if ($this->stripExt)
				{
					$file = JFile::stripExt($file);
				}

				$options[] = JHtml::_('select.option', $file, $file);
			}
		}

		// Merge any additional options in the XML definition.
		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/cachehandler.php000064400000002062152177723700013363 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('list');

/**
 * Form Field class for the Joomla Platform.
 * Provides a list of available cache handlers
 *
 * @see    JCache
 * @since  1.7.0
 */
class JFormFieldCacheHandler extends JFormFieldList
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'CacheHandler';

	/**
	 * Method to get the field options.
	 *
	 * @return  array  The field option objects.
	 *
	 * @since   1.7.0
	 */
	protected function getOptions()
	{
		$options = array();

		// Convert to name => name array.
		foreach (JCache::getStores() as $store)
		{
			$options[] = JHtml::_('select.option', $store, JText::_('JLIB_FORM_VALUE_CACHE_' . $store), 'value', 'text');
		}

		$options = array_merge(parent::getOptions(), $options);

		return $options;
	}
}
joomla/form/fields/groupedlist.php000064400000012577152177723700013337 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Form Field class for the Joomla Platform.
 * Provides a grouped list select field.
 *
 * @since  1.7.0
 */
class JFormFieldGroupedList extends JFormField
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'GroupedList';

	/**
	 * Method to get the field option groups.
	 *
	 * @return  array  The field option objects as a nested array in groups.
	 *
	 * @since   1.7.0
	 * @throws  UnexpectedValueException
	 */
	protected function getGroups()
	{
		$groups = array();
		$label = 0;

		foreach ($this->element->children() as $element)
		{
			switch ($element->getName())
			{
				// The element is an <option />
				case 'option':
					// Initialize the group if necessary.
					if (!isset($groups[$label]))
					{
						$groups[$label] = array();
					}

					$disabled = (string) $element['disabled'];
					$disabled = ($disabled == 'true' || $disabled == 'disabled' || $disabled == '1');

					// Create a new option object based on the <option /> element.
					$tmp = JHtml::_(
						'select.option', ($element['value']) ? (string) $element['value'] : trim((string) $element),
						JText::alt(trim((string) $element), preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname)), 'value', 'text',
						$disabled
					);

					// Set some option attributes.
					$tmp->class = (string) $element['class'];

					// Set some JavaScript option attributes.
					$tmp->onclick = (string) $element['onclick'];

					// Add the option.
					$groups[$label][] = $tmp;
					break;

				// The element is a <group />
				case 'group':
					// Get the group label.
					if ($groupLabel = (string) $element['label'])
					{
						$label = JText::_($groupLabel);
					}

					// Initialize the group if necessary.
					if (!isset($groups[$label]))
					{
						$groups[$label] = array();
					}

					// Iterate through the children and build an array of options.
					foreach ($element->children() as $option)
					{
						// Only add <option /> elements.
						if ($option->getName() != 'option')
						{
							continue;
						}

						$disabled = (string) $option['disabled'];
						$disabled = ($disabled == 'true' || $disabled == 'disabled' || $disabled == '1');

						// Create a new option object based on the <option /> element.
						$tmp = JHtml::_(
							'select.option', ($option['value']) ? (string) $option['value'] : JText::_(trim((string) $option)),
							JText::_(trim((string) $option)), 'value', 'text', $disabled
						);

						// Set some option attributes.
						$tmp->class = (string) $option['class'];

						// Set some JavaScript option attributes.
						$tmp->onclick = (string) $option['onclick'];

						// Add the option.
						$groups[$label][] = $tmp;
					}

					if ($groupLabel)
					{
						$label = count($groups);
					}
					break;

				// Unknown element type.
				default:
					throw new UnexpectedValueException(sprintf('Unsupported element %s in JFormFieldGroupedList', $element->getName()), 500);
			}
		}

		reset($groups);

		return $groups;
	}

	/**
	 * Method to get the field input markup fora grouped list.
	 * Multiselect is enabled by using the multiple attribute.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		$html = array();
		$attr = '';

		// Initialize some field attributes.
		$attr .= !empty($this->class) ? ' class="' . $this->class . '"' : '';
		$attr .= !empty($this->size) ? ' size="' . $this->size . '"' : '';
		$attr .= $this->multiple ? ' multiple' : '';
		$attr .= $this->required ? ' required aria-required="true"' : '';
		$attr .= $this->autofocus ? ' autofocus' : '';

		// To avoid user's confusion, readonly="true" should imply disabled="true".
		if ($this->readonly || $this->disabled)
		{
			$attr .= ' disabled="disabled"';
		}

		// Initialize JavaScript field attributes.
		$attr .= !empty($this->onchange) ? ' onchange="' . $this->onchange . '"' : '';

		// Get the field groups.
		$groups = (array) $this->getGroups();

		// Create a read-only list (no name) with a hidden input to store the value.
		if ($this->readonly)
		{
			$html[] = JHtml::_(
				'select.groupedlist', $groups, null,
				array(
					'list.attr' => $attr, 'id' => $this->id, 'list.select' => $this->value, 'group.items' => null, 'option.key.toHtml' => false,
					'option.text.toHtml' => false,
				)
			);

			// E.g. form field type tag sends $this->value as array
			if ($this->multiple && is_array($this->value))
			{
				if (!count($this->value))
				{
					$this->value[] = '';
				}

				foreach ($this->value as $value)
				{
					$html[] = '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($value, ENT_COMPAT, 'UTF-8') . '"/>';
				}
			}
			else
			{
				$html[] = '<input type="hidden" name="' . $this->name . '" value="' . htmlspecialchars($this->value, ENT_COMPAT, 'UTF-8') . '"/>';
			}
		}

		// Create a regular list.
		else
		{
			$html[] = JHtml::_(
				'select.groupedlist', $groups, $this->name,
				array(
					'list.attr' => $attr, 'id' => $this->id, 'list.select' => $this->value, 'group.items' => null, 'option.key.toHtml' => false,
					'option.text.toHtml' => false,
				)
			);
		}

		return implode($html);
	}
}
joomla/form/fields/email.php000064400000002727152177723700012061 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Form
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JFormHelper::loadFieldClass('text');

/**
 * Form Field class for the Joomla Platform.
 * Provides and input field for email addresses
 *
 * @link   http://www.w3.org/TR/html-markup/input.email.html#input.email
 * @see    JFormRuleEmail
 * @since  1.7.0
 */
class JFormFieldEMail extends JFormFieldText
{
	/**
	 * The form field type.
	 *
	 * @var    string
	 * @since  1.7.0
	 */
	protected $type = 'Email';

	/**
	 * Name of the layout being used to render the field
	 *
	 * @var    string
	 * @since  3.7
	 */
	protected $layout = 'joomla.form.field.email';

	/**
	 * Method to get the field input markup for email addresses.
	 *
	 * @return  string  The field input markup.
	 *
	 * @since   1.7.0
	 */
	protected function getInput()
	{
		// Trim the trailing line in the layout file
		return rtrim($this->getRenderer($this->layout)->render($this->getLayoutData()), PHP_EOL);
	}
	/**
	 * Method to get the data to be passed to the layout for rendering.
	 *
	 * @return  array
	 *
	 * @since 3.5
	 */
	protected function getLayoutData()
	{
		$data = parent::getLayoutData();

		$extraData = array(
			'maxLength'  => $this->maxLength,
			'multiple'   => $this->multiple,
		);

		return array_merge($data, $extraData);
	}
}
joomla/grid/grid.php000064400000022631152177723700010427 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Grid

 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * JGrid class to dynamically generate HTML tables
 *
 * @since       1.7.3
 * @deprecated  4.0 This class will be removed without any replacement
 */
class JGrid
{
	/**
	 * Array of columns
	 * @var array
	 * @since 1.7.3
	 */
	protected $columns = array();

	/**
	 * Current active row
	 * @var int
	 * @since 1.7.3
	 */
	protected $activeRow = 0;

	/**
	 * Rows of the table (including header and footer rows)
	 * @var array
	 * @since 1.7.3
	 */
	protected $rows = array();

	/**
	 * Header and Footer row-IDs
	 * @var array
	 * @since 1.7.3
	 */
	protected $specialRows = array('header' => array(), 'footer' => array());

	/**
	 * Associative array of attributes for the table-tag
	 * @var array
	 * @since 1.7.3
	 */
	protected $options;

	/**
	 * Constructor for a JGrid object
	 *
	 * @param   array  $options  Associative array of attributes for the table-tag
	 *
	 * @since 1.7.3
	 */
	public function __construct($options = array())
	{
		$this->setTableOptions($options, true);
	}

	/**
	 * Magic function to render this object as a table.
	 *
	 * @return  string
	 *
	 * @since 1.7.3
	 */
	public function __toString()
	{
		return $this->toString();
	}

	/**
	 * Method to set the attributes for a table-tag
	 *
	 * @param   array  $options  Associative array of attributes for the table-tag
	 * @param   bool   $replace  Replace possibly existing attributes
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function setTableOptions($options = array(), $replace = false)
	{
		if ($replace)
		{
			$this->options = $options;
		}
		else
		{
			$this->options = array_merge($this->options, $options);
		}

		return $this;
	}

	/**
	 * Get the Attributes of the current table
	 *
	 * @return  array Associative array of attributes
	 *
	 * @since 1.7.3
	 */
	public function getTableOptions()
	{
		return $this->options;
	}

	/**
	 * Add new column name to process
	 *
	 * @param   string  $name  Internal column name
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function addColumn($name)
	{
		$this->columns[] = $name;

		return $this;
	}

	/**
	 * Returns the list of internal columns
	 *
	 * @return  array List of internal columns
	 *
	 * @since 1.7.3
	 */
	public function getColumns()
	{
		return $this->columns;
	}

	/**
	 * Delete column by name
	 *
	 * @param   string  $name  Name of the column to be deleted
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function deleteColumn($name)
	{
		$index = array_search($name, $this->columns);

		if ($index !== false)
		{
			unset($this->columns[$index]);
			$this->columns = array_values($this->columns);
		}

		return $this;
	}

	/**
	 * Method to set a whole range of columns at once
	 * This can be used to re-order the columns, too
	 *
	 * @param   array  $columns  List of internal column names
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function setColumns($columns)
	{
		$this->columns = array_values($columns);

		return $this;
	}

	/**
	 * Adds a row to the table and sets the currently
	 * active row to the new row
	 *
	 * @param   array  $options  Associative array of attributes for the row
	 * @param   int    $special  1 for a new row in the header, 2 for a new row in the footer
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function addRow($options = array(), $special = false)
	{
		$this->rows[]['_row'] = $options;
		$this->activeRow = count($this->rows) - 1;

		if ($special)
		{
			if ($special === 1)
			{
				$this->specialRows['header'][] = $this->activeRow;
			}
			else
			{
				$this->specialRows['footer'][] = $this->activeRow;
			}
		}

		return $this;
	}

	/**
	 * Method to get the attributes of the currently active row
	 *
	 * @return array Associative array of attributes
	 *
	 * @since 1.7.3
	 */
	public function getRowOptions()
	{
		return $this->rows[$this->activeRow]['_row'];
	}

	/**
	 * Method to set the attributes of the currently active row
	 *
	 * @param   array  $options  Associative array of attributes
	 *
	 * @return JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function setRowOptions($options)
	{
		$this->rows[$this->activeRow]['_row'] = $options;

		return $this;
	}

	/**
	 * Get the currently active row ID
	 *
	 * @return  int ID of the currently active row
	 *
	 * @since 1.7.3
	 */
	public function getActiveRow()
	{
		return $this->activeRow;
	}

	/**
	 * Set the currently active row
	 *
	 * @param   int  $id  ID of the row to be set to current
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function setActiveRow($id)
	{
		$this->activeRow = (int) $id;

		return $this;
	}

	/**
	 * Set cell content for a specific column for the
	 * currently active row
	 *
	 * @param   string  $name     Name of the column
	 * @param   string  $content  Content for the cell
	 * @param   array   $option   Associative array of attributes for the td-element
	 * @param   bool    $replace  If false, the content is appended to the current content of the cell
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function setRowCell($name, $content, $option = array(), $replace = true)
	{
		if ($replace || !isset($this->rows[$this->activeRow][$name]))
		{
			$cell = new stdClass;
			$cell->options = $option;
			$cell->content = $content;
			$this->rows[$this->activeRow][$name] = $cell;
		}
		else
		{
			$this->rows[$this->activeRow][$name]->content .= $content;
			$this->rows[$this->activeRow][$name]->options = $option;
		}

		return $this;
	}

	/**
	 * Get all data for a row
	 *
	 * @param   int  $id  ID of the row to return
	 *
	 * @return  array Array of columns of a table row
	 *
	 * @since 1.7.3
	 */
	public function getRow($id = false)
	{
		if ($id === false)
		{
			$id = $this->activeRow;
		}

		if (isset($this->rows[(int) $id]))
		{
			return $this->rows[(int) $id];
		}
		else
		{
			return false;
		}
	}

	/**
	 * Get the IDs of all rows in the table
	 *
	 * @param   int  $special  false for the standard rows, 1 for the header rows, 2 for the footer rows
	 *
	 * @return  array Array of IDs
	 *
	 * @since 1.7.3
	 */
	public function getRows($special = false)
	{
		if ($special)
		{
			if ($special === 1)
			{
				return $this->specialRows['header'];
			}
			else
			{
				return $this->specialRows['footer'];
			}
		}

		return array_diff(array_keys($this->rows), array_merge($this->specialRows['header'], $this->specialRows['footer']));
	}

	/**
	 * Delete a row from the object
	 *
	 * @param   int  $id  ID of the row to be deleted
	 *
	 * @return  JGrid This object for chaining
	 *
	 * @since 1.7.3
	 */
	public function deleteRow($id)
	{
		unset($this->rows[$id]);

		if (in_array($id, $this->specialRows['header']))
		{
			unset($this->specialRows['header'][array_search($id, $this->specialRows['header'])]);
		}

		if (in_array($id, $this->specialRows['footer']))
		{
			unset($this->specialRows['footer'][array_search($id, $this->specialRows['footer'])]);
		}

		if ($this->activeRow == $id)
		{
			end($this->rows);
			$this->activeRow = key($this->rows);
		}

		return $this;
	}

	/**
	 * Render the HTML table
	 *
	 * @return  string The rendered HTML table
	 *
	 * @since 1.7.3
	 */
	public function toString()
	{
		$output = array();
		$output[] = '<table' . $this->renderAttributes($this->getTableOptions()) . '>';

		if (count($this->specialRows['header']))
		{
			$output[] = $this->renderArea($this->specialRows['header'], 'thead', 'th');
		}

		if (count($this->specialRows['footer']))
		{
			$output[] = $this->renderArea($this->specialRows['footer'], 'tfoot');
		}

		$ids = array_diff(array_keys($this->rows), array_merge($this->specialRows['header'], $this->specialRows['footer']));

		if (count($ids))
		{
			$output[] = $this->renderArea($ids);
		}

		$output[] = '</table>';

		return implode('', $output);
	}

	/**
	 * Render an area of the table
	 *
	 * @param   array   $ids   IDs of the rows to render
	 * @param   string  $area  Name of the area to render. Valid: tbody, tfoot, thead
	 * @param   string  $cell  Name of the cell to render. Valid: td, th
	 *
	 * @return string The rendered table area
	 *
	 * @since 1.7.3
	 */
	protected function renderArea($ids, $area = 'tbody', $cell = 'td')
	{
		$output = array();
		$output[] = '<' . $area . ">\n";

		foreach ($ids as $id)
		{
			$output[] = "\t<tr" . $this->renderAttributes($this->rows[$id]['_row']) . ">\n";

			foreach ($this->getColumns() as $name)
			{
				if (isset($this->rows[$id][$name]))
				{
					$column = $this->rows[$id][$name];
					$output[] = "\t\t<" . $cell . $this->renderAttributes($column->options) . '>' . $column->content . '</' . $cell . ">\n";
				}
			}

			$output[] = "\t</tr>\n";
		}

		$output[] = '</' . $area . '>';

		return implode('', $output);
	}

	/**
	 * Renders an HTML attribute from an associative array
	 *
	 * @param   array  $attributes  Associative array of attributes
	 *
	 * @return  string The HTML attribute string
	 *
	 * @since 1.7.3
	 */
	protected function renderAttributes($attributes)
	{
		if (count((array) $attributes) == 0)
		{
			return '';
		}

		$return = array();

		foreach ($attributes as $key => $option)
		{
			$return[] = $key . '="' . $option . '"';
		}

		return ' ' . implode(' ', $return);
	}
}
joomla/mediawiki/users.php000064400000026600152177723700011661 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Users class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiUsers extends JMediawikiObject
{
	/**
	 * Method to login and get authentication tokens.
	 *
	 * @param   string  $lgname      User Name.
	 * @param   string  $lgpassword  Password.
	 * @param   string  $lgdomain    Domain (optional).
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function login($lgname, $lgpassword, $lgdomain = null)
	{
		// Build the request path.
		$path = '?action=login&lgname=' . $lgname . '&lgpassword=' . $lgpassword;

		if (isset($lgdomain))
		{
			$path .= '&lgdomain=' . $lgdomain;
		}

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), null);

		// Request path with login token.
		$path = '?action=login&lgname=' . $lgname . '&lgpassword=' . $lgpassword . '&lgtoken=' . $this->validateResponse($response)->login['token'];

		if (isset($lgdomain))
		{
			$path .= '&lgdomain=' . $lgdomain;
		}

		// Set the session cookies returned.
		$headers = (array) $this->options->get('headers');
		$headers['Cookie'] = !empty($headers['Cookie']) ? empty($headers['Cookie']) : '';
		$headers['Cookie'] = $headers['Cookie'] . $response->headers['Set-Cookie'];
		$this->options->set('headers', $headers);

		// Send the request again with the token.
		$response = $this->client->post($this->fetchUrl($path), null);
		$response_body = $this->validateResponse($response);

		$headers = (array) $this->options->get('headers');
		$cookie_prefix = $response_body->login['cookieprefix'];
		$cookie = $cookie_prefix . 'UserID=' . $response_body->login['lguserid'] . '; ' . $cookie_prefix
			. 'UserName=' . $response_body->login['lgusername'];
		$headers['Cookie'] = $headers['Cookie'] . '; ' . $response->headers['Set-Cookie'] . '; ' . $cookie;
		$this->options->set('headers', $headers);

		return $this->validateResponse($response);
	}

	/**
	 * Method to logout and clear session data.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function logout()
	{
		// Build the request path.
		$path = '?action=login';

		// @TODO clear internal data as well

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get user information.
	 *
	 * @param   array  $ususers  A list of users to obtain the same information for.
	 * @param   array  $usprop   What pieces of information to include.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getUserInfo(array $ususers, array $usprop = null)
	{
		// Build the request path.
		$path = '?action=query&list=users';

		// Append users to the request.
		$path .= '&ususers=' . $this->buildParameter($ususers);

		if (isset($usprop))
		{
			$path .= '&usprop' . $this->buildParameter($usprop);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get current user information.
	 *
	 * @param   array  $uiprop  What pieces of information to include.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getCurrentUserInfo(array $uiprop = null)
	{
		// Build the request path.
		$path = '?action=query&meta=userinfo';

		if (isset($uiprop))
		{
			$path .= '&uiprop' . $this->buildParameter($uiprop);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get user contributions.
	 *
	 * @param   string   $ucuser        The users to retrieve contributions for.
	 * @param   string   $ucuserprefix  Retrieve contibutions for all users whose names begin with this value.
	 * @param   integer  $uclimit       The users to retrieve contributions for.
	 * @param   string   $ucstart       The start timestamp to return from.
	 * @param   string   $ucend         The end timestamp to return to.
	 * @param   boolean  $uccontinue    When more results are available, use this to continue.
	 * @param   string   $ucdir         In which direction to enumerate.
	 * @param   array    $ucnamespace   Only list contributions in these namespaces.
	 * @param   array    $ucprop        Include additional pieces of information.
	 * @param   array    $ucshow        Show only items that meet this criteria.
	 * @param   string   $uctag         Only list revisions tagged with this tag.
	 * @param   string   $uctoponly     Only list changes which are the latest revision
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getUserContribs($ucuser = null, $ucuserprefix = null, $uclimit = null, $ucstart = null, $ucend = null, $uccontinue = null,
		$ucdir = null, array $ucnamespace = null, array $ucprop = null, array $ucshow = null, $uctag = null, $uctoponly = null)
	{
		// Build the request path.
		$path = '?action=query&list=usercontribs';

		if (isset($ucuser))
		{
			$path .= '&ucuser=' . $ucuser;
		}

		if (isset($ucuserprefix))
		{
			$path .= '&ucuserprefix=' . $ucuserprefix;
		}

		if (isset($uclimit))
		{
			$path .= '&uclimit=' . $uclimit;
		}

		if (isset($ucstart))
		{
			$path .= '&ucstart=' . $ucstart;
		}

		if (isset($ucend))
		{
			$path .= '&ucend=' . $ucend;
		}

		if ($uccontinue)
		{
			$path .= '&uccontinue=';
		}

		if (isset($ucdir))
		{
			$path .= '&ucdir=' . $ucdir;
		}

		if (isset($ucnamespace))
		{
			$path .= '&ucnamespace=' . $this->buildParameter($ucnamespace);
		}

		if (isset($ucprop))
		{
			$path .= '&ucprop=' . $this->buildParameter($ucprop);
		}

		if (isset($ucshow))
		{
			$path .= '&ucshow=' . $this->buildParameter($ucshow);
		}

		if (isset($uctag))
		{
			$path .= '&uctag=' . $uctag;
		}

		if (isset($uctoponly))
		{
			$path .= '&uctoponly=' . $uctoponly;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to block a user.
	 *
	 * @param   string   $user           Username, IP address or IP range you want to block.
	 * @param   string   $expiry         Relative expiry time, Default: never.
	 * @param   string   $reason         Reason for block (optional).
	 * @param   boolean  $anononly       Block anonymous users only.
	 * @param   boolean  $nocreate       Prevent account creation.
	 * @param   boolean  $autoblock      Automatically block the last used IP address, and any subsequent IP addresses they try to login from.
	 * @param   boolean  $noemail        Prevent user from sending email through the wiki.
	 * @param   boolean  $hidename       Hide the username from the block log.
	 * @param   boolean  $allowusertalk  Allow the user to edit their own talk page.
	 * @param   boolean  $reblock        If the user is already blocked, overwrite the existing block.
	 * @param   boolean  $watchuser      Watch the user/IP's user and talk pages.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function blockUser($user, $expiry = null, $reason = null, $anononly = null, $nocreate = null, $autoblock = null, $noemail = null,
		$hidename = null, $allowusertalk = null, $reblock = null, $watchuser = null)
	{
		// Get the token.
		$token = $this->getToken($user, 'block');

		// Build the request path.
		$path = '?action=unblock';

		// Build the request data.
		$data = array(
			'user' => $user,
			'token' => $token,
			'expiry' => $expiry,
			'reason' => $reason,
			'anononly' => $anononly,
			'nocreate' => $nocreate,
			'autoblock' => $autoblock,
			'noemail' => $noemail,
			'hidename' => $hidename,
			'allowusetalk' => $allowusertalk,
			'reblock' => $reblock,
			'watchuser' => $watchuser,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to unblock a user.
	 *
	 * @param   string  $user    Username, IP address or IP range you want to unblock.
	 * @param   string  $reason  Reason for unblock (optional).
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function unBlockUserByName($user, $reason = null)
	{
		// Get the token.
		$token = $this->getToken($user, 'unblock');

		// Build the request path.
		$path = '?action=unblock';

		// Build the request data.
		$data = array(
				'user' => $user,
				'token' => $token,
				'reason' => $reason,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to unblock a user.
	 *
	 * @param   int     $id      Username, IP address or IP range you want to unblock.
	 * @param   string  $reason  Reason for unblock (optional).
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function unBlockUserById($id, $reason = null)
	{
		// Get the token.
		$token = $this->getToken($id, 'unblock');

		// Build the request path.
		$path = '?action=unblock';

		// Build the request data.
		// TODO: $data doesn't seem to be used!
		$data = array(
			'id' => $id,
			'token' => $token,
			'reason' => $reason,
		);

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to assign a user to a group.
	 *
	 * @param   string  $username  User name.
	 * @param   array   $add       Add the user to these groups.
	 * @param   array   $remove    Remove the user from these groups.
	 * @param   string  $reason    Reason for the change.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function assignGroup($username, $add = null, $remove = null, $reason = null)
	{
		// Get the token.
		$token = $this->getToken($username, 'unblock');

		// Build the request path.
		$path = '?action=userrights';

		// Build the request data.
		$data = array(
			'username' => $username,
			'token' => $token,
			'add' => $add,
			'remove' => $remove,
			'reason' => $reason,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to email a user.
	 *
	 * @param   string   $target   User to send email to.
	 * @param   string   $subject  Subject header.
	 * @param   string   $text     Mail body.
	 * @param   boolean  $ccme     Send a copy of this mail to me.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function emailUser($target, $subject = null, $text = null, $ccme = null)
	{
		// Get the token.
		$token = $this->getToken($target, 'emailuser');

		// Build the request path.
		$path = '?action=emailuser';

		// Build the request data.
		$data = array(
			'target' => $target,
			'token' => $token,
			'subject' => $subject,
			'text' => $text,
			'ccme' => $ccme,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to get access token.
	 *
	 * @param   string  $user     The User to get token.
	 * @param   string  $intoken  The type of token.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getToken($user, $intoken)
	{
		// Build the request path.
		$path = '?action=query&prop=info&intoken=' . $intoken . '&titles=User:' . $user;

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), null);

		return (string) $this->validateResponse($response)->query->pages->page[$intoken . 'token'];
	}
}
joomla/mediawiki/search.php000064400000006136152177723700011767 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Search class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiSearch extends JMediawikiObject
{
	/**
	 * Method to perform a full text search.
	 *
	 * @param   string   $srsearch     Search for all page titles (or content) that has this value.
	 * @param   array    $srnamespace  The namespace(s) to enumerate.
	 * @param   string   $srwhat       Search inside the text or titles.
	 * @param   array    $srinfo       What metadata to return.
	 * @param   array    $srprop       What properties to return.
	 * @param   boolean  $srredirects  Include redirect pages in the search.
	 * @param   integer  $sroffest     Use this value to continue paging.
	 * @param   integer  $srlimit      How many total pages to return.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function search($srsearch, array $srnamespace = null, $srwhat = null, array $srinfo = null, array $srprop = null,
		$srredirects = null, $sroffest = null, $srlimit = null)
	{
		// Build the request.
		$path = '?action=query&list=search';

		if (isset($srsearch))
		{
			$path .= '&srsearch=' . $srsearch;
		}

		if (isset($srnamespace))
		{
			$path .= '&srnamespace=' . $this->buildParameter($srnamespace);
		}

		if (isset($srwhat))
		{
			$path .= '&srwhat=' . $srwhat;
		}

		if (isset($srinfo))
		{
			$path .= '&srinfo=' . $this->buildParameter($srinfo);
		}

		if (isset($srprop))
		{
			$path .= '&srprop=' . $this->buildParameter($srprop);
		}

		if ($srredirects)
		{
			$path .= '&srredirects=';
		}

		if (isset($sroffest))
		{
			$path .= '&sroffest=' . $sroffest;
		}

		if (isset($srlimit))
		{
			$path .= '&srlimit=' . $srlimit;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to search the wiki using opensearch protocol.
	 *
	 * @param   string   $search     Search string.
	 * @param   integer  $limit	     Maximum amount of results to return.
	 * @param   array    $namespace  Namespaces to search.
	 * @param   string   $suggest    Do nothing if $wgEnableOpenSearchSuggest is false.
	 * @param   string   $format     Output format.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function openSearch($search, $limit = null, array $namespace = null, $suggest = null, $format = null)
	{
		// Build the request.
		$path = '?action=query&list=search';

		if (isset($search))
		{
			$path .= '&search=' . $search;
		}

		if (isset($limit))
		{
			$path .= '&limit=' . $limit;
		}

		if (isset($namespace))
		{
			$path .= '&namespace=' . $this->buildParameter($namespace);
		}

		if (isset($suggest))
		{
			$path .= '&suggest=' . $suggest;
		}

		if (isset($format))
		{
			$path .= '&format=' . $format;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}
}
joomla/mediawiki/http.php000064400000005770152177723700011504 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * HTTP client class for connecting to a MediaWiki instance.
 *
 * @since  3.1.4
 */
class JMediawikiHttp extends JHttp
{
	/**
     * Constructor.
     *
     * @param   Registry        $options    Client options object.
     * @param   JHttpTransport  $transport  The HTTP transport object.
     *
     * @since   3.1.4
     */
	public function __construct(Registry $options = null, JHttpTransport $transport = null)
	{
		// Override the JHttp contructor to use JHttpTransportStream.
		$this->options = isset($options) ? $options : new Registry;
		$this->transport = isset($transport) ? $transport : new JHttpTransportStream($this->options);

		// Make sure the user agent string is defined.
		$this->options->def('api.useragent', 'JMediawiki/1.0');

		// Set the default timeout to 120 seconds.
		$this->options->def('api.timeout', 120);
	}

	/**
	 * Method to send the GET command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request.
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  JHttpResponse
	 *
	 * @since   3.1.4
	 */
	public function get($url, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('api.timeout'))
		{
			$timeout = $this->options->get('api.timeout');
		}

		return $this->transport->request('GET', new JUri($url), null, $headers, $timeout, $this->options->get('api.useragent'));
	}

	/**
	 * Method to send the POST command to the server.
	 *
	 * @param   string   $url      Path to the resource.
	 * @param   mixed    $data     Either an associative array or a string to be sent with the request.
	 * @param   array    $headers  An array of name-value pairs to include in the header of the request
	 * @param   integer  $timeout  Read timeout in seconds.
	 *
	 * @return  JHttpResponse
	 *
	 * @since   3.1.4
	 */
	public function post($url, $data, array $headers = null, $timeout = null)
	{
		// Look for headers set in the options.
		$temp = (array) $this->options->get('headers');

		foreach ($temp as $key => $val)
		{
			if (!isset($headers[$key]))
			{
				$headers[$key] = $val;
			}
		}

		// Look for timeout set in the options.
		if ($timeout === null && $this->options->exists('api.timeout'))
		{
			$timeout = $this->options->get('api.timeout');
		}

		return $this->transport->request('POST', new JUri($url), $data, $headers, $timeout, $this->options->get('api.useragent'));
	}
}
joomla/mediawiki/object.php000064400000005125152177723700011765 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * MediaWiki API object class for the Joomla Platform.
 *
 * @since  3.1.4
 */
abstract class JMediawikiObject
{
	/**
	 * @var    Registry  Options for the MediaWiki object.
	 * @since  3.1.4
	 */
	protected $options;

	/**
	 * @var    JMediawikiHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $client;

	/**
     * Constructor.
     *
     * @param   Registry        $options  Mediawiki options object.
     * @param   JMediawikiHttp  $client   The HTTP client object.
     *
     * @since   3.1.4
     */
	public function __construct(Registry $options = null, JMediawikiHttp $client = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JMediawikiHttp($this->options);
	}

	/**
	 * Method to build and return a full request URL for the request.
	 *
	 * @param   string  $path  URL to inflect
	 *
	 * @return  string   The request URL.
	 *
	 * @since   3.1.4
	 */
	protected function fetchUrl($path)
	{
		// Append the path with output format
		$path .= '&format=xml';

		$uri = new JUri($this->options->get('api.url') . '/api.php' . $path);

		if ($this->options->get('api.username', false))
		{
			$uri->setUser($this->options->get('api.username'));
		}

		if ($this->options->get('api.password', false))
		{
			$uri->setPass($this->options->get('api.password'));
		}

		return (string) $uri;
	}

	/**
	 * Method to build request parameters from a string array.
	 *
	 * @param   array  $params  string array that contains the parameters
	 *
	 * @return  string   request parameter
	 *
	 * @since   3.1.4
	 */
	public function buildParameter(array $params)
	{
		$path = '';

		foreach ($params as $param)
		{
			$path .= $param;

			if (next($params) == true)
			{
				$path .= '|';
			}
		}

		return $path;
	}

	/**
	 * Method to validate response for errors
	 *
	 * @param   JHttpresponse  $response  reponse from the mediawiki server
	 *
	 * @return  Object
	 *
	 * @since   3.1.4
	 *
	 * @throws  DomainException
	 */
	public function validateResponse($response)
	{
		$xml = simplexml_load_string($response->body);

		if (isset($xml->warnings))
		{
			throw new DomainException($xml->warnings->info);
		}

		if (isset($xml->error))
		{
			throw new DomainException($xml->error['info']);
		}

		return $xml;
	}
}
joomla/mediawiki/sites.php000064400000016631152177723700011652 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Sites class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiSites extends JMediawikiObject
{
	/**
	 * Method to get site information.
	 *
	 * @param   array    $siprop            The sysinfo properties to get.
	 * @param   string   $sifilteriw        Only local or only non local entries to return.
	 * @param   boolean  $sishowalldb       List all database servers.
	 * @param   boolean  $sinumberingroup   List the number of users in usergroups.
	 * @param   array    $siinlanguagecode  Language code for localized languages.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getSiteInfo(array $siprop = null, $sifilteriw = null, $sishowalldb = false, $sinumberingroup = false, array $siinlanguagecode = null)
	{
		// Build the request.
		$path = '?action=query&meta=siteinfo';

		if (isset($siprop))
		{
			$path .= '&siprop=' . $this->buildParameter($siprop);
		}

		if (isset($sifilteriw))
		{
			$path .= '&sifilteriw=' . $sifilteriw;
		}

		if ($sishowalldb)
		{
			$path .= '&sishowalldb=';
		}

		if ($sinumberingroup)
		{
			$path .= '&sinumberingroup=';
		}

		if (isset($siinlanguagecode))
		{
			$path .= '&siinlanguagecode=' . $this->buildParameter($siinlanguagecode);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get events from logs.
	 *
	 * @param   array    $leprop    List of properties to get.
	 * @param   string   $letype    Filter log actions to only this type.
	 * @param   string   $leaction  Filter log actions to only this type.
	 * @param   string   $letitle   Filter entries to those related to a page.
	 * @param   string   $leprefix  Filter entries that start with this prefix.
	 * @param   string   $letag     Filter entries with tag.
	 * @param   string   $leuser    Filter entries made by the given user.
	 * @param   string   $lestart   Starting timestamp.
	 * @param   string   $leend     Ending timestamp.
	 * @param   string   $ledir     Direction of enumeration.
	 * @param   integer  $lelimit   Event limit to return.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getEvents(array $leprop = null, $letype = null, $leaction = null, $letitle = null, $leprefix = null, $letag = null,
		$leuser = null, $lestart = null, $leend = null, $ledir = null, $lelimit = null)
	{
		// Build the request
		$path = '?action=query&list=logevents';

		if (isset($leprop))
		{
			$path .= '&leprop=' . $this->buildParameter($leprop);
		}

		if (isset($letype))
		{
			$path .= '&letype=' . $letype;
		}

		if (isset($leaction))
		{
			$path .= '&leaction=' . $leaction;
		}

		if (isset($letitle))
		{
			$path .= '&letitle=' . $letitle;
		}

		if (isset($leprefix))
		{
			$path .= '&leprefix=' . $leprefix;
		}

		if (isset($letag))
		{
			$path .= '&letag=' . $letag;
		}

		if (isset($leuser))
		{
			$path .= '&leuser=' . $leuser;
		}

		if (isset($lestart))
		{
			$path .= '&lestart=' . $lestart;
		}

		if (isset($leend))
		{
			$path .= '&leend=' . $leend;
		}

		if (isset($ledir))
		{
			$path .= '&ledir=' . $ledir;
		}

		if (isset($lelimit))
		{
			$path .= '&lelimit=' . $lelimit;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get recent changes on a site.
	 *
	 * @param   string  $rcstart        Starting timestamp.
	 * @param   string  $rcend          Ending timestamp.
	 * @param   string  $rcdir          Direction of enumeration.
	 * @param   array   $rcnamespace    Filter changes to only this namespace(s).
	 * @param   string  $rcuser         Filter changes by this user.
	 * @param   string  $rcexcludeuser  Filter changes to exclude changes by this user.
	 * @param   string  $rctag          Filter changes by this tag.
	 * @param   array   $rcprop         Filter log actions to only this type.
	 * @param   array   $rctoken        Which token to obtain for each change.
	 * @param   array   $rcshow         Filter changes by this criteria.
	 * @param   string  $rclimit        Changes limit to return.
	 * @param   string  $rctype         Filter event by type of changes.
	 * @param   string  $rctoponly      Filter changes which are latest revision.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getRecentChanges($rcstart = null, $rcend = null, $rcdir = null, array $rcnamespace = null, $rcuser = null, $rcexcludeuser = null,
		$rctag = null, array $rcprop = null, array $rctoken = null, array $rcshow = null, $rclimit = null, $rctype = null, $rctoponly = null)
	{
		// Build the request.
		$path = '?action=query&list=recentchanges';

		if (isset($rcstart))
		{
			$path .= '&rcstart=' . $rcstart;
		}

		if (isset($rcend))
		{
			$path .= '&rcend=' . $rcend;
		}

		if (isset($rcdir))
		{
			$path .= '&rcdir=' . $rcdir;
		}

		if (isset($rcnamespace))
		{
			$path .= '&rcnamespaces=' . $this->buildParameter($rcnamespace);
		}

		if (isset($rcuser))
		{
			$path .= '&rcuser=' . $rcuser;
		}

		if (isset($rcexcludeuser))
		{
			$path .= '&rcexcludeuser=' . $rcexcludeuser;
		}

		if (isset($rctag))
		{
			$path .= '&rctag=' . $rctag;
		}

		if (isset($rcprop))
		{
			$path .= '&rcprop=' . $this->buildParameter($rcprop);
		}

		if (isset($rctoken))
		{
			$path .= '&rctoken=' . $this->buildParameter($rctoken);
		}

		if (isset($rcshow))
		{
			$path .= '&rcshow=' . $this->buildParameter($rcshow);
		}

		if (isset($rclimit))
		{
			$path .= '&rclimit=' . $rclimit;
		}

		if (isset($rctype))
		{
			$path .= '&rctype=' . $rctype;
		}

		if (isset($rctoponly))
		{
			$path .= '&rctoponly=' . $rctoponly;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get protected titles on a site.
	 *
	 * @param   array    $ptnamespace  Only list titles in this namespace.
	 * @param   array    $ptlevel      Only list titles with these protection level.
	 * @param   integer  $ptlimit      Limit of pages to return.
	 * @param   string   $ptdir        Direction of enumeration.
	 * @param   string   $ptstart      Starting timestamp.
	 * @param   string   $ptend        Ending timestamp.
	 * @param   array    $ptprop       List of properties to get.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getProtectedTitles(array $ptnamespace = null, array $ptlevel = null, $ptlimit = null, $ptdir = null, $ptstart = null,
		$ptend = null, array $ptprop = null)
	{
		// Build the request.
		$path = '?action=query&list=protectedtitles';

		if (isset($ptnamespace))
		{
			$path .= '&ptnamespace=' . $this->buildParameter($ptnamespace);
		}

		if (isset($ptlevel))
		{
			$path .= '&ptlevel=' . $this->buildParameter($ptlevel);
		}

		if (isset($ptlimit))
		{
			$path .= '&ptlimit=' . $ptlimit;
		}

		if (isset($ptdir))
		{
			$path .= '&ptdir=' . $ptdir;
		}

		if (isset($ptstart))
		{
			$path .= '&ptstart=' . $ptstart;
		}

		if (isset($ptend))
		{
			$path .= '&ptend=' . $ptend;
		}

		if (isset($ptprop))
		{
			$path .= '&ptprop=' . $this->buildParameter($ptprop);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}
}
joomla/mediawiki/images.php000064400000014034152177723700011763 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Images class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiImages extends JMediawikiObject
{
	/**
	 * Method to get all images contained on the given page(s).
	 *
	 * @param   array    $titles         Page titles to retrieve images.
	 * @param   integer  $imagelimit     How many images to return.
	 * @param   boolean  $imagecontinue  When more results are available, use this to continue.
	 * @param   integer  $imimages       Only list these images.
	 * @param   string   $imdir          The direction in which to list.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getImages(array $titles, $imagelimit = null, $imagecontinue = null, $imimages = null, $imdir = null)
	{
		// Build the request.
		$path = '?action=query&prop=images';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($imagelimit))
		{
			$path .= '&imagelimit=' . $imagelimit;
		}

		if ($imagecontinue)
		{
			$path .= '&imagecontinue=';
		}

		if (isset($imimages))
		{
			$path .= '&imimages=' . $imimages;
		}

		if (isset($imdir))
		{
			$path .= '&imdir=' . $imdir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get all images contained on the given page(s).
	 *
	 * @param   array  $titles  Page titles to retrieve links.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getImagesUsed(array $titles)
	{
		// Build the request.
		$path = '?action=query&generator=images&prop=info';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get all image information and upload history.
	 *
	 * @param   array    $liprop             What image information to get.
	 * @param   integer  $lilimit            How many image revisions to return.
	 * @param   string   $listart            Timestamp to start listing from.
	 * @param   string   $liend              Timestamp to stop listing at.
	 * @param   integer  $liurlwidth         URL to an image scaled to this width will be returned..
	 * @param   integer  $liurlheight        URL to an image scaled to this height will be returned.
	 * @param   string   $limetadataversion  Version of metadata to use.
	 * @param   string   $liurlparam         A handler specific parameter string.
	 * @param   boolean  $licontinue         When more results are available, use this to continue.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getImageInfo(array $liprop = null, $lilimit = null, $listart = null, $liend = null, $liurlwidth = null,
		$liurlheight = null, $limetadataversion = null, $liurlparam = null, $licontinue = null)
	{
		// Build the request.
		$path = '?action=query&prop=imageinfo';

		if (isset($liprop))
		{
			$path .= '&liprop=' . $this->buildParameter($liprop);
		}

		if (isset($lilimit))
		{
			$path .= '&lilimit=' . $lilimit;
		}

		if (isset($listart))
		{
			$path .= '&listart=' . $listart;
		}

		if (isset($liend))
		{
			$path .= '&liend=' . $liend;
		}

		if (isset($liurlwidth))
		{
			$path .= '&liurlwidth=' . $liurlwidth;
		}

		if (isset($liurlheight))
		{
			$path .= '&liurlheight=' . $liurlheight;
		}

		if (isset($limetadataversion))
		{
			$path .= '&limetadataversion=' . $limetadataversion;
		}

		if (isset($liurlparam))
		{
			$path .= '&liurlparam=' . $liurlparam;
		}

		if ($licontinue)
		{
			$path .= '&alcontinue=';
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to enumerate all images.
	 *
	 * @param   string   $aifrom        The image title to start enumerating from.
	 * @param   string   $aito          The image title to stop enumerating at.
	 * @param   string   $aiprefix      Search for all image titles that begin with this value.
	 * @param   integer  $aiminsize     Limit to images with at least this many bytes.
	 * @param   integer  $aimaxsize     Limit to images with at most this many bytes.
	 * @param   integer  $ailimit       How many images in total to return.
	 * @param   string   $aidir         The direction in which to list.
	 * @param   string   $aisha1        SHA1 hash of image.
	 * @param   string   $aisha1base36  SHA1 hash of image in base 36.
	 * @param   array    $aiprop        What image information to get.
	 * @param   string   $aimime        What MIME type to search for.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function enumerateImages($aifrom = null, $aito = null, $aiprefix = null, $aiminsize = null, $aimaxsize = null, $ailimit = null,
		$aidir = null, $aisha1 = null, $aisha1base36 = null, array $aiprop = null, $aimime = null)
	{
		// Build the request.
		$path = '?action=query&list=allimages';

		if (isset($aifrom))
		{
			$path .= '&aifrom=' . $aifrom;
		}

		if (isset($aito))
		{
			$path .= '&aito=' . $aito;
		}

		if (isset($aiprefix))
		{
			$path .= '&aiprefix=' . $aiprefix;
		}

		if (isset($aiminsize))
		{
			$path .= '&aiminsize=' . $aiminsize;
		}

		if (isset($aimaxsize))
		{
			$path .= '&aimaxsize=' . $aimaxsize;
		}

		if (isset($ailimit))
		{
			$path .= '&ailimit=' . $ailimit;
		}

		if (isset($aidir))
		{
			$path .= '&aidir=' . $aidir;
		}

		if (isset($aisha1))
		{
			$path .= '&aisha1=' . $aisha1;
		}

		if (isset($aisha1base36))
		{
			$path .= '&$aisha1base36=' . $aisha1base36;
		}

		if (isset($aiprop))
		{
			$path .= '&aiprop=' . $this->buildParameter($aiprop);
		}

		if (isset($aimime))
		{
			$path .= '&aimime=' . $aimime;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}
}
joomla/mediawiki/mediawiki.php000064400000007537152177723700012473 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform class for interacting with a Mediawiki server instance.
 *
 * @property-read  JMediawikiSites          $sites          MediaWiki API object for sites.
 * @property-read  JMediawikiPages          $pages          MediaWiki API object for pages.
 * @property-read  JMediawikiUsers          $users          MediaWiki API object for users.
 * @property-read  JMediawikiLinks          $links          MediaWiki API object for links.
 * @property-read  JMediawikiCategories     $categories     MediaWiki API object for categories.
 * @property-read  JMediawikiImages         $images         MediaWiki API object for images.
 * @property-read  JMediawikiSearch         $search         MediaWiki API object for search.
 *
 * @since  3.1.4
 */
class JMediawiki
{
	/**
	 * @var    Registry  Options for the MediaWiki object.
	 * @since  3.0.0
	 */
	protected $options;

	/**
	 * @var    JMediawikiHttp  The HTTP client object to use in sending HTTP requests.
	 * @since  3.1.4
	 */
	protected $client;

	/**
	 * @var    JMediawikiSites  MediaWiki API object for Site.
	 * @since  3.1.4
	 */
	protected $sites;

	/**
	 * @var    JMediawikiPages  MediaWiki API object for pages.
	 * @since  3.0.0
	 */
	protected $pages;

	/**
	 * @var    JMediawikiUsers  MediaWiki API object for users.
	 * @since  3.1.4
	 */
	protected $users;

	/**
	 * @var    JMediawikiLinks  MediaWiki API object for links.
	 * @since  3.1.4
	 */
	protected $links;

	/**
	 * @var    JMediawikiCategories  MediaWiki API object for categories.
	 * @since  3.1.4
	 */
	protected $categories;

	/**
	 * @var    JMediawikiImages  MediaWiki API object for images.
	 * @since  3.1.4
	 */
	protected $images;

	/**
	 * @var    JMediawikiSearch  MediaWiki API object for search.
	 * @since  3.0.0
	 */
	protected $search;

	/**
     * Constructor.
     *
     * @param   Registry        $options  MediaWiki options object.
     * @param   JMediawikiHttp  $client   The HTTP client object.
     *
     * @since   3.1.4
     */
	public function __construct(Registry $options = null, JMediawikiHttp $client = null)
	{
		$this->options = isset($options) ? $options : new Registry;
		$this->client = isset($client) ? $client : new JMediawikiHttp($this->options);
	}

	/**
	 * Magic method to lazily create API objects
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  JMediaWikiObject  MediaWiki API object (users, reviews, etc).
	 *
	 * @since   3.1.4
	 * @throws  InvalidArgumentException
	 */
	public function __get($name)
	{
		$name = strtolower($name);
		$class = 'JMediawiki' . ucfirst($name);
		$accessible = array(
			'categories',
			'images',
			'links',
			'pages',
			'search',
			'sites',
			'users',
		);

		if (class_exists($class) && in_array($name, $accessible))
		{
			if (!isset($this->$name))
			{
				$this->$name = new $class($this->options, $this->client);
			}

			return $this->$name;
		}

		throw new InvalidArgumentException(sprintf('Property %s is not accessible.', $name));
	}

	/**
	 * Get an option from the JMediawiki instance.
	 *
	 * @param   string  $key  The name of the option to get.
	 *
	 * @return  mixed  The option value.
	 *
	 * @since   3.1.4
	 */
	public function getOption($key)
	{
		return $this->options->get($key);
	}

	/**
	 * Set an option for the JMediawiki instance.
	 *
	 * @param   string  $key    The name of the option to set.
	 * @param   mixed   $value  The option value to set.
	 *
	 * @return  JMediawiki  This object for method chaining.
	 *
	 * @since   3.1.4
	 */
	public function setOption($key, $value)
	{
		$this->options->set($key, $value);

		return $this;
	}
}
joomla/mediawiki/categories.php000064400000022470152177723700012646 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Categories class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiCategories extends JMediawikiObject
{
	/**
	 * Method to list all categories the page(s) belong to.
	 *
	 * @param   array    $titles        Page titles to retrieve categories.
	 * @param   array    $clprop        List of additional properties to get.
	 * @param   array    $clshow        Type of categories to show.
	 * @param   integer  $cllimit       Number of categories to return.
	 * @param   boolean  $clcontinue    Continue when more results are available.
	 * @param   array    $clcategories  Only list these categories.
	 * @param   string   $cldir         Direction of listing.
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function getCategories(array $titles, array $clprop = null, array $clshow = null, $cllimit = null, $clcontinue = false,
		array $clcategories = null, $cldir = null)
	{
		// Build the request.
		$path = '?action=query&prop=categories';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($clprop))
		{
			$path .= '&clprop=' . $this->buildParameter($clprop);
		}

		if (isset($clshow))
		{
			$path .= '&$clshow=' . $this->buildParameter($clshow);
		}

		if (isset($cllimit))
		{
			$path .= '&cllimit=' . $cllimit;
		}

		if ($clcontinue)
		{
			$path .= '&clcontinue=';
		}

		if (isset($clcategories))
		{
			$path .= '&clcategories=' . $this->buildParameter($clcategories);
		}

		if (isset($cldir))
		{
			$path .= '&cldir=' . $cldir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get information about all categories used.
	 *
	 * @param   array  $titles  Page titles to retrieve categories.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getCategoriesUsed(array $titles)
	{
		// Build the request
		$path = '?action=query&generator=categories&prop=info';

		// Append titles to the request
		$path .= '&titles=' . $this->buildParameter($titles);

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get information about the given categories.
	 *
	 * @param   array    $titles      Page titles to retrieve categories.
	 * @param   boolean  $clcontinue  Continue when more results are available.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getCategoriesInfo(array $titles, $clcontinue = false)
	{
		// Build the request.
		$path = '?action=query&prop=categoryinfo';

		// Append titles to the request
		$path .= '&titles=' . $this->buildParameter($titles);

		if ($clcontinue)
		{
			$path .= '&clcontinue=';
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get information about the pages within a category
	 *
	 * @param   string  $cmtitle               The category title, must contain 'Category:' prefix, cannot be used together with $cmpageid
	 * @param   string  $cmpageid              The category's page ID, cannot be used together with $cmtitle
	 * @param   string  $cmlimit               Maximum number of pages to retrieve
	 * @param   array   $cmprop                Array of properties to retrieve
	 * @param   array   $cmnamespace           Namespaces to retrieve pages from
	 * @param   array   $cmtype                Array of category members to include, ignored if $cmsort is set to 'timestamp'
	 * @param   string  $cmstart               Timestamp to start listing from, only used if $cmsort is set to 'timestamp'
	 * @param   string  $cmend                 Timestamp to end listing at, only used if $cmsort is set to 'timestamp'
	 * @param   string  $cmstartsortkey        Hexadecimal key to start listing from, only used if $cmsort is set to 'sortkey'
	 * @param   string  $cmendsortkey          Hexadecimal key to end listing at, only used if $cmsort is set to 'sortkey'
	 * @param   string  $cmstartsortkeyprefix  Hexadecimal key prefix to start listing from, only used if $cmsort is set to 'sortkey',
	 *                                         overrides $cmstartsortkey
	 * @param   string  $cmendsortkeyprefix    Hexadecimal key prefix to end listing before, only used if $cmsort is set to 'sortkey',
	 *                                         overrides $cmendsortkey
	 * @param   string  $cmsort                Property to sort by
	 * @param   string  $cmdir                 Direction to sort in
	 * @param   string  $cmcontinue            Used to continue a previous request
	 *
	 * @return  object
	 *
	 * @since   3.2.2 (CMS)
	 * @throws  RuntimeException
	 */
	public function getCategoryMembers($cmtitle = null, $cmpageid = null, $cmlimit = null, array $cmprop = null, array $cmnamespace = null,
		array $cmtype = null, $cmstart = null, $cmend = null, $cmstartsortkey = null, $cmendsortkey = null, $cmstartsortkeyprefix = null,
		$cmendsortkeyprefix = null, $cmsort = null, $cmdir = null, $cmcontinue = null)
	{
		// Build the request.
		$path = '?action=query&list=categorymembers';

		// Make sure both $cmtitle and $cmpageid are not set
		if (isset($cmtitle) && isset($cmpageid))
		{
			throw new RuntimeException('Both the $cmtitle and $cmpageid parameters cannot be set, please only use one of the two.');
		}

		if (isset($cmtitle))
		{
			// Verify that the Category: prefix exists
			if (strpos($cmtitle, 'Category:') !== 0)
			{
				throw new RuntimeException('The $cmtitle parameter must include the Category: prefix.');
			}

			$path .= '&cmtitle=' . $cmtitle;
		}

		if (isset($cmpageid))
		{
			$path .= '&cmpageid=' . $cmpageid;
		}

		if (isset($cmlimit))
		{
			$path .= '&cmlimit=' . $cmlimit;
		}

		if (isset($cmprop))
		{
			$path .= '&cmprop=' . $this->buildParameter($cmprop);
		}

		if (isset($cmnamespace))
		{
			$path .= '&cmnamespace=' . $this->buildParameter($cmnamespace);
		}

		if (isset($cmtype))
		{
			$path .= '&cmtype=' . $this->buildParameter($cmtype);
		}

		if (isset($cmstart))
		{
			$path .= '&cmstart=' . $cmstart;
		}

		if (isset($cmend))
		{
			$path .= '&cmend=' . $cmend;
		}

		if (isset($cmstartsortkey))
		{
			$path .= '&cmstartsortkey=' . $cmstartsortkey;
		}

		if (isset($cmendsortkey))
		{
			$path .= '&cmendsortkey=' . $cmendsortkey;
		}

		if (isset($cmstartsortkeyprefix))
		{
			$path .= '&cmstartsortkeyprefix=' . $cmstartsortkeyprefix;
		}

		if (isset($cmendsortkeyprefix))
		{
			$path .= '&cmendsortkeyprefix=' . $cmendsortkeyprefix;
		}

		if (isset($cmsort))
		{
			$path .= '&cmsort=' . $cmsort;
		}

		if (isset($cmdir))
		{
			$path .= '&cmdir=' . $cmdir;
		}

		if (isset($cmcontinue))
		{
			$path .= '&cmcontinue=' . $cmcontinue;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to enumerate all categories.
	 *
	 * @param   string   $acfrom    The category to start enumerating from.
	 * @param   string   $acto      The category to stop enumerating at.
	 * @param   string   $acprefix  Search for all category titles that begin with this value.
	 * @param   string   $acdir     Direction to sort in.
	 * @param   integer  $acmin     Minimum number of category members.
	 * @param   integer  $acmax     Maximum number of category members.
	 * @param   integer  $aclimit   How many categories to return.
	 * @param   array    $acprop    Which properties to get.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function enumerateCategories($acfrom = null, $acto = null, $acprefix = null, $acdir = null, $acmin = null,
		$acmax = null, $aclimit = null, array $acprop = null)
	{
		// Build the request.
		$path = '?action=query&list=allcategories';

		if (isset($acfrom))
		{
			$path .= '&acfrom=' . $acfrom;
		}

		if (isset($acto))
		{
			$path .= '&acto=' . $acto;
		}

		if (isset($acprefix))
		{
			$path .= '&acprefix=' . $acprefix;
		}

		if (isset($acdir))
		{
			$path .= '&acdir=' . $acdir;
		}

		if (isset($acfrom))
		{
			$path .= '&acfrom=' . $acfrom;
		}

		if (isset($acmin))
		{
			$path .= '&acmin=' . $acmin;
		}

		if (isset($acmax))
		{
			$path .= '&acmax=' . $acmax;
		}

		if (isset($aclimit))
		{
			$path .= '&aclimit=' . $aclimit;
		}

		if (isset($acprop))
		{
			$path .= '&acprop=' . $this->buildParameter($acprop);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to list change tags.
	 *
	 * @param   array   $tgprop   List of properties to get.
	 * @param   string  $tglimit  The maximum number of tags to limit.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getChangeTags(array $tgprop = null, $tglimit = null)
	{
		// Build the request.
		$path = '?action=query&list=tags';

		if (isset($tgprop))
		{
			$path .= '&tgprop=' . $this->buildParameter($tgprop);
		}

		if (isset($tglimit))
		{
			$path .= '&tglimit=' . $tglimit;
		}

		// @TODO add support for $tgcontinue

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}
}
joomla/mediawiki/pages.php000064400000040705152177723700011621 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Pages class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiPages extends JMediawikiObject
{
	/**
	 * Method to edit a page.
	 *
	 * @param   string  $title         Page title.
	 * @param   int     $section       Section number.
	 * @param   string  $sectiontitle  The title for a new section.
	 * @param   string  $text          Page content.
	 * @param   string  $summary       Title of the page you want to delete.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function editPage($title, $section = null, $sectiontitle = null, $text = null, $summary = null)
	{
		// Get the token.
		$token = $this->getToken($title, 'edit');

		// Build the request path.
		$path = '?action=edit';

		// Build the request data.
		$data = array(
			'title' => $title,
			'token' => $token,
			'section' => $section,
			'sectiontitle' => $section,
			'text' => $text,
			'summary' => $summary,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to delete a page.
	 *
	 * @param   string  $title      Title of the page you want to delete.
	 * @param   string  $reason     Reason for the deletion.
	 * @param   string  $watchlist  Unconditionally add or remove the page from your watchlis.
	 * @param   string  $oldimage   The name of the old image to delete.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function deletePageByName($title, $reason = null, $watchlist = null, $oldimage = null)
	{
		// Get the token.
		$token = $this->getToken($title, 'delete');

		// Build the request path.
		$path = '?action=delete';

		// Build the request data.
		$data = array(
			'title' => $title,
			'token' => $token,
			'reason' => $reason,
			'watchlist' => $watchlist,
			'oldimage' => $oldimage,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to delete a page.
	 *
	 * @param   string  $pageid     Page ID of the page you want to delete.
	 * @param   string  $reason     Reason for the deletion.
	 * @param   string  $watchlist  Unconditionally add or remove the page from your watchlis.
	 * @param   string  $oldimage   The name of the old image to delete.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function deletePageById($pageid,  $reason = null, $watchlist = null, $oldimage = null)
	{
		// Get the token.
		$token = $this->getToken($pageid, 'delete');

		// Build the request path.
		$path = '?action=delete';

		// Build the request data.
		$data = array(
			'pageid' => $pageid,
			'token' => $token,
			'reason' => $reason,
			'watchlist' => $watchlist,
			'oldimage' => $oldimage,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to restore certain revisions of a deleted page.
	 *
	 * @param   string  $title      Title of the page you want to restore.
	 * @param   string  $reason     Reason for restoring (optional).
	 * @param   string  $timestamp  Timestamps of the revisions to restore.
	 * @param   string  $watchlist  Unconditionally add or remove the page from your watchlist.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function undeletePage($title, $reason = null, $timestamp = null, $watchlist = null)
	{
		// Get the token.
		$token = $this->getToken($title, 'undelete');

		// Build the request path.
		$path = '?action=undelete';

		// Build the request data.
		$data = array(
			'title' => $title,
			'token' => $token,
			'reason' => $reason,
			'timestamp' => $timestamp,
			'watchlist' => $watchlist,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to move a page.
	 *
	 * @param   string   $from            Title of the page you want to move.
	 * @param   string   $to              Title you want to rename the page to.
	 * @param   string   $reason          Reason for the move (optional).
	 * @param   string   $movetalk        Move the talk page, if it exists.
	 * @param   string   $movesubpages    Move subpages, if applicable.
	 * @param   boolean  $noredirect      Don't create a redirect.
	 * @param   string   $watchlist       Unconditionally add or remove the page from your watchlist.
	 * @param   boolean  $ignorewarnings  Ignore any warnings.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function movePageByName($from, $to, $reason = null, $movetalk = null, $movesubpages = null, $noredirect = null,
		$watchlist =null, $ignorewarnings = null)
	{
		// Get the token.
		$token = $this->getToken($from, 'move');

		// Build the request path.
		$path = '?action=move';

		// Build the request data.
		$data = array(
			'from' => $from,
			'to' => $reason,
			'token' => $token,
			'reason' => $reason,
			'movetalk' => $movetalk,
			'movesubpages' => $movesubpages,
			'noredirect' => $noredirect,
			'watchlist' => $watchlist,
			'ignorewarnings' => $ignorewarnings,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to move a page.
	 *
	 * @param   int      $fromid          Page ID of the page you want to move.
	 * @param   string   $to              Title you want to rename the page to.
	 * @param   string   $reason          Reason for the move (optional).
	 * @param   string   $movetalk        Move the talk page, if it exists.
	 * @param   string   $movesubpages    Move subpages, if applicable.
	 * @param   boolean  $noredirect      Don't create a redirect.
	 * @param   string   $watchlist       Unconditionally add or remove the page from your watchlist.
	 * @param   boolean  $ignorewarnings  Ignore any warnings.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function movePageById($fromid, $to, $reason = null, $movetalk = null, $movesubpages = null, $noredirect = null,
		$watchlist =null, $ignorewarnings = null)
	{
		// Get the token.
		$token = $this->getToken($fromid, 'move');

		// Build the request path.
		$path = '?action=move';

		// Build the request data.
		$data = array(
			'fromid' => $fromid,
			'to' => $reason,
			'token' => $token,
			'reason' => $reason,
			'movetalk' => $movetalk,
			'movesubpages' => $movesubpages,
			'noredirect' => $noredirect,
			'watchlist' => $watchlist,
			'ignorewarnings' => $ignorewarnings,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to undo the last edit to the page.
	 *
	 * @param   string  $title      Title of the page you want to rollback.
	 * @param   string  $user       Name of the user whose edits are to be rolled back.
	 * @param   string  $summary    Custom edit summary. If not set, default summary will be used.
	 * @param   string  $markbot    Mark the reverted edits and the revert as bot edits.
	 * @param   string  $watchlist  Unconditionally add or remove the page from your watchlist.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function rollback($title, $user, $summary = null, $markbot = null, $watchlist = null)
	{
		// Get the token.
		$token = $this->getToken($title, 'rollback');

		// Build the request path.
		$path = '?action=rollback';

		// Build the request data.
		$data = array(
			'title' => $title,
			'token' => $token,
			'user' => $user,
			'expiry' => $summary,
			'markbot' => $markbot,
			'watchlist' => $watchlist,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to change the protection level of a page.
	 *
	 * @param   string  $title        Title of the page you want to (un)protect.
	 * @param   string  $protections  Pipe-separated list of protection levels.
	 * @param   string  $expiry       Expiry timestamps.
	 * @param   string  $reason       Reason for (un)protecting (optional).
	 * @param   string  $cascade      Enable cascading protection.
	 * @param   string  $watchlist    Unconditionally add or remove the page from your watchlist.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function changeProtection($title, $protections, $expiry = null, $reason = null, $cascade = null, $watchlist = null)
	{
		// Get the token.
		$token = $this->getToken($title, 'unblock');

		// Build the request path.
		$path = '?action=protect';

		// Build the request data.
		$data = array(
			'title' => $title,
			'token' => $token,
			'protections' => $protections,
			'expiry' => $expiry,
			'reason' => $reason,
			'cascade' => $cascade,
			'watchlist' => $watchlist,
		);

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), $data);

		return $this->validateResponse($response);
	}

	/**
	 * Method to get basic page information.
	 *
	 * @param   array    $titles      Page titles to retrieve info.
	 * @param   array    $inprop      Which additional properties to get.
	 * @param   array    $intoken     Request a token to perform a data-modifying action on a page
	 * @param   boolean  $incontinue  When more results are available, use this to continue.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getPageInfo(array $titles, array $inprop = null, array $intoken = null, $incontinue = null)
	{
		// Build the request
		$path = '?action=query&prop=info';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($inprop))
		{
			$path .= '&inprop=' . $this->buildParameter($inprop);
		}

		if (isset($intoken))
		{
			$path .= '&intoken=' . $this->buildParameter($intoken);
		}

		if ($incontinue)
		{
			$path .= '&incontinue=';
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get various properties defined in the page content.
	 *
	 * @param   array    $titles      Page titles to retrieve properties.
	 * @param   boolean  $ppcontinue  When more results are available, use this to continue.
	 * @param   string   $ppprop      Page prop to look on the page for.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getPageProperties(array $titles, $ppcontinue = null, $ppprop = null)
	{
		// Build the request
		$path = '?action=query&prop=pageprops';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if ($ppcontinue)
		{
			$path .= '&ppcontinue=';
		}

		if (isset($ppprop))
		{
			$path .= '&ppprop=' . $ppprop;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get a list of revisions.
	 *
	 * @param   array    $titles   Page titles to retrieve revisions.
	 * @param   array    $rvprop   Which properties to get for each revision.
	 * @param   boolean  $rvparse  Parse revision content.
	 * @param   int      $rvlimit  Limit how many revisions will be returned.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getRevisions(array $titles, array $rvprop = null, $rvparse = null, $rvlimit = null)
	{
		// Build the request
		$path = '?action=query&prop=revisions';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($rvprop))
		{
			$path .= '&rvprop=' . $this->buildParameter($rvprop);
		}

		if ($rvparse)
		{
			$path .= '&rvparse=';
		}

		if (isset($rvlimit))
		{
			$path .= '&rvlimit=' . $rvlimit;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get all page templates from the given page.
	 *
	 * @param   array    $titles       Page titles to retrieve templates.
	 * @param   array    $tlnamespace  Show templates in this namespace(s) only.
	 * @param   integer  $tllimit      How many templates to return.
	 * @param   boolean  $tlcontinue   When more results are available, use this to continue.
	 * @param   string   $tltemplates  Only list these templates.
	 * @param   string   $tldir        The direction in which to list.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getPageTemplates(array $titles, array $tlnamespace = null, $tllimit = null, $tlcontinue = null, $tltemplates = null, $tldir = null)
	{
		// Build the request.
		$path = '?action=query&prop=templates';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($tlnamespace))
		{
			$path .= '&tlnamespace=' . $this->buildParameter($tlnamespace);
		}

		if (isset($tllimit))
		{
			$path .= '&tllimit=' . $tllimit;
		}

		if ($tlcontinue)
		{
			$path .= '&tlcontinue=';
		}

		if (isset($tltemplates))
		{
			$path .= '&tltemplates=' . $tltemplates;
		}

		if (isset($tldir))
		{
			$path .= '&tldir=' . $tldir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get all pages that link to the given page.
	 *
	 * @param   string   $bltitle           Title to search.
	 * @param   integer  $blpageid          Pageid to search.
	 * @param   boolean  $blcontinue        When more results are available, use this to continue.
	 * @param   array    $blnamespace       The namespace to enumerate.
	 * @param   string   $blfilterredirect  How to filter for redirects..
	 * @param   integer  $bllimit           How many total pages to return.
	 * @param   boolean  $blredirect        If linking page is a redirect, find all pages that link to that redirect as well.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getBackLinks($bltitle, $blpageid = null, $blcontinue = null, array $blnamespace = null, $blfilterredirect = null,
		$bllimit = null, $blredirect = null)
	{
		// Build the request.
		$path = '?action=query&list=backlinks';

		if (isset($bltitle))
		{
			$path .= '&bltitle=' . $bltitle;
		}

		if (isset($blpageid))
		{
			$path .= '&blpageid=' . $blpageid;
		}

		if ($blcontinue)
		{
			$path .= '&blcontinue=';
		}

		if (isset($blnamespace))
		{
			$path .= '&blnamespace=' . $this->buildParameter($blnamespace);
		}

		if (isset($blfilterredirect))
		{
			$path .= '&blfilterredirect=' . $blfilterredirect;
		}

		if (isset($bllimit))
		{
			$path .= '&bllimit=' . $bllimit;
		}

		if ($blredirect)
		{
			$path .= '&blredirect=';
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get all pages that link to the given interwiki link.
	 *
	 * @param   string   $iwbltitle     Interwiki link to search for. Must be used with iwblprefix.
	 * @param   string   $iwblprefix    Prefix for the interwiki.
	 * @param   boolean  $iwblcontinue  When more results are available, use this to continue.
	 * @param   integer  $iwbllimit     How many total pages to return.
	 * @param   array    $iwblprop      Which properties to get.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getIWBackLinks($iwbltitle, $iwblprefix = null, $iwblcontinue = null, $iwbllimit = null, array $iwblprop = null)
	{
		// Build the request
		$path = '?action=query&list=iwbacklinks';

		if (isset($iwbltitle))
		{
			$path .= '&iwbltitle=' . $iwbltitle;
		}

		if (isset($iwblprefix))
		{
			$path .= '&iwblprefix=' . $iwblprefix;
		}

		if ($iwblcontinue)
		{
			$path .= '&iwblcontinue=';
		}

		if (isset($iwbllimit))
		{
			$path .= '&bllimit=' . $iwbllimit;
		}

		if (isset($iwblprop))
		{
			$path .= '&iwblprop=' . $this->buildParameter($iwblprop);
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to get access token.
	 *
	 * @param   string  $user     The User to get token.
	 * @param   string  $intoken  The type of token.
	 *
	 * @return  object
	 *
	 * @since   3.0.0
	 */
	public function getToken($user, $intoken)
	{
		// Build the request path.
		$path = '?action=query&prop=info&intoken=' . $intoken . '&titles=User:' . $user;

		// Send the request.
		$response = $this->client->post($this->fetchUrl($path), null);

		return (string) $this->validateResponse($response)->query->pages->page[$intoken . 'token'];
	}
}
joomla/mediawiki/links.php000064400000017370152177723700011644 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  MediaWiki
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * MediaWiki API Links class for the Joomla Platform.
 *
 * @since  3.1.4
 */
class JMediawikiLinks extends JMediawikiObject
{
	/**
	 * Method to return all links from the given page(s).
	 *
	 * @param   array   $titles       Page titles to retrieve links.
	 * @param   array   $plnamespace  Namespaces to get links.
	 * @param   string  $pllimit      Number of links to return.
	 * @param   string  $plcontinue   Continue when more results are available.
	 * @param   array   $pltitles     List links to these titles.
	 * @param   string  $pldir        Direction of listing.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getLinks(array $titles, array $plnamespace = null, $pllimit = null, $plcontinue = null, array $pltitles = null, $pldir = null)
	{
		// Build the request.
		$path = '?action=query&prop=links';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($plnamespace))
		{
			$path .= '&plnamespace=' . $this->buildParameter($plnamespace);
		}

		if (isset($pllimit))
		{
			$path .= '&pllimit=' . $pllimit;
		}

		if (isset($plcontinue))
		{
			$path .= '&plcontinue=' . $plcontinue;
		}

		if (isset($pltitles))
		{
			$path .= '&pltitles=' . $this->buildParameter($pltitles);
		}

		if (isset($pldir))
		{
			$path .= '&pldir=' . $pldir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to return info about the link pages.
	 *
	 * @param   array  $titles  Page titles to retrieve links.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getLinksUsed(array $titles)
	{
		// Build the request.
		$path = '?action=query&generator=links&prop=info';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to return all interwiki links from the given page(s).
	 *
	 * @param   array    $titles      Page titles to retrieve links.
	 * @param   boolean  $iwurl       Whether to get the full url.
	 * @param   integer  $iwlimit     Number of interwiki links to return.
	 * @param   boolean  $iwcontinue  When more results are available, use this to continue.
	 * @param   string   $iwprefix    Prefix for the interwiki.
	 * @param   string   $iwtitle     Interwiki link to search for.
	 * @param   string   $iwdir       The direction in which to list.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getIWLinks(array $titles, $iwurl = false, $iwlimit = null, $iwcontinue = false, $iwprefix = null, $iwtitle = null, $iwdir = null)
	{
		// Build the request.
		$path = '?action=query&prop=links';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if ($iwurl)
		{
			$path .= '&iwurl=';
		}

		if (isset($iwlimit))
		{
			$path .= '&iwlimit=' . $iwlimit;
		}

		if ($iwcontinue)
		{
			$path .= '&iwcontinue=';
		}

		if (isset($iwprefix))
		{
			$path .= '&iwprefix=' . $iwprefix;
		}

		if (isset($iwtitle))
		{
			$path .= '&iwtitle=' . $iwtitle;
		}

		if (isset($iwdir))
		{
			$path .= '&iwdir=' . $iwdir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to return all interlanguage links from the given page(s).
	 *
	 * @param   array    $titles      Page titles to retrieve links.
	 * @param   integer  $lllimit     Number of language links to return.
	 * @param   boolean  $llcontinue  When more results are available, use this to continue.
	 * @param   string   $llurl       Whether to get the full URL.
	 * @param   string   $lllang      Language code.
	 * @param   string   $lltitle     Link to search for.
	 * @param   string   $lldir       The direction in which to list.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getLangLinks(array $titles, $lllimit = null, $llcontinue = false, $llurl = null, $lllang = null, $lltitle = null, $lldir = null)
	{
		// Build the request.
		$path = '?action=query&prop=langlinks';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($lllimit))
		{
			$path .= '&lllimit=' . $lllimit;
		}

		if ($llcontinue)
		{
			$path .= '&llcontinue=';
		}

		if (isset($llurl))
		{
			$path .= '&llurl=' . $llurl;
		}

		if (isset($lllang))
		{
			$path .= '&lllang=' . $lllang;
		}

		if (isset($lltitle))
		{
			$path .= '&lltitle=' . $lltitle;
		}

		if (isset($lldir))
		{
			$path .= '&lldir=' . $lldir;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to return all external urls from the given page(s).
	 *
	 * @param   array    $titles      Page titles to retrieve links.
	 * @param   integer  $ellimit     Number of links to return.
	 * @param   string   $eloffset    When more results are available, use this to continue.
	 * @param   string   $elprotocol  Protocol of the url.
	 * @param   string   $elquery     Search string without protocol.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function getExtLinks(array $titles, $ellimit = null, $eloffset = null, $elprotocol = null, $elquery = null)
	{
		// Build the request.
		$path = '?action=query&prop=extlinks';

		// Append titles to the request.
		$path .= '&titles=' . $this->buildParameter($titles);

		if (isset($ellimit))
		{
			$path .= '&ellimit=' . $ellimit;
		}

		if (isset($eloffset))
		{
			$path .= '&eloffset=' . $eloffset;
		}

		if (isset($elprotocol))
		{
			$path .= '&elprotocol=' . $elprotocol;
		}

		if (isset($elquery))
		{
			$path .= '&elquery=' . $elquery;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}

	/**
	 * Method to enumerate all links that point to a given namespace.
	 *
	 * @param   boolean  $alcontinue   When more results are available, use this to continue.
	 * @param   string   $alfrom       Start listing at this title. The title need not exist.
	 * @param   string   $alto         The page title to stop enumerating at.
	 * @param   string   $alprefix     Search for all page titles that begin with this value.
	 * @param   string   $alunique     Only show unique links.
	 * @param   array    $alprop       What pieces of information to include.
	 * @param   string   $alnamespace  The namespace to enumerate.
	 * @param   integer  $allimit      Number of links to return.
	 *
	 * @return  object
	 *
	 * @since   3.1.4
	 */
	public function enumerateLinks($alcontinue = false, $alfrom = null, $alto = null, $alprefix = null, $alunique = null, array $alprop = null,
		$alnamespace = null, $allimit = null)
	{
		// Build the request.
		$path = '?action=query&meta=siteinfo';

		if ($alcontinue)
		{
			$path .= '&alcontinue=';
		}

		if (isset($alfrom))
		{
			$path .= '&alfrom=' . $alfrom;
		}

		if (isset($alto))
		{
			$path .= '&alto=' . $alto;
		}

		if (isset($alprefix))
		{
			$path .= '&alprefix=' . $alprefix;
		}

		if (isset($alunique))
		{
			$path .= '&alunique=' . $alunique;
		}

		if (isset($alprop))
		{
			$path .= '&alprop=' . $this->buildParameter($alprop);
		}

		if (isset($alnamespace))
		{
			$path .= '&alnamespace=' . $alnamespace;
		}

		if (isset($allimit))
		{
			$path .= '&allimit=' . $allimit;
		}

		// Send the request.
		$response = $this->client->get($this->fetchUrl($path));

		return $this->validateResponse($response);
	}
}
joomla/observer/mapper.php000064400000004617152177723700011674 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Observer mapping pattern implementation for Joomla
 *
 * @since  3.1.2
 */
class JObserverMapper
{
	/**
	 * Array: array( JObservableInterface_classname => array( JObserverInterface_classname => array( paramname => param, .... ) ) )
	 *
	 * @var    array
	 * @since  3.1.2
	 */
	protected static $observations = array();

	/**
	 * Adds a mapping to observe $observerClass subjects with $observableClass observer/listener, attaching it on creation with $params
	 * on $observableClass instance creations
	 *
	 * @param   string         $observerClass    The name of the observer class (implementing JObserverInterface)
	 * @param   string         $observableClass  The name of the observable class (implementing JObservableInterface)
	 * @param   array|boolean  $params           The params to give to the JObserverInterface::createObserver() function, or false to remove mapping
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public static function addObserverClassToClass($observerClass, $observableClass, $params = array())
	{
		if ($params !== false)
		{
			static::$observations[$observableClass][$observerClass] = $params;
		}
		else
		{
			unset(static::$observations[$observableClass][$observerClass]);
		}
	}

	/**
	 * Attaches all applicable observers to an $observableObject
	 *
	 * @param   JObservableInterface  $observableObject  The observable subject object
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public static function attachAllObservers(JObservableInterface $observableObject)
	{
		$observableClass = get_class($observableObject);

		while ($observableClass != false)
		{
			// Attach applicable Observers for the class to the Observable subject:
			if (isset(static::$observations[$observableClass]))
			{
				foreach (static::$observations[$observableClass] as $observerClass => $params)
				{
					// Attach an Observer to the Observable subject:
					/**
					 * @var JObserverInterface $observerClass
					 */
					$observerClass::createObserver($observableObject, $params);
				}
			}

			// Get parent class name (or false if none), and redo the above on it:
			$observableClass = get_parent_class($observableClass);
		}
	}
}
joomla/observer/interface.php000064400000004027152177723700012343 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Observer pattern interface for Joomla
 *
 * A class that wants to observe another class must:
 *
 * 1) Add: implements JObserverInterface
 *    to its class
 *
 * 2) Implement a constructor, that can look like this:
 * 	public function __construct(JObservableInterface $observableObject)
 * 	{
 * 	    $observableObject->attachObserver($this);
 *  	$this->observableObject = $observableObject;
 * 	}
 *
 * 3) and must implement the instanciator function createObserver() below, e.g. as follows:
 * 	public static function createObserver(JObservableInterface $observableObject, $params = array())
 * 	{
 * 	    $observer = new self($observableObject);
 *      $observer->... = $params['...']; ...
 * 	    return $observer;
 * 	}
 *
 * 4) Then add functions corresponding to the events to be observed,
 *    E.g. to respond to event: $this->_observers->update('onBeforeLoad', array($keys, $reset));
 *    following function is needed in the obser:
 *  public function onBeforeLoad($keys, $reset) { ... }
 *
 * 5) Finally, the binding is made outside the observable and observer classes, using:
 * JObserverMapper::addObserverClassToClass('ObserverClassname', 'ObservableClassname', array('paramName' => 'paramValue'));
 * where the last array will be provided to the observer instanciator function createObserver.
 *
 * @since  3.1.2
 */
interface JObserverInterface
{
	/**
	 * Creates the associated observer instance and attaches it to the $observableObject
	 *
	 * @param   JObservableInterface  $observableObject  The observable subject object
	 * @param   array                 $params            Params for this observer
	 *
	 * @return  JObserverInterface
	 *
	 * @since   3.1.2
	 */
	public static function createObserver(JObservableInterface $observableObject, $params = array());
}
joomla/observer/updater.php000064400000007627152177723700012060 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Observer updater pattern implementation for Joomla
 *
 * @since  3.1.2
 */
class JObserverUpdater implements JObserverUpdaterInterface
{
	/**
	 * Holds the key aliases for observers.
	 *
	 * @var    array
	 * @since  3.9.0
	 */
	protected $aliases = array();

	/**
	 * Generic JObserverInterface observers for this JObservableInterface
	 *
	 * @var    JObserverInterface
	 * @since  3.1.2
	 */
	protected $observers = array();

	/**
	 * Process observers (useful when a class extends significantly an observerved method, and calls observers itself
	 *
	 * @var    boolean
	 * @since  3.1.2
	 */
	protected $doCallObservers = true;

	/**
	 * Constructor
	 *
	 * @param   JObservableInterface  $observable  The observable subject object
	 *
	 * @since   3.1.2
	 */
	public function __construct(JObservableInterface $observable)
	{
		// Not yet needed, but possible:  $this->observable = $observable;
	}

	/**
	 * Adds an observer to the JObservableInterface instance updated by this
	 * This method can be called from JObservableInterface::attachObserver
	 *
	 * @param   JObserverInterface  $observer  The observer object
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function attachObserver(JObserverInterface $observer)
	{
		$class = get_class($observer);

		// Also register the alias if exists
		foreach (JLoader::getDeprecatedAliases() as $alias)
		{
			$realClass  = trim($alias['new'], '\\');

			// Check if we have an alias for the observer class
			if ($realClass === $class)
			{
				$aliasClass = trim($alias['old'], '\\');

				// Add an alias to known aliases
				$this->aliases[$aliasClass] = $class;
			}
		}

		// Register the real class
		$this->observers[$class] = $observer;
	}

	/**
	 * Removes an observer from the JObservableInterface instance updated by this
	 * This method can be called from JObservableInterface::attachObserver
	 *
	 * @param   String  $observer  The observer class name
	 *
	 * @return  void
	 *
	 * @since   3.6.0
	 */
	public function detachObserver($observer)
	{
		$observer = trim($observer, '\\');

		if (isset($this->aliases[$observer]))
		{
			$observer = $this->aliases[$observer];
		}

		if (isset($this->observers[$observer]))
		{
			unset($this->observers[$observer]);
		}
	}

	/**
	 * Gets the instance of the observer of class $observerClass
	 *
	 * @param   string  $observerClass  The class name of the observer
	 *
	 * @return  JTableObserver|null  The observer object of this class if any
	 *
	 * @since   3.1.2
	 */
	public function getObserverOfClass($observerClass)
	{
		$observerClass = trim($observerClass, '\\');

		if (isset($this->aliases[$observerClass]))
		{
			$observerClass = $this->aliases[$observerClass];
		}

		if (isset($this->observers[$observerClass]))
		{
			return $this->observers[$observerClass];
		}

		return null;
	}

	/**
	 * Call all observers for $event with $params
	 *
	 * @param   string  $event   Name of the event
	 * @param   array   $params  Params of the event
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function update($event, $params)
	{
		if ($this->doCallObservers)
		{
			foreach ($this->observers as $observer)
			{
				$eventListener = array($observer, $event);

				if (is_callable($eventListener))
				{
					call_user_func_array($eventListener, $params);
				}
			}
		}
	}

	/**
	 * Enable/Disable calling of observers (this is useful when calling parent:: function
	 *
	 * @param   boolean  $enabled  Enable (true) or Disable (false) the observer events
	 *
	 * @return  boolean  Returns old state
	 *
	 * @since   3.1.2
	 */
	public function doCallObservers($enabled)
	{
		$oldState = $this->doCallObservers;
		$this->doCallObservers = $enabled;

		return $oldState;
	}
}
joomla/observer/wrapper/mapper.php000064400000003020152177723700013337 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for JObserverMapper
 *
 * @package     Joomla.Platform
 * @subpackage  Observer
 * @since       3.4
 */
class JObserverWrapperMapper
{
	/**
	 * Helper wrapper method for addObserverClassToClass
	 *
	 * @param   string         $observerClass    The name of the observer class (implementing JObserverInterface).
	 * @param   string         $observableClass  The name of the observable class (implementing JObservableInterface).
	 * @param   array|boolean  $params           The params to give to the JObserverInterface::createObserver() function, or false to remove mapping.
	 *
	 * @return void
	 *
	 * @see     JObserverMapper::addObserverClassToClass
	 * @since   3.4
	 */
	public function addObserverClassToClass($observerClass, $observableClass, $params = array())
	{
		return JObserverMapper::addObserverClassToClass($observerClass, $observableClass, $params);
	}

	/**
	 * Helper wrapper method for attachAllObservers
	 *
	 * @param   JObservableInterface  $observableObject  The observable subject object.
	 *
	 * @return void
	 *
	 * @see     JObserverMapper::attachAllObservers
	 * @since   3.4
	 */
	public function attachAllObservers(JObservableInterface $observableObject)
	{
		return JObserverMapper::attachAllObservers($observableObject);
	}
}
joomla/observer/updater/interface.php000064400000002754152177723700014014 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Observer
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Observer updater pattern implementation for Joomla
 *
 * @since  3.1.2
 */
interface JObserverUpdaterInterface
{
	/**
	 * Constructor
	 *
	 * @param   JObservableInterface  $observable  The observable subject object
	 *
	 * @since   3.1.2
	 */
	public function __construct(JObservableInterface $observable);

	/**
	 * Adds an observer to the JObservableInterface instance updated by this
	 * This method can be called fron JObservableInterface::attachObserver
	 *
	 * @param   JObserverInterface  $observer  The observer object
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function attachObserver(JObserverInterface $observer);

	/**
	 * Call all observers for $event with $params
	 *
	 * @param   string  $event   Event name (function name in observer)
	 * @param   array   $params  Params of event (params in observer function)
	 *
	 * @return  void
	 *
	 * @since   3.1.2
	 */
	public function update($event, $params);

	/**
	 * Enable/Disable calling of observers (this is useful when calling parent:: function
	 *
	 * @param   boolean  $enabled  Enable (true) or Disable (false) the observer events
	 *
	 * @return  boolean  Returns old state
	 *
	 * @since   3.1.2
	 */
	public function doCallObservers($enabled);
}
joomla/route/wrapper/route.php000064400000001656152177723700012535 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Application
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for JRoute
 *
 * @package     Joomla.Platform
 * @subpackage  Application
 * @since       3.4
 * @deprecated  4.0 Will be removed without replacement
 */
class JRouteWrapperRoute
{
	/**
	 * Helper wrapper method for _
	 *
	 * @param   string   $url    Absolute or Relative URI to Joomla resource.
	 * @param   boolean  $xhtml  Replace & by &amp; for XML compliance.
	 * @param   integer  $ssl    Secure state for the resolved URI.
	 *
	 * @return  string The translated humanly readable URL.
	 *
	 * @see     JRoute::_()
	 * @since   3.4
	 */
	public function _($url, $xhtml = true, $ssl = null)
	{
		return JRoute::_($url, $xhtml, $ssl);
	}
}
joomla/event/dispatcher.php000064400000013372152177723700012026 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Event
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Class to handle dispatching of events.
 *
 * This is the Observable part of the Observer design pattern
 * for the event architecture.
 *
 * @see         JPlugin
 * @since       3.0.0
 * @deprecated  4.0  The CMS' Event classes will be replaced with the `joomla/event` package
 */
class JEventDispatcher extends JObject
{
	/**
	 * An array of Observer objects to notify
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $_observers = array();

	/**
	 * The state of the observable object
	 *
	 * @var    mixed
	 * @since  3.0.0
	 */
	protected $_state = null;

	/**
	 * A multi dimensional array of [function][] = key for observers
	 *
	 * @var    array
	 * @since  3.0.0
	 */
	protected $_methods = array();

	/**
	 * Stores the singleton instance of the dispatcher.
	 *
	 * @var    JEventDispatcher
	 * @since  3.0.0
	 */
	protected static $instance = null;

	/**
	 * Returns the global Event Dispatcher object, only creating it
	 * if it doesn't already exist.
	 *
	 * @return  JEventDispatcher  The EventDispatcher object.
	 *
	 * @since   3.0.0
	 */
	public static function getInstance()
	{
		if (self::$instance === null)
		{
			self::$instance = new static;
		}

		return self::$instance;
	}

	/**
	 * Get the state of the JEventDispatcher object
	 *
	 * @return  mixed    The state of the object.
	 *
	 * @since   3.0.0
	 */
	public function getState()
	{
		return $this->_state;
	}

	/**
	 * Registers an event handler to the event dispatcher
	 *
	 * @param   string  $event    Name of the event to register handler for
	 * @param   string  $handler  Name of the event handler
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 * @throws  InvalidArgumentException
	 */
	public function register($event, $handler)
	{
		// Are we dealing with a class or callback type handler?
		if (is_callable($handler))
		{
			// Ok, function type event handler... let's attach it.
			$method = array('event' => $event, 'handler' => $handler);
			$this->attach($method);
		}
		elseif (class_exists($handler))
		{
			// Ok, class type event handler... let's instantiate and attach it.
			$this->attach(new $handler($this));
		}
		else
		{
			throw new InvalidArgumentException('Invalid event handler.');
		}
	}

	/**
	 * Triggers an event by dispatching arguments to all observers that handle
	 * the event and returning their return values.
	 *
	 * @param   string  $event  The event to trigger.
	 * @param   array   $args   An array of arguments.
	 *
	 * @return  array  An array of results from each function call.
	 *
	 * @since   3.0.0
	 */
	public function trigger($event, $args = array())
	{
		$result = array();

		/*
		 * If no arguments were passed, we still need to pass an empty array to
		 * the call_user_func_array function.
		 */
		$args = (array) $args;

		$event = strtolower($event);

		// Check if any plugins are attached to the event.
		if (!isset($this->_methods[$event]) || empty($this->_methods[$event]))
		{
			// No Plugins Associated To Event!
			return $result;
		}

		// Loop through all plugins having a method matching our event
		foreach ($this->_methods[$event] as $key)
		{
			// Check if the plugin is present.
			if (!isset($this->_observers[$key]))
			{
				continue;
			}

			// Fire the event for an object based observer.
			if (is_object($this->_observers[$key]))
			{
				$args['event'] = $event;
				$value = $this->_observers[$key]->update($args);
			}
			// Fire the event for a function based observer.
			elseif (is_array($this->_observers[$key]))
			{
				$value = call_user_func_array($this->_observers[$key]['handler'], $args);
			}

			if (isset($value))
			{
				$result[] = $value;
			}
		}

		return $result;
	}

	/**
	 * Attach an observer object
	 *
	 * @param   object  $observer  An observer object to attach
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function attach($observer)
	{
		if (is_array($observer))
		{
			if (!isset($observer['handler']) || !isset($observer['event']) || !is_callable($observer['handler']))
			{
				return;
			}

			// Make sure we haven't already attached this array as an observer
			foreach ($this->_observers as $check)
			{
				if (is_array($check) && $check['event'] === $observer['event'] && $check['handler'] === $observer['handler'])
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			$methods = array($observer['event']);
		}
		else
		{
			if (!($observer instanceof JEvent))
			{
				return;
			}

			// Make sure we haven't already attached this object as an observer
			$class = get_class($observer);

			foreach ($this->_observers as $check)
			{
				if ($check instanceof $class)
				{
					return;
				}
			}

			$this->_observers[] = $observer;
			$methods = array_diff(get_class_methods($observer), get_class_methods('JPlugin'));
		}

		end($this->_observers);
		$key = key($this->_observers);

		foreach ($methods as $method)
		{
			$method = strtolower($method);

			if (!isset($this->_methods[$method]))
			{
				$this->_methods[$method] = array();
			}

			$this->_methods[$method][] = $key;
		}
	}

	/**
	 * Detach an observer object
	 *
	 * @param   object  $observer  An observer object to detach.
	 *
	 * @return  boolean  True if the observer object was detached.
	 *
	 * @since   3.0.0
	 */
	public function detach($observer)
	{
		$retval = false;

		$key = array_search($observer, $this->_observers);

		if ($key !== false)
		{
			unset($this->_observers[$key]);
			$retval = true;

			foreach ($this->_methods as &$method)
			{
				$k = array_search($key, $method);

				if ($k !== false)
				{
					unset($method[$k]);
				}
			}
		}

		return $retval;
	}
}
joomla/event/event.php000064400000003340152177723700011013 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Event
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * JEvent Class
 *
 * @since       1.5
 * @deprecated  4.0  The CMS' Event classes will be replaced with the `joomla/event` package
 */
abstract class JEvent extends JObject
{
	/**
	 * Event object to observe.
	 *
	 * @var    object
	 * @since  2.5
	 */
	protected $_subject = null;

	/**
	 * Constructor
	 *
	 * @param   object  &$subject  The object to observe.
	 *
	 * @since   2.5
	 */
	public function __construct(&$subject)
	{
		// Register the observer ($this) so we can be notified
		$subject->attach($this);

		// Set the subject to observe
		$this->_subject = &$subject;
	}

	/**
	 * Method to trigger events.
	 * The method first generates the even from the argument array. Then it unsets the argument
	 * since the argument has no bearing on the event handler.
	 * If the method exists it is called and returns its return value. If it does not exist it
	 * returns null.
	 *
	 * @param   array  &$args  Arguments
	 *
	 * @return  mixed  Routine return value
	 *
	 * @since   1.5
	 */
	public function update(&$args)
	{
		// First let's get the event from the argument array.  Next we will unset the
		// event argument as it has no bearing on the method to handle the event.
		$event = $args['event'];
		unset($args['event']);

		/*
		 * If the method to handle an event exists, call it and return its return
		 * value.  If it does not exist, return null.
		 */
		if (method_exists($this, $event))
		{
			return call_user_func_array(array($this, $event), $args);
		}
	}
}
joomla/archive/extractable.php000064400000002007152177723700012467 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Archieve class interface
 *
 * @since       3.0.0
 * @deprecated  4.0 use the Joomla\Archive\ExtractableInterface interface instead
 */
interface JArchiveExtractable
{
	/**
	 * Extract a compressed file to a given path
	 *
	 * @param   string  $archive      Path to archive to extract
	 * @param   string  $destination  Path to extract archive to
	 * @param   array   $options      Extraction options [may be unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.0.0
	 */
	public function extract($archive, $destination, array $options = array());

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   3.0.0
	 */
	public static function isSupported();
}
joomla/archive/archive.php000064400000003644152177723700011622 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');

use Joomla\Archive\Archive;

/**
 * An Archive handling class
 *
 * @since       1.5
 * @deprecated  4.0 use the Joomla\Archive\Archive class instead
 */
class JArchive
{
	/**
	 * The array of instantiated archive adapters.
	 *
	 * @var    JArchiveExtractable[]
	 * @since  3.0.0
	 */
	protected static $adapters = array();

	/**
	 * Extract an archive file to a directory.
	 *
	 * @param   string  $archivename  The name of the archive file
	 * @param   string  $extractdir   Directory to unpack into
	 *
	 * @return  boolean  True for success
	 *
	 * @since   1.5
	 * @throws  InvalidArgumentException
	 * @deprecated  4.0 use the Joomla\Archive\Archive class instead
	 */
	public static function extract($archivename, $extractdir)
	{
		// The archive instance
		$archive = new Archive(array('tmp_path' => JFactory::getConfig()->get('tmp_path')));

		// Extract the archive
		return $archive->extract($archivename, $extractdir);
	}

	/**
	 * Get a file compression adapter.
	 *
	 * @param   string  $type  The type of adapter (bzip2|gzip|tar|zip).
	 *
	 * @return  JArchiveExtractable  Adapter for the requested type
	 *
	 * @since   1.5
	 * @throws  UnexpectedValueException
	 * @deprecated  4.0 use the Joomla\Archive\Archive class instead
	 */
	public static function getAdapter($type)
	{
		if (!isset(self::$adapters[$type]))
		{
			// Try to load the adapter object
			$class = 'JArchive' . ucfirst($type);

			if (!class_exists($class))
			{
				throw new UnexpectedValueException('Unable to load archive', 500);
			}

			self::$adapters[$type] = new $class;
		}

		return self::$adapters[$type];
	}
}
joomla/archive/tar.php000064400000014302152177723700010760 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');
jimport('joomla.filesystem.path');

/**
 * Tar format adapter for the JArchive class
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <https://www.horde.org>
 *
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since       1.5
 * @deprecated  4.0 use the Joomla\Archive\Tar class instead
 */
class JArchiveTar implements JArchiveExtractable
{
	/**
	 * Tar file types.
	 *
	 * @var    array
	 * @since  1.5
	 */
	private $_types = array(
		0x0  => 'Unix file',
		0x30 => 'File',
		0x31 => 'Link',
		0x32 => 'Symbolic link',
		0x33 => 'Character special file',
		0x34 => 'Block special file',
		0x35 => 'Directory',
		0x36 => 'FIFO special file',
		0x37 => 'Contiguous file',
	);

	/**
	 * Tar file data buffer
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_data = null;

	/**
	 * Tar file metadata array
	 *
	 * @var    array
	 * @since  1.5
	 */
	private $_metadata = null;

	/**
	 * Extract a ZIP compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean|JException  True on success, JException instance on failure if JError class exists
	 *
	 * @since   1.5
	 * @throws  RuntimeException if JError class does not exist
	 */
	public function extract($archive, $destination, array $options = array())
	{
		$this->_data = null;
		$this->_metadata = null;

		$this->_data = file_get_contents($archive);

		if (!$this->_data)
		{
			if (class_exists('JError'))
			{
				return JError::raiseWarning(100, 'Unable to read archive');
			}
			else
			{
				throw new RuntimeException('Unable to read archive');
			}
		}

		$this->_getTarInfo($this->_data);

		for ($i = 0, $n = count($this->_metadata); $i < $n; $i++)
		{
			$type = strtolower($this->_metadata[$i]['type']);

			if ($type == 'file' || $type == 'unix file')
			{
				$buffer = $this->_metadata[$i]['data'];
				$path = JPath::clean($destination . '/' . $this->_metadata[$i]['name']);

				// Make sure the destination folder exists
				if (!JFolder::create(dirname($path)))
				{
					if (class_exists('JError'))
					{
						return JError::raiseWarning(100, 'Unable to create destination');
					}
					else
					{
						throw new RuntimeException('Unable to create destination');
					}
				}

				if (JFile::write($path, $buffer) === false)
				{
					if (class_exists('JError'))
					{
						return JError::raiseWarning(100, 'Unable to write entry');
					}
					else
					{
						throw new RuntimeException('Unable to write entry');
					}
				}
			}
		}

		return true;
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   2.5.0
	 */
	public static function isSupported()
	{
		return true;
	}

	/**
	 * Get the list of files/data from a Tar archive buffer.
	 *
	 * @param   string  &$data  The Tar archive buffer.
	 *
	 * @return  boolean|JException  True on success, JException instance on failure if JError class exists
	 *
	 * @since   1.5
	 * @throws  RuntimeException if JError class does not exist
	 */
	protected function _getTarInfo(& $data)
	{
		$position = 0;
		$return_array = array();

		while ($position < strlen($data))
		{
			if (version_compare(PHP_VERSION, '5.5', '>='))
			{
				$info = @unpack(
					'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Ctypeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor',
					substr($data, $position)
				);
			}
			else
			{
				$info = @unpack(
					'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor',
					substr($data, $position)
				);
			}

			/**
			 * This variable has been set in the previous loop,
			 * meaning that the filename was present in the previous block
			 * to allow more than 100 characters - see below
			 */
			if (isset($longlinkfilename))
			{
				$info['filename'] = $longlinkfilename;
				unset($longlinkfilename);
			}

			if (!$info)
			{
				if (class_exists('JError'))
				{
					return JError::raiseWarning(100, 'Unable to decompress data');
				}
				else
				{
					throw new RuntimeException('Unable to decompress data');
				}
			}

			$position += 512;
			$contents = substr($data, $position, octdec($info['size']));
			$position += ceil(octdec($info['size']) / 512) * 512;

			if ($info['filename'])
			{
				$file = array(
					'attr' => null,
					'data' => null,
					'date' => octdec($info['mtime']),
					'name' => trim($info['filename']),
					'size' => octdec($info['size']),
					'type' => isset($this->_types[$info['typeflag']]) ? $this->_types[$info['typeflag']] : null,
				);

				if (($info['typeflag'] == 0) || ($info['typeflag'] == 0x30) || ($info['typeflag'] == 0x35))
				{
					// File or folder.
					$file['data'] = $contents;

					$mode = hexdec(substr($info['mode'], 4, 3));
					$file['attr'] = (($info['typeflag'] == 0x35) ? 'd' : '-') . (($mode & 0x400) ? 'r' : '-') . (($mode & 0x200) ? 'w' : '-') .
						(($mode & 0x100) ? 'x' : '-') . (($mode & 0x040) ? 'r' : '-') . (($mode & 0x020) ? 'w' : '-') . (($mode & 0x010) ? 'x' : '-') .
						(($mode & 0x004) ? 'r' : '-') . (($mode & 0x002) ? 'w' : '-') . (($mode & 0x001) ? 'x' : '-');
				}
				elseif (chr($info['typeflag']) == 'L' && $info['filename'] == '././@LongLink')
				{
					// GNU tar ././@LongLink support - the filename is actually in the contents,
					// setting a variable here so we can test in the next loop
					$longlinkfilename = $contents;

					// And the file contents are in the next block so we'll need to skip this
					continue;
				}
				else
				{
					// Some other type.
				}

				$return_array[] = $file;
			}
		}

		$this->_metadata = $return_array;

		return true;
	}
}
joomla/archive/wrapper/archive.php000064400000002537152177723700013302 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for JArchive
 *
 * @package     Joomla.Platform
 * @subpackage  Archive
 * @since       3.4
 * @deprecated  4.0 use the Joomla\Archive\Archive class instead
 */
class JArchiveWrapperArchive
{
	/**
	 * Helper wrapper method for extract
	 *
	 * @param   string  $archivename  The name of the archive file
	 * @param   string  $extractdir   Directory to unpack into
	 *
	 * @return  boolean  True for success
	 *
	 * @see     JArchive::extract()
	 * @since   3.4
	 * @throws InvalidArgumentException
	 * @deprecated 4.0 use the Joomla\Archive\Archive class instead
	 */
	public function extract($archivename, $extractdir)
	{
		return JArchive::extract($archivename, $extractdir);
	}

	/**
	 * Helper wrapper method for getAdapter
	 *
	 * @param   string  $type  The type of adapter (bzip2|gzip|tar|zip).
	 *
	 * @return  JArchiveExtractable  Adapter for the requested type
	 *
	 * @see     JUserHelper::getAdapter()
	 * @since   3.4
	 * @deprecated 4.0 use the Joomla\Archive\Archive class instead
	 */
	public function getAdapter($type)
	{
		return JArchive::getAdapter($type);
	}
}
joomla/archive/gzip.php000064400000012361152177723700011146 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.file');

/**
 * Gzip format adapter for the JArchive class
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <https://www.horde.org>
 *
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since       1.5
 * @deprecated  4.0 use the Joomla\Archive\Gzip class instead
 */
class JArchiveGzip implements JArchiveExtractable
{
	/**
	 * Gzip file flags.
	 *
	 * @var    array
	 * @since  1.5
	 */
	private $_flags = array('FTEXT' => 0x01, 'FHCRC' => 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08, 'FCOMMENT' => 0x10);

	/**
	 * Gzip file data buffer
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_data = null;

	/**
	 * Extract a Gzip compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive to
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 * @throws  RuntimeException
	 */
	public function extract($archive, $destination, array $options = array())
	{
		$this->_data = null;

		if (!extension_loaded('zlib'))
		{
			return $this->raiseWarning(100, 'The zlib extension is not available.');
		}

		if (isset($options['use_streams']) && $options['use_streams'] != false)
		{
			return $this->extractStream($archive, $destination, $options);
		}

		$this->_data = file_get_contents($archive);

		if (!$this->_data)
		{
			return $this->raiseWarning(100, 'Unable to read archive');
		}

		$position = $this->_getFilePosition();
		$buffer = gzinflate(substr($this->_data, $position, strlen($this->_data) - $position));

		if (empty($buffer))
		{
			return $this->raiseWarning(100, 'Unable to decompress data');
		}

		if (JFile::write($destination, $buffer) === false)
		{
			return $this->raiseWarning(100, 'Unable to write archive');
		}

		return true;
	}

	/**
	 * Method to extract archive using stream objects
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive to
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.6.0
	 */
	protected function extractStream($archive, $destination, $options = array())
	{
		// New style! streams!
		$input = JFactory::getStream();

		// Use gz
		$input->set('processingmethod', 'gz');

		if (!$input->open($archive))
		{
			return $this->raiseWarning(100, 'Unable to read archive (gz)');
		}

		$output = JFactory::getStream();

		if (!$output->open($destination, 'w'))
		{
			$input->close();

			return $this->raiseWarning(100, 'Unable to write archive (gz)');
		}

		do
		{
			$this->_data = $input->read($input->get('chunksize', 8196));

			if ($this->_data && !$output->write($this->_data))
			{
				$input->close();

				return $this->raiseWarning(100, 'Unable to write file (gz)');
			}
		}

		while ($this->_data);

		$output->close();
		$input->close();

		return true;
	}

	/**
	 * Temporary private method to isolate JError from the extract method
	 * This code should be removed when JError is removed.
	 *
	 * @param   int     $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 *
	 * @return  JException  JException instance if JError class exists
	 *
	 * @since   3.6.0
	 * @throws  RuntimeException if JError class does not exist
	 */
	private function raiseWarning($code, $msg)
	{
		if (class_exists('JError'))
		{
			return JError::raiseWarning($code, $msg);
		}

		throw new RuntimeException($msg);
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   2.5.0
	 */
	public static function isSupported()
	{
		return extension_loaded('zlib');
	}

	/**
	 * Get file data offset for archive
	 *
	 * @return  integer  Data position marker for archive
	 *
	 * @since   1.5
	 * @throws  RuntimeException
	 */
	public function _getFilePosition()
	{
		// Gzipped file... unpack it first
		$position = 0;
		$info = @ unpack('CCM/CFLG/VTime/CXFL/COS', substr($this->_data, $position + 2));

		if (!$info)
		{
			return $this->raiseWarning(100, 'Unable to decompress data.');
		}

		$position += 10;

		if ($info['FLG'] & $this->_flags['FEXTRA'])
		{
			$XLEN = unpack('vLength', substr($this->_data, $position + 0, 2));
			$XLEN = $XLEN['Length'];
			$position += $XLEN + 2;
		}

		if ($info['FLG'] & $this->_flags['FNAME'])
		{
			$filenamePos = strpos($this->_data, "\x0", $position);
			$position = $filenamePos + 1;
		}

		if ($info['FLG'] & $this->_flags['FCOMMENT'])
		{
			$commentPos = strpos($this->_data, "\x0", $position);
			$position = $commentPos + 1;
		}

		if ($info['FLG'] & $this->_flags['FHCRC'])
		{
			$hcrc = unpack('vCRC', substr($this->_data, $position + 0, 2));
			$hcrc = $hcrc['CRC'];
			$position += 2;
		}

		return $position;
	}
}
joomla/archive/bzip2.php000064400000007403152177723700011224 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.stream');

/**
 * Bzip2 format adapter for the JArchive class
 *
 * @since       1.5
 * @deprecated  4.0 use the Joomla\Archive\Bzip2 class instead
 */
class JArchiveBzip2 implements JArchiveExtractable
{
	/**
	 * Bzip2 file data buffer
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_data = null;

	/**
	 * Extract a Bzip2 compressed file to a given path
	 *
	 * @param   string  $archive      Path to Bzip2 archive to extract
	 * @param   string  $destination  Path to extract archive to
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 * @throws  RuntimeException
	 */
	public function extract($archive, $destination, array $options = array())
	{
		$this->_data = null;

		if (!extension_loaded('bz2'))
		{
			$this->raiseWarning(100, 'The bz2 extension is not available.');
		}

		if (isset($options['use_streams']) && $options['use_streams'] != false)
		{
			return $this->extractStream($archive, $destination, $options);
		}

		// Old style: read the whole file and then parse it
		$this->_data = file_get_contents($archive);

		if (!$this->_data)
		{
			return $this->raiseWarning(100, 'Unable to read archive');
		}

		$buffer = bzdecompress($this->_data);
		unset($this->_data);

		if (empty($buffer))
		{
			return $this->raiseWarning(100, 'Unable to decompress data');
		}

		if (JFile::write($destination, $buffer) === false)
		{
			return $this->raiseWarning(100, 'Unable to write archive');
		}

		return true;
	}

	/**
	 * Method to extract archive using stream objects
	 *
	 * @param   string  $archive      Path to Bzip2 archive to extract
	 * @param   string  $destination  Path to extract archive to
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   3.6.0
	 */
	protected function extractStream($archive, $destination, $options = array())
	{
		// New style! streams!
		$input = JFactory::getStream();

		// Use bzip
		$input->set('processingmethod', 'bz');

		if (!$input->open($archive))
		{
			return $this->raiseWarning(100, 'Unable to read archive (bz2)');

		}

		$output = JFactory::getStream();

		if (!$output->open($destination, 'w'))
		{
			$input->close();

			return $this->raiseWarning(100, 'Unable to write archive (bz2)');

		}

		do
		{
			$this->_data = $input->read($input->get('chunksize', 8196));

			if ($this->_data && !$output->write($this->_data))
			{
				$input->close();

				return $this->raiseWarning(100, 'Unable to write archive (bz2)');
			}
		}

		while ($this->_data);

		$output->close();
		$input->close();

		return true;
	}

	/**
	 * Temporary private method to isolate JError from the extract method
	 * This code should be removed when JError is removed.
	 *
	 * @param   int     $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 *
	 * @return  JException  JException instance if JError class exists
	 *
	 * @since   3.6.0
	 * @throws  RuntimeException if JError class does not exist
	 */
	private function raiseWarning($code, $msg)
	{
		if (class_exists('JError'))
		{
			return JError::raiseWarning($code, $msg);
		}

		throw new RuntimeException($msg);
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   2.5.0
	 */
	public static function isSupported()
	{
		return extension_loaded('bz2');
	}
}
joomla/archive/zip.php000064400000041030152177723700010772 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Archive
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

jimport('joomla.filesystem.file');
jimport('joomla.filesystem.folder');

/**
 * ZIP format adapter for the JArchive class
 *
 * The ZIP compression code is partially based on code from:
 * Eric Mueller <eric@themepark.com>
 * http://www.zend.com/codex.php?id=535&single=1
 *
 * Deins125 <webmaster@atlant.ru>
 * http://www.zend.com/codex.php?id=470&single=1
 *
 * The ZIP compression date code is partially based on code from
 * Peter Listiak <mlady@users.sourceforge.net>
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <https://www.horde.org>
 *
 * @contributor  Chuck Hagenbuch <chuck@horde.org>
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since       1.5
 * @deprecated  4.0 use the Joomla\Archive\Zip class instead
 */
class JArchiveZip implements JArchiveExtractable
{
	/**
	 * ZIP compression methods.
	 *
	 * @var    array
	 * @since  1.5
	 */
	private $_methods = array(
		0x0 => 'None',
		0x1 => 'Shrunk',
		0x2 => 'Super Fast',
		0x3 => 'Fast',
		0x4 => 'Normal',
		0x5 => 'Maximum',
		0x6 => 'Imploded',
		0x8 => 'Deflated',
	);

	/**
	 * Beginning of central directory record.
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_ctrlDirHeader = "\x50\x4b\x01\x02";

	/**
	 * End of central directory record.
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_ctrlDirEnd = "\x50\x4b\x05\x06\x00\x00\x00\x00";

	/**
	 * Beginning of file contents.
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_fileHeader = "\x50\x4b\x03\x04";

	/**
	 * ZIP file data buffer
	 *
	 * @var    string
	 * @since  1.5
	 */
	private $_data = null;

	/**
	 * ZIP file metadata array
	 *
	 * @var    array
	 * @since  1.5
	 */
	private $_metadata = null;

	/**
	 * Create a ZIP compressed file from an array of file data.
	 *
	 * @param   string  $archive  Path to save archive.
	 * @param   array   $files    Array of files to add to archive.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   1.5
	 *
	 * @todo    Finish Implementation
	 */
	public function create($archive, $files)
	{
		$contents = array();
		$ctrldir = array();

		foreach ($files as $file)
		{
			$this->_addToZIPFile($file, $contents, $ctrldir);
		}

		return $this->_createZIPFile($contents, $ctrldir, $archive);
	}

	/**
	 * Extract a ZIP compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 * @param   array   $options      Extraction options [unused]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 * @throws  RuntimeException
	 */
	public function extract($archive, $destination, array $options = array())
	{
		if (!is_file($archive))
		{
			return $this->raiseWarning(100, 'Archive does not exist');
		}

		if ($this->hasNativeSupport())
		{
			return $this->extractNative($archive, $destination);
		}

		return $this->extractCustom($archive, $destination);
	}

	/**
	 * Temporary private method to isolate JError from the extract method
	 * This code should be removed when JError is removed.
	 *
	 * @param   int     $code  The application-internal error code for this error
	 * @param   string  $msg   The error message, which may also be shown the user if need be.
	 *
	 * @return  JException  JException instance if JError class exists
	 *
	 * @since   3.6.0
	 * @throws  RuntimeException if JError class does not exist
	 */
	private function raiseWarning($code, $msg)
	{
		if (class_exists('JError'))
		{
			return JError::raiseWarning($code, $msg);
		}

		throw new RuntimeException($msg);
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   2.5.0
	 */
	public static function isSupported()
	{
		return self::hasNativeSupport() || extension_loaded('zlib');
	}

	/**
	 * Method to determine if the server has native zip support for faster handling
	 *
	 * @return  boolean  True if php has native ZIP support
	 *
	 * @since   1.5
	 */
	public static function hasNativeSupport()
	{
		return function_exists('zip_open') && function_exists('zip_read');
	}

	/**
	 * Checks to see if the data is a valid ZIP file.
	 *
	 * @param   string  &$data  ZIP archive data buffer.
	 *
	 * @return  boolean  True if valid, false if invalid.
	 *
	 * @since   1.5
	 */
	public function checkZipData(&$data)
	{
		if (strpos($data, $this->_fileHeader) === false)
		{
			return false;
		}

		return true;
	}

	/**
	 * Extract a ZIP compressed file to a given path using a php based algorithm that only requires zlib support
	 *
	 * @param   string  $archive      Path to ZIP archive to extract.
	 * @param   string  $destination  Path to extract archive into.
	 *
	 * @return  mixed   True if successful
	 *
	 * @since   3.0
	 * @throws  RuntimeException
	 */
	protected function extractCustom($archive, $destination)
	{
		$this->_data = null;
		$this->_metadata = null;

		if (!extension_loaded('zlib'))
		{
			return $this->raiseWarning(100, 'Zlib not supported');
		}

		$this->_data = file_get_contents($archive);

		if (!$this->_data)
		{
			return $this->raiseWarning(100, 'Unable to read archive (zip)');
		}

		if (!$this->_readZipInfo($this->_data))
		{
			return $this->raiseWarning(100, 'Get ZIP Information failed');
		}

		for ($i = 0, $n = count($this->_metadata); $i < $n; $i++)
		{
			$lastPathCharacter = substr($this->_metadata[$i]['name'], -1, 1);

			if ($lastPathCharacter !== '/' && $lastPathCharacter !== '\\')
			{
				$buffer = $this->_getFileData($i);
				$path = JPath::clean($destination . '/' . $this->_metadata[$i]['name']);

				// Make sure the destination folder exists
				if (!JFolder::create(dirname($path)))
				{
					return $this->raiseWarning(100, 'Unable to create destination');
				}

				if (JFile::write($path, $buffer) === false)
				{
					return $this->raiseWarning(100, 'Unable to write entry');
				}
			}
		}

		return true;
	}

	/**
	 * Extract a ZIP compressed file to a given path using native php api calls for speed
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 *
	 * @return  boolean  True on success
	 *
	 * @since   3.0
	 * @throws  RuntimeException
	 */
	protected function extractNative($archive, $destination)
	{
		$zip = zip_open($archive);

		if (!is_resource($zip))
		{
			return $this->raiseWarning(100, 'Unable to open archive');
		}

		// Make sure the destination folder exists
		if (!JFolder::create($destination))
		{
			return $this->raiseWarning(100, 'Unable to create destination');
		}

		// Read files in the archive
		while ($file = @zip_read($zip))
		{
			if (!zip_entry_open($zip, $file, 'r'))
			{
				return $this->raiseWarning(100, 'Unable to read entry');
			}

			if (substr(zip_entry_name($file), strlen(zip_entry_name($file)) - 1) != '/')
			{
				$buffer = zip_entry_read($file, zip_entry_filesize($file));

				if (JFile::write($destination . '/' . zip_entry_name($file), $buffer) === false)
				{
					return $this->raiseWarning(100, 'Unable to write entry');
				}

				zip_entry_close($file);
			}
		}

		@zip_close($zip);

		return true;
	}

	/**
	 * Get the list of files/data from a ZIP archive buffer.
	 *
	 * <pre>
	 * KEY: Position in zipfile
	 * VALUES: 'attr'  --  File attributes
	 * 'crc'   --  CRC checksum
	 * 'csize' --  Compressed file size
	 * 'date'  --  File modification time
	 * 'name'  --  Filename
	 * 'method'--  Compression method
	 * 'size'  --  Original file size
	 * 'type'  --  File type
	 * </pre>
	 *
	 * @param   string  &$data  The ZIP archive buffer.
	 *
	 * @return  boolean True on success
	 *
	 * @since   2.5.0
	 * @throws  RuntimeException
	 */
	private function _readZipInfo(&$data)
	{
		$entries = array();

		// Find the last central directory header entry
		$fhLast = strpos($data, $this->_ctrlDirEnd);

		do
		{
			$last = $fhLast;
		}

		while (($fhLast = strpos($data, $this->_ctrlDirEnd, $fhLast + 1)) !== false);

		// Find the central directory offset
		$offset = 0;

		if ($last)
		{
			$endOfCentralDirectory = unpack(
				'vNumberOfDisk/vNoOfDiskWithStartOfCentralDirectory/vNoOfCentralDirectoryEntriesOnDisk/' .
				'vTotalCentralDirectoryEntries/VSizeOfCentralDirectory/VCentralDirectoryOffset/vCommentLength',
				substr($data, $last + 4)
			);
			$offset = $endOfCentralDirectory['CentralDirectoryOffset'];
		}

		// Get details from central directory structure.
		$fhStart = strpos($data, $this->_ctrlDirHeader, $offset);
		$dataLength = strlen($data);

		do
		{
			if ($dataLength < $fhStart + 31)
			{
				return $this->raiseWarning(100, 'Invalid Zip Data');
			}

			$info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength', substr($data, $fhStart + 10, 20));
			$name = substr($data, $fhStart + 46, $info['Length']);

			$entries[$name] = array(
				'attr' => null,
				'crc' => sprintf('%08s', dechex($info['CRC32'])),
				'csize' => $info['Compressed'],
				'date' => null,
				'_dataStart' => null,
				'name' => $name,
				'method' => $this->_methods[$info['Method']],
				'_method' => $info['Method'],
				'size' => $info['Uncompressed'],
				'type' => null,
			);

			$entries[$name]['date'] = mktime(
				(($info['Time'] >> 11) & 0x1f),
				(($info['Time'] >> 5) & 0x3f),
				(($info['Time'] << 1) & 0x3e),
				(($info['Time'] >> 21) & 0x07),
				(($info['Time'] >> 16) & 0x1f),
				((($info['Time'] >> 25) & 0x7f) + 1980)
			);

			if ($dataLength < $fhStart + 43)
			{
				return $this->raiseWarning(100, 'Invalid ZIP data');
			}

			$info = unpack('vInternal/VExternal/VOffset', substr($data, $fhStart + 36, 10));

			$entries[$name]['type'] = ($info['Internal'] & 0x01) ? 'text' : 'binary';
			$entries[$name]['attr'] = (($info['External'] & 0x10) ? 'D' : '-') . (($info['External'] & 0x20) ? 'A' : '-')
				. (($info['External'] & 0x03) ? 'S' : '-') . (($info['External'] & 0x02) ? 'H' : '-') . (($info['External'] & 0x01) ? 'R' : '-');
			$entries[$name]['offset'] = $info['Offset'];

			// Get details from local file header since we have the offset
			$lfhStart = strpos($data, $this->_fileHeader, $entries[$name]['offset']);

			if ($dataLength < $lfhStart + 34)
			{
				return $this->raiseWarning(100, 'Invalid Zip Data');
			}

			$info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength', substr($data, $lfhStart + 8, 25));
			$name = substr($data, $lfhStart + 30, $info['Length']);
			$entries[$name]['_dataStart'] = $lfhStart + 30 + $info['Length'] + $info['ExtraLength'];

			// Bump the max execution time because not using the built in php zip libs makes this process slow.
			@set_time_limit(ini_get('max_execution_time'));
		}

		while ((($fhStart = strpos($data, $this->_ctrlDirHeader, $fhStart + 46)) !== false));

		$this->_metadata = array_values($entries);

		return true;
	}

	/**
	 * Returns the file data for a file by offsest in the ZIP archive
	 *
	 * @param   integer  $key  The position of the file in the archive.
	 *
	 * @return  string  Uncompressed file data buffer.
	 *
	 * @since   1.5
	 */
	private function _getFileData($key)
	{
		$method = $this->_metadata[$key]['_method'];

		if ($method == 0x12 && !extension_loaded('bz2'))
		{
			return '';
		}

		switch ($method)
		{
			case 0x8:
				return gzinflate(substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']));

			case 0x0:
				// Files that aren't compressed.
				return substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']);

			case 0x12:

				return bzdecompress(substr($this->_data, $this->_metadata[$key]['_dataStart'], $this->_metadata[$key]['csize']));
		}

		return '';
	}

	/**
	 * Converts a UNIX timestamp to a 4-byte DOS date and time format
	 * (date in high 2-bytes, time in low 2-bytes allowing magnitude
	 * comparison).
	 *
	 * @param   int  $unixtime  The current UNIX timestamp.
	 *
	 * @return  int  The current date in a 4-byte DOS format.
	 *
	 * @since   1.5
	 */
	protected function _unix2DOSTime($unixtime = null)
	{
		$timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);

		if ($timearray['year'] < 1980)
		{
			$timearray['year'] = 1980;
			$timearray['mon'] = 1;
			$timearray['mday'] = 1;
			$timearray['hours'] = 0;
			$timearray['minutes'] = 0;
			$timearray['seconds'] = 0;
		}

		return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) |
			($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
	}

	/**
	 * Adds a "file" to the ZIP archive.
	 *
	 * @param   array  &$file      File data array to add
	 * @param   array  &$contents  An array of existing zipped files.
	 * @param   array  &$ctrldir   An array of central directory information.
	 *
	 * @return  void
	 *
	 * @since   1.5
	 *
	 * @todo    Review and finish implementation
	 */
	private function _addToZIPFile(array &$file, array &$contents, array &$ctrldir)
	{
		$data = &$file['data'];
		$name = str_replace('\\', '/', $file['name']);

		/* See if time/date information has been provided. */
		$ftime = null;

		if (isset($file['time']))
		{
			$ftime = $file['time'];
		}

		// Get the hex time.
		$dtime = dechex($this->_unix2DosTime($ftime));
		$hexdtime = chr(hexdec($dtime[6] . $dtime[7])) . chr(hexdec($dtime[4] . $dtime[5])) . chr(hexdec($dtime[2] . $dtime[3]))
			. chr(hexdec($dtime[0] . $dtime[1]));

		/* Begin creating the ZIP data. */
		$fr = $this->_fileHeader;
		/* Version needed to extract. */
		$fr .= "\x14\x00";
		/* General purpose bit flag. */
		$fr .= "\x00\x00";
		/* Compression method. */
		$fr .= "\x08\x00";
		/* Last modification time/date. */
		$fr .= $hexdtime;

		/* "Local file header" segment. */
		$unc_len = strlen($data);
		$crc = crc32($data);
		$zdata = gzcompress($data);
		$zdata = substr(substr($zdata, 0, strlen($zdata) - 4), 2);
		$c_len = strlen($zdata);

		/* CRC 32 information. */
		$fr .= pack('V', $crc);
		/* Compressed filesize. */
		$fr .= pack('V', $c_len);
		/* Uncompressed filesize. */
		$fr .= pack('V', $unc_len);
		/* Length of filename. */
		$fr .= pack('v', strlen($name));
		/* Extra field length. */
		$fr .= pack('v', 0);
		/* File name. */
		$fr .= $name;

		/* "File data" segment. */
		$fr .= $zdata;

		/* Add this entry to array. */
		$old_offset = strlen(implode('', $contents));
		$contents[] = &$fr;

		/* Add to central directory record. */
		$cdrec = $this->_ctrlDirHeader;
		/* Version made by. */
		$cdrec .= "\x00\x00";
		/* Version needed to extract */
		$cdrec .= "\x14\x00";
		/* General purpose bit flag */
		$cdrec .= "\x00\x00";
		/* Compression method */
		$cdrec .= "\x08\x00";
		/* Last mod time/date. */
		$cdrec .= $hexdtime;
		/* CRC 32 information. */
		$cdrec .= pack('V', $crc);
		/* Compressed filesize. */
		$cdrec .= pack('V', $c_len);
		/* Uncompressed filesize. */
		$cdrec .= pack('V', $unc_len);
		/* Length of filename. */
		$cdrec .= pack('v', strlen($name));
		/* Extra field length. */
		$cdrec .= pack('v', 0);
		/* File comment length. */
		$cdrec .= pack('v', 0);
		/* Disk number start. */
		$cdrec .= pack('v', 0);
		/* Internal file attributes. */
		$cdrec .= pack('v', 0);
		/* External file attributes -'archive' bit set. */
		$cdrec .= pack('V', 32);
		/* Relative offset of local header. */
		$cdrec .= pack('V', $old_offset);
		/* File name. */
		$cdrec .= $name;
		/* Optional extra field, file comment goes here. */

		/* Save to central directory array. */
		$ctrldir[] = &$cdrec;
	}

	/**
	 * Creates the ZIP file.
	 *
	 * Official ZIP file format: https://support.pkware.com/display/PKZIP/APPNOTE
	 *
	 * @param   array   &$contents  An array of existing zipped files.
	 * @param   array   &$ctrlDir   An array of central directory information.
	 * @param   string  $path       The path to store the archive.
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.5
	 *
	 * @todo	Review and finish implementation
	 */
	private function _createZIPFile(array &$contents, array &$ctrlDir, $path)
	{
		$data = implode('', $contents);
		$dir = implode('', $ctrlDir);

		$buffer = $data . $dir . $this->_ctrlDirEnd . /* Total # of entries "on this disk". */
		pack('v', count($ctrlDir)) . /* Total # of entries overall. */
		pack('v', count($ctrlDir)) . /* Size of central directory. */
		pack('V', strlen($dir)) . /* Offset to start of central dir. */
		pack('V', strlen($data)) . /* ZIP file comment length. */
		"\x00\x00";

		if (JFile::write($path, $buffer) === false)
		{
			return false;
		}

		return true;
	}
}
joomla/model/model.php000064400000001355152177723700010755 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Model
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform Model Interface
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
interface JModel
{
	/**
	 * Get the model state.
	 *
	 * @return  Registry  The state object.
	 *
	 * @since   3.0.0
	 */
	public function getState();

	/**
	 * Set the model state.
	 *
	 * @param   Registry  $state  The state object.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function setState(Registry $state);
}
joomla/model/base.php000064400000002507152177723700010567 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Model
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform Base Model Class
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
abstract class JModelBase implements JModel
{
	/**
	 * The model state.
	 *
	 * @var    Registry
	 * @since  3.0.0
	 */
	protected $state;

	/**
	 * Instantiate the model.
	 *
	 * @param   Registry  $state  The model state.
	 *
	 * @since   3.0.0
	 */
	public function __construct(Registry $state = null)
	{
		// Setup the model.
		$this->state = isset($state) ? $state : $this->loadState();
	}

	/**
	 * Get the model state.
	 *
	 * @return  Registry  The state object.
	 *
	 * @since   3.0.0
	 */
	public function getState()
	{
		return $this->state;
	}

	/**
	 * Set the model state.
	 *
	 * @param   Registry  $state  The state object.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function setState(Registry $state)
	{
		$this->state = $state;
	}

	/**
	 * Load the model state.
	 *
	 * @return  Registry  The state object.
	 *
	 * @since   3.0.0
	 */
	protected function loadState()
	{
		return new Registry;
	}
}
joomla/model/database.php000064400000002752152177723700011423 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Model
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\Registry\Registry;

/**
 * Joomla Platform Database Model Class
 *
 * @since       3.0.0
 * @deprecated  5.0 Use the default MVC library
 */
abstract class JModelDatabase extends JModelBase
{
	/**
	 * The database driver.
	 *
	 * @var    JDatabaseDriver
	 * @since  3.0.0
	 */
	protected $db;

	/**
	 * Instantiate the model.
	 *
	 * @param   Registry         $state  The model state.
	 * @param   JDatabaseDriver  $db     The database adpater.
	 *
	 * @since   3.0.0
	 */
	public function __construct(Registry $state = null, JDatabaseDriver $db = null)
	{
		parent::__construct($state);

		// Setup the model.
		$this->db = isset($db) ? $db : $this->loadDb();
	}

	/**
	 * Get the database driver.
	 *
	 * @return  JDatabaseDriver  The database driver.
	 *
	 * @since   3.0.0
	 */
	public function getDb()
	{
		return $this->db;
	}

	/**
	 * Set the database driver.
	 *
	 * @param   JDatabaseDriver  $db  The database driver.
	 *
	 * @return  void
	 *
	 * @since   3.0.0
	 */
	public function setDb(JDatabaseDriver $db)
	{
		$this->db = $db;
	}

	/**
	 * Load the database driver.
	 *
	 * @return  JDatabaseDriver  The database driver.
	 *
	 * @since   3.0.0
	 */
	protected function loadDb()
	{
		return JFactory::getDbo();
	}
}
joomla/string/string.php000064400000004154152177723700011371 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  String
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

use Joomla\String\StringHelper;

/**
 * String handling class for utf-8 data
 * Wraps the phputf8 library
 * All functions assume the validity of utf-8 strings.
 *
 * @since       1.7.0
 * @deprecated  4.0  Use {@link \Joomla\String\StringHelper} instead unless otherwise noted.
 */
abstract class JString extends StringHelper
{
	/**
	 * Split a string in camel case format
	 *
	 * "FooBarABCDef"            becomes  array("Foo", "Bar", "ABC", "Def");
	 * "JFooBar"                 becomes  array("J", "Foo", "Bar");
	 * "J001FooBar002"           becomes  array("J001", "Foo", "Bar002");
	 * "abcDef"                  becomes  array("abc", "Def");
	 * "abc_defGhi_Jkl"          becomes  array("abc_def", "Ghi_Jkl");
	 * "ThisIsA_NASAAstronaut"   becomes  array("This", "Is", "A_NASA", "Astronaut")),
	 * "JohnFitzgerald_Kennedy"  becomes  array("John", "Fitzgerald_Kennedy")),
	 *
	 * @param   string  $string  The source string.
	 *
	 * @return  array   The splitted string.
	 *
	 * @deprecated  4.0 - Use JStringNormalise::fromCamelCase()
	 * @since   1.7.3
	 */
	public static function splitCamelCase($string)
	{
		JLog::add('JString::splitCamelCase has been deprecated. Use JStringNormalise::fromCamelCase.', JLog::WARNING, 'deprecated');

		return JStringNormalise::fromCamelCase($string, true);
	}

	/**
	 * Does a UTF-8 safe version of PHP parse_url function
	 *
	 * @param   string  $url  URL to parse
	 *
	 * @return  mixed  Associative array or false if badly formed URL.
	 *
	 * @link    http://us3.php.net/manual/en/function.parse-url.php
	 * @since   1.7.0
	 * @deprecated  4.0 (CMS) - Use {@link \Joomla\Uri\UriHelper::parse_url()} instead.
	 */
	public static function parse_url($url)
	{
		JLog::add('JString::parse_url has been deprecated. Use \\Joomla\\Uri\\UriHelper::parse_url.', JLog::WARNING, 'deprecated');

		return \Joomla\Uri\UriHelper::parse_url($url);
	}
}
joomla/string/wrapper/punycode.php000064400000004675152177723700013401 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  String
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

JLoader::register('idna_convert', JPATH_LIBRARIES . '/idna_convert/idna_convert.class.php');

/**
 * Wrapper class for JStringPunycode
 *
 * @package     Joomla.Platform
 * @subpackage  String
 * @since       3.4
 * @deprecated  4.0 Will be removed without replacement
 */
class JStringWrapperPunycode
{
	/**
	 * Helper wrapper method for toPunycode
	 *
	 * @param   string  $utfString  The UTF-8 string to transform.
	 *
	 * @return string  The punycode string.
	 *
	 * @see     JUserHelper::toPunycode()
	 * @since   3.4
	 */
	public function toPunycode($utfString)
	{
		return JStringPunycode::toPunycode($utfString);
	}

	/**
	 * Helper wrapper method for fromPunycode
	 *
	 * @param   string  $punycodeString  The Punycode string to transform.
	 *
	 * @return string  The UF-8 URL.
	 *
	 * @see     JUserHelper::fromPunycode()
	 * @since   3.4
	 */
	public function fromPunycode($punycodeString)
	{
		return JStringPunycode::fromPunycode($punycodeString);
	}

	/**
	 * Helper wrapper method for urlToPunycode
	 *
	 * @param   string  $uri  The UTF-8 URL to transform.
	 *
	 * @return string  The punycode URL.
	 *
	 * @see     JUserHelper::urlToPunycode()
	 * @since   3.4
	 */
	public function urlToPunycode($uri)
	{
		return JStringPunycode::urlToPunycode($uri);
	}

	/**
	 * Helper wrapper method for urlToUTF8
	 *
	 * @param   string  $uri  The Punycode URL to transform.
	 *
	 * @return string  The UTF-8 URL.
	 *
	 * @see     JStringPunycode::urlToUTF8()
	 * @since   3.4
	 */
	public function urlToUTF8($uri)
	{
		return JStringPunycode::urlToUTF8($uri);
	}

	/**
	 * Helper wrapper method for emailToPunycode
	 *
	 * @param   string  $email  The UTF-8 email to transform.
	 *
	 * @return string  The punycode email.
	 *
	 * @see     JStringPunycode::emailToPunycode()
	 * @since   3.4
	 */
	public function emailToPunycode($email)
	{
		return JStringPunycode::emailToPunycode($email);
	}

	/**
	 * Helper wrapper method for emailToUTF8
	 *
	 * @param   string  $email  The punycode email to transform.
	 *
	 * @return string  The punycode email.
	 *
	 * @see     JStringPunycode::emailToUTF8()
	 * @since   3.4
	 */
	public function emailToUTF8($email)
	{
		return JStringPunycode::emailToUTF8($email);
	}
}
joomla/string/wrapper/normalise.php000064400000005572152177723700013541 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  String
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Wrapper class for JStringNormalise
 *
 * @package     Joomla.Platform
 * @subpackage  String
 * @since       3.4
 * @deprecated  4.0 Will be removed without replacement
 */
class JStringWrapperNormalise
{
	/**
	 * Helper wrapper method for fromCamelCase
	 *
	 * @param   string   $input    The string input (ASCII only).
	 * @param   boolean  $grouped  Optionally allows splitting on groups of uppercase characters.
	 *
	 * @return mixed  The space separated string or an array of substrings if grouped is true.
	 *
	 * @see     JUserHelper::fromCamelCase()
	 * @since   3.4
	 */
	public function fromCamelCase($input, $grouped = false)
	{
		return JStringNormalise::fromCamelCase($input, $grouped);
	}

	/**
	 * Helper wrapper method for toCamelCase
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The camel case string.
	 *
	 * @see     JUserHelper::toCamelCase()
	 * @since   3.4
	 */
	public function toCamelCase($input)
	{
		return JStringNormalise::toCamelCase($input);
	}

	/**
	 * Helper wrapper method for toDashSeparated
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The dash separated string.
	 *
	 * @see     JUserHelper::toDashSeparated()
	 * @since   3.4
	 */
	public function toDashSeparated($input)
	{
		return JStringNormalise::toDashSeparated($input);
	}

	/**
	 * Helper wrapper method for toSpaceSeparated
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The space separated string.
	 *
	 * @see     JUserHelper::toSpaceSeparated()
	 * @since   3.4
	 */
	public function toSpaceSeparated($input)
	{
		return JStringNormalise::toSpaceSeparated($input);
	}

	/**
	 * Helper wrapper method for toUnderscoreSeparated
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The underscore separated string.
	 *
	 * @see     JUserHelper::toUnderscoreSeparated()
	 * @since   3.4
	 */
	public function toUnderscoreSeparated($input)
	{
		return JStringNormalise::toUnderscoreSeparated($input);
	}

	/**
	 * Helper wrapper method for toVariable
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The variable string.
	 *
	 * @see     JUserHelper::toVariable()
	 * @since   3.4
	 */
	public function toVariable($input)
	{
		return JStringNormalise::toVariable($input);
	}

	/**
	 * Helper wrapper method for toKey
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return string  The key string.
	 *
	 * @see     JUserHelper::toKey()
	 * @since   3.4
	 */
	public function toKey($input)
	{
		return JStringNormalise::toKey($input);
	}
}
joomla/application/web/router/base.php000064400000011067152177723700014070 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Application
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Basic Web application router class for the Joomla Platform.
 *
 * @since       3.0
 * @deprecated  4.0  Use the `joomla/router` package via Composer instead
 */
class JApplicationWebRouterBase extends JApplicationWebRouter
{
	/**
	 * @var    array  An array of rules, each rule being an associative array('regex'=> $regex, 'vars' => $vars, 'controller' => $controller)
	 *                for routing the request.
	 * @since  3.0
	 */
	protected $maps = array();

	/**
	 * Add a route map to the router.  If the pattern already exists it will be overwritten.
	 *
	 * @param   string  $pattern     The route pattern to use for matching.
	 * @param   string  $controller  The controller name to map to the given pattern.
	 *
	 * @return  JApplicationWebRouter  This object for method chaining.
	 *
	 * @since   3.0
	 */
	public function addMap($pattern, $controller)
	{
		// Sanitize and explode the pattern.
		$pattern = explode('/', trim(parse_url((string) $pattern, PHP_URL_PATH), ' /'));

		// Prepare the route variables
		$vars = array();

		// Initialize regular expression
		$regex = array();

		// Loop on each segment
		foreach ($pattern as $segment)
		{
			// Match a splat with no variable.
			if ($segment == '*')
			{
				$regex[] = '.*';
			}
			// Match a splat and capture the data to a named variable.
			elseif ($segment[0] == '*')
			{
				$vars[] = substr($segment, 1);
				$regex[] = '(.*)';
			}
			// Match an escaped splat segment.
			elseif ($segment[0] == '\\' && $segment[1] == '*')
			{
				$regex[] = '\*' . preg_quote(substr($segment, 2));
			}
			// Match an unnamed variable without capture.
			elseif ($segment == ':')
			{
				$regex[] = '[^/]*';
			}
			// Match a named variable and capture the data.
			elseif ($segment[0] == ':')
			{
				$vars[] = substr($segment, 1);
				$regex[] = '([^/]*)';
			}
			// Match a segment with an escaped variable character prefix.
			elseif ($segment[0] == '\\' && $segment[1] == ':')
			{
				$regex[] = preg_quote(substr($segment, 1));
			}
			// Match the standard segment.
			else
			{
				$regex[] = preg_quote($segment);
			}
		}

		$this->maps[] = array(
			'regex' => chr(1) . '^' . implode('/', $regex) . '$' . chr(1),
			'vars' => $vars,
			'controller' => (string) $controller,
		);

		return $this;
	}

	/**
	 * Add a route map to the router.  If the pattern already exists it will be overwritten.
	 *
	 * @param   array  $maps  A list of route maps to add to the router as $pattern => $controller.
	 *
	 * @return  JApplicationWebRouter  This object for method chaining.
	 *
	 * @since   3.0
	 */
	public function addMaps($maps)
	{
		foreach ($maps as $pattern => $controller)
		{
			$this->addMap($pattern, $controller);
		}

		return $this;
	}

	/**
	 * Parse the given route and return the name of a controller mapped to the given route.
	 *
	 * @param   string  $route  The route string for which to find and execute a controller.
	 *
	 * @return  string  The controller name for the given route excluding prefix.
	 *
	 * @since   3.0
	 * @throws  InvalidArgumentException
	 */
	protected function parseRoute($route)
	{
		$controller = false;

		// Trim the query string off.
		$route = preg_replace('/([^?]*).*/u', '\1', $route);

		// Sanitize and explode the route.
		$route = trim(parse_url($route, PHP_URL_PATH), ' /');

		// If the route is empty then simply return the default route.  No parsing necessary.
		if ($route == '')
		{
			return $this->default;
		}

		// Iterate through all of the known route maps looking for a match.
		foreach ($this->maps as $rule)
		{
			if (preg_match($rule['regex'], $route, $matches))
			{
				// If we have gotten this far then we have a positive match.
				$controller = $rule['controller'];

				// Time to set the input variables.
				// We are only going to set them if they don't already exist to avoid overwriting things.
				foreach ($rule['vars'] as $i => $var)
				{
					$this->input->def($var, $matches[$i + 1]);

					// Don't forget to do an explicit set on the GET superglobal.
					$this->input->get->def($var, $matches[$i + 1]);
				}

				$this->input->def('_rawRoute', $route);

				break;
			}
		}

		// We were unable to find a route match for the request.  Panic.
		if (!$controller)
		{
			throw new InvalidArgumentException(sprintf('Unable to handle request for route `%s`.', $route), 404);
		}

		return $controller;
	}
}
joomla/application/web/router/rest.php000064400000007016152177723700014132 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Application
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * RESTful Web application router class for the Joomla Platform.
 *
 * @since       3.0
 * @deprecated  4.0  Use the `joomla/router` package via Composer instead
 */
class JApplicationWebRouterRest extends JApplicationWebRouterBase
{
	/**
	 * @var    boolean  A boolean allowing to pass _method as parameter in POST requests
	 * @since  3.0
	 */
	protected $methodInPostRequest = false;

	/**
	 * @var    array  An array of HTTP Method => controller suffix pairs for routing the request.
	 * @since  3.0
	 */
	protected $suffixMap = array(
		'GET' => 'Get',
		'POST' => 'Create',
		'PUT' => 'Update',
		'PATCH' => 'Update',
		'DELETE' => 'Delete',
		'HEAD' => 'Head',
		'OPTIONS' => 'Options',
	);

	/**
	 * Find and execute the appropriate controller based on a given route.
	 *
	 * @param   string  $route  The route string for which to find and execute a controller.
	 *
	 * @return  void
	 *
	 * @since   3.0
	 * @throws  InvalidArgumentException
	 * @throws  RuntimeException
	 */
	public function execute($route)
	{
		// Get the controller name based on the route patterns and requested route.
		$name = $this->parseRoute($route);

		// Append the HTTP method based suffix.
		$name .= $this->fetchControllerSuffix();

		// Get the controller object by name.
		$controller = $this->fetchController($name);

		// Execute the controller.
		$controller->execute();
	}

	/**
	 * Set a controller class suffix for a given HTTP method.
	 *
	 * @param   string  $method  The HTTP method for which to set the class suffix.
	 * @param   string  $suffix  The class suffix to use when fetching the controller name for a given request.
	 *
	 * @return  JApplicationWebRouter  This object for method chaining.
	 *
	 * @since   3.0
	 */
	public function setHttpMethodSuffix($method, $suffix)
	{
		$this->suffixMap[strtoupper((string) $method)] = (string) $suffix;

		return $this;
	}

	/**
	 * Set to allow or not method in POST request
	 *
	 * @param   boolean  $value  A boolean to allow or not method in POST request
	 *
	 * @return  void
	 *
	 * @since   3.0
	 */
	public function setMethodInPostRequest($value)
	{
		$this->methodInPostRequest = $value;
	}

	/**
	 * Get the property to allow or not method in POST request
	 *
	 * @return  boolean
	 *
	 * @since   3.0
	 */
	public function isMethodInPostRequest()
	{
		return $this->methodInPostRequest;
	}

	/**
	 * Get the controller class suffix string.
	 *
	 * @return  string
	 *
	 * @since   3.0
	 * @throws  RuntimeException
	 */
	protected function fetchControllerSuffix()
	{
		// Validate that we have a map to handle the given HTTP method.
		if (!isset($this->suffixMap[$this->input->getMethod()]))
		{
			throw new RuntimeException(sprintf('Unable to support the HTTP method `%s`.', $this->input->getMethod()), 404);
		}

		// Check if request method is POST
		if ($this->methodInPostRequest == true && strcmp(strtoupper($this->input->server->getMethod()), 'POST') === 0)
		{
			// Get the method from input
			$postMethod = $this->input->get->getWord('_method');

			// Validate that we have a map to handle the given HTTP method from input
			if ($postMethod && isset($this->suffixMap[strtoupper($postMethod)]))
			{
				return ucfirst($this->suffixMap[strtoupper($postMethod)]);
			}
		}

		return ucfirst($this->suffixMap[$this->input->getMethod()]);
	}
}
joomla/application/web/router.php000064400000007564152177723700013165 0ustar00<?php
/**
 * @package     Joomla.Platform
 * @subpackage  Application
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 */

defined('JPATH_PLATFORM') or die;

/**
 * Class to define an abstract Web application router.
 *
 * @since       3.0
 * @deprecated  4.0  Use the `joomla/router` package via Composer instead
 */
abstract class JApplicationWebRouter
{
	/**
	 * @var    JApplicationWeb  The web application on whose behalf we are routing the request.
	 * @since  3.0
	 */
	protected $app;

	/**
	 * @var    string  The default page controller name for an empty route.
	 * @since  3.0
	 */
	protected $default;

	/**
	 * @var    string  Controller class name prefix for creating controller objects by name.
	 * @since  3.0
	 */
	protected $controllerPrefix;

	/**
	 * @var    JInput  An input object from which to derive the route.
	 * @since  3.0
	 */
	protected $input;

	/**
	 * Constructor.
	 *
	 * @param   JApplicationWeb  $app    The web application on whose behalf we are routing the request.
	 * @param   JInput           $input  An optional input object from which to derive the route.  If none
	 *                                   is given than the input from the application object will be used.
	 *
	 * @since   3.0
	 */
	public function __construct(JApplicationWeb $app, JInput $input = null)
	{
		$this->app   = $app;
		$this->input = ($input === null) ? $this->app->input : $input;
	}

	/**
	 * Find and execute the appropriate controller based on a given route.
	 *
	 * @param   string  $route  The route string for which to find and execute a controller.
	 *
	 * @return  mixed   The return value of the controller executed
	 *
	 * @since   3.0
	 * @throws  InvalidArgumentException
	 * @throws  RuntimeException
	 */
	public function execute($route)
	{
		// Get the controller name based on the route patterns and requested route.
		$name = $this->parseRoute($route);

		// Get the controller object by name.
		$controller = $this->fetchController($name);

		// Execute the controller.
		return $controller->execute();
	}

	/**
	 * Set the controller name prefix.
	 *
	 * @param   string  $prefix  Controller class name prefix for creating controller objects by name.
	 *
	 * @return  JApplicationWebRouter  This object for method chaining.
	 *
	 * @since   3.0
	 */
	public function setControllerPrefix($prefix)
	{
		$this->controllerPrefix	= (string) $prefix;

		return $this;
	}

	/**
	 * Set the default controller name.
	 *
	 * @param   string  $name  The default page controller name for an empty route.
	 *
	 * @return  JApplicationWebRouter  This object for method chaining.
	 *
	 * @since   3.0
	 */
	public function setDefaultController($name)
	{
		$this->default = (string) $name;

		return $this;
	}

	/**
	 * Parse the given route and return the name of a controller mapped to the given route.
	 *
	 * @param   string  $route  The route string for which to find and execute a controller.
	 *
	 * @return  string  The controller name for the given route excluding prefix.
	 *
	 * @since   3.0
	 * @throws  InvalidArgumentException
	 */
	abstract protected function parseRoute($route);

	/**
	 * Get a JController object for a given name.
	 *
	 * @param   string  $name  The controller name (excluding prefix) for which to fetch and instance.
	 *
	 * @return  JController
	 *
	 * @since   3.0
	 * @throws  RuntimeException
	 */
	protected function fetchController($name)
	{
		// Derive the controller class name.
		$class = $this->controllerPrefix . ucfirst($name);

		// If the controller class does not exist panic.
		if (!class_exists($class) || !is_subclass_of($class, 'JController'))
		{
			throw new RuntimeException(sprintf('Unable to locate controller `%s`.', $class), 404);
		}

		// Instantiate the controller.
		$controller = new $class($this->input, $this->app);

		return $controller;
	}
}
phpass/PasswordHash.php000064400000014170152177723700011177 0ustar00<?php
#
# Portable PHP password hashing framework.
#
# Version 0.5 / genuine.
#
# Written by Solar Designer <solar at openwall.com> in 2004-2006 and placed in
# the public domain.  Revised in subsequent years, still public domain.
#
# There's absolutely no warranty.
#
# The homepage URL for this framework is:
#
#	http://www.openwall.com/phpass/
#
# Please be sure to update the Version line if you edit this file in any way.
# It is suggested that you leave the main version number intact, but indicate
# your project name (after the slash) and add your own revision information.
#
# Please do not change the "private" password hashing method implemented in
# here, thereby making your hashes incompatible.  However, if you must, please
# change the hash type identifier (the "$P$") to something different.
#
# Obviously, since this code is in the public domain, the above are not
# requirements (there can be none), but merely suggestions.
#
class PasswordHash {
	var $itoa64;
	var $iteration_count_log2;
	var $portable_hashes;
	var $random_state;

	function __construct($iteration_count_log2, $portable_hashes)
	{
		$this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';

		if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31)
			$iteration_count_log2 = 8;
		$this->iteration_count_log2 = $iteration_count_log2;

		$this->portable_hashes = $portable_hashes;

		$this->random_state = microtime();
		if (function_exists('getmypid'))
			$this->random_state .= getmypid();
	}

	function PasswordHash($iteration_count_log2, $portable_hashes)
	{
		self::__construct($iteration_count_log2, $portable_hashes);
	}

	function get_random_bytes($count)
	{
		$output = '';
		if (@is_readable('/dev/urandom') &&
		    ($fh = @fopen('/dev/urandom', 'rb'))) {
			$output = fread($fh, $count);
			fclose($fh);
		}

		if (strlen($output) < $count) {
			$output = '';
			for ($i = 0; $i < $count; $i += 16) {
				$this->random_state =
				    md5(microtime() . $this->random_state);
				$output .= md5($this->random_state, TRUE);
			}
			$output = substr($output, 0, $count);
		}

		return $output;
	}

	function encode64($input, $count)
	{
		$output = '';
		$i = 0;
		do {
			$value = ord($input[$i++]);
			$output .= $this->itoa64[$value & 0x3f];
			if ($i < $count)
				$value |= ord($input[$i]) << 8;
			$output .= $this->itoa64[($value >> 6) & 0x3f];
			if ($i++ >= $count)
				break;
			if ($i < $count)
				$value |= ord($input[$i]) << 16;
			$output .= $this->itoa64[($value >> 12) & 0x3f];
			if ($i++ >= $count)
				break;
			$output .= $this->itoa64[($value >> 18) & 0x3f];
		} while ($i < $count);

		return $output;
	}

	function gensalt_private($input)
	{
		$output = '$P$';
		$output .= $this->itoa64[min($this->iteration_count_log2 +
			((PHP_VERSION >= '5') ? 5 : 3), 30)];
		$output .= $this->encode64($input, 6);

		return $output;
	}

	function crypt_private($password, $setting)
	{
		$output = '*0';
		if (substr($setting, 0, 2) === $output)
			$output = '*1';

		$id = substr($setting, 0, 3);
		# We use "$P$", phpBB3 uses "$H$" for the same thing
		if ($id !== '$P$' && $id !== '$H$')
			return $output;

		$count_log2 = strpos($this->itoa64, $setting[3]);
		if ($count_log2 < 7 || $count_log2 > 30)
			return $output;

		$count = 1 << $count_log2;

		$salt = substr($setting, 4, 8);
		if (strlen($salt) !== 8)
			return $output;

		# We were kind of forced to use MD5 here since it's the only
		# cryptographic primitive that was available in all versions
		# of PHP in use.  To implement our own low-level crypto in PHP
		# would have resulted in much worse performance and
		# consequently in lower iteration counts and hashes that are
		# quicker to crack (by non-PHP code).
		$hash = md5($salt . $password, TRUE);
		do {
			$hash = md5($hash . $password, TRUE);
		} while (--$count);

		$output = substr($setting, 0, 12);
		$output .= $this->encode64($hash, 16);

		return $output;
	}

	function gensalt_blowfish($input)
	{
		# This one needs to use a different order of characters and a
		# different encoding scheme from the one in encode64() above.
		# We care because the last character in our encoded string will
		# only represent 2 bits.  While two known implementations of
		# bcrypt will happily accept and correct a salt string which
		# has the 4 unused bits set to non-zero, we do not want to take
		# chances and we also do not want to waste an additional byte
		# of entropy.
		$itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

		$output = '$2a$';
		$output .= chr(ord('0') + $this->iteration_count_log2 / 10);
		$output .= chr(ord('0') + $this->iteration_count_log2 % 10);
		$output .= '$';

		$i = 0;
		do {
			$c1 = ord($input[$i++]);
			$output .= $itoa64[$c1 >> 2];
			$c1 = ($c1 & 0x03) << 4;
			if ($i >= 16) {
				$output .= $itoa64[$c1];
				break;
			}

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 4;
			$output .= $itoa64[$c1];
			$c1 = ($c2 & 0x0f) << 2;

			$c2 = ord($input[$i++]);
			$c1 |= $c2 >> 6;
			$output .= $itoa64[$c1];
			$output .= $itoa64[$c2 & 0x3f];
		} while (1);

		return $output;
	}

	function HashPassword($password)
	{
		$random = '';

		if (CRYPT_BLOWFISH === 1 && !$this->portable_hashes) {
			$random = $this->get_random_bytes(16);
			$hash =
			    crypt($password, $this->gensalt_blowfish($random));
			if (strlen($hash) === 60)
				return $hash;
		}

		if (strlen($random) < 6)
			$random = $this->get_random_bytes(6);
		$hash =
		    $this->crypt_private($password,
		    $this->gensalt_private($random));
		if (strlen($hash) === 34)
			return $hash;

		# Returning '*' on error is safe here, but would _not_ be safe
		# in a crypt(3)-like function used _both_ for generating new
		# hashes and for validating passwords against existing hashes.
		return '*';
	}

	function CheckPassword($password, $stored_hash)
	{
		$hash = $this->crypt_private($password, $stored_hash);
		if ($hash[0] === '*')
			$hash = crypt($password, $stored_hash);

		# This is not constant-time.  In order to keep the code simple,
		# for timing safety we currently rely on the salts being
		# unpredictable, which they are at least in the non-fallback
		# cases (that is, when we use /dev/urandom and bcrypt).
		return $hash === $stored_hash;
	}
}

?>
classmap.php000064400000122026152177723700007076 0ustar00<?php
/**
 * @package    Joomla.Libraries
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE.txt
 */

// No direct access.
defined('_JEXEC') or die;

JLoader::registerAlias('JRegistry',                         '\\Joomla\\Registry\\Registry', '5.0');
JLoader::registerAlias('JRegistryFormat',                   '\\Joomla\\Registry\\AbstractRegistryFormat', '4.0');
JLoader::registerAlias('JRegistryFormatIni',                '\\Joomla\\Registry\\Format\\Ini', '5.0');
JLoader::registerAlias('JRegistryFormatJson',               '\\Joomla\\Registry\\Format\\Json', '5.0');
JLoader::registerAlias('JRegistryFormatPhp',                '\\Joomla\\Registry\\Format\\Php', '5.0');
JLoader::registerAlias('JRegistryFormatXml',                '\\Joomla\\Registry\\Format\\Xml', '5.0');
JLoader::registerAlias('JStringInflector',                  '\\Joomla\\String\\Inflector', '5.0');
JLoader::registerAlias('JStringNormalise',                  '\\Joomla\\String\\Normalise', '5.0');
JLoader::registerAlias('JData',                             '\\Joomla\\Data\\DataObject', '5.0');
JLoader::registerAlias('JDataSet',                          '\\Joomla\\Data\\DataSet', '5.0');
JLoader::registerAlias('JDataDumpable',                     '\\Joomla\\Data\\DumpableInterface', '5.0');

JLoader::registerAlias('JApplicationAdministrator',         '\\Joomla\\CMS\\Application\\AdministratorApplication', '5.0');
JLoader::registerAlias('JApplicationHelper',                '\\Joomla\\CMS\\Application\\ApplicationHelper', '5.0');
JLoader::registerAlias('JApplicationBase',                  '\\Joomla\\CMS\\Application\\BaseApplication', '5.0');
JLoader::registerAlias('JApplicationCli',                   '\\Joomla\\CMS\\Application\\CliApplication', '5.0');
JLoader::registerAlias('JApplicationCms',                   '\\Joomla\\CMS\\Application\\CMSApplication', '5.0');
JLoader::registerAlias('JApplicationDaemon',                '\\Joomla\\CMS\\Application\\DaemonApplication', '5.0');
JLoader::registerAlias('JApplicationSite',                  '\\Joomla\\CMS\\Application\\SiteApplication', '5.0');
JLoader::registerAlias('JApplicationWeb',                   '\\Joomla\\CMS\\Application\\WebApplication', '5.0');
JLoader::registerAlias('JApplicationWebClient',             '\\Joomla\\Application\\Web\\WebClient', '5.0');
JLoader::registerAlias('JDaemon',                           '\\Joomla\\CMS\\Application\\DaemonApplication', '5.0');
JLoader::registerAlias('JCli',                              '\\Joomla\\CMS\\Application\\CliApplication', '5.0');
JLoader::registerAlias('JWeb',                              '\\Joomla\\CMS\\Application\\WebApplication', '4.0');
JLoader::registerAlias('JWebClient',                        '\\Joomla\\Application\\Web\\WebClient', '4.0');

JLoader::registerAlias('JModelAdmin',                       '\\Joomla\\CMS\\MVC\\Model\\AdminModel', '5.0');
JLoader::registerAlias('JModelForm',                        '\\Joomla\\CMS\\MVC\\Model\\FormModel', '5.0');
JLoader::registerAlias('JModelItem',                        '\\Joomla\\CMS\\MVC\\Model\\ItemModel', '5.0');
JLoader::registerAlias('JModelList',                        '\\Joomla\\CMS\\MVC\\Model\\ListModel', '5.0');
JLoader::registerAlias('JModelLegacy',                      '\\Joomla\\CMS\\MVC\\Model\\BaseDatabaseModel', '5.0');
JLoader::registerAlias('JViewCategories',                   '\\Joomla\\CMS\\MVC\\View\\CategoriesView', '5.0');
JLoader::registerAlias('JViewCategory',                     '\\Joomla\\CMS\\MVC\\View\\CategoryView', '5.0');
JLoader::registerAlias('JViewCategoryfeed',                 '\\Joomla\\CMS\\MVC\\View\\CategoryFeedView', '5.0');
JLoader::registerAlias('JViewLegacy',                       '\\Joomla\\CMS\\MVC\\View\\HtmlView', '5.0');
JLoader::registerAlias('JControllerAdmin',                  '\\Joomla\\CMS\\MVC\\Controller\\AdminController', '5.0');
JLoader::registerAlias('JControllerLegacy',                 '\\Joomla\\CMS\\MVC\\Controller\\BaseController', '5.0');
JLoader::registerAlias('JControllerForm',                   '\\Joomla\\CMS\\MVC\\Controller\\FormController', '5.0');
JLoader::registerAlias('JTableInterface',                   '\\Joomla\\CMS\\Table\\TableInterface', '5.0');
JLoader::registerAlias('JTable',                            '\\Joomla\\CMS\\Table\\Table', '5.0');
JLoader::registerAlias('JTableNested',                      '\\Joomla\\CMS\\Table\\Nested', '5.0');
JLoader::registerAlias('JTableAsset',                       '\\Joomla\\CMS\\Table\\Asset', '5.0');
JLoader::registerAlias('JTableExtension',                   '\\Joomla\\CMS\\Table\\Extension', '5.0');
JLoader::registerAlias('JTableLanguage',                    '\\Joomla\\CMS\\Table\\Language', '5.0');
JLoader::registerAlias('JTableUpdate',                      '\\Joomla\\CMS\\Table\\Update', '5.0');
JLoader::registerAlias('JTableUpdatesite',                  '\\Joomla\\CMS\\Table\\UpdateSite', '5.0');
JLoader::registerAlias('JTableUser',                        '\\Joomla\\CMS\\Table\\User', '5.0');
JLoader::registerAlias('JTableUsergroup',                   '\\Joomla\\CMS\\Table\\Usergroup', '5.0');
JLoader::registerAlias('JTableViewlevel',                   '\\Joomla\\CMS\\Table\\ViewLevel', '5.0');
JLoader::registerAlias('JTableContenthistory',              '\\Joomla\\CMS\\Table\\ContentHistory', '5.0');
JLoader::registerAlias('JTableContenttype',                 '\\Joomla\\CMS\\Table\\ContentType', '5.0');
JLoader::registerAlias('JTableCorecontent',                 '\\Joomla\\CMS\\Table\\CoreContent', '5.0');
JLoader::registerAlias('JTableUcm',                         '\\Joomla\\CMS\\Table\\Ucm', '5.0');
JLoader::registerAlias('JTableCategory',                    '\\Joomla\\CMS\\Table\\Category', '5.0');
JLoader::registerAlias('JTableContent',                     '\\Joomla\\CMS\\Table\\Content', '5.0');
JLoader::registerAlias('JTableMenu',                        '\\Joomla\\CMS\\Table\\Menu', '5.0');
JLoader::registerAlias('JTableMenuType',                    '\\Joomla\\CMS\\Table\\MenuType', '5.0');
JLoader::registerAlias('JTableModule',                      '\\Joomla\\CMS\\Table\\Module', '5.0');
JLoader::registerAlias('JTableObserver',                    '\\Joomla\\CMS\\Table\\Observer\\AbstractObserver', '5.0');
JLoader::registerAlias('JTableObserverContenthistory',      '\\Joomla\\CMS\\Table\\Observer\\ContentHistory', '5.0');
JLoader::registerAlias('JTableObserverTags',                '\\Joomla\\CMS\\Table\\Observer\\Tags', '5.0');

JLoader::registerAlias('JAccess',                           '\\Joomla\\CMS\\Access\\Access', '5.0');
JLoader::registerAlias('JAccessRule',                       '\\Joomla\\CMS\\Access\\Rule', '5.0');
JLoader::registerAlias('JAccessRules',                      '\\Joomla\\CMS\\Access\\Rules', '5.0');
JLoader::registerAlias('JAccessWrapperAccess',              '\\Joomla\\CMS\\Access\\Wrapper\\Access', '4.0');
JLoader::registerAlias('JAccessExceptionNotallowed',        '\\Joomla\\CMS\\Access\\Exception\\NotAllowed', '5.0');
JLoader::registerAlias('JRule',                             '\\Joomla\\CMS\\Access\\Rule', '5.0');
JLoader::registerAlias('JRules',                            '\\Joomla\\CMS\\Access\\Rules', '5.0');

JLoader::registerAlias('JHelp',                             '\\Joomla\\CMS\\Help\\Help', '5.0');
JLoader::registerAlias('JCaptcha',                          '\\Joomla\\CMS\\Captcha\\Captcha', '5.0');

JLoader::registerAlias('JLanguageAssociations',             '\\Joomla\\CMS\\Language\\Associations', '5.0');
JLoader::registerAlias('JLanguage',                         '\\Joomla\\CMS\\Language\\Language', '5.0');
JLoader::registerAlias('JLanguageHelper',                   '\\Joomla\\CMS\\Language\\LanguageHelper', '5.0');
JLoader::registerAlias('JLanguageStemmer',                  '\\Joomla\\CMS\\Language\\LanguageStemmer', '5.0');
JLoader::registerAlias('JLanguageMultilang',                '\\Joomla\\CMS\\Language\\Multilanguage', '5.0');
JLoader::registerAlias('JText',                             '\\Joomla\\CMS\\Language\\Text', '5.0');
JLoader::registerAlias('JLanguageTransliterate',            '\\Joomla\\CMS\\Language\\Transliterate', '5.0');
JLoader::registerAlias('JLanguageStemmerPorteren',          '\\Joomla\\CMS\\Language\\Stemmer\\Porteren', '5.0');
JLoader::registerAlias('JLanguageWrapperText',              '\\Joomla\\CMS\\Language\\Wrapper\\JTextWrapper', '4.0');
JLoader::registerAlias('JLanguageWrapperHelper',            '\\Joomla\\CMS\\Language\\Wrapper\\LanguageHelperWrapper', '4.0');
JLoader::registerAlias('JLanguageWrapperTransliterate',     '\\Joomla\\CMS\\Language\\Wrapper\\TransliterateWrapper', '4.0');

JLoader::registerAlias('JComponentHelper',                  '\\Joomla\\CMS\\Component\\ComponentHelper', '5.0');
JLoader::registerAlias('JComponentRecord',                  '\\Joomla\\CMS\\Component\\ComponentRecord', '5.0');
JLoader::registerAlias('JComponentExceptionMissing',        '\\Joomla\\CMS\\Component\\Exception\\MissingComponentException', '5.0');
JLoader::registerAlias('JComponentRouterBase',              '\\Joomla\\CMS\\Component\\Router\\RouterBase', '5.0');
JLoader::registerAlias('JComponentRouterInterface',         '\\Joomla\\CMS\\Component\\Router\\RouterInterface', '5.0');
JLoader::registerAlias('JComponentRouterLegacy',            '\\Joomla\\CMS\\Component\\Router\\RouterLegacy', '5.0');
JLoader::registerAlias('JComponentRouterView',              '\\Joomla\\CMS\\Component\\Router\\RouterView', '5.0');
JLoader::registerAlias('JComponentRouterViewconfiguration', '\\Joomla\\CMS\\Component\\Router\\RouterViewConfiguration', '5.0');
JLoader::registerAlias('JComponentRouterRulesMenu',         '\\Joomla\\CMS\\Component\\Router\\Rules\\MenuRules', '5.0');
JLoader::registerAlias('JComponentRouterRulesNomenu',       '\\Joomla\\CMS\\Component\\Router\\Rules\\NomenuRules', '5.0');
JLoader::registerAlias('JComponentRouterRulesInterface',    '\\Joomla\\CMS\\Component\\Router\\Rules\\RulesInterface', '5.0');
JLoader::registerAlias('JComponentRouterRulesStandard',     '\\Joomla\\CMS\\Component\\Router\\Rules\\StandardRules', '5.0');

JLoader::registerAlias('JEditor',                           '\\Joomla\\CMS\\Editor\\Editor', '5.0');

JLoader::registerAlias('JErrorPage',                        '\\Joomla\\CMS\\Exception\\ExceptionHandler', '5.0');

JLoader::registerAlias('JAuthenticationHelper',             '\\Joomla\\CMS\\Helper\\AuthenticationHelper', '5.0');
JLoader::registerAlias('JHelper',                           '\\Joomla\\CMS\\Helper\\CMSHelper', '5.0');
JLoader::registerAlias('JHelperContent',                    '\\Joomla\\CMS\\Helper\\ContentHelper', '5.0');
JLoader::registerAlias('JHelperContenthistory',             '\\Joomla\\CMS\\Helper\\ContentHistoryHelper', '5.0');
JLoader::registerAlias('JLibraryHelper',                    '\\Joomla\\CMS\\Helper\\LibraryHelper', '5.0');
JLoader::registerAlias('JHelperMedia',                      '\\Joomla\\CMS\\Helper\\MediaHelper', '5.0');
JLoader::registerAlias('JModuleHelper',                     '\\Joomla\\CMS\\Helper\\ModuleHelper', '5.0');
JLoader::registerAlias('JHelperRoute',                      '\\Joomla\\CMS\\Helper\\RouteHelper', '5.0');
JLoader::registerAlias('JSearchHelper',                     '\\Joomla\\CMS\\Helper\\SearchHelper', '5.0');
JLoader::registerAlias('JHelperTags',                       '\\Joomla\\CMS\\Helper\\TagsHelper', '5.0');
JLoader::registerAlias('JHelperUsergroups',                 '\\Joomla\\CMS\\Helper\\UserGroupsHelper', '5.0');

JLoader::registerAlias('JLayoutBase',                       '\\Joomla\\CMS\\Layout\\BaseLayout', '5.0');
JLoader::registerAlias('JLayoutFile',                       '\\Joomla\\CMS\\Layout\\FileLayout', '5.0');
JLoader::registerAlias('JLayoutHelper',                     '\\Joomla\\CMS\\Layout\\LayoutHelper', '5.0');
JLoader::registerAlias('JLayout',                           '\\Joomla\\CMS\\Layout\\LayoutInterface', '5.0');

JLoader::registerAlias('JResponseJson',                     '\\Joomla\\CMS\\Response\\JsonResponse', '5.0');

JLoader::registerAlias('JPlugin',                           '\\Joomla\\CMS\\Plugin\\CMSPlugin', '5.0');
JLoader::registerAlias('JPluginHelper',                     '\\Joomla\\CMS\\Plugin\\PluginHelper', '5.0');

JLoader::registerAlias('JMenu',                             '\\Joomla\\CMS\\Menu\\AbstractMenu', '5.0');
JLoader::registerAlias('JMenuAdministrator',                '\\Joomla\\CMS\\Menu\\AdministratorMenu', '5.0');
JLoader::registerAlias('JMenuItem',                         '\\Joomla\\CMS\\Menu\\MenuItem', '5.0');
JLoader::registerAlias('JMenuSite',                         '\\Joomla\\CMS\\Menu\\SiteMenu', '5.0');

JLoader::registerAlias('JPagination',                       '\\Joomla\\CMS\\Pagination\\Pagination', '5.0');
JLoader::registerAlias('JPaginationObject',                 '\\Joomla\\CMS\\Pagination\\PaginationObject', '5.0');

JLoader::registerAlias('JPathway',                          '\\Joomla\\CMS\\Pathway\\Pathway', '5.0');
JLoader::registerAlias('JPathwaySite',                      '\\Joomla\\CMS\\Pathway\\SitePathway', '5.0');

JLoader::registerAlias('JSchemaChangeitem',                 '\\Joomla\\CMS\\Schema\\ChangeItem', '5.0');
JLoader::registerAlias('JSchemaChangeset',                  '\\Joomla\\CMS\\Schema\\ChangeSet', '5.0');
JLoader::registerAlias('JSchemaChangeitemMysql',            '\\Joomla\\CMS\\Schema\\ChangeItem\\MysqlChangeItem', '5.0');
JLoader::registerAlias('JSchemaChangeitemPostgresql',       '\\Joomla\\CMS\\Schema\\ChangeItem\\PostgresqlChangeItem', '5.0');
JLoader::registerAlias('JSchemaChangeitemSqlsrv',           '\\Joomla\\CMS\\Schema\\ChangeItem\\SqlsrvChangeItem', '5.0');

JLoader::registerAlias('JUcm',                              '\\Joomla\\CMS\\UCM\\UCM', '5.0');
JLoader::registerAlias('JUcmBase',                          '\\Joomla\\CMS\\UCM\\UCMBase', '5.0');
JLoader::registerAlias('JUcmContent',                       '\\Joomla\\CMS\\UCM\\UCMContent', '5.0');
JLoader::registerAlias('JUcmType',                          '\\Joomla\\CMS\\UCM\\UCMType', '5.0');

JLoader::registerAlias('JToolbar',                          '\\Joomla\\CMS\\Toolbar\\Toolbar', '5.0');
JLoader::registerAlias('JToolbarButton',                    '\\Joomla\\CMS\\Toolbar\\ToolbarButton', '5.0');
JLoader::registerAlias('JToolbarButtonConfirm',             '\\Joomla\\CMS\\Toolbar\\Button\\ConfirmButton', '5.0');
JLoader::registerAlias('JToolbarButtonCustom',              '\\Joomla\\CMS\\Toolbar\\Button\\CustomButton', '5.0');
JLoader::registerAlias('JToolbarButtonHelp',                '\\Joomla\\CMS\\Toolbar\\Button\\HelpButton', '5.0');
JLoader::registerAlias('JToolbarButtonLink',                '\\Joomla\\CMS\\Toolbar\\Button\\LinkButton', '5.0');
JLoader::registerAlias('JToolbarButtonPopup',               '\\Joomla\\CMS\\Toolbar\\Button\\PopupButton', '5.0');
JLoader::registerAlias('JToolbarButtonSeparator',           '\\Joomla\\CMS\\Toolbar\\Button\\SeparatorButton', '5.0');
JLoader::registerAlias('JToolbarButtonSlider',              '\\Joomla\\CMS\\Toolbar\\Button\\SliderButton', '5.0');
JLoader::registerAlias('JToolbarButtonStandard',            '\\Joomla\\CMS\\Toolbar\\Button\\StandardButton', '5.0');
JLoader::registerAlias('JToolbarHelper',                    '\\Joomla\\CMS\\Toolbar\\ToolbarHelper', '5.0');
JLoader::registerAlias('JButton',                           '\\Joomla\\CMS\\Toolbar\\ToolbarButton', '5.0');

JLoader::registerAlias('JVersion',                          '\\Joomla\\CMS\\Version', '5.0');

JLoader::registerAlias('JAuthentication',                   '\\Joomla\\CMS\\Authentication\\Authentication', '5.0');
JLoader::registerAlias('JAuthenticationResponse',           '\\Joomla\\CMS\\Authentication\\AuthenticationResponse', '5.0');

JLoader::registerAlias('JBrowser',                          '\\Joomla\\CMS\\Environment\\Browser', '5.0');

JLoader::registerAlias('JAssociationExtensionInterface',    '\\Joomla\\CMS\\Association\\AssociationExtensionInterface', '5.0');
JLoader::registerAlias('JAssociationExtensionHelper',       '\\Joomla\\CMS\\Association\\AssociationExtensionHelper', '5.0');

JLoader::registerAlias('JDocument',                         '\\Joomla\\CMS\\Document\\Document', '5.0');
JLoader::registerAlias('JDocumentError',                    '\\Joomla\\CMS\\Document\\ErrorDocument', '5.0');
JLoader::registerAlias('JDocumentFeed',                     '\\Joomla\\CMS\\Document\\FeedDocument', '5.0');
JLoader::registerAlias('JDocumentHtml',                     '\\Joomla\\CMS\\Document\\HtmlDocument', '5.0');
JLoader::registerAlias('JDocumentImage',                    '\\Joomla\\CMS\\Document\\ImageDocument', '5.0');
JLoader::registerAlias('JDocumentJson',                     '\\Joomla\\CMS\\Document\\JsonDocument', '5.0');
JLoader::registerAlias('JDocumentOpensearch',               '\\Joomla\\CMS\\Document\\OpensearchDocument', '5.0');
JLoader::registerAlias('JDocumentRaw',                      '\\Joomla\\CMS\\Document\\RawDocument', '5.0');
JLoader::registerAlias('JDocumentRenderer',                 '\\Joomla\\CMS\\Document\\DocumentRenderer', '5.0');
JLoader::registerAlias('JDocumentXml',                      '\\Joomla\\CMS\\Document\\XmlDocument', '5.0');
JLoader::registerAlias('JDocumentRendererFeedAtom',         '\\Joomla\\CMS\\Document\\Renderer\\Feed\\AtomRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererFeedRss',          '\\Joomla\\CMS\\Document\\Renderer\\Feed\\RssRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererHtmlComponent',    '\\Joomla\\CMS\\Document\\Renderer\\Html\\ComponentRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererHtmlHead',         '\\Joomla\\CMS\\Document\\Renderer\\Html\\HeadRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererHtmlMessage',      '\\Joomla\\CMS\\Document\\Renderer\\Html\\MessageRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererHtmlModule',       '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModuleRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererHtmlModules',      '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModulesRenderer', '5.0');
JLoader::registerAlias('JDocumentRendererAtom',             '\\Joomla\\CMS\\Document\\Renderer\\Feed\\AtomRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererRSS',              '\\Joomla\\CMS\\Document\\Renderer\\Feed\\RssRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererComponent',        '\\Joomla\\CMS\\Document\\Renderer\\Html\\ComponentRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererHead',             '\\Joomla\\CMS\\Document\\Renderer\\Html\\HeadRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererMessage',          '\\Joomla\\CMS\\Document\\Renderer\\Html\\MessageRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererModule',           '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModuleRenderer', '4.0');
JLoader::registerAlias('JDocumentRendererModules',          '\\Joomla\\CMS\\Document\\Renderer\\Html\\ModulesRenderer', '4.0');
JLoader::registerAlias('JFeedEnclosure',                    '\\Joomla\\CMS\\Document\\Feed\\FeedEnclosure', '5.0');
JLoader::registerAlias('JFeedImage',                        '\\Joomla\\CMS\\Document\\Feed\\FeedImage', '5.0');
JLoader::registerAlias('JFeedItem',                         '\\Joomla\\CMS\\Document\\Feed\\FeedItem', '5.0');
JLoader::registerAlias('JOpenSearchImage',                  '\\Joomla\\CMS\\Document\\Opensearch\\OpensearchImage', '5.0');
JLoader::registerAlias('JOpenSearchUrl',                    '\\Joomla\\CMS\\Document\\Opensearch\\OpensearchUrl', '5.0');

JLoader::registerAlias('JFilterInput',                      '\\Joomla\\CMS\\Filter\\InputFilter', '5.0');
JLoader::registerAlias('JFilterOutput',                     '\\Joomla\\CMS\\Filter\\OutputFilter', '5.0');
JLoader::registerAlias('JFilterWrapperOutput',              '\\Joomla\\CMS\\Filter\\Wrapper\\OutputFilterWrapper', '4.0');

JLoader::registerAlias('JHttp',                             '\\Joomla\\CMS\\Http\\Http', '5.0');
JLoader::registerAlias('JHttpFactory',                      '\\Joomla\\CMS\\Http\\HttpFactory', '5.0');
JLoader::registerAlias('JHttpResponse',                     '\\Joomla\\CMS\\Http\\Response', '5.0');
JLoader::registerAlias('JHttpTransport',                    '\\Joomla\\CMS\\Http\\TransportInterface', '5.0');
JLoader::registerAlias('JHttpTransportCurl',                '\\Joomla\\CMS\\Http\\Transport\\CurlTransport', '5.0');
JLoader::registerAlias('JHttpTransportSocket',              '\\Joomla\\CMS\\Http\\Transport\\SocketTransport', '5.0');
JLoader::registerAlias('JHttpTransportStream',              '\\Joomla\\CMS\\Http\\Transport\\StreamTransport', '5.0');
JLoader::registerAlias('JHttpWrapperFactory',               '\\Joomla\\CMS\\Http\\Wrapper\\FactoryWrapper', '4.0');

JLoader::registerAlias('JInstaller',                        '\\Joomla\\CMS\\Installer\\Installer', '5.0');
JLoader::registerAlias('JInstallerAdapter',                 '\\Joomla\\CMS\\Installer\\InstallerAdapter', '5.0');
JLoader::registerAlias('JInstallerExtension',               '\\Joomla\\CMS\\Installer\\InstallerExtension', '5.0');
JLoader::registerAlias('JExtension',                        '\\Joomla\\CMS\\Installer\\InstallerExtension', '5.0');
JLoader::registerAlias('JInstallerHelper',                  '\\Joomla\\CMS\\Installer\\InstallerHelper', '5.0');
JLoader::registerAlias('JInstallerScript',                  '\\Joomla\\CMS\\Installer\\InstallerScript', '5.0');
JLoader::registerAlias('JInstallerManifest',                '\\Joomla\\CMS\\Installer\\Manifest', '5.0');
JLoader::registerAlias('JInstallerAdapterComponent',        '\\Joomla\\CMS\\Installer\\Adapter\\ComponentAdapter', '5.0');
JLoader::registerAlias('JInstallerComponent',               '\\Joomla\\CMS\\Installer\\Adapter\\ComponentAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterFile',             '\\Joomla\\CMS\\Installer\\Adapter\\FileAdapter', '5.0');
JLoader::registerAlias('JInstallerFile',                    '\\Joomla\\CMS\\Installer\\Adapter\\FileAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterLanguage',         '\\Joomla\\CMS\\Installer\\Adapter\\LanguageAdapter', '5.0');
JLoader::registerAlias('JInstallerLanguage',                '\\Joomla\\CMS\\Installer\\Adapter\\LanguageAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterLibrary',          '\\Joomla\\CMS\\Installer\\Adapter\\LibraryAdapter', '5.0');
JLoader::registerAlias('JInstallerLibrary',                 '\\Joomla\\CMS\\Installer\\Adapter\\LibraryAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterModule',           '\\Joomla\\CMS\\Installer\\Adapter\\ModuleAdapter', '5.0');
JLoader::registerAlias('JInstallerModule',                  '\\Joomla\\CMS\\Installer\\Adapter\\ModuleAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterPackage',          '\\Joomla\\CMS\\Installer\\Adapter\\PackageAdapter', '5.0');
JLoader::registerAlias('JInstallerPackage',                 '\\Joomla\\CMS\\Installer\\Adapter\\PackageAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterPlugin',           '\\Joomla\\CMS\\Installer\\Adapter\\PluginAdapter', '5.0');
JLoader::registerAlias('JInstallerPlugin',                  '\\Joomla\\CMS\\Installer\\Adapter\\PluginAdapter', '5.0');
JLoader::registerAlias('JInstallerAdapterTemplate',         '\\Joomla\\CMS\\Installer\\Adapter\\TemplateAdapter', '5.0');
JLoader::registerAlias('JInstallerTemplate',                '\\Joomla\\CMS\\Installer\\Adapter\\TemplateAdapter', '5.0');
JLoader::registerAlias('JInstallerManifestLibrary',         '\\Joomla\\CMS\\Installer\\Manifest\\LibraryManifest', '5.0');
JLoader::registerAlias('JInstallerManifestPackage',         '\\Joomla\\CMS\\Installer\\Manifest\\PackageManifest', '5.0');

JLoader::registerAlias('JRouterAdministrator',              '\\Joomla\\CMS\\Router\\AdministratorRouter', '5.0');
JLoader::registerAlias('JRoute',                            '\\Joomla\\CMS\\Router\\Route', '5.0');
JLoader::registerAlias('JRouter',                           '\\Joomla\\CMS\\Router\\Router', '5.0');
JLoader::registerAlias('JRouterSite',                       '\\Joomla\\CMS\\Router\\SiteRouter', '5.0');

JLoader::registerAlias('JCategories',                       '\\Joomla\\CMS\\Categories\\Categories', '5.0');
JLoader::registerAlias('JCategoryNode',                     '\\Joomla\\CMS\\Categories\\CategoryNode', '5.0');

JLoader::registerAlias('JDate',                             '\\Joomla\\CMS\\Date\\Date', '5.0');

JLoader::registerAlias('JLog',                              '\\Joomla\\CMS\\Log\\Log', '5.0');
JLoader::registerAlias('JLogEntry',                         '\\Joomla\\CMS\\Log\\LogEntry', '5.0');
JLoader::registerAlias('JLogLogger',                        '\\Joomla\\CMS\\Log\\Logger', '5.0');
JLoader::registerAlias('JLogger',                           '\\Joomla\\CMS\\Log\\Logger', '5.0');
JLoader::registerAlias('JLogLoggerCallback',                '\\Joomla\\CMS\\Log\\Logger\\CallbackLogger', '5.0');
JLoader::registerAlias('JLogLoggerDatabase',                '\\Joomla\\CMS\\Log\\Logger\\DatabaseLogger', '5.0');
JLoader::registerAlias('JLogLoggerEcho',                    '\\Joomla\\CMS\\Log\\Logger\\EchoLogger', '5.0');
JLoader::registerAlias('JLogLoggerFormattedtext',           '\\Joomla\\CMS\\Log\\Logger\\FormattedtextLogger', '5.0');
JLoader::registerAlias('JLogLoggerMessagequeue',            '\\Joomla\\CMS\\Log\\Logger\\MessagequeueLogger', '5.0');
JLoader::registerAlias('JLogLoggerSyslog',                  '\\Joomla\\CMS\\Log\\Logger\\SyslogLogger', '5.0');
JLoader::registerAlias('JLogLoggerW3c',                     '\\Joomla\\CMS\\Log\\Logger\\W3cLogger', '5.0');

JLoader::registerAlias('JProfiler',                         '\\Joomla\\CMS\\Profiler\\Profiler', '5.0');

JLoader::registerAlias('JUri',                              '\\Joomla\\CMS\\Uri\\Uri', '5.0');

JLoader::registerAlias('JCache',                            '\\Joomla\\CMS\\Cache\\Cache', '5.0');
JLoader::registerAlias('JCacheController',                  '\\Joomla\\CMS\\Cache\\CacheController', '5.0');
JLoader::registerAlias('JCacheStorage',                     '\\Joomla\\CMS\\Cache\\CacheStorage', '5.0');
JLoader::registerAlias('JCacheControllerCallback',          '\\Joomla\\CMS\\Cache\\Controller\\CallbackController', '5.0');
JLoader::registerAlias('JCacheControllerOutput',            '\\Joomla\\CMS\\Cache\\Controller\\OutputController', '5.0');
JLoader::registerAlias('JCacheControllerPage',              '\\Joomla\\CMS\\Cache\\Controller\\PageController', '5.0');
JLoader::registerAlias('JCacheControllerView',              '\\Joomla\\CMS\\Cache\\Controller\\ViewController', '5.0');
JLoader::registerAlias('JCacheStorageApc',                  '\\Joomla\\CMS\\Cache\\Storage\\ApcStorage', '4.0');
JLoader::registerAlias('JCacheStorageApcu',                 '\\Joomla\\CMS\\Cache\\Storage\\ApcuStorage', '5.0');
JLoader::registerAlias('JCacheStorageHelper',               '\\Joomla\\CMS\\Cache\\Storage\\CacheStorageHelper', '5.0');
JLoader::registerAlias('JCacheStorageCachelite',            '\\Joomla\\CMS\\Cache\\Storage\\CacheliteStorage', '4.0');
JLoader::registerAlias('JCacheStorageFile',                 '\\Joomla\\CMS\\Cache\\Storage\\FileStorage', '5.0');
JLoader::registerAlias('JCacheStorageMemcached',            '\\Joomla\\CMS\\Cache\\Storage\\MemcachedStorage', '5.0');
JLoader::registerAlias('JCacheStorageMemcache',             '\\Joomla\\CMS\\Cache\\Storage\\MemcacheStorage', '4.0');
JLoader::registerAlias('JCacheStorageRedis',                '\\Joomla\\CMS\\Cache\\Storage\\RedisStorage', '5.0');
JLoader::registerAlias('JCacheStorageWincache',             '\\Joomla\\CMS\\Cache\\Storage\\WincacheStorage', '5.0');
JLoader::registerAlias('JCacheStorageXcache',               '\\Joomla\\CMS\\Cache\\Storage\\XcacheStorage', '4.0');
JLoader::registerAlias('JCacheException',                   '\\Joomla\\CMS\\Cache\\Exception\\CacheExceptionInterface', '5.0');
JLoader::registerAlias('JCacheExceptionConnecting',         '\\Joomla\\CMS\\Cache\\Exception\\CacheConnectingException', '5.0');
JLoader::registerAlias('JCacheExceptionUnsupported',        '\\Joomla\\CMS\\Cache\\Exception\\UnsupportedCacheException', '5.0');

JLoader::registerAlias('JSession',                          '\\Joomla\\CMS\\Session\\Session', '5.0');
JLoader::registerAlias('JSessionExceptionUnsupported',      '\\Joomla\\CMS\\Session\\Exception\\UnsupportedStorageException', '5.0');

JLoader::registerAlias('JUser',                             '\\Joomla\\CMS\\User\\User', '5.0');
JLoader::registerAlias('JUserHelper',                       '\\Joomla\\CMS\\User\\UserHelper', '5.0');
JLoader::registerAlias('JUserWrapperHelper',                '\\Joomla\\CMS\\User\\UserWrapper', '4.0');

JLoader::registerAlias('JForm',                             '\\Joomla\\CMS\\Form\\Form', '5.0');
JLoader::registerAlias('JFormField',                        '\\Joomla\\CMS\\Form\\FormField', '5.0');
JLoader::registerAlias('JFormHelper',                       '\\Joomla\\CMS\\Form\\FormHelper', '5.0');
JLoader::registerAlias('JFormRule',                         '\\Joomla\\CMS\\Form\\FormRule', '5.0');
JLoader::registerAlias('JFormWrapper',                      '\\Joomla\\CMS\\Form\\FormWrapper', '4.0');
JLoader::registerAlias('JFormFieldAuthor',                  '\\Joomla\\CMS\\Form\\Field\\AuthorField', '5.0');
JLoader::registerAlias('JFormFieldCaptcha',                 '\\Joomla\\CMS\\Form\\Field\\CaptchaField', '5.0');
JLoader::registerAlias('JFormFieldChromeStyle',             '\\Joomla\\CMS\\Form\\Field\\ChromestyleField', '5.0');
JLoader::registerAlias('JFormFieldContenthistory',          '\\Joomla\\CMS\\Form\\Field\\ContenthistoryField', '5.0');
JLoader::registerAlias('JFormFieldContentlanguage',         '\\Joomla\\CMS\\Form\\Field\\ContentlanguageField', '5.0');
JLoader::registerAlias('JFormFieldContenttype',             '\\Joomla\\CMS\\Form\\Field\\ContenttypeField', '5.0');
JLoader::registerAlias('JFormFieldEditor',                  '\\Joomla\\CMS\\Form\\Field\\EditorField', '5.0');
JLoader::registerAlias('JFormFieldFrontend_Language',       '\\Joomla\\CMS\\Form\\Field\\FrontendlanguageField', '5.0');
JLoader::registerAlias('JFormFieldHeadertag',               '\\Joomla\\CMS\\Form\\Field\\HeadertagField', '5.0');
JLoader::registerAlias('JFormFieldHelpsite',                '\\Joomla\\CMS\\Form\\Field\\HelpsiteField', '5.0');
JLoader::registerAlias('JFormFieldLastvisitDateRange',      '\\Joomla\\CMS\\Form\\Field\\LastvisitdaterangeField', '5.0');
JLoader::registerAlias('JFormFieldLimitbox',                '\\Joomla\\CMS\\Form\\Field\\LimitboxField', '5.0');
JLoader::registerAlias('JFormFieldMedia',                   '\\Joomla\\CMS\\Form\\Field\\MediaField', '5.0');
JLoader::registerAlias('JFormFieldMenu',                    '\\Joomla\\CMS\\Form\\Field\\MenuField', '5.0');
JLoader::registerAlias('JFormFieldMenuitem',                '\\Joomla\\CMS\\Form\\Field\\MenuitemField', '5.0');
JLoader::registerAlias('JFormFieldModuleOrder',             '\\Joomla\\CMS\\Form\\Field\\ModuleorderField', '5.0');
JLoader::registerAlias('JFormFieldModulePosition',          '\\Joomla\\CMS\\Form\\Field\\ModulepositionField', '5.0');
JLoader::registerAlias('JFormFieldModuletag',               '\\Joomla\\CMS\\Form\\Field\\ModuletagField', '5.0');
JLoader::registerAlias('JFormFieldOrdering',                '\\Joomla\\CMS\\Form\\Field\\OrderingField', '5.0');
JLoader::registerAlias('JFormFieldPlugin_Status',           '\\Joomla\\CMS\\Form\\Field\\PluginstatusField', '5.0');
JLoader::registerAlias('JFormFieldRedirect_Status',         '\\Joomla\\CMS\\Form\\Field\\RedirectStatusField', '5.0');
JLoader::registerAlias('JFormFieldRegistrationDateRange',   '\\Joomla\\CMS\\Form\\Field\\RegistrationdaterangeField', '5.0');
JLoader::registerAlias('JFormFieldStatus',                  '\\Joomla\\CMS\\Form\\Field\\StatusField', '5.0');
JLoader::registerAlias('JFormFieldTag',                     '\\Joomla\\CMS\\Form\\Field\\TagField', '5.0');
JLoader::registerAlias('JFormFieldTemplatestyle',           '\\Joomla\\CMS\\Form\\Field\\TemplatestyleField', '5.0');
JLoader::registerAlias('JFormFieldUserActive',              '\\Joomla\\CMS\\Form\\Field\\UseractiveField', '5.0');
JLoader::registerAlias('JFormFieldUserGroupList',           '\\Joomla\\CMS\\Form\\Field\\UsergrouplistField', '5.0');
JLoader::registerAlias('JFormFieldUserState',               '\\Joomla\\CMS\\Form\\Field\\UserstateField', '5.0');
JLoader::registerAlias('JFormFieldUser',                    '\\Joomla\\CMS\\Form\\Field\\UserField', '5.0');
JLoader::registerAlias('JFormRuleBoolean',                  '\\Joomla\\CMS\\Form\\Rule\\BooleanRule', '5.0');
JLoader::registerAlias('JFormRuleCalendar',                 '\\Joomla\\CMS\\Form\\Rule\\CalendarRule', '5.0');
JLoader::registerAlias('JFormRuleCaptcha',                  '\\Joomla\\CMS\\Form\\Rule\\CaptchaRule', '5.0');
JLoader::registerAlias('JFormRuleColor',                    '\\Joomla\\CMS\\Form\\Rule\\ColorRule', '5.0');
JLoader::registerAlias('JFormRuleEmail',                    '\\Joomla\\CMS\\Form\\Rule\\EmailRule', '5.0');
JLoader::registerAlias('JFormRuleEquals',                   '\\Joomla\\CMS\\Form\\Rule\\EqualsRule', '5.0');
JLoader::registerAlias('JFormRuleNotequals',                '\\Joomla\\CMS\\Form\\Rule\\NotequalsRule', '5.0');
JLoader::registerAlias('JFormRuleNumber',                   '\\Joomla\\CMS\\Form\\Rule\\NumberRule', '5.0');
JLoader::registerAlias('JFormRuleOptions',                  '\\Joomla\\CMS\\Form\\Rule\\OptionsRule', '5.0');
JLoader::registerAlias('JFormRulePassword',                 '\\Joomla\\CMS\\Form\\Rule\\PasswordRule', '5.0');
JLoader::registerAlias('JFormRuleRules',                    '\\Joomla\\CMS\\Form\\Rule\\RulesRule', '5.0');
JLoader::registerAlias('JFormRuleTel',                      '\\Joomla\\CMS\\Form\\Rule\\TelRule', '5.0');
JLoader::registerAlias('JFormRuleUrl',                      '\\Joomla\\CMS\\Form\\Rule\\UrlRule', '5.0');
JLoader::registerAlias('JFormRuleUsername',                 '\\Joomla\\CMS\\Form\\Rule\\UsernameRule', '5.0');

JLoader::registerAlias('JMicrodata',                        '\\Joomla\\CMS\\Microdata\\Microdata', '5.0');

JLoader::registerAlias('JFactory',                          '\\Joomla\\CMS\\Factory', '5.0');

JLoader::registerAlias('JMail',                             '\\Joomla\\CMS\\Mail\\Mail', '5.0');
JLoader::registerAlias('JMailHelper',                       '\\Joomla\\CMS\\Mail\\MailHelper', '5.0');
JLoader::registerAlias('JMailWrapperHelper',                '\\Joomla\\CMS\\Mail\\MailWrapper', '4.0');

JLoader::registerAlias('JClientHelper',                     '\\Joomla\\CMS\\Client\\ClientHelper', '5.0');
JLoader::registerAlias('JClientWrapperHelper',              '\\Joomla\\CMS\\Client\\ClientWrapper', '5.0');
JLoader::registerAlias('JClientFtp',                        '\\Joomla\\CMS\\Client\\FtpClient', '5.0');
JLoader::registerAlias('JFTP',                              '\\Joomla\\CMS\\Client\\FtpClient', '4.0');
JLoader::registerAlias('JClientLdap',                       '\\Joomla\\Ldap\\LdapClient', '5.0');
JLoader::registerAlias('JLDAP',                             '\\Joomla\\Ldap\\LdapClient', '4.0');

JLoader::registerAlias('JUpdate',                           '\\Joomla\\CMS\\Updater\\Update', '5.0');
JLoader::registerAlias('JUpdateAdapter',                    '\\Joomla\\CMS\\Updater\\UpdateAdapter', '5.0');
JLoader::registerAlias('JUpdater',                          '\\Joomla\\CMS\\Updater\\Updater', '5.0');
JLoader::registerAlias('JUpdaterCollection',                '\\Joomla\\CMS\\Updater\\Adapter\\CollectionAdapter', '5.0');
JLoader::registerAlias('JUpdaterExtension',                 '\\Joomla\\CMS\\Updater\\Adapter\\ExtensionAdapter', '5.0');

JLoader::registerAlias('JCrypt',                            '\\Joomla\\CMS\\Crypt\\Crypt', '5.0');
JLoader::registerAlias('JCryptCipher',                      '\\Joomla\\CMS\\Crypt\\CipherInterface', '5.0');
JLoader::registerAlias('JCryptKey',                         '\\Joomla\\CMS\\Crypt\\Key', '5.0');
JLoader::registerAlias('JCryptPassword',                    '\\Joomla\\CMS\\Crypt\\CryptPassword', '4.0');
JLoader::registerAlias('JCryptCipherBlowfish',              '\\Joomla\\CMS\\Crypt\\Cipher\\BlowfishCipher', '4.0');
JLoader::registerAlias('JCryptCipherCrypto',                '\\Joomla\\CMS\\Crypt\\Cipher\\CryptoCipher', '5.0');
JLoader::registerAlias('JCryptCipherMcrypt',                '\\Joomla\\CMS\\Crypt\\Cipher\\McryptCipher', '4.0');
JLoader::registerAlias('JCryptCipherRijndael256',           '\\Joomla\\CMS\\Crypt\\Cipher\\Rijndael256Cipher', '4.0');
JLoader::registerAlias('JCryptCipherSimple',                '\\Joomla\\CMS\\Crypt\\Cipher\\SimpleCipher', '4.0');
JLoader::registerAlias('JCryptCipherSodium',                '\\Joomla\\CMS\\Crypt\\Cipher\\SodiumCipher', '5.0');
JLoader::registerAlias('JCryptCipher3Des',                  '\\Joomla\\CMS\\Crypt\\Cipher\\TripleDesCipher', '4.0');
JLoader::registerAlias('JCryptPasswordSimple',              '\\Joomla\\CMS\\Crypt\\Password\\SimpleCryptPassword', '4.0');

JLoader::registerAlias('JStringPunycode',                   '\\Joomla\\CMS\\String\\PunycodeHelper', '5.0');

JLoader::registerAlias('JBuffer',                           '\\Joomla\\CMS\\Utility\\BufferStreamHandler', '5.0');
JLoader::registerAlias('JUtility',                          '\\Joomla\\CMS\\Utility\\Utility', '5.0');

JLoader::registerAlias('JInputCli',                         '\\Joomla\\CMS\\Input\\Cli', '5.0');
JLoader::registerAlias('JInputCookie',                      '\\Joomla\\CMS\\Input\\Cookie', '5.0');
JLoader::registerAlias('JInputFiles',                       '\\Joomla\\CMS\\Input\\Files', '5.0');
JLoader::registerAlias('JInput',                            '\\Joomla\\CMS\\Input\\Input', '5.0');
JLoader::registerAlias('JInputJSON',                        '\\Joomla\\CMS\\Input\\Json', '5.0');

JLoader::registerAlias('JFeed',                             '\\Joomla\\CMS\\Feed\\Feed', '5.0');
JLoader::registerAlias('JFeedEntry',                        '\\Joomla\\CMS\\Feed\\FeedEntry', '5.0');
JLoader::registerAlias('JFeedFactory',                      '\\Joomla\\CMS\\Feed\\FeedFactory', '5.0');
JLoader::registerAlias('JFeedLink',                         '\\Joomla\\CMS\\Feed\\FeedLink', '5.0');
JLoader::registerAlias('JFeedParser',                       '\\Joomla\\CMS\\Feed\\FeedParser', '5.0');
JLoader::registerAlias('JFeedPerson',                       '\\Joomla\\CMS\\Feed\\FeedPerson', '5.0');
JLoader::registerAlias('JFeedParserAtom',                   '\\Joomla\\CMS\\Feed\\Parser\\AtomParser', '5.0');
JLoader::registerAlias('JFeedParserNamespace',              '\\Joomla\\CMS\\Feed\\Parser\\NamespaceParserInterface', '5.0');
JLoader::registerAlias('JFeedParserRss',                    '\\Joomla\\CMS\\Feed\\Parser\\RssParser', '5.0');
JLoader::registerAlias('JFeedParserRssItunes',              '\\Joomla\\CMS\\Feed\\Parser\\Rss\\ItunesRssParser', '5.0');
JLoader::registerAlias('JFeedParserRssMedia',               '\\Joomla\\CMS\\Feed\\Parser\\Rss\\MediaRssParser', '5.0');

JLoader::registerAlias('JImage',                            '\\Joomla\\CMS\\Image\\Image', '5.0');
JLoader::registerAlias('JImageFilter',                      '\\Joomla\\CMS\\Image\\ImageFilter', '5.0');
JLoader::registerAlias('JImageFilterBackgroundfill',        '\\Joomla\\Image\\Filter\\Backgroundfill', '5.0');
JLoader::registerAlias('JImageFilterBrightness',            '\\Joomla\\Image\\Filter\\Brightness', '5.0');
JLoader::registerAlias('JImageFilterContrast',              '\\Joomla\\Image\\Filter\\Contrast', '5.0');
JLoader::registerAlias('JImageFilterEdgedetect',            '\\Joomla\\Image\\Filter\\Edgedetect', '5.0');
JLoader::registerAlias('JImageFilterEmboss',                '\\Joomla\\Image\\Filter\\Emboss', '5.0');
JLoader::registerAlias('JImageFilterNegate',                '\\Joomla\\Image\\Filter\\Negate', '5.0');
JLoader::registerAlias('JImageFilterSketchy',               '\\Joomla\\Image\\Filter\\Sketchy', '5.0');
JLoader::registerAlias('JImageFilterSmooth',                '\\Joomla\\Image\\Filter\\Smooth', '5.0');

JLoader::registerAlias('JObject',                           '\\Joomla\\CMS\\Object\\CMSObject', '5.0');

JLoader::registerAlias('JExtensionHelper',                  '\\Joomla\\CMS\\Extension\\ExtensionHelper', '5.0');

JLoader::registerAlias('JHtml',                             '\\Joomla\\CMS\\HTML\\HTMLHelper', '5.0');

JLoader::registerAlias('JFile',                             '\\Joomla\\CMS\\Filesystem\\File', '5.0');
JLoader::registerAlias('JFolder',                           '\\Joomla\\CMS\\Filesystem\\Folder', '5.0');
JLoader::registerAlias('JFilesystemHelper',                 '\\Joomla\\CMS\\Filesystem\\FilesystemHelper', '5.0');
JLoader::registerAlias('JFilesystemPatcher',                '\\Joomla\\CMS\\Filesystem\\Patcher', '5.0');
JLoader::registerAlias('JPath',                             '\\Joomla\\CMS\\Filesystem\\Path', '5.0');
JLoader::registerAlias('JStream',                           '\\Joomla\\CMS\\Filesystem\\Stream', '5.0');
JLoader::registerAlias('JStreamString',                     '\\Joomla\\CMS\\Filesystem\\Streams\\StreamString', '5.0');
JLoader::registerAlias('JStringController',                 '\\Joomla\\CMS\\Filesystem\\Support\\StringController', '5.0');
JLoader::registerAlias('JFilesystemWrapperFile',            '\\Joomla\\CMS\\Filesystem\\Wrapper\\FileWrapper', '5.0');
JLoader::registerAlias('JFilesystemWrapperFolder',          '\\Joomla\\CMS\\Filesystem\\Wrapper\\FolderWrapper', '5.0');
JLoader::registerAlias('JFilesystemWrapperPath',            '\\Joomla\\CMS\\Filesystem\\Wrapper\\PathWrapper', '5.0');
vendor/autoload.php000064400000000262152177723700010375 0ustar00<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit205c915b9c7d3e718e7c95793ee67ffe::getLoader();
vendor/paragonie/random_compat/LICENSE000064400000002112152177723700013645 0ustar00The MIT License (MIT)

Copyright (c) 2015 Paragon Initiative Enterprises

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

vendor/paragonie/random_compat/lib/random_bytes_openssl.php000064400000005062152177723700020357 0ustar00<?php
/**
 * Random_* Compatibility Library 
 * for using the new PHP 7 random_* API in PHP 5 projects
 * 
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 Paragon Initiative Enterprises
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/**
 * Since openssl_random_pseudo_bytes() uses openssl's 
 * RAND_pseudo_bytes() API, which has been marked as deprecated by the
 * OpenSSL team, this is our last resort before failure.
 * 
 * @ref https://www.openssl.org/docs/crypto/RAND_bytes.html
 * 
 * @param int $bytes
 * 
 * @throws Exception
 * 
 * @return string
 */
function random_bytes($bytes)
{
    try {
        $bytes = RandomCompat_intval($bytes);
    } catch (TypeError $ex) {
        throw new TypeError(
            'random_bytes(): $bytes must be an integer'
        );
    }

    if ($bytes < 1) {
        throw new Error(
            'Length must be greater than 0'
        );
    }

    /**
     * $secure is passed by reference. If it's set to false, fail. Note
     * that this will only return false if this function fails to return
     * any data.
     * 
     * @ref https://github.com/paragonie/random_compat/issues/6#issuecomment-119564973
     */
    $secure = true;
    /**
     * @var string
     */
    $buf = openssl_random_pseudo_bytes($bytes, $secure);
    if (
        is_string($buf)
        &&
        $secure
        &&
        RandomCompat_strlen($buf) === $bytes
    ) {
        return $buf;
    }

    /**
     * If we reach here, PHP has failed us.
     */
    throw new Exception(
        'Could not gather sufficient random data'
    );
}
vendor/paragonie/random_compat/lib/error_polyfill.php000064400000003174152177723700017173 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!class_exists('Error', false)) {
    // We can't really avoid making this extend Exception in PHP 5.
    class Error extends Exception
    {

    }
}

if (!class_exists('TypeError', false)) {
    if (is_subclass_of('Error', 'Exception')) {
        class TypeError extends Error
        {

        }
    } else {
        class TypeError extends Exception
        {

        }
    }
}
vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php000064400000005475152177723700022217 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('random_bytes')) {
    /**
     * If the libsodium PHP extension is loaded, we'll use it above any other
     * solution.
     *
     * libsodium-php project:
     * @ref https://github.com/jedisct1/libsodium-php
     *
     * @param int $bytes
     *
     * @throws Exception
     *
     * @return string
     */
    function random_bytes($bytes)
    {
        try {
            $bytes = RandomCompat_intval($bytes);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_bytes(): $bytes must be an integer'
            );
        }

        if ($bytes < 1) {
            throw new Error(
                'Length must be greater than 0'
            );
        }

        /**
         * @var string
         */
        $buf = '';

        /**
         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
         * generated in one invocation.
         */
        if ($bytes > 2147483647) {
            for ($i = 0; $i < $bytes; $i += 1073741824) {
                $n = ($bytes - $i) > 1073741824
                    ? 1073741824
                    : $bytes - $i;
                $buf .= Sodium::randombytes_buf($n);
            }
        } else {
            $buf .= Sodium::randombytes_buf($bytes);
        }

        if (is_string($buf)) {
            if (RandomCompat_strlen($buf) === $bytes) {
                return $buf;
            }
        }

        /**
         * If we reach here, PHP has failed us.
         */
        throw new Exception(
            'Could not gather sufficient random data'
        );
    }
}
vendor/paragonie/random_compat/lib/random_bytes_libsodium.php000064400000005417152177723700020667 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('random_bytes')) {
    /**
     * If the libsodium PHP extension is loaded, we'll use it above any other
     * solution.
     *
     * libsodium-php project:
     * @ref https://github.com/jedisct1/libsodium-php
     *
     * @param int $bytes
     *
     * @throws Exception
     *
     * @return string
     */
    function random_bytes($bytes)
    {
        try {
            $bytes = RandomCompat_intval($bytes);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_bytes(): $bytes must be an integer'
            );
        }

        if ($bytes < 1) {
            throw new Error(
                'Length must be greater than 0'
            );
        }

        /**
         * \Sodium\randombytes_buf() doesn't allow more than 2147483647 bytes to be
         * generated in one invocation.
         */
        if ($bytes > 2147483647) {
            $buf = '';
            for ($i = 0; $i < $bytes; $i += 1073741824) {
                $n = ($bytes - $i) > 1073741824
                    ? 1073741824
                    : $bytes - $i;
                $buf .= \Sodium\randombytes_buf($n);
            }
        } else {
            $buf = \Sodium\randombytes_buf($bytes);
        }

        if ($buf !== false) {
            if (RandomCompat_strlen($buf) === $bytes) {
                return $buf;
            }
        }

        /**
         * If we reach here, PHP has failed us.
         */
        throw new Exception(
            'Could not gather sufficient random data'
        );
    }
}
vendor/paragonie/random_compat/lib/random.php000064400000020120152177723700015376 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * @version 1.4.3
 * @released 2018-04-04
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!defined('PHP_VERSION_ID')) {
    // This constant was introduced in PHP 5.2.7
    $RandomCompatversion = array_map('intval', explode('.', PHP_VERSION));
    define(
        'PHP_VERSION_ID',
        $RandomCompatversion[0] * 10000
        + $RandomCompatversion[1] * 100
        + $RandomCompatversion[2]
    );
    $RandomCompatversion = null;
}

/**
 * PHP 7.0.0 and newer have these functions natively.
 */
if (PHP_VERSION_ID >= 70000) {
    return;
}

if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    define('RANDOM_COMPAT_READ_BUFFER', 8);
}

$RandomCompatDIR = dirname(__FILE__);

require_once $RandomCompatDIR . '/byte_safe_strings.php';
require_once $RandomCompatDIR . '/cast_to_int.php';
require_once $RandomCompatDIR . '/error_polyfill.php';

if (!is_callable('random_bytes')) {
    /**
     * PHP 5.2.0 - 5.6.x way to implement random_bytes()
     *
     * We use conditional statements here to define the function in accordance
     * to the operating environment. It's a micro-optimization.
     *
     * In order of preference:
     *   1. Use libsodium if available.
     *   2. fread() /dev/urandom if available (never on Windows)
     *   3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM)
     *   4. COM('CAPICOM.Utilities.1')->GetRandom()
     *
     * See RATIONALE.md for our reasoning behind this particular order
     */
    if (extension_loaded('libsodium')) {
        // See random_bytes_libsodium.php
        if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) {
            require_once $RandomCompatDIR . '/random_bytes_libsodium.php';
        } elseif (method_exists('Sodium', 'randombytes_buf')) {
            require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php';
        }
    }

    /**
     * Reading directly from /dev/urandom:
     */
    if (DIRECTORY_SEPARATOR === '/') {
        // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast
        // way to exclude Windows.
        $RandomCompatUrandom = true;
        $RandomCompat_basedir = ini_get('open_basedir');

        if (!empty($RandomCompat_basedir)) {
            $RandomCompat_open_basedir = explode(
                PATH_SEPARATOR,
                strtolower($RandomCompat_basedir)
            );
            $RandomCompatUrandom = (array() !== array_intersect(
                    array('/dev', '/dev/', '/dev/urandom'),
                    $RandomCompat_open_basedir
                ));
            $RandomCompat_open_basedir = null;
        }

        if (
            !is_callable('random_bytes')
            &&
            $RandomCompatUrandom
            &&
            @is_readable('/dev/urandom')
        ) {
            // Error suppression on is_readable() in case of an open_basedir
            // or safe_mode failure. All we care about is whether or not we
            // can read it at this point. If the PHP environment is going to
            // panic over trying to see if the file can be read in the first
            // place, that is not helpful to us here.

            // See random_bytes_dev_urandom.php
            require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php';
        }
        // Unset variables after use
        $RandomCompat_basedir = null;
    } else {
        $RandomCompatUrandom = false;
    }

    /**
     * mcrypt_create_iv()
     *
     * We only want to use mcypt_create_iv() if:
     *
     * - random_bytes() hasn't already been defined
     * - the mcrypt extensions is loaded
     * - One of these two conditions is true:
     *   - We're on Windows (DIRECTORY_SEPARATOR !== '/')
     *   - We're not on Windows and /dev/urandom is readabale
     *     (i.e. we're not in a chroot jail)
     * - Special case:
     *   - If we're not on Windows, but the PHP version is between
     *     5.6.10 and 5.6.12, we don't want to use mcrypt. It will
     *     hang indefinitely. This is bad.
     *   - If we're on Windows, we want to use PHP >= 5.3.7 or else
     *     we get insufficient entropy errors.
     */
    if (
        !is_callable('random_bytes')
        &&
        // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be.
        (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307)
        &&
        // Prevent this code from hanging indefinitely on non-Windows;
        // see https://bugs.php.net/bug.php?id=69833
        (
            DIRECTORY_SEPARATOR !== '/' ||
            (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613)
        )
        &&
        extension_loaded('mcrypt')
    ) {
        // See random_bytes_mcrypt.php
        require_once $RandomCompatDIR . '/random_bytes_mcrypt.php';
    }
    $RandomCompatUrandom = null;

    /**
     * This is a Windows-specific fallback, for when the mcrypt extension
     * isn't loaded.
     */
    if (
        !is_callable('random_bytes')
        &&
        extension_loaded('com_dotnet')
        &&
        class_exists('COM')
    ) {
        $RandomCompat_disabled_classes = preg_split(
            '#\s*,\s*#',
            strtolower(ini_get('disable_classes'))
        );

        if (!in_array('com', $RandomCompat_disabled_classes)) {
            try {
                $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1');
                if (method_exists($RandomCompatCOMtest, 'GetRandom')) {
                    // See random_bytes_com_dotnet.php
                    require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php';
                }
            } catch (com_exception $e) {
                // Don't try to use it.
            }
        }
        $RandomCompat_disabled_classes = null;
        $RandomCompatCOMtest = null;
    }

    /**
     * openssl_random_pseudo_bytes()
     */
    if (
        (
            // Unix-like with PHP >= 5.3.0 or
            (
                DIRECTORY_SEPARATOR === '/'
                &&
                PHP_VERSION_ID >= 50300
            )
            ||
            // Windows with PHP >= 5.4.1
            PHP_VERSION_ID >= 50401
        )
        &&
        !function_exists('random_bytes')
        &&
        extension_loaded('openssl')
    ) {
        // See random_bytes_openssl.php
        require_once $RandomCompatDIR . '/random_bytes_openssl.php';
    }

    /**
     * throw new Exception
     */
    if (!is_callable('random_bytes')) {
        /**
         * We don't have any more options, so let's throw an exception right now
         * and hope the developer won't let it fail silently.
         *
         * @param mixed $length
         * @return void
         * @throws Exception
         */
        function random_bytes($length)
        {
            unset($length); // Suppress "variable not used" warnings.
            throw new Exception(
                'There is no suitable CSPRNG installed on your system'
            );
        }
    }
}

if (!is_callable('random_int')) {
    require_once $RandomCompatDIR . '/random_int.php';
}

$RandomCompatDIR = null;
vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php000064400000011612152177723700021175 0ustar00<?php
/**
 * Random_* Compatibility Library 
 * for using the new PHP 7 random_* API in PHP 5 projects
 * 
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 Paragon Initiative Enterprises
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!defined('RANDOM_COMPAT_READ_BUFFER')) {
    define('RANDOM_COMPAT_READ_BUFFER', 8);
}

if (!is_callable('random_bytes')) {
    /**
     * Unless open_basedir is enabled, use /dev/urandom for
     * random numbers in accordance with best practices
     *
     * Why we use /dev/urandom and not /dev/random
     * @ref http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers
     *
     * @param int $bytes
     *
     * @throws Exception
     *
     * @return string
     */
    function random_bytes($bytes)
    {
        static $fp = null;
        /**
         * This block should only be run once
         */
        if (empty($fp)) {
            /**
             * We use /dev/urandom if it is a char device.
             * We never fall back to /dev/random
             */
            $fp = fopen('/dev/urandom', 'rb');
            if (!empty($fp)) {
                $st = fstat($fp);
                if (($st['mode'] & 0170000) !== 020000) {
                    fclose($fp);
                    $fp = false;
                }
            }

            if (!empty($fp)) {
                /**
                 * stream_set_read_buffer() does not exist in HHVM
                 * 
                 * If we don't set the stream's read buffer to 0, PHP will
                 * internally buffer 8192 bytes, which can waste entropy
                 * 
                 * stream_set_read_buffer returns 0 on success
                 */
                if (function_exists('stream_set_read_buffer')) {
                    stream_set_read_buffer($fp, RANDOM_COMPAT_READ_BUFFER);
                }
                if (function_exists('stream_set_chunk_size')) {
                    stream_set_chunk_size($fp, RANDOM_COMPAT_READ_BUFFER);
                }
            }
        }

        try {
            $bytes = RandomCompat_intval($bytes);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_bytes(): $bytes must be an integer'
            );
        }

        if ($bytes < 1) {
            throw new Error(
                'Length must be greater than 0'
            );
        }

        /**
         * This if() block only runs if we managed to open a file handle
         *
         * It does not belong in an else {} block, because the above
         * if (empty($fp)) line is logic that should only be run once per
         * page load.
         */
        if (!empty($fp)) {
            $remaining = $bytes;
            $buf = '';

            /**
             * We use fread() in a loop to protect against partial reads
             */
            do {
                $read = fread($fp, $remaining);
                if ($read === false) {
                    /**
                     * We cannot safely read from the file. Exit the
                     * do-while loop and trigger the exception condition
                     */
                    $buf = false;
                    break;
                }
                /**
                 * Decrease the number of bytes returned from remaining
                 */
                $remaining -= RandomCompat_strlen($read);
                $buf .= $read;
            } while ($remaining > 0);

            /**
             * Is our result valid?
             */
            if ($buf !== false) {
                if (RandomCompat_strlen($buf) === $bytes) {
                    /**
                     * Return our random entropy buffer here:
                     */
                    return $buf;
                }
            }
        }

        /**
         * If we reach here, PHP has failed us.
         */
        throw new Exception(
            'Error reading from source device'
        );
    }
}
vendor/paragonie/random_compat/lib/random_int.php000064400000014157152177723700016265 0ustar00<?php

if (!is_callable('random_int')) {
    /**
     * Random_* Compatibility Library
     * for using the new PHP 7 random_* API in PHP 5 projects
     *
     * The MIT License (MIT)
     *
     * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
     *
     * Permission is hereby granted, free of charge, to any person obtaining a copy
     * of this software and associated documentation files (the "Software"), to deal
     * in the Software without restriction, including without limitation the rights
     * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     * copies of the Software, and to permit persons to whom the Software is
     * furnished to do so, subject to the following conditions:
     *
     * The above copyright notice and this permission notice shall be included in
     * all copies or substantial portions of the Software.
     *
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
     * SOFTWARE.
     */

    /**
     * Fetch a random integer between $min and $max inclusive
     *
     * @param int $min
     * @param int $max
     *
     * @throws Exception
     *
     * @return int
     */
    function random_int($min, $max)
    {
        /**
         * Type and input logic checks
         *
         * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
         * (non-inclusive), it will sanely cast it to an int. If you it's equal to
         * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
         * lose precision, so the <= and => operators might accidentally let a float
         * through.
         */

        try {
            $min = RandomCompat_intval($min);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_int(): $min must be an integer'
            );
        }

        try {
            $max = RandomCompat_intval($max);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_int(): $max must be an integer'
            );
        }

        /**
         * Now that we've verified our weak typing system has given us an integer,
         * let's validate the logic then we can move forward with generating random
         * integers along a given range.
         */
        if ($min > $max) {
            throw new Error(
                'Minimum value must be less than or equal to the maximum value'
            );
        }

        if ($max === $min) {
            return $min;
        }

        /**
         * Initialize variables to 0
         *
         * We want to store:
         * $bytes => the number of random bytes we need
         * $mask => an integer bitmask (for use with the &) operator
         *          so we can minimize the number of discards
         */
        $attempts = $bits = $bytes = $mask = $valueShift = 0;

        /**
         * At this point, $range is a positive number greater than 0. It might
         * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to
         * a float and we will lose some precision.
         */
        $range = $max - $min;

        /**
         * Test for integer overflow:
         */
        if (!is_int($range)) {

            /**
             * Still safely calculate wider ranges.
             * Provided by @CodesInChaos, @oittaa
             *
             * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435
             *
             * We use ~0 as a mask in this case because it generates all 1s
             *
             * @ref https://eval.in/400356 (32-bit)
             * @ref http://3v4l.org/XX9r5  (64-bit)
             */
            $bytes = PHP_INT_SIZE;
            $mask = ~0;

        } else {

            /**
             * $bits is effectively ceil(log($range, 2)) without dealing with
             * type juggling
             */
            while ($range > 0) {
                if ($bits % 8 === 0) {
                    ++$bytes;
                }
                ++$bits;
                $range >>= 1;
                $mask = $mask << 1 | 1;
            }
            $valueShift = $min;
        }

        $val = 0;
        /**
         * Now that we have our parameters set up, let's begin generating
         * random integers until one falls between $min and $max
         */
        do {
            /**
             * The rejection probability is at most 0.5, so this corresponds
             * to a failure probability of 2^-128 for a working RNG
             */
            if ($attempts > 128) {
                throw new Exception(
                    'random_int: RNG is broken - too many rejections'
                );
            }

            /**
             * Let's grab the necessary number of random bytes
             */
            $randomByteString = random_bytes($bytes);

            /**
             * Let's turn $randomByteString into an integer
             *
             * This uses bitwise operators (<< and |) to build an integer
             * out of the values extracted from ord()
             *
             * Example: [9F] | [6D] | [32] | [0C] =>
             *   159 + 27904 + 3276800 + 201326592 =>
             *   204631455
             */
            $val &= 0;
            for ($i = 0; $i < $bytes; ++$i) {
                $val |= ord($randomByteString[$i]) << ($i * 8);
            }

            /**
             * Apply mask
             */
            $val &= $mask;
            $val += $valueShift;

            ++$attempts;
            /**
             * If $val overflows to a floating point number,
             * ... or is larger than $max,
             * ... or smaller than $min,
             * then try again.
             */
        } while (!is_int($val) || $val > $max || $val < $min);

        return (int)$val;
    }
}
vendor/paragonie/random_compat/lib/byte_safe_strings.php000064400000013525152177723700017643 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('RandomCompat_strlen')) {
    if (
        defined('MB_OVERLOAD_STRING') &&
        ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
    ) {
        /**
         * strlen() implementation that isn't brittle to mbstring.func_overload
         *
         * This version uses mb_strlen() in '8bit' mode to treat strings as raw
         * binary rather than UTF-8, ISO-8859-1, etc
         *
         * @param string $binary_string
         *
         * @throws TypeError
         *
         * @return int
         */
        function RandomCompat_strlen($binary_string)
        {
            if (!is_string($binary_string)) {
                throw new TypeError(
                    'RandomCompat_strlen() expects a string'
                );
            }

            return (int) mb_strlen($binary_string, '8bit');
        }

    } else {
        /**
         * strlen() implementation that isn't brittle to mbstring.func_overload
         *
         * This version just used the default strlen()
         *
         * @param string $binary_string
         *
         * @throws TypeError
         *
         * @return int
         */
        function RandomCompat_strlen($binary_string)
        {
            if (!is_string($binary_string)) {
                throw new TypeError(
                    'RandomCompat_strlen() expects a string'
                );
            }
            return (int) strlen($binary_string);
        }
    }
}

if (!is_callable('RandomCompat_substr')) {

    if (
        defined('MB_OVERLOAD_STRING')
        &&
        ini_get('mbstring.func_overload') & MB_OVERLOAD_STRING
    ) {
        /**
         * substr() implementation that isn't brittle to mbstring.func_overload
         *
         * This version uses mb_substr() in '8bit' mode to treat strings as raw
         * binary rather than UTF-8, ISO-8859-1, etc
         *
         * @param string $binary_string
         * @param int $start
         * @param int $length (optional)
         *
         * @throws TypeError
         *
         * @return string
         */
        function RandomCompat_substr($binary_string, $start, $length = null)
        {
            if (!is_string($binary_string)) {
                throw new TypeError(
                    'RandomCompat_substr(): First argument should be a string'
                );
            }

            if (!is_int($start)) {
                throw new TypeError(
                    'RandomCompat_substr(): Second argument should be an integer'
                );
            }

            if ($length === null) {
                /**
                 * mb_substr($str, 0, NULL, '8bit') returns an empty string on
                 * PHP 5.3, so we have to find the length ourselves.
                 */
                $length = RandomCompat_strlen($binary_string) - $start;
            } elseif (!is_int($length)) {
                throw new TypeError(
                    'RandomCompat_substr(): Third argument should be an integer, or omitted'
                );
            }

            // Consistency with PHP's behavior
            if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
                return '';
            }
            if ($start > RandomCompat_strlen($binary_string)) {
                return '';
            }

            return (string) mb_substr($binary_string, $start, $length, '8bit');
        }

    } else {

        /**
         * substr() implementation that isn't brittle to mbstring.func_overload
         *
         * This version just uses the default substr()
         *
         * @param string $binary_string
         * @param int $start
         * @param int $length (optional)
         *
         * @throws TypeError
         *
         * @return string
         */
        function RandomCompat_substr($binary_string, $start, $length = null)
        {
            if (!is_string($binary_string)) {
                throw new TypeError(
                    'RandomCompat_substr(): First argument should be a string'
                );
            }

            if (!is_int($start)) {
                throw new TypeError(
                    'RandomCompat_substr(): Second argument should be an integer'
                );
            }

            if ($length !== null) {
                if (!is_int($length)) {
                    throw new TypeError(
                        'RandomCompat_substr(): Third argument should be an integer, or omitted'
                    );
                }

                return (string) substr($binary_string, $start, $length);
            }

            return (string) substr($binary_string, $start);
        }
    }
}
vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php000064400000005522152177723700021030 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('random_bytes')) {
    /**
     * Windows with PHP < 5.3.0 will not have the function
     * openssl_random_pseudo_bytes() available, so let's use
     * CAPICOM to work around this deficiency.
     *
     * @param int $bytes
     *
     * @throws Exception
     *
     * @return string
     */
    function random_bytes($bytes)
    {
        try {
            $bytes = RandomCompat_intval($bytes);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_bytes(): $bytes must be an integer'
            );
        }

        if ($bytes < 1) {
            throw new Error(
                'Length must be greater than 0'
            );
        }

        $buf = '';
        if (!class_exists('COM')) {
            throw new Error(
                'COM does not exist'
            );
        }
        $util = new COM('CAPICOM.Utilities.1');
        $execCount = 0;

        /**
         * Let's not let it loop forever. If we run N times and fail to
         * get N bytes of random data, then CAPICOM has failed us.
         */
        do {
            $buf .= base64_decode($util->GetRandom($bytes, 0));
            if (RandomCompat_strlen($buf) >= $bytes) {
                /**
                 * Return our random entropy buffer here:
                 */
                return RandomCompat_substr($buf, 0, $bytes);
            }
            ++$execCount;
        } while ($execCount < $bytes);

        /**
         * If we reach here, PHP has failed us.
         */
        throw new Exception(
            'Could not gather sufficient random data'
        );
    }
}
vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php000064400000004724152177723700020216 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2017 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('random_bytes')) {
    /**
     * Powered by ext/mcrypt (and thankfully NOT libmcrypt)
     *
     * @ref https://bugs.php.net/bug.php?id=55169
     * @ref https://github.com/php/php-src/blob/c568ffe5171d942161fc8dda066bce844bdef676/ext/mcrypt/mcrypt.c#L1321-L1386
     *
     * @param int $bytes
     *
     * @throws Exception
     *
     * @return string
     */
    function random_bytes($bytes)
    {
        try {
            $bytes = RandomCompat_intval($bytes);
        } catch (TypeError $ex) {
            throw new TypeError(
                'random_bytes(): $bytes must be an integer'
            );
        }

        if ($bytes < 1) {
            throw new Error(
                'Length must be greater than 0'
            );
        }

        $buf = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
        if (
            $buf !== false
            &&
            RandomCompat_strlen($buf) === $bytes
        ) {
            /**
             * Return our random entropy buffer here:
             */
            return $buf;
        }

        /**
         * If we reach here, PHP has failed us.
         */
        throw new Exception(
            'Could not gather sufficient random data'
        );
    }
}
vendor/paragonie/random_compat/lib/cast_to_int.php000064400000005025152177723700016433 0ustar00<?php
/**
 * Random_* Compatibility Library
 * for using the new PHP 7 random_* API in PHP 5 projects
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 - 2016 Paragon Initiative Enterprises
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

if (!is_callable('RandomCompat_intval')) {

    /**
     * Cast to an integer if we can, safely.
     *
     * If you pass it a float in the range (~PHP_INT_MAX, PHP_INT_MAX)
     * (non-inclusive), it will sanely cast it to an int. If you it's equal to
     * ~PHP_INT_MAX or PHP_INT_MAX, we let it fail as not an integer. Floats
     * lose precision, so the <= and => operators might accidentally let a float
     * through.
     *
     * @param int|float $number    The number we want to convert to an int
     * @param boolean   $fail_open Set to true to not throw an exception
     *
     * @return float|int
     *
     * @throws TypeError
     */
    function RandomCompat_intval($number, $fail_open = false)
    {
        if (is_int($number) || is_float($number)) {
            $number += 0;
        } elseif (is_numeric($number)) {
            $number += 0;
        }

        if (
            is_float($number)
            &&
            $number > ~PHP_INT_MAX
            &&
            $number < PHP_INT_MAX
        ) {
            $number = (int) $number;
        }

        if (is_int($number)) {
            return (int) $number;
        } elseif (!$fail_open) {
            throw new TypeError(
                'Expected an integer.'
            );
        }
        return $number;
    }
}
vendor/paragonie/sodium_compat/autoload.php000064400000003157152177723700015213 0ustar00<?php

if (!is_callable('sodiumCompatAutoloader')) {
    /**
     * Sodium_Compat autoloader.
     *
     * @param string $class Class name to be autoloaded.
     *
     * @return bool         Stop autoloading?
     */
    function sodiumCompatAutoloader($class)
    {
        $namespace = 'ParagonIE_Sodium_';
        // Does the class use the namespace prefix?
        $len = strlen($namespace);
        if (strncmp($namespace, $class, $len) !== 0) {
            // no, move to the next registered autoloader
            return false;
        }

        // Get the relative class name
        $relative_class = substr($class, $len);

        // Replace the namespace prefix with the base directory, replace namespace
        // separators with directory separators in the relative class name, append
        // with .php
        $file = dirname(__FILE__) . '/src/' . str_replace('_', '/', $relative_class) . '.php';
        // if the file exists, require it
        if (file_exists($file)) {
            require_once $file;
            return true;
        }
        return false;
    }

    // Now that we have an autoloader, let's register it!
    spl_autoload_register('sodiumCompatAutoloader');
}

require_once dirname(__FILE__) . '/src/SodiumException.php';
if (PHP_VERSION_ID >= 50300) {
    // Namespaces didn't exist before 5.3.0, so don't even try to use this
    // unless PHP >= 5.3.0
    require_once dirname(__FILE__) . '/lib/namespaced.php';
    require_once dirname(__FILE__) . '/lib/sodium_compat.php';
}
if (PHP_VERSION_ID < 70200 || !extension_loaded('sodium')) {
    require_once dirname(__FILE__) . '/lib/php72compat.php';
}
vendor/paragonie/sodium_compat/autoload-fast.php000064400000000117152177723700016137 0ustar00<?php

require_once 'autoload.php';
ParagonIE_Sodium_Compat::$fastMult = true;
vendor/paragonie/sodium_compat/LICENSE000064400000001626152177723700013676 0ustar00/*
 * ISC License
 *
 * Copyright (c) 2016-2018
 * Paragon Initiative Enterprises <security at paragonie dot com>
 *
 * Copyright (c) 2013-2018
 * Frank Denis <j at pureftpd dot org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */vendor/paragonie/sodium_compat/lib/php72compat.php000064400000076263152177723700016325 0ustar00<?php

/**
 * This file will monkey patch the pure-PHP implementation in place of the
 * PECL functions and constants, but only if they do not already exist.
 *
 * Thus, the functions or constants just proxy to the appropriate
 * ParagonIE_Sodium_Compat method or class constant, respectively.
 */
foreach (array(
    'CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_ABYTES',
    'CRYPTO_AEAD_AES256GCM_KEYBYTES',
    'CRYPTO_AEAD_AES256GCM_NSECBYTES',
    'CRYPTO_AEAD_AES256GCM_NPUBBYTES',
    'CRYPTO_AEAD_AES256GCM_ABYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES',
    'CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES',
    'CRYPTO_AUTH_BYTES',
    'CRYPTO_AUTH_KEYBYTES',
    'CRYPTO_BOX_SEALBYTES',
    'CRYPTO_BOX_SECRETKEYBYTES',
    'CRYPTO_BOX_PUBLICKEYBYTES',
    'CRYPTO_BOX_KEYPAIRBYTES',
    'CRYPTO_BOX_MACBYTES',
    'CRYPTO_BOX_NONCEBYTES',
    'CRYPTO_BOX_SEEDBYTES',
    'CRYPTO_KX_BYTES',
    'CRYPTO_KX_SEEDBYTES',
    'CRYPTO_KX_PUBLICKEYBYTES',
    'CRYPTO_KX_SECRETKEYBYTES',
    'CRYPTO_GENERICHASH_BYTES',
    'CRYPTO_GENERICHASH_BYTES_MIN',
    'CRYPTO_GENERICHASH_BYTES_MAX',
    'CRYPTO_GENERICHASH_KEYBYTES',
    'CRYPTO_GENERICHASH_KEYBYTES_MIN',
    'CRYPTO_GENERICHASH_KEYBYTES_MAX',
    'CRYPTO_PWHASH_SALTBYTES',
    'CRYPTO_PWHASH_STRPREFIX',
    'CRYPTO_PWHASH_ALG_ARGON2I13',
    'CRYPTO_PWHASH_ALG_ARGON2ID13',
    'CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE',
    'CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE',
    'CRYPTO_PWHASH_MEMLIMIT_MODERATE',
    'CRYPTO_PWHASH_OPSLIMIT_MODERATE',
    'CRYPTO_PWHASH_MEMLIMIT_SENSITIVE',
    'CRYPTO_PWHASH_OPSLIMIT_SENSITIVE',
    'CRYPTO_SCALARMULT_BYTES',
    'CRYPTO_SCALARMULT_SCALARBYTES',
    'CRYPTO_SHORTHASH_BYTES',
    'CRYPTO_SHORTHASH_KEYBYTES',
    'CRYPTO_SECRETBOX_KEYBYTES',
    'CRYPTO_SECRETBOX_MACBYTES',
    'CRYPTO_SECRETBOX_NONCEBYTES',
    'CRYPTO_SIGN_BYTES',
    'CRYPTO_SIGN_SEEDBYTES',
    'CRYPTO_SIGN_PUBLICKEYBYTES',
    'CRYPTO_SIGN_SECRETKEYBYTES',
    'CRYPTO_SIGN_KEYPAIRBYTES',
    'CRYPTO_STREAM_KEYBYTES',
    'CRYPTO_STREAM_NONCEBYTES',
    'LIBRARY_VERSION_MAJOR',
    'LIBRARY_VERSION_MINOR',
    'VERSION_STRING'
    ) as $constant
) {
    if (!defined("SODIUM_$constant")) {
        define("SODIUM_$constant", constant("ParagonIE_Sodium_Compat::$constant"));
    }
}

if (!is_callable('sodium_bin2hex')) {
    /**
     * @see ParagonIE_Sodium_Compat::hex2bin()
     * @param string $string
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_bin2hex($string)
    {
        return ParagonIE_Sodium_Compat::bin2hex($string);
    }
}
if (!is_callable('sodium_compare')) {
    /**
     * @see ParagonIE_Sodium_Compat::compare()
     * @param string $a
     * @param string $b
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_compare($a, $b)
    {
        return ParagonIE_Sodium_Compat::compare($a, $b);
    }
}
if (!is_callable('sodium_crypto_aead_aes256gcm_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     */
    function sodium_crypto_aead_aes256gcm_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_aead_aes256gcm_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_aead_aes256gcm_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('sodium_crypto_aead_aes256gcm_is_available')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available()
     * @return bool
     */
    function sodium_crypto_aead_aes256gcm_is_available()
    {
        return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available();
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     */
    function sodium_crypto_aead_chacha20poly1305_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_aead_chacha20poly1305_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_keygen()
     * @return string
     */
    function sodium_crypto_aead_chacha20poly1305_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_keygen();
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     */
    function sodium_crypto_aead_chacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_aead_chacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('sodium_crypto_aead_chacha20poly1305_ietf_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_keygen()
     * @return string
     */
    function sodium_crypto_aead_chacha20poly1305_ietf_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_keygen();
    }
}
if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     */
    function sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key, true);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key, true);
    }
}
if (!is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_keygen()
     * @return string
     */
    function sodium_crypto_aead_xchacha20poly1305_ietf_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_ietf_keygen();
    }
}
if (!is_callable('sodium_crypto_auth')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_auth()
     * @param string $message
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_auth($message, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_auth($message, $key);
    }
}
if (!is_callable('sodium_crypto_auth_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_auth_keygen()
     * @return string
     */
    function sodium_crypto_auth_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_auth_keygen();
    }
}
if (!is_callable('sodium_crypto_auth_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_auth_verify()
     * @param string $mac
     * @param string $message
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_auth_verify($mac, $message, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key);
    }
}
if (!is_callable('sodium_crypto_box')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box()
     * @param string $message
     * @param string $nonce
     * @param string $kp
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box($message, $nonce, $kp)
    {
        return ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $kp);
    }
}
if (!is_callable('sodium_crypto_box_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_keypair()
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_keypair()
    {
        return ParagonIE_Sodium_Compat::crypto_box_keypair();
    }
}
if (!is_callable('sodium_crypto_box_keypair_from_secretkey_and_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey()
     * @param string $sk
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_keypair_from_secretkey_and_publickey($sk, $pk)
    {
        return ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey($sk, $pk);
    }
}
if (!is_callable('sodium_crypto_box_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_open()
     * @param string $message
     * @param string $nonce
     * @param string $kp
     * @return string|bool
     */
    function sodium_crypto_box_open($message, $nonce, $kp)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_box_open($message, $nonce, $kp);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_box_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_publickey()
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_publickey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_box_publickey($keypair);
    }
}
if (!is_callable('sodium_crypto_box_publickey_from_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey()
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_publickey_from_secretkey($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey($sk);
    }
}
if (!is_callable('sodium_crypto_box_seal')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_seal()
     * @param string $message
     * @param string $publicKey
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_seal($message, $publicKey)
    {
        return ParagonIE_Sodium_Compat::crypto_box_seal($message, $publicKey);
    }
}
if (!is_callable('sodium_crypto_box_seal_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_seal_open()
     * @param string $message
     * @param string $kp
     * @return string|bool
     * @throws SodiumException
     */
    function sodium_crypto_box_seal_open($message, $kp)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_box_seal_open($message, $kp);
        } catch (SodiumException $ex) {
            if ($ex->getMessage() === 'Argument 2 must be CRYPTO_BOX_KEYPAIRBYTES long.') {
                throw $ex;
            }
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_box_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_secretkey()
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_secretkey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_box_secretkey($keypair);
    }
}
if (!is_callable('sodium_crypto_box_seed_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_seed_keypair()
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_box_seed_keypair($seed)
    {
        return ParagonIE_Sodium_Compat::crypto_box_seed_keypair($seed);
    }
}
if (!is_callable('sodium_crypto_generichash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash()
     * @param string $message
     * @param string|null $key
     * @param int $outLen
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_generichash($message, $key = null, $outLen = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash($message, $key, $outLen);
    }
}
if (!is_callable('sodium_crypto_generichash_final')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_final()
     * @param string|null $ctx
     * @param int $outputLength
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_generichash_final(&$ctx, $outputLength = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
    }
}
if (!is_callable('sodium_crypto_generichash_init')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_init()
     * @param string|null $key
     * @param int $outLen
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_generichash_init($key = null, $outLen = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outLen);
    }
}
if (!is_callable('sodium_crypto_generichash_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_keygen()
     * @return string
     */
    function sodium_crypto_generichash_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_generichash_keygen();
    }
}
if (!is_callable('sodium_crypto_generichash_update')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_update()
     * @param string|null $ctx
     * @param string $message
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_generichash_update(&$ctx, $message = '')
    {
        ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $message);
    }
}
if (!is_callable('sodium_crypto_kx')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_kx()
     * @param string $my_secret
     * @param string $their_public
     * @param string $client_public
     * @param string $server_public
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_kx($my_secret, $their_public, $client_public, $server_public)
    {
        return ParagonIE_Sodium_Compat::crypto_kx(
            $my_secret,
            $their_public,
            $client_public,
            $server_public
        );
    }
}
if (!is_callable('sodium_crypto_pwhash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash()
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @param int|null $algo
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit, $algo = null)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit, $algo);
    }
}
if (!is_callable('sodium_crypto_pwhash_str')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_str()
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash_str($passwd, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_str($passwd, $opslimit, $memlimit);
    }
}
if (!is_callable('sodium_crypto_pwhash_str_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_str_verify()
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash_str_verify($passwd, $hash)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_str_verify($passwd, $hash);
    }
}
if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256()
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash_scryptsalsa208sha256($outlen, $passwd, $salt, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256($outlen, $passwd, $salt, $opslimit, $memlimit);
    }
}
if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256_str')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str()
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit);
    }
}
if (!is_callable('sodium_crypto_pwhash_scryptsalsa208sha256_str_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify()
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash);
    }
}
if (!is_callable('sodium_crypto_scalarmult')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_scalarmult()
     * @param string $n
     * @param string $p
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_scalarmult($n, $p)
    {
        return ParagonIE_Sodium_Compat::crypto_scalarmult($n, $p);
    }
}
if (!is_callable('sodium_crypto_scalarmult_base')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_scalarmult_base()
     * @param string $n
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_scalarmult_base($n)
    {
        return ParagonIE_Sodium_Compat::crypto_scalarmult_base($n);
    }
}
if (!is_callable('sodium_crypto_secretbox')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_secretbox()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_secretbox($message, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_secretbox($message, $nonce, $key);
    }
}
if (!is_callable('sodium_crypto_secretbox_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_secretbox_keygen()
     * @return string
     */
    function sodium_crypto_secretbox_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_secretbox_keygen();
    }
}
if (!is_callable('sodium_crypto_secretbox_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_secretbox_open()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string|bool
     */
    function sodium_crypto_secretbox_open($message, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_secretbox_open($message, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_shorthash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_shorthash()
     * @param string $message
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_shorthash($message, $key = '')
    {
        return ParagonIE_Sodium_Compat::crypto_shorthash($message, $key);
    }
}
if (!is_callable('sodium_crypto_shorthash_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_shorthash_keygen()
     * @return string
     */
    function sodium_crypto_shorthash_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_shorthash_keygen();
    }
}
if (!is_callable('sodium_crypto_sign')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign()
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign($message, $sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign($message, $sk);
    }
}
if (!is_callable('sodium_crypto_sign_detached')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_detached()
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_detached($message, $sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_detached($message, $sk);
    }
}
if (!is_callable('sodium_crypto_sign_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_keypair()
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_keypair()
    {
        return ParagonIE_Sodium_Compat::crypto_sign_keypair();
    }
}
if (!is_callable('sodium_crypto_sign_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_open()
     * @param string $signedMessage
     * @param string $pk
     * @return string|bool
     */
    function sodium_crypto_sign_open($signedMessage, $pk)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_sign_open($signedMessage, $pk);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('sodium_crypto_sign_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_publickey()
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_publickey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_publickey($keypair);
    }
}
if (!is_callable('sodium_crypto_sign_publickey_from_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey()
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_publickey_from_secretkey($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($sk);
    }
}
if (!is_callable('sodium_crypto_sign_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_secretkey()
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_secretkey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_secretkey($keypair);
    }
}
if (!is_callable('sodium_crypto_sign_seed_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_seed_keypair()
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_seed_keypair($seed)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_seed_keypair($seed);
    }
}
if (!is_callable('sodium_crypto_sign_verify_detached')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_verify_detached()
     * @param string $signature
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_verify_detached($signature, $message, $pk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_verify_detached($signature, $message, $pk);
    }
}
if (!is_callable('sodium_crypto_sign_ed25519_pk_to_curve25519')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519()
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_ed25519_pk_to_curve25519($pk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519($pk);
    }
}
if (!is_callable('sodium_crypto_sign_ed25519_sk_to_curve25519')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519()
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_sign_ed25519_sk_to_curve25519($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519($sk);
    }
}
if (!is_callable('sodium_crypto_stream')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_stream()
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_stream($len, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_stream($len, $nonce, $key);
    }
}
if (!is_callable('sodium_crypto_stream_keygen')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_stream_keygen()
     * @return string
     */
    function sodium_crypto_stream_keygen()
    {
        return ParagonIE_Sodium_Compat::crypto_stream_keygen();
    }
}
if (!is_callable('sodium_crypto_stream_xor')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_stream_xor()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_crypto_stream_xor($message, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_stream_xor($message, $nonce, $key);
    }
}
if (!is_callable('sodium_hex2bin')) {
    /**
     * @see ParagonIE_Sodium_Compat::hex2bin()
     * @param string $string
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_hex2bin($string)
    {
        return ParagonIE_Sodium_Compat::hex2bin($string);
    }
}
if (!is_callable('sodium_increment')) {
    /**
     * @see ParagonIE_Sodium_Compat::increment()
     * @param &string $string
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_increment(&$string)
    {
        ParagonIE_Sodium_Compat::increment($string);
    }
}
if (!is_callable('sodium_library_version_major')) {
    /**
     * @see ParagonIE_Sodium_Compat::library_version_major()
     * @return int
     */
    function sodium_library_version_major()
    {
        return ParagonIE_Sodium_Compat::library_version_major();
    }
}
if (!is_callable('sodium_library_version_minor')) {
    /**
     * @see ParagonIE_Sodium_Compat::library_version_minor()
     * @return int
     */
    function sodium_library_version_minor()
    {
        return ParagonIE_Sodium_Compat::library_version_minor();
    }
}
if (!is_callable('sodium_version_string')) {
    /**
     * @see ParagonIE_Sodium_Compat::version_string()
     * @return string
     */
    function sodium_version_string()
    {
        return ParagonIE_Sodium_Compat::version_string();
    }
}
if (!is_callable('sodium_memcmp')) {
    /**
     * @see ParagonIE_Sodium_Compat::memcmp()
     * @param string $a
     * @param string $b
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_memcmp($a, $b)
    {
        return ParagonIE_Sodium_Compat::memcmp($a, $b);
    }
}
if (!is_callable('sodium_memzero')) {
    /**
     * @see ParagonIE_Sodium_Compat::memzero()
     * @param string &$str
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    function sodium_memzero(&$str)
    {
        ParagonIE_Sodium_Compat::memzero($str);
    }
}
if (!is_callable('sodium_randombytes_buf')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_buf()
     * @param int $amount
     * @return string
     * @throws Exception
     */
    function sodium_randombytes_buf($amount)
    {
        return ParagonIE_Sodium_Compat::randombytes_buf($amount);
    }
}

if (!is_callable('sodium_randombytes_uniform')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_uniform()
     * @param int $upperLimit
     * @return int
     * @throws Exception
     */
    function sodium_randombytes_uniform($upperLimit)
    {
        return ParagonIE_Sodium_Compat::randombytes_uniform($upperLimit);
    }
}

if (!is_callable('sodium_randombytes_random16')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_random16()
     * @return int
     */
    function sodium_randombytes_random16()
    {
        return ParagonIE_Sodium_Compat::randombytes_random16();
    }
}
vendor/paragonie/sodium_compat/lib/sodium_compat.php000064400000060160152177723700017011 0ustar00<?php
namespace Sodium;

use ParagonIE_Sodium_Compat;

/**
 * This file will monkey patch the pure-PHP implementation in place of the
 * PECL functions, but only if they do not already exist.
 *
 * Thus, the functions just proxy to the appropriate ParagonIE_Sodium_Compat
 * method.
 */
if (!is_callable('\\Sodium\\bin2hex')) {
    /**
     * @see ParagonIE_Sodium_Compat::bin2hex()
     * @param string $string
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function bin2hex($string)
    {
        return ParagonIE_Sodium_Compat::bin2hex($string);
    }
}
if (!is_callable('\\Sodium\\compare')) {
    /**
     * @see ParagonIE_Sodium_Compat::compare()
     * @param string $a
     * @param string $b
     * @return int
     * @throws \SodiumException
     * @throws \TypeError
     */
    function compare($a, $b)
    {
        return ParagonIE_Sodium_Compat::compare($a, $b);
    }
}
if (!is_callable('\\Sodium\\crypto_aead_aes256gcm_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string|bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_aes256gcm_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_aead_aes256gcm_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_aes256gcm_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_aead_aes256gcm_is_available')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available()
     * @return bool
     */
    function crypto_aead_aes256gcm_is_available()
    {
        return ParagonIE_Sodium_Compat::crypto_aead_aes256gcm_is_available();
    }
}
if (!is_callable('\\Sodium\\crypto_aead_chacha20poly1305_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string|bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_chacha20poly1305_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_aead_chacha20poly1305_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_chacha20poly1305_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_aead_chacha20poly1305_ietf_decrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string|bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_chacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_decrypt($message, $assocData, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_aead_chacha20poly1305_ietf_encrypt')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt()
     * @param string $message
     * @param string $assocData
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_aead_chacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_aead_chacha20poly1305_ietf_encrypt($message, $assocData, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_auth')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_auth()
     * @param string $message
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_auth($message, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_auth($message, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_auth_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_auth_verify()
     * @param string $mac
     * @param string $message
     * @param string $key
     * @return bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_auth_verify($mac, $message, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_auth_verify($mac, $message, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_box')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box()
     * @param string $message
     * @param string $nonce
     * @param string $kp
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box($message, $nonce, $kp)
    {
        return ParagonIE_Sodium_Compat::crypto_box($message, $nonce, $kp);
    }
}
if (!is_callable('\\Sodium\\crypto_box_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_keypair()
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_keypair()
    {
        return ParagonIE_Sodium_Compat::crypto_box_keypair();
    }
}
if (!is_callable('\\Sodium\\crypto_box_keypair_from_secretkey_and_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey()
     * @param string $sk
     * @param string $pk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_keypair_from_secretkey_and_publickey($sk, $pk)
    {
        return ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey($sk, $pk);
    }
}
if (!is_callable('\\Sodium\\crypto_box_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_open()
     * @param string $message
     * @param string $nonce
     * @param string $kp
     * @return string|bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_open($message, $nonce, $kp)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_box_open($message, $nonce, $kp);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_box_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_publickey()
     * @param string $keypair
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_publickey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_box_publickey($keypair);
    }
}
if (!is_callable('\\Sodium\\crypto_box_publickey_from_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey()
     * @param string $sk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_publickey_from_secretkey($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_box_publickey_from_secretkey($sk);
    }
}
if (!is_callable('\\Sodium\\crypto_box_seal')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_seal_open()
     * @param string $message
     * @param string $publicKey
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_seal($message, $publicKey)
    {
        return ParagonIE_Sodium_Compat::crypto_box_seal($message, $publicKey);
    }
}
if (!is_callable('\\Sodium\\crypto_box_seal_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_seal_open()
     * @param string $message
     * @param string $kp
     * @return string|bool
     * @throws \TypeError
     */
    function crypto_box_seal_open($message, $kp)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_box_seal_open($message, $kp);
        } catch (\Error $ex) {
            return false;
        } catch (\Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_box_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_box_secretkey()
     * @param string $keypair
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_box_secretkey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_box_secretkey($keypair);
    }
}
if (!is_callable('\\Sodium\\crypto_generichash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash()
     * @param string $message
     * @param string|null $key
     * @param int $outLen
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_generichash($message, $key = null, $outLen = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash($message, $key, $outLen);
    }
}
if (!is_callable('\\Sodium\\crypto_generichash_final')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_final()
     * @param string|null $ctx
     * @param int $outputLength
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_generichash_final(&$ctx, $outputLength = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
    }
}
if (!is_callable('\\Sodium\\crypto_generichash_init')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_init()
     * @param string|null $key
     * @param int $outLen
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_generichash_init($key = null, $outLen = 32)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outLen);
    }
}
if (!is_callable('\\Sodium\\crypto_generichash_update')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_generichash_update()
     * @param string|null $ctx
     * @param string $message
     * @return void
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_generichash_update(&$ctx, $message = '')
    {
        ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $message);
    }
}
if (!is_callable('\\Sodium\\crypto_kx')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_kx()
     * @param string $my_secret
     * @param string $their_public
     * @param string $client_public
     * @param string $server_public
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_kx($my_secret, $their_public, $client_public, $server_public)
    {
        return ParagonIE_Sodium_Compat::crypto_kx(
            $my_secret,
            $their_public,
            $client_public,
            $server_public
        );
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash()
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit);
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash_str')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_str()
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash_str($passwd, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_str($passwd, $opslimit, $memlimit);
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash_str_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_str_verify()
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash_str_verify($passwd, $hash)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_str_verify($passwd, $hash);
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash_scryptsalsa208sha256')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256()
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash_scryptsalsa208sha256($outlen, $passwd, $salt, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256($outlen, $passwd, $salt, $opslimit, $memlimit);
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str()
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit);
    }
}
if (!is_callable('\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str_verify')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify()
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash)
    {
        return ParagonIE_Sodium_Compat::crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash);
    }
}
if (!is_callable('\\Sodium\\crypto_scalarmult')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_scalarmult()
     * @param string $n
     * @param string $p
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_scalarmult($n, $p)
    {
        return ParagonIE_Sodium_Compat::crypto_scalarmult($n, $p);
    }
}
if (!is_callable('\\Sodium\\crypto_scalarmult_base')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_scalarmult_base()
     * @param string $n
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_scalarmult_base($n)
    {
        return ParagonIE_Sodium_Compat::crypto_scalarmult_base($n);
    }
}
if (!is_callable('\\Sodium\\crypto_secretbox')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_secretbox()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_secretbox($message, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_secretbox($message, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_secretbox_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_secretbox_open()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string|bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_secretbox_open($message, $nonce, $key)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_secretbox_open($message, $nonce, $key);
        } catch (Error $ex) {
            return false;
        } catch (Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_shorthash')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_shorthash()
     * @param string $message
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_shorthash($message, $key = '')
    {
        return ParagonIE_Sodium_Compat::crypto_shorthash($message, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_sign')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign()
     * @param string $message
     * @param string $sk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign($message, $sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign($message, $sk);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_detached')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_detached()
     * @param string $message
     * @param string $sk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_detached($message, $sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_detached($message, $sk);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_keypair()
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_keypair()
    {
        return ParagonIE_Sodium_Compat::crypto_sign_keypair();
    }
}
if (!is_callable('\\Sodium\\crypto_sign_open')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_open()
     * @param string $signedMessage
     * @param string $pk
     * @return string|bool
     */
    function crypto_sign_open($signedMessage, $pk)
    {
        try {
            return ParagonIE_Sodium_Compat::crypto_sign_open($signedMessage, $pk);
        } catch (\Error $ex) {
            return false;
        } catch (\Exception $ex) {
            return false;
        }
    }
}
if (!is_callable('\\Sodium\\crypto_sign_publickey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_publickey()
     * @param string $keypair
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_publickey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_publickey($keypair);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_publickey_from_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey()
     * @param string $sk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_publickey_from_secretkey($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_publickey_from_secretkey($sk);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_secretkey')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_secretkey()
     * @param string $keypair
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_secretkey($keypair)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_secretkey($keypair);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_seed_keypair')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_seed_keypair()
     * @param string $seed
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_seed_keypair($seed)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_seed_keypair($seed);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_verify_detached')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_verify_detached()
     * @param string $signature
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_verify_detached($signature, $message, $pk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_verify_detached($signature, $message, $pk);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_ed25519_pk_to_curve25519')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519()
     * @param string $pk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_ed25519_pk_to_curve25519($pk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_ed25519_pk_to_curve25519($pk);
    }
}
if (!is_callable('\\Sodium\\crypto_sign_ed25519_sk_to_curve25519')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519()
     * @param string $sk
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_sign_ed25519_sk_to_curve25519($sk)
    {
        return ParagonIE_Sodium_Compat::crypto_sign_ed25519_sk_to_curve25519($sk);
    }
}
if (!is_callable('\\Sodium\\crypto_stream')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_stream()
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_stream($len, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_stream($len, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\crypto_stream_xor')) {
    /**
     * @see ParagonIE_Sodium_Compat::crypto_stream_xor()
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function crypto_stream_xor($message, $nonce, $key)
    {
        return ParagonIE_Sodium_Compat::crypto_stream_xor($message, $nonce, $key);
    }
}
if (!is_callable('\\Sodium\\hex2bin')) {
    /**
     * @see ParagonIE_Sodium_Compat::hex2bin()
     * @param string $string
     * @return string
     * @throws \SodiumException
     * @throws \TypeError
     */
    function hex2bin($string)
    {
        return ParagonIE_Sodium_Compat::hex2bin($string);
    }
}
if (!is_callable('\\Sodium\\memcmp')) {
    /**
     * @see ParagonIE_Sodium_Compat::memcmp()
     * @param string $a
     * @param string $b
     * @return int
     * @throws \SodiumException
     * @throws \TypeError
     */
    function memcmp($a, $b)
    {
        return ParagonIE_Sodium_Compat::memcmp($a, $b);
    }
}
if (!is_callable('\\Sodium\\memzero')) {
    /**
     * @see ParagonIE_Sodium_Compat::memzero()
     * @param string $str
     * @return void
     * @throws \SodiumException
     * @throws \TypeError
     */
    function memzero(&$str)
    {
        ParagonIE_Sodium_Compat::memzero($str);
    }
}
if (!is_callable('\\Sodium\\randombytes_buf')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_buf()
     * @param int $amount
     * @return string
     * @throws \TypeError
     */
    function randombytes_buf($amount)
    {
        return ParagonIE_Sodium_Compat::randombytes_buf($amount);
    }
}

if (!is_callable('\\Sodium\\randombytes_uniform')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_uniform()
     * @param int $upperLimit
     * @return int
     * @throws \Exception
     * @throws \Error
     */
    function randombytes_uniform($upperLimit)
    {
        return ParagonIE_Sodium_Compat::randombytes_uniform($upperLimit);
    }
}

if (!is_callable('\\Sodium\\randombytes_random16')) {
    /**
     * @see ParagonIE_Sodium_Compat::randombytes_random16()
     * @return int
     */
    function randombytes_random16()
    {
        return ParagonIE_Sodium_Compat::randombytes_random16();
    }
}

if (!defined('\\Sodium\\CRYPTO_AUTH_BYTES')) {
    require_once dirname(__FILE__) . '/constants.php';
}
vendor/paragonie/sodium_compat/lib/namespaced.php000064400000002373152177723700016250 0ustar00<?php

if (PHP_VERSION_ID < 50300) {
    return;
}

/*
 * This file is just for convenience, to allow developers to reduce verbosity when
 * they add this project to their libraries.
 *
 * Replace this:
 *
 * $x = ParagonIE_Sodium_Compat::crypto_aead_xchacha20poly1305_encrypt(...$args);
 *
 * with this:
 *
 * use ParagonIE\Sodium\Compat;
 *
 * $x = Compat::crypto_aead_xchacha20poly1305_encrypt(...$args);
 */
spl_autoload_register(function ($class) {
    if ($class[0] === '\\') {
        $class = substr($class, 1);
    }
    $namespace = 'ParagonIE\\Sodium';
    // Does the class use the namespace prefix?
    $len = strlen($namespace);
    if (strncmp($namespace, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return false;
    }

    // Get the relative class name
    $relative_class = substr($class, $len);

    // Replace the namespace prefix with the base directory, replace namespace
    // separators with directory separators in the relative class name, append
    // with .php
    $file = dirname(__DIR__) . '/namespaced/' . str_replace('\\', '/', $relative_class) . '.php';
    // if the file exists, require it
    if (file_exists($file)) {
        require_once $file;
        return true;
    }
    return false;
});
vendor/paragonie/sodium_compat/lib/constants.php000064400000010005152177723700016153 0ustar00<?php
namespace Sodium;

use ParagonIE_Sodium_Compat;

const CRYPTO_AEAD_AES256GCM_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_AES256GCM_KEYBYTES;
const CRYPTO_AEAD_AES256GCM_NSECBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_AES256GCM_NSECBYTES;
const CRYPTO_AEAD_AES256GCM_NPUBBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_AES256GCM_NPUBBYTES;
const CRYPTO_AEAD_AES256GCM_ABYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_AES256GCM_ABYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_ABYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES;
const CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = ParagonIE_Sodium_Compat::CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES;
const CRYPTO_AUTH_BYTES = ParagonIE_Sodium_Compat::CRYPTO_AUTH_BYTES;
const CRYPTO_AUTH_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_AUTH_KEYBYTES;
const CRYPTO_BOX_SEALBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_SEALBYTES;
const CRYPTO_BOX_SECRETKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES;
const CRYPTO_BOX_PUBLICKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES;
const CRYPTO_BOX_KEYPAIRBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES;
const CRYPTO_BOX_MACBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_MACBYTES;
const CRYPTO_BOX_NONCEBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES;
const CRYPTO_BOX_SEEDBYTES = ParagonIE_Sodium_Compat::CRYPTO_BOX_SEEDBYTES;
const CRYPTO_KX_BYTES = ParagonIE_Sodium_Compat::CRYPTO_KX_BYTES;
const CRYPTO_KX_SEEDBYTES = ParagonIE_Sodium_Compat::CRYPTO_KX_SEEDBYTES;
const CRYPTO_KX_PUBLICKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_KX_PUBLICKEYBYTES;
const CRYPTO_KX_SECRETKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_KX_SECRETKEYBYTES;
const CRYPTO_GENERICHASH_BYTES = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES;
const CRYPTO_GENERICHASH_BYTES_MIN = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN;
const CRYPTO_GENERICHASH_BYTES_MAX = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX;
const CRYPTO_GENERICHASH_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES;
const CRYPTO_GENERICHASH_KEYBYTES_MIN = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN;
const CRYPTO_GENERICHASH_KEYBYTES_MAX = ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX;
const CRYPTO_SCALARMULT_BYTES = ParagonIE_Sodium_Compat::CRYPTO_SCALARMULT_BYTES;
const CRYPTO_SCALARMULT_SCALARBYTES = ParagonIE_Sodium_Compat::CRYPTO_SCALARMULT_SCALARBYTES;
const CRYPTO_SHORTHASH_BYTES = ParagonIE_Sodium_Compat::CRYPTO_SHORTHASH_BYTES;
const CRYPTO_SHORTHASH_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_SHORTHASH_KEYBYTES;
const CRYPTO_SECRETBOX_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES;
const CRYPTO_SECRETBOX_MACBYTES = ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES;
const CRYPTO_SECRETBOX_NONCEBYTES = ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES;
const CRYPTO_SIGN_BYTES = ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES;
const CRYPTO_SIGN_SEEDBYTES = ParagonIE_Sodium_Compat::CRYPTO_SIGN_SEEDBYTES;
const CRYPTO_SIGN_PUBLICKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES;
const CRYPTO_SIGN_SECRETKEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES;
const CRYPTO_SIGN_KEYPAIRBYTES = ParagonIE_Sodium_Compat::CRYPTO_SIGN_KEYPAIRBYTES;
const CRYPTO_STREAM_KEYBYTES = ParagonIE_Sodium_Compat::CRYPTO_STREAM_KEYBYTES;
const CRYPTO_STREAM_NONCEBYTES = ParagonIE_Sodium_Compat::CRYPTO_STREAM_NONCEBYTES;
vendor/paragonie/sodium_compat/src/Crypto32.php000064400000115271152177723700015620 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Crypto32', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Crypto
 *
 * ATTENTION!
 *
 * If you are using this library, you should be using
 * ParagonIE_Sodium_Compat in your code, not this class.
 */
abstract class ParagonIE_Sodium_Crypto32
{
    const aead_chacha20poly1305_KEYBYTES = 32;
    const aead_chacha20poly1305_NSECBYTES = 0;
    const aead_chacha20poly1305_NPUBBYTES = 8;
    const aead_chacha20poly1305_ABYTES = 16;

    const aead_chacha20poly1305_IETF_KEYBYTES = 32;
    const aead_chacha20poly1305_IETF_NSECBYTES = 0;
    const aead_chacha20poly1305_IETF_NPUBBYTES = 12;
    const aead_chacha20poly1305_IETF_ABYTES = 16;

    const aead_xchacha20poly1305_IETF_KEYBYTES = 32;
    const aead_xchacha20poly1305_IETF_NSECBYTES = 0;
    const aead_xchacha20poly1305_IETF_NPUBBYTES = 24;
    const aead_xchacha20poly1305_IETF_ABYTES = 16;

    const box_curve25519xsalsa20poly1305_SEEDBYTES = 32;
    const box_curve25519xsalsa20poly1305_PUBLICKEYBYTES = 32;
    const box_curve25519xsalsa20poly1305_SECRETKEYBYTES = 32;
    const box_curve25519xsalsa20poly1305_BEFORENMBYTES = 32;
    const box_curve25519xsalsa20poly1305_NONCEBYTES = 24;
    const box_curve25519xsalsa20poly1305_MACBYTES = 16;
    const box_curve25519xsalsa20poly1305_BOXZEROBYTES = 16;
    const box_curve25519xsalsa20poly1305_ZEROBYTES = 32;

    const onetimeauth_poly1305_BYTES = 16;
    const onetimeauth_poly1305_KEYBYTES = 32;

    const secretbox_xsalsa20poly1305_KEYBYTES = 32;
    const secretbox_xsalsa20poly1305_NONCEBYTES = 24;
    const secretbox_xsalsa20poly1305_MACBYTES = 16;
    const secretbox_xsalsa20poly1305_BOXZEROBYTES = 16;
    const secretbox_xsalsa20poly1305_ZEROBYTES = 32;

    const secretbox_xchacha20poly1305_KEYBYTES = 32;
    const secretbox_xchacha20poly1305_NONCEBYTES = 24;
    const secretbox_xchacha20poly1305_MACBYTES = 16;
    const secretbox_xchacha20poly1305_BOXZEROBYTES = 16;
    const secretbox_xchacha20poly1305_ZEROBYTES = 32;

    const stream_salsa20_KEYBYTES = 32;

    /**
     * AEAD Decryption with ChaCha20-Poly1305
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of message (ciphertext + MAC) */
        $len = ParagonIE_Sodium_Core32_Util::strlen($message);

        /** @var int  $clen - Length of ciphertext */
        $clen = $len - self::aead_chacha20poly1305_ABYTES;

        /** @var int $adlen - Length of associated data */
        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);

        /** @var string $mac - Message authentication code */
        $mac = ParagonIE_Sodium_Core32_Util::substr(
            $message,
            $clen,
            self::aead_chacha20poly1305_ABYTES
        );

        /** @var string $ciphertext - The encrypted message (sans MAC) */
        $ciphertext = ParagonIE_Sodium_Core32_Util::substr($message, 0, $clen);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
            32,
            $nonce,
            $key
        );

        /* Recalculate the Poly1305 authentication tag (MAC): */
        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }
        $state->update($ad);
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
        $state->update($ciphertext);
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($clen));
        $computed_mac = $state->finish();

        /* Compare the given MAC with the recalculated MAC: */
        if (!ParagonIE_Sodium_Core32_Util::verify_16($computed_mac, $mac)) {
            throw new SodiumException('Invalid MAC');
        }

        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
        return ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
            $ciphertext,
            $nonce,
            $key,
            ParagonIE_Sodium_Core32_Util::store64_le(1)
        );
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of the plaintext message */
        $len = ParagonIE_Sodium_Core32_Util::strlen($message);

        /** @var int $adlen - Length of the associated data */
        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
            32,
            $nonce,
            $key
        );
        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }

        /** @var string $ciphertext - Raw encrypted data */
        $ciphertext = ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
            $message,
            $nonce,
            $key,
            ParagonIE_Sodium_Core32_Util::store64_le(1)
        );

        $state->update($ad);
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
        $state->update($ciphertext);
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($len));
        return $ciphertext . $state->finish();
    }

    /**
     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_ietf_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $adlen - Length of associated data */
        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);

        /** @var int $len - Length of message (ciphertext + MAC) */
        $len = ParagonIE_Sodium_Core32_Util::strlen($message);

        /** @var int  $clen - Length of ciphertext */
        $clen = $len - self::aead_chacha20poly1305_IETF_ABYTES;

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::ietfStream(
            32,
            $nonce,
            $key
        );

        /** @var string $mac - Message authentication code */
        $mac = ParagonIE_Sodium_Core32_Util::substr(
            $message,
            $len - self::aead_chacha20poly1305_IETF_ABYTES,
            self::aead_chacha20poly1305_IETF_ABYTES
        );

        /** @var string $ciphertext - The encrypted message (sans MAC) */
        $ciphertext = ParagonIE_Sodium_Core32_Util::substr(
            $message,
            0,
            $len - self::aead_chacha20poly1305_IETF_ABYTES
        );

        /* Recalculate the Poly1305 authentication tag (MAC): */
        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }
        $state->update($ad);
        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
        $state->update($ciphertext);
        $state->update(str_repeat("\x00", (0x10 - $clen) & 0xf));
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($clen));
        $computed_mac = $state->finish();

        /* Compare the given MAC with the recalculated MAC: */
        if (!ParagonIE_Sodium_Core32_Util::verify_16($computed_mac, $mac)) {
            throw new SodiumException('Invalid MAC');
        }

        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
        return ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc(
            $ciphertext,
            $nonce,
            $key,
            ParagonIE_Sodium_Core32_Util::store64_le(1)
        );
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_ietf_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of the plaintext message */
        $len = ParagonIE_Sodium_Core32_Util::strlen($message);

        /** @var int $adlen - Length of the associated data */
        $adlen = ParagonIE_Sodium_Core32_Util::strlen($ad);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::ietfStream(
            32,
            $nonce,
            $key
        );
        $state = new ParagonIE_Sodium_Core32_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }

        /** @var string $ciphertext - Raw encrypted data */
        $ciphertext = ParagonIE_Sodium_Core32_ChaCha20::ietfStreamXorIc(
            $message,
            $nonce,
            $key,
            ParagonIE_Sodium_Core32_Util::store64_le(1)
        );

        $state->update($ad);
        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
        $state->update($ciphertext);
        $state->update(str_repeat("\x00", ((0x10 - $len) & 0xf)));
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($adlen));
        $state->update(ParagonIE_Sodium_Core32_Util::store64_le($len));
        return $ciphertext . $state->finish();
    }

    /**
     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_xchacha20poly1305_ietf_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = "\x00\x00\x00\x00" .
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);

        return self::aead_chacha20poly1305_ietf_decrypt($message, $ad, $nonceLast, $subkey);
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_xchacha20poly1305_ietf_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = "\x00\x00\x00\x00" .
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);

        return self::aead_chacha20poly1305_ietf_encrypt($message, $ad, $nonceLast, $subkey);
    }

    /**
     * HMAC-SHA-512-256 (a.k.a. the leftmost 256 bits of HMAC-SHA-512)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $key
     * @return string
     * @throws TypeError
     */
    public static function auth($message, $key)
    {
        return ParagonIE_Sodium_Core32_Util::substr(
            hash_hmac('sha512', $message, $key, true),
            0,
            32
        );
    }

    /**
     * HMAC-SHA-512-256 validation. Constant-time via hash_equals().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $mac
     * @param string $message
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function auth_verify($mac, $message, $key)
    {
        return ParagonIE_Sodium_Core32_Util::hashEquals(
            $mac,
            self::auth($message, $key)
        );
    }

    /**
     * X25519 key exchange followed by XSalsa20Poly1305 symmetric encryption
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box($plaintext, $nonce, $keypair)
    {
        return self::secretbox(
            $plaintext,
            $nonce,
            self::box_beforenm(
                self::box_secretkey($keypair),
                self::box_publickey($keypair)
            )
        );
    }

    /**
     * X25519-XSalsa20-Poly1305 with one ephemeral X25519 keypair.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $publicKey
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal($message, $publicKey)
    {
        /** @var string $ephemeralKeypair */
        $ephemeralKeypair = self::box_keypair();

        /** @var string $ephemeralSK */
        $ephemeralSK = self::box_secretkey($ephemeralKeypair);

        /** @var string $ephemeralPK */
        $ephemeralPK = self::box_publickey($ephemeralKeypair);

        /** @var string $nonce */
        $nonce = self::generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );

        /** @var string $keypair - The combined keypair used in crypto_box() */
        $keypair = self::box_keypair_from_secretkey_and_publickey($ephemeralSK, $publicKey);

        /** @var string $ciphertext Ciphertext + MAC from crypto_box */
        $ciphertext = self::box($message, $nonce, $keypair);
        try {
            ParagonIE_Sodium_Compat::memzero($ephemeralKeypair);
            ParagonIE_Sodium_Compat::memzero($ephemeralSK);
            ParagonIE_Sodium_Compat::memzero($nonce);
        } catch (SodiumException $ex) {
            $ephemeralKeypair = null;
            $ephemeralSK = null;
            $nonce = null;
        }
        return $ephemeralPK . $ciphertext;
    }

    /**
     * Opens a message encrypted via box_seal().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal_open($message, $keypair)
    {
        /** @var string $ephemeralPK */
        $ephemeralPK = ParagonIE_Sodium_Core32_Util::substr($message, 0, 32);

        /** @var string $ciphertext (ciphertext + MAC) */
        $ciphertext = ParagonIE_Sodium_Core32_Util::substr($message, 32);

        /** @var string $secretKey */
        $secretKey = self::box_secretkey($keypair);

        /** @var string $publicKey */
        $publicKey = self::box_publickey($keypair);

        /** @var string $nonce */
        $nonce = self::generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );

        /** @var string $keypair */
        $keypair = self::box_keypair_from_secretkey_and_publickey($secretKey, $ephemeralPK);

        /** @var string $m */
        $m = self::box_open($ciphertext, $nonce, $keypair);
        try {
            ParagonIE_Sodium_Compat::memzero($secretKey);
            ParagonIE_Sodium_Compat::memzero($ephemeralPK);
            ParagonIE_Sodium_Compat::memzero($nonce);
        } catch (SodiumException $ex) {
            $secretKey = null;
            $ephemeralPK = null;
            $nonce = null;
        }
        return $m;
    }

    /**
     * Used by crypto_box() to get the crypto_secretbox() key.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sk
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_beforenm($sk, $pk)
    {
        return ParagonIE_Sodium_Core32_HSalsa20::hsalsa20(
            str_repeat("\x00", 16),
            self::scalarmult($sk, $pk)
        );
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @return string
     * @throws Exception
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_keypair()
    {
        $sKey = random_bytes(32);
        $pKey = self::scalarmult_base($sKey);
        return $sKey . $pKey;
    }

    /**
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seed_keypair($seed)
    {
        $sKey = ParagonIE_Sodium_Core32_Util::substr(
            hash('sha512', $seed, true),
            0,
            32
        );
        $pKey = self::scalarmult_base($sKey);
        return $sKey . $pKey;
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @param string $pKey
     * @return string
     * @throws TypeError
     */
    public static function box_keypair_from_secretkey_and_publickey($sKey, $pKey)
    {
        return ParagonIE_Sodium_Core32_Util::substr($sKey, 0, 32) .
            ParagonIE_Sodium_Core32_Util::substr($pKey, 0, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function box_secretkey($keypair)
    {
        if (ParagonIE_Sodium_Core32_Util::strlen($keypair) !== 64) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
            );
        }
        return ParagonIE_Sodium_Core32_Util::substr($keypair, 0, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function box_publickey($keypair)
    {
        if (ParagonIE_Sodium_Core32_Util::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
            );
        }
        return ParagonIE_Sodium_Core32_Util::substr($keypair, 32, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_publickey_from_secretkey($sKey)
    {
        if (ParagonIE_Sodium_Core32_Util::strlen($sKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES bytes long.'
            );
        }
        return self::scalarmult_base($sKey);
    }

    /**
     * Decrypt a message encrypted with box().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_open($ciphertext, $nonce, $keypair)
    {
        return self::secretbox_open(
            $ciphertext,
            $nonce,
            self::box_beforenm(
                self::box_secretkey($keypair),
                self::box_publickey($keypair)
            )
        );
    }

    /**
     * Calculate a BLAKE2b hash.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string|null $key
     * @param int $outlen
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash($message, $key = '', $outlen = 32)
    {
        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();

        $k = null;
        if (!empty($key)) {
            /** @var SplFixedArray $k */
            $k = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($key);
            if ($k->count() > ParagonIE_Sodium_Core32_BLAKE2b::KEYBYTES) {
                throw new RangeException('Invalid key size');
            }
        }

        /** @var SplFixedArray $in */
        $in = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($message);

        /** @var SplFixedArray $ctx */
        $ctx = ParagonIE_Sodium_Core32_BLAKE2b::init($k, $outlen);
        ParagonIE_Sodium_Core32_BLAKE2b::update($ctx, $in, $in->count());

        /** @var SplFixedArray $out */
        $out = new SplFixedArray($outlen);
        $out = ParagonIE_Sodium_Core32_BLAKE2b::finish($ctx, $out);

        /** @var array<int, int> */
        $outArray = $out->toArray();
        return ParagonIE_Sodium_Core32_Util::intArrayToString($outArray);
    }

    /**
     * Finalize a BLAKE2b hashing context, returning the hash.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ctx
     * @param int $outlen
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_final($ctx, $outlen = 32)
    {
        if (!is_string($ctx)) {
            throw new TypeError('Context must be a string');
        }
        $out = new SplFixedArray($outlen);

        /** @var SplFixedArray $context */
        $context = ParagonIE_Sodium_Core32_BLAKE2b::stringToContext($ctx);

        /** @var SplFixedArray $out */
        $out = ParagonIE_Sodium_Core32_BLAKE2b::finish($context, $out);

        /** @var array<int, int> */
        $outArray = $out->toArray();
        return ParagonIE_Sodium_Core32_Util::intArrayToString($outArray);
    }

    /**
     * Initialize a hashing context for BLAKE2b.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $key
     * @param int $outputLength
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_init($key = '', $outputLength = 32)
    {
        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();

        $k = null;
        if (!empty($key)) {
            $k = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($key);
            if ($k->count() > ParagonIE_Sodium_Core32_BLAKE2b::KEYBYTES) {
                throw new RangeException('Invalid key size');
            }
        }

        /** @var SplFixedArray $ctx */
        $ctx = ParagonIE_Sodium_Core32_BLAKE2b::init($k, $outputLength);

        return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($ctx);
    }

    /**
     * Update a hashing context for BLAKE2b with $message
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ctx
     * @param string $message
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_update($ctx, $message)
    {
        // This ensures that ParagonIE_Sodium_Core32_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core32_BLAKE2b::pseudoConstructor();

        /** @var SplFixedArray $context */
        $context = ParagonIE_Sodium_Core32_BLAKE2b::stringToContext($ctx);

        /** @var SplFixedArray $in */
        $in = ParagonIE_Sodium_Core32_BLAKE2b::stringToSplFixedArray($message);

        ParagonIE_Sodium_Core32_BLAKE2b::update($context, $in, $in->count());

        return ParagonIE_Sodium_Core32_BLAKE2b::contextToString($context);
    }

    /**
     * Libsodium's crypto_kx().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $my_sk
     * @param string $their_pk
     * @param string $client_pk
     * @param string $server_pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function keyExchange($my_sk, $their_pk, $client_pk, $server_pk)
    {
        return self::generichash(
            self::scalarmult($my_sk, $their_pk) .
            $client_pk .
            $server_pk
        );
    }

    /**
     * ECDH over Curve25519
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @param string $pKey
     * @return string
     *
     * @throws SodiumException
     * @throws TypeError
     */
    public static function scalarmult($sKey, $pKey)
    {
        $q = ParagonIE_Sodium_Core32_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey);
        self::scalarmult_throw_if_zero($q);
        return $q;
    }

    /**
     * ECDH over Curve25519, using the basepoint.
     * Used to get a secret key from a public key.
     *
     * @param string $secret
     * @return string
     *
     * @throws SodiumException
     * @throws TypeError
     */
    public static function scalarmult_base($secret)
    {
        $q = ParagonIE_Sodium_Core32_X25519::crypto_scalarmult_curve25519_ref10_base($secret);
        self::scalarmult_throw_if_zero($q);
        return $q;
    }

    /**
     * This throws an Error if a zero public key was passed to the function.
     *
     * @param string $q
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function scalarmult_throw_if_zero($q)
    {
        $d = 0;
        for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) {
            $d |= ParagonIE_Sodium_Core32_Util::chrToInt($q[$i]);
        }

        /* branch-free variant of === 0 */
        if (-(1 & (($d - 1) >> 8))) {
            throw new SodiumException('Zero public key is not allowed');
        }
    }

    /**
     * XSalsa20-Poly1305 authenticated symmetric-key encryption.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox($plaintext, $nonce, $key)
    {
        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen = ParagonIE_Sodium_Core32_Util::strlen($plaintext);
        $mlen0 = $mlen;
        if ($mlen0 > 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES) {
            $mlen0 = 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
            $block0,
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
            $subkey
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core32_Util::substr(
            $block0,
            self::secretbox_xsalsa20poly1305_ZEROBYTES
        );
        if ($mlen > $mlen0) {
            $c .= ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
                ParagonIE_Sodium_Core32_Util::substr(
                    $plaintext,
                    self::secretbox_xsalsa20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
                1,
                $subkey
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            ParagonIE_Sodium_Core32_Util::substr(
                $block0,
                0,
                self::onetimeauth_poly1305_KEYBYTES
            )
        );
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }

        $state->update($c);

        /** @var string $c - MAC || ciphertext */
        $c = $state->finish() . $c;
        unset($state);

        return $c;
    }

    /**
     * Decrypt a ciphertext generated via secretbox().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_open($ciphertext, $nonce, $key)
    {
        /** @var string $mac */
        $mac = ParagonIE_Sodium_Core32_Util::substr(
            $ciphertext,
            0,
            self::secretbox_xsalsa20poly1305_MACBYTES
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core32_Util::substr(
            $ciphertext,
            self::secretbox_xsalsa20poly1305_MACBYTES
        );

        /** @var int $clen */
        $clen = ParagonIE_Sodium_Core32_Util::strlen($c);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
            64,
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
            $subkey
        );
        $verified = ParagonIE_Sodium_Core32_Poly1305::onetimeauth_verify(
            $mac,
            $c,
            ParagonIE_Sodium_Core32_Util::substr($block0, 0, 32)
        );
        if (!$verified) {
            try {
                ParagonIE_Sodium_Compat::memzero($subkey);
            } catch (SodiumException $ex) {
                $subkey = null;
            }
            throw new SodiumException('Invalid MAC');
        }

        /** @var string $m - Decrypted message */
        $m = ParagonIE_Sodium_Core32_Util::xorStrings(
            ParagonIE_Sodium_Core32_Util::substr($block0, self::secretbox_xsalsa20poly1305_ZEROBYTES),
            ParagonIE_Sodium_Core32_Util::substr($c, 0, self::secretbox_xsalsa20poly1305_ZEROBYTES)
        );
        if ($clen > self::secretbox_xsalsa20poly1305_ZEROBYTES) {
            // We had more than 1 block, so let's continue to decrypt the rest.
            $m .= ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
                ParagonIE_Sodium_Core32_Util::substr(
                    $c,
                    self::secretbox_xsalsa20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
                1,
                (string) $subkey
            );
        }
        return $m;
    }

    /**
     * XChaCha20-Poly1305 authenticated symmetric-key encryption.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_xchacha20poly1305($plaintext, $nonce, $key)
    {
        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core32_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen = ParagonIE_Sodium_Core32_Util::strlen($plaintext);
        $mlen0 = $mlen;
        if ($mlen0 > 64 - self::secretbox_xchacha20poly1305_ZEROBYTES) {
            $mlen0 = 64 - self::secretbox_xchacha20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
            $block0,
            $nonceLast,
            $subkey
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core32_Util::substr(
            $block0,
            self::secretbox_xchacha20poly1305_ZEROBYTES
        );
        if ($mlen > $mlen0) {
            $c .= ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
                ParagonIE_Sodium_Core32_Util::substr(
                    $plaintext,
                    self::secretbox_xchacha20poly1305_ZEROBYTES
                ),
                $nonceLast,
                $subkey,
                ParagonIE_Sodium_Core32_Util::store64_le(1)
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            ParagonIE_Sodium_Core32_Util::substr(
                $block0,
                0,
                self::onetimeauth_poly1305_KEYBYTES
            )
        );
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }

        $state->update($c);

        /** @var string $c - MAC || ciphertext */
        $c = $state->finish() . $c;
        unset($state);

        return $c;
    }

    /**
     * Decrypt a ciphertext generated via secretbox_xchacha20poly1305().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key)
    {
        /** @var string $mac */
        $mac = ParagonIE_Sodium_Core32_Util::substr(
            $ciphertext,
            0,
            self::secretbox_xchacha20poly1305_MACBYTES
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core32_Util::substr(
            $ciphertext,
            self::secretbox_xchacha20poly1305_MACBYTES
        );

        /** @var int $clen */
        $clen = ParagonIE_Sodium_Core32_Util::strlen($c);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HChaCha20::hchacha20($nonce, $key);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_ChaCha20::stream(
            64,
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
            $subkey
        );
        $verified = ParagonIE_Sodium_Core32_Poly1305::onetimeauth_verify(
            $mac,
            $c,
            ParagonIE_Sodium_Core32_Util::substr($block0, 0, 32)
        );

        if (!$verified) {
            try {
                ParagonIE_Sodium_Compat::memzero($subkey);
            } catch (SodiumException $ex) {
                $subkey = null;
            }
            throw new SodiumException('Invalid MAC');
        }

        /** @var string $m - Decrypted message */
        $m = ParagonIE_Sodium_Core32_Util::xorStrings(
            ParagonIE_Sodium_Core32_Util::substr($block0, self::secretbox_xchacha20poly1305_ZEROBYTES),
            ParagonIE_Sodium_Core32_Util::substr($c, 0, self::secretbox_xchacha20poly1305_ZEROBYTES)
        );

        if ($clen > self::secretbox_xchacha20poly1305_ZEROBYTES) {
            // We had more than 1 block, so let's continue to decrypt the rest.
            $m .= ParagonIE_Sodium_Core32_ChaCha20::streamXorIc(
                ParagonIE_Sodium_Core32_Util::substr(
                    $c,
                    self::secretbox_xchacha20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
                (string) $subkey,
                ParagonIE_Sodium_Core32_Util::store64_le(1)
            );
        }
        return $m;
    }

    /**
     * Detached Ed25519 signature.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_detached($message, $sk)
    {
        return ParagonIE_Sodium_Core32_Ed25519::sign_detached($message, $sk);
    }

    /**
     * Attached Ed25519 signature. (Returns a signed message.)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($message, $sk)
    {
        return ParagonIE_Sodium_Core32_Ed25519::sign($message, $sk);
    }

    /**
     * Opens a signed message. If valid, returns the message.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $signedMessage
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_open($signedMessage, $pk)
    {
        return ParagonIE_Sodium_Core32_Ed25519::sign_open($signedMessage, $pk);
    }

    /**
     * Verify a detached signature of a given message and public key.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $signature
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_verify_detached($signature, $message, $pk)
    {
        return ParagonIE_Sodium_Core32_Ed25519::verify_detached($signature, $message, $pk);
    }
}
vendor/paragonie/sodium_compat/src/Core/HChaCha20.php000064400000007437152177723700016450 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_HChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_HChaCha20
 */
class ParagonIE_Sodium_Core_HChaCha20 extends ParagonIE_Sodium_Core_ChaCha20
{
    /**
     * @param string $in
     * @param string $key
     * @param string|null $c
     * @return string
     * @throws TypeError
     */
    public static function hChaCha20($in = '', $key = '', $c = null)
    {
        $ctx = array();

        if ($c === null) {
            $ctx[0] = 0x61707865;
            $ctx[1] = 0x3320646e;
            $ctx[2] = 0x79622d32;
            $ctx[3] = 0x6b206574;
        } else {
            $ctx[0] = self::load_4(self::substr($c,  0, 4));
            $ctx[1] = self::load_4(self::substr($c,  4, 4));
            $ctx[2] = self::load_4(self::substr($c,  8, 4));
            $ctx[3] = self::load_4(self::substr($c, 12, 4));
        }
        $ctx[4]  = self::load_4(self::substr($key,  0, 4));
        $ctx[5]  = self::load_4(self::substr($key,  4, 4));
        $ctx[6]  = self::load_4(self::substr($key,  8, 4));
        $ctx[7]  = self::load_4(self::substr($key, 12, 4));
        $ctx[8]  = self::load_4(self::substr($key, 16, 4));
        $ctx[9]  = self::load_4(self::substr($key, 20, 4));
        $ctx[10] = self::load_4(self::substr($key, 24, 4));
        $ctx[11] = self::load_4(self::substr($key, 28, 4));
        $ctx[12] = self::load_4(self::substr($in,   0, 4));
        $ctx[13] = self::load_4(self::substr($in,   4, 4));
        $ctx[14] = self::load_4(self::substr($in,   8, 4));
        $ctx[15] = self::load_4(self::substr($in,  12, 4));
        return self::hChaCha20Bytes($ctx);
    }

    /**
     * @param array $ctx
     * @return string
     * @throws TypeError
     */
    protected static function hChaCha20Bytes(array $ctx)
    {
        $x0  = (int) $ctx[0];
        $x1  = (int) $ctx[1];
        $x2  = (int) $ctx[2];
        $x3  = (int) $ctx[3];
        $x4  = (int) $ctx[4];
        $x5  = (int) $ctx[5];
        $x6  = (int) $ctx[6];
        $x7  = (int) $ctx[7];
        $x8  = (int) $ctx[8];
        $x9  = (int) $ctx[9];
        $x10 = (int) $ctx[10];
        $x11 = (int) $ctx[11];
        $x12 = (int) $ctx[12];
        $x13 = (int) $ctx[13];
        $x14 = (int) $ctx[14];
        $x15 = (int) $ctx[15];

        for ($i = 0; $i < 10; ++$i) {
            # QUARTERROUND( x0,  x4,  x8,  x12)
            list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12);

            # QUARTERROUND( x1,  x5,  x9,  x13)
            list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13);

            # QUARTERROUND( x2,  x6,  x10,  x14)
            list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14);

            # QUARTERROUND( x3,  x7,  x11,  x15)
            list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15);

            # QUARTERROUND( x0,  x5,  x10,  x15)
            list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15);

            # QUARTERROUND( x1,  x6,  x11,  x12)
            list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12);

            # QUARTERROUND( x2,  x7,  x8,  x13)
            list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13);

            # QUARTERROUND( x3,  x4,  x9,  x14)
            list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14);
        }

        return self::store32_le((int) ($x0  & 0xffffffff)) .
            self::store32_le((int) ($x1  & 0xffffffff)) .
            self::store32_le((int) ($x2  & 0xffffffff)) .
            self::store32_le((int) ($x3  & 0xffffffff)) .
            self::store32_le((int) ($x12 & 0xffffffff)) .
            self::store32_le((int) ($x13 & 0xffffffff)) .
            self::store32_le((int) ($x14 & 0xffffffff)) .
            self::store32_le((int) ($x15 & 0xffffffff));
    }
}
vendor/paragonie/sodium_compat/src/Core/ChaCha20.php000064400000031206152177723700016327 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_ChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_ChaCha20
 */
class ParagonIE_Sodium_Core_ChaCha20 extends ParagonIE_Sodium_Core_Util
{
    /**
     * Bitwise left rotation
     *
     * @internal You should not use this directly from another application
     *
     * @param int $v
     * @param int $n
     * @return int
     */
    public static function rotate($v, $n)
    {
        $v &= 0xffffffff;
        $n &= 31;
        return (int) (
            0xffffffff & (
                ($v << $n)
                    |
                ($v >> (32 - $n))
            )
        );
    }

    /**
     * The ChaCha20 quarter round function. Works on four 32-bit integers.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $a
     * @param int $b
     * @param int $c
     * @param int $d
     * @return array<int, int>
     */
    protected static function quarterRound($a, $b, $c, $d)
    {
        # a = PLUS(a,b); d = ROTATE(XOR(d,a),16);
        /** @var int $a */
        $a = ($a + $b) & 0xffffffff;
        $d = self::rotate($d ^ $a, 16);

        # c = PLUS(c,d); b = ROTATE(XOR(b,c),12);
        /** @var int $c */
        $c = ($c + $d) & 0xffffffff;
        $b = self::rotate($b ^ $c, 12);

        # a = PLUS(a,b); d = ROTATE(XOR(d,a), 8);
        /** @var int $a */
        $a = ($a + $b) & 0xffffffff;
        $d = self::rotate($d ^ $a, 8);

        # c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
        /** @var int $c */
        $c = ($c + $d) & 0xffffffff;
        $b = self::rotate($b ^ $c, 7);
        return array((int) $a, (int) $b, (int) $c, (int) $d);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_ChaCha20_Ctx $ctx
     * @param string $message
     *
     * @return string
     * @throws TypeError
     * @throws SodiumException
     */
    public static function encryptBytes(
        ParagonIE_Sodium_Core_ChaCha20_Ctx $ctx,
        $message = ''
    ) {
        $bytes = self::strlen($message);

        /*
        j0 = ctx->input[0];
        j1 = ctx->input[1];
        j2 = ctx->input[2];
        j3 = ctx->input[3];
        j4 = ctx->input[4];
        j5 = ctx->input[5];
        j6 = ctx->input[6];
        j7 = ctx->input[7];
        j8 = ctx->input[8];
        j9 = ctx->input[9];
        j10 = ctx->input[10];
        j11 = ctx->input[11];
        j12 = ctx->input[12];
        j13 = ctx->input[13];
        j14 = ctx->input[14];
        j15 = ctx->input[15];
        */
        $j0  = (int) $ctx[0];
        $j1  = (int) $ctx[1];
        $j2  = (int) $ctx[2];
        $j3  = (int) $ctx[3];
        $j4  = (int) $ctx[4];
        $j5  = (int) $ctx[5];
        $j6  = (int) $ctx[6];
        $j7  = (int) $ctx[7];
        $j8  = (int) $ctx[8];
        $j9  = (int) $ctx[9];
        $j10 = (int) $ctx[10];
        $j11 = (int) $ctx[11];
        $j12 = (int) $ctx[12];
        $j13 = (int) $ctx[13];
        $j14 = (int) $ctx[14];
        $j15 = (int) $ctx[15];

        $c = '';
        for (;;) {
            if ($bytes < 64) {
                $message .= str_repeat("\x00", 64 - $bytes);
            }

            $x0 =  (int) $j0;
            $x1 =  (int) $j1;
            $x2 =  (int) $j2;
            $x3 =  (int) $j3;
            $x4 =  (int) $j4;
            $x5 =  (int) $j5;
            $x6 =  (int) $j6;
            $x7 =  (int) $j7;
            $x8 =  (int) $j8;
            $x9 =  (int) $j9;
            $x10 = (int) $j10;
            $x11 = (int) $j11;
            $x12 = (int) $j12;
            $x13 = (int) $j13;
            $x14 = (int) $j14;
            $x15 = (int) $j15;

            # for (i = 20; i > 0; i -= 2) {
            for ($i = 20; $i > 0; $i -= 2) {
                # QUARTERROUND( x0,  x4,  x8,  x12)
                list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12);

                # QUARTERROUND( x1,  x5,  x9,  x13)
                list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13);

                # QUARTERROUND( x2,  x6,  x10,  x14)
                list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14);

                # QUARTERROUND( x3,  x7,  x11,  x15)
                list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15);

                # QUARTERROUND( x0,  x5,  x10,  x15)
                list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15);

                # QUARTERROUND( x1,  x6,  x11,  x12)
                list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12);

                # QUARTERROUND( x2,  x7,  x8,  x13)
                list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13);

                # QUARTERROUND( x3,  x4,  x9,  x14)
                list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14);
            }
            /*
            x0 = PLUS(x0, j0);
            x1 = PLUS(x1, j1);
            x2 = PLUS(x2, j2);
            x3 = PLUS(x3, j3);
            x4 = PLUS(x4, j4);
            x5 = PLUS(x5, j5);
            x6 = PLUS(x6, j6);
            x7 = PLUS(x7, j7);
            x8 = PLUS(x8, j8);
            x9 = PLUS(x9, j9);
            x10 = PLUS(x10, j10);
            x11 = PLUS(x11, j11);
            x12 = PLUS(x12, j12);
            x13 = PLUS(x13, j13);
            x14 = PLUS(x14, j14);
            x15 = PLUS(x15, j15);
            */
            /** @var int $x0 */
            $x0  = ($x0 & 0xffffffff) + $j0;
            /** @var int $x1 */
            $x1  = ($x1 & 0xffffffff) + $j1;
            /** @var int $x2 */
            $x2  = ($x2 & 0xffffffff) + $j2;
            /** @var int $x3 */
            $x3  = ($x3 & 0xffffffff) + $j3;
            /** @var int $x4 */
            $x4  = ($x4 & 0xffffffff) + $j4;
            /** @var int $x5 */
            $x5  = ($x5 & 0xffffffff) + $j5;
            /** @var int $x6 */
            $x6  = ($x6 & 0xffffffff) + $j6;
            /** @var int $x7 */
            $x7  = ($x7 & 0xffffffff) + $j7;
            /** @var int $x8 */
            $x8  = ($x8 & 0xffffffff) + $j8;
            /** @var int $x9 */
            $x9  = ($x9 & 0xffffffff) + $j9;
            /** @var int $x10 */
            $x10 = ($x10 & 0xffffffff) + $j10;
            /** @var int $x11 */
            $x11 = ($x11 & 0xffffffff) + $j11;
            /** @var int $x12 */
            $x12 = ($x12 & 0xffffffff) + $j12;
            /** @var int $x13 */
            $x13 = ($x13 & 0xffffffff) + $j13;
            /** @var int $x14 */
            $x14 = ($x14 & 0xffffffff) + $j14;
            /** @var int $x15 */
            $x15 = ($x15 & 0xffffffff) + $j15;

            /*
            x0 = XOR(x0, LOAD32_LE(m + 0));
            x1 = XOR(x1, LOAD32_LE(m + 4));
            x2 = XOR(x2, LOAD32_LE(m + 8));
            x3 = XOR(x3, LOAD32_LE(m + 12));
            x4 = XOR(x4, LOAD32_LE(m + 16));
            x5 = XOR(x5, LOAD32_LE(m + 20));
            x6 = XOR(x6, LOAD32_LE(m + 24));
            x7 = XOR(x7, LOAD32_LE(m + 28));
            x8 = XOR(x8, LOAD32_LE(m + 32));
            x9 = XOR(x9, LOAD32_LE(m + 36));
            x10 = XOR(x10, LOAD32_LE(m + 40));
            x11 = XOR(x11, LOAD32_LE(m + 44));
            x12 = XOR(x12, LOAD32_LE(m + 48));
            x13 = XOR(x13, LOAD32_LE(m + 52));
            x14 = XOR(x14, LOAD32_LE(m + 56));
            x15 = XOR(x15, LOAD32_LE(m + 60));
            */
            $x0  ^= self::load_4(self::substr($message, 0, 4));
            $x1  ^= self::load_4(self::substr($message, 4, 4));
            $x2  ^= self::load_4(self::substr($message, 8, 4));
            $x3  ^= self::load_4(self::substr($message, 12, 4));
            $x4  ^= self::load_4(self::substr($message, 16, 4));
            $x5  ^= self::load_4(self::substr($message, 20, 4));
            $x6  ^= self::load_4(self::substr($message, 24, 4));
            $x7  ^= self::load_4(self::substr($message, 28, 4));
            $x8  ^= self::load_4(self::substr($message, 32, 4));
            $x9  ^= self::load_4(self::substr($message, 36, 4));
            $x10 ^= self::load_4(self::substr($message, 40, 4));
            $x11 ^= self::load_4(self::substr($message, 44, 4));
            $x12 ^= self::load_4(self::substr($message, 48, 4));
            $x13 ^= self::load_4(self::substr($message, 52, 4));
            $x14 ^= self::load_4(self::substr($message, 56, 4));
            $x15 ^= self::load_4(self::substr($message, 60, 4));

            /*
                j12 = PLUSONE(j12);
                if (!j12) {
                    j13 = PLUSONE(j13);
                }
             */
            ++$j12;
            if ($j12 & 0xf0000000) {
                throw new SodiumException('Overflow');
            }

            /*
            STORE32_LE(c + 0, x0);
            STORE32_LE(c + 4, x1);
            STORE32_LE(c + 8, x2);
            STORE32_LE(c + 12, x3);
            STORE32_LE(c + 16, x4);
            STORE32_LE(c + 20, x5);
            STORE32_LE(c + 24, x6);
            STORE32_LE(c + 28, x7);
            STORE32_LE(c + 32, x8);
            STORE32_LE(c + 36, x9);
            STORE32_LE(c + 40, x10);
            STORE32_LE(c + 44, x11);
            STORE32_LE(c + 48, x12);
            STORE32_LE(c + 52, x13);
            STORE32_LE(c + 56, x14);
            STORE32_LE(c + 60, x15);
            */
            $block = self::store32_le((int) ($x0  & 0xffffffff)) .
                 self::store32_le((int) ($x1  & 0xffffffff)) .
                 self::store32_le((int) ($x2  & 0xffffffff)) .
                 self::store32_le((int) ($x3  & 0xffffffff)) .
                 self::store32_le((int) ($x4  & 0xffffffff)) .
                 self::store32_le((int) ($x5  & 0xffffffff)) .
                 self::store32_le((int) ($x6  & 0xffffffff)) .
                 self::store32_le((int) ($x7  & 0xffffffff)) .
                 self::store32_le((int) ($x8  & 0xffffffff)) .
                 self::store32_le((int) ($x9  & 0xffffffff)) .
                 self::store32_le((int) ($x10 & 0xffffffff)) .
                 self::store32_le((int) ($x11 & 0xffffffff)) .
                 self::store32_le((int) ($x12 & 0xffffffff)) .
                 self::store32_le((int) ($x13 & 0xffffffff)) .
                 self::store32_le((int) ($x14 & 0xffffffff)) .
                 self::store32_le((int) ($x15 & 0xffffffff));

            /* Partial block */
            if ($bytes < 64) {
                $c .= self::substr($block, 0, $bytes);
                break;
            }

            /* Full block */
            $c .= $block;
            $bytes -= 64;
            if ($bytes <= 0) {
                break;
            }
            $message = self::substr($message, 64);
        }
        /* end for(;;) loop */

        $ctx[12] = $j12;
        $ctx[13] = $j13;
        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function stream($len = 64, $nonce = '', $key = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_Ctx($key, $nonce),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ietfStream($len, $nonce = '', $key = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_IetfCtx($key, $nonce),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ietfStreamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_IetfCtx($key, $nonce, $ic),
            $message
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function streamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_Ctx($key, $nonce, $ic),
            $message
        );
    }
}
vendor/paragonie/sodium_compat/src/Core/XChaCha20.php000064400000003343152177723700016460 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_XChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_XChaCha20
 */
class ParagonIE_Sodium_Core_XChaCha20 extends ParagonIE_Sodium_Core_HChaCha20
{
    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function stream($len = 64, $nonce = '', $key = '')
    {
        if (self::strlen($nonce) !== 24) {
            throw new SodiumException('Nonce must be 24 bytes long');
        }
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_Ctx(
                self::hChaCha20(
                    self::substr($nonce, 0, 16),
                    $key
                ),
                self::substr($nonce, 16, 8)
            ),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function streamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        if (self::strlen($nonce) !== 24) {
            throw new SodiumException('Nonce must be 24 bytes long');
        }
        return self::encryptBytes(
            new ParagonIE_Sodium_Core_ChaCha20_Ctx(
                self::hChaCha20(self::substr($nonce, 0, 16), $key),
                self::substr($nonce, 16, 8),
                $ic
            ),
            $message
        );
    }
}
vendor/paragonie/sodium_compat/src/Core/Xsalsa20.php000064400000002533152177723700016454 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_XSalsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_XSalsa20
 */
abstract class ParagonIE_Sodium_Core_XSalsa20 extends ParagonIE_Sodium_Core_HSalsa20
{
    /**
     * Expand a key and nonce into an xsalsa20 keystream.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function xsalsa20($len, $nonce, $key)
    {
        $ret = self::salsa20(
            $len,
            self::substr($nonce, 16, 8),
            self::hsalsa20($nonce, $key)
        );
        return $ret;
    }

    /**
     * Encrypt a string with XSalsa20. Doesn't provide integrity.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function xsalsa20_xor($message, $nonce, $key)
    {
        return self::xorStrings(
            $message,
            self::xsalsa20(
                self::strlen($message),
                $nonce,
                $key
            )
        );
    }
}
vendor/paragonie/sodium_compat/src/Core/ChaCha20/IetfCtx.php000064400000002452152177723700017676 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_ChaCha20_IetfCtx', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_ChaCha20_IetfCtx
 */
class ParagonIE_Sodium_Core_ChaCha20_IetfCtx extends ParagonIE_Sodium_Core_ChaCha20_Ctx
{
    /**
     * ParagonIE_Sodium_Core_ChaCha20_IetfCtx constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key     ChaCha20 key.
     * @param string $iv      Initialization Vector (a.k.a. nonce).
     * @param string $counter The initial counter value.
     *                        Defaults to 4 0x00 bytes.
     * @throws InvalidArgumentException
     * @throws TypeError
     */
    public function __construct($key = '', $iv = '', $counter = '')
    {
        if (self::strlen($iv) !== 12) {
            throw new InvalidArgumentException('ChaCha20 expects a 96-bit nonce in IETF mode.');
        }
        parent::__construct($key, self::substr($iv, 0, 8), $counter);

        if (!empty($counter)) {
            $this->container[12] = self::load_4(self::substr($counter, 0, 4));
        }
        $this->container[13] = self::load_4(self::substr($iv, 0, 4));
        $this->container[14] = self::load_4(self::substr($iv, 4, 4));
        $this->container[15] = self::load_4(self::substr($iv, 8, 4));
    }
}
vendor/paragonie/sodium_compat/src/Core/ChaCha20/Ctx.php000064400000007444152177723700017074 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_ChaCha20_Ctx', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_ChaCha20_Ctx
 */
class ParagonIE_Sodium_Core_ChaCha20_Ctx extends ParagonIE_Sodium_Core_Util implements ArrayAccess
{
    /**
     * @var SplFixedArray internally, <int, int>
     */
    protected $container;

    /**
     * ParagonIE_Sodium_Core_ChaCha20_Ctx constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key     ChaCha20 key.
     * @param string $iv      Initialization Vector (a.k.a. nonce).
     * @param string $counter The initial counter value.
     *                        Defaults to 8 0x00 bytes.
     * @throws InvalidArgumentException
     * @throws TypeError
     */
    public function __construct($key = '', $iv = '', $counter = '')
    {
        if (self::strlen($key) !== 32) {
            throw new InvalidArgumentException('ChaCha20 expects a 256-bit key.');
        }
        if (self::strlen($iv) !== 8) {
            throw new InvalidArgumentException('ChaCha20 expects a 64-bit nonce.');
        }
        $this->container = new SplFixedArray(16);

        /* "expand 32-byte k" as per ChaCha20 spec */
        $this->container[0]  = 0x61707865;
        $this->container[1]  = 0x3320646e;
        $this->container[2]  = 0x79622d32;
        $this->container[3]  = 0x6b206574;
        $this->container[4]  = self::load_4(self::substr($key, 0, 4));
        $this->container[5]  = self::load_4(self::substr($key, 4, 4));
        $this->container[6]  = self::load_4(self::substr($key, 8, 4));
        $this->container[7]  = self::load_4(self::substr($key, 12, 4));
        $this->container[8]  = self::load_4(self::substr($key, 16, 4));
        $this->container[9]  = self::load_4(self::substr($key, 20, 4));
        $this->container[10] = self::load_4(self::substr($key, 24, 4));
        $this->container[11] = self::load_4(self::substr($key, 28, 4));

        if (empty($counter)) {
            $this->container[12] = 0;
            $this->container[13] = 0;
        } else {
            $this->container[12] = self::load_4(self::substr($counter, 0, 4));
            $this->container[13] = self::load_4(self::substr($counter, 4, 4));
        }
        $this->container[14] = self::load_4(self::substr($iv, 0, 4));
        $this->container[15] = self::load_4(self::substr($iv, 4, 4));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $offset
     * @param int $value
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetSet($offset, $value)
    {
        if (!is_int($offset)) {
            throw new InvalidArgumentException('Expected an integer');
        }
        if (!is_int($value)) {
            throw new InvalidArgumentException('Expected an integer');
        }
        $this->container[$offset] = $value;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return bool
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return mixed|null
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetGet($offset)
    {
        return isset($this->container[$offset])
            ? $this->container[$offset]
            : null;
    }
}
vendor/paragonie/sodium_compat/src/Core/Util.php000064400000065445152177723700016007 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Util', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Util
 */
abstract class ParagonIE_Sodium_Core_Util
{
    /**
     * @param int $integer
     * @param int $size (16, 32, 64)
     * @return int
     */
    public static function abs($integer, $size = 0)
    {
        /** @var int $realSize */
        $realSize = (PHP_INT_SIZE << 3) - 1;
        if ($size) {
            --$size;
        } else {
            /** @var int $size */
            $size = $realSize;
        }

        $negative = -(($integer >> $size) & 1);
        return (int) (
            ($integer ^ $negative)
                +
            (($negative >> $realSize) & 1)
        );
    }

    /**
     * Convert a binary string into a hexadecimal string without cache-timing
     * leaks
     *
     * @internal You should not use this directly from another application
     *
     * @param string $binaryString (raw binary)
     * @return string
     * @throws TypeError
     */
    public static function bin2hex($binaryString)
    {
        /* Type checks: */
        if (!is_string($binaryString)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($binaryString) . ' given.');
        }

        $hex = '';
        $len = self::strlen($binaryString);
        for ($i = 0; $i < $len; ++$i) {
            /** @var array<int, int> $chunk */
            $chunk = unpack('C', $binaryString[$i]);
            /** @var int $c */
            $c = $chunk[1] & 0xf;
            /** @var int $b */
            $b = $chunk[1] >> 4;
            $hex .= pack(
                'CC',
                (87 + $b + ((($b - 10) >> 8) & ~38)),
                (87 + $c + ((($c - 10) >> 8) & ~38))
            );
        }
        return $hex;
    }

    /**
     * Convert a binary string into a hexadecimal string without cache-timing
     * leaks, returning uppercase letters (as per RFC 4648)
     *
     * @internal You should not use this directly from another application
     *
     * @param string $bin_string (raw binary)
     * @return string
     * @throws TypeError
     */
    public static function bin2hexUpper($bin_string)
    {
        $hex = '';
        $len = self::strlen($bin_string);
        for ($i = 0; $i < $len; ++$i) {
            /** @var array<int, int> $chunk */
            $chunk = unpack('C', $bin_string[$i]);
            /**
             * Lower 16 bits
             *
             * @var int $c
             */
            $c = $chunk[1] & 0xf;

            /**
             * Upper 16 bits
             * @var int $b
             */
            $b = $chunk[1] >> 4;

            /**
             * Use pack() and binary operators to turn the two integers
             * into hexadecimal characters. We don't use chr() here, because
             * it uses a lookup table internally and we want to avoid
             * cache-timing side-channels.
             */
            $hex .= pack(
                'CC',
                (55 + $b + ((($b - 10) >> 8) & ~6)),
                (55 + $c + ((($c - 10) >> 8) & ~6))
            );
        }
        return $hex;
    }

    /**
     * Cache-timing-safe variant of ord()
     *
     * @internal You should not use this directly from another application
     *
     * @param string $chr
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    public static function chrToInt($chr)
    {
        /* Type checks: */
        if (!is_string($chr)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($chr) . ' given.');
        }
        if (self::strlen($chr) !== 1) {
            throw new SodiumException('chrToInt() expects a string that is exactly 1 character long');
        }
        /** @var array<int, int> $chunk */
        $chunk = unpack('C', $chr);
        return (int) ($chunk[1]);
    }

    /**
     * Compares two strings.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $left
     * @param string $right
     * @param int $len
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    public static function compare($left, $right, $len = null)
    {
        $leftLen = self::strlen($left);
        $rightLen = self::strlen($right);
        if ($len === null) {
            $len = max($leftLen, $rightLen);
            $left = str_pad($left, $len, "\x00", STR_PAD_RIGHT);
            $right = str_pad($right, $len, "\x00", STR_PAD_RIGHT);
        }

        $gt = 0;
        $eq = 1;
        $i = $len;
        while ($i !== 0) {
            --$i;
            $gt |= ((self::chrToInt($right[$i]) - self::chrToInt($left[$i])) >> 8) & $eq;
            $eq &= ((self::chrToInt($right[$i]) ^ self::chrToInt($left[$i])) - 1) >> 8;
        }
        return ($gt + $gt + $eq) - 1;
    }

    /**
     * If a variable does not match a given type, throw a TypeError.
     *
     * @param mixed $mixedVar
     * @param string $type
     * @param int $argumentIndex
     * @throws TypeError
     * @throws SodiumException
     * @return void
     */
    public static function declareScalarType(&$mixedVar = null, $type = 'void', $argumentIndex = 0)
    {
        if (func_num_args() === 0) {
            /* Tautology, by default */
            return;
        }
        if (func_num_args() === 1) {
            throw new TypeError('Declared void, but passed a variable');
        }
        $realType = strtolower(gettype($mixedVar));
        $type = strtolower($type);
        switch ($type) {
            case 'null':
                if ($mixedVar !== null) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be null, ' . $realType . ' given.');
                }
                break;
            case 'integer':
            case 'int':
                $allow = array('int', 'integer');
                if (!in_array($type, $allow)) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be an integer, ' . $realType . ' given.');
                }
                $mixedVar = (int) $mixedVar;
                break;
            case 'boolean':
            case 'bool':
                $allow = array('bool', 'boolean');
                if (!in_array($type, $allow)) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be a boolean, ' . $realType . ' given.');
                }
                $mixedVar = (bool) $mixedVar;
                break;
            case 'string':
                if (!is_string($mixedVar)) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be a string, ' . $realType . ' given.');
                }
                $mixedVar = (string) $mixedVar;
                break;
            case 'decimal':
            case 'double':
            case 'float':
                $allow = array('decimal', 'double', 'float');
                if (!in_array($type, $allow)) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be a float, ' . $realType . ' given.');
                }
                $mixedVar = (float) $mixedVar;
                break;
            case 'object':
                if (!is_object($mixedVar)) {
                    throw new TypeError('Argument ' . $argumentIndex . ' must be an object, ' . $realType . ' given.');
                }
                break;
            case 'array':
                if (!is_array($mixedVar)) {
                    if (is_object($mixedVar)) {
                        if ($mixedVar instanceof ArrayAccess) {
                            return;
                        }
                    }
                    throw new TypeError('Argument ' . $argumentIndex . ' must be an array, ' . $realType . ' given.');
                }
                break;
            default:
                throw new SodiumException('Unknown type (' . $realType .') does not match expect type (' . $type . ')');
        }
    }

    /**
     * Evaluate whether or not two strings are equal (in constant-time)
     *
     * @param string $left
     * @param string $right
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function hashEquals($left, $right)
    {
        /* Type checks: */
        if (!is_string($left)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($left) . ' given.');
        }
        if (!is_string($right)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($right) . ' given.');
        }

        if (is_callable('hash_equals')) {
            return hash_equals($left, $right);
        }
        $d = 0;
        /** @var int $len */
        $len = self::strlen($left);
        if ($len !== self::strlen($right)) {
            return false;
        }
        for ($i = 0; $i < $len; ++$i) {
            $d |= self::chrToInt($left[$i]) ^ self::chrToInt($right[$i]);
        }

        if ($d !== 0) {
            return false;
        }
        return $left === $right;
    }

    /**
     * Convert a hexadecimal string into a binary string without cache-timing
     * leaks
     *
     * @internal You should not use this directly from another application
     *
     * @param string $hexString
     * @param bool $strictPadding
     * @return string (raw binary)
     * @throws RangeException
     * @throws TypeError
     */
    public static function hex2bin($hexString, $strictPadding = false)
    {
        /* Type checks: */
        if (!is_string($hexString)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($hexString) . ' given.');
        }

        /** @var int $hex_pos */
        $hex_pos = 0;
        /** @var string $bin */
        $bin = '';
        /** @var int $c_acc */
        $c_acc = 0;
        /** @var int $hex_len */
        $hex_len = self::strlen($hexString);
        /** @var int $state */
        $state = 0;
        if (($hex_len & 1) !== 0) {
            if ($strictPadding) {
                throw new RangeException(
                    'Expected an even number of hexadecimal characters'
                );
            } else {
                $hexString = '0' . $hexString;
                ++$hex_len;
            }
        }

        $chunk = unpack('C*', $hexString);
        while ($hex_pos < $hex_len) {
            ++$hex_pos;
            /** @var int $c */
            $c = $chunk[$hex_pos];
            /** @var int $c_num */
            $c_num = $c ^ 48;
            /** @var int $c_num0 */
            $c_num0 = ($c_num - 10) >> 8;
            /** @var int $c_alpha */
            $c_alpha = ($c & ~32) - 55;
            /** @var int $c_alpha0 */
            $c_alpha0 = (($c_alpha - 10) ^ ($c_alpha - 16)) >> 8;
            if (($c_num0 | $c_alpha0) === 0) {
                throw new RangeException(
                    'hex2bin() only expects hexadecimal characters'
                );
            }
            /** @var int $c_val */
            $c_val = ($c_num0 & $c_num) | ($c_alpha & $c_alpha0);
            if ($state === 0) {
                $c_acc = $c_val * 16;
            } else {
                $bin .= pack('C', $c_acc | $c_val);
            }
            $state ^= 1;
        }
        return $bin;
    }

    /**
     * Turn an array of integers into a string
     *
     * @internal You should not use this directly from another application
     *
     * @param array<int, int> $ints
     * @return string
     */
    public static function intArrayToString(array $ints)
    {
        /** @var array<int, int> $args */
        $args = $ints;
        foreach ($args as $i => $v) {
            $args[$i] = (int) ($v & 0xff);
        }
        array_unshift($args, str_repeat('C', count($ints)));
        return (string) (call_user_func_array('pack', $args));
    }

    /**
     * Cache-timing-safe variant of ord()
     *
     * @internal You should not use this directly from another application
     *
     * @param int $int
     * @return string
     * @throws TypeError
     */
    public static function intToChr($int)
    {
        return pack('C', $int);
    }

    /**
     * Load a 3 character substring into an integer
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return int
     * @throws RangeException
     * @throws TypeError
     */
    public static function load_3($string)
    {
        /* Type checks: */
        if (!is_string($string)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($string) < 3) {
            throw new RangeException(
                'String must be 3 bytes or more; ' . self::strlen($string) . ' given.'
            );
        }
        /** @var array<int, int> $unpacked */
        $unpacked = unpack('V', $string . "\0");
        return (int) ($unpacked[1] & 0xffffff);
    }

    /**
     * Load a 4 character substring into an integer
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return int
     * @throws RangeException
     * @throws TypeError
     */
    public static function load_4($string)
    {
        /* Type checks: */
        if (!is_string($string)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($string) < 4) {
            throw new RangeException(
                'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
            );
        }
        /** @var array<int, int> $unpacked */
        $unpacked = unpack('V', $string);
        return (int) ($unpacked[1] & 0xffffffff);
    }

    /**
     * Load a 8 character substring into an integer
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return int
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function load64_le($string)
    {
        /* Type checks: */
        if (!is_string($string)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($string) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($string) < 4) {
            throw new RangeException(
                'String must be 4 bytes or more; ' . self::strlen($string) . ' given.'
            );
        }
        if (PHP_VERSION_ID >= 50603 && PHP_INT_SIZE === 8) {
            /** @var array<int, int> $unpacked */
            $unpacked = unpack('P', $string);
            return (int) $unpacked[1];
        }

        /** @var int $result */
        $result  = (self::chrToInt($string[0]) & 0xff);
        $result |= (self::chrToInt($string[1]) & 0xff) <<  8;
        $result |= (self::chrToInt($string[2]) & 0xff) << 16;
        $result |= (self::chrToInt($string[3]) & 0xff) << 24;
        $result |= (self::chrToInt($string[4]) & 0xff) << 32;
        $result |= (self::chrToInt($string[5]) & 0xff) << 40;
        $result |= (self::chrToInt($string[6]) & 0xff) << 48;
        $result |= (self::chrToInt($string[7]) & 0xff) << 56;
        return (int) $result;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $left
     * @param string $right
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    public static function memcmp($left, $right)
    {
        if (self::hashEquals($left, $right)) {
            return 0;
        }
        return -1;
    }

    /**
     * Multiply two integers in constant-time
     *
     * Micro-architecture timing side-channels caused by how your CPU
     * implements multiplication are best prevented by never using the
     * multiplication operators and ensuring that our code always takes
     * the same number of operations to complete, regardless of the values
     * of $a and $b.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $a
     * @param int $b
     * @param int $size Limits the number of operations (useful for small,
     *                  constant operands)
     * @return int
     */
    public static function mul($a, $b, $size = 0)
    {
        if (ParagonIE_Sodium_Compat::$fastMult) {
            return (int) ($a * $b);
        }

        static $defaultSize = null;
        /** @var int $defaultSize */
        if (!$defaultSize) {
            /** @var int $defaultSize */
            $defaultSize = (PHP_INT_SIZE << 3) - 1;
        }
        if ($size < 1) {
            /** @var int $size */
            $size = $defaultSize;
        }
        /** @var int $size */

        $c = 0;

        /**
         * Mask is either -1 or 0.
         *
         * -1 in binary looks like 0x1111 ... 1111
         *  0 in binary looks like 0x0000 ... 0000
         *
         * @var int
         */
        $mask = -(($b >> ((int) $defaultSize)) & 1);

        /**
         * Ensure $b is a positive integer, without creating
         * a branching side-channel
         *
         * @var int $b
         */
        $b = ($b & ~$mask) | ($mask & -$b);

        /**
         * Unless $size is provided:
         *
         * This loop always runs 32 times when PHP_INT_SIZE is 4.
         * This loop always runs 64 times when PHP_INT_SIZE is 8.
         */
        for ($i = $size; $i >= 0; --$i) {
            $c += (int) ($a & -($b & 1));
            $a <<= 1;
            $b >>= 1;
        }

        /**
         * If $b was negative, we then apply the same value to $c here.
         * It doesn't matter much if $a was negative; the $c += above would
         * have produced a negative integer to begin with. But a negative $b
         * makes $b >>= 1 never return 0, so we would end up with incorrect
         * results.
         *
         * The end result is what we'd expect from integer multiplication.
         */
        return (int) (($c & ~$mask) | ($mask & -$c));
    }

    /**
     * Convert any arbitrary numbers into two 32-bit integers that represent
     * a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int|float $num
     * @return array<int, int>
     */
    public static function numericTo64BitInteger($num)
    {
        $high = 0;
        /** @var int $low */
        $low = $num & 0xffffffff;

        if ((+(abs($num))) >= 1) {
            if ($num > 0) {
                /** @var int $high */
                $high = min((+(floor($num/4294967296))), 4294967295);
            } else {
                /** @var int $high */
                $high = ~~((+(ceil(($num - (+((~~($num)))))/4294967296))));
            }
        }
        return array((int) $high, (int) $low);
    }

    /**
     * Store a 24-bit integer into a string, treating it as big-endian.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $int
     * @return string
     * @throws TypeError
     */
    public static function store_3($int)
    {
        /* Type checks: */
        if (!is_int($int)) {
            if (is_numeric($int)) {
                $int = (int) $int;
            } else {
                throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
            }
        }
        /** @var string $packed */
        $packed = pack('N', $int);
        return self::substr($packed, 1, 3);
    }

    /**
     * Store a 32-bit integer into a string, treating it as little-endian.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $int
     * @return string
     * @throws TypeError
     */
    public static function store32_le($int)
    {
        /* Type checks: */
        if (!is_int($int)) {
            if (is_numeric($int)) {
                $int = (int) $int;
            } else {
                throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
            }
        }

        /** @var string $packed */
        $packed = pack('V', $int);
        return $packed;
    }

    /**
     * Store a 32-bit integer into a string, treating it as big-endian.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $int
     * @return string
     * @throws TypeError
     */
    public static function store_4($int)
    {
        /* Type checks: */
        if (!is_int($int)) {
            if (is_numeric($int)) {
                $int = (int) $int;
            } else {
                throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
            }
        }

        /** @var string $packed */
        $packed = pack('N', $int);
        return $packed;
    }

    /**
     * Stores a 64-bit integer as an string, treating it as little-endian.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $int
     * @return string
     * @throws TypeError
     */
    public static function store64_le($int)
    {
        /* Type checks: */
        if (!is_int($int)) {
            if (is_numeric($int)) {
                $int = (int) $int;
            } else {
                throw new TypeError('Argument 1 must be an integer, ' . gettype($int) . ' given.');
            }
        }

        if (PHP_INT_SIZE === 8) {
            if (PHP_VERSION_ID >= 50603) {
                /** @var string $packed */
                $packed = pack('P', $int);
                return $packed;
            }
            return self::intToChr($int & 0xff) .
                self::intToChr(($int >>  8) & 0xff) .
                self::intToChr(($int >> 16) & 0xff) .
                self::intToChr(($int >> 24) & 0xff) .
                self::intToChr(($int >> 32) & 0xff) .
                self::intToChr(($int >> 40) & 0xff) .
                self::intToChr(($int >> 48) & 0xff) .
                self::intToChr(($int >> 56) & 0xff);
        }
        if ($int > PHP_INT_MAX) {
            list($hiB, $int) = self::numericTo64BitInteger($int);
        } else {
            $hiB = 0;
        }
        return
            self::intToChr(($int      ) & 0xff) .
            self::intToChr(($int >>  8) & 0xff) .
            self::intToChr(($int >> 16) & 0xff) .
            self::intToChr(($int >> 24) & 0xff) .
            self::intToChr($hiB & 0xff) .
            self::intToChr(($hiB >>  8) & 0xff) .
            self::intToChr(($hiB >> 16) & 0xff) .
            self::intToChr(($hiB >> 24) & 0xff);
    }

    /**
     * Safe string length
     *
     * @internal You should not use this directly from another application
     *
     * @ref mbstring.func_overload
     *
     * @param string $str
     * @return int
     * @throws TypeError
     */
    public static function strlen($str)
    {
        /* Type checks: */
        if (!is_string($str)) {
            throw new TypeError('String expected');
        }

        return (int) (
        self::isMbStringOverride()
            ? mb_strlen($str, '8bit')
            : strlen($str)
        );
    }

    /**
     * Turn a string into an array of integers
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return array<int, int>
     * @throws TypeError
     */
    public static function stringToIntArray($string)
    {
        if (!is_string($string)) {
            throw new TypeError('String expected');
        }
        /**
         * @var array<int, int>
         */
        $values = array_values(
            unpack('C*', $string)
        );
        return $values;
    }

    /**
     * Safe substring
     *
     * @internal You should not use this directly from another application
     *
     * @ref mbstring.func_overload
     *
     * @param string $str
     * @param int $start
     * @param int $length
     * @return string
     * @throws TypeError
     */
    public static function substr($str, $start = 0, $length = null)
    {
        /* Type checks: */
        if (!is_string($str)) {
            throw new TypeError('String expected');
        }

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

        if (self::isMbStringOverride()) {
            if (PHP_VERSION_ID < 50400 && $length === null) {
                $length = self::strlen($str);
            }
            $sub = (string) mb_substr($str, $start, $length, '8bit');
        } elseif ($length === null) {
            $sub = (string) substr($str, $start);
        } else {
            $sub = (string) substr($str, $start, $length);
        }
        if (isset($sub)) {
            return $sub;
        }
        return '';
    }

    /**
     * Compare a 16-character byte string in constant time.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param string $b
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function verify_16($a, $b)
    {
        /* Type checks: */
        if (!is_string($a)) {
            throw new TypeError('String expected');
        }
        if (!is_string($b)) {
            throw new TypeError('String expected');
        }
        return self::hashEquals(
            self::substr($a, 0, 16),
            self::substr($b, 0, 16)
        );
    }

    /**
     * Compare a 32-character byte string in constant time.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param string $b
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function verify_32($a, $b)
    {
        /* Type checks: */
        if (!is_string($a)) {
            throw new TypeError('String expected');
        }
        if (!is_string($b)) {
            throw new TypeError('String expected');
        }
        return self::hashEquals(
            self::substr($a, 0, 32),
            self::substr($b, 0, 32)
        );
    }

    /**
     * Calculate $a ^ $b for two strings.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param string $b
     * @return string
     * @throws TypeError
     */
    public static function xorStrings($a, $b)
    {
        /* Type checks: */
        if (!is_string($a)) {
            throw new TypeError('Argument 1 must be a string');
        }
        if (!is_string($b)) {
            throw new TypeError('Argument 2 must be a string');
        }

        return (string) ($a ^ $b);
    }

    /**
     * Returns whether or not mbstring.func_overload is in effect.
     *
     * @internal You should not use this directly from another application
     *
     * @return bool
     */
    protected static function isMbStringOverride()
    {
        static $mbstring = null;

        if ($mbstring === null) {
            $mbstring = extension_loaded('mbstring')
                &&
            ((int) (ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING);
        }
        /** @var bool $mbstring */

        return $mbstring;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519.php000064400000315762152177723700016564 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519
 *
 * Implements Curve25519 core functions
 *
 * Based on the ref10 curve25519 code provided by libsodium
 *
 * @ref https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c
 */
abstract class ParagonIE_Sodium_Core_Curve25519 extends ParagonIE_Sodium_Core_Curve25519_H
{
    /**
     * Get a field element of size 10 with a value of 0
     *
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_0()
    {
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
        );
    }

    /**
     * Get a field element of size 10 with a value of 1
     *
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_1()
    {
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(1, 0, 0, 0, 0, 0, 0, 0, 0, 0)
        );
    }

    /**
     * Add two field elements.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     */
    public static function fe_add(
        ParagonIE_Sodium_Core_Curve25519_Fe $f,
        ParagonIE_Sodium_Core_Curve25519_Fe $g
    ) {
        /** @var array<int, int> $arr */
        $arr = array();
        for ($i = 0; $i < 10; ++$i) {
            $arr[$i] = (int) ($f[$i] + $g[$i]);
        }
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($arr);
    }

    /**
     * Constant-time conditional move.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $g
     * @param int $b
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     * @psalm-suppress MixedAssignment
     */
    public static function fe_cmov(
        ParagonIE_Sodium_Core_Curve25519_Fe $f,
        ParagonIE_Sodium_Core_Curve25519_Fe $g,
        $b = 0
    ) {
        /** @var array<int, int> $h */
        $h = array();
        $b *= -1;
        for ($i = 0; $i < 10; ++$i) {
            /** @var int $x */
            $x = (($f[$i] ^ $g[$i]) & $b);
            $h[$i] = (int) ((int) ($f[$i]) ^ $x);
        }
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($h);
    }

    /**
     * Create a copy of a field element.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_copy(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $h = clone $f;
        return $h;
    }

    /**
     * Give: 32-byte string.
     * Receive: A field element object to use for internal calculations.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     * @throws RangeException
     * @throws TypeError
     */
    public static function fe_frombytes($s)
    {
        if (self::strlen($s) !== 32) {
            throw new RangeException('Expected a 32-byte string.');
        }
        /** @var int $h0 */
        $h0 = self::load_4($s);
        /** @var int $h1 */
        $h1 = self::load_3(self::substr($s, 4, 3)) << 6;
        /** @var int $h2 */
        $h2 = self::load_3(self::substr($s, 7, 3)) << 5;
        /** @var int $h3 */
        $h3 = self::load_3(self::substr($s, 10, 3)) << 3;
        /** @var int $h4 */
        $h4 = self::load_3(self::substr($s, 13, 3)) << 2;
        /** @var int $h5 */
        $h5 = self::load_4(self::substr($s, 16, 4));
        /** @var int $h6 */
        $h6 = self::load_3(self::substr($s, 20, 3)) << 7;
        /** @var int $h7 */
        $h7 = self::load_3(self::substr($s, 23, 3)) << 5;
        /** @var int $h8 */
        $h8 = self::load_3(self::substr($s, 26, 3)) << 4;
        /** @var int $h9 */
        $h9 = (self::load_3(self::substr($s, 29, 3)) & 8388607) << 2;

        /** @var int $carry9 */
        $carry9 = ($h9 + (1 << 24)) >> 25;
        $h0 += self::mul($carry9, 19, 5);
        $h9 -= $carry9 << 25;
        /** @var int $carry1 */
        $carry1 = ($h1 + (1 << 24)) >> 25;
        $h2 += $carry1;
        $h1 -= $carry1 << 25;
        /** @var int $carry3 */
        $carry3 = ($h3 + (1 << 24)) >> 25;
        $h4 += $carry3;
        $h3 -= $carry3 << 25;
        /** @var int $carry5 */
        $carry5 = ($h5 + (1 << 24)) >> 25;
        $h6 += $carry5;
        $h5 -= $carry5 << 25;
        /** @var int $carry7 */
        $carry7 = ($h7 + (1 << 24)) >> 25;
        $h8 += $carry7;
        $h7 -= $carry7 << 25;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;
        /** @var int $carry2 */
        $carry2 = ($h2 + (1 << 25)) >> 26;
        $h3 += $carry2;
        $h2 -= $carry2 << 26;
        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;
        /** @var int $carry6 */
        $carry6 = ($h6 + (1 << 25)) >> 26;
        $h7 += $carry6;
        $h6 -= $carry6 << 26;
        /** @var int $carry8 */
        $carry8 = ($h8 + (1 << 25)) >> 26;
        $h9 += $carry8;
        $h8 -= $carry8 << 26;

        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(
                (int) $h0,
                (int) $h1,
                (int) $h2,
                (int) $h3,
                (int) $h4,
                (int) $h5,
                (int) $h6,
                (int) $h7,
                (int) $h8,
                (int) $h9
            )
        );
    }

    /**
     * Convert a field element to a byte string.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $h
     * @return string
     */
    public static function fe_tobytes(ParagonIE_Sodium_Core_Curve25519_Fe $h)
    {
        /** @var int $h0 */
        $h0 = (int) $h[0];
        /** @var int $h1 */
        $h1 = (int) $h[1];
        /** @var int $h2 */
        $h2 = (int) $h[2];
        /** @var int $h3 */
        $h3 = (int) $h[3];
        /** @var int $h4 */
        $h4 = (int) $h[4];
        /** @var int $h5 */
        $h5 = (int) $h[5];
        /** @var int $h6 */
        $h6 = (int) $h[6];
        /** @var int $h7 */
        $h7 = (int) $h[7];
        /** @var int $h8 */
        $h8 = (int) $h[8];
        /** @var int $h9 */
        $h9 = (int) $h[9];

        /** @var int $q */
        $q = (self::mul($h9, 19, 5) + (1 << 24)) >> 25;
        /** @var int $q */
        $q = ($h0 + $q) >> 26;
        /** @var int $q */
        $q = ($h1 + $q) >> 25;
        /** @var int $q */
        $q = ($h2 + $q) >> 26;
        /** @var int $q */
        $q = ($h3 + $q) >> 25;
        /** @var int $q */
        $q = ($h4 + $q) >> 26;
        /** @var int $q */
        $q = ($h5 + $q) >> 25;
        /** @var int $q */
        $q = ($h6 + $q) >> 26;
        /** @var int $q */
        $q = ($h7 + $q) >> 25;
        /** @var int $q */
        $q = ($h8 + $q) >> 26;
        /** @var int $q */
        $q = ($h9 + $q) >> 25;

        $h0 += self::mul($q, 19, 5);

        /** @var int $carry0 */
        $carry0 = $h0 >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;
        /** @var int $carry1 */
        $carry1 = $h1 >> 25;
        $h2 += $carry1;
        $h1 -= $carry1 << 25;
        /** @var int $carry2 */
        $carry2 = $h2 >> 26;
        $h3 += $carry2;
        $h2 -= $carry2 << 26;
        /** @var int $carry3 */
        $carry3 = $h3 >> 25;
        $h4 += $carry3;
        $h3 -= $carry3 << 25;
        /** @var int $carry4 */
        $carry4 = $h4 >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;
        /** @var int $carry5 */
        $carry5 = $h5 >> 25;
        $h6 += $carry5;
        $h5 -= $carry5 << 25;
        /** @var int $carry6 */
        $carry6 = $h6 >> 26;
        $h7 += $carry6;
        $h6 -= $carry6 << 26;
        /** @var int $carry7 */
        $carry7 = $h7 >> 25;
        $h8 += $carry7;
        $h7 -= $carry7 << 25;
        /** @var int $carry8 */
        $carry8 = $h8 >> 26;
        $h9 += $carry8;
        $h8 -= $carry8 << 26;
        /** @var int $carry9 */
        $carry9 = $h9 >> 25;
        $h9 -= $carry9 << 25;

        /**
         * @var array<int, int>
         */
        $s = array(
            (int) (($h0 >> 0) & 0xff),
            (int) (($h0 >> 8) & 0xff),
            (int) (($h0 >> 16) & 0xff),
            (int) ((($h0 >> 24) | ($h1 << 2)) & 0xff),
            (int) (($h1 >> 6) & 0xff),
            (int) (($h1 >> 14) & 0xff),
            (int) ((($h1 >> 22) | ($h2 << 3)) & 0xff),
            (int) (($h2 >> 5) & 0xff),
            (int) (($h2 >> 13) & 0xff),
            (int) ((($h2 >> 21) | ($h3 << 5)) & 0xff),
            (int) (($h3 >> 3) & 0xff),
            (int) (($h3 >> 11) & 0xff),
            (int) ((($h3 >> 19) | ($h4 << 6)) & 0xff),
            (int) (($h4 >> 2) & 0xff),
            (int) (($h4 >> 10) & 0xff),
            (int) (($h4 >> 18) & 0xff),
            (int) (($h5 >> 0) & 0xff),
            (int) (($h5 >> 8) & 0xff),
            (int) (($h5 >> 16) & 0xff),
            (int) ((($h5 >> 24) | ($h6 << 1)) & 0xff),
            (int) (($h6 >> 7) & 0xff),
            (int) (($h6 >> 15) & 0xff),
            (int) ((($h6 >> 23) | ($h7 << 3)) & 0xff),
            (int) (($h7 >> 5) & 0xff),
            (int) (($h7 >> 13) & 0xff),
            (int) ((($h7 >> 21) | ($h8 << 4)) & 0xff),
            (int) (($h8 >> 4) & 0xff),
            (int) (($h8 >> 12) & 0xff),
            (int) ((($h8 >> 20) | ($h9 << 6)) & 0xff),
            (int) (($h9 >> 2) & 0xff),
            (int) (($h9 >> 10) & 0xff),
            (int) (($h9 >> 18) & 0xff)
        );
        return self::intArrayToString($s);
    }

    /**
     * Is a field element negative? (1 = yes, 0 = no. Used in calculations.)
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_isnegative(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $str = self::fe_tobytes($f);
        return (int) (self::chrToInt($str[0]) & 1);
    }

    /**
     * Returns 0 if this field element results in all NUL bytes.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_isnonzero(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        static $zero;
        if ($zero === null) {
            $zero = str_repeat("\x00", 32);
        }
        /** @var string $zero */
        /** @var string $str */
        $str = self::fe_tobytes($f);
        return !self::verify_32($str, (string) $zero);
    }

    /**
     * Multiply two field elements
     *
     * h = f * g
     *
     * @internal You should not use this directly from another application
     *
     * @security Is multiplication a source of timing leaks? If so, can we do
     *           anything to prevent that from happening?
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_mul(
        ParagonIE_Sodium_Core_Curve25519_Fe $f,
        ParagonIE_Sodium_Core_Curve25519_Fe $g
    ) {
        /** @var int $f0 */
        $f0 = $f[0];
        /** @var int $f1 */
        $f1 = $f[1];
        /** @var int $f2 */
        $f2 = $f[2];
        /** @var int $f3 */
        $f3 = $f[3];
        /** @var int $f4 */
        $f4 = $f[4];
        /** @var int $f5 */
        $f5 = $f[5];
        /** @var int $f6 */
        $f6 = $f[6];
        /** @var int $f7 */
        $f7 = $f[7];
        /** @var int $f8 */
        $f8 = $f[8];
        /** @var int $f9 */
        $f9 = $f[9];
        /** @var int $g0 */
        $g0 = $g[0];
        /** @var int $g1 */
        $g1 = $g[1];
        /** @var int $g2 */
        $g2 = $g[2];
        /** @var int $g3 */
        $g3 = $g[3];
        /** @var int $g4 */
        $g4 = $g[4];
        /** @var int $g5 */
        $g5 = $g[5];
        /** @var int $g6 */
        $g6 = $g[6];
        /** @var int $g7 */
        $g7 = $g[7];
        /** @var int $g8 */
        $g8 = $g[8];
        /** @var int $g9 */
        $g9 = $g[9];
        $g1_19 = self::mul($g1, 19, 5);
        $g2_19 = self::mul($g2, 19, 5);
        $g3_19 = self::mul($g3, 19, 5);
        $g4_19 = self::mul($g4, 19, 5);
        $g5_19 = self::mul($g5, 19, 5);
        $g6_19 = self::mul($g6, 19, 5);
        $g7_19 = self::mul($g7, 19, 5);
        $g8_19 = self::mul($g8, 19, 5);
        $g9_19 = self::mul($g9, 19, 5);
        /** @var int $f1_2 */
        $f1_2 = $f1 << 1;
        /** @var int $f3_2 */
        $f3_2 = $f3 << 1;
        /** @var int $f5_2 */
        $f5_2 = $f5 << 1;
        /** @var int $f7_2 */
        $f7_2 = $f7 << 1;
        /** @var int $f9_2 */
        $f9_2 = $f9 << 1;
        $f0g0    = self::mul($f0,    $g0, 26);
        $f0g1    = self::mul($f0,    $g1, 25);
        $f0g2    = self::mul($f0,    $g2, 26);
        $f0g3    = self::mul($f0,    $g3, 25);
        $f0g4    = self::mul($f0,    $g4, 26);
        $f0g5    = self::mul($f0,    $g5, 25);
        $f0g6    = self::mul($f0,    $g6, 26);
        $f0g7    = self::mul($f0,    $g7, 25);
        $f0g8    = self::mul($f0,    $g8, 26);
        $f0g9    = self::mul($f0,    $g9, 26);
        $f1g0    = self::mul($f1,    $g0, 26);
        $f1g1_2  = self::mul($f1_2,  $g1, 25);
        $f1g2    = self::mul($f1,    $g2, 26);
        $f1g3_2  = self::mul($f1_2,  $g3, 25);
        $f1g4    = self::mul($f1,    $g4, 26);
        $f1g5_2  = self::mul($f1_2,  $g5, 25);
        $f1g6    = self::mul($f1,    $g6, 26);
        $f1g7_2  = self::mul($f1_2,  $g7, 25);
        $f1g8    = self::mul($f1,    $g8, 26);
        $f1g9_38 = self::mul($g9_19, $f1_2, 26);
        $f2g0    = self::mul($f2,    $g0, 26);
        $f2g1    = self::mul($f2,    $g1, 25);
        $f2g2    = self::mul($f2,    $g2, 26);
        $f2g3    = self::mul($f2,    $g3, 25);
        $f2g4    = self::mul($f2,    $g4, 26);
        $f2g5    = self::mul($f2,    $g5, 25);
        $f2g6    = self::mul($f2,    $g6, 26);
        $f2g7    = self::mul($f2,    $g7, 25);
        $f2g8_19 = self::mul($g8_19, $f2, 26);
        $f2g9_19 = self::mul($g9_19, $f2, 26);
        $f3g0    = self::mul($f3,    $g0, 26);
        $f3g1_2  = self::mul($f3_2,  $g1, 25);
        $f3g2    = self::mul($f3,    $g2, 26);
        $f3g3_2  = self::mul($f3_2,  $g3, 25);
        $f3g4    = self::mul($f3,    $g4, 26);
        $f3g5_2  = self::mul($f3_2,  $g5, 25);
        $f3g6    = self::mul($f3,    $g6, 26);
        $f3g7_38 = self::mul($g7_19, $f3_2, 26);
        $f3g8_19 = self::mul($g8_19, $f3, 25);
        $f3g9_38 = self::mul($g9_19, $f3_2, 26);
        $f4g0    = self::mul($f4,    $g0, 26);
        $f4g1    = self::mul($f4,    $g1, 25);
        $f4g2    = self::mul($f4,    $g2, 26);
        $f4g3    = self::mul($f4,    $g3, 25);
        $f4g4    = self::mul($f4,    $g4, 26);
        $f4g5    = self::mul($f4,    $g5, 25);
        $f4g6_19 = self::mul($g6_19, $f4, 26);
        $f4g7_19 = self::mul($g7_19, $f4, 26);
        $f4g8_19 = self::mul($g8_19, $f4, 26);
        $f4g9_19 = self::mul($g9_19, $f4, 26);
        $f5g0    = self::mul($f5,    $g0, 26);
        $f5g1_2  = self::mul($f5_2,  $g1, 25);
        $f5g2    = self::mul($f5,    $g2, 26);
        $f5g3_2  = self::mul($f5_2,  $g3, 25);
        $f5g4    = self::mul($f5,    $g4, 26);
        $f5g5_38 = self::mul($g5_19, $f5_2, 26);
        $f5g6_19 = self::mul($g6_19, $f5, 25);
        $f5g7_38 = self::mul($g7_19, $f5_2, 26);
        $f5g8_19 = self::mul($g8_19, $f5, 25);
        $f5g9_38 = self::mul($g9_19, $f5_2, 26);
        $f6g0    = self::mul($f6,    $g0, 26);
        $f6g1    = self::mul($f6,    $g1, 25);
        $f6g2    = self::mul($f6,    $g2, 26);
        $f6g3    = self::mul($f6,    $g3, 25);
        $f6g4_19 = self::mul($g4_19, $f6, 26);
        $f6g5_19 = self::mul($g5_19, $f6, 26);
        $f6g6_19 = self::mul($g6_19, $f6, 26);
        $f6g7_19 = self::mul($g7_19, $f6, 26);
        $f6g8_19 = self::mul($g8_19, $f6, 26);
        $f6g9_19 = self::mul($g9_19, $f6, 26);
        $f7g0    = self::mul($f7,    $g0, 26);
        $f7g1_2  = self::mul($f7_2,  $g1, 25);
        $f7g2    = self::mul($f7,    $g2, 26);
        $f7g3_38 = self::mul($g3_19, $f7_2, 26);
        $f7g4_19 = self::mul($g4_19, $f7, 26);
        $f7g5_38 = self::mul($g5_19, $f7_2, 26);
        $f7g6_19 = self::mul($g6_19, $f7, 25);
        $f7g7_38 = self::mul($g7_19, $f7_2, 26);
        $f7g8_19 = self::mul($g8_19, $f7, 25);
        $f7g9_38 = self::mul($g9_19,$f7_2, 26);
        $f8g0    = self::mul($f8,    $g0, 26);
        $f8g1    = self::mul($f8,    $g1, 25);
        $f8g2_19 = self::mul($g2_19, $f8, 26);
        $f8g3_19 = self::mul($g3_19, $f8, 26);
        $f8g4_19 = self::mul($g4_19, $f8, 26);
        $f8g5_19 = self::mul($g5_19, $f8, 26);
        $f8g6_19 = self::mul($g6_19, $f8, 26);
        $f8g7_19 = self::mul($g7_19, $f8, 26);
        $f8g8_19 = self::mul($g8_19, $f8, 26);
        $f8g9_19 = self::mul($g9_19, $f8, 26);
        $f9g0    = self::mul($f9,    $g0, 26);
        $f9g1_38 = self::mul($g1_19, $f9_2, 26);
        $f9g2_19 = self::mul($g2_19, $f9, 25);
        $f9g3_38 = self::mul($g3_19, $f9_2, 26);
        $f9g4_19 = self::mul($g4_19, $f9, 25);
        $f9g5_38 = self::mul($g5_19, $f9_2, 26);
        $f9g6_19 = self::mul($g6_19, $f9, 25);
        $f9g7_38 = self::mul($g7_19, $f9_2, 26);
        $f9g8_19 = self::mul($g8_19, $f9, 25);
        $f9g9_38 = self::mul($g9_19, $f9_2, 26);
        $h0 = $f0g0 + $f1g9_38 + $f2g8_19 + $f3g7_38 + $f4g6_19 + $f5g5_38 + $f6g4_19 + $f7g3_38 + $f8g2_19 + $f9g1_38;
        $h1 = $f0g1 + $f1g0    + $f2g9_19 + $f3g8_19 + $f4g7_19 + $f5g6_19 + $f6g5_19 + $f7g4_19 + $f8g3_19 + $f9g2_19;
        $h2 = $f0g2 + $f1g1_2  + $f2g0    + $f3g9_38 + $f4g8_19 + $f5g7_38 + $f6g6_19 + $f7g5_38 + $f8g4_19 + $f9g3_38;
        $h3 = $f0g3 + $f1g2    + $f2g1    + $f3g0    + $f4g9_19 + $f5g8_19 + $f6g7_19 + $f7g6_19 + $f8g5_19 + $f9g4_19;
        $h4 = $f0g4 + $f1g3_2  + $f2g2    + $f3g1_2  + $f4g0    + $f5g9_38 + $f6g8_19 + $f7g7_38 + $f8g6_19 + $f9g5_38;
        $h5 = $f0g5 + $f1g4    + $f2g3    + $f3g2    + $f4g1    + $f5g0    + $f6g9_19 + $f7g8_19 + $f8g7_19 + $f9g6_19;
        $h6 = $f0g6 + $f1g5_2  + $f2g4    + $f3g3_2  + $f4g2    + $f5g1_2  + $f6g0    + $f7g9_38 + $f8g8_19 + $f9g7_38;
        $h7 = $f0g7 + $f1g6    + $f2g5    + $f3g4    + $f4g3    + $f5g2    + $f6g1    + $f7g0    + $f8g9_19 + $f9g8_19;
        $h8 = $f0g8 + $f1g7_2  + $f2g6    + $f3g5_2  + $f4g4    + $f5g3_2  + $f6g2    + $f7g1_2  + $f8g0    + $f9g9_38;
        $h9 = $f0g9 + $f1g8    + $f2g7    + $f3g6    + $f4g5    + $f5g4    + $f6g3    + $f7g2    + $f8g1    + $f9g0   ;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;
        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;

        /** @var int $carry1 */
        $carry1 = ($h1 + (1 << 24)) >> 25;
        $h2 += $carry1;
        $h1 -= $carry1 << 25;
        /** @var int $carry5 */
        $carry5 = ($h5 + (1 << 24)) >> 25;
        $h6 += $carry5;
        $h5 -= $carry5 << 25;

        /** @var int $carry2 */
        $carry2 = ($h2 + (1 << 25)) >> 26;
        $h3 += $carry2;
        $h2 -= $carry2 << 26;
        /** @var int $carry6 */
        $carry6 = ($h6 + (1 << 25)) >> 26;
        $h7 += $carry6;
        $h6 -= $carry6 << 26;

        /** @var int $carry3 */
        $carry3 = ($h3 + (1 << 24)) >> 25;
        $h4 += $carry3;
        $h3 -= $carry3 << 25;
        /** @var int $carry7 */
        $carry7 = ($h7 + (1 << 24)) >> 25;
        $h8 += $carry7;
        $h7 -= $carry7 << 25;

        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;
        /** @var int $carry8 */
        $carry8 = ($h8 + (1 << 25)) >> 26;
        $h9 += $carry8;
        $h8 -= $carry8 << 26;

        /** @var int $carry9 */
        $carry9 = ($h9 + (1 << 24)) >> 25;
        $h0 += self::mul($carry9, 19, 5);
        $h9 -= $carry9 << 25;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;

        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(
                (int) $h0,
                (int) $h1,
                (int) $h2,
                (int) $h3,
                (int) $h4,
                (int) $h5,
                (int) $h6,
                (int) $h7,
                (int) $h8,
                (int) $h9
            )
        );
    }

    /**
     * Get the negative values for each piece of the field element.
     *
     * h = -f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     * @psalm-suppress MixedAssignment
     */
    public static function fe_neg(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $h = new ParagonIE_Sodium_Core_Curve25519_Fe();
        for ($i = 0; $i < 10; ++$i) {
            $h[$i] = -$f[$i];
        }
        return $h;
    }

    /**
     * Square a field element
     *
     * h = f * f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_sq(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $f0 = (int) $f[0];
        $f1 = (int) $f[1];
        $f2 = (int) $f[2];
        $f3 = (int) $f[3];
        $f4 = (int) $f[4];
        $f5 = (int) $f[5];
        $f6 = (int) $f[6];
        $f7 = (int) $f[7];
        $f8 = (int) $f[8];
        $f9 = (int) $f[9];

        /** @var int $f0_2 */
        $f0_2 = $f0 << 1;
        /** @var int $f1_2 */
        $f1_2 = $f1 << 1;
        /** @var int $f2_2 */
        $f2_2 = $f2 << 1;
        /** @var int $f3_2 */
        $f3_2 = $f3 << 1;
        /** @var int $f4_2 */
        $f4_2 = $f4 << 1;
        /** @var int $f5_2 */
        $f5_2 = $f5 << 1;
        /** @var int $f6_2 */
        $f6_2 = $f6 << 1;
        /** @var int $f7_2 */
        $f7_2 = $f7 << 1;
        $f5_38 = self::mul($f5, 38, 6);
        $f6_19 = self::mul($f6, 19, 5);
        $f7_38 = self::mul($f7, 38, 6);
        $f8_19 = self::mul($f8, 19, 5);
        $f9_38 = self::mul($f9, 38, 6);
        $f0f0    = self::mul($f0,    $f0,    25);
        $f0f1_2  = self::mul($f0_2,  $f1,    24);
        $f0f2_2  = self::mul($f0_2,  $f2,    25);
        $f0f3_2  = self::mul($f0_2,  $f3,    24);
        $f0f4_2  = self::mul($f0_2,  $f4,    25);
        $f0f5_2  = self::mul($f0_2,  $f5,    25);
        $f0f6_2  = self::mul($f0_2,  $f6,    25);
        $f0f7_2  = self::mul($f0_2,  $f7,    24);
        $f0f8_2  = self::mul($f0_2,  $f8,    25);
        $f0f9_2  = self::mul($f0_2,  $f9,    25);
        $f1f1_2  = self::mul($f1_2,  $f1,    24);
        $f1f2_2  = self::mul($f1_2,  $f2,    25);
        $f1f3_4  = self::mul($f1_2,  $f3_2,  25);
        $f1f4_2  = self::mul($f1_2,  $f4,    25);
        $f1f5_4  = self::mul($f1_2,  $f5_2,  26);
        $f1f6_2  = self::mul($f1_2,  $f6,    25);
        $f1f7_4  = self::mul($f1_2,  $f7_2,  25);
        $f1f8_2  = self::mul($f1_2,  $f8,    25);
        $f1f9_76 = self::mul($f9_38, $f1_2,  25);
        $f2f2    = self::mul($f2,    $f2,    25);
        $f2f3_2  = self::mul($f2_2,  $f3,    24);
        $f2f4_2  = self::mul($f2_2,  $f4,    25);
        $f2f5_2  = self::mul($f2_2,  $f5,    25);
        $f2f6_2  = self::mul($f2_2,  $f6,    25);
        $f2f7_2  = self::mul($f2_2,  $f7,    24);
        $f2f8_38 = self::mul($f8_19, $f2_2,  26);
        $f2f9_38 = self::mul($f9_38, $f2,    25);
        $f3f3_2  = self::mul($f3_2,  $f3,    24);
        $f3f4_2  = self::mul($f3_2,  $f4,    25);
        $f3f5_4  = self::mul($f3_2,  $f5_2,  26);
        $f3f6_2  = self::mul($f3_2,  $f6,    25);
        $f3f7_76 = self::mul($f7_38, $f3_2,  25);
        $f3f8_38 = self::mul($f8_19, $f3_2,  25);
        $f3f9_76 = self::mul($f9_38, $f3_2,  25);
        $f4f4    = self::mul($f4,    $f4,    25);
        $f4f5_2  = self::mul($f4_2,  $f5,    25);
        $f4f6_38 = self::mul($f6_19, $f4_2,  26);
        $f4f7_38 = self::mul($f7_38, $f4,    25);
        $f4f8_38 = self::mul($f8_19, $f4_2,  26);
        $f4f9_38 = self::mul($f9_38, $f4,    25);
        $f5f5_38 = self::mul($f5_38, $f5,    25);
        $f5f6_38 = self::mul($f6_19, $f5_2,  26);
        $f5f7_76 = self::mul($f7_38, $f5_2,  26);
        $f5f8_38 = self::mul($f8_19, $f5_2,  26);
        $f5f9_76 = self::mul($f9_38, $f5_2,  26);
        $f6f6_19 = self::mul($f6_19, $f6,    25);
        $f6f7_38 = self::mul($f7_38, $f6,    25);
        $f6f8_38 = self::mul($f8_19, $f6_2,  26);
        $f6f9_38 = self::mul($f9_38, $f6,    25);
        $f7f7_38 = self::mul($f7_38, $f7,    24);
        $f7f8_38 = self::mul($f8_19, $f7_2,  25);
        $f7f9_76 = self::mul($f9_38, $f7_2,  25);
        $f8f8_19 = self::mul($f8_19, $f8,    25);
        $f8f9_38 = self::mul($f9_38, $f8,    25);
        $f9f9_38 = self::mul($f9_38, $f9,    25);
        $h0 = $f0f0   + $f1f9_76 + $f2f8_38 + $f3f7_76 + $f4f6_38 + $f5f5_38;
        $h1 = $f0f1_2 + $f2f9_38 + $f3f8_38 + $f4f7_38 + $f5f6_38;
        $h2 = $f0f2_2 + $f1f1_2  + $f3f9_76 + $f4f8_38 + $f5f7_76 + $f6f6_19;
        $h3 = $f0f3_2 + $f1f2_2  + $f4f9_38 + $f5f8_38 + $f6f7_38;
        $h4 = $f0f4_2 + $f1f3_4  + $f2f2    + $f5f9_76 + $f6f8_38 + $f7f7_38;
        $h5 = $f0f5_2 + $f1f4_2  + $f2f3_2  + $f6f9_38 + $f7f8_38;
        $h6 = $f0f6_2 + $f1f5_4  + $f2f4_2  + $f3f3_2  + $f7f9_76 + $f8f8_19;
        $h7 = $f0f7_2 + $f1f6_2  + $f2f5_2  + $f3f4_2  + $f8f9_38;
        $h8 = $f0f8_2 + $f1f7_4  + $f2f6_2  + $f3f5_4  + $f4f4    + $f9f9_38;
        $h9 = $f0f9_2 + $f1f8_2  + $f2f7_2  + $f3f6_2  + $f4f5_2;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;
        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;

        /** @var int $carry1 */
        $carry1 = ($h1 + (1 << 24)) >> 25;
        $h2 += $carry1;
        $h1 -= $carry1 << 25;
        /** @var int $carry5 */
        $carry5 = ($h5 + (1 << 24)) >> 25;
        $h6 += $carry5;
        $h5 -= $carry5 << 25;

        /** @var int $carry2 */
        $carry2 = ($h2 + (1 << 25)) >> 26;
        $h3 += $carry2;
        $h2 -= $carry2 << 26;
        /** @var int $carry6 */
        $carry6 = ($h6 + (1 << 25)) >> 26;
        $h7 += $carry6;
        $h6 -= $carry6 << 26;

        /** @var int $carry3 */
        $carry3 = ($h3 + (1 << 24)) >> 25;
        $h4 += $carry3;
        $h3 -= $carry3 << 25;
        /** @var int $carry7 */
        $carry7 = ($h7 + (1 << 24)) >> 25;
        $h8 += $carry7;
        $h7 -= $carry7 << 25;

        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;
        /** @var int $carry8 */
        $carry8 = ($h8 + (1 << 25)) >> 26;
        $h9 += $carry8;
        $h8 -= $carry8 << 26;

        /** @var int $carry9 */
        $carry9 = ($h9 + (1 << 24)) >> 25;
        $h0 += self::mul($carry9, 19, 5);
        $h9 -= $carry9 << 25;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;

        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(
                (int) $h0,
                (int) $h1,
                (int) $h2,
                (int) $h3,
                (int) $h4,
                (int) $h5,
                (int) $h6,
                (int) $h7,
                (int) $h8,
                (int) $h9
            )
        );
    }


    /**
     * Square and double a field element
     *
     * h = 2 * f * f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_sq2(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $f0 = (int) $f[0];
        $f1 = (int) $f[1];
        $f2 = (int) $f[2];
        $f3 = (int) $f[3];
        $f4 = (int) $f[4];
        $f5 = (int) $f[5];
        $f6 = (int) $f[6];
        $f7 = (int) $f[7];
        $f8 = (int) $f[8];
        $f9 = (int) $f[9];

        /** @var int $f0_2 */
        $f0_2 = $f0 << 1;
        /** @var int $f1_2 */
        $f1_2 = $f1 << 1;
        /** @var int $f2_2 */
        $f2_2 = $f2 << 1;
        /** @var int $f3_2 */
        $f3_2 = $f3 << 1;
        /** @var int $f4_2 */
        $f4_2 = $f4 << 1;
        /** @var int $f5_2 */
        $f5_2 = $f5 << 1;
        /** @var int $f6_2 */
        $f6_2 = $f6 << 1;
        /** @var int $f7_2 */
        $f7_2 = $f7 << 1;
        $f5_38 = self::mul($f5, 38, 6); /* 1.959375*2^30 */
        $f6_19 = self::mul($f6, 19, 5); /* 1.959375*2^30 */
        $f7_38 = self::mul($f7, 38, 6); /* 1.959375*2^30 */
        $f8_19 = self::mul($f8, 19, 5); /* 1.959375*2^30 */
        $f9_38 = self::mul($f9, 38, 6); /* 1.959375*2^30 */
        $f0f0 = self::mul($f0, $f0, 24);
        $f0f1_2 = self::mul($f0_2, $f1, 24);
        $f0f2_2 = self::mul($f0_2, $f2, 24);
        $f0f3_2 = self::mul($f0_2, $f3, 24);
        $f0f4_2 = self::mul($f0_2, $f4, 24);
        $f0f5_2 = self::mul($f0_2, $f5, 24);
        $f0f6_2 = self::mul($f0_2, $f6, 24);
        $f0f7_2 = self::mul($f0_2, $f7, 24);
        $f0f8_2 = self::mul($f0_2, $f8, 24);
        $f0f9_2 = self::mul($f0_2, $f9, 24);
        $f1f1_2 = self::mul($f1_2,  $f1, 24);
        $f1f2_2 = self::mul($f1_2,  $f2, 24);
        $f1f3_4 = self::mul($f1_2,  $f3_2, 24);
        $f1f4_2 = self::mul($f1_2,  $f4, 24);
        $f1f5_4 = self::mul($f1_2,  $f5_2, 24);
        $f1f6_2 = self::mul($f1_2,  $f6, 24);
        $f1f7_4 = self::mul($f1_2,  $f7_2, 24);
        $f1f8_2 = self::mul($f1_2,  $f8, 24);
        $f1f9_76 = self::mul($f9_38, $f1_2, 24);
        $f2f2 = self::mul($f2,  $f2, 24);
        $f2f3_2 = self::mul($f2_2,  $f3, 24);
        $f2f4_2 = self::mul($f2_2,  $f4, 24);
        $f2f5_2 = self::mul($f2_2,  $f5, 24);
        $f2f6_2 = self::mul($f2_2,  $f6, 24);
        $f2f7_2 = self::mul($f2_2,  $f7, 24);
        $f2f8_38 = self::mul($f8_19, $f2_2, 25);
        $f2f9_38 = self::mul($f9_38, $f2, 24);
        $f3f3_2 = self::mul($f3_2,  $f3, 24);
        $f3f4_2 = self::mul($f3_2,  $f4, 24);
        $f3f5_4 = self::mul($f3_2,  $f5_2, 24);
        $f3f6_2 = self::mul($f3_2,  $f6, 24);
        $f3f7_76 = self::mul($f7_38, $f3_2, 24);
        $f3f8_38 = self::mul($f8_19, $f3_2, 24);
        $f3f9_76 = self::mul($f9_38, $f3_2, 24);
        $f4f4 = self::mul($f4,  $f4, 24);
        $f4f5_2 = self::mul($f4_2,  $f5, 24);
        $f4f6_38 = self::mul($f6_19, $f4_2, 25);
        $f4f7_38 = self::mul($f7_38, $f4, 24);
        $f4f8_38 = self::mul($f8_19, $f4_2, 25);
        $f4f9_38 = self::mul($f9_38, $f4, 24);
        $f5f5_38 = self::mul($f5_38, $f5, 24);
        $f5f6_38 = self::mul($f6_19, $f5_2, 24);
        $f5f7_76 = self::mul($f7_38, $f5_2, 24);
        $f5f8_38 = self::mul($f8_19, $f5_2, 24);
        $f5f9_76 = self::mul($f9_38, $f5_2, 24);
        $f6f6_19 = self::mul($f6_19, $f6, 24);
        $f6f7_38 = self::mul($f7_38, $f6, 24);
        $f6f8_38 = self::mul($f8_19, $f6_2, 25);
        $f6f9_38 = self::mul($f9_38, $f6, 24);
        $f7f7_38 = self::mul($f7_38, $f7, 24);
        $f7f8_38 = self::mul($f8_19, $f7_2, 24);
        $f7f9_76 = self::mul($f9_38, $f7_2, 24);
        $f8f8_19 = self::mul($f8_19, $f8, 24);
        $f8f9_38 = self::mul($f9_38, $f8, 24);
        $f9f9_38 = self::mul($f9_38, $f9, 24);

        /** @var int $h0 */
        $h0 = (int) ($f0f0 + $f1f9_76 + $f2f8_38 + $f3f7_76 + $f4f6_38 + $f5f5_38) << 1;
        /** @var int $h1 */
        $h1 = (int) ($f0f1_2 + $f2f9_38 + $f3f8_38 + $f4f7_38 + $f5f6_38) << 1;
        /** @var int $h2 */
        $h2 = (int) ($f0f2_2 + $f1f1_2  + $f3f9_76 + $f4f8_38 + $f5f7_76 + $f6f6_19) << 1;
        /** @var int $h3 */
        $h3 = (int) ($f0f3_2 + $f1f2_2  + $f4f9_38 + $f5f8_38 + $f6f7_38) << 1;
        /** @var int $h4 */
        $h4 = (int) ($f0f4_2 + $f1f3_4  + $f2f2    + $f5f9_76 + $f6f8_38 + $f7f7_38) << 1;
        /** @var int $h5 */
        $h5 = (int) ($f0f5_2 + $f1f4_2  + $f2f3_2  + $f6f9_38 + $f7f8_38) << 1;
        /** @var int $h6 */
        $h6 = (int) ($f0f6_2 + $f1f5_4  + $f2f4_2  + $f3f3_2  + $f7f9_76 + $f8f8_19) << 1;
        /** @var int $h7 */
        $h7 = (int) ($f0f7_2 + $f1f6_2  + $f2f5_2  + $f3f4_2  + $f8f9_38) << 1;
        /** @var int $h8 */
        $h8 = (int) ($f0f8_2 + $f1f7_4  + $f2f6_2  + $f3f5_4  + $f4f4    + $f9f9_38) << 1;
        /** @var int $h9 */
        $h9 = (int) ($f0f9_2 + $f1f8_2  + $f2f7_2  + $f3f6_2  + $f4f5_2) << 1;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;
        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;

        /** @var int $carry1 */
        $carry1 = ($h1 + (1 << 24)) >> 25;
        $h2 += $carry1;
        $h1 -= $carry1 << 25;
        /** @var int $carry5 */
        $carry5 = ($h5 + (1 << 24)) >> 25;
        $h6 += $carry5;
        $h5 -= $carry5 << 25;

        /** @var int $carry2 */
        $carry2 = ($h2 + (1 << 25)) >> 26;
        $h3 += $carry2;
        $h2 -= $carry2 << 26;
        /** @var int $carry6 */
        $carry6 = ($h6 + (1 << 25)) >> 26;
        $h7 += $carry6;
        $h6 -= $carry6 << 26;

        /** @var int $carry3 */
        $carry3 = ($h3 + (1 << 24)) >> 25;
        $h4 += $carry3;
        $h3 -= $carry3 << 25;
        /** @var int $carry7 */
        $carry7 = ($h7 + (1 << 24)) >> 25;
        $h8 += $carry7;
        $h7 -= $carry7 << 25;

        /** @var int $carry4 */
        $carry4 = ($h4 + (1 << 25)) >> 26;
        $h5 += $carry4;
        $h4 -= $carry4 << 26;
        /** @var int $carry8 */
        $carry8 = ($h8 + (1 << 25)) >> 26;
        $h9 += $carry8;
        $h8 -= $carry8 << 26;

        /** @var int $carry9 */
        $carry9 = ($h9 + (1 << 24)) >> 25;
        $h0 += self::mul($carry9, 19, 5);
        $h9 -= $carry9 << 25;

        /** @var int $carry0 */
        $carry0 = ($h0 + (1 << 25)) >> 26;
        $h1 += $carry0;
        $h0 -= $carry0 << 26;

        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(
                (int) $h0,
                (int) $h1,
                (int) $h2,
                (int) $h3,
                (int) $h4,
                (int) $h5,
                (int) $h6,
                (int) $h7,
                (int) $h8,
                (int) $h9
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $Z
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_invert(ParagonIE_Sodium_Core_Curve25519_Fe $Z)
    {
        $z = clone $Z;
        $t0 = self::fe_sq($z);
        $t1 = self::fe_sq($t0);
        $t1 = self::fe_sq($t1);
        $t1 = self::fe_mul($z, $t1);
        $t0 = self::fe_mul($t0, $t1);
        $t2 = self::fe_sq($t0);
        $t1 = self::fe_mul($t1, $t2);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 5; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 10; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t2 = self::fe_mul($t2, $t1);
        $t3 = self::fe_sq($t2);
        for ($i = 1; $i < 20; ++$i) {
            $t3 = self::fe_sq($t3);
        }
        $t2 = self::fe_mul($t3, $t2);
        $t2 = self::fe_sq($t2);
        for ($i = 1; $i < 10; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 50; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t2 = self::fe_mul($t2, $t1);
        $t3 = self::fe_sq($t2);
        for ($i = 1; $i < 100; ++$i) {
            $t3 = self::fe_sq($t3);
        }
        $t2 = self::fe_mul($t3, $t2);
        $t2 = self::fe_sq($t2);
        for ($i = 1; $i < 50; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);
        for ($i = 1; $i < 5; ++$i) {
            $t1 = self::fe_sq($t1);
        }
        return self::fe_mul($t1, $t0);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @ref https://github.com/jedisct1/libsodium/blob/68564326e1e9dc57ef03746f85734232d20ca6fb/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1054-L1106
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $z
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_pow22523(ParagonIE_Sodium_Core_Curve25519_Fe $z)
    {
        # fe_sq(t0, z);
        # fe_sq(t1, t0);
        # fe_sq(t1, t1);
        # fe_mul(t1, z, t1);
        # fe_mul(t0, t0, t1);
        # fe_sq(t0, t0);
        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_sq($z);
        $t1 = self::fe_sq($t0);
        $t1 = self::fe_sq($t1);
        $t1 = self::fe_mul($z, $t1);
        $t0 = self::fe_mul($t0, $t1);
        $t0 = self::fe_sq($t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 5; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 5; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 10; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 10; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t1, t1, t0);
        # fe_sq(t2, t1);
        $t1 = self::fe_mul($t1, $t0);
        $t2 = self::fe_sq($t1);

        # for (i = 1; i < 20; ++i) {
        #     fe_sq(t2, t2);
        # }
        for ($i = 1; $i < 20; ++$i) {
            $t2 = self::fe_sq($t2);
        }

        # fe_mul(t1, t2, t1);
        # fe_sq(t1, t1);
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);

        # for (i = 1; i < 10; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 10; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 50; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 50; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t1, t1, t0);
        # fe_sq(t2, t1);
        $t1 = self::fe_mul($t1, $t0);
        $t2 = self::fe_sq($t1);

        # for (i = 1; i < 100; ++i) {
        #     fe_sq(t2, t2);
        # }
        for ($i = 1; $i < 100; ++$i) {
            $t2 = self::fe_sq($t2);
        }

        # fe_mul(t1, t2, t1);
        # fe_sq(t1, t1);
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);

        # for (i = 1; i < 50; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 50; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t0, t0);
        # fe_sq(t0, t0);
        # fe_mul(out, t0, z);
        $t0 = self::fe_mul($t1, $t0);
        $t0 = self::fe_sq($t0);
        $t0 = self::fe_sq($t0);
        return self::fe_mul($t0, $z);
    }

    /**
     * Subtract two field elements.
     *
     * h = f - g
     *
     * Preconditions:
     * |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
     * |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
     *
     * Postconditions:
     * |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     * @psalm-suppress MixedOperand
     */
    public static function fe_sub(ParagonIE_Sodium_Core_Curve25519_Fe $f, ParagonIE_Sodium_Core_Curve25519_Fe $g)
    {
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(
            array(
                (int) ($f[0] - $g[0]),
                (int) ($f[1] - $g[1]),
                (int) ($f[2] - $g[2]),
                (int) ($f[3] - $g[3]),
                (int) ($f[4] - $g[4]),
                (int) ($f[5] - $g[5]),
                (int) ($f[6] - $g[6]),
                (int) ($f[7] - $g[7]),
                (int) ($f[8] - $g[8]),
                (int) ($f[9] - $g[9])
            )
        );
    }

    /**
     * Add two group elements.
     *
     * r = p + q
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_add(
        ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q
    ) {
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1();
        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->YplusX);
        $r->Y = self::fe_mul($r->Y, $q->YminusX);
        $r->T = self::fe_mul($q->T2d, $p->T);
        $r->X = self::fe_mul($p->Z, $q->Z);
        $t0   = self::fe_add($r->X, $r->X);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_add($t0, $r->T);
        $r->T = self::fe_sub($t0, $r->T);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @ref https://github.com/jedisct1/libsodium/blob/157c4a80c13b117608aeae12178b2d38825f9f8f/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1185-L1215
     * @param string $a
     * @return array<int, mixed>
     * @throws SodiumException
     * @throws TypeError
     */
    public static function slide($a)
    {
        if (self::strlen($a) < 256) {
            if (self::strlen($a) < 16) {
                $a = str_pad($a, 256, '0', STR_PAD_RIGHT);
            }
        }
        /** @var array<int, int> $r */
        $r = array();

        /** @var int $i */
        for ($i = 0; $i < 256; ++$i) {
            $r[$i] = (int) (
                1 & (
                    self::chrToInt($a[(int) ($i >> 3)])
                        >>
                    ($i & 7)
                )
            );
        }

        for ($i = 0;$i < 256;++$i) {
            if ($r[$i]) {
                for ($b = 1;$b <= 6 && $i + $b < 256;++$b) {
                    if ($r[$i + $b]) {
                        if ($r[$i] + ($r[$i + $b] << $b) <= 15) {
                            $r[$i] += $r[$i + $b] << $b;
                            $r[$i + $b] = 0;
                        } elseif ($r[$i] - ($r[$i + $b] << $b) >= -15) {
                            $r[$i] -= $r[$i + $b] << $b;
                            for ($k = $i + $b; $k < 256; ++$k) {
                                if (!$r[$k]) {
                                    $r[$k] = 1;
                                    break;
                                }
                                $r[$k] = 0;
                            }
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_frombytes_negate_vartime($s)
    {
        static $d = null;
        if (!$d) {
            $d = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d);
        }

        # fe_frombytes(h->Y,s);
        # fe_1(h->Z);
        $h = new ParagonIE_Sodium_Core_Curve25519_Ge_P3(
            self::fe_0(),
            self::fe_frombytes($s),
            self::fe_1()
        );

        # fe_sq(u,h->Y);
        # fe_mul(v,u,d);
        # fe_sub(u,u,h->Z);       /* u = y^2-1 */
        # fe_add(v,v,h->Z);       /* v = dy^2+1 */
        $u = self::fe_sq($h->Y);
        /** @var ParagonIE_Sodium_Core_Curve25519_Fe $d */
        $v = self::fe_mul($u, $d);
        $u = self::fe_sub($u, $h->Z); /* u =  y^2 - 1 */
        $v = self::fe_add($v, $h->Z); /* v = dy^2 + 1 */

        # fe_sq(v3,v);
        # fe_mul(v3,v3,v);        /* v3 = v^3 */
        # fe_sq(h->X,v3);
        # fe_mul(h->X,h->X,v);
        # fe_mul(h->X,h->X,u);    /* x = uv^7 */
        $v3 = self::fe_sq($v);
        $v3 = self::fe_mul($v3, $v); /* v3 = v^3 */
        $h->X = self::fe_sq($v3);
        $h->X = self::fe_mul($h->X, $v);
        $h->X = self::fe_mul($h->X, $u); /* x = uv^7 */

        # fe_pow22523(h->X,h->X); /* x = (uv^7)^((q-5)/8) */
        # fe_mul(h->X,h->X,v3);
        # fe_mul(h->X,h->X,u);    /* x = uv^3(uv^7)^((q-5)/8) */
        $h->X = self::fe_pow22523($h->X); /* x = (uv^7)^((q-5)/8) */
        $h->X = self::fe_mul($h->X, $v3);
        $h->X = self::fe_mul($h->X, $u); /* x = uv^3(uv^7)^((q-5)/8) */

        # fe_sq(vxx,h->X);
        # fe_mul(vxx,vxx,v);
        # fe_sub(check,vxx,u);    /* vx^2-u */
        $vxx = self::fe_sq($h->X);
        $vxx = self::fe_mul($vxx, $v);
        $check = self::fe_sub($vxx, $u); /* vx^2 - u */

        # if (fe_isnonzero(check)) {
        #     fe_add(check,vxx,u);  /* vx^2+u */
        #     if (fe_isnonzero(check)) {
        #         return -1;
        #     }
        #     fe_mul(h->X,h->X,sqrtm1);
        # }
        if (self::fe_isnonzero($check)) {
            $check = self::fe_add($vxx, $u); /* vx^2 + u */
            if (self::fe_isnonzero($check)) {
                throw new RangeException('Internal check failed.');
            }
            $h->X = self::fe_mul(
                $h->X,
                ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$sqrtm1)
            );
        }

        # if (fe_isnegative(h->X) == (s[31] >> 7)) {
        #     fe_neg(h->X,h->X);
        # }
        $i = self::chrToInt($s[31]);
        if (self::fe_isnegative($h->X) === ($i >> 7)) {
            $h->X = self::fe_neg($h->X);
        }

        # fe_mul(h->T,h->X,h->Y);
        $h->T = self::fe_mul($h->X, $h->Y);
        return $h;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_madd(
        ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R,
        ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q
    ) {
        $r = clone $R;
        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->yplusx);
        $r->Y = self::fe_mul($r->Y, $q->yminusx);
        $r->T = self::fe_mul($q->xy2d, $p->T);
        $t0 = self::fe_add(clone $p->Z, clone $p->Z);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_add($t0, $r->T);
        $r->T = self::fe_sub($t0, $r->T);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_msub(
        ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $R,
        ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $q
    ) {
        $r = clone $R;

        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->yminusx);
        $r->Y = self::fe_mul($r->Y, $q->yplusx);
        $r->T = self::fe_mul($q->xy2d, $p->T);
        $t0 = self::fe_add($p->Z, $p->Z);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_sub($t0, $r->T);
        $r->T = self::fe_add($t0, $r->T);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2
     */
    public static function ge_p1p1_to_p2(ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p)
    {
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P2();
        $r->X = self::fe_mul($p->X, $p->T);
        $r->Y = self::fe_mul($p->Y, $p->Z);
        $r->Z = self::fe_mul($p->Z, $p->T);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
     */
    public static function ge_p1p1_to_p3(ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 $p)
    {
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P3();
        $r->X = self::fe_mul($p->X, $p->T);
        $r->Y = self::fe_mul($p->Y, $p->Z);
        $r->Z = self::fe_mul($p->Z, $p->T);
        $r->T = self::fe_mul($p->X, $p->Y);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2
     */
    public static function ge_p2_0()
    {
        return new ParagonIE_Sodium_Core_Curve25519_Ge_P2(
            self::fe_0(),
            self::fe_1(),
            self::fe_1()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P2 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_p2_dbl(ParagonIE_Sodium_Core_Curve25519_Ge_P2 $p)
    {
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1();

        $r->X = self::fe_sq($p->X);
        $r->Z = self::fe_sq($p->Y);
        $r->T = self::fe_sq2($p->Z);
        $r->Y = self::fe_add($p->X, $p->Y);
        $t0   = self::fe_sq($r->Y);
        $r->Y = self::fe_add($r->Z, $r->X);
        $r->Z = self::fe_sub($r->Z, $r->X);
        $r->X = self::fe_sub($t0, $r->Y);
        $r->T = self::fe_sub($r->T, $r->Z);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
     */
    public static function ge_p3_0()
    {
        return new ParagonIE_Sodium_Core_Curve25519_Ge_P3(
            self::fe_0(),
            self::fe_1(),
            self::fe_1(),
            self::fe_0()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_Cached
     */
    public static function ge_p3_to_cached(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p)
    {
        static $d2 = null;
        if ($d2 === null) {
            $d2 = ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$d2);
        }
        /** @var ParagonIE_Sodium_Core_Curve25519_Fe $d2 */
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached();
        $r->YplusX = self::fe_add($p->Y, $p->X);
        $r->YminusX = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_copy($p->Z);
        $r->T2d = self::fe_mul($p->T, $d2);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2
     */
    public static function ge_p3_to_p2(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p)
    {
        return new ParagonIE_Sodium_Core_Curve25519_Ge_P2(
            $p->X,
            $p->Y,
            $p->Z
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p3_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $h)
    {
        $recip = self::fe_invert($h->Z);
        $x = self::fe_mul($h->X, $recip);
        $y = self::fe_mul($h->Y, $recip);
        $s = self::fe_tobytes($y);
        $s[31] = self::intToChr(
            self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7)
        );
        return $s;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_p3_dbl(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p)
    {
        $q = self::ge_p3_to_p2($p);
        return self::ge_p2_dbl($q);
    }

    /**
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
     */
    public static function ge_precomp_0()
    {
        return new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp(
            self::fe_1(),
            self::fe_1(),
            self::fe_0()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $b
     * @param int $c
     * @return int
     */
    public static function equal($b, $c)
    {
        return (int) ((($b ^ $c) - 1 & 0xffffffff) >> 31);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int|string $char
     * @return int (1 = yes, 0 = no)
     * @throws SodiumException
     * @throws TypeError
     */
    public static function negative($char)
    {
        if (is_int($char)) {
            return $char < 0 ? 1 : 0;
        }
        $x = self::chrToInt(self::substr($char, 0, 1));
        return (int) ($x >> 63);
    }

    /**
     * Conditional move
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $t
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $u
     * @param int $b
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
     */
    public static function cmov(
        ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $t,
        ParagonIE_Sodium_Core_Curve25519_Ge_Precomp $u,
        $b
    ) {
        if (!is_int($b)) {
            throw new InvalidArgumentException('Expected an integer.');
        }
        return new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp(
            self::fe_cmov($t->yplusx, $u->yplusx, $b),
            self::fe_cmov($t->yminusx, $u->yminusx, $b),
            self::fe_cmov($t->xy2d, $u->xy2d, $b)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $pos
     * @param int $b
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayOffset
     */
    public static function ge_select($pos = 0, $b = 0)
    {
        static $base = null;
        if ($base === null) {
            $base = array();
            /** @var int $i */
            foreach (self::$base as $i => $bas) {
                for ($j = 0; $j < 8; ++$j) {
                    $base[$i][$j] = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp(
                        ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][0]),
                        ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][1]),
                        ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($bas[$j][2])
                    );
                }
            }
        }
        /** @var array<int, array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp>> $base */
        if (!is_int($pos)) {
            throw new InvalidArgumentException('Position must be an integer');
        }
        if ($pos < 0 || $pos > 31) {
            throw new RangeException('Position is out of range [0, 31]');
        }

        /** @var int $bnegative */
        $bnegative = self::negative($b);
        /** @var int $babs */
        $babs = $b - (((-$bnegative) & $b) << 1);

        $t = self::ge_precomp_0();
        for ($i = 0; $i < 8; ++$i) {
            $t = self::cmov(
                $t,
                $base[$pos][$i],
                self::equal($babs, $i + 1)
            );
        }
        $minusT = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp(
            self::fe_copy($t->yminusx),
            self::fe_copy($t->yplusx),
            self::fe_neg($t->xy2d)
        );
        return self::cmov($t, $minusT, $bnegative);
    }

    /**
     * Subtract two group elements.
     *
     * r = p - q
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
     */
    public static function ge_sub(
        ParagonIE_Sodium_Core_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core_Curve25519_Ge_Cached $q
    ) {
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1();

        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->YminusX);
        $r->Y = self::fe_mul($r->Y, $q->YplusX);
        $r->T = self::fe_mul($q->T2d, $p->T);
        $r->X = self::fe_mul($p->Z, $q->Z);
        $t0 = self::fe_add($r->X, $r->X);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_sub($t0, $r->T);
        $r->T = self::fe_add($t0, $r->T);

        return $r;
    }

    /**
     * Convert a group element to a byte string.
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P2 $h
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_tobytes(ParagonIE_Sodium_Core_Curve25519_Ge_P2 $h)
    {
        $recip = self::fe_invert($h->Z);
        $x = self::fe_mul($h->X, $recip);
        $y = self::fe_mul($h->Y, $recip);
        $s = self::fe_tobytes($y);
        $s[31] = self::intToChr(
            self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7)
        );
        return $s;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A
     * @param string $b
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P2
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     */
    public static function ge_double_scalarmult_vartime(
        $a,
        ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A,
        $b
    ) {
        /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Cached> $Ai */
        $Ai = array();

        /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Precomp> $Bi */
        static $Bi = array();
        if (!$Bi) {
            for ($i = 0; $i < 8; ++$i) {
                $Bi[$i] = new ParagonIE_Sodium_Core_Curve25519_Ge_Precomp(
                    ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][0]),
                    ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][1]),
                    ParagonIE_Sodium_Core_Curve25519_Fe::fromArray(self::$base2[$i][2])
                );
            }
        }
        for ($i = 0; $i < 8; ++$i) {
            $Ai[$i] = new ParagonIE_Sodium_Core_Curve25519_Ge_Cached(
                self::fe_0(),
                self::fe_0(),
                self::fe_0(),
                self::fe_0()
            );
        }

        # slide(aslide,a);
        # slide(bslide,b);
        /** @var array<int, int> $aslide */
        $aslide = self::slide($a);
        /** @var array<int, int> $bslide */
        $bslide = self::slide($b);

        # ge_p3_to_cached(&Ai[0],A);
        # ge_p3_dbl(&t,A); ge_p1p1_to_p3(&A2,&t);
        $Ai[0] = self::ge_p3_to_cached($A);
        $t = self::ge_p3_dbl($A);
        $A2 = self::ge_p1p1_to_p3($t);

        # ge_add(&t,&A2,&Ai[0]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[1],&u);
        # ge_add(&t,&A2,&Ai[1]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[2],&u);
        # ge_add(&t,&A2,&Ai[2]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[3],&u);
        # ge_add(&t,&A2,&Ai[3]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[4],&u);
        # ge_add(&t,&A2,&Ai[4]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[5],&u);
        # ge_add(&t,&A2,&Ai[5]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[6],&u);
        # ge_add(&t,&A2,&Ai[6]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[7],&u);
        for ($i = 0; $i < 7; ++$i) {
            $t = self::ge_add($A2, $Ai[$i]);
            $u = self::ge_p1p1_to_p3($t);
            $Ai[$i + 1] = self::ge_p3_to_cached($u);
        }

        # ge_p2_0(r);
        $r = self::ge_p2_0();

        # for (i = 255;i >= 0;--i) {
        #     if (aslide[i] || bslide[i]) break;
        # }
        $i = 255;
        for (; $i >= 0; --$i) {
            if ($aslide[$i] || $bslide[$i]) {
                break;
            }
        }

        # for (;i >= 0;--i) {
        for (; $i >= 0; --$i) {
            # ge_p2_dbl(&t,r);
            $t = self::ge_p2_dbl($r);

            # if (aslide[i] > 0) {
            if ($aslide[$i] > 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_add(&t,&u,&Ai[aslide[i]/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_add(
                    $u,
                    $Ai[(int) floor($aslide[$i] / 2)]
                );
            # } else if (aslide[i] < 0) {
            } elseif ($aslide[$i] < 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_sub(&t,&u,&Ai[(-aslide[i])/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_sub(
                    $u,
                    $Ai[(int) floor(-$aslide[$i] / 2)]
                );
            }

            # if (bslide[i] > 0) {
            if ($bslide[$i] > 0) {
                /** @var int $index */
                $index = (int) floor($bslide[$i] / 2);
                # ge_p1p1_to_p3(&u,&t);
                # ge_madd(&t,&u,&Bi[bslide[i]/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_madd($t, $u, $Bi[$index]);
            # } else if (bslide[i] < 0) {
            } elseif ($bslide[$i] < 0) {
                /** @var int $index */
                $index = (int) floor(-$bslide[$i] / 2);
                # ge_p1p1_to_p3(&u,&t);
                # ge_msub(&t,&u,&Bi[(-bslide[i])/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_msub($t, $u, $Bi[$index]);
            }
            # ge_p1p1_to_p2(r,&t);
            $r = self::ge_p1p1_to_p2($t);
        }
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     */
    public static function ge_scalarmult_base($a)
    {
        /** @var array<int, int> $e */
        $e = array();
        $r = new ParagonIE_Sodium_Core_Curve25519_Ge_P1p1();

        for ($i = 0; $i < 32; ++$i) {
            /** @var int $dbl */
            $dbl = (int) $i << 1;
            $e[$dbl] = (int) self::chrToInt($a[$i]) & 15;
            $e[$dbl + 1] = (int) (self::chrToInt($a[$i]) >> 4) & 15;
        }

        /** @var int $carry */
        $carry = 0;
        for ($i = 0; $i < 63; ++$i) {
            $e[$i] += $carry;
            /** @var int $carry */
            $carry = $e[$i] + 8;
            /** @var int $carry */
            $carry >>= 4;
            $e[$i] -= $carry << 4;
        }
        /** @var array<int, int> $e */
        $e[63] += (int) $carry;

        $h = self::ge_p3_0();

        for ($i = 1; $i < 64; $i += 2) {
            $t = self::ge_select((int) floor($i / 2), (int) $e[$i]);
            $r = self::ge_madd($r, $h, $t);
            $h = self::ge_p1p1_to_p3($r);
        }

        $r = self::ge_p3_dbl($h);

        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);
        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);
        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);

        $h = self::ge_p1p1_to_p3($r);

        for ($i = 0; $i < 64; $i += 2) {
            $t = self::ge_select($i >> 1, (int) $e[$i]);
            $r = self::ge_madd($r, $h, $t);
            $h = self::ge_p1p1_to_p3($r);
        }
        return $h;
    }

    /**
     * Calculates (ab + c) mod l
     * where l = 2^252 + 27742317777372353535851937790883648493
     *
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param string $b
     * @param string $c
     * @return string
     * @throws TypeError
     */
    public static function sc_muladd($a, $b, $c)
    {
        /** @var int $a0 */
        $a0 = 2097151 & self::load_3(self::substr($a, 0, 3));
        /** @var int $a1 */
        $a1 = 2097151 & (self::load_4(self::substr($a, 2, 4)) >> 5);
        /** @var int $a2 */
        $a2 = 2097151 & (self::load_3(self::substr($a, 5, 3)) >> 2);
        /** @var int $a3 */
        $a3 = 2097151 & (self::load_4(self::substr($a, 7, 4)) >> 7);
        /** @var int $a4 */
        $a4 = 2097151 & (self::load_4(self::substr($a, 10, 4)) >> 4);
        /** @var int $a5 */
        $a5 = 2097151 & (self::load_3(self::substr($a, 13, 3)) >> 1);
        /** @var int $a6 */
        $a6 = 2097151 & (self::load_4(self::substr($a, 15, 4)) >> 6);
        /** @var int $a7 */
        $a7 = 2097151 & (self::load_3(self::substr($a, 18, 3)) >> 3);
        /** @var int $a8 */
        $a8 = 2097151 & self::load_3(self::substr($a, 21, 3));
        /** @var int $a9 */
        $a9 = 2097151 & (self::load_4(self::substr($a, 23, 4)) >> 5);
        /** @var int $a10 */
        $a10 = 2097151 & (self::load_3(self::substr($a, 26, 3)) >> 2);
        /** @var int $a11 */
        $a11 = (self::load_4(self::substr($a, 28, 4)) >> 7);

        /** @var int $b0 */
        $b0 = 2097151 & self::load_3(self::substr($b, 0, 3));
        /** @var int $b1 */
        $b1 = 2097151 & (self::load_4(self::substr($b, 2, 4)) >> 5);
        /** @var int $b2 */
        $b2 = 2097151 & (self::load_3(self::substr($b, 5, 3)) >> 2);
        /** @var int $b3 */
        $b3 = 2097151 & (self::load_4(self::substr($b, 7, 4)) >> 7);
        /** @var int $b4 */
        $b4 = 2097151 & (self::load_4(self::substr($b, 10, 4)) >> 4);
        /** @var int $b5 */
        $b5 = 2097151 & (self::load_3(self::substr($b, 13, 3)) >> 1);
        /** @var int $b6 */
        $b6 = 2097151 & (self::load_4(self::substr($b, 15, 4)) >> 6);
        /** @var int $b7 */
        $b7 = 2097151 & (self::load_3(self::substr($b, 18, 3)) >> 3);
        /** @var int $b8 */
        $b8 = 2097151 & self::load_3(self::substr($b, 21, 3));
        /** @var int $b9 */
        $b9 = 2097151 & (self::load_4(self::substr($b, 23, 4)) >> 5);
        /** @var int $b10 */
        $b10 = 2097151 & (self::load_3(self::substr($b, 26, 3)) >> 2);
        /** @var int $b11 */
        $b11 = (self::load_4(self::substr($b, 28, 4)) >> 7);

        /** @var int $c0 */
        $c0 = 2097151 & self::load_3(self::substr($c, 0, 3));
        /** @var int $c1 */
        $c1 = 2097151 & (self::load_4(self::substr($c, 2, 4)) >> 5);
        /** @var int $c2 */
        $c2 = 2097151 & (self::load_3(self::substr($c, 5, 3)) >> 2);
        /** @var int $c3 */
        $c3 = 2097151 & (self::load_4(self::substr($c, 7, 4)) >> 7);
        /** @var int $c4 */
        $c4 = 2097151 & (self::load_4(self::substr($c, 10, 4)) >> 4);
        /** @var int $c5 */
        $c5 = 2097151 & (self::load_3(self::substr($c, 13, 3)) >> 1);
        /** @var int $c6 */
        $c6 = 2097151 & (self::load_4(self::substr($c, 15, 4)) >> 6);
        /** @var int $c7 */
        $c7 = 2097151 & (self::load_3(self::substr($c, 18, 3)) >> 3);
        /** @var int $c8 */
        $c8 = 2097151 & self::load_3(self::substr($c, 21, 3));
        /** @var int $c9 */
        $c9 = 2097151 & (self::load_4(self::substr($c, 23, 4)) >> 5);
        /** @var int $c10 */
        $c10 = 2097151 & (self::load_3(self::substr($c, 26, 3)) >> 2);
        /** @var int $c11 */
        $c11 = (self::load_4(self::substr($c, 28, 4)) >> 7);

        /* Can't really avoid the pyramid here: */
        $s0 = $c0 + self::mul($a0, $b0, 24);
        $s1 = $c1 + self::mul($a0, $b1, 24) + self::mul($a1, $b0, 24);
        $s2 = $c2 + self::mul($a0, $b2, 24) + self::mul($a1, $b1, 24) + self::mul($a2, $b0, 24);
        $s3 = $c3 + self::mul($a0, $b3, 24) + self::mul($a1, $b2, 24) + self::mul($a2, $b1, 24) + self::mul($a3, $b0, 24);
        $s4 = $c4 + self::mul($a0, $b4, 24) + self::mul($a1, $b3, 24) + self::mul($a2, $b2, 24) + self::mul($a3, $b1, 24) +
               self::mul($a4, $b0, 24);
        $s5 = $c5 + self::mul($a0, $b5, 24) + self::mul($a1, $b4, 24) + self::mul($a2, $b3, 24) + self::mul($a3, $b2, 24) +
               self::mul($a4, $b1, 24) + self::mul($a5, $b0, 24);
        $s6 = $c6 + self::mul($a0, $b6, 24) + self::mul($a1, $b5, 24) + self::mul($a2, $b4, 24) + self::mul($a3, $b3, 24) +
               self::mul($a4, $b2, 24) + self::mul($a5, $b1, 24) + self::mul($a6, $b0, 24);
        $s7 = $c7 + self::mul($a0, $b7, 24) + self::mul($a1, $b6, 24) + self::mul($a2, $b5, 24) + self::mul($a3, $b4, 24) +
               self::mul($a4, $b3, 24) + self::mul($a5, $b2, 24) + self::mul($a6, $b1, 24) + self::mul($a7, $b0, 24);
        $s8 = $c8 + self::mul($a0, $b8, 24) + self::mul($a1, $b7, 24) + self::mul($a2, $b6, 24) + self::mul($a3, $b5, 24) +
               self::mul($a4, $b4, 24) + self::mul($a5, $b3, 24) + self::mul($a6, $b2, 24) + self::mul($a7, $b1, 24) +
               self::mul($a8, $b0, 24);
        $s9 = $c9 + self::mul($a0, $b9, 24) + self::mul($a1, $b8, 24) + self::mul($a2, $b7, 24) + self::mul($a3, $b6, 24) +
               self::mul($a4, $b5, 24) + self::mul($a5, $b4, 24) + self::mul($a6, $b3, 24) + self::mul($a7, $b2, 24) +
               self::mul($a8, $b1, 24) + self::mul($a9, $b0, 24);
        $s10 = $c10 + self::mul($a0, $b10, 24) + self::mul($a1, $b9, 24) + self::mul($a2, $b8, 24) + self::mul($a3, $b7, 24) +
               self::mul($a4, $b6, 24) + self::mul($a5, $b5, 24) + self::mul($a6, $b4, 24) + self::mul($a7, $b3, 24) +
               self::mul($a8, $b2, 24) + self::mul($a9, $b1, 24) + self::mul($a10, $b0, 24);
        $s11 = $c11 + self::mul($a0, $b11, 24) + self::mul($a1, $b10, 24) + self::mul($a2, $b9, 24) + self::mul($a3, $b8, 24) +
               self::mul($a4, $b7, 24) + self::mul($a5, $b6, 24) + self::mul($a6, $b5, 24) + self::mul($a7, $b4, 24) +
               self::mul($a8, $b3, 24) + self::mul($a9, $b2, 24) + self::mul($a10, $b1, 24) + self::mul($a11, $b0, 24);
        $s12 = self::mul($a1, $b11, 24) + self::mul($a2, $b10, 24) + self::mul($a3, $b9, 24) + self::mul($a4, $b8, 24) +
               self::mul($a5, $b7, 24) + self::mul($a6, $b6, 24) + self::mul($a7, $b5, 24) + self::mul($a8, $b4, 24) +
               self::mul($a9, $b3, 24) + self::mul($a10, $b2, 24) + self::mul($a11, $b1, 24);
        $s13 = self::mul($a2, $b11, 24) + self::mul($a3, $b10, 24) + self::mul($a4, $b9, 24) + self::mul($a5, $b8, 24) +
               self::mul($a6, $b7, 24) + self::mul($a7, $b6, 24) + self::mul($a8, $b5, 24) + self::mul($a9, $b4, 24) +
               self::mul($a10, $b3, 24) + self::mul($a11, $b2, 24);
        $s14 = self::mul($a3, $b11, 24) + self::mul($a4, $b10, 24) + self::mul($a5, $b9, 24) + self::mul($a6, $b8, 24) +
               self::mul($a7, $b7, 24) + self::mul($a8, $b6, 24) + self::mul($a9, $b5, 24) + self::mul($a10, $b4, 24) +
               self::mul($a11, $b3, 24);
        $s15 = self::mul($a4, $b11, 24) + self::mul($a5, $b10, 24) + self::mul($a6, $b9, 24) + self::mul($a7, $b8, 24) +
               self::mul($a8, $b7, 24) + self::mul($a9, $b6, 24) + self::mul($a10, $b5, 24) + self::mul($a11, $b4, 24);
        $s16 = self::mul($a5, $b11, 24) + self::mul($a6, $b10, 24) + self::mul($a7, $b9, 24) + self::mul($a8, $b8, 24) +
               self::mul($a9, $b7, 24) + self::mul($a10, $b6, 24) + self::mul($a11, $b5, 24);
        $s17 = self::mul($a6, $b11, 24) + self::mul($a7, $b10, 24) + self::mul($a8, $b9, 24) + self::mul($a9, $b8, 24) +
               self::mul($a10, $b7, 24) + self::mul($a11, $b6, 24);
        $s18 = self::mul($a7, $b11, 24) + self::mul($a8, $b10, 24) + self::mul($a9, $b9, 24) + self::mul($a10, $b8, 24) +
               self::mul($a11, $b7, 24);
        $s19 = self::mul($a8, $b11, 24) + self::mul($a9, $b10, 24) + self::mul($a10, $b9, 24) + self::mul($a11, $b8, 24);
        $s20 = self::mul($a9, $b11, 24) + self::mul($a10, $b10, 24) + self::mul($a11, $b9, 24);
        $s21 = self::mul($a10, $b11, 24) + self::mul($a11, $b10, 24);
        $s22 = self::mul($a11, $b11, 24);
        $s23 = 0;

        /** @var int $carry0 */
        $carry0 = ($s0 + (1 << 20)) >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry2 */
        $carry2 = ($s2 + (1 << 20)) >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry4 */
        $carry4 = ($s4 + (1 << 20)) >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry6 */
        $carry6 = ($s6 + (1 << 20)) >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry8 */
        $carry8 = ($s8 + (1 << 20)) >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry10 */
        $carry10 = ($s10 + (1 << 20)) >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;
        /** @var int $carry12 */
        $carry12 = ($s12 + (1 << 20)) >> 21;
        $s13 += $carry12;
        $s12 -= $carry12 << 21;
        /** @var int $carry14 */
        $carry14 = ($s14 + (1 << 20)) >> 21;
        $s15 += $carry14;
        $s14 -= $carry14 << 21;
        /** @var int $carry16 */
        $carry16 = ($s16 + (1 << 20)) >> 21;
        $s17 += $carry16;
        $s16 -= $carry16 << 21;
        /** @var int $carry18 */
        $carry18 = ($s18 + (1 << 20)) >> 21;
        $s19 += $carry18;
        $s18 -= $carry18 << 21;
        /** @var int $carry20 */
        $carry20 = ($s20 + (1 << 20)) >> 21;
        $s21 += $carry20;
        $s20 -= $carry20 << 21;
        /** @var int $carry22 */
        $carry22 = ($s22 + (1 << 20)) >> 21;
        $s23 += $carry22;
        $s22 -= $carry22 << 21;

        /** @var int $carry1 */
        $carry1 = ($s1 + (1 << 20)) >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry3 */
        $carry3 = ($s3 + (1 << 20)) >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry5 */
        $carry5 = ($s5 + (1 << 20)) >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry7 */
        $carry7 = ($s7 + (1 << 20)) >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry9 */
        $carry9 = ($s9 + (1 << 20)) >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry11 */
        $carry11 = ($s11 + (1 << 20)) >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;
        /** @var int $carry13 */
        $carry13 = ($s13 + (1 << 20)) >> 21;
        $s14 += $carry13;
        $s13 -= $carry13 << 21;
        /** @var int $carry15 */
        $carry15 = ($s15 + (1 << 20)) >> 21;
        $s16 += $carry15;
        $s15 -= $carry15 << 21;
        /** @var int $carry17 */
        $carry17 = ($s17 + (1 << 20)) >> 21;
        $s18 += $carry17;
        $s17 -= $carry17 << 21;
        /** @var int $carry19 */
        $carry19 = ($s19 + (1 << 20)) >> 21;
        $s20 += $carry19;
        $s19 -= $carry19 << 21;
        /** @var int $carry21 */
        $carry21 = ($s21 + (1 << 20)) >> 21;
        $s22 += $carry21;
        $s21 -= $carry21 << 21;

        $s11 += self::mul($s23, 666643, 20);
        $s12 += self::mul($s23, 470296, 19);
        $s13 += self::mul($s23, 654183, 20);
        $s14 -= self::mul($s23, 997805, 20);
        $s15 += self::mul($s23, 136657, 18);
        $s16 -= self::mul($s23, 683901, 20);

        $s10 += self::mul($s22, 666643, 20);
        $s11 += self::mul($s22, 470296, 19);
        $s12 += self::mul($s22, 654183, 20);
        $s13 -= self::mul($s22, 997805, 20);
        $s14 += self::mul($s22, 136657, 18);
        $s15 -= self::mul($s22, 683901, 20);

        $s9  += self::mul($s21,  666643, 20);
        $s10 += self::mul($s21,  470296, 19);
        $s11 += self::mul($s21,  654183, 20);
        $s12 -= self::mul($s21,  997805, 20);
        $s13 += self::mul($s21,  136657, 18);
        $s14 -= self::mul($s21,  683901, 20);

        $s8  += self::mul($s20,  666643, 20);
        $s9  += self::mul($s20,  470296, 19);
        $s10 += self::mul($s20,  654183, 20);
        $s11 -= self::mul($s20,  997805, 20);
        $s12 += self::mul($s20,  136657, 18);
        $s13 -= self::mul($s20,  683901, 20);

        $s7  += self::mul($s19,  666643, 20);
        $s8  += self::mul($s19,  470296, 19);
        $s9  += self::mul($s19,  654183, 20);
        $s10 -= self::mul($s19,  997805, 20);
        $s11 += self::mul($s19,  136657, 18);
        $s12 -= self::mul($s19,  683901, 20);

        $s6  += self::mul($s18,  666643, 20);
        $s7  += self::mul($s18,  470296, 19);
        $s8  += self::mul($s18,  654183, 20);
        $s9  -= self::mul($s18,  997805, 20);
        $s10 += self::mul($s18,  136657, 18);
        $s11 -= self::mul($s18,  683901, 20);

        /** @var int $carry6 */
        $carry6 = ($s6 + (1 << 20)) >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry8 */
        $carry8 = ($s8 + (1 << 20)) >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry10 */
        $carry10 = ($s10 + (1 << 20)) >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;
        /** @var int $carry12 */
        $carry12 = ($s12 + (1 << 20)) >> 21;
        $s13 += $carry12;
        $s12 -= $carry12 << 21;
        /** @var int $carry14 */
        $carry14 = ($s14 + (1 << 20)) >> 21;
        $s15 += $carry14;
        $s14 -= $carry14 << 21;
        /** @var int $carry16 */
        $carry16 = ($s16 + (1 << 20)) >> 21;
        $s17 += $carry16;
        $s16 -= $carry16 << 21;

        /** @var int $carry7 */
        $carry7 = ($s7 + (1 << 20)) >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry9 */
        $carry9 = ($s9 + (1 << 20)) >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry11 */
        $carry11 = ($s11 + (1 << 20)) >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;
        /** @var int $carry13 */
        $carry13 = ($s13 + (1 << 20)) >> 21;
        $s14 += $carry13;
        $s13 -= $carry13 << 21;
        /** @var int $carry15 */
        $carry15 = ($s15 + (1 << 20)) >> 21;
        $s16 += $carry15;
        $s15 -= $carry15 << 21;

        $s5  += self::mul($s17,  666643, 20);
        $s6  += self::mul($s17,  470296, 19);
        $s7  += self::mul($s17,  654183, 20);
        $s8  -= self::mul($s17,  997805, 20);
        $s9  += self::mul($s17,  136657, 18);
        $s10 -= self::mul($s17,  683901, 20);

        $s4 += self::mul($s16,  666643, 20);
        $s5 += self::mul($s16,  470296, 19);
        $s6 += self::mul($s16,  654183, 20);
        $s7 -= self::mul($s16,  997805, 20);
        $s8 += self::mul($s16,  136657, 18);
        $s9 -= self::mul($s16,  683901, 20);

        $s3 += self::mul($s15,  666643, 20);
        $s4 += self::mul($s15,  470296, 19);
        $s5 += self::mul($s15,  654183, 20);
        $s6 -= self::mul($s15,  997805, 20);
        $s7 += self::mul($s15,  136657, 18);
        $s8 -= self::mul($s15,  683901, 20);

        $s2 += self::mul($s14,  666643, 20);
        $s3 += self::mul($s14,  470296, 19);
        $s4 += self::mul($s14,  654183, 20);
        $s5 -= self::mul($s14,  997805, 20);
        $s6 += self::mul($s14,  136657, 18);
        $s7 -= self::mul($s14,  683901, 20);

        $s1 += self::mul($s13,  666643, 20);
        $s2 += self::mul($s13,  470296, 19);
        $s3 += self::mul($s13,  654183, 20);
        $s4 -= self::mul($s13,  997805, 20);
        $s5 += self::mul($s13,  136657, 18);
        $s6 -= self::mul($s13,  683901, 20);

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);
        $s12 = 0;

        /** @var int $carry0 */
        $carry0 = ($s0 + (1 << 20)) >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry2 */
        $carry2 = ($s2 + (1 << 20)) >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry4 */
        $carry4 = ($s4 + (1 << 20)) >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry6 */
        $carry6 = ($s6 + (1 << 20)) >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry8 */
        $carry8 = ($s8 + (1 << 20)) >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry10 */
        $carry10 = ($s10 + (1 << 20)) >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;

        /** @var int $carry1 */
        $carry1 = ($s1 + (1 << 20)) >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry3 */
        $carry3 = ($s3 + (1 << 20)) >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry5 */
        $carry5 = ($s5 + (1 << 20)) >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry7 */
        $carry7 = ($s7 + (1 << 20)) >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry9 */
        $carry9 = ($s9 + (1 << 20)) >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry11 */
        $carry11 = ($s11 + (1 << 20)) >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);
        $s12 = 0;

        /** @var int $carry0 */
        $carry0 = $s0 >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry1 */
        $carry1 = $s1 >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry2 */
        $carry2 = $s2 >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry3 */
        $carry3 = $s3 >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry4 */
        $carry4 = $s4 >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry5 */
        $carry5 = $s5 >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry6 */
        $carry6 = $s6 >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry7 */
        $carry7 = $s7 >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry8 */
        $carry8 = $s8 >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry9 */
        $carry9 = $s9 >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry10 */
        $carry10 = $s10 >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;
        /** @var int $carry11 */
        $carry11 = $s11 >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);

        /** @var int $carry0 */
        $carry0 = $s0 >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry1 */
        $carry1 = $s1 >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry2 */
        $carry2 = $s2 >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry3 */
        $carry3 = $s3 >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry4 */
        $carry4 = $s4 >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry5 */
        $carry5 = $s5 >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry6 */
        $carry6 = $s6 >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry7 */
        $carry7 = $s7 >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry8 */
        $carry8 = $s8 >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry9 */
        $carry9 = $s9 >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry10 */
        $carry10 = $s10 >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;

        /**
         * @var array<int, int>
         */
        $arr = array(
            (int) (0xff & ($s0 >> 0)),
            (int) (0xff & ($s0 >> 8)),
            (int) (0xff & (($s0 >> 16) | $s1 << 5)),
            (int) (0xff & ($s1 >> 3)),
            (int) (0xff & ($s1 >> 11)),
            (int) (0xff & (($s1 >> 19) | $s2 << 2)),
            (int) (0xff & ($s2 >> 6)),
            (int) (0xff & (($s2 >> 14) | $s3 << 7)),
            (int) (0xff & ($s3 >> 1)),
            (int) (0xff & ($s3 >> 9)),
            (int) (0xff & (($s3 >> 17) | $s4 << 4)),
            (int) (0xff & ($s4 >> 4)),
            (int) (0xff & ($s4 >> 12)),
            (int) (0xff & (($s4 >> 20) | $s5 << 1)),
            (int) (0xff & ($s5 >> 7)),
            (int) (0xff & (($s5 >> 15) | $s6 << 6)),
            (int) (0xff & ($s6 >> 2)),
            (int) (0xff & ($s6 >> 10)),
            (int) (0xff & (($s6 >> 18) | $s7 << 3)),
            (int) (0xff & ($s7 >> 5)),
            (int) (0xff & ($s7 >> 13)),
            (int) (0xff & ($s8 >> 0)),
            (int) (0xff & ($s8 >> 8)),
            (int) (0xff & (($s8 >> 16) | $s9 << 5)),
            (int) (0xff & ($s9 >> 3)),
            (int) (0xff & ($s9 >> 11)),
            (int) (0xff & (($s9 >> 19) | $s10 << 2)),
            (int) (0xff & ($s10 >> 6)),
            (int) (0xff & (($s10 >> 14) | $s11 << 7)),
            (int) (0xff & ($s11 >> 1)),
            (int) (0xff & ($s11 >> 9)),
            0xff & ($s11 >> 17)
        );
        return self::intArrayToString($arr);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return string
     * @throws TypeError
     */
    public static function sc_reduce($s)
    {
        /** @var int $s0 */
        $s0 = 2097151 & self::load_3(self::substr($s, 0, 3));
        /** @var int $s1 */
        $s1 = 2097151 & (self::load_4(self::substr($s, 2, 4)) >> 5);
        /** @var int $s2 */
        $s2 = 2097151 & (self::load_3(self::substr($s, 5, 3)) >> 2);
        /** @var int $s3 */
        $s3 = 2097151 & (self::load_4(self::substr($s, 7, 4)) >> 7);
        /** @var int $s4 */
        $s4 = 2097151 & (self::load_4(self::substr($s, 10, 4)) >> 4);
        /** @var int $s5 */
        $s5 = 2097151 & (self::load_3(self::substr($s, 13, 3)) >> 1);
        /** @var int $s6 */
        $s6 = 2097151 & (self::load_4(self::substr($s, 15, 4)) >> 6);
        /** @var int $s7 */
        $s7 = 2097151 & (self::load_3(self::substr($s, 18, 4)) >> 3);
        /** @var int $s8 */
        $s8 = 2097151 & self::load_3(self::substr($s, 21, 3));
        /** @var int $s9 */
        $s9 = 2097151 & (self::load_4(self::substr($s, 23, 4)) >> 5);
        /** @var int $s10 */
        $s10 = 2097151 & (self::load_3(self::substr($s, 26, 3)) >> 2);
        /** @var int $s11 */
        $s11 = 2097151 & (self::load_4(self::substr($s, 28, 4)) >> 7);
        /** @var int $s12 */
        $s12 = 2097151 & (self::load_4(self::substr($s, 31, 4)) >> 4);
        /** @var int $s13 */
        $s13 = 2097151 & (self::load_3(self::substr($s, 34, 3)) >> 1);
        /** @var int $s14 */
        $s14 = 2097151 & (self::load_4(self::substr($s, 36, 4)) >> 6);
        /** @var int $s15 */
        $s15 = 2097151 & (self::load_3(self::substr($s, 39, 4)) >> 3);
        /** @var int $s16 */
        $s16 = 2097151 & self::load_3(self::substr($s, 42, 3));
        /** @var int $s17 */
        $s17 = 2097151 & (self::load_4(self::substr($s, 44, 4)) >> 5);
        /** @var int $s18 */
        $s18 = 2097151 & (self::load_3(self::substr($s, 47, 3)) >> 2);
        /** @var int $s19 */
        $s19 = 2097151 & (self::load_4(self::substr($s, 49, 4)) >> 7);
        /** @var int $s20 */
        $s20 = 2097151 & (self::load_4(self::substr($s, 52, 4)) >> 4);
        /** @var int $s21 */
        $s21 = 2097151 & (self::load_3(self::substr($s, 55, 3)) >> 1);
        /** @var int $s22 */
        $s22 = 2097151 & (self::load_4(self::substr($s, 57, 4)) >> 6);
        /** @var int $s23 */
        $s23 = (self::load_4(self::substr($s, 60, 4)) >> 3);

        $s11 += self::mul($s23,  666643, 20);
        $s12 += self::mul($s23,  470296, 19);
        $s13 += self::mul($s23,  654183, 20);
        $s14 -= self::mul($s23,  997805, 20);
        $s15 += self::mul($s23,  136657, 18);
        $s16 -= self::mul($s23,  683901, 20);

        $s10 += self::mul($s22,  666643, 20);
        $s11 += self::mul($s22,  470296, 19);
        $s12 += self::mul($s22,  654183, 20);
        $s13 -= self::mul($s22,  997805, 20);
        $s14 += self::mul($s22,  136657, 18);
        $s15 -= self::mul($s22,  683901, 20);

        $s9  += self::mul($s21,  666643, 20);
        $s10 += self::mul($s21,  470296, 19);
        $s11 += self::mul($s21,  654183, 20);
        $s12 -= self::mul($s21,  997805, 20);
        $s13 += self::mul($s21,  136657, 18);
        $s14 -= self::mul($s21,  683901, 20);

        $s8  += self::mul($s20,  666643, 20);
        $s9  += self::mul($s20,  470296, 19);
        $s10 += self::mul($s20,  654183, 20);
        $s11 -= self::mul($s20,  997805, 20);
        $s12 += self::mul($s20,  136657, 18);
        $s13 -= self::mul($s20,  683901, 20);

        $s7  += self::mul($s19,  666643, 20);
        $s8  += self::mul($s19,  470296, 19);
        $s9  += self::mul($s19,  654183, 20);
        $s10 -= self::mul($s19,  997805, 20);
        $s11 += self::mul($s19,  136657, 18);
        $s12 -= self::mul($s19,  683901, 20);

        $s6  += self::mul($s18,  666643, 20);
        $s7  += self::mul($s18,  470296, 19);
        $s8  += self::mul($s18,  654183, 20);
        $s9  -= self::mul($s18,  997805, 20);
        $s10 += self::mul($s18,  136657, 18);
        $s11 -= self::mul($s18,  683901, 20);

        /** @var int $carry6 */
        $carry6 = ($s6 + (1 << 20)) >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry8 */
        $carry8 = ($s8 + (1 << 20)) >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry10 */
        $carry10 = ($s10 + (1 << 20)) >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;
        /** @var int $carry12 */
        $carry12 = ($s12 + (1 << 20)) >> 21;
        $s13 += $carry12;
        $s12 -= $carry12 << 21;
        /** @var int $carry14 */
        $carry14 = ($s14 + (1 << 20)) >> 21;
        $s15 += $carry14;
        $s14 -= $carry14 << 21;
        /** @var int $carry16 */
        $carry16 = ($s16 + (1 << 20)) >> 21;
        $s17 += $carry16;
        $s16 -= $carry16 << 21;

        /** @var int $carry7 */
        $carry7 = ($s7 + (1 << 20)) >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry9 */
        $carry9 = ($s9 + (1 << 20)) >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry11 */
        $carry11 = ($s11 + (1 << 20)) >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;
        /** @var int $carry13 */
        $carry13 = ($s13 + (1 << 20)) >> 21;
        $s14 += $carry13;
        $s13 -= $carry13 << 21;
        /** @var int $carry15 */
        $carry15 = ($s15 + (1 << 20)) >> 21;
        $s16 += $carry15;
        $s15 -= $carry15 << 21;

        $s5  += self::mul($s17,  666643, 20);
        $s6  += self::mul($s17,  470296, 19);
        $s7  += self::mul($s17,  654183, 20);
        $s8  -= self::mul($s17,  997805, 20);
        $s9  += self::mul($s17,  136657, 18);
        $s10 -= self::mul($s17,  683901, 20);

        $s4 += self::mul($s16,  666643, 20);
        $s5 += self::mul($s16,  470296, 19);
        $s6 += self::mul($s16,  654183, 20);
        $s7 -= self::mul($s16,  997805, 20);
        $s8 += self::mul($s16,  136657, 18);
        $s9 -= self::mul($s16,  683901, 20);

        $s3 += self::mul($s15,  666643, 20);
        $s4 += self::mul($s15,  470296, 19);
        $s5 += self::mul($s15,  654183, 20);
        $s6 -= self::mul($s15,  997805, 20);
        $s7 += self::mul($s15,  136657, 18);
        $s8 -= self::mul($s15,  683901, 20);

        $s2 += self::mul($s14,  666643, 20);
        $s3 += self::mul($s14,  470296, 19);
        $s4 += self::mul($s14,  654183, 20);
        $s5 -= self::mul($s14,  997805, 20);
        $s6 += self::mul($s14,  136657, 18);
        $s7 -= self::mul($s14,  683901, 20);

        $s1 += self::mul($s13,  666643, 20);
        $s2 += self::mul($s13,  470296, 19);
        $s3 += self::mul($s13,  654183, 20);
        $s4 -= self::mul($s13,  997805, 20);
        $s5 += self::mul($s13,  136657, 18);
        $s6 -= self::mul($s13,  683901, 20);

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);
        $s12 = 0;

        /** @var int $carry0 */
        $carry0 = ($s0 + (1 << 20)) >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry2 */
        $carry2 = ($s2 + (1 << 20)) >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry4 */
        $carry4 = ($s4 + (1 << 20)) >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry6 */
        $carry6 = ($s6 + (1 << 20)) >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry8 */
        $carry8 = ($s8 + (1 << 20)) >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry10 */
        $carry10 = ($s10 + (1 << 20)) >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;

        /** @var int $carry1 */
        $carry1 = ($s1 + (1 << 20)) >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry3 */
        $carry3 = ($s3 + (1 << 20)) >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry5 */
        $carry5 = ($s5 + (1 << 20)) >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry7 */
        $carry7 = ($s7 + (1 << 20)) >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry9 */
        $carry9 = ($s9 + (1 << 20)) >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry11 */
        $carry11 = ($s11 + (1 << 20)) >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);
        $s12 = 0;

        /** @var int $carry0 */
        $carry0 = $s0 >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry1 */
        $carry1 = $s1 >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry2 */
        $carry2 = $s2 >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry3 */
        $carry3 = $s3 >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry4 */
        $carry4 = $s4 >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry5 */
        $carry5 = $s5 >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry6 */
        $carry6 = $s6 >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry7 */
        $carry7 = $s7 >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry8 */
        $carry8 = $s8 >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry9 */
        $carry9 = $s9 >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry10 */
        $carry10 = $s10 >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;
        /** @var int $carry11 */
        $carry11 = $s11 >> 21;
        $s12 += $carry11;
        $s11 -= $carry11 << 21;

        $s0 += self::mul($s12,  666643, 20);
        $s1 += self::mul($s12,  470296, 19);
        $s2 += self::mul($s12,  654183, 20);
        $s3 -= self::mul($s12,  997805, 20);
        $s4 += self::mul($s12,  136657, 18);
        $s5 -= self::mul($s12,  683901, 20);

        /** @var int $carry0 */
        $carry0 = $s0 >> 21;
        $s1 += $carry0;
        $s0 -= $carry0 << 21;
        /** @var int $carry1 */
        $carry1 = $s1 >> 21;
        $s2 += $carry1;
        $s1 -= $carry1 << 21;
        /** @var int $carry2 */
        $carry2 = $s2 >> 21;
        $s3 += $carry2;
        $s2 -= $carry2 << 21;
        /** @var int $carry3 */
        $carry3 = $s3 >> 21;
        $s4 += $carry3;
        $s3 -= $carry3 << 21;
        /** @var int $carry4 */
        $carry4 = $s4 >> 21;
        $s5 += $carry4;
        $s4 -= $carry4 << 21;
        /** @var int $carry5 */
        $carry5 = $s5 >> 21;
        $s6 += $carry5;
        $s5 -= $carry5 << 21;
        /** @var int $carry6 */
        $carry6 = $s6 >> 21;
        $s7 += $carry6;
        $s6 -= $carry6 << 21;
        /** @var int $carry7 */
        $carry7 = $s7 >> 21;
        $s8 += $carry7;
        $s7 -= $carry7 << 21;
        /** @var int $carry8 */
        $carry8 = $s8 >> 21;
        $s9 += $carry8;
        $s8 -= $carry8 << 21;
        /** @var int $carry9 */
        $carry9 = $s9 >> 21;
        $s10 += $carry9;
        $s9 -= $carry9 << 21;
        /** @var int $carry10 */
        $carry10 = $s10 >> 21;
        $s11 += $carry10;
        $s10 -= $carry10 << 21;

        /**
         * @var array<int, int>
         */
        $arr = array(
            (int) ($s0 >> 0),
            (int) ($s0 >> 8),
            (int) (($s0 >> 16) | $s1 << 5),
            (int) ($s1 >> 3),
            (int) ($s1 >> 11),
            (int) (($s1 >> 19) | $s2 << 2),
            (int) ($s2 >> 6),
            (int) (($s2 >> 14) | $s3 << 7),
            (int) ($s3 >> 1),
            (int) ($s3 >> 9),
            (int) (($s3 >> 17) | $s4 << 4),
            (int) ($s4 >> 4),
            (int) ($s4 >> 12),
            (int) (($s4 >> 20) | $s5 << 1),
            (int) ($s5 >> 7),
            (int) (($s5 >> 15) | $s6 << 6),
            (int) ($s6 >> 2),
            (int) ($s6 >> 10),
            (int) (($s6 >> 18) | $s7 << 3),
            (int) ($s7 >> 5),
            (int) ($s7 >> 13),
            (int) ($s8 >> 0),
            (int) ($s8 >> 8),
            (int) (($s8 >> 16) | $s9 << 5),
            (int) ($s9 >> 3),
            (int) ($s9 >> 11),
            (int) (($s9 >> 19) | $s10 << 2),
            (int) ($s10 >> 6),
            (int) (($s10 >> 14) | $s11 << 7),
            (int) ($s11 >> 1),
            (int) ($s11 >> 9),
            (int) $s11 >> 17
        );
        return self::intArrayToString($arr);
    }

    /**
     * multiply by the order of the main subgroup l = 2^252+27742317777372353535851937790883648493
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A
     * @return ParagonIE_Sodium_Core_Curve25519_Ge_P3
     */
    public static function ge_mul_l(ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A)
    {
        /** @var array<int, int> $aslide */
        $aslide = array(
            13, 0, 0, 0, 0, -1, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0,
            0, 0, 0, -3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 3, 0,
            0, 0, 0, -13, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0,
            0, 0, 11, 0, 0, 0, 0, -13, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -1,
            0, 0, 0, 0, 3, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
            0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 5, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
        );

        /** @var array<int, ParagonIE_Sodium_Core_Curve25519_Ge_Cached> $Ai size 8 */
        $Ai = array();

        # ge_p3_to_cached(&Ai[0], A);
        $Ai[0] = self::ge_p3_to_cached($A);
        # ge_p3_dbl(&t, A);
        $t = self::ge_p3_dbl($A);
        # ge_p1p1_to_p3(&A2, &t);
        $A2 = self::ge_p1p1_to_p3($t);

        for ($i = 1; $i < 8; ++$i) {
            # ge_add(&t, &A2, &Ai[0]);
            $t = self::ge_add($A2, $Ai[$i - 1]);
            # ge_p1p1_to_p3(&u, &t);
            $u = self::ge_p1p1_to_p3($t);
            # ge_p3_to_cached(&Ai[i], &u);
            $Ai[$i] = self::ge_p3_to_cached($u);
        }

        $r = self::ge_p3_0();
        for ($i = 252; $i >= 0; --$i) {
            $t = self::ge_p3_dbl($r);
            if ($aslide[$i] > 0) {
                # ge_p1p1_to_p3(&u, &t);
                $u = self::ge_p1p1_to_p3($t);
                # ge_add(&t, &u, &Ai[aslide[i] / 2]);
                $t = self::ge_add($u, $Ai[(int)($aslide[$i] / 2)]);
            } elseif ($aslide[$i] < 0) {
                # ge_p1p1_to_p3(&u, &t);
                $u = self::ge_p1p1_to_p3($t);
                # ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
                $t = self::ge_sub($u, $Ai[(int)(-$aslide[$i] / 2)]);
            }
        }

        # ge_p1p1_to_p3(r, &t);
        return self::ge_p1p1_to_p3($t);
    }
}
vendor/paragonie/sodium_compat/src/Core/BLAKE2b.php000064400000054322152177723700016124 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_BLAKE2b', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_BLAKE2b
 *
 * Based on the work of Devi Mandiri in devi/salt.
 */
abstract class ParagonIE_Sodium_Core_BLAKE2b extends ParagonIE_Sodium_Core_Util
{
    /**
     * @var SplFixedArray
     */
    protected static $iv;

    /**
     * @var array<int, array<int, int>>
     */
    protected static $sigma = array(
        array(  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15),
        array( 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3),
        array( 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4),
        array(  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8),
        array(  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13),
        array(  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9),
        array( 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11),
        array( 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10),
        array(  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5),
        array( 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0),
        array(  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15),
        array( 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3)
    );

    const BLOCKBYTES = 128;
    const OUTBYTES   = 64;
    const KEYBYTES   = 64;

    /**
     * Turn two 32-bit integers into a fixed array representing a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $high
     * @param int $low
     * @return SplFixedArray
     * @psalm-suppress MixedAssignment
     */
    public static function new64($high, $low)
    {
        $i64 = new SplFixedArray(2);
        $i64[0] = $high & 0xffffffff;
        $i64[1] = $low & 0xffffffff;
        return $i64;
    }

    /**
     * Convert an arbitrary number into an SplFixedArray of two 32-bit integers
     * that represents a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $num
     * @return SplFixedArray
     */
    protected static function to64($num)
    {
        list($hi, $lo) = self::numericTo64BitInteger($num);
        return self::new64($hi, $lo);
    }

    /**
     * Adds two 64-bit integers together, returning their sum as a SplFixedArray
     * containing two 32-bit integers (representing a 64-bit integer).
     *
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param SplFixedArray $y
     * @return SplFixedArray
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     */
    protected static function add64($x, $y)
    {
        $l = ($x[1] + $y[1]) & 0xffffffff;
        return self::new64(
            $x[0] + $y[0] + (
                ($l < $x[1]) ? 1 : 0
            ),
            $l
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param SplFixedArray $y
     * @param SplFixedArray $z
     * @return SplFixedArray
     */
    protected static function add364($x, $y, $z)
    {
        return self::add64($x, self::add64($y, $z));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param SplFixedArray $y
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function xor64(SplFixedArray $x, SplFixedArray $y)
    {
        if (!is_numeric($x[0])) {
            throw new SodiumException('x[0] is not an integer');
        }
        if (!is_numeric($x[1])) {
            throw new SodiumException('x[1] is not an integer');
        }
        if (!is_numeric($y[0])) {
            throw new SodiumException('y[0] is not an integer');
        }
        if (!is_numeric($y[1])) {
            throw new SodiumException('y[1] is not an integer');
        }
        return self::new64(
            (int) ($x[0] ^ $y[0]),
            (int) ($x[1] ^ $y[1])
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param int $c
     * @return SplFixedArray
     * @psalm-suppress MixedAssignment
     */
    public static function rotr64($x, $c)
    {
        if ($c >= 64) {
            $c %= 64;
        }
        if ($c >= 32) {
            /** @var int $tmp */
            $tmp = $x[0];
            $x[0] = $x[1];
            $x[1] = $tmp;
            $c -= 32;
        }
        if ($c === 0) {
            return $x;
        }

        $l0 = 0;
        $c = 64 - $c;

        if ($c < 32) {
            /** @var int $h0 */
            $h0 = ((int) ($x[0]) << $c) | (
                (
                    (int) ($x[1]) & ((1 << $c) - 1)
                        <<
                    (32 - $c)
                ) >> (32 - $c)
            );
            /** @var int $l0 */
            $l0 = (int) ($x[1]) << $c;
        } else {
            /** @var int $h0 */
            $h0 = (int) ($x[1]) << ($c - 32);
        }

        $h1 = 0;
        $c1 = 64 - $c;

        if ($c1 < 32) {
            /** @var int $h1 */
            $h1 = (int) ($x[0]) >> $c1;
            /** @var int $l1 */
            $l1 = ((int) ($x[1]) >> $c1) | ((int) ($x[0]) & ((1 << $c1) - 1)) << (32 - $c1);
        } else {
            /** @var int $l1 */
            $l1 = (int) ($x[0]) >> ($c1 - 32);
        }

        return self::new64($h0 | $h1, $l0 | $l1);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @return int
     * @psalm-suppress MixedOperand
     */
    protected static function flatten64($x)
    {
        return (int) ($x[0] * 4294967296 + $x[1]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param int $i
     * @return SplFixedArray
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayOffset
     */
    protected static function load64(SplFixedArray $x, $i)
    {
        /** @var int $l */
        $l = (int) ($x[$i])
             | ((int) ($x[$i+1]) << 8)
             | ((int) ($x[$i+2]) << 16)
             | ((int) ($x[$i+3]) << 24);
        /** @var int $h */
        $h = (int) ($x[$i+4])
             | ((int) ($x[$i+5]) << 8)
             | ((int) ($x[$i+6]) << 16)
             | ((int) ($x[$i+7]) << 24);
        return self::new64($h, $l);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param int $i
     * @param SplFixedArray $u
     * @return void
     * @psalm-suppress MixedAssignment
     */
    protected static function store64(SplFixedArray $x, $i, SplFixedArray $u)
    {
        $maxLength = $x->getSize() - 1;
        for ($j = 0; $j < 8; ++$j) {
            /*
               [0, 1, 2, 3, 4, 5, 6, 7]
                    ... becomes ...
               [0, 0, 0, 0, 1, 1, 1, 1]
            */
            /** @var int $uIdx */
            $uIdx = ((7 - $j) & 4) >> 2;
            $x[$i]   = ((int) ($u[$uIdx]) & 0xff);
            if (++$i > $maxLength) {
                return;
            }
            $u[$uIdx] >>= 8;
        }
    }

    /**
     * This just sets the $iv static variable.
     *
     * @internal You should not use this directly from another application
     *
     * @return void
     */
    public static function pseudoConstructor()
    {
        static $called = false;
        if ($called) {
            return;
        }
        self::$iv = new SplFixedArray(8);
        self::$iv[0] = self::new64(0x6a09e667, 0xf3bcc908);
        self::$iv[1] = self::new64(0xbb67ae85, 0x84caa73b);
        self::$iv[2] = self::new64(0x3c6ef372, 0xfe94f82b);
        self::$iv[3] = self::new64(0xa54ff53a, 0x5f1d36f1);
        self::$iv[4] = self::new64(0x510e527f, 0xade682d1);
        self::$iv[5] = self::new64(0x9b05688c, 0x2b3e6c1f);
        self::$iv[6] = self::new64(0x1f83d9ab, 0xfb41bd6b);
        self::$iv[7] = self::new64(0x5be0cd19, 0x137e2179);

        $called = true;
    }

    /**
     * Returns a fresh BLAKE2 context.
     *
     * @internal You should not use this directly from another application
     *
     * @return SplFixedArray
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     */
    protected static function context()
    {
        $ctx    = new SplFixedArray(5);
        $ctx[0] = new SplFixedArray(8);   // h
        $ctx[1] = new SplFixedArray(2);   // t
        $ctx[2] = new SplFixedArray(2);   // f
        $ctx[3] = new SplFixedArray(256); // buf
        $ctx[4] = 0;                      // buflen

        for ($i = 8; $i--;) {
            $ctx[0][$i] = self::$iv[$i];
        }
        for ($i = 256; $i--;) {
            $ctx[3][$i] = 0;
        }

        $zero = self::new64(0, 0);
        $ctx[1][0] = $zero;
        $ctx[1][1] = $zero;
        $ctx[2][0] = $zero;
        $ctx[2][1] = $zero;

        return $ctx;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $buf
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     */
    protected static function compress(SplFixedArray $ctx, SplFixedArray $buf)
    {
        $m = new SplFixedArray(16);
        $v = new SplFixedArray(16);

        for ($i = 16; $i--;) {
            $m[$i] = self::load64($buf, $i << 3);
        }

        for ($i = 8; $i--;) {
            $v[$i] = $ctx[0][$i];
        }

        $v[ 8] = self::$iv[0];
        $v[ 9] = self::$iv[1];
        $v[10] = self::$iv[2];
        $v[11] = self::$iv[3];

        $v[12] = self::xor64($ctx[1][0], self::$iv[4]);
        $v[13] = self::xor64($ctx[1][1], self::$iv[5]);
        $v[14] = self::xor64($ctx[2][0], self::$iv[6]);
        $v[15] = self::xor64($ctx[2][1], self::$iv[7]);

        for ($r = 0; $r < 12; ++$r) {
            $v = self::G($r, 0, 0, 4, 8, 12, $v, $m);
            $v = self::G($r, 1, 1, 5, 9, 13, $v, $m);
            $v = self::G($r, 2, 2, 6, 10, 14, $v, $m);
            $v = self::G($r, 3, 3, 7, 11, 15, $v, $m);
            $v = self::G($r, 4, 0, 5, 10, 15, $v, $m);
            $v = self::G($r, 5, 1, 6, 11, 12, $v, $m);
            $v = self::G($r, 6, 2, 7, 8, 13, $v, $m);
            $v = self::G($r, 7, 3, 4, 9, 14, $v, $m);
        }

        for ($i = 8; $i--;) {
            $ctx[0][$i] = self::xor64(
                $ctx[0][$i], self::xor64($v[$i], $v[$i+8])
            );
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $r
     * @param int $i
     * @param int $a
     * @param int $b
     * @param int $c
     * @param int $d
     * @param SplFixedArray $v
     * @param SplFixedArray $m
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayOffset
     */
    public static function G($r, $i, $a, $b, $c, $d, SplFixedArray $v, SplFixedArray $m)
    {
        $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][$i << 1]]);
        $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 32);
        $v[$c] = self::add64($v[$c], $v[$d]);
        $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 24);
        $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][($i << 1) + 1]]);
        $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 16);
        $v[$c] = self::add64($v[$c], $v[$d]);
        $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 63);
        return $v;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param int $inc
     * @return void
     * @throws SodiumException
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     */
    public static function increment_counter($ctx, $inc)
    {
        if ($inc < 0) {
            throw new SodiumException('Increasing by a negative number makes no sense.');
        }
        $t = self::to64($inc);
        # S->t is $ctx[1] in our implementation

        # S->t[0] = ( uint64_t )( t >> 0 );
        $ctx[1][0] = self::add64($ctx[1][0], $t);

        # S->t[1] += ( S->t[0] < inc );
        if (self::flatten64($ctx[1][0]) < $inc) {
            $ctx[1][1] = self::add64($ctx[1][1], self::to64(1));
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $p
     * @param int $plen
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedOperand
     */
    public static function update(SplFixedArray $ctx, SplFixedArray $p, $plen)
    {
        self::pseudoConstructor();

        $offset = 0;
        while ($plen > 0) {
            $left = $ctx[4];
            $fill = 256 - $left;

            if ($plen > $fill) {
                # memcpy( S->buf + left, in, fill ); /* Fill buffer */
                for ($i = $fill; $i--;) {
                    $ctx[3][$i + $left] = $p[$i + $offset];
                }

                # S->buflen += fill;
                $ctx[4] += $fill;

                # blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
                self::increment_counter($ctx, 128);

                # blake2b_compress( S, S->buf ); /* Compress */
                self::compress($ctx, $ctx[3]);

                # memcpy( S->buf, S->buf + BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); /* Shift buffer left */
                for ($i = 128; $i--;) {
                    $ctx[3][$i] = $ctx[3][$i + 128];
                }

                # S->buflen -= BLAKE2B_BLOCKBYTES;
                $ctx[4] -= 128;

                # in += fill;
                $offset += $fill;

                # inlen -= fill;
                $plen -= $fill;
            } else {
                for ($i = $plen; $i--;) {
                    $ctx[3][$i + $left] = $p[$i + $offset];
                }
                $ctx[4] += $plen;
                $offset += $plen;
                $plen -= $plen;
            }
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $out
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedOperand
     */
    public static function finish(SplFixedArray $ctx, SplFixedArray $out)
    {
        self::pseudoConstructor();
        if ($ctx[4] > 128) {
            self::increment_counter($ctx, 128);
            self::compress($ctx, $ctx[3]);
            $ctx[4] -= 128;
            if ($ctx[4] > 128) {
                throw new SodiumException('Failed to assert that buflen <= 128 bytes');
            }
            for ($i = $ctx[4]; $i--;) {
                $ctx[3][$i] = $ctx[3][$i + 128];
            }
        }

        self::increment_counter($ctx, $ctx[4]);
        $ctx[2][0] = self::new64(0xffffffff, 0xffffffff);

        for ($i = 256 - $ctx[4]; $i--;) {
            $ctx[3][$i+$ctx[4]] = 0;
        }

        self::compress($ctx, $ctx[3]);

        $i = (int) (($out->getSize() - 1) / 8);
        for (; $i >= 0; --$i) {
            self::store64($out, $i << 3, $ctx[0][$i]);
        }
        return $out;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray|null $key
     * @param int $outlen
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     */
    public static function init($key = null, $outlen = 64)
    {
        self::pseudoConstructor();
        $klen = 0;

        if ($key !== null) {
            if (count($key) > 64) {
                throw new SodiumException('Invalid key size');
            }
            $klen = count($key);
        }

        if ($outlen > 64) {
            throw new SodiumException('Invalid output size');
        }

        $ctx = self::context();

        $p = new SplFixedArray(64);
        for ($i = 64; --$i;) {
            $p[$i] = 0;
        }

        $p[0] = $outlen; // digest_length
        $p[1] = $klen;   // key_length
        $p[2] = 1;       // fanout
        $p[3] = 1;       // depth

        $ctx[0][0] = self::xor64(
            $ctx[0][0],
            self::load64($p, 0)
        );

        if ($klen > 0 && $key instanceof SplFixedArray) {
            $block = new SplFixedArray(128);
            for ($i = 128; $i--;) {
                $block[$i] = 0;
            }
            for ($i = $klen; $i--;) {
                $block[$i] = $key[$i];
            }
            self::update($ctx, $block, 128);
        }

        return $ctx;
    }

    /**
     * Convert a string into an SplFixedArray of integers
     *
     * @internal You should not use this directly from another application
     *
     * @param string $str
     * @return SplFixedArray
     */
    public static function stringToSplFixedArray($str = '')
    {
        $values = unpack('C*', $str);
        return SplFixedArray::fromArray(array_values($values));
    }

    /**
     * Convert an SplFixedArray of integers into a string
     *
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $a
     * @return string
     * @throws TypeError
     */
    public static function SplFixedArrayToString(SplFixedArray $a)
    {
        /**
         * @var array<int, int|string> $arr
         */
        $arr = $a->toArray();
        $c = $a->count();
        array_unshift($arr, str_repeat('C', $c));
        return (string) (call_user_func_array('pack', $arr));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray[SplFixedArray] $ctx
     * @return string
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedMethodCall
     */
    public static function contextToString(SplFixedArray $ctx)
    {
        $str = '';
        /** @var array<int, array<int, int>> $ctxA */
        $ctxA = $ctx[0]->toArray();

        # uint64_t h[8];
        for ($i = 0; $i < 8; ++$i) {
            $str .= self::store32_le($ctxA[$i][1]);
            $str .= self::store32_le($ctxA[$i][0]);
        }

        # uint64_t t[2];
        # uint64_t f[2];
        for ($i = 1; $i < 3; ++$i) {
            $ctxA = $ctx[$i]->toArray();
            $str .= self::store32_le($ctxA[0][1]);
            $str .= self::store32_le($ctxA[0][0]);
            $str .= self::store32_le($ctxA[1][1]);
            $str .= self::store32_le($ctxA[1][0]);
        }

        # uint8_t buf[2 * 128];
        $str .= self::SplFixedArrayToString($ctx[3]);

        /** @var int $ctx4 */
        $ctx4 = (int) $ctx[4];

        # size_t buflen;
        $str .= implode('', array(
            self::intToChr($ctx4 & 0xff),
            self::intToChr(($ctx4 >> 8) & 0xff),
            self::intToChr(($ctx4 >> 16) & 0xff),
            self::intToChr(($ctx4 >> 24) & 0xff),
            self::intToChr(($ctx4 >> 32) & 0xff),
            self::intToChr(($ctx4 >> 40) & 0xff),
            self::intToChr(($ctx4 >> 48) & 0xff),
            self::intToChr(($ctx4 >> 56) & 0xff)
        ));
        # uint8_t last_node;
        return $str . "\x00";
    }

    /**
     * Creates an SplFixedArray containing other SplFixedArray elements, from
     * a string (compatible with \Sodium\crypto_generichash_{init, update, final})
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAssignment
     */
    public static function stringToContext($string)
    {
        $ctx = self::context();

        # uint64_t h[8];
        for ($i = 0; $i < 8; ++$i) {
            $ctx[0][$i] = SplFixedArray::fromArray(
                array(
                    self::load_4(
                        self::substr($string, (($i << 3) + 4), 4)
                    ),
                    self::load_4(
                        self::substr($string, (($i << 3) + 0), 4)
                    )
                )
            );
        }

        # uint64_t t[2];
        # uint64_t f[2];
        for ($i = 1; $i < 3; ++$i) {
            $ctx[$i][1] = SplFixedArray::fromArray(
                array(
                    self::load_4(self::substr($string, 76 + (($i - 1) << 4), 4)),
                    self::load_4(self::substr($string, 72 + (($i - 1) << 4), 4))
                )
            );
            $ctx[$i][0] = SplFixedArray::fromArray(
                array(
                    self::load_4(self::substr($string, 68 + (($i - 1) << 4), 4)),
                    self::load_4(self::substr($string, 64 + (($i - 1) << 4), 4))
                )
            );
        }

        # uint8_t buf[2 * 128];
        $ctx[3] = self::stringToSplFixedArray(self::substr($string, 96, 256));


        # uint8_t buf[2 * 128];
        $int = 0;
        for ($i = 0; $i < 8; ++$i) {
            $int |= self::chrToInt($string[352 + $i]) << ($i << 3);
        }
        $ctx[4] = $int;

        return $ctx;
    }
}
vendor/paragonie/sodium_compat/src/Core/X25519.php000064400000022352152177723700015675 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_X25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_X25519
 */
abstract class ParagonIE_Sodium_Core_X25519 extends ParagonIE_Sodium_Core_Curve25519
{
    /**
     * Alters the objects passed to this method in place.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $g
     * @param int $b
     * @return void
     * @psalm-suppress MixedAssignment
     */
    public static function fe_cswap(
        ParagonIE_Sodium_Core_Curve25519_Fe $f,
        ParagonIE_Sodium_Core_Curve25519_Fe $g,
        $b = 0
    ) {
        $f0 = (int) $f[0];
        $f1 = (int) $f[1];
        $f2 = (int) $f[2];
        $f3 = (int) $f[3];
        $f4 = (int) $f[4];
        $f5 = (int) $f[5];
        $f6 = (int) $f[6];
        $f7 = (int) $f[7];
        $f8 = (int) $f[8];
        $f9 = (int) $f[9];
        $g0 = (int) $g[0];
        $g1 = (int) $g[1];
        $g2 = (int) $g[2];
        $g3 = (int) $g[3];
        $g4 = (int) $g[4];
        $g5 = (int) $g[5];
        $g6 = (int) $g[6];
        $g7 = (int) $g[7];
        $g8 = (int) $g[8];
        $g9 = (int) $g[9];
        $b = -$b;
        $x0 = ($f0 ^ $g0) & $b;
        $x1 = ($f1 ^ $g1) & $b;
        $x2 = ($f2 ^ $g2) & $b;
        $x3 = ($f3 ^ $g3) & $b;
        $x4 = ($f4 ^ $g4) & $b;
        $x5 = ($f5 ^ $g5) & $b;
        $x6 = ($f6 ^ $g6) & $b;
        $x7 = ($f7 ^ $g7) & $b;
        $x8 = ($f8 ^ $g8) & $b;
        $x9 = ($f9 ^ $g9) & $b;
        $f[0] = $f0 ^ $x0;
        $f[1] = $f1 ^ $x1;
        $f[2] = $f2 ^ $x2;
        $f[3] = $f3 ^ $x3;
        $f[4] = $f4 ^ $x4;
        $f[5] = $f5 ^ $x5;
        $f[6] = $f6 ^ $x6;
        $f[7] = $f7 ^ $x7;
        $f[8] = $f8 ^ $x8;
        $f[9] = $f9 ^ $x9;
        $g[0] = $g0 ^ $x0;
        $g[1] = $g1 ^ $x1;
        $g[2] = $g2 ^ $x2;
        $g[3] = $g3 ^ $x3;
        $g[4] = $g4 ^ $x4;
        $g[5] = $g5 ^ $x5;
        $g[6] = $g6 ^ $x6;
        $g[7] = $g7 ^ $x7;
        $g[8] = $g8 ^ $x8;
        $g[9] = $g9 ^ $x9;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function fe_mul121666(ParagonIE_Sodium_Core_Curve25519_Fe $f)
    {
        $h = array(
            self::mul((int) $f[0], 121666, 17),
            self::mul((int) $f[1], 121666, 17),
            self::mul((int) $f[2], 121666, 17),
            self::mul((int) $f[3], 121666, 17),
            self::mul((int) $f[4], 121666, 17),
            self::mul((int) $f[5], 121666, 17),
            self::mul((int) $f[6], 121666, 17),
            self::mul((int) $f[7], 121666, 17),
            self::mul((int) $f[8], 121666, 17),
            self::mul((int) $f[9], 121666, 17)
        );

        /** @var int $carry9 */
        $carry9 = ($h[9] + (1 << 24)) >> 25;
        $h[0] += self::mul($carry9, 19, 5);
        $h[9] -= $carry9 << 25;
        /** @var int $carry1 */
        $carry1 = ($h[1] + (1 << 24)) >> 25;
        $h[2] += $carry1;
        $h[1] -= $carry1 << 25;
        /** @var int $carry3 */
        $carry3 = ($h[3] + (1 << 24)) >> 25;
        $h[4] += $carry3;
        $h[3] -= $carry3 << 25;
        /** @var int $carry5 */
        $carry5 = ($h[5] + (1 << 24)) >> 25;
        $h[6] += $carry5;
        $h[5] -= $carry5 << 25;
        /** @var int $carry7 */
        $carry7 = ($h[7] + (1 << 24)) >> 25;
        $h[8] += $carry7;
        $h[7] -= $carry7 << 25;

        /** @var int $carry0 */
        $carry0 = ($h[0] + (1 << 25)) >> 26;
        $h[1] += $carry0;
        $h[0] -= $carry0 << 26;
        /** @var int $carry2 */
        $carry2 = ($h[2] + (1 << 25)) >> 26;
        $h[3] += $carry2;
        $h[2] -= $carry2 << 26;
        /** @var int $carry4 */
        $carry4 = ($h[4] + (1 << 25)) >> 26;
        $h[5] += $carry4;
        $h[4] -= $carry4 << 26;
        /** @var int $carry6 */
        $carry6 = ($h[6] + (1 << 25)) >> 26;
        $h[7] += $carry6;
        $h[6] -= $carry6 << 26;
        /** @var int $carry8 */
        $carry8 = ($h[8] + (1 << 25)) >> 26;
        $h[9] += $carry8;
        $h[8] -= $carry8 << 26;

        foreach ($h as $i => $value) {
            $h[$i] = (int) $value;
        }
        return ParagonIE_Sodium_Core_Curve25519_Fe::fromArray($h);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * Inline comments preceded by # are from libsodium's ref10 code.
     *
     * @param string $n
     * @param string $p
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_scalarmult_curve25519_ref10($n, $p)
    {
        # for (i = 0;i < 32;++i) e[i] = n[i];
        $e = '' . $n;
        # e[0] &= 248;
        $e[0] = self::intToChr(
            self::chrToInt($e[0]) & 248
        );
        # e[31] &= 127;
        # e[31] |= 64;
        $e[31] = self::intToChr(
            (self::chrToInt($e[31]) & 127) | 64
        );
        # fe_frombytes(x1,p);
        $x1 = self::fe_frombytes($p);
        # fe_1(x2);
        $x2 = self::fe_1();
        # fe_0(z2);
        $z2 = self::fe_0();
        # fe_copy(x3,x1);
        $x3 = self::fe_copy($x1);
        # fe_1(z3);
        $z3 = self::fe_1();

        # swap = 0;
        /** @var int $swap */
        $swap = 0;

        # for (pos = 254;pos >= 0;--pos) {
        for ($pos = 254; $pos >= 0; --$pos) {
            # b = e[pos / 8] >> (pos & 7);
            /** @var int $b */
            $b = self::chrToInt(
                    $e[(int) floor($pos / 8)]
                ) >> ($pos & 7);
            # b &= 1;
            $b &= 1;
            # swap ^= b;
            $swap ^= $b;
            # fe_cswap(x2,x3,swap);
            self::fe_cswap($x2, $x3, $swap);
            # fe_cswap(z2,z3,swap);
            self::fe_cswap($z2, $z3, $swap);
            # swap = b;
            $swap = $b;
            # fe_sub(tmp0,x3,z3);
            $tmp0 = self::fe_sub($x3, $z3);
            # fe_sub(tmp1,x2,z2);
            $tmp1 = self::fe_sub($x2, $z2);

            # fe_add(x2,x2,z2);
            $x2 = self::fe_add($x2, $z2);

            # fe_add(z2,x3,z3);
            $z2 = self::fe_add($x3, $z3);

            # fe_mul(z3,tmp0,x2);
            $z3 = self::fe_mul($tmp0, $x2);

            # fe_mul(z2,z2,tmp1);
            $z2 = self::fe_mul($z2, $tmp1);

            # fe_sq(tmp0,tmp1);
            $tmp0 = self::fe_sq($tmp1);

            # fe_sq(tmp1,x2);
            $tmp1 = self::fe_sq($x2);

            # fe_add(x3,z3,z2);
            $x3 = self::fe_add($z3, $z2);

            # fe_sub(z2,z3,z2);
            $z2 = self::fe_sub($z3, $z2);

            # fe_mul(x2,tmp1,tmp0);
            $x2 = self::fe_mul($tmp1, $tmp0);

            # fe_sub(tmp1,tmp1,tmp0);
            $tmp1 = self::fe_sub($tmp1, $tmp0);

            # fe_sq(z2,z2);
            $z2 = self::fe_sq($z2);

            # fe_mul121666(z3,tmp1);
            $z3 = self::fe_mul121666($tmp1);

            # fe_sq(x3,x3);
            $x3 = self::fe_sq($x3);

            # fe_add(tmp0,tmp0,z3);
            $tmp0 = self::fe_add($tmp0, $z3);

            # fe_mul(z3,x1,z2);
            $z3 = self::fe_mul($x1, $z2);

            # fe_mul(z2,tmp1,tmp0);
            $z2 = self::fe_mul($tmp1, $tmp0);
        }

        # fe_cswap(x2,x3,swap);
        self::fe_cswap($x2, $x3, $swap);

        # fe_cswap(z2,z3,swap);
        self::fe_cswap($z2, $z3, $swap);

        # fe_invert(z2,z2);
        $z2 = self::fe_invert($z2);

        # fe_mul(x2,x2,z2);
        $x2 = self::fe_mul($x2, $z2);
        # fe_tobytes(q,x2);
        return self::fe_tobytes($x2);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $edwardsY
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $edwardsZ
     * @return ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public static function edwards_to_montgomery(
        ParagonIE_Sodium_Core_Curve25519_Fe $edwardsY,
        ParagonIE_Sodium_Core_Curve25519_Fe $edwardsZ
    ) {
        $tempX = self::fe_add($edwardsZ, $edwardsY);
        $tempZ = self::fe_sub($edwardsZ, $edwardsY);
        $tempZ = self::fe_invert($tempZ);
        return self::fe_mul($tempX, $tempZ);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $n
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_scalarmult_curve25519_ref10_base($n)
    {
        # for (i = 0;i < 32;++i) e[i] = n[i];
        $e = '' . $n;

        # e[0] &= 248;
        $e[0] = self::intToChr(
            self::chrToInt($e[0]) & 248
        );

        # e[31] &= 127;
        # e[31] |= 64;
        $e[31] = self::intToChr(
            (self::chrToInt($e[31]) & 127) | 64
        );

        $A = self::ge_scalarmult_base($e);
        if (
            !($A->Y instanceof ParagonIE_Sodium_Core_Curve25519_Fe)
                ||
            !($A->Z instanceof ParagonIE_Sodium_Core_Curve25519_Fe)
        ) {
            throw new TypeError('Null points encountered');
        }
        $pk = self::edwards_to_montgomery($A->Y, $A->Z);
        return self::fe_tobytes($pk);
    }
}
vendor/paragonie/sodium_compat/src/Core/Poly1305.php000064400000003046152177723700016313 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Poly1305', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Poly1305
 */
abstract class ParagonIE_Sodium_Core_Poly1305 extends ParagonIE_Sodium_Core_Util
{
    const BLOCK_SIZE = 16;

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth($m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core_Poly1305_State(
            self::substr($key, 0, 32)
        );
        return $state
            ->update($m)
            ->finish();
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $mac
     * @param string $m
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth_verify($mac, $m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core_Poly1305_State(
            self::substr($key, 0, 32)
        );
        $calc = $state
            ->update($m)
            ->finish();
        return self::verify_16($calc, $mac);
    }
}
vendor/paragonie/sodium_compat/src/Core/Poly1305/State.php000064400000027611152177723700017377 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Poly1305_State', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Poly1305_State
 */
class ParagonIE_Sodium_Core_Poly1305_State extends ParagonIE_Sodium_Core_Util
{
    /**
     * @var array<int, int>
     */
    protected $buffer = array();

    /**
     * @var bool
     */
    protected $final = false;

    /**
     * @var array<int, int>
     */
    public $h;

    /**
     * @var int
     */
    protected $leftover = 0;

    /**
     * @var int[]
     */
    public $r;

    /**
     * @var int[]
     */
    public $pad;

    /**
     * ParagonIE_Sodium_Core_Poly1305_State constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key
     * @throws InvalidArgumentException
     * @throws TypeError
     */
    public function __construct($key = '')
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Poly1305 requires a 32-byte key'
            );
        }
        /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
        $this->r = array(
            (int) ((self::load_4(self::substr($key, 0, 4))) & 0x3ffffff),
            (int) ((self::load_4(self::substr($key, 3, 4)) >> 2) & 0x3ffff03),
            (int) ((self::load_4(self::substr($key, 6, 4)) >> 4) & 0x3ffc0ff),
            (int) ((self::load_4(self::substr($key, 9, 4)) >> 6) & 0x3f03fff),
            (int) ((self::load_4(self::substr($key, 12, 4)) >> 8) & 0x00fffff)
        );

        /* h = 0 */
        $this->h = array(0, 0, 0, 0, 0);

        /* save pad for later */
        $this->pad = array(
            self::load_4(self::substr($key, 16, 4)),
            self::load_4(self::substr($key, 20, 4)),
            self::load_4(self::substr($key, 24, 4)),
            self::load_4(self::substr($key, 28, 4)),
        );

        $this->leftover = 0;
        $this->final = false;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public function update($message = '')
    {
        $bytes = self::strlen($message);

        /* handle leftover */
        if ($this->leftover) {
            $want = ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE - $this->leftover;
            if ($want > $bytes) {
                $want = $bytes;
            }
            for ($i = 0; $i < $want; ++$i) {
                $mi = self::chrToInt($message[$i]);
                $this->buffer[$this->leftover + $i] = $mi;
            }
            // We snip off the leftmost bytes.
            $message = self::substr($message, $want);
            $bytes = self::strlen($message);
            $this->leftover += $want;
            if ($this->leftover < ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) {
                // We still don't have enough to run $this->blocks()
                return $this;
            }

            $this->blocks(
                static::intArrayToString($this->buffer),
                ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE
            );
            $this->leftover = 0;
        }

        /* process full blocks */
        if ($bytes >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) {
            /** @var int $want */
            $want = $bytes & ~(ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE - 1);
            if ($want >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) {
                $block = self::substr($message, 0, $want);
                if (self::strlen($block) >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) {
                    $this->blocks($block, $want);
                    $message = self::substr($message, $want);
                    $bytes = self::strlen($message);
                }
            }
        }

        /* store leftover */
        if ($bytes) {
            for ($i = 0; $i < $bytes; ++$i) {
                $mi = self::chrToInt($message[$i]);
                $this->buffer[$this->leftover + $i] = $mi;
            }
            $this->leftover = (int) $this->leftover + $bytes;
        }
        return $this;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param int $bytes
     * @return self
     * @throws TypeError
     */
    public function blocks($message, $bytes)
    {
        if (self::strlen($message) < 16) {
            $message = str_pad($message, 16, "\x00", STR_PAD_RIGHT);
        }
        /** @var int $hibit */
        $hibit = $this->final ? 0 : 1 << 24; /* 1 << 128 */
        $r0 = (int) $this->r[0];
        $r1 = (int) $this->r[1];
        $r2 = (int) $this->r[2];
        $r3 = (int) $this->r[3];
        $r4 = (int) $this->r[4];

        $s1 = self::mul($r1, 5, 3);
        $s2 = self::mul($r2, 5, 3);
        $s3 = self::mul($r3, 5, 3);
        $s4 = self::mul($r4, 5, 3);

        $h0 = $this->h[0];
        $h1 = $this->h[1];
        $h2 = $this->h[2];
        $h3 = $this->h[3];
        $h4 = $this->h[4];

        while ($bytes >= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE) {
            /* h += m[i] */
            $h0 +=  self::load_4(self::substr($message, 0, 4))       & 0x3ffffff;
            $h1 += (self::load_4(self::substr($message, 3, 4)) >> 2) & 0x3ffffff;
            $h2 += (self::load_4(self::substr($message, 6, 4)) >> 4) & 0x3ffffff;
            $h3 += (self::load_4(self::substr($message, 9, 4)) >> 6) & 0x3ffffff;
            $h4 += (self::load_4(self::substr($message, 12, 4)) >> 8) | $hibit;

            /* h *= r */
            $d0 = (
                self::mul($h0, $r0, 25) +
                self::mul($s4, $h1, 26) +
                self::mul($s3, $h2, 26) +
                self::mul($s2, $h3, 26) +
                self::mul($s1, $h4, 26)
            );

            $d1 = (
                self::mul($h0, $r1, 25) +
                self::mul($h1, $r0, 25) +
                self::mul($s4, $h2, 26) +
                self::mul($s3, $h3, 26) +
                self::mul($s2, $h4, 26)
            );

            $d2 = (
                self::mul($h0, $r2, 25) +
                self::mul($h1, $r1, 25) +
                self::mul($h2, $r0, 25) +
                self::mul($s4, $h3, 26) +
                self::mul($s3, $h4, 26)
            );

            $d3 = (
                self::mul($h0, $r3, 25) +
                self::mul($h1, $r2, 25) +
                self::mul($h2, $r1, 25) +
                self::mul($h3, $r0, 25) +
                self::mul($s4, $h4, 26)
            );

            $d4 = (
                self::mul($h0, $r4, 25) +
                self::mul($h1, $r3, 25) +
                self::mul($h2, $r2, 25) +
                self::mul($h3, $r1, 25) +
                self::mul($h4, $r0, 25)
            );

            /* (partial) h %= p */
            /** @var int $c */
            $c = $d0 >> 26;
            /** @var int $h0 */
            $h0 = $d0 & 0x3ffffff;
            $d1 += $c;

            /** @var int $c */
            $c = $d1 >> 26;
            /** @var int $h1 */
            $h1 = $d1 & 0x3ffffff;
            $d2 += $c;

            /** @var int $c */
            $c = $d2 >> 26;
            /** @var int $h2  */
            $h2 = $d2 & 0x3ffffff;
            $d3 += $c;

            /** @var int $c */
            $c = $d3 >> 26;
            /** @var int $h3 */
            $h3 = $d3 & 0x3ffffff;
            $d4 += $c;

            /** @var int $c */
            $c = $d4 >> 26;
            /** @var int $h4 */
            $h4 = $d4 & 0x3ffffff;
            $h0 += (int) self::mul($c, 5, 3);

            /** @var int $c */
            $c = $h0 >> 26;
            /** @var int $h0 */
            $h0 &= 0x3ffffff;
            $h1 += $c;

            // Chop off the left 32 bytes.
            $message = self::substr(
                $message,
                ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE
            );
            $bytes -= ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE;
        }

        $this->h = array(
            (int) ($h0 & 0xffffffff),
            (int) ($h1 & 0xffffffff),
            (int) ($h2 & 0xffffffff),
            (int) ($h3 & 0xffffffff),
            (int) ($h4 & 0xffffffff)
        );
        return $this;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return string
     * @throws TypeError
     */
    public function finish()
    {
        /* process the remaining block */
        if ($this->leftover) {
            $i = $this->leftover;
            $this->buffer[$i++] = 1;
            for (; $i < ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE; ++$i) {
                $this->buffer[$i] = 0;
            }
            $this->final = true;
            $this->blocks(
                self::substr(
                    static::intArrayToString($this->buffer),
                    0,
                    ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE
                ),
                ParagonIE_Sodium_Core_Poly1305::BLOCK_SIZE
            );
        }

        $h0 = (int) $this->h[0];
        $h1 = (int) $this->h[1];
        $h2 = (int) $this->h[2];
        $h3 = (int) $this->h[3];
        $h4 = (int) $this->h[4];

        /** @var int $c */
        $c = $h1 >> 26;
        /** @var int $h1 */
        $h1 &= 0x3ffffff;
        /** @var int $h2 */
        $h2 += $c;
        /** @var int $c */
        $c = $h2 >> 26;
        /** @var int $h2 */
        $h2 &= 0x3ffffff;
        $h3 += $c;
        /** @var int $c */
        $c = $h3 >> 26;
        $h3 &= 0x3ffffff;
        $h4 += $c;
        /** @var int $c */
        $c = $h4 >> 26;
        $h4 &= 0x3ffffff;
        /** @var int $h0 */
        $h0 += self::mul($c, 5, 3);
        /** @var int $c */
        $c = $h0 >> 26;
        /** @var int $h0 */
        $h0 &= 0x3ffffff;
        /** @var int $h1 */
        $h1 += $c;

        /* compute h + -p */
        /** @var int $g0 */
        $g0 = $h0 + 5;
        /** @var int $c */
        $c = $g0 >> 26;
        /** @var int $g0 */
        $g0 &= 0x3ffffff;

        /** @var int $g1 */
        $g1 = $h1 + $c;
        /** @var int $c */
        $c = $g1 >> 26;
        $g1 &= 0x3ffffff;

        /** @var int $g2 */
        $g2 = $h2 + $c;
        /** @var int $c */
        $c = $g2 >> 26;
        /** @var int $g2 */
        $g2 &= 0x3ffffff;

        /** @var int $g3 */
        $g3 = $h3 + $c;
        /** @var int $c */
        $c = $g3 >> 26;
        /** @var int $g3 */
        $g3 &= 0x3ffffff;

        /** @var int $g4 */
        $g4 = ($h4 + $c - (1 << 26)) & 0xffffffff;

        /* select h if h < p, or h + -p if h >= p */
        /** @var int $mask */
        $mask = ($g4 >> 31) - 1;

        $g0 &= $mask;
        $g1 &= $mask;
        $g2 &= $mask;
        $g3 &= $mask;
        $g4 &= $mask;

        /** @var int $mask */
        $mask = ~$mask & 0xffffffff;
        /** @var int $h0 */
        $h0 = ($h0 & $mask) | $g0;
        /** @var int $h1 */
        $h1 = ($h1 & $mask) | $g1;
        /** @var int $h2 */
        $h2 = ($h2 & $mask) | $g2;
        /** @var int $h3 */
        $h3 = ($h3 & $mask) | $g3;
        /** @var int $h4 */
        $h4 = ($h4 & $mask) | $g4;

        /* h = h % (2^128) */
        /** @var int $h0 */
        $h0 = (($h0) | ($h1 << 26)) & 0xffffffff;
        /** @var int $h1 */
        $h1 = (($h1 >>  6) | ($h2 << 20)) & 0xffffffff;
        /** @var int $h2 */
        $h2 = (($h2 >> 12) | ($h3 << 14)) & 0xffffffff;
        /** @var int $h3 */
        $h3 = (($h3 >> 18) | ($h4 <<  8)) & 0xffffffff;

        /* mac = (h + pad) % (2^128) */
        $f = (int) ($h0 + $this->pad[0]);
        $h0 = (int) $f;
        $f = (int) ($h1 + $this->pad[1] + ($f >> 32));
        $h1 = (int) $f;
        $f = (int) ($h2 + $this->pad[2] + ($f >> 32));
        $h2 = (int) $f;
        $f = (int) ($h3 + $this->pad[3] + ($f >> 32));
        $h3 = (int) $f;

        return self::store32_le($h0 & 0xffffffff) .
            self::store32_le($h1 & 0xffffffff) .
            self::store32_le($h2 & 0xffffffff) .
            self::store32_le($h3 & 0xffffffff);
    }
}
vendor/paragonie/sodium_compat/src/Core/Salsa20.php000064400000020051152177723700016257 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Salsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Salsa20
 */
abstract class ParagonIE_Sodium_Core_Salsa20 extends ParagonIE_Sodium_Core_Util
{
    const ROUNDS = 20;

    /**
     * Calculate an salsa20 hash of a single block
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws TypeError
     */
    public static function core_salsa20($in, $k, $c = null)
    {
        if (self::strlen($k) < 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        if ($c === null) {
            $j0  = $x0  = 0x61707865;
            $j5  = $x5  = 0x3320646e;
            $j10 = $x10 = 0x79622d32;
            $j15 = $x15 = 0x6b206574;
        } else {
            $j0  = $x0  = self::load_4(self::substr($c, 0, 4));
            $j5  = $x5  = self::load_4(self::substr($c, 4, 4));
            $j10 = $x10 = self::load_4(self::substr($c, 8, 4));
            $j15 = $x15 = self::load_4(self::substr($c, 12, 4));
        }
        $j1  = $x1  = self::load_4(self::substr($k, 0, 4));
        $j2  = $x2  = self::load_4(self::substr($k, 4, 4));
        $j3  = $x3  = self::load_4(self::substr($k, 8, 4));
        $j4  = $x4  = self::load_4(self::substr($k, 12, 4));
        $j6  = $x6  = self::load_4(self::substr($in, 0, 4));
        $j7  = $x7  = self::load_4(self::substr($in, 4, 4));
        $j8  = $x8  = self::load_4(self::substr($in, 8, 4));
        $j9  = $x9  = self::load_4(self::substr($in, 12, 4));
        $j11 = $x11 = self::load_4(self::substr($k, 16, 4));
        $j12 = $x12 = self::load_4(self::substr($k, 20, 4));
        $j13 = $x13 = self::load_4(self::substr($k, 24, 4));
        $j14 = $x14 = self::load_4(self::substr($k, 28, 4));

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4 ^= self::rotate($x0 + $x12, 7);
            $x8 ^= self::rotate($x4 + $x0, 9);
            $x12 ^= self::rotate($x8 + $x4, 13);
            $x0 ^= self::rotate($x12 + $x8, 18);

            $x9 ^= self::rotate($x5 + $x1, 7);
            $x13 ^= self::rotate($x9 + $x5, 9);
            $x1 ^= self::rotate($x13 + $x9, 13);
            $x5 ^= self::rotate($x1 + $x13, 18);

            $x14 ^= self::rotate($x10 + $x6, 7);
            $x2 ^= self::rotate($x14 + $x10, 9);
            $x6 ^= self::rotate($x2 + $x14, 13);
            $x10 ^= self::rotate($x6 + $x2, 18);

            $x3 ^= self::rotate($x15 + $x11, 7);
            $x7 ^= self::rotate($x3 + $x15, 9);
            $x11 ^= self::rotate($x7 + $x3, 13);
            $x15 ^= self::rotate($x11 + $x7, 18);

            $x1 ^= self::rotate($x0 + $x3, 7);
            $x2 ^= self::rotate($x1 + $x0, 9);
            $x3 ^= self::rotate($x2 + $x1, 13);
            $x0 ^= self::rotate($x3 + $x2, 18);

            $x6 ^= self::rotate($x5 + $x4, 7);
            $x7 ^= self::rotate($x6 + $x5, 9);
            $x4 ^= self::rotate($x7 + $x6, 13);
            $x5 ^= self::rotate($x4 + $x7, 18);

            $x11 ^= self::rotate($x10 + $x9, 7);
            $x8 ^= self::rotate($x11 + $x10, 9);
            $x9 ^= self::rotate($x8 + $x11, 13);
            $x10 ^= self::rotate($x9 + $x8, 18);

            $x12 ^= self::rotate($x15 + $x14, 7);
            $x13 ^= self::rotate($x12 + $x15, 9);
            $x14 ^= self::rotate($x13 + $x12, 13);
            $x15 ^= self::rotate($x14 + $x13, 18);
        }

        $x0  += $j0;
        $x1  += $j1;
        $x2  += $j2;
        $x3  += $j3;
        $x4  += $j4;
        $x5  += $j5;
        $x6  += $j6;
        $x7  += $j7;
        $x8  += $j8;
        $x9  += $j9;
        $x10 += $j10;
        $x11 += $j11;
        $x12 += $j12;
        $x13 += $j13;
        $x14 += $j14;
        $x15 += $j15;

        return self::store32_le($x0) .
            self::store32_le($x1) .
            self::store32_le($x2) .
            self::store32_le($x3) .
            self::store32_le($x4) .
            self::store32_le($x5) .
            self::store32_le($x6) .
            self::store32_le($x7) .
            self::store32_le($x8) .
            self::store32_le($x9) .
            self::store32_le($x10) .
            self::store32_le($x11) .
            self::store32_le($x12) .
            self::store32_le($x13) .
            self::store32_le($x14) .
            self::store32_le($x15);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20($len, $nonce, $key)
    {
        if (self::strlen($key) !== 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        $kcopy = '' . $key;
        $in = self::substr($nonce, 0, 8) . str_repeat("\0", 8);
        $c = '';
        while ($len >= 64) {
            $c .= self::core_salsa20($in, $kcopy, null);
            $u = 1;
            // Internal counter.
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }
            $len -= 64;
        }
        if ($len > 0) {
            $c .= self::substr(
                self::core_salsa20($in, $kcopy, null),
                0,
                $len
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $kcopy = null;
        }
        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $n
     * @param int $ic
     * @param string $k
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor_ic($m, $n, $ic, $k)
    {
        $mlen = self::strlen($m);
        if ($mlen < 1) {
            return '';
        }
        $kcopy = self::substr($k, 0, 32);
        $in = self::substr($n, 0, 8);
        // Initialize the counter
        $in .= ParagonIE_Sodium_Core_Util::store64_le($ic);

        $c = '';
        while ($mlen >= 64) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, 64),
                self::substr($block, 0, 64)
            );
            $u = 1;
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }

            $mlen -= 64;
            $m = self::substr($m, 64);
        }

        if ($mlen) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, $mlen),
                self::substr($block, 0, $mlen)
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($block);
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $block = null;
            $kcopy = null;
        }

        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor($message, $nonce, $key)
    {
        return self::xorStrings(
            $message,
            self::salsa20(
                self::strlen($message),
                $nonce,
                $key
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $u
     * @param int $c
     * @return int
     */
    public static function rotate($u, $c)
    {
        $u &= 0xffffffff;
        $c %= 32;
        return (int) (0xffffffff & (
                ($u << $c)
                    |
                ($u >> (32 - $c))
            )
        );
    }
}
vendor/paragonie/sodium_compat/src/Core/SipHash.php000064400000017532152177723700016423 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_SipHash', false)) {
    return;
}

/**
 * Class ParagonIE_SodiumCompat_Core_SipHash
 *
 * Only uses 32-bit arithmetic, while the original SipHash used 64-bit integers
 */
class ParagonIE_Sodium_Core_SipHash extends ParagonIE_Sodium_Core_Util
{
    /**
     * @internal You should not use this directly from another application
     *
     * @param int[] $v
     * @return int[]
     */
    public static function sipRound(array $v)
    {
        # v0 += v1;
        list($v[0], $v[1]) = self::add(
            array($v[0], $v[1]),
            array($v[2], $v[3])
        );

        #  v1=ROTL(v1,13);
        list($v[2], $v[3]) = self::rotl_64($v[2], $v[3], 13);

        #  v1 ^= v0;
        $v[2] ^= $v[0];
        $v[3] ^= $v[1];

        #  v0=ROTL(v0,32);
        list($v[0], $v[1]) = self::rotl_64((int) $v[0], (int) $v[1], 32);

        # v2 += v3;
        list($v[4], $v[5]) = self::add(
            array($v[4], $v[5]),
            array($v[6], $v[7])
        );

        # v3=ROTL(v3,16);
        list($v[6], $v[7]) = self::rotl_64($v[6], $v[7], 16);

        #  v3 ^= v2;
        $v[6] ^= $v[4];
        $v[7] ^= $v[5];

        # v0 += v3;
        list($v[0], $v[1]) = self::add(
            array((int) $v[0], (int) $v[1]),
            array((int) $v[6], (int) $v[7])
        );

        # v3=ROTL(v3,21);
        list($v[6], $v[7]) = self::rotl_64((int) $v[6], (int) $v[7], 21);

        # v3 ^= v0;
        $v[6] ^= $v[0];
        $v[7] ^= $v[1];

        # v2 += v1;
        list($v[4], $v[5]) = self::add(
            array((int) $v[4], (int) $v[5]),
            array((int) $v[2], (int) $v[3])
        );

        # v1=ROTL(v1,17);
        list($v[2], $v[3]) = self::rotl_64((int) $v[2], (int) $v[3], 17);

        #  v1 ^= v2;;
        $v[2] ^= $v[4];
        $v[3] ^= $v[5];

        # v2=ROTL(v2,32)
        list($v[4], $v[5]) = self::rotl_64((int) $v[4], (int) $v[5], 32);

        return $v;
    }

    /**
     * Add two 32 bit integers representing a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int[] $a
     * @param int[] $b
     * @return array<int, mixed>
     */
    public static function add(array $a, array $b)
    {
        /** @var int $x1 */
        $x1 = $a[1] + $b[1];
        /** @var int $c */
        $c = $x1 >> 32; // Carry if ($a + $b) > 0xffffffff
        /** @var int $x0 */
        $x0 = $a[0] + $b[0] + $c;
        return array(
            $x0 & 0xffffffff,
            $x1 & 0xffffffff
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $int0
     * @param int $int1
     * @param int $c
     * @return array<int, mixed>
     */
    public static function rotl_64($int0, $int1, $c)
    {
        $int0 &= 0xffffffff;
        $int1 &= 0xffffffff;
        $c &= 63;
        if ($c === 32) {
            return array($int1, $int0);
        }
        if ($c > 31) {
            $tmp = $int1;
            $int1 = $int0;
            $int0 = $tmp;
            $c &= 31;
        }
        if ($c === 0) {
            return array($int0, $int1);
        }
        return array(
            0xffffffff & (
                ($int0 << $c)
                    |
                ($int1 >> (32 - $c))
            ),
            0xffffffff & (
                ($int1 << $c)
                    |
                ($int0 >> (32 - $c))
            ),
        );
    }

    /**
     * Implements Siphash-2-4 using only 32-bit numbers.
     *
     * When we split an int into two, the higher bits go to the lower index.
     * e.g. 0xDEADBEEFAB10C92D becomes [
     *     0 => 0xDEADBEEF,
     *     1 => 0xAB10C92D
     * ].
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sipHash24($in, $key)
    {
        $inlen = self::strlen($in);

        # /* "somepseudorandomlygeneratedbytes" */
        # u64 v0 = 0x736f6d6570736575ULL;
        # u64 v1 = 0x646f72616e646f6dULL;
        # u64 v2 = 0x6c7967656e657261ULL;
        # u64 v3 = 0x7465646279746573ULL;
        $v = array(
            0x736f6d65, // 0
            0x70736575, // 1
            0x646f7261, // 2
            0x6e646f6d, // 3
            0x6c796765, // 4
            0x6e657261, // 5
            0x74656462, // 6
            0x79746573  // 7
        );
        // v0 => $v[0], $v[1]
        // v1 => $v[2], $v[3]
        // v2 => $v[4], $v[5]
        // v3 => $v[6], $v[7]

        # u64 k0 = LOAD64_LE( k );
        # u64 k1 = LOAD64_LE( k + 8 );
        $k = array(
            self::load_4(self::substr($key, 4, 4)),
            self::load_4(self::substr($key, 0, 4)),
            self::load_4(self::substr($key, 12, 4)),
            self::load_4(self::substr($key, 8, 4))
        );
        // k0 => $k[0], $k[1]
        // k1 => $k[2], $k[3]

        # b = ( ( u64 )inlen ) << 56;
        $b = array(
            $inlen << 24,
            0
        );
        // See docblock for why the 0th index gets the higher bits.

        # v3 ^= k1;
        $v[6] ^= $k[2];
        $v[7] ^= $k[3];
        # v2 ^= k0;
        $v[4] ^= $k[0];
        $v[5] ^= $k[1];
        # v1 ^= k1;
        $v[2] ^= $k[2];
        $v[3] ^= $k[3];
        # v0 ^= k0;
        $v[0] ^= $k[0];
        $v[1] ^= $k[1];

        $left = $inlen;
        # for ( ; in != end; in += 8 )
        while ($left >= 8) {
            # m = LOAD64_LE( in );
            $m = array(
                self::load_4(self::substr($in, 4, 4)),
                self::load_4(self::substr($in, 0, 4))
            );

            # v3 ^= m;
            $v[6] ^= $m[0];
            $v[7] ^= $m[1];

            # SIPROUND;
            # SIPROUND;
            $v = self::sipRound($v);
            $v = self::sipRound($v);

            # v0 ^= m;
            $v[0] ^= $m[0];
            $v[1] ^= $m[1];

            $in = self::substr($in, 8);
            $left -= 8;
        }

        # switch( left )
        #  {
        #     case 7: b |= ( ( u64 )in[ 6] )  << 48;
        #     case 6: b |= ( ( u64 )in[ 5] )  << 40;
        #     case 5: b |= ( ( u64 )in[ 4] )  << 32;
        #     case 4: b |= ( ( u64 )in[ 3] )  << 24;
        #     case 3: b |= ( ( u64 )in[ 2] )  << 16;
        #     case 2: b |= ( ( u64 )in[ 1] )  <<  8;
        #     case 1: b |= ( ( u64 )in[ 0] ); break;
        #     case 0: break;
        # }
        switch ($left) {
            case 7:
                $b[0] |= self::chrToInt($in[6]) << 16;
            case 6:
                $b[0] |= self::chrToInt($in[5]) << 8;
            case 5:
                $b[0] |= self::chrToInt($in[4]);
            case 4:
                $b[1] |= self::chrToInt($in[3]) << 24;
            case 3:
                $b[1] |= self::chrToInt($in[2]) << 16;
            case 2:
                $b[1] |= self::chrToInt($in[1]) << 8;
            case 1:
                $b[1] |= self::chrToInt($in[0]);
            case 0:
                break;
        }
        // See docblock for why the 0th index gets the higher bits.

        # v3 ^= b;
        $v[6] ^= $b[0];
        $v[7] ^= $b[1];

        # SIPROUND;
        # SIPROUND;
        $v = self::sipRound($v);
        $v = self::sipRound($v);

        # v0 ^= b;
        $v[0] ^= $b[0];
        $v[1] ^= $b[1];

        // Flip the lower 8 bits of v2 which is ($v[4], $v[5]) in our implementation
        # v2 ^= 0xff;
        $v[5] ^= 0xff;

        # SIPROUND;
        # SIPROUND;
        # SIPROUND;
        # SIPROUND;
        $v = self::sipRound($v);
        $v = self::sipRound($v);
        $v = self::sipRound($v);
        $v = self::sipRound($v);

        # b = v0 ^ v1 ^ v2 ^ v3;
        # STORE64_LE( out, b );
        return  self::store32_le($v[1] ^ $v[3] ^ $v[5] ^ $v[7]) .
            self::store32_le($v[0] ^ $v[2] ^ $v[4] ^ $v[6]);
    }
}
vendor/paragonie/sodium_compat/src/Core/HSalsa20.php000064400000007131152177723700016373 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_HSalsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_HSalsa20
 */
abstract class ParagonIE_Sodium_Core_HSalsa20 extends ParagonIE_Sodium_Core_Salsa20
{
    /**
     * Calculate an hsalsa20 hash of a single block
     *
     * HSalsa20 doesn't have a counter and will never be used for more than
     * one block (used to derive a subkey for xsalsa20).
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws TypeError
     */
    public static function hsalsa20($in, $k, $c = null)
    {
        if ($c === null) {
            $x0  = 0x61707865;
            $x5  = 0x3320646e;
            $x10 = 0x79622d32;
            $x15 = 0x6b206574;
        } else {
            $x0  = self::load_4(self::substr($c, 0, 4));
            $x5  = self::load_4(self::substr($c, 4, 4));
            $x10 = self::load_4(self::substr($c, 8, 4));
            $x15 = self::load_4(self::substr($c, 12, 4));
        }
        $x1  = self::load_4(self::substr($k, 0, 4));
        $x2  = self::load_4(self::substr($k, 4, 4));
        $x3  = self::load_4(self::substr($k, 8, 4));
        $x4  = self::load_4(self::substr($k, 12, 4));
        $x11 = self::load_4(self::substr($k, 16, 4));
        $x12 = self::load_4(self::substr($k, 20, 4));
        $x13 = self::load_4(self::substr($k, 24, 4));
        $x14 = self::load_4(self::substr($k, 28, 4));
        $x6  = self::load_4(self::substr($in, 0, 4));
        $x7  = self::load_4(self::substr($in, 4, 4));
        $x8  = self::load_4(self::substr($in, 8, 4));
        $x9  = self::load_4(self::substr($in, 12, 4));

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4 ^= self::rotate($x0 + $x12, 7);
            $x8 ^= self::rotate($x4 + $x0, 9);
            $x12 ^= self::rotate($x8 + $x4, 13);
            $x0 ^= self::rotate($x12 + $x8, 18);
            $x9 ^= self::rotate($x5 + $x1, 7);
            $x13 ^= self::rotate($x9 + $x5, 9);
            $x1 ^= self::rotate($x13 + $x9, 13);
            $x5 ^= self::rotate($x1 + $x13, 18);
            $x14 ^= self::rotate($x10 + $x6, 7);
            $x2 ^= self::rotate($x14 + $x10, 9);
            $x6 ^= self::rotate($x2 + $x14, 13);
            $x10 ^= self::rotate($x6 + $x2, 18);
            $x3 ^= self::rotate($x15 + $x11, 7);
            $x7 ^= self::rotate($x3 + $x15, 9);
            $x11 ^= self::rotate($x7 + $x3, 13);
            $x15 ^= self::rotate($x11 + $x7, 18);
            $x1 ^= self::rotate($x0 + $x3, 7);
            $x2 ^= self::rotate($x1 + $x0, 9);
            $x3 ^= self::rotate($x2 + $x1, 13);
            $x0 ^= self::rotate($x3 + $x2, 18);
            $x6 ^= self::rotate($x5 + $x4, 7);
            $x7 ^= self::rotate($x6 + $x5, 9);
            $x4 ^= self::rotate($x7 + $x6, 13);
            $x5 ^= self::rotate($x4 + $x7, 18);
            $x11 ^= self::rotate($x10 + $x9, 7);
            $x8 ^= self::rotate($x11 + $x10, 9);
            $x9 ^= self::rotate($x8 + $x11, 13);
            $x10 ^= self::rotate($x9 + $x8, 18);
            $x12 ^= self::rotate($x15 + $x14, 7);
            $x13 ^= self::rotate($x12 + $x15, 9);
            $x14 ^= self::rotate($x13 + $x12, 13);
            $x15 ^= self::rotate($x14 + $x13, 18);
        }

        return self::store32_le($x0) .
            self::store32_le($x5) .
            self::store32_le($x10) .
            self::store32_le($x15) .
            self::store32_le($x6) .
            self::store32_le($x7) .
            self::store32_le($x8) .
            self::store32_le($x9);
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/H.php000064400000324323152177723700016744 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_H', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519_H
 *
 * This just contains the constants in the ref10/base.h file
 */
class ParagonIE_Sodium_Core_Curve25519_H extends ParagonIE_Sodium_Core_Util
{
    /**
     * See: libsodium's crypto_core/curve25519/ref10/base.h
     *
     * @var array<int, array<int, array<int, array<int, int>>>> Basically, int[32][8][3][10]
     */
    protected static $base = array(
        array(
            array(
                array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605),
                array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378),
                array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546),
            ),
            array(
                array(-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303),
                array(-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081),
                array(26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697),
            ),
            array(
                array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024),
                array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574),
                array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357),
            ),
            array(
                array(-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540),
                array(23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397),
                array(7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325),
            ),
            array(
                array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380),
                array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306),
                array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942),
            ),
            array(
                array(-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777),
                array(-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737),
                array(-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652),
            ),
            array(
                array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766),
                array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701),
                array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300),
            ),
            array(
                array(14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726),
                array(-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955),
                array(27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425),
            ),
        ),
        array(
            array(
                array(-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171),
                array(27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510),
                array(17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660),
            ),
            array(
                array(-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639),
                array(29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963),
                array(5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950),
            ),
            array(
                array(-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568),
                array(12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335),
                array(25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628),
            ),
            array(
                array(-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007),
                array(-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772),
                array(-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653),
            ),
            array(
                array(2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567),
                array(13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686),
                array(21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372),
            ),
            array(
                array(-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887),
                array(-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954),
                array(-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953),
            ),
            array(
                array(24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833),
                array(-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532),
                array(-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876),
            ),
            array(
                array(2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268),
                array(33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214),
                array(1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038),
            ),
        ),
        array(
            array(
                array(6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800),
                array(4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645),
                array(-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664),
            ),
            array(
                array(1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933),
                array(-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182),
                array(-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222),
            ),
            array(
                array(-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991),
                array(20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880),
                array(9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092),
            ),
            array(
                array(-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295),
                array(19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788),
                array(8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553),
            ),
            array(
                array(-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026),
                array(11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347),
                array(-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033),
            ),
            array(
                array(-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395),
                array(-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278),
                array(1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890),
            ),
            array(
                array(32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995),
                array(-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596),
                array(-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891),
            ),
            array(
                array(31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060),
                array(11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608),
                array(-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606),
            ),
        ),
        array(
            array(
                array(7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389),
                array(-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016),
                array(-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341),
            ),
            array(
                array(-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505),
                array(14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553),
                array(-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655),
            ),
            array(
                array(15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220),
                array(12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631),
                array(-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099),
            ),
            array(
                array(26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556),
                array(14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749),
                array(236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930),
            ),
            array(
                array(1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391),
                array(5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253),
                array(20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066),
            ),
            array(
                array(24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958),
                array(-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082),
                array(-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383),
            ),
            array(
                array(-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521),
                array(-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807),
                array(23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948),
            ),
            array(
                array(9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134),
                array(-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455),
                array(27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629),
            ),
        ),
        array(
            array(
                array(-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069),
                array(-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746),
                array(24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919),
            ),
            array(
                array(11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837),
                array(8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906),
                array(-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771),
            ),
            array(
                array(-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817),
                array(10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098),
                array(10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409),
            ),
            array(
                array(-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504),
                array(-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727),
                array(28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420),
            ),
            array(
                array(-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003),
                array(-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605),
                array(-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384),
            ),
            array(
                array(-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701),
                array(-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683),
                array(29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708),
            ),
            array(
                array(-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563),
                array(-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260),
                array(-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387),
            ),
            array(
                array(-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672),
                array(23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686),
                array(-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665),
            ),
        ),
        array(
            array(
                array(11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182),
                array(-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277),
                array(14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628),
            ),
            array(
                array(-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474),
                array(-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539),
                array(-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822),
            ),
            array(
                array(-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970),
                array(19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756),
                array(-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508),
            ),
            array(
                array(-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683),
                array(-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655),
                array(-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158),
            ),
            array(
                array(-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125),
                array(-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839),
                array(-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664),
            ),
            array(
                array(27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294),
                array(-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899),
                array(-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070),
            ),
            array(
                array(3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294),
                array(-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949),
                array(-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083),
            ),
            array(
                array(31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420),
                array(-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940),
                array(29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396),
            ),
        ),
        array(
            array(
                array(-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567),
                array(20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127),
                array(-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294),
            ),
            array(
                array(-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887),
                array(22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964),
                array(16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195),
            ),
            array(
                array(9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244),
                array(24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999),
                array(-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762),
            ),
            array(
                array(-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274),
                array(-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236),
                array(-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605),
            ),
            array(
                array(-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761),
                array(-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884),
                array(-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482),
            ),
            array(
                array(-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638),
                array(-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490),
                array(-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170),
            ),
            array(
                array(5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736),
                array(10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124),
                array(-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392),
            ),
            array(
                array(8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029),
                array(6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048),
                array(28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958),
            ),
        ),
        array(
            array(
                array(24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593),
                array(26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071),
                array(-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692),
            ),
            array(
                array(11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687),
                array(-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441),
                array(-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001),
            ),
            array(
                array(-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460),
                array(-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007),
                array(-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762),
            ),
            array(
                array(15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005),
                array(-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674),
                array(4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035),
            ),
            array(
                array(7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590),
                array(-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957),
                array(-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812),
            ),
            array(
                array(33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740),
                array(-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122),
                array(-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158),
            ),
            array(
                array(8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885),
                array(26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140),
                array(19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857),
            ),
            array(
                array(801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155),
                array(19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260),
                array(19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483),
            ),
        ),
        array(
            array(
                array(-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677),
                array(32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815),
                array(22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751),
            ),
            array(
                array(-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203),
                array(-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208),
                array(1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230),
            ),
            array(
                array(16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850),
                array(-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389),
                array(-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968),
            ),
            array(
                array(-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689),
                array(14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880),
                array(5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304),
            ),
            array(
                array(30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632),
                array(-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412),
                array(20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566),
            ),
            array(
                array(-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038),
                array(-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232),
                array(-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943),
            ),
            array(
                array(17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856),
                array(23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738),
                array(15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971),
            ),
            array(
                array(-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718),
                array(-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697),
                array(-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883),
            ),
        ),
        array(
            array(
                array(5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912),
                array(-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358),
                array(3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849),
            ),
            array(
                array(29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307),
                array(-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977),
                array(-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335),
            ),
            array(
                array(-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644),
                array(-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616),
                array(-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735),
            ),
            array(
                array(-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099),
                array(29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341),
                array(-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336),
            ),
            array(
                array(-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646),
                array(31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425),
                array(-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388),
            ),
            array(
                array(-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743),
                array(-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822),
                array(-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462),
            ),
            array(
                array(18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985),
                array(9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702),
                array(-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797),
            ),
            array(
                array(21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293),
                array(27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100),
                array(19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688),
            ),
        ),
        array(
            array(
                array(12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186),
                array(2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610),
                array(-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707),
            ),
            array(
                array(7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220),
                array(915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025),
                array(32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044),
            ),
            array(
                array(32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992),
                array(-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027),
                array(21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197),
            ),
            array(
                array(8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901),
                array(31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952),
                array(19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878),
            ),
            array(
                array(-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390),
                array(32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730),
                array(2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730),
            ),
            array(
                array(-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180),
                array(-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272),
                array(-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715),
            ),
            array(
                array(-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970),
                array(-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772),
                array(-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865),
            ),
            array(
                array(15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750),
                array(20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373),
                array(32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348),
            ),
        ),
        array(
            array(
                array(9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144),
                array(-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195),
                array(5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086),
            ),
            array(
                array(-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684),
                array(-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518),
                array(-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233),
            ),
            array(
                array(-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793),
                array(-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794),
                array(580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435),
            ),
            array(
                array(23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921),
                array(13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518),
                array(2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563),
            ),
            array(
                array(14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278),
                array(-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024),
                array(4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030),
            ),
            array(
                array(10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783),
                array(27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717),
                array(6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844),
            ),
            array(
                array(14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333),
                array(16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048),
                array(22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760),
            ),
            array(
                array(-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760),
                array(-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757),
                array(-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112),
            ),
        ),
        array(
            array(
                array(-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468),
                array(3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184),
                array(10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289),
            ),
            array(
                array(15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066),
                array(24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882),
                array(13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226),
            ),
            array(
                array(16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101),
                array(29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279),
                array(-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811),
            ),
            array(
                array(27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709),
                array(20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714),
                array(-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121),
            ),
            array(
                array(9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464),
                array(12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847),
                array(13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400),
            ),
            array(
                array(4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414),
                array(-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158),
                array(17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045),
            ),
            array(
                array(-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415),
                array(-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459),
                array(-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079),
            ),
            array(
                array(21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412),
                array(-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743),
                array(-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836),
            ),
        ),
        array(
            array(
                array(12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022),
                array(18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429),
                array(-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065),
            ),
            array(
                array(30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861),
                array(10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000),
                array(-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101),
            ),
            array(
                array(32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815),
                array(29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642),
                array(10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966),
            ),
            array(
                array(25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574),
                array(-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742),
                array(-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689),
            ),
            array(
                array(12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020),
                array(-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772),
                array(3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982),
            ),
            array(
                array(-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953),
                array(-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218),
                array(-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265),
            ),
            array(
                array(29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073),
                array(-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325),
                array(-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798),
            ),
            array(
                array(-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870),
                array(-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863),
                array(-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927),
            ),
        ),
        array(
            array(
                array(-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267),
                array(-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663),
                array(22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862),
            ),
            array(
                array(-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673),
                array(15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943),
                array(15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020),
            ),
            array(
                array(-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238),
                array(11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064),
                array(14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795),
            ),
            array(
                array(15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052),
                array(-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904),
                array(29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531),
            ),
            array(
                array(-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979),
                array(-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841),
                array(10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431),
            ),
            array(
                array(10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324),
                array(-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940),
                array(10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320),
            ),
            array(
                array(-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184),
                array(14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114),
                array(30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878),
            ),
            array(
                array(12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784),
                array(-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091),
                array(-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585),
            ),
        ),
        array(
            array(
                array(-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208),
                array(10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864),
                array(17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661),
            ),
            array(
                array(7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233),
                array(26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212),
                array(-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525),
            ),
            array(
                array(-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068),
                array(9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397),
                array(-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988),
            ),
            array(
                array(5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889),
                array(32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038),
                array(14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697),
            ),
            array(
                array(20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875),
                array(-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905),
                array(-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656),
            ),
            array(
                array(11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818),
                array(27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714),
                array(10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203),
            ),
            array(
                array(20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931),
                array(-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024),
                array(-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084),
            ),
            array(
                array(-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204),
                array(20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817),
                array(27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667),
            ),
        ),
        array(
            array(
                array(11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504),
                array(-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768),
                array(-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255),
            ),
            array(
                array(6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790),
                array(1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438),
                array(-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333),
            ),
            array(
                array(17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971),
                array(31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905),
                array(29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409),
            ),
            array(
                array(12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409),
                array(6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499),
                array(-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363),
            ),
            array(
                array(28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664),
                array(-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324),
                array(-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940),
            ),
            array(
                array(13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990),
                array(-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914),
                array(-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290),
            ),
            array(
                array(24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257),
                array(-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433),
                array(-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236),
            ),
            array(
                array(-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045),
                array(11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093),
                array(-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347),
            ),
        ),
        array(
            array(
                array(-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191),
                array(-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507),
                array(-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906),
            ),
            array(
                array(3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018),
                array(-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109),
                array(-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926),
            ),
            array(
                array(-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528),
                array(8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625),
                array(-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286),
            ),
            array(
                array(2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033),
                array(27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866),
                array(21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896),
            ),
            array(
                array(30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075),
                array(26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347),
                array(-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437),
            ),
            array(
                array(-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165),
                array(-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588),
                array(-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193),
            ),
            array(
                array(-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017),
                array(-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883),
                array(21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961),
            ),
            array(
                array(8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043),
                array(29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663),
                array(-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362),
            ),
        ),
        array(
            array(
                array(-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860),
                array(2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466),
                array(-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063),
            ),
            array(
                array(-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997),
                array(-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295),
                array(-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369),
            ),
            array(
                array(9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385),
                array(18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109),
                array(2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906),
            ),
            array(
                array(4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424),
                array(-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185),
                array(7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962),
            ),
            array(
                array(-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325),
                array(10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593),
                array(696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404),
            ),
            array(
                array(-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644),
                array(17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801),
                array(26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804),
            ),
            array(
                array(-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884),
                array(-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577),
                array(-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849),
            ),
            array(
                array(32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473),
                array(-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644),
                array(-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319),
            ),
        ),
        array(
            array(
                array(-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599),
                array(-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768),
                array(-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084),
            ),
            array(
                array(-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328),
                array(-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369),
                array(20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920),
            ),
            array(
                array(12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815),
                array(-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025),
                array(-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397),
            ),
            array(
                array(-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448),
                array(6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981),
                array(30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165),
            ),
            array(
                array(32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501),
                array(17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073),
                array(-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861),
            ),
            array(
                array(14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845),
                array(-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211),
                array(18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870),
            ),
            array(
                array(10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096),
                array(33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803),
                array(-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168),
            ),
            array(
                array(30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965),
                array(-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505),
                array(18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598),
            ),
        ),
        array(
            array(
                array(5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782),
                array(5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900),
                array(-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479),
            ),
            array(
                array(-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208),
                array(8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232),
                array(17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719),
            ),
            array(
                array(16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271),
                array(-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326),
                array(-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132),
            ),
            array(
                array(14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300),
                array(8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570),
                array(15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670),
            ),
            array(
                array(-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994),
                array(-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913),
                array(31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317),
            ),
            array(
                array(-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730),
                array(842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096),
                array(-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078),
            ),
            array(
                array(-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411),
                array(-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905),
                array(-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654),
            ),
            array(
                array(-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870),
                array(-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498),
                array(12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579),
            ),
        ),
        array(
            array(
                array(14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677),
                array(10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647),
                array(-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743),
            ),
            array(
                array(-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468),
                array(21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375),
                array(-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155),
            ),
            array(
                array(6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725),
                array(-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612),
                array(-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943),
            ),
            array(
                array(-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944),
                array(30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928),
                array(9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406),
            ),
            array(
                array(22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139),
                array(-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963),
                array(-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693),
            ),
            array(
                array(1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734),
                array(-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680),
                array(-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410),
            ),
            array(
                array(-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931),
                array(-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654),
                array(22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710),
            ),
            array(
                array(29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180),
                array(-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684),
                array(-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895),
            ),
        ),
        array(
            array(
                array(22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501),
                array(-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413),
                array(6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880),
            ),
            array(
                array(-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874),
                array(22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962),
                array(-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899),
            ),
            array(
                array(21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152),
                array(9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063),
                array(7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080),
            ),
            array(
                array(-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146),
                array(-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183),
                array(-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133),
            ),
            array(
                array(-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421),
                array(-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622),
                array(-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197),
            ),
            array(
                array(2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663),
                array(31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753),
                array(4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755),
            ),
            array(
                array(-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862),
                array(-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118),
                array(26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171),
            ),
            array(
                array(15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380),
                array(16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824),
                array(28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270),
            ),
        ),
        array(
            array(
                array(-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438),
                array(-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584),
                array(-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562),
            ),
            array(
                array(30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471),
                array(18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610),
                array(19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269),
            ),
            array(
                array(-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650),
                array(14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369),
                array(19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461),
            ),
            array(
                array(30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462),
                array(-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793),
                array(-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218),
            ),
            array(
                array(-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226),
                array(18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019),
                array(-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037),
            ),
            array(
                array(31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171),
                array(-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132),
                array(-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841),
            ),
            array(
                array(21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181),
                array(-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210),
                array(-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040),
            ),
            array(
                array(3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935),
                array(24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105),
                array(-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814),
            ),
        ),
        array(
            array(
                array(793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852),
                array(5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581),
                array(-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646),
            ),
            array(
                array(10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844),
                array(10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025),
                array(27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453),
            ),
            array(
                array(-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068),
                array(4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192),
                array(-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921),
            ),
            array(
                array(-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259),
                array(-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426),
                array(-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072),
            ),
            array(
                array(-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305),
                array(13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832),
                array(28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943),
            ),
            array(
                array(-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011),
                array(24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447),
                array(17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494),
            ),
            array(
                array(-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245),
                array(-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859),
                array(28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915),
            ),
            array(
                array(16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707),
                array(10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848),
                array(-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224),
            ),
        ),
        array(
            array(
                array(-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391),
                array(15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215),
                array(-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101),
            ),
            array(
                array(23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713),
                array(21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849),
                array(-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930),
            ),
            array(
                array(-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940),
                array(-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031),
                array(-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404),
            ),
            array(
                array(-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243),
                array(-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116),
                array(-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525),
            ),
            array(
                array(-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509),
                array(-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883),
                array(15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865),
            ),
            array(
                array(-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660),
                array(4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273),
                array(-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138),
            ),
            array(
                array(-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560),
                array(-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135),
                array(2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941),
            ),
            array(
                array(-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739),
                array(18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756),
                array(-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819),
            ),
        ),
        array(
            array(
                array(-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347),
                array(-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028),
                array(21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075),
            ),
            array(
                array(16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799),
                array(-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609),
                array(-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817),
            ),
            array(
                array(-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989),
                array(-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523),
                array(4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278),
            ),
            array(
                array(31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045),
                array(19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377),
                array(24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480),
            ),
            array(
                array(17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016),
                array(510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426),
                array(18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525),
            ),
            array(
                array(13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396),
                array(9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080),
                array(12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892),
            ),
            array(
                array(15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275),
                array(11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074),
                array(20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140),
            ),
            array(
                array(-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717),
                array(-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101),
                array(24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127),
            ),
        ),
        array(
            array(
                array(-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632),
                array(-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415),
                array(-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160),
            ),
            array(
                array(31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876),
                array(22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625),
                array(-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478),
            ),
            array(
                array(27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164),
                array(26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595),
                array(-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248),
            ),
            array(
                array(-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858),
                array(15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193),
                array(8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184),
            ),
            array(
                array(-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942),
                array(-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635),
                array(21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948),
            ),
            array(
                array(11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935),
                array(-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415),
                array(-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416),
            ),
            array(
                array(-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018),
                array(4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778),
                array(366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659),
            ),
            array(
                array(-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385),
                array(18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503),
                array(476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329),
            ),
        ),
        array(
            array(
                array(20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056),
                array(-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838),
                array(24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948),
            ),
            array(
                array(-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691),
                array(-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118),
                array(-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517),
            ),
            array(
                array(-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269),
                array(-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904),
                array(-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589),
            ),
            array(
                array(-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193),
                array(-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910),
                array(-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930),
            ),
            array(
                array(-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667),
                array(25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481),
                array(-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876),
            ),
            array(
                array(22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640),
                array(-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278),
                array(-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112),
            ),
            array(
                array(26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272),
                array(17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012),
                array(-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221),
            ),
            array(
                array(30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046),
                array(13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345),
                array(-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310),
            ),
        ),
        array(
            array(
                array(19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937),
                array(31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636),
                array(-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008),
            ),
            array(
                array(-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429),
                array(-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576),
                array(31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066),
            ),
            array(
                array(-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490),
                array(-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104),
                array(33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053),
            ),
            array(
                array(31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275),
                array(-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511),
                array(22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095),
            ),
            array(
                array(-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439),
                array(23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939),
                array(-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424),
            ),
            array(
                array(2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310),
                array(3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608),
                array(-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079),
            ),
            array(
                array(-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101),
                array(21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418),
                array(18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576),
            ),
            array(
                array(30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356),
                array(9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996),
                array(-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099),
            ),
        ),
        array(
            array(
                array(-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728),
                array(-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658),
                array(-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242),
            ),
            array(
                array(-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001),
                array(-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766),
                array(18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373),
            ),
            array(
                array(26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458),
                array(-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628),
                array(-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657),
            ),
            array(
                array(-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062),
                array(25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616),
                array(31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014),
            ),
            array(
                array(24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383),
                array(-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814),
                array(-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718),
            ),
            array(
                array(30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417),
                array(2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222),
                array(33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444),
            ),
            array(
                array(-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597),
                array(23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970),
                array(1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799),
            ),
            array(
                array(-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647),
                array(13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511),
                array(-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032),
            ),
        ),
        array(
            array(
                array(9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834),
                array(-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461),
                array(29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062),
            ),
            array(
                array(-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516),
                array(-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547),
                array(-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240),
            ),
            array(
                array(-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038),
                array(-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741),
                array(16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103),
            ),
            array(
                array(-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747),
                array(-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323),
                array(31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016),
            ),
            array(
                array(-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373),
                array(15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228),
                array(-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141),
            ),
            array(
                array(16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399),
                array(11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831),
                array(-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376),
            ),
            array(
                array(-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313),
                array(-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958),
                array(-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577),
            ),
            array(
                array(-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743),
                array(29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684),
                array(-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476),
            ),
        )
    );

    /**
     * See: libsodium's crypto_core/curve25519/ref10/base2.h
     *
     * @var array basically int[8][3]
     */
    protected static $base2 = array(
        array(
            array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605),
            array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378),
            array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546),
        ),
        array(
            array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024),
            array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574),
            array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357),
        ),
        array(
            array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380),
            array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306),
            array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942),
        ),
        array(
            array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766),
            array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701),
            array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300),
        ),
        array(
            array(-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877),
            array(-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951),
            array(4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784),
        ),
        array(
            array(-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436),
            array(25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918),
            array(23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877),
        ),
        array(
            array(-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800),
            array(-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305),
            array(-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300),
        ),
        array(
            array(-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876),
            array(-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619),
            array(-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683),
        )
    );

    /**
     * 37095705934669439343138083508754565189542113879843219016388785533085940283555
     *
     * @var array<int, int>
     */
    protected static $d = array(
        -10913610,
        13857413,
        -15372611,
        6949391,
        114729,
        -8787816,
        -6275908,
        -3247719,
        -18696448,
        -12055116
    );

    /**
     * 2 * d = 16295367250680780974490674513165176452449235426866156013048779062215315747161
     *
     * @var array<int, int>
     */
    protected static $d2 = array(
        -21827239,
        -5839606,
        -30745221,
        13898782,
        229458,
        15978800,
        -12551817,
        -6495438,
        29715968,
        9444199
    );

    /**
     * sqrt(-1)
     *
     * @var array<int, int>
     */
    protected static $sqrtm1 = array(
        -32595792,
        -7943725,
        9377950,
        3500415,
        12389472,
        -272473,
        -25146209,
        -2005654,
        326686,
        11406482
    );
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Ge/Precomp.php000064400000002650152177723700020511 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_Precomp', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
 */
class ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
{
    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $yplusx;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $yminusx;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $xy2d;

    /**
     * ParagonIE_Sodium_Core_Curve25519_Ge_Precomp constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $yplusx
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $yminusx
     * @param ParagonIE_Sodium_Core_Curve25519_Fe $xy2d
     */
    public function __construct(
        ParagonIE_Sodium_Core_Curve25519_Fe $yplusx = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $yminusx = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $xy2d = null
    ) {
        if ($yplusx === null) {
            $yplusx = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->yplusx = $yplusx;
        if ($yminusx === null) {
            $yminusx = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->yminusx = $yminusx;
        if ($xy2d === null) {
            $xy2d = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->xy2d = $xy2d;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Ge/P1p1.php000064400000003201152177723700017616 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P1p1', false)) {
    return;
}
/**
 * Class ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
 */
class ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
{
    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $T;

    /**
     * ParagonIE_Sodium_Core_Curve25519_Ge_P1p1 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t
     */
    public function __construct(
        ParagonIE_Sodium_Core_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $z = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $t = null
    ) {
        if ($x === null) {
            $x = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->X = $x;
        if ($y === null) {
            $y = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Z = $z;
        if ($t === null) {
            $t = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->T = $t;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Ge/P3.php000064400000003172152177723700017366 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P3', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519_Ge_P3
 */
class ParagonIE_Sodium_Core_Curve25519_Ge_P3
{
    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $T;

    /**
     * ParagonIE_Sodium_Core_Curve25519_Ge_P3 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $t
     */
    public function __construct(
        ParagonIE_Sodium_Core_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $z = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $t = null
    ) {
        if ($x === null) {
            $x = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->X = $x;
        if ($y === null) {
            $y = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Z = $z;
        if ($t === null) {
            $t = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->T = $t;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Ge/Cached.php000064400000003345152177723700020255 0ustar00<?php


if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_Cached', false)) {
    return;
}
/**
 * Class ParagonIE_Sodium_Core_Curve25519_Ge_Cached
 */
class ParagonIE_Sodium_Core_Curve25519_Ge_Cached
{
    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $YplusX;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $YminusX;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $T2d;

    /**
     * ParagonIE_Sodium_Core_Curve25519_Ge_Cached constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $YplusX
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $YminusX
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $Z
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $T2d
     */
    public function __construct(
        ParagonIE_Sodium_Core_Curve25519_Fe $YplusX = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $YminusX = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $Z = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $T2d = null
    ) {
        if ($YplusX === null) {
            $YplusX = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->YplusX = $YplusX;
        if ($YminusX === null) {
            $YminusX = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->YminusX = $YminusX;
        if ($Z === null) {
            $Z = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Z = $Z;
        if ($T2d === null) {
            $T2d = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->T2d = $T2d;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Ge/P2.php000064400000002501152177723700017360 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_Ge_P2', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519_Ge_P2
 */
class ParagonIE_Sodium_Core_Curve25519_Ge_P2
{
    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core_Curve25519_Fe
     */
    public $Z;

    /**
     * ParagonIE_Sodium_Core_Curve25519_Ge_P2 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core_Curve25519_Fe|null $z
     */
    public function __construct(
        ParagonIE_Sodium_Core_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core_Curve25519_Fe $z = null
    ) {
        if ($x === null) {
            $x = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->X = $x;
        if ($y === null) {
            $y = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = new ParagonIE_Sodium_Core_Curve25519_Fe();
        }
        $this->Z = $z;
    }
}
vendor/paragonie/sodium_compat/src/Core/Curve25519/Fe.php000064400000005503152177723700017103 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Curve25519_Fe', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Curve25519_Fe
 *
 * This represents a Field Element
 */
class ParagonIE_Sodium_Core_Curve25519_Fe implements ArrayAccess
{
    /**
     * @var array
     */
    protected $container = array();

    /**
     * @var int
     */
    protected $size = 10;

    /**
     * @internal You should not use this directly from another application
     *
     * @param array $array
     * @param bool $save_indexes
     * @return self
     */
    public static function fromArray($array, $save_indexes = null)
    {
        $count = count($array);
        if ($save_indexes) {
            $keys = array_keys($array);
        } else {
            $keys = range(0, $count - 1);
        }
        $array = array_values($array);

        $obj = new ParagonIE_Sodium_Core_Curve25519_Fe();
        if ($save_indexes) {
            for ($i = 0; $i < $count; ++$i) {
                $obj->offsetSet($keys[$i], $array[$i]);
            }
        } else {
            for ($i = 0; $i < $count; ++$i) {
                $obj->offsetSet($i, $array[$i]);
            }
        }
        return $obj;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @param mixed $value
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetSet($offset, $value)
    {
        if (!is_int($value)) {
            throw new InvalidArgumentException('Expected an integer');
        }
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            $this->container[$offset] = $value;
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return bool
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return mixed|null
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetGet($offset)
    {
        return isset($this->container[$offset])
            ? $this->container[$offset]
            : null;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return array
     */
    public function __debugInfo()
    {
        return array(implode(', ', $this->container));
    }
}
vendor/paragonie/sodium_compat/src/Core/Ed25519.php000064400000036243152177723700016022 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_Ed25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Ed25519
 */
abstract class ParagonIE_Sodium_Core_Ed25519 extends ParagonIE_Sodium_Core_Curve25519
{
    const KEYPAIR_BYTES = 96;
    const SEED_BYTES = 32;

    /**
     * @internal You should not use this directly from another application
     *
     * @return string (96 bytes)
     * @throws Exception
     * @throws SodiumException
     * @throws TypeError
     */
    public static function keypair()
    {
        $seed = random_bytes(self::SEED_BYTES);
        $pk = '';
        $sk = '';
        self::seed_keypair($pk, $sk, $seed);
        return $sk . $pk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $pk
     * @param string $sk
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function seed_keypair(&$pk, &$sk, $seed)
    {
        if (self::strlen($seed) !== self::SEED_BYTES) {
            throw new RangeException('crypto_sign keypair seed must be 32 bytes long');
        }

        /** @var string $pk */
        $pk = self::publickey_from_secretkey($seed);
        $sk = $seed . $pk;
        return $sk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws TypeError
     */
    public static function secretkey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 0, 64);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws TypeError
     */
    public static function publickey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 64, 32);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function publickey_from_secretkey($sk)
    {
        /** @var string $sk */
        $sk = hash('sha512', self::substr($sk, 0, 32), true);
        $sk[0] = self::intToChr(
            self::chrToInt($sk[0]) & 248
        );
        $sk[31] = self::intToChr(
            (self::chrToInt($sk[31]) & 63) | 64
        );
        return self::sk_to_pk($sk);
    }

    /**
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function pk_to_curve25519($pk)
    {
        if (self::small_order($pk)) {
            throw new SodiumException('Public key is on a small order');
        }
        $A = self::ge_frombytes_negate_vartime(self::substr($pk, 0, 32));
        $p1 = self::ge_mul_l($A);
        if (!self::fe_isnonzero($p1->X)) {
            throw new SodiumException('Unexpected zero result');
        }

        # fe_1(one_minus_y);
        # fe_sub(one_minus_y, one_minus_y, A.Y);
        # fe_invert(one_minus_y, one_minus_y);
        $one_minux_y = self::fe_invert(
            self::fe_sub(
                self::fe_1(),
                $A->Y
            )
        );

        # fe_1(x);
        # fe_add(x, x, A.Y);
        # fe_mul(x, x, one_minus_y);
        $x = self::fe_mul(
            self::fe_add(self::fe_1(), $A->Y),
            $one_minux_y
        );

        # fe_tobytes(curve25519_pk, x);
        return self::fe_tobytes($x);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sk_to_pk($sk)
    {
        return self::ge_p3_tobytes(
            self::ge_scalarmult_base(
                self::substr($sk, 0, 32)
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($message, $sk)
    {
        /** @var string $signature */
        $signature = self::sign_detached($message, $sk);
        return $signature . $message;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message A signed message
     * @param string $pk      Public key
     * @return string         Message (without signature)
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_open($message, $pk)
    {
        /** @var string $signature */
        $signature = self::substr($message, 0, 64);

        /** @var string $message */
        $message = self::substr($message, 64);

        if (self::verify_detached($signature, $message, $pk)) {
            return $message;
        }
        throw new SodiumException('Invalid signature');
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_detached($message, $sk)
    {
        # crypto_hash_sha512(az, sk, 32);
        $az =  hash('sha512', self::substr($sk, 0, 32), true);

        # az[0] &= 248;
        # az[31] &= 63;
        # az[31] |= 64;
        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, az + 32, 32);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, nonce);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($az, 32, 32));
        hash_update($hs, $message);
        $nonceHash = hash_final($hs, true);

        # memmove(sig + 32, sk + 32, 32);
        $pk = self::substr($sk, 32, 32);

        # sc_reduce(nonce);
        # ge_scalarmult_base(&R, nonce);
        # ge_p3_tobytes(sig, &R);
        $nonce = self::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
        $sig = self::ge_p3_tobytes(
            self::ge_scalarmult_base($nonce)
        );

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, sig, 64);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, hram);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($pk, 0, 32));
        hash_update($hs, $message);
        $hramHash = hash_final($hs, true);

        # sc_reduce(hram);
        # sc_muladd(sig + 32, hram, az, nonce);
        $hram = self::sc_reduce($hramHash);
        $sigAfter = self::sc_muladd($hram, $az, $nonce);
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);

        try {
            ParagonIE_Sodium_Compat::memzero($az);
        } catch (SodiumException $ex) {
            $az = null;
        }
        return $sig;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sig
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function verify_detached($sig, $message, $pk)
    {
        if (self::strlen($sig) < 64) {
            throw new SodiumException('Signature is too short');
        }
        if (self::check_S_lt_L(self::substr($sig, 32, 32))) {
            throw new SodiumException('S < L - Invalid signature');
        }
        if (self::small_order($sig)) {
            throw new SodiumException('Signature is on too small of an order');
        }
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
            throw new SodiumException('Invalid signature');
        }
        $d = 0;
        for ($i = 0; $i < 32; ++$i) {
            $d |= self::chrToInt($pk[$i]);
        }
        if ($d === 0) {
            throw new SodiumException('All zero public key');
        }

        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
        $orig = ParagonIE_Sodium_Compat::$fastMult;

        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
        ParagonIE_Sodium_Compat::$fastMult = true;

        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
        $A = self::ge_frombytes_negate_vartime($pk);

        /** @var string $hDigest */
        $hDigest = hash(
            'sha512',
            self::substr($sig, 0, 32) .
                self::substr($pk, 0, 32) .
                $message,
            true
        );

        /** @var string $h */
        $h = self::sc_reduce($hDigest) . self::substr($hDigest, 32);

        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
        $R = self::ge_double_scalarmult_vartime(
            $h,
            $A,
            self::substr($sig, 32)
        );

        /** @var string $rcheck */
        $rcheck = self::ge_tobytes($R);

        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
        ParagonIE_Sodium_Compat::$fastMult = $orig;

        return self::verify_32($rcheck, self::substr($sig, 0, 32));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $S
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function check_S_lt_L($S)
    {
        if (self::strlen($S) < 32) {
            throw new SodiumException('Signature must be 32 bytes');
        }
        $L = array(
            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
            0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
        );
        $c = 0;
        $n = 1;
        $i = 32;

        /** @var array<int, int> $L */
        do {
            --$i;
            $x = self::chrToInt($S[$i]);
            $c |= (
                (($x - $L[$i]) >> 8) & $n
            );
            $n &= (
                (($x ^ $L[$i]) - 1) >> 8
            );
        } while ($i !== 0);

        return $c === 0;
    }

    /**
     * @param string $R
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function small_order($R)
    {
        /** @var array<int, array<int, int>> $blacklist */
        $blacklist = array(
            /* 0 (order 4) */
            array(
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 1 (order 1) */
            array(
                0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05
            ),
            /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a
            ),
            /* p-1 (order 2) */
            array(
                0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85
            ),
            /* p (order 4) */
            array(
                0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa
            ),
            /* p+1 (order 1) */
            array(
                0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* 2p-1 (order 2) */
            array(
                0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p (order 4) */
            array(
                0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p+1 (order 1) */
            array(
                0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            )
        );
        /** @var int $countBlacklist */
        $countBlacklist = count($blacklist);

        for ($i = 0; $i < $countBlacklist; ++$i) {
            $c = 0;
            for ($j = 0; $j < 32; ++$j) {
                $c |= self::chrToInt($R[$j]) ^ (int) $blacklist[$i][$j];
            }
            if ($c === 0) {
                return true;
            }
        }
        return false;
    }
}
vendor/paragonie/sodium_compat/src/Compat.php000064400000323036152177723700015416 0ustar00<?php

/**
 * Libsodium compatibility layer
 *
 * This is the only class you should be interfacing with, as a user of
 * sodium_compat.
 *
 * If the PHP extension for libsodium is installed, it will always use that
 * instead of our implementations. You get better performance and stronger
 * guarantees against side-channels that way.
 *
 * However, if your users don't have the PHP extension installed, we offer a
 * compatible interface here. It will give you the correct results as if the
 * PHP extension was installed. It won't be as fast, of course.
 *
 * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION *
 *                                                                               *
 *     Until audited, this is probably not safe to use! DANGER WILL ROBINSON     *
 *                                                                               *
 * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION * CAUTION *
 */

if (class_exists('ParagonIE_Sodium_Compat', false)) {
    return;
}

class ParagonIE_Sodium_Compat
{
    /**
     * This parameter prevents the use of the PECL extension.
     * It should only be used for unit testing.
     *
     * @var bool
     */
    public static $disableFallbackForUnitTests = false;

    /**
     * Use fast multiplication rather than our constant-time multiplication
     * implementation. Can be enabled at runtime. Only enable this if you
     * are absolutely certain that there is no timing leak on your platform.
     *
     * @var bool
     */
    public static $fastMult = false;

    const LIBRARY_VERSION_MAJOR = 9;
    const LIBRARY_VERSION_MINOR = 1;
    const VERSION_STRING = 'polyfill-1.0.8';

    // From libsodium
    const CRYPTO_AEAD_AES256GCM_KEYBYTES = 32;
    const CRYPTO_AEAD_AES256GCM_NSECBYTES = 0;
    const CRYPTO_AEAD_AES256GCM_NPUBBYTES = 12;
    const CRYPTO_AEAD_AES256GCM_ABYTES = 16;
    const CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES = 32;
    const CRYPTO_AEAD_CHACHA20POLY1305_NSECBYTES = 0;
    const CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES = 8;
    const CRYPTO_AEAD_CHACHA20POLY1305_ABYTES = 16;
    const CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES = 32;
    const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NSECBYTES = 0;
    const CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES = 12;
    const CRYPTO_AEAD_CHACHA20POLY1305_IETF_ABYTES = 16;
    const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES = 32;
    const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NSECBYTES = 0;
    const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES = 24;
    const CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES = 16;
    const CRYPTO_AUTH_BYTES = 32;
    const CRYPTO_AUTH_KEYBYTES = 32;
    const CRYPTO_BOX_SEALBYTES = 16;
    const CRYPTO_BOX_SECRETKEYBYTES = 32;
    const CRYPTO_BOX_PUBLICKEYBYTES = 32;
    const CRYPTO_BOX_KEYPAIRBYTES = 64;
    const CRYPTO_BOX_MACBYTES = 16;
    const CRYPTO_BOX_NONCEBYTES = 24;
    const CRYPTO_BOX_SEEDBYTES = 32;
    const CRYPTO_KX_BYTES = 32;
    const CRYPTO_KX_SEEDBYTES = 32;
    const CRYPTO_KX_PUBLICKEYBYTES = 32;
    const CRYPTO_KX_SECRETKEYBYTES = 32;
    const CRYPTO_GENERICHASH_BYTES = 32;
    const CRYPTO_GENERICHASH_BYTES_MIN = 16;
    const CRYPTO_GENERICHASH_BYTES_MAX = 64;
    const CRYPTO_GENERICHASH_KEYBYTES = 32;
    const CRYPTO_GENERICHASH_KEYBYTES_MIN = 16;
    const CRYPTO_GENERICHASH_KEYBYTES_MAX = 64;
    const CRYPTO_PWHASH_SALTBYTES = 16;
    const CRYPTO_PWHASH_STRPREFIX = '$argon2i$';
    const CRYPTO_PWHASH_ALG_ARGON2I13 = 1;
    const CRYPTO_PWHASH_ALG_ARGON2ID13 = 2;
    const CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE = 33554432;
    const CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE = 4;
    const CRYPTO_PWHASH_MEMLIMIT_MODERATE = 134217728;
    const CRYPTO_PWHASH_OPSLIMIT_MODERATE = 6;
    const CRYPTO_PWHASH_MEMLIMIT_SENSITIVE = 536870912;
    const CRYPTO_PWHASH_OPSLIMIT_SENSITIVE = 8;
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_SALTBYTES = 32;
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_STRPREFIX = '$7$';
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_INTERACTIVE = 534288;
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_INTERACTIVE = 16777216;
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_OPSLIMIT_SENSITIVE = 33554432;
    const CRYPTO_PWHASH_SCRYPTSALSA208SHA256_MEMLIMIT_SENSITIVE = 1073741824;
    const CRYPTO_SCALARMULT_BYTES = 32;
    const CRYPTO_SCALARMULT_SCALARBYTES = 32;
    const CRYPTO_SHORTHASH_BYTES = 8;
    const CRYPTO_SHORTHASH_KEYBYTES = 16;
    const CRYPTO_SECRETBOX_KEYBYTES = 32;
    const CRYPTO_SECRETBOX_MACBYTES = 16;
    const CRYPTO_SECRETBOX_NONCEBYTES = 24;
    const CRYPTO_SIGN_BYTES = 64;
    const CRYPTO_SIGN_SEEDBYTES = 32;
    const CRYPTO_SIGN_PUBLICKEYBYTES = 32;
    const CRYPTO_SIGN_SECRETKEYBYTES = 64;
    const CRYPTO_SIGN_KEYPAIRBYTES = 96;
    const CRYPTO_STREAM_KEYBYTES = 32;
    const CRYPTO_STREAM_NONCEBYTES = 24;

    /**
     * Cache-timing-safe implementation of bin2hex().
     *
     * @param string $string A string (probably raw binary)
     * @return string        A hexadecimal-encoded string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function bin2hex($string)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($string, 'string', 1);

        if (self::useNewSodiumAPI()) {
            return (string) sodium_bin2hex($string);
        }
        if (self::use_fallback('bin2hex')) {
            return (string) call_user_func('\\Sodium\\bin2hex', $string);
        }
        return ParagonIE_Sodium_Core_Util::bin2hex($string);
    }

    /**
     * Compare two strings, in constant-time.
     * Compared to memcmp(), compare() is more useful for sorting.
     *
     * @param string $left  The left operand; must be a string
     * @param string $right The right operand; must be a string
     * @return int          < 0 if the left operand is less than the right
     *                      = 0 if both strings are equal
     *                      > 0 if the right operand is less than the left
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function compare($left, $right)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($left, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($right, 'string', 2);

        if (self::useNewSodiumAPI()) {
            return (int) sodium_compare($left, $right);
        }
        if (self::use_fallback('compare')) {
            return (int) call_user_func('\\Sodium\\compare', $left, $right);
        }
        return ParagonIE_Sodium_Core_Util::compare($left, $right);
    }

    /**
     * Is AES-256-GCM even available to use?
     *
     * @return bool
     * @psalm-suppress UndefinedFunction
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_aead_aes256gcm_is_available()
    {
        if (self::useNewSodiumAPI()) {
            return sodium_crypto_aead_aes256gcm_is_available();
        }
        if (self::use_fallback('crypto_aead_aes256gcm_is_available')) {
            return call_user_func('\\Sodium\\crypto_aead_aes256gcm_is_available');
        }
        if (PHP_VERSION_ID < 70100) {
            // OpenSSL doesn't support AEAD before 7.1.0
            return false;
        }
        if (!is_callable('openssl_encrypt') || !is_callable('openssl_decrypt')) {
            // OpenSSL isn't installed
            return false;
        }
        return (bool) in_array('aes-256-gcm', openssl_get_cipher_methods());
    }

    /**
     * Authenticated Encryption with Associated Data: Decryption
     *
     * Algorithm:
     *     AES-256-GCM
     *
     * This mode uses a 64-bit random nonce with a 64-bit counter.
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     *
     * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
     * @param string $assocData  Authenticated Associated Data (unencrypted)
     * @param string $nonce      Number to be used only Once; must be 8 bytes
     * @param string $key        Encryption key
     *
     * @return string|bool       The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_aead_aes256gcm_decrypt(
        $ciphertext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        if (!self::crypto_aead_aes256gcm_is_available()) {
            throw new SodiumException('AES-256-GCM is not available');
        }
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AES256GCM_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_AES256GCM_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AES256GCM_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_AES256GCM_KEYBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_AES256GCM_ABYTES) {
            throw new SodiumException('Message must be at least CRYPTO_AEAD_AES256GCM_ABYTES long');
        }
        if (!is_callable('openssl_decrypt')) {
            throw new SodiumException('The OpenSSL extension is not installed, or openssl_decrypt() is not available');
        }

        /** @var string $ctext */
        $ctext = ParagonIE_Sodium_Core_Util::substr($ciphertext, 0, -self::CRYPTO_AEAD_AES256GCM_ABYTES);
        /** @var string $authTag */
        $authTag = ParagonIE_Sodium_Core_Util::substr($ciphertext, -self::CRYPTO_AEAD_AES256GCM_ABYTES, 16);
        return openssl_decrypt(
            $ctext,
            'aes-256-gcm',
            $key,
            OPENSSL_RAW_DATA,
            $nonce,
            $authTag,
            $assocData
        );
    }

    /**
     * Authenticated Encryption with Associated Data: Encryption
     *
     * Algorithm:
     *     AES-256-GCM
     *
     * @param string $plaintext Message to be encrypted
     * @param string $assocData Authenticated Associated Data (unencrypted)
     * @param string $nonce     Number to be used only Once; must be 8 bytes
     * @param string $key       Encryption key
     *
     * @return string           Ciphertext with a 16-byte GCM message
     *                          authentication code appended
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_aead_aes256gcm_encrypt(
        $plaintext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        if (!self::crypto_aead_aes256gcm_is_available()) {
            throw new SodiumException('AES-256-GCM is not available');
        }
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_AES256GCM_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_AES256GCM_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_AES256GCM_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_AES256GCM_KEYBYTES long');
        }

        if (!is_callable('openssl_encrypt')) {
            throw new SodiumException('The OpenSSL extension is not installed, or openssl_encrypt() is not available');
        }

        $authTag = '';
        $ciphertext = openssl_encrypt(
            $plaintext,
            'aes-256-gcm',
            $key,
            OPENSSL_RAW_DATA,
            $nonce,
            $authTag,
            $assocData
        );
        return $ciphertext . $authTag;
    }

    /**
     * Return a secure random key for use with the AES-256-GCM
     * symmetric AEAD interface.
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_aead_aes256gcm_keygen()
    {
        return random_bytes(self::CRYPTO_AEAD_AES256GCM_KEYBYTES);
    }

    /**
     * Authenticated Encryption with Associated Data: Decryption
     *
     * Algorithm:
     *     ChaCha20-Poly1305
     *
     * This mode uses a 64-bit random nonce with a 64-bit counter.
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     *
     * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
     * @param string $assocData  Authenticated Associated Data (unencrypted)
     * @param string $nonce      Number to be used only Once; must be 8 bytes
     * @param string $key        Encryption key
     *
     * @return string            The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_aead_chacha20poly1305_decrypt(
        $ciphertext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_CHACHA20POLY1305_ABYTES) {
            throw new SodiumException('Message must be at least CRYPTO_AEAD_CHACHA20POLY1305_ABYTES long');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_aead_chacha20poly1305_decrypt(
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (self::use_fallback('crypto_aead_chacha20poly1305_decrypt')) {
            return call_user_func(
                '\\Sodium\\crypto_aead_chacha20poly1305_decrypt',
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_decrypt(
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_decrypt(
            $ciphertext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Authenticated Encryption with Associated Data
     *
     * Algorithm:
     *     ChaCha20-Poly1305
     *
     * This mode uses a 64-bit random nonce with a 64-bit counter.
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     *
     * @param string $plaintext Message to be encrypted
     * @param string $assocData Authenticated Associated Data (unencrypted)
     * @param string $nonce     Number to be used only Once; must be 8 bytes
     * @param string $key       Encryption key
     *
     * @return string           Ciphertext with a 16-byte Poly1305 message
     *                          authentication code appended
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_aead_chacha20poly1305_encrypt(
        $plaintext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_aead_chacha20poly1305_encrypt(
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (self::use_fallback('crypto_aead_chacha20poly1305_encrypt')) {
            return (string) call_user_func(
                '\\Sodium\\crypto_aead_chacha20poly1305_encrypt',
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_encrypt(
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_encrypt(
            $plaintext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Authenticated Encryption with Associated Data: Decryption
     *
     * Algorithm:
     *     ChaCha20-Poly1305
     *
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     * Regular mode uses a 64-bit random nonce with a 64-bit counter.
     *
     * @param string $ciphertext Encrypted message (with Poly1305 MAC appended)
     * @param string $assocData  Authenticated Associated Data (unencrypted)
     * @param string $nonce      Number to be used only Once; must be 12 bytes
     * @param string $key        Encryption key
     *
     * @return string            The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_aead_chacha20poly1305_ietf_decrypt(
        $ciphertext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_CHACHA20POLY1305_ABYTES) {
            throw new SodiumException('Message must be at least CRYPTO_AEAD_CHACHA20POLY1305_ABYTES long');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_aead_chacha20poly1305_ietf_decrypt(
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (self::use_fallback('crypto_aead_chacha20poly1305_ietf_decrypt')) {
            return call_user_func(
                '\\Sodium\\crypto_aead_chacha20poly1305_ietf_decrypt',
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_ietf_decrypt(
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_ietf_decrypt(
            $ciphertext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Return a secure random key for use with the ChaCha20-Poly1305
     * symmetric AEAD interface.
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_aead_chacha20poly1305_keygen()
    {
        return random_bytes(self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES);
    }

    /**
     * Authenticated Encryption with Associated Data
     *
     * Algorithm:
     *     ChaCha20-Poly1305
     *
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     * Regular mode uses a 64-bit random nonce with a 64-bit counter.
     *
     * @param string $plaintext Message to be encrypted
     * @param string $assocData Authenticated Associated Data (unencrypted)
     * @param string $nonce Number to be used only Once; must be 8 bytes
     * @param string $key Encryption key
     *
     * @return string           Ciphertext with a 16-byte Poly1305 message
     *                          authentication code appended
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_aead_chacha20poly1305_ietf_encrypt(
        $plaintext = '',
        $assocData = '',
        $nonce = '',
        $key = ''
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_CHACHA20POLY1305_IETF_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES long');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_aead_chacha20poly1305_ietf_encrypt(
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (self::use_fallback('crypto_aead_chacha20poly1305_ietf_encrypt')) {
            return (string) call_user_func(
                '\\Sodium\\crypto_aead_chacha20poly1305_ietf_encrypt',
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_chacha20poly1305_ietf_encrypt(
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_chacha20poly1305_ietf_encrypt(
            $plaintext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Return a secure random key for use with the ChaCha20-Poly1305
     * symmetric AEAD interface. (IETF version)
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_aead_chacha20poly1305_ietf_keygen()
    {
        return random_bytes(self::CRYPTO_AEAD_CHACHA20POLY1305_IETF_KEYBYTES);
    }

    /**
     * Authenticated Encryption with Associated Data: Decryption
     *
     * Algorithm:
     *     XChaCha20-Poly1305
     *
     * This mode uses a 64-bit random nonce with a 64-bit counter.
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     *
     * @param string $ciphertext   Encrypted message (with Poly1305 MAC appended)
     * @param string $assocData    Authenticated Associated Data (unencrypted)
     * @param string $nonce        Number to be used only Once; must be 8 bytes
     * @param string $key          Encryption key
     * @param bool   $dontFallback Don't fallback to ext/sodium
     *
     * @return string            The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_aead_xchacha20poly1305_ietf_decrypt(
        $ciphertext = '',
        $assocData = '',
        $nonce = '',
        $key = '',
        $dontFallback = false
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES) {
            throw new SodiumException('Message must be at least CRYPTO_AEAD_XCHACHA20POLY1305_IETF_ABYTES long');
        }
        if (self::useNewSodiumAPI() && !$dontFallback) {
            if (is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
                return sodium_crypto_aead_xchacha20poly1305_ietf_decrypt(
                    $ciphertext,
                    $assocData,
                    $nonce,
                    $key
                );
            }
        }

        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_xchacha20poly1305_ietf_decrypt(
                $ciphertext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_xchacha20poly1305_ietf_decrypt(
            $ciphertext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Authenticated Encryption with Associated Data
     *
     * Algorithm:
     *     XChaCha20-Poly1305
     *
     * This mode uses a 64-bit random nonce with a 64-bit counter.
     * IETF mode uses a 96-bit random nonce with a 32-bit counter.
     *
     * @param string $plaintext    Message to be encrypted
     * @param string $assocData    Authenticated Associated Data (unencrypted)
     * @param string $nonce        Number to be used only Once; must be 8 bytes
     * @param string $key          Encryption key
     * @param bool   $dontFallback Don't fallback to ext/sodium
     *
     * @return string           Ciphertext with a 16-byte Poly1305 message
     *                          authentication code appended
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_aead_xchacha20poly1305_ietf_encrypt(
        $plaintext = '',
        $assocData = '',
        $nonce = '',
        $key = '',
        $dontFallback = false
    ) {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($assocData, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES) {
            throw new SodiumException('Nonce must be CRYPTO_AEAD_XCHACHA20POLY1305_NPUBBYTES long');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES) {
            throw new SodiumException('Key must be CRYPTO_AEAD_XCHACHA20POLY1305_KEYBYTES long');
        }
        if (self::useNewSodiumAPI() && !$dontFallback) {
            if (is_callable('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
                return sodium_crypto_aead_xchacha20poly1305_ietf_encrypt(
                    $plaintext,
                    $assocData,
                    $nonce,
                    $key
                );
            }
        }

        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::aead_xchacha20poly1305_ietf_encrypt(
                $plaintext,
                $assocData,
                $nonce,
                $key
            );
        }
        return ParagonIE_Sodium_Crypto::aead_xchacha20poly1305_ietf_encrypt(
            $plaintext,
            $assocData,
            $nonce,
            $key
        );
    }

    /**
     * Return a secure random key for use with the XChaCha20-Poly1305
     * symmetric AEAD interface.
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_aead_xchacha20poly1305_ietf_keygen()
    {
        return random_bytes(self::CRYPTO_AEAD_XCHACHA20POLY1305_IETF_KEYBYTES);
    }

    /**
     * Authenticate a message. Uses symmetric-key cryptography.
     *
     * Algorithm:
     *     HMAC-SHA512-256. Which is HMAC-SHA-512 truncated to 256 bits.
     *     Not to be confused with HMAC-SHA-512/256 which would use the
     *     SHA-512/256 hash function (uses different initial parameters
     *     but still truncates to 256 bits to sidestep length-extension
     *     attacks).
     *
     * @param string $message Message to be authenticated
     * @param string $key Symmetric authentication key
     * @return string         Message authentication code
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_auth($message, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AUTH_KEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_AUTH_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_auth($message, $key);
        }
        if (self::use_fallback('crypto_auth')) {
            return (string) call_user_func('\\Sodium\\crypto_auth', $message, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::auth($message, $key);
        }
        return ParagonIE_Sodium_Crypto::auth($message, $key);
    }

    /**
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_auth_keygen()
    {
        return random_bytes(self::CRYPTO_AUTH_KEYBYTES);
    }

    /**
     * Verify the MAC of a message previously authenticated with crypto_auth.
     *
     * @param string $mac Message authentication code
     * @param string $message Message whose authenticity you are attempting to
     *                        verify (with a given MAC and key)
     * @param string $key Symmetric authentication key
     * @return bool           TRUE if authenticated, FALSE otherwise
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_auth_verify($mac, $message, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($mac, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($mac) !== self::CRYPTO_AUTH_BYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_AUTH_BYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_AUTH_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_AUTH_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (bool) sodium_crypto_auth_verify($mac, $message, $key);
        }
        if (self::use_fallback('crypto_auth_verify')) {
            return (bool) call_user_func('\\Sodium\\crypto_auth_verify', $mac, $message, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::auth_verify($mac, $message, $key);
        }
        return ParagonIE_Sodium_Crypto::auth_verify($mac, $message, $key);
    }

    /**
     * Authenticated asymmetric-key encryption. Both the sender and recipient
     * may decrypt messages.
     *
     * Algorithm: X25519-XSalsa20-Poly1305.
     *     X25519: Elliptic-Curve Diffie Hellman over Curve25519.
     *     XSalsa20: Extended-nonce variant of salsa20.
     *     Poyl1305: Polynomial MAC for one-time message authentication.
     *
     * @param string $plaintext The message to be encrypted
     * @param string $nonce A Number to only be used Once; must be 24 bytes
     * @param string $keypair Your secret key and your recipient's public key
     * @return string           Ciphertext with 16-byte Poly1305 MAC
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box($plaintext, $nonce, $keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_BOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box($plaintext, $nonce, $keypair);
        }
        if (self::use_fallback('crypto_box')) {
            return (string) call_user_func('\\Sodium\\crypto_box', $plaintext, $nonce, $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box($plaintext, $nonce, $keypair);
        }
        return ParagonIE_Sodium_Crypto::box($plaintext, $nonce, $keypair);
    }

    /**
     * Anonymous public-key encryption. Only the recipient may decrypt messages.
     *
     * Algorithm: X25519-XSalsa20-Poly1305, as with crypto_box.
     *     The sender's X25519 keypair is ephemeral.
     *     Nonce is generated from the BLAKE2b hash of both public keys.
     *
     * This provides ciphertext integrity.
     *
     * @param string $plaintext Message to be sealed
     * @param string $publicKey Your recipient's public key
     * @return string           Sealed message that only your recipient can
     *                          decrypt
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_seal($plaintext, $publicKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_seal($plaintext, $publicKey);
        }
        if (self::use_fallback('crypto_box_seal')) {
            return (string) call_user_func('\\Sodium\\crypto_box_seal', $plaintext, $publicKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_seal($plaintext, $publicKey);
        }
        return ParagonIE_Sodium_Crypto::box_seal($plaintext, $publicKey);
    }

    /**
     * Opens a message encrypted with crypto_box_seal(). Requires
     * the recipient's keypair (sk || pk) to decrypt successfully.
     *
     * This validates ciphertext integrity.
     *
     * @param string $ciphertext Sealed message to be opened
     * @param string $keypair    Your crypto_box keypair
     * @return string            The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_box_seal_open($ciphertext, $keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_box_seal_open($ciphertext, $keypair);
        }
        if (self::use_fallback('crypto_box_seal_open')) {
            return call_user_func('\\Sodium\\crypto_box_seal_open', $ciphertext, $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_seal_open($ciphertext, $keypair);
        }
        return ParagonIE_Sodium_Crypto::box_seal_open($ciphertext, $keypair);
    }

    /**
     * Generate a new random X25519 keypair.
     *
     * @return string A 64-byte string; the first 32 are your secret key, while
     *                the last 32 are your public key. crypto_box_secretkey()
     *                and crypto_box_publickey() exist to separate them so you
     *                don't accidentally get them mixed up!
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_keypair()
    {
        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_keypair();
        }
        if (self::use_fallback('crypto_box_keypair')) {
            return (string) call_user_func('\\Sodium\\crypto_box_keypair');
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_keypair();
        }
        return ParagonIE_Sodium_Crypto::box_keypair();
    }

    /**
     * Combine two keys into a keypair for use in library methods that expect
     * a keypair. This doesn't necessarily have to be the same person's keys.
     *
     * @param string $secretKey Secret key
     * @param string $publicKey Public key
     * @return string    Keypair
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_keypair_from_secretkey_and_publickey($secretKey, $publicKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
        }
        if (self::use_fallback('crypto_box_keypair_from_secretkey_and_publickey')) {
            return (string) call_user_func('\\Sodium\\crypto_box_keypair_from_secretkey_and_publickey', $secretKey, $publicKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
        }
        return ParagonIE_Sodium_Crypto::box_keypair_from_secretkey_and_publickey($secretKey, $publicKey);
    }

    /**
     * Decrypt a message previously encrypted with crypto_box().
     *
     * @param string $ciphertext Encrypted message
     * @param string $nonce      Number to only be used Once; must be 24 bytes
     * @param string $keypair    Your secret key and the sender's public key
     * @return string            The original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_box_open($ciphertext, $nonce, $keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($ciphertext) < self::CRYPTO_BOX_MACBYTES) {
            throw new SodiumException('Argument 1 must be at least CRYPTO_BOX_MACBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_BOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_box_open($ciphertext, $nonce, $keypair);
        }
        if (self::use_fallback('crypto_box_open')) {
            return call_user_func('\\Sodium\\crypto_box_open', $ciphertext, $nonce, $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_open($ciphertext, $nonce, $keypair);
        }
        return ParagonIE_Sodium_Crypto::box_open($ciphertext, $nonce, $keypair);
    }

    /**
     * Extract the public key from a crypto_box keypair.
     *
     * @param string $keypair Keypair containing secret and public key
     * @return string         Your crypto_box public key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_publickey($keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_publickey($keypair);
        }
        if (self::use_fallback('crypto_box_publickey')) {
            return (string) call_user_func('\\Sodium\\crypto_box_publickey', $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_publickey($keypair);
        }
        return ParagonIE_Sodium_Crypto::box_publickey($keypair);
    }

    /**
     * Calculate the X25519 public key from a given X25519 secret key.
     *
     * @param string $secretKey Any X25519 secret key
     * @return string           The corresponding X25519 public key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_publickey_from_secretkey($secretKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_publickey_from_secretkey($secretKey);
        }
        if (self::use_fallback('crypto_box_publickey_from_secretkey')) {
            return (string) call_user_func('\\Sodium\\crypto_box_publickey_from_secretkey', $secretKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_publickey_from_secretkey($secretKey);
        }
        return ParagonIE_Sodium_Crypto::box_publickey_from_secretkey($secretKey);
    }

    /**
     * Extract the secret key from a crypto_box keypair.
     *
     * @param string $keypair
     * @return string         Your crypto_box secret key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_box_secretkey($keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_secretkey($keypair);
        }
        if (self::use_fallback('crypto_box_secretkey')) {
            return (string) call_user_func('\\Sodium\\crypto_box_secretkey', $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_secretkey($keypair);
        }
        return ParagonIE_Sodium_Crypto::box_secretkey($keypair);
    }

    /**
     * Generate an X25519 keypair from a seed.
     *
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress UndefinedFunction
     */
    public static function crypto_box_seed_keypair($seed)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1);

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_box_seed_keypair($seed);
        }
        if (self::use_fallback('crypto_box_seed_keypair')) {
            return (string) call_user_func('\\Sodium\\crypto_box_seed_keypair', $seed);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::box_seed_keypair($seed);
        }
        return ParagonIE_Sodium_Crypto::box_seed_keypair($seed);
    }

    /**
     * Calculates a BLAKE2b hash, with an optional key.
     *
     * @param string      $message The message to be hashed
     * @param string|null $key     If specified, must be a string between 16
     *                             and 64 bytes long
     * @param int         $length  Output length in bytes; must be between 16
     *                             and 64 (default = 32)
     * @return string              Raw binary
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_generichash($message, $key = '', $length = self::CRYPTO_GENERICHASH_BYTES)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        if (is_null($key)) {
            $key = '';
        }
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 3);

        /* Input validation: */
        if (!empty($key)) {
            if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
                throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.');
            }
            if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
                throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.');
            }
        }

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_generichash($message, $key, $length);
        }
        if (self::use_fallback('crypto_generichash')) {
            return (string) call_user_func('\\Sodium\\crypto_generichash', $message, $key, $length);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::generichash($message, $key, $length);
        }
        return ParagonIE_Sodium_Crypto::generichash($message, $key, $length);
    }

    /**
     * Get the final BLAKE2b hash output for a given context.
     *
     * @param string &$ctx BLAKE2 hashing context. Generated by crypto_generichash_init().
     * @param int $length  Hash output size.
     * @return string      Final BLAKE2b hash.
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_generichash_final(&$ctx, $length = self::CRYPTO_GENERICHASH_BYTES)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ctx, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2);

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_generichash_final($ctx, $length);
        }
        if (self::use_fallback('crypto_generichash_final')) {
            $func = '\\Sodium\\crypto_generichash_final';
            return (string) $func($ctx, $length);
        }
        if (PHP_INT_SIZE === 4) {
            $result = ParagonIE_Sodium_Crypto32::generichash_final($ctx, $length);
        } else {
            $result = ParagonIE_Sodium_Crypto::generichash_final($ctx, $length);
        }
        try {
            self::memzero($ctx);
        } catch (SodiumException $ex) {
            unset($ctx);
        }
        return $result;
    }

    /**
     * Initialize a BLAKE2b hashing context, for use in a streaming interface.
     *
     * @param string|null $key If specified must be a string between 16 and 64 bytes
     * @param int $length      The size of the desired hash output
     * @return string          A BLAKE2 hashing context, encoded as a string
     *                         (To be 100% compatible with ext/libsodium)
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_generichash_init($key = '', $length = self::CRYPTO_GENERICHASH_BYTES)
    {
        /* Type checks: */
        if (is_null($key)) {
            $key = '';
        }
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($length, 'int', 2);

        /* Input validation: */
        if (!empty($key)) {
            if (ParagonIE_Sodium_Core_Util::strlen($key) < self::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
                throw new SodiumException('Unsupported key size. Must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes long.');
            }
            if (ParagonIE_Sodium_Core_Util::strlen($key) > self::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
                throw new SodiumException('Unsupported key size. Must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes long.');
            }
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_generichash_init($key, $length);
        }
        if (self::use_fallback('crypto_generichash_init')) {
            return (string) call_user_func('\\Sodium\\crypto_generichash_init', $key, $length);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::generichash_init($key, $length);
        }
        return ParagonIE_Sodium_Crypto::generichash_init($key, $length);
    }

    /**
     * Update a BLAKE2b hashing context with additional data.
     *
     * @param string &$ctx    BLAKE2 hashing context. Generated by crypto_generichash_init().
     *                        $ctx is passed by reference and gets updated in-place.
     * @param string $message The message to append to the existing hash state.
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_generichash_update(&$ctx, $message)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ctx, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);

        if (self::useNewSodiumAPI()) {
            sodium_crypto_generichash_update($ctx, $message);
            return;
        }
        if (self::use_fallback('crypto_generichash_update')) {
            $func = '\\Sodium\\crypto_generichash_update';
            $func($ctx, $message);
            return;
        }
        if (PHP_INT_SIZE === 4) {
            $ctx = ParagonIE_Sodium_Crypto32::generichash_update($ctx, $message);
        } else {
            $ctx = ParagonIE_Sodium_Crypto::generichash_update($ctx, $message);
        }
    }

    /**
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_generichash_keygen()
    {
        return random_bytes(self::CRYPTO_GENERICHASH_KEYBYTES);
    }

    /**
     * Perform a key exchange, between a designated client and a server.
     *
     * Typically, you would designate one machine to be the client and the
     * other to be the server. The first two keys are what you'd expect for
     * scalarmult() below, but the latter two public keys don't swap places.
     *
     * | ALICE                          | BOB                                 |
     * | Client                         | Server                              |
     * |--------------------------------|-------------------------------------|
     * | shared = crypto_kx(            | shared = crypto_kx(                 |
     * |     alice_sk,                  |     bob_sk,                         | <- contextual
     * |     bob_pk,                    |     alice_pk,                       | <- contextual
     * |     alice_pk,                  |     alice_pk,                       | <----- static
     * |     bob_pk                     |     bob_pk                          | <----- static
     * | )                              | )                                   |
     *
     * They are used along with the scalarmult product to generate a 256-bit
     * BLAKE2b hash unique to the client and server keys.
     *
     * @param string $my_secret
     * @param string $their_public
     * @param string $client_public
     * @param string $server_public
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_kx($my_secret, $their_public, $client_public, $server_public)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($my_secret, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($their_public, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($client_public, 'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($server_public, 'string', 4);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($my_secret) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($their_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($client_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($server_public) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 4 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            if (is_callable('sodium_crypto_kx')) {
                return (string) sodium_crypto_kx(
                    $my_secret,
                    $their_public,
                    $client_public,
                    $server_public
                );
            }
        }
        if (self::use_fallback('crypto_kx')) {
            return (string) call_user_func(
                '\\Sodium\\crypto_kx',
                $my_secret,
                $their_public,
                $client_public,
                $server_public
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::keyExchange(
                $my_secret,
                $their_public,
                $client_public,
                $server_public
            );
        }
        return ParagonIE_Sodium_Crypto::keyExchange(
            $my_secret,
            $their_public,
            $client_public,
            $server_public
        );
    }

    /**
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @param int|null $alg
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit, $alg = null)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($outlen, 'int', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($salt,  'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 4);
        ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 5);

        if (self::useNewSodiumAPI()) {
            if (!is_null($alg)) {
                ParagonIE_Sodium_Core_Util::declareScalarType($alg, 'int', 6);
                return sodium_crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit, $alg);
            }
            return sodium_crypto_pwhash($outlen, $passwd, $salt, $opslimit, $memlimit);
        }
        if (self::use_fallback('crypto_pwhash')) {
            return (string) call_user_func('\\Sodium\\crypto_pwhash', $outlen, $passwd, $salt, $opslimit, $memlimit);
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
        );
    }

    /**
     * !Exclusive to sodium_compat!
     *
     * This returns TRUE if the native crypto_pwhash API is available by libsodium.
     * This returns FALSE if only sodium_compat is available.
     *
     * @return bool
     */
    public static function crypto_pwhash_is_available()
    {
        if (self::useNewSodiumAPI()) {
            return true;
        }
        if (self::use_fallback('crypto_pwhash')) {
            return true;
        }
        return false;
    }

    /**
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_pwhash_str($passwd, $opslimit, $memlimit)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3);

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_pwhash_str($passwd, $opslimit, $memlimit);
        }
        if (self::use_fallback('crypto_pwhash_str')) {
            return (string) call_user_func('\\Sodium\\crypto_pwhash_str', $passwd, $opslimit, $memlimit);
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
        );
    }

    /**
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_pwhash_str_verify($passwd, $hash)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 2);

        if (self::useNewSodiumAPI()) {
            return (bool) sodium_crypto_pwhash_str_verify($passwd, $hash);
        }
        if (self::use_fallback('crypto_pwhash_str_verify')) {
            return (bool) call_user_func('\\Sodium\\crypto_pwhash_str_verify', $passwd, $hash);
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Argon2i with acceptable performance in pure-PHP'
        );
    }

    /**
     * @param int $outlen
     * @param string $passwd
     * @param string $salt
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_pwhash_scryptsalsa208sha256($outlen, $passwd, $salt, $opslimit, $memlimit)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($outlen, 'int', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($salt,  'string', 3);
        ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 4);
        ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 5);

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_pwhash_scryptsalsa208sha256(
                (int) $outlen,
                (string) $passwd,
                (string) $salt,
                (int) $opslimit,
                (int) $memlimit
            );
        }
        if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256')) {
            return (string) call_user_func(
                '\\Sodium\\crypto_pwhash_scryptsalsa208sha256',
                (int) $outlen,
                (string) $passwd,
                (string) $salt,
                (int) $opslimit,
                (int) $memlimit
            );
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
        );
    }

    /**
     * !Exclusive to sodium_compat!
     *
     * This returns TRUE if the native crypto_pwhash API is available by libsodium.
     * This returns FALSE if only sodium_compat is available.
     *
     * @return bool
     */
    public static function crypto_pwhash_scryptsalsa208sha256_is_available()
    {
        if (self::useNewSodiumAPI()) {
            return true;
        }
        if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256')) {
            return true;
        }
        return false;
    }

    /**
     * @param string $passwd
     * @param int $opslimit
     * @param int $memlimit
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_pwhash_scryptsalsa208sha256_str($passwd, $opslimit, $memlimit)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($opslimit, 'int', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($memlimit, 'int', 3);

        if (self::useNewSodiumAPI()) {
            return (string) sodium_crypto_pwhash_scryptsalsa208sha256_str(
                (string) $passwd,
                (int) $opslimit,
                (int) $memlimit
            );
        }
        if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256_str')) {
            return (string) call_user_func(
                '\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str',
                (string) $passwd,
                (int) $opslimit,
                (int) $memlimit
            );
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
        );
    }

    /**
     * @param string $passwd
     * @param string $hash
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_pwhash_scryptsalsa208sha256_str_verify($passwd, $hash)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($passwd, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($hash, 'string', 2);

        if (self::useNewSodiumAPI()) {
            return (bool) sodium_crypto_pwhash_scryptsalsa208sha256_str_verify(
                (string) $passwd,
                (string) $hash
            );
        }
        if (self::use_fallback('crypto_pwhash_scryptsalsa208sha256_str_verify')) {
            return (bool) call_user_func(
                '\\Sodium\\crypto_pwhash_scryptsalsa208sha256_str_verify',
                (string) $passwd,
                (string) $hash
            );
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented, as it is not possible to implement Scrypt with acceptable performance in pure-PHP'
        );
    }

    /**
     * Calculate the shared secret between your secret key and your
     * recipient's public key.
     *
     * Algorithm: X25519 (ECDH over Curve25519)
     *
     * @param string $secretKey
     * @param string $publicKey
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_scalarmult($secretKey, $publicKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_BOX_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_scalarmult($secretKey, $publicKey);
        }
        if (self::use_fallback('crypto_scalarmult')) {
            return (string) call_user_func('\\Sodium\\crypto_scalarmult', $secretKey, $publicKey);
        }

        /* Output validation: Forbid all-zero keys */
        if (ParagonIE_Sodium_Core_Util::hashEquals($secretKey, str_repeat("\0", self::CRYPTO_BOX_SECRETKEYBYTES))) {
            throw new SodiumException('Zero secret key is not allowed');
        }
        if (ParagonIE_Sodium_Core_Util::hashEquals($publicKey, str_repeat("\0", self::CRYPTO_BOX_PUBLICKEYBYTES))) {
            throw new SodiumException('Zero public key is not allowed');
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::scalarmult($secretKey, $publicKey);
        }
        return ParagonIE_Sodium_Crypto::scalarmult($secretKey, $publicKey);
    }

    /**
     * Calculate an X25519 public key from an X25519 secret key.
     *
     * @param string $secretKey
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress TooFewArguments
     * @psalm-suppress MixedArgument
     */
    public static function crypto_scalarmult_base($secretKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_BOX_SECRETKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_scalarmult_base($secretKey);
        }
        if (self::use_fallback('crypto_scalarmult_base')) {
            return (string) call_user_func('\\Sodium\\crypto_scalarmult_base', $secretKey);
        }
        if (ParagonIE_Sodium_Core_Util::hashEquals($secretKey, str_repeat("\0", self::CRYPTO_BOX_SECRETKEYBYTES))) {
            throw new SodiumException('Zero secret key is not allowed');
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::scalarmult_base($secretKey);
        }
        return ParagonIE_Sodium_Crypto::scalarmult_base($secretKey);
    }

    /**
     * Authenticated symmetric-key encryption.
     *
     * Algorithm: XSalsa20-Poly1305
     *
     * @param string $plaintext The message you're encrypting
     * @param string $nonce A Number to be used Once; must be 24 bytes
     * @param string $key Symmetric encryption key
     * @return string           Ciphertext with Poly1305 MAC
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_secretbox($plaintext, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_secretbox($plaintext, $nonce, $key);
        }
        if (self::use_fallback('crypto_secretbox')) {
            return (string) call_user_func('\\Sodium\\crypto_secretbox', $plaintext, $nonce, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::secretbox($plaintext, $nonce, $key);
        }
        return ParagonIE_Sodium_Crypto::secretbox($plaintext, $nonce, $key);
    }

    /**
     * Decrypts a message previously encrypted with crypto_secretbox().
     *
     * @param string $ciphertext Ciphertext with Poly1305 MAC
     * @param string $nonce      A Number to be used Once; must be 24 bytes
     * @param string $key        Symmetric encryption key
     * @return string            Original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_secretbox_open($ciphertext, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_secretbox_open($ciphertext, $nonce, $key);
        }
        if (self::use_fallback('crypto_secretbox_open')) {
            return call_user_func('\\Sodium\\crypto_secretbox_open', $ciphertext, $nonce, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::secretbox_open($ciphertext, $nonce, $key);
        }
        return ParagonIE_Sodium_Crypto::secretbox_open($ciphertext, $nonce, $key);
    }

    /**
     * Return a secure random key for use with crypto_secretbox
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_secretbox_keygen()
    {
        return random_bytes(self::CRYPTO_SECRETBOX_KEYBYTES);
    }

    /**
     * Authenticated symmetric-key encryption.
     *
     * Algorithm: XChaCha20-Poly1305
     *
     * @param string $plaintext The message you're encrypting
     * @param string $nonce     A Number to be used Once; must be 24 bytes
     * @param string $key       Symmetric encryption key
     * @return string           Ciphertext with Poly1305 MAC
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_secretbox_xchacha20poly1305($plaintext, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($plaintext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::secretbox_xchacha20poly1305($plaintext, $nonce, $key);
        }
        return ParagonIE_Sodium_Crypto::secretbox_xchacha20poly1305($plaintext, $nonce, $key);
    }
    /**
     * Decrypts a message previously encrypted with crypto_secretbox_xchacha20poly1305().
     *
     * @param string $ciphertext Ciphertext with Poly1305 MAC
     * @param string $nonce      A Number to be used Once; must be 24 bytes
     * @param string $key        Symmetric encryption key
     * @return string            Original plaintext message
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($ciphertext, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
        }

        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key);
        }
        return ParagonIE_Sodium_Crypto::secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key);
    }

    /**
     * Calculates a SipHash-2-4 hash of a message for a given key.
     *
     * @param string $message Input message
     * @param string $key SipHash-2-4 key
     * @return string         Hash
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_shorthash($message, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_SHORTHASH_KEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SHORTHASH_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_shorthash($message, $key);
        }
        if (self::use_fallback('crypto_shorthash')) {
            return (string) call_user_func('\\Sodium\\crypto_shorthash', $message, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_SipHash::sipHash24($message, $key);
        }
        return ParagonIE_Sodium_Core_SipHash::sipHash24($message, $key);
    }

    /**
     * Return a secure random key for use with crypto_shorthash
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_shorthash_keygen()
    {
        return random_bytes(self::CRYPTO_SHORTHASH_KEYBYTES);
    }

    /**
     * Returns a signed message. You probably want crypto_sign_detached()
     * instead, which only returns the signature.
     *
     * Algorithm: Ed25519 (EdDSA over Curve25519)
     *
     * @param string $message Message to be signed.
     * @param string $secretKey Secret signing key.
     * @return string           Signed message (signature is prefixed).
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_sign($message, $secretKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign($message, $secretKey);
        }
        if (self::use_fallback('crypto_sign')) {
            return (string) call_user_func('\\Sodium\\crypto_sign', $message, $secretKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::sign($message, $secretKey);
        }
        return ParagonIE_Sodium_Crypto::sign($message, $secretKey);
    }

    /**
     * Validates a signed message then returns the message.
     *
     * @param string $signedMessage A signed message
     * @param string $publicKey A public key
     * @return string               The original message (if the signature is
     *                              valid for this public key)
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress MixedReturnStatement
     */
    public static function crypto_sign_open($signedMessage, $publicKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($signedMessage, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($signedMessage) < self::CRYPTO_SIGN_BYTES) {
            throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_BYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SIGN_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            /**
             * @psalm-suppress InvalidReturnStatement
             * @psalm-suppress FalsableReturnStatement
             */
            return sodium_crypto_sign_open($signedMessage, $publicKey);
        }
        if (self::use_fallback('crypto_sign_open')) {
            return call_user_func('\\Sodium\\crypto_sign_open', $signedMessage, $publicKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::sign_open($signedMessage, $publicKey);
        }
        return ParagonIE_Sodium_Crypto::sign_open($signedMessage, $publicKey);
    }

    /**
     * Generate a new random Ed25519 keypair.
     *
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_sign_keypair()
    {
        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_keypair();
        }
        if (self::use_fallback('crypto_sign_keypair')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_keypair');
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_Ed25519::keypair();
        }
        return ParagonIE_Sodium_Core_Ed25519::keypair();
    }

    /**
     * Generate an Ed25519 keypair from a seed.
     *
     * @param string $seed Input seed
     * @return string      Keypair
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_seed_keypair($seed)
    {
        ParagonIE_Sodium_Core_Util::declareScalarType($seed, 'string', 1);

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_seed_keypair($seed);
        }
        if (self::use_fallback('crypto_sign_keypair')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_seed_keypair', $seed);
        }
        $publicKey = '';
        $secretKey = '';
        if (PHP_INT_SIZE === 4) {
            ParagonIE_Sodium_Core32_Ed25519::seed_keypair($publicKey, $secretKey, $seed);
        } else {
            ParagonIE_Sodium_Core_Ed25519::seed_keypair($publicKey, $secretKey, $seed);
        }
        return $secretKey . $publicKey;
    }

    /**
     * Extract an Ed25519 public key from an Ed25519 keypair.
     *
     * @param string $keypair Keypair
     * @return string         Public key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_publickey($keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_SIGN_KEYPAIRBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_SIGN_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_publickey($keypair);
        }
        if (self::use_fallback('crypto_sign_publickey')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_publickey', $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_Ed25519::publickey($keypair);
        }
        return ParagonIE_Sodium_Core_Ed25519::publickey($keypair);
    }

    /**
     * Calculate an Ed25519 public key from an Ed25519 secret key.
     *
     * @param string $secretKey Your Ed25519 secret key
     * @return string           The corresponding Ed25519 public key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_publickey_from_secretkey($secretKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_publickey_from_secretkey($secretKey);
        }
        if (self::use_fallback('crypto_sign_publickey_from_secretkey')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_publickey_from_secretkey', $secretKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_Ed25519::publickey_from_secretkey($secretKey);
        }
        return ParagonIE_Sodium_Core_Ed25519::publickey_from_secretkey($secretKey);
    }

    /**
     * Extract an Ed25519 secret key from an Ed25519 keypair.
     *
     * @param string $keypair Keypair
     * @return string         Secret key
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_secretkey($keypair)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($keypair, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== self::CRYPTO_SIGN_KEYPAIRBYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_SIGN_KEYPAIRBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_secretkey($keypair);
        }
        if (self::use_fallback('crypto_sign_secretkey')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_secretkey', $keypair);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_Ed25519::secretkey($keypair);
        }
        return ParagonIE_Sodium_Core_Ed25519::secretkey($keypair);
    }

    /**
     * Calculate the Ed25519 signature of a message and return ONLY the signature.
     *
     * Algorithm: Ed25519 (EdDSA over Curve25519)
     *
     * @param string $message Message to be signed
     * @param string $secretKey Secret signing key
     * @return string           Digital signature
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_detached($message, $secretKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($secretKey, 'string', 2);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($secretKey) !== self::CRYPTO_SIGN_SECRETKEYBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_detached($message, $secretKey);
        }
        if (self::use_fallback('crypto_sign_detached')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_detached', $message, $secretKey);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::sign_detached($message, $secretKey);
        }
        return ParagonIE_Sodium_Crypto::sign_detached($message, $secretKey);
    }

    /**
     * Verify the Ed25519 signature of a message.
     *
     * @param string $signature Digital sginature
     * @param string $message Message to be verified
     * @param string $publicKey Public key
     * @return bool             TRUE if this signature is good for this public key;
     *                          FALSE otherwise
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_verify_detached($signature, $message, $publicKey)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($signature, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($publicKey, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($signature) !== self::CRYPTO_SIGN_BYTES) {
            throw new SodiumException('Argument 1 must be CRYPTO_SIGN_BYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($publicKey) !== self::CRYPTO_SIGN_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_sign_verify_detached($signature, $message, $publicKey);
        }
        if (self::use_fallback('crypto_sign_verify_detached')) {
            return (bool) call_user_func(
                '\\Sodium\\crypto_sign_verify_detached',
                $signature,
                $message,
                $publicKey
            );
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Crypto32::sign_verify_detached($signature, $message, $publicKey);
        }
        return ParagonIE_Sodium_Crypto::sign_verify_detached($signature, $message, $publicKey);
    }

    /**
     * Convert an Ed25519 public key to a Curve25519 public key
     *
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_ed25519_pk_to_curve25519($pk)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($pk, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($pk) < self::CRYPTO_SIGN_PUBLICKEYBYTES) {
            throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_PUBLICKEYBYTES long.');
        }
        if (self::useNewSodiumAPI()) {
            if (is_callable('crypto_sign_ed25519_pk_to_curve25519')) {
                return (string) sodium_crypto_sign_ed25519_pk_to_curve25519($pk);
            }
        }
        if (self::use_fallback('crypto_sign_ed25519_pk_to_curve25519')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_ed25519_pk_to_curve25519', $pk);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_Ed25519::pk_to_curve25519($pk);
        }
        return ParagonIE_Sodium_Core_Ed25519::pk_to_curve25519($pk);
    }

    /**
     * Convert an Ed25519 secret key to a Curve25519 secret key
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_sign_ed25519_sk_to_curve25519($sk)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($sk, 'string', 1);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($sk) < self::CRYPTO_SIGN_SEEDBYTES) {
            throw new SodiumException('Argument 1 must be at least CRYPTO_SIGN_SEEDBYTES long.');
        }
        if (self::useNewSodiumAPI()) {
            if (is_callable('crypto_sign_ed25519_sk_to_curve25519')) {
                return sodium_crypto_sign_ed25519_sk_to_curve25519($sk);
            }
        }
        if (self::use_fallback('crypto_sign_ed25519_sk_to_curve25519')) {
            return (string) call_user_func('\\Sodium\\crypto_sign_ed25519_sk_to_curve25519', $sk);
        }

        $h = hash('sha512', ParagonIE_Sodium_Core_Util::substr($sk, 0, 32), true);
        $h[0] = ParagonIE_Sodium_Core_Util::intToChr(
            ParagonIE_Sodium_Core_Util::chrToInt($h[0]) & 248
        );
        $h[31] = ParagonIE_Sodium_Core_Util::intToChr(
            (ParagonIE_Sodium_Core_Util::chrToInt($h[31]) & 127) | 64
        );
        return ParagonIE_Sodium_Core_Util::substr($h, 0, 32);
    }

    /**
     * Expand a key and nonce into a keystream of pseudorandom bytes.
     *
     * @param int $len Number of bytes desired
     * @param string $nonce Number to be used Once; must be 24 bytes
     * @param string $key XSalsa20 key
     * @return string       Pseudorandom stream that can be XORed with messages
     *                      to provide encryption (but not authentication; see
     *                      Poly1305 or crypto_auth() for that, which is not
     *                      optional for security)
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_stream($len, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($len, 'int', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_STREAM_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_stream($len, $nonce, $key);
        }
        if (self::use_fallback('crypto_stream')) {
            return (string) call_user_func('\\Sodium\\crypto_stream', $len, $nonce, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_XSalsa20::xsalsa20($len, $nonce, $key);
        }
        return ParagonIE_Sodium_Core_XSalsa20::xsalsa20($len, $nonce, $key);
    }

    /**
     * DANGER! UNAUTHENTICATED ENCRYPTION!
     *
     * Unless you are following expert advice, do not used this feature.
     *
     * Algorithm: XSalsa20
     *
     * This DOES NOT provide ciphertext integrity.
     *
     * @param string $message Plaintext message
     * @param string $nonce Number to be used Once; must be 24 bytes
     * @param string $key Encryption key
     * @return string         Encrypted text which is vulnerable to chosen-
     *                        ciphertext attacks unless you implement some
     *                        other mitigation to the ciphertext (i.e.
     *                        Encrypt then MAC)
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function crypto_stream_xor($message, $nonce, $key)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($message, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($nonce, 'string', 2);
        ParagonIE_Sodium_Core_Util::declareScalarType($key, 'string', 3);

        /* Input validation: */
        if (ParagonIE_Sodium_Core_Util::strlen($nonce) !== self::CRYPTO_STREAM_NONCEBYTES) {
            throw new SodiumException('Argument 2 must be CRYPTO_SECRETBOX_NONCEBYTES long.');
        }
        if (ParagonIE_Sodium_Core_Util::strlen($key) !== self::CRYPTO_STREAM_KEYBYTES) {
            throw new SodiumException('Argument 3 must be CRYPTO_SECRETBOX_KEYBYTES long.');
        }

        if (self::useNewSodiumAPI()) {
            return sodium_crypto_stream_xor($message, $nonce, $key);
        }
        if (self::use_fallback('crypto_stream_xor')) {
            return (string) call_user_func('\\Sodium\\crypto_stream_xor', $message, $nonce, $key);
        }
        if (PHP_INT_SIZE === 4) {
            return ParagonIE_Sodium_Core32_XSalsa20::xsalsa20_xor($message, $nonce, $key);
        }
        return ParagonIE_Sodium_Core_XSalsa20::xsalsa20_xor($message, $nonce, $key);
    }

    /**
     * Return a secure random key for use with crypto_stream
     *
     * @return string
     * @throws Exception
     * @throws Error
     */
    public static function crypto_stream_keygen()
    {
        return random_bytes(self::CRYPTO_STREAM_KEYBYTES);
    }

    /**
     * Cache-timing-safe implementation of hex2bin().
     *
     * @param string $string Hexadecimal string
     * @return string        Raw binary string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress TooFewArguments
     * @psalm-suppress MixedArgument
     */
    public static function hex2bin($string)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($string, 'string', 1);

        if (self::useNewSodiumAPI()) {
            if (is_callable('sodium_hex2bin')) {
                return (string) sodium_hex2bin($string);
            }
        }
        if (self::use_fallback('hex2bin')) {
            return (string) call_user_func('\\Sodium\\hex2bin', $string);
        }
        return ParagonIE_Sodium_Core_Util::hex2bin($string);
    }

    /**
     * Increase a string (little endian)
     *
     * @param string $var
     *
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function increment(&$var)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($var, 'string', 1);

        if (self::useNewSodiumAPI()) {
            sodium_increment($var);
            return;
        }
        if (self::use_fallback('increment')) {
            $func = '\\Sodium\\increment';
            $func($var);
            return;
        }

        $len = ParagonIE_Sodium_Core_Util::strlen($var);
        $c = 1;
        $copy = '';
        for ($i = 0; $i < $len; ++$i) {
            $c += ParagonIE_Sodium_Core_Util::chrToInt(
                ParagonIE_Sodium_Core_Util::substr($var, $i, 1)
            );
            $copy .= ParagonIE_Sodium_Core_Util::intToChr($c);
            $c >>= 8;
        }
        $var = $copy;
    }

    /**
     * The equivalent to the libsodium minor version we aim to be compatible
     * with (sans pwhash and memzero).
     *
     * @return int
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress UndefinedFunction
     */
    public static function library_version_major()
    {
        if (self::useNewSodiumAPI()) {
            return sodium_library_version_major();
        }
        if (self::use_fallback('library_version_major')) {
            return (int) call_user_func('\\Sodium\\library_version_major');
        }
        return self::LIBRARY_VERSION_MAJOR;
    }

    /**
     * The equivalent to the libsodium minor version we aim to be compatible
     * with (sans pwhash and memzero).
     *
     * @return int
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress UndefinedFunction
     */
    public static function library_version_minor()
    {
        if (self::useNewSodiumAPI()) {
            return sodium_library_version_minor();
        }
        if (self::use_fallback('library_version_minor')) {
            return (int) call_user_func('\\Sodium\\library_version_minor');
        }
        return self::LIBRARY_VERSION_MINOR;
    }

    /**
     * Compare two strings.
     *
     * @param string $left
     * @param string $right
     * @return int
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     */
    public static function memcmp($left, $right)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($left, 'string', 1);
        ParagonIE_Sodium_Core_Util::declareScalarType($right, 'string', 2);

        if (self::use_fallback('memcmp')) {
            return (int) call_user_func('\\Sodium\\memcmp', $left, $right);
        }
        /** @var string $left */
        /** @var string $right */
        return ParagonIE_Sodium_Core_Util::memcmp($left, $right);
    }

    /**
     * It's actually not possible to zero memory buffers in PHP. You need the
     * native library for that.
     *
     * @param string|null $var
     *
     * @return void
     * @throws SodiumException (Unless libsodium is installed)
     * @throws TypeError
     * @psalm-suppress TooFewArguments
     */
    public static function memzero(&$var)
    {
        /* Type checks: */
        ParagonIE_Sodium_Core_Util::declareScalarType($var, 'string', 1);

        if (self::useNewSodiumAPI()) {
            sodium_memzero($var);
            return;
        }
        if (self::use_fallback('memzero')) {
            $func = '\\Sodium\\memzero';
            $func($var);
            if ($var === null) {
                return;
            }
        }
        // This is the best we can do.
        throw new SodiumException(
            'This is not implemented in sodium_compat, as it is not possible to securely wipe memory from PHP. ' .
            'To fix this error, make sure libsodium is installed and the PHP extension is enabled.'
        );
    }

    /**
     * Will sodium_compat run fast on the current hardware and PHP configuration?
     *
     * @return bool
     */
    public static function polyfill_is_fast()
    {
        if (extension_loaded('sodium')) {
            return true;
        }
        if (extension_loaded('libsodium')) {
            return true;
        }
        return PHP_INT_SIZE === 8;
    }

    /**
     * Generate a string of bytes from the kernel's CSPRNG.
     * Proudly uses /dev/urandom (if getrandom(2) is not available).
     *
     * @param int $numBytes
     * @return string
     * @throws Exception
     * @throws TypeError
     */
    public static function randombytes_buf($numBytes)
    {
        /* Type checks: */
        if (!is_int($numBytes)) {
            if (is_numeric($numBytes)) {
                $numBytes = (int) $numBytes;
            } else {
                throw new TypeError(
                    'Argument 1 must be an integer, ' . gettype($numBytes) . ' given.'
                );
            }
        }
        if (self::use_fallback('randombytes_buf')) {
            return (string) call_user_func('\\Sodium\\randombytes_buf', $numBytes);
        }
        return random_bytes($numBytes);
    }

    /**
     * Generate an integer between 0 and $range (non-inclusive).
     *
     * @param int $range
     * @return int
     * @throws Exception
     * @throws Error
     * @throws TypeError
     */
    public static function randombytes_uniform($range)
    {
        /* Type checks: */
        if (!is_int($range)) {
            if (is_numeric($range)) {
                $range = (int) $range;
            } else {
                throw new TypeError(
                    'Argument 1 must be an integer, ' . gettype($range) . ' given.'
                );
            }
        }
        if (self::use_fallback('randombytes_uniform')) {
            return (int) call_user_func('\\Sodium\\randombytes_uniform', $range);
        }
        return random_int(0, $range - 1);
    }

    /**
     * Generate a random 16-bit integer.
     *
     * @return int
     * @throws Exception
     * @throws Error
     * @throws TypeError
     */
    public static function randombytes_random16()
    {
        if (self::use_fallback('randombytes_random16')) {
            return (int) call_user_func('\\Sodium\\randombytes_random16');
        }
        return random_int(0, 65535);
    }

    /**
     * This emulates libsodium's version_string() function, except ours is
     * prefixed with 'polyfill-'.
     *
     * @return string
     * @psalm-suppress MixedInferredReturnType
     * @psalm-suppress UndefinedFunction
     */
    public static function version_string()
    {
        if (self::useNewSodiumAPI()) {
            return (string) sodium_version_string();
        }
        if (self::use_fallback('version_string')) {
            return (string) call_user_func('\\Sodium\\version_string');
        }
        return (string) self::VERSION_STRING;
    }

    /**
     * Should we use the libsodium core function instead?
     * This is always a good idea, if it's available. (Unless we're in the
     * middle of running our unit test suite.)
     *
     * If ext/libsodium is available, use it. Return TRUE.
     * Otherwise, we have to use the code provided herein. Return FALSE.
     *
     * @param string $sodium_func_name
     *
     * @return bool
     */
    protected static function use_fallback($sodium_func_name = '')
    {
        static $res = null;
        if ($res === null) {
            $res = extension_loaded('libsodium') && PHP_VERSION_ID >= 50300;
        }
        if ($res === false) {
            // No libsodium installed
            return false;
        }
        if (self::$disableFallbackForUnitTests) {
            // Don't fallback. Use the PHP implementation.
            return false;
        }
        if (!empty($sodium_func_name)) {
            return is_callable('\\Sodium\\' . $sodium_func_name);
        }
        return true;
    }

    /**
     * Libsodium as implemented in PHP 7.2
     * and/or ext/sodium (via PECL)
     *
     * @ref https://wiki.php.net/rfc/libsodium
     * @return bool
     */
    protected static function useNewSodiumAPI()
    {
        static $res = null;
        if ($res === null) {
            $res = PHP_VERSION_ID >= 70000 && extension_loaded('sodium');
        }
        if (self::$disableFallbackForUnitTests) {
            // Don't fallback. Use the PHP implementation.
            return false;
        }
        return (bool) $res;
    }
}
vendor/paragonie/sodium_compat/src/File.php000064400000147344152177723700015060 0ustar00<?php

if (class_exists('ParagonIE_Sodium_File', false)) {
    return;
}
/**
 * Class ParagonIE_Sodium_File
 */
class ParagonIE_Sodium_File extends ParagonIE_Sodium_Core_Util
{
    /* PHP's default buffer size is 8192 for fread()/fwrite(). */
    const BUFFER_SIZE = 8192;

    /**
     * Box a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_box(), but produces
     * the same result.
     *
     * @param string $inputFile  Absolute path to a file on the filesystem
     * @param string $outputFile Absolute path to a file on the filesystem
     * @param string $nonce      Number to be used only once
     * @param string $keyPair    ECDH secret key and ECDH public key concatenated
     *
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box($inputFile, $outputFile, $nonce, $keyPair)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($nonce)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
        }

        /* Input validation: */
        if (!is_string($keyPair)) {
            throw new TypeError('Argument 4 must be a string, ' . gettype($keyPair) . ' given.');
        }
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
            throw new TypeError('Argument 3 must be CRYPTO_BOX_NONCEBYTES bytes');
        }
        if (self::strlen($keyPair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
        }

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $keyPair);
        fclose($ifp);
        fclose($ofp);
        return $res;
    }

    /**
     * Open a boxed file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_box_open(), but produces
     * the same result.
     *
     * Warning: Does not protect against TOCTOU attacks. You should
     * just load the file into memory and use crypto_box_open() if
     * you are worried about those.
     *
     * @param string $inputFile
     * @param string $outputFile
     * @param string $nonce
     * @param string $keypair
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_open($inputFile, $outputFile, $nonce, $keypair)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($nonce)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
        }
        if (!is_string($keypair)) {
            throw new TypeError('Argument 4 must be a string, ' . gettype($keypair) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_NONCEBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_BOX_NONCEBYTES bytes');
        }
        if (self::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
        }

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $keypair);
        fclose($ifp);
        fclose($ofp);
        try {
            ParagonIE_Sodium_Compat::memzero($nonce);
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
        } catch (SodiumException $ex) {
            unset($ephKeypair);
        }
        return $res;
    }

    /**
     * Seal a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_box_seal(), but produces
     * the same result.
     *
     * @param string $inputFile  Absolute path to a file on the filesystem
     * @param string $outputFile Absolute path to a file on the filesystem
     * @param string $publicKey  ECDH public key
     *
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal($inputFile, $outputFile, $publicKey)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($publicKey)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
            throw new TypeError('Argument 3 must be CRYPTO_BOX_PUBLICKEYBYTES bytes');
        }

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        /** @var string $ephKeypair */
        $ephKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair();

        /** @var string $msgKeypair */
        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
            ParagonIE_Sodium_Compat::crypto_box_secretkey($ephKeypair),
            $publicKey
        );

        /** @var string $ephemeralPK */
        $ephemeralPK = ParagonIE_Sodium_Compat::crypto_box_publickey($ephKeypair);

        /** @var string $nonce */
        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );

        /** @var int $firstWrite */
        $firstWrite = fwrite(
            $ofp,
            $ephemeralPK,
            ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES
        );
        if (!is_int($firstWrite)) {
            fclose($ifp);
            fclose($ofp);
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
            throw new SodiumException('Could not write to output file');
        }
        if ($firstWrite !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
            fclose($ifp);
            fclose($ofp);
            throw new SodiumException('Error writing public key to output file');
        }

        $res = self::box_encrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
        fclose($ifp);
        fclose($ofp);
        try {
            ParagonIE_Sodium_Compat::memzero($nonce);
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
        } catch (SodiumException $ex) {
            unset($ephKeypair);
        }
        return $res;
    }

    /**
     * Open a sealed file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_box_seal_open(), but produces
     * the same result.
     *
     * Warning: Does not protect against TOCTOU attacks. You should
     * just load the file into memory and use crypto_box_seal_open() if
     * you are worried about those.
     *
     * @param string $inputFile
     * @param string $outputFile
     * @param string $ecdhKeypair
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal_open($inputFile, $outputFile, $ecdhKeypair)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($ecdhKeypair)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($ecdhKeypair) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($ecdhKeypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new TypeError('Argument 3 must be CRYPTO_BOX_KEYPAIRBYTES bytes');
        }

        $publicKey = ParagonIE_Sodium_Compat::crypto_box_publickey($ecdhKeypair);

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        $ephemeralPK = fread($ifp, ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES);
        if (!is_string($ephemeralPK)) {
            throw new SodiumException('Could not read input file');
        }
        if (self::strlen($ephemeralPK) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_PUBLICKEYBYTES) {
            fclose($ifp);
            fclose($ofp);
            throw new SodiumException('Could not read public key from sealed file');
        }

        $nonce = ParagonIE_Sodium_Compat::crypto_generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );
        $msgKeypair = ParagonIE_Sodium_Compat::crypto_box_keypair_from_secretkey_and_publickey(
            ParagonIE_Sodium_Compat::crypto_box_secretkey($ecdhKeypair),
            $ephemeralPK
        );

        $res = self::box_decrypt($ifp, $ofp, $size, $nonce, $msgKeypair);
        fclose($ifp);
        fclose($ofp);
        try {
            ParagonIE_Sodium_Compat::memzero($nonce);
            ParagonIE_Sodium_Compat::memzero($ephKeypair);
        } catch (SodiumException $ex) {
            unset($ephKeypair);
        }
        return $res;
    }

    /**
     * Calculate the BLAKE2b hash of a file.
     *
     * @param string      $filePath     Absolute path to a file on the filesystem
     * @param string|null $key          BLAKE2b key
     * @param int         $outputLength Length of hash output
     *
     * @return string                   BLAKE2b hash
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress FailedTypeResolution
     */
    public static function generichash($filePath, $key = '', $outputLength = 32)
    {
        /* Type checks: */
        if (!is_string($filePath)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
        }
        if (!is_string($key)) {
            if (is_null($key)) {
                $key = '';
            } else {
                throw new TypeError('Argument 2 must be a string, ' . gettype($key) . ' given.');
            }
        }
        if (!is_int($outputLength)) {
            if (!is_numeric($outputLength)) {
                throw new TypeError('Argument 3 must be an integer, ' . gettype($outputLength) . ' given.');
            }
            $outputLength = (int) $outputLength;
        }

        /* Input validation: */
        if (!empty($key)) {
            if (self::strlen($key) < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MIN) {
                throw new TypeError('Argument 2 must be at least CRYPTO_GENERICHASH_KEYBYTES_MIN bytes');
            }
            if (self::strlen($key) > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_KEYBYTES_MAX) {
                throw new TypeError('Argument 2 must be at most CRYPTO_GENERICHASH_KEYBYTES_MAX bytes');
            }
        }
        if ($outputLength < ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MIN) {
            throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MIN');
        }
        if ($outputLength > ParagonIE_Sodium_Compat::CRYPTO_GENERICHASH_BYTES_MAX) {
            throw new SodiumException('Argument 3 must be at least CRYPTO_GENERICHASH_BYTES_MAX');
        }

        /** @var int $size */
        $size = filesize($filePath);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $fp */
        $fp = fopen($filePath, 'rb');
        if (!is_resource($fp)) {
            throw new SodiumException('Could not open input file for reading');
        }
        $ctx = ParagonIE_Sodium_Compat::crypto_generichash_init($key, $outputLength);
        while ($size > 0) {
            $blockSize = $size > 64
                ? 64
                : $size;
            $read = fread($fp, $blockSize);
            if (!is_string($read)) {
                throw new SodiumException('Could not read input file');
            }
            ParagonIE_Sodium_Compat::crypto_generichash_update($ctx, $read);
            $size -= $blockSize;
        }

        fclose($fp);
        return ParagonIE_Sodium_Compat::crypto_generichash_final($ctx, $outputLength);
    }

    /**
     * Encrypt a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_secretbox(), but produces
     * the same result.
     *
     * @param string $inputFile  Absolute path to a file on the filesystem
     * @param string $outputFile Absolute path to a file on the filesystem
     * @param string $nonce      Number to be used only once
     * @param string $key        Encryption key
     *
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox($inputFile, $outputFile, $nonce, $key)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given..');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($nonce)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new TypeError('Argument 3 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
        }
        if (!is_string($key)) {
            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
        }
        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_KEYBYTES bytes');
        }

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        $res = self::secretbox_encrypt($ifp, $ofp, $size, $nonce, $key);
        fclose($ifp);
        fclose($ofp);
        return $res;
    }
    /**
     * Seal a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_secretbox_open(), but produces
     * the same result.
     *
     * Warning: Does not protect against TOCTOU attacks. You should
     * just load the file into memory and use crypto_secretbox_open() if
     * you are worried about those.
     *
     * @param string $inputFile
     * @param string $outputFile
     * @param string $nonce
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_open($inputFile, $outputFile, $nonce, $key)
    {
        /* Type checks: */
        if (!is_string($inputFile)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($inputFile) . ' given.');
        }
        if (!is_string($outputFile)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($outputFile) . ' given.');
        }
        if (!is_string($nonce)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($nonce) . ' given.');
        }
        if (!is_string($key)) {
            throw new TypeError('Argument 4 must be a string, ' . gettype($key) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($nonce) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_NONCEBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOX_NONCEBYTES bytes');
        }
        if (self::strlen($key) !== ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_KEYBYTES) {
            throw new TypeError('Argument 4 must be CRYPTO_SECRETBOXBOX_KEYBYTES bytes');
        }

        /** @var int $size */
        $size = filesize($inputFile);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $ifp */
        $ifp = fopen($inputFile, 'rb');
        if (!is_resource($ifp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var resource $ofp */
        $ofp = fopen($outputFile, 'wb');
        if (!is_resource($ofp)) {
            fclose($ifp);
            throw new SodiumException('Could not open output file for writing');
        }

        $res = self::secretbox_decrypt($ifp, $ofp, $size, $nonce, $key);
        fclose($ifp);
        fclose($ofp);
        try {
            ParagonIE_Sodium_Compat::memzero($key);
        } catch (SodiumException $ex) {
            unset($key);
        }
        return $res;
    }

    /**
     * Sign a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
     * the same result.
     *
     * @param string $filePath  Absolute path to a file on the filesystem
     * @param string $secretKey Secret signing key
     *
     * @return string           Ed25519 signature
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($filePath, $secretKey)
    {
        /* Type checks: */
        if (!is_string($filePath)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($filePath) . ' given.');
        }
        if (!is_string($secretKey)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($secretKey) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($secretKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_SECRETKEYBYTES) {
            throw new TypeError('Argument 2 must be CRYPTO_SIGN_SECRETKEYBYTES bytes');
        }
        if (PHP_INT_SIZE === 4) {
            return self::sign_core32($filePath, $secretKey);
        }

        /** @var int $size */
        $size = filesize($filePath);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $fp */
        $fp = fopen($filePath, 'rb');
        if (!is_resource($fp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var string $az */
        $az = hash('sha512', self::substr($secretKey, 0, 32), true);

        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($az, 32, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);

        /** @var string $nonceHash */
        $nonceHash = hash_final($hs, true);

        /** @var string $pk */
        $pk = self::substr($secretKey, 32, 32);

        /** @var string $nonce */
        $nonce = ParagonIE_Sodium_Core_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);

        /** @var string $sig */
        $sig = ParagonIE_Sodium_Core_Ed25519::ge_p3_tobytes(
            ParagonIE_Sodium_Core_Ed25519::ge_scalarmult_base($nonce)
        );

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($pk, 0, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);

        /** @var string $hramHash */
        $hramHash = hash_final($hs, true);

        /** @var string $hram */
        $hram = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hramHash);

        /** @var string $sigAfter */
        $sigAfter = ParagonIE_Sodium_Core_Ed25519::sc_muladd($hram, $az, $nonce);

        /** @var string $sig */
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);

        try {
            ParagonIE_Sodium_Compat::memzero($az);
        } catch (SodiumException $ex) {
            $az = null;
        }
        fclose($fp);
        return $sig;
    }

    /**
     * Verify a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
     * produces the same result.
     *
     * @param string $sig       Ed25519 signature
     * @param string $filePath  Absolute path to a file on the filesystem
     * @param string $publicKey Signing public key
     *
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     * @throws Exception
     */
    public static function verify($sig, $filePath, $publicKey)
    {
        /* Type checks: */
        if (!is_string($sig)) {
            throw new TypeError('Argument 1 must be a string, ' . gettype($sig) . ' given.');
        }
        if (!is_string($filePath)) {
            throw new TypeError('Argument 2 must be a string, ' . gettype($filePath) . ' given.');
        }
        if (!is_string($publicKey)) {
            throw new TypeError('Argument 3 must be a string, ' . gettype($publicKey) . ' given.');
        }

        /* Input validation: */
        if (self::strlen($sig) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_BYTES) {
            throw new TypeError('Argument 1 must be CRYPTO_SIGN_BYTES bytes');
        }
        if (self::strlen($publicKey) !== ParagonIE_Sodium_Compat::CRYPTO_SIGN_PUBLICKEYBYTES) {
            throw new TypeError('Argument 3 must be CRYPTO_SIGN_PUBLICKEYBYTES bytes');
        }
        if (self::strlen($sig) < 64) {
            throw new SodiumException('Signature is too short');
        }

        if (PHP_INT_SIZE === 4) {
            return self::verify_core32($sig, $filePath, $publicKey);
        }

        /* Security checks */
        if (ParagonIE_Sodium_Core_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
            throw new SodiumException('S < L - Invalid signature');
        }
        if (ParagonIE_Sodium_Core_Ed25519::small_order($sig)) {
            throw new SodiumException('Signature is on too small of an order');
        }
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
            throw new SodiumException('Invalid signature');
        }
        $d = 0;
        for ($i = 0; $i < 32; ++$i) {
            $d |= self::chrToInt($publicKey[$i]);
        }
        if ($d === 0) {
            throw new SodiumException('All zero public key');
        }

        /** @var int $size */
        $size = filesize($filePath);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }

        /** @var resource $fp */
        $fp = fopen($filePath, 'rb');
        if (!is_resource($fp)) {
            throw new SodiumException('Could not open input file for reading');
        }

        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
        $orig = ParagonIE_Sodium_Compat::$fastMult;

        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
        ParagonIE_Sodium_Compat::$fastMult = true;

        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P3 $A */
        $A = ParagonIE_Sodium_Core_Ed25519::ge_frombytes_negate_vartime($publicKey);

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($publicKey, 0, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);
        /** @var string $hDigest */
        $hDigest = hash_final($hs, true);

        /** @var string $h */
        $h = ParagonIE_Sodium_Core_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);

        /** @var ParagonIE_Sodium_Core_Curve25519_Ge_P2 $R */
        $R = ParagonIE_Sodium_Core_Ed25519::ge_double_scalarmult_vartime(
            $h,
            $A,
            self::substr($sig, 32)
        );

        /** @var string $rcheck */
        $rcheck = ParagonIE_Sodium_Core_Ed25519::ge_tobytes($R);

        // Close the file handle
        fclose($fp);

        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
        ParagonIE_Sodium_Compat::$fastMult = $orig;
        return self::verify_32($rcheck, self::substr($sig, 0, 32));
    }

    /**
     * @param resource $ifp
     * @param resource $ofp
     * @param int      $mlen
     * @param string   $nonce
     * @param string   $boxKeypair
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function box_encrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
    {
        if (PHP_INT_SIZE === 4) {
            return self::secretbox_encrypt(
                $ifp,
                $ofp,
                $mlen,
                $nonce,
                ParagonIE_Sodium_Crypto32::box_beforenm(
                    ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
                    ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
                )
            );
        }
        return self::secretbox_encrypt(
            $ifp,
            $ofp,
            $mlen,
            $nonce,
            ParagonIE_Sodium_Crypto::box_beforenm(
                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
            )
        );
    }


    /**
     * @param resource $ifp
     * @param resource $ofp
     * @param int      $mlen
     * @param string   $nonce
     * @param string   $boxKeypair
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function box_decrypt($ifp, $ofp, $mlen, $nonce, $boxKeypair)
    {
        if (PHP_INT_SIZE === 4) {
            return self::secretbox_decrypt(
                $ifp,
                $ofp,
                $mlen,
                $nonce,
                ParagonIE_Sodium_Crypto32::box_beforenm(
                    ParagonIE_Sodium_Crypto32::box_secretkey($boxKeypair),
                    ParagonIE_Sodium_Crypto32::box_publickey($boxKeypair)
                )
            );
        }
        return self::secretbox_decrypt(
            $ifp,
            $ofp,
            $mlen,
            $nonce,
            ParagonIE_Sodium_Crypto::box_beforenm(
                ParagonIE_Sodium_Crypto::box_secretkey($boxKeypair),
                ParagonIE_Sodium_Crypto::box_publickey($boxKeypair)
            )
        );
    }

    /**
     * Encrypt a file
     *
     * @param resource $ifp
     * @param resource $ofp
     * @param int $mlen
     * @param string $nonce
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function secretbox_encrypt($ifp, $ofp, $mlen, $nonce, $key)
    {
        if (PHP_INT_SIZE === 4) {
            return self::secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
        }

        $plaintext = fread($ifp, 32);
        if (!is_string($plaintext)) {
            throw new SodiumException('Could not read input file');
        }
        $first32 = ftell($ifp);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $realNonce */
        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen0 = $mlen;
        if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
            $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
            $block0,
            $realNonce,
            $subkey
        );

        $state = new ParagonIE_Sodium_Core_Poly1305_State(
            ParagonIE_Sodium_Core_Util::substr(
                $block0,
                0,
                ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
            )
        );

        // Pre-write 16 blank bytes for the Poly1305 tag
        $start = ftell($ofp);
        fwrite($ofp, str_repeat("\x00", 16));

        /** @var string $c */
        $cBlock = ParagonIE_Sodium_Core_Util::substr(
            $block0,
            ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
        );
        $state->update($cBlock);
        fwrite($ofp, $cBlock);
        $mlen -= 32;

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        /*
         * Set the cursor to the end of the first half-block. All future bytes will
         * generated from salsa20_xor_ic, starting from 1 (second block).
         */
        fseek($ifp, $first32, SEEK_SET);

        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $plaintext = fread($ifp, $blockSize);
            if (!is_string($plaintext)) {
                throw new SodiumException('Could not read input file');
            }
            $cBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
                $plaintext,
                $realNonce,
                $iter,
                $subkey
            );
            fwrite($ofp, $cBlock, $blockSize);
            $state->update($cBlock);

            $mlen -= $blockSize;
            $iter += $incr;
        }
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }
        $end = ftell($ofp);

        /*
         * Write the Poly1305 authentication tag that provides integrity
         * over the ciphertext (encrypt-then-MAC)
         */
        fseek($ofp, $start, SEEK_SET);
        fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
        fseek($ofp, $end, SEEK_SET);
        unset($state);

        return true;
    }

    /**
     * Decrypt a file
     *
     * @param resource $ifp
     * @param resource $ofp
     * @param int $mlen
     * @param string $nonce
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function secretbox_decrypt($ifp, $ofp, $mlen, $nonce, $key)
    {
        if (PHP_INT_SIZE === 4) {
            return self::secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key);
        }
        $tag = fread($ifp, 16);
        if (!is_string($tag)) {
            throw new SodiumException('Could not read input file');
        }

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $realNonce */
        $realNonce = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
            64,
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
            $subkey
        );

        /* Verify the Poly1305 MAC -before- attempting to decrypt! */
        $state = new ParagonIE_Sodium_Core_Poly1305_State(self::substr($block0, 0, 32));
        if (!self::onetimeauth_verify($state, $ifp, $tag, $mlen)) {
            throw new SodiumException('Invalid MAC');
        }

        /*
         * Set the cursor to the end of the first half-block. All future bytes will
         * generated from salsa20_xor_ic, starting from 1 (second block).
         */
        $first32 = fread($ifp, 32);
        if (!is_string($first32)) {
            throw new SodiumException('Could not read input file');
        }
        $first32len = self::strlen($first32);
        fwrite(
            $ofp,
            self::xorStrings(
                self::substr($block0, 32, $first32len),
                self::substr($first32, 0, $first32len)
            )
        );
        $mlen -= 32;

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        /* Decrypts ciphertext, writes to output file. */
        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $ciphertext = fread($ifp, $blockSize);
            if (!is_string($ciphertext)) {
                throw new SodiumException('Could not read input file');
            }
            $pBlock = ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
                $ciphertext,
                $realNonce,
                $iter,
                $subkey
            );
            fwrite($ofp, $pBlock, $blockSize);
            $mlen -= $blockSize;
            $iter += $incr;
        }
        return true;
    }

    /**
     * @param ParagonIE_Sodium_Core_Poly1305_State $state
     * @param resource $ifp
     * @param string $tag
     * @param int $mlen
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function onetimeauth_verify(
        ParagonIE_Sodium_Core_Poly1305_State $state,
        $ifp,
        $tag = '',
        $mlen = 0
    ) {
        /** @var int $pos */
        $pos = ftell($ifp);

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $ciphertext = fread($ifp, $blockSize);
            if (!is_string($ciphertext)) {
                throw new SodiumException('Could not read input file');
            }
            $state->update($ciphertext);
            $mlen -= $blockSize;
            $iter += $incr;
        }
        $res = ParagonIE_Sodium_Core_Util::verify_16($tag, $state->finish());

        fseek($ifp, $pos, SEEK_SET);
        return $res;
    }

    /**
     * Update a hash context with the contents of a file, without
     * loading the entire file into memory.
     *
     * @param resource|object $hash
     * @param resource $fp
     * @param int $size
     * @return mixed (resource on PHP < 7.2, object on PHP >= 7.2)
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress PossiblyInvalidArgument
     *                 PHP 7.2 changes from a resource to an object,
     *                 which causes Psalm to complain about an error.
     * @psalm-suppress TypeCoercion
     *                 Ditto.
     */
    public static function updateHashWithFile($hash, $fp, $size = 0)
    {
        /* Type checks: */
        if (PHP_VERSION_ID < 70200) {
            if (!is_resource($hash)) {
                throw new TypeError('Argument 1 must be a resource, ' . gettype($hash) . ' given.');
            }

        } else {
            if (!is_object($hash)) {
                throw new TypeError('Argument 1 must be an object (PHP 7.2+), ' . gettype($hash) . ' given.');
            }
        }
        if (!is_resource($fp)) {
            throw new TypeError('Argument 2 must be a resource, ' . gettype($fp) . ' given.');
        }
        if (!is_int($size)) {
            throw new TypeError('Argument 3 must be an integer, ' . gettype($size) . ' given.');
        }

        /** @var int $originalPosition */
        $originalPosition = ftell($fp);

        // Move file pointer to beginning of file
        fseek($fp, 0, SEEK_SET);
        for ($i = 0; $i < $size; $i += self::BUFFER_SIZE) {
            /** @var string|bool $message */
            $message = fread(
                $fp,
                ($size - $i) > self::BUFFER_SIZE
                    ? $size - $i
                    : self::BUFFER_SIZE
            );
            if (!is_string($message)) {
                throw new SodiumException('Unexpected error reading from file.');
            }
            /** @var string $message */
            /** @psalm-suppress InvalidArgument */
            hash_update($hash, $message);
        }
        // Reset file pointer's position
        fseek($fp, $originalPosition, SEEK_SET);
        return $hash;
    }

    /**
     * Sign a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_sign_detached(), but produces
     * the same result. (32-bit)
     *
     * @param string $filePath  Absolute path to a file on the filesystem
     * @param string $secretKey Secret signing key
     *
     * @return string           Ed25519 signature
     * @throws SodiumException
     * @throws TypeError
     */
    private static function sign_core32($filePath, $secretKey)
    {
        /** @var int|bool $size */
        $size = filesize($filePath);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }
        /** @var int $size */

        /** @var resource|bool $fp */
        $fp = fopen($filePath, 'rb');
        if (!is_resource($fp)) {
            throw new SodiumException('Could not open input file for reading');
        }
        /** @var resource $fp */

        /** @var string $az */
        $az = hash('sha512', self::substr($secretKey, 0, 32), true);

        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($az, 32, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);

        /** @var string $nonceHash */
        $nonceHash = hash_final($hs, true);

        /** @var string $pk */
        $pk = self::substr($secretKey, 32, 32);

        /** @var string $nonce */
        $nonce = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($nonceHash) . self::substr($nonceHash, 32);

        /** @var string $sig */
        $sig = ParagonIE_Sodium_Core32_Ed25519::ge_p3_tobytes(
            ParagonIE_Sodium_Core32_Ed25519::ge_scalarmult_base($nonce)
        );

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($pk, 0, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);

        /** @var string $hramHash */
        $hramHash = hash_final($hs, true);

        /** @var string $hram */
        $hram = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hramHash);

        /** @var string $sigAfter */
        $sigAfter = ParagonIE_Sodium_Core32_Ed25519::sc_muladd($hram, $az, $nonce);

        /** @var string $sig */
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);

        try {
            ParagonIE_Sodium_Compat::memzero($az);
        } catch (SodiumException $ex) {
            $az = null;
        }
        fclose($fp);
        return $sig;
    }

    /**
     *
     * Verify a file (rather than a string). Uses less memory than
     * ParagonIE_Sodium_Compat::crypto_sign_verify_detached(), but
     * produces the same result. (32-bit)
     *
     * @param string $sig       Ed25519 signature
     * @param string $filePath  Absolute path to a file on the filesystem
     * @param string $publicKey Signing public key
     *
     * @return bool
     * @throws SodiumException
     * @throws Exception
     */
    public static function verify_core32($sig, $filePath, $publicKey)
    {
        /* Security checks */
        if (ParagonIE_Sodium_Core32_Ed25519::check_S_lt_L(self::substr($sig, 32, 32))) {
            throw new SodiumException('S < L - Invalid signature');
        }
        if (ParagonIE_Sodium_Core32_Ed25519::small_order($sig)) {
            throw new SodiumException('Signature is on too small of an order');
        }
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
            throw new SodiumException('Invalid signature');
        }
        $d = 0;
        for ($i = 0; $i < 32; ++$i) {
            $d |= self::chrToInt($publicKey[$i]);
        }
        if ($d === 0) {
            throw new SodiumException('All zero public key');
        }

        /** @var int|bool $size */
        $size = filesize($filePath);
        if (!is_int($size)) {
            throw new SodiumException('Could not obtain the file size');
        }
        /** @var int $size */

        /** @var resource|bool $fp */
        $fp = fopen($filePath, 'rb');
        if (!is_resource($fp)) {
            throw new SodiumException('Could not open input file for reading');
        }
        /** @var resource $fp */

        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
        $orig = ParagonIE_Sodium_Compat::$fastMult;

        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
        ParagonIE_Sodium_Compat::$fastMult = true;

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
        $A = ParagonIE_Sodium_Core32_Ed25519::ge_frombytes_negate_vartime($publicKey);

        /** @var resource $hs */
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($publicKey, 0, 32));
        /** @var resource $hs */
        $hs = self::updateHashWithFile($hs, $fp, $size);
        /** @var string $hDigest */
        $hDigest = hash_final($hs, true);

        /** @var string $h */
        $h = ParagonIE_Sodium_Core32_Ed25519::sc_reduce($hDigest) . self::substr($hDigest, 32);

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
        $R = ParagonIE_Sodium_Core32_Ed25519::ge_double_scalarmult_vartime(
            $h,
            $A,
            self::substr($sig, 32)
        );

        /** @var string $rcheck */
        $rcheck = ParagonIE_Sodium_Core32_Ed25519::ge_tobytes($R);

        // Close the file handle
        fclose($fp);

        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
        ParagonIE_Sodium_Compat::$fastMult = $orig;
        return self::verify_32($rcheck, self::substr($sig, 0, 32));
    }

    /**
     * Encrypt a file (32-bit)
     *
     * @param resource $ifp
     * @param resource $ofp
     * @param int $mlen
     * @param string $nonce
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function secretbox_encrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
    {
        $plaintext = fread($ifp, 32);
        if (!is_string($plaintext)) {
            throw new SodiumException('Could not read input file');
        }
        $first32 = ftell($ifp);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $realNonce */
        $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen0 = $mlen;
        if ($mlen0 > 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES) {
            $mlen0 = 64 - ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core32_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor(
            $block0,
            $realNonce,
            $subkey
        );

        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            ParagonIE_Sodium_Core32_Util::substr(
                $block0,
                0,
                ParagonIE_Sodium_Crypto::onetimeauth_poly1305_KEYBYTES
            )
        );

        // Pre-write 16 blank bytes for the Poly1305 tag
        $start = ftell($ofp);
        fwrite($ofp, str_repeat("\x00", 16));

        /** @var string $c */
        $cBlock = ParagonIE_Sodium_Core32_Util::substr(
            $block0,
            ParagonIE_Sodium_Crypto::secretbox_xsalsa20poly1305_ZEROBYTES
        );
        $state->update($cBlock);
        fwrite($ofp, $cBlock);
        $mlen -= 32;

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        /*
         * Set the cursor to the end of the first half-block. All future bytes will
         * generated from salsa20_xor_ic, starting from 1 (second block).
         */
        fseek($ifp, $first32, SEEK_SET);

        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $plaintext = fread($ifp, $blockSize);
            if (!is_string($plaintext)) {
                throw new SodiumException('Could not read input file');
            }
            $cBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
                $plaintext,
                $realNonce,
                $iter,
                $subkey
            );
            fwrite($ofp, $cBlock, $blockSize);
            $state->update($cBlock);

            $mlen -= $blockSize;
            $iter += $incr;
        }
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }
        $end = ftell($ofp);

        /*
         * Write the Poly1305 authentication tag that provides integrity
         * over the ciphertext (encrypt-then-MAC)
         */
        fseek($ofp, $start, SEEK_SET);
        fwrite($ofp, $state->finish(), ParagonIE_Sodium_Compat::CRYPTO_SECRETBOX_MACBYTES);
        fseek($ofp, $end, SEEK_SET);
        unset($state);

        return true;
    }

    /**
     * Decrypt a file (32-bit)
     *
     * @param resource $ifp
     * @param resource $ofp
     * @param int $mlen
     * @param string $nonce
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function secretbox_decrypt_core32($ifp, $ofp, $mlen, $nonce, $key)
    {
        $tag = fread($ifp, 16);
        if (!is_string($tag)) {
            throw new SodiumException('Could not read input file');
        }

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core32_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $realNonce */
        $realNonce = ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core32_Salsa20::salsa20(
            64,
            ParagonIE_Sodium_Core32_Util::substr($nonce, 16, 8),
            $subkey
        );

        /* Verify the Poly1305 MAC -before- attempting to decrypt! */
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(self::substr($block0, 0, 32));
        if (!self::onetimeauth_verify_core32($state, $ifp, $tag, $mlen)) {
            throw new SodiumException('Invalid MAC');
        }

        /*
         * Set the cursor to the end of the first half-block. All future bytes will
         * generated from salsa20_xor_ic, starting from 1 (second block).
         */
        $first32 = fread($ifp, 32);
        if (!is_string($first32)) {
            throw new SodiumException('Could not read input file');
        }
        $first32len = self::strlen($first32);
        fwrite(
            $ofp,
            self::xorStrings(
                self::substr($block0, 32, $first32len),
                self::substr($first32, 0, $first32len)
            )
        );
        $mlen -= 32;

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        /* Decrypts ciphertext, writes to output file. */
        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $ciphertext = fread($ifp, $blockSize);
            if (!is_string($ciphertext)) {
                throw new SodiumException('Could not read input file');
            }
            $pBlock = ParagonIE_Sodium_Core32_Salsa20::salsa20_xor_ic(
                $ciphertext,
                $realNonce,
                $iter,
                $subkey
            );
            fwrite($ofp, $pBlock, $blockSize);
            $mlen -= $blockSize;
            $iter += $incr;
        }
        return true;
    }

    /**
     * One-time message authentication for 32-bit systems
     *
     * @param ParagonIE_Sodium_Core32_Poly1305_State $state
     * @param resource $ifp
     * @param string $tag
     * @param int $mlen
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function onetimeauth_verify_core32(
        ParagonIE_Sodium_Core32_Poly1305_State $state,
        $ifp,
        $tag = '',
        $mlen = 0
    ) {
        /** @var int $pos */
        $pos = ftell($ifp);

        /** @var int $iter */
        $iter = 1;

        /** @var int $incr */
        $incr = self::BUFFER_SIZE >> 6;

        while ($mlen > 0) {
            $blockSize = $mlen > self::BUFFER_SIZE
                ? self::BUFFER_SIZE
                : $mlen;
            $ciphertext = fread($ifp, $blockSize);
            if (!is_string($ciphertext)) {
                throw new SodiumException('Could not read input file');
            }
            $state->update($ciphertext);
            $mlen -= $blockSize;
            $iter += $incr;
        }
        $res = ParagonIE_Sodium_Core32_Util::verify_16($tag, $state->finish());

        fseek($ifp, $pos, SEEK_SET);
        return $res;
    }
}
vendor/paragonie/sodium_compat/src/SodiumException.php000064400000000236152177723700017304 0ustar00<?php

if (!class_exists('SodiumException', false)) {
    /**
     * Class SodiumException
     */
    class SodiumException extends Exception
    {

    }
}
vendor/paragonie/sodium_compat/src/Crypto.php000064400000114742152177723700015455 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Crypto', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Crypto
 *
 * ATTENTION!
 *
 * If you are using this library, you should be using
 * ParagonIE_Sodium_Compat in your code, not this class.
 */
abstract class ParagonIE_Sodium_Crypto
{
    const aead_chacha20poly1305_KEYBYTES = 32;
    const aead_chacha20poly1305_NSECBYTES = 0;
    const aead_chacha20poly1305_NPUBBYTES = 8;
    const aead_chacha20poly1305_ABYTES = 16;

    const aead_chacha20poly1305_IETF_KEYBYTES = 32;
    const aead_chacha20poly1305_IETF_NSECBYTES = 0;
    const aead_chacha20poly1305_IETF_NPUBBYTES = 12;
    const aead_chacha20poly1305_IETF_ABYTES = 16;

    const aead_xchacha20poly1305_IETF_KEYBYTES = 32;
    const aead_xchacha20poly1305_IETF_NSECBYTES = 0;
    const aead_xchacha20poly1305_IETF_NPUBBYTES = 24;
    const aead_xchacha20poly1305_IETF_ABYTES = 16;

    const box_curve25519xsalsa20poly1305_SEEDBYTES = 32;
    const box_curve25519xsalsa20poly1305_PUBLICKEYBYTES = 32;
    const box_curve25519xsalsa20poly1305_SECRETKEYBYTES = 32;
    const box_curve25519xsalsa20poly1305_BEFORENMBYTES = 32;
    const box_curve25519xsalsa20poly1305_NONCEBYTES = 24;
    const box_curve25519xsalsa20poly1305_MACBYTES = 16;
    const box_curve25519xsalsa20poly1305_BOXZEROBYTES = 16;
    const box_curve25519xsalsa20poly1305_ZEROBYTES = 32;

    const onetimeauth_poly1305_BYTES = 16;
    const onetimeauth_poly1305_KEYBYTES = 32;

    const secretbox_xsalsa20poly1305_KEYBYTES = 32;
    const secretbox_xsalsa20poly1305_NONCEBYTES = 24;
    const secretbox_xsalsa20poly1305_MACBYTES = 16;
    const secretbox_xsalsa20poly1305_BOXZEROBYTES = 16;
    const secretbox_xsalsa20poly1305_ZEROBYTES = 32;

    const secretbox_xchacha20poly1305_KEYBYTES = 32;
    const secretbox_xchacha20poly1305_NONCEBYTES = 24;
    const secretbox_xchacha20poly1305_MACBYTES = 16;
    const secretbox_xchacha20poly1305_BOXZEROBYTES = 16;
    const secretbox_xchacha20poly1305_ZEROBYTES = 32;

    const stream_salsa20_KEYBYTES = 32;

    /**
     * AEAD Decryption with ChaCha20-Poly1305
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of message (ciphertext + MAC) */
        $len = ParagonIE_Sodium_Core_Util::strlen($message);

        /** @var int  $clen - Length of ciphertext */
        $clen = $len - self::aead_chacha20poly1305_ABYTES;

        /** @var int $adlen - Length of associated data */
        $adlen = ParagonIE_Sodium_Core_Util::strlen($ad);

        /** @var string $mac - Message authentication code */
        $mac = ParagonIE_Sodium_Core_Util::substr(
            $message,
            $clen,
            self::aead_chacha20poly1305_ABYTES
        );

        /** @var string $ciphertext - The encrypted message (sans MAC) */
        $ciphertext = ParagonIE_Sodium_Core_Util::substr($message, 0, $clen);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
            32,
            $nonce,
            $key
        );

        /* Recalculate the Poly1305 authentication tag (MAC): */
        $state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }
        $state->update($ad);
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
        $state->update($ciphertext);
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($clen));
        $computed_mac = $state->finish();

        /* Compare the given MAC with the recalculated MAC: */
        if (!ParagonIE_Sodium_Core_Util::verify_16($computed_mac, $mac)) {
            throw new SodiumException('Invalid MAC');
        }

        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
        return ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
            $ciphertext,
            $nonce,
            $key,
            ParagonIE_Sodium_Core_Util::store64_le(1)
        );
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of the plaintext message */
        $len = ParagonIE_Sodium_Core_Util::strlen($message);

        /** @var int $adlen - Length of the associated data */
        $adlen = ParagonIE_Sodium_Core_Util::strlen($ad);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
            32,
            $nonce,
            $key
        );
        $state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }

        /** @var string $ciphertext - Raw encrypted data */
        $ciphertext = ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
            $message,
            $nonce,
            $key,
            ParagonIE_Sodium_Core_Util::store64_le(1)
        );

        $state->update($ad);
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
        $state->update($ciphertext);
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($len));
        return $ciphertext . $state->finish();
    }

    /**
     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_ietf_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $adlen - Length of associated data */
        $adlen = ParagonIE_Sodium_Core_Util::strlen($ad);

        /** @var int $len - Length of message (ciphertext + MAC) */
        $len = ParagonIE_Sodium_Core_Util::strlen($message);

        /** @var int  $clen - Length of ciphertext */
        $clen = $len - self::aead_chacha20poly1305_IETF_ABYTES;

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::ietfStream(
            32,
            $nonce,
            $key
        );

        /** @var string $mac - Message authentication code */
        $mac = ParagonIE_Sodium_Core_Util::substr(
            $message,
            $len - self::aead_chacha20poly1305_IETF_ABYTES,
            self::aead_chacha20poly1305_IETF_ABYTES
        );

        /** @var string $ciphertext - The encrypted message (sans MAC) */
        $ciphertext = ParagonIE_Sodium_Core_Util::substr(
            $message,
            0,
            $len - self::aead_chacha20poly1305_IETF_ABYTES
        );

        /* Recalculate the Poly1305 authentication tag (MAC): */
        $state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }
        $state->update($ad);
        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
        $state->update($ciphertext);
        $state->update(str_repeat("\x00", (0x10 - $clen) & 0xf));
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($clen));
        $computed_mac = $state->finish();

        /* Compare the given MAC with the recalculated MAC: */
        if (!ParagonIE_Sodium_Core_Util::verify_16($computed_mac, $mac)) {
            throw new SodiumException('Invalid MAC');
        }

        // Here, we know that the MAC is valid, so we decrypt and return the plaintext
        return ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc(
            $ciphertext,
            $nonce,
            $key,
            ParagonIE_Sodium_Core_Util::store64_le(1)
        );
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_chacha20poly1305_ietf_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        /** @var int $len - Length of the plaintext message */
        $len = ParagonIE_Sodium_Core_Util::strlen($message);

        /** @var int $adlen - Length of the associated data */
        $adlen = ParagonIE_Sodium_Core_Util::strlen($ad);

        /** @var string The first block of the chacha20 keystream, used as a poly1305 key */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::ietfStream(
            32,
            $nonce,
            $key
        );
        $state = new ParagonIE_Sodium_Core_Poly1305_State($block0);
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
        } catch (SodiumException $ex) {
            $block0 = null;
        }

        /** @var string $ciphertext - Raw encrypted data */
        $ciphertext = ParagonIE_Sodium_Core_ChaCha20::ietfStreamXorIc(
            $message,
            $nonce,
            $key,
            ParagonIE_Sodium_Core_Util::store64_le(1)
        );

        $state->update($ad);
        $state->update(str_repeat("\x00", ((0x10 - $adlen) & 0xf)));
        $state->update($ciphertext);
        $state->update(str_repeat("\x00", ((0x10 - $len) & 0xf)));
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($adlen));
        $state->update(ParagonIE_Sodium_Core_Util::store64_le($len));
        return $ciphertext . $state->finish();
    }

    /**
     * AEAD Decryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_xchacha20poly1305_ietf_decrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        $subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = "\x00\x00\x00\x00" .
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);

        return self::aead_chacha20poly1305_ietf_decrypt($message, $ad, $nonceLast, $subkey);
    }

    /**
     * AEAD Encryption with ChaCha20-Poly1305, IETF mode (96-bit nonce)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $ad
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function aead_xchacha20poly1305_ietf_encrypt(
        $message = '',
        $ad = '',
        $nonce = '',
        $key = ''
    ) {
        $subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = "\x00\x00\x00\x00" .
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);

        return self::aead_chacha20poly1305_ietf_encrypt($message, $ad, $nonceLast, $subkey);
    }

    /**
     * HMAC-SHA-512-256 (a.k.a. the leftmost 256 bits of HMAC-SHA-512)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $key
     * @return string
     * @throws TypeError
     */
    public static function auth($message, $key)
    {
        return ParagonIE_Sodium_Core_Util::substr(
            hash_hmac('sha512', $message, $key, true),
            0,
            32
        );
    }

    /**
     * HMAC-SHA-512-256 validation. Constant-time via hash_equals().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $mac
     * @param string $message
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function auth_verify($mac, $message, $key)
    {
        return ParagonIE_Sodium_Core_Util::hashEquals(
            $mac,
            self::auth($message, $key)
        );
    }

    /**
     * X25519 key exchange followed by XSalsa20Poly1305 symmetric encryption
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box($plaintext, $nonce, $keypair)
    {
        $c = self::secretbox(
            $plaintext,
            $nonce,
            self::box_beforenm(
                self::box_secretkey($keypair),
                self::box_publickey($keypair)
            )
        );
        return $c;
    }

    /**
     * X25519-XSalsa20-Poly1305 with one ephemeral X25519 keypair.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $publicKey
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal($message, $publicKey)
    {
        /** @var string $ephemeralKeypair */
        $ephemeralKeypair = self::box_keypair();

        /** @var string $ephemeralSK */
        $ephemeralSK = self::box_secretkey($ephemeralKeypair);

        /** @var string $ephemeralPK */
        $ephemeralPK = self::box_publickey($ephemeralKeypair);

        /** @var string $nonce */
        $nonce = self::generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );

        /** @var string $keypair - The combined keypair used in crypto_box() */
        $keypair = self::box_keypair_from_secretkey_and_publickey($ephemeralSK, $publicKey);

        /** @var string $ciphertext Ciphertext + MAC from crypto_box */
        $ciphertext = self::box($message, $nonce, $keypair);
        try {
            ParagonIE_Sodium_Compat::memzero($ephemeralKeypair);
            ParagonIE_Sodium_Compat::memzero($ephemeralSK);
            ParagonIE_Sodium_Compat::memzero($nonce);
        } catch (SodiumException $ex) {
            $ephemeralKeypair = null;
            $ephemeralSK = null;
            $nonce = null;
        }
        return $ephemeralPK . $ciphertext;
    }

    /**
     * Opens a message encrypted via box_seal().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seal_open($message, $keypair)
    {
        /** @var string $ephemeralPK */
        $ephemeralPK = ParagonIE_Sodium_Core_Util::substr($message, 0, 32);

        /** @var string $ciphertext (ciphertext + MAC) */
        $ciphertext = ParagonIE_Sodium_Core_Util::substr($message, 32);

        /** @var string $secretKey */
        $secretKey = self::box_secretkey($keypair);

        /** @var string $publicKey */
        $publicKey = self::box_publickey($keypair);

        /** @var string $nonce */
        $nonce = self::generichash(
            $ephemeralPK . $publicKey,
            '',
            24
        );

        /** @var string $keypair */
        $keypair = self::box_keypair_from_secretkey_and_publickey($secretKey, $ephemeralPK);

        /** @var string $m */
        $m = self::box_open($ciphertext, $nonce, $keypair);
        try {
            ParagonIE_Sodium_Compat::memzero($secretKey);
            ParagonIE_Sodium_Compat::memzero($ephemeralPK);
            ParagonIE_Sodium_Compat::memzero($nonce);
        } catch (SodiumException $ex) {
            $secretKey = null;
            $ephemeralPK = null;
            $nonce = null;
        }
        return $m;
    }

    /**
     * Used by crypto_box() to get the crypto_secretbox() key.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sk
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_beforenm($sk, $pk)
    {
        return ParagonIE_Sodium_Core_HSalsa20::hsalsa20(
            str_repeat("\x00", 16),
            self::scalarmult($sk, $pk)
        );
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @return string
     * @throws Exception
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_keypair()
    {
        $sKey = random_bytes(32);
        $pKey = self::scalarmult_base($sKey);
        return $sKey . $pKey;
    }

    /**
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_seed_keypair($seed)
    {
        $sKey = ParagonIE_Sodium_Core_Util::substr(
            hash('sha512', $seed, true),
            0,
            32
        );
        $pKey = self::scalarmult_base($sKey);
        return $sKey . $pKey;
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @param string $pKey
     * @return string
     * @throws TypeError
     */
    public static function box_keypair_from_secretkey_and_publickey($sKey, $pKey)
    {
        return ParagonIE_Sodium_Core_Util::substr($sKey, 0, 32) .
            ParagonIE_Sodium_Core_Util::substr($pKey, 0, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function box_secretkey($keypair)
    {
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== 64) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
            );
        }
        return ParagonIE_Sodium_Core_Util::substr($keypair, 0, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function box_publickey($keypair)
    {
        if (ParagonIE_Sodium_Core_Util::strlen($keypair) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_KEYPAIRBYTES bytes long.'
            );
        }
        return ParagonIE_Sodium_Core_Util::substr($keypair, 32, 32);
    }

    /**
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_publickey_from_secretkey($sKey)
    {
        if (ParagonIE_Sodium_Core_Util::strlen($sKey) !== ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES) {
            throw new RangeException(
                'Must be ParagonIE_Sodium_Compat::CRYPTO_BOX_SECRETKEYBYTES bytes long.'
            );
        }
        return self::scalarmult_base($sKey);
    }

    /**
     * Decrypt a message encrypted with box().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $keypair
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function box_open($ciphertext, $nonce, $keypair)
    {
        return self::secretbox_open(
            $ciphertext,
            $nonce,
            self::box_beforenm(
                self::box_secretkey($keypair),
                self::box_publickey($keypair)
            )
        );
    }

    /**
     * Calculate a BLAKE2b hash.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string|null $key
     * @param int $outlen
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash($message, $key = '', $outlen = 32)
    {
        // This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();

        $k = null;
        if (!empty($key)) {
            /** @var SplFixedArray $k */
            $k = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($key);
            if ($k->count() > ParagonIE_Sodium_Core_BLAKE2b::KEYBYTES) {
                throw new RangeException('Invalid key size');
            }
        }

        /** @var SplFixedArray $in */
        $in = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($message);

        /** @var SplFixedArray $ctx */
        $ctx = ParagonIE_Sodium_Core_BLAKE2b::init($k, $outlen);
        ParagonIE_Sodium_Core_BLAKE2b::update($ctx, $in, $in->count());

        /** @var SplFixedArray $out */
        $out = new SplFixedArray($outlen);
        $out = ParagonIE_Sodium_Core_BLAKE2b::finish($ctx, $out);

        /** @var array<int, int> */
        $outArray = $out->toArray();
        return ParagonIE_Sodium_Core_Util::intArrayToString($outArray);
    }

    /**
     * Finalize a BLAKE2b hashing context, returning the hash.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ctx
     * @param int $outlen
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_final($ctx, $outlen = 32)
    {
        if (!is_string($ctx)) {
            throw new TypeError('Context must be a string');
        }
        $out = new SplFixedArray($outlen);

        /** @var SplFixedArray $context */
        $context = ParagonIE_Sodium_Core_BLAKE2b::stringToContext($ctx);

        /** @var SplFixedArray $out */
        $out = ParagonIE_Sodium_Core_BLAKE2b::finish($context, $out);

        /** @var array<int, int> */
        $outArray = $out->toArray();
        return ParagonIE_Sodium_Core_Util::intArrayToString($outArray);
    }

    /**
     * Initialize a hashing context for BLAKE2b.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $key
     * @param int $outputLength
     * @return string
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_init($key = '', $outputLength = 32)
    {
        // This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();

        $k = null;
        if (!empty($key)) {
            $k = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($key);
            if ($k->count() > ParagonIE_Sodium_Core_BLAKE2b::KEYBYTES) {
                throw new RangeException('Invalid key size');
            }
        }

        /** @var SplFixedArray $ctx */
        $ctx = ParagonIE_Sodium_Core_BLAKE2b::init($k, $outputLength);

        return ParagonIE_Sodium_Core_BLAKE2b::contextToString($ctx);
    }

    /**
     * Update a hashing context for BLAKE2b with $message
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ctx
     * @param string $message
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function generichash_update($ctx, $message)
    {
        // This ensures that ParagonIE_Sodium_Core_BLAKE2b::$iv is initialized
        ParagonIE_Sodium_Core_BLAKE2b::pseudoConstructor();

        /** @var SplFixedArray $context */
        $context = ParagonIE_Sodium_Core_BLAKE2b::stringToContext($ctx);

        /** @var SplFixedArray $in */
        $in = ParagonIE_Sodium_Core_BLAKE2b::stringToSplFixedArray($message);

        ParagonIE_Sodium_Core_BLAKE2b::update($context, $in, $in->count());

        return ParagonIE_Sodium_Core_BLAKE2b::contextToString($context);
    }

    /**
     * Libsodium's crypto_kx().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $my_sk
     * @param string $their_pk
     * @param string $client_pk
     * @param string $server_pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function keyExchange($my_sk, $their_pk, $client_pk, $server_pk)
    {
        return ParagonIE_Sodium_Compat::crypto_generichash(
            ParagonIE_Sodium_Compat::crypto_scalarmult($my_sk, $their_pk) .
            $client_pk .
            $server_pk
        );
    }

    /**
     * ECDH over Curve25519
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $sKey
     * @param string $pKey
     * @return string
     *
     * @throws SodiumException
     * @throws TypeError
     */
    public static function scalarmult($sKey, $pKey)
    {
        $q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10($sKey, $pKey);
        self::scalarmult_throw_if_zero($q);
        return $q;
    }

    /**
     * ECDH over Curve25519, using the basepoint.
     * Used to get a secret key from a public key.
     *
     * @param string $secret
     * @return string
     *
     * @throws SodiumException
     * @throws TypeError
     */
    public static function scalarmult_base($secret)
    {
        $q = ParagonIE_Sodium_Core_X25519::crypto_scalarmult_curve25519_ref10_base($secret);
        self::scalarmult_throw_if_zero($q);
        return $q;
    }

    /**
     * This throws an Error if a zero public key was passed to the function.
     *
     * @param string $q
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function scalarmult_throw_if_zero($q)
    {
        $d = 0;
        for ($i = 0; $i < self::box_curve25519xsalsa20poly1305_SECRETKEYBYTES; ++$i) {
            $d |= ParagonIE_Sodium_Core_Util::chrToInt($q[$i]);
        }

        /* branch-free variant of === 0 */
        if (-(1 & (($d - 1) >> 8))) {
            throw new SodiumException('Zero public key is not allowed');
        }
    }

    /**
     * XSalsa20-Poly1305 authenticated symmetric-key encryption.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox($plaintext, $nonce, $key)
    {
        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen = ParagonIE_Sodium_Core_Util::strlen($plaintext);
        $mlen0 = $mlen;
        if ($mlen0 > 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES) {
            $mlen0 = 64 - self::secretbox_xsalsa20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20_xor(
            $block0,
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
            $subkey
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core_Util::substr(
            $block0,
            self::secretbox_xsalsa20poly1305_ZEROBYTES
        );
        if ($mlen > $mlen0) {
            $c .= ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
                ParagonIE_Sodium_Core_Util::substr(
                    $plaintext,
                    self::secretbox_xsalsa20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
                1,
                $subkey
            );
        }
        $state = new ParagonIE_Sodium_Core_Poly1305_State(
            ParagonIE_Sodium_Core_Util::substr(
                $block0,
                0,
                self::onetimeauth_poly1305_KEYBYTES
            )
        );
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }

        $state->update($c);

        /** @var string $c - MAC || ciphertext */
        $c = $state->finish() . $c;
        unset($state);

        return $c;
    }

    /**
     * Decrypt a ciphertext generated via secretbox().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_open($ciphertext, $nonce, $key)
    {
        /** @var string $mac */
        $mac = ParagonIE_Sodium_Core_Util::substr(
            $ciphertext,
            0,
            self::secretbox_xsalsa20poly1305_MACBYTES
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core_Util::substr(
            $ciphertext,
            self::secretbox_xsalsa20poly1305_MACBYTES
        );

        /** @var int $clen */
        $clen = ParagonIE_Sodium_Core_Util::strlen($c);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HSalsa20::hsalsa20($nonce, $key);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_Salsa20::salsa20(
            64,
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
            $subkey
        );
        $verified = ParagonIE_Sodium_Core_Poly1305::onetimeauth_verify(
            $mac,
            $c,
            ParagonIE_Sodium_Core_Util::substr($block0, 0, 32)
        );
        if (!$verified) {
            try {
                ParagonIE_Sodium_Compat::memzero($subkey);
            } catch (SodiumException $ex) {
                $subkey = null;
            }
            throw new SodiumException('Invalid MAC');
        }

        /** @var string $m - Decrypted message */
        $m = ParagonIE_Sodium_Core_Util::xorStrings(
            ParagonIE_Sodium_Core_Util::substr($block0, self::secretbox_xsalsa20poly1305_ZEROBYTES),
            ParagonIE_Sodium_Core_Util::substr($c, 0, self::secretbox_xsalsa20poly1305_ZEROBYTES)
        );
        if ($clen > self::secretbox_xsalsa20poly1305_ZEROBYTES) {
            // We had more than 1 block, so let's continue to decrypt the rest.
            $m .= ParagonIE_Sodium_Core_Salsa20::salsa20_xor_ic(
                ParagonIE_Sodium_Core_Util::substr(
                    $c,
                    self::secretbox_xsalsa20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
                1,
                (string) $subkey
            );
        }
        return $m;
    }

    /**
     * XChaCha20-Poly1305 authenticated symmetric-key encryption.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $plaintext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_xchacha20poly1305($plaintext, $nonce, $key)
    {
        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HChaCha20::hChaCha20(
            ParagonIE_Sodium_Core_Util::substr($nonce, 0, 16),
            $key
        );
        $nonceLast = ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8);

        /** @var string $block0 */
        $block0 = str_repeat("\x00", 32);

        /** @var int $mlen - Length of the plaintext message */
        $mlen = ParagonIE_Sodium_Core_Util::strlen($plaintext);
        $mlen0 = $mlen;
        if ($mlen0 > 64 - self::secretbox_xchacha20poly1305_ZEROBYTES) {
            $mlen0 = 64 - self::secretbox_xchacha20poly1305_ZEROBYTES;
        }
        $block0 .= ParagonIE_Sodium_Core_Util::substr($plaintext, 0, $mlen0);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
            $block0,
            $nonceLast,
            $subkey
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core_Util::substr(
            $block0,
            self::secretbox_xchacha20poly1305_ZEROBYTES
        );
        if ($mlen > $mlen0) {
            $c .= ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
                ParagonIE_Sodium_Core_Util::substr(
                    $plaintext,
                    self::secretbox_xchacha20poly1305_ZEROBYTES
                ),
                $nonceLast,
                $subkey,
                ParagonIE_Sodium_Core_Util::store64_le(1)
            );
        }
        $state = new ParagonIE_Sodium_Core_Poly1305_State(
            ParagonIE_Sodium_Core_Util::substr(
                $block0,
                0,
                self::onetimeauth_poly1305_KEYBYTES
            )
        );
        try {
            ParagonIE_Sodium_Compat::memzero($block0);
            ParagonIE_Sodium_Compat::memzero($subkey);
        } catch (SodiumException $ex) {
            $block0 = null;
            $subkey = null;
        }

        $state->update($c);

        /** @var string $c - MAC || ciphertext */
        $c = $state->finish() . $c;
        unset($state);

        return $c;
    }

    /**
     * Decrypt a ciphertext generated via secretbox_xchacha20poly1305().
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $ciphertext
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function secretbox_xchacha20poly1305_open($ciphertext, $nonce, $key)
    {
        /** @var string $mac */
        $mac = ParagonIE_Sodium_Core_Util::substr(
            $ciphertext,
            0,
            self::secretbox_xchacha20poly1305_MACBYTES
        );

        /** @var string $c */
        $c = ParagonIE_Sodium_Core_Util::substr(
            $ciphertext,
            self::secretbox_xchacha20poly1305_MACBYTES
        );

        /** @var int $clen */
        $clen = ParagonIE_Sodium_Core_Util::strlen($c);

        /** @var string $subkey */
        $subkey = ParagonIE_Sodium_Core_HChaCha20::hchacha20($nonce, $key);

        /** @var string $block0 */
        $block0 = ParagonIE_Sodium_Core_ChaCha20::stream(
            64,
            ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
            $subkey
        );
        $verified = ParagonIE_Sodium_Core_Poly1305::onetimeauth_verify(
            $mac,
            $c,
            ParagonIE_Sodium_Core_Util::substr($block0, 0, 32)
        );

        if (!$verified) {
            try {
                ParagonIE_Sodium_Compat::memzero($subkey);
            } catch (SodiumException $ex) {
                $subkey = null;
            }
            throw new SodiumException('Invalid MAC');
        }

        /** @var string $m - Decrypted message */
        $m = ParagonIE_Sodium_Core_Util::xorStrings(
            ParagonIE_Sodium_Core_Util::substr($block0, self::secretbox_xchacha20poly1305_ZEROBYTES),
            ParagonIE_Sodium_Core_Util::substr($c, 0, self::secretbox_xchacha20poly1305_ZEROBYTES)
        );

        if ($clen > self::secretbox_xchacha20poly1305_ZEROBYTES) {
            // We had more than 1 block, so let's continue to decrypt the rest.
            $m .= ParagonIE_Sodium_Core_ChaCha20::streamXorIc(
                ParagonIE_Sodium_Core_Util::substr(
                    $c,
                    self::secretbox_xchacha20poly1305_ZEROBYTES
                ),
                ParagonIE_Sodium_Core_Util::substr($nonce, 16, 8),
                (string) $subkey,
                ParagonIE_Sodium_Core_Util::store64_le(1)
            );
        }
        return $m;
    }

    /**
     * Detached Ed25519 signature.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_detached($message, $sk)
    {
        return ParagonIE_Sodium_Core_Ed25519::sign_detached($message, $sk);
    }

    /**
     * Attached Ed25519 signature. (Returns a signed message.)
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($message, $sk)
    {
        return ParagonIE_Sodium_Core_Ed25519::sign($message, $sk);
    }

    /**
     * Opens a signed message. If valid, returns the message.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $signedMessage
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_open($signedMessage, $pk)
    {
        return ParagonIE_Sodium_Core_Ed25519::sign_open($signedMessage, $pk);
    }

    /**
     * Verify a detached signature of a given message and public key.
     *
     * @internal Do not use this directly. Use ParagonIE_Sodium_Compat.
     *
     * @param string $signature
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_verify_detached($signature, $message, $pk)
    {
        return ParagonIE_Sodium_Core_Ed25519::verify_detached($signature, $message, $pk);
    }
}
vendor/paragonie/sodium_compat/src/Core32/HChaCha20.php000064400000012261152177723700016604 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_HChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_HChaCha20
 */
class ParagonIE_Sodium_Core32_HChaCha20 extends ParagonIE_Sodium_Core32_ChaCha20
{
    /**
     * @param string $in
     * @param string $key
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function hChaCha20($in = '', $key = '', $c = null)
    {
        $ctx = array();

        if ($c === null) {
            $ctx[0] = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $ctx[1] = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $ctx[2] = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $ctx[3] = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $ctx[0] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $ctx[1] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $ctx[2] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $ctx[3] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $ctx[4]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 0, 4));
        $ctx[5]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 4, 4));
        $ctx[6]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 8, 4));
        $ctx[7]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 12, 4));
        $ctx[8]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 16, 4));
        $ctx[9]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 20, 4));
        $ctx[10] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 24, 4));
        $ctx[11] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 28, 4));
        $ctx[12] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $ctx[13] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $ctx[14] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $ctx[15] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));

        return self::hChaCha20Bytes($ctx);
    }

    /**
     * @param array $ctx
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function hChaCha20Bytes(array $ctx)
    {
        /** @var ParagonIE_Sodium_Core32_Int32 $x0 */
        $x0  = $ctx[0];
        /** @var ParagonIE_Sodium_Core32_Int32 $x1 */
        $x1  = $ctx[1];
        /** @var ParagonIE_Sodium_Core32_Int32 $x2 */
        $x2  = $ctx[2];
        /** @var ParagonIE_Sodium_Core32_Int32 $x3 */
        $x3  = $ctx[3];
        /** @var ParagonIE_Sodium_Core32_Int32 $x4 */
        $x4  = $ctx[4];
        /** @var ParagonIE_Sodium_Core32_Int32 $x5 */
        $x5  = $ctx[5];
        /** @var ParagonIE_Sodium_Core32_Int32 $x6 */
        $x6  = $ctx[6];
        /** @var ParagonIE_Sodium_Core32_Int32 $x7 */
        $x7  = $ctx[7];
        /** @var ParagonIE_Sodium_Core32_Int32 $x8 */
        $x8  = $ctx[8];
        /** @var ParagonIE_Sodium_Core32_Int32 $x9 */
        $x9  = $ctx[9];
        /** @var ParagonIE_Sodium_Core32_Int32 $x10 */
        $x10 = $ctx[10];
        /** @var ParagonIE_Sodium_Core32_Int32 $x11 */
        $x11 = $ctx[11];
        /** @var ParagonIE_Sodium_Core32_Int32 $x12 */
        $x12 = $ctx[12];
        /** @var ParagonIE_Sodium_Core32_Int32 $x13 */
        $x13 = $ctx[13];
        /** @var ParagonIE_Sodium_Core32_Int32 $x14 */
        $x14 = $ctx[14];
        /** @var ParagonIE_Sodium_Core32_Int32 $x15 */
        $x15 = $ctx[15];

        for ($i = 0; $i < 10; ++$i) {
            # QUARTERROUND( x0,  x4,  x8,  x12)
            list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12);

            # QUARTERROUND( x1,  x5,  x9,  x13)
            list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13);

            # QUARTERROUND( x2,  x6,  x10,  x14)
            list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14);

            # QUARTERROUND( x3,  x7,  x11,  x15)
            list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15);

            # QUARTERROUND( x0,  x5,  x10,  x15)
            list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15);

            # QUARTERROUND( x1,  x6,  x11,  x12)
            list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12);

            # QUARTERROUND( x2,  x7,  x8,  x13)
            list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13);

            # QUARTERROUND( x3,  x4,  x9,  x14)
            list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14);
        }

        return $x0->toReverseString() .
            $x1->toReverseString() .
            $x2->toReverseString() .
            $x3->toReverseString() .
            $x12->toReverseString() .
            $x13->toReverseString() .
            $x14->toReverseString() .
            $x15->toReverseString();
    }
}
vendor/paragonie/sodium_compat/src/Core32/ChaCha20.php000064400000034257152177723700016505 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_ChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_ChaCha20
 */
class ParagonIE_Sodium_Core32_ChaCha20 extends ParagonIE_Sodium_Core32_Util
{
    /**
     * The ChaCha20 quarter round function. Works on four 32-bit integers.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Int32 $a
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @param ParagonIE_Sodium_Core32_Int32 $c
     * @param ParagonIE_Sodium_Core32_Int32 $d
     * @return array<int, ParagonIE_Sodium_Core32_Int32>
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function quarterRound(
        ParagonIE_Sodium_Core32_Int32 $a,
        ParagonIE_Sodium_Core32_Int32 $b,
        ParagonIE_Sodium_Core32_Int32 $c,
        ParagonIE_Sodium_Core32_Int32 $d
    ) {
        /** @var ParagonIE_Sodium_Core32_Int32 $a */
        /** @var ParagonIE_Sodium_Core32_Int32 $b */
        /** @var ParagonIE_Sodium_Core32_Int32 $c */
        /** @var ParagonIE_Sodium_Core32_Int32 $d */

        # a = PLUS(a,b); d = ROTATE(XOR(d,a),16);
        $a = $a->addInt32($b);
        $d = $d->xorInt32($a)->rotateLeft(16);

        # c = PLUS(c,d); b = ROTATE(XOR(b,c),12);
        $c = $c->addInt32($d);
        $b = $b->xorInt32($c)->rotateLeft(12);

        # a = PLUS(a,b); d = ROTATE(XOR(d,a), 8);
        $a = $a->addInt32($b);
        $d = $d->xorInt32($a)->rotateLeft(8);

        # c = PLUS(c,d); b = ROTATE(XOR(b,c), 7);
        $c = $c->addInt32($d);
        $b = $b->xorInt32($c)->rotateLeft(7);

        return array($a, $b, $c, $d);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_ChaCha20_Ctx $ctx
     * @param string $message
     *
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function encryptBytes(
        ParagonIE_Sodium_Core32_ChaCha20_Ctx $ctx,
        $message = ''
    ) {
        $bytes = self::strlen($message);

        /** @var ParagonIE_Sodium_Core32_Int32 $x0 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x1 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x2 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x3 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x4 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x5 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x6 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x7 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x8 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x9 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x10 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x11 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x12 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x13 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x14 */
        /** @var ParagonIE_Sodium_Core32_Int32 $x15 */

        /*
        j0 = ctx->input[0];
        j1 = ctx->input[1];
        j2 = ctx->input[2];
        j3 = ctx->input[3];
        j4 = ctx->input[4];
        j5 = ctx->input[5];
        j6 = ctx->input[6];
        j7 = ctx->input[7];
        j8 = ctx->input[8];
        j9 = ctx->input[9];
        j10 = ctx->input[10];
        j11 = ctx->input[11];
        j12 = ctx->input[12];
        j13 = ctx->input[13];
        j14 = ctx->input[14];
        j15 = ctx->input[15];
        */
        /** @var ParagonIE_Sodium_Core32_Int32 $j0 */
        $j0  = $ctx[0];
        /** @var ParagonIE_Sodium_Core32_Int32 $j1 */
        $j1  = $ctx[1];
        /** @var ParagonIE_Sodium_Core32_Int32 $j2 */
        $j2  = $ctx[2];
        /** @var ParagonIE_Sodium_Core32_Int32 $j3 */
        $j3  = $ctx[3];
        /** @var ParagonIE_Sodium_Core32_Int32 $j4 */
        $j4  = $ctx[4];
        /** @var ParagonIE_Sodium_Core32_Int32 $j5 */
        $j5  = $ctx[5];
        /** @var ParagonIE_Sodium_Core32_Int32 $j6 */
        $j6  = $ctx[6];
        /** @var ParagonIE_Sodium_Core32_Int32 $j7 */
        $j7  = $ctx[7];
        /** @var ParagonIE_Sodium_Core32_Int32 $j8 */
        $j8  = $ctx[8];
        /** @var ParagonIE_Sodium_Core32_Int32 $j9 */
        $j9  = $ctx[9];
        /** @var ParagonIE_Sodium_Core32_Int32 $j10 */
        $j10 = $ctx[10];
        /** @var ParagonIE_Sodium_Core32_Int32 $j11 */
        $j11 = $ctx[11];
        /** @var ParagonIE_Sodium_Core32_Int32 $j12 */
        $j12 = $ctx[12];
        /** @var ParagonIE_Sodium_Core32_Int32 $j13 */
        $j13 = $ctx[13];
        /** @var ParagonIE_Sodium_Core32_Int32 $j14 */
        $j14 = $ctx[14];
        /** @var ParagonIE_Sodium_Core32_Int32 $j15 */
        $j15 = $ctx[15];

        $c = '';
        for (;;) {
            if ($bytes < 64) {
                $message .= str_repeat("\x00", 64 - $bytes);
            }

            $x0 =  clone $j0;
            $x1 =  clone $j1;
            $x2 =  clone $j2;
            $x3 =  clone $j3;
            $x4 =  clone $j4;
            $x5 =  clone $j5;
            $x6 =  clone $j6;
            $x7 =  clone $j7;
            $x8 =  clone $j8;
            $x9 =  clone $j9;
            $x10 = clone $j10;
            $x11 = clone $j11;
            $x12 = clone $j12;
            $x13 = clone $j13;
            $x14 = clone $j14;
            $x15 = clone $j15;

            # for (i = 20; i > 0; i -= 2) {
            for ($i = 20; $i > 0; $i -= 2) {
                # QUARTERROUND( x0,  x4,  x8,  x12)
                list($x0, $x4, $x8, $x12) = self::quarterRound($x0, $x4, $x8, $x12);

                # QUARTERROUND( x1,  x5,  x9,  x13)
                list($x1, $x5, $x9, $x13) = self::quarterRound($x1, $x5, $x9, $x13);

                # QUARTERROUND( x2,  x6,  x10,  x14)
                list($x2, $x6, $x10, $x14) = self::quarterRound($x2, $x6, $x10, $x14);

                # QUARTERROUND( x3,  x7,  x11,  x15)
                list($x3, $x7, $x11, $x15) = self::quarterRound($x3, $x7, $x11, $x15);

                # QUARTERROUND( x0,  x5,  x10,  x15)
                list($x0, $x5, $x10, $x15) = self::quarterRound($x0, $x5, $x10, $x15);

                # QUARTERROUND( x1,  x6,  x11,  x12)
                list($x1, $x6, $x11, $x12) = self::quarterRound($x1, $x6, $x11, $x12);

                # QUARTERROUND( x2,  x7,  x8,  x13)
                list($x2, $x7, $x8, $x13) = self::quarterRound($x2, $x7, $x8, $x13);

                # QUARTERROUND( x3,  x4,  x9,  x14)
                list($x3, $x4, $x9, $x14) = self::quarterRound($x3, $x4, $x9, $x14);
            }
            /*
            x0 = PLUS(x0, j0);
            x1 = PLUS(x1, j1);
            x2 = PLUS(x2, j2);
            x3 = PLUS(x3, j3);
            x4 = PLUS(x4, j4);
            x5 = PLUS(x5, j5);
            x6 = PLUS(x6, j6);
            x7 = PLUS(x7, j7);
            x8 = PLUS(x8, j8);
            x9 = PLUS(x9, j9);
            x10 = PLUS(x10, j10);
            x11 = PLUS(x11, j11);
            x12 = PLUS(x12, j12);
            x13 = PLUS(x13, j13);
            x14 = PLUS(x14, j14);
            x15 = PLUS(x15, j15);
            */
            $x0 = $x0->addInt32($j0);
            $x1 = $x1->addInt32($j1);
            $x2 = $x2->addInt32($j2);
            $x3 = $x3->addInt32($j3);
            $x4 = $x4->addInt32($j4);
            $x5 = $x5->addInt32($j5);
            $x6 = $x6->addInt32($j6);
            $x7 = $x7->addInt32($j7);
            $x8 = $x8->addInt32($j8);
            $x9 = $x9->addInt32($j9);
            $x10 = $x10->addInt32($j10);
            $x11 = $x11->addInt32($j11);
            $x12 = $x12->addInt32($j12);
            $x13 = $x13->addInt32($j13);
            $x14 = $x14->addInt32($j14);
            $x15 = $x15->addInt32($j15);

            /*
            x0 = XOR(x0, LOAD32_LE(m + 0));
            x1 = XOR(x1, LOAD32_LE(m + 4));
            x2 = XOR(x2, LOAD32_LE(m + 8));
            x3 = XOR(x3, LOAD32_LE(m + 12));
            x4 = XOR(x4, LOAD32_LE(m + 16));
            x5 = XOR(x5, LOAD32_LE(m + 20));
            x6 = XOR(x6, LOAD32_LE(m + 24));
            x7 = XOR(x7, LOAD32_LE(m + 28));
            x8 = XOR(x8, LOAD32_LE(m + 32));
            x9 = XOR(x9, LOAD32_LE(m + 36));
            x10 = XOR(x10, LOAD32_LE(m + 40));
            x11 = XOR(x11, LOAD32_LE(m + 44));
            x12 = XOR(x12, LOAD32_LE(m + 48));
            x13 = XOR(x13, LOAD32_LE(m + 52));
            x14 = XOR(x14, LOAD32_LE(m + 56));
            x15 = XOR(x15, LOAD32_LE(m + 60));
            */
            $x0  =  $x0->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message,  0, 4)));
            $x1  =  $x1->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message,  4, 4)));
            $x2  =  $x2->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message,  8, 4)));
            $x3  =  $x3->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 12, 4)));
            $x4  =  $x4->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 16, 4)));
            $x5  =  $x5->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 20, 4)));
            $x6  =  $x6->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 24, 4)));
            $x7  =  $x7->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 28, 4)));
            $x8  =  $x8->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 32, 4)));
            $x9  =  $x9->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 36, 4)));
            $x10 = $x10->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 40, 4)));
            $x11 = $x11->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 44, 4)));
            $x12 = $x12->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 48, 4)));
            $x13 = $x13->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 52, 4)));
            $x14 = $x14->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 56, 4)));
            $x15 = $x15->xorInt32(ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 60, 4)));

            /*
                j12 = PLUSONE(j12);
                if (!j12) {
                    j13 = PLUSONE(j13);
                }
             */
            /** @var ParagonIE_Sodium_Core32_Int32 $j12 */
            $j12 = $j12->addInt(1);
            if ($j12->limbs[0] === 0 && $j12->limbs[1] === 0) {
                $j13 = $j13->addInt(1);
            }

            /*
            STORE32_LE(c + 0, x0);
            STORE32_LE(c + 4, x1);
            STORE32_LE(c + 8, x2);
            STORE32_LE(c + 12, x3);
            STORE32_LE(c + 16, x4);
            STORE32_LE(c + 20, x5);
            STORE32_LE(c + 24, x6);
            STORE32_LE(c + 28, x7);
            STORE32_LE(c + 32, x8);
            STORE32_LE(c + 36, x9);
            STORE32_LE(c + 40, x10);
            STORE32_LE(c + 44, x11);
            STORE32_LE(c + 48, x12);
            STORE32_LE(c + 52, x13);
            STORE32_LE(c + 56, x14);
            STORE32_LE(c + 60, x15);
            */

            $block = $x0->toReverseString() .
                $x1->toReverseString() .
                $x2->toReverseString() .
                $x3->toReverseString() .
                $x4->toReverseString() .
                $x5->toReverseString() .
                $x6->toReverseString() .
                $x7->toReverseString() .
                $x8->toReverseString() .
                $x9->toReverseString() .
                $x10->toReverseString() .
                $x11->toReverseString() .
                $x12->toReverseString() .
                $x13->toReverseString() .
                $x14->toReverseString() .
                $x15->toReverseString();

            /* Partial block */
            if ($bytes < 64) {
                $c .= self::substr($block, 0, $bytes);
                break;
            }

            /* Full block */
            $c .= $block;
            $bytes -= 64;
            if ($bytes <= 0) {
                break;
            }
            $message = self::substr($message, 64);
        }
        /* end for(;;) loop */

        $ctx[12] = $j12;
        $ctx[13] = $j13;
        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function stream($len = 64, $nonce = '', $key = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_Ctx($key, $nonce),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ietfStream($len, $nonce = '', $key = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_IetfCtx($key, $nonce),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ietfStreamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_IetfCtx($key, $nonce, $ic),
            $message
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function streamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_Ctx($key, $nonce, $ic),
            $message
        );
    }
}
vendor/paragonie/sodium_compat/src/Core32/XChaCha20.php000064400000003357152177723700016632 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_XChaCha20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_XChaCha20
 */
class ParagonIE_Sodium_Core32_XChaCha20 extends ParagonIE_Sodium_Core32_HChaCha20
{
    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function stream($len = 64, $nonce = '', $key = '')
    {
        if (self::strlen($nonce) !== 24) {
            throw new SodiumException('Nonce must be 24 bytes long');
        }
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_Ctx(
                self::hChaCha20(
                    self::substr($nonce, 0, 16),
                    $key
                ),
                self::substr($nonce, 16, 8)
            ),
            str_repeat("\x00", $len)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @param string $ic
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function streamXorIc($message, $nonce = '', $key = '', $ic = '')
    {
        if (self::strlen($nonce) !== 24) {
            throw new SodiumException('Nonce must be 24 bytes long');
        }
        return self::encryptBytes(
            new ParagonIE_Sodium_Core32_ChaCha20_Ctx(
                self::hChaCha20(self::substr($nonce, 0, 16), $key),
                self::substr($nonce, 16, 8),
                $ic
            ),
            $message
        );
    }
}
vendor/paragonie/sodium_compat/src/Core32/Int64.php000064400000064543152177723700016141 0ustar00<?php

/**
 * Class ParagonIE_Sodium_Core32_Int64
 *
 * Encapsulates a 64-bit integer.
 *
 * These are immutable. It always returns a new instance.
 */
class ParagonIE_Sodium_Core32_Int64
{
    /**
     * @var array<int, int> - four 16-bit integers
     */
    public $limbs = array(0, 0, 0, 0);

    /**
     * @var int
     */
    public $overflow = 0;

    /**
     * @var bool
     */
    public $unsignedInt = false;

    /**
     * ParagonIE_Sodium_Core32_Int64 constructor.
     * @param array $array
     * @param bool $unsignedInt
     */
    public function __construct($array = array(0, 0, 0, 0), $unsignedInt = false)
    {
        $this->limbs = array(
            (int) $array[0],
            (int) $array[1],
            (int) $array[2],
            (int) $array[3]
        );
        $this->overflow = 0;
        $this->unsignedInt = $unsignedInt;
    }

    /**
     * Adds two int64 objects
     *
     * @param ParagonIE_Sodium_Core32_Int64 $addend
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function addInt64(ParagonIE_Sodium_Core32_Int64 $addend)
    {
        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $i2 = $this->limbs[2];
        $i3 = $this->limbs[3];
        $j0 = $addend->limbs[0];
        $j1 = $addend->limbs[1];
        $j2 = $addend->limbs[2];
        $j3 = $addend->limbs[3];

        $r3 = $i3 + ($j3 & 0xffff);
        $carry = $r3 >> 16;

        $r2 = $i2 + ($j2 & 0xffff) + $carry;
        $carry = $r2 >> 16;

        $r1 = $i1 + ($j1 & 0xffff) + $carry;
        $carry = $r1 >> 16;

        $r0 = $i0 + ($j0 & 0xffff) + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $r2 &= 0xffff;
        $r3 &= 0xffff;

        $return = new ParagonIE_Sodium_Core32_Int64(
            array($r0, $r1, $r2, $r3)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * Adds a normal integer to an int64 object
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function addInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $i2 = $this->limbs[2];
        $i3 = $this->limbs[3];

        $r3 = $i3 + ($int & 0xffff);
        $carry = $r3 >> 16;

        $r2 = $i2 + (($int >> 16) & 0xffff) + $carry;
        $carry = $r2 >> 16;

        $r1 = $i1 + $carry;
        $carry = $r1 >> 16;

        $r0 = $i0 + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $r2 &= 0xffff;
        $r3 &= 0xffff;
        $return = new ParagonIE_Sodium_Core32_Int64(
            array($r0, $r1, $r2, $r3)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param int $b
     * @return int
     */
    public function compareInt($b = 0)
    {
        $gt = 0;
        $eq = 1;

        $i = 4;
        $j = 0;
        while ($i > 0) {
            --$i;
            /** @var int $x1 */
            $x1 = $this->limbs[$i];
            /** @var int $x2 */
            $x2 = ($b >> ($j << 4)) & 0xffff;
            /** int */
            $gt |= (($x2 - $x1) >> 8) & $eq;
            /** int */
            $eq &= (($x2 ^ $x1) - 1) >> 8;
        }
        return ($gt + $gt - $eq) + 1;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isGreaterThan($b = 0)
    {
        return $this->compareInt($b) > 0;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isLessThanInt($b = 0)
    {
        return $this->compareInt($b) < 0;
    }

    /**
     * @param int $hi
     * @param int $lo
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function mask64($hi = 0, $lo = 0)
    {
        /** @var int $a */
        $a = ($hi >> 16) & 0xffff;
        /** @var int $b */
        $b = ($hi) & 0xffff;
        /** @var int $c */
        $c = ($lo >> 16) & 0xffff;
        /** @var int $d */
        $d = ($lo & 0xffff);
        return new ParagonIE_Sodium_Core32_Int64(
            array(
                $this->limbs[0] & $a,
                $this->limbs[1] & $b,
                $this->limbs[2] & $c,
                $this->limbs[3] & $d
            ),
            $this->unsignedInt
        );
    }

    /**
     * @param int $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     */
    public function mulInt($int = 0, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        /** @var int $int */
        $int = (int) $int;
        /** @var int $size */
        $size = (int) $size;

        if (!$size) {
            $size = 63;
        }

        $a = clone $this;
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $ret2 = 0;
        $ret3 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $a2 = $a->limbs[2];
        $a3 = $a->limbs[3];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $mask = -($int & 1);
            $x0 = $a0 & $mask;
            $x1 = $a1 & $mask;
            $x2 = $a2 & $mask;
            $x3 = $a3 & $mask;

            $ret3 += $x3;
            $c = $ret3 >> 16;

            $ret2 += $x2 + $c;
            $c = $ret2 >> 16;

            $ret1 += $x1 + $c;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;
            $ret2 &= 0xffff;
            $ret3 &= 0xffff;

            $a3 = $a3 << 1;
            $x3 = $a3 >> 16;
            $a2 = ($a2 << 1) | $x3;
            $x2 = $a2 >> 16;
            $a1 = ($a1 << 1) | $x2;
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $a2 &= 0xffff;
            $a3 &= 0xffff;

            $int >>= 1;
            $return->limbs[0] = $ret0;
            $return->limbs[1] = $ret1;
            $return->limbs[2] = $ret2;
            $return->limbs[3] = $ret3;
        }
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int64 $A
     * @param ParagonIE_Sodium_Core32_Int64 $B
     * @return array<int, ParagonIE_Sodium_Core32_Int64>
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedInferredReturnType
     */
    public static function ctSelect(
        ParagonIE_Sodium_Core32_Int64 $A,
        ParagonIE_Sodium_Core32_Int64 $B
    ) {
        $a = clone $A;
        $b = clone $B;
        /** @var int $aNeg */
        $aNeg = ($a->limbs[0] >> 15) & 1;
        /** @var int $bNeg */
        $bNeg = ($b->limbs[0] >> 15) & 1;
        /** @var int $m */
        $m = (-($aNeg & $bNeg)) | 1;
        /** @var int $swap */
        $swap = $bNeg & ~$aNeg;
        /** @var int $d */
        $d = -$swap;

        /*
        if ($bNeg && !$aNeg) {
            $a = clone $int;
            $b = clone $this;
        } elseif($bNeg && $aNeg) {
            $a = $this->mulInt(-1);
            $b = $int->mulInt(-1);
        }
         */
        $x = $a->xorInt64($b)->mask64($d, $d);
        return array(
            $a->xorInt64($x)->mulInt($m),
            $b->xorInt64($x)->mulInt($m)
        );
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int64 $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     */
    public function mulInt64(ParagonIE_Sodium_Core32_Int64 $int, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        if (!$size) {
            $size = 63;
        }
        list($a, $b) = self::ctSelect($this, $int);

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $ret2 = 0;
        $ret3 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $a2 = $a->limbs[2];
        $a3 = $a->limbs[3];
        $b0 = $b->limbs[0];
        $b1 = $b->limbs[1];
        $b2 = $b->limbs[2];
        $b3 = $b->limbs[3];

        /** @var int $size */
        /** @var int $i */
        for ($i = (int) $size; $i >= 0; --$i) {
            $mask = -($b3 & 1);
            $x0 = $a0 & $mask;
            $x1 = $a1 & $mask;
            $x2 = $a2 & $mask;
            $x3 = $a3 & $mask;

            $ret3 += $x3;
            $c = $ret3 >> 16;

            $ret2 += $x2 + $c;
            $c = $ret2 >> 16;

            $ret1 += $x1 + $c;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;
            $ret2 &= 0xffff;
            $ret3 &= 0xffff;

            $a3 = $a3 << 1;
            $x3 = $a3 >> 16;
            $a2 = ($a2 << 1) | $x3;
            $x2 = $a2 >> 16;
            $a1 = ($a1 << 1) | $x2;
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $a2 &= 0xffff;
            $a3 &= 0xffff;

            $x0 = ($b0 & 1) << 16;
            $x1 = ($b1 & 1) << 16;
            $x2 = ($b2 & 1) << 16;

            $b0 = ($b0 >> 1);
            $b1 = (($b1 | $x0) >> 1);
            $b2 = (($b2 | $x1) >> 1);
            $b3 = (($b3 | $x2) >> 1);

            $b0 &= 0xffff;
            $b1 &= 0xffff;
            $b2 &= 0xffff;
            $b3 &= 0xffff;

        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;
        $return->limbs[2] = $ret2;
        $return->limbs[3] = $ret3;

        return $return;
    }

    /**
     * OR this 64-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function orInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] | $b->limbs[0]),
            (int) ($this->limbs[1] | $b->limbs[1]),
            (int) ($this->limbs[2] | $b->limbs[2]),
            (int) ($this->limbs[3] | $b->limbs[3])
        );
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 3;
            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            for ($i = 3; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i + $idx_shift) & 3;
                /** @var int $k */
                $k = ($i + $idx_shift + 1) & 3;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) << $sub_shift)
                            |
                        ((int) ($myLimbs[$k]) >> (16 - $sub_shift))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * Rotate to the right
     *
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        /** @var ParagonIE_Sodium_Core32_Int64 $return */
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 3;
            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            for ($i = 3; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i - $idx_shift) & 3;
                /** @var int $k */
                $k = ($i - $idx_shift - 1) & 3;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) >> (int) ($sub_shift))
                            |
                        ((int) ($myLimbs[$k]) << (16 - (int) ($sub_shift)))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }
    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;

        if ($c >= 16) {
            if ($c >= 48) {
                $return->limbs = array(
                    $this->limbs[3], 0, 0, 0
                );
            } elseif ($c >= 32) {
                $return->limbs = array(
                    $this->limbs[2], $this->limbs[3], 0, 0
                );
            } else {
                $return->limbs = array(
                    $this->limbs[1], $this->limbs[2], $this->limbs[3], 0
                );
            }
            return $return->shiftLeft($c & 15);
        }
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftRight(-$c);
        } else {
            if (is_null($c)) {
                throw new TypeError();
            }
            /** @var int $carry */
            $carry = 0;
            for ($i = 3; $i >= 0; --$i) {
                /** @var int $tmp */
                $tmp = ($this->limbs[$i] << $c) | ($carry & 0xffff);
                $return->limbs[$i] = (int) ($tmp & 0xffff);
                /** @var int $carry */
                $carry = $tmp >> 16;
            }
        }
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */

        $negative = -(($this->limbs[0] >> 15) & 1);
        if ($c >= 16) {
            if ($c >= 48) {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0]
                );
            } elseif ($c >= 32) {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0],
                    (int) $this->limbs[1]
                );
            } else {
                $return->limbs = array(
                    (int) ($negative & 0xffff),
                    (int) $this->limbs[0],
                    (int) $this->limbs[1],
                    (int) $this->limbs[2]
                );
            }
            return $return->shiftRight($c & 15);
        }

        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            return $this->shiftLeft(-$c);
        } else {
            if (!is_int($c)) {
                throw new TypeError();
            }
            /** @var int $carryRight */
            $carryRight = ($negative & 0xffff);
            $mask = (int) (((1 << ($c + 1)) - 1) & 0xffff);
            for ($i = 0; $i < 4; ++$i) {
                $return->limbs[$i] = (int) (
                    (($this->limbs[$i] >> $c) | ($carryRight << (16 - $c))) & 0xffff
                );
                $carryRight = (int) ($this->limbs[$i] & $mask);
            }
        }
        return $return;
    }


    /**
     * Subtract a normal integer from an int64 object.
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public function subInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        $int = (int) $int;

        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $carry */
        $carry = 0;
        for ($i = 3; $i >= 0; --$i) {
            /** @var int $tmp */
            $tmp = $this->limbs[$i] - (($int >> 16) & 0xffff) + $carry;
            /** @var int $carry */
            $carry = $tmp >> 16;
            $return->limbs[$i] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * The difference between two Int64 objects.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function subInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        /** @var int $carry */
        $carry = 0;
        for ($i = 3; $i >= 0; --$i) {
            /** @var int $tmp */
            $tmp = $this->limbs[$i] - $b->limbs[$i] + $carry;
            /** @var int $carry */
            $carry = ($tmp >> 16);
            $return->limbs[$i] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * XOR this 64-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int64 $b
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function xorInt64(ParagonIE_Sodium_Core32_Int64 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] ^ $b->limbs[0]),
            (int) ($this->limbs[1] ^ $b->limbs[1]),
            (int) ($this->limbs[2] ^ $b->limbs[2]),
            (int) ($this->limbs[3] ^ $b->limbs[3])
        );
        return $return;
    }

    /**
     * @param int $low
     * @param int $high
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInts($low, $high)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($low, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($high, 'int', 2);

        $high = (int) $high;
        $low = (int) $low;
        return new ParagonIE_Sodium_Core32_Int64(
            array(
                (int) (($high >> 16) & 0xffff),
                (int) ($high & 0xffff),
                (int) (($low >> 16) & 0xffff),
                (int) ($low & 0xffff)
            )
        );
    }

    /**
     * @param int $low
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInt($low)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($low, 'int', 1);
        $low = (int) $low;

        return new ParagonIE_Sodium_Core32_Int64(
            array(
                0,
                0,
                (int) (($low >> 16) & 0xffff),
                (int) ($low & 0xffff)
            )
        );
    }

    /**
     * @return int
     */
    public function toInt()
    {
        return (int) (
            (($this->limbs[2] & 0xffff) << 16)
                |
            ($this->limbs[3] & 0xffff)
        );
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 8) {
            throw new RangeException(
                'String must be 8 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int64();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff);
        $return->limbs[2]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[4]) & 0xff) << 8);
        $return->limbs[2] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[5]) & 0xff);
        $return->limbs[3]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[6]) & 0xff) << 8);
        $return->limbs[3] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[7]) & 0xff);
        return $return;
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromReverseString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 8) {
            throw new RangeException(
                'String must be 8 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int64();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[7]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[6]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[5]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[4]) & 0xff);
        $return->limbs[2]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff) << 8);
        $return->limbs[2] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff);
        $return->limbs[3]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff) << 8);
        $return->limbs[3] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff);
        return $return;
    }

    /**
     * @return array<int, int>
     */
    public function toArray()
    {
        return array(
            (int) ((($this->limbs[0] & 0xffff) << 16) | ($this->limbs[1] & 0xffff)),
            (int) ((($this->limbs[2] & 0xffff) << 16) | ($this->limbs[3] & 0xffff))
        );
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function toInt32()
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs[0] = (int) ($this->limbs[2]);
        $return->limbs[1] = (int) ($this->limbs[3]);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = (int) (ParagonIE_Sodium_Core32_Util::abs($this->limbs[1], 16) & 0xffff);
        return $return;
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function toInt64()
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->limbs[0] = (int) ($this->limbs[0]);
        $return->limbs[1] = (int) ($this->limbs[1]);
        $return->limbs[2] = (int) ($this->limbs[2]);
        $return->limbs[3] = (int) ($this->limbs[3]);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = ParagonIE_Sodium_Core32_Util::abs($this->overflow);
        return $return;
    }

    /**
     * @param bool $bool
     * @return self
     */
    public function setUnsignedInt($bool = false)
    {
        $this->unsignedInt = !empty($bool);
        return $this;
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[2] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[2] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[3] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[3] & 0xff);
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toReverseString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[3] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[3] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[2] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[2] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff);
    }

    /**
     * @return string
     */
    public function __toString()
    {
        try {
            return $this->toString();
        } catch (TypeError $ex) {
            // PHP engine can't handle exceptions from __toString()
            return '';
        }
    }
}
vendor/paragonie/sodium_compat/src/Core32/ChaCha20/IetfCtx.php000064400000002737152177723700020051 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_ChaCha20_IetfCtx', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_ChaCha20_IetfCtx
 */
class ParagonIE_Sodium_Core32_ChaCha20_IetfCtx extends ParagonIE_Sodium_Core32_ChaCha20_Ctx
{
    /**
     * ParagonIE_Sodium_Core_ChaCha20_IetfCtx constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key     ChaCha20 key.
     * @param string $iv      Initialization Vector (a.k.a. nonce).
     * @param string $counter The initial counter value.
     *                        Defaults to 4 0x00 bytes.
     * @throws InvalidArgumentException
     * @throws SodiumException
     * @throws TypeError
     */
    public function __construct($key = '', $iv = '', $counter = '')
    {
        if (self::strlen($iv) !== 12) {
            throw new InvalidArgumentException('ChaCha20 expects a 96-bit nonce in IETF mode.');
        }
        parent::__construct($key, self::substr($iv, 0, 8), $counter);

        if (!empty($counter)) {
            $this->container[12] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($counter, 0, 4));
        }
        $this->container[13] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($iv, 0, 4));
        $this->container[14] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($iv, 4, 4));
        $this->container[15] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($iv, 8, 4));
    }
}
vendor/paragonie/sodium_compat/src/Core32/ChaCha20/Ctx.php000064400000011276152177723700017237 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_ChaCha20_Ctx', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_ChaCha20_Ctx
 */
class ParagonIE_Sodium_Core32_ChaCha20_Ctx extends ParagonIE_Sodium_Core32_Util implements ArrayAccess
{
    /**
     * @var SplFixedArray internally, <int, ParagonIE_Sodium_Core32_Int32>
     */
    protected $container;

    /**
     * ParagonIE_Sodium_Core_ChaCha20_Ctx constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key     ChaCha20 key.
     * @param string $iv      Initialization Vector (a.k.a. nonce).
     * @param string $counter The initial counter value.
     *                        Defaults to 8 0x00 bytes.
     * @throws InvalidArgumentException
     * @throws SodiumException
     * @throws TypeError
     */
    public function __construct($key = '', $iv = '', $counter = '')
    {
        if (self::strlen($key) !== 32) {
            throw new InvalidArgumentException('ChaCha20 expects a 256-bit key.');
        }
        if (self::strlen($iv) !== 8) {
            throw new InvalidArgumentException('ChaCha20 expects a 64-bit nonce.');
        }
        $this->container = new SplFixedArray(16);

        /* "expand 32-byte k" as per ChaCha20 spec */
        $this->container[0]  = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
        $this->container[1]  = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
        $this->container[2]  = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
        $this->container[3]  = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));

        $this->container[4]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 0, 4));
        $this->container[5]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 4, 4));
        $this->container[6]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 8, 4));
        $this->container[7]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 12, 4));
        $this->container[8]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 16, 4));
        $this->container[9]  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 20, 4));
        $this->container[10] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 24, 4));
        $this->container[11] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 28, 4));

        if (empty($counter)) {
            $this->container[12] = new ParagonIE_Sodium_Core32_Int32();
            $this->container[13] = new ParagonIE_Sodium_Core32_Int32();
        } else {
            $this->container[12] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($counter, 0, 4));
            $this->container[13] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($counter, 4, 4));
        }
        $this->container[14] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($iv, 0, 4));
        $this->container[15] = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($iv, 4, 4));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $offset
     * @param int|ParagonIE_Sodium_Core32_Int32 $value
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        if (!is_int($offset)) {
            throw new InvalidArgumentException('Expected an integer');
        }
        if ($value instanceof ParagonIE_Sodium_Core32_Int32) {
            /*
        } elseif (is_int($value)) {
            $value = ParagonIE_Sodium_Core32_Int32::fromInt($value);
            */
        } else {
            throw new InvalidArgumentException('Expected an integer');
        }
        $this->container[$offset] = $value;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return bool
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return mixed|null
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetGet($offset)
    {
        return isset($this->container[$offset])
            ? $this->container[$offset]
            : null;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Util.php000064400000000321152177723700016132 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Util', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_Util
 */
abstract class ParagonIE_Sodium_Core32_Util extends ParagonIE_Sodium_Core_Util
{

}
vendor/paragonie/sodium_compat/src/Core32/Curve25519.php000064400000407142152177723700016723 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519
 *
 * Implements Curve25519 core functions
 *
 * Based on the ref10 curve25519 code provided by libsodium
 *
 * @ref https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c
 */
abstract class ParagonIE_Sodium_Core32_Curve25519 extends ParagonIE_Sodium_Core32_Curve25519_H
{
    /**
     * Get a field element of size 10 with a value of 0
     *
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_0()
    {
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32()
            )
        );
    }

    /**
     * Get a field element of size 10 with a value of 1
     *
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_1()
    {
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                ParagonIE_Sodium_Core32_Int32::fromInt(1),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32(),
                new ParagonIE_Sodium_Core32_Int32()
            )
        );
    }

    /**
     * Add two field elements.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_add(
        ParagonIE_Sodium_Core32_Curve25519_Fe $f,
        ParagonIE_Sodium_Core32_Curve25519_Fe $g
    ) {
        $arr = array();
        for ($i = 0; $i < 10; ++$i) {
            $arr[$i] = $f[$i]->addInt32($g[$i]);
        }
        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $arr */
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray($arr);
    }

    /**
     * Constant-time conditional move.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $g
     * @param int $b
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_cmov(
        ParagonIE_Sodium_Core32_Curve25519_Fe $f,
        ParagonIE_Sodium_Core32_Curve25519_Fe $g,
        $b = 0
    ) {
        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $h */
        $h = array();
        for ($i = 0; $i < 10; ++$i) {
            if (!($f[$i] instanceof ParagonIE_Sodium_Core32_Int32)) {
                throw new TypeError('Expected Int32');
            }
            if (!($g[$i] instanceof ParagonIE_Sodium_Core32_Int32)) {
                throw new TypeError('Expected Int32');
            }
            $h[$i] = $f[$i]->xorInt32(
                $f[$i]->xorInt32($g[$i])->mask($b)
            );
        }
        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $h */
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray($h);
    }

    /**
     * Create a copy of a field element.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public static function fe_copy(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        $h = clone $f;
        return $h;
    }

    /**
     * Give: 32-byte string.
     * Receive: A field element object to use for internal calculations.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws RangeException
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_frombytes($s)
    {
        if (self::strlen($s) !== 32) {
            throw new RangeException('Expected a 32-byte string.');
        }
        /** @var ParagonIE_Sodium_Core32_Int32 $h0 */
        $h0 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_4($s)
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h1 */
        $h1 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 4, 3)) << 6
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h2 */
        $h2 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 7, 3)) << 5
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h3 */
        $h3 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 10, 3)) << 3
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h4 */
        $h4 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 13, 3)) << 2
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h5 */
        $h5 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_4(self::substr($s, 16, 4))
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h6 */
        $h6 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 20, 3)) << 7
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h7 */
        $h7 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 23, 3)) << 5
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h8 */
        $h8 = ParagonIE_Sodium_Core32_Int32::fromInt(
            self::load_3(self::substr($s, 26, 3)) << 4
        );
        /** @var ParagonIE_Sodium_Core32_Int32 $h9 */
        $h9 = ParagonIE_Sodium_Core32_Int32::fromInt(
            (self::load_3(self::substr($s, 29, 3)) & 8388607) << 2
        );

        $carry9 = $h9->addInt(1 << 24)->shiftRight(25);
        $h0 = $h0->addInt32($carry9->mulInt(19, 5));
        $h9 = $h9->subInt32($carry9->shiftLeft(25));

        $carry1 = $h1->addInt(1 << 24)->shiftRight(25);
        $h2 = $h2->addInt32($carry1);
        $h1 = $h1->subInt32($carry1->shiftLeft(25));

        $carry3 = $h3->addInt(1 << 24)->shiftRight(25);
        $h4 = $h4->addInt32($carry3);
        $h3 = $h3->subInt32($carry3->shiftLeft(25));

        $carry5 = $h5->addInt(1 << 24)->shiftRight(25);
        $h6 = $h6->addInt32($carry5);
        $h5 = $h5->subInt32($carry5->shiftLeft(25));

        $carry7 = $h7->addInt(1 << 24)->shiftRight(25);
        $h8 = $h8->addInt32($carry7);
        $h7 = $h7->subInt32($carry7->shiftLeft(25));

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt32($carry0);
        $h0 = $h0->subInt32($carry0->shiftLeft(26));

        $carry2 = $h2->addInt(1 << 25)->shiftRight(26);
        $h3 = $h3->addInt32($carry2);
        $h2 = $h2->subInt32($carry2->shiftLeft(26));

        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt32($carry4);
        $h4 = $h4->subInt32($carry4->shiftLeft(26));

        $carry6 = $h6->addInt(1 << 25)->shiftRight(26);
        $h7 = $h7->addInt32($carry6);
        $h6 = $h6->subInt32($carry6->shiftLeft(26));

        $carry8 = $h8->addInt(1 << 25)->shiftRight(26);
        $h9 = $h9->addInt32($carry8);
        $h8 = $h8->subInt32($carry8->shiftLeft(26));

        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array($h0, $h1, $h2,$h3, $h4, $h5, $h6, $h7, $h8, $h9)
        );
    }

    /**
     * Convert a field element to a byte string.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $h
     * @return string
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_tobytes(ParagonIE_Sodium_Core32_Curve25519_Fe $h)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int64[] $f
         * @var ParagonIE_Sodium_Core32_Int64 $q
         */
        $f = array();

        for ($i = 0; $i < 10; ++$i) {
            $f[$i] = $h[$i]->toInt64();
        }

        $q = $f[9]->mulInt(19, 5)->addInt(1 << 14)->shiftRight(25)
            ->addInt64($f[0])->shiftRight(26)
            ->addInt64($f[1])->shiftRight(25)
            ->addInt64($f[2])->shiftRight(26)
            ->addInt64($f[3])->shiftRight(25)
            ->addInt64($f[4])->shiftRight(26)
            ->addInt64($f[5])->shiftRight(25)
            ->addInt64($f[6])->shiftRight(26)
            ->addInt64($f[7])->shiftRight(25)
            ->addInt64($f[8])->shiftRight(26)
            ->addInt64($f[9])->shiftRight(25);

        $f[0] = $f[0]->addInt64($q->mulInt(19, 5));

        $carry0 = $f[0]->shiftRight(26);
        $f[1] = $f[1]->addInt64($carry0);
        $f[0] = $f[0]->subInt64($carry0->shiftLeft(26));

        $carry1 = $f[1]->shiftRight(25);
        $f[2] = $f[2]->addInt64($carry1);
        $f[1] = $f[1]->subInt64($carry1->shiftLeft(25));

        $carry2 = $f[2]->shiftRight(26);
        $f[3] = $f[3]->addInt64($carry2);
        $f[2] = $f[2]->subInt64($carry2->shiftLeft(26));

        $carry3 = $f[3]->shiftRight(25);
        $f[4] = $f[4]->addInt64($carry3);
        $f[3] = $f[3]->subInt64($carry3->shiftLeft(25));

        $carry4 = $f[4]->shiftRight(26);
        $f[5] = $f[5]->addInt64($carry4);
        $f[4] = $f[4]->subInt64($carry4->shiftLeft(26));

        $carry5 = $f[5]->shiftRight(25);
        $f[6] = $f[6]->addInt64($carry5);
        $f[5] = $f[5]->subInt64($carry5->shiftLeft(25));

        $carry6 = $f[6]->shiftRight(26);
        $f[7] = $f[7]->addInt64($carry6);
        $f[6] = $f[6]->subInt64($carry6->shiftLeft(26));

        $carry7 = $f[7]->shiftRight(25);
        $f[8] = $f[8]->addInt64($carry7);
        $f[7] = $f[7]->subInt64($carry7->shiftLeft(25));

        $carry8 = $f[8]->shiftRight(26);
        $f[9] = $f[9]->addInt64($carry8);
        $f[8] = $f[8]->subInt64($carry8->shiftLeft(26));

        $carry9 = $f[9]->shiftRight(25);
        $f[9] = $f[9]->subInt64($carry9->shiftLeft(25));

        /** @var int $h0 */
        $h0 = $f[0]->toInt32()->toInt();
        /** @var int $h1 */
        $h1 = $f[1]->toInt32()->toInt();
        /** @var int $h2 */
        $h2 = $f[2]->toInt32()->toInt();
        /** @var int $h3 */
        $h3 = $f[3]->toInt32()->toInt();
        /** @var int $h4 */
        $h4 = $f[4]->toInt32()->toInt();
        /** @var int $h5 */
        $h5 = $f[5]->toInt32()->toInt();
        /** @var int $h6 */
        $h6 = $f[6]->toInt32()->toInt();
        /** @var int $h7 */
        $h7 = $f[7]->toInt32()->toInt();
        /** @var int $h8 */
        $h8 = $f[8]->toInt32()->toInt();
        /** @var int $h9 */
        $h9 = $f[9]->toInt32()->toInt();

        /**
         * @var array<int, int>
         */
        $s = array(
            (int) (($h0 >> 0) & 0xff),
            (int) (($h0 >> 8) & 0xff),
            (int) (($h0 >> 16) & 0xff),
            (int) ((($h0 >> 24) | ($h1 << 2)) & 0xff),
            (int) (($h1 >> 6) & 0xff),
            (int) (($h1 >> 14) & 0xff),
            (int) ((($h1 >> 22) | ($h2 << 3)) & 0xff),
            (int) (($h2 >> 5) & 0xff),
            (int) (($h2 >> 13) & 0xff),
            (int) ((($h2 >> 21) | ($h3 << 5)) & 0xff),
            (int) (($h3 >> 3) & 0xff),
            (int) (($h3 >> 11) & 0xff),
            (int) ((($h3 >> 19) | ($h4 << 6)) & 0xff),
            (int) (($h4 >> 2) & 0xff),
            (int) (($h4 >> 10) & 0xff),
            (int) (($h4 >> 18) & 0xff),
            (int) (($h5 >> 0) & 0xff),
            (int) (($h5 >> 8) & 0xff),
            (int) (($h5 >> 16) & 0xff),
            (int) ((($h5 >> 24) | ($h6 << 1)) & 0xff),
            (int) (($h6 >> 7) & 0xff),
            (int) (($h6 >> 15) & 0xff),
            (int) ((($h6 >> 23) | ($h7 << 3)) & 0xff),
            (int) (($h7 >> 5) & 0xff),
            (int) (($h7 >> 13) & 0xff),
            (int) ((($h7 >> 21) | ($h8 << 4)) & 0xff),
            (int) (($h8 >> 4) & 0xff),
            (int) (($h8 >> 12) & 0xff),
            (int) ((($h8 >> 20) | ($h9 << 6)) & 0xff),
            (int) (($h9 >> 2) & 0xff),
            (int) (($h9 >> 10) & 0xff),
            (int) (($h9 >> 18) & 0xff)
        );
        return self::intArrayToString($s);
    }

    /**
     * Is a field element negative? (1 = yes, 0 = no. Used in calculations.)
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return int
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_isnegative(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        $str = self::fe_tobytes($f);
        return (int) (self::chrToInt($str[0]) & 1);
    }

    /**
     * Returns 0 if this field element results in all NUL bytes.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_isnonzero(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        static $zero;
        if ($zero === null) {
            $zero = str_repeat("\x00", 32);
        }
        /** @var string $str */
        $str = self::fe_tobytes($f);
        /** @var string $zero */
        return !self::verify_32($str, $zero);
    }

    /**
     * Multiply two field elements
     *
     * h = f * g
     *
     * @internal You should not use this directly from another application
     *
     * @security Is multiplication a source of timing leaks? If so, can we do
     *           anything to prevent that from happening?
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_mul(
        ParagonIE_Sodium_Core32_Curve25519_Fe $f,
        ParagonIE_Sodium_Core32_Curve25519_Fe $g
    ) {
        /**
         * @var ParagonIE_Sodium_Core32_Int32[] $f
         * @var ParagonIE_Sodium_Core32_Int32[] $g
         * @var ParagonIE_Sodium_Core32_Int64 $f0
         * @var ParagonIE_Sodium_Core32_Int64 $f1
         * @var ParagonIE_Sodium_Core32_Int64 $f2
         * @var ParagonIE_Sodium_Core32_Int64 $f3
         * @var ParagonIE_Sodium_Core32_Int64 $f4
         * @var ParagonIE_Sodium_Core32_Int64 $f5
         * @var ParagonIE_Sodium_Core32_Int64 $f6
         * @var ParagonIE_Sodium_Core32_Int64 $f7
         * @var ParagonIE_Sodium_Core32_Int64 $f8
         * @var ParagonIE_Sodium_Core32_Int64 $f9
         * @var ParagonIE_Sodium_Core32_Int64 $g0
         * @var ParagonIE_Sodium_Core32_Int64 $g1
         * @var ParagonIE_Sodium_Core32_Int64 $g2
         * @var ParagonIE_Sodium_Core32_Int64 $g3
         * @var ParagonIE_Sodium_Core32_Int64 $g4
         * @var ParagonIE_Sodium_Core32_Int64 $g5
         * @var ParagonIE_Sodium_Core32_Int64 $g6
         * @var ParagonIE_Sodium_Core32_Int64 $g7
         * @var ParagonIE_Sodium_Core32_Int64 $g8
         * @var ParagonIE_Sodium_Core32_Int64 $g9
         */
        $f0 = $f[0]->toInt64();
        $f1 = $f[1]->toInt64();
        $f2 = $f[2]->toInt64();
        $f3 = $f[3]->toInt64();
        $f4 = $f[4]->toInt64();
        $f5 = $f[5]->toInt64();
        $f6 = $f[6]->toInt64();
        $f7 = $f[7]->toInt64();
        $f8 = $f[8]->toInt64();
        $f9 = $f[9]->toInt64();
        $g0 = $g[0]->toInt64();
        $g1 = $g[1]->toInt64();
        $g2 = $g[2]->toInt64();
        $g3 = $g[3]->toInt64();
        $g4 = $g[4]->toInt64();
        $g5 = $g[5]->toInt64();
        $g6 = $g[6]->toInt64();
        $g7 = $g[7]->toInt64();
        $g8 = $g[8]->toInt64();
        $g9 = $g[9]->toInt64();
        $g1_19 = $g1->mulInt(19, 5); /* 2^4 <= 19 <= 2^5, but we only want 5 bits */
        $g2_19 = $g2->mulInt(19, 5);
        $g3_19 = $g3->mulInt(19, 5);
        $g4_19 = $g4->mulInt(19, 5);
        $g5_19 = $g5->mulInt(19, 5);
        $g6_19 = $g6->mulInt(19, 5);
        $g7_19 = $g7->mulInt(19, 5);
        $g8_19 = $g8->mulInt(19, 5);
        $g9_19 = $g9->mulInt(19, 5);
        /** @var ParagonIE_Sodium_Core32_Int64 $f1_2 */
        $f1_2 = $f1->shiftLeft(1);
        /** @var ParagonIE_Sodium_Core32_Int64 $f3_2 */
        $f3_2 = $f3->shiftLeft(1);
        /** @var ParagonIE_Sodium_Core32_Int64 $f5_2 */
        $f5_2 = $f5->shiftLeft(1);
        /** @var ParagonIE_Sodium_Core32_Int64 $f7_2 */
        $f7_2 = $f7->shiftLeft(1);
        /** @var ParagonIE_Sodium_Core32_Int64 $f9_2 */
        $f9_2 = $f9->shiftLeft(1);
        $f0g0    = $f0->mulInt64($g0, 27);
        $f0g1    = $f0->mulInt64($g1, 27);
        $f0g2    = $f0->mulInt64($g2, 27);
        $f0g3    = $f0->mulInt64($g3, 27);
        $f0g4    = $f0->mulInt64($g4, 27);
        $f0g5    = $f0->mulInt64($g5, 27);
        $f0g6    = $f0->mulInt64($g6, 27);
        $f0g7    = $f0->mulInt64($g7, 27);
        $f0g8    = $f0->mulInt64($g8, 27);
        $f0g9    = $f0->mulInt64($g9, 27);
        $f1g0    = $f1->mulInt64($g0, 27);
        $f1g1_2  = $f1_2->mulInt64($g1, 27);
        $f1g2    = $f1->mulInt64($g2, 27);
        $f1g3_2  = $f1_2->mulInt64($g3, 27);
        $f1g4    = $f1->mulInt64($g4, 30);
        $f1g5_2  = $f1_2->mulInt64($g5, 30);
        $f1g6    = $f1->mulInt64($g6, 30);
        $f1g7_2  = $f1_2->mulInt64($g7, 30);
        $f1g8    = $f1->mulInt64($g8, 30);
        $f1g9_38 = $g9_19->mulInt64($f1_2, 30);
        $f2g0    = $f2->mulInt64($g0, 30);
        $f2g1    = $f2->mulInt64($g1, 29);
        $f2g2    = $f2->mulInt64($g2, 30);
        $f2g3    = $f2->mulInt64($g3, 29);
        $f2g4    = $f2->mulInt64($g4, 30);
        $f2g5    = $f2->mulInt64($g5, 29);
        $f2g6    = $f2->mulInt64($g6, 30);
        $f2g7    = $f2->mulInt64($g7, 29);
        $f2g8_19 = $g8_19->mulInt64($f2, 30);
        $f2g9_19 = $g9_19->mulInt64($f2, 30);
        $f3g0    = $f3->mulInt64($g0, 30);
        $f3g1_2  = $f3_2->mulInt64($g1, 30);
        $f3g2    = $f3->mulInt64($g2, 30);
        $f3g3_2  = $f3_2->mulInt64($g3, 30);
        $f3g4    = $f3->mulInt64($g4, 30);
        $f3g5_2  = $f3_2->mulInt64($g5, 30);
        $f3g6    = $f3->mulInt64($g6, 30);
        $f3g7_38 = $g7_19->mulInt64($f3_2, 30);
        $f3g8_19 = $g8_19->mulInt64($f3, 30);
        $f3g9_38 = $g9_19->mulInt64($f3_2, 30);
        $f4g0    = $f4->mulInt64($g0, 30);
        $f4g1    = $f4->mulInt64($g1, 30);
        $f4g2    = $f4->mulInt64($g2, 30);
        $f4g3    = $f4->mulInt64($g3, 30);
        $f4g4    = $f4->mulInt64($g4, 30);
        $f4g5    = $f4->mulInt64($g5, 30);
        $f4g6_19 = $g6_19->mulInt64($f4, 30);
        $f4g7_19 = $g7_19->mulInt64($f4, 30);
        $f4g8_19 = $g8_19->mulInt64($f4, 30);
        $f4g9_19 = $g9_19->mulInt64($f4, 30);
        $f5g0    = $f5->mulInt64($g0, 30);
        $f5g1_2  = $f5_2->mulInt64($g1, 30);
        $f5g2    = $f5->mulInt64($g2, 30);
        $f5g3_2  = $f5_2->mulInt64($g3, 30);
        $f5g4    = $f5->mulInt64($g4, 30);
        $f5g5_38 = $g5_19->mulInt64($f5_2, 30);
        $f5g6_19 = $g6_19->mulInt64($f5, 30);
        $f5g7_38 = $g7_19->mulInt64($f5_2, 30);
        $f5g8_19 = $g8_19->mulInt64($f5, 30);
        $f5g9_38 = $g9_19->mulInt64($f5_2, 30);
        $f6g0    = $f6->mulInt64($g0, 30);
        $f6g1    = $f6->mulInt64($g1, 30);
        $f6g2    = $f6->mulInt64($g2, 30);
        $f6g3    = $f6->mulInt64($g3, 30);
        $f6g4_19 = $g4_19->mulInt64($f6, 30);
        $f6g5_19 = $g5_19->mulInt64($f6, 30);
        $f6g6_19 = $g6_19->mulInt64($f6, 30);
        $f6g7_19 = $g7_19->mulInt64($f6, 30);
        $f6g8_19 = $g8_19->mulInt64($f6, 30);
        $f6g9_19 = $g9_19->mulInt64($f6, 30);
        $f7g0    = $f7->mulInt64($g0, 30);
        $f7g1_2  = $g1->mulInt64($f7_2, 30);
        $f7g2    = $f7->mulInt64($g2, 30);
        $f7g3_38 = $g3_19->mulInt64($f7_2, 30);
        $f7g4_19 = $g4_19->mulInt64($f7, 30);
        $f7g5_38 = $g5_19->mulInt64($f7_2, 30);
        $f7g6_19 = $g6_19->mulInt64($f7, 30);
        $f7g7_38 = $g7_19->mulInt64($f7_2, 30);
        $f7g8_19 = $g8_19->mulInt64($f7, 30);
        $f7g9_38 = $g9_19->mulInt64($f7_2, 30);
        $f8g0    = $f8->mulInt64($g0, 30);
        $f8g1    = $f8->mulInt64($g1, 29);
        $f8g2_19 = $g2_19->mulInt64($f8, 30);
        $f8g3_19 = $g3_19->mulInt64($f8, 30);
        $f8g4_19 = $g4_19->mulInt64($f8, 30);
        $f8g5_19 = $g5_19->mulInt64($f8, 30);
        $f8g6_19 = $g6_19->mulInt64($f8, 30);
        $f8g7_19 = $g7_19->mulInt64($f8, 30);
        $f8g8_19 = $g8_19->mulInt64($f8, 30);
        $f8g9_19 = $g9_19->mulInt64($f8, 30);
        $f9g0    = $f9->mulInt64($g0, 30);
        $f9g1_38 = $g1_19->mulInt64($f9_2, 30);
        $f9g2_19 = $g2_19->mulInt64($f9, 30);
        $f9g3_38 = $g3_19->mulInt64($f9_2, 30);
        $f9g4_19 = $g4_19->mulInt64($f9, 30);
        $f9g5_38 = $g5_19->mulInt64($f9_2, 30);
        $f9g6_19 = $g6_19->mulInt64($f9, 30);
        $f9g7_38 = $g7_19->mulInt64($f9_2, 30);
        $f9g8_19 = $g8_19->mulInt64($f9, 30);
        $f9g9_38 = $g9_19->mulInt64($f9_2, 30);

        // $h0 = $f0g0 + $f1g9_38 + $f2g8_19 + $f3g7_38 + $f4g6_19 + $f5g5_38 + $f6g4_19 + $f7g3_38 + $f8g2_19 + $f9g1_38;
        $h0 = $f0g0->addInt64($f1g9_38)->addInt64($f2g8_19)->addInt64($f3g7_38)
            ->addInt64($f4g6_19)->addInt64($f5g5_38)->addInt64($f6g4_19)
            ->addInt64($f7g3_38)->addInt64($f8g2_19)->addInt64($f9g1_38);

        // $h1 = $f0g1 + $f1g0    + $f2g9_19 + $f3g8_19 + $f4g7_19 + $f5g6_19 + $f6g5_19 + $f7g4_19 + $f8g3_19 + $f9g2_19;
        $h1 = $f0g1->addInt64($f1g0)->addInt64($f2g9_19)->addInt64($f3g8_19)
            ->addInt64($f4g7_19)->addInt64($f5g6_19)->addInt64($f6g5_19)
            ->addInt64($f7g4_19)->addInt64($f8g3_19)->addInt64($f9g2_19);

        // $h2 = $f0g2 + $f1g1_2  + $f2g0    + $f3g9_38 + $f4g8_19 + $f5g7_38 + $f6g6_19 + $f7g5_38 + $f8g4_19 + $f9g3_38;
        $h2 = $f0g2->addInt64($f1g1_2)->addInt64($f2g0)->addInt64($f3g9_38)
            ->addInt64($f4g8_19)->addInt64($f5g7_38)->addInt64($f6g6_19)
            ->addInt64($f7g5_38)->addInt64($f8g4_19)->addInt64($f9g3_38);

        // $h3 = $f0g3 + $f1g2    + $f2g1    + $f3g0    + $f4g9_19 + $f5g8_19 + $f6g7_19 + $f7g6_19 + $f8g5_19 + $f9g4_19;
        $h3 = $f0g3->addInt64($f1g2)->addInt64($f2g1)->addInt64($f3g0)
            ->addInt64($f4g9_19)->addInt64($f5g8_19)->addInt64($f6g7_19)
            ->addInt64($f7g6_19)->addInt64($f8g5_19)->addInt64($f9g4_19);

        // $h4 = $f0g4 + $f1g3_2  + $f2g2    + $f3g1_2  + $f4g0    + $f5g9_38 + $f6g8_19 + $f7g7_38 + $f8g6_19 + $f9g5_38;
        $h4 = $f0g4->addInt64($f1g3_2)->addInt64($f2g2)->addInt64($f3g1_2)
            ->addInt64($f4g0)->addInt64($f5g9_38)->addInt64($f6g8_19)
            ->addInt64($f7g7_38)->addInt64($f8g6_19)->addInt64($f9g5_38);

        // $h5 = $f0g5 + $f1g4    + $f2g3    + $f3g2    + $f4g1    + $f5g0    + $f6g9_19 + $f7g8_19 + $f8g7_19 + $f9g6_19;
        $h5 = $f0g5->addInt64($f1g4)->addInt64($f2g3)->addInt64($f3g2)
            ->addInt64($f4g1)->addInt64($f5g0)->addInt64($f6g9_19)
            ->addInt64($f7g8_19)->addInt64($f8g7_19)->addInt64($f9g6_19);

        // $h6 = $f0g6 + $f1g5_2  + $f2g4    + $f3g3_2  + $f4g2    + $f5g1_2  + $f6g0    + $f7g9_38 + $f8g8_19 + $f9g7_38;
        $h6 = $f0g6->addInt64($f1g5_2)->addInt64($f2g4)->addInt64($f3g3_2)
            ->addInt64($f4g2)->addInt64($f5g1_2)->addInt64($f6g0)
            ->addInt64($f7g9_38)->addInt64($f8g8_19)->addInt64($f9g7_38);

        // $h7 = $f0g7 + $f1g6    + $f2g5    + $f3g4    + $f4g3    + $f5g2    + $f6g1    + $f7g0    + $f8g9_19 + $f9g8_19;
        $h7 = $f0g7->addInt64($f1g6)->addInt64($f2g5)->addInt64($f3g4)
            ->addInt64($f4g3)->addInt64($f5g2)->addInt64($f6g1)
            ->addInt64($f7g0)->addInt64($f8g9_19)->addInt64($f9g8_19);

        // $h8 = $f0g8 + $f1g7_2  + $f2g6    + $f3g5_2  + $f4g4    + $f5g3_2  + $f6g2    + $f7g1_2  + $f8g0    + $f9g9_38;
        $h8 = $f0g8->addInt64($f1g7_2)->addInt64($f2g6)->addInt64($f3g5_2)
            ->addInt64($f4g4)->addInt64($f5g3_2)->addInt64($f6g2)
            ->addInt64($f7g1_2)->addInt64($f8g0)->addInt64($f9g9_38);

        // $h9 = $f0g9 + $f1g8    + $f2g7    + $f3g6    + $f4g5    + $f5g4    + $f6g3    + $f7g2    + $f8g1    + $f9g0   ;
        $h9 = $f0g9->addInt64($f1g8)->addInt64($f2g7)->addInt64($f3g6)
            ->addInt64($f4g5)->addInt64($f5g4)->addInt64($f6g3)
            ->addInt64($f7g2)->addInt64($f8g1)->addInt64($f9g0);

        /**
         * @var ParagonIE_Sodium_Core32_Int64 $h0
         * @var ParagonIE_Sodium_Core32_Int64 $h1
         * @var ParagonIE_Sodium_Core32_Int64 $h2
         * @var ParagonIE_Sodium_Core32_Int64 $h3
         * @var ParagonIE_Sodium_Core32_Int64 $h4
         * @var ParagonIE_Sodium_Core32_Int64 $h5
         * @var ParagonIE_Sodium_Core32_Int64 $h6
         * @var ParagonIE_Sodium_Core32_Int64 $h7
         * @var ParagonIE_Sodium_Core32_Int64 $h8
         * @var ParagonIE_Sodium_Core32_Int64 $h9
         * @var ParagonIE_Sodium_Core32_Int64 $carry0
         * @var ParagonIE_Sodium_Core32_Int64 $carry1
         * @var ParagonIE_Sodium_Core32_Int64 $carry2
         * @var ParagonIE_Sodium_Core32_Int64 $carry3
         * @var ParagonIE_Sodium_Core32_Int64 $carry4
         * @var ParagonIE_Sodium_Core32_Int64 $carry5
         * @var ParagonIE_Sodium_Core32_Int64 $carry6
         * @var ParagonIE_Sodium_Core32_Int64 $carry7
         * @var ParagonIE_Sodium_Core32_Int64 $carry8
         * @var ParagonIE_Sodium_Core32_Int64 $carry9
         */
        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));
        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));

        $carry1 = $h1->addInt(1 << 24)->shiftRight(25);
        $h2 = $h2->addInt64($carry1);
        $h1 = $h1->subInt64($carry1->shiftLeft(25));
        $carry5 = $h5->addInt(1 << 24)->shiftRight(25);
        $h6 = $h6->addInt64($carry5);
        $h5 = $h5->subInt64($carry5->shiftLeft(25));

        $carry2 = $h2->addInt(1 << 25)->shiftRight(26);
        $h3 = $h3->addInt64($carry2);
        $h2 = $h2->subInt64($carry2->shiftLeft(26));
        $carry6 = $h6->addInt(1 << 25)->shiftRight(26);
        $h7 = $h7->addInt64($carry6);
        $h6 = $h6->subInt64($carry6->shiftLeft(26));

        $carry3 = $h3->addInt(1 << 24)->shiftRight(25);
        $h4 = $h4->addInt64($carry3);
        $h3 = $h3->subInt64($carry3->shiftLeft(25));
        $carry7 = $h7->addInt(1 << 24)->shiftRight(25);
        $h8 = $h8->addInt64($carry7);
        $h7 = $h7->subInt64($carry7->shiftLeft(25));

        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));
        $carry8 = $h8->addInt(1 << 25)->shiftRight(26);
        $h9 = $h9->addInt64($carry8);
        $h8 = $h8->subInt64($carry8->shiftLeft(26));

        $carry9 = $h9->addInt(1 << 24)->shiftRight(25);
        $h0 = $h0->addInt64($carry9->mulInt(19, 5));
        $h9 = $h9->subInt64($carry9->shiftLeft(25));

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));

        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                $h0->toInt32(),
                $h1->toInt32(),
                $h2->toInt32(),
                $h3->toInt32(),
                $h4->toInt32(),
                $h5->toInt32(),
                $h6->toInt32(),
                $h7->toInt32(),
                $h8->toInt32(),
                $h9->toInt32()
            )
        );
    }

    /**
     * Get the negative values for each piece of the field element.
     *
     * h = -f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_neg(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        $h = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        for ($i = 0; $i < 10; ++$i) {
            $h[$i] = $h[$i]->subInt32($f[$i]);
        }
        return $h;
    }

    /**
     * Square a field element
     *
     * h = f * f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_sq(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        /** @var ParagonIE_Sodium_Core32_Int64 $f0 */
        $f0 = $f[0]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f1 */
        $f1 = $f[1]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f2 */
        $f2 = $f[2]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f3 */
        $f3 = $f[3]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f4 */
        $f4 = $f[4]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f5 */
        $f5 = $f[5]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f6 */
        $f6 = $f[6]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f7 */
        $f7 = $f[7]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f8 */
        $f8 = $f[8]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f9 */
        $f9 = $f[9]->toInt64();

        /** @var ParagonIE_Sodium_Core32_Int64 $f0_2 */
        $f0_2 = $f0->shiftLeft(1);
        $f1_2 = $f1->shiftLeft(1);
        $f2_2 = $f2->shiftLeft(1);
        $f3_2 = $f3->shiftLeft(1);
        $f4_2 = $f4->shiftLeft(1);
        $f5_2 = $f5->shiftLeft(1);
        $f6_2 = $f6->shiftLeft(1);
        $f7_2 = $f7->shiftLeft(1);
        $f5_38 = $f5->mulInt(38, 6);
        $f6_19 = $f6->mulInt(19, 5);
        $f7_38 = $f7->mulInt(38, 6);
        $f8_19 = $f8->mulInt(19, 5);
        $f9_38 = $f9->mulInt(38, 6);
        /** @var ParagonIE_Sodium_Core32_Int64 $f0f0*/
        $f0f0    = $f0->mulInt64($f0, 28);
        $f0f1_2  = $f0_2->mulInt64($f1, 28);
        $f0f2_2 =  $f0_2->mulInt64($f2, 28);
        $f0f3_2 =  $f0_2->mulInt64($f3, 28);
        $f0f4_2 =  $f0_2->mulInt64($f4, 28);
        $f0f5_2 =  $f0_2->mulInt64($f5, 28);
        $f0f6_2 =  $f0_2->mulInt64($f6, 28);
        $f0f7_2 =  $f0_2->mulInt64($f7, 28);
        $f0f8_2 =  $f0_2->mulInt64($f8, 28);
        $f0f9_2 =  $f0_2->mulInt64($f9, 28);

        $f1f1_2 = $f1_2->mulInt64($f1, 28);
        $f1f2_2 = $f1_2->mulInt64($f2, 28);
        $f1f3_4 = $f1_2->mulInt64($f3_2, 28);
        $f1f4_2 = $f1_2->mulInt64($f4, 28);
        $f1f5_4 = $f1_2->mulInt64($f5_2, 30);
        $f1f6_2 = $f1_2->mulInt64($f6, 28);
        $f1f7_4 = $f1_2->mulInt64($f7_2, 28);
        $f1f8_2 = $f1_2->mulInt64($f8, 28);
        $f1f9_76 = $f9_38->mulInt64($f1_2, 30);

        $f2f2 = $f2->mulInt64($f2, 28);
        $f2f3_2 = $f2_2->mulInt64($f3, 28);
        $f2f4_2 = $f2_2->mulInt64($f4, 28);
        $f2f5_2 = $f2_2->mulInt64($f5, 28);
        $f2f6_2 = $f2_2->mulInt64($f6, 28);
        $f2f7_2 = $f2_2->mulInt64($f7, 28);
        $f2f8_38 = $f8_19->mulInt64($f2_2, 30);
        $f2f9_38 = $f9_38->mulInt64($f2, 30);

        $f3f3_2 = $f3_2->mulInt64($f3, 28);
        $f3f4_2 = $f3_2->mulInt64($f4, 28);
        $f3f5_4 = $f3_2->mulInt64($f5_2, 30);
        $f3f6_2 = $f3_2->mulInt64($f6, 28);
        $f3f7_76 = $f7_38->mulInt64($f3_2, 30);
        $f3f8_38 = $f8_19->mulInt64($f3_2, 30);
        $f3f9_76 = $f9_38->mulInt64($f3_2, 30);

        $f4f4 = $f4->mulInt64($f4, 28);
        $f4f5_2 = $f4_2->mulInt64($f5, 28);
        $f4f6_38 = $f6_19->mulInt64($f4_2, 30);
        $f4f7_38 = $f7_38->mulInt64($f4, 30);
        $f4f8_38 = $f8_19->mulInt64($f4_2, 30);
        $f4f9_38 = $f9_38->mulInt64($f4, 30);

        $f5f5_38 = $f5_38->mulInt64($f5, 30);
        $f5f6_38 = $f6_19->mulInt64($f5_2, 30);
        $f5f7_76 = $f7_38->mulInt64($f5_2, 30);
        $f5f8_38 = $f8_19->mulInt64($f5_2, 30);
        $f5f9_76 = $f9_38->mulInt64($f5_2, 30);

        $f6f6_19 = $f6_19->mulInt64($f6, 30);
        $f6f7_38 = $f7_38->mulInt64($f6, 30);
        $f6f8_38 = $f8_19->mulInt64($f6_2, 30);
        $f6f9_38 = $f9_38->mulInt64($f6, 30);

        $f7f7_38 = $f7_38->mulInt64($f7, 28);
        $f7f8_38 = $f8_19->mulInt64($f7_2, 30);
        $f7f9_76 = $f9_38->mulInt64($f7_2, 30);

        $f8f8_19 = $f8_19->mulInt64($f8, 30);
        $f8f9_38 = $f9_38->mulInt64($f8, 30);

        $f9f9_38 = $f9_38->mulInt64($f9, 28);

        $h0 = $f0f0->addInt64($f1f9_76)->addInt64($f2f8_38)->addInt64($f3f7_76)->addInt64($f4f6_38)->addInt64($f5f5_38);
        $h1 = $f0f1_2->addInt64($f2f9_38)->addInt64($f3f8_38)->addInt64($f4f7_38)->addInt64($f5f6_38);
        $h2 = $f0f2_2->addInt64($f1f1_2)->addInt64($f3f9_76)->addInt64($f4f8_38)->addInt64($f5f7_76)->addInt64($f6f6_19);
        $h3 = $f0f3_2->addInt64($f1f2_2)->addInt64($f4f9_38)->addInt64($f5f8_38)->addInt64($f6f7_38);
        $h4 = $f0f4_2->addInt64($f1f3_4)->addInt64($f2f2)->addInt64($f5f9_76)->addInt64($f6f8_38)->addInt64($f7f7_38);
        $h5 = $f0f5_2->addInt64($f1f4_2)->addInt64($f2f3_2)->addInt64($f6f9_38)->addInt64($f7f8_38);
        $h6 = $f0f6_2->addInt64($f1f5_4)->addInt64($f2f4_2)->addInt64($f3f3_2)->addInt64($f7f9_76)->addInt64($f8f8_19);
        $h7 = $f0f7_2->addInt64($f1f6_2)->addInt64($f2f5_2)->addInt64($f3f4_2)->addInt64($f8f9_38);
        $h8 = $f0f8_2->addInt64($f1f7_4)->addInt64($f2f6_2)->addInt64($f3f5_4)->addInt64($f4f4)->addInt64($f9f9_38);
        $h9 = $f0f9_2->addInt64($f1f8_2)->addInt64($f2f7_2)->addInt64($f3f6_2)->addInt64($f4f5_2);

        /**
         * @var ParagonIE_Sodium_Core32_Int64 $h0
         * @var ParagonIE_Sodium_Core32_Int64 $h1
         * @var ParagonIE_Sodium_Core32_Int64 $h2
         * @var ParagonIE_Sodium_Core32_Int64 $h3
         * @var ParagonIE_Sodium_Core32_Int64 $h4
         * @var ParagonIE_Sodium_Core32_Int64 $h5
         * @var ParagonIE_Sodium_Core32_Int64 $h6
         * @var ParagonIE_Sodium_Core32_Int64 $h7
         * @var ParagonIE_Sodium_Core32_Int64 $h8
         * @var ParagonIE_Sodium_Core32_Int64 $h9
         */

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));

        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));

        $carry1 = $h1->addInt(1 << 24)->shiftRight(25);
        $h2 = $h2->addInt64($carry1);
        $h1 = $h1->subInt64($carry1->shiftLeft(25));

        $carry5 = $h5->addInt(1 << 24)->shiftRight(25);
        $h6 = $h6->addInt64($carry5);
        $h5 = $h5->subInt64($carry5->shiftLeft(25));

        $carry2 = $h2->addInt(1 << 25)->shiftRight(26);
        $h3 = $h3->addInt64($carry2);
        $h2 = $h2->subInt64($carry2->shiftLeft(26));

        $carry6 = $h6->addInt(1 << 25)->shiftRight(26);
        $h7 = $h7->addInt64($carry6);
        $h6 = $h6->subInt64($carry6->shiftLeft(26));

        $carry3 = $h3->addInt(1 << 24)->shiftRight(25);
        $h4 = $h4->addInt64($carry3);
        $h3 = $h3->subInt64($carry3->shiftLeft(25));

        $carry7 = $h7->addInt(1 << 24)->shiftRight(25);
        $h8 = $h8->addInt64($carry7);
        $h7 = $h7->subInt64($carry7->shiftLeft(25));

        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));

        $carry8 = $h8->addInt(1 << 25)->shiftRight(26);
        $h9 = $h9->addInt64($carry8);
        $h8 = $h8->subInt64($carry8->shiftLeft(26));

        $carry9 = $h9->addInt(1 << 24)->shiftRight(25);
        $h0 = $h0->addInt64($carry9->mulInt(19, 5));
        $h9 = $h9->subInt64($carry9->shiftLeft(25));

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));

        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                $h0->toInt32(),
                $h1->toInt32(),
                $h2->toInt32(),
                $h3->toInt32(),
                $h4->toInt32(),
                $h5->toInt32(),
                $h6->toInt32(),
                $h7->toInt32(),
                $h8->toInt32(),
                $h9->toInt32()
            )
        );
    }

    /**
     * Square and double a field element
     *
     * h = 2 * f * f
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_sq2(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        /** @var ParagonIE_Sodium_Core32_Int64 $f0 */
        $f0 = $f[0]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f1 */
        $f1 = $f[1]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f2 */
        $f2 = $f[2]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f3 */
        $f3 = $f[3]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f4 */
        $f4 = $f[4]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f5 */
        $f5 = $f[5]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f6 */
        $f6 = $f[6]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f7 */
        $f7 = $f[7]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f8 */
        $f8 = $f[8]->toInt64();
        /** @var ParagonIE_Sodium_Core32_Int64 $f9 */
        $f9 = $f[9]->toInt64();

        $f0_2 = $f0->shiftLeft(1);
        $f1_2 = $f1->shiftLeft(1);
        $f2_2 = $f2->shiftLeft(1);
        $f3_2 = $f3->shiftLeft(1);
        $f4_2 = $f4->shiftLeft(1);
        $f5_2 = $f5->shiftLeft(1);
        $f6_2 = $f6->shiftLeft(1);
        $f7_2 = $f7->shiftLeft(1);
        $f5_38 = $f5->mulInt(38, 6); /* 1.959375*2^30 */
        $f6_19 = $f6->mulInt(19, 5); /* 1.959375*2^30 */
        $f7_38 = $f7->mulInt(38, 6); /* 1.959375*2^30 */
        $f8_19 = $f8->mulInt(19, 5); /* 1.959375*2^30 */
        $f9_38 = $f9->mulInt(38, 6); /* 1.959375*2^30 */
        $f0f0 = $f0->mulInt64($f0, 28);
        $f0f1_2 = $f0_2->mulInt64($f1, 28);
        $f0f2_2 = $f0_2->mulInt64($f2, 28);
        $f0f3_2 = $f0_2->mulInt64($f3, 28);
        $f0f4_2 = $f0_2->mulInt64($f4, 28);
        $f0f5_2 = $f0_2->mulInt64($f5, 28);
        $f0f6_2 = $f0_2->mulInt64($f6, 28);
        $f0f7_2 = $f0_2->mulInt64($f7, 28);
        $f0f8_2 = $f0_2->mulInt64($f8, 28);
        $f0f9_2 = $f0_2->mulInt64($f9, 28);
        $f1f1_2 = $f1_2->mulInt64($f1, 28);
        $f1f2_2 = $f1_2->mulInt64($f2, 28);
        $f1f3_4 = $f1_2->mulInt64($f3_2, 29);
        $f1f4_2 = $f1_2->mulInt64($f4, 28);
        $f1f5_4 = $f1_2->mulInt64($f5_2, 29);
        $f1f6_2 = $f1_2->mulInt64($f6, 28);
        $f1f7_4 = $f1_2->mulInt64($f7_2, 29);
        $f1f8_2 = $f1_2->mulInt64($f8, 28);
        $f1f9_76 = $f9_38->mulInt64($f1_2, 29);
        $f2f2 = $f2->mulInt64($f2, 28);
        $f2f3_2 = $f2_2->mulInt64($f3, 28);
        $f2f4_2 = $f2_2->mulInt64($f4, 28);
        $f2f5_2 = $f2_2->mulInt64($f5, 28);
        $f2f6_2 = $f2_2->mulInt64($f6, 28);
        $f2f7_2 = $f2_2->mulInt64($f7, 28);
        $f2f8_38 = $f8_19->mulInt64($f2_2, 29);
        $f2f9_38 = $f9_38->mulInt64($f2, 29);
        $f3f3_2 = $f3_2->mulInt64($f3, 28);
        $f3f4_2 = $f3_2->mulInt64($f4, 28);
        $f3f5_4 = $f3_2->mulInt64($f5_2, 28);
        $f3f6_2 = $f3_2->mulInt64($f6, 28);
        $f3f7_76 = $f7_38->mulInt64($f3_2, 29);
        $f3f8_38 = $f8_19->mulInt64($f3_2, 29);
        $f3f9_76 = $f9_38->mulInt64($f3_2, 29);
        $f4f4 = $f4->mulInt64($f4, 28);
        $f4f5_2 = $f4_2->mulInt64($f5, 28);
        $f4f6_38 = $f6_19->mulInt64($f4_2, 29);
        $f4f7_38 = $f7_38->mulInt64($f4, 29);
        $f4f8_38 = $f8_19->mulInt64($f4_2, 29);
        $f4f9_38 = $f9_38->mulInt64($f4, 29);
        $f5f5_38 = $f5_38->mulInt64($f5, 29);
        $f5f6_38 = $f6_19->mulInt64($f5_2, 29);
        $f5f7_76 = $f7_38->mulInt64($f5_2, 29);
        $f5f8_38 = $f8_19->mulInt64($f5_2, 29);
        $f5f9_76 = $f9_38->mulInt64($f5_2, 29);
        $f6f6_19 = $f6_19->mulInt64($f6, 29);
        $f6f7_38 = $f7_38->mulInt64($f6, 29);
        $f6f8_38 = $f8_19->mulInt64($f6_2, 29);
        $f6f9_38 = $f9_38->mulInt64($f6, 29);
        $f7f7_38 = $f7_38->mulInt64($f7, 29);
        $f7f8_38 = $f8_19->mulInt64($f7_2, 29);
        $f7f9_76 = $f9_38->mulInt64($f7_2, 29);
        $f8f8_19 = $f8_19->mulInt64($f8, 29);
        $f8f9_38 = $f9_38->mulInt64($f8, 29);
        $f9f9_38 = $f9_38->mulInt64($f9, 29);

        $h0 = $f0f0->addInt64($f1f9_76)->addInt64($f2f8_38)->addInt64($f3f7_76)->addInt64($f4f6_38)->addInt64($f5f5_38);
        $h1 = $f0f1_2->addInt64($f2f9_38)->addInt64($f3f8_38)->addInt64($f4f7_38)->addInt64($f5f6_38);
        $h2 = $f0f2_2->addInt64($f1f1_2)->addInt64($f3f9_76)->addInt64($f4f8_38)->addInt64($f5f7_76)->addInt64($f6f6_19);
        $h3 = $f0f3_2->addInt64($f1f2_2)->addInt64($f4f9_38)->addInt64($f5f8_38)->addInt64($f6f7_38);
        $h4 = $f0f4_2->addInt64($f1f3_4)->addInt64($f2f2)->addInt64($f5f9_76)->addInt64($f6f8_38)->addInt64($f7f7_38);
        $h5 = $f0f5_2->addInt64($f1f4_2)->addInt64($f2f3_2)->addInt64($f6f9_38)->addInt64($f7f8_38);
        $h6 = $f0f6_2->addInt64($f1f5_4)->addInt64($f2f4_2)->addInt64($f3f3_2)->addInt64($f7f9_76)->addInt64($f8f8_19);
        $h7 = $f0f7_2->addInt64($f1f6_2)->addInt64($f2f5_2)->addInt64($f3f4_2)->addInt64($f8f9_38);
        $h8 = $f0f8_2->addInt64($f1f7_4)->addInt64($f2f6_2)->addInt64($f3f5_4)->addInt64($f4f4)->addInt64($f9f9_38);
        $h9 = $f0f9_2->addInt64($f1f8_2)->addInt64($f2f7_2)->addInt64($f3f6_2)->addInt64($f4f5_2);

        /**
         * @var ParagonIE_Sodium_Core32_Int64 $h0
         * @var ParagonIE_Sodium_Core32_Int64 $h1
         * @var ParagonIE_Sodium_Core32_Int64 $h2
         * @var ParagonIE_Sodium_Core32_Int64 $h3
         * @var ParagonIE_Sodium_Core32_Int64 $h4
         * @var ParagonIE_Sodium_Core32_Int64 $h5
         * @var ParagonIE_Sodium_Core32_Int64 $h6
         * @var ParagonIE_Sodium_Core32_Int64 $h7
         * @var ParagonIE_Sodium_Core32_Int64 $h8
         * @var ParagonIE_Sodium_Core32_Int64 $h9
         */
        $h0 = $h0->shiftLeft(1);
        $h1 = $h1->shiftLeft(1);
        $h2 = $h2->shiftLeft(1);
        $h3 = $h3->shiftLeft(1);
        $h4 = $h4->shiftLeft(1);
        $h5 = $h5->shiftLeft(1);
        $h6 = $h6->shiftLeft(1);
        $h7 = $h7->shiftLeft(1);
        $h8 = $h8->shiftLeft(1);
        $h9 = $h9->shiftLeft(1);

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));
        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));

        $carry1 = $h1->addInt(1 << 24)->shiftRight(25);
        $h2 = $h2->addInt64($carry1);
        $h1 = $h1->subInt64($carry1->shiftLeft(25));
        $carry5 = $h5->addInt(1 << 24)->shiftRight(25);
        $h6 = $h6->addInt64($carry5);
        $h5 = $h5->subInt64($carry5->shiftLeft(25));

        $carry2 = $h2->addInt(1 << 25)->shiftRight(26);
        $h3 = $h3->addInt64($carry2);
        $h2 = $h2->subInt64($carry2->shiftLeft(26));
        $carry6 = $h6->addInt(1 << 25)->shiftRight(26);
        $h7 = $h7->addInt64($carry6);
        $h6 = $h6->subInt64($carry6->shiftLeft(26));

        $carry3 = $h3->addInt(1 << 24)->shiftRight(25);
        $h4 = $h4->addInt64($carry3);
        $h3 = $h3->subInt64($carry3->shiftLeft(25));
        $carry7 = $h7->addInt(1 << 24)->shiftRight(25);
        $h8 = $h8->addInt64($carry7);
        $h7 = $h7->subInt64($carry7->shiftLeft(25));

        $carry4 = $h4->addInt(1 << 25)->shiftRight(26);
        $h5 = $h5->addInt64($carry4);
        $h4 = $h4->subInt64($carry4->shiftLeft(26));
        $carry8 = $h8->addInt(1 << 25)->shiftRight(26);
        $h9 = $h9->addInt64($carry8);
        $h8 = $h8->subInt64($carry8->shiftLeft(26));

        $carry9 = $h9->addInt(1 << 24)->shiftRight(25);
        $h0 = $h0->addInt64($carry9->mulInt(19, 5));
        $h9 = $h9->subInt64($carry9->shiftLeft(25));

        $carry0 = $h0->addInt(1 << 25)->shiftRight(26);
        $h1 = $h1->addInt64($carry0);
        $h0 = $h0->subInt64($carry0->shiftLeft(26));

        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                $h0->toInt32(),
                $h1->toInt32(),
                $h2->toInt32(),
                $h3->toInt32(),
                $h4->toInt32(),
                $h5->toInt32(),
                $h6->toInt32(),
                $h7->toInt32(),
                $h8->toInt32(),
                $h9->toInt32()
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $Z
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_invert(ParagonIE_Sodium_Core32_Curve25519_Fe $Z)
    {
        $z = clone $Z;
        $t0 = self::fe_sq($z);
        $t1 = self::fe_sq($t0);
        $t1 = self::fe_sq($t1);
        $t1 = self::fe_mul($z, $t1);
        $t0 = self::fe_mul($t0, $t1);
        $t2 = self::fe_sq($t0);
        $t1 = self::fe_mul($t1, $t2);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 5; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 10; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t2 = self::fe_mul($t2, $t1);
        $t3 = self::fe_sq($t2);
        for ($i = 1; $i < 20; ++$i) {
            $t3 = self::fe_sq($t3);
        }
        $t2 = self::fe_mul($t3, $t2);
        $t2 = self::fe_sq($t2);
        for ($i = 1; $i < 10; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t2 = self::fe_sq($t1);
        for ($i = 1; $i < 50; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t2 = self::fe_mul($t2, $t1);
        $t3 = self::fe_sq($t2);
        for ($i = 1; $i < 100; ++$i) {
            $t3 = self::fe_sq($t3);
        }
        $t2 = self::fe_mul($t3, $t2);
        $t2 = self::fe_sq($t2);
        for ($i = 1; $i < 50; ++$i) {
            $t2 = self::fe_sq($t2);
        }
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);
        for ($i = 1; $i < 5; ++$i) {
            $t1 = self::fe_sq($t1);
        }
        return self::fe_mul($t1, $t0);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @ref https://github.com/jedisct1/libsodium/blob/68564326e1e9dc57ef03746f85734232d20ca6fb/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1054-L1106
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $z
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fe_pow22523(ParagonIE_Sodium_Core32_Curve25519_Fe $z)
    {
        # fe_sq(t0, z);
        # fe_sq(t1, t0);
        # fe_sq(t1, t1);
        # fe_mul(t1, z, t1);
        # fe_mul(t0, t0, t1);
        # fe_sq(t0, t0);
        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_sq($z);
        $t1 = self::fe_sq($t0);
        $t1 = self::fe_sq($t1);
        $t1 = self::fe_mul($z, $t1);
        $t0 = self::fe_mul($t0, $t1);
        $t0 = self::fe_sq($t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 5; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 5; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 10; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 10; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t1, t1, t0);
        # fe_sq(t2, t1);
        $t1 = self::fe_mul($t1, $t0);
        $t2 = self::fe_sq($t1);

        # for (i = 1; i < 20; ++i) {
        #     fe_sq(t2, t2);
        # }
        for ($i = 1; $i < 20; ++$i) {
            $t2 = self::fe_sq($t2);
        }

        # fe_mul(t1, t2, t1);
        # fe_sq(t1, t1);
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);

        # for (i = 1; i < 10; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 10; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t1, t0);
        $t0 = self::fe_mul($t1, $t0);
        $t1 = self::fe_sq($t0);

        # for (i = 1; i < 50; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 50; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t1, t1, t0);
        # fe_sq(t2, t1);
        $t1 = self::fe_mul($t1, $t0);
        $t2 = self::fe_sq($t1);

        # for (i = 1; i < 100; ++i) {
        #     fe_sq(t2, t2);
        # }
        for ($i = 1; $i < 100; ++$i) {
            $t2 = self::fe_sq($t2);
        }

        # fe_mul(t1, t2, t1);
        # fe_sq(t1, t1);
        $t1 = self::fe_mul($t2, $t1);
        $t1 = self::fe_sq($t1);

        # for (i = 1; i < 50; ++i) {
        #     fe_sq(t1, t1);
        # }
        for ($i = 1; $i < 50; ++$i) {
            $t1 = self::fe_sq($t1);
        }

        # fe_mul(t0, t1, t0);
        # fe_sq(t0, t0);
        # fe_sq(t0, t0);
        # fe_mul(out, t0, z);
        $t0 = self::fe_mul($t1, $t0);
        $t0 = self::fe_sq($t0);
        $t0 = self::fe_sq($t0);
        return self::fe_mul($t0, $z);
    }

    /**
     * Subtract two field elements.
     *
     * h = f - g
     *
     * Preconditions:
     * |f| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
     * |g| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
     *
     * Postconditions:
     * |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $g
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedMethodCall
     * @psalm-suppress MixedTypeCoercion
     */
    public static function fe_sub(ParagonIE_Sodium_Core32_Curve25519_Fe $f, ParagonIE_Sodium_Core32_Curve25519_Fe $g)
    {
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
            array(
                $f[0]->subInt32($g[0]),
                $f[1]->subInt32($g[1]),
                $f[2]->subInt32($g[2]),
                $f[3]->subInt32($g[3]),
                $f[4]->subInt32($g[4]),
                $f[5]->subInt32($g[5]),
                $f[6]->subInt32($g[6]),
                $f[7]->subInt32($g[7]),
                $f[8]->subInt32($g[8]),
                $f[9]->subInt32($g[9])
            )
        );
    }

    /**
     * Add two group elements.
     *
     * r = p + q
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Cached $q
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_add(
        ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core32_Curve25519_Ge_Cached $q
    ) {
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1();
        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->YplusX);
        $r->Y = self::fe_mul($r->Y, $q->YminusX);
        $r->T = self::fe_mul($q->T2d, $p->T);
        $r->X = self::fe_mul($p->Z, $q->Z);
        $t0   = self::fe_add($r->X, $r->X);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_add($t0, $r->T);
        $r->T = self::fe_sub($t0, $r->T);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @ref https://github.com/jedisct1/libsodium/blob/157c4a80c13b117608aeae12178b2d38825f9f8f/src/libsodium/crypto_core/curve25519/ref10/curve25519_ref10.c#L1185-L1215
     * @param string $a
     * @return array<int, mixed>
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayOffset
     */
    public static function slide($a)
    {
        if (self::strlen($a) < 256) {
            if (self::strlen($a) < 16) {
                $a = str_pad($a, 256, '0', STR_PAD_RIGHT);
            }
        }
        /** @var array<int, int> $r */
        $r = array();
        for ($i = 0; $i < 256; ++$i) {
            $r[$i] = (int) (1 &
                (
                    self::chrToInt($a[$i >> 3])
                        >>
                    ($i & 7)
                )
            );
        }

        for ($i = 0;$i < 256;++$i) {
            if ($r[$i]) {
                for ($b = 1;$b <= 6 && $i + $b < 256;++$b) {
                    if ($r[$i + $b]) {
                        if ($r[$i] + ($r[$i + $b] << $b) <= 15) {
                            $r[$i] += $r[$i + $b] << $b;
                            $r[$i + $b] = 0;
                        } elseif ($r[$i] - ($r[$i + $b] << $b) >= -15) {
                            $r[$i] -= $r[$i + $b] << $b;
                            for ($k = $i + $b; $k < 256; ++$k) {
                                if (!$r[$k]) {
                                    $r[$k] = 1;
                                    break;
                                }
                                $r[$k] = 0;
                            }
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_frombytes_negate_vartime($s)
    {
        static $d = null;
        if (!$d) {
            /** @var ParagonIE_Sodium_Core32_Curve25519_Fe $d */
            $d = ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                array(
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[0]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[1]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[2]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[3]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[4]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[5]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[6]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[7]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[8]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d[9])
                )
            );
        }

        # fe_frombytes(h->Y,s);
        # fe_1(h->Z);
        $h = new ParagonIE_Sodium_Core32_Curve25519_Ge_P3(
            self::fe_0(),
            self::fe_frombytes($s),
            self::fe_1()
        );

        # fe_sq(u,h->Y);
        # fe_mul(v,u,d);
        # fe_sub(u,u,h->Z);       /* u = y^2-1 */
        # fe_add(v,v,h->Z);       /* v = dy^2+1 */
        $u = self::fe_sq($h->Y);
        /** @var ParagonIE_Sodium_Core32_Curve25519_Fe $d */
        $v = self::fe_mul($u, $d);
        $u = self::fe_sub($u, $h->Z); /* u =  y^2 - 1 */
        $v = self::fe_add($v, $h->Z); /* v = dy^2 + 1 */

        # fe_sq(v3,v);
        # fe_mul(v3,v3,v);        /* v3 = v^3 */
        # fe_sq(h->X,v3);
        # fe_mul(h->X,h->X,v);
        # fe_mul(h->X,h->X,u);    /* x = uv^7 */
        $v3 = self::fe_sq($v);
        $v3 = self::fe_mul($v3, $v); /* v3 = v^3 */
        $h->X = self::fe_sq($v3);
        $h->X = self::fe_mul($h->X, $v);
        $h->X = self::fe_mul($h->X, $u); /* x = uv^7 */

        # fe_pow22523(h->X,h->X); /* x = (uv^7)^((q-5)/8) */
        # fe_mul(h->X,h->X,v3);
        # fe_mul(h->X,h->X,u);    /* x = uv^3(uv^7)^((q-5)/8) */
        $h->X = self::fe_pow22523($h->X); /* x = (uv^7)^((q-5)/8) */
        $h->X = self::fe_mul($h->X, $v3);
        $h->X = self::fe_mul($h->X, $u); /* x = uv^3(uv^7)^((q-5)/8) */

        # fe_sq(vxx,h->X);
        # fe_mul(vxx,vxx,v);
        # fe_sub(check,vxx,u);    /* vx^2-u */
        $vxx = self::fe_sq($h->X);
        $vxx = self::fe_mul($vxx, $v);
        $check = self::fe_sub($vxx, $u); /* vx^2 - u */

        # if (fe_isnonzero(check)) {
        #     fe_add(check,vxx,u);  /* vx^2+u */
        #     if (fe_isnonzero(check)) {
        #         return -1;
        #     }
        #     fe_mul(h->X,h->X,sqrtm1);
        # }
        if (self::fe_isnonzero($check)) {
            $check = self::fe_add($vxx, $u); /* vx^2 + u */
            if (self::fe_isnonzero($check)) {
                throw new RangeException('Internal check failed.');
            }
            $h->X = self::fe_mul(
                $h->X,
                ParagonIE_Sodium_Core32_Curve25519_Fe::fromIntArray(self::$sqrtm1)
            );
        }

        # if (fe_isnegative(h->X) == (s[31] >> 7)) {
        #     fe_neg(h->X,h->X);
        # }
        $i = self::chrToInt($s[31]);
        if (self::fe_isnegative($h->X) === ($i >> 7)) {
            $h->X = self::fe_neg($h->X);
        }

        # fe_mul(h->T,h->X,h->Y);
        $h->T = self::fe_mul($h->X, $h->Y);
        return $h;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $R
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $q
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_madd(
        ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $R,
        ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $q
    ) {
        $r = clone $R;
        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->yplusx);
        $r->Y = self::fe_mul($r->Y, $q->yminusx);
        $r->T = self::fe_mul($q->xy2d, $p->T);
        $t0 = self::fe_add(clone $p->Z, clone $p->Z);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_add($t0, $r->T);
        $r->T = self::fe_sub($t0, $r->T);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $R
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $q
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_msub(
        ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $R,
        ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $q
    ) {
        $r = clone $R;

        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->yminusx);
        $r->Y = self::fe_mul($r->Y, $q->yplusx);
        $r->T = self::fe_mul($q->xy2d, $p->T);
        $t0 = self::fe_add($p->Z, $p->Z);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_sub($t0, $r->T);
        $r->T = self::fe_add($t0, $r->T);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P2
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p1p1_to_p2(ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $p)
    {
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P2();
        $r->X = self::fe_mul($p->X, $p->T);
        $r->Y = self::fe_mul($p->Y, $p->Z);
        $r->Z = self::fe_mul($p->Z, $p->T);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p1p1_to_p3(ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 $p)
    {
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P3();
        $r->X = self::fe_mul($p->X, $p->T);
        $r->Y = self::fe_mul($p->Y, $p->Z);
        $r->Z = self::fe_mul($p->Z, $p->T);
        $r->T = self::fe_mul($p->X, $p->Y);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P2
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p2_0()
    {
        return new ParagonIE_Sodium_Core32_Curve25519_Ge_P2(
            self::fe_0(),
            self::fe_1(),
            self::fe_1()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p2_dbl(ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $p)
    {
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1();

        $r->X = self::fe_sq($p->X);
        $r->Z = self::fe_sq($p->Y);
        $r->T = self::fe_sq2($p->Z);
        $r->Y = self::fe_add($p->X, $p->Y);
        $t0   = self::fe_sq($r->Y);
        $r->Y = self::fe_add($r->Z, $r->X);
        $r->Z = self::fe_sub($r->Z, $r->X);
        $r->X = self::fe_sub($t0, $r->Y);
        $r->T = self::fe_sub($r->T, $r->Z);

        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p3_0()
    {
        return new ParagonIE_Sodium_Core32_Curve25519_Ge_P3(
            self::fe_0(),
            self::fe_1(),
            self::fe_1(),
            self::fe_0()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_Cached
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p3_to_cached(ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p)
    {
        static $d2 = null;
        if ($d2 === null) {
            $d2 = ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                array(
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[0]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[1]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[2]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[3]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[4]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[5]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[6]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[7]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[8]),
                    ParagonIE_Sodium_Core32_Int32::fromInt(self::$d2[9])
                )
            );
        }
        /** @var ParagonIE_Sodium_Core32_Curve25519_Fe $d2 */
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_Cached();
        $r->YplusX = self::fe_add($p->Y, $p->X);
        $r->YminusX = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_copy($p->Z);
        $r->T2d = self::fe_mul($p->T, $d2);
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P2
     */
    public static function ge_p3_to_p2(ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p)
    {
        return new ParagonIE_Sodium_Core32_Curve25519_Ge_P2(
            $p->X,
            $p->Y,
            $p->Z
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $h
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p3_tobytes(ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $h)
    {
        $recip = self::fe_invert($h->Z);
        $x = self::fe_mul($h->X, $recip);
        $y = self::fe_mul($h->Y, $recip);
        $s = self::fe_tobytes($y);
        $s[31] = self::intToChr(
            self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7)
        );
        return $s;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_p3_dbl(ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p)
    {
        $q = self::ge_p3_to_p2($p);
        return self::ge_p2_dbl($q);
    }

    /**
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_precomp_0()
    {
        return new ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp(
            self::fe_1(),
            self::fe_1(),
            self::fe_0()
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $b
     * @param int $c
     * @return int
     * @psalm-suppress MixedReturnStatement
     */
    public static function equal($b, $c)
    {
        return (int) ((($b ^ $c) - 1 & 0xffffffff) >> 31);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string|int $char
     * @return int (1 = yes, 0 = no)
     * @throws SodiumException
     * @throws TypeError
     */
    public static function negative($char)
    {
        if (is_int($char)) {
            return $char < 0 ? 1 : 0;
        }
        /** @var string $char */
        /** @var int $x */
        $x = self::chrToInt(self::substr($char, 0, 1));
        return (int) ($x >> 31);
    }

    /**
     * Conditional move
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $t
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $u
     * @param int $b
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp
     * @throws SodiumException
     * @throws TypeError
     */
    public static function cmov(
        ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $t,
        ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $u,
        $b
    ) {
        if (!is_int($b)) {
            throw new InvalidArgumentException('Expected an integer.');
        }
        return new ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp(
            self::fe_cmov($t->yplusx, $u->yplusx, $b),
            self::fe_cmov($t->yminusx, $u->yminusx, $b),
            self::fe_cmov($t->xy2d, $u->xy2d, $b)
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $pos
     * @param int $b
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedArgument
     */
    public static function ge_select($pos = 0, $b = 0)
    {
        static $base = null;
        if ($base === null) {
            $base = array();
            foreach (self::$base as $i => $bas) {
                for ($j = 0; $j < 8; ++$j) {
                    $base[$i][$j] = new ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp(
                        ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                            array(
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][0]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][1]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][2]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][3]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][4]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][5]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][6]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][7]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][8]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][0][9])
                            )
                        ),
                        ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                            array(
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][0]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][1]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][2]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][3]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][4]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][5]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][6]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][7]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][8]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][1][9])
                            )
                        ),
                        ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                            array(
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][0]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][1]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][2]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][3]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][4]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][5]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][6]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][7]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][8]),
                                ParagonIE_Sodium_Core32_Int32::fromInt($bas[$j][2][9])
                            )
                        )
                    );
                }
            }
        }
        if (!is_int($pos)) {
            throw new InvalidArgumentException('Position must be an integer');
        }
        if ($pos < 0 || $pos > 31) {
            throw new RangeException('Position is out of range [0, 31]');
        }

        $bnegative = self::negative($b);
        /** @var int $babs */
        $babs = $b - (((-$bnegative) & $b) << 1);

        $t = self::ge_precomp_0();
        for ($i = 0; $i < 8; ++$i) {
            $t = self::cmov(
                $t,
                $base[$pos][$i],
                self::equal($babs, $i + 1)
            );
        }
        $minusT = new ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp(
            self::fe_copy($t->yminusx),
            self::fe_copy($t->yplusx),
            self::fe_neg($t->xy2d)
        );
        return self::cmov($t, $minusT, -$bnegative);
    }

    /**
     * Subtract two group elements.
     *
     * r = p - q
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_Cached $q
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_sub(
        ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $p,
        ParagonIE_Sodium_Core32_Curve25519_Ge_Cached $q
    ) {
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1();

        $r->X = self::fe_add($p->Y, $p->X);
        $r->Y = self::fe_sub($p->Y, $p->X);
        $r->Z = self::fe_mul($r->X, $q->YminusX);
        $r->Y = self::fe_mul($r->Y, $q->YplusX);
        $r->T = self::fe_mul($q->T2d, $p->T);
        $r->X = self::fe_mul($p->Z, $q->Z);
        $t0 = self::fe_add($r->X, $r->X);
        $r->X = self::fe_sub($r->Z, $r->Y);
        $r->Y = self::fe_add($r->Z, $r->Y);
        $r->Z = self::fe_sub($t0, $r->T);
        $r->T = self::fe_add($t0, $r->T);

        return $r;
    }

    /**
     * Convert a group element to a byte string.
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $h
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_tobytes(ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $h)
    {
        $recip = self::fe_invert($h->Z);
        $x = self::fe_mul($h->X, $recip);
        $y = self::fe_mul($h->Y, $recip);
        $s = self::fe_tobytes($y);
        $s[31] = self::intToChr(
            self::chrToInt($s[31]) ^ (self::fe_isnegative($x) << 7)
        );
        return $s;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A
     * @param string $b
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P2
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public static function ge_double_scalarmult_vartime(
        $a,
        ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A,
        $b
    ) {
        /** @var array<int, ParagonIE_Sodium_Core32_Curve25519_Ge_Cached> $Ai */
        $Ai = array();

        static $Bi = array();
        /** @var array<int, ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp> $Bi */
        if (!$Bi) {
            for ($i = 0; $i < 8; ++$i) {
                $Bi[$i] = new ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp(
                    ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                        array(
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][0]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][1]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][2]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][3]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][4]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][5]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][6]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][7]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][8]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][0][9])
                        )
                    ),
                    ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                        array(
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][0]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][1]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][2]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][3]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][4]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][5]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][6]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][7]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][8]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][1][9])
                        )
                    ),
                    ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray(
                        array(
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][0]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][1]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][2]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][3]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][4]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][5]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][6]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][7]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][8]),
                            ParagonIE_Sodium_Core32_Int32::fromInt(self::$base2[$i][2][9])
                        )
                    )
                );
            }
        }

        for ($i = 0; $i < 8; ++$i) {
            $Ai[$i] = new ParagonIE_Sodium_Core32_Curve25519_Ge_Cached(
                self::fe_0(),
                self::fe_0(),
                self::fe_0(),
                self::fe_0()
            );
        }
        /** @var array<int, ParagonIE_Sodium_Core32_Curve25519_Ge_Cached> $Ai */

        # slide(aslide,a);
        # slide(bslide,b);
        /** @var array<int, int> $aslide */
        $aslide = self::slide($a);
        /** @var array<int, int> $bslide */
        $bslide = self::slide($b);

        # ge_p3_to_cached(&Ai[0],A);
        # ge_p3_dbl(&t,A); ge_p1p1_to_p3(&A2,&t);
        $Ai[0] = self::ge_p3_to_cached($A);
        $t = self::ge_p3_dbl($A);
        $A2 = self::ge_p1p1_to_p3($t);

        # ge_add(&t,&A2,&Ai[0]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[1],&u);
        # ge_add(&t,&A2,&Ai[1]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[2],&u);
        # ge_add(&t,&A2,&Ai[2]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[3],&u);
        # ge_add(&t,&A2,&Ai[3]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[4],&u);
        # ge_add(&t,&A2,&Ai[4]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[5],&u);
        # ge_add(&t,&A2,&Ai[5]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[6],&u);
        # ge_add(&t,&A2,&Ai[6]); ge_p1p1_to_p3(&u,&t); ge_p3_to_cached(&Ai[7],&u);
        for ($i = 0; $i < 7; ++$i) {
            $t = self::ge_add($A2, $Ai[$i]);
            $u = self::ge_p1p1_to_p3($t);
            $Ai[$i + 1] = self::ge_p3_to_cached($u);
        }

        # ge_p2_0(r);
        $r = self::ge_p2_0();

        # for (i = 255;i >= 0;--i) {
        #     if (aslide[i] || bslide[i]) break;
        # }
        $i = 255;
        for (; $i >= 0; --$i) {
            if ($aslide[$i] || $bslide[$i]) {
                break;
            }
        }

        # for (;i >= 0;--i) {
        for (; $i >= 0; --$i) {
            # ge_p2_dbl(&t,r);
            $t = self::ge_p2_dbl($r);

            # if (aslide[i] > 0) {
            if ($aslide[$i] > 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_add(&t,&u,&Ai[aslide[i]/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_add(
                    $u,
                    $Ai[(int) floor($aslide[$i] / 2)]
                );
                # } else if (aslide[i] < 0) {
            } elseif ($aslide[$i] < 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_sub(&t,&u,&Ai[(-aslide[i])/2]);
                $u = self::ge_p1p1_to_p3($t);
                $t = self::ge_sub(
                    $u,
                    $Ai[(int) floor(-$aslide[$i] / 2)]
                );
            }
            /** @var array<int, ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp> $Bi */

            # if (bslide[i] > 0) {
            if ($bslide[$i] > 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_madd(&t,&u,&Bi[bslide[i]/2]);
                $u = self::ge_p1p1_to_p3($t);
                /** @var int $index */
                $index = (int) floor($bslide[$i] / 2);
                /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $thisB */
                $thisB = $Bi[$index];
                $t = self::ge_madd($t, $u, $thisB);
                # } else if (bslide[i] < 0) {
            } elseif ($bslide[$i] < 0) {
                # ge_p1p1_to_p3(&u,&t);
                # ge_msub(&t,&u,&Bi[(-bslide[i])/2]);
                $u = self::ge_p1p1_to_p3($t);

                /** @var int $index */
                $index = (int) floor(-$bslide[$i] / 2);

                /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp $thisB */
                $thisB = $Bi[$index];
                $t = self::ge_msub($t, $u, $thisB);
            }
            # ge_p1p1_to_p2(r,&t);
            $r = self::ge_p1p1_to_p2($t);
        }
        return $r;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P3
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_scalarmult_base($a)
    {
        /** @var array<int, int> $e */
        $e = array();
        $r = new ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1();

        for ($i = 0; $i < 32; ++$i) {
            /** @var int $dbl */
            $dbl = (int) $i << 1;
            $e[$dbl] = (int) self::chrToInt($a[$i]) & 15;
            $e[$dbl + 1] = (int) (self::chrToInt($a[$i]) >> 4) & 15;
        }

        /** @var int $carry */
        $carry = 0;
        for ($i = 0; $i < 63; ++$i) {
            $e[$i] += $carry;
            /** @var int $carry */
            $carry = $e[$i] + 8;
            /** @var int $carry */
            $carry >>= 4;
            $e[$i] -= $carry << 4;
        }

        /** @var array<int, int> $e */
        $e[63] += (int) $carry;

        $h = self::ge_p3_0();

        for ($i = 1; $i < 64; $i += 2) {
            $t = self::ge_select((int) floor($i / 2), (int) $e[$i]);
            $r = self::ge_madd($r, $h, $t);
            $h = self::ge_p1p1_to_p3($r);
        }

        $r = self::ge_p3_dbl($h);

        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);
        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);
        $s = self::ge_p1p1_to_p2($r);
        $r = self::ge_p2_dbl($s);

        $h = self::ge_p1p1_to_p3($r);

        for ($i = 0; $i < 64; $i += 2) {
            $t = self::ge_select($i >> 1, (int) $e[$i]);
            $r = self::ge_madd($r, $h, $t);
            $h = self::ge_p1p1_to_p3($r);
        }
        return $h;
    }

    /**
     * Calculates (ab + c) mod l
     * where l = 2^252 + 27742317777372353535851937790883648493
     *
     * @internal You should not use this directly from another application
     *
     * @param string $a
     * @param string $b
     * @param string $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sc_muladd($a, $b, $c)
    {
        $a0 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($a, 0, 3)));
        $a1 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($a, 2, 4)) >> 5));
        $a2 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($a, 5, 3)) >> 2));
        $a3 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($a, 7, 4)) >> 7));
        $a4 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($a, 10, 4)) >> 4));
        $a5 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($a, 13, 3)) >> 1));
        $a6 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($a, 15, 4)) >> 6));
        $a7 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($a, 18, 3)) >> 3));
        $a8 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($a, 21, 3)));
        $a9 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($a, 23, 4)) >> 5));
        $a10 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($a, 26, 3)) >> 2));
        $a11 = ParagonIE_Sodium_Core32_Int64::fromInt(0x1fffffff & (self::load_4(self::substr($a, 28, 4)) >> 7));
        $b0 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($b, 0, 3)));
        $b1 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($b, 2, 4)) >> 5));
        $b2 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($b, 5, 3)) >> 2));
        $b3 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($b, 7, 4)) >> 7));
        $b4 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($b, 10, 4)) >> 4));
        $b5 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($b, 13, 3)) >> 1));
        $b6 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($b, 15, 4)) >> 6));
        $b7 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($b, 18, 3)) >> 3));
        $b8 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($b, 21, 3)));
        $b9 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($b, 23, 4)) >> 5));
        $b10 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($b, 26, 3)) >> 2));
        $b11 = ParagonIE_Sodium_Core32_Int64::fromInt(0x1fffffff & (self::load_4(self::substr($b, 28, 4)) >> 7));
        $c0 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($c, 0, 3)));
        $c1 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($c, 2, 4)) >> 5));
        $c2 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($c, 5, 3)) >> 2));
        $c3 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($c, 7, 4)) >> 7));
        $c4 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($c, 10, 4)) >> 4));
        $c5 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($c, 13, 3)) >> 1));
        $c6 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($c, 15, 4)) >> 6));
        $c7 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($c, 18, 3)) >> 3));
        $c8 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($c, 21, 3)));
        $c9 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($c, 23, 4)) >> 5));
        $c10 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($c, 26, 3)) >> 2));
        $c11 = ParagonIE_Sodium_Core32_Int64::fromInt(0x1fffffff & (self::load_4(self::substr($c, 28, 4)) >> 7));

        /* Can't really avoid the pyramid here: */
        /**
         * @var ParagonIE_Sodium_Core32_Int64 $s0
         * @var ParagonIE_Sodium_Core32_Int64 $s1
         * @var ParagonIE_Sodium_Core32_Int64 $s2
         * @var ParagonIE_Sodium_Core32_Int64 $s3
         * @var ParagonIE_Sodium_Core32_Int64 $s4
         * @var ParagonIE_Sodium_Core32_Int64 $s5
         * @var ParagonIE_Sodium_Core32_Int64 $s6
         * @var ParagonIE_Sodium_Core32_Int64 $s7
         * @var ParagonIE_Sodium_Core32_Int64 $s8
         * @var ParagonIE_Sodium_Core32_Int64 $s9
         * @var ParagonIE_Sodium_Core32_Int64 $s10
         * @var ParagonIE_Sodium_Core32_Int64 $s11
         * @var ParagonIE_Sodium_Core32_Int64 $s12
         * @var ParagonIE_Sodium_Core32_Int64 $s13
         * @var ParagonIE_Sodium_Core32_Int64 $s14
         * @var ParagonIE_Sodium_Core32_Int64 $s15
         * @var ParagonIE_Sodium_Core32_Int64 $s16
         * @var ParagonIE_Sodium_Core32_Int64 $s17
         * @var ParagonIE_Sodium_Core32_Int64 $s18
         * @var ParagonIE_Sodium_Core32_Int64 $s19
         * @var ParagonIE_Sodium_Core32_Int64 $s20
         * @var ParagonIE_Sodium_Core32_Int64 $s21
         * @var ParagonIE_Sodium_Core32_Int64 $s22
         * @var ParagonIE_Sodium_Core32_Int64 $s23
         */

        $s0 = $c0->addInt64($a0->mulInt64($b0, 24));
        $s1 = $c1->addInt64($a0->mulInt64($b1, 24))->addInt64($a1->mulInt64($b0, 24));
        $s2 = $c2->addInt64($a0->mulInt64($b2, 24))->addInt64($a1->mulInt64($b1, 24))->addInt64($a2->mulInt64($b0, 24));
        $s3 = $c3->addInt64($a0->mulInt64($b3, 24))->addInt64($a1->mulInt64($b2, 24))->addInt64($a2->mulInt64($b1, 24))
                 ->addInt64($a3->mulInt64($b0, 24));
        $s4 = $c4->addInt64($a0->mulInt64($b4, 24))->addInt64($a1->mulInt64($b3, 24))->addInt64($a2->mulInt64($b2, 24))
                 ->addInt64($a3->mulInt64($b1, 24))->addInt64($a4->mulInt64($b0, 24));
        $s5 = $c5->addInt64($a0->mulInt64($b5, 24))->addInt64($a1->mulInt64($b4, 24))->addInt64($a2->mulInt64($b3, 24))
                 ->addInt64($a3->mulInt64($b2, 24))->addInt64($a4->mulInt64($b1, 24))->addInt64($a5->mulInt64($b0, 24));
        $s6 = $c6->addInt64($a0->mulInt64($b6, 24))->addInt64($a1->mulInt64($b5, 24))->addInt64($a2->mulInt64($b4, 24))
                 ->addInt64($a3->mulInt64($b3, 24))->addInt64($a4->mulInt64($b2, 24))->addInt64($a5->mulInt64($b1, 24))
                 ->addInt64($a6->mulInt64($b0, 24));
        $s7 = $c7->addInt64($a0->mulInt64($b7, 24))->addInt64($a1->mulInt64($b6, 24))->addInt64($a2->mulInt64($b5, 24))
                 ->addInt64($a3->mulInt64($b4, 24))->addInt64($a4->mulInt64($b3, 24))->addInt64($a5->mulInt64($b2, 24))
                 ->addInt64($a6->mulInt64($b1, 24))->addInt64($a7->mulInt64($b0, 24));
        $s8 = $c8->addInt64($a0->mulInt64($b8, 24))->addInt64($a1->mulInt64($b7, 24))->addInt64($a2->mulInt64($b6, 24))
                 ->addInt64($a3->mulInt64($b5, 24))->addInt64($a4->mulInt64($b4, 24))->addInt64($a5->mulInt64($b3, 24))
                 ->addInt64($a6->mulInt64($b2, 24))->addInt64($a7->mulInt64($b1, 24))->addInt64($a8->mulInt64($b0, 24));
        $s9 = $c9->addInt64($a0->mulInt64($b9, 24))->addInt64($a1->mulInt64($b8, 24))->addInt64($a2->mulInt64($b7, 24))
                 ->addInt64($a3->mulInt64($b6, 24))->addInt64($a4->mulInt64($b5, 24))->addInt64($a5->mulInt64($b4, 24))
                 ->addInt64($a6->mulInt64($b3, 24))->addInt64($a7->mulInt64($b2, 24))->addInt64($a8->mulInt64($b1, 24))
                 ->addInt64($a9->mulInt64($b0, 24));
        $s10 = $c10->addInt64($a0->mulInt64($b10, 24))->addInt64($a1->mulInt64($b9, 24))->addInt64($a2->mulInt64($b8, 24))
                   ->addInt64($a3->mulInt64($b7, 24))->addInt64($a4->mulInt64($b6, 24))->addInt64($a5->mulInt64($b5, 24))
                   ->addInt64($a6->mulInt64($b4, 24))->addInt64($a7->mulInt64($b3, 24))->addInt64($a8->mulInt64($b2, 24))
                   ->addInt64($a9->mulInt64($b1, 24))->addInt64($a10->mulInt64($b0, 24));
        $s11 = $c11->addInt64($a0->mulInt64($b11, 24))->addInt64($a1->mulInt64($b10, 24))->addInt64($a2->mulInt64($b9, 24))
                   ->addInt64($a3->mulInt64($b8, 24))->addInt64($a4->mulInt64($b7, 24))->addInt64($a5->mulInt64($b6, 24))
                   ->addInt64($a6->mulInt64($b5, 24))->addInt64($a7->mulInt64($b4, 24))->addInt64($a8->mulInt64($b3, 24))
                   ->addInt64($a9->mulInt64($b2, 24))->addInt64($a10->mulInt64($b1, 24))->addInt64($a11->mulInt64($b0, 24));
        $s12 = $a1->mulInt64($b11, 24)->addInt64($a2->mulInt64($b10, 24))->addInt64($a3->mulInt64($b9, 24))
                  ->addInt64($a4->mulInt64($b8, 24))->addInt64($a5->mulInt64($b7, 24))->addInt64($a6->mulInt64($b6, 24))
                  ->addInt64($a7->mulInt64($b5, 24))->addInt64($a8->mulInt64($b4, 24))->addInt64($a9->mulInt64($b3, 24))
                  ->addInt64($a10->mulInt64($b2, 24))->addInt64($a11->mulInt64($b1, 24));
        $s13 = $a2->mulInt64($b11, 24)->addInt64($a3->mulInt64($b10, 24))->addInt64($a4->mulInt64($b9, 24))
                  ->addInt64($a5->mulInt64($b8, 24))->addInt64($a6->mulInt64($b7, 24))->addInt64($a7->mulInt64($b6, 24))
                  ->addInt64($a8->mulInt64($b5, 24))->addInt64($a9->mulInt64($b4, 24))->addInt64($a10->mulInt64($b3, 24))
                  ->addInt64($a11->mulInt64($b2, 24));
        $s14 = $a3->mulInt64($b11, 24)->addInt64($a4->mulInt64($b10, 24))->addInt64($a5->mulInt64($b9, 24))
                  ->addInt64($a6->mulInt64($b8, 24))->addInt64($a7->mulInt64($b7, 24))->addInt64($a8->mulInt64($b6, 24))
                  ->addInt64($a9->mulInt64($b5, 24))->addInt64($a10->mulInt64($b4, 24))->addInt64($a11->mulInt64($b3, 24));
        $s15 = $a4->mulInt64($b11, 24)->addInt64($a5->mulInt64($b10, 24))->addInt64($a6->mulInt64($b9, 24))
                  ->addInt64($a7->mulInt64($b8, 24))->addInt64($a8->mulInt64($b7, 24))->addInt64($a9->mulInt64($b6, 24))
                  ->addInt64($a10->mulInt64($b5, 24))->addInt64($a11->mulInt64($b4, 24));
        $s16 = $a5->mulInt64($b11, 24)->addInt64($a6->mulInt64($b10, 24))->addInt64($a7->mulInt64($b9, 24))
                  ->addInt64($a8->mulInt64($b8, 24))->addInt64($a9->mulInt64($b7, 24))->addInt64($a10->mulInt64($b6, 24))
                  ->addInt64($a11->mulInt64($b5, 24));
        $s17 = $a6->mulInt64($b11, 24)->addInt64($a7->mulInt64($b10, 24))->addInt64($a8->mulInt64($b9, 24))
                  ->addInt64($a9->mulInt64($b8, 24))->addInt64($a10->mulInt64($b7, 24))->addInt64($a11->mulInt64($b6, 24));
        $s18 = $a7->mulInt64($b11, 24)->addInt64($a8->mulInt64($b10, 24))->addInt64($a9->mulInt64($b9, 24))
                  ->addInt64($a10->mulInt64($b8, 24))->addInt64($a11->mulInt64($b7, 24));
        $s19 = $a8->mulInt64($b11, 24)->addInt64($a9->mulInt64($b10, 24))->addInt64($a10->mulInt64($b9, 24))
                  ->addInt64($a11->mulInt64($b8, 24));
        $s20 = $a9->mulInt64($b11, 24)->addInt64($a10->mulInt64($b10, 24))->addInt64($a11->mulInt64($b9, 24));
        $s21 = $a10->mulInt64($b11, 24)->addInt64($a11->mulInt64($b10, 24));
        $s22 = $a11->mulInt64($b11, 24);
        $s23 = new ParagonIE_Sodium_Core32_Int64();

        $carry0 = $s0->addInt(1 << 20)->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry2 = $s2->addInt(1 << 20)->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry4 = $s4->addInt(1 << 20)->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry6 = $s6->addInt(1 << 20)->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry8 = $s8->addInt(1 << 20)->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry10 = $s10->addInt(1 << 20)->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry12 = $s12->addInt(1 << 20)->shiftRight(21);
        $s13 = $s13->addInt64($carry12);
        $s12 = $s12->subInt64($carry12->shiftLeft(21));
        $carry14 = $s14->addInt(1 << 20)->shiftRight(21);
        $s15 = $s15->addInt64($carry14);
        $s14 = $s14->subInt64($carry14->shiftLeft(21));
        $carry16 = $s16->addInt(1 << 20)->shiftRight(21);
        $s17 = $s17->addInt64($carry16);
        $s16 = $s16->subInt64($carry16->shiftLeft(21));
        $carry18 = $s18->addInt(1 << 20)->shiftRight(21);
        $s19 = $s19->addInt64($carry18);
        $s18 = $s18->subInt64($carry18->shiftLeft(21));
        $carry20 = $s20->addInt(1 << 20)->shiftRight(21);
        $s21 = $s21->addInt64($carry20);
        $s20 = $s20->subInt64($carry20->shiftLeft(21));
        $carry22 = $s22->addInt(1 << 20)->shiftRight(21);
        $s23 = $s23->addInt64($carry22);
        $s22 = $s22->subInt64($carry22->shiftLeft(21));

        $carry1 = $s1->addInt(1 << 20)->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry3 = $s3->addInt(1 << 20)->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry5 = $s5->addInt(1 << 20)->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry7 = $s7->addInt(1 << 20)->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry9 = $s9->addInt(1 << 20)->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry11 = $s11->addInt(1 << 20)->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));
        $carry13 = $s13->addInt(1 << 20)->shiftRight(21);
        $s14 = $s14->addInt64($carry13);
        $s13 = $s13->subInt64($carry13->shiftLeft(21));
        $carry15 = $s15->addInt(1 << 20)->shiftRight(21);
        $s16 = $s16->addInt64($carry15);
        $s15 = $s15->subInt64($carry15->shiftLeft(21));
        $carry17 = $s17->addInt(1 << 20)->shiftRight(21);
        $s18 = $s18->addInt64($carry17);
        $s17 = $s17->subInt64($carry17->shiftLeft(21));
        $carry19 = $s19->addInt(1 << 20)->shiftRight(21);
        $s20 = $s20->addInt64($carry19);
        $s19 = $s19->subInt64($carry19->shiftLeft(21));
        $carry21 = $s21->addInt(1 << 20)->shiftRight(21);
        $s22 = $s22->addInt64($carry21);
        $s21 = $s21->subInt64($carry21->shiftLeft(21));

        $s11 = $s11->addInt64($s23->mulInt(666643, 20));
        $s12 = $s12->addInt64($s23->mulInt(470296, 19));
        $s13 = $s13->addInt64($s23->mulInt(654183, 20));
        $s14 = $s14->subInt64($s23->mulInt(997805, 20));
        $s15 = $s15->addInt64($s23->mulInt(136657, 18));
        $s16 = $s16->subInt64($s23->mulInt(683901, 20));

        $s10 = $s10->addInt64($s22->mulInt(666643, 20));
        $s11 = $s11->addInt64($s22->mulInt(470296, 19));
        $s12 = $s12->addInt64($s22->mulInt(654183, 20));
        $s13 = $s13->subInt64($s22->mulInt(997805, 20));
        $s14 = $s14->addInt64($s22->mulInt(136657, 18));
        $s15 = $s15->subInt64($s22->mulInt(683901, 20));

        $s9  =  $s9->addInt64($s21->mulInt(666643, 20));
        $s10 = $s10->addInt64($s21->mulInt(470296, 19));
        $s11 = $s11->addInt64($s21->mulInt(654183, 20));
        $s12 = $s12->subInt64($s21->mulInt(997805, 20));
        $s13 = $s13->addInt64($s21->mulInt(136657, 18));
        $s14 = $s14->subInt64($s21->mulInt(683901, 20));

        $s8  =  $s8->addInt64($s20->mulInt(666643, 20));
        $s9  =  $s9->addInt64($s20->mulInt(470296, 19));
        $s10 = $s10->addInt64($s20->mulInt(654183, 20));
        $s11 = $s11->subInt64($s20->mulInt(997805, 20));
        $s12 = $s12->addInt64($s20->mulInt(136657, 18));
        $s13 = $s13->subInt64($s20->mulInt(683901, 20));

        $s7  =  $s7->addInt64($s19->mulInt(666643, 20));
        $s8  =  $s8->addInt64($s19->mulInt(470296, 19));
        $s9  =  $s9->addInt64($s19->mulInt(654183, 20));
        $s10 = $s10->subInt64($s19->mulInt(997805, 20));
        $s11 = $s11->addInt64($s19->mulInt(136657, 18));
        $s12 = $s12->subInt64($s19->mulInt(683901, 20));

        $s6  =  $s6->addInt64($s18->mulInt(666643, 20));
        $s7  =  $s7->addInt64($s18->mulInt(470296, 19));
        $s8  =  $s8->addInt64($s18->mulInt(654183, 20));
        $s9  =  $s9->subInt64($s18->mulInt(997805, 20));
        $s10 = $s10->addInt64($s18->mulInt(136657, 18));
        $s11 = $s11->subInt64($s18->mulInt(683901, 20));

        $carry6 = $s6->addInt(1 << 20)->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry8 = $s8->addInt(1 << 20)->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry10 = $s10->addInt(1 << 20)->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry12 = $s12->addInt(1 << 20)->shiftRight(21);
        $s13 = $s13->addInt64($carry12);
        $s12 = $s12->subInt64($carry12->shiftLeft(21));
        $carry14 = $s14->addInt(1 << 20)->shiftRight(21);
        $s15 = $s15->addInt64($carry14);
        $s14 = $s14->subInt64($carry14->shiftLeft(21));
        $carry16 = $s16->addInt(1 << 20)->shiftRight(21);
        $s17 = $s17->addInt64($carry16);
        $s16 = $s16->subInt64($carry16->shiftLeft(21));

        $carry7 = $s7->addInt(1 << 20)->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry9 = $s9->addInt(1 << 20)->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry11 = $s11->addInt(1 << 20)->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));
        $carry13 = $s13->addInt(1 << 20)->shiftRight(21);
        $s14 = $s14->addInt64($carry13);
        $s13 = $s13->subInt64($carry13->shiftLeft(21));
        $carry15 = $s15->addInt(1 << 20)->shiftRight(21);
        $s16 = $s16->addInt64($carry15);
        $s15 = $s15->subInt64($carry15->shiftLeft(21));

        $s5  =  $s5->addInt64($s17->mulInt(666643, 20));
        $s6  =  $s6->addInt64($s17->mulInt(470296, 19));
        $s7  =  $s7->addInt64($s17->mulInt(654183, 20));
        $s8  =  $s8->subInt64($s17->mulInt(997805, 20));
        $s9  =  $s9->addInt64($s17->mulInt(136657, 18));
        $s10 = $s10->subInt64($s17->mulInt(683901, 20));

        $s4  =  $s4->addInt64($s16->mulInt(666643, 20));
        $s5  =  $s5->addInt64($s16->mulInt(470296, 19));
        $s6  =  $s6->addInt64($s16->mulInt(654183, 20));
        $s7  =  $s7->subInt64($s16->mulInt(997805, 20));
        $s8  =  $s8->addInt64($s16->mulInt(136657, 18));
        $s9  =  $s9->subInt64($s16->mulInt(683901, 20));

        $s3  =  $s3->addInt64($s15->mulInt(666643, 20));
        $s4  =  $s4->addInt64($s15->mulInt(470296, 19));
        $s5  =  $s5->addInt64($s15->mulInt(654183, 20));
        $s6  =  $s6->subInt64($s15->mulInt(997805, 20));
        $s7  =  $s7->addInt64($s15->mulInt(136657, 18));
        $s8  =  $s8->subInt64($s15->mulInt(683901, 20));

        $s2  =  $s2->addInt64($s14->mulInt(666643, 20));
        $s3  =  $s3->addInt64($s14->mulInt(470296, 19));
        $s4  =  $s4->addInt64($s14->mulInt(654183, 20));
        $s5  =  $s5->subInt64($s14->mulInt(997805, 20));
        $s6  =  $s6->addInt64($s14->mulInt(136657, 18));
        $s7  =  $s7->subInt64($s14->mulInt(683901, 20));

        $s1  =  $s1->addInt64($s13->mulInt(666643, 20));
        $s2  =  $s2->addInt64($s13->mulInt(470296, 19));
        $s3  =  $s3->addInt64($s13->mulInt(654183, 20));
        $s4  =  $s4->subInt64($s13->mulInt(997805, 20));
        $s5  =  $s5->addInt64($s13->mulInt(136657, 18));
        $s6  =  $s6->subInt64($s13->mulInt(683901, 20));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));
        $s12 = new ParagonIE_Sodium_Core32_Int64();

        $carry0 = $s0->addInt(1 << 20)->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry2 = $s2->addInt(1 << 20)->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry4 = $s4->addInt(1 << 20)->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry6 = $s6->addInt(1 << 20)->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry8 = $s8->addInt(1 << 20)->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry10 = $s10->addInt(1 << 20)->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));

        $carry1 = $s1->addInt(1 << 20)->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry3 = $s3->addInt(1 << 20)->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry5 = $s5->addInt(1 << 20)->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry7 = $s7->addInt(1 << 20)->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry9 = $s9->addInt(1 << 20)->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry11 = $s11->addInt(1 << 20)->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));
        $s12 = new ParagonIE_Sodium_Core32_Int64();

        $carry0 = $s0->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry1 = $s1->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry2 = $s2->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry3 = $s3->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry4 = $s4->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry5 = $s5->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry6 = $s6->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry7 = $s7->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry8 = $s8->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry9 = $s9->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry10 = $s10->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry11 = $s11->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));

        $carry0 = $s0->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry1 = $s1->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry2 = $s2->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry3 = $s3->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry4 = $s4->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry5 = $s5->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry6 = $s6->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry7 = $s7->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry8 = $s10->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry9 = $s9->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry10 = $s10->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));

        $S0  =  $s0->toInt();
        $S1  =  $s1->toInt();
        $S2  =  $s2->toInt();
        $S3  =  $s3->toInt();
        $S4  =  $s4->toInt();
        $S5  =  $s5->toInt();
        $S6  =  $s6->toInt();
        $S7  =  $s7->toInt();
        $S8  =  $s8->toInt();
        $S9  =  $s9->toInt();
        $S10 = $s10->toInt();
        $S11 = $s11->toInt();

        /**
         * @var array<int, int>
         */
        $arr = array(
            (int) (0xff & ($S0 >> 0)),
            (int) (0xff & ($S0 >> 8)),
            (int) (0xff & (($S0 >> 16) | ($S1 << 5))),
            (int) (0xff & ($S1 >> 3)),
            (int) (0xff & ($S1 >> 11)),
            (int) (0xff & (($S1 >> 19) | ($S2 << 2))),
            (int) (0xff & ($S2 >> 6)),
            (int) (0xff & (($S2 >> 14) | ($S3 << 7))),
            (int) (0xff & ($S3 >> 1)),
            (int) (0xff & ($S3 >> 9)),
            (int) (0xff & (($S3 >> 17) | ($S4 << 4))),
            (int) (0xff & ($S4 >> 4)),
            (int) (0xff & ($S4 >> 12)),
            (int) (0xff & (($S4 >> 20) | ($S5 << 1))),
            (int) (0xff & ($S5 >> 7)),
            (int) (0xff & (($S5 >> 15) | ($S6 << 6))),
            (int) (0xff & ($S6 >> 2)),
            (int) (0xff & ($S6 >> 10)),
            (int) (0xff & (($S6 >> 18) | ($S7 << 3))),
            (int) (0xff & ($S7 >> 5)),
            (int) (0xff & ($S7 >> 13)),
            (int) (0xff & ($S8 >> 0)),
            (int) (0xff & ($S8 >> 8)),
            (int) (0xff & (($S8 >> 16) | ($S9 << 5))),
            (int) (0xff & ($S9 >> 3)),
            (int) (0xff & ($S9 >> 11)),
            (int) (0xff & (($S9 >> 19) | ($S10 << 2))),
            (int) (0xff & ($S10 >> 6)),
            (int) (0xff & (($S10 >> 14) | ($S11 << 7))),
            (int) (0xff & ($S11 >> 1)),
            (int) (0xff & ($S11 >> 9)),
            (int) (0xff & ($S11 >> 17))
        );
        return self::intArrayToString($arr);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $s
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sc_reduce($s)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int64 $s0
         * @var ParagonIE_Sodium_Core32_Int64 $s1
         * @var ParagonIE_Sodium_Core32_Int64 $s2
         * @var ParagonIE_Sodium_Core32_Int64 $s3
         * @var ParagonIE_Sodium_Core32_Int64 $s4
         * @var ParagonIE_Sodium_Core32_Int64 $s5
         * @var ParagonIE_Sodium_Core32_Int64 $s6
         * @var ParagonIE_Sodium_Core32_Int64 $s7
         * @var ParagonIE_Sodium_Core32_Int64 $s8
         * @var ParagonIE_Sodium_Core32_Int64 $s9
         * @var ParagonIE_Sodium_Core32_Int64 $s10
         * @var ParagonIE_Sodium_Core32_Int64 $s11
         * @var ParagonIE_Sodium_Core32_Int64 $s12
         * @var ParagonIE_Sodium_Core32_Int64 $s13
         * @var ParagonIE_Sodium_Core32_Int64 $s14
         * @var ParagonIE_Sodium_Core32_Int64 $s15
         * @var ParagonIE_Sodium_Core32_Int64 $s16
         * @var ParagonIE_Sodium_Core32_Int64 $s17
         * @var ParagonIE_Sodium_Core32_Int64 $s18
         * @var ParagonIE_Sodium_Core32_Int64 $s19
         * @var ParagonIE_Sodium_Core32_Int64 $s20
         * @var ParagonIE_Sodium_Core32_Int64 $s21
         * @var ParagonIE_Sodium_Core32_Int64 $s22
         * @var ParagonIE_Sodium_Core32_Int64 $s23
         */
        $s0 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($s, 0, 3)));
        $s1 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 2, 4)) >> 5));
        $s2 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 5, 3)) >> 2));
        $s3 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 7, 4)) >> 7));
        $s4 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 10, 4)) >> 4));
        $s5 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 13, 3)) >> 1));
        $s6 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 15, 4)) >> 6));
        $s7 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 18, 4)) >> 3));
        $s8 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($s, 21, 3)));
        $s9 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 23, 4)) >> 5));
        $s10 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 26, 3)) >> 2));
        $s11 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 28, 4)) >> 7));
        $s12 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 31, 4)) >> 4));
        $s13 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 34, 3)) >> 1));
        $s14 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 36, 4)) >> 6));
        $s15 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 39, 4)) >> 3));
        $s16 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & self::load_3(self::substr($s, 42, 3)));
        $s17 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 44, 4)) >> 5));
        $s18 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 47, 3)) >> 2));
        $s19 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 49, 4)) >> 7));
        $s20 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 52, 4)) >> 4));
        $s21 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_3(self::substr($s, 55, 3)) >> 1));
        $s22 = ParagonIE_Sodium_Core32_Int64::fromInt(2097151 & (self::load_4(self::substr($s, 57, 4)) >> 6));
        $s23 = ParagonIE_Sodium_Core32_Int64::fromInt(0x1fffffff & (self::load_4(self::substr($s, 60, 4)) >> 3));

        $s11 = $s11->addInt64($s23->mulInt(666643, 20));
        $s12 = $s12->addInt64($s23->mulInt(470296, 19));
        $s13 = $s13->addInt64($s23->mulInt(654183, 20));
        $s14 = $s14->subInt64($s23->mulInt(997805, 20));
        $s15 = $s15->addInt64($s23->mulInt(136657, 18));
        $s16 = $s16->subInt64($s23->mulInt(683901, 20));

        $s10 = $s10->addInt64($s22->mulInt(666643, 20));
        $s11 = $s11->addInt64($s22->mulInt(470296, 19));
        $s12 = $s12->addInt64($s22->mulInt(654183, 20));
        $s13 = $s13->subInt64($s22->mulInt(997805, 20));
        $s14 = $s14->addInt64($s22->mulInt(136657, 18));
        $s15 = $s15->subInt64($s22->mulInt(683901, 20));

        $s9  =  $s9->addInt64($s21->mulInt(666643, 20));
        $s10 = $s10->addInt64($s21->mulInt(470296, 19));
        $s11 = $s11->addInt64($s21->mulInt(654183, 20));
        $s12 = $s12->subInt64($s21->mulInt(997805, 20));
        $s13 = $s13->addInt64($s21->mulInt(136657, 18));
        $s14 = $s14->subInt64($s21->mulInt(683901, 20));

        $s8  =  $s8->addInt64($s20->mulInt(666643, 20));
        $s9  =  $s9->addInt64($s20->mulInt(470296, 19));
        $s10 = $s10->addInt64($s20->mulInt(654183, 20));
        $s11 = $s11->subInt64($s20->mulInt(997805, 20));
        $s12 = $s12->addInt64($s20->mulInt(136657, 18));
        $s13 = $s13->subInt64($s20->mulInt(683901, 20));

        $s7  =  $s7->addInt64($s19->mulInt(666643, 20));
        $s8  =  $s8->addInt64($s19->mulInt(470296, 19));
        $s9  =  $s9->addInt64($s19->mulInt(654183, 20));
        $s10 = $s10->subInt64($s19->mulInt(997805, 20));
        $s11 = $s11->addInt64($s19->mulInt(136657, 18));
        $s12 = $s12->subInt64($s19->mulInt(683901, 20));

        $s6  =  $s6->addInt64($s18->mulInt(666643, 20));
        $s7  =  $s7->addInt64($s18->mulInt(470296, 19));
        $s8  =  $s8->addInt64($s18->mulInt(654183, 20));
        $s9  =  $s9->subInt64($s18->mulInt(997805, 20));
        $s10 = $s10->addInt64($s18->mulInt(136657, 18));
        $s11 = $s11->subInt64($s18->mulInt(683901, 20));

        $carry6 = $s6->addInt(1 << 20)->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry8 = $s8->addInt(1 << 20)->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry10 = $s10->addInt(1 << 20)->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry12 = $s12->addInt(1 << 20)->shiftRight(21);
        $s13 = $s13->addInt64($carry12);
        $s12 = $s12->subInt64($carry12->shiftLeft(21));
        $carry14 = $s14->addInt(1 << 20)->shiftRight(21);
        $s15 = $s15->addInt64($carry14);
        $s14 = $s14->subInt64($carry14->shiftLeft(21));
        $carry16 = $s16->addInt(1 << 20)->shiftRight(21);
        $s17 = $s17->addInt64($carry16);
        $s16 = $s16->subInt64($carry16->shiftLeft(21));

        $carry7 = $s7->addInt(1 << 20)->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry9 = $s9->addInt(1 << 20)->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry11 = $s11->addInt(1 << 20)->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));
        $carry13 = $s13->addInt(1 << 20)->shiftRight(21);
        $s14 = $s14->addInt64($carry13);
        $s13 = $s13->subInt64($carry13->shiftLeft(21));
        $carry15 = $s15->addInt(1 << 20)->shiftRight(21);
        $s16 = $s16->addInt64($carry15);
        $s15 = $s15->subInt64($carry15->shiftLeft(21));

        $s5  =  $s5->addInt64($s17->mulInt(666643, 20));
        $s6  =  $s6->addInt64($s17->mulInt(470296, 19));
        $s7  =  $s7->addInt64($s17->mulInt(654183, 20));
        $s8  =  $s8->subInt64($s17->mulInt(997805, 20));
        $s9  =  $s9->addInt64($s17->mulInt(136657, 18));
        $s10 = $s10->subInt64($s17->mulInt(683901, 20));

        $s4  =  $s4->addInt64($s16->mulInt(666643, 20));
        $s5  =  $s5->addInt64($s16->mulInt(470296, 19));
        $s6  =  $s6->addInt64($s16->mulInt(654183, 20));
        $s7  =  $s7->subInt64($s16->mulInt(997805, 20));
        $s8  =  $s8->addInt64($s16->mulInt(136657, 18));
        $s9  =  $s9->subInt64($s16->mulInt(683901, 20));

        $s3  =  $s3->addInt64($s15->mulInt(666643, 20));
        $s4  =  $s4->addInt64($s15->mulInt(470296, 19));
        $s5  =  $s5->addInt64($s15->mulInt(654183, 20));
        $s6  =  $s6->subInt64($s15->mulInt(997805, 20));
        $s7  =  $s7->addInt64($s15->mulInt(136657, 18));
        $s8  =  $s8->subInt64($s15->mulInt(683901, 20));

        $s2  =  $s2->addInt64($s14->mulInt(666643, 20));
        $s3  =  $s3->addInt64($s14->mulInt(470296, 19));
        $s4  =  $s4->addInt64($s14->mulInt(654183, 20));
        $s5  =  $s5->subInt64($s14->mulInt(997805, 20));
        $s6  =  $s6->addInt64($s14->mulInt(136657, 18));
        $s7  =  $s7->subInt64($s14->mulInt(683901, 20));

        $s1  =  $s1->addInt64($s13->mulInt(666643, 20));
        $s2  =  $s2->addInt64($s13->mulInt(470296, 19));
        $s3  =  $s3->addInt64($s13->mulInt(654183, 20));
        $s4  =  $s4->subInt64($s13->mulInt(997805, 20));
        $s5  =  $s5->addInt64($s13->mulInt(136657, 18));
        $s6  =  $s6->subInt64($s13->mulInt(683901, 20));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));
        $s12 = new ParagonIE_Sodium_Core32_Int64();

        $carry0 = $s0->addInt(1 << 20)->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry2 = $s2->addInt(1 << 20)->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry4 = $s4->addInt(1 << 20)->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry6 = $s6->addInt(1 << 20)->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry8 = $s8->addInt(1 << 20)->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry10 = $s10->addInt(1 << 20)->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry1 = $s1->addInt(1 << 20)->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry3 = $s3->addInt(1 << 20)->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry5 = $s5->addInt(1 << 20)->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry7 = $s7->addInt(1 << 20)->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry9 = $s9->addInt(1 << 20)->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry11 = $s11->addInt(1 << 20)->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));
        $s12 = new ParagonIE_Sodium_Core32_Int64();

        $carry0 = $s0->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry1 = $s1->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry2 = $s2->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry3 = $s3->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry4 = $s4->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry5 = $s5->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry6 = $s6->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry7 = $s7->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry8 = $s8->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry9 = $s9->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry10 = $s10->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));
        $carry11 = $s11->shiftRight(21);
        $s12 = $s12->addInt64($carry11);
        $s11 = $s11->subInt64($carry11->shiftLeft(21));

        $s0  =  $s0->addInt64($s12->mulInt(666643, 20));
        $s1  =  $s1->addInt64($s12->mulInt(470296, 19));
        $s2  =  $s2->addInt64($s12->mulInt(654183, 20));
        $s3  =  $s3->subInt64($s12->mulInt(997805, 20));
        $s4  =  $s4->addInt64($s12->mulInt(136657, 18));
        $s5  =  $s5->subInt64($s12->mulInt(683901, 20));

        $carry0 = $s0->shiftRight(21);
        $s1 = $s1->addInt64($carry0);
        $s0 = $s0->subInt64($carry0->shiftLeft(21));
        $carry1 = $s1->shiftRight(21);
        $s2 = $s2->addInt64($carry1);
        $s1 = $s1->subInt64($carry1->shiftLeft(21));
        $carry2 = $s2->shiftRight(21);
        $s3 = $s3->addInt64($carry2);
        $s2 = $s2->subInt64($carry2->shiftLeft(21));
        $carry3 = $s3->shiftRight(21);
        $s4 = $s4->addInt64($carry3);
        $s3 = $s3->subInt64($carry3->shiftLeft(21));
        $carry4 = $s4->shiftRight(21);
        $s5 = $s5->addInt64($carry4);
        $s4 = $s4->subInt64($carry4->shiftLeft(21));
        $carry5 = $s5->shiftRight(21);
        $s6 = $s6->addInt64($carry5);
        $s5 = $s5->subInt64($carry5->shiftLeft(21));
        $carry6 = $s6->shiftRight(21);
        $s7 = $s7->addInt64($carry6);
        $s6 = $s6->subInt64($carry6->shiftLeft(21));
        $carry7 = $s7->shiftRight(21);
        $s8 = $s8->addInt64($carry7);
        $s7 = $s7->subInt64($carry7->shiftLeft(21));
        $carry8 = $s8->shiftRight(21);
        $s9 = $s9->addInt64($carry8);
        $s8 = $s8->subInt64($carry8->shiftLeft(21));
        $carry9 = $s9->shiftRight(21);
        $s10 = $s10->addInt64($carry9);
        $s9 = $s9->subInt64($carry9->shiftLeft(21));
        $carry10 = $s10->shiftRight(21);
        $s11 = $s11->addInt64($carry10);
        $s10 = $s10->subInt64($carry10->shiftLeft(21));

        $S0 = $s0->toInt32()->toInt();
        $S1 = $s1->toInt32()->toInt();
        $S2 = $s2->toInt32()->toInt();
        $S3 = $s3->toInt32()->toInt();
        $S4 = $s4->toInt32()->toInt();
        $S5 = $s5->toInt32()->toInt();
        $S6 = $s6->toInt32()->toInt();
        $S7 = $s7->toInt32()->toInt();
        $S8 = $s8->toInt32()->toInt();
        $S9 = $s9->toInt32()->toInt();
        $S10 = $s10->toInt32()->toInt();
        $S11 = $s11->toInt32()->toInt();

        /**
         * @var array<int, int>
         */
        $arr = array(
            (int) ($S0 >> 0),
            (int) ($S0 >> 8),
            (int) (($S0 >> 16) | ($S1 << 5)),
            (int) ($S1 >> 3),
            (int) ($S1 >> 11),
            (int) (($S1 >> 19) | ($S2 << 2)),
            (int) ($S2 >> 6),
            (int) (($S2 >> 14) | ($S3 << 7)),
            (int) ($S3 >> 1),
            (int) ($S3 >> 9),
            (int) (($S3 >> 17) | ($S4 << 4)),
            (int) ($S4 >> 4),
            (int) ($S4 >> 12),
            (int) (($S4 >> 20) | ($S5 << 1)),
            (int) ($S5 >> 7),
            (int) (($S5 >> 15) | ($S6 << 6)),
            (int) ($S6 >> 2),
            (int) ($S6 >> 10),
            (int) (($S6 >> 18) | ($S7 << 3)),
            (int) ($S7 >> 5),
            (int) ($S7 >> 13),
            (int) ($S8 >> 0),
            (int) ($S8 >> 8),
            (int) (($S8 >> 16) | ($S9 << 5)),
            (int) ($S9 >> 3),
            (int) ($S9 >> 11),
            (int) (($S9 >> 19) | ($S10 << 2)),
            (int) ($S10 >> 6),
            (int) (($S10 >> 14) | ($S11 << 7)),
            (int) ($S11 >> 1),
            (int) ($S11 >> 9),
            (int) $S11 >> 17
        );
        return self::intArrayToString($arr);
    }

    /**
     * multiply by the order of the main subgroup l = 2^252+27742317777372353535851937790883648493
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A
     * @return ParagonIE_Sodium_Core32_Curve25519_Ge_P3
     * @throws SodiumException
     * @throws TypeError
     */
    public static function ge_mul_l(ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A)
    {
        /** @var array<int, int> $aslide */
        $aslide = array(
            13, 0, 0, 0, 0, -1, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, -5, 0, 0, 0,
            0, 0, 0, -3, 0, 0, 0, 0, -13, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 3, 0,
            0, 0, 0, -13, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0,
            0, 0, 11, 0, 0, 0, 0, -13, 0, 0, 0, 0, 0, 0, -3, 0, 0, 0, 0, 0, -1,
            0, 0, 0, 0, 3, 0, 0, 0, 0, -11, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0,
            0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 7, 0, 0, 0, 0, 5, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
        );

        /** @var array<int, ParagonIE_Sodium_Core32_Curve25519_Ge_Cached> $Ai size 8 */
        $Ai = array();

        # ge_p3_to_cached(&Ai[0], A);
        $Ai[0] = self::ge_p3_to_cached($A);
        # ge_p3_dbl(&t, A);
        $t = self::ge_p3_dbl($A);
        # ge_p1p1_to_p3(&A2, &t);
        $A2 = self::ge_p1p1_to_p3($t);

        for ($i = 1; $i < 8; ++$i) {
            # ge_add(&t, &A2, &Ai[0]);
            $t = self::ge_add($A2, $Ai[$i - 1]);
            # ge_p1p1_to_p3(&u, &t);
            $u = self::ge_p1p1_to_p3($t);
            # ge_p3_to_cached(&Ai[i], &u);
            $Ai[$i] = self::ge_p3_to_cached($u);
        }

        $r = self::ge_p3_0();
        for ($i = 252; $i >= 0; --$i) {
            $t = self::ge_p3_dbl($r);
            if ($aslide[$i] > 0) {
                # ge_p1p1_to_p3(&u, &t);
                $u = self::ge_p1p1_to_p3($t);
                # ge_add(&t, &u, &Ai[aslide[i] / 2]);
                $t = self::ge_add($u, $Ai[(int)($aslide[$i] / 2)]);
            } elseif ($aslide[$i] < 0) {
                # ge_p1p1_to_p3(&u, &t);
                $u = self::ge_p1p1_to_p3($t);
                # ge_sub(&t, &u, &Ai[(-aslide[i]) / 2]);
                $t = self::ge_sub($u, $Ai[(int)(-$aslide[$i] / 2)]);
            }
        }
        # ge_p1p1_to_p3(r, &t);
        return self::ge_p1p1_to_p3($t);
    }
}
vendor/paragonie/sodium_compat/src/Core32/BLAKE2b.php000064400000051165152177723700016273 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core_BLAKE2b', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core_BLAKE2b
 *
 * Based on the work of Devi Mandiri in devi/salt.
 */
abstract class ParagonIE_Sodium_Core32_BLAKE2b extends ParagonIE_Sodium_Core_Util
{
    /**
     * @var SplFixedArray
     */
    public static $iv;

    /**
     * @var array<int, array<int, int>>
     */
    public static $sigma = array(
        array(  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15),
        array( 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3),
        array( 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4),
        array(  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8),
        array(  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13),
        array(  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9),
        array( 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11),
        array( 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10),
        array(  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5),
        array( 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0),
        array(  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15),
        array( 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3)
    );

    const BLOCKBYTES = 128;
    const OUTBYTES   = 64;
    const KEYBYTES   = 64;

    /**
     * Turn two 32-bit integers into a fixed array representing a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $high
     * @param int $low
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public static function new64($high, $low)
    {
        return ParagonIE_Sodium_Core32_Int64::fromInts($low, $high);
    }

    /**
     * Convert an arbitrary number into an SplFixedArray of two 32-bit integers
     * that represents a 64-bit integer.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $num
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function to64($num)
    {
        list($hi, $lo) = self::numericTo64BitInteger($num);
        return self::new64($hi, $lo);
    }

    /**
     * Adds two 64-bit integers together, returning their sum as a SplFixedArray
     * containing two 32-bit integers (representing a 64-bit integer).
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Int64 $x
     * @param ParagonIE_Sodium_Core32_Int64 $y
     * @return ParagonIE_Sodium_Core32_Int64
     */
    protected static function add64($x, $y)
    {
        return $x->addInt64($y);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Int64 $x
     * @param ParagonIE_Sodium_Core32_Int64 $y
     * @param ParagonIE_Sodium_Core32_Int64 $z
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public static function add364($x, $y, $z)
    {
        return $x->addInt64($y)->addInt64($z);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Int64 $x
     * @param ParagonIE_Sodium_Core32_Int64 $y
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws TypeError
     */
    public static function xor64(ParagonIE_Sodium_Core32_Int64 $x, ParagonIE_Sodium_Core32_Int64 $y)
    {
        return $x->xorInt64($y);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Int64 $x
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public static function rotr64(ParagonIE_Sodium_Core32_Int64 $x, $c)
    {
        return $x->rotateRight($c);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param int $i
     * @return ParagonIE_Sodium_Core32_Int64
     * @throws SodiumException
     * @throws TypeError
     */
    public static function load64($x, $i)
    {
        /** @var int $l */
        $l = (int) ($x[$i])
             | ((int) ($x[$i+1]) << 8)
             | ((int) ($x[$i+2]) << 16)
             | ((int) ($x[$i+3]) << 24);
        /** @var int $h */
        $h = (int) ($x[$i+4])
             | ((int) ($x[$i+5]) << 8)
             | ((int) ($x[$i+6]) << 16)
             | ((int) ($x[$i+7]) << 24);
        return self::new64($h, $l);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $x
     * @param int $i
     * @param ParagonIE_Sodium_Core32_Int64 $u
     * @return void
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     */
    public static function store64(SplFixedArray $x, $i, ParagonIE_Sodium_Core32_Int64 $u)
    {
        $v = clone $u;
        $maxLength = $x->getSize() - 1;
        for ($j = 0; $j < 8; ++$j) {
            $k = 3 - ($j >> 1);
            $x[$i] = $v->limbs[$k] & 0xff;
            if (++$i > $maxLength) {
                return;
            }
            $v->limbs[$k] >>= 8;
        }
    }

    /**
     * This just sets the $iv static variable.
     *
     * @internal You should not use this directly from another application
     *
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    public static function pseudoConstructor()
    {
        static $called = false;
        if ($called) {
            return;
        }
        self::$iv = new SplFixedArray(8);
        self::$iv[0] = self::new64(0x6a09e667, 0xf3bcc908);
        self::$iv[1] = self::new64(0xbb67ae85, 0x84caa73b);
        self::$iv[2] = self::new64(0x3c6ef372, 0xfe94f82b);
        self::$iv[3] = self::new64(0xa54ff53a, 0x5f1d36f1);
        self::$iv[4] = self::new64(0x510e527f, 0xade682d1);
        self::$iv[5] = self::new64(0x9b05688c, 0x2b3e6c1f);
        self::$iv[6] = self::new64(0x1f83d9ab, 0xfb41bd6b);
        self::$iv[7] = self::new64(0x5be0cd19, 0x137e2179);

        $called = true;
    }

    /**
     * Returns a fresh BLAKE2 context.
     *
     * @internal You should not use this directly from another application
     *
     * @return SplFixedArray
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @throws SodiumException
     * @throws TypeError
     */
    protected static function context()
    {
        $ctx    = new SplFixedArray(5);
        $ctx[0] = new SplFixedArray(8);   // h
        $ctx[1] = new SplFixedArray(2);   // t
        $ctx[2] = new SplFixedArray(2);   // f
        $ctx[3] = new SplFixedArray(256); // buf
        $ctx[4] = 0;                      // buflen

        for ($i = 8; $i--;) {
            $ctx[0][$i] = self::$iv[$i];
        }
        for ($i = 256; $i--;) {
            $ctx[3][$i] = 0;
        }

        $zero = self::new64(0, 0);
        $ctx[1][0] = $zero;
        $ctx[1][1] = $zero;
        $ctx[2][0] = $zero;
        $ctx[2][1] = $zero;

        return $ctx;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $buf
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedAssignment
     */
    protected static function compress(SplFixedArray $ctx, SplFixedArray $buf)
    {
        $m = new SplFixedArray(16);
        $v = new SplFixedArray(16);

        for ($i = 16; $i--;) {
            $m[$i] = self::load64($buf, $i << 3);
        }

        for ($i = 8; $i--;) {
            $v[$i] = $ctx[0][$i];
        }

        $v[ 8] = self::$iv[0];
        $v[ 9] = self::$iv[1];
        $v[10] = self::$iv[2];
        $v[11] = self::$iv[3];

        $v[12] = self::xor64($ctx[1][0], self::$iv[4]);
        $v[13] = self::xor64($ctx[1][1], self::$iv[5]);
        $v[14] = self::xor64($ctx[2][0], self::$iv[6]);
        $v[15] = self::xor64($ctx[2][1], self::$iv[7]);

        for ($r = 0; $r < 12; ++$r) {
            $v = self::G($r, 0, 0, 4, 8, 12, $v, $m);
            $v = self::G($r, 1, 1, 5, 9, 13, $v, $m);
            $v = self::G($r, 2, 2, 6, 10, 14, $v, $m);
            $v = self::G($r, 3, 3, 7, 11, 15, $v, $m);
            $v = self::G($r, 4, 0, 5, 10, 15, $v, $m);
            $v = self::G($r, 5, 1, 6, 11, 12, $v, $m);
            $v = self::G($r, 6, 2, 7, 8, 13, $v, $m);
            $v = self::G($r, 7, 3, 4, 9, 14, $v, $m);
        }

        for ($i = 8; $i--;) {
            $ctx[0][$i] = self::xor64(
                $ctx[0][$i], self::xor64($v[$i], $v[$i+8])
            );
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $r
     * @param int $i
     * @param int $a
     * @param int $b
     * @param int $c
     * @param int $d
     * @param SplFixedArray $v
     * @param SplFixedArray $m
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayOffset
     */
    public static function G($r, $i, $a, $b, $c, $d, SplFixedArray $v, SplFixedArray $m)
    {
        $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][$i << 1]]);
        $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 32);
        $v[$c] = self::add64($v[$c], $v[$d]);
        $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 24);
        $v[$a] = self::add364($v[$a], $v[$b], $m[self::$sigma[$r][($i << 1) + 1]]);
        $v[$d] = self::rotr64(self::xor64($v[$d], $v[$a]), 16);
        $v[$c] = self::add64($v[$c], $v[$d]);
        $v[$b] = self::rotr64(self::xor64($v[$b], $v[$c]), 63);
        return $v;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param int $inc
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     */
    public static function increment_counter($ctx, $inc)
    {
        if ($inc < 0) {
            throw new SodiumException('Increasing by a negative number makes no sense.');
        }
        $t = self::to64($inc);
        # S->t is $ctx[1] in our implementation

        # S->t[0] = ( uint64_t )( t >> 0 );
        $ctx[1][0] = self::add64($ctx[1][0], $t);

        # S->t[1] += ( S->t[0] < inc );
        if (!($ctx[1][0] instanceof ParagonIE_Sodium_Core32_Int64)) {
            throw new TypeError('Not an int64');
        }
        /** @var ParagonIE_Sodium_Core32_Int64 $c*/
        $c = $ctx[1][0];
        if ($c->isLessThanInt($inc)) {
            $ctx[1][1] = self::add64($ctx[1][1], self::to64(1));
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $p
     * @param int $plen
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedMethodCall
     * @psalm-suppress MixedOperand
     */
    public static function update(SplFixedArray $ctx, SplFixedArray $p, $plen)
    {
        self::pseudoConstructor();

        $offset = 0;
        while ($plen > 0) {
            $left = $ctx[4];
            $fill = 256 - $left;

            if ($plen > $fill) {
                # memcpy( S->buf + left, in, fill ); /* Fill buffer */
                for ($i = $fill; $i--;) {
                    $ctx[3][$i + $left] = $p[$i + $offset];
                }

                # S->buflen += fill;
                $ctx[4] += $fill;

                # blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );
                self::increment_counter($ctx, 128);

                # blake2b_compress( S, S->buf ); /* Compress */
                self::compress($ctx, $ctx[3]);

                # memcpy( S->buf, S->buf + BLAKE2B_BLOCKBYTES, BLAKE2B_BLOCKBYTES ); /* Shift buffer left */
                for ($i = 128; $i--;) {
                    $ctx[3][$i] = $ctx[3][$i + 128];
                }

                # S->buflen -= BLAKE2B_BLOCKBYTES;
                $ctx[4] -= 128;

                # in += fill;
                $offset += $fill;

                # inlen -= fill;
                $plen -= $fill;
            } else {
                for ($i = $plen; $i--;) {
                    $ctx[3][$i + $left] = $p[$i + $offset];
                }
                $ctx[4] += $plen;
                $offset += $plen;
                $plen -= $plen;
            }
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $ctx
     * @param SplFixedArray $out
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedArrayOffset
     * @psalm-suppress MixedMethodCall
     * @psalm-suppress MixedOperand
     */
    public static function finish(SplFixedArray $ctx, SplFixedArray $out)
    {
        self::pseudoConstructor();
        if ($ctx[4] > 128) {
            self::increment_counter($ctx, 128);
            self::compress($ctx, $ctx[3]);
            $ctx[4] -= 128;
            if ($ctx[4] > 128) {
                throw new SodiumException('Failed to assert that buflen <= 128 bytes');
            }
            for ($i = $ctx[4]; $i--;) {
                $ctx[3][$i] = $ctx[3][$i + 128];
            }
        }

        self::increment_counter($ctx, $ctx[4]);
        $ctx[2][0] = self::new64(0xffffffff, 0xffffffff);

        for ($i = 256 - $ctx[4]; $i--;) {
            /** @var int $i */
            $ctx[3][$i + $ctx[4]] = 0;
        }

        self::compress($ctx, $ctx[3]);

        $i = (int) (($out->getSize() - 1) / 8);
        for (; $i >= 0; --$i) {
            self::store64($out, $i << 3, $ctx[0][$i]);
        }
        return $out;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray|null $key
     * @param int $outlen
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function init($key = null, $outlen = 64)
    {
        self::pseudoConstructor();
        $klen = 0;

        if ($key !== null) {
            if (count($key) > 64) {
                throw new SodiumException('Invalid key size');
            }
            $klen = count($key);
        }

        if ($outlen > 64) {
            throw new SodiumException('Invalid output size');
        }

        $ctx = self::context();

        $p = new SplFixedArray(64);
        for ($i = 64; --$i;) {
            $p[$i] = 0;
        }

        $p[0] = $outlen; // digest_length
        $p[1] = $klen;   // key_length
        $p[2] = 1;       // fanout
        $p[3] = 1;       // depth

        $ctx[0][0] = self::xor64(
            $ctx[0][0],
            self::load64($p, 0)
        );

        if ($klen > 0 && $key instanceof SplFixedArray) {
            $block = new SplFixedArray(128);
            for ($i = 128; $i--;) {
                $block[$i] = 0;
            }
            for ($i = $klen; $i--;) {
                $block[$i] = $key[$i];
            }
            self::update($ctx, $block, 128);
        }

        return $ctx;
    }

    /**
     * Convert a string into an SplFixedArray of integers
     *
     * @internal You should not use this directly from another application
     *
     * @param string $str
     * @return SplFixedArray
     */
    public static function stringToSplFixedArray($str = '')
    {
        $values = unpack('C*', $str);
        return SplFixedArray::fromArray(array_values($values));
    }

    /**
     * Convert an SplFixedArray of integers into a string
     *
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray $a
     * @return string
     */
    public static function SplFixedArrayToString(SplFixedArray $a)
    {
        /**
         * @var array<int, string|int>
         */
        $arr = $a->toArray();
        $c = $a->count();
        array_unshift($arr, str_repeat('C', $c));
        return (string) (call_user_func_array('pack', $arr));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param SplFixedArray[SplFixedArray] $ctx
     * @return string
     * @throws TypeError
     * @psalm-suppress MixedArgument
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function contextToString(SplFixedArray $ctx)
    {
        $str = '';
        /** @var array<int, ParagonIE_Sodium_Core32_Int64> $ctxA */
        $ctxA = $ctx[0]->toArray();

        # uint64_t h[8];
        for ($i = 0; $i < 8; ++$i) {
            if (!($ctxA[$i] instanceof ParagonIE_Sodium_Core32_Int64)) {
                throw new TypeError('Not an instance of Int64');
            }
            /** @var ParagonIE_Sodium_Core32_Int64 $ctxAi */
            $ctxAi = $ctxA[$i];
            $str .= $ctxAi->toString();
        }

        # uint64_t t[2];
        # uint64_t f[2];
        for ($i = 1; $i < 3; ++$i) {
            /** @var array<int, ParagonIE_Sodium_Core32_Int64> $ctxA */
            $ctxA = $ctx[$i]->toArray();
            /** @var ParagonIE_Sodium_Core32_Int64 $ctxA1 */
            $ctxA1 = $ctxA[0];
            /** @var ParagonIE_Sodium_Core32_Int64 $ctxA2 */
            $ctxA2 = $ctxA[1];

            $str .= $ctxA1->toString();
            $str .= $ctxA2->toString();
        }

        # uint8_t buf[2 * 128];
        $str .= self::SplFixedArrayToString($ctx[3]);

        /** @var int $ctx4 */
        $ctx4 = $ctx[4];

        # size_t buflen;
        $str .= implode('', array(
            self::intToChr($ctx4 & 0xff),
            self::intToChr(($ctx4 >> 8) & 0xff),
            self::intToChr(($ctx4 >> 16) & 0xff),
            self::intToChr(($ctx4 >> 24) & 0xff),
            self::intToChr(($ctx4 >> 32) & 0xff),
            self::intToChr(($ctx4 >> 40) & 0xff),
            self::intToChr(($ctx4 >> 48) & 0xff),
            self::intToChr(($ctx4 >> 56) & 0xff)
        ));
        # uint8_t last_node;
        return $str . "\x00";
    }

    /**
     * Creates an SplFixedArray containing other SplFixedArray elements, from
     * a string (compatible with \Sodium\crypto_generichash_{init, update, final})
     *
     * @internal You should not use this directly from another application
     *
     * @param string $string
     * @return SplFixedArray
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     * @psalm-suppress MixedArrayAssignment
     */
    public static function stringToContext($string)
    {
        $ctx = self::context();

        # uint64_t h[8];
        for ($i = 0; $i < 8; ++$i) {
            $ctx[0][$i] = ParagonIE_Sodium_Core32_Int64::fromString(
                self::substr($string, (($i << 3) + 0), 8)
            );
        }

        # uint64_t t[2];
        # uint64_t f[2];
        for ($i = 1; $i < 3; ++$i) {
            $ctx[$i][1] = ParagonIE_Sodium_Core32_Int64::fromString(
                self::substr($string, 72 + (($i - 1) << 4), 8)
            );
            $ctx[$i][0] = ParagonIE_Sodium_Core32_Int64::fromString(
                self::substr($string, 64 + (($i - 1) << 4), 8)
            );
        }

        # uint8_t buf[2 * 128];
        $ctx[3] = self::stringToSplFixedArray(self::substr($string, 96, 256));


        # uint8_t buf[2 * 128];
        $int = 0;
        for ($i = 0; $i < 8; ++$i) {
            $int |= self::chrToInt($string[352 + $i]) << ($i << 3);
        }
        $ctx[4] = $int;

        return $ctx;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Int32.php000064400000050100152177723700016114 0ustar00<?php

/**
 * Class ParagonIE_Sodium_Core32_Int32
 *
 * Encapsulates a 32-bit integer.
 *
 * These are immutable. It always returns a new instance.
 */
class ParagonIE_Sodium_Core32_Int32
{
    /**
     * @var array<int, int> - two 16-bit integers
     *
     * 0 is the higher 16 bits
     * 1 is the lower 16 bits
     */
    public $limbs = array(0, 0);

    /**
     * @var int
     */
    public $overflow = 0;

    /**
     * @var bool
     */
    public $unsignedInt = false;

    /**
     * ParagonIE_Sodium_Core32_Int32 constructor.
     * @param array $array
     * @param bool $unsignedInt
     */
    public function __construct($array = array(0, 0), $unsignedInt = false)
    {
        $this->limbs = array(
            (int) $array[0],
            (int) $array[1]
        );
        $this->overflow = 0;
        $this->unsignedInt = $unsignedInt;
    }

    /**
     * Adds two int32 objects
     *
     * @param ParagonIE_Sodium_Core32_Int32 $addend
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function addInt32(ParagonIE_Sodium_Core32_Int32 $addend)
    {
        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];
        $j0 = $addend->limbs[0];
        $j1 = $addend->limbs[1];

        $r1 = $i1 + ($j1 & 0xffff);
        $carry = $r1 >> 16;

        $r0 = $i0 + ($j0 & 0xffff) + $carry;
        $carry = $r0 >> 16;

        $r0 &= 0xffff;
        $r1 &= 0xffff;

        $return = new ParagonIE_Sodium_Core32_Int32(
            array($r0, $r1)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * Adds a normal integer to an int32 object
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function addInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $int = (int) $int;

        $i0 = $this->limbs[0];
        $i1 = $this->limbs[1];

        $r1 = $i1 + ($int & 0xffff);
        $carry = $r1 >> 16;

        $r0 = $i0 + (($int >> 16) & 0xffff) + $carry;
        $carry = $r0 >> 16;
        $r0 &= 0xffff;
        $r1 &= 0xffff;
        $return = new ParagonIE_Sodium_Core32_Int32(
            array($r0, $r1)
        );
        $return->overflow = $carry;
        $return->unsignedInt = $this->unsignedInt;
        return $return;
    }

    /**
     * @param int $b
     * @return int
     */
    public function compareInt($b = 0)
    {
        $gt = 0;
        $eq = 1;

        $i = 2;
        $j = 0;
        while ($i > 0) {
            --$i;
            /** @var int $x1 */
            $x1 = $this->limbs[$i];
            /** @var int $x2 */
            $x2 = ($b >> ($j << 4)) & 0xffff;
            /** @var int $gt */
            $gt |= (($x2 - $x1) >> 8) & $eq;
            /** @var int $eq */
            $eq &= (($x2 ^ $x1) - 1) >> 8;
        }
        return ($gt + $gt - $eq) + 1;
    }

    /**
     * @param int $m
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function mask($m = 0)
    {
        /** @var int $hi */
        $hi = ($m >> 16) & 0xffff;
        /** @var int $lo */
        $lo = ($m & 0xffff);
        return new ParagonIE_Sodium_Core32_Int32(
            array(
                (int) ($this->limbs[0] & $hi),
                (int) ($this->limbs[1] & $lo)
            ),
            $this->unsignedInt
        );
    }

    /**
     * @param int $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function mulInt($int = 0, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        /** @var int $int */
        $int = (int) $int;
        /** @var int $size */
        $size = (int) $size;

        if (!$size) {
            $size = 31;
        }
        /** @var int $size */

        $a = clone $this;
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $m = (int) (-($int & 1));
            $x0 = $a0 & $m;
            $x1 = $a1 & $m;

            $ret1 += $x1;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;

            $a1 = ($a1 << 1);
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;
            $int >>= 1;
        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;
        return $return;
    }

    /**
     * @param ParagonIE_Sodium_Core32_Int32 $int
     * @param int $size
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function mulInt32(ParagonIE_Sodium_Core32_Int32 $int, $size = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($size, 'int', 2);
        if (!$size) {
            $size = 31;
        }
        /** @var int $size */

        $a = clone $this;
        $b = clone $int;
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        // Initialize:
        $ret0 = 0;
        $ret1 = 0;
        $a0 = $a->limbs[0];
        $a1 = $a->limbs[1];
        $b0 = $b->limbs[0];
        $b1 = $b->limbs[1];

        /** @var int $size */
        /** @var int $i */
        for ($i = $size; $i >= 0; --$i) {
            $m = (int) (-($b1 & 1));
            $x0 = $a0 & $m;
            $x1 = $a1 & $m;

            $ret1 += $x1;
            $c = $ret1 >> 16;

            $ret0 += $x0 + $c;

            $ret0 &= 0xffff;
            $ret1 &= 0xffff;

            $a1 = ($a1 << 1);
            $x1 = $a1 >> 16;
            $a0 = ($a0 << 1) | $x1;
            $a0 &= 0xffff;
            $a1 &= 0xffff;

            $x0 = ($b0 & 1) << 16;
            $b0 = ($b0 >> 1);
            $b1 = (($b1 | $x0) >> 1);

            $b0 &= 0xffff;
            $b1 &= 0xffff;

        }
        $return->limbs[0] = $ret0;
        $return->limbs[1] = $ret1;

        return $return;
    }

    /**
     * OR this 32-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function orInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] | $b->limbs[0]),
            (int) ($this->limbs[1] | $b->limbs[1])
        );
        /** @var int overflow */
        $return->overflow = $this->overflow | $b->overflow;
        return $return;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isGreaterThan($b = 0)
    {
        return $this->compareInt($b) > 0;
    }

    /**
     * @param int $b
     * @return bool
     */
    public function isLessThanInt($b = 0)
    {
        return $this->compareInt($b) < 0;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 31;
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var int $c */

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 1;

            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            for ($i = 1; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i + $idx_shift) & 1;
                /** @var int $k */
                $k = ($i + $idx_shift + 1) & 1;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) << $sub_shift)
                            |
                        ((int) ($myLimbs[$k]) >> (16 - $sub_shift))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * Rotate to the right
     *
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedArrayAccess
     */
    public function rotateRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 31;
        /** @var int $c */
        if ($c === 0) {
            // NOP, but we want a copy.
            $return->limbs = $this->limbs;
        } else {
            /** @var int $c */

            /** @var int $idx_shift */
            $idx_shift = ($c >> 4) & 1;

            /** @var int $sub_shift */
            $sub_shift = $c & 15;

            /** @var array<int, int> $limbs */
            $limbs =& $return->limbs;

            /** @var array<int, int> $myLimbs */
            $myLimbs =& $this->limbs;

            for ($i = 1; $i >= 0; --$i) {
                /** @var int $j */
                $j = ($i - $idx_shift) & 1;
                /** @var int $k */
                $k = ($i - $idx_shift - 1) & 1;
                $limbs[$i] = (int) (
                    (
                        ((int) ($myLimbs[$j]) >> (int) ($sub_shift))
                            |
                        ((int) ($myLimbs[$k]) << (16 - (int) ($sub_shift)))
                    ) & 0xffff
                );
            }
        }
        return $return;
    }

    /**
     * @param bool $bool
     * @return self
     */
    public function setUnsignedInt($bool = false)
    {
        $this->unsignedInt = !empty($bool);
        return $this;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function shiftLeft($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftRight(-$c);
        } else {
            /** @var int $c */
            /** @var int $tmp */
            $tmp = $this->limbs[1] << $c;
            $return->limbs[1] = (int)($tmp & 0xffff);
            /** @var int $carry */
            $carry = $tmp >> 16;

            /** @var int $tmp */
            $tmp = ($this->limbs[0] << $c) | ($carry & 0xffff);
            $return->limbs[0] = (int) ($tmp & 0xffff);
        }
        return $return;
    }

    /**
     * @param int $c
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedOperand
     */
    public function shiftRight($c = 0)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($c, 'int', 1);
        /** @var int $c */
        $c = (int) $c;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $c &= 63;
        /** @var int $c */
        if ($c >= 16) {
            $return->limbs = array(
                (int) ($this->overflow & 0xffff),
                (int) ($this->limbs[0])
            );
            $return->overflow = $this->overflow >> 16;
            return $return->shiftRight($c & 15);
        }
        if ($c === 0) {
            $return->limbs = $this->limbs;
        } elseif ($c < 0) {
            /** @var int $c */
            return $this->shiftLeft(-$c);
        } else {
            if (is_null($c)) {
                throw new TypeError();
            }
            /** @var int $c */
            // $return->limbs[0] = (int) (($this->limbs[0] >> $c) & 0xffff);
            $carryLeft = (int) ($this->overflow & ((1 << ($c + 1)) - 1));
            $return->limbs[0] = (int) ((($this->limbs[0] >> $c) | ($carryLeft << (16 - $c))) & 0xffff);
            $carryRight = (int) ($this->limbs[0] & ((1 << ($c + 1)) - 1));
            $return->limbs[1] = (int) ((($this->limbs[1] >> $c) | ($carryRight << (16 - $c))) & 0xffff);
            $return->overflow >>= $c;
        }
        return $return;
    }

    /**
     * Subtract a normal integer from an int32 object.
     *
     * @param int $int
     * @return ParagonIE_Sodium_Core32_Int32
     * @throws SodiumException
     * @throws TypeError
     */
    public function subInt($int)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($int, 'int', 1);
        /** @var int $int */
        $int = (int) $int;

        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $tmp */
        $tmp = $this->limbs[1] - ($int & 0xffff);
        /** @var int $carry */
        $carry = $tmp >> 16;
        $return->limbs[1] = (int) ($tmp & 0xffff);

        /** @var int $tmp */
        $tmp = $this->limbs[0] - (($int >> 16) & 0xffff) + $carry;
        $return->limbs[0] = (int) ($tmp & 0xffff);
        return $return;
    }

    /**
     * Subtract two int32 objects from each other
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function subInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;

        /** @var int $tmp */
        $tmp = $this->limbs[1] - ($b->limbs[1] & 0xffff);
        /** @var int $carry */
        $carry = $tmp >> 16;
        $return->limbs[1] = (int) ($tmp & 0xffff);

        /** @var int $tmp */
        $tmp = $this->limbs[0] - ($b->limbs[0] & 0xffff) + $carry;
        $return->limbs[0] = (int) ($tmp & 0xffff);
        return $return;
    }

    /**
     * XOR this 32-bit integer with another.
     *
     * @param ParagonIE_Sodium_Core32_Int32 $b
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function xorInt32(ParagonIE_Sodium_Core32_Int32 $b)
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->unsignedInt = $this->unsignedInt;
        $return->limbs = array(
            (int) ($this->limbs[0] ^ $b->limbs[0]),
            (int) ($this->limbs[1] ^ $b->limbs[1])
        );
        return $return;
    }

    /**
     * @param int $signed
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromInt($signed)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($signed, 'int', 1);;
        /** @var int $signed */
        $signed = (int) $signed;

        return new ParagonIE_Sodium_Core32_Int32(
            array(
                (int) (($signed >> 16) & 0xffff),
                (int) ($signed & 0xffff)
            )
        );
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 4) {
            throw new RangeException(
                'String must be 4 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int32();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff);
        return $return;
    }

    /**
     * @param string $string
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromReverseString($string)
    {
        ParagonIE_Sodium_Core32_Util::declareScalarType($string, 'string', 1);
        $string = (string) $string;
        if (ParagonIE_Sodium_Core32_Util::strlen($string) !== 4) {
            throw new RangeException(
                'String must be 4 bytes; ' . ParagonIE_Sodium_Core32_Util::strlen($string) . ' given.'
            );
        }
        $return = new ParagonIE_Sodium_Core32_Int32();

        $return->limbs[0]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[3]) & 0xff) << 8);
        $return->limbs[0] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[2]) & 0xff);
        $return->limbs[1]  = (int) ((ParagonIE_Sodium_Core32_Util::chrToInt($string[1]) & 0xff) << 8);
        $return->limbs[1] |= (ParagonIE_Sodium_Core32_Util::chrToInt($string[0]) & 0xff);
        return $return;
    }

    /**
     * @return array<int, int>
     */
    public function toArray()
    {
        return array((int) ($this->limbs[0] << 16 | $this->limbs[1]));
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toString()
    {
        return
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff);
    }

    /**
     * @return int
     */
    public function toInt()
    {
        return (int) (
            (($this->limbs[0] & 0xffff) << 16)
                |
            ($this->limbs[1] & 0xffff)
        );
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int32
     */
    public function toInt32()
    {
        $return = new ParagonIE_Sodium_Core32_Int32();
        $return->limbs[0] = (int) ($this->limbs[0] & 0xffff);
        $return->limbs[1] = (int) ($this->limbs[1] & 0xffff);
        $return->unsignedInt = $this->unsignedInt;
        $return->overflow = (int) ($this->overflow & 0x7fffffff);
        return $return;
    }

    /**
     * @return ParagonIE_Sodium_Core32_Int64
     */
    public function toInt64()
    {
        $return = new ParagonIE_Sodium_Core32_Int64();
        $return->unsignedInt = $this->unsignedInt;
        if ($this->unsignedInt) {
            $return->limbs[0] += (($this->overflow >> 16) & 0xffff);
            $return->limbs[1] += (($this->overflow) & 0xffff);
        } else {
            $neg = -(($this->limbs[0] >> 15) & 1);
            $return->limbs[0] = (int)($neg & 0xffff);
            $return->limbs[1] = (int)($neg & 0xffff);
        }
        $return->limbs[2] = (int) ($this->limbs[0] & 0xffff);
        $return->limbs[3] = (int) ($this->limbs[1] & 0xffff);
        return $return;
    }

    /**
     * @return string
     * @throws TypeError
     */
    public function toReverseString()
    {
        return ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[1] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[1] >> 8) & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr($this->limbs[0] & 0xff) .
            ParagonIE_Sodium_Core32_Util::intToChr(($this->limbs[0] >> 8) & 0xff);
    }

    /**
     * @return string
     */
    public function __toString()
    {
        try {
            return $this->toString();
        } catch (TypeError $ex) {
            // PHP engine can't handle exceptions from __toString()
            return '';
        }
    }
}
vendor/paragonie/sodium_compat/src/Core32/X25519.php000064400000026522152177723700016045 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_X25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_X25519
 */
abstract class ParagonIE_Sodium_Core32_X25519 extends ParagonIE_Sodium_Core32_Curve25519
{
    /**
     * Alters the objects passed to this method in place.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $g
     * @param int $b
     * @return void
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_cswap(
        ParagonIE_Sodium_Core32_Curve25519_Fe $f,
        ParagonIE_Sodium_Core32_Curve25519_Fe $g,
        $b = 0
    ) {
        $f0 = (int) $f[0]->toInt();
        $f1 = (int) $f[1]->toInt();
        $f2 = (int) $f[2]->toInt();
        $f3 = (int) $f[3]->toInt();
        $f4 = (int) $f[4]->toInt();
        $f5 = (int) $f[5]->toInt();
        $f6 = (int) $f[6]->toInt();
        $f7 = (int) $f[7]->toInt();
        $f8 = (int) $f[8]->toInt();
        $f9 = (int) $f[9]->toInt();
        $g0 = (int) $g[0]->toInt();
        $g1 = (int) $g[1]->toInt();
        $g2 = (int) $g[2]->toInt();
        $g3 = (int) $g[3]->toInt();
        $g4 = (int) $g[4]->toInt();
        $g5 = (int) $g[5]->toInt();
        $g6 = (int) $g[6]->toInt();
        $g7 = (int) $g[7]->toInt();
        $g8 = (int) $g[8]->toInt();
        $g9 = (int) $g[9]->toInt();
        $b = -$b;
        /** @var int $x0 */
        $x0 = ($f0 ^ $g0) & $b;
        /** @var int $x1 */
        $x1 = ($f1 ^ $g1) & $b;
        /** @var int $x2 */
        $x2 = ($f2 ^ $g2) & $b;
        /** @var int $x3 */
        $x3 = ($f3 ^ $g3) & $b;
        /** @var int $x4 */
        $x4 = ($f4 ^ $g4) & $b;
        /** @var int $x5 */
        $x5 = ($f5 ^ $g5) & $b;
        /** @var int $x6 */
        $x6 = ($f6 ^ $g6) & $b;
        /** @var int $x7 */
        $x7 = ($f7 ^ $g7) & $b;
        /** @var int $x8 */
        $x8 = ($f8 ^ $g8) & $b;
        /** @var int $x9 */
        $x9 = ($f9 ^ $g9) & $b;
        $f[0] = ParagonIE_Sodium_Core32_Int32::fromInt($f0 ^ $x0);
        $f[1] = ParagonIE_Sodium_Core32_Int32::fromInt($f1 ^ $x1);
        $f[2] = ParagonIE_Sodium_Core32_Int32::fromInt($f2 ^ $x2);
        $f[3] = ParagonIE_Sodium_Core32_Int32::fromInt($f3 ^ $x3);
        $f[4] = ParagonIE_Sodium_Core32_Int32::fromInt($f4 ^ $x4);
        $f[5] = ParagonIE_Sodium_Core32_Int32::fromInt($f5 ^ $x5);
        $f[6] = ParagonIE_Sodium_Core32_Int32::fromInt($f6 ^ $x6);
        $f[7] = ParagonIE_Sodium_Core32_Int32::fromInt($f7 ^ $x7);
        $f[8] = ParagonIE_Sodium_Core32_Int32::fromInt($f8 ^ $x8);
        $f[9] = ParagonIE_Sodium_Core32_Int32::fromInt($f9 ^ $x9);
        $g[0] = ParagonIE_Sodium_Core32_Int32::fromInt($g0 ^ $x0);
        $g[1] = ParagonIE_Sodium_Core32_Int32::fromInt($g1 ^ $x1);
        $g[2] = ParagonIE_Sodium_Core32_Int32::fromInt($g2 ^ $x2);
        $g[3] = ParagonIE_Sodium_Core32_Int32::fromInt($g3 ^ $x3);
        $g[4] = ParagonIE_Sodium_Core32_Int32::fromInt($g4 ^ $x4);
        $g[5] = ParagonIE_Sodium_Core32_Int32::fromInt($g5 ^ $x5);
        $g[6] = ParagonIE_Sodium_Core32_Int32::fromInt($g6 ^ $x6);
        $g[7] = ParagonIE_Sodium_Core32_Int32::fromInt($g7 ^ $x7);
        $g[8] = ParagonIE_Sodium_Core32_Int32::fromInt($g8 ^ $x8);
        $g[9] = ParagonIE_Sodium_Core32_Int32::fromInt($g9 ^ $x9);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $f
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     * @psalm-suppress MixedAssignment
     * @psalm-suppress MixedMethodCall
     */
    public static function fe_mul121666(ParagonIE_Sodium_Core32_Curve25519_Fe $f)
    {
        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $h */
        $h = array();
        for ($i = 0; $i < 10; ++$i) {
            $h[$i] = $f[$i]->toInt64()->mulInt(121666, 17);
        }

        /** @var ParagonIE_Sodium_Core32_Int32 $carry9 */
        $carry9 = $h[9]->addInt(1 << 24)->shiftRight(25);
        $h[0] = $h[0]->addInt64($carry9->mulInt(19, 5));
        $h[9] = $h[9]->subInt64($carry9->shiftLeft(25));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry1 */
        $carry1 = $h[1]->addInt(1 << 24)->shiftRight(25);
        $h[2] = $h[2]->addInt64($carry1);
        $h[1] = $h[1]->subInt64($carry1->shiftLeft(25));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry3 */
        $carry3 = $h[3]->addInt(1 << 24)->shiftRight(25);
        $h[4] = $h[4]->addInt64($carry3);
        $h[3] = $h[3]->subInt64($carry3->shiftLeft(25));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry5 */
        $carry5 = $h[5]->addInt(1 << 24)->shiftRight(25);
        $h[6] = $h[6]->addInt64($carry5);
        $h[5] = $h[5]->subInt64($carry5->shiftLeft(25));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry7 */
        $carry7 = $h[7]->addInt(1 << 24)->shiftRight(25);
        $h[8] = $h[8]->addInt64($carry7);
        $h[7] = $h[7]->subInt64($carry7->shiftLeft(25));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry0 */
        $carry0 = $h[0]->addInt(1 << 25)->shiftRight(26);
        $h[1] = $h[1]->addInt64($carry0);
        $h[0] = $h[0]->subInt64($carry0->shiftLeft(26));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry2 */
        $carry2 = $h[2]->addInt(1 << 25)->shiftRight(26);
        $h[3] = $h[3]->addInt64($carry2);
        $h[2] = $h[2]->subInt64($carry2->shiftLeft(26));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry4 */
        $carry4 = $h[4]->addInt(1 << 25)->shiftRight(26);
        $h[5] = $h[5]->addInt64($carry4);
        $h[4] = $h[4]->subInt64($carry4->shiftLeft(26));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry6 */
        $carry6 = $h[6]->addInt(1 << 25)->shiftRight(26);
        $h[7] = $h[7]->addInt64($carry6);
        $h[6] = $h[6]->subInt64($carry6->shiftLeft(26));

        /** @var ParagonIE_Sodium_Core32_Int32 $carry8 */
        $carry8 = $h[8]->addInt(1 << 25)->shiftRight(26);
        $h[9] = $h[9]->addInt64($carry8);
        $h[8] = $h[8]->subInt64($carry8->shiftLeft(26));

        for ($i = 0; $i < 10; ++$i) {
            $h[$i] = $h[$i]->toInt32();
        }
        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $h */
        return ParagonIE_Sodium_Core32_Curve25519_Fe::fromArray($h);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * Inline comments preceded by # are from libsodium's ref10 code.
     *
     * @param string $n
     * @param string $p
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_scalarmult_curve25519_ref10($n, $p)
    {
        # for (i = 0;i < 32;++i) e[i] = n[i];
        $e = '' . $n;
        # e[0] &= 248;
        $e[0] = self::intToChr(
            self::chrToInt($e[0]) & 248
        );
        # e[31] &= 127;
        # e[31] |= 64;
        $e[31] = self::intToChr(
            (self::chrToInt($e[31]) & 127) | 64
        );
        # fe_frombytes(x1,p);
        $x1 = self::fe_frombytes($p);
        # fe_1(x2);
        $x2 = self::fe_1();
        # fe_0(z2);
        $z2 = self::fe_0();
        # fe_copy(x3,x1);
        $x3 = self::fe_copy($x1);
        # fe_1(z3);
        $z3 = self::fe_1();

        # swap = 0;
        /** @var int $swap */
        $swap = 0;

        # for (pos = 254;pos >= 0;--pos) {
        for ($pos = 254; $pos >= 0; --$pos) {
            # b = e[pos / 8] >> (pos & 7);
            /** @var int $b */
            $b = self::chrToInt(
                    $e[(int) floor($pos / 8)]
                ) >> ($pos & 7);
            # b &= 1;
            $b &= 1;

            # swap ^= b;
            $swap ^= $b;

            # fe_cswap(x2,x3,swap);
            self::fe_cswap($x2, $x3, $swap);

            # fe_cswap(z2,z3,swap);
            self::fe_cswap($z2, $z3, $swap);

            # swap = b;
            /** @var int $swap */
            $swap = $b;

            # fe_sub(tmp0,x3,z3);
            $tmp0 = self::fe_sub($x3, $z3);

            # fe_sub(tmp1,x2,z2);
            $tmp1 = self::fe_sub($x2, $z2);

            # fe_add(x2,x2,z2);
            $x2 = self::fe_add($x2, $z2);

            # fe_add(z2,x3,z3);
            $z2 = self::fe_add($x3, $z3);

            # fe_mul(z3,tmp0,x2);
            $z3 = self::fe_mul($tmp0, $x2);

            # fe_mul(z2,z2,tmp1);
            $z2 = self::fe_mul($z2, $tmp1);

            # fe_sq(tmp0,tmp1);
            $tmp0 = self::fe_sq($tmp1);

            # fe_sq(tmp1,x2);
            $tmp1 = self::fe_sq($x2);

            # fe_add(x3,z3,z2);
            $x3 = self::fe_add($z3, $z2);

            # fe_sub(z2,z3,z2);
            $z2 = self::fe_sub($z3, $z2);

            # fe_mul(x2,tmp1,tmp0);
            $x2 = self::fe_mul($tmp1, $tmp0);

            # fe_sub(tmp1,tmp1,tmp0);
            $tmp1 = self::fe_sub($tmp1, $tmp0);

            # fe_sq(z2,z2);
            $z2 = self::fe_sq($z2);

            # fe_mul121666(z3,tmp1);
            $z3 = self::fe_mul121666($tmp1);

            # fe_sq(x3,x3);
            $x3 = self::fe_sq($x3);

            # fe_add(tmp0,tmp0,z3);
            $tmp0 = self::fe_add($tmp0, $z3);

            # fe_mul(z3,x1,z2);
            $z3 = self::fe_mul($x1, $z2);

            # fe_mul(z2,tmp1,tmp0);
            $z2 = self::fe_mul($tmp1, $tmp0);
        }

        # fe_cswap(x2,x3,swap);
        self::fe_cswap($x2, $x3, $swap);

        # fe_cswap(z2,z3,swap);
        self::fe_cswap($z2, $z3, $swap);

        # fe_invert(z2,z2);
        $z2 = self::fe_invert($z2);

        # fe_mul(x2,x2,z2);
        $x2 = self::fe_mul($x2, $z2);
        # fe_tobytes(q,x2);
        return (string) self::fe_tobytes($x2);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $edwardsY
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $edwardsZ
     * @return ParagonIE_Sodium_Core32_Curve25519_Fe
     * @throws SodiumException
     * @throws TypeError
     */
    public static function edwards_to_montgomery(
        ParagonIE_Sodium_Core32_Curve25519_Fe $edwardsY,
        ParagonIE_Sodium_Core32_Curve25519_Fe $edwardsZ
    ) {
        $tempX = self::fe_add($edwardsZ, $edwardsY);
        $tempZ = self::fe_sub($edwardsZ, $edwardsY);
        $tempZ = self::fe_invert($tempZ);
        return self::fe_mul($tempX, $tempZ);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $n
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function crypto_scalarmult_curve25519_ref10_base($n)
    {
        # for (i = 0;i < 32;++i) e[i] = n[i];
        $e = '' . $n;

        # e[0] &= 248;
        $e[0] = self::intToChr(
            self::chrToInt($e[0]) & 248
        );

        # e[31] &= 127;
        # e[31] |= 64;
        $e[31] = self::intToChr(
            (self::chrToInt($e[31]) & 127) | 64
        );

        $A = self::ge_scalarmult_base($e);
        if (
            !($A->Y instanceof ParagonIE_Sodium_Core32_Curve25519_Fe)
                ||
            !($A->Z instanceof ParagonIE_Sodium_Core32_Curve25519_Fe)
        ) {
            throw new TypeError('Null points encountered');
        }
        $pk = self::edwards_to_montgomery($A->Y, $A->Z);
        return self::fe_tobytes($pk);
    }
}
vendor/paragonie/sodium_compat/src/Core32/Poly1305.php000064400000003062152177723700016456 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Poly1305', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Poly1305
 */
abstract class ParagonIE_Sodium_Core32_Poly1305 extends ParagonIE_Sodium_Core32_Util
{
    const BLOCK_SIZE = 16;

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth($m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            self::substr($key, 0, 32)
        );
        return $state
            ->update($m)
            ->finish();
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $mac
     * @param string $m
     * @param string $key
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function onetimeauth_verify($mac, $m, $key)
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Key must be 32 bytes long.'
            );
        }
        $state = new ParagonIE_Sodium_Core32_Poly1305_State(
            self::substr($key, 0, 32)
        );
        $calc = $state
            ->update($m)
            ->finish();
        return self::verify_16($calc, $mac);
    }
}
vendor/paragonie/sodium_compat/src/Core32/Poly1305/State.php000064400000037160152177723700017544 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Poly1305_State', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Poly1305_State
 */
class ParagonIE_Sodium_Core32_Poly1305_State extends ParagonIE_Sodium_Core32_Util
{
    /**
     * @var array<int, int>
     */
    protected $buffer = array();

    /**
     * @var bool
     */
    protected $final = false;

    /**
     * @var array<int, ParagonIE_Sodium_Core32_Int32>
     */
    public $h;

    /**
     * @var int
     */
    protected $leftover = 0;

    /**
     * @var array<int, ParagonIE_Sodium_Core32_Int32>
     */
    public $r;

    /**
     * @var array<int, ParagonIE_Sodium_Core32_Int64>
     */
    public $pad;

    /**
     * ParagonIE_Sodium_Core32_Poly1305_State constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $key
     * @throws InvalidArgumentException
     * @throws SodiumException
     * @throws TypeError
     */
    public function __construct($key = '')
    {
        if (self::strlen($key) < 32) {
            throw new InvalidArgumentException(
                'Poly1305 requires a 32-byte key'
            );
        }
        /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */
        $this->r = array(
            // st->r[0] = ...
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 0, 4))
                ->setUnsignedInt(true)
                ->mask(0x3ffffff),
            // st->r[1] = ...
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 3, 4))
                ->setUnsignedInt(true)
                ->shiftRight(2)
                ->mask(0x3ffff03),
            // st->r[2] = ...
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 6, 4))
                ->setUnsignedInt(true)
                ->shiftRight(4)
                ->mask(0x3ffc0ff),
            // st->r[3] = ...
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 9, 4))
                ->setUnsignedInt(true)
                ->shiftRight(6)
                ->mask(0x3f03fff),
            // st->r[4] = ...
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 12, 4))
                ->setUnsignedInt(true)
                ->shiftRight(8)
                ->mask(0x00fffff)
        );

        /* h = 0 */
        $this->h = array(
            new ParagonIE_Sodium_Core32_Int32(array(0, 0), true),
            new ParagonIE_Sodium_Core32_Int32(array(0, 0), true),
            new ParagonIE_Sodium_Core32_Int32(array(0, 0), true),
            new ParagonIE_Sodium_Core32_Int32(array(0, 0), true),
            new ParagonIE_Sodium_Core32_Int32(array(0, 0), true)
        );

        /* save pad for later */
        $this->pad = array(
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 16, 4))
                ->setUnsignedInt(true)->toInt64(),
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 20, 4))
                ->setUnsignedInt(true)->toInt64(),
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 24, 4))
                ->setUnsignedInt(true)->toInt64(),
            ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($key, 28, 4))
                ->setUnsignedInt(true)->toInt64(),
        );

        $this->leftover = 0;
        $this->final = false;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public function update($message = '')
    {
        $bytes = self::strlen($message);

        /* handle leftover */
        if ($this->leftover) {
            /** @var int $want */
            $want = ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE - $this->leftover;
            if ($want > $bytes) {
                $want = $bytes;
            }
            for ($i = 0; $i < $want; ++$i) {
                $mi = self::chrToInt($message[$i]);
                $this->buffer[$this->leftover + $i] = $mi;
            }
            // We snip off the leftmost bytes.
            $message = self::substr($message, $want);
            $bytes = self::strlen($message);
            $this->leftover += $want;
            if ($this->leftover < ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE) {
                // We still don't have enough to run $this->blocks()
                return $this;
            }

            $this->blocks(
                static::intArrayToString($this->buffer),
                ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE
            );
            $this->leftover = 0;
        }

        /* process full blocks */
        if ($bytes >= ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE) {
            /** @var int $want */
            $want = $bytes & ~(ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE - 1);
            if ($want >= ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE) {
                /** @var string $block */
                $block = self::substr($message, 0, $want);
                if (self::strlen($block) >= ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE) {
                    $this->blocks($block, $want);
                    $message = self::substr($message, $want);
                    $bytes = self::strlen($message);
                }
            }
        }

        /* store leftover */
        if ($bytes) {
            for ($i = 0; $i < $bytes; ++$i) {
                $mi = self::chrToInt($message[$i]);
                $this->buffer[$this->leftover + $i] = $mi;
            }
            $this->leftover = (int) $this->leftover + $bytes;
        }
        return $this;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param int $bytes
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public function blocks($message, $bytes)
    {
        if (self::strlen($message) < 16) {
            $message = str_pad($message, 16, "\x00", STR_PAD_RIGHT);
        }
        $hibit = ParagonIE_Sodium_Core32_Int32::fromInt((int) ($this->final ? 0 : 1 << 24)); /* 1 << 128 */
        $hibit->setUnsignedInt(true);
        $zero = new ParagonIE_Sodium_Core32_Int64(array(0, 0, 0, 0), true);
        /**
         * @var ParagonIE_Sodium_Core32_Int64 $d0
         * @var ParagonIE_Sodium_Core32_Int64 $d1
         * @var ParagonIE_Sodium_Core32_Int64 $d2
         * @var ParagonIE_Sodium_Core32_Int64 $d3
         * @var ParagonIE_Sodium_Core32_Int64 $d4
         * @var ParagonIE_Sodium_Core32_Int64 $r0
         * @var ParagonIE_Sodium_Core32_Int64 $r1
         * @var ParagonIE_Sodium_Core32_Int64 $r2
         * @var ParagonIE_Sodium_Core32_Int64 $r3
         * @var ParagonIE_Sodium_Core32_Int64 $r4
         *
         * @var ParagonIE_Sodium_Core32_Int32 $h0
         * @var ParagonIE_Sodium_Core32_Int32 $h1
         * @var ParagonIE_Sodium_Core32_Int32 $h2
         * @var ParagonIE_Sodium_Core32_Int32 $h3
         * @var ParagonIE_Sodium_Core32_Int32 $h4
         */
        $r0 = $this->r[0]->toInt64();
        $r1 = $this->r[1]->toInt64();
        $r2 = $this->r[2]->toInt64();
        $r3 = $this->r[3]->toInt64();
        $r4 = $this->r[4]->toInt64();

        $s1 = $r1->toInt64()->mulInt(5, 3);
        $s2 = $r2->toInt64()->mulInt(5, 3);
        $s3 = $r3->toInt64()->mulInt(5, 3);
        $s4 = $r4->toInt64()->mulInt(5, 3);

        $h0 = $this->h[0];
        $h1 = $this->h[1];
        $h2 = $this->h[2];
        $h3 = $this->h[3];
        $h4 = $this->h[4];

        while ($bytes >= ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE) {
            /* h += m[i] */
            $h0 = $h0->addInt32(
                ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 0, 4))
                    ->mask(0x3ffffff)
            )->toInt64();
            $h1 = $h1->addInt32(
                ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 3, 4))
                    ->shiftRight(2)
                    ->mask(0x3ffffff)
            )->toInt64();
            $h2 = $h2->addInt32(
                ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 6, 4))
                    ->shiftRight(4)
                    ->mask(0x3ffffff)
            )->toInt64();
            $h3 = $h3->addInt32(
                ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 9, 4))
                    ->shiftRight(6)
                    ->mask(0x3ffffff)
            )->toInt64();
            $h4 = $h4->addInt32(
                ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($message, 12, 4))
                    ->shiftRight(8)
                    ->orInt32($hibit)
            )->toInt64();

            /* h *= r */
            $d0 = $zero
                ->addInt64($h0->mulInt64($r0, 25))
                ->addInt64($s4->mulInt64($h1, 26))
                ->addInt64($s3->mulInt64($h2, 26))
                ->addInt64($s2->mulInt64($h3, 26))
                ->addInt64($s1->mulInt64($h4, 26));

            $d1 = $zero
                ->addInt64($h0->mulInt64($r1, 25))
                ->addInt64($h1->mulInt64($r0, 25))
                ->addInt64($s4->mulInt64($h2, 26))
                ->addInt64($s3->mulInt64($h3, 26))
                ->addInt64($s2->mulInt64($h4, 26));

            $d2 = $zero
                ->addInt64($h0->mulInt64($r2, 25))
                ->addInt64($h1->mulInt64($r1, 25))
                ->addInt64($h2->mulInt64($r0, 25))
                ->addInt64($s4->mulInt64($h3, 26))
                ->addInt64($s3->mulInt64($h4, 26));

            $d3 = $zero
                ->addInt64($h0->mulInt64($r3, 25))
                ->addInt64($h1->mulInt64($r2, 25))
                ->addInt64($h2->mulInt64($r1, 25))
                ->addInt64($h3->mulInt64($r0, 25))
                ->addInt64($s4->mulInt64($h4, 26));

            $d4 = $zero
                ->addInt64($h0->mulInt64($r4, 25))
                ->addInt64($h1->mulInt64($r3, 25))
                ->addInt64($h2->mulInt64($r2, 25))
                ->addInt64($h3->mulInt64($r1, 25))
                ->addInt64($h4->mulInt64($r0, 25));

            /* (partial) h %= p */
            $c = $d0->shiftRight(26);
            $h0 = $d0->toInt32()->mask(0x3ffffff);
            $d1 = $d1->addInt64($c);

            $c = $d1->shiftRight(26);
            $h1 = $d1->toInt32()->mask(0x3ffffff);
            $d2 = $d2->addInt64($c);

            $c = $d2->shiftRight(26);
            $h2 = $d2->toInt32()->mask(0x3ffffff);
            $d3 = $d3->addInt64($c);

            $c = $d3->shiftRight(26);
            $h3 = $d3->toInt32()->mask(0x3ffffff);
            $d4 = $d4->addInt64($c);

            $c = $d4->shiftRight(26);
            $h4 = $d4->toInt32()->mask(0x3ffffff);
            $h0 = $h0->addInt32($c->toInt32()->mulInt(5, 3));

            $c = $h0->shiftRight(26);
            $h0 = $h0->mask(0x3ffffff);
            $h1 = $h1->addInt32($c);

            // Chop off the left 32 bytes.
            $message = self::substr(
                $message,
                ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE
            );
            $bytes -= ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE;
        }

        /** @var array<int, ParagonIE_Sodium_Core32_Int32> $h */
        $this->h = array($h0, $h1, $h2, $h3, $h4);
        return $this;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public function finish()
    {
        /* process the remaining block */
        if ($this->leftover) {
            $i = $this->leftover;
            $this->buffer[$i++] = 1;
            for (; $i < ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE; ++$i) {
                $this->buffer[$i] = 0;
            }
            $this->final = true;
            $this->blocks(
                self::substr(
                    static::intArrayToString($this->buffer),
                    0,
                    ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE
                ),
                $b = ParagonIE_Sodium_Core32_Poly1305::BLOCK_SIZE
            );
        }

        /**
         * @var ParagonIE_Sodium_Core32_Int32 $f
         * @var ParagonIE_Sodium_Core32_Int32 $g0
         * @var ParagonIE_Sodium_Core32_Int32 $g1
         * @var ParagonIE_Sodium_Core32_Int32 $g2
         * @var ParagonIE_Sodium_Core32_Int32 $g3
         * @var ParagonIE_Sodium_Core32_Int32 $g4
         * @var ParagonIE_Sodium_Core32_Int32 $h0
         * @var ParagonIE_Sodium_Core32_Int32 $h1
         * @var ParagonIE_Sodium_Core32_Int32 $h2
         * @var ParagonIE_Sodium_Core32_Int32 $h3
         * @var ParagonIE_Sodium_Core32_Int32 $h4
         */
        $h0 = $this->h[0];
        $h1 = $this->h[1];
        $h2 = $this->h[2];
        $h3 = $this->h[3];
        $h4 = $this->h[4];

        $c = $h1->shiftRight(26);           # $c = $h1 >> 26;
        $h1 = $h1->mask(0x3ffffff);         # $h1 &= 0x3ffffff;

        $h2 = $h2->addInt32($c);            # $h2 += $c;
        $c = $h2->shiftRight(26);           # $c = $h2 >> 26;
        $h2 = $h2->mask(0x3ffffff);         # $h2 &= 0x3ffffff;

        $h3 = $h3->addInt32($c);            # $h3 += $c;
        $c = $h3->shiftRight(26);           # $c = $h3 >> 26;
        $h3 = $h3->mask(0x3ffffff);         # $h3 &= 0x3ffffff;

        $h4 = $h4->addInt32($c);            # $h4 += $c;
        $c = $h4->shiftRight(26);           # $c = $h4 >> 26;
        $h4 = $h4->mask(0x3ffffff);         # $h4 &= 0x3ffffff;

        $h0 = $h0->addInt32($c->mulInt(5, 3)); # $h0 += self::mul($c, 5);
        $c = $h0->shiftRight(26);           # $c = $h0 >> 26;
        $h0 = $h0->mask(0x3ffffff);         # $h0 &= 0x3ffffff;
        $h1 = $h1->addInt32($c);            # $h1 += $c;

        /* compute h + -p */
        $g0 = $h0->addInt(5);
        $c  = $g0->shiftRight(26);
        $g0 = $g0->mask(0x3ffffff);
        $g1 = $h1->addInt32($c);
        $c  = $g1->shiftRight(26);
        $g1 = $g1->mask(0x3ffffff);
        $g2 = $h2->addInt32($c);
        $c  = $g2->shiftRight(26);
        $g2 = $g2->mask(0x3ffffff);
        $g3 = $h3->addInt32($c);
        $c  = $g3->shiftRight(26);
        $g3 = $g3->mask(0x3ffffff);
        $g4 = $h4->addInt32($c)->subInt(1 << 26);

        # $mask = ($g4 >> 31) - 1;
        /* select h if h < p, or h + -p if h >= p */
        $mask = (int) (($g4->toInt() >> 31) + 1);

        $g0 = $g0->mask($mask);
        $g1 = $g1->mask($mask);
        $g2 = $g2->mask($mask);
        $g3 = $g3->mask($mask);
        $g4 = $g4->mask($mask);

        /** @var int $mask */
        $mask = (~$mask) & 0xffffffff;

        $h0 = $h0->mask($mask)->orInt32($g0);
        $h1 = $h1->mask($mask)->orInt32($g1);
        $h2 = $h2->mask($mask)->orInt32($g2);
        $h3 = $h3->mask($mask)->orInt32($g3);
        $h4 = $h4->mask($mask)->orInt32($g4);

        /* h = h % (2^128) */
        $h0 = $h0->orInt32($h1->shiftLeft(26));
        $h1 = $h1->shiftRight(6)->orInt32($h2->shiftLeft(20));
        $h2 = $h2->shiftRight(12)->orInt32($h3->shiftLeft(14));
        $h3 = $h3->shiftRight(18)->orInt32($h4->shiftLeft(8));

        /* mac = (h + pad) % (2^128) */
        $f = $h0->toInt64()->addInt64($this->pad[0]);
        $h0 = $f->toInt32();
        $f = $h1->toInt64()->addInt64($this->pad[1])->addInt($h0->overflow);
        $h1 = $f->toInt32();
        $f = $h2->toInt64()->addInt64($this->pad[2])->addInt($h1->overflow);
        $h2 = $f->toInt32();
        $f = $h3->toInt64()->addInt64($this->pad[3])->addInt($h2->overflow);
        $h3 = $f->toInt32();

        return $h0->toReverseString() .
            $h1->toReverseString() .
            $h2->toReverseString() .
            $h3->toReverseString();
    }
}
vendor/paragonie/sodium_compat/src/Core32/Salsa20.php000064400000026362152177723700016437 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Salsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Salsa20
 */
abstract class ParagonIE_Sodium_Core32_Salsa20 extends ParagonIE_Sodium_Core32_Util
{
    const ROUNDS = 20;

    /**
     * Calculate an salsa20 hash of a single block
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function core_salsa20($in, $k, $c = null)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int32 $x0
         * @var ParagonIE_Sodium_Core32_Int32 $x1
         * @var ParagonIE_Sodium_Core32_Int32 $x2
         * @var ParagonIE_Sodium_Core32_Int32 $x3
         * @var ParagonIE_Sodium_Core32_Int32 $x4
         * @var ParagonIE_Sodium_Core32_Int32 $x5
         * @var ParagonIE_Sodium_Core32_Int32 $x6
         * @var ParagonIE_Sodium_Core32_Int32 $x7
         * @var ParagonIE_Sodium_Core32_Int32 $x8
         * @var ParagonIE_Sodium_Core32_Int32 $x9
         * @var ParagonIE_Sodium_Core32_Int32 $x10
         * @var ParagonIE_Sodium_Core32_Int32 $x11
         * @var ParagonIE_Sodium_Core32_Int32 $x12
         * @var ParagonIE_Sodium_Core32_Int32 $x13
         * @var ParagonIE_Sodium_Core32_Int32 $x14
         * @var ParagonIE_Sodium_Core32_Int32 $x15
         * @var ParagonIE_Sodium_Core32_Int32 $j0
         * @var ParagonIE_Sodium_Core32_Int32 $j1
         * @var ParagonIE_Sodium_Core32_Int32 $j2
         * @var ParagonIE_Sodium_Core32_Int32 $j3
         * @var ParagonIE_Sodium_Core32_Int32 $j4
         * @var ParagonIE_Sodium_Core32_Int32 $j5
         * @var ParagonIE_Sodium_Core32_Int32 $j6
         * @var ParagonIE_Sodium_Core32_Int32 $j7
         * @var ParagonIE_Sodium_Core32_Int32 $j8
         * @var ParagonIE_Sodium_Core32_Int32 $j9
         * @var ParagonIE_Sodium_Core32_Int32 $j10
         * @var ParagonIE_Sodium_Core32_Int32 $j11
         * @var ParagonIE_Sodium_Core32_Int32 $j12
         * @var ParagonIE_Sodium_Core32_Int32 $j13
         * @var ParagonIE_Sodium_Core32_Int32 $j14
         * @var ParagonIE_Sodium_Core32_Int32 $j15
         */
        if (self::strlen($k) < 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        if ($c === null) {
            $x0  = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $x5  = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $x10 = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $x15 = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $x0  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $x5  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $x10 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $x15 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $x1  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 0, 4));
        $x2  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 4, 4));
        $x3  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 8, 4));
        $x4  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 12, 4));
        $x6  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $x7  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $x8  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $x9  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));
        $x11 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 16, 4));
        $x12 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 20, 4));
        $x13 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 24, 4));
        $x14 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 28, 4));

        $j0  = clone $x0;
        $j1  = clone $x1;
        $j2  = clone $x2;
        $j3  = clone $x3;
        $j4  = clone $x4;
        $j5  = clone $x5;
        $j6  = clone $x6;
        $j7  = clone $x7;
        $j8  = clone $x8;
        $j9  = clone $x9;
        $j10  = clone $x10;
        $j11  = clone $x11;
        $j12  = clone $x12;
        $j13  = clone $x13;
        $j14  = clone $x14;
        $j15  = clone $x15;

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4  = $x4->xorInt32($x0->addInt32($x12)->rotateLeft(7));
            $x8  = $x8->xorInt32($x4->addInt32($x0)->rotateLeft(9));
            $x12 = $x12->xorInt32($x8->addInt32($x4)->rotateLeft(13));
            $x0  = $x0->xorInt32($x12->addInt32($x8)->rotateLeft(18));

            $x9  = $x9->xorInt32($x5->addInt32($x1)->rotateLeft(7));
            $x13 = $x13->xorInt32($x9->addInt32($x5)->rotateLeft(9));
            $x1  = $x1->xorInt32($x13->addInt32($x9)->rotateLeft(13));
            $x5  = $x5->xorInt32($x1->addInt32($x13)->rotateLeft(18));

            $x14 = $x14->xorInt32($x10->addInt32($x6)->rotateLeft(7));
            $x2  = $x2->xorInt32($x14->addInt32($x10)->rotateLeft(9));
            $x6  = $x6->xorInt32($x2->addInt32($x14)->rotateLeft(13));
            $x10 = $x10->xorInt32($x6->addInt32($x2)->rotateLeft(18));

            $x3  = $x3->xorInt32($x15->addInt32($x11)->rotateLeft(7));
            $x7  = $x7->xorInt32($x3->addInt32($x15)->rotateLeft(9));
            $x11 = $x11->xorInt32($x7->addInt32($x3)->rotateLeft(13));
            $x15 = $x15->xorInt32($x11->addInt32($x7)->rotateLeft(18));

            $x1  = $x1->xorInt32($x0->addInt32($x3)->rotateLeft(7));
            $x2  = $x2->xorInt32($x1->addInt32($x0)->rotateLeft(9));
            $x3  = $x3->xorInt32($x2->addInt32($x1)->rotateLeft(13));
            $x0  = $x0->xorInt32($x3->addInt32($x2)->rotateLeft(18));

            $x6  = $x6->xorInt32($x5->addInt32($x4)->rotateLeft(7));
            $x7  = $x7->xorInt32($x6->addInt32($x5)->rotateLeft(9));
            $x4  = $x4->xorInt32($x7->addInt32($x6)->rotateLeft(13));
            $x5  = $x5->xorInt32($x4->addInt32($x7)->rotateLeft(18));

            $x11 = $x11->xorInt32($x10->addInt32($x9)->rotateLeft(7));
            $x8  = $x8->xorInt32($x11->addInt32($x10)->rotateLeft(9));
            $x9  = $x9->xorInt32($x8->addInt32($x11)->rotateLeft(13));
            $x10 = $x10->xorInt32($x9->addInt32($x8)->rotateLeft(18));

            $x12 = $x12->xorInt32($x15->addInt32($x14)->rotateLeft(7));
            $x13 = $x13->xorInt32($x12->addInt32($x15)->rotateLeft(9));
            $x14 = $x14->xorInt32($x13->addInt32($x12)->rotateLeft(13));
            $x15 = $x15->xorInt32($x14->addInt32($x13)->rotateLeft(18));
        }

        $x0  = $x0->addInt32($j0);
        $x1  = $x1->addInt32($j1);
        $x2  = $x2->addInt32($j2);
        $x3  = $x3->addInt32($j3);
        $x4  = $x4->addInt32($j4);
        $x5  = $x5->addInt32($j5);
        $x6  = $x6->addInt32($j6);
        $x7  = $x7->addInt32($j7);
        $x8  = $x8->addInt32($j8);
        $x9  = $x9->addInt32($j9);
        $x10 = $x10->addInt32($j10);
        $x11 = $x11->addInt32($j11);
        $x12 = $x12->addInt32($j12);
        $x13 = $x13->addInt32($j13);
        $x14 = $x14->addInt32($j14);
        $x15 = $x15->addInt32($j15);

        return $x0->toReverseString() .
            $x1->toReverseString() .
            $x2->toReverseString() .
            $x3->toReverseString() .
            $x4->toReverseString() .
            $x5->toReverseString() .
            $x6->toReverseString() .
            $x7->toReverseString() .
            $x8->toReverseString() .
            $x9->toReverseString() .
            $x10->toReverseString() .
            $x11->toReverseString() .
            $x12->toReverseString() .
            $x13->toReverseString() .
            $x14->toReverseString() .
            $x15->toReverseString();
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20($len, $nonce, $key)
    {
        if (self::strlen($key) !== 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        $kcopy = '' . $key;
        $in = self::substr($nonce, 0, 8) . str_repeat("\0", 8);
        $c = '';
        while ($len >= 64) {
            $c .= self::core_salsa20($in, $kcopy, null);
            $u = 1;
            // Internal counter.
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }
            $len -= 64;
        }
        if ($len > 0) {
            $c .= self::substr(
                self::core_salsa20($in, $kcopy, null),
                0,
                $len
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $kcopy = null;
        }
        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $m
     * @param string $n
     * @param int $ic
     * @param string $k
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor_ic($m, $n, $ic, $k)
    {
        $mlen = self::strlen($m);
        if ($mlen < 1) {
            return '';
        }
        $kcopy = self::substr($k, 0, 32);
        $in = self::substr($n, 0, 8);
        // Initialize the counter
        $in .= ParagonIE_Sodium_Core32_Util::store64_le($ic);

        $c = '';
        while ($mlen >= 64) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, 64),
                self::substr($block, 0, 64)
            );
            $u = 1;
            for ($i = 8; $i < 16; ++$i) {
                $u += self::chrToInt($in[$i]);
                $in[$i] = self::intToChr($u & 0xff);
                $u >>= 8;
            }

            $mlen -= 64;
            $m = self::substr($m, 64);
        }

        if ($mlen) {
            $block = self::core_salsa20($in, $kcopy, null);
            $c .= self::xorStrings(
                self::substr($m, 0, $mlen),
                self::substr($block, 0, $mlen)
            );
        }
        try {
            ParagonIE_Sodium_Compat::memzero($block);
            ParagonIE_Sodium_Compat::memzero($kcopy);
        } catch (SodiumException $ex) {
            $block = null;
            $kcopy = null;
        }

        return $c;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function salsa20_xor($message, $nonce, $key)
    {
        return self::xorStrings(
            $message,
            self::salsa20(
                self::strlen($message),
                $nonce,
                $key
            )
        );
    }
}
vendor/paragonie/sodium_compat/src/Core32/XSalsa20.php000064400000002543152177723700016562 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_XSalsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_XSalsa20
 */
abstract class ParagonIE_Sodium_Core32_XSalsa20 extends ParagonIE_Sodium_Core32_HSalsa20
{
    /**
     * Expand a key and nonce into an xsalsa20 keystream.
     *
     * @internal You should not use this directly from another application
     *
     * @param int $len
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function xsalsa20($len, $nonce, $key)
    {
        $ret = self::salsa20(
            $len,
            self::substr($nonce, 16, 8),
            self::hsalsa20($nonce, $key)
        );
        return $ret;
    }

    /**
     * Encrypt a string with XSalsa20. Doesn't provide integrity.
     *
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $nonce
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function xsalsa20_xor($message, $nonce, $key)
    {
        return self::xorStrings(
            $message,
            self::xsalsa20(
                self::strlen($message),
                $nonce,
                $key
            )
        );
    }
}
vendor/paragonie/sodium_compat/src/Core32/SipHash.php000064400000014725152177723700016571 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_SipHash', false)) {
    return;
}

/**
 * Class ParagonIE_SodiumCompat_Core32_SipHash
 *
 * Only uses 32-bit arithmetic, while the original SipHash used 64-bit integers
 */
class ParagonIE_Sodium_Core32_SipHash extends ParagonIE_Sodium_Core32_Util
{
    /**
     * @internal You should not use this directly from another application
     *
     * @param array<int, ParagonIE_Sodium_Core32_Int64> $v
     * @return array<int, ParagonIE_Sodium_Core32_Int64>
     */
    public static function sipRound(array $v)
    {
        # v0 += v1;
        $v[0] = $v[0]->addInt64($v[1]);

        # v1 = ROTL(v1, 13);
        $v[1] = $v[1]->rotateLeft(13);

        #  v1 ^= v0;
        $v[1] = $v[1]->xorInt64($v[0]);

        #  v0=ROTL(v0,32);
        $v[0] = $v[0]->rotateLeft(32);

        # v2 += v3;
        $v[2] = $v[2]->addInt64($v[3]);

        # v3=ROTL(v3,16);
        $v[3] = $v[3]->rotateLeft(16);

        #  v3 ^= v2;
        $v[3] = $v[3]->xorInt64($v[2]);

        # v0 += v3;
        $v[0] = $v[0]->addInt64($v[3]);

        # v3=ROTL(v3,21);
        $v[3] = $v[3]->rotateLeft(21);

        # v3 ^= v0;
        $v[3] = $v[3]->xorInt64($v[0]);

        # v2 += v1;
        $v[2] = $v[2]->addInt64($v[1]);

        # v1=ROTL(v1,17);
        $v[1] = $v[1]->rotateLeft(17);

        #  v1 ^= v2;
        $v[1] = $v[1]->xorInt64($v[2]);

        # v2=ROTL(v2,32)
        $v[2] = $v[2]->rotateLeft(32);

        return $v;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $key
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sipHash24($in, $key)
    {
        $inlen = self::strlen($in);

        # /* "somepseudorandomlygeneratedbytes" */
        # u64 v0 = 0x736f6d6570736575ULL;
        # u64 v1 = 0x646f72616e646f6dULL;
        # u64 v2 = 0x6c7967656e657261ULL;
        # u64 v3 = 0x7465646279746573ULL;
        $v = array(
            new ParagonIE_Sodium_Core32_Int64(
                array(0x736f, 0x6d65, 0x7073, 0x6575)
            ),
            new ParagonIE_Sodium_Core32_Int64(
                array(0x646f, 0x7261, 0x6e64, 0x6f6d)
            ),
            new ParagonIE_Sodium_Core32_Int64(
                array(0x6c79, 0x6765, 0x6e65, 0x7261)
            ),
            new ParagonIE_Sodium_Core32_Int64(
                array(0x7465, 0x6462, 0x7974, 0x6573)
            )
        );

        # u64 k0 = LOAD64_LE( k );
        # u64 k1 = LOAD64_LE( k + 8 );
        $k = array(
            ParagonIE_Sodium_Core32_Int64::fromReverseString(
                self::substr($key, 0, 8)
            ),
            ParagonIE_Sodium_Core32_Int64::fromReverseString(
                self::substr($key, 8, 8)
            )
        );

        # b = ( ( u64 )inlen ) << 56;
        $b = new ParagonIE_Sodium_Core32_Int64(
            array(($inlen << 8) & 0xffff, 0, 0, 0)
        );

        # v3 ^= k1;
        $v[3] = $v[3]->xorInt64($k[1]);
        # v2 ^= k0;
        $v[2] = $v[2]->xorInt64($k[0]);
        # v1 ^= k1;
        $v[1] = $v[1]->xorInt64($k[1]);
        # v0 ^= k0;
        $v[0] = $v[0]->xorInt64($k[0]);

        $left = $inlen;
        # for ( ; in != end; in += 8 )
        while ($left >= 8) {
            # m = LOAD64_LE( in );
            $m = ParagonIE_Sodium_Core32_Int64::fromReverseString(
                self::substr($in, 0, 8)
            );

            # v3 ^= m;
            $v[3] = $v[3]->xorInt64($m);

            # SIPROUND;
            # SIPROUND;
            $v = self::sipRound($v);
            $v = self::sipRound($v);

            # v0 ^= m;
            $v[0] = $v[0]->xorInt64($m);

            $in = self::substr($in, 8);
            $left -= 8;
        }

        # switch( left )
        #  {
        #     case 7: b |= ( ( u64 )in[ 6] )  << 48;
        #     case 6: b |= ( ( u64 )in[ 5] )  << 40;
        #     case 5: b |= ( ( u64 )in[ 4] )  << 32;
        #     case 4: b |= ( ( u64 )in[ 3] )  << 24;
        #     case 3: b |= ( ( u64 )in[ 2] )  << 16;
        #     case 2: b |= ( ( u64 )in[ 1] )  <<  8;
        #     case 1: b |= ( ( u64 )in[ 0] ); break;
        #     case 0: break;
        # }
        switch ($left) {
            case 7:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        0, self::chrToInt($in[6]) << 16
                    )
                );
            case 6:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        0, self::chrToInt($in[5]) << 8
                    )
                );
            case 5:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        0, self::chrToInt($in[4])
                    )
                );
            case 4:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        self::chrToInt($in[3]) << 24, 0
                    )
                );
            case 3:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        self::chrToInt($in[2]) << 16, 0
                    )
                );
            case 2:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        self::chrToInt($in[1]) << 8, 0
                    )
                );
            case 1:
                $b = $b->orInt64(
                    ParagonIE_Sodium_Core32_Int64::fromInts(
                        self::chrToInt($in[0]), 0
                    )
                );
            case 0:
                break;
        }

        # v3 ^= b;
        $v[3] = $v[3]->xorInt64($b);

        # SIPROUND;
        # SIPROUND;
        $v = self::sipRound($v);
        $v = self::sipRound($v);

        # v0 ^= b;
        $v[0] = $v[0]->xorInt64($b);

        // Flip the lower 8 bits of v2 which is ($v[4], $v[5]) in our implementation
        # v2 ^= 0xff;
        $v[2]->limbs[3] ^= 0xff;

        # SIPROUND;
        # SIPROUND;
        # SIPROUND;
        # SIPROUND;
        $v = self::sipRound($v);
        $v = self::sipRound($v);
        $v = self::sipRound($v);
        $v = self::sipRound($v);

        # b = v0 ^ v1 ^ v2 ^ v3;
        # STORE64_LE( out, b );
        return $v[0]
            ->xorInt64($v[1])
            ->xorInt64($v[2])
            ->xorInt64($v[3])
            ->toReverseString();
    }
}
vendor/paragonie/sodium_compat/src/Core32/HSalsa20.php000064400000015435152177723700016546 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_HSalsa20', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_HSalsa20
 */
abstract class ParagonIE_Sodium_Core32_HSalsa20 extends ParagonIE_Sodium_Core32_Salsa20
{
    /**
     * Calculate an hsalsa20 hash of a single block
     *
     * HSalsa20 doesn't have a counter and will never be used for more than
     * one block (used to derive a subkey for xsalsa20).
     *
     * @internal You should not use this directly from another application
     *
     * @param string $in
     * @param string $k
     * @param string|null $c
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function hsalsa20($in, $k, $c = null)
    {
        /**
         * @var ParagonIE_Sodium_Core32_Int32 $x0
         * @var ParagonIE_Sodium_Core32_Int32 $x1
         * @var ParagonIE_Sodium_Core32_Int32 $x2
         * @var ParagonIE_Sodium_Core32_Int32 $x3
         * @var ParagonIE_Sodium_Core32_Int32 $x4
         * @var ParagonIE_Sodium_Core32_Int32 $x5
         * @var ParagonIE_Sodium_Core32_Int32 $x6
         * @var ParagonIE_Sodium_Core32_Int32 $x7
         * @var ParagonIE_Sodium_Core32_Int32 $x8
         * @var ParagonIE_Sodium_Core32_Int32 $x9
         * @var ParagonIE_Sodium_Core32_Int32 $x10
         * @var ParagonIE_Sodium_Core32_Int32 $x11
         * @var ParagonIE_Sodium_Core32_Int32 $x12
         * @var ParagonIE_Sodium_Core32_Int32 $x13
         * @var ParagonIE_Sodium_Core32_Int32 $x14
         * @var ParagonIE_Sodium_Core32_Int32 $x15
         * @var ParagonIE_Sodium_Core32_Int32 $j0
         * @var ParagonIE_Sodium_Core32_Int32 $j1
         * @var ParagonIE_Sodium_Core32_Int32 $j2
         * @var ParagonIE_Sodium_Core32_Int32 $j3
         * @var ParagonIE_Sodium_Core32_Int32 $j4
         * @var ParagonIE_Sodium_Core32_Int32 $j5
         * @var ParagonIE_Sodium_Core32_Int32 $j6
         * @var ParagonIE_Sodium_Core32_Int32 $j7
         * @var ParagonIE_Sodium_Core32_Int32 $j8
         * @var ParagonIE_Sodium_Core32_Int32 $j9
         * @var ParagonIE_Sodium_Core32_Int32 $j10
         * @var ParagonIE_Sodium_Core32_Int32 $j11
         * @var ParagonIE_Sodium_Core32_Int32 $j12
         * @var ParagonIE_Sodium_Core32_Int32 $j13
         * @var ParagonIE_Sodium_Core32_Int32 $j14
         * @var ParagonIE_Sodium_Core32_Int32 $j15
         */
        if (self::strlen($k) < 32) {
            throw new RangeException('Key must be 32 bytes long');
        }
        if ($c === null) {
            $x0  = new ParagonIE_Sodium_Core32_Int32(array(0x6170, 0x7865));
            $x5  = new ParagonIE_Sodium_Core32_Int32(array(0x3320, 0x646e));
            $x10 = new ParagonIE_Sodium_Core32_Int32(array(0x7962, 0x2d32));
            $x15 = new ParagonIE_Sodium_Core32_Int32(array(0x6b20, 0x6574));
        } else {
            $x0  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 0, 4));
            $x5  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 4, 4));
            $x10 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 8, 4));
            $x15 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($c, 12, 4));
        }
        $x1  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 0, 4));
        $x2  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 4, 4));
        $x3  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 8, 4));
        $x4  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 12, 4));
        $x6  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 0, 4));
        $x7  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 4, 4));
        $x8  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 8, 4));
        $x9  = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($in, 12, 4));
        $x11 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 16, 4));
        $x12 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 20, 4));
        $x13 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 24, 4));
        $x14 = ParagonIE_Sodium_Core32_Int32::fromReverseString(self::substr($k, 28, 4));

        for ($i = self::ROUNDS; $i > 0; $i -= 2) {
            $x4  = $x4->xorInt32($x0->addInt32($x12)->rotateLeft(7));
            $x8  = $x8->xorInt32($x4->addInt32($x0)->rotateLeft(9));
            $x12 = $x12->xorInt32($x8->addInt32($x4)->rotateLeft(13));
            $x0  = $x0->xorInt32($x12->addInt32($x8)->rotateLeft(18));

            $x9  = $x9->xorInt32($x5->addInt32($x1)->rotateLeft(7));
            $x13 = $x13->xorInt32($x9->addInt32($x5)->rotateLeft(9));
            $x1  = $x1->xorInt32($x13->addInt32($x9)->rotateLeft(13));
            $x5  = $x5->xorInt32($x1->addInt32($x13)->rotateLeft(18));

            $x14 = $x14->xorInt32($x10->addInt32($x6)->rotateLeft(7));
            $x2  = $x2->xorInt32($x14->addInt32($x10)->rotateLeft(9));
            $x6  = $x6->xorInt32($x2->addInt32($x14)->rotateLeft(13));
            $x10 = $x10->xorInt32($x6->addInt32($x2)->rotateLeft(18));

            $x3  = $x3->xorInt32($x15->addInt32($x11)->rotateLeft(7));
            $x7  = $x7->xorInt32($x3->addInt32($x15)->rotateLeft(9));
            $x11 = $x11->xorInt32($x7->addInt32($x3)->rotateLeft(13));
            $x15 = $x15->xorInt32($x11->addInt32($x7)->rotateLeft(18));

            $x1  = $x1->xorInt32($x0->addInt32($x3)->rotateLeft(7));
            $x2  = $x2->xorInt32($x1->addInt32($x0)->rotateLeft(9));
            $x3  = $x3->xorInt32($x2->addInt32($x1)->rotateLeft(13));
            $x0  = $x0->xorInt32($x3->addInt32($x2)->rotateLeft(18));

            $x6  = $x6->xorInt32($x5->addInt32($x4)->rotateLeft(7));
            $x7  = $x7->xorInt32($x6->addInt32($x5)->rotateLeft(9));
            $x4  = $x4->xorInt32($x7->addInt32($x6)->rotateLeft(13));
            $x5  = $x5->xorInt32($x4->addInt32($x7)->rotateLeft(18));

            $x11 = $x11->xorInt32($x10->addInt32($x9)->rotateLeft(7));
            $x8  = $x8->xorInt32($x11->addInt32($x10)->rotateLeft(9));
            $x9  = $x9->xorInt32($x8->addInt32($x11)->rotateLeft(13));
            $x10 = $x10->xorInt32($x9->addInt32($x8)->rotateLeft(18));

            $x12 = $x12->xorInt32($x15->addInt32($x14)->rotateLeft(7));
            $x13 = $x13->xorInt32($x12->addInt32($x15)->rotateLeft(9));
            $x14 = $x14->xorInt32($x13->addInt32($x12)->rotateLeft(13));
            $x15 = $x15->xorInt32($x14->addInt32($x13)->rotateLeft(18));
        }

        return $x0->toReverseString() .
            $x5->toReverseString() .
            $x10->toReverseString() .
            $x15->toReverseString() .
            $x6->toReverseString() .
            $x7->toReverseString() .
            $x8->toReverseString() .
            $x9->toReverseString();
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/H.php000064400000324375152177723700017120 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_H', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519_H
 *
 * This just contains the constants in the ref10/base.h file
 */
class ParagonIE_Sodium_Core32_Curve25519_H extends ParagonIE_Sodium_Core32_Util
{
    /**
     * See: libsodium's crypto_core/curve25519/ref10/base.h
     *
     * @var array<int, array<int, array<int, array<int, int>>>> Basically, int[32][8][3][10]
     */
    protected static $base = array(
        array(
            array(
                array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605),
                array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378),
                array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546),
            ),
            array(
                array(-12815894, -12976347, -21581243, 11784320, -25355658, -2750717, -11717903, -3814571, -358445, -10211303),
                array(-21703237, 6903825, 27185491, 6451973, -29577724, -9554005, -15616551, 11189268, -26829678, -5319081),
                array(26966642, 11152617, 32442495, 15396054, 14353839, -12752335, -3128826, -9541118, -15472047, -4166697),
            ),
            array(
                array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024),
                array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574),
                array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357),
            ),
            array(
                array(-17036878, 13921892, 10945806, -6033431, 27105052, -16084379, -28926210, 15006023, 3284568, -6276540),
                array(23599295, -8306047, -11193664, -7687416, 13236774, 10506355, 7464579, 9656445, 13059162, 10374397),
                array(7798556, 16710257, 3033922, 2874086, 28997861, 2835604, 32406664, -3839045, -641708, -101325),
            ),
            array(
                array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380),
                array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306),
                array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942),
            ),
            array(
                array(-15371964, -12862754, 32573250, 4720197, -26436522, 5875511, -19188627, -15224819, -9818940, -12085777),
                array(-8549212, 109983, 15149363, 2178705, 22900618, 4543417, 3044240, -15689887, 1762328, 14866737),
                array(-18199695, -15951423, -10473290, 1707278, -17185920, 3916101, -28236412, 3959421, 27914454, 4383652),
            ),
            array(
                array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766),
                array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701),
                array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300),
            ),
            array(
                array(14499471, -2729599, -33191113, -4254652, 28494862, 14271267, 30290735, 10876454, -33154098, 2381726),
                array(-7195431, -2655363, -14730155, 462251, -27724326, 3941372, -6236617, 3696005, -32300832, 15351955),
                array(27431194, 8222322, 16448760, -3907995, -18707002, 11938355, -32961401, -2970515, 29551813, 10109425),
            ),
        ),
        array(
            array(
                array(-13657040, -13155431, -31283750, 11777098, 21447386, 6519384, -2378284, -1627556, 10092783, -4764171),
                array(27939166, 14210322, 4677035, 16277044, -22964462, -12398139, -32508754, 12005538, -17810127, 12803510),
                array(17228999, -15661624, -1233527, 300140, -1224870, -11714777, 30364213, -9038194, 18016357, 4397660),
            ),
            array(
                array(-10958843, -7690207, 4776341, -14954238, 27850028, -15602212, -26619106, 14544525, -17477504, 982639),
                array(29253598, 15796703, -2863982, -9908884, 10057023, 3163536, 7332899, -4120128, -21047696, 9934963),
                array(5793303, 16271923, -24131614, -10116404, 29188560, 1206517, -14747930, 4559895, -30123922, -10897950),
            ),
            array(
                array(-27643952, -11493006, 16282657, -11036493, 28414021, -15012264, 24191034, 4541697, -13338309, 5500568),
                array(12650548, -1497113, 9052871, 11355358, -17680037, -8400164, -17430592, 12264343, 10874051, 13524335),
                array(25556948, -3045990, 714651, 2510400, 23394682, -10415330, 33119038, 5080568, -22528059, 5376628),
            ),
            array(
                array(-26088264, -4011052, -17013699, -3537628, -6726793, 1920897, -22321305, -9447443, 4535768, 1569007),
                array(-2255422, 14606630, -21692440, -8039818, 28430649, 8775819, -30494562, 3044290, 31848280, 12543772),
                array(-22028579, 2943893, -31857513, 6777306, 13784462, -4292203, -27377195, -2062731, 7718482, 14474653),
            ),
            array(
                array(2385315, 2454213, -22631320, 46603, -4437935, -15680415, 656965, -7236665, 24316168, -5253567),
                array(13741529, 10911568, -33233417, -8603737, -20177830, -1033297, 33040651, -13424532, -20729456, 8321686),
                array(21060490, -2212744, 15712757, -4336099, 1639040, 10656336, 23845965, -11874838, -9984458, 608372),
            ),
            array(
                array(-13672732, -15087586, -10889693, -7557059, -6036909, 11305547, 1123968, -6780577, 27229399, 23887),
                array(-23244140, -294205, -11744728, 14712571, -29465699, -2029617, 12797024, -6440308, -1633405, 16678954),
                array(-29500620, 4770662, -16054387, 14001338, 7830047, 9564805, -1508144, -4795045, -17169265, 4904953),
            ),
            array(
                array(24059557, 14617003, 19037157, -15039908, 19766093, -14906429, 5169211, 16191880, 2128236, -4326833),
                array(-16981152, 4124966, -8540610, -10653797, 30336522, -14105247, -29806336, 916033, -6882542, -2986532),
                array(-22630907, 12419372, -7134229, -7473371, -16478904, 16739175, 285431, 2763829, 15736322, 4143876),
            ),
            array(
                array(2379352, 11839345, -4110402, -5988665, 11274298, 794957, 212801, -14594663, 23527084, -16458268),
                array(33431127, -11130478, -17838966, -15626900, 8909499, 8376530, -32625340, 4087881, -15188911, -14416214),
                array(1767683, 7197987, -13205226, -2022635, -13091350, 448826, 5799055, 4357868, -4774191, -16323038),
            ),
        ),
        array(
            array(
                array(6721966, 13833823, -23523388, -1551314, 26354293, -11863321, 23365147, -3949732, 7390890, 2759800),
                array(4409041, 2052381, 23373853, 10530217, 7676779, -12885954, 21302353, -4264057, 1244380, -12919645),
                array(-4421239, 7169619, 4982368, -2957590, 30256825, -2777540, 14086413, 9208236, 15886429, 16489664),
            ),
            array(
                array(1996075, 10375649, 14346367, 13311202, -6874135, -16438411, -13693198, 398369, -30606455, -712933),
                array(-25307465, 9795880, -2777414, 14878809, -33531835, 14780363, 13348553, 12076947, -30836462, 5113182),
                array(-17770784, 11797796, 31950843, 13929123, -25888302, 12288344, -30341101, -7336386, 13847711, 5387222),
            ),
            array(
                array(-18582163, -3416217, 17824843, -2340966, 22744343, -10442611, 8763061, 3617786, -19600662, 10370991),
                array(20246567, -14369378, 22358229, -543712, 18507283, -10413996, 14554437, -8746092, 32232924, 16763880),
                array(9648505, 10094563, 26416693, 14745928, -30374318, -6472621, 11094161, 15689506, 3140038, -16510092),
            ),
            array(
                array(-16160072, 5472695, 31895588, 4744994, 8823515, 10365685, -27224800, 9448613, -28774454, 366295),
                array(19153450, 11523972, -11096490, -6503142, -24647631, 5420647, 28344573, 8041113, 719605, 11671788),
                array(8678025, 2694440, -6808014, 2517372, 4964326, 11152271, -15432916, -15266516, 27000813, -10195553),
            ),
            array(
                array(-15157904, 7134312, 8639287, -2814877, -7235688, 10421742, 564065, 5336097, 6750977, -14521026),
                array(11836410, -3979488, 26297894, 16080799, 23455045, 15735944, 1695823, -8819122, 8169720, 16220347),
                array(-18115838, 8653647, 17578566, -6092619, -8025777, -16012763, -11144307, -2627664, -5990708, -14166033),
            ),
            array(
                array(-23308498, -10968312, 15213228, -10081214, -30853605, -11050004, 27884329, 2847284, 2655861, 1738395),
                array(-27537433, -14253021, -25336301, -8002780, -9370762, 8129821, 21651608, -3239336, -19087449, -11005278),
                array(1533110, 3437855, 23735889, 459276, 29970501, 11335377, 26030092, 5821408, 10478196, 8544890),
            ),
            array(
                array(32173121, -16129311, 24896207, 3921497, 22579056, -3410854, 19270449, 12217473, 17789017, -3395995),
                array(-30552961, -2228401, -15578829, -10147201, 13243889, 517024, 15479401, -3853233, 30460520, 1052596),
                array(-11614875, 13323618, 32618793, 8175907, -15230173, 12596687, 27491595, -4612359, 3179268, -9478891),
            ),
            array(
                array(31947069, -14366651, -4640583, -15339921, -15125977, -6039709, -14756777, -16411740, 19072640, -9511060),
                array(11685058, 11822410, 3158003, -13952594, 33402194, -4165066, 5977896, -5215017, 473099, 5040608),
                array(-20290863, 8198642, -27410132, 11602123, 1290375, -2799760, 28326862, 1721092, -19558642, -3131606),
            ),
        ),
        array(
            array(
                array(7881532, 10687937, 7578723, 7738378, -18951012, -2553952, 21820786, 8076149, -27868496, 11538389),
                array(-19935666, 3899861, 18283497, -6801568, -15728660, -11249211, 8754525, 7446702, -5676054, 5797016),
                array(-11295600, -3793569, -15782110, -7964573, 12708869, -8456199, 2014099, -9050574, -2369172, -5877341),
            ),
            array(
                array(-22472376, -11568741, -27682020, 1146375, 18956691, 16640559, 1192730, -3714199, 15123619, 10811505),
                array(14352098, -3419715, -18942044, 10822655, 32750596, 4699007, -70363, 15776356, -28886779, -11974553),
                array(-28241164, -8072475, -4978962, -5315317, 29416931, 1847569, -20654173, -16484855, 4714547, -9600655),
            ),
            array(
                array(15200332, 8368572, 19679101, 15970074, -31872674, 1959451, 24611599, -4543832, -11745876, 12340220),
                array(12876937, -10480056, 33134381, 6590940, -6307776, 14872440, 9613953, 8241152, 15370987, 9608631),
                array(-4143277, -12014408, 8446281, -391603, 4407738, 13629032, -7724868, 15866074, -28210621, -8814099),
            ),
            array(
                array(26660628, -15677655, 8393734, 358047, -7401291, 992988, -23904233, 858697, 20571223, 8420556),
                array(14620715, 13067227, -15447274, 8264467, 14106269, 15080814, 33531827, 12516406, -21574435, -12476749),
                array(236881, 10476226, 57258, -14677024, 6472998, 2466984, 17258519, 7256740, 8791136, 15069930),
            ),
            array(
                array(1276410, -9371918, 22949635, -16322807, -23493039, -5702186, 14711875, 4874229, -30663140, -2331391),
                array(5855666, 4990204, -13711848, 7294284, -7804282, 1924647, -1423175, -7912378, -33069337, 9234253),
                array(20590503, -9018988, 31529744, -7352666, -2706834, 10650548, 31559055, -11609587, 18979186, 13396066),
            ),
            array(
                array(24474287, 4968103, 22267082, 4407354, 24063882, -8325180, -18816887, 13594782, 33514650, 7021958),
                array(-11566906, -6565505, -21365085, 15928892, -26158305, 4315421, -25948728, -3916677, -21480480, 12868082),
                array(-28635013, 13504661, 19988037, -2132761, 21078225, 6443208, -21446107, 2244500, -12455797, -8089383),
            ),
            array(
                array(-30595528, 13793479, -5852820, 319136, -25723172, -6263899, 33086546, 8957937, -15233648, 5540521),
                array(-11630176, -11503902, -8119500, -7643073, 2620056, 1022908, -23710744, -1568984, -16128528, -14962807),
                array(23152971, 775386, 27395463, 14006635, -9701118, 4649512, 1689819, 892185, -11513277, -15205948),
            ),
            array(
                array(9770129, 9586738, 26496094, 4324120, 1556511, -3550024, 27453819, 4763127, -19179614, 5867134),
                array(-32765025, 1927590, 31726409, -4753295, 23962434, -16019500, 27846559, 5931263, -29749703, -16108455),
                array(27461885, -2977536, 22380810, 1815854, -23033753, -3031938, 7283490, -15148073, -19526700, 7734629),
            ),
        ),
        array(
            array(
                array(-8010264, -9590817, -11120403, 6196038, 29344158, -13430885, 7585295, -3176626, 18549497, 15302069),
                array(-32658337, -6171222, -7672793, -11051681, 6258878, 13504381, 10458790, -6418461, -8872242, 8424746),
                array(24687205, 8613276, -30667046, -3233545, 1863892, -1830544, 19206234, 7134917, -11284482, -828919),
            ),
            array(
                array(11334899, -9218022, 8025293, 12707519, 17523892, -10476071, 10243738, -14685461, -5066034, 16498837),
                array(8911542, 6887158, -9584260, -6958590, 11145641, -9543680, 17303925, -14124238, 6536641, 10543906),
                array(-28946384, 15479763, -17466835, 568876, -1497683, 11223454, -2669190, -16625574, -27235709, 8876771),
            ),
            array(
                array(-25742899, -12566864, -15649966, -846607, -33026686, -796288, -33481822, 15824474, -604426, -9039817),
                array(10330056, 70051, 7957388, -9002667, 9764902, 15609756, 27698697, -4890037, 1657394, 3084098),
                array(10477963, -7470260, 12119566, -13250805, 29016247, -5365589, 31280319, 14396151, -30233575, 15272409),
            ),
            array(
                array(-12288309, 3169463, 28813183, 16658753, 25116432, -5630466, -25173957, -12636138, -25014757, 1950504),
                array(-26180358, 9489187, 11053416, -14746161, -31053720, 5825630, -8384306, -8767532, 15341279, 8373727),
                array(28685821, 7759505, -14378516, -12002860, -31971820, 4079242, 298136, -10232602, -2878207, 15190420),
            ),
            array(
                array(-32932876, 13806336, -14337485, -15794431, -24004620, 10940928, 8669718, 2742393, -26033313, -6875003),
                array(-1580388, -11729417, -25979658, -11445023, -17411874, -10912854, 9291594, -16247779, -12154742, 6048605),
                array(-30305315, 14843444, 1539301, 11864366, 20201677, 1900163, 13934231, 5128323, 11213262, 9168384),
            ),
            array(
                array(-26280513, 11007847, 19408960, -940758, -18592965, -4328580, -5088060, -11105150, 20470157, -16398701),
                array(-23136053, 9282192, 14855179, -15390078, -7362815, -14408560, -22783952, 14461608, 14042978, 5230683),
                array(29969567, -2741594, -16711867, -8552442, 9175486, -2468974, 21556951, 3506042, -5933891, -12449708),
            ),
            array(
                array(-3144746, 8744661, 19704003, 4581278, -20430686, 6830683, -21284170, 8971513, -28539189, 15326563),
                array(-19464629, 10110288, -17262528, -3503892, -23500387, 1355669, -15523050, 15300988, -20514118, 9168260),
                array(-5353335, 4488613, -23803248, 16314347, 7780487, -15638939, -28948358, 9601605, 33087103, -9011387),
            ),
            array(
                array(-19443170, -15512900, -20797467, -12445323, -29824447, 10229461, -27444329, -15000531, -5996870, 15664672),
                array(23294591, -16632613, -22650781, -8470978, 27844204, 11461195, 13099750, -2460356, 18151676, 13417686),
                array(-24722913, -4176517, -31150679, 5988919, -26858785, 6685065, 1661597, -12551441, 15271676, -15452665),
            ),
        ),
        array(
            array(
                array(11433042, -13228665, 8239631, -5279517, -1985436, -725718, -18698764, 2167544, -6921301, -13440182),
                array(-31436171, 15575146, 30436815, 12192228, -22463353, 9395379, -9917708, -8638997, 12215110, 12028277),
                array(14098400, 6555944, 23007258, 5757252, -15427832, -12950502, 30123440, 4617780, -16900089, -655628),
            ),
            array(
                array(-4026201, -15240835, 11893168, 13718664, -14809462, 1847385, -15819999, 10154009, 23973261, -12684474),
                array(-26531820, -3695990, -1908898, 2534301, -31870557, -16550355, 18341390, -11419951, 32013174, -10103539),
                array(-25479301, 10876443, -11771086, -14625140, -12369567, 1838104, 21911214, 6354752, 4425632, -837822),
            ),
            array(
                array(-10433389, -14612966, 22229858, -3091047, -13191166, 776729, -17415375, -12020462, 4725005, 14044970),
                array(19268650, -7304421, 1555349, 8692754, -21474059, -9910664, 6347390, -1411784, -19522291, -16109756),
                array(-24864089, 12986008, -10898878, -5558584, -11312371, -148526, 19541418, 8180106, 9282262, 10282508),
            ),
            array(
                array(-26205082, 4428547, -8661196, -13194263, 4098402, -14165257, 15522535, 8372215, 5542595, -10702683),
                array(-10562541, 14895633, 26814552, -16673850, -17480754, -2489360, -2781891, 6993761, -18093885, 10114655),
                array(-20107055, -929418, 31422704, 10427861, -7110749, 6150669, -29091755, -11529146, 25953725, -106158),
            ),
            array(
                array(-4234397, -8039292, -9119125, 3046000, 2101609, -12607294, 19390020, 6094296, -3315279, 12831125),
                array(-15998678, 7578152, 5310217, 14408357, -33548620, -224739, 31575954, 6326196, 7381791, -2421839),
                array(-20902779, 3296811, 24736065, -16328389, 18374254, 7318640, 6295303, 8082724, -15362489, 12339664),
            ),
            array(
                array(27724736, 2291157, 6088201, -14184798, 1792727, 5857634, 13848414, 15768922, 25091167, 14856294),
                array(-18866652, 8331043, 24373479, 8541013, -701998, -9269457, 12927300, -12695493, -22182473, -9012899),
                array(-11423429, -5421590, 11632845, 3405020, 30536730, -11674039, -27260765, 13866390, 30146206, 9142070),
            ),
            array(
                array(3924129, -15307516, -13817122, -10054960, 12291820, -668366, -27702774, 9326384, -8237858, 4171294),
                array(-15921940, 16037937, 6713787, 16606682, -21612135, 2790944, 26396185, 3731949, 345228, -5462949),
                array(-21327538, 13448259, 25284571, 1143661, 20614966, -8849387, 2031539, -12391231, -16253183, -13582083),
            ),
            array(
                array(31016211, -16722429, 26371392, -14451233, -5027349, 14854137, 17477601, 3842657, 28012650, -16405420),
                array(-5075835, 9368966, -8562079, -4600902, -15249953, 6970560, -9189873, 16292057, -8867157, 3507940),
                array(29439664, 3537914, 23333589, 6997794, -17555561, -11018068, -15209202, -15051267, -9164929, 6580396),
            ),
        ),
        array(
            array(
                array(-12185861, -7679788, 16438269, 10826160, -8696817, -6235611, 17860444, -9273846, -2095802, 9304567),
                array(20714564, -4336911, 29088195, 7406487, 11426967, -5095705, 14792667, -14608617, 5289421, -477127),
                array(-16665533, -10650790, -6160345, -13305760, 9192020, -1802462, 17271490, 12349094, 26939669, -3752294),
            ),
            array(
                array(-12889898, 9373458, 31595848, 16374215, 21471720, 13221525, -27283495, -12348559, -3698806, 117887),
                array(22263325, -6560050, 3984570, -11174646, -15114008, -566785, 28311253, 5358056, -23319780, 541964),
                array(16259219, 3261970, 2309254, -15534474, -16885711, -4581916, 24134070, -16705829, -13337066, -13552195),
            ),
            array(
                array(9378160, -13140186, -22845982, -12745264, 28198281, -7244098, -2399684, -717351, 690426, 14876244),
                array(24977353, -314384, -8223969, -13465086, 28432343, -1176353, -13068804, -12297348, -22380984, 6618999),
                array(-1538174, 11685646, 12944378, 13682314, -24389511, -14413193, 8044829, -13817328, 32239829, -5652762),
            ),
            array(
                array(-18603066, 4762990, -926250, 8885304, -28412480, -3187315, 9781647, -10350059, 32779359, 5095274),
                array(-33008130, -5214506, -32264887, -3685216, 9460461, -9327423, -24601656, 14506724, 21639561, -2630236),
                array(-16400943, -13112215, 25239338, 15531969, 3987758, -4499318, -1289502, -6863535, 17874574, 558605),
            ),
            array(
                array(-13600129, 10240081, 9171883, 16131053, -20869254, 9599700, 33499487, 5080151, 2085892, 5119761),
                array(-22205145, -2519528, -16381601, 414691, -25019550, 2170430, 30634760, -8363614, -31999993, -5759884),
                array(-6845704, 15791202, 8550074, -1312654, 29928809, -12092256, 27534430, -7192145, -22351378, 12961482),
            ),
            array(
                array(-24492060, -9570771, 10368194, 11582341, -23397293, -2245287, 16533930, 8206996, -30194652, -5159638),
                array(-11121496, -3382234, 2307366, 6362031, -135455, 8868177, -16835630, 7031275, 7589640, 8945490),
                array(-32152748, 8917967, 6661220, -11677616, -1192060, -15793393, 7251489, -11182180, 24099109, -14456170),
            ),
            array(
                array(5019558, -7907470, 4244127, -14714356, -26933272, 6453165, -19118182, -13289025, -6231896, -10280736),
                array(10853594, 10721687, 26480089, 5861829, -22995819, 1972175, -1866647, -10557898, -3363451, -6441124),
                array(-17002408, 5906790, 221599, -6563147, 7828208, -13248918, 24362661, -2008168, -13866408, 7421392),
            ),
            array(
                array(8139927, -6546497, 32257646, -5890546, 30375719, 1886181, -21175108, 15441252, 28826358, -4123029),
                array(6267086, 9695052, 7709135, -16603597, -32869068, -1886135, 14795160, -7840124, 13746021, -1742048),
                array(28584902, 7787108, -6732942, -15050729, 22846041, -7571236, -3181936, -363524, 4771362, -8419958),
            ),
        ),
        array(
            array(
                array(24949256, 6376279, -27466481, -8174608, -18646154, -9930606, 33543569, -12141695, 3569627, 11342593),
                array(26514989, 4740088, 27912651, 3697550, 19331575, -11472339, 6809886, 4608608, 7325975, -14801071),
                array(-11618399, -14554430, -24321212, 7655128, -1369274, 5214312, -27400540, 10258390, -17646694, -8186692),
            ),
            array(
                array(11431204, 15823007, 26570245, 14329124, 18029990, 4796082, -31446179, 15580664, 9280358, -3973687),
                array(-160783, -10326257, -22855316, -4304997, -20861367, -13621002, -32810901, -11181622, -15545091, 4387441),
                array(-20799378, 12194512, 3937617, -5805892, -27154820, 9340370, -24513992, 8548137, 20617071, -7482001),
            ),
            array(
                array(-938825, -3930586, -8714311, 16124718, 24603125, -6225393, -13775352, -11875822, 24345683, 10325460),
                array(-19855277, -1568885, -22202708, 8714034, 14007766, 6928528, 16318175, -1010689, 4766743, 3552007),
                array(-21751364, -16730916, 1351763, -803421, -4009670, 3950935, 3217514, 14481909, 10988822, -3994762),
            ),
            array(
                array(15564307, -14311570, 3101243, 5684148, 30446780, -8051356, 12677127, -6505343, -8295852, 13296005),
                array(-9442290, 6624296, -30298964, -11913677, -4670981, -2057379, 31521204, 9614054, -30000824, 12074674),
                array(4771191, -135239, 14290749, -13089852, 27992298, 14998318, -1413936, -1556716, 29832613, -16391035),
            ),
            array(
                array(7064884, -7541174, -19161962, -5067537, -18891269, -2912736, 25825242, 5293297, -27122660, 13101590),
                array(-2298563, 2439670, -7466610, 1719965, -27267541, -16328445, 32512469, -5317593, -30356070, -4190957),
                array(-30006540, 10162316, -33180176, 3981723, -16482138, -13070044, 14413974, 9515896, 19568978, 9628812),
            ),
            array(
                array(33053803, 199357, 15894591, 1583059, 27380243, -4580435, -17838894, -6106839, -6291786, 3437740),
                array(-18978877, 3884493, 19469877, 12726490, 15913552, 13614290, -22961733, 70104, 7463304, 4176122),
                array(-27124001, 10659917, 11482427, -16070381, 12771467, -6635117, -32719404, -5322751, 24216882, 5944158),
            ),
            array(
                array(8894125, 7450974, -2664149, -9765752, -28080517, -12389115, 19345746, 14680796, 11632993, 5847885),
                array(26942781, -2315317, 9129564, -4906607, 26024105, 11769399, -11518837, 6367194, -9727230, 4782140),
                array(19916461, -4828410, -22910704, -11414391, 25606324, -5972441, 33253853, 8220911, 6358847, -1873857),
            ),
            array(
                array(801428, -2081702, 16569428, 11065167, 29875704, 96627, 7908388, -4480480, -13538503, 1387155),
                array(19646058, 5720633, -11416706, 12814209, 11607948, 12749789, 14147075, 15156355, -21866831, 11835260),
                array(19299512, 1155910, 28703737, 14890794, 2925026, 7269399, 26121523, 15467869, -26560550, 5052483),
            ),
        ),
        array(
            array(
                array(-3017432, 10058206, 1980837, 3964243, 22160966, 12322533, -6431123, -12618185, 12228557, -7003677),
                array(32944382, 14922211, -22844894, 5188528, 21913450, -8719943, 4001465, 13238564, -6114803, 8653815),
                array(22865569, -4652735, 27603668, -12545395, 14348958, 8234005, 24808405, 5719875, 28483275, 2841751),
            ),
            array(
                array(-16420968, -1113305, -327719, -12107856, 21886282, -15552774, -1887966, -315658, 19932058, -12739203),
                array(-11656086, 10087521, -8864888, -5536143, -19278573, -3055912, 3999228, 13239134, -4777469, -13910208),
                array(1382174, -11694719, 17266790, 9194690, -13324356, 9720081, 20403944, 11284705, -14013818, 3093230),
            ),
            array(
                array(16650921, -11037932, -1064178, 1570629, -8329746, 7352753, -302424, 16271225, -24049421, -6691850),
                array(-21911077, -5927941, -4611316, -5560156, -31744103, -10785293, 24123614, 15193618, -21652117, -16739389),
                array(-9935934, -4289447, -25279823, 4372842, 2087473, 10399484, 31870908, 14690798, 17361620, 11864968),
            ),
            array(
                array(-11307610, 6210372, 13206574, 5806320, -29017692, -13967200, -12331205, -7486601, -25578460, -16240689),
                array(14668462, -12270235, 26039039, 15305210, 25515617, 4542480, 10453892, 6577524, 9145645, -6443880),
                array(5974874, 3053895, -9433049, -10385191, -31865124, 3225009, -7972642, 3936128, -5652273, -3050304),
            ),
            array(
                array(30625386, -4729400, -25555961, -12792866, -20484575, 7695099, 17097188, -16303496, -27999779, 1803632),
                array(-3553091, 9865099, -5228566, 4272701, -5673832, -16689700, 14911344, 12196514, -21405489, 7047412),
                array(20093277, 9920966, -11138194, -5343857, 13161587, 12044805, -32856851, 4124601, -32343828, -10257566),
            ),
            array(
                array(-20788824, 14084654, -13531713, 7842147, 19119038, -13822605, 4752377, -8714640, -21679658, 2288038),
                array(-26819236, -3283715, 29965059, 3039786, -14473765, 2540457, 29457502, 14625692, -24819617, 12570232),
                array(-1063558, -11551823, 16920318, 12494842, 1278292, -5869109, -21159943, -3498680, -11974704, 4724943),
            ),
            array(
                array(17960970, -11775534, -4140968, -9702530, -8876562, -1410617, -12907383, -8659932, -29576300, 1903856),
                array(23134274, -14279132, -10681997, -1611936, 20684485, 15770816, -12989750, 3190296, 26955097, 14109738),
                array(15308788, 5320727, -30113809, -14318877, 22902008, 7767164, 29425325, -11277562, 31960942, 11934971),
            ),
            array(
                array(-27395711, 8435796, 4109644, 12222639, -24627868, 14818669, 20638173, 4875028, 10491392, 1379718),
                array(-13159415, 9197841, 3875503, -8936108, -1383712, -5879801, 33518459, 16176658, 21432314, 12180697),
                array(-11787308, 11500838, 13787581, -13832590, -22430679, 10140205, 1465425, 12689540, -10301319, -13872883),
            ),
        ),
        array(
            array(
                array(5414091, -15386041, -21007664, 9643570, 12834970, 1186149, -2622916, -1342231, 26128231, 6032912),
                array(-26337395, -13766162, 32496025, -13653919, 17847801, -12669156, 3604025, 8316894, -25875034, -10437358),
                array(3296484, 6223048, 24680646, -12246460, -23052020, 5903205, -8862297, -4639164, 12376617, 3188849),
            ),
            array(
                array(29190488, -14659046, 27549113, -1183516, 3520066, -10697301, 32049515, -7309113, -16109234, -9852307),
                array(-14744486, -9309156, 735818, -598978, -20407687, -5057904, 25246078, -15795669, 18640741, -960977),
                array(-6928835, -16430795, 10361374, 5642961, 4910474, 12345252, -31638386, -494430, 10530747, 1053335),
            ),
            array(
                array(-29265967, -14186805, -13538216, -12117373, -19457059, -10655384, -31462369, -2948985, 24018831, 15026644),
                array(-22592535, -3145277, -2289276, 5953843, -13440189, 9425631, 25310643, 13003497, -2314791, -15145616),
                array(-27419985, -603321, -8043984, -1669117, -26092265, 13987819, -27297622, 187899, -23166419, -2531735),
            ),
            array(
                array(-21744398, -13810475, 1844840, 5021428, -10434399, -15911473, 9716667, 16266922, -5070217, 726099),
                array(29370922, -6053998, 7334071, -15342259, 9385287, 2247707, -13661962, -4839461, 30007388, -15823341),
                array(-936379, 16086691, 23751945, -543318, -1167538, -5189036, 9137109, 730663, 9835848, 4555336),
            ),
            array(
                array(-23376435, 1410446, -22253753, -12899614, 30867635, 15826977, 17693930, 544696, -11985298, 12422646),
                array(31117226, -12215734, -13502838, 6561947, -9876867, -12757670, -5118685, -4096706, 29120153, 13924425),
                array(-17400879, -14233209, 19675799, -2734756, -11006962, -5858820, -9383939, -11317700, 7240931, -237388),
            ),
            array(
                array(-31361739, -11346780, -15007447, -5856218, -22453340, -12152771, 1222336, 4389483, 3293637, -15551743),
                array(-16684801, -14444245, 11038544, 11054958, -13801175, -3338533, -24319580, 7733547, 12796905, -6335822),
                array(-8759414, -10817836, -25418864, 10783769, -30615557, -9746811, -28253339, 3647836, 3222231, -11160462),
            ),
            array(
                array(18606113, 1693100, -25448386, -15170272, 4112353, 10045021, 23603893, -2048234, -7550776, 2484985),
                array(9255317, -3131197, -12156162, -1004256, 13098013, -9214866, 16377220, -2102812, -19802075, -3034702),
                array(-22729289, 7496160, -5742199, 11329249, 19991973, -3347502, -31718148, 9936966, -30097688, -10618797),
            ),
            array(
                array(21878590, -5001297, 4338336, 13643897, -3036865, 13160960, 19708896, 5415497, -7360503, -4109293),
                array(27736861, 10103576, 12500508, 8502413, -3413016, -9633558, 10436918, -1550276, -23659143, -8132100),
                array(19492550, -12104365, -29681976, -852630, -3208171, 12403437, 30066266, 8367329, 13243957, 8709688),
            ),
        ),
        array(
            array(
                array(12015105, 2801261, 28198131, 10151021, 24818120, -4743133, -11194191, -5645734, 5150968, 7274186),
                array(2831366, -12492146, 1478975, 6122054, 23825128, -12733586, 31097299, 6083058, 31021603, -9793610),
                array(-2529932, -2229646, 445613, 10720828, -13849527, -11505937, -23507731, 16354465, 15067285, -14147707),
            ),
            array(
                array(7840942, 14037873, -33364863, 15934016, -728213, -3642706, 21403988, 1057586, -19379462, -12403220),
                array(915865, -16469274, 15608285, -8789130, -24357026, 6060030, -17371319, 8410997, -7220461, 16527025),
                array(32922597, -556987, 20336074, -16184568, 10903705, -5384487, 16957574, 52992, 23834301, 6588044),
            ),
            array(
                array(32752030, 11232950, 3381995, -8714866, 22652988, -10744103, 17159699, 16689107, -20314580, -1305992),
                array(-4689649, 9166776, -25710296, -10847306, 11576752, 12733943, 7924251, -2752281, 1976123, -7249027),
                array(21251222, 16309901, -2983015, -6783122, 30810597, 12967303, 156041, -3371252, 12331345, -8237197),
            ),
            array(
                array(8651614, -4477032, -16085636, -4996994, 13002507, 2950805, 29054427, -5106970, 10008136, -4667901),
                array(31486080, 15114593, -14261250, 12951354, 14369431, -7387845, 16347321, -13662089, 8684155, -10532952),
                array(19443825, 11385320, 24468943, -9659068, -23919258, 2187569, -26263207, -6086921, 31316348, 14219878),
            ),
            array(
                array(-28594490, 1193785, 32245219, 11392485, 31092169, 15722801, 27146014, 6992409, 29126555, 9207390),
                array(32382935, 1110093, 18477781, 11028262, -27411763, -7548111, -4980517, 10843782, -7957600, -14435730),
                array(2814918, 7836403, 27519878, -7868156, -20894015, -11553689, -21494559, 8550130, 28346258, 1994730),
            ),
            array(
                array(-19578299, 8085545, -14000519, -3948622, 2785838, -16231307, -19516951, 7174894, 22628102, 8115180),
                array(-30405132, 955511, -11133838, -15078069, -32447087, -13278079, -25651578, 3317160, -9943017, 930272),
                array(-15303681, -6833769, 28856490, 1357446, 23421993, 1057177, 24091212, -1388970, -22765376, -10650715),
            ),
            array(
                array(-22751231, -5303997, -12907607, -12768866, -15811511, -7797053, -14839018, -16554220, -1867018, 8398970),
                array(-31969310, 2106403, -4736360, 1362501, 12813763, 16200670, 22981545, -6291273, 18009408, -15772772),
                array(-17220923, -9545221, -27784654, 14166835, 29815394, 7444469, 29551787, -3727419, 19288549, 1325865),
            ),
            array(
                array(15100157, -15835752, -23923978, -1005098, -26450192, 15509408, 12376730, -3479146, 33166107, -8042750),
                array(20909231, 13023121, -9209752, 16251778, -5778415, -8094914, 12412151, 10018715, 2213263, -13878373),
                array(32529814, -11074689, 30361439, -16689753, -9135940, 1513226, 22922121, 6382134, -5766928, 8371348),
            ),
        ),
        array(
            array(
                array(9923462, 11271500, 12616794, 3544722, -29998368, -1721626, 12891687, -8193132, -26442943, 10486144),
                array(-22597207, -7012665, 8587003, -8257861, 4084309, -12970062, 361726, 2610596, -23921530, -11455195),
                array(5408411, -1136691, -4969122, 10561668, 24145918, 14240566, 31319731, -4235541, 19985175, -3436086),
            ),
            array(
                array(-13994457, 16616821, 14549246, 3341099, 32155958, 13648976, -17577068, 8849297, 65030, 8370684),
                array(-8320926, -12049626, 31204563, 5839400, -20627288, -1057277, -19442942, 6922164, 12743482, -9800518),
                array(-2361371, 12678785, 28815050, 4759974, -23893047, 4884717, 23783145, 11038569, 18800704, 255233),
            ),
            array(
                array(-5269658, -1773886, 13957886, 7990715, 23132995, 728773, 13393847, 9066957, 19258688, -14753793),
                array(-2936654, -10827535, -10432089, 14516793, -3640786, 4372541, -31934921, 2209390, -1524053, 2055794),
                array(580882, 16705327, 5468415, -2683018, -30926419, -14696000, -7203346, -8994389, -30021019, 7394435),
            ),
            array(
                array(23838809, 1822728, -15738443, 15242727, 8318092, -3733104, -21672180, -3492205, -4821741, 14799921),
                array(13345610, 9759151, 3371034, -16137791, 16353039, 8577942, 31129804, 13496856, -9056018, 7402518),
                array(2286874, -4435931, -20042458, -2008336, -13696227, 5038122, 11006906, -15760352, 8205061, 1607563),
            ),
            array(
                array(14414086, -8002132, 3331830, -3208217, 22249151, -5594188, 18364661, -2906958, 30019587, -9029278),
                array(-27688051, 1585953, -10775053, 931069, -29120221, -11002319, -14410829, 12029093, 9944378, 8024),
                array(4368715, -3709630, 29874200, -15022983, -20230386, -11410704, -16114594, -999085, -8142388, 5640030),
            ),
            array(
                array(10299610, 13746483, 11661824, 16234854, 7630238, 5998374, 9809887, -16694564, 15219798, -14327783),
                array(27425505, -5719081, 3055006, 10660664, 23458024, 595578, -15398605, -1173195, -18342183, 9742717),
                array(6744077, 2427284, 26042789, 2720740, -847906, 1118974, 32324614, 7406442, 12420155, 1994844),
            ),
            array(
                array(14012521, -5024720, -18384453, -9578469, -26485342, -3936439, -13033478, -10909803, 24319929, -6446333),
                array(16412690, -4507367, 10772641, 15929391, -17068788, -4658621, 10555945, -10484049, -30102368, -4739048),
                array(22397382, -7767684, -9293161, -12792868, 17166287, -9755136, -27333065, 6199366, 21880021, -12250760),
            ),
            array(
                array(-4283307, 5368523, -31117018, 8163389, -30323063, 3209128, 16557151, 8890729, 8840445, 4957760),
                array(-15447727, 709327, -6919446, -10870178, -29777922, 6522332, -21720181, 12130072, -14796503, 5005757),
                array(-2114751, -14308128, 23019042, 15765735, -25269683, 6002752, 10183197, -13239326, -16395286, -2176112),
            ),
        ),
        array(
            array(
                array(-19025756, 1632005, 13466291, -7995100, -23640451, 16573537, -32013908, -3057104, 22208662, 2000468),
                array(3065073, -1412761, -25598674, -361432, -17683065, -5703415, -8164212, 11248527, -3691214, -7414184),
                array(10379208, -6045554, 8877319, 1473647, -29291284, -12507580, 16690915, 2553332, -3132688, 16400289),
            ),
            array(
                array(15716668, 1254266, -18472690, 7446274, -8448918, 6344164, -22097271, -7285580, 26894937, 9132066),
                array(24158887, 12938817, 11085297, -8177598, -28063478, -4457083, -30576463, 64452, -6817084, -2692882),
                array(13488534, 7794716, 22236231, 5989356, 25426474, -12578208, 2350710, -3418511, -4688006, 2364226),
            ),
            array(
                array(16335052, 9132434, 25640582, 6678888, 1725628, 8517937, -11807024, -11697457, 15445875, -7798101),
                array(29004207, -7867081, 28661402, -640412, -12794003, -7943086, 31863255, -4135540, -278050, -15759279),
                array(-6122061, -14866665, -28614905, 14569919, -10857999, -3591829, 10343412, -6976290, -29828287, -10815811),
            ),
            array(
                array(27081650, 3463984, 14099042, -4517604, 1616303, -6205604, 29542636, 15372179, 17293797, 960709),
                array(20263915, 11434237, -5765435, 11236810, 13505955, -10857102, -16111345, 6493122, -19384511, 7639714),
                array(-2830798, -14839232, 25403038, -8215196, -8317012, -16173699, 18006287, -16043750, 29994677, -15808121),
            ),
            array(
                array(9769828, 5202651, -24157398, -13631392, -28051003, -11561624, -24613141, -13860782, -31184575, 709464),
                array(12286395, 13076066, -21775189, -1176622, -25003198, 4057652, -32018128, -8890874, 16102007, 13205847),
                array(13733362, 5599946, 10557076, 3195751, -5557991, 8536970, -25540170, 8525972, 10151379, 10394400),
            ),
            array(
                array(4024660, -16137551, 22436262, 12276534, -9099015, -2686099, 19698229, 11743039, -33302334, 8934414),
                array(-15879800, -4525240, -8580747, -2934061, 14634845, -698278, -9449077, 3137094, -11536886, 11721158),
                array(17555939, -5013938, 8268606, 2331751, -22738815, 9761013, 9319229, 8835153, -9205489, -1280045),
            ),
            array(
                array(-461409, -7830014, 20614118, 16688288, -7514766, -4807119, 22300304, 505429, 6108462, -6183415),
                array(-5070281, 12367917, -30663534, 3234473, 32617080, -8422642, 29880583, -13483331, -26898490, -7867459),
                array(-31975283, 5726539, 26934134, 10237677, -3173717, -605053, 24199304, 3795095, 7592688, -14992079),
            ),
            array(
                array(21594432, -14964228, 17466408, -4077222, 32537084, 2739898, 6407723, 12018833, -28256052, 4298412),
                array(-20650503, -11961496, -27236275, 570498, 3767144, -1717540, 13891942, -1569194, 13717174, 10805743),
                array(-14676630, -15644296, 15287174, 11927123, 24177847, -8175568, -796431, 14860609, -26938930, -5863836),
            ),
        ),
        array(
            array(
                array(12962541, 5311799, -10060768, 11658280, 18855286, -7954201, 13286263, -12808704, -4381056, 9882022),
                array(18512079, 11319350, -20123124, 15090309, 18818594, 5271736, -22727904, 3666879, -23967430, -3299429),
                array(-6789020, -3146043, 16192429, 13241070, 15898607, -14206114, -10084880, -6661110, -2403099, 5276065),
            ),
            array(
                array(30169808, -5317648, 26306206, -11750859, 27814964, 7069267, 7152851, 3684982, 1449224, 13082861),
                array(10342826, 3098505, 2119311, 193222, 25702612, 12233820, 23697382, 15056736, -21016438, -8202000),
                array(-33150110, 3261608, 22745853, 7948688, 19370557, -15177665, -26171976, 6482814, -10300080, -11060101),
            ),
            array(
                array(32869458, -5408545, 25609743, 15678670, -10687769, -15471071, 26112421, 2521008, -22664288, 6904815),
                array(29506923, 4457497, 3377935, -9796444, -30510046, 12935080, 1561737, 3841096, -29003639, -6657642),
                array(10340844, -6630377, -18656632, -2278430, 12621151, -13339055, 30878497, -11824370, -25584551, 5181966),
            ),
            array(
                array(25940115, -12658025, 17324188, -10307374, -8671468, 15029094, 24396252, -16450922, -2322852, -12388574),
                array(-21765684, 9916823, -1300409, 4079498, -1028346, 11909559, 1782390, 12641087, 20603771, -6561742),
                array(-18882287, -11673380, 24849422, 11501709, 13161720, -4768874, 1925523, 11914390, 4662781, 7820689),
            ),
            array(
                array(12241050, -425982, 8132691, 9393934, 32846760, -1599620, 29749456, 12172924, 16136752, 15264020),
                array(-10349955, -14680563, -8211979, 2330220, -17662549, -14545780, 10658213, 6671822, 19012087, 3772772),
                array(3753511, -3421066, 10617074, 2028709, 14841030, -6721664, 28718732, -15762884, 20527771, 12988982),
            ),
            array(
                array(-14822485, -5797269, -3707987, 12689773, -898983, -10914866, -24183046, -10564943, 3299665, -12424953),
                array(-16777703, -15253301, -9642417, 4978983, 3308785, 8755439, 6943197, 6461331, -25583147, 8991218),
                array(-17226263, 1816362, -1673288, -6086439, 31783888, -8175991, -32948145, 7417950, -30242287, 1507265),
            ),
            array(
                array(29692663, 6829891, -10498800, 4334896, 20945975, -11906496, -28887608, 8209391, 14606362, -10647073),
                array(-3481570, 8707081, 32188102, 5672294, 22096700, 1711240, -33020695, 9761487, 4170404, -2085325),
                array(-11587470, 14855945, -4127778, -1531857, -26649089, 15084046, 22186522, 16002000, -14276837, -8400798),
            ),
            array(
                array(-4811456, 13761029, -31703877, -2483919, -3312471, 7869047, -7113572, -9620092, 13240845, 10965870),
                array(-7742563, -8256762, -14768334, -13656260, -23232383, 12387166, 4498947, 14147411, 29514390, 4302863),
                array(-13413405, -12407859, 20757302, -13801832, 14785143, 8976368, -5061276, -2144373, 17846988, -13971927),
            ),
        ),
        array(
            array(
                array(-2244452, -754728, -4597030, -1066309, -6247172, 1455299, -21647728, -9214789, -5222701, 12650267),
                array(-9906797, -16070310, 21134160, 12198166, -27064575, 708126, 387813, 13770293, -19134326, 10958663),
                array(22470984, 12369526, 23446014, -5441109, -21520802, -9698723, -11772496, -11574455, -25083830, 4271862),
            ),
            array(
                array(-25169565, -10053642, -19909332, 15361595, -5984358, 2159192, 75375, -4278529, -32526221, 8469673),
                array(15854970, 4148314, -8893890, 7259002, 11666551, 13824734, -30531198, 2697372, 24154791, -9460943),
                array(15446137, -15806644, 29759747, 14019369, 30811221, -9610191, -31582008, 12840104, 24913809, 9815020),
            ),
            array(
                array(-4709286, -5614269, -31841498, -12288893, -14443537, 10799414, -9103676, 13438769, 18735128, 9466238),
                array(11933045, 9281483, 5081055, -5183824, -2628162, -4905629, -7727821, -10896103, -22728655, 16199064),
                array(14576810, 379472, -26786533, -8317236, -29426508, -10812974, -102766, 1876699, 30801119, 2164795),
            ),
            array(
                array(15995086, 3199873, 13672555, 13712240, -19378835, -4647646, -13081610, -15496269, -13492807, 1268052),
                array(-10290614, -3659039, -3286592, 10948818, 23037027, 3794475, -3470338, -12600221, -17055369, 3565904),
                array(29210088, -9419337, -5919792, -4952785, 10834811, -13327726, -16512102, -10820713, -27162222, -14030531),
            ),
            array(
                array(-13161890, 15508588, 16663704, -8156150, -28349942, 9019123, -29183421, -3769423, 2244111, -14001979),
                array(-5152875, -3800936, -9306475, -6071583, 16243069, 14684434, -25673088, -16180800, 13491506, 4641841),
                array(10813417, 643330, -19188515, -728916, 30292062, -16600078, 27548447, -7721242, 14476989, -12767431),
            ),
            array(
                array(10292079, 9984945, 6481436, 8279905, -7251514, 7032743, 27282937, -1644259, -27912810, 12651324),
                array(-31185513, -813383, 22271204, 11835308, 10201545, 15351028, 17099662, 3988035, 21721536, -3148940),
                array(10202177, -6545839, -31373232, -9574638, -32150642, -8119683, -12906320, 3852694, 13216206, 14842320),
            ),
            array(
                array(-15815640, -10601066, -6538952, -7258995, -6984659, -6581778, -31500847, 13765824, -27434397, 9900184),
                array(14465505, -13833331, -32133984, -14738873, -27443187, 12990492, 33046193, 15796406, -7051866, -8040114),
                array(30924417, -8279620, 6359016, -12816335, 16508377, 9071735, -25488601, 15413635, 9524356, -7018878),
            ),
            array(
                array(12274201, -13175547, 32627641, -1785326, 6736625, 13267305, 5237659, -5109483, 15663516, 4035784),
                array(-2951309, 8903985, 17349946, 601635, -16432815, -4612556, -13732739, -15889334, -22258478, 4659091),
                array(-16916263, -4952973, -30393711, -15158821, 20774812, 15897498, 5736189, 15026997, -2178256, -13455585),
            ),
        ),
        array(
            array(
                array(-8858980, -2219056, 28571666, -10155518, -474467, -10105698, -3801496, 278095, 23440562, -290208),
                array(10226241, -5928702, 15139956, 120818, -14867693, 5218603, 32937275, 11551483, -16571960, -7442864),
                array(17932739, -12437276, -24039557, 10749060, 11316803, 7535897, 22503767, 5561594, -3646624, 3898661),
            ),
            array(
                array(7749907, -969567, -16339731, -16464, -25018111, 15122143, -1573531, 7152530, 21831162, 1245233),
                array(26958459, -14658026, 4314586, 8346991, -5677764, 11960072, -32589295, -620035, -30402091, -16716212),
                array(-12165896, 9166947, 33491384, 13673479, 29787085, 13096535, 6280834, 14587357, -22338025, 13987525),
            ),
            array(
                array(-24349909, 7778775, 21116000, 15572597, -4833266, -5357778, -4300898, -5124639, -7469781, -2858068),
                array(9681908, -6737123, -31951644, 13591838, -6883821, 386950, 31622781, 6439245, -14581012, 4091397),
                array(-8426427, 1470727, -28109679, -1596990, 3978627, -5123623, -19622683, 12092163, 29077877, -14741988),
            ),
            array(
                array(5269168, -6859726, -13230211, -8020715, 25932563, 1763552, -5606110, -5505881, -20017847, 2357889),
                array(32264008, -15407652, -5387735, -1160093, -2091322, -3946900, 23104804, -12869908, 5727338, 189038),
                array(14609123, -8954470, -6000566, -16622781, -14577387, -7743898, -26745169, 10942115, -25888931, -14884697),
            ),
            array(
                array(20513500, 5557931, -15604613, 7829531, 26413943, -2019404, -21378968, 7471781, 13913677, -5137875),
                array(-25574376, 11967826, 29233242, 12948236, -6754465, 4713227, -8940970, 14059180, 12878652, 8511905),
                array(-25656801, 3393631, -2955415, -7075526, -2250709, 9366908, -30223418, 6812974, 5568676, -3127656),
            ),
            array(
                array(11630004, 12144454, 2116339, 13606037, 27378885, 15676917, -17408753, -13504373, -14395196, 8070818),
                array(27117696, -10007378, -31282771, -5570088, 1127282, 12772488, -29845906, 10483306, -11552749, -1028714),
                array(10637467, -5688064, 5674781, 1072708, -26343588, -6982302, -1683975, 9177853, -27493162, 15431203),
            ),
            array(
                array(20525145, 10892566, -12742472, 12779443, -29493034, 16150075, -28240519, 14943142, -15056790, -7935931),
                array(-30024462, 5626926, -551567, -9981087, 753598, 11981191, 25244767, -3239766, -3356550, 9594024),
                array(-23752644, 2636870, -5163910, -10103818, 585134, 7877383, 11345683, -6492290, 13352335, -10977084),
            ),
            array(
                array(-1931799, -5407458, 3304649, -12884869, 17015806, -4877091, -29783850, -7752482, -13215537, -319204),
                array(20239939, 6607058, 6203985, 3483793, -18386976, -779229, -20723742, 15077870, -22750759, 14523817),
                array(27406042, -6041657, 27423596, -4497394, 4996214, 10002360, -28842031, -4545494, -30172742, -4805667),
            ),
        ),
        array(
            array(
                array(11374242, 12660715, 17861383, -12540833, 10935568, 1099227, -13886076, -9091740, -27727044, 11358504),
                array(-12730809, 10311867, 1510375, 10778093, -2119455, -9145702, 32676003, 11149336, -26123651, 4985768),
                array(-19096303, 341147, -6197485, -239033, 15756973, -8796662, -983043, 13794114, -19414307, -15621255),
            ),
            array(
                array(6490081, 11940286, 25495923, -7726360, 8668373, -8751316, 3367603, 6970005, -1691065, -9004790),
                array(1656497, 13457317, 15370807, 6364910, 13605745, 8362338, -19174622, -5475723, -16796596, -5031438),
                array(-22273315, -13524424, -64685, -4334223, -18605636, -10921968, -20571065, -7007978, -99853, -10237333),
            ),
            array(
                array(17747465, 10039260, 19368299, -4050591, -20630635, -16041286, 31992683, -15857976, -29260363, -5511971),
                array(31932027, -4986141, -19612382, 16366580, 22023614, 88450, 11371999, -3744247, 4882242, -10626905),
                array(29796507, 37186, 19818052, 10115756, -11829032, 3352736, 18551198, 3272828, -5190932, -4162409),
            ),
            array(
                array(12501286, 4044383, -8612957, -13392385, -32430052, 5136599, -19230378, -3529697, 330070, -3659409),
                array(6384877, 2899513, 17807477, 7663917, -2358888, 12363165, 25366522, -8573892, -271295, 12071499),
                array(-8365515, -4042521, 25133448, -4517355, -6211027, 2265927, -32769618, 1936675, -5159697, 3829363),
            ),
            array(
                array(28425966, -5835433, -577090, -4697198, -14217555, 6870930, 7921550, -6567787, 26333140, 14267664),
                array(-11067219, 11871231, 27385719, -10559544, -4585914, -11189312, 10004786, -8709488, -21761224, 8930324),
                array(-21197785, -16396035, 25654216, -1725397, 12282012, 11008919, 1541940, 4757911, -26491501, -16408940),
            ),
            array(
                array(13537262, -7759490, -20604840, 10961927, -5922820, -13218065, -13156584, 6217254, -15943699, 13814990),
                array(-17422573, 15157790, 18705543, 29619, 24409717, -260476, 27361681, 9257833, -1956526, -1776914),
                array(-25045300, -10191966, 15366585, 15166509, -13105086, 8423556, -29171540, 12361135, -18685978, 4578290),
            ),
            array(
                array(24579768, 3711570, 1342322, -11180126, -27005135, 14124956, -22544529, 14074919, 21964432, 8235257),
                array(-6528613, -2411497, 9442966, -5925588, 12025640, -1487420, -2981514, -1669206, 13006806, 2355433),
                array(-16304899, -13605259, -6632427, -5142349, 16974359, -10911083, 27202044, 1719366, 1141648, -12796236),
            ),
            array(
                array(-12863944, -13219986, -8318266, -11018091, -6810145, -4843894, 13475066, -3133972, 32674895, 13715045),
                array(11423335, -5468059, 32344216, 8962751, 24989809, 9241752, -13265253, 16086212, -28740881, -15642093),
                array(-1409668, 12530728, -6368726, 10847387, 19531186, -14132160, -11709148, 7791794, -27245943, 4383347),
            ),
        ),
        array(
            array(
                array(-28970898, 5271447, -1266009, -9736989, -12455236, 16732599, -4862407, -4906449, 27193557, 6245191),
                array(-15193956, 5362278, -1783893, 2695834, 4960227, 12840725, 23061898, 3260492, 22510453, 8577507),
                array(-12632451, 11257346, -32692994, 13548177, -721004, 10879011, 31168030, 13952092, -29571492, -3635906),
            ),
            array(
                array(3877321, -9572739, 32416692, 5405324, -11004407, -13656635, 3759769, 11935320, 5611860, 8164018),
                array(-16275802, 14667797, 15906460, 12155291, -22111149, -9039718, 32003002, -8832289, 5773085, -8422109),
                array(-23788118, -8254300, 1950875, 8937633, 18686727, 16459170, -905725, 12376320, 31632953, 190926),
            ),
            array(
                array(-24593607, -16138885, -8423991, 13378746, 14162407, 6901328, -8288749, 4508564, -25341555, -3627528),
                array(8884438, -5884009, 6023974, 10104341, -6881569, -4941533, 18722941, -14786005, -1672488, 827625),
                array(-32720583, -16289296, -32503547, 7101210, 13354605, 2659080, -1800575, -14108036, -24878478, 1541286),
            ),
            array(
                array(2901347, -1117687, 3880376, -10059388, -17620940, -3612781, -21802117, -3567481, 20456845, -1885033),
                array(27019610, 12299467, -13658288, -1603234, -12861660, -4861471, -19540150, -5016058, 29439641, 15138866),
                array(21536104, -6626420, -32447818, -10690208, -22408077, 5175814, -5420040, -16361163, 7779328, 109896),
            ),
            array(
                array(30279744, 14648750, -8044871, 6425558, 13639621, -743509, 28698390, 12180118, 23177719, -554075),
                array(26572847, 3405927, -31701700, 12890905, -19265668, 5335866, -6493768, 2378492, 4439158, -13279347),
                array(-22716706, 3489070, -9225266, -332753, 18875722, -1140095, 14819434, -12731527, -17717757, -5461437),
            ),
            array(
                array(-5056483, 16566551, 15953661, 3767752, -10436499, 15627060, -820954, 2177225, 8550082, -15114165),
                array(-18473302, 16596775, -381660, 15663611, 22860960, 15585581, -27844109, -3582739, -23260460, -8428588),
                array(-32480551, 15707275, -8205912, -5652081, 29464558, 2713815, -22725137, 15860482, -21902570, 1494193),
            ),
            array(
                array(-19562091, -14087393, -25583872, -9299552, 13127842, 759709, 21923482, 16529112, 8742704, 12967017),
                array(-28464899, 1553205, 32536856, -10473729, -24691605, -406174, -8914625, -2933896, -29903758, 15553883),
                array(21877909, 3230008, 9881174, 10539357, -4797115, 2841332, 11543572, 14513274, 19375923, -12647961),
            ),
            array(
                array(8832269, -14495485, 13253511, 5137575, 5037871, 4078777, 24880818, -6222716, 2862653, 9455043),
                array(29306751, 5123106, 20245049, -14149889, 9592566, 8447059, -2077124, -2990080, 15511449, 4789663),
                array(-20679756, 7004547, 8824831, -9434977, -4045704, -3750736, -5754762, 108893, 23513200, 16652362),
            ),
        ),
        array(
            array(
                array(-33256173, 4144782, -4476029, -6579123, 10770039, -7155542, -6650416, -12936300, -18319198, 10212860),
                array(2756081, 8598110, 7383731, -6859892, 22312759, -1105012, 21179801, 2600940, -9988298, -12506466),
                array(-24645692, 13317462, -30449259, -15653928, 21365574, -10869657, 11344424, 864440, -2499677, -16710063),
            ),
            array(
                array(-26432803, 6148329, -17184412, -14474154, 18782929, -275997, -22561534, 211300, 2719757, 4940997),
                array(-1323882, 3911313, -6948744, 14759765, -30027150, 7851207, 21690126, 8518463, 26699843, 5276295),
                array(-13149873, -6429067, 9396249, 365013, 24703301, -10488939, 1321586, 149635, -15452774, 7159369),
            ),
            array(
                array(9987780, -3404759, 17507962, 9505530, 9731535, -2165514, 22356009, 8312176, 22477218, -8403385),
                array(18155857, -16504990, 19744716, 9006923, 15154154, -10538976, 24256460, -4864995, -22548173, 9334109),
                array(2986088, -4911893, 10776628, -3473844, 10620590, -7083203, -21413845, 14253545, -22587149, 536906),
            ),
            array(
                array(4377756, 8115836, 24567078, 15495314, 11625074, 13064599, 7390551, 10589625, 10838060, -15420424),
                array(-19342404, 867880, 9277171, -3218459, -14431572, -1986443, 19295826, -15796950, 6378260, 699185),
                array(7895026, 4057113, -7081772, -13077756, -17886831, -323126, -716039, 15693155, -5045064, -13373962),
            ),
            array(
                array(-7737563, -5869402, -14566319, -7406919, 11385654, 13201616, 31730678, -10962840, -3918636, -9669325),
                array(10188286, -15770834, -7336361, 13427543, 22223443, 14896287, 30743455, 7116568, -21786507, 5427593),
                array(696102, 13206899, 27047647, -10632082, 15285305, -9853179, 10798490, -4578720, 19236243, 12477404),
            ),
            array(
                array(-11229439, 11243796, -17054270, -8040865, -788228, -8167967, -3897669, 11180504, -23169516, 7733644),
                array(17800790, -14036179, -27000429, -11766671, 23887827, 3149671, 23466177, -10538171, 10322027, 15313801),
                array(26246234, 11968874, 32263343, -5468728, 6830755, -13323031, -15794704, -101982, -24449242, 10890804),
            ),
            array(
                array(-31365647, 10271363, -12660625, -6267268, 16690207, -13062544, -14982212, 16484931, 25180797, -5334884),
                array(-586574, 10376444, -32586414, -11286356, 19801893, 10997610, 2276632, 9482883, 316878, 13820577),
                array(-9882808, -4510367, -2115506, 16457136, -11100081, 11674996, 30756178, -7515054, 30696930, -3712849),
            ),
            array(
                array(32988917, -9603412, 12499366, 7910787, -10617257, -11931514, -7342816, -9985397, -32349517, 7392473),
                array(-8855661, 15927861, 9866406, -3649411, -2396914, -16655781, -30409476, -9134995, 25112947, -2926644),
                array(-2504044, -436966, 25621774, -5678772, 15085042, -5479877, -24884878, -13526194, 5537438, -13914319),
            ),
        ),
        array(
            array(
                array(-11225584, 2320285, -9584280, 10149187, -33444663, 5808648, -14876251, -1729667, 31234590, 6090599),
                array(-9633316, 116426, 26083934, 2897444, -6364437, -2688086, 609721, 15878753, -6970405, -9034768),
                array(-27757857, 247744, -15194774, -9002551, 23288161, -10011936, -23869595, 6503646, 20650474, 1804084),
            ),
            array(
                array(-27589786, 15456424, 8972517, 8469608, 15640622, 4439847, 3121995, -10329713, 27842616, -202328),
                array(-15306973, 2839644, 22530074, 10026331, 4602058, 5048462, 28248656, 5031932, -11375082, 12714369),
                array(20807691, -7270825, 29286141, 11421711, -27876523, -13868230, -21227475, 1035546, -19733229, 12796920),
            ),
            array(
                array(12076899, -14301286, -8785001, -11848922, -25012791, 16400684, -17591495, -12899438, 3480665, -15182815),
                array(-32361549, 5457597, 28548107, 7833186, 7303070, -11953545, -24363064, -15921875, -33374054, 2771025),
                array(-21389266, 421932, 26597266, 6860826, 22486084, -6737172, -17137485, -4210226, -24552282, 15673397),
            ),
            array(
                array(-20184622, 2338216, 19788685, -9620956, -4001265, -8740893, -20271184, 4733254, 3727144, -12934448),
                array(6120119, 814863, -11794402, -622716, 6812205, -15747771, 2019594, 7975683, 31123697, -10958981),
                array(30069250, -11435332, 30434654, 2958439, 18399564, -976289, 12296869, 9204260, -16432438, 9648165),
            ),
            array(
                array(32705432, -1550977, 30705658, 7451065, -11805606, 9631813, 3305266, 5248604, -26008332, -11377501),
                array(17219865, 2375039, -31570947, -5575615, -19459679, 9219903, 294711, 15298639, 2662509, -16297073),
                array(-1172927, -7558695, -4366770, -4287744, -21346413, -8434326, 32087529, -1222777, 32247248, -14389861),
            ),
            array(
                array(14312628, 1221556, 17395390, -8700143, -4945741, -8684635, -28197744, -9637817, -16027623, -13378845),
                array(-1428825, -9678990, -9235681, 6549687, -7383069, -468664, 23046502, 9803137, 17597934, 2346211),
                array(18510800, 15337574, 26171504, 981392, -22241552, 7827556, -23491134, -11323352, 3059833, -11782870),
            ),
            array(
                array(10141598, 6082907, 17829293, -1947643, 9830092, 13613136, -25556636, -5544586, -33502212, 3592096),
                array(33114168, -15889352, -26525686, -13343397, 33076705, 8716171, 1151462, 1521897, -982665, -6837803),
                array(-32939165, -4255815, 23947181, -324178, -33072974, -12305637, -16637686, 3891704, 26353178, 693168),
            ),
            array(
                array(30374239, 1595580, -16884039, 13186931, 4600344, 406904, 9585294, -400668, 31375464, 14369965),
                array(-14370654, -7772529, 1510301, 6434173, -18784789, -6262728, 32732230, -13108839, 17901441, 16011505),
                array(18171223, -11934626, -12500402, 15197122, -11038147, -15230035, -19172240, -16046376, 8764035, 12309598),
            ),
        ),
        array(
            array(
                array(5975908, -5243188, -19459362, -9681747, -11541277, 14015782, -23665757, 1228319, 17544096, -10593782),
                array(5811932, -1715293, 3442887, -2269310, -18367348, -8359541, -18044043, -15410127, -5565381, 12348900),
                array(-31399660, 11407555, 25755363, 6891399, -3256938, 14872274, -24849353, 8141295, -10632534, -585479),
            ),
            array(
                array(-12675304, 694026, -5076145, 13300344, 14015258, -14451394, -9698672, -11329050, 30944593, 1130208),
                array(8247766, -6710942, -26562381, -7709309, -14401939, -14648910, 4652152, 2488540, 23550156, -271232),
                array(17294316, -3788438, 7026748, 15626851, 22990044, 113481, 2267737, -5908146, -408818, -137719),
            ),
            array(
                array(16091085, -16253926, 18599252, 7340678, 2137637, -1221657, -3364161, 14550936, 3260525, -7166271),
                array(-4910104, -13332887, 18550887, 10864893, -16459325, -7291596, -23028869, -13204905, -12748722, 2701326),
                array(-8574695, 16099415, 4629974, -16340524, -20786213, -6005432, -10018363, 9276971, 11329923, 1862132),
            ),
            array(
                array(14763076, -15903608, -30918270, 3689867, 3511892, 10313526, -21951088, 12219231, -9037963, -940300),
                array(8894987, -3446094, 6150753, 3013931, 301220, 15693451, -31981216, -2909717, -15438168, 11595570),
                array(15214962, 3537601, -26238722, -14058872, 4418657, -15230761, 13947276, 10730794, -13489462, -4363670),
            ),
            array(
                array(-2538306, 7682793, 32759013, 263109, -29984731, -7955452, -22332124, -10188635, 977108, 699994),
                array(-12466472, 4195084, -9211532, 550904, -15565337, 12917920, 19118110, -439841, -30534533, -14337913),
                array(31788461, -14507657, 4799989, 7372237, 8808585, -14747943, 9408237, -10051775, 12493932, -5409317),
            ),
            array(
                array(-25680606, 5260744, -19235809, -6284470, -3695942, 16566087, 27218280, 2607121, 29375955, 6024730),
                array(842132, -2794693, -4763381, -8722815, 26332018, -12405641, 11831880, 6985184, -9940361, 2854096),
                array(-4847262, -7969331, 2516242, -5847713, 9695691, -7221186, 16512645, 960770, 12121869, 16648078),
            ),
            array(
                array(-15218652, 14667096, -13336229, 2013717, 30598287, -464137, -31504922, -7882064, 20237806, 2838411),
                array(-19288047, 4453152, 15298546, -16178388, 22115043, -15972604, 12544294, -13470457, 1068881, -12499905),
                array(-9558883, -16518835, 33238498, 13506958, 30505848, -1114596, -8486907, -2630053, 12521378, 4845654),
            ),
            array(
                array(-28198521, 10744108, -2958380, 10199664, 7759311, -13088600, 3409348, -873400, -6482306, -12885870),
                array(-23561822, 6230156, -20382013, 10655314, -24040585, -11621172, 10477734, -1240216, -3113227, 13974498),
                array(12966261, 15550616, -32038948, -1615346, 21025980, -629444, 5642325, 7188737, 18895762, 12629579),
            ),
        ),
        array(
            array(
                array(14741879, -14946887, 22177208, -11721237, 1279741, 8058600, 11758140, 789443, 32195181, 3895677),
                array(10758205, 15755439, -4509950, 9243698, -4879422, 6879879, -2204575, -3566119, -8982069, 4429647),
                array(-2453894, 15725973, -20436342, -10410672, -5803908, -11040220, -7135870, -11642895, 18047436, -15281743),
            ),
            array(
                array(-25173001, -11307165, 29759956, 11776784, -22262383, -15820455, 10993114, -12850837, -17620701, -9408468),
                array(21987233, 700364, -24505048, 14972008, -7774265, -5718395, 32155026, 2581431, -29958985, 8773375),
                array(-25568350, 454463, -13211935, 16126715, 25240068, 8594567, 20656846, 12017935, -7874389, -13920155),
            ),
            array(
                array(6028182, 6263078, -31011806, -11301710, -818919, 2461772, -31841174, -5468042, -1721788, -2776725),
                array(-12278994, 16624277, 987579, -5922598, 32908203, 1248608, 7719845, -4166698, 28408820, 6816612),
                array(-10358094, -8237829, 19549651, -12169222, 22082623, 16147817, 20613181, 13982702, -10339570, 5067943),
            ),
            array(
                array(-30505967, -3821767, 12074681, 13582412, -19877972, 2443951, -19719286, 12746132, 5331210, -10105944),
                array(30528811, 3601899, -1957090, 4619785, -27361822, -15436388, 24180793, -12570394, 27679908, -1648928),
                array(9402404, -13957065, 32834043, 10838634, -26580150, -13237195, 26653274, -8685565, 22611444, -12715406),
            ),
            array(
                array(22190590, 1118029, 22736441, 15130463, -30460692, -5991321, 19189625, -4648942, 4854859, 6622139),
                array(-8310738, -2953450, -8262579, -3388049, -10401731, -271929, 13424426, -3567227, 26404409, 13001963),
                array(-31241838, -15415700, -2994250, 8939346, 11562230, -12840670, -26064365, -11621720, -15405155, 11020693),
            ),
            array(
                array(1866042, -7949489, -7898649, -10301010, 12483315, 13477547, 3175636, -12424163, 28761762, 1406734),
                array(-448555, -1777666, 13018551, 3194501, -9580420, -11161737, 24760585, -4347088, 25577411, -13378680),
                array(-24290378, 4759345, -690653, -1852816, 2066747, 10693769, -29595790, 9884936, -9368926, 4745410),
            ),
            array(
                array(-9141284, 6049714, -19531061, -4341411, -31260798, 9944276, -15462008, -11311852, 10931924, -11931931),
                array(-16561513, 14112680, -8012645, 4817318, -8040464, -11414606, -22853429, 10856641, -20470770, 13434654),
                array(22759489, -10073434, -16766264, -1871422, 13637442, -10168091, 1765144, -12654326, 28445307, -5364710),
            ),
            array(
                array(29875063, 12493613, 2795536, -3786330, 1710620, 15181182, -10195717, -8788675, 9074234, 1167180),
                array(-26205683, 11014233, -9842651, -2635485, -26908120, 7532294, -18716888, -9535498, 3843903, 9367684),
                array(-10969595, -6403711, 9591134, 9582310, 11349256, 108879, 16235123, 8601684, -139197, 4242895),
            ),
        ),
        array(
            array(
                array(22092954, -13191123, -2042793, -11968512, 32186753, -11517388, -6574341, 2470660, -27417366, 16625501),
                array(-11057722, 3042016, 13770083, -9257922, 584236, -544855, -7770857, 2602725, -27351616, 14247413),
                array(6314175, -10264892, -32772502, 15957557, -10157730, 168750, -8618807, 14290061, 27108877, -1180880),
            ),
            array(
                array(-8586597, -7170966, 13241782, 10960156, -32991015, -13794596, 33547976, -11058889, -27148451, 981874),
                array(22833440, 9293594, -32649448, -13618667, -9136966, 14756819, -22928859, -13970780, -10479804, -16197962),
                array(-7768587, 3326786, -28111797, 10783824, 19178761, 14905060, 22680049, 13906969, -15933690, 3797899),
            ),
            array(
                array(21721356, -4212746, -12206123, 9310182, -3882239, -13653110, 23740224, -2709232, 20491983, -8042152),
                array(9209270, -15135055, -13256557, -6167798, -731016, 15289673, 25947805, 15286587, 30997318, -6703063),
                array(7392032, 16618386, 23946583, -8039892, -13265164, -1533858, -14197445, -2321576, 17649998, -250080),
            ),
            array(
                array(-9301088, -14193827, 30609526, -3049543, -25175069, -1283752, -15241566, -9525724, -2233253, 7662146),
                array(-17558673, 1763594, -33114336, 15908610, -30040870, -12174295, 7335080, -8472199, -3174674, 3440183),
                array(-19889700, -5977008, -24111293, -9688870, 10799743, -16571957, 40450, -4431835, 4862400, 1133),
            ),
            array(
                array(-32856209, -7873957, -5422389, 14860950, -16319031, 7956142, 7258061, 311861, -30594991, -7379421),
                array(-3773428, -1565936, 28985340, 7499440, 24445838, 9325937, 29727763, 16527196, 18278453, 15405622),
                array(-4381906, 8508652, -19898366, -3674424, -5984453, 15149970, -13313598, 843523, -21875062, 13626197),
            ),
            array(
                array(2281448, -13487055, -10915418, -2609910, 1879358, 16164207, -10783882, 3953792, 13340839, 15928663),
                array(31727126, -7179855, -18437503, -8283652, 2875793, -16390330, -25269894, -7014826, -23452306, 5964753),
                array(4100420, -5959452, -17179337, 6017714, -18705837, 12227141, -26684835, 11344144, 2538215, -7570755),
            ),
            array(
                array(-9433605, 6123113, 11159803, -2156608, 30016280, 14966241, -20474983, 1485421, -629256, -15958862),
                array(-26804558, 4260919, 11851389, 9658551, -32017107, 16367492, -20205425, -13191288, 11659922, -11115118),
                array(26180396, 10015009, -30844224, -8581293, 5418197, 9480663, 2231568, -10170080, 33100372, -1306171),
            ),
            array(
                array(15121113, -5201871, -10389905, 15427821, -27509937, -15992507, 21670947, 4486675, -5931810, -14466380),
                array(16166486, -9483733, -11104130, 6023908, -31926798, -1364923, 2340060, -16254968, -10735770, -10039824),
                array(28042865, -3557089, -12126526, 12259706, -3717498, -6945899, 6766453, -8689599, 18036436, 5803270),
            ),
        ),
        array(
            array(
                array(-817581, 6763912, 11803561, 1585585, 10958447, -2671165, 23855391, 4598332, -6159431, -14117438),
                array(-31031306, -14256194, 17332029, -2383520, 31312682, -5967183, 696309, 50292, -20095739, 11763584),
                array(-594563, -2514283, -32234153, 12643980, 12650761, 14811489, 665117, -12613632, -19773211, -10713562),
            ),
            array(
                array(30464590, -11262872, -4127476, -12734478, 19835327, -7105613, -24396175, 2075773, -17020157, 992471),
                array(18357185, -6994433, 7766382, 16342475, -29324918, 411174, 14578841, 8080033, -11574335, -10601610),
                array(19598397, 10334610, 12555054, 2555664, 18821899, -10339780, 21873263, 16014234, 26224780, 16452269),
            ),
            array(
                array(-30223925, 5145196, 5944548, 16385966, 3976735, 2009897, -11377804, -7618186, -20533829, 3698650),
                array(14187449, 3448569, -10636236, -10810935, -22663880, -3433596, 7268410, -10890444, 27394301, 12015369),
                array(19695761, 16087646, 28032085, 12999827, 6817792, 11427614, 20244189, -1312777, -13259127, -3402461),
            ),
            array(
                array(30860103, 12735208, -1888245, -4699734, -16974906, 2256940, -8166013, 12298312, -8550524, -10393462),
                array(-5719826, -11245325, -1910649, 15569035, 26642876, -7587760, -5789354, -15118654, -4976164, 12651793),
                array(-2848395, 9953421, 11531313, -5282879, 26895123, -12697089, -13118820, -16517902, 9768698, -2533218),
            ),
            array(
                array(-24719459, 1894651, -287698, -4704085, 15348719, -8156530, 32767513, 12765450, 4940095, 10678226),
                array(18860224, 15980149, -18987240, -1562570, -26233012, -11071856, -7843882, 13944024, -24372348, 16582019),
                array(-15504260, 4970268, -29893044, 4175593, -20993212, -2199756, -11704054, 15444560, -11003761, 7989037),
            ),
            array(
                array(31490452, 5568061, -2412803, 2182383, -32336847, 4531686, -32078269, 6200206, -19686113, -14800171),
                array(-17308668, -15879940, -31522777, -2831, -32887382, 16375549, 8680158, -16371713, 28550068, -6857132),
                array(-28126887, -5688091, 16837845, -1820458, -6850681, 12700016, -30039981, 4364038, 1155602, 5988841),
            ),
            array(
                array(21890435, -13272907, -12624011, 12154349, -7831873, 15300496, 23148983, -4470481, 24618407, 8283181),
                array(-33136107, -10512751, 9975416, 6841041, -31559793, 16356536, 3070187, -7025928, 1466169, 10740210),
                array(-1509399, -15488185, -13503385, -10655916, 32799044, 909394, -13938903, -5779719, -32164649, -15327040),
            ),
            array(
                array(3960823, -14267803, -28026090, -15918051, -19404858, 13146868, 15567327, 951507, -3260321, -573935),
                array(24740841, 5052253, -30094131, 8961361, 25877428, 6165135, -24368180, 14397372, -7380369, -6144105),
                array(-28888365, 3510803, -28103278, -1158478, -11238128, -10631454, -15441463, -14453128, -1625486, -6494814),
            ),
        ),
        array(
            array(
                array(793299, -9230478, 8836302, -6235707, -27360908, -2369593, 33152843, -4885251, -9906200, -621852),
                array(5666233, 525582, 20782575, -8038419, -24538499, 14657740, 16099374, 1468826, -6171428, -15186581),
                array(-4859255, -3779343, -2917758, -6748019, 7778750, 11688288, -30404353, -9871238, -1558923, -9863646),
            ),
            array(
                array(10896332, -7719704, 824275, 472601, -19460308, 3009587, 25248958, 14783338, -30581476, -15757844),
                array(10566929, 12612572, -31944212, 11118703, -12633376, 12362879, 21752402, 8822496, 24003793, 14264025),
                array(27713862, -7355973, -11008240, 9227530, 27050101, 2504721, 23886875, -13117525, 13958495, -5732453),
            ),
            array(
                array(-23481610, 4867226, -27247128, 3900521, 29838369, -8212291, -31889399, -10041781, 7340521, -15410068),
                array(4646514, -8011124, -22766023, -11532654, 23184553, 8566613, 31366726, -1381061, -15066784, -10375192),
                array(-17270517, 12723032, -16993061, 14878794, 21619651, -6197576, 27584817, 3093888, -8843694, 3849921),
            ),
            array(
                array(-9064912, 2103172, 25561640, -15125738, -5239824, 9582958, 32477045, -9017955, 5002294, -15550259),
                array(-12057553, -11177906, 21115585, -13365155, 8808712, -12030708, 16489530, 13378448, -25845716, 12741426),
                array(-5946367, 10645103, -30911586, 15390284, -3286982, -7118677, 24306472, 15852464, 28834118, -7646072),
            ),
            array(
                array(-17335748, -9107057, -24531279, 9434953, -8472084, -583362, -13090771, 455841, 20461858, 5491305),
                array(13669248, -16095482, -12481974, -10203039, -14569770, -11893198, -24995986, 11293807, -28588204, -9421832),
                array(28497928, 6272777, -33022994, 14470570, 8906179, -1225630, 18504674, -14165166, 29867745, -8795943),
            ),
            array(
                array(-16207023, 13517196, -27799630, -13697798, 24009064, -6373891, -6367600, -13175392, 22853429, -4012011),
                array(24191378, 16712145, -13931797, 15217831, 14542237, 1646131, 18603514, -11037887, 12876623, -2112447),
                array(17902668, 4518229, -411702, -2829247, 26878217, 5258055, -12860753, 608397, 16031844, 3723494),
            ),
            array(
                array(-28632773, 12763728, -20446446, 7577504, 33001348, -13017745, 17558842, -7872890, 23896954, -4314245),
                array(-20005381, -12011952, 31520464, 605201, 2543521, 5991821, -2945064, 7229064, -9919646, -8826859),
                array(28816045, 298879, -28165016, -15920938, 19000928, -1665890, -12680833, -2949325, -18051778, -2082915),
            ),
            array(
                array(16000882, -344896, 3493092, -11447198, -29504595, -13159789, 12577740, 16041268, -19715240, 7847707),
                array(10151868, 10572098, 27312476, 7922682, 14825339, 4723128, -32855931, -6519018, -10020567, 3852848),
                array(-11430470, 15697596, -21121557, -4420647, 5386314, 15063598, 16514493, -15932110, 29330899, -15076224),
            ),
        ),
        array(
            array(
                array(-25499735, -4378794, -15222908, -6901211, 16615731, 2051784, 3303702, 15490, -27548796, 12314391),
                array(15683520, -6003043, 18109120, -9980648, 15337968, -5997823, -16717435, 15921866, 16103996, -3731215),
                array(-23169824, -10781249, 13588192, -1628807, -3798557, -1074929, -19273607, 5402699, -29815713, -9841101),
            ),
            array(
                array(23190676, 2384583, -32714340, 3462154, -29903655, -1529132, -11266856, 8911517, -25205859, 2739713),
                array(21374101, -3554250, -33524649, 9874411, 15377179, 11831242, -33529904, 6134907, 4931255, 11987849),
                array(-7732, -2978858, -16223486, 7277597, 105524, -322051, -31480539, 13861388, -30076310, 10117930),
            ),
            array(
                array(-29501170, -10744872, -26163768, 13051539, -25625564, 5089643, -6325503, 6704079, 12890019, 15728940),
                array(-21972360, -11771379, -951059, -4418840, 14704840, 2695116, 903376, -10428139, 12885167, 8311031),
                array(-17516482, 5352194, 10384213, -13811658, 7506451, 13453191, 26423267, 4384730, 1888765, -5435404),
            ),
            array(
                array(-25817338, -3107312, -13494599, -3182506, 30896459, -13921729, -32251644, -12707869, -19464434, -3340243),
                array(-23607977, -2665774, -526091, 4651136, 5765089, 4618330, 6092245, 14845197, 17151279, -9854116),
                array(-24830458, -12733720, -15165978, 10367250, -29530908, -265356, 22825805, -7087279, -16866484, 16176525),
            ),
            array(
                array(-23583256, 6564961, 20063689, 3798228, -4740178, 7359225, 2006182, -10363426, -28746253, -10197509),
                array(-10626600, -4486402, -13320562, -5125317, 3432136, -6393229, 23632037, -1940610, 32808310, 1099883),
                array(15030977, 5768825, -27451236, -2887299, -6427378, -15361371, -15277896, -6809350, 2051441, -15225865),
            ),
            array(
                array(-3362323, -7239372, 7517890, 9824992, 23555850, 295369, 5148398, -14154188, -22686354, 16633660),
                array(4577086, -16752288, 13249841, -15304328, 19958763, -14537274, 18559670, -10759549, 8402478, -9864273),
                array(-28406330, -1051581, -26790155, -907698, -17212414, -11030789, 9453451, -14980072, 17983010, 9967138),
            ),
            array(
                array(-25762494, 6524722, 26585488, 9969270, 24709298, 1220360, -1677990, 7806337, 17507396, 3651560),
                array(-10420457, -4118111, 14584639, 15971087, -15768321, 8861010, 26556809, -5574557, -18553322, -11357135),
                array(2839101, 14284142, 4029895, 3472686, 14402957, 12689363, -26642121, 8459447, -5605463, -7621941),
            ),
            array(
                array(-4839289, -3535444, 9744961, 2871048, 25113978, 3187018, -25110813, -849066, 17258084, -7977739),
                array(18164541, -10595176, -17154882, -1542417, 19237078, -9745295, 23357533, -15217008, 26908270, 12150756),
                array(-30264870, -7647865, 5112249, -7036672, -1499807, -6974257, 43168, -5537701, -32302074, 16215819),
            ),
        ),
        array(
            array(
                array(-6898905, 9824394, -12304779, -4401089, -31397141, -6276835, 32574489, 12532905, -7503072, -8675347),
                array(-27343522, -16515468, -27151524, -10722951, 946346, 16291093, 254968, 7168080, 21676107, -1943028),
                array(21260961, -8424752, -16831886, -11920822, -23677961, 3968121, -3651949, -6215466, -3556191, -7913075),
            ),
            array(
                array(16544754, 13250366, -16804428, 15546242, -4583003, 12757258, -2462308, -8680336, -18907032, -9662799),
                array(-2415239, -15577728, 18312303, 4964443, -15272530, -12653564, 26820651, 16690659, 25459437, -4564609),
                array(-25144690, 11425020, 28423002, -11020557, -6144921, -15826224, 9142795, -2391602, -6432418, -1644817),
            ),
            array(
                array(-23104652, 6253476, 16964147, -3768872, -25113972, -12296437, -27457225, -16344658, 6335692, 7249989),
                array(-30333227, 13979675, 7503222, -12368314, -11956721, -4621693, -30272269, 2682242, 25993170, -12478523),
                array(4364628, 5930691, 32304656, -10044554, -8054781, 15091131, 22857016, -10598955, 31820368, 15075278),
            ),
            array(
                array(31879134, -8918693, 17258761, 90626, -8041836, -4917709, 24162788, -9650886, -17970238, 12833045),
                array(19073683, 14851414, -24403169, -11860168, 7625278, 11091125, -19619190, 2074449, -9413939, 14905377),
                array(24483667, -11935567, -2518866, -11547418, -1553130, 15355506, -25282080, 9253129, 27628530, -7555480),
            ),
            array(
                array(17597607, 8340603, 19355617, 552187, 26198470, -3176583, 4593324, -9157582, -14110875, 15297016),
                array(510886, 14337390, -31785257, 16638632, 6328095, 2713355, -20217417, -11864220, 8683221, 2921426),
                array(18606791, 11874196, 27155355, -5281482, -24031742, 6265446, -25178240, -1278924, 4674690, 13890525),
            ),
            array(
                array(13609624, 13069022, -27372361, -13055908, 24360586, 9592974, 14977157, 9835105, 4389687, 288396),
                array(9922506, -519394, 13613107, 5883594, -18758345, -434263, -12304062, 8317628, 23388070, 16052080),
                array(12720016, 11937594, -31970060, -5028689, 26900120, 8561328, -20155687, -11632979, -14754271, -10812892),
            ),
            array(
                array(15961858, 14150409, 26716931, -665832, -22794328, 13603569, 11829573, 7467844, -28822128, 929275),
                array(11038231, -11582396, -27310482, -7316562, -10498527, -16307831, -23479533, -9371869, -21393143, 2465074),
                array(20017163, -4323226, 27915242, 1529148, 12396362, 15675764, 13817261, -9658066, 2463391, -4622140),
            ),
            array(
                array(-16358878, -12663911, -12065183, 4996454, -1256422, 1073572, 9583558, 12851107, 4003896, 12673717),
                array(-1731589, -15155870, -3262930, 16143082, 19294135, 13385325, 14741514, -9103726, 7903886, 2348101),
                array(24536016, -16515207, 12715592, -3862155, 1511293, 10047386, -3842346, -7129159, -28377538, 10048127),
            ),
        ),
        array(
            array(
                array(-12622226, -6204820, 30718825, 2591312, -10617028, 12192840, 18873298, -7297090, -32297756, 15221632),
                array(-26478122, -11103864, 11546244, -1852483, 9180880, 7656409, -21343950, 2095755, 29769758, 6593415),
                array(-31994208, -2907461, 4176912, 3264766, 12538965, -868111, 26312345, -6118678, 30958054, 8292160),
            ),
            array(
                array(31429822, -13959116, 29173532, 15632448, 12174511, -2760094, 32808831, 3977186, 26143136, -3148876),
                array(22648901, 1402143, -22799984, 13746059, 7936347, 365344, -8668633, -1674433, -3758243, -2304625),
                array(-15491917, 8012313, -2514730, -12702462, -23965846, -10254029, -1612713, -1535569, -16664475, 8194478),
            ),
            array(
                array(27338066, -7507420, -7414224, 10140405, -19026427, -6589889, 27277191, 8855376, 28572286, 3005164),
                array(26287124, 4821776, 25476601, -4145903, -3764513, -15788984, -18008582, 1182479, -26094821, -13079595),
                array(-7171154, 3178080, 23970071, 6201893, -17195577, -4489192, -21876275, -13982627, 32208683, -1198248),
            ),
            array(
                array(-16657702, 2817643, -10286362, 14811298, 6024667, 13349505, -27315504, -10497842, -27672585, -11539858),
                array(15941029, -9405932, -21367050, 8062055, 31876073, -238629, -15278393, -1444429, 15397331, -4130193),
                array(8934485, -13485467, -23286397, -13423241, -32446090, 14047986, 31170398, -1441021, -27505566, 15087184),
            ),
            array(
                array(-18357243, -2156491, 24524913, -16677868, 15520427, -6360776, -15502406, 11461896, 16788528, -5868942),
                array(-1947386, 16013773, 21750665, 3714552, -17401782, -16055433, -3770287, -10323320, 31322514, -11615635),
                array(21426655, -5650218, -13648287, -5347537, -28812189, -4920970, -18275391, -14621414, 13040862, -12112948),
            ),
            array(
                array(11293895, 12478086, -27136401, 15083750, -29307421, 14748872, 14555558, -13417103, 1613711, 4896935),
                array(-25894883, 15323294, -8489791, -8057900, 25967126, -13425460, 2825960, -4897045, -23971776, -11267415),
                array(-15924766, -5229880, -17443532, 6410664, 3622847, 10243618, 20615400, 12405433, -23753030, -8436416),
            ),
            array(
                array(-7091295, 12556208, -20191352, 9025187, -17072479, 4333801, 4378436, 2432030, 23097949, -566018),
                array(4565804, -16025654, 20084412, -7842817, 1724999, 189254, 24767264, 10103221, -18512313, 2424778),
                array(366633, -11976806, 8173090, -6890119, 30788634, 5745705, -7168678, 1344109, -3642553, 12412659),
            ),
            array(
                array(-24001791, 7690286, 14929416, -168257, -32210835, -13412986, 24162697, -15326504, -3141501, 11179385),
                array(18289522, -14724954, 8056945, 16430056, -21729724, 7842514, -6001441, -1486897, -18684645, -11443503),
                array(476239, 6601091, -6152790, -9723375, 17503545, -4863900, 27672959, 13403813, 11052904, 5219329),
            ),
        ),
        array(
            array(
                array(20678546, -8375738, -32671898, 8849123, -5009758, 14574752, 31186971, -3973730, 9014762, -8579056),
                array(-13644050, -10350239, -15962508, 5075808, -1514661, -11534600, -33102500, 9160280, 8473550, -3256838),
                array(24900749, 14435722, 17209120, -15292541, -22592275, 9878983, -7689309, -16335821, -24568481, 11788948),
            ),
            array(
                array(-3118155, -11395194, -13802089, 14797441, 9652448, -6845904, -20037437, 10410733, -24568470, -1458691),
                array(-15659161, 16736706, -22467150, 10215878, -9097177, 7563911, 11871841, -12505194, -18513325, 8464118),
                array(-23400612, 8348507, -14585951, -861714, -3950205, -6373419, 14325289, 8628612, 33313881, -8370517),
            ),
            array(
                array(-20186973, -4967935, 22367356, 5271547, -1097117, -4788838, -24805667, -10236854, -8940735, -5818269),
                array(-6948785, -1795212, -32625683, -16021179, 32635414, -7374245, 15989197, -12838188, 28358192, -4253904),
                array(-23561781, -2799059, -32351682, -1661963, -9147719, 10429267, -16637684, 4072016, -5351664, 5596589),
            ),
            array(
                array(-28236598, -3390048, 12312896, 6213178, 3117142, 16078565, 29266239, 2557221, 1768301, 15373193),
                array(-7243358, -3246960, -4593467, -7553353, -127927, -912245, -1090902, -4504991, -24660491, 3442910),
                array(-30210571, 5124043, 14181784, 8197961, 18964734, -11939093, 22597931, 7176455, -18585478, 13365930),
            ),
            array(
                array(-7877390, -1499958, 8324673, 4690079, 6261860, 890446, 24538107, -8570186, -9689599, -3031667),
                array(25008904, -10771599, -4305031, -9638010, 16265036, 15721635, 683793, -11823784, 15723479, -15163481),
                array(-9660625, 12374379, -27006999, -7026148, -7724114, -12314514, 11879682, 5400171, 519526, -1235876),
            ),
            array(
                array(22258397, -16332233, -7869817, 14613016, -22520255, -2950923, -20353881, 7315967, 16648397, 7605640),
                array(-8081308, -8464597, -8223311, 9719710, 19259459, -15348212, 23994942, -5281555, -9468848, 4763278),
                array(-21699244, 9220969, -15730624, 1084137, -25476107, -2852390, 31088447, -7764523, -11356529, 728112),
            ),
            array(
                array(26047220, -11751471, -6900323, -16521798, 24092068, 9158119, -4273545, -12555558, -29365436, -5498272),
                array(17510331, -322857, 5854289, 8403524, 17133918, -3112612, -28111007, 12327945, 10750447, 10014012),
                array(-10312768, 3936952, 9156313, -8897683, 16498692, -994647, -27481051, -666732, 3424691, 7540221),
            ),
            array(
                array(30322361, -6964110, 11361005, -4143317, 7433304, 4989748, -7071422, -16317219, -9244265, 15258046),
                array(13054562, -2779497, 19155474, 469045, -12482797, 4566042, 5631406, 2711395, 1062915, -5136345),
                array(-19240248, -11254599, -29509029, -7499965, -5835763, 13005411, -6066489, 12194497, 32960380, 1459310),
            ),
        ),
        array(
            array(
                array(19852034, 7027924, 23669353, 10020366, 8586503, -6657907, 394197, -6101885, 18638003, -11174937),
                array(31395534, 15098109, 26581030, 8030562, -16527914, -5007134, 9012486, -7584354, -6643087, -5442636),
                array(-9192165, -2347377, -1997099, 4529534, 25766844, 607986, -13222, 9677543, -32294889, -6456008),
            ),
            array(
                array(-2444496, -149937, 29348902, 8186665, 1873760, 12489863, -30934579, -7839692, -7852844, -8138429),
                array(-15236356, -15433509, 7766470, 746860, 26346930, -10221762, -27333451, 10754588, -9431476, 5203576),
                array(31834314, 14135496, -770007, 5159118, 20917671, -16768096, -7467973, -7337524, 31809243, 7347066),
            ),
            array(
                array(-9606723, -11874240, 20414459, 13033986, 13716524, -11691881, 19797970, -12211255, 15192876, -2087490),
                array(-12663563, -2181719, 1168162, -3804809, 26747877, -14138091, 10609330, 12694420, 33473243, -13382104),
                array(33184999, 11180355, 15832085, -11385430, -1633671, 225884, 15089336, -11023903, -6135662, 14480053),
            ),
            array(
                array(31308717, -5619998, 31030840, -1897099, 15674547, -6582883, 5496208, 13685227, 27595050, 8737275),
                array(-20318852, -15150239, 10933843, -16178022, 8335352, -7546022, -31008351, -12610604, 26498114, 66511),
                array(22644454, -8761729, -16671776, 4884562, -3105614, -13559366, 30540766, -4286747, -13327787, -7515095),
            ),
            array(
                array(-28017847, 9834845, 18617207, -2681312, -3401956, -13307506, 8205540, 13585437, -17127465, 15115439),
                array(23711543, -672915, 31206561, -8362711, 6164647, -9709987, -33535882, -1426096, 8236921, 16492939),
                array(-23910559, -13515526, -26299483, -4503841, 25005590, -7687270, 19574902, 10071562, 6708380, -6222424),
            ),
            array(
                array(2101391, -4930054, 19702731, 2367575, -15427167, 1047675, 5301017, 9328700, 29955601, -11678310),
                array(3096359, 9271816, -21620864, -15521844, -14847996, -7592937, -25892142, -12635595, -9917575, 6216608),
                array(-32615849, 338663, -25195611, 2510422, -29213566, -13820213, 24822830, -6146567, -26767480, 7525079),
            ),
            array(
                array(-23066649, -13985623, 16133487, -7896178, -3389565, 778788, -910336, -2782495, -19386633, 11994101),
                array(21691500, -13624626, -641331, -14367021, 3285881, -3483596, -25064666, 9718258, -7477437, 13381418),
                array(18445390, -4202236, 14979846, 11622458, -1727110, -3582980, 23111648, -6375247, 28535282, 15779576),
            ),
            array(
                array(30098053, 3089662, -9234387, 16662135, -21306940, 11308411, -14068454, 12021730, 9955285, -16303356),
                array(9734894, -14576830, -7473633, -9138735, 2060392, 11313496, -18426029, 9924399, 20194861, 13380996),
                array(-26378102, -7965207, -22167821, 15789297, -18055342, -6168792, -1984914, 15707771, 26342023, 10146099),
            ),
        ),
        array(
            array(
                array(-26016874, -219943, 21339191, -41388, 19745256, -2878700, -29637280, 2227040, 21612326, -545728),
                array(-13077387, 1184228, 23562814, -5970442, -20351244, -6348714, 25764461, 12243797, -20856566, 11649658),
                array(-10031494, 11262626, 27384172, 2271902, 26947504, -15997771, 39944, 6114064, 33514190, 2333242),
            ),
            array(
                array(-21433588, -12421821, 8119782, 7219913, -21830522, -9016134, -6679750, -12670638, 24350578, -13450001),
                array(-4116307, -11271533, -23886186, 4843615, -30088339, 690623, -31536088, -10406836, 8317860, 12352766),
                array(18200138, -14475911, -33087759, -2696619, -23702521, -9102511, -23552096, -2287550, 20712163, 6719373),
            ),
            array(
                array(26656208, 6075253, -7858556, 1886072, -28344043, 4262326, 11117530, -3763210, 26224235, -3297458),
                array(-17168938, -14854097, -3395676, -16369877, -19954045, 14050420, 21728352, 9493610, 18620611, -16428628),
                array(-13323321, 13325349, 11432106, 5964811, 18609221, 6062965, -5269471, -9725556, -30701573, -16479657),
            ),
            array(
                array(-23860538, -11233159, 26961357, 1640861, -32413112, -16737940, 12248509, -5240639, 13735342, 1934062),
                array(25089769, 6742589, 17081145, -13406266, 21909293, -16067981, -15136294, -3765346, -21277997, 5473616),
                array(31883677, -7961101, 1083432, -11572403, 22828471, 13290673, -7125085, 12469656, 29111212, -5451014),
            ),
            array(
                array(24244947, -15050407, -26262976, 2791540, -14997599, 16666678, 24367466, 6388839, -10295587, 452383),
                array(-25640782, -3417841, 5217916, 16224624, 19987036, -4082269, -24236251, -5915248, 15766062, 8407814),
                array(-20406999, 13990231, 15495425, 16395525, 5377168, 15166495, -8917023, -4388953, -8067909, 2276718),
            ),
            array(
                array(30157918, 12924066, -17712050, 9245753, 19895028, 3368142, -23827587, 5096219, 22740376, -7303417),
                array(2041139, -14256350, 7783687, 13876377, -25946985, -13352459, 24051124, 13742383, -15637599, 13295222),
                array(33338237, -8505733, 12532113, 7977527, 9106186, -1715251, -17720195, -4612972, -4451357, -14669444),
            ),
            array(
                array(-20045281, 5454097, -14346548, 6447146, 28862071, 1883651, -2469266, -4141880, 7770569, 9620597),
                array(23208068, 7979712, 33071466, 8149229, 1758231, -10834995, 30945528, -1694323, -33502340, -14767970),
                array(1439958, -16270480, -1079989, -793782, 4625402, 10647766, -5043801, 1220118, 30494170, -11440799),
            ),
            array(
                array(-5037580, -13028295, -2970559, -3061767, 15640974, -6701666, -26739026, 926050, -1684339, -13333647),
                array(13908495, -3549272, 30919928, -6273825, -21521863, 7989039, 9021034, 9078865, 3353509, 4033511),
                array(-29663431, -15113610, 32259991, -344482, 24295849, -12912123, 23161163, 8839127, 27485041, 7356032),
            ),
        ),
        array(
            array(
                array(9661027, 705443, 11980065, -5370154, -1628543, 14661173, -6346142, 2625015, 28431036, -16771834),
                array(-23839233, -8311415, -25945511, 7480958, -17681669, -8354183, -22545972, 14150565, 15970762, 4099461),
                array(29262576, 16756590, 26350592, -8793563, 8529671, -11208050, 13617293, -9937143, 11465739, 8317062),
            ),
            array(
                array(-25493081, -6962928, 32500200, -9419051, -23038724, -2302222, 14898637, 3848455, 20969334, -5157516),
                array(-20384450, -14347713, -18336405, 13884722, -33039454, 2842114, -21610826, -3649888, 11177095, 14989547),
                array(-24496721, -11716016, 16959896, 2278463, 12066309, 10137771, 13515641, 2581286, -28487508, 9930240),
            ),
            array(
                array(-17751622, -2097826, 16544300, -13009300, -15914807, -14949081, 18345767, -13403753, 16291481, -5314038),
                array(-33229194, 2553288, 32678213, 9875984, 8534129, 6889387, -9676774, 6957617, 4368891, 9788741),
                array(16660756, 7281060, -10830758, 12911820, 20108584, -8101676, -21722536, -8613148, 16250552, -11111103),
            ),
            array(
                array(-19765507, 2390526, -16551031, 14161980, 1905286, 6414907, 4689584, 10604807, -30190403, 4782747),
                array(-1354539, 14736941, -7367442, -13292886, 7710542, -14155590, -9981571, 4383045, 22546403, 437323),
                array(31665577, -12180464, -16186830, 1491339, -18368625, 3294682, 27343084, 2786261, -30633590, -14097016),
            ),
            array(
                array(-14467279, -683715, -33374107, 7448552, 19294360, 14334329, -19690631, 2355319, -19284671, -6114373),
                array(15121312, -15796162, 6377020, -6031361, -10798111, -12957845, 18952177, 15496498, -29380133, 11754228),
                array(-2637277, -13483075, 8488727, -14303896, 12728761, -1622493, 7141596, 11724556, 22761615, -10134141),
            ),
            array(
                array(16918416, 11729663, -18083579, 3022987, -31015732, -13339659, -28741185, -12227393, 32851222, 11717399),
                array(11166634, 7338049, -6722523, 4531520, -29468672, -7302055, 31474879, 3483633, -1193175, -4030831),
                array(-185635, 9921305, 31456609, -13536438, -12013818, 13348923, 33142652, 6546660, -19985279, -3948376),
            ),
            array(
                array(-32460596, 11266712, -11197107, -7899103, 31703694, 3855903, -8537131, -12833048, -30772034, -15486313),
                array(-18006477, 12709068, 3991746, -6479188, -21491523, -10550425, -31135347, -16049879, 10928917, 3011958),
                array(-6957757, -15594337, 31696059, 334240, 29576716, 14796075, -30831056, -12805180, 18008031, 10258577),
            ),
            array(
                array(-22448644, 15655569, 7018479, -4410003, -30314266, -1201591, -1853465, 1367120, 25127874, 6671743),
                array(29701166, -14373934, -10878120, 9279288, -17568, 13127210, 21382910, 11042292, 25838796, 4642684),
                array(-20430234, 14955537, -24126347, 8124619, -5369288, -5990470, 30468147, -13900640, 18423289, 4177476),
            ),
        )
    );

    /**
     * See: libsodium's crypto_core/curve25519/ref10/base2.h
     *
     * @var array<int, array<int, array<int, int>>> basically int[8][3]
     */
    protected static $base2 = array(
        array(
            array(25967493, -14356035, 29566456, 3660896, -12694345, 4014787, 27544626, -11754271, -6079156, 2047605),
            array(-12545711, 934262, -2722910, 3049990, -727428, 9406986, 12720692, 5043384, 19500929, -15469378),
            array(-8738181, 4489570, 9688441, -14785194, 10184609, -12363380, 29287919, 11864899, -24514362, -4438546),
        ),
        array(
            array(15636291, -9688557, 24204773, -7912398, 616977, -16685262, 27787600, -14772189, 28944400, -1550024),
            array(16568933, 4717097, -11556148, -1102322, 15682896, -11807043, 16354577, -11775962, 7689662, 11199574),
            array(30464156, -5976125, -11779434, -15670865, 23220365, 15915852, 7512774, 10017326, -17749093, -9920357),
        ),
        array(
            array(10861363, 11473154, 27284546, 1981175, -30064349, 12577861, 32867885, 14515107, -15438304, 10819380),
            array(4708026, 6336745, 20377586, 9066809, -11272109, 6594696, -25653668, 12483688, -12668491, 5581306),
            array(19563160, 16186464, -29386857, 4097519, 10237984, -4348115, 28542350, 13850243, -23678021, -15815942),
        ),
        array(
            array(5153746, 9909285, 1723747, -2777874, 30523605, 5516873, 19480852, 5230134, -23952439, -15175766),
            array(-30269007, -3463509, 7665486, 10083793, 28475525, 1649722, 20654025, 16520125, 30598449, 7715701),
            array(28881845, 14381568, 9657904, 3680757, -20181635, 7843316, -31400660, 1370708, 29794553, -1409300),
        ),
        array(
            array(-22518993, -6692182, 14201702, -8745502, -23510406, 8844726, 18474211, -1361450, -13062696, 13821877),
            array(-6455177, -7839871, 3374702, -4740862, -27098617, -10571707, 31655028, -7212327, 18853322, -14220951),
            array(4566830, -12963868, -28974889, -12240689, -7602672, -2830569, -8514358, -10431137, 2207753, -3209784),
        ),
        array(
            array(-25154831, -4185821, 29681144, 7868801, -6854661, -9423865, -12437364, -663000, -31111463, -16132436),
            array(25576264, -2703214, 7349804, -11814844, 16472782, 9300885, 3844789, 15725684, 171356, 6466918),
            array(23103977, 13316479, 9739013, -16149481, 817875, -15038942, 8965339, -14088058, -30714912, 16193877),
        ),
        array(
            array(-33521811, 3180713, -2394130, 14003687, -16903474, -16270840, 17238398, 4729455, -18074513, 9256800),
            array(-25182317, -4174131, 32336398, 5036987, -21236817, 11360617, 22616405, 9761698, -19827198, 630305),
            array(-13720693, 2639453, -24237460, -7406481, 9494427, -5774029, -6554551, -15960994, -2449256, -14291300),
        ),
        array(
            array(-3151181, -5046075, 9282714, 6866145, -31907062, -863023, -18940575, 15033784, 25105118, -7894876),
            array(-24326370, 15950226, -31801215, -14592823, -11662737, -5090925, 1573892, -2625887, 2198790, -15804619),
            array(-3099351, 10324967, -2241613, 7453183, -5446979, -2735503, -13812022, -16236442, -32461234, -12290683),
        )
    );

    /**
     * 37095705934669439343138083508754565189542113879843219016388785533085940283555
     *
     * @var array<int, int>
     */
    protected static $d = array(
        -10913610,
        13857413,
        -15372611,
        6949391,
        114729,
        -8787816,
        -6275908,
        -3247719,
        -18696448,
        -12055116
    );

    /**
     * 2 * d = 16295367250680780974490674513165176452449235426866156013048779062215315747161
     *
     * @var array<int, int>
     */
    protected static $d2 = array(
        -21827239,
        -5839606,
        -30745221,
        13898782,
        229458,
        15978800,
        -12551817,
        -6495438,
        29715968,
        9444199
    );

    /**
     * sqrt(-1)
     *
     * @var array<int, int>
     */
    protected static $sqrtm1 = array(
        -32595792,
        -7943725,
        9377950,
        3500415,
        12389472,
        -272473,
        -25146209,
        -2005654,
        326686,
        11406482
    );
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Ge/Precomp.php000064400000002775152177723700020666 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp
 */
class ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp
{
    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $yplusx;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $yminusx;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $xy2d;

    /**
     * ParagonIE_Sodium_Core32_Curve25519_Ge_Precomp constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $yplusx
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $yminusx
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe $xy2d
     * @throws SodiumException
     * @throws TypeError
     */
    public function __construct(
        ParagonIE_Sodium_Core32_Curve25519_Fe $yplusx = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $yminusx = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $xy2d = null
    ) {
        if ($yplusx === null) {
            $yplusx = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->yplusx = $yplusx;
        if ($yminusx === null) {
            $yminusx = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->yminusx = $yminusx;
        if ($xy2d === null) {
            $xy2d = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->xy2d = $xy2d;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Ge/P1p1.php000064400000003344152177723700017773 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1', false)) {
    return;
}
/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
 */
class ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1
{
    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $T;

    /**
     * ParagonIE_Sodium_Core32_Curve25519_Ge_P1p1 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $z
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $t
     *
     * @throws SodiumException
     * @throws TypeError
     */
    public function __construct(
        ParagonIE_Sodium_Core32_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $z = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $t = null
    ) {
        if ($x === null) {
            $x = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->X = $x;
        if ($y === null) {
            $y = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->Z = $z;
        if ($t === null) {
            $t = ParagonIE_Sodium_Core32_Curve25519::fe_0();
        }
        $this->T = $t;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Ge/P3.php000064400000003242152177723700017531 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Ge_P3', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Ge_P3
 */
class ParagonIE_Sodium_Core32_Curve25519_Ge_P3
{
    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $T;

    /**
     * ParagonIE_Sodium_Core32_Curve25519_Ge_P3 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $z
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $t
     */
    public function __construct(
        ParagonIE_Sodium_Core32_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $z = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $t = null
    ) {
        if ($x === null) {
            $x = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->X = $x;
        if ($y === null) {
            $y = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->Z = $z;
        if ($t === null) {
            $t = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->T = $t;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Ge/Cached.php000064400000003415152177723700020420 0ustar00<?php


if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Ge_Cached', false)) {
    return;
}
/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Ge_Cached
 */
class ParagonIE_Sodium_Core32_Curve25519_Ge_Cached
{
    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $YplusX;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $YminusX;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Z;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $T2d;

    /**
     * ParagonIE_Sodium_Core32_Curve25519_Ge_Cached constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $YplusX
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $YminusX
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $Z
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $T2d
     */
    public function __construct(
        ParagonIE_Sodium_Core32_Curve25519_Fe $YplusX = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $YminusX = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $Z = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $T2d = null
    ) {
        if ($YplusX === null) {
            $YplusX = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->YplusX = $YplusX;
        if ($YminusX === null) {
            $YminusX = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->YminusX = $YminusX;
        if ($Z === null) {
            $Z = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->Z = $Z;
        if ($T2d === null) {
            $T2d = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->T2d = $T2d;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Ge/P2.php000064400000002541152177723700017531 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Ge_P2', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Ge_P2
 */
class ParagonIE_Sodium_Core32_Curve25519_Ge_P2
{
    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $X;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Y;

    /**
     * @var ParagonIE_Sodium_Core32_Curve25519_Fe
     */
    public $Z;

    /**
     * ParagonIE_Sodium_Core32_Curve25519_Ge_P2 constructor.
     *
     * @internal You should not use this directly from another application
     *
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $x
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $y
     * @param ParagonIE_Sodium_Core32_Curve25519_Fe|null $z
     */
    public function __construct(
        ParagonIE_Sodium_Core32_Curve25519_Fe $x = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $y = null,
        ParagonIE_Sodium_Core32_Curve25519_Fe $z = null
    ) {
        if ($x === null) {
            $x = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->X = $x;
        if ($y === null) {
            $y = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->Y = $y;
        if ($z === null) {
            $z = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        }
        $this->Z = $z;
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/Fe.php000064400000012127152177723700017250 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Curve25519_Fe', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Curve25519_Fe
 *
 * This represents a Field Element
 */
class ParagonIE_Sodium_Core32_Curve25519_Fe implements ArrayAccess
{
    /**
     * @var array<int, ParagonIE_Sodium_Core32_Int32>
     */
    protected $container = array();

    /**
     * @var int
     */
    protected $size = 10;

    /**
     * @internal You should not use this directly from another application
     *
     * @param array<int, ParagonIE_Sodium_Core32_Int32> $array
     * @param bool $save_indexes
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromArray($array, $save_indexes = null)
    {
        $count = count($array);
        if ($save_indexes) {
            $keys = array_keys($array);
        } else {
            $keys = range(0, $count - 1);
        }
        $array = array_values($array);

        $obj = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        if ($save_indexes) {
            for ($i = 0; $i < $count; ++$i) {
                $array[$i]->overflow = 0;
                $obj->offsetSet($keys[$i], $array[$i]);
            }
        } else {
            for ($i = 0; $i < $count; ++$i) {
                $array[$i]->overflow = 0;
                $obj->offsetSet($i, $array[$i]);
            }
        }
        return $obj;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param array<int, int> $array
     * @param bool $save_indexes
     * @return self
     * @throws SodiumException
     * @throws TypeError
     */
    public static function fromIntArray($array, $save_indexes = null)
    {
        $count = count($array);
        if ($save_indexes) {
            $keys = array_keys($array);
        } else {
            $keys = range(0, $count - 1);
        }
        $array = array_values($array);
        $set = array();
        /** @var int $i */
        /** @var int $v */
        foreach ($array as $i => $v) {
            $set[$i] = ParagonIE_Sodium_Core32_Int32::fromInt($v);
        }

        $obj = new ParagonIE_Sodium_Core32_Curve25519_Fe();
        if ($save_indexes) {
            for ($i = 0; $i < $count; ++$i) {
                $set[$i]->overflow = 0;
                $obj->offsetSet($keys[$i], $set[$i]);
            }
        } else {
            for ($i = 0; $i < $count; ++$i) {
                $set[$i]->overflow = 0;
                $obj->offsetSet($i, $set[$i]);
            }
        }
        return $obj;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @param mixed $value
     * @return void
     * @throws SodiumException
     * @throws TypeError
     */
    public function offsetSet($offset, $value)
    {
        if (!($value instanceof ParagonIE_Sodium_Core32_Int32)) {
            throw new InvalidArgumentException('Expected an instance of ParagonIE_Sodium_Core32_Int32');
        }
        if (is_null($offset)) {
            $this->container[] = $value;
        } else {
            ParagonIE_Sodium_Core32_Util::declareScalarType($offset, 'int', 1);
            $this->container[(int) $offset] = $value;
        }
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return bool
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return void
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetUnset($offset)
    {
        unset($this->container[$offset]);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param mixed $offset
     * @return ParagonIE_Sodium_Core32_Int32
     * @psalm-suppress MixedArrayOffset
     */
    public function offsetGet($offset)
    {
        if (!isset($this->container[$offset])) {
            $this->container[(int) $offset] = new ParagonIE_Sodium_Core32_Int32();
        }
        /** @var ParagonIE_Sodium_Core32_Int32 $get */
        $get = $this->container[$offset];
        return $get;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @return array
     */
    public function __debugInfo()
    {
        if (empty($this->container)) {
            return array();
        }
        $c = array(
            (int) ($this->container[0]->toInt()),
            (int) ($this->container[1]->toInt()),
            (int) ($this->container[2]->toInt()),
            (int) ($this->container[3]->toInt()),
            (int) ($this->container[4]->toInt()),
            (int) ($this->container[5]->toInt()),
            (int) ($this->container[6]->toInt()),
            (int) ($this->container[7]->toInt()),
            (int) ($this->container[8]->toInt()),
            (int) ($this->container[9]->toInt())
        );
        return array(implode(', ', $c));
    }
}
vendor/paragonie/sodium_compat/src/Core32/Curve25519/README.md000064400000000332152177723700017457 0ustar00# Curve25519 Data Structures

These are PHP implementation of the [structs used in the ref10 curve25519 code](https://github.com/jedisct1/libsodium/blob/master/src/libsodium/include/sodium/private/curve25519_ref10.h).
vendor/paragonie/sodium_compat/src/Core32/Ed25519.php000064400000036221152177723700016163 0ustar00<?php

if (class_exists('ParagonIE_Sodium_Core32_Ed25519', false)) {
    return;
}

/**
 * Class ParagonIE_Sodium_Core32_Ed25519
 */
abstract class ParagonIE_Sodium_Core32_Ed25519 extends ParagonIE_Sodium_Core32_Curve25519
{
    const KEYPAIR_BYTES = 96;
    const SEED_BYTES = 32;

    /**
     * @internal You should not use this directly from another application
     *
     * @return string (96 bytes)
     * @throws Exception
     * @throws SodiumException
     * @throws TypeError
     */
    public static function keypair()
    {
        $seed = random_bytes(self::SEED_BYTES);
        $pk = '';
        $sk = '';
        self::seed_keypair($pk, $sk, $seed);
        return $sk . $pk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $pk
     * @param string $sk
     * @param string $seed
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function seed_keypair(&$pk, &$sk, $seed)
    {
        if (self::strlen($seed) !== self::SEED_BYTES) {
            throw new RangeException('crypto_sign keypair seed must be 32 bytes long');
        }

        /** @var string $pk */
        $pk = self::publickey_from_secretkey($seed);
        $sk = $seed . $pk;
        return $sk;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws TypeError
     */
    public static function secretkey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 0, 64);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $keypair
     * @return string
     * @throws RangeException
     * @throws TypeError
     */
    public static function publickey($keypair)
    {
        if (self::strlen($keypair) !== self::KEYPAIR_BYTES) {
            throw new RangeException('crypto_sign keypair must be 96 bytes long');
        }
        return self::substr($keypair, 64, 32);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function publickey_from_secretkey($sk)
    {
        /** @var string $sk */
        $sk = hash('sha512', self::substr($sk, 0, 32), true);
        $sk[0] = self::intToChr(
            self::chrToInt($sk[0]) & 248
        );
        $sk[31] = self::intToChr(
            (self::chrToInt($sk[31]) & 63) | 64
        );
        return self::sk_to_pk($sk);
    }

    /**
     * @param string $pk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function pk_to_curve25519($pk)
    {
        if (self::small_order($pk)) {
            throw new SodiumException('Public key is on a small order');
        }
        $A = self::ge_frombytes_negate_vartime($pk);
        $p1 = self::ge_mul_l($A);
        if (!self::fe_isnonzero($p1->X)) {
            throw new SodiumException('Unexpected zero result');
        }

        # fe_1(one_minus_y);
        # fe_sub(one_minus_y, one_minus_y, A.Y);
        # fe_invert(one_minus_y, one_minus_y);
        $one_minux_y = self::fe_invert(
            self::fe_sub(
                self::fe_1(),
                $A->Y
            )
        );


        # fe_1(x);
        # fe_add(x, x, A.Y);
        # fe_mul(x, x, one_minus_y);
        $x = self::fe_mul(
            self::fe_add(self::fe_1(), $A->Y),
            $one_minux_y
        );

        # fe_tobytes(curve25519_pk, x);
        return self::fe_tobytes($x);
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sk_to_pk($sk)
    {
        return self::ge_p3_tobytes(
            self::ge_scalarmult_base(
                self::substr($sk, 0, 32)
            )
        );
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign($message, $sk)
    {
        /** @var string $signature */
        $signature = self::sign_detached($message, $sk);
        return $signature . $message;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message A signed message
     * @param string $pk      Public key
     * @return string         Message (without signature)
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_open($message, $pk)
    {
        /** @var string $signature */
        $signature = self::substr($message, 0, 64);

        /** @var string $message */
        $message = self::substr($message, 64);

        if (self::verify_detached($signature, $message, $pk)) {
            return $message;
        }
        throw new SodiumException('Invalid signature');
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $message
     * @param string $sk
     * @return string
     * @throws SodiumException
     * @throws TypeError
     */
    public static function sign_detached($message, $sk)
    {
        # crypto_hash_sha512(az, sk, 32);
        $az =  hash('sha512', self::substr($sk, 0, 32), true);

        # az[0] &= 248;
        # az[31] &= 63;
        # az[31] |= 64;
        $az[0] = self::intToChr(self::chrToInt($az[0]) & 248);
        $az[31] = self::intToChr((self::chrToInt($az[31]) & 63) | 64);

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, az + 32, 32);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, nonce);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($az, 32, 32));
        hash_update($hs, $message);
        $nonceHash = hash_final($hs, true);

        # memmove(sig + 32, sk + 32, 32);
        $pk = self::substr($sk, 32, 32);

        # sc_reduce(nonce);
        # ge_scalarmult_base(&R, nonce);
        # ge_p3_tobytes(sig, &R);
        $nonce = self::sc_reduce($nonceHash) . self::substr($nonceHash, 32);
        $sig = self::ge_p3_tobytes(
            self::ge_scalarmult_base($nonce)
        );

        # crypto_hash_sha512_init(&hs);
        # crypto_hash_sha512_update(&hs, sig, 64);
        # crypto_hash_sha512_update(&hs, m, mlen);
        # crypto_hash_sha512_final(&hs, hram);
        $hs = hash_init('sha512');
        hash_update($hs, self::substr($sig, 0, 32));
        hash_update($hs, self::substr($pk, 0, 32));
        hash_update($hs, $message);
        $hramHash = hash_final($hs, true);

        # sc_reduce(hram);
        # sc_muladd(sig + 32, hram, az, nonce);
        $hram = self::sc_reduce($hramHash);
        $sigAfter = self::sc_muladd($hram, $az, $nonce);
        $sig = self::substr($sig, 0, 32) . self::substr($sigAfter, 0, 32);

        try {
            ParagonIE_Sodium_Compat::memzero($az);
        } catch (SodiumException $ex) {
            $az = null;
        }
        return $sig;
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $sig
     * @param string $message
     * @param string $pk
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function verify_detached($sig, $message, $pk)
    {
        if (self::strlen($sig) < 64) {
            throw new SodiumException('Signature is too short');
        }
        if (self::check_S_lt_L(self::substr($sig, 32, 32))) {
            throw new SodiumException('S < L - Invalid signature');
        }
        if (self::small_order($sig)) {
            throw new SodiumException('Signature is on too small of an order');
        }
        if ((self::chrToInt($sig[63]) & 224) !== 0) {
            throw new SodiumException('Invalid signature');
        }
        $d = 0;
        for ($i = 0; $i < 32; ++$i) {
            $d |= self::chrToInt($pk[$i]);
        }
        if ($d === 0) {
            throw new SodiumException('All zero public key');
        }

        /** @var bool The original value of ParagonIE_Sodium_Compat::$fastMult */
        $orig = ParagonIE_Sodium_Compat::$fastMult;

        // Set ParagonIE_Sodium_Compat::$fastMult to true to speed up verification.
        ParagonIE_Sodium_Compat::$fastMult = true;

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P3 $A */
        $A = self::ge_frombytes_negate_vartime($pk);

        /** @var string $hDigest */
        $hDigest = hash(
            'sha512',
            self::substr($sig, 0, 32) .
            self::substr($pk, 0, 32) .
            $message,
            true
        );

        /** @var string $h */
        $h = self::sc_reduce($hDigest) . self::substr($hDigest, 32);

        /** @var ParagonIE_Sodium_Core32_Curve25519_Ge_P2 $R */
        $R = self::ge_double_scalarmult_vartime(
            $h,
            $A,
            self::substr($sig, 32)
        );

        /** @var string $rcheck */
        $rcheck = self::ge_tobytes($R);

        // Reset ParagonIE_Sodium_Compat::$fastMult to what it was before.
        ParagonIE_Sodium_Compat::$fastMult = $orig;

        return self::verify_32($rcheck, self::substr($sig, 0, 32));
    }

    /**
     * @internal You should not use this directly from another application
     *
     * @param string $S
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function check_S_lt_L($S)
    {
        if (self::strlen($S) < 32) {
            throw new SodiumException('Signature must be 32 bytes');
        }
        static $L = array(
            0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58,
            0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
        );
        /** @var array<int, int> $L */
        $c = 0;
        $n = 1;
        $i = 32;

        do {
            --$i;
            $x = self::chrToInt($S[$i]);
            $c |= (
                (($x - $L[$i]) >> 8) & $n
            );
            $n &= (
                (($x ^ $L[$i]) - 1) >> 8
            );
        } while ($i !== 0);

        return $c === 0;
    }

    /**
     * @param string $R
     * @return bool
     * @throws SodiumException
     * @throws TypeError
     */
    public static function small_order($R)
    {
        static $blacklist = array(
            /* 0 (order 4) */
            array(
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 1 (order 1) */
            array(
                0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
            ),
            /* 2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0x26, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x05
            ),
            /* 55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xc7, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0x7a
            ),
            /* p-1 (order 2) */
            array(
                0x13, 0xe8, 0x95, 0x8f, 0xc2, 0xb2, 0x27, 0xb0,
                0x45, 0xc3, 0xf4, 0x89, 0xf2, 0xef, 0x98, 0xf0,
                0xd5, 0xdf, 0xac, 0x05, 0xd3, 0xc6, 0x33, 0x39,
                0xb1, 0x38, 0x02, 0x88, 0x6d, 0x53, 0xfc, 0x85
            ),
            /* p (order 4) */
            array(
                0xb4, 0x17, 0x6a, 0x70, 0x3d, 0x4d, 0xd8, 0x4f,
                0xba, 0x3c, 0x0b, 0x76, 0x0d, 0x10, 0x67, 0x0f,
                0x2a, 0x20, 0x53, 0xfa, 0x2c, 0x39, 0xcc, 0xc6,
                0x4e, 0xc7, 0xfd, 0x77, 0x92, 0xac, 0x03, 0xfa
            ),
            /* p+1 (order 1) */
            array(
                0xec, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+2707385501144840649318225287225658788936804267575313519463743609750303402022 (order 8) */
            array(
                0xed, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* p+55188659117513257062467267217118295137698188065244968500265048394206261417927 (order 8) */
            array(
                0xee, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f
            ),
            /* 2p-1 (order 2) */
            array(
                0xd9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p (order 4) */
            array(
                0xda, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            ),
            /* 2p+1 (order 1) */
            array(
                0xdb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
                0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
            )
        );
        /** @var array<int, array<int, int>> $blacklist */
        $countBlacklist = count($blacklist);

        for ($i = 0; $i < $countBlacklist; ++$i) {
            $c = 0;
            for ($j = 0; $j < 32; ++$j) {
                $c |= self::chrToInt($R[$j]) ^ $blacklist[$i][$j];
            }
            if ($c === 0) {
                return true;
            }
        }
        return false;
    }
}
vendor/paragonie/sodium_compat/autoload-pedantic.php000064400000000162152177723700016771 0ustar00<?php

require_once 'autoload.php';
define('DO_PEDANTIC_TEST', true);

ParagonIE_Sodium_Compat::$fastMult = true;
vendor/paragonie/sodium_compat/namespaced/Core/HChaCha20.php000064400000000146152177723700017747 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class HChaCha20 extends \ParagonIE_Sodium_Core_HChaCha20
{

}
vendor/paragonie/sodium_compat/namespaced/Core/ChaCha20.php000064400000000144152177723700017635 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class ChaCha20 extends \ParagonIE_Sodium_Core_ChaCha20
{

}
vendor/paragonie/sodium_compat/namespaced/Core/XChaCha20.php000064400000000146152177723700017767 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class XChaCha20 extends \ParagonIE_Sodium_Core_XChaCha20
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Xsalsa20.php000064400000000144152177723700017761 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class Xsalsa20 extends \ParagonIE_Sodium_Core_Xsalsa20
{

}
vendor/paragonie/sodium_compat/namespaced/Core/ChaCha20/IetfCtx.php000064400000000164152177723700021205 0ustar00<?php
namespace ParagonIE\Sodium\Core\ChaCha20;

class IetfCtx extends \ParagonIE_Sodium_Core_ChaCha20_IetfCtx
{

}
vendor/paragonie/sodium_compat/namespaced/Core/ChaCha20/Ctx.php000064400000000154152177723700020374 0ustar00<?php
namespace ParagonIE\Sodium\Core\ChaCha20;

class Ctx extends \ParagonIE_Sodium_Core_ChaCha20_Ctx
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Util.php000064400000000134152177723700017300 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class Util extends \ParagonIE_Sodium_Core_Util
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519.php000064400000000150152177723700020053 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class Curve25519 extends \ParagonIE_Sodium_Core_Curve25519
{

}
vendor/paragonie/sodium_compat/namespaced/Core/BLAKE2b.php000064400000000142152177723700017424 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class BLAKE2b extends \ParagonIE_Sodium_Core_BLAKE2b
{

}
vendor/paragonie/sodium_compat/namespaced/Core/X25519.php000064400000000140152177723700017175 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class X25519 extends \ParagonIE_Sodium_Core_X25519
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Poly1305.php000064400000000144152177723700017620 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class Poly1305 extends \ParagonIE_Sodium_Core_Poly1305
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Poly1305/State.php000064400000000160152177723700020676 0ustar00<?php
namespace ParagonIE\Sodium\Core\Poly1305;

class State extends \ParagonIE_Sodium_Core_Poly1305_State
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Salsa20.php000064400000000141152177723700017566 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class SipHash extends \ParagonIE_Sodium_Core_Salsa20
{

}vendor/paragonie/sodium_compat/namespaced/Core/SipHash.php000064400000000142152177723700017721 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class SipHash extends \ParagonIE_Sodium_Core_SipHash
{

}
vendor/paragonie/sodium_compat/namespaced/Core/HSalsa20.php000064400000000144152177723700017701 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class HSalsa20 extends \ParagonIE_Sodium_Core_HSalsa20
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/H.php000064400000000154152177723700020246 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519;

class H extends \ParagonIE_Sodium_Core_Curve25519_H
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Ge/Precomp.php000064400000000176152177723700022023 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519\Ge;

class Precomp extends \ParagonIE_Sodium_Core_Curve25519_Ge_Precomp
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Ge/P1p1.php000064400000000170152177723700021131 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519\Ge;

class P1p1 extends \ParagonIE_Sodium_Core_Curve25519_Ge_P1p1
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Ge/P3.php000064400000000164152177723700020675 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519\Ge;

class P3 extends \ParagonIE_Sodium_Core_Curve25519_Ge_P3
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Ge/Cached.php000064400000000174152177723700021563 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519\Ge;

class Cached extends \ParagonIE_Sodium_Core_Curve25519_Ge_Cached
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Ge/P2.php000064400000000164152177723700020674 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519\Ge;

class P2 extends \ParagonIE_Sodium_Core_Curve25519_Ge_P2
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Curve25519/Fe.php000064400000000156152177723700020413 0ustar00<?php
namespace ParagonIE\Sodium\Core\Curve25519;

class Fe extends \ParagonIE_Sodium_Core_Curve25519_Fe
{

}
vendor/paragonie/sodium_compat/namespaced/Core/Ed25519.php000064400000000142152177723700017320 0ustar00<?php
namespace ParagonIE\Sodium\Core;

class Ed25519 extends \ParagonIE_Sodium_Core_Ed25519
{

}
vendor/paragonie/sodium_compat/namespaced/Compat.php000064400000000126152177723700016717 0ustar00<?php
namespace ParagonIE\Sodium;

class Compat extends \ParagonIE_Sodium_Compat
{

}
vendor/paragonie/sodium_compat/namespaced/File.php000064400000000122152177723700016347 0ustar00<?php
namespace ParagonIE\Sodium;

class File extends \ParagonIE_Sodium_File
{

}
vendor/paragonie/sodium_compat/namespaced/Crypto.php000064400000000126152177723700016754 0ustar00<?php
namespace ParagonIE\Sodium;

class Crypto extends \ParagonIE_Sodium_Crypto
{

}
vendor/google/recaptcha/LICENSE000064400000002704152177723700012272 0ustar00Copyright 2014, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

    * Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
    * Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

vendor/google/recaptcha/src/autoload.php000064400000002222152177723700014370 0ustar00<?php

/* An autoloader for ReCaptcha\Foo classes. This should be require()d
 * by the user before attempting to instantiate any of the ReCaptcha
 * classes.
 */

spl_autoload_register(function ($class) {
    if (substr($class, 0, 10) !== 'ReCaptcha\\') {
      /* If the class does not lie under the "ReCaptcha" namespace,
       * then we can exit immediately.
       */
      return;
    }

    /* All of the classes have names like "ReCaptcha\Foo", so we need
     * to replace the backslashes with frontslashes if we want the
     * name to map directly to a location in the filesystem.
     */
    $class = str_replace('\\', '/', $class);

    /* First, check under the current directory. It is important that
     * we look here first, so that we don't waste time searching for
     * test classes in the common case.
     */
    $path = dirname(__FILE__).'/'.$class.'.php';
    if (is_readable($path)) {
        require_once $path;
    }

    /* If we didn't find what we're looking for already, maybe it's
     * a test class?
     */
    $path = dirname(__FILE__).'/../tests/'.$class.'.php';
    if (is_readable($path)) {
        require_once $path;
    }
});
vendor/google/recaptcha/src/ReCaptcha/Response.php000064400000005352152177723700016217 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha;

/**
 * The response returned from the service.
 */
class Response
{
    /**
     * Succes or failure.
     * @var boolean
     */
    private $success = false;

    /**
     * Error code strings.
     * @var array
     */
    private $errorCodes = array();

    /**
     * Build the response from the expected JSON returned by the service.
     *
     * @param string $json
     * @return \ReCaptcha\Response
     */
    public static function fromJson($json)
    {
        $responseData = json_decode($json, true);

        if (!$responseData) {
            return new Response(false, array('invalid-json'));
        }

        if (isset($responseData['success']) && $responseData['success'] == true) {
            return new Response(true);
        }

        if (isset($responseData['error-codes']) && is_array($responseData['error-codes'])) {
            return new Response(false, $responseData['error-codes']);
        }

        return new Response(false);
    }

    /**
     * Constructor.
     *
     * @param boolean $success
     * @param array $errorCodes
     */
    public function __construct($success, array $errorCodes = array())
    {
        $this->success = $success;
        $this->errorCodes = $errorCodes;
    }

    /**
     * Is success?
     *
     * @return boolean
     */
    public function isSuccess()
    {
        return $this->success;
    }

    /**
     * Get error codes.
     *
     * @return array
     */
    public function getErrorCodes()
    {
        return $this->errorCodes;
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod.php000064400000003077152177723700017214 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha;

/**
 * Method used to send the request to the service.
 */
interface RequestMethod
{

    /**
     * Submit the request with the specified parameters.
     *
     * @param RequestParameters $params Request parameters
     * @return string Body of the reCAPTCHA response
     */
    public function submit(RequestParameters $params);
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php000064400000005563152177723700020446 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha\RequestMethod;

/**
 * Convenience wrapper around native socket and file functions to allow for
 * mocking.
 */
class Socket
{
    private $handle = null;

    /**
     * fsockopen
     *
     * @see http://php.net/fsockopen
     * @param string $hostname
     * @param int $port
     * @param int $errno
     * @param string $errstr
     * @param float $timeout
     * @return resource
     */
    public function fsockopen($hostname, $port = -1, &$errno = 0, &$errstr = '', $timeout = null)
    {
        $this->handle = fsockopen($hostname, $port, $errno, $errstr, (is_null($timeout) ? ini_get("default_socket_timeout") : $timeout));

        if ($this->handle != false && $errno === 0 && $errstr === '') {
            return $this->handle;
        } else {
            return false;
        }
    }

    /**
     * fwrite
     *
     * @see http://php.net/fwrite
     * @param string $string
     * @param int $length
     * @return int | bool
     */
    public function fwrite($string, $length = null)
    {
        return fwrite($this->handle, $string, (is_null($length) ? strlen($string) : $length));
    }

    /**
     * fgets
     *
     * @see http://php.net/fgets
     * @param int $length
     * @return string
     */
    public function fgets($length = null)
    {
        return fgets($this->handle, $length);
    }

    /**
     * feof
     *
     * @see http://php.net/feof
     * @return bool
     */
    public function feof()
    {
        return feof($this->handle);
    }

    /**
     * fclose
     *
     * @see http://php.net/fclose
     * @return bool
     */
    public function fclose()
    {
        return fclose($this->handle);
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php000064400000007361152177723700021312 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha\RequestMethod;

use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;

/**
 * Sends a POST request to the reCAPTCHA service, but makes use of fsockopen()
 * instead of get_file_contents(). This is to account for people who may be on
 * servers where allow_furl_open is disabled.
 */
class SocketPost implements RequestMethod
{
    /**
     * reCAPTCHA service host.
     * @const string
     */
    const RECAPTCHA_HOST = 'www.google.com';

    /**
     * @const string reCAPTCHA service path
     */
    const SITE_VERIFY_PATH = '/recaptcha/api/siteverify';

    /**
     * @const string Bad request error
     */
    const BAD_REQUEST = '{"success": false, "error-codes": ["invalid-request"]}';

    /**
     * @const string Bad response error
     */
    const BAD_RESPONSE = '{"success": false, "error-codes": ["invalid-response"]}';

    /**
     * Socket to the reCAPTCHA service
     * @var Socket
     */
    private $socket;

    /**
     * Constructor
     *
     * @param \ReCaptcha\RequestMethod\Socket $socket optional socket, injectable for testing
     */
    public function __construct(Socket $socket = null)
    {
        if (!is_null($socket)) {
            $this->socket = $socket;
        } else {
            $this->socket = new Socket();
        }
    }

    /**
     * Submit the POST request with the specified parameters.
     *
     * @param RequestParameters $params Request parameters
     * @return string Body of the reCAPTCHA response
     */
    public function submit(RequestParameters $params)
    {
        $errno = 0;
        $errstr = '';

        if (false === $this->socket->fsockopen('ssl://' . self::RECAPTCHA_HOST, 443, $errno, $errstr, 30)) {
            return self::BAD_REQUEST;
        }

        $content = $params->toQueryString();

        $request = "POST " . self::SITE_VERIFY_PATH . " HTTP/1.1\r\n";
        $request .= "Host: " . self::RECAPTCHA_HOST . "\r\n";
        $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
        $request .= "Content-length: " . strlen($content) . "\r\n";
        $request .= "Connection: close\r\n\r\n";
        $request .= $content . "\r\n\r\n";

        $this->socket->fwrite($request);
        $response = '';

        while (!$this->socket->feof()) {
            $response .= $this->socket->fgets(4096);
        }

        $this->socket->fclose();

        if (0 !== strpos($response, 'HTTP/1.1 200 OK')) {
            return self::BAD_RESPONSE;
        }

        $parts = preg_split("#\n\s*\n#Uis", $response);

        return $parts[1];
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php000064400000004137152177723700020117 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha\RequestMethod;

/**
 * Convenience wrapper around the cURL functions to allow mocking.
 */
class Curl
{

    /**
     * @see http://php.net/curl_init
     * @param string $url
     * @return resource cURL handle
     */
    public function init($url = null)
    {
        return curl_init($url);
    }

    /**
     * @see http://php.net/curl_setopt_array
     * @param resource $ch
     * @param array $options
     * @return bool
     */
    public function setoptArray($ch, array $options)
    {
        return curl_setopt_array($ch, $options);
    }

    /**
     * @see http://php.net/curl_exec
     * @param resource $ch
     * @return mixed
     */
    public function exec($ch)
    {
        return curl_exec($ch);
    }

    /**
     * @see http://php.net/curl_close
     * @param resource $ch
     */
    public function close($ch)
    {
        curl_close($ch);
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php000064400000005311152177723700020132 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha\RequestMethod;

use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;

/**
 * Sends POST requests to the reCAPTCHA service.
 */
class Post implements RequestMethod
{
    /**
     * URL to which requests are POSTed.
     * @const string
     */
    const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';

    /**
     * Submit the POST request with the specified parameters.
     *
     * @param RequestParameters $params Request parameters
     * @return string Body of the reCAPTCHA response
     */
    public function submit(RequestParameters $params)
    {
        /**
         * PHP 5.6.0 changed the way you specify the peer name for SSL context options.
         * Using "CN_name" will still work, but it will raise deprecated errors.
         */
        $peer_key = version_compare(PHP_VERSION, '5.6.0', '<') ? 'CN_name' : 'peer_name';
        $options = array(
            'http' => array(
                'header' => "Content-type: application/x-www-form-urlencoded\r\n",
                'method' => 'POST',
                'content' => $params->toQueryString(),
                // Force the peer to validate (not needed in 5.6.0+, but still works
                'verify_peer' => true,
                // Force the peer validation to use www.google.com
                $peer_key => 'www.google.com',
            ),
        );
        $context = stream_context_create($options);
        return file_get_contents(self::SITE_VERIFY_URL, false, $context);
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php000064400000005547152177723700020773 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha\RequestMethod;

use ReCaptcha\RequestMethod;
use ReCaptcha\RequestParameters;

/**
 * Sends cURL request to the reCAPTCHA service.
 * Note: this requires the cURL extension to be enabled in PHP
 * @see http://php.net/manual/en/book.curl.php
 */
class CurlPost implements RequestMethod
{
    /**
     * URL to which requests are sent via cURL.
     * @const string
     */
    const SITE_VERIFY_URL = 'https://www.google.com/recaptcha/api/siteverify';

    /**
     * Curl connection to the reCAPTCHA service
     * @var Curl
     */
    private $curl;

    public function __construct(Curl $curl = null)
    {
        if (!is_null($curl)) {
            $this->curl = $curl;
        } else {
            $this->curl = new Curl();
        }
    }

    /**
     * Submit the cURL request with the specified parameters.
     *
     * @param RequestParameters $params Request parameters
     * @return string Body of the reCAPTCHA response
     */
    public function submit(RequestParameters $params)
    {
        $handle = $this->curl->init(self::SITE_VERIFY_URL);

        $options = array(
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $params->toQueryString(),
            CURLOPT_HTTPHEADER => array(
                'Content-Type: application/x-www-form-urlencoded'
            ),
            CURLINFO_HEADER_OUT => false,
            CURLOPT_HEADER => false,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => true
        );
        $this->curl->setoptArray($handle, $options);

        $response = $this->curl->exec($handle);
        $this->curl->close($handle);

        return $response;
    }
}
vendor/google/recaptcha/src/ReCaptcha/RequestParameters.php000064400000005560152177723700020076 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha;

/**
 * Stores and formats the parameters for the request to the reCAPTCHA service.
 */
class RequestParameters
{
    /**
     * Site secret.
     * @var string
     */
    private $secret;

    /**
     * Form response.
     * @var string
     */
    private $response;

    /**
     * Remote user's IP address.
     * @var string
     */
    private $remoteIp;

    /**
     * Client version.
     * @var string
     */
    private $version;

    /**
     * Initialise parameters.
     *
     * @param string $secret Site secret.
     * @param string $response Value from g-captcha-response form field.
     * @param string $remoteIp User's IP address.
     * @param string $version Version of this client library.
     */
    public function __construct($secret, $response, $remoteIp = null, $version = null)
    {
        $this->secret = $secret;
        $this->response = $response;
        $this->remoteIp = $remoteIp;
        $this->version = $version;
    }

    /**
     * Array representation.
     *
     * @return array Array formatted parameters.
     */
    public function toArray()
    {
        $params = array('secret' => $this->secret, 'response' => $this->response);

        if (!is_null($this->remoteIp)) {
            $params['remoteip'] = $this->remoteIp;
        }

        if (!is_null($this->version)) {
            $params['version'] = $this->version;
        }

        return $params;
    }

    /**
     * Query string representation for HTTP request.
     *
     * @return string Query string formatted parameters.
     */
    public function toQueryString()
    {
        return http_build_query($this->toArray(), '', '&');
    }
}
vendor/google/recaptcha/src/ReCaptcha/ReCaptcha.php000064400000006304152177723700016251 0ustar00<?php
/**
 * This is a PHP library that handles calling reCAPTCHA.
 *
 * @copyright Copyright (c) 2015, Google Inc.
 * @link      http://www.google.com/recaptcha
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

namespace ReCaptcha;

/**
 * reCAPTCHA client.
 */
class ReCaptcha
{
    /**
     * Version of this client library.
     * @const string
     */
    const VERSION = 'php_1.1.2';

    /**
     * Shared secret for the site.
     * @var type string
     */
    private $secret;

    /**
     * Method used to communicate  with service. Defaults to POST request.
     * @var RequestMethod
     */
    private $requestMethod;

    /**
     * Create a configured instance to use the reCAPTCHA service.
     *
     * @param string $secret shared secret between site and reCAPTCHA server.
     * @param RequestMethod $requestMethod method used to send the request. Defaults to POST.
     */
    public function __construct($secret, RequestMethod $requestMethod = null)
    {
        if (empty($secret)) {
            throw new \RuntimeException('No secret provided');
        }

        if (!is_string($secret)) {
            throw new \RuntimeException('The provided secret must be a string');
        }

        $this->secret = $secret;

        if (!is_null($requestMethod)) {
            $this->requestMethod = $requestMethod;
        } else {
            $this->requestMethod = new RequestMethod\Post();
        }
    }

    /**
     * Calls the reCAPTCHA siteverify API to verify whether the user passes
     * CAPTCHA test.
     *
     * @param string $response The value of 'g-recaptcha-response' in the submitted form.
     * @param string $remoteIp The end user's IP address.
     * @return Response Response from the service.
     */
    public function verify($response, $remoteIp = null)
    {
        // Discard empty solution submissions
        if (empty($response)) {
            $recaptchaResponse = new Response(false, array('missing-input-response'));
            return $recaptchaResponse;
        }

        $params = new RequestParameters($this->secret, $response, $remoteIp, self::VERSION);
        $rawResponse = $this->requestMethod->submit($params);
        return Response::fromJson($rawResponse);
    }
}
vendor/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php000064400000002717152177723700023646 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Interceptor;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Assertable;
use TYPO3\PharStreamWrapper\Exception;
use TYPO3\PharStreamWrapper\Manager;

class PharExtensionInterceptor implements Assertable
{
    /**
     * Determines whether the base file name has a ".phar" suffix.
     *
     * @param string $path
     * @param string $command
     * @return bool
     * @throws Exception
     */
    public function assert($path, $command)
    {
        if ($this->baseFileContainsPharExtension($path)) {
            return true;
        }
        throw new Exception(
            sprintf(
                'Unexpected file extension in "%s"',
                $path
            ),
            1535198703
        );
    }

    /**
     * @param string $path
     * @return bool
     */
    private function baseFileContainsPharExtension($path)
    {
        $invocation = Manager::instance()->resolve($path);
        if ($invocation === null) {
            return false;
        }
        $fileExtension = pathinfo($invocation->getBaseName(), PATHINFO_EXTENSION);
        return strtolower($fileExtension) === 'phar';
    }
}
vendor/typo3/phar-stream-wrapper/src/Interceptor/ConjunctionInterceptor.php000064400000004250152177723700023342 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Interceptor;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Assertable;
use TYPO3\PharStreamWrapper\Exception;

class ConjunctionInterceptor implements Assertable
{
    /**
     * @var Assertable[]
     */
    private $assertions;

    public function __construct(array $assertions)
    {
        $this->assertAssertions($assertions);
        $this->assertions = $assertions;
    }

    /**
     * Executes assertions based on all contained assertions.
     *
     * @param string $path
     * @param string $command
     * @return bool
     * @throws Exception
     */
    public function assert($path, $command)
    {
        if ($this->invokeAssertions($path, $command)) {
            return true;
        }
        throw new Exception(
            sprintf(
                'Assertion failed in "%s"',
                $path
            ),
            1539625084
        );
    }

    /**
     * @param Assertable[] $assertions
     */
    private function assertAssertions(array $assertions)
    {
        foreach ($assertions as $assertion) {
            if (!$assertion instanceof Assertable) {
                throw new \InvalidArgumentException(
                    sprintf(
                        'Instance %s must implement Assertable',
                        get_class($assertion)
                    ),
                    1539624719
                );
            }
        }
    }

    /**
     * @param string $path
     * @param string $command
     * @return bool
     */
    private function invokeAssertions($path, $command)
    {
        try {
            foreach ($this->assertions as $assertion) {
                if (!$assertion->assert($path, $command)) {
                    return false;
                }
            }
        } catch (Exception $exception) {
            return false;
        }
        return true;
    }
}
vendor/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php000064400000004213152177723700023343 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Interceptor;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Assertable;
use TYPO3\PharStreamWrapper\Exception;
use TYPO3\PharStreamWrapper\Manager;
use TYPO3\PharStreamWrapper\Phar\DeserializationException;
use TYPO3\PharStreamWrapper\Phar\Reader;

/**
 * @internal Experimental implementation of checking against serialized objects in Phar meta-data
 * @internal This functionality has not been 100% pentested...
 */
class PharMetaDataInterceptor implements Assertable
{
    /**
     * Determines whether the according Phar archive contains
     * (potential insecure) serialized objects.
     *
     * @param string $path
     * @param string $command
     * @return bool
     * @throws Exception
     */
    public function assert($path, $command)
    {
        if ($this->baseFileDoesNotHaveMetaDataIssues($path)) {
            return true;
        }
        throw new Exception(
            sprintf(
                'Problematic meta-data in "%s"',
                $path
            ),
            1539632368
        );
    }

    /**
     * @param string $path
     * @return bool
     */
    private function baseFileDoesNotHaveMetaDataIssues($path)
    {
        $invocation = Manager::instance()->resolve($path);
        if ($invocation === null) {
            return false;
        }
        // directly return in case invocation was checked before
        if ($invocation->getVariable(__CLASS__) === true) {
            return true;
        }
        // otherwise analyze meta-data
        try {
            $reader = new Reader($invocation->getBaseName());
            $reader->resolveContainer()->getManifest()->deserializeMetaData();
            $invocation->setVariable(__CLASS__, true);
        } catch (DeserializationException $exception) {
            return false;
        }
        return true;
    }
}
vendor/typo3/phar-stream-wrapper/src/Collectable.php000064400000001663152177723700016552 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Resolver\PharInvocation;

interface Collectable
{
    /**
     * @param PharInvocation $invocation
     * @return bool
     */
    public function has(PharInvocation $invocation);

    /**
     * @param PharInvocation $invocation
     * @param null $flags
     * @return bool
     */
    public function collect(PharInvocation $invocation, $flags = null);

    /**
     * @param callable $callback
     * @param bool $reverse
     * @return null|PharInvocation
     */
    public function findByCallback($callback, $reverse = false);
}
vendor/typo3/phar-stream-wrapper/src/Resolvable.php000064400000001155152177723700016433 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Resolver\PharInvocation;

interface Resolvable
{
    /**
     * @param string $path
     * @param null|int $flags
     * @return null|PharInvocation
     */
    public function resolve($path, $flags = null);
}
vendor/typo3/phar-stream-wrapper/src/Phar/Stub.php000064400000002613152177723700016144 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

/**
 * @internal Experimental implementation of Phar archive internals
 */
class Stub
{
    /**
     * @param string $content
     * @return self
     */
    public static function fromContent($content)
    {
        $target = new static();
        $target->content = $content;

        if (
            stripos($content, 'Phar::mapPhar(') !== false
            && preg_match('#Phar\:\:mapPhar\(([^)]+)\)#', $content, $matches)
        ) {
            // remove spaces, single & double quotes
            // @todo `'my' . 'alias' . '.phar'` is not evaluated here
            $target->mappedAlias = trim($matches[1], ' \'"');
        }

        return $target;
    }

    /**
     * @var string
     */
    private $content;

    /**
     * @var string
     */
    private $mappedAlias = '';

    /**
     * @return string
     */
    public function getContent()
    {
        return $this->content;
    }

    /**
     * @return string
     */
    public function getMappedAlias()
    {
        return $this->mappedAlias;
    }
}
vendor/typo3/phar-stream-wrapper/src/Phar/Manifest.php000064400000007121152177723700016774 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use Brumann\Polyfill\Unserialize;

class Manifest
{
    /**
     * @param string $content
     * @return self
     * @see http://php.net/manual/en/phar.fileformat.phar.php
     */
    public static function fromContent($content)
    {
        $target = new static();
        $target->manifestLength = Reader::resolveFourByteLittleEndian($content, 0);
        $target->amountOfFiles = Reader::resolveFourByteLittleEndian($content, 4);
        $target->flags = Reader::resolveFourByteLittleEndian($content, 10);
        $target->aliasLength = Reader::resolveFourByteLittleEndian($content, 14);
        $target->alias = substr($content, 18, $target->aliasLength);
        $target->metaDataLength = Reader::resolveFourByteLittleEndian($content, 18 + $target->aliasLength);
        $target->metaData = substr($content, 22 + $target->aliasLength, $target->metaDataLength);

        $apiVersionNibbles = Reader::resolveTwoByteBigEndian($content, 8);
        $target->apiVersion = implode('.', array(
            ($apiVersionNibbles & 0xf000) >> 12,
            ($apiVersionNibbles & 0x0f00) >> 8,
            ($apiVersionNibbles & 0x00f0) >> 4,
        ));

        return $target;
    }

    /**
     * @var int
     */
    private $manifestLength;

    /**
     * @var int
     */
    private $amountOfFiles;

    /**
     * @var string
     */
    private $apiVersion;

    /**
     * @var int
     */
    private $flags;

    /**
     * @var int
     */
    private $aliasLength;

    /**
     * @var string
     */
    private $alias;

    /**
     * @var int
     */
    private $metaDataLength;

    /**
     * @var string
     */
    private $metaData;

    /**
     * Avoid direct instantiation.
     */
    private function __construct()
    {
    }

    /**
     * @return int
     */
    public function getManifestLength()
    {
        return $this->manifestLength;
    }

    /**
     * @return int
     */
    public function getAmountOfFiles()
    {
        return $this->amountOfFiles;
    }

    /**
     * @return string
     */
    public function getApiVersion()
    {
        return $this->apiVersion;
    }

    /**
     * @return int
     */
    public function getFlags()
    {
        return $this->flags;
    }

    /**
     * @return int
     */
    public function getAliasLength()
    {
        return $this->aliasLength;
    }

    /**
     * @return string
     */
    public function getAlias()
    {
        return $this->alias;
    }

    /**
     * @return int
     */
    public function getMetaDataLength()
    {
        return $this->metaDataLength;
    }

    /**
     * @return string
     */
    public function getMetaData()
    {
        return $this->metaData;
    }

    /**
     * @return mixed|null
     */
    public function deserializeMetaData()
    {
        if (empty($this->metaData)) {
            return null;
        }

        $result = Unserialize::unserialize($this->metaData, array('allowed_classes' => false));

        $serialized = json_encode($result);
        if (strpos($serialized, '__PHP_Incomplete_Class_Name') !== false) {
            throw new DeserializationException(
                'Meta-data contains serialized object',
                1539623382
            );
        }

        return $result;
    }
}
vendor/typo3/phar-stream-wrapper/src/Phar/Container.php000064400000002143152177723700017147 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

class Container
{
    /**
     * @var Stub
     */
    private $stub;

    /**
     * @var Manifest
     */
    private $manifest;

    /**
     * @param Stub $stub
     * @param Manifest $manifest
     */
    public function __construct(Stub $stub, Manifest $manifest)
    {
        $this->stub = $stub;
        $this->manifest = $manifest;
    }

    /**
     * @return Stub
     */
    public function getStub()
    {
        return $this->stub;
    }

    /**
     * @return Manifest
     */
    public function getManifest()
    {
        return $this->manifest;
    }

    /**
     * @return string
     */
    public function getAlias()
    {
        return $this->manifest->getAlias() ?: $this->stub->getMappedAlias();
    }
}
vendor/typo3/phar-stream-wrapper/src/Phar/Reader.php000064400000017212152177723700016432 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

class Reader
{
    /**
     * @var string
     */
    private $fileName;

    /**
     * Mime-type in order to use zlib, bzip2 or no compression.
     * In case ext-fileinfo is not present only the relevant types
     * 'application/x-gzip' and 'application/x-bzip2' are assigned
     * to this class property.
     *
     * @var string
     */
    private $fileType;

    /**
     * @param string $fileName
     */
    public function __construct($fileName)
    {
        if (strpos($fileName, '://') !== false) {
            throw new ReaderException(
                'File name must not contain stream prefix',
                1539623708
            );
        }

        $this->fileName = $fileName;
        $this->fileType = $this->determineFileType();
    }

    /**
     * @return Container
     */
    public function resolveContainer()
    {
        $data = $this->extractData($this->resolveStream() . $this->fileName);

        if ($data['stubContent'] === null) {
            throw new ReaderException(
                'Cannot resolve stub',
                1547807881
            );
        }
        if ($data['manifestContent'] === null || $data['manifestLength'] === null) {
            throw new ReaderException(
                'Cannot resolve manifest',
                1547807882
            );
        }
        if (strlen($data['manifestContent']) < $data['manifestLength']) {
            throw new ReaderException(
                sprintf(
                    'Exected manifest length %d, got %d',
                    strlen($data['manifestContent']),
                    $data['manifestLength']
                ),
                1547807883
            );
        }

        return new Container(
            Stub::fromContent($data['stubContent']),
            Manifest::fromContent($data['manifestContent'])
        );
    }

    /**
     * @param string $fileName e.g. '/path/file.phar' or 'compress.zlib:///path/file.phar'
     * @return array
     */
    private function extractData($fileName)
    {
        $stubContent = null;
        $manifestContent = null;
        $manifestLength = null;

        $resource = fopen($fileName, 'r');
        if (!is_resource($resource)) {
            throw new ReaderException(
                sprintf('Resource %s could not be opened', $fileName),
                1547902055
            );
        }

        while (!feof($resource)) {
            $line = fgets($resource);
            // stop reading file when manifest can be extracted
            if ($manifestLength !== null && $manifestContent !== null && strlen($manifestContent) >= $manifestLength) {
                break;
            }

            $manifestPosition = strpos($line, '__HALT_COMPILER();');

            // first line contains start of manifest
            if ($stubContent === null && $manifestContent === null && $manifestPosition !== false) {
                $stubContent = substr($line, 0, $manifestPosition - 1);
                $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
                $manifestLength = $this->resolveManifestLength($manifestContent);
            // line contains start of stub
            } elseif ($stubContent === null) {
                $stubContent = $line;
            // line contains start of manifest
            } elseif ($manifestContent === null && $manifestPosition !== false) {
                $manifestContent = preg_replace('#^.*__HALT_COMPILER\(\);(?>[ \n]\?>(?>\r\n|\n)?)?#', '', $line);
                $manifestLength = $this->resolveManifestLength($manifestContent);
            // manifest has been started (thus is cannot be stub anymore), add content
            } elseif ($manifestContent !== null) {
                $manifestContent .= $line;
                $manifestLength = $this->resolveManifestLength($manifestContent);
            // stub has been started (thus cannot be manifest here, yet), add content
            } elseif ($stubContent !== null) {
                $stubContent .= $line;
            }
        }
        fclose($resource);

        return array(
            'stubContent' => $stubContent,
            'manifestContent' => $manifestContent,
            'manifestLength' => $manifestLength,
        );
    }

    /**
     * Resolves stream in order to handle compressed Phar archives.
     *
     * @return string
     */
    private function resolveStream()
    {
        if ($this->fileType === 'application/x-gzip') {
            return 'compress.zlib://';
        } elseif ($this->fileType === 'application/x-bzip2') {
            return 'compress.bzip2://';
        }
        return '';
    }

    /**
     * @return string
     */
    private function determineFileType()
    {
        if (class_exists('\\finfo')) {
            $fileInfo = new \finfo();
            return $fileInfo->file($this->fileName, FILEINFO_MIME_TYPE);
        }
        return $this->determineFileTypeByHeader();
    }

    /**
     * In case ext-fileinfo is not present only the relevant types
     * 'application/x-gzip' and 'application/x-bzip2' are resolved.
     *
     * @return string
     */
    private function determineFileTypeByHeader()
    {
        $resource = fopen($this->fileName, 'r');
        if (!is_resource($resource)) {
            throw new ReaderException(
                sprintf('Resource %s could not be opened', $this->fileName),
                1557753055
            );
        }
        $header = fgets($resource, 4);
        fclose($resource);
        $mimeType = '';
        if (strpos($header, "\x42\x5a\x68") === 0) {
            $mimeType = 'application/x-bzip2';
        } elseif (strpos($header, "\x1f\x8b") === 0) {
            $mimeType = 'application/x-gzip';
        }
        return $mimeType;
    }

    /**
     * @param string $content
     * @return int|null
     */
    private function resolveManifestLength($content)
    {
        if (strlen($content) < 4) {
            return null;
        }
        return static::resolveFourByteLittleEndian($content, 0);
    }

    /**
     * @param string $content
     * @param int $start
     * @return int
     */
    public static function resolveFourByteLittleEndian($content, $start)
    {
        $payload = substr($content, $start, 4);
        if (!is_string($payload)) {
            throw new ReaderException(
                sprintf('Cannot resolve value at offset %d', $start),
                1539614260
            );
        }

        $value = unpack('V', $payload);
        if (!isset($value[1])) {
            throw new ReaderException(
                sprintf('Cannot resolve value at offset %d', $start),
                1539614261
            );
        }
        return $value[1];
    }

    /**
     * @param string $content
     * @param int $start
     * @return int
     */
    public static function resolveTwoByteBigEndian($content, $start)
    {
        $payload = substr($content, $start, 2);
        if (!is_string($payload)) {
            throw new ReaderException(
                sprintf('Cannot resolve value at offset %d', $start),
                1539614263
            );
        }

        $value = unpack('n', $payload);
        if (!isset($value[1])) {
            throw new ReaderException(
                sprintf('Cannot resolve value at offset %d', $start),
                1539614264
            );
        }
        return $value[1];
    }
}
vendor/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php000064400000000741152177723700022234 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Exception;

class DeserializationException extends Exception
{
}
vendor/typo3/phar-stream-wrapper/src/Phar/ReaderException.php000064400000000730152177723700020306 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Phar;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Exception;

class ReaderException extends Exception
{
}
vendor/typo3/phar-stream-wrapper/src/PharStreamWrapper.php000064400000030607152177723700017750 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Resolver\PharInvocation;

class PharStreamWrapper
{
    /**
     * Internal stream constants that are not exposed to PHP, but used...
     * @see https://github.com/php/php-src/blob/e17fc0d73c611ad0207cac8a4a01ded38251a7dc/main/php_streams.h
     */
    const STREAM_OPEN_FOR_INCLUDE = 128;

    /**
     * @var resource
     */
    public $context;

    /**
     * @var resource
     */
    protected $internalResource;

    /**
     * @var PharInvocation
     */
    protected $invocation;

    /**
     * @return bool
     */
    public function dir_closedir()
    {
        if (!is_resource($this->internalResource)) {
            return false;
        }

        $this->invokeInternalStreamWrapper(
            'closedir',
            $this->internalResource
        );
        return !is_resource($this->internalResource);
    }

    /**
     * @param string $path
     * @param int $options
     * @return bool
     */
    public function dir_opendir($path, $options)
    {
        $this->assert($path, Behavior::COMMAND_DIR_OPENDIR);
        $this->internalResource = $this->invokeInternalStreamWrapper(
            'opendir',
            $path,
            $this->context
        );
        return is_resource($this->internalResource);
    }

    /**
     * @return string|false
     */
    public function dir_readdir()
    {
        return $this->invokeInternalStreamWrapper(
            'readdir',
            $this->internalResource
        );
    }

    /**
     * @return bool
     */
    public function dir_rewinddir()
    {
        if (!is_resource($this->internalResource)) {
            return false;
        }

        $this->invokeInternalStreamWrapper(
            'rewinddir',
            $this->internalResource
        );
        return is_resource($this->internalResource);
    }

    /**
     * @param string $path
     * @param int $mode
     * @param int $options
     * @return bool
     */
    public function mkdir($path, $mode, $options)
    {
        $this->assert($path, Behavior::COMMAND_MKDIR);
        return $this->invokeInternalStreamWrapper(
            'mkdir',
            $path,
            $mode,
            (bool) ($options & STREAM_MKDIR_RECURSIVE),
            $this->context
        );
    }

    /**
     * @param string $path_from
     * @param string $path_to
     * @return bool
     */
    public function rename($path_from, $path_to)
    {
        $this->assert($path_from, Behavior::COMMAND_RENAME);
        $this->assert($path_to, Behavior::COMMAND_RENAME);
        return $this->invokeInternalStreamWrapper(
            'rename',
            $path_from,
            $path_to,
            $this->context
        );
    }

    /**
     * @param string $path
     * @param int $options
     * @return bool
     */
    public function rmdir($path, $options)
    {
        $this->assert($path, Behavior::COMMAND_RMDIR);
        return $this->invokeInternalStreamWrapper(
            'rmdir',
            $path,
            $this->context
        );
    }

    /**
     * @param int $cast_as
     */
    public function stream_cast($cast_as)
    {
        throw new Exception(
            'Method stream_select() cannot be used',
            1530103999
        );
    }

    public function stream_close()
    {
        $this->invokeInternalStreamWrapper(
            'fclose',
            $this->internalResource
        );
    }

    /**
     * @return bool
     */
    public function stream_eof()
    {
        return $this->invokeInternalStreamWrapper(
            'feof',
            $this->internalResource
        );
    }

    /**
     * @return bool
     */
    public function stream_flush()
    {
        return $this->invokeInternalStreamWrapper(
            'fflush',
            $this->internalResource
        );
    }

    /**
     * @param int $operation
     * @return bool
     */
    public function stream_lock($operation)
    {
        return $this->invokeInternalStreamWrapper(
            'flock',
            $this->internalResource,
            $operation
        );
    }

    /**
     * @param string $path
     * @param int $option
     * @param string|int $value
     * @return bool
     */
    public function stream_metadata($path, $option, $value)
    {
        $this->assert($path, Behavior::COMMAND_STEAM_METADATA);
        if ($option === STREAM_META_TOUCH) {
            return call_user_func_array(
                array($this, 'invokeInternalStreamWrapper'),
                array_merge(array('touch', $path), (array) $value)
            );
        }
        if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) {
            return $this->invokeInternalStreamWrapper(
                'chown',
                $path,
                $value
            );
        }
        if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) {
            return $this->invokeInternalStreamWrapper(
                'chgrp',
                $path,
                $value
            );
        }
        if ($option === STREAM_META_ACCESS) {
            return $this->invokeInternalStreamWrapper(
                'chmod',
                $path,
                $value
            );
        }
        return false;
    }

    /**
     * @param string $path
     * @param string $mode
     * @param int $options
     * @param string|null $opened_path
     * @return bool
     */
    public function stream_open(
        $path,
        $mode,
        $options,
        &$opened_path = null
    ) {
        $this->assert($path, Behavior::COMMAND_STREAM_OPEN);
        $arguments = array($path, $mode, (bool) ($options & STREAM_USE_PATH));
        // only add stream context for non include/require calls
        if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) {
            $arguments[] = $this->context;
        // work around https://bugs.php.net/bug.php?id=66569
        // for including files from Phar stream with OPcache enabled
        } else {
            Helper::resetOpCache();
        }
        $this->internalResource = call_user_func_array(
            array($this, 'invokeInternalStreamWrapper'),
            array_merge(array('fopen'), $arguments)
        );
        if (!is_resource($this->internalResource)) {
            return false;
        }
        if ($opened_path !== null) {
            $metaData = stream_get_meta_data($this->internalResource);
            $opened_path = $metaData['uri'];
        }
        return true;
    }

    /**
     * @param int $count
     * @return string
     */
    public function stream_read($count)
    {
        return $this->invokeInternalStreamWrapper(
            'fread',
            $this->internalResource,
            $count
        );
    }

    /**
     * @param int $offset
     * @param int $whence
     * @return bool
     */
    public function stream_seek($offset, $whence = SEEK_SET)
    {
        return $this->invokeInternalStreamWrapper(
            'fseek',
            $this->internalResource,
            $offset,
            $whence
        ) !== -1;
    }

    /**
     * @param int $option
     * @param int $arg1
     * @param int $arg2
     * @return bool
     */
    public function stream_set_option($option, $arg1, $arg2)
    {
        if ($option === STREAM_OPTION_BLOCKING) {
            return $this->invokeInternalStreamWrapper(
                'stream_set_blocking',
                $this->internalResource,
                $arg1
            );
        }
        if ($option === STREAM_OPTION_READ_TIMEOUT) {
            return $this->invokeInternalStreamWrapper(
                'stream_set_timeout',
                $this->internalResource,
                $arg1,
                $arg2
            );
        }
        if ($option === STREAM_OPTION_WRITE_BUFFER) {
            return $this->invokeInternalStreamWrapper(
                'stream_set_write_buffer',
                $this->internalResource,
                $arg2
            ) === 0;
        }
        return false;
    }

    /**
     * @return array
     */
    public function stream_stat()
    {
        return $this->invokeInternalStreamWrapper(
            'fstat',
            $this->internalResource
        );
    }

    /**
     * @return int
     */
    public function stream_tell()
    {
        return $this->invokeInternalStreamWrapper(
            'ftell',
            $this->internalResource
        );
    }

    /**
     * @param int $new_size
     * @return bool
     */
    public function stream_truncate($new_size)
    {
        return $this->invokeInternalStreamWrapper(
            'ftruncate',
            $this->internalResource,
            $new_size
        );
    }

    /**
     * @param string $data
     * @return int
     */
    public function stream_write($data)
    {
        return $this->invokeInternalStreamWrapper(
            'fwrite',
            $this->internalResource,
            $data
        );
    }

    /**
     * @param string $path
     * @return bool
     */
    public function unlink($path)
    {
        $this->assert($path, Behavior::COMMAND_UNLINK);
        return $this->invokeInternalStreamWrapper(
            'unlink',
            $path,
            $this->context
        );
    }

    /**
     * @param string $path
     * @param int $flags
     * @return array|false
     */
    public function url_stat($path, $flags)
    {
        $this->assert($path, Behavior::COMMAND_URL_STAT);
        $functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat';
        return $this->invokeInternalStreamWrapper($functionName, $path);
    }

    /**
     * @param string $path
     * @param string $command
     */
    protected function assert($path, $command)
    {
        if (Manager::instance()->assert($path, $command) === true) {
            $this->collectInvocation($path);
            return;
        }

        throw new Exception(
            sprintf(
                'Denied invocation of "%s" for command "%s"',
                $path,
                $command
            ),
            1535189880
        );
    }

    /**
     * @param string $path
     */
    protected function collectInvocation($path)
    {
        if (isset($this->invocation)) {
            return;
        }

        $manager = Manager::instance();
        $this->invocation = $manager->resolve($path);
        if ($this->invocation === null) {
            throw new Exception(
                'Expected invocation could not be resolved',
                1556389591
            );
        }
        // confirm, previous interceptor(s) validated invocation
        $this->invocation->confirm();
        $collection = $manager->getCollection();
        if (!$collection->has($this->invocation)) {
            $collection->collect($this->invocation);
        }
    }

    /**
     * @return Manager|Assertable
     * @deprecated Use Manager::instance() directly
     */
    protected function resolveAssertable()
    {
        return Manager::instance();
    }

    /**
     * Invokes commands on the native PHP Phar stream wrapper.
     *
     * @param string $functionName
     * @param mixed ...$arguments
     * @return mixed
     */
    private function invokeInternalStreamWrapper($functionName)
    {
        $arguments = func_get_args();
        array_shift($arguments);
        $silentExecution = $functionName{0} === '@';
        $functionName = ltrim($functionName, '@');
        $this->restoreInternalSteamWrapper();

        try {
            if ($silentExecution) {
                $result = @call_user_func_array($functionName, $arguments);
            } else {
                $result = call_user_func_array($functionName, $arguments);
            }
        } catch (\Exception $exception) {
            $this->registerStreamWrapper();
            throw $exception;
        } catch (\Throwable $throwable) {
            $this->registerStreamWrapper();
            throw $throwable;
        }

        $this->registerStreamWrapper();
        return $result;
    }

    private function restoreInternalSteamWrapper()
    {
        stream_wrapper_restore('phar');
    }

    private function registerStreamWrapper()
    {
        stream_wrapper_unregister('phar');
        stream_wrapper_register('phar', get_class($this));
    }
}
vendor/typo3/phar-stream-wrapper/src/Assertable.php000064400000001042152177723700016415 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

interface Assertable
{
    /**
     * @param string $path
     * @param string $command
     * @return bool
     */
    public function assert($path, $command);
}
vendor/typo3/phar-stream-wrapper/src/Manager.php000064400000006012152177723700015704 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Resolver\PharInvocation;
use TYPO3\PharStreamWrapper\Resolver\PharInvocationCollection;
use TYPO3\PharStreamWrapper\Resolver\PharInvocationResolver;

class Manager
{
    /**
     * @var self
     */
    private static $instance;

    /**
     * @var Behavior
     */
    private $behavior;

    /**
     * @var Resolvable
     */
    private $resolver;

    /**
     * @var Collectable
     */
    private $collection;

    /**
     * @param Behavior $behaviour
     * @param Resolvable $resolver
     * @param Collectable $collection
     * @return self
     */
    public static function initialize(
        Behavior $behaviour,
        Resolvable $resolver = null,
        Collectable $collection = null
    ) {
        if (self::$instance === null) {
            self::$instance = new self($behaviour, $resolver, $collection);
            return self::$instance;
        }
        throw new \LogicException(
            'Manager can only be initialized once',
            1535189871
        );
    }

    /**
     * @return self
     */
    public static function instance()
    {
        if (self::$instance !== null) {
            return self::$instance;
        }
        throw new \LogicException(
            'Manager needs to be initialized first',
            1535189872
        );
    }

    /**
     * @return bool
     */
    public static function destroy()
    {
        if (self::$instance === null) {
            return false;
        }
        self::$instance = null;
        return true;
    }

    /**
     * @param Behavior $behaviour
     * @param Resolvable $resolver
     * @param Collectable $collection
     */
    private function __construct(
        Behavior $behaviour,
        Resolvable $resolver = null,
        Collectable $collection = null
    ) {
        if ($collection === null) {
            $collection = new PharInvocationCollection();
        }
        if ($resolver === null) {
            $resolver = new PharInvocationResolver();
        }
        $this->collection = $collection;
        $this->resolver = $resolver;
        $this->behavior = $behaviour;
    }

    /**
     * @param string $path
     * @param string $command
     * @return bool
     */
    public function assert($path, $command)
    {
        return $this->behavior->assert($path, $command);
    }

    /**
     * @param string $path
     * @param null|int $flags
     * @return null|PharInvocation
     */
    public function resolve($path, $flags = null)
    {
        return $this->resolver->resolve($path, $flags);
    }

    /**
     * @return Collectable
     */
    public function getCollection()
    {
        return $this->collection;
    }
}
vendor/typo3/phar-stream-wrapper/src/Resolver/PharInvocation.php000064400000004654152177723700021071 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Resolver;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Exception;

class PharInvocation
{
    /**
     * @var string
     */
    private $baseName;

    /**
     * @var string
     */
    private $alias;

    /**
     * @var bool
     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
     */
    private $confirmed = false;

    /**
     * Arbitrary variables to be used by interceptors as registry
     * (e.g. in order to avoid duplicate processing and assertions)
     *
     * @var array
     */
    private $variables;

    /**
     * @param string $baseName
     * @param string $alias
     */
    public function __construct($baseName, $alias = '')
    {
        if ($baseName === '') {
            throw new Exception(
                'Base-name cannot be empty',
                1551283689
            );
        }
        $this->baseName = $baseName;
        $this->alias = $alias;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->baseName;
    }

    /**
     * @return string
     */
    public function getBaseName()
    {
        return $this->baseName;
    }

    /**
     * @return null|string
     */
    public function getAlias()
    {
        return $this->alias;
    }

    /**
     * @return bool
     */
    public function isConfirmed()
    {
        return $this->confirmed;
    }

    public function confirm()
    {
        $this->confirmed = true;
    }

    /**
     * @param string $name
     * @return mixed|null
     */
    public function getVariable($name)
    {
        if (!isset($this->variables[$name])) {
            return null;
        }
        return $this->variables[$name];
    }

    /**
     * @param string $name
     * @param mixed $value
     */
    public function setVariable($name, $value)
    {
        $this->variables[$name] = $value;
    }

    /**
     * @param PharInvocation $other
     * @return bool
     */
    public function equals(PharInvocation $other)
    {
        return $other->baseName === $this->baseName
            && $other->alias === $this->alias;
    }
}vendor/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php000064400000011145152177723700023076 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Resolver;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Collectable;

class PharInvocationCollection implements Collectable
{
    const UNIQUE_INVOCATION = 1;
    const UNIQUE_BASE_NAME = 2;
    const DUPLICATE_ALIAS_WARNING = 32;

    /**
     * @var PharInvocation[]
     */
    private $invocations = array();

    /**
     * @param PharInvocation $invocation
     * @return bool
     */
    public function has(PharInvocation $invocation)
    {
        return in_array($invocation, $this->invocations, true);
    }

    /**
     * @param PharInvocation $invocation
     * @param null|int $flags
     * @return bool
     */
    public function collect(PharInvocation $invocation, $flags = null)
    {
        if ($flags === null) {
            $flags = static::UNIQUE_INVOCATION | static::DUPLICATE_ALIAS_WARNING;
        }
        if ($invocation->getBaseName() === ''
            || $invocation->getAlias() === ''
            || !$this->assertUniqueBaseName($invocation, $flags)
            || !$this->assertUniqueInvocation($invocation, $flags)
        ) {
            return false;
        }
        if ($flags & static::DUPLICATE_ALIAS_WARNING) {
            $this->triggerDuplicateAliasWarning($invocation);
        }

        $this->invocations[] = $invocation;
        return true;
    }

    /**
     * @param callable $callback
     * @param bool $reverse
     * @return null|PharInvocation
     */
    public function findByCallback($callback, $reverse = false)
    {
        foreach ($this->getInvocations($reverse) as $invocation) {
            if (call_user_func($callback, $invocation) === true) {
                return $invocation;
            }
        }
        return null;
    }

    /**
     * Asserts that base-name is unique. This disallows having multiple invocations for
     * same base-name but having different alias names.
     *
     * @param PharInvocation $invocation
     * @param int $flags
     * @return bool
     */
    private function assertUniqueBaseName(PharInvocation $invocation, $flags)
    {
        if (!($flags & static::UNIQUE_BASE_NAME)) {
            return true;
        }
        return $this->findByCallback(
                function (PharInvocation $candidate) use ($invocation) {
                    return $candidate->getBaseName() === $invocation->getBaseName();
                }
            ) === null;
    }

    /**
     * Asserts that combination of base-name and alias is unique. This allows having multiple
     * invocations for same base-name but having different alias names (for whatever reason).
     *
     * @param PharInvocation $invocation
     * @param int $flags
     * @return bool
     */
    private function assertUniqueInvocation(PharInvocation $invocation, $flags)
    {
        if (!($flags & static::UNIQUE_INVOCATION)) {
            return true;
        }
        return $this->findByCallback(
                function (PharInvocation $candidate) use ($invocation) {
                    return $candidate->equals($invocation);
                }
            ) === null;
    }

    /**
     * Triggers warning for invocations with same alias and same confirmation state.
     *
     * @param PharInvocation $invocation
     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
     */
    private function triggerDuplicateAliasWarning(PharInvocation $invocation)
    {
        $sameAliasInvocation = $this->findByCallback(
            function (PharInvocation $candidate) use ($invocation) {
                return $candidate->isConfirmed() === $invocation->isConfirmed()
                    && $candidate->getAlias() === $invocation->getAlias();
            },
            true
        );
        if ($sameAliasInvocation === null) {
            return;
        }
        trigger_error(
            sprintf(
                'Alias %s cannot be used by %s, already used by %s',
                $invocation->getAlias(),
                $invocation->getBaseName(),
                $sameAliasInvocation->getBaseName()
            ),
            E_USER_WARNING
        );
    }

    /**
     * @param bool $reverse
     * @return PharInvocation[]
     */
    private function getInvocations($reverse = false)
    {
        if ($reverse) {
            return array_reverse($this->invocations);
        }
        return $this->invocations;
    }
}vendor/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php000064400000016677152177723700022623 0ustar00<?php
namespace TYPO3\PharStreamWrapper\Resolver;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

use TYPO3\PharStreamWrapper\Helper;
use TYPO3\PharStreamWrapper\Manager;
use TYPO3\PharStreamWrapper\Phar\Reader;
use TYPO3\PharStreamWrapper\Phar\ReaderException;
use TYPO3\PharStreamWrapper\Resolvable;

class PharInvocationResolver implements Resolvable
{
    const RESOLVE_REALPATH = 1;
    const RESOLVE_ALIAS = 2;
    const ASSERT_INTERNAL_INVOCATION = 32;

    /**
     * @var string[]
     */
    private $invocationFunctionNames = array(
        'include',
        'include_once',
        'require',
        'require_once'
    );

    /**
     * Contains resolved base names in order to reduce file IO.
     *
     * @var string[]
     */
    private $baseNames = array();

    /**
     * Resolves PharInvocation value object (baseName and optional alias).
     *
     * Phar aliases are intended to be used only inside Phar archives, however
     * PharStreamWrapper needs this information exposed outside of Phar as well
     * It is possible that same alias is used for different $baseName values.
     * That's why PharInvocationCollection behaves like a stack when resolving
     * base-name for a given alias. On the other hand it is not possible that
     * one $baseName is referring to multiple aliases.
     * @see https://secure.php.net/manual/en/phar.setalias.php
     * @see https://secure.php.net/manual/en/phar.mapphar.php
     *
     * @param string $path
     * @param int|null $flags
     * @return null|PharInvocation
     */
    public function resolve($path, $flags = null)
    {
        $hasPharPrefix = Helper::hasPharPrefix($path);
        if ($flags === null) {
            $flags = static::RESOLVE_REALPATH | static::RESOLVE_ALIAS;
        }

        if ($hasPharPrefix && $flags & static::RESOLVE_ALIAS) {
            $invocation = $this->findByAlias($path);
            if ($invocation !== null) {
                return $invocation;
            }
        }

        $baseName = $this->resolveBaseName($path, $flags);
        if ($baseName === null) {
            return null;
        }

        if ($flags & static::RESOLVE_REALPATH) {
            $baseName = $this->baseNames[$baseName];
        }

        return $this->retrieveInvocation($baseName, $flags);
    }

    /**
     * Retrieves PharInvocation, either existing in collection or created on demand
     * with resolving a potential alias name used in the according Phar archive.
     *
     * @param string $baseName
     * @param int $flags
     * @return PharInvocation
     */
    private function retrieveInvocation($baseName, $flags)
    {
        $invocation = $this->findByBaseName($baseName);
        if ($invocation !== null) {
            return $invocation;
        }

        if ($flags & static::RESOLVE_ALIAS) {
            $reader = new Reader($baseName);
            $alias = $reader->resolveContainer()->getAlias();
        } else {
            $alias = '';
        }
        // add unconfirmed(!) new invocation to collection
        $invocation = new PharInvocation($baseName, $alias);
        Manager::instance()->getCollection()->collect($invocation);
        return $invocation;
    }

    /**
     * @param string $path
     * @param int $flags
     * @return null|string
     */
    private function resolveBaseName($path, $flags)
    {
        $baseName = $this->findInBaseNames($path);
        if ($baseName !== null) {
            return $baseName;
        }

        $baseName = Helper::determineBaseFile($path);
        if ($baseName !== null) {
            $this->addBaseName($baseName);
            return $baseName;
        }

        $possibleAlias = $this->resolvePossibleAlias($path);
        if (!($flags & static::RESOLVE_ALIAS) || $possibleAlias === null) {
            return null;
        }

        $trace = debug_backtrace();
        foreach ($trace as $item) {
            if (!isset($item['function']) || !isset($item['args'][0])
                || !in_array($item['function'], $this->invocationFunctionNames, true)) {
                continue;
            }
            $currentPath = $item['args'][0];
            if (Helper::hasPharPrefix($currentPath)) {
                continue;
            }
            $currentBaseName = Helper::determineBaseFile($currentPath);
            if ($currentBaseName === null) {
                continue;
            }
            // ensure the possible alias name (how we have been called initially) matches
            // the resolved alias name that was retrieved by the current possible base name
            try {
                $reader = new Reader($currentBaseName);
                $currentAlias = $reader->resolveContainer()->getAlias();
            } catch (ReaderException $exception) {
                // most probably that was not a Phar file
                continue;
            }
            if (empty($currentAlias) || $currentAlias !== $possibleAlias) {
                continue;
            }
            $this->addBaseName($currentBaseName);
            return $currentBaseName;
        }

        return null;
    }

    /**
     * @param string $path
     * @return null|string
     */
    private function resolvePossibleAlias($path)
    {
        $normalizedPath = Helper::normalizePath($path);
        return strstr($normalizedPath, '/', true) ?: null;
    }

    /**
     * @param string $baseName
     * @return null|PharInvocation
     */
    private function findByBaseName($baseName)
    {
        return Manager::instance()->getCollection()->findByCallback(
            function (PharInvocation $candidate) use ($baseName) {
                return $candidate->getBaseName() === $baseName;
            },
            true
        );
    }

    /**
     * @param string $path
     * @return null|string
     */
    private function findInBaseNames($path)
    {
        // return directly if the resolved base name was submitted
        if (in_array($path, $this->baseNames, true)) {
            return $path;
        }

        $parts = explode('/', Helper::normalizePath($path));

        while (count($parts)) {
            $currentPath = implode('/', $parts);
            if (isset($this->baseNames[$currentPath])) {
                return $currentPath;
            }
            array_pop($parts);
        }

        return null;
    }

    /**
     * @param string $baseName
     */
    private function addBaseName($baseName)
    {
        if (isset($this->baseNames[$baseName])) {
            return;
        }
        $this->baseNames[$baseName] = Helper::normalizeWindowsPath(
            realpath($baseName)
        );
    }

    /**
     * Finds confirmed(!) invocations by alias.
     *
     * @param string $path
     * @return null|PharInvocation
     * @see \TYPO3\PharStreamWrapper\PharStreamWrapper::collectInvocation()
     */
    private function findByAlias($path)
    {
        $possibleAlias = $this->resolvePossibleAlias($path);
        if ($possibleAlias === null) {
            return null;
        }
        return Manager::instance()->getCollection()->findByCallback(
            function (PharInvocation $candidate) use ($possibleAlias) {
                return $candidate->isConfirmed() && $candidate->getAlias() === $possibleAlias;
            },
            true
        );
    }
}
vendor/typo3/phar-stream-wrapper/src/Behavior.php000064400000006213152177723700016074 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

class Behavior implements Assertable
{
    const COMMAND_DIR_OPENDIR = 'dir_opendir';
    const COMMAND_MKDIR = 'mkdir';
    const COMMAND_RENAME = 'rename';
    const COMMAND_RMDIR = 'rmdir';
    const COMMAND_STEAM_METADATA = 'stream_metadata';
    const COMMAND_STREAM_OPEN = 'stream_open';
    const COMMAND_UNLINK = 'unlink';
    const COMMAND_URL_STAT = 'url_stat';

    /**
     * @var string[]
     */
    private $availableCommands = array(
        self::COMMAND_DIR_OPENDIR,
        self::COMMAND_MKDIR,
        self::COMMAND_RENAME,
        self::COMMAND_RMDIR,
        self::COMMAND_STEAM_METADATA,
        self::COMMAND_STREAM_OPEN,
        self::COMMAND_UNLINK,
        self::COMMAND_URL_STAT,
    );

    /**
     * @var Assertable[]
     */
    private $assertions;

    /**
     * @param Assertable $assertable
     * @return static
     */
    public function withAssertion(Assertable $assertable)
    {
        $commands = func_get_args();
        array_shift($commands);
        $this->assertCommands($commands);
        $commands = $commands ?: $this->availableCommands;

        $target = clone $this;
        foreach ($commands as $command) {
            $target->assertions[$command] = $assertable;
        }
        return $target;
    }

    /**
     * @param string $path
     * @param string $command
     * @return bool
     */
    public function assert($path, $command)
    {
        $this->assertCommand($command);
        $this->assertAssertionCompleteness();

        return $this->assertions[$command]->assert($path, $command);
    }

    /**
     * @param array $commands
     */
    private function assertCommands(array $commands)
    {
        $unknownCommands = array_diff($commands, $this->availableCommands);
        if (empty($unknownCommands)) {
            return;
        }
        throw new \LogicException(
            sprintf(
                'Unknown commands: %s',
                implode(', ', $unknownCommands)
            ),
            1535189881
        );
    }

    private function assertCommand($command)
    {
        if (in_array($command, $this->availableCommands, true)) {
            return;
        }
        throw new \LogicException(
            sprintf(
                'Unknown command "%s"',
                $command
            ),
            1535189882
        );
    }

    private function assertAssertionCompleteness()
    {
        $undefinedAssertions = array_diff(
            $this->availableCommands,
            array_keys($this->assertions)
        );
        if (empty($undefinedAssertions)) {
            return;
        }
        throw new \LogicException(
            sprintf(
                'Missing assertions for commands: %s',
                implode(', ', $undefinedAssertions)
            ),
            1535189883
        );
    }
}
vendor/typo3/phar-stream-wrapper/src/Exception.php000064400000000655152177723700016277 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

class Exception extends \RuntimeException
{
}
vendor/typo3/phar-stream-wrapper/src/Helper.php000064400000014010152177723700015546 0ustar00<?php
namespace TYPO3\PharStreamWrapper;

/*
 * This file is part of the TYPO3 project.
 *
 * It is free software; you can redistribute it and/or modify it under the terms
 * of the MIT License (MIT). For the full copyright and license information,
 * please read the LICENSE file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

/**
 * Helper provides low-level tools on file name resolving. However it does not
 * (and should not) maintain any runtime state information. In order to resolve
 * Phar archive paths according resolvers have to be used.
 *
 * @see \TYPO3\PharStreamWrapper\Resolvable::resolve()
 */
class Helper
{
    /*
     * Resets PHP's OPcache if enabled as work-around for issues in `include()`
     * or `require()` calls and OPcache delivering wrong results.
     *
     * @see https://bugs.php.net/bug.php?id=66569
     */
    public static function resetOpCache()
    {
        if (function_exists('opcache_reset')
            && function_exists('opcache_get_status')
        ) {
            $status = opcache_get_status();
            if (!empty($status['opcache_enabled'])) {
                opcache_reset();
            }
        }
    }

    /**
     * Determines base file that can be accessed using the regular file system.
     * For e.g. "phar:///home/user/bundle.phar/content.txt" that would result
     * into "/home/user/bundle.phar".
     *
     * @param string $path
     * @return string|null
     */
    public static function determineBaseFile($path)
    {
        $parts = explode('/', static::normalizePath($path));

        while (count($parts)) {
            $currentPath = implode('/', $parts);
            if (@is_file($currentPath) && realpath($currentPath) !== false) {
                return $currentPath;
            }
            array_pop($parts);
        }

        return null;
    }

    /**
     * @param string $path
     * @return bool
     */
    public static function hasPharPrefix($path)
    {
        return stripos($path, 'phar://') === 0;
    }

    /**
     * @param string $path
     * @return string
     */
    public static function removePharPrefix($path)
    {
        $path = trim($path);
        if (!static::hasPharPrefix($path)) {
            return $path;
        }
        return substr($path, 7);
    }

    /**
     * Normalizes a path, removes phar:// prefix, fixes Windows directory
     * separators. Result is without trailing slash.
     *
     * @param string $path
     * @return string
     */
    public static function normalizePath($path)
    {
        return rtrim(
            static::normalizeWindowsPath(
                static::removePharPrefix($path)
            ),
            '/'
        );
    }

    /**
     * Fixes a path for windows-backslashes and reduces double-slashes to single slashes
     *
     * @param string $path File path to process
     * @return string
     */
    public static function normalizeWindowsPath($path)
    {
        return str_replace('\\', '/', $path);
    }

    /**
     * Resolves all dots, slashes and removes spaces after or before a path...
     *
     * @param string $path Input string
     * @return string Canonical path, always without trailing slash
     */
    private static function getCanonicalPath($path)
    {
        $path = static::normalizeWindowsPath($path);

        $absolutePathPrefix = '';
        if (static::isAbsolutePath($path)) {
            if (static::isWindows() && strpos($path, ':/') === 1) {
                $absolutePathPrefix = substr($path, 0, 3);
                $path = substr($path, 3);
            } else {
                $path = ltrim($path, '/');
                $absolutePathPrefix = '/';
            }
        }

        $pathParts = explode('/', $path);
        $pathPartsLength = count($pathParts);
        for ($partCount = 0; $partCount < $pathPartsLength; $partCount++) {
            // double-slashes in path: remove element
            if ($pathParts[$partCount] === '') {
                array_splice($pathParts, $partCount, 1);
                $partCount--;
                $pathPartsLength--;
            }
            // "." in path: remove element
            if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '.') {
                array_splice($pathParts, $partCount, 1);
                $partCount--;
                $pathPartsLength--;
            }
            // ".." in path:
            if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '..') {
                if ($partCount === 0) {
                    array_splice($pathParts, $partCount, 1);
                    $partCount--;
                    $pathPartsLength--;
                } elseif ($partCount >= 1) {
                    // Rremove this and previous element
                    array_splice($pathParts, $partCount - 1, 2);
                    $partCount -= 2;
                    $pathPartsLength -= 2;
                } elseif ($absolutePathPrefix) {
                    // can't go higher than root dir
                    // simply remove this part and continue
                    array_splice($pathParts, $partCount, 1);
                    $partCount--;
                    $pathPartsLength--;
                }
            }
        }

        return $absolutePathPrefix . implode('/', $pathParts);
    }

    /**
     * Checks if the $path is absolute or relative (detecting either '/' or
     * 'x:/' as first part of string) and returns TRUE if so.
     *
     * @param string $path File path to evaluate
     * @return bool
     */
    private static function isAbsolutePath($path)
    {
        // Path starting with a / is always absolute, on every system
        // On Windows also a path starting with a drive letter is absolute: X:/
        return (isset($path[0]) ? $path[0] : null) === '/'
            || static::isWindows() && (
                strpos($path, ':/') === 1
                || strpos($path, ':\\') === 1
            );
    }

    /**
     * @return bool
     */
    private static function isWindows()
    {
        return stripos(PHP_OS, 'WIN') === 0;
    }
}vendor/typo3/phar-stream-wrapper/composer.json000064400000001470152177723700015557 0ustar00{
    "name": "typo3/phar-stream-wrapper",
    "description": "Interceptors for PHP's native phar:// stream handling",
    "type": "library",
    "license": "MIT",
    "homepage": "https://typo3.org/",
    "keywords": ["php", "phar", "stream-wrapper", "security"],
    "require": {
        "php": "^5.3.3|^7.0",
        "ext-json": "*",
        "brumann/polyfill-unserialize": "^1.0"
    },
    "require-dev": {
        "ext-xdebug": "*",
        "phpunit/phpunit": "^4.8.36"
    },
    "suggest": {
        "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing"
    },
    "autoload": {
        "psr-4": {
            "TYPO3\\PharStreamWrapper\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "TYPO3\\PharStreamWrapper\\Tests\\": "tests/"
        }
    }
}
vendor/brumann/polyfill-unserialize/LICENSE000064400000002056152177723700014710 0ustar00MIT License

Copyright (c) 2016 Denis Brumann

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
vendor/brumann/polyfill-unserialize/src/Unserialize.php000064400000003575152177723700017504 0ustar00<?php

namespace Brumann\Polyfill;

final class Unserialize
{
    /**
     * @see https://secure.php.net/manual/en/function.unserialize.php
     *
     * @param string $serialized Serialized data
     * @param array $options Associative array containing options
     *
     * @return mixed
     */
    public static function unserialize($serialized, array $options = array())
    {
        if (PHP_VERSION_ID >= 70000) {
            return \unserialize($serialized, $options);
        }
        if (!array_key_exists('allowed_classes', $options)) {
            $options['allowed_classes'] = true;
        }
        $allowedClasses = $options['allowed_classes'];
        if (true === $allowedClasses) {
            return \unserialize($serialized);
        }
        if (false === $allowedClasses) {
            $allowedClasses = array();
        }
        if (!is_array($allowedClasses)) {
            trigger_error(
                'unserialize(): allowed_classes option should be array or boolean',
                E_USER_WARNING
            );
            $allowedClasses = array();
        }

        $sanitizedSerialized = preg_replace_callback(
            '/(^|;)O:\d+:"([^"]*)":(\d+):{/',
            function ($match) use ($allowedClasses) {
                list($completeMatch, $leftBorder, $className, $objectSize) = $match;
                if (in_array($className, $allowedClasses)) {
                    return $completeMatch;
                } else {
                    return sprintf(
                        '%sO:22:"__PHP_Incomplete_Class":%d:{s:27:"__PHP_Incomplete_Class_Name";%s',
                        $leftBorder,
                        $objectSize + 1, // size of object + 1 for added string
                        \serialize($className)
                    );
                }
            },
            $serialized
        );

        return \unserialize($sanitizedSerialized);
    }
}
vendor/brumann/polyfill-unserialize/composer.json000064400000001144152177723700016422 0ustar00{
    "name": "brumann/polyfill-unserialize",
    "description": "Backports unserialize options introduced in PHP 7.0 to older PHP versions.",
    "type": "library",
    "license": "MIT",
    "authors": [
        {
            "name": "Denis Brumann",
            "email": "denis.brumann@sensiolabs.de"
        }
    ],
    "autoload": {
        "psr-4": {
            "Brumann\\Polyfill\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\Brumann\\Polyfill\\": "tests/"
        }
    },
    "minimum-stability": "stable",
    "require": {
        "php": "^5.3|^7.0"
    }
}
vendor/web.config000064400000000267152177723700010025 0ustar00<?xml version="1.0"?>
<configuration>
    <system.web>
        <authorization>
            <deny users="*" />              
        </authorization>
    </system.web>
</configuration>vendor/psr/log/LICENSE000064400000002075152177723700010452 0ustar00Copyright (c) 2012 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/psr/log/Psr/Log/LoggerAwareInterface.php000064400000000451152177723700015457 0ustar00<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     *
     * @return void
     */
    public function setLogger(LoggerInterface $logger);
}
vendor/psr/log/Psr/Log/LoggerTrait.php000064400000006437152177723700013674 0ustar00<?php

namespace Psr\Log;

/**
 * This is a simple Logger trait that classes unable to extend AbstractLogger
 * (because they extend another class, etc) can include.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
trait LoggerTrait
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    abstract public function log($level, $message, array $context = array());
}
vendor/psr/log/Psr/Log/LoggerInterface.php000064400000005737152177723700014513 0ustar00<?php

namespace Psr\Log;

/**
 * Describes a logger instance.
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data. The only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array());
}
vendor/psr/log/Psr/Log/LogLevel.php000064400000000520152177723700013145 0ustar00<?php

namespace Psr\Log;

/**
 * Describes log levels.
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}
vendor/psr/log/Psr/Log/InvalidArgumentException.php000064400000000140152177723700016402 0ustar00<?php

namespace Psr\Log;

class InvalidArgumentException extends \InvalidArgumentException
{
}
vendor/psr/log/Psr/Log/AbstractLogger.php000064400000006020152177723700014340 0ustar00<?php

namespace Psr\Log;

/**
 * This is a simple Logger implementation that other Loggers can inherit from.
 *
 * It simply delegates all log-level-specific methods to the `log` method to
 * reduce boilerplate code that a simple Logger that does the same thing with
 * messages regardless of the error level has to implement.
 */
abstract class AbstractLogger implements LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}
vendor/psr/log/Psr/Log/NullLogger.php000064400000001213152177723700013506 0ustar00<?php

namespace Psr\Log;

/**
 * This Logger can be used to avoid conditional log calls.
 *
 * Logging should always be optional, and if no logger is provided to your
 * library creating a NullLogger instance to have something to throw logs at
 * is a good way to avoid littering your code with `if ($this->logger) { }`
 * blocks.
 */
class NullLogger extends AbstractLogger
{
    /**
     * Logs with an arbitrary level.
     *
     * @param mixed  $level
     * @param string $message
     * @param array  $context
     *
     * @return void
     */
    public function log($level, $message, array $context = array())
    {
        // noop
    }
}
vendor/psr/log/Psr/Log/LoggerAwareTrait.php000064400000000615152177723700014644 0ustar00<?php

namespace Psr\Log;

/**
 * Basic Implementation of LoggerAwareInterface.
 */
trait LoggerAwareTrait
{
    /**
     * The logger instance.
     *
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * Sets a logger.
     *
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
}
vendor/psr/container/LICENSE000064400000002171152177723700011650 0ustar00The MIT License (MIT)

Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/psr/container/src/ContainerInterface.php000064400000002112152177723700015701 0ustar00<?php
/**
 * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
 */

namespace Psr\Container;

/**
 * Describes the interface of a container that exposes methods to read its entries.
 */
interface ContainerInterface
{
    /**
     * Finds an entry of the container by its identifier and returns it.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @throws NotFoundExceptionInterface  No entry was found for **this** identifier.
     * @throws ContainerExceptionInterface Error while retrieving the entry.
     *
     * @return mixed Entry.
     */
    public function get($id);

    /**
     * Returns true if the container can return an entry for the given identifier.
     * Returns false otherwise.
     *
     * `has($id)` returning true does not mean that `get($id)` will not throw an exception.
     * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
     *
     * @param string $id Identifier of the entry to look for.
     *
     * @return bool
     */
    public function has($id);
}
vendor/psr/container/src/NotFoundExceptionInterface.php000064400000000400152177723700017370 0ustar00<?php
/**
 * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
 */

namespace Psr\Container;

/**
 * No entry was found in the container.
 */
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
vendor/psr/container/src/ContainerExceptionInterface.php000064400000000370152177723700017564 0ustar00<?php
/**
 * @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
 */

namespace Psr\Container;

/**
 * Base interface representing a generic exception in a container.
 */
interface ContainerExceptionInterface
{
}
vendor/leafo/lessphp/LICENSE000064400000101557152177723700011636 0ustar00For ease of distribution, lessphp 0.4.0 is under a dual license.
You are free to pick which one suits your needs.




MIT LICENSE




Copyright (c) 2013 Leaf Corcoran, http://leafo.net/lessphp

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.




GPL VERSION 3




					GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

vendor/leafo/lessphp/lessc.inc.php000064400000271403152177723700013221 0ustar00<?php

/**
 * lessphp v0.5.0
 * http://leafo.net/lessphp
 *
 * LESS CSS compiler, adapted from http://lesscss.org
 *
 * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
 * Licensed under MIT or GPLv3, see LICENSE
 */


/**
 * The LESS compiler and parser.
 *
 * Converting LESS to CSS is a three stage process. The incoming file is parsed
 * by `lessc_parser` into a syntax tree, then it is compiled into another tree
 * representing the CSS structure by `lessc`. The CSS tree is fed into a
 * formatter, like `lessc_formatter` which then outputs CSS as a string.
 *
 * During the first compile, all values are *reduced*, which means that their
 * types are brought to the lowest form before being dump as strings. This
 * handles math equations, variable dereferences, and the like.
 *
 * The `parse` function of `lessc` is the entry point.
 *
 * In summary:
 *
 * The `lessc` class creates an instance of the parser, feeds it LESS code,
 * then transforms the resulting tree to a CSS tree. This class also holds the
 * evaluation context, such as all available mixins and variables at any given
 * time.
 *
 * The `lessc_parser` class is only concerned with parsing its input.
 *
 * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
 * handling things like indentation.
 */
class lessc {
	static public $VERSION = "v0.5.0";

	static public $TRUE = array("keyword", "true");
	static public $FALSE = array("keyword", "false");

	protected $libFunctions = array();
	protected $registeredVars = array();
	protected $preserveComments = false;

	public $vPrefix = '@'; // prefix of abstract properties
	public $mPrefix = '$'; // prefix of abstract blocks
	public $parentSelector = '&';

	public $importDisabled = false;
	public $importDir = '';

	protected $numberPrecision = null;

	protected $allParsedFiles = array();

	// set to the parser that generated the current line when compiling
	// so we know how to create error messages
	protected $sourceParser = null;
	protected $sourceLoc = null;

	static protected $nextImportId = 0; // uniquely identify imports

	// attempts to find the path of an import url, returns null for css files
	protected function findImport($url) {
		foreach ((array)$this->importDir as $dir) {
			$full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
			if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
				return $file;
			}
		}

		return null;
	}

	protected function fileExists($name) {
		return is_file($name);
	}

	static public function compressList($items, $delim) {
		if (!isset($items[1]) && isset($items[0])) return $items[0];
		else return array('list', $delim, $items);
	}

	static public function preg_quote($what) {
		return preg_quote($what, '/');
	}

	protected function tryImport($importPath, $parentBlock, $out) {
		if ($importPath[0] == "function" && $importPath[1] == "url") {
			$importPath = $this->flattenList($importPath[2]);
		}

		$str = $this->coerceString($importPath);
		if ($str === null) return false;

		$url = $this->compileValue($this->lib_e($str));

		// don't import if it ends in css
		if (substr_compare($url, '.css', -4, 4) === 0) return false;

		$realPath = $this->findImport($url);

		if ($realPath === null) return false;

		if ($this->importDisabled) {
			return array(false, "/* import disabled */");
		}

		if (isset($this->allParsedFiles[realpath($realPath)])) {
			return array(false, null);
		}

		$this->addParsedFile($realPath);
		$parser = $this->makeParser($realPath);
		$root = $parser->parse(file_get_contents($realPath));

		// set the parents of all the block props
		foreach ($root->props as $prop) {
			if ($prop[0] == "block") {
				$prop[1]->parent = $parentBlock;
			}
		}

		// copy mixins into scope, set their parents
		// bring blocks from import into current block
		// TODO: need to mark the source parser	these came from this file
		foreach ($root->children as $childName => $child) {
			if (isset($parentBlock->children[$childName])) {
				$parentBlock->children[$childName] = array_merge(
					$parentBlock->children[$childName],
					$child);
			} else {
				$parentBlock->children[$childName] = $child;
			}
		}

		$pi = pathinfo($realPath);
		$dir = $pi["dirname"];

		list($top, $bottom) = $this->sortProps($root->props, true);
		$this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);

		return array(true, $bottom, $parser, $dir);
	}

	protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
		$oldSourceParser = $this->sourceParser;

		$oldImport = $this->importDir;

		// TODO: this is because the importDir api is stupid
		$this->importDir = (array)$this->importDir;
		array_unshift($this->importDir, $importDir);

		foreach ($props as $prop) {
			$this->compileProp($prop, $block, $out);
		}

		$this->importDir = $oldImport;
		$this->sourceParser = $oldSourceParser;
	}

	/**
	 * Recursively compiles a block.
	 *
	 * A block is analogous to a CSS block in most cases. A single LESS document
	 * is encapsulated in a block when parsed, but it does not have parent tags
	 * so all of it's children appear on the root level when compiled.
	 *
	 * Blocks are made up of props and children.
	 *
	 * Props are property instructions, array tuples which describe an action
	 * to be taken, eg. write a property, set a variable, mixin a block.
	 *
	 * The children of a block are just all the blocks that are defined within.
	 * This is used to look up mixins when performing a mixin.
	 *
	 * Compiling the block involves pushing a fresh environment on the stack,
	 * and iterating through the props, compiling each one.
	 *
	 * See lessc::compileProp()
	 *
	 */
	protected function compileBlock($block) {
		switch ($block->type) {
		case "root":
			$this->compileRoot($block);
			break;
		case null:
			$this->compileCSSBlock($block);
			break;
		case "media":
			$this->compileMedia($block);
			break;
		case "directive":
			$name = "@" . $block->name;
			if (!empty($block->value)) {
				$name .= " " . $this->compileValue($this->reduce($block->value));
			}

			$this->compileNestedBlock($block, array($name));
			break;
		default:
			$this->throwError("unknown block type: $block->type\n");
		}
	}

	protected function compileCSSBlock($block) {
		$env = $this->pushEnv();

		$selectors = $this->compileSelectors($block->tags);
		$env->selectors = $this->multiplySelectors($selectors);
		$out = $this->makeOutputBlock(null, $env->selectors);

		$this->scope->children[] = $out;
		$this->compileProps($block, $out);

		$block->scope = $env; // mixins carry scope with them!
		$this->popEnv();
	}

	protected function compileMedia($media) {
		$env = $this->pushEnv($media);
		$parentScope = $this->mediaParent($this->scope);

		$query = $this->compileMediaQuery($this->multiplyMedia($env));

		$this->scope = $this->makeOutputBlock($media->type, array($query));
		$parentScope->children[] = $this->scope;

		$this->compileProps($media, $this->scope);

		if (count($this->scope->lines) > 0) {
			$orphanSelelectors = $this->findClosestSelectors();
			if (!is_null($orphanSelelectors)) {
				$orphan = $this->makeOutputBlock(null, $orphanSelelectors);
				$orphan->lines = $this->scope->lines;
				array_unshift($this->scope->children, $orphan);
				$this->scope->lines = array();
			}
		}

		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	protected function mediaParent($scope) {
		while (!empty($scope->parent)) {
			if (!empty($scope->type) && $scope->type != "media") {
				break;
			}
			$scope = $scope->parent;
		}

		return $scope;
	}

	protected function compileNestedBlock($block, $selectors) {
		$this->pushEnv($block);
		$this->scope = $this->makeOutputBlock($block->type, $selectors);
		$this->scope->parent->children[] = $this->scope;

		$this->compileProps($block, $this->scope);

		$this->scope = $this->scope->parent;
		$this->popEnv();
	}

	protected function compileRoot($root) {
		$this->pushEnv();
		$this->scope = $this->makeOutputBlock($root->type);
		$this->compileProps($root, $this->scope);
		$this->popEnv();
	}

	protected function compileProps($block, $out) {
		foreach ($this->sortProps($block->props) as $prop) {
			$this->compileProp($prop, $block, $out);
		}
		$out->lines = $this->deduplicate($out->lines);
	}

	/**
	 * Deduplicate lines in a block. Comments are not deduplicated. If a
	 * duplicate rule is detected, the comments immediately preceding each
	 * occurence are consolidated.
	 */
	protected function deduplicate($lines) {
		$unique = array();
		$comments = array();

		foreach($lines as $line) {
			if (strpos($line, '/*') === 0) {
				$comments[] = $line;
				continue;
			}
			if (!in_array($line, $unique)) {
				$unique[] = $line;
			}
			array_splice($unique, array_search($line, $unique), 0, $comments);
			$comments = array();
		}
		return array_merge($unique, $comments);
	}

	protected function sortProps($props, $split = false) {
		$vars = array();
		$imports = array();
		$other = array();
		$stack = array();

		foreach ($props as $prop) {
			switch ($prop[0]) {
			case "comment":
				$stack[] = $prop;
				break;
			case "assign":
				$stack[] = $prop;
				if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
					$vars = array_merge($vars, $stack);
				} else {
					$other = array_merge($other, $stack);
				}
				$stack = array();
				break;
			case "import":
				$id = self::$nextImportId++;
				$prop[] = $id;
				$stack[] = $prop;
				$imports = array_merge($imports, $stack);
				$other[] = array("import_mixin", $id);
				$stack = array();
				break;
			default:
				$stack[] = $prop;
				$other = array_merge($other, $stack);
				$stack = array();
				break;
			}
		}
		$other = array_merge($other, $stack);

		if ($split) {
			return array(array_merge($imports, $vars), $other);
		} else {
			return array_merge($imports, $vars, $other);
		}
	}

	protected function compileMediaQuery($queries) {
		$compiledQueries = array();
		foreach ($queries as $query) {
			$parts = array();
			foreach ($query as $q) {
				switch ($q[0]) {
				case "mediaType":
					$parts[] = implode(" ", array_slice($q, 1));
					break;
				case "mediaExp":
					if (isset($q[2])) {
						$parts[] = "($q[1]: " .
							$this->compileValue($this->reduce($q[2])) . ")";
					} else {
						$parts[] = "($q[1])";
					}
					break;
				case "variable":
					$parts[] = $this->compileValue($this->reduce($q));
				break;
				}
			}

			if (count($parts) > 0) {
				$compiledQueries[] =  implode(" and ", $parts);
			}
		}

		$out = "@media";
		if (!empty($parts)) {
			$out .= " " .
				implode($this->formatter->selectorSeparator, $compiledQueries);
		}
		return $out;
	}

	protected function multiplyMedia($env, $childQueries = null) {
		if (is_null($env) ||
			!empty($env->block->type) && $env->block->type != "media")
		{
			return $childQueries;
		}

		// plain old block, skip
		if (empty($env->block->type)) {
			return $this->multiplyMedia($env->parent, $childQueries);
		}

		$out = array();
		$queries = $env->block->queries;
		if (is_null($childQueries)) {
			$out = $queries;
		} else {
			foreach ($queries as $parent) {
				foreach ($childQueries as $child) {
					$out[] = array_merge($parent, $child);
				}
			}
		}

		return $this->multiplyMedia($env->parent, $out);
	}

	protected function expandParentSelectors(&$tag, $replace) {
		$parts = explode("$&$", $tag);
		$count = 0;
		foreach ($parts as &$part) {
			$part = str_replace($this->parentSelector, $replace, $part, $c);
			$count += $c;
		}
		$tag = implode($this->parentSelector, $parts);
		return $count;
	}

	protected function findClosestSelectors() {
		$env = $this->env;
		$selectors = null;
		while ($env !== null) {
			if (isset($env->selectors)) {
				$selectors = $env->selectors;
				break;
			}
			$env = $env->parent;
		}

		return $selectors;
	}


	// multiply $selectors against the nearest selectors in env
	protected function multiplySelectors($selectors) {
		// find parent selectors

		$parentSelectors = $this->findClosestSelectors();
		if (is_null($parentSelectors)) {
			// kill parent reference in top level selector
			foreach ($selectors as &$s) {
				$this->expandParentSelectors($s, "");
			}

			return $selectors;
		}

		$out = array();
		foreach ($parentSelectors as $parent) {
			foreach ($selectors as $child) {
				$count = $this->expandParentSelectors($child, $parent);

				// don't prepend the parent tag if & was used
				if ($count > 0) {
					$out[] = trim($child);
				} else {
					$out[] = trim($parent . ' ' . $child);
				}
			}
		}

		return $out;
	}

	// reduces selector expressions
	protected function compileSelectors($selectors) {
		$out = array();

		foreach ($selectors as $s) {
			if (is_array($s)) {
				list(, $value) = $s;
				$out[] = trim($this->compileValue($this->reduce($value)));
			} else {
				$out[] = $s;
			}
		}

		return $out;
	}

	protected function eq($left, $right) {
		return $left == $right;
	}

	protected function patternMatch($block, $orderedArgs, $keywordArgs) {
		// match the guards if it has them
		// any one of the groups must have all its guards pass for a match
		if (!empty($block->guards)) {
			$groupPassed = false;
			foreach ($block->guards as $guardGroup) {
				foreach ($guardGroup as $guard) {
					$this->pushEnv();
					$this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);

					$negate = false;
					if ($guard[0] == "negate") {
						$guard = $guard[1];
						$negate = true;
					}

					$passed = $this->reduce($guard) == self::$TRUE;
					if ($negate) $passed = !$passed;

					$this->popEnv();

					if ($passed) {
						$groupPassed = true;
					} else {
						$groupPassed = false;
						break;
					}
				}

				if ($groupPassed) break;
			}

			if (!$groupPassed) {
				return false;
			}
		}

		if (empty($block->args)) {
			return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
		}

		$remainingArgs = $block->args;
		if ($keywordArgs) {
			$remainingArgs = array();
			foreach ($block->args as $arg) {
				if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
					continue;
				}

				$remainingArgs[] = $arg;
			}
		}

		$i = -1; // no args
		// try to match by arity or by argument literal
		foreach ($remainingArgs as $i => $arg) {
			switch ($arg[0]) {
			case "lit":
				if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
					return false;
				}
				break;
			case "arg":
				// no arg and no default value
				if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
					return false;
				}
				break;
			case "rest":
				$i--; // rest can be empty
				break 2;
			}
		}

		if ($block->isVararg) {
			return true; // not having enough is handled above
		} else {
			$numMatched = $i + 1;
			// greater than becuase default values always match
			return $numMatched >= count($orderedArgs);
		}
	}

	protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
		$matches = null;
		foreach ($blocks as $block) {
			// skip seen blocks that don't have arguments
			if (isset($skip[$block->id]) && !isset($block->args)) {
				continue;
			}

			if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
				$matches[] = $block;
			}
		}

		return $matches;
	}

	// attempt to find blocks matched by path and args
	protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
		if ($searchIn == null) return null;
		if (isset($seen[$searchIn->id])) return null;
		$seen[$searchIn->id] = true;

		$name = $path[0];

		if (isset($searchIn->children[$name])) {
			$blocks = $searchIn->children[$name];
			if (count($path) == 1) {
				$matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
				if (!empty($matches)) {
					// This will return all blocks that match in the closest
					// scope that has any matching block, like lessjs
					return $matches;
				}
			} else {
				$matches = array();
				foreach ($blocks as $subBlock) {
					$subMatches = $this->findBlocks($subBlock,
						array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);

					if (!is_null($subMatches)) {
						foreach ($subMatches as $sm) {
							$matches[] = $sm;
						}
					}
				}

				return count($matches) > 0 ? $matches : null;
			}
		}
		if ($searchIn->parent === $searchIn) return null;
		return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
	}

	// sets all argument names in $args to either the default value
	// or the one passed in through $values
	protected function zipSetArgs($args, $orderedValues, $keywordValues) {
		$assignedValues = array();

		$i = 0;
		foreach ($args as  $a) {
			if ($a[0] == "arg") {
				if (isset($keywordValues[$a[1]])) {
					// has keyword arg
					$value = $keywordValues[$a[1]];
				} elseif (isset($orderedValues[$i])) {
					// has ordered arg
					$value = $orderedValues[$i];
					$i++;
				} elseif (isset($a[2])) {
					// has default value
					$value = $a[2];
				} else {
					$this->throwError("Failed to assign arg " . $a[1]);
					$value = null; // :(
				}

				$value = $this->reduce($value);
				$this->set($a[1], $value);
				$assignedValues[] = $value;
			} else {
				// a lit
				$i++;
			}
		}

		// check for a rest
		$last = end($args);
		if ($last[0] == "rest") {
			$rest = array_slice($orderedValues, count($args) - 1);
			$this->set($last[1], $this->reduce(array("list", " ", $rest)));
		}

		// wow is this the only true use of PHP's + operator for arrays?
		$this->env->arguments = $assignedValues + $orderedValues;
	}

	// compile a prop and update $lines or $blocks appropriately
	protected function compileProp($prop, $block, $out) {
		// set error position context
		$this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;

		switch ($prop[0]) {
		case 'assign':
			list(, $name, $value) = $prop;
			if ($name[0] == $this->vPrefix) {
				$this->set($name, $value);
			} else {
				$out->lines[] = $this->formatter->property($name,
						$this->compileValue($this->reduce($value)));
			}
			break;
		case 'block':
			list(, $child) = $prop;
			$this->compileBlock($child);
			break;
		case 'mixin':
			list(, $path, $args, $suffix) = $prop;

			$orderedArgs = array();
			$keywordArgs = array();
			foreach ((array)$args as $arg) {
				$argval = null;
				switch ($arg[0]) {
				case "arg":
					if (!isset($arg[2])) {
						$orderedArgs[] = $this->reduce(array("variable", $arg[1]));
					} else {
						$keywordArgs[$arg[1]] = $this->reduce($arg[2]);
					}
					break;

				case "lit":
					$orderedArgs[] = $this->reduce($arg[1]);
					break;
				default:
					$this->throwError("Unknown arg type: " . $arg[0]);
				}
			}

			$mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);

			if ($mixins === null) {
				$this->throwError("{$prop[1][0]} is undefined");
			}

			foreach ($mixins as $mixin) {
				if ($mixin === $block && !$orderedArgs) {
					continue;
				}

				$haveScope = false;
				if (isset($mixin->parent->scope)) {
					$haveScope = true;
					$mixinParentEnv = $this->pushEnv();
					$mixinParentEnv->storeParent = $mixin->parent->scope;
				}

				$haveArgs = false;
				if (isset($mixin->args)) {
					$haveArgs = true;
					$this->pushEnv();
					$this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
				}

				$oldParent = $mixin->parent;
				if ($mixin != $block) $mixin->parent = $block;

				foreach ($this->sortProps($mixin->props) as $subProp) {
					if ($suffix !== null &&
						$subProp[0] == "assign" &&
						is_string($subProp[1]) &&
						$subProp[1]{0} != $this->vPrefix)
					{
						$subProp[2] = array(
							'list', ' ',
							array($subProp[2], array('keyword', $suffix))
						);
					}

					$this->compileProp($subProp, $mixin, $out);
				}

				$mixin->parent = $oldParent;

				if ($haveArgs) $this->popEnv();
				if ($haveScope) $this->popEnv();
			}

			break;
		case 'raw':
			$out->lines[] = $prop[1];
			break;
		case "directive":
			list(, $name, $value) = $prop;
			$out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
			break;
		case "comment":
			$out->lines[] = $prop[1];
			break;
		case "import";
			list(, $importPath, $importId) = $prop;
			$importPath = $this->reduce($importPath);

			if (!isset($this->env->imports)) {
				$this->env->imports = array();
			}

			$result = $this->tryImport($importPath, $block, $out);

			$this->env->imports[$importId] = $result === false ?
				array(false, "@import " . $this->compileValue($importPath).";") :
				$result;

			break;
		case "import_mixin":
			list(,$importId) = $prop;
			$import = $this->env->imports[$importId];
			if ($import[0] === false) {
				if (isset($import[1])) {
					$out->lines[] = $import[1];
				}
			} else {
				list(, $bottom, $parser, $importDir) = $import;
				$this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
			}

			break;
		default:
			$this->throwError("unknown op: {$prop[0]}\n");
		}
	}


	/**
	 * Compiles a primitive value into a CSS property value.
	 *
	 * Values in lessphp are typed by being wrapped in arrays, their format is
	 * typically:
	 *
	 *     array(type, contents [, additional_contents]*)
	 *
	 * The input is expected to be reduced. This function will not work on
	 * things like expressions and variables.
	 */
	public function compileValue($value) {
		switch ($value[0]) {
		case 'list':
			// [1] - delimiter
			// [2] - array of values
			return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
		case 'raw_color':
			if (!empty($this->formatter->compressColors)) {
				return $this->compileValue($this->coerceColor($value));
			}
			return $value[1];
		case 'keyword':
			// [1] - the keyword
			return $value[1];
		case 'number':
			list(, $num, $unit) = $value;
			// [1] - the number
			// [2] - the unit
			if ($this->numberPrecision !== null) {
				$num = round($num, $this->numberPrecision);
			}
			return $num . $unit;
		case 'string':
			// [1] - contents of string (includes quotes)
			list(, $delim, $content) = $value;
			foreach ($content as &$part) {
				if (is_array($part)) {
					$part = $this->compileValue($part);
				}
			}
			return $delim . implode($content) . $delim;
		case 'color':
			// [1] - red component (either number or a %)
			// [2] - green component
			// [3] - blue component
			// [4] - optional alpha component
			list(, $r, $g, $b) = $value;
			$r = round($r);
			$g = round($g);
			$b = round($b);

			if (count($value) == 5 && $value[4] != 1) { // rgba
				return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
			}

			$h = sprintf("#%02x%02x%02x", $r, $g, $b);

			if (!empty($this->formatter->compressColors)) {
				// Converting hex color to short notation (e.g. #003399 to #039)
				if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
					$h = '#' . $h[1] . $h[3] . $h[5];
				}
			}

			return $h;

		case 'function':
			list(, $name, $args) = $value;
			return $name.'('.$this->compileValue($args).')';
		default: // assumed to be unit
			$this->throwError("unknown value type: $value[0]");
		}
	}

	protected function lib_pow($args) {
		list($base, $exp) = $this->assertArgs($args, 2, "pow");
		return pow($this->assertNumber($base), $this->assertNumber($exp));
	}

	protected function lib_pi() {
		return pi();
	}

	protected function lib_mod($args) {
		list($a, $b) = $this->assertArgs($args, 2, "mod");
		return $this->assertNumber($a) % $this->assertNumber($b);
	}

	protected function lib_tan($num) {
		return tan($this->assertNumber($num));
	}

	protected function lib_sin($num) {
		return sin($this->assertNumber($num));
	}

	protected function lib_cos($num) {
		return cos($this->assertNumber($num));
	}

	protected function lib_atan($num) {
		$num = atan($this->assertNumber($num));
		return array("number", $num, "rad");
	}

	protected function lib_asin($num) {
		$num = asin($this->assertNumber($num));
		return array("number", $num, "rad");
	}

	protected function lib_acos($num) {
		$num = acos($this->assertNumber($num));
		return array("number", $num, "rad");
	}

	protected function lib_sqrt($num) {
		return sqrt($this->assertNumber($num));
	}

	protected function lib_extract($value) {
		list($list, $idx) = $this->assertArgs($value, 2, "extract");
		$idx = $this->assertNumber($idx);
		// 1 indexed
		if ($list[0] == "list" && isset($list[2][$idx - 1])) {
			return $list[2][$idx - 1];
		}
	}

	protected function lib_isnumber($value) {
		return $this->toBool($value[0] == "number");
	}

	protected function lib_isstring($value) {
		return $this->toBool($value[0] == "string");
	}

	protected function lib_iscolor($value) {
		return $this->toBool($this->coerceColor($value));
	}

	protected function lib_iskeyword($value) {
		return $this->toBool($value[0] == "keyword");
	}

	protected function lib_ispixel($value) {
		return $this->toBool($value[0] == "number" && $value[2] == "px");
	}

	protected function lib_ispercentage($value) {
		return $this->toBool($value[0] == "number" && $value[2] == "%");
	}

	protected function lib_isem($value) {
		return $this->toBool($value[0] == "number" && $value[2] == "em");
	}

	protected function lib_isrem($value) {
		return $this->toBool($value[0] == "number" && $value[2] == "rem");
	}

	protected function lib_rgbahex($color) {
		$color = $this->coerceColor($color);
		if (is_null($color))
			$this->throwError("color expected for rgbahex");

		return sprintf("#%02x%02x%02x%02x",
			isset($color[4]) ? $color[4]*255 : 255,
			$color[1],$color[2], $color[3]);
	}

	protected function lib_argb($color){
		return $this->lib_rgbahex($color);
	}

	/**
	 * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
	 *
	 * @param  array  $value either an argument list (two strings) or a single string
	 * @return string        formatted url(), either as a link or base64-encoded
	 */
	protected function lib_data_uri($value) {
		$mime = ($value[0] === 'list') ? $value[2][0][2] : null;
		$url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];

		$fullpath = $this->findImport($url);

		if($fullpath && ($fsize = filesize($fullpath)) !== false) {
			// IE8 can't handle data uris larger than 32KB
			if($fsize/1024 < 32) {
				if(is_null($mime)) {
					if(class_exists('finfo')) { // php 5.3+
						$finfo = new finfo(FILEINFO_MIME);
						$mime = explode('; ', $finfo->file($fullpath));
						$mime = $mime[0];
					} elseif(function_exists('mime_content_type')) { // PHP 5.2
						$mime = mime_content_type($fullpath);
					}
				}

				if(!is_null($mime)) // fallback if the mime type is still unknown
					$url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
			}
		}

		return 'url("'.$url.'")';
	}

	// utility func to unquote a string
	protected function lib_e($arg) {
		switch ($arg[0]) {
			case "list":
				$items = $arg[2];
				if (isset($items[0])) {
					return $this->lib_e($items[0]);
				}
				$this->throwError("unrecognised input");
			case "string":
				$arg[1] = "";
				return $arg;
			case "keyword":
				return $arg;
			default:
				return array("keyword", $this->compileValue($arg));
		}
	}

	protected function lib__sprintf($args) {
		if ($args[0] != "list") return $args;
		$values = $args[2];
		$string = array_shift($values);
		$template = $this->compileValue($this->lib_e($string));

		$i = 0;
		if (preg_match_all('/%[dsa]/', $template, $m)) {
			foreach ($m[0] as $match) {
				$val = isset($values[$i]) ?
					$this->reduce($values[$i]) : array('keyword', '');

				// lessjs compat, renders fully expanded color, not raw color
				if ($color = $this->coerceColor($val)) {
					$val = $color;
				}

				$i++;
				$rep = $this->compileValue($this->lib_e($val));
				$template = preg_replace('/'.self::preg_quote($match).'/',
					$rep, $template, 1);
			}
		}

		$d = $string[0] == "string" ? $string[1] : '"';
		return array("string", $d, array($template));
	}

	protected function lib_floor($arg) {
		$value = $this->assertNumber($arg);
		return array("number", floor($value), $arg[2]);
	}

	protected function lib_ceil($arg) {
		$value = $this->assertNumber($arg);
		return array("number", ceil($value), $arg[2]);
	}

	protected function lib_round($arg) {
		if($arg[0] != "list") {
			$value = $this->assertNumber($arg);
			return array("number", round($value), $arg[2]);
		} else {
			$value = $this->assertNumber($arg[2][0]);
			$precision = $this->assertNumber($arg[2][1]);
			return array("number", round($value, $precision), $arg[2][0][2]);
		}
	}

	protected function lib_unit($arg) {
		if ($arg[0] == "list") {
			list($number, $newUnit) = $arg[2];
			return array("number", $this->assertNumber($number),
				$this->compileValue($this->lib_e($newUnit)));
		} else {
			return array("number", $this->assertNumber($arg), "");
		}
	}

	/**
	 * Helper function to get arguments for color manipulation functions.
	 * takes a list that contains a color like thing and a percentage
	 */
	public function colorArgs($args) {
		if ($args[0] != 'list' || count($args[2]) < 2) {
			return array(array('color', 0, 0, 0), 0);
		}
		list($color, $delta) = $args[2];
		$color = $this->assertColor($color);
		$delta = floatval($delta[1]);

		return array($color, $delta);
	}

	protected function lib_darken($args) {
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] - $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function lib_lighten($args) {
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[3] = $this->clamp($hsl[3] + $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function lib_saturate($args) {
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] + $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function lib_desaturate($args) {
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);
		$hsl[2] = $this->clamp($hsl[2] - $delta, 100);
		return $this->toRGB($hsl);
	}

	protected function lib_spin($args) {
		list($color, $delta) = $this->colorArgs($args);

		$hsl = $this->toHSL($color);

		$hsl[1] = $hsl[1] + $delta % 360;
		if ($hsl[1] < 0) $hsl[1] += 360;

		return $this->toRGB($hsl);
	}

	protected function lib_fadeout($args) {
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
		return $color;
	}

	protected function lib_fadein($args) {
		list($color, $delta) = $this->colorArgs($args);
		$color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
		return $color;
	}

	protected function lib_hue($color) {
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[1]);
	}

	protected function lib_saturation($color) {
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[2]);
	}

	protected function lib_lightness($color) {
		$hsl = $this->toHSL($this->assertColor($color));
		return round($hsl[3]);
	}

	// get the alpha of a color
	// defaults to 1 for non-colors or colors without an alpha
	protected function lib_alpha($value) {
		if (!is_null($color = $this->coerceColor($value))) {
			return isset($color[4]) ? $color[4] : 1;
		}
	}

	// set the alpha of the color
	protected function lib_fade($args) {
		list($color, $alpha) = $this->colorArgs($args);
		$color[4] = $this->clamp($alpha / 100.0);
		return $color;
	}

	protected function lib_percentage($arg) {
		$num = $this->assertNumber($arg);
		return array("number", $num*100, "%");
	}

	// mixes two colors by weight
	// mix(@color1, @color2, [@weight: 50%]);
	// http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
	protected function lib_mix($args) {
		if ($args[0] != "list" || count($args[2]) < 2)
			$this->throwError("mix expects (color1, color2, weight)");

		list($first, $second) = $args[2];
		$first = $this->assertColor($first);
		$second = $this->assertColor($second);

		$first_a = $this->lib_alpha($first);
		$second_a = $this->lib_alpha($second);

		if (isset($args[2][2])) {
			$weight = $args[2][2][1] / 100.0;
		} else {
			$weight = 0.5;
		}

		$w = $weight * 2 - 1;
		$a = $first_a - $second_a;

		$w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
		$w2 = 1.0 - $w1;

		$new = array('color',
			$w1 * $first[1] + $w2 * $second[1],
			$w1 * $first[2] + $w2 * $second[2],
			$w1 * $first[3] + $w2 * $second[3],
		);

		if ($first_a != 1.0 || $second_a != 1.0) {
			$new[] = $first_a * $weight + $second_a * ($weight - 1);
		}

		return $this->fixColor($new);
	}

	protected function lib_contrast($args) {
	    $darkColor  = array('color', 0, 0, 0);
	    $lightColor = array('color', 255, 255, 255);
	    $threshold  = 0.43;

	    if ( $args[0] == 'list' ) {
	        $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
	        $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
	        $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
	        $threshold  = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
	    }
	    else {
	        $inputColor  = $this->assertColor($args);
	    }

	    $inputColor = $this->coerceColor($inputColor);
	    $darkColor  = $this->coerceColor($darkColor);
	    $lightColor = $this->coerceColor($lightColor);

	    //Figure out which is actually light and dark!
	    if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
	        $t  = $lightColor;
	        $lightColor = $darkColor;
	        $darkColor  = $t;
	    }

	    $inputColor_alpha = $this->lib_alpha($inputColor);
	    if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
	        return $lightColor;
	    }
	    return $darkColor;
	}

	protected function lib_luma($color) {
	    $color = $this->coerceColor($color);
	    return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
	}


	public function assertColor($value, $error = "expected color value") {
		$color = $this->coerceColor($value);
		if (is_null($color)) $this->throwError($error);
		return $color;
	}

	public function assertNumber($value, $error = "expecting number") {
		if ($value[0] == "number") return $value[1];
		$this->throwError($error);
	}

	public function assertArgs($value, $expectedArgs, $name="") {
		if ($expectedArgs == 1) {
			return $value;
		} else {
			if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
			$values = $value[2];
			$numValues = count($values);
			if ($expectedArgs != $numValues) {
				if ($name) {
					$name = $name . ": ";
				}

				$this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
			}

			return $values;
		}
	}

	protected function toHSL($color) {
		if ($color[0] == 'hsl') return $color;

		$r = $color[1] / 255;
		$g = $color[2] / 255;
		$b = $color[3] / 255;

		$min = min($r, $g, $b);
		$max = max($r, $g, $b);

		$L = ($min + $max) / 2;
		if ($min == $max) {
			$S = $H = 0;
		} else {
			if ($L < 0.5)
				$S = ($max - $min)/($max + $min);
			else
				$S = ($max - $min)/(2.0 - $max - $min);

			if ($r == $max) $H = ($g - $b)/($max - $min);
			elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
			elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);

		}

		$out = array('hsl',
			($H < 0 ? $H + 6 : $H)*60,
			$S*100,
			$L*100,
		);

		if (count($color) > 4) $out[] = $color[4]; // copy alpha
		return $out;
	}

	protected function toRGB_helper($comp, $temp1, $temp2) {
		if ($comp < 0) $comp += 1.0;
		elseif ($comp > 1) $comp -= 1.0;

		if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
		if (2 * $comp < 1) return $temp2;
		if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;

		return $temp1;
	}

	/**
	 * Converts a hsl array into a color value in rgb.
	 * Expects H to be in range of 0 to 360, S and L in 0 to 100
	 */
	protected function toRGB($color) {
		if ($color[0] == 'color') return $color;

		$H = $color[1] / 360;
		$S = $color[2] / 100;
		$L = $color[3] / 100;

		if ($S == 0) {
			$r = $g = $b = $L;
		} else {
			$temp2 = $L < 0.5 ?
				$L*(1.0 + $S) :
				$L + $S - $L * $S;

			$temp1 = 2.0 * $L - $temp2;

			$r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
			$g = $this->toRGB_helper($H, $temp1, $temp2);
			$b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
		}

		// $out = array('color', round($r*255), round($g*255), round($b*255));
		$out = array('color', $r*255, $g*255, $b*255);
		if (count($color) > 4) $out[] = $color[4]; // copy alpha
		return $out;
	}

	protected function clamp($v, $max = 1, $min = 0) {
		return min($max, max($min, $v));
	}

	/**
	 * Convert the rgb, rgba, hsl color literals of function type
	 * as returned by the parser into values of color type.
	 */
	protected function funcToColor($func) {
		$fname = $func[1];
		if ($func[2][0] != 'list') return false; // need a list of arguments
		$rawComponents = $func[2][2];

		if ($fname == 'hsl' || $fname == 'hsla') {
			$hsl = array('hsl');
			$i = 0;
			foreach ($rawComponents as $c) {
				$val = $this->reduce($c);
				$val = isset($val[1]) ? floatval($val[1]) : 0;

				if ($i == 0) $clamp = 360;
				elseif ($i < 3) $clamp = 100;
				else $clamp = 1;

				$hsl[] = $this->clamp($val, $clamp);
				$i++;
			}

			while (count($hsl) < 4) $hsl[] = 0;
			return $this->toRGB($hsl);

		} elseif ($fname == 'rgb' || $fname == 'rgba') {
			$components = array();
			$i = 1;
			foreach	($rawComponents as $c) {
				$c = $this->reduce($c);
				if ($i < 4) {
					if ($c[0] == "number" && $c[2] == "%") {
						$components[] = 255 * ($c[1] / 100);
					} else {
						$components[] = floatval($c[1]);
					}
				} elseif ($i == 4) {
					if ($c[0] == "number" && $c[2] == "%") {
						$components[] = 1.0 * ($c[1] / 100);
					} else {
						$components[] = floatval($c[1]);
					}
				} else break;

				$i++;
			}
			while (count($components) < 3) $components[] = 0;
			array_unshift($components, 'color');
			return $this->fixColor($components);
		}

		return false;
	}

	protected function reduce($value, $forExpression = false) {
		switch ($value[0]) {
		case "interpolate":
			$reduced = $this->reduce($value[1]);
			$var = $this->compileValue($reduced);
			$res = $this->reduce(array("variable", $this->vPrefix . $var));

			if ($res[0] == "raw_color") {
				$res = $this->coerceColor($res);
			}

			if (empty($value[2])) $res = $this->lib_e($res);

			return $res;
		case "variable":
			$key = $value[1];
			if (is_array($key)) {
				$key = $this->reduce($key);
				$key = $this->vPrefix . $this->compileValue($this->lib_e($key));
			}

			$seen =& $this->env->seenNames;

			if (!empty($seen[$key])) {
				$this->throwError("infinite loop detected: $key");
			}

			$seen[$key] = true;
			$out = $this->reduce($this->get($key));
			$seen[$key] = false;
			return $out;
		case "list":
			foreach ($value[2] as &$item) {
				$item = $this->reduce($item, $forExpression);
			}
			return $value;
		case "expression":
			return $this->evaluate($value);
		case "string":
			foreach ($value[2] as &$part) {
				if (is_array($part)) {
					$strip = $part[0] == "variable";
					$part = $this->reduce($part);
					if ($strip) $part = $this->lib_e($part);
				}
			}
			return $value;
		case "escape":
			list(,$inner) = $value;
			return $this->lib_e($this->reduce($inner));
		case "function":
			$color = $this->funcToColor($value);
			if ($color) return $color;

			list(, $name, $args) = $value;
			if ($name == "%") $name = "_sprintf";

			$f = isset($this->libFunctions[$name]) ?
				$this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));

			if (is_callable($f)) {
				if ($args[0] == 'list')
					$args = self::compressList($args[2], $args[1]);

				$ret = call_user_func($f, $this->reduce($args, true), $this);

				if (is_null($ret)) {
					return array("string", "", array(
						$name, "(", $args, ")"
					));
				}

				// convert to a typed value if the result is a php primitive
				if (is_numeric($ret)) $ret = array('number', $ret, "");
				elseif (!is_array($ret)) $ret = array('keyword', $ret);

				return $ret;
			}

			// plain function, reduce args
			$value[2] = $this->reduce($value[2]);
			return $value;
		case "unary":
			list(, $op, $exp) = $value;
			$exp = $this->reduce($exp);

			if ($exp[0] == "number") {
				switch ($op) {
				case "+":
					return $exp;
				case "-":
					$exp[1] *= -1;
					return $exp;
				}
			}
			return array("string", "", array($op, $exp));
		}

		if ($forExpression) {
			switch ($value[0]) {
			case "keyword":
				if ($color = $this->coerceColor($value)) {
					return $color;
				}
				break;
			case "raw_color":
				return $this->coerceColor($value);
			}
		}

		return $value;
	}


	// coerce a value for use in color operation
	protected function coerceColor($value) {
		switch($value[0]) {
			case 'color': return $value;
			case 'raw_color':
				$c = array("color", 0, 0, 0);
				$colorStr = substr($value[1], 1);
				$num = hexdec($colorStr);
				$width = strlen($colorStr) == 3 ? 16 : 256;

				for ($i = 3; $i > 0; $i--) { // 3 2 1
					$t = $num % $width;
					$num /= $width;

					$c[$i] = $t * (256/$width) + $t * floor(16/$width);
				}

				return $c;
			case 'keyword':
				$name = $value[1];
				if (isset(self::$cssColors[$name])) {
					$rgba = explode(',', self::$cssColors[$name]);

					if(isset($rgba[3]))
						return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);

					return array('color', $rgba[0], $rgba[1], $rgba[2]);
				}
				return null;
		}
	}

	// make something string like into a string
	protected function coerceString($value) {
		switch ($value[0]) {
		case "string":
			return $value;
		case "keyword":
			return array("string", "", array($value[1]));
		}
		return null;
	}

	// turn list of length 1 into value type
	protected function flattenList($value) {
		if ($value[0] == "list" && count($value[2]) == 1) {
			return $this->flattenList($value[2][0]);
		}
		return $value;
	}

	public function toBool($a) {
		if ($a) return self::$TRUE;
		else return self::$FALSE;
	}

	// evaluate an expression
	protected function evaluate($exp) {
		list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;

		$left = $this->reduce($left, true);
		$right = $this->reduce($right, true);

		if ($leftColor = $this->coerceColor($left)) {
			$left = $leftColor;
		}

		if ($rightColor = $this->coerceColor($right)) {
			$right = $rightColor;
		}

		$ltype = $left[0];
		$rtype = $right[0];

		// operators that work on all types
		if ($op == "and") {
			return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
		}

		if ($op == "=") {
			return $this->toBool($this->eq($left, $right) );
		}

		if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
			return $str;
		}

		// type based operators
		$fname = "op_${ltype}_${rtype}";
		if (is_callable(array($this, $fname))) {
			$out = $this->$fname($op, $left, $right);
			if (!is_null($out)) return $out;
		}

		// make the expression look it did before being parsed
		$paddedOp = $op;
		if ($whiteBefore) $paddedOp = " " . $paddedOp;
		if ($whiteAfter) $paddedOp .= " ";

		return array("string", "", array($left, $paddedOp, $right));
	}

	protected function stringConcatenate($left, $right) {
		if ($strLeft = $this->coerceString($left)) {
			if ($right[0] == "string") {
				$right[1] = "";
			}
			$strLeft[2][] = $right;
			return $strLeft;
		}

		if ($strRight = $this->coerceString($right)) {
			array_unshift($strRight[2], $left);
			return $strRight;
		}
	}


	// make sure a color's components don't go out of bounds
	protected function fixColor($c) {
		foreach (range(1, 3) as $i) {
			if ($c[$i] < 0) $c[$i] = 0;
			if ($c[$i] > 255) $c[$i] = 255;
		}

		return $c;
	}

	protected function op_number_color($op, $lft, $rgt) {
		if ($op == '+' || $op == '*') {
			return $this->op_color_number($op, $rgt, $lft);
		}
	}

	protected function op_color_number($op, $lft, $rgt) {
		if ($rgt[0] == '%') $rgt[1] /= 100;

		return $this->op_color_color($op, $lft,
			array_fill(1, count($lft) - 1, $rgt[1]));
	}

	protected function op_color_color($op, $left, $right) {
		$out = array('color');
		$max = count($left) > count($right) ? count($left) : count($right);
		foreach (range(1, $max - 1) as $i) {
			$lval = isset($left[$i]) ? $left[$i] : 0;
			$rval = isset($right[$i]) ? $right[$i] : 0;
			switch ($op) {
			case '+':
				$out[] = $lval + $rval;
				break;
			case '-':
				$out[] = $lval - $rval;
				break;
			case '*':
				$out[] = $lval * $rval;
				break;
			case '%':
				$out[] = $lval % $rval;
				break;
			case '/':
				if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
				$out[] = $lval / $rval;
				break;
			default:
				$this->throwError('evaluate error: color op number failed on op '.$op);
			}
		}
		return $this->fixColor($out);
	}

	function lib_red($color){
		$color = $this->coerceColor($color);
		if (is_null($color)) {
			$this->throwError('color expected for red()');
		}

		return $color[1];
	}

	function lib_green($color){
		$color = $this->coerceColor($color);
		if (is_null($color)) {
			$this->throwError('color expected for green()');
		}

		return $color[2];
	}

	function lib_blue($color){
		$color = $this->coerceColor($color);
		if (is_null($color)) {
			$this->throwError('color expected for blue()');
		}

		return $color[3];
	}


	// operator on two numbers
	protected function op_number_number($op, $left, $right) {
		$unit = empty($left[2]) ? $right[2] : $left[2];

		$value = 0;
		switch ($op) {
		case '+':
			$value = $left[1] + $right[1];
			break;
		case '*':
			$value = $left[1] * $right[1];
			break;
		case '-':
			$value = $left[1] - $right[1];
			break;
		case '%':
			$value = $left[1] % $right[1];
			break;
		case '/':
			if ($right[1] == 0) $this->throwError('parse error: divide by zero');
			$value = $left[1] / $right[1];
			break;
		case '<':
			return $this->toBool($left[1] < $right[1]);
		case '>':
			return $this->toBool($left[1] > $right[1]);
		case '>=':
			return $this->toBool($left[1] >= $right[1]);
		case '=<':
			return $this->toBool($left[1] <= $right[1]);
		default:
			$this->throwError('parse error: unknown number operator: '.$op);
		}

		return array("number", $value, $unit);
	}


	/* environment functions */

	protected function makeOutputBlock($type, $selectors = null) {
		$b = new stdclass;
		$b->lines = array();
		$b->children = array();
		$b->selectors = $selectors;
		$b->type = $type;
		$b->parent = $this->scope;
		return $b;
	}

	// the state of execution
	protected function pushEnv($block = null) {
		$e = new stdclass;
		$e->parent = $this->env;
		$e->store = array();
		$e->block = $block;

		$this->env = $e;
		return $e;
	}

	// pop something off the stack
	protected function popEnv() {
		$old = $this->env;
		$this->env = $this->env->parent;
		return $old;
	}

	// set something in the current env
	protected function set($name, $value) {
		$this->env->store[$name] = $value;
	}


	// get the highest occurrence entry for a name
	protected function get($name) {
		$current = $this->env;

		$isArguments = $name == $this->vPrefix . 'arguments';
		while ($current) {
			if ($isArguments && isset($current->arguments)) {
				return array('list', ' ', $current->arguments);
			}

			if (isset($current->store[$name]))
				return $current->store[$name];
			else {
				$current = isset($current->storeParent) ?
					$current->storeParent : $current->parent;
			}
		}

		$this->throwError("variable $name is undefined");
	}

	// inject array of unparsed strings into environment as variables
	protected function injectVariables($args) {
		$this->pushEnv();
		$parser = new lessc_parser($this, __METHOD__);
		foreach ($args as $name => $strValue) {
			if ($name{0} != '@') $name = '@'.$name;
			$parser->count = 0;
			$parser->buffer = (string)$strValue;
			if (!$parser->propertyValue($value)) {
				throw new Exception("failed to parse passed in variable $name: $strValue");
			}

			$this->set($name, $value);
		}
	}

	/**
	 * Initialize any static state, can initialize parser for a file
	 * $opts isn't used yet
	 */
	public function __construct($fname = null) {
		if ($fname !== null) {
			// used for deprecated parse method
			$this->_parseFile = $fname;
		}
	}

	public function compile($string, $name = null) {
		$locale = setlocale(LC_NUMERIC, 0);
		setlocale(LC_NUMERIC, "C");

		$this->parser = $this->makeParser($name);
		$root = $this->parser->parse($string);

		$this->env = null;
		$this->scope = null;

		$this->formatter = $this->newFormatter();

		if (!empty($this->registeredVars)) {
			$this->injectVariables($this->registeredVars);
		}

		$this->sourceParser = $this->parser; // used for error messages
		$this->compileBlock($root);

		ob_start();
		$this->formatter->block($this->scope);
		$out = ob_get_clean();
		setlocale(LC_NUMERIC, $locale);
		return $out;
	}

	public function compileFile($fname, $outFname = null) {
		if (!is_readable($fname)) {
			throw new Exception('load error: failed to find '.$fname);
		}

		$pi = pathinfo($fname);

		$oldImport = $this->importDir;

		$this->importDir = (array)$this->importDir;
		$this->importDir[] = $pi['dirname'].'/';

		$this->addParsedFile($fname);

		$out = $this->compile(file_get_contents($fname), $fname);

		$this->importDir = $oldImport;

		if ($outFname !== null) {
			return file_put_contents($outFname, $out);
		}

		return $out;
	}

	// compile only if changed input has changed or output doesn't exist
	public function checkedCompile($in, $out) {
		if (!is_file($out) || filemtime($in) > filemtime($out)) {
			$this->compileFile($in, $out);
			return true;
		}
		return false;
	}

	/**
	 * Execute lessphp on a .less file or a lessphp cache structure
	 *
	 * The lessphp cache structure contains information about a specific
	 * less file having been parsed. It can be used as a hint for future
	 * calls to determine whether or not a rebuild is required.
	 *
	 * The cache structure contains two important keys that may be used
	 * externally:
	 *
	 * compiled: The final compiled CSS
	 * updated: The time (in seconds) the CSS was last compiled
	 *
	 * The cache structure is a plain-ol' PHP associative array and can
	 * be serialized and unserialized without a hitch.
	 *
	 * @param mixed $in Input
	 * @param bool $force Force rebuild?
	 * @return array lessphp cache structure
	 */
	public function cachedCompile($in, $force = false) {
		// assume no root
		$root = null;

		if (is_string($in)) {
			$root = $in;
		} elseif (is_array($in) and isset($in['root'])) {
			if ($force or ! isset($in['files'])) {
				// If we are forcing a recompile or if for some reason the
				// structure does not contain any file information we should
				// specify the root to trigger a rebuild.
				$root = $in['root'];
			} elseif (isset($in['files']) and is_array($in['files'])) {
				foreach ($in['files'] as $fname => $ftime ) {
					if (!file_exists($fname) or filemtime($fname) > $ftime) {
						// One of the files we knew about previously has changed
						// so we should look at our incoming root again.
						$root = $in['root'];
						break;
					}
				}
			}
		} else {
			// TODO: Throw an exception? We got neither a string nor something
			// that looks like a compatible lessphp cache structure.
			return null;
		}

		if ($root !== null) {
			// If we have a root value which means we should rebuild.
			$out = array();
			$out['root'] = $root;
			$out['compiled'] = $this->compileFile($root);
			$out['files'] = $this->allParsedFiles();
			$out['updated'] = time();
			return $out;
		} else {
			// No changes, pass back the structure
			// we were given initially.
			return $in;
		}

	}

	// parse and compile buffer
	// This is deprecated
	public function parse($str = null, $initialVariables = null) {
		if (is_array($str)) {
			$initialVariables = $str;
			$str = null;
		}

		$oldVars = $this->registeredVars;
		if ($initialVariables !== null) {
			$this->setVariables($initialVariables);
		}

		if ($str == null) {
			if (empty($this->_parseFile)) {
				throw new exception("nothing to parse");
			}

			$out = $this->compileFile($this->_parseFile);
		} else {
			$out = $this->compile($str);
		}

		$this->registeredVars = $oldVars;
		return $out;
	}

	protected function makeParser($name) {
		$parser = new lessc_parser($this, $name);
		$parser->writeComments = $this->preserveComments;

		return $parser;
	}

	public function setFormatter($name) {
		$this->formatterName = $name;
	}

	protected function newFormatter() {
		$className = "lessc_formatter_lessjs";
		if (!empty($this->formatterName)) {
			if (!is_string($this->formatterName))
				return $this->formatterName;
			$className = "lessc_formatter_$this->formatterName";
		}

		return new $className;
	}

	public function setPreserveComments($preserve) {
		$this->preserveComments = $preserve;
	}

	public function registerFunction($name, $func) {
		$this->libFunctions[$name] = $func;
	}

	public function unregisterFunction($name) {
		unset($this->libFunctions[$name]);
	}

	public function setVariables($variables) {
		$this->registeredVars = array_merge($this->registeredVars, $variables);
	}

	public function unsetVariable($name) {
		unset($this->registeredVars[$name]);
	}

	public function setImportDir($dirs) {
		$this->importDir = (array)$dirs;
	}

	public function addImportDir($dir) {
		$this->importDir = (array)$this->importDir;
		$this->importDir[] = $dir;
	}

	public function allParsedFiles() {
		return $this->allParsedFiles;
	}

	public function addParsedFile($file) {
		$this->allParsedFiles[realpath($file)] = filemtime($file);
	}

	/**
	 * Uses the current value of $this->count to show line and line number
	 */
	public function throwError($msg = null) {
		if ($this->sourceLoc >= 0) {
			$this->sourceParser->throwError($msg, $this->sourceLoc);
		}
		throw new exception($msg);
	}

	// compile file $in to file $out if $in is newer than $out
	// returns true when it compiles, false otherwise
	public static function ccompile($in, $out, $less = null) {
		if ($less === null) {
			$less = new self;
		}
		return $less->checkedCompile($in, $out);
	}

	public static function cexecute($in, $force = false, $less = null) {
		if ($less === null) {
			$less = new self;
		}
		return $less->cachedCompile($in, $force);
	}

	static protected $cssColors = array(
		'aliceblue' => '240,248,255',
		'antiquewhite' => '250,235,215',
		'aqua' => '0,255,255',
		'aquamarine' => '127,255,212',
		'azure' => '240,255,255',
		'beige' => '245,245,220',
		'bisque' => '255,228,196',
		'black' => '0,0,0',
		'blanchedalmond' => '255,235,205',
		'blue' => '0,0,255',
		'blueviolet' => '138,43,226',
		'brown' => '165,42,42',
		'burlywood' => '222,184,135',
		'cadetblue' => '95,158,160',
		'chartreuse' => '127,255,0',
		'chocolate' => '210,105,30',
		'coral' => '255,127,80',
		'cornflowerblue' => '100,149,237',
		'cornsilk' => '255,248,220',
		'crimson' => '220,20,60',
		'cyan' => '0,255,255',
		'darkblue' => '0,0,139',
		'darkcyan' => '0,139,139',
		'darkgoldenrod' => '184,134,11',
		'darkgray' => '169,169,169',
		'darkgreen' => '0,100,0',
		'darkgrey' => '169,169,169',
		'darkkhaki' => '189,183,107',
		'darkmagenta' => '139,0,139',
		'darkolivegreen' => '85,107,47',
		'darkorange' => '255,140,0',
		'darkorchid' => '153,50,204',
		'darkred' => '139,0,0',
		'darksalmon' => '233,150,122',
		'darkseagreen' => '143,188,143',
		'darkslateblue' => '72,61,139',
		'darkslategray' => '47,79,79',
		'darkslategrey' => '47,79,79',
		'darkturquoise' => '0,206,209',
		'darkviolet' => '148,0,211',
		'deeppink' => '255,20,147',
		'deepskyblue' => '0,191,255',
		'dimgray' => '105,105,105',
		'dimgrey' => '105,105,105',
		'dodgerblue' => '30,144,255',
		'firebrick' => '178,34,34',
		'floralwhite' => '255,250,240',
		'forestgreen' => '34,139,34',
		'fuchsia' => '255,0,255',
		'gainsboro' => '220,220,220',
		'ghostwhite' => '248,248,255',
		'gold' => '255,215,0',
		'goldenrod' => '218,165,32',
		'gray' => '128,128,128',
		'green' => '0,128,0',
		'greenyellow' => '173,255,47',
		'grey' => '128,128,128',
		'honeydew' => '240,255,240',
		'hotpink' => '255,105,180',
		'indianred' => '205,92,92',
		'indigo' => '75,0,130',
		'ivory' => '255,255,240',
		'khaki' => '240,230,140',
		'lavender' => '230,230,250',
		'lavenderblush' => '255,240,245',
		'lawngreen' => '124,252,0',
		'lemonchiffon' => '255,250,205',
		'lightblue' => '173,216,230',
		'lightcoral' => '240,128,128',
		'lightcyan' => '224,255,255',
		'lightgoldenrodyellow' => '250,250,210',
		'lightgray' => '211,211,211',
		'lightgreen' => '144,238,144',
		'lightgrey' => '211,211,211',
		'lightpink' => '255,182,193',
		'lightsalmon' => '255,160,122',
		'lightseagreen' => '32,178,170',
		'lightskyblue' => '135,206,250',
		'lightslategray' => '119,136,153',
		'lightslategrey' => '119,136,153',
		'lightsteelblue' => '176,196,222',
		'lightyellow' => '255,255,224',
		'lime' => '0,255,0',
		'limegreen' => '50,205,50',
		'linen' => '250,240,230',
		'magenta' => '255,0,255',
		'maroon' => '128,0,0',
		'mediumaquamarine' => '102,205,170',
		'mediumblue' => '0,0,205',
		'mediumorchid' => '186,85,211',
		'mediumpurple' => '147,112,219',
		'mediumseagreen' => '60,179,113',
		'mediumslateblue' => '123,104,238',
		'mediumspringgreen' => '0,250,154',
		'mediumturquoise' => '72,209,204',
		'mediumvioletred' => '199,21,133',
		'midnightblue' => '25,25,112',
		'mintcream' => '245,255,250',
		'mistyrose' => '255,228,225',
		'moccasin' => '255,228,181',
		'navajowhite' => '255,222,173',
		'navy' => '0,0,128',
		'oldlace' => '253,245,230',
		'olive' => '128,128,0',
		'olivedrab' => '107,142,35',
		'orange' => '255,165,0',
		'orangered' => '255,69,0',
		'orchid' => '218,112,214',
		'palegoldenrod' => '238,232,170',
		'palegreen' => '152,251,152',
		'paleturquoise' => '175,238,238',
		'palevioletred' => '219,112,147',
		'papayawhip' => '255,239,213',
		'peachpuff' => '255,218,185',
		'peru' => '205,133,63',
		'pink' => '255,192,203',
		'plum' => '221,160,221',
		'powderblue' => '176,224,230',
		'purple' => '128,0,128',
		'red' => '255,0,0',
		'rosybrown' => '188,143,143',
		'royalblue' => '65,105,225',
		'saddlebrown' => '139,69,19',
		'salmon' => '250,128,114',
		'sandybrown' => '244,164,96',
		'seagreen' => '46,139,87',
		'seashell' => '255,245,238',
		'sienna' => '160,82,45',
		'silver' => '192,192,192',
		'skyblue' => '135,206,235',
		'slateblue' => '106,90,205',
		'slategray' => '112,128,144',
		'slategrey' => '112,128,144',
		'snow' => '255,250,250',
		'springgreen' => '0,255,127',
		'steelblue' => '70,130,180',
		'tan' => '210,180,140',
		'teal' => '0,128,128',
		'thistle' => '216,191,216',
		'tomato' => '255,99,71',
		'transparent' => '0,0,0,0',
		'turquoise' => '64,224,208',
		'violet' => '238,130,238',
		'wheat' => '245,222,179',
		'white' => '255,255,255',
		'whitesmoke' => '245,245,245',
		'yellow' => '255,255,0',
		'yellowgreen' => '154,205,50'
	);
}

// responsible for taking a string of LESS code and converting it into a
// syntax tree
class lessc_parser {
	static protected $nextBlockId = 0; // used to uniquely identify blocks

	static protected $precedence = array(
		'=<' => 0,
		'>=' => 0,
		'=' => 0,
		'<' => 0,
		'>' => 0,

		'+' => 1,
		'-' => 1,
		'*' => 2,
		'/' => 2,
		'%' => 2,
	);

	static protected $whitePattern;
	static protected $commentMulti;

	static protected $commentSingle = "//";
	static protected $commentMultiLeft = "/*";
	static protected $commentMultiRight = "*/";

	// regex string to match any of the operators
	static protected $operatorString;

	// these properties will supress division unless it's inside parenthases
	static protected $supressDivisionProps =
		array('/border-radius$/i', '/^font$/i');

	protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
	protected $lineDirectives = array("charset");

	/**
	 * if we are in parens we can be more liberal with whitespace around
	 * operators because it must evaluate to a single value and thus is less
	 * ambiguous.
	 *
	 * Consider:
	 *     property1: 10 -5; // is two numbers, 10 and -5
	 *     property2: (10 -5); // should evaluate to 5
	 */
	protected $inParens = false;

	// caches preg escaped literals
	static protected $literalCache = array();

	public function __construct($lessc, $sourceName = null) {
		$this->eatWhiteDefault = true;
		// reference to less needed for vPrefix, mPrefix, and parentSelector
		$this->lessc = $lessc;

		$this->sourceName = $sourceName; // name used for error messages

		$this->writeComments = false;

		if (!self::$operatorString) {
			self::$operatorString =
				'('.implode('|', array_map(array('lessc', 'preg_quote'),
					array_keys(self::$precedence))).')';

			$commentSingle = lessc::preg_quote(self::$commentSingle);
			$commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
			$commentMultiRight = lessc::preg_quote(self::$commentMultiRight);

			self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
			self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
		}
	}

	public function parse($buffer) {
		$this->count = 0;
		$this->line = 1;

		$this->env = null; // block stack
		$this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
		$this->pushSpecialBlock("root");
		$this->eatWhiteDefault = true;
		$this->seenComments = array();

		// trim whitespace on head
		// if (preg_match('/^\s+/', $this->buffer, $m)) {
		// 	$this->line += substr_count($m[0], "\n");
		// 	$this->buffer = ltrim($this->buffer);
		// }
		$this->whitespace();

		// parse the entire file
		while (false !== $this->parseChunk());

		if ($this->count != strlen($this->buffer))
			$this->throwError();

		// TODO report where the block was opened
		if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
			throw new exception('parse error: unclosed block');

		return $this->env;
	}

	/**
	 * Parse a single chunk off the head of the buffer and append it to the
	 * current parse environment.
	 * Returns false when the buffer is empty, or when there is an error.
	 *
	 * This function is called repeatedly until the entire document is
	 * parsed.
	 *
	 * This parser is most similar to a recursive descent parser. Single
	 * functions represent discrete grammatical rules for the language, and
	 * they are able to capture the text that represents those rules.
	 *
	 * Consider the function lessc::keyword(). (all parse functions are
	 * structured the same)
	 *
	 * The function takes a single reference argument. When calling the
	 * function it will attempt to match a keyword on the head of the buffer.
	 * If it is successful, it will place the keyword in the referenced
	 * argument, advance the position in the buffer, and return true. If it
	 * fails then it won't advance the buffer and it will return false.
	 *
	 * All of these parse functions are powered by lessc::match(), which behaves
	 * the same way, but takes a literal regular expression. Sometimes it is
	 * more convenient to use match instead of creating a new function.
	 *
	 * Because of the format of the functions, to parse an entire string of
	 * grammatical rules, you can chain them together using &&.
	 *
	 * But, if some of the rules in the chain succeed before one fails, then
	 * the buffer position will be left at an invalid state. In order to
	 * avoid this, lessc::seek() is used to remember and set buffer positions.
	 *
	 * Before parsing a chain, use $s = $this->seek() to remember the current
	 * position into $s. Then if a chain fails, use $this->seek($s) to
	 * go back where we started.
	 */
	protected function parseChunk() {
		if (empty($this->buffer)) return false;
		$s = $this->seek();

		if ($this->whitespace()) {
			return true;
		}

		// setting a property
		if ($this->keyword($key) && $this->assign() &&
			$this->propertyValue($value, $key) && $this->end())
		{
			$this->append(array('assign', $key, $value), $s);
			return true;
		} else {
			$this->seek($s);
		}


		// look for special css blocks
		if ($this->literal('@', false)) {
			$this->count--;

			// media
			if ($this->literal('@media')) {
				if (($this->mediaQueryList($mediaQueries) || true)
					&& $this->literal('{'))
				{
					$media = $this->pushSpecialBlock("media");
					$media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
					return true;
				} else {
					$this->seek($s);
					return false;
				}
			}

			if ($this->literal("@", false) && $this->keyword($dirName)) {
				if ($this->isDirective($dirName, $this->blockDirectives)) {
					if (($this->openString("{", $dirValue, null, array(";")) || true) &&
						$this->literal("{"))
					{
						$dir = $this->pushSpecialBlock("directive");
						$dir->name = $dirName;
						if (isset($dirValue)) $dir->value = $dirValue;
						return true;
					}
				} elseif ($this->isDirective($dirName, $this->lineDirectives)) {
					if ($this->propertyValue($dirValue) && $this->end()) {
						$this->append(array("directive", $dirName, $dirValue));
						return true;
					}
				}
			}

			$this->seek($s);
		}

		// setting a variable
		if ($this->variable($var) && $this->assign() &&
			$this->propertyValue($value) && $this->end())
		{
			$this->append(array('assign', $var, $value), $s);
			return true;
		} else {
			$this->seek($s);
		}

		if ($this->import($importValue)) {
			$this->append($importValue, $s);
			return true;
		}

		// opening parametric mixin
		if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
			($this->guards($guards) || true) &&
			$this->literal('{'))
		{
			$block = $this->pushBlock($this->fixTags(array($tag)));
			$block->args = $args;
			$block->isVararg = $isVararg;
			if (!empty($guards)) $block->guards = $guards;
			return true;
		} else {
			$this->seek($s);
		}

		// opening a simple block
		if ($this->tags($tags) && $this->literal('{', false)) {
			$tags = $this->fixTags($tags);
			$this->pushBlock($tags);
			return true;
		} else {
			$this->seek($s);
		}

		// closing a block
		if ($this->literal('}', false)) {
			try {
				$block = $this->pop();
			} catch (exception $e) {
				$this->seek($s);
				$this->throwError($e->getMessage());
			}

			$hidden = false;
			if (is_null($block->type)) {
				$hidden = true;
				if (!isset($block->args)) {
					foreach ($block->tags as $tag) {
						if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
							$hidden = false;
							break;
						}
					}
				}

				foreach ($block->tags as $tag) {
					if (is_string($tag)) {
						$this->env->children[$tag][] = $block;
					}
				}
			}

			if (!$hidden) {
				$this->append(array('block', $block), $s);
			}

			// this is done here so comments aren't bundled into he block that
			// was just closed
			$this->whitespace();
			return true;
		}

		// mixin
		if ($this->mixinTags($tags) &&
			($this->argumentDef($argv, $isVararg) || true) &&
			($this->keyword($suffix) || true) && $this->end())
		{
			$tags = $this->fixTags($tags);
			$this->append(array('mixin', $tags, $argv, $suffix), $s);
			return true;
		} else {
			$this->seek($s);
		}

		// spare ;
		if ($this->literal(';')) return true;

		return false; // got nothing, throw error
	}

	protected function isDirective($dirname, $directives) {
		// TODO: cache pattern in parser
		$pattern = implode("|",
			array_map(array("lessc", "preg_quote"), $directives));
		$pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';

		return preg_match($pattern, $dirname);
	}

	protected function fixTags($tags) {
		// move @ tags out of variable namespace
		foreach ($tags as &$tag) {
			if ($tag{0} == $this->lessc->vPrefix)
				$tag[0] = $this->lessc->mPrefix;
		}
		return $tags;
	}

	// a list of expressions
	protected function expressionList(&$exps) {
		$values = array();

		while ($this->expression($exp)) {
			$values[] = $exp;
		}

		if (count($values) == 0) return false;

		$exps = lessc::compressList($values, ' ');
		return true;
	}

	/**
	 * Attempt to consume an expression.
	 * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
	 */
	protected function expression(&$out) {
		if ($this->value($lhs)) {
			$out = $this->expHelper($lhs, 0);

			// look for / shorthand
			if (!empty($this->env->supressedDivision)) {
				unset($this->env->supressedDivision);
				$s = $this->seek();
				if ($this->literal("/") && $this->value($rhs)) {
					$out = array("list", "",
						array($out, array("keyword", "/"), $rhs));
				} else {
					$this->seek($s);
				}
			}

			return true;
		}
		return false;
	}

	/**
	 * recursively parse infix equation with $lhs at precedence $minP
	 */
	protected function expHelper($lhs, $minP) {
		$this->inExp = true;
		$ss = $this->seek();

		while (true) {
			$whiteBefore = isset($this->buffer[$this->count - 1]) &&
				ctype_space($this->buffer[$this->count - 1]);

			// If there is whitespace before the operator, then we require
			// whitespace after the operator for it to be an expression
			$needWhite = $whiteBefore && !$this->inParens;

			if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
				if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
					foreach (self::$supressDivisionProps as $pattern) {
						if (preg_match($pattern, $this->env->currentProperty)) {
							$this->env->supressedDivision = true;
							break 2;
						}
					}
				}


				$whiteAfter = isset($this->buffer[$this->count - 1]) &&
					ctype_space($this->buffer[$this->count - 1]);

				if (!$this->value($rhs)) break;

				// peek for next operator to see what to do with rhs
				if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
					$rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
				}

				$lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
				$ss = $this->seek();

				continue;
			}

			break;
		}

		$this->seek($ss);

		return $lhs;
	}

	// consume a list of values for a property
	public function propertyValue(&$value, $keyName = null) {
		$values = array();

		if ($keyName !== null) $this->env->currentProperty = $keyName;

		$s = null;
		while ($this->expressionList($v)) {
			$values[] = $v;
			$s = $this->seek();
			if (!$this->literal(',')) break;
		}

		if ($s) $this->seek($s);

		if ($keyName !== null) unset($this->env->currentProperty);

		if (count($values) == 0) return false;

		$value = lessc::compressList($values, ', ');
		return true;
	}

	protected function parenValue(&$out) {
		$s = $this->seek();

		// speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
			return false;
		}

		$inParens = $this->inParens;
		if ($this->literal("(") &&
			($this->inParens = true) && $this->expression($exp) &&
			$this->literal(")"))
		{
			$out = $exp;
			$this->inParens = $inParens;
			return true;
		} else {
			$this->inParens = $inParens;
			$this->seek($s);
		}

		return false;
	}

	// a single value
	protected function value(&$value) {
		$s = $this->seek();

		// speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
			// negation
			if ($this->literal("-", false) &&
				(($this->variable($inner) && $inner = array("variable", $inner)) ||
				$this->unit($inner) ||
				$this->parenValue($inner)))
			{
				$value = array("unary", "-", $inner);
				return true;
			} else {
				$this->seek($s);
			}
		}

		if ($this->parenValue($value)) return true;
		if ($this->unit($value)) return true;
		if ($this->color($value)) return true;
		if ($this->func($value)) return true;
		if ($this->string($value)) return true;

		if ($this->keyword($word)) {
			$value = array('keyword', $word);
			return true;
		}

		// try a variable
		if ($this->variable($var)) {
			$value = array('variable', $var);
			return true;
		}

		// unquote string (should this work on any type?
		if ($this->literal("~") && $this->string($str)) {
			$value = array("escape", $str);
			return true;
		} else {
			$this->seek($s);
		}

		// css hack: \0
		if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
			$value = array('keyword', '\\'.$m[1]);
			return true;
		} else {
			$this->seek($s);
		}

		return false;
	}

	// an import statement
	protected function import(&$out) {
		if (!$this->literal('@import')) return false;

		// @import "something.css" media;
		// @import url("something.css") media;
		// @import url(something.css) media;

		if ($this->propertyValue($value)) {
			$out = array("import", $value);
			return true;
		}
	}

	protected function mediaQueryList(&$out) {
		if ($this->genericList($list, "mediaQuery", ",", false)) {
			$out = $list[2];
			return true;
		}
		return false;
	}

	protected function mediaQuery(&$out) {
		$s = $this->seek();

		$expressions = null;
		$parts = array();

		if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
			$prop = array("mediaType");
			if (isset($only)) $prop[] = "only";
			if (isset($not)) $prop[] = "not";
			$prop[] = $mediaType;
			$parts[] = $prop;
		} else {
			$this->seek($s);
		}


		if (!empty($mediaType) && !$this->literal("and")) {
			// ~
		} else {
			$this->genericList($expressions, "mediaExpression", "and", false);
			if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
		}

		if (count($parts) == 0) {
			$this->seek($s);
			return false;
		}

		$out = $parts;
		return true;
	}

	protected function mediaExpression(&$out) {
		$s = $this->seek();
		$value = null;
		if ($this->literal("(") &&
			$this->keyword($feature) &&
			($this->literal(":") && $this->expression($value) || true) &&
			$this->literal(")"))
		{
			$out = array("mediaExp", $feature);
			if ($value) $out[] = $value;
			return true;
		} elseif ($this->variable($variable)) {
			$out = array('variable', $variable);
			return true;
		}

		$this->seek($s);
		return false;
	}

	// an unbounded string stopped by $end
	protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = false;

		$stop = array("'", '"', "@{", $end);
		$stop = array_map(array("lessc", "preg_quote"), $stop);
		// $stop[] = self::$commentMulti;

		if (!is_null($rejectStrs)) {
			$stop = array_merge($stop, $rejectStrs);
		}

		$patt = '(.*?)('.implode("|", $stop).')';

		$nestingLevel = 0;

		$content = array();
		while ($this->match($patt, $m, false)) {
			if (!empty($m[1])) {
				$content[] = $m[1];
				if ($nestingOpen) {
					$nestingLevel += substr_count($m[1], $nestingOpen);
				}
			}

			$tok = $m[2];

			$this->count-= strlen($tok);
			if ($tok == $end) {
				if ($nestingLevel == 0) {
					break;
				} else {
					$nestingLevel--;
				}
			}

			if (($tok == "'" || $tok == '"') && $this->string($str)) {
				$content[] = $str;
				continue;
			}

			if ($tok == "@{" && $this->interpolation($inter)) {
				$content[] = $inter;
				continue;
			}

			if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
				break;
			}

			$content[] = $tok;
			$this->count+= strlen($tok);
		}

		$this->eatWhiteDefault = $oldWhite;

		if (count($content) == 0) return false;

		// trim the end
		if (is_string(end($content))) {
			$content[count($content) - 1] = rtrim(end($content));
		}

		$out = array("string", "", $content);
		return true;
	}

	protected function string(&$out) {
		$s = $this->seek();
		if ($this->literal('"', false)) {
			$delim = '"';
		} elseif ($this->literal("'", false)) {
			$delim = "'";
		} else {
			return false;
		}

		$content = array();

		// look for either ending delim , escape, or string interpolation
		$patt = '([^\n]*?)(@\{|\\\\|' .
			lessc::preg_quote($delim).')';

		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = false;

		while ($this->match($patt, $m, false)) {
			$content[] = $m[1];
			if ($m[2] == "@{") {
				$this->count -= strlen($m[2]);
				if ($this->interpolation($inter, false)) {
					$content[] = $inter;
				} else {
					$this->count += strlen($m[2]);
					$content[] = "@{"; // ignore it
				}
			} elseif ($m[2] == '\\') {
				$content[] = $m[2];
				if ($this->literal($delim, false)) {
					$content[] = $delim;
				}
			} else {
				$this->count -= strlen($delim);
				break; // delim
			}
		}

		$this->eatWhiteDefault = $oldWhite;

		if ($this->literal($delim)) {
			$out = array("string", $delim, $content);
			return true;
		}

		$this->seek($s);
		return false;
	}

	protected function interpolation(&$out) {
		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = true;

		$s = $this->seek();
		if ($this->literal("@{") &&
			$this->openString("}", $interp, null, array("'", '"', ";")) &&
			$this->literal("}", false))
		{
			$out = array("interpolate", $interp);
			$this->eatWhiteDefault = $oldWhite;
			if ($this->eatWhiteDefault) $this->whitespace();
			return true;
		}

		$this->eatWhiteDefault = $oldWhite;
		$this->seek($s);
		return false;
	}

	protected function unit(&$unit) {
		// speed shortcut
		if (isset($this->buffer[$this->count])) {
			$char = $this->buffer[$this->count];
			if (!ctype_digit($char) && $char != ".") return false;
		}

		if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
			$unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
			return true;
		}
		return false;
	}

	// a # color
	protected function color(&$out) {
		if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
			if (strlen($m[1]) > 7) {
				$out = array("string", "", array($m[1]));
			} else {
				$out = array("raw_color", $m[1]);
			}
			return true;
		}

		return false;
	}

	// consume an argument definition list surrounded by ()
	// each argument is a variable name with optional value
	// or at the end a ... or a variable named followed by ...
	// arguments are separated by , unless a ; is in the list, then ; is the
	// delimiter.
	protected function argumentDef(&$args, &$isVararg) {
		$s = $this->seek();
		if (!$this->literal('(')) return false;

		$values = array();
		$delim = ",";
		$method = "expressionList";

		$isVararg = false;
		while (true) {
			if ($this->literal("...")) {
				$isVararg = true;
				break;
			}

			if ($this->$method($value)) {
				if ($value[0] == "variable") {
					$arg = array("arg", $value[1]);
					$ss = $this->seek();

					if ($this->assign() && $this->$method($rhs)) {
						$arg[] = $rhs;
					} else {
						$this->seek($ss);
						if ($this->literal("...")) {
							$arg[0] = "rest";
							$isVararg = true;
						}
					}

					$values[] = $arg;
					if ($isVararg) break;
					continue;
				} else {
					$values[] = array("lit", $value);
				}
			}


			if (!$this->literal($delim)) {
				if ($delim == "," && $this->literal(";")) {
					// found new delim, convert existing args
					$delim = ";";
					$method = "propertyValue";

					// transform arg list
					if (isset($values[1])) { // 2 items
						$newList = array();
						foreach ($values as $i => $arg) {
							switch($arg[0]) {
							case "arg":
								if ($i) {
									$this->throwError("Cannot mix ; and , as delimiter types");
								}
								$newList[] = $arg[2];
								break;
							case "lit":
								$newList[] = $arg[1];
								break;
							case "rest":
								$this->throwError("Unexpected rest before semicolon");
							}
						}

						$newList = array("list", ", ", $newList);

						switch ($values[0][0]) {
						case "arg":
							$newArg = array("arg", $values[0][1], $newList);
							break;
						case "lit":
							$newArg = array("lit", $newList);
							break;
						}

					} elseif ($values) { // 1 item
						$newArg = $values[0];
					}

					if ($newArg) {
						$values = array($newArg);
					}
				} else {
					break;
				}
			}
		}

		if (!$this->literal(')')) {
			$this->seek($s);
			return false;
		}

		$args = $values;

		return true;
	}

	// consume a list of tags
	// this accepts a hanging delimiter
	protected function tags(&$tags, $simple = false, $delim = ',') {
		$tags = array();
		while ($this->tag($tt, $simple)) {
			$tags[] = $tt;
			if (!$this->literal($delim)) break;
		}
		if (count($tags) == 0) return false;

		return true;
	}

	// list of tags of specifying mixin path
	// optionally separated by > (lazy, accepts extra >)
	protected function mixinTags(&$tags) {
		$tags = array();
		while ($this->tag($tt, true)) {
			$tags[] = $tt;
			$this->literal(">");
		}

		if (count($tags) == 0) return false;

		return true;
	}

	// a bracketed value (contained within in a tag definition)
	protected function tagBracket(&$parts, &$hasExpression) {
		// speed shortcut
		if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
			return false;
		}

		$s = $this->seek();

		$hasInterpolation = false;

		if ($this->literal("[", false)) {
			$attrParts = array("[");
			// keyword, string, operator
			while (true) {
				if ($this->literal("]", false)) {
					$this->count--;
					break; // get out early
				}

				if ($this->match('\s+', $m)) {
					$attrParts[] = " ";
					continue;
				}
				if ($this->string($str)) {
					// escape parent selector, (yuck)
					foreach ($str[2] as &$chunk) {
						$chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
					}

					$attrParts[] = $str;
					$hasInterpolation = true;
					continue;
				}

				if ($this->keyword($word)) {
					$attrParts[] = $word;
					continue;
				}

				if ($this->interpolation($inter, false)) {
					$attrParts[] = $inter;
					$hasInterpolation = true;
					continue;
				}

				// operator, handles attr namespace too
				if ($this->match('[|-~\$\*\^=]+', $m)) {
					$attrParts[] = $m[0];
					continue;
				}

				break;
			}

			if ($this->literal("]", false)) {
				$attrParts[] = "]";
				foreach ($attrParts as $part) {
					$parts[] = $part;
				}
				$hasExpression = $hasExpression || $hasInterpolation;
				return true;
			}
			$this->seek($s);
		}

		$this->seek($s);
		return false;
	}

	// a space separated list of selectors
	protected function tag(&$tag, $simple = false) {
		if ($simple)
			$chars = '^@,:;{}\][>\(\) "\'';
		else
			$chars = '^@,;{}["\'';

		$s = $this->seek();

		$hasExpression = false;
		$parts = array();
		while ($this->tagBracket($parts, $hasExpression));

		$oldWhite = $this->eatWhiteDefault;
		$this->eatWhiteDefault = false;

		while (true) {
			if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
				$parts[] = $m[1];
				if ($simple) break;

				while ($this->tagBracket($parts, $hasExpression));
				continue;
			}

			if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
				if ($this->interpolation($interp)) {
					$hasExpression = true;
					$interp[2] = true; // don't unescape
					$parts[] = $interp;
					continue;
				}

				if ($this->literal("@")) {
					$parts[] = "@";
					continue;
				}
			}

			if ($this->unit($unit)) { // for keyframes
				$parts[] = $unit[1];
				$parts[] = $unit[2];
				continue;
			}

			break;
		}

		$this->eatWhiteDefault = $oldWhite;
		if (!$parts) {
			$this->seek($s);
			return false;
		}

		if ($hasExpression) {
			$tag = array("exp", array("string", "", $parts));
		} else {
			$tag = trim(implode($parts));
		}

		$this->whitespace();
		return true;
	}

	// a css function
	protected function func(&$func) {
		$s = $this->seek();

		if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
			$fname = $m[1];

			$sPreArgs = $this->seek();

			$args = array();
			while (true) {
				$ss = $this->seek();
				// this ugly nonsense is for ie filter properties
				if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
					$args[] = array("string", "", array($name, "=", $value));
				} else {
					$this->seek($ss);
					if ($this->expressionList($value)) {
						$args[] = $value;
					}
				}

				if (!$this->literal(',')) break;
			}
			$args = array('list', ',', $args);

			if ($this->literal(')')) {
				$func = array('function', $fname, $args);
				return true;
			} elseif ($fname == 'url') {
				// couldn't parse and in url? treat as string
				$this->seek($sPreArgs);
				if ($this->openString(")", $string) && $this->literal(")")) {
					$func = array('function', $fname, $string);
					return true;
				}
			}
		}

		$this->seek($s);
		return false;
	}

	// consume a less variable
	protected function variable(&$name) {
		$s = $this->seek();
		if ($this->literal($this->lessc->vPrefix, false) &&
			($this->variable($sub) || $this->keyword($name)))
		{
			if (!empty($sub)) {
				$name = array('variable', $sub);
			} else {
				$name = $this->lessc->vPrefix.$name;
			}
			return true;
		}

		$name = null;
		$this->seek($s);
		return false;
	}

	/**
	 * Consume an assignment operator
	 * Can optionally take a name that will be set to the current property name
	 */
	protected function assign($name = null) {
		if ($name) $this->currentProperty = $name;
		return $this->literal(':') || $this->literal('=');
	}

	// consume a keyword
	protected function keyword(&$word) {
		if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
			$word = $m[1];
			return true;
		}
		return false;
	}

	// consume an end of statement delimiter
	protected function end() {
		if ($this->literal(';', false)) {
			return true;
		} elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
			// if there is end of file or a closing block next then we don't need a ;
			return true;
		}
		return false;
	}

	protected function guards(&$guards) {
		$s = $this->seek();

		if (!$this->literal("when")) {
			$this->seek($s);
			return false;
		}

		$guards = array();

		while ($this->guardGroup($g)) {
			$guards[] = $g;
			if (!$this->literal(",")) break;
		}

		if (count($guards) == 0) {
			$guards = null;
			$this->seek($s);
			return false;
		}

		return true;
	}

	// a bunch of guards that are and'd together
	// TODO rename to guardGroup
	protected function guardGroup(&$guardGroup) {
		$s = $this->seek();
		$guardGroup = array();
		while ($this->guard($guard)) {
			$guardGroup[] = $guard;
			if (!$this->literal("and")) break;
		}

		if (count($guardGroup) == 0) {
			$guardGroup = null;
			$this->seek($s);
			return false;
		}

		return true;
	}

	protected function guard(&$guard) {
		$s = $this->seek();
		$negate = $this->literal("not");

		if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
			$guard = $exp;
			if ($negate) $guard = array("negate", $guard);
			return true;
		}

		$this->seek($s);
		return false;
	}

	/* raw parsing functions */

	protected function literal($what, $eatWhitespace = null) {
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;

		// shortcut on single letter
		if (!isset($what[1]) && isset($this->buffer[$this->count])) {
			if ($this->buffer[$this->count] == $what) {
				if (!$eatWhitespace) {
					$this->count++;
					return true;
				}
				// goes below...
			} else {
				return false;
			}
		}

		if (!isset(self::$literalCache[$what])) {
			self::$literalCache[$what] = lessc::preg_quote($what);
		}

		return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
	}

	protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
		$s = $this->seek();
		$items = array();
		while ($this->$parseItem($value)) {
			$items[] = $value;
			if ($delim) {
				if (!$this->literal($delim)) break;
			}
		}

		if (count($items) == 0) {
			$this->seek($s);
			return false;
		}

		if ($flatten && count($items) == 1) {
			$out = $items[0];
		} else {
			$out = array("list", $delim, $items);
		}

		return true;
	}


	// advance counter to next occurrence of $what
	// $until - don't include $what in advance
	// $allowNewline, if string, will be used as valid char set
	protected function to($what, &$out, $until = false, $allowNewline = false) {
		if (is_string($allowNewline)) {
			$validChars = $allowNewline;
		} else {
			$validChars = $allowNewline ? "." : "[^\n]";
		}
		if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
		if ($until) $this->count -= strlen($what); // give back $what
		$out = $m[1];
		return true;
	}

	// try to match something on head of buffer
	protected function match($regex, &$out, $eatWhitespace = null) {
		if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;

		$r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
			$this->count += strlen($out[0]);
			if ($eatWhitespace && $this->writeComments) $this->whitespace();
			return true;
		}
		return false;
	}

	// match some whitespace
	protected function whitespace() {
		if ($this->writeComments) {
			$gotWhite = false;
			while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
				if (isset($m[1]) && empty($this->seenComments[$this->count])) {
					$this->append(array("comment", $m[1]));
					$this->seenComments[$this->count] = true;
				}
				$this->count += strlen($m[0]);
				$gotWhite = true;
			}
			return $gotWhite;
		} else {
			$this->match("", $m);
			return strlen($m[0]) > 0;
		}
	}

	// match something without consuming it
	protected function peek($regex, &$out = null, $from=null) {
		if (is_null($from)) $from = $this->count;
		$r = '/'.$regex.'/Ais';
		$result = preg_match($r, $this->buffer, $out, null, $from);

		return $result;
	}

	// seek to a spot in the buffer or return where we are on no argument
	protected function seek($where = null) {
		if ($where === null) return $this->count;
		else $this->count = $where;
		return true;
	}

	/* misc functions */

	public function throwError($msg = "parse error", $count = null) {
		$count = is_null($count) ? $this->count : $count;

		$line = $this->line +
			substr_count(substr($this->buffer, 0, $count), "\n");

		if (!empty($this->sourceName)) {
			$loc = "$this->sourceName on line $line";
		} else {
			$loc = "line: $line";
		}

		// TODO this depends on $this->count
		if ($this->peek("(.*?)(\n|$)", $m, $count)) {
			throw new exception("$msg: failed at `$m[1]` $loc");
		} else {
			throw new exception("$msg: $loc");
		}
	}

	protected function pushBlock($selectors=null, $type=null) {
		$b = new stdclass;
		$b->parent = $this->env;

		$b->type = $type;
		$b->id = self::$nextBlockId++;

		$b->isVararg = false; // TODO: kill me from here
		$b->tags = $selectors;

		$b->props = array();
		$b->children = array();

		$this->env = $b;
		return $b;
	}

	// push a block that doesn't multiply tags
	protected function pushSpecialBlock($type) {
		return $this->pushBlock(null, $type);
	}

	// append a property to the current block
	protected function append($prop, $pos = null) {
		if ($pos !== null) $prop[-1] = $pos;
		$this->env->props[] = $prop;
	}

	// pop something off the stack
	protected function pop() {
		$old = $this->env;
		$this->env = $this->env->parent;
		return $old;
	}

	// remove comments from $text
	// todo: make it work for all functions, not just url
	protected function removeComments($text) {
		$look = array(
			'url(', '//', '/*', '"', "'"
		);

		$out = '';
		$min = null;
		while (true) {
			// find the next item
			foreach ($look as $token) {
				$pos = strpos($text, $token);
				if ($pos !== false) {
					if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
				}
			}

			if (is_null($min)) break;

			$count = $min[1];
			$skip = 0;
			$newlines = 0;
			switch ($min[0]) {
			case 'url(':
				if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
					$count += strlen($m[0]) - strlen($min[0]);
				break;
			case '"':
			case "'":
				if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
					$count += strlen($m[0]) - 1;
				break;
			case '//':
				$skip = strpos($text, "\n", $count);
				if ($skip === false) $skip = strlen($text) - $count;
				else $skip -= $count;
				break;
			case '/*':
				if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
					$skip = strlen($m[0]);
					$newlines = substr_count($m[0], "\n");
				}
				break;
			}

			if ($skip == 0) $count += strlen($min[0]);

			$out .= substr($text, 0, $count).str_repeat("\n", $newlines);
			$text = substr($text, $count + $skip);

			$min = null;
		}

		return $out.$text;
	}

}

class lessc_formatter_classic {
	public $indentChar = "  ";

	public $break = "\n";
	public $open = " {";
	public $close = "}";
	public $selectorSeparator = ", ";
	public $assignSeparator = ":";

	public $openSingle = " { ";
	public $closeSingle = " }";

	public $disableSingle = false;
	public $breakSelectors = false;

	public $compressColors = false;

	public function __construct() {
		$this->indentLevel = 0;
	}

	public function indentStr($n = 0) {
		return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
	}

	public function property($name, $value) {
		return $name . $this->assignSeparator . $value . ";";
	}

	protected function isEmpty($block) {
		if (empty($block->lines)) {
			foreach ($block->children as $child) {
				if (!$this->isEmpty($child)) return false;
			}

			return true;
		}
		return false;
	}

	public function block($block) {
		if ($this->isEmpty($block)) return;

		$inner = $pre = $this->indentStr();

		$isSingle = !$this->disableSingle &&
			is_null($block->type) && count($block->lines) == 1;

		if (!empty($block->selectors)) {
			$this->indentLevel++;

			if ($this->breakSelectors) {
				$selectorSeparator = $this->selectorSeparator . $this->break . $pre;
			} else {
				$selectorSeparator = $this->selectorSeparator;
			}

			echo $pre .
				implode($selectorSeparator, $block->selectors);
			if ($isSingle) {
				echo $this->openSingle;
				$inner = "";
			} else {
				echo $this->open . $this->break;
				$inner = $this->indentStr();
			}

		}

		if (!empty($block->lines)) {
			$glue = $this->break.$inner;
			echo $inner . implode($glue, $block->lines);
			if (!$isSingle && !empty($block->children)) {
				echo $this->break;
			}
		}

		foreach ($block->children as $child) {
			$this->block($child);
		}

		if (!empty($block->selectors)) {
			if (!$isSingle && empty($block->children)) echo $this->break;

			if ($isSingle) {
				echo $this->closeSingle . $this->break;
			} else {
				echo $pre . $this->close . $this->break;
			}

			$this->indentLevel--;
		}
	}
}

class lessc_formatter_compressed extends lessc_formatter_classic {
	public $disableSingle = true;
	public $open = "{";
	public $selectorSeparator = ",";
	public $assignSeparator = ":";
	public $break = "";
	public $compressColors = true;

	public function indentStr($n = 0) {
		return "";
	}
}

class lessc_formatter_lessjs extends lessc_formatter_classic {
	public $disableSingle = true;
	public $breakSelectors = true;
	public $assignSeparator = ": ";
	public $selectorSeparator = ",";
}


vendor/leafo/lessphp/lessify000064400000000635152177723700012225 0ustar00#!/usr/bin/php
<?php

if (php_sapi_name() != "cli") {
	err($fa.$argv[0]." must be run in the command line.");
	exit(1);
}
$exe = array_shift($argv); // remove filename

if (!$fname = array_shift($argv)) {
	exit("Usage: ".$exe." input-file\n");
}

require "lessify.inc.php";

try  {
	$parser = new lessify($fname);
	echo $parser->parse();
} catch (exception $e) {
	exit("Fatal error: ".$e->getMessage()."\n");
}


vendor/leafo/lessphp/plessc000064400000011457152177723700012044 0ustar00#!/usr/bin/env php
<?php
// Command line utility to compile LESS to STDOUT
// Leaf Corcoran <leafot@gmail.com>, 2013

$exe = array_shift($argv); // remove filename

$HELP = <<<EOT
Usage: $exe [options] input-file [output-file]

Options include:

    -h, --help  Show this message
    -v          Print the version
    -f=format   Set the output format, includes "default", "compressed"
    -c          Keep /* */ comments in output
    -r          Read from STDIN instead of input-file
    -w          Watch input-file, and compile to output-file if it is changed
    -T          Dump formatted parse tree
    -X          Dump raw parse tree


EOT;

$opts = getopt('hvrwncXTf:', array('help'));
while (count($argv) > 0 && preg_match('/^-([-hvrwncXT]$|[f]=)/', $argv[0])) {
	array_shift($argv);
}

function has() {
	global $opts;
	foreach (func_get_args() as $arg) {
		if (isset($opts[$arg])) return true;
	}
	return false;
}

if (has("h", "help")) {
	exit($HELP);
}

error_reporting(E_ALL);
$path  = realpath(dirname(__FILE__)).'/';

require $path."lessc.inc.php";

$VERSION = lessc::$VERSION;

$fa = "Fatal Error: ";
function err($msg) {
	fwrite(STDERR, $msg."\n");
}

if (php_sapi_name() != "cli") {
	err($fa.$argv[0]." must be run in the command line.");
	exit(1);
}

function make_less($fname = null) {
	global $opts;
	$l = new lessc($fname);

	if (has("f")) {
		$format = $opts["f"];
		if ($format != "default") $l->setFormatter($format);
	}

	if (has("c")) {
		$l->setPreserveComments(true);
	}

	return $l;
}

function process($data, $import = null) {
	global $fa;

	$l = make_less();
	if ($import) $l->importDir = $import;

	try {
		echo $l->parse($data);
		exit(0);
	} catch (exception $ex) {
		err($fa."\n".str_repeat('=', 20)."\n".
			$ex->getMessage());
		exit(1);
	}
}

if (has("v")) {
	exit($VERSION."\n");
}

if (has("r")) {
	if (!empty($argv)) {
		$data = $argv[0];
	} else {
		$data = "";
		while (!feof(STDIN)) {
			$data .= fread(STDIN, 8192);
		}
	}
	exit(process($data));
}

if (has("w")) {
	// need two files
	if (!is_file($in = array_shift($argv)) ||
		null == $out = array_shift($argv))
	{
		err($fa.$exe." -w infile outfile");
		exit(1);
	}

	echo "Watching ".$in.
		(has("n") ? ' with notifications' : '').
		", press Ctrl + c to exit.\n";

	$cache = $in;
	$last_action = 0;
	while (true) {
		clearstatcache();

		// check if anything has changed since last fail
		$updated = false;
		if (is_array($cache)) {
			foreach ($cache['files'] as $fname=>$_) {
				if (filemtime($fname) > $last_action) {
					$updated = true;
					break;
				}
			}
		} else $updated = true;

		// try to compile it
		if ($updated) {
			$last_action = time();

			try {
				$cache = lessc::cexecute($cache);
				echo "Writing updated file: ".$out."\n";
				if (!file_put_contents($out, $cache['compiled'])) {
					err($fa."Could not write to file ".$out);
					exit(1);
				}
			} catch (exception $ex) {
				echo "\nFatal Error:\n".str_repeat('=', 20)."\n".
					$ex->getMessage()."\n\n";

				if (has("n")) {
					`notify-send -u critical "compile failed" "{$ex->getMessage()}"`;
				}
			}
		}

		sleep(1);
	}
	exit(0);
}

if (!$fname = array_shift($argv)) {
	echo $HELP;
	exit(1);
}

function dumpValue($node, $depth = 0) {
	if (is_object($node)) {
		$indent = str_repeat("  ", $depth);
		$out = array();
		foreach ($node->props as $prop) {
			$out[] = $indent . dumpValue($prop, $depth + 1);
		}
		$out = implode("\n", $out);
		if (!empty($node->tags)) {
			$out = "+ ".implode(", ", $node->tags)."\n".$out;
		}
		return $out;
	} elseif (is_array($node)) {
		if (empty($node)) return "[]";
		$type = $node[0];
		if ($type == "block")
			return dumpValue($node[1], $depth);

		$out = array();
		foreach ($node as $value) {
			$out[] = dumpValue($value, $depth);
		}
		return "{ ".implode(", ", $out)." }";
	} else {
		if (is_string($node) && preg_match("/[\s,]/", $node)) {
			return '"'.$node.'"';
		}
		return $node; // normal value
	}
}


function stripValue($o, $toStrip) {
	if (is_array($o) || is_object($o)) {
		$isObject = is_object($o);
		$o = (array)$o;
		foreach ($toStrip as $removeKey) {
			if (!empty($o[$removeKey])) {
				$o[$removeKey] = "*stripped*";
			}
		}

		foreach ($o as $k => $v) {
			$o[$k] = stripValue($v, $toStrip);
		}

		if ($isObject) {
			$o = (object)$o;
		}
	}

	return $o;
}

function dumpWithoutParent($o, $alsoStrip=array()) {
	$toStrip = array_merge(array("parent"), $alsoStrip);
	print_r(stripValue($o, $toStrip));
}

try {
	$less = make_less($fname);
	if (has("T", "X")) {
		$parser = new lessc_parser($less, $fname);
		$tree = $parser->parse(file_get_contents($fname));
		if (has("X"))
			$out = print_r($tree, 1);
		else
			$out = dumpValue($tree)."\n";
	} else {
		$out = $less->parse();
	}

	if (!$fout = array_shift($argv)) {
		echo $out;
	} else {
		file_put_contents($fout, $out);
	}

} catch (exception $ex) {
	err($fa.$ex->getMessage());
	exit(1);
}

?>
vendor/leafo/lessphp/lessify.inc.php000064400000022726152177723700013570 0ustar00<?php
/**
 * lessify
 * Convert a css file into a less file
 * http://leafo.net/lessphp
 * Copyright 2010, leaf corcoran <leafot@gmail.com>
 *
 * WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR
 * LATEST VERSION OF LESSPHP.
 *
 */

require "lessc.inc.php";

//
// check if the merge during mixin is overwriting values. should or should it not?
//

//
// 1. split apart class tags
//

class easyparse {
	var $buffer;
	var $count;

	function __construct($str) {
		$this->count = 0;
		$this->buffer = trim($str);
	}

	function seek($where = null) {
		if ($where === null) return $this->count;
		else $this->count = $where;
		return true;
	}

	function preg_quote($what) {
		return preg_quote($what, '/');
	}

	function match($regex, &$out, $eatWhitespace = true) {
		$r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
		if (preg_match($r, $this->buffer, $out, null, $this->count)) {
			$this->count += strlen($out[0]);
			return true;
		}
		return false;
	}

	function literal($what, $eatWhitespace = true) {
		// this is here mainly prevent notice from { } string accessor
		if ($this->count >= strlen($this->buffer)) return false;

		// shortcut on single letter
		if (!$eatWhitespace and strlen($what) == 1) {
			if ($this->buffer{$this->count} == $what) {
				$this->count++;
				return true;
			}
			else return false;
		}

		return $this->match($this->preg_quote($what), $m, $eatWhitespace);
	}

}

class tagparse extends easyparse {
	static private $combinators = null;
	static private $match_opts = null;

	function parse() {
		if (empty(self::$combinators)) {
			self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
				array('+', '>', '~'))).')';
			self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
				array('=', '~=', '|=', '$=', '*='))).')';
		}

		// crush whitespace
		$this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';

		$tags = array();
		while ($this->tag($t)) $tags[] = $t;

		return $tags;
	}

	static function compileString($string) {
		list(, $delim, $str) = $string;
		$str = str_replace($delim, "\\".$delim, $str);
		$str = str_replace("\n", "\\\n", $str);
		return $delim.$str.$delim;
	}

	static function compilePaths($paths) {
		return implode(', ', array_map(array('self', 'compilePath'), $paths));
	}

	// array of tags
	static function compilePath($path) {
		return implode(' ', array_map(array('self', 'compileTag'), $path));
	}


	static function compileTag($tag) {
		ob_start();
		if (isset($tag['comb'])) echo $tag['comb']." ";
		if (isset($tag['front'])) echo $tag['front'];
		if (isset($tag['attr'])) {
			echo '['.$tag['attr'];
			if (isset($tag['op'])) {
				echo $tag['op'].$tag['op_value'];
			}
			echo ']';
		}
		return ob_get_clean();
	}

	function string(&$out) {
		$s = $this->seek();

		if ($this->literal('"')) {
			$delim = '"';
		} elseif ($this->literal("'")) {
			$delim = "'";
		} else {
			return false;
		}

		while (true) {
			// step through letters looking for either end or escape
			$buff = "";
			$escapeNext = false;
			$finished = false;
			for ($i = $this->count; $i < strlen($this->buffer); $i++) {
				$char = $this->buffer[$i];
				switch ($char) {
				case $delim:
					if ($escapeNext) {
						$buff .= $char;
						$escapeNext = false;
						break;
					}
					$finished = true;
					break 2;
				case "\\":
					if ($escapeNext) {
						$buff .= $char;
						$escapeNext = false;
					} else {
						$escapeNext = true;
					}
					break;
				case "\n":
					if (!$escapeNext) {
						break 3;
					}

					$buff .= $char;
					$escapeNext = false;
					break;
				default:
					if ($escapeNext) {
						$buff .= "\\";
						$escapeNext = false;
					}
					$buff .= $char;
				}
			}
			if (!$finished) break;
			$out = array('string', $delim, $buff);
			$this->seek($i+1);
			return true;
		}

		$this->seek($s);
		return false;
	}

	function tag(&$out) {
		$s = $this->seek();
		$tag = array();
		if ($this->combinator($op)) $tag['comb'] = $op;

		if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
			$this->seek($s);
			return false;
		}

		if (!empty($match[3])) {
			// give back combinator
			$this->count-=strlen($match[3]);
		}

		if (!empty($match[1])) $tag['front'] = $match[1];

		if ($match[2] == '[') {
			if ($this->ident($i)) {
				$tag['attr'] = $i;

				if ($this->match(self::$match_opts, $m) && $this->value($v)) {
					$tag['op'] = $m[1];
					$tag['op_value'] = $v;
				}

				if ($this->literal(']')) {
					$out = $tag;
					return true;
				}
			}
		} elseif (isset($tag['front'])) {
			$out = $tag;
			return true;
		}

		$this->seek($s);
		return false;
	}

	function ident(&$out) {
		// [-]?{nmstart}{nmchar}*
		// nmstart: [_a-z]|{nonascii}|{escape}
		// nmchar: [_a-z0-9-]|{nonascii}|{escape}
		if ($this->match('(-?[_a-z][_\w]*)', $m)) {
			$out = $m[1];
			return true;
		}
		return false;
	}

	function value(&$out) {
		if ($this->string($str)) {
			$out = $this->compileString($str);
			return true;
		} elseif ($this->ident($id)) {
			$out = $id;
			return true;
		}
		return false;
	}


	function combinator(&$op) {
		if ($this->match(self::$combinators, $m)) {
			$op = $m[1];
			return true;
		}
		return false;
	}
}

class nodecounter {
	var $count = 0;
	var $children = array();

	var $name;
	var $child_blocks;
	var $the_block;

	function __construct($name) {
		$this->name = $name;
	}

	function dump($stack = null) {
		if (is_null($stack)) $stack = array();
		$stack[] = $this->getName();
		echo implode(' -> ', $stack)." ($this->count)\n";
		foreach ($this->children as $child) {
			$child->dump($stack);
		}
	}

	static function compileProperties($c, $block) {
		foreach($block as $name => $value) {
			if ($c->isProperty($name, $value)) {
				echo $c->compileProperty($name, $value)."\n";
			}
		}
	}

	function compile($c, $path = null) {
		if (is_null($path)) $path = array();
		$path[] = $this->name;

		$isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);

		if ($isVisible) {
			echo $c->indent(implode(' ', $path).' {');
			$c->indentLevel++;
			$path = array();

			if ($this->the_block) {
				$this->compileProperties($c, $this->the_block);
			}

			if ($this->child_blocks) {
				foreach ($this->child_blocks as $block) {
					echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
					$c->indentLevel++;
					$this->compileProperties($c, $block);
					$c->indentLevel--;
					echo $c->indent('}');
				}
			}
		}

		// compile child nodes
		foreach($this->children as $node) {
			$node->compile($c, $path);
		}

		if ($isVisible) {
			$c->indentLevel--;
			echo $c->indent('}');
		}

	}

	function getName() {
		if (is_null($this->name)) return "[root]";
		else return $this->name;
	}

	function getNode($name) {
		if (!isset($this->children[$name])) {
			$this->children[$name] = new nodecounter($name);
		}

		return $this->children[$name];
	}

	function findNode($path) {
		$current = $this;
		for ($i = 0; $i < count($path); $i++) {
			$t = tagparse::compileTag($path[$i]);
			$current = $current->getNode($t);
		}

		return $current;
	}

	function addBlock($path, $block) {
		$node = $this->findNode($path);
		if (!is_null($node->the_block)) throw new exception("can this happen?");

		unset($block['__tags']);
		$node->the_block = $block;
	}

	function addToNode($path, $block) {
		$node = $this->findNode($path);
		$node->child_blocks[] = $block;
	}
}

/**
 * create a less file from a css file by combining blocks where appropriate
 */
class lessify extends lessc {
	public function dump() {
		print_r($this->env);
	}

	public function parse($str = null) {
		$this->prepareParser($str ? $str : $this->buffer);
		while (false !== $this->parseChunk());

		$root = new nodecounter(null);

		// attempt to preserve some of the block order
		$order = array();

		$visitedTags = array();
		foreach (end($this->env) as $name => $block) {
			if (!$this->isBlock($name, $block)) continue;
			if (isset($visitedTags[$name])) continue;

			foreach ($block['__tags'] as $t) {
				$visitedTags[$t] = true;
			}

			// skip those with more than 1
			if (count($block['__tags']) == 1) {
				$p = new tagparse(end($block['__tags']));
				$path = $p->parse();
				$root->addBlock($path, $block);
				$order[] = array('compressed', $path, $block);
				continue;
			} else {
				$common = null;
				$paths = array();
				foreach ($block['__tags'] as $rawtag) {
					$p = new tagparse($rawtag);
					$paths[] = $path = $p->parse();
					if (is_null($common)) $common = $path;
					else {
						$new_common = array();
						foreach ($path as $tag) {
							$head = array_shift($common);
							if ($tag == $head) {
								$new_common[] = $head;
							} else break;
						}
						$common = $new_common;
						if (empty($common)) {
							// nothing in common
							break;
						}
					}
				}

				if (!empty($common)) {
					$new_paths = array();
					foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
					$block['__tags'] = $new_paths;
					$root->addToNode($common, $block);
					$order[] = array('compressed', $common, $block);
					continue;
				}

			}

			$order[] = array('none', $block['__tags'], $block);
		}


		$compressed = $root->children;
		foreach ($order as $item) {
			list($type, $tags, $block) = $item;
			if ($type == 'compressed') {
				$top = tagparse::compileTag(reset($tags));
				if (isset($compressed[$top])) {
					$compressed[$top]->compile($this);
					unset($compressed[$top]);
				}
			} else {
				echo $this->indent(implode(', ', $tags).' {');
				$this->indentLevel++;
				nodecounter::compileProperties($this, $block);
				$this->indentLevel--;
				echo $this->indent('}');
			}
		}
	}
}
vendor/.htaccess000064400000000226152177723700007652 0ustar00# Apache 2.4+
<IfModule mod_authz_core.c>
  Require all denied
</IfModule>

# Apache 2.0-2.2
<IfModule !mod_authz_core.c>
  Deny from all
</IfModule>
vendor/phpmailer/phpmailer/class.pop3.php000064400000025376152177723700014531 0ustar00<?php
/**
 * PHPMailer POP-Before-SMTP Authentication Class.
 * PHP Version 5
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailer POP-Before-SMTP Authentication Class.
 * Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
 * Does not support APOP.
 * @package PHPMailer
 * @author Richard Davey (original author) <rich@corephp.co.uk>
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 */
class POP3
{
    /**
     * The POP3 PHPMailer Version number.
     * @var string
     * @access public
     */
    public $Version = '5.2.27';

    /**
     * Default POP3 port number.
     * @var integer
     * @access public
     */
    public $POP3_PORT = 110;

    /**
     * Default timeout in seconds.
     * @var integer
     * @access public
     */
    public $POP3_TIMEOUT = 30;

    /**
     * POP3 Carriage Return + Line Feed.
     * @var string
     * @access public
     * @deprecated Use the constant instead
     */
    public $CRLF = "\r\n";

    /**
     * Debug display level.
     * Options: 0 = no, 1+ = yes
     * @var integer
     * @access public
     */
    public $do_debug = 0;

    /**
     * POP3 mail server hostname.
     * @var string
     * @access public
     */
    public $host;

    /**
     * POP3 port number.
     * @var integer
     * @access public
     */
    public $port;

    /**
     * POP3 Timeout Value in seconds.
     * @var integer
     * @access public
     */
    public $tval;

    /**
     * POP3 username
     * @var string
     * @access public
     */
    public $username;

    /**
     * POP3 password.
     * @var string
     * @access public
     */
    public $password;

    /**
     * Resource handle for the POP3 connection socket.
     * @var resource
     * @access protected
     */
    protected $pop_conn;

    /**
     * Are we connected?
     * @var boolean
     * @access protected
     */
    protected $connected = false;

    /**
     * Error container.
     * @var array
     * @access protected
     */
    protected $errors = array();

    /**
     * Line break constant
     */
    const CRLF = "\r\n";

    /**
     * Simple static wrapper for all-in-one POP before SMTP
     * @param $host
     * @param integer|boolean $port The port number to connect to
     * @param integer|boolean $timeout The timeout value
     * @param string $username
     * @param string $password
     * @param integer $debug_level
     * @return boolean
     */
    public static function popBeforeSmtp(
        $host,
        $port = false,
        $timeout = false,
        $username = '',
        $password = '',
        $debug_level = 0
    ) {
        $pop = new POP3;
        return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
    }

    /**
     * Authenticate with a POP3 server.
     * A connect, login, disconnect sequence
     * appropriate for POP-before SMTP authorisation.
     * @access public
     * @param string $host The hostname to connect to
     * @param integer|boolean $port The port number to connect to
     * @param integer|boolean $timeout The timeout value
     * @param string $username
     * @param string $password
     * @param integer $debug_level
     * @return boolean
     */
    public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
    {
        $this->host = $host;
        // If no port value provided, use default
        if (false === $port) {
            $this->port = $this->POP3_PORT;
        } else {
            $this->port = (integer)$port;
        }
        // If no timeout value provided, use default
        if (false === $timeout) {
            $this->tval = $this->POP3_TIMEOUT;
        } else {
            $this->tval = (integer)$timeout;
        }
        $this->do_debug = $debug_level;
        $this->username = $username;
        $this->password = $password;
        //  Reset the error log
        $this->errors = array();
        //  connect
        $result = $this->connect($this->host, $this->port, $this->tval);
        if ($result) {
            $login_result = $this->login($this->username, $this->password);
            if ($login_result) {
                $this->disconnect();
                return true;
            }
        }
        // We need to disconnect regardless of whether the login succeeded
        $this->disconnect();
        return false;
    }

    /**
     * Connect to a POP3 server.
     * @access public
     * @param string $host
     * @param integer|boolean $port
     * @param integer $tval
     * @return boolean
     */
    public function connect($host, $port = false, $tval = 30)
    {
        //  Are we already connected?
        if ($this->connected) {
            return true;
        }

        //On Windows this will raise a PHP Warning error if the hostname doesn't exist.
        //Rather than suppress it with @fsockopen, capture it cleanly instead
        set_error_handler(array($this, 'catchWarning'));

        if (false === $port) {
            $port = $this->POP3_PORT;
        }

        //  connect to the POP3 server
        $this->pop_conn = fsockopen(
            $host, //  POP3 Host
            $port, //  Port #
            $errno, //  Error Number
            $errstr, //  Error Message
            $tval
        ); //  Timeout (seconds)
        //  Restore the error handler
        restore_error_handler();

        //  Did we connect?
        if (false === $this->pop_conn) {
            //  It would appear not...
            $this->setError(array(
                'error' => "Failed to connect to server $host on port $port",
                'errno' => $errno,
                'errstr' => $errstr
            ));
            return false;
        }

        //  Increase the stream time-out
        stream_set_timeout($this->pop_conn, $tval, 0);

        //  Get the POP3 server response
        $pop3_response = $this->getResponse();
        //  Check for the +OK
        if ($this->checkResponse($pop3_response)) {
            //  The connection is established and the POP3 server is talking
            $this->connected = true;
            return true;
        }
        return false;
    }

    /**
     * Log in to the POP3 server.
     * Does not support APOP (RFC 2828, 4949).
     * @access public
     * @param string $username
     * @param string $password
     * @return boolean
     */
    public function login($username = '', $password = '')
    {
        if (!$this->connected) {
            $this->setError('Not connected to POP3 server');
        }
        if (empty($username)) {
            $username = $this->username;
        }
        if (empty($password)) {
            $password = $this->password;
        }

        // Send the Username
        $this->sendString("USER $username" . self::CRLF);
        $pop3_response = $this->getResponse();
        if ($this->checkResponse($pop3_response)) {
            // Send the Password
            $this->sendString("PASS $password" . self::CRLF);
            $pop3_response = $this->getResponse();
            if ($this->checkResponse($pop3_response)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Disconnect from the POP3 server.
     * @access public
     */
    public function disconnect()
    {
        $this->sendString('QUIT');
        //The QUIT command may cause the daemon to exit, which will kill our connection
        //So ignore errors here
        try {
            @fclose($this->pop_conn);
        } catch (Exception $e) {
            //Do nothing
        };
    }

    /**
     * Get a response from the POP3 server.
     * $size is the maximum number of bytes to retrieve
     * @param integer $size
     * @return string
     * @access protected
     */
    protected function getResponse($size = 128)
    {
        $response = fgets($this->pop_conn, $size);
        if ($this->do_debug >= 1) {
            echo "Server -> Client: $response";
        }
        return $response;
    }

    /**
     * Send raw data to the POP3 server.
     * @param string $string
     * @return integer
     * @access protected
     */
    protected function sendString($string)
    {
        if ($this->pop_conn) {
            if ($this->do_debug >= 2) { //Show client messages when debug >= 2
                echo "Client -> Server: $string";
            }
            return fwrite($this->pop_conn, $string, strlen($string));
        }
        return 0;
    }

    /**
     * Checks the POP3 server response.
     * Looks for for +OK or -ERR.
     * @param string $string
     * @return boolean
     * @access protected
     */
    protected function checkResponse($string)
    {
        if (substr($string, 0, 3) !== '+OK') {
            $this->setError(array(
                'error' => "Server reported an error: $string",
                'errno' => 0,
                'errstr' => ''
            ));
            return false;
        } else {
            return true;
        }
    }

    /**
     * Add an error to the internal error store.
     * Also display debug output if it's enabled.
     * @param $error
     * @access protected
     */
    protected function setError($error)
    {
        $this->errors[] = $error;
        if ($this->do_debug >= 1) {
            echo '<pre>';
            foreach ($this->errors as $error) {
                print_r($error);
            }
            echo '</pre>';
        }
    }

    /**
     * Get an array of error messages, if any.
     * @return array
     */
    public function getErrors()
    {
        return $this->errors;
    }

    /**
     * POP3 connection error handler.
     * @param integer $errno
     * @param string $errstr
     * @param string $errfile
     * @param integer $errline
     * @access protected
     */
    protected function catchWarning($errno, $errstr, $errfile, $errline)
    {
        $this->setError(array(
            'error' => "Connecting to the POP3 server raised a PHP warning: ",
            'errno' => $errno,
            'errstr' => $errstr,
            'errfile' => $errfile,
            'errline' => $errline
        ));
    }
}
vendor/phpmailer/phpmailer/composer.lock000064400000373711152177723700014533 0ustar00{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
        "This file is @generated automatically"
    ],
    "content-hash": "7e4b1bef833056eed0df39fad5399d7a",
    "packages": [],
    "packages-dev": [
        {
            "name": "cilex/cilex",
            "version": "1.1.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Cilex/Cilex.git",
                "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Cilex/Cilex/zipball/7acd965a609a56d0345e8b6071c261fbdb926cb5",
                "reference": "7acd965a609a56d0345e8b6071c261fbdb926cb5",
                "shasum": ""
            },
            "require": {
                "cilex/console-service-provider": "1.*",
                "php": ">=5.3.3",
                "pimple/pimple": "~1.0",
                "symfony/finder": "~2.1",
                "symfony/process": "~2.1"
            },
            "require-dev": {
                "phpunit/phpunit": "3.7.*",
                "symfony/validator": "~2.1"
            },
            "suggest": {
                "monolog/monolog": ">=1.0.0",
                "symfony/validator": ">=1.0.0",
                "symfony/yaml": ">=1.0.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Cilex": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "mike.vanriel@naenius.com"
                }
            ],
            "description": "The PHP micro-framework for Command line tools based on the Symfony2 Components",
            "homepage": "http://cilex.github.com",
            "keywords": [
                "cli",
                "microframework"
            ],
            "time": "2014-03-29T14:03:13+00:00"
        },
        {
            "name": "cilex/console-service-provider",
            "version": "1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Cilex/console-service-provider.git",
                "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Cilex/console-service-provider/zipball/25ee3d1875243d38e1a3448ff94bdf944f70d24e",
                "reference": "25ee3d1875243d38e1a3448ff94bdf944f70d24e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "pimple/pimple": "1.*@dev",
                "symfony/console": "~2.1"
            },
            "require-dev": {
                "cilex/cilex": "1.*@dev",
                "silex/silex": "1.*@dev"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Cilex\\Provider\\Console": "src"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Beau Simensen",
                    "email": "beau@dflydev.com",
                    "homepage": "http://beausimensen.com"
                },
                {
                    "name": "Mike van Riel",
                    "email": "mike.vanriel@naenius.com"
                }
            ],
            "description": "Console Service Provider",
            "keywords": [
                "cilex",
                "console",
                "pimple",
                "service-provider",
                "silex"
            ],
            "time": "2012-12-19T10:50:58+00:00"
        },
        {
            "name": "doctrine/annotations",
            "version": "v1.2.7",
            "source": {
                "type": "git",
                "url": "https://github.com/doctrine/annotations.git",
                "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/doctrine/annotations/zipball/f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
                "reference": "f25c8aab83e0c3e976fd7d19875f198ccf2f7535",
                "shasum": ""
            },
            "require": {
                "doctrine/lexer": "1.*",
                "php": ">=5.3.2"
            },
            "require-dev": {
                "doctrine/cache": "1.*",
                "phpunit/phpunit": "4.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Doctrine\\Common\\Annotations\\": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Roman Borschel",
                    "email": "roman@code-factory.org"
                },
                {
                    "name": "Benjamin Eberlei",
                    "email": "kontakt@beberlei.de"
                },
                {
                    "name": "Guilherme Blanco",
                    "email": "guilhermeblanco@gmail.com"
                },
                {
                    "name": "Jonathan Wage",
                    "email": "jonwage@gmail.com"
                },
                {
                    "name": "Johannes Schmitt",
                    "email": "schmittjoh@gmail.com"
                }
            ],
            "description": "Docblock Annotations Parser",
            "homepage": "http://www.doctrine-project.org",
            "keywords": [
                "annotations",
                "docblock",
                "parser"
            ],
            "time": "2015-08-31T12:32:49+00:00"
        },
        {
            "name": "doctrine/instantiator",
            "version": "1.0.5",
            "source": {
                "type": "git",
                "url": "https://github.com/doctrine/instantiator.git",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3,<8.0-DEV"
            },
            "require-dev": {
                "athletic/athletic": "~0.1.8",
                "ext-pdo": "*",
                "ext-phar": "*",
                "phpunit/phpunit": "~4.0",
                "squizlabs/php_codesniffer": "~2.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Marco Pivetta",
                    "email": "ocramius@gmail.com",
                    "homepage": "http://ocramius.github.com/"
                }
            ],
            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
            "homepage": "https://github.com/doctrine/instantiator",
            "keywords": [
                "constructor",
                "instantiate"
            ],
            "time": "2015-06-14T21:17:01+00:00"
        },
        {
            "name": "doctrine/lexer",
            "version": "v1.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/doctrine/lexer.git",
                "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c",
                "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Doctrine\\Common\\Lexer\\": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Roman Borschel",
                    "email": "roman@code-factory.org"
                },
                {
                    "name": "Guilherme Blanco",
                    "email": "guilhermeblanco@gmail.com"
                },
                {
                    "name": "Johannes Schmitt",
                    "email": "schmittjoh@gmail.com"
                }
            ],
            "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.",
            "homepage": "http://www.doctrine-project.org",
            "keywords": [
                "lexer",
                "parser"
            ],
            "time": "2014-09-09T13:34:57+00:00"
        },
        {
            "name": "erusev/parsedown",
            "version": "1.6.1",
            "source": {
                "type": "git",
                "url": "https://github.com/erusev/parsedown.git",
                "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/erusev/parsedown/zipball/20ff8bbb57205368b4b42d094642a3e52dac85fb",
                "reference": "20ff8bbb57205368b4b42d094642a3e52dac85fb",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "Parsedown": ""
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Emanuil Rusev",
                    "email": "hello@erusev.com",
                    "homepage": "http://erusev.com"
                }
            ],
            "description": "Parser for Markdown.",
            "homepage": "http://parsedown.org",
            "keywords": [
                "markdown",
                "parser"
            ],
            "time": "2016-11-02T15:56:58+00:00"
        },
        {
            "name": "herrera-io/json",
            "version": "1.0.3",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-php/json.git",
                "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-php/json/zipball/60c696c9370a1e5136816ca557c17f82a6fa83f1",
                "reference": "60c696c9370a1e5136816ca557c17f82a6fa83f1",
                "shasum": ""
            },
            "require": {
                "ext-json": "*",
                "justinrainbow/json-schema": ">=1.0,<2.0-dev",
                "php": ">=5.3.3",
                "seld/jsonlint": ">=1.0,<2.0-dev"
            },
            "require-dev": {
                "herrera-io/phpunit-test-case": "1.*",
                "mikey179/vfsstream": "1.1.0",
                "phpunit/phpunit": "3.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/lib/json_version.php"
                ],
                "psr-0": {
                    "Herrera\\Json": "src/lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "kevin@herrera.io",
                    "homepage": "http://kevin.herrera.io/",
                    "role": "Developer"
                }
            ],
            "description": "A library for simplifying JSON linting and validation.",
            "homepage": "http://herrera-io.github.com/php-json",
            "keywords": [
                "json",
                "lint",
                "schema",
                "validate"
            ],
            "abandoned": "kherge/json",
            "time": "2013-10-30T16:51:34+00:00"
        },
        {
            "name": "herrera-io/phar-update",
            "version": "1.0.3",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-abandoned/php-phar-update.git",
                "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-abandoned/php-phar-update/zipball/00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b",
                "reference": "00a79e1d5b8cf3c080a2e3becf1ddf7a7fea025b",
                "shasum": ""
            },
            "require": {
                "herrera-io/json": "1.*",
                "kherge/version": "1.*",
                "php": ">=5.3.3"
            },
            "require-dev": {
                "herrera-io/phpunit-test-case": "1.*",
                "mikey179/vfsstream": "1.1.0",
                "phpunit/phpunit": "3.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "files": [
                    "src/lib/constants.php"
                ],
                "psr-0": {
                    "Herrera\\Phar\\Update": "src/lib"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "kevin@herrera.io",
                    "homepage": "http://kevin.herrera.io/",
                    "role": "Developer"
                }
            ],
            "description": "A library for self-updating Phars.",
            "homepage": "http://herrera-io.github.com/php-phar-update",
            "keywords": [
                "phar",
                "update"
            ],
            "abandoned": true,
            "time": "2013-10-30T17:23:01+00:00"
        },
        {
            "name": "jms/metadata",
            "version": "1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/schmittjoh/metadata.git",
                "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab",
                "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "require-dev": {
                "doctrine/cache": "~1.0",
                "symfony/cache": "~3.1"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.5.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Metadata\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "Johannes M. Schmitt",
                    "email": "schmittjoh@gmail.com"
                }
            ],
            "description": "Class/method/property metadata management in PHP",
            "keywords": [
                "annotations",
                "metadata",
                "xml",
                "yaml"
            ],
            "time": "2016-12-05T10:18:33+00:00"
        },
        {
            "name": "jms/parser-lib",
            "version": "1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/schmittjoh/parser-lib.git",
                "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d",
                "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d",
                "shasum": ""
            },
            "require": {
                "phpoption/phpoption": ">=0.9,<2.0-dev"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "JMS\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache2"
            ],
            "description": "A library for easily creating recursive-descent parsers.",
            "time": "2012-11-18T18:08:43+00:00"
        },
        {
            "name": "jms/serializer",
            "version": "0.16.0",
            "source": {
                "type": "git",
                "url": "https://github.com/schmittjoh/serializer.git",
                "reference": "c8a171357ca92b6706e395c757f334902d430ea9"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9",
                "reference": "c8a171357ca92b6706e395c757f334902d430ea9",
                "shasum": ""
            },
            "require": {
                "doctrine/annotations": "1.*",
                "jms/metadata": "~1.1",
                "jms/parser-lib": "1.*",
                "php": ">=5.3.2",
                "phpcollection/phpcollection": "~0.1"
            },
            "require-dev": {
                "doctrine/orm": "~2.1",
                "doctrine/phpcr-odm": "~1.0.1",
                "jackalope/jackalope-doctrine-dbal": "1.0.*",
                "propel/propel1": "~1.7",
                "symfony/filesystem": "2.*",
                "symfony/form": "~2.1",
                "symfony/translation": "~2.0",
                "symfony/validator": "~2.0",
                "symfony/yaml": "2.*",
                "twig/twig": ">=1.8,<2.0-dev"
            },
            "suggest": {
                "symfony/yaml": "Required if you'd like to serialize data to YAML format."
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "0.15-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "JMS\\Serializer": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache2"
            ],
            "authors": [
                {
                    "name": "Johannes Schmitt",
                    "email": "schmittjoh@gmail.com",
                    "homepage": "https://github.com/schmittjoh",
                    "role": "Developer of wrapped JMSSerializerBundle"
                }
            ],
            "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.",
            "homepage": "http://jmsyst.com/libs/serializer",
            "keywords": [
                "deserialization",
                "jaxb",
                "json",
                "serialization",
                "xml"
            ],
            "time": "2014-03-18T08:39:00+00:00"
        },
        {
            "name": "justinrainbow/json-schema",
            "version": "1.6.1",
            "source": {
                "type": "git",
                "url": "https://github.com/justinrainbow/json-schema.git",
                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341",
                "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.29"
            },
            "require-dev": {
                "json-schema/json-schema-test-suite": "1.1.0",
                "phpdocumentor/phpdocumentor": "~2",
                "phpunit/phpunit": "~3.7"
            },
            "bin": [
                "bin/validate-json"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.6.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "JsonSchema\\": "src/JsonSchema/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Bruno Prieto Reis",
                    "email": "bruno.p.reis@gmail.com"
                },
                {
                    "name": "Justin Rainbow",
                    "email": "justin.rainbow@gmail.com"
                },
                {
                    "name": "Igor Wiedler",
                    "email": "igor@wiedler.ch"
                },
                {
                    "name": "Robert Schönthal",
                    "email": "seroscho@googlemail.com"
                }
            ],
            "description": "A library to validate a json schema.",
            "homepage": "https://github.com/justinrainbow/json-schema",
            "keywords": [
                "json",
                "schema"
            ],
            "time": "2016-01-25T15:43:01+00:00"
        },
        {
            "name": "kherge/version",
            "version": "1.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/kherge-abandoned/Version.git",
                "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/kherge-abandoned/Version/zipball/f07cf83f8ce533be8f93d2893d96d674bbeb7e30",
                "reference": "f07cf83f8ce533be8f93d2893d96d674bbeb7e30",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "KevinGH\\Version": "src/lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Kevin Herrera",
                    "email": "me@kevingh.com",
                    "homepage": "http://www.kevingh.com/"
                }
            ],
            "description": "A parsing and comparison library for semantic versioning.",
            "homepage": "http://github.com/kherge/Version",
            "abandoned": true,
            "time": "2012-08-16T17:13:03+00:00"
        },
        {
            "name": "monolog/monolog",
            "version": "1.22.1",
            "source": {
                "type": "git",
                "url": "https://github.com/Seldaek/monolog.git",
                "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0",
                "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0",
                "psr/log": "~1.0"
            },
            "provide": {
                "psr/log-implementation": "1.0.0"
            },
            "require-dev": {
                "aws/aws-sdk-php": "^2.4.9 || ^3.0",
                "doctrine/couchdb": "~1.0@dev",
                "graylog2/gelf-php": "~1.0",
                "jakub-onderka/php-parallel-lint": "0.9",
                "php-amqplib/php-amqplib": "~2.4",
                "php-console/php-console": "^3.1.3",
                "phpunit/phpunit": "~4.5",
                "phpunit/phpunit-mock-objects": "2.3.0",
                "ruflin/elastica": ">=0.90 <3.0",
                "sentry/sentry": "^0.13",
                "swiftmailer/swiftmailer": "~5.3"
            },
            "suggest": {
                "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
                "doctrine/couchdb": "Allow sending log messages to a CouchDB server",
                "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
                "ext-mongo": "Allow sending log messages to a MongoDB server",
                "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
                "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
                "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
                "php-console/php-console": "Allow sending log messages to Google Chrome",
                "rollbar/rollbar": "Allow sending log messages to Rollbar",
                "ruflin/elastica": "Allow sending log messages to an Elastic Search server",
                "sentry/sentry": "Allow sending log messages to a Sentry server"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Monolog\\": "src/Monolog"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jordi Boggiano",
                    "email": "j.boggiano@seld.be",
                    "homepage": "http://seld.be"
                }
            ],
            "description": "Sends your logs to files, sockets, inboxes, databases and various web services",
            "homepage": "http://github.com/Seldaek/monolog",
            "keywords": [
                "log",
                "logging",
                "psr-3"
            ],
            "time": "2017-03-13T07:08:03+00:00"
        },
        {
            "name": "nikic/php-parser",
            "version": "v1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/nikic/PHP-Parser.git",
                "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51",
                "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "files": [
                    "lib/bootstrap.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Nikita Popov"
                }
            ],
            "description": "A PHP parser written in PHP",
            "keywords": [
                "parser",
                "php"
            ],
            "time": "2015-09-19T14:15:08+00:00"
        },
        {
            "name": "phpcollection/phpcollection",
            "version": "0.5.0",
            "source": {
                "type": "git",
                "url": "https://github.com/schmittjoh/php-collection.git",
                "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6",
                "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6",
                "shasum": ""
            },
            "require": {
                "phpoption/phpoption": "1.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "0.4-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "PhpCollection": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache2"
            ],
            "authors": [
                {
                    "name": "Johannes M. Schmitt",
                    "email": "schmittjoh@gmail.com"
                }
            ],
            "description": "General-Purpose Collection Library for PHP",
            "keywords": [
                "collection",
                "list",
                "map",
                "sequence",
                "set"
            ],
            "time": "2015-05-17T12:39:23+00:00"
        },
        {
            "name": "phpdocumentor/fileset",
            "version": "1.0.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/Fileset.git",
                "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/Fileset/zipball/bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0",
                "reference": "bfa78d8fa9763dfce6d0e5d3730c1d8ab25d34b0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "symfony/finder": "~2.1"
            },
            "require-dev": {
                "phpunit/phpunit": "~3.7"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/",
                        "tests/unit/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "description": "Fileset component for collecting a set of files given directories and file paths",
            "homepage": "http://www.phpdoc.org",
            "keywords": [
                "files",
                "fileset",
                "phpdoc"
            ],
            "time": "2013-08-06T21:07:42+00:00"
        },
        {
            "name": "phpdocumentor/graphviz",
            "version": "1.0.4",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/GraphViz.git",
                "reference": "a906a90a9f230535f25ea31caf81b2323956283f"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/GraphViz/zipball/a906a90a9f230535f25ea31caf81b2323956283f",
                "reference": "a906a90a9f230535f25ea31caf81b2323956283f",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.0"
            },
            "type": "library",
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/",
                        "tests/unit"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "mike.vanriel@naenius.com"
                }
            ],
            "time": "2016-02-02T13:00:08+00:00"
        },
        {
            "name": "phpdocumentor/phpdocumentor",
            "version": "v2.9.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/phpDocumentor2.git",
                "reference": "be607da0eef9b9249c43c5b4820d25d631c73667"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/be607da0eef9b9249c43c5b4820d25d631c73667",
                "reference": "be607da0eef9b9249c43c5b4820d25d631c73667",
                "shasum": ""
            },
            "require": {
                "cilex/cilex": "~1.0",
                "erusev/parsedown": "~1.0",
                "herrera-io/phar-update": "1.0.3",
                "jms/serializer": ">=0.12",
                "monolog/monolog": "~1.6",
                "php": ">=5.3.3",
                "phpdocumentor/fileset": "~1.0",
                "phpdocumentor/graphviz": "~1.0",
                "phpdocumentor/reflection": "^3.0",
                "phpdocumentor/reflection-docblock": "~2.0",
                "symfony/config": "~2.3",
                "symfony/console": "~2.3",
                "symfony/event-dispatcher": "~2.1",
                "symfony/process": "~2.0",
                "symfony/stopwatch": "~2.3",
                "symfony/validator": "~2.2",
                "twig/twig": "~1.3",
                "zendframework/zend-cache": "~2.1",
                "zendframework/zend-config": "~2.1",
                "zendframework/zend-filter": "~2.1",
                "zendframework/zend-i18n": "~2.1",
                "zendframework/zend-serializer": "~2.1",
                "zendframework/zend-servicemanager": "~2.1",
                "zendframework/zend-stdlib": "~2.1",
                "zetacomponents/document": ">=1.3.1"
            },
            "require-dev": {
                "behat/behat": "~3.0",
                "mikey179/vfsstream": "~1.2",
                "mockery/mockery": "~0.9@dev",
                "phpunit/phpunit": "~4.0",
                "squizlabs/php_codesniffer": "~1.4",
                "symfony/expression-language": "~2.4"
            },
            "suggest": {
                "ext-twig": "Enabling the twig extension improves the generation of twig based templates.",
                "ext-xslcache": "Enabling the XSLCache extension improves the generation of xml based templates."
            },
            "bin": [
                "bin/phpdoc.php",
                "bin/phpdoc"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-develop": "2.9-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/",
                        "tests/unit/"
                    ],
                    "Cilex\\Provider": [
                        "src/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "description": "Documentation Generator for PHP",
            "homepage": "http://www.phpdoc.org",
            "keywords": [
                "api",
                "application",
                "dga",
                "documentation",
                "phpdoc"
            ],
            "time": "2016-05-22T09:50:56+00:00"
        },
        {
            "name": "phpdocumentor/reflection",
            "version": "3.0.1",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/Reflection.git",
                "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d",
                "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d",
                "shasum": ""
            },
            "require": {
                "nikic/php-parser": "^1.0",
                "php": ">=5.3.3",
                "phpdocumentor/reflection-docblock": "~2.0",
                "psr/log": "~1.0"
            },
            "require-dev": {
                "behat/behat": "~2.4",
                "mockery/mockery": "~0.8",
                "phpunit/phpunit": "~4.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/",
                        "tests/unit/",
                        "tests/mocks/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "description": "Reflection library to do Static Analysis for PHP Projects",
            "homepage": "http://www.phpdoc.org",
            "keywords": [
                "phpDocumentor",
                "phpdoc",
                "reflection",
                "static analysis"
            ],
            "time": "2016-05-21T08:42:32+00:00"
        },
        {
            "name": "phpdocumentor/reflection-docblock",
            "version": "2.0.4",
            "source": {
                "type": "git",
                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.0"
            },
            "suggest": {
                "dflydev/markdown": "~1.0",
                "erusev/parsedown": "~1.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.0.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "phpDocumentor": [
                        "src/"
                    ]
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Mike van Riel",
                    "email": "mike.vanriel@naenius.com"
                }
            ],
            "time": "2015-02-03T12:10:50+00:00"
        },
        {
            "name": "phpoption/phpoption",
            "version": "1.5.0",
            "source": {
                "type": "git",
                "url": "https://github.com/schmittjoh/php-option.git",
                "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed",
                "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "require-dev": {
                "phpunit/phpunit": "4.7.*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "PhpOption\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache2"
            ],
            "authors": [
                {
                    "name": "Johannes M. Schmitt",
                    "email": "schmittjoh@gmail.com"
                }
            ],
            "description": "Option Type for PHP",
            "keywords": [
                "language",
                "option",
                "php",
                "type"
            ],
            "time": "2015-07-25T16:39:46+00:00"
        },
        {
            "name": "phpspec/prophecy",
            "version": "v1.7.0",
            "source": {
                "type": "git",
                "url": "https://github.com/phpspec/prophecy.git",
                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/93d39f1f7f9326d746203c7c056f300f7f126073",
                "reference": "93d39f1f7f9326d746203c7c056f300f7f126073",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "php": "^5.3|^7.0",
                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2",
                "sebastian/comparator": "^1.1|^2.0",
                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
            },
            "require-dev": {
                "phpspec/phpspec": "^2.5|^3.2",
                "phpunit/phpunit": "^4.8 || ^5.6.5"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.6.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Prophecy\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Konstantin Kudryashov",
                    "email": "ever.zet@gmail.com",
                    "homepage": "http://everzet.com"
                },
                {
                    "name": "Marcello Duarte",
                    "email": "marcello.duarte@gmail.com"
                }
            ],
            "description": "Highly opinionated mocking framework for PHP 5.3+",
            "homepage": "https://github.com/phpspec/prophecy",
            "keywords": [
                "Double",
                "Dummy",
                "fake",
                "mock",
                "spy",
                "stub"
            ],
            "time": "2017-03-02T20:05:34+00:00"
        },
        {
            "name": "phpunit/php-code-coverage",
            "version": "2.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "phpunit/php-file-iterator": "~1.3",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-token-stream": "~1.3",
                "sebastian/environment": "^1.3.2",
                "sebastian/version": "~1.0"
            },
            "require-dev": {
                "ext-xdebug": ">=2.1.4",
                "phpunit/phpunit": "~4"
            },
            "suggest": {
                "ext-dom": "*",
                "ext-xdebug": ">=2.2.1",
                "ext-xmlwriter": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
            "keywords": [
                "coverage",
                "testing",
                "xunit"
            ],
            "time": "2015-10-06T15:47:00+00:00"
        },
        {
            "name": "phpunit/php-file-iterator",
            "version": "1.4.2",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
                "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
            "keywords": [
                "filesystem",
                "iterator"
            ],
            "time": "2016-10-03T07:40:28+00:00"
        },
        {
            "name": "phpunit/php-text-template",
            "version": "1.2.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-text-template.git",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Simple template engine.",
            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
            "keywords": [
                "template"
            ],
            "time": "2015-06-21T13:50:34+00:00"
        },
        {
            "name": "phpunit/php-timer",
            "version": "1.0.9",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-timer.git",
                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
                "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f",
                "shasum": ""
            },
            "require": {
                "php": "^5.3.3 || ^7.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Utility class for timing",
            "homepage": "https://github.com/sebastianbergmann/php-timer/",
            "keywords": [
                "timer"
            ],
            "time": "2017-02-26T11:10:40+00:00"
        },
        {
            "name": "phpunit/php-token-stream",
            "version": "1.4.11",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e03f8f67534427a787e21a385a67ec3ca6978ea7",
                "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7",
                "shasum": ""
            },
            "require": {
                "ext-tokenizer": "*",
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Wrapper around PHP's tokenizer extension.",
            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
            "keywords": [
                "tokenizer"
            ],
            "time": "2017-02-27T10:12:30+00:00"
        },
        {
            "name": "phpunit/phpunit",
            "version": "4.8.35",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit.git",
                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/791b1a67c25af50e230f841ee7a9c6eba507dc87",
                "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87",
                "shasum": ""
            },
            "require": {
                "ext-dom": "*",
                "ext-json": "*",
                "ext-pcre": "*",
                "ext-reflection": "*",
                "ext-spl": "*",
                "php": ">=5.3.3",
                "phpspec/prophecy": "^1.3.1",
                "phpunit/php-code-coverage": "~2.1",
                "phpunit/php-file-iterator": "~1.4",
                "phpunit/php-text-template": "~1.2",
                "phpunit/php-timer": "^1.0.6",
                "phpunit/phpunit-mock-objects": "~2.3",
                "sebastian/comparator": "~1.2.2",
                "sebastian/diff": "~1.2",
                "sebastian/environment": "~1.3",
                "sebastian/exporter": "~1.2",
                "sebastian/global-state": "~1.0",
                "sebastian/version": "~1.0",
                "symfony/yaml": "~2.1|~3.0"
            },
            "suggest": {
                "phpunit/php-invoker": "~1.1"
            },
            "bin": [
                "phpunit"
            ],
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "4.8.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "The PHP Unit Testing framework.",
            "homepage": "https://phpunit.de/",
            "keywords": [
                "phpunit",
                "testing",
                "xunit"
            ],
            "time": "2017-02-06T05:18:07+00:00"
        },
        {
            "name": "phpunit/phpunit-mock-objects",
            "version": "2.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
                "shasum": ""
            },
            "require": {
                "doctrine/instantiator": "^1.0.2",
                "php": ">=5.3.3",
                "phpunit/php-text-template": "~1.2",
                "sebastian/exporter": "~1.2"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "suggest": {
                "ext-soap": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sb@sebastian-bergmann.de",
                    "role": "lead"
                }
            ],
            "description": "Mock Object library for PHPUnit",
            "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
            "keywords": [
                "mock",
                "xunit"
            ],
            "time": "2015-10-02T06:51:40+00:00"
        },
        {
            "name": "pimple/pimple",
            "version": "v1.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/silexphp/Pimple.git",
                "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/silexphp/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d",
                "reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.1.x-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Pimple": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com",
                    "homepage": "http://fabien.potencier.org",
                    "role": "Lead Developer"
                }
            ],
            "description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
            "homepage": "http://pimple.sensiolabs.org",
            "keywords": [
                "container",
                "dependency injection"
            ],
            "time": "2013-11-22T08:30:29+00:00"
        },
        {
            "name": "psr/log",
            "version": "1.0.2",
            "source": {
                "type": "git",
                "url": "https://github.com/php-fig/log.git",
                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Psr\\Log\\": "Psr/Log/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "PHP-FIG",
                    "homepage": "http://www.php-fig.org/"
                }
            ],
            "description": "Common interface for logging libraries",
            "homepage": "https://github.com/php-fig/log",
            "keywords": [
                "log",
                "psr",
                "psr-3"
            ],
            "time": "2016-10-10T12:19:37+00:00"
        },
        {
            "name": "sebastian/comparator",
            "version": "1.2.4",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/comparator.git",
                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
                "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/diff": "~1.2",
                "sebastian/exporter": "~1.2 || ~2.0"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.2.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides the functionality to compare PHP values for equality",
            "homepage": "http://www.github.com/sebastianbergmann/comparator",
            "keywords": [
                "comparator",
                "compare",
                "equality"
            ],
            "time": "2017-01-29T09:50:25+00:00"
        },
        {
            "name": "sebastian/diff",
            "version": "1.4.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/diff.git",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
                "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.8"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.4-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Kore Nordmann",
                    "email": "mail@kore-nordmann.de"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Diff implementation",
            "homepage": "https://github.com/sebastianbergmann/diff",
            "keywords": [
                "diff"
            ],
            "time": "2015-12-08T07:14:41+00:00"
        },
        {
            "name": "sebastian/environment",
            "version": "1.3.8",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/environment.git",
                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea",
                "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea",
                "shasum": ""
            },
            "require": {
                "php": "^5.3.3 || ^7.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.8 || ^5.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Provides functionality to handle HHVM/PHP environments",
            "homepage": "http://www.github.com/sebastianbergmann/environment",
            "keywords": [
                "Xdebug",
                "environment",
                "hhvm"
            ],
            "time": "2016-08-18T05:49:44+00:00"
        },
        {
            "name": "sebastian/exporter",
            "version": "1.2.2",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/exporter.git",
                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4",
                "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3",
                "sebastian/recursion-context": "~1.0"
            },
            "require-dev": {
                "ext-mbstring": "*",
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Volker Dusch",
                    "email": "github@wallbash.com"
                },
                {
                    "name": "Bernhard Schussek",
                    "email": "bschussek@2bepublished.at"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides the functionality to export PHP variables for visualization",
            "homepage": "http://www.github.com/sebastianbergmann/exporter",
            "keywords": [
                "export",
                "exporter"
            ],
            "time": "2016-06-17T09:04:28+00:00"
        },
        {
            "name": "sebastian/global-state",
            "version": "1.1.1",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/global-state.git",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.2"
            },
            "suggest": {
                "ext-uopz": "*"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                }
            ],
            "description": "Snapshotting of global state",
            "homepage": "http://www.github.com/sebastianbergmann/global-state",
            "keywords": [
                "global state"
            ],
            "time": "2015-10-12T03:26:01+00:00"
        },
        {
            "name": "sebastian/recursion-context",
            "version": "1.0.5",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/recursion-context.git",
                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
                "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "require-dev": {
                "phpunit/phpunit": "~4.4"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.0.x-dev"
                }
            },
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Jeff Welch",
                    "email": "whatthejeff@gmail.com"
                },
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de"
                },
                {
                    "name": "Adam Harvey",
                    "email": "aharvey@php.net"
                }
            ],
            "description": "Provides functionality to recursively process PHP variables",
            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
            "time": "2016-10-03T07:41:43+00:00"
        },
        {
            "name": "sebastian/version",
            "version": "1.0.6",
            "source": {
                "type": "git",
                "url": "https://github.com/sebastianbergmann/version.git",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
                "shasum": ""
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann",
                    "email": "sebastian@phpunit.de",
                    "role": "lead"
                }
            ],
            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
            "homepage": "https://github.com/sebastianbergmann/version",
            "time": "2015-06-21T13:59:46+00:00"
        },
        {
            "name": "seld/jsonlint",
            "version": "1.6.0",
            "source": {
                "type": "git",
                "url": "https://github.com/Seldaek/jsonlint.git",
                "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/791f8c594f300d246cdf01c6b3e1e19611e301d8",
                "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8",
                "shasum": ""
            },
            "require": {
                "php": "^5.3 || ^7.0"
            },
            "require-dev": {
                "phpunit/phpunit": "^4.5"
            },
            "bin": [
                "bin/jsonlint"
            ],
            "type": "library",
            "autoload": {
                "psr-4": {
                    "Seld\\JsonLint\\": "src/Seld/JsonLint/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Jordi Boggiano",
                    "email": "j.boggiano@seld.be",
                    "homepage": "http://seld.be"
                }
            ],
            "description": "JSON Linter",
            "keywords": [
                "json",
                "linter",
                "parser",
                "validator"
            ],
            "time": "2017-03-06T16:42:24+00:00"
        },
        {
            "name": "symfony/config",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/config.git",
                "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/config/zipball/06ce6bb46c24963ec09323da45d0f4f85d3cecd2",
                "reference": "06ce6bb46c24963ec09323da45d0f4f85d3cecd2",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/filesystem": "~2.3|~3.0.0"
            },
            "require-dev": {
                "symfony/yaml": "~2.7|~3.0.0"
            },
            "suggest": {
                "symfony/yaml": "To use the yaml reference dumper"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Config\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Config Component",
            "homepage": "https://symfony.com",
            "time": "2017-03-01T18:13:50+00:00"
        },
        {
            "name": "symfony/console",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/console.git",
                "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/console/zipball/81508e6fac4476771275a3f4f53c3fee9b956bfa",
                "reference": "81508e6fac4476771275a3f4f53c3fee9b956bfa",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/debug": "^2.7.2|~3.0.0",
                "symfony/polyfill-mbstring": "~1.0"
            },
            "require-dev": {
                "psr/log": "~1.0",
                "symfony/event-dispatcher": "~2.1|~3.0.0",
                "symfony/process": "~2.1|~3.0.0"
            },
            "suggest": {
                "psr/log": "For using the console logger",
                "symfony/event-dispatcher": "",
                "symfony/process": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Console\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Console Component",
            "homepage": "https://symfony.com",
            "time": "2017-03-04T11:00:12+00:00"
        },
        {
            "name": "symfony/debug",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/debug.git",
                "reference": "e90099a2958d4833a02d05b504cc06e1c234abcc"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/debug/zipball/e90099a2958d4833a02d05b504cc06e1c234abcc",
                "reference": "e90099a2958d4833a02d05b504cc06e1c234abcc",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "psr/log": "~1.0"
            },
            "conflict": {
                "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
            },
            "require-dev": {
                "symfony/class-loader": "~2.2|~3.0.0",
                "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Debug\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Debug Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-18T19:13:35+00:00"
        },
        {
            "name": "symfony/event-dispatcher",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/event-dispatcher.git",
                "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bb4ec47e8e109c1c1172145732d0aa468d967cd0",
                "reference": "bb4ec47e8e109c1c1172145732d0aa468d967cd0",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "require-dev": {
                "psr/log": "~1.0",
                "symfony/config": "^2.0.5|~3.0.0",
                "symfony/dependency-injection": "~2.6|~3.0.0",
                "symfony/expression-language": "~2.6|~3.0.0",
                "symfony/stopwatch": "~2.3|~3.0.0"
            },
            "suggest": {
                "symfony/dependency-injection": "",
                "symfony/http-kernel": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\EventDispatcher\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony EventDispatcher Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-21T08:33:48+00:00"
        },
        {
            "name": "symfony/filesystem",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/filesystem.git",
                "reference": "e542d4765092d22552b1bf01ddccfb01d98ee325"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/filesystem/zipball/e542d4765092d22552b1bf01ddccfb01d98ee325",
                "reference": "e542d4765092d22552b1bf01ddccfb01d98ee325",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Filesystem\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Filesystem Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-18T17:06:33+00:00"
        },
        {
            "name": "symfony/finder",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/finder.git",
                "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/finder/zipball/5fc4b5cab38b9d28be318fcffd8066988e7d9451",
                "reference": "5fc4b5cab38b9d28be318fcffd8066988e7d9451",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Finder\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Finder Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-21T08:33:48+00:00"
        },
        {
            "name": "symfony/polyfill-mbstring",
            "version": "v1.3.0",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/polyfill-mbstring.git",
                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4",
                "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.3"
            },
            "suggest": {
                "ext-mbstring": "For best performance"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.3-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Polyfill\\Mbstring\\": ""
                },
                "files": [
                    "bootstrap.php"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Nicolas Grekas",
                    "email": "p@tchwork.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony polyfill for the Mbstring extension",
            "homepage": "https://symfony.com",
            "keywords": [
                "compatibility",
                "mbstring",
                "polyfill",
                "portable",
                "shim"
            ],
            "time": "2016-11-14T01:06:16+00:00"
        },
        {
            "name": "symfony/process",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/process.git",
                "reference": "41336b20b52f5fd5b42a227e394e673c8071118f"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/process/zipball/41336b20b52f5fd5b42a227e394e673c8071118f",
                "reference": "41336b20b52f5fd5b42a227e394e673c8071118f",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Process\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Process Component",
            "homepage": "https://symfony.com",
            "time": "2017-03-04T12:20:59+00:00"
        },
        {
            "name": "symfony/stopwatch",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/stopwatch.git",
                "reference": "9e4369666d02ee9b8830da878b7f6a769eb96f4b"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/stopwatch/zipball/9e4369666d02ee9b8830da878b7f6a769eb96f4b",
                "reference": "9e4369666d02ee9b8830da878b7f6a769eb96f4b",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Stopwatch\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Stopwatch Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-18T17:06:33+00:00"
        },
        {
            "name": "symfony/translation",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/translation.git",
                "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/translation/zipball/b538355bc99db2ec7cc35284ec76d92ae7d1d256",
                "reference": "b538355bc99db2ec7cc35284ec76d92ae7d1d256",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/polyfill-mbstring": "~1.0"
            },
            "conflict": {
                "symfony/config": "<2.7"
            },
            "require-dev": {
                "psr/log": "~1.0",
                "symfony/config": "~2.8",
                "symfony/intl": "~2.7.25|^2.8.18|~3.2.5",
                "symfony/yaml": "~2.2|~3.0.0"
            },
            "suggest": {
                "psr/log": "To use logging capability in translator",
                "symfony/config": "",
                "symfony/yaml": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Translation\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Translation Component",
            "homepage": "https://symfony.com",
            "time": "2017-03-04T12:20:59+00:00"
        },
        {
            "name": "symfony/validator",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/validator.git",
                "reference": "8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/validator/zipball/8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63",
                "reference": "8d4bfa7ec24e70ebc28d0cea5f2702d3f1257a63",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9",
                "symfony/polyfill-mbstring": "~1.0",
                "symfony/translation": "~2.4|~3.0.0"
            },
            "require-dev": {
                "doctrine/annotations": "~1.0",
                "doctrine/cache": "~1.0",
                "egulias/email-validator": "^1.2.1",
                "symfony/config": "~2.2|~3.0.0",
                "symfony/expression-language": "~2.4|~3.0.0",
                "symfony/http-foundation": "~2.3|~3.0.0",
                "symfony/intl": "~2.7.25|^2.8.18|~3.2.5",
                "symfony/property-access": "~2.3|~3.0.0",
                "symfony/yaml": "^2.0.5|~3.0.0"
            },
            "suggest": {
                "doctrine/annotations": "For using the annotation mapping. You will also need doctrine/cache.",
                "doctrine/cache": "For using the default cached annotation reader and metadata cache.",
                "egulias/email-validator": "Strict (RFC compliant) email validation",
                "symfony/config": "",
                "symfony/expression-language": "For using the 2.4 Expression validator",
                "symfony/http-foundation": "",
                "symfony/intl": "",
                "symfony/property-access": "For using the 2.4 Validator API",
                "symfony/yaml": ""
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Validator\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Validator Component",
            "homepage": "https://symfony.com",
            "time": "2017-02-28T02:24:56+00:00"
        },
        {
            "name": "symfony/yaml",
            "version": "v2.8.18",
            "source": {
                "type": "git",
                "url": "https://github.com/symfony/yaml.git",
                "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/symfony/yaml/zipball/2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d",
                "reference": "2a7bab3c16f6f452c47818fdd08f3b1e49ffcf7d",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.9"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.8-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Symfony\\Component\\Yaml\\": ""
                },
                "exclude-from-classmap": [
                    "/Tests/"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "MIT"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com"
                },
                {
                    "name": "Symfony Community",
                    "homepage": "https://symfony.com/contributors"
                }
            ],
            "description": "Symfony Yaml Component",
            "homepage": "https://symfony.com",
            "time": "2017-03-01T18:13:50+00:00"
        },
        {
            "name": "twig/twig",
            "version": "v1.32.0",
            "source": {
                "type": "git",
                "url": "https://github.com/twigphp/Twig.git",
                "reference": "9935b662e24d6e634da88901ab534cc12e8c728f"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/twigphp/Twig/zipball/9935b662e24d6e634da88901ab534cc12e8c728f",
                "reference": "9935b662e24d6e634da88901ab534cc12e8c728f",
                "shasum": ""
            },
            "require": {
                "php": ">=5.2.7"
            },
            "require-dev": {
                "psr/container": "^1.0",
                "symfony/debug": "~2.7",
                "symfony/phpunit-bridge": "~3.2"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "1.32-dev"
                }
            },
            "autoload": {
                "psr-0": {
                    "Twig_": "lib/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "authors": [
                {
                    "name": "Fabien Potencier",
                    "email": "fabien@symfony.com",
                    "homepage": "http://fabien.potencier.org",
                    "role": "Lead Developer"
                },
                {
                    "name": "Armin Ronacher",
                    "email": "armin.ronacher@active-4.com",
                    "role": "Project Founder"
                },
                {
                    "name": "Twig Team",
                    "homepage": "http://twig.sensiolabs.org/contributors",
                    "role": "Contributors"
                }
            ],
            "description": "Twig, the flexible, fast, and secure template language for PHP",
            "homepage": "http://twig.sensiolabs.org",
            "keywords": [
                "templating"
            ],
            "time": "2017-02-27T00:07:03+00:00"
        },
        {
            "name": "zendframework/zend-cache",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-cache.git",
                "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-cache/zipball/5999e5a03f7dcf82abbbe67eea74da641f959684",
                "reference": "5999e5a03f7dcf82abbbe67eea74da641f959684",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-eventmanager": "~2.5",
                "zendframework/zend-serializer": "~2.5",
                "zendframework/zend-servicemanager": "~2.5",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-session": "~2.5"
            },
            "suggest": {
                "ext-apc": "APC >= 3.1.6 to use the APC storage adapter",
                "ext-dba": "DBA, to use the DBA storage adapter",
                "ext-memcached": "Memcached >= 1.0.0 to use the Memcached storage adapter",
                "ext-mongo": "Mongo, to use MongoDb storage adapter",
                "ext-wincache": "WinCache, to use the WinCache storage adapter",
                "mongofill/mongofill": "Alternative to ext-mongo - a pure PHP implementation designed as a drop in replacement",
                "zendframework/zend-serializer": "Zend\\Serializer component",
                "zendframework/zend-session": "Zend\\Session component"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Cache\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "description": "provides a generic way to cache any data",
            "homepage": "https://github.com/zendframework/zend-cache",
            "keywords": [
                "cache",
                "zf2"
            ],
            "time": "2015-06-03T15:31:59+00:00"
        },
        {
            "name": "zendframework/zend-config",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-config.git",
                "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-config/zipball/ec49b1df1bdd9772df09dc2f612fbfc279bf4c27",
                "reference": "ec49b1df1bdd9772df09dc2f612fbfc279bf4c27",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-filter": "~2.5",
                "zendframework/zend-i18n": "~2.5",
                "zendframework/zend-json": "~2.5",
                "zendframework/zend-mvc": "~2.5",
                "zendframework/zend-servicemanager": "~2.5"
            },
            "suggest": {
                "zendframework/zend-filter": "Zend\\Filter component",
                "zendframework/zend-i18n": "Zend\\I18n component",
                "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes",
                "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Config\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "description": "provides a nested object property based user interface for accessing this configuration data within application code",
            "homepage": "https://github.com/zendframework/zend-config",
            "keywords": [
                "config",
                "zf2"
            ],
            "time": "2015-06-03T15:32:00+00:00"
        },
        {
            "name": "zendframework/zend-eventmanager",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-eventmanager.git",
                "reference": "d94a16039144936f107f906896349900fd634443"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d94a16039144936f107f906896349900fd634443",
                "reference": "d94a16039144936f107f906896349900fd634443",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\EventManager\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "homepage": "https://github.com/zendframework/zend-eventmanager",
            "keywords": [
                "eventmanager",
                "zf2"
            ],
            "time": "2015-06-03T15:32:01+00:00"
        },
        {
            "name": "zendframework/zend-filter",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-filter.git",
                "reference": "93e6990a198e6cdd811064083acac4693f4b29ae"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/93e6990a198e6cdd811064083acac4693f4b29ae",
                "reference": "93e6990a198e6cdd811064083acac4693f4b29ae",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-config": "~2.5",
                "zendframework/zend-crypt": "~2.5",
                "zendframework/zend-i18n": "~2.5",
                "zendframework/zend-loader": "~2.5",
                "zendframework/zend-servicemanager": "~2.5",
                "zendframework/zend-uri": "~2.5"
            },
            "suggest": {
                "zendframework/zend-crypt": "Zend\\Crypt component",
                "zendframework/zend-i18n": "Zend\\I18n component",
                "zendframework/zend-servicemanager": "Zend\\ServiceManager component",
                "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Filter\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "description": "provides a set of commonly needed data filters",
            "homepage": "https://github.com/zendframework/zend-filter",
            "keywords": [
                "filter",
                "zf2"
            ],
            "time": "2015-06-03T15:32:01+00:00"
        },
        {
            "name": "zendframework/zend-i18n",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-i18n.git",
                "reference": "509271eb7947e4aabebfc376104179cffea42696"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/509271eb7947e4aabebfc376104179cffea42696",
                "reference": "509271eb7947e4aabebfc376104179cffea42696",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-cache": "~2.5",
                "zendframework/zend-config": "~2.5",
                "zendframework/zend-eventmanager": "~2.5",
                "zendframework/zend-filter": "~2.5",
                "zendframework/zend-servicemanager": "~2.5",
                "zendframework/zend-validator": "~2.5",
                "zendframework/zend-view": "~2.5"
            },
            "suggest": {
                "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP",
                "zendframework/zend-cache": "Zend\\Cache component",
                "zendframework/zend-config": "Zend\\Config component",
                "zendframework/zend-eventmanager": "You should install this package to use the events in the translator",
                "zendframework/zend-filter": "You should install this package to use the provided filters",
                "zendframework/zend-resources": "Translation resources",
                "zendframework/zend-servicemanager": "Zend\\ServiceManager component",
                "zendframework/zend-validator": "You should install this package to use the provided validators",
                "zendframework/zend-view": "You should install this package to use the provided view helpers"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\I18n\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "homepage": "https://github.com/zendframework/zend-i18n",
            "keywords": [
                "i18n",
                "zf2"
            ],
            "time": "2015-06-03T15:32:01+00:00"
        },
        {
            "name": "zendframework/zend-json",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-json.git",
                "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-json/zipball/c74eaf17d2dd37dc1e964be8dfde05706a821ebc",
                "reference": "c74eaf17d2dd37dc1e964be8dfde05706a821ebc",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-http": "~2.5",
                "zendframework/zend-server": "~2.5",
                "zendframework/zendxml": "~1.0"
            },
            "suggest": {
                "zendframework/zend-http": "Zend\\Http component",
                "zendframework/zend-server": "Zend\\Server component",
                "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Json\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP",
            "homepage": "https://github.com/zendframework/zend-json",
            "keywords": [
                "json",
                "zf2"
            ],
            "time": "2015-06-03T15:32:01+00:00"
        },
        {
            "name": "zendframework/zend-math",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-math.git",
                "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-math/zipball/9f02a1ac4d3374d3332c80f9215deec9c71558fc",
                "reference": "9f02a1ac4d3374d3332c80f9215deec9c71558fc",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "ircmaxell/random-lib": "~1.1",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-servicemanager": "~2.5"
            },
            "suggest": {
                "ext-bcmath": "If using the bcmath functionality",
                "ext-gmp": "If using the gmp functionality",
                "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable",
                "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Math\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "homepage": "https://github.com/zendframework/zend-math",
            "keywords": [
                "math",
                "zf2"
            ],
            "time": "2015-06-03T15:32:02+00:00"
        },
        {
            "name": "zendframework/zend-serializer",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-serializer.git",
                "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/b7208eb17dc4a4fb3a660b85e6c4af035eeed40c",
                "reference": "b7208eb17dc4a4fb3a660b85e6c4af035eeed40c",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23",
                "zendframework/zend-json": "~2.5",
                "zendframework/zend-math": "~2.5",
                "zendframework/zend-stdlib": "~2.5"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-servicemanager": "~2.5"
            },
            "suggest": {
                "zendframework/zend-servicemanager": "To support plugin manager support"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Serializer\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover",
            "homepage": "https://github.com/zendframework/zend-serializer",
            "keywords": [
                "serializer",
                "zf2"
            ],
            "time": "2015-06-03T15:32:02+00:00"
        },
        {
            "name": "zendframework/zend-servicemanager",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-servicemanager.git",
                "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/3b22c403e351d92526c642cba0bd810bc22e1c56",
                "reference": "3b22c403e351d92526c642cba0bd810bc22e1c56",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-di": "~2.5",
                "zendframework/zend-mvc": "~2.5"
            },
            "suggest": {
                "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services",
                "zendframework/zend-di": "Zend\\Di component"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\ServiceManager\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "homepage": "https://github.com/zendframework/zend-servicemanager",
            "keywords": [
                "servicemanager",
                "zf2"
            ],
            "time": "2015-06-03T15:32:02+00:00"
        },
        {
            "name": "zendframework/zend-stdlib",
            "version": "2.5.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zendframework/zend-stdlib.git",
                "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/cc8e90a60dd5d44b9730b77d07b97550091da1ae",
                "reference": "cc8e90a60dd5d44b9730b77d07b97550091da1ae",
                "shasum": ""
            },
            "require": {
                "php": ">=5.3.23"
            },
            "require-dev": {
                "fabpot/php-cs-fixer": "1.7.*",
                "phpunit/phpunit": "~4.0",
                "zendframework/zend-config": "~2.5",
                "zendframework/zend-eventmanager": "~2.5",
                "zendframework/zend-filter": "~2.5",
                "zendframework/zend-inputfilter": "~2.5",
                "zendframework/zend-serializer": "~2.5",
                "zendframework/zend-servicemanager": "~2.5"
            },
            "suggest": {
                "zendframework/zend-eventmanager": "To support aggregate hydrator usage",
                "zendframework/zend-filter": "To support naming strategy hydrator usage",
                "zendframework/zend-serializer": "Zend\\Serializer component",
                "zendframework/zend-servicemanager": "To support hydrator plugin manager usage"
            },
            "type": "library",
            "extra": {
                "branch-alias": {
                    "dev-master": "2.5-dev",
                    "dev-develop": "2.6-dev"
                }
            },
            "autoload": {
                "psr-4": {
                    "Zend\\Stdlib\\": "src/"
                }
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "BSD-3-Clause"
            ],
            "homepage": "https://github.com/zendframework/zend-stdlib",
            "keywords": [
                "stdlib",
                "zf2"
            ],
            "time": "2015-06-03T15:32:03+00:00"
        },
        {
            "name": "zetacomponents/base",
            "version": "1.9",
            "source": {
                "type": "git",
                "url": "https://github.com/zetacomponents/Base.git",
                "reference": "f20df24e8de3e48b6b69b2503f917e457281e687"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zetacomponents/Base/zipball/f20df24e8de3e48b6b69b2503f917e457281e687",
                "reference": "f20df24e8de3e48b6b69b2503f917e457281e687",
                "shasum": ""
            },
            "require-dev": {
                "zetacomponents/unit-test": "*"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "Sergey Alexeev"
                },
                {
                    "name": "Sebastian Bergmann"
                },
                {
                    "name": "Jan Borsodi"
                },
                {
                    "name": "Raymond Bosman"
                },
                {
                    "name": "Frederik Holljen"
                },
                {
                    "name": "Kore Nordmann"
                },
                {
                    "name": "Derick Rethans"
                },
                {
                    "name": "Vadym Savchuk"
                },
                {
                    "name": "Tobias Schlitt"
                },
                {
                    "name": "Alexandru Stanoi"
                }
            ],
            "description": "The Base package provides the basic infrastructure that all packages rely on. Therefore every component relies on this package.",
            "homepage": "https://github.com/zetacomponents",
            "time": "2014-09-19T03:28:34+00:00"
        },
        {
            "name": "zetacomponents/document",
            "version": "1.3.1",
            "source": {
                "type": "git",
                "url": "https://github.com/zetacomponents/Document.git",
                "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8"
            },
            "dist": {
                "type": "zip",
                "url": "https://api.github.com/repos/zetacomponents/Document/zipball/688abfde573cf3fe0730f82538fbd7aa9fc95bc8",
                "reference": "688abfde573cf3fe0730f82538fbd7aa9fc95bc8",
                "shasum": ""
            },
            "require": {
                "zetacomponents/base": "*"
            },
            "require-dev": {
                "zetacomponents/unit-test": "dev-master"
            },
            "type": "library",
            "autoload": {
                "classmap": [
                    "src"
                ]
            },
            "notification-url": "https://packagist.org/downloads/",
            "license": [
                "Apache-2.0"
            ],
            "authors": [
                {
                    "name": "Sebastian Bergmann"
                },
                {
                    "name": "Kore Nordmann"
                },
                {
                    "name": "Derick Rethans"
                },
                {
                    "name": "Tobias Schlitt"
                },
                {
                    "name": "Alexandru Stanoi"
                }
            ],
            "description": "The Document components provides a general conversion framework for different semantic document markup languages like XHTML, Docbook, RST and similar.",
            "homepage": "https://github.com/zetacomponents",
            "time": "2013-12-19T11:40:00+00:00"
        }
    ],
    "aliases": [],
    "minimum-stability": "stable",
    "stability-flags": [],
    "prefer-stable": false,
    "prefer-lowest": false,
    "platform": {
        "php": ">=5.0.0"
    },
    "platform-dev": []
}
vendor/phpmailer/phpmailer/PHPMailerAutoload.php000064400000003231152177723700016000 0ustar00<?php
/**
 * PHPMailer SPL autoloader.
 * PHP Version 5
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailer SPL autoloader.
 * @param string $classname The name of the class to load
 */
function PHPMailerAutoload($classname)
{
    //Can't use __DIR__ as it's only in PHP 5.3+
    $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php';
    if (is_readable($filename)) {
        require $filename;
    }
}

if (version_compare(PHP_VERSION, '5.1.2', '>=')) {
    //SPL autoloading was introduced in PHP 5.1.2
    if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
        spl_autoload_register('PHPMailerAutoload', true, true);
    } else {
        spl_autoload_register('PHPMailerAutoload');
    }
} else {
    /**
     * Fall back to traditional autoload for old PHP versions
     * @param string $classname The name of the class to load
     */
    function __autoload($classname)
    {
        PHPMailerAutoload($classname);
    }
}
vendor/phpmailer/phpmailer/LICENSE000064400000063641152177723700013035 0ustar00                  GNU LESSER GENERAL PUBLIC LICENSE
                       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

                  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

                            NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

                     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!vendor/phpmailer/phpmailer/class.smtp.php000064400000124747152177723700014635 0ustar00<?php
/**
 * PHPMailer RFC821 SMTP email transport class.
 * PHP Version 5
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailer RFC821 SMTP email transport class.
 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
 * @package PHPMailer
 * @author Chris Ryan
 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
 */
class SMTP
{
    /**
     * The PHPMailer SMTP version number.
     * @var string
     */
    const VERSION = '5.2.27';

    /**
     * SMTP line break constant.
     * @var string
     */
    const CRLF = "\r\n";

    /**
     * The SMTP port to use if one is not specified.
     * @var integer
     */
    const DEFAULT_SMTP_PORT = 25;

    /**
     * The maximum line length allowed by RFC 2822 section 2.1.1
     * @var integer
     */
    const MAX_LINE_LENGTH = 998;

    /**
     * Debug level for no output
     */
    const DEBUG_OFF = 0;

    /**
     * Debug level to show client -> server messages
     */
    const DEBUG_CLIENT = 1;

    /**
     * Debug level to show client -> server and server -> client messages
     */
    const DEBUG_SERVER = 2;

    /**
     * Debug level to show connection status, client -> server and server -> client messages
     */
    const DEBUG_CONNECTION = 3;

    /**
     * Debug level to show all messages
     */
    const DEBUG_LOWLEVEL = 4;

    /**
     * The PHPMailer SMTP Version number.
     * @var string
     * @deprecated Use the `VERSION` constant instead
     * @see SMTP::VERSION
     */
    public $Version = '5.2.27';

    /**
     * SMTP server port number.
     * @var integer
     * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
     * @see SMTP::DEFAULT_SMTP_PORT
     */
    public $SMTP_PORT = 25;

    /**
     * SMTP reply line ending.
     * @var string
     * @deprecated Use the `CRLF` constant instead
     * @see SMTP::CRLF
     */
    public $CRLF = "\r\n";

    /**
     * Debug output level.
     * Options:
     * * self::DEBUG_OFF (`0`) No debug output, default
     * * self::DEBUG_CLIENT (`1`) Client commands
     * * self::DEBUG_SERVER (`2`) Client commands and server responses
     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
     * @var integer
     */
    public $do_debug = self::DEBUG_OFF;

    /**
     * How to handle debug output.
     * Options:
     * * `echo` Output plain-text as-is, appropriate for CLI
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
     * * `error_log` Output to error log as configured in php.ini
     *
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
     * <code>
     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
     * </code>
     * @var string|callable
     */
    public $Debugoutput = 'echo';

    /**
     * Whether to use VERP.
     * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
     * @link http://www.postfix.org/VERP_README.html Info on VERP
     * @var boolean
     */
    public $do_verp = false;

    /**
     * The timeout value for connection, in seconds.
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
     * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
     * @var integer
     */
    public $Timeout = 300;

    /**
     * How long to wait for commands to complete, in seconds.
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
     * @var integer
     */
    public $Timelimit = 300;

    /**
     * @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
     * The first capture group in each regex will be used as the ID.
     */
    protected $smtp_transaction_id_patterns = array(
        'exim' => '/[0-9]{3} OK id=(.*)/',
        'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
        'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
    );

    /**
     * @var string The last transaction ID issued in response to a DATA command,
     * if one was detected
     */
    protected $last_smtp_transaction_id;

    /**
     * The socket for the server connection.
     * @var resource
     */
    protected $smtp_conn;

    /**
     * Error information, if any, for the last SMTP command.
     * @var array
     */
    protected $error = array(
        'error' => '',
        'detail' => '',
        'smtp_code' => '',
        'smtp_code_ex' => ''
    );

    /**
     * The reply the server sent to us for HELO.
     * If null, no HELO string has yet been received.
     * @var string|null
     */
    protected $helo_rply = null;

    /**
     * The set of SMTP extensions sent in reply to EHLO command.
     * Indexes of the array are extension names.
     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
     * represents the server name. In case of HELO it is the only element of the array.
     * Other values can be boolean TRUE or an array containing extension options.
     * If null, no HELO/EHLO string has yet been received.
     * @var array|null
     */
    protected $server_caps = null;

    /**
     * The most recent reply received from the server.
     * @var string
     */
    protected $last_reply = '';

    /**
     * Output debugging info via a user-selected method.
     * @see SMTP::$Debugoutput
     * @see SMTP::$do_debug
     * @param string $str Debug string to output
     * @param integer $level The debug level of this message; see DEBUG_* constants
     * @return void
     */
    protected function edebug($str, $level = 0)
    {
        if ($level > $this->do_debug) {
            return;
        }
        //Avoid clash with built-in function names
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
            call_user_func($this->Debugoutput, $str, $level);
            return;
        }
        switch ($this->Debugoutput) {
            case 'error_log':
                //Don't output, just log
                error_log($str);
                break;
            case 'html':
                //Cleans up output a bit for a better looking, HTML-safe output
                echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
                    preg_replace('/[\r\n]+/', '', $str),
                    ENT_QUOTES,
                    'UTF-8'
                ) . "<br>\n";
                break;
            case 'echo':
            default:
                //Normalize line breaks
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
                    "\n",
                    "\n                   \t                  ",
                    trim($str)
                ) . "\n";
        }
    }

    /**
     * Connect to an SMTP server.
     * @param string $host SMTP server IP or host name
     * @param integer $port The port number to connect to
     * @param integer $timeout How long to wait for the connection to open
     * @param array $options An array of options for stream_context_create()
     * @access public
     * @return boolean
     */
    public function connect($host, $port = null, $timeout = 30, $options = array())
    {
        static $streamok;
        //This is enabled by default since 5.0.0 but some providers disable it
        //Check this once and cache the result
        if (is_null($streamok)) {
            $streamok = function_exists('stream_socket_client');
        }
        // Clear errors to avoid confusion
        $this->setError('');
        // Make sure we are __not__ connected
        if ($this->connected()) {
            // Already connected, generate error
            $this->setError('Already connected to a server');
            return false;
        }
        if (empty($port)) {
            $port = self::DEFAULT_SMTP_PORT;
        }
        // Connect to the SMTP server
        $this->edebug(
            "Connection: opening to $host:$port, timeout=$timeout, options=" .
            var_export($options, true),
            self::DEBUG_CONNECTION
        );
        $errno = 0;
        $errstr = '';
        if ($streamok) {
            $socket_context = stream_context_create($options);
            set_error_handler(array($this, 'errorHandler'));
            $this->smtp_conn = stream_socket_client(
                $host . ":" . $port,
                $errno,
                $errstr,
                $timeout,
                STREAM_CLIENT_CONNECT,
                $socket_context
            );
            restore_error_handler();
        } else {
            //Fall back to fsockopen which should work in more places, but is missing some features
            $this->edebug(
                "Connection: stream_socket_client not available, falling back to fsockopen",
                self::DEBUG_CONNECTION
            );
            set_error_handler(array($this, 'errorHandler'));
            $this->smtp_conn = fsockopen(
                $host,
                $port,
                $errno,
                $errstr,
                $timeout
            );
            restore_error_handler();
        }
        // Verify we connected properly
        if (!is_resource($this->smtp_conn)) {
            $this->setError(
                'Failed to connect to server',
                $errno,
                $errstr
            );
            $this->edebug(
                'SMTP ERROR: ' . $this->error['error']
                . ": $errstr ($errno)",
                self::DEBUG_CLIENT
            );
            return false;
        }
        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
        // SMTP server can take longer to respond, give longer timeout for first read
        // Windows does not have support for this timeout function
        if (substr(PHP_OS, 0, 3) != 'WIN') {
            $max = ini_get('max_execution_time');
            // Don't bother if unlimited
            if ($max != 0 && $timeout > $max) {
                @set_time_limit($timeout);
            }
            stream_set_timeout($this->smtp_conn, $timeout, 0);
        }
        // Get any announcement
        $announce = $this->get_lines();
        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
        return true;
    }

    /**
     * Initiate a TLS (encrypted) session.
     * @access public
     * @return boolean
     */
    public function startTLS()
    {
        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
            return false;
        }

        //Allow the best TLS version(s) we can
        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;

        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
        //so add them back in manually if we can
        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
        }

        // Begin encrypted connection
        set_error_handler(array($this, 'errorHandler'));
        $crypto_ok = stream_socket_enable_crypto(
            $this->smtp_conn,
            true,
            $crypto_method
        );
        restore_error_handler();
        return $crypto_ok;
    }

    /**
     * Perform SMTP authentication.
     * Must be run after hello().
     * @see hello()
     * @param string $username The user name
     * @param string $password The password
     * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
     * @param string $realm The auth realm for NTLM
     * @param string $workstation The auth workstation for NTLM
     * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
     * @return bool True if successfully authenticated.* @access public
     */
    public function authenticate(
        $username,
        $password,
        $authtype = null,
        $realm = '',
        $workstation = '',
        $OAuth = null
    ) {
        if (!$this->server_caps) {
            $this->setError('Authentication is not allowed before HELO/EHLO');
            return false;
        }

        if (array_key_exists('EHLO', $this->server_caps)) {
            // SMTP extensions are available; try to find a proper authentication method
            if (!array_key_exists('AUTH', $this->server_caps)) {
                $this->setError('Authentication is not allowed at this stage');
                // 'at this stage' means that auth may be allowed after the stage changes
                // e.g. after STARTTLS
                return false;
            }

            self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
            self::edebug(
                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
                self::DEBUG_LOWLEVEL
            );

            if (empty($authtype)) {
                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
                    if (in_array($method, $this->server_caps['AUTH'])) {
                        $authtype = $method;
                        break;
                    }
                }
                if (empty($authtype)) {
                    $this->setError('No supported authentication methods found');
                    return false;
                }
                self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
            }

            if (!in_array($authtype, $this->server_caps['AUTH'])) {
                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
                return false;
            }
        } elseif (empty($authtype)) {
            $authtype = 'LOGIN';
        }
        switch ($authtype) {
            case 'PLAIN':
                // Start authentication
                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
                    return false;
                }
                // Send encoded username and password
                if (!$this->sendCommand(
                    'User & Password',
                    base64_encode("\0" . $username . "\0" . $password),
                    235
                )
                ) {
                    return false;
                }
                break;
            case 'LOGIN':
                // Start authentication
                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
                    return false;
                }
                if (!$this->sendCommand("Username", base64_encode($username), 334)) {
                    return false;
                }
                if (!$this->sendCommand("Password", base64_encode($password), 235)) {
                    return false;
                }
                break;
            case 'XOAUTH2':
                //If the OAuth Instance is not set. Can be a case when PHPMailer is used
                //instead of PHPMailerOAuth
                if (is_null($OAuth)) {
                    return false;
                }
                $oauth = $OAuth->getOauth64();

                // Start authentication
                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
                    return false;
                }
                break;
            case 'NTLM':
                /*
                 * ntlm_sasl_client.php
                 * Bundled with Permission
                 *
                 * How to telnet in windows:
                 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
                 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
                 */
                require_once 'extras/ntlm_sasl_client.php';
                $temp = new stdClass;
                $ntlm_client = new ntlm_sasl_client_class;
                //Check that functions are available
                if (!$ntlm_client->initialize($temp)) {
                    $this->setError($temp->error);
                    $this->edebug(
                        'You need to enable some modules in your php.ini file: '
                        . $this->error['error'],
                        self::DEBUG_CLIENT
                    );
                    return false;
                }
                //msg1
                $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1

                if (!$this->sendCommand(
                    'AUTH NTLM',
                    'AUTH NTLM ' . base64_encode($msg1),
                    334
                )
                ) {
                    return false;
                }
                //Though 0 based, there is a white space after the 3 digit number
                //msg2
                $challenge = substr($this->last_reply, 3);
                $challenge = base64_decode($challenge);
                $ntlm_res = $ntlm_client->NTLMResponse(
                    substr($challenge, 24, 8),
                    $password
                );
                //msg3
                $msg3 = $ntlm_client->typeMsg3(
                    $ntlm_res,
                    $username,
                    $realm,
                    $workstation
                );
                // send encoded username
                return $this->sendCommand('Username', base64_encode($msg3), 235);
            case 'CRAM-MD5':
                // Start authentication
                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
                    return false;
                }
                // Get the challenge
                $challenge = base64_decode(substr($this->last_reply, 4));

                // Build the response
                $response = $username . ' ' . $this->hmac($challenge, $password);

                // send encoded credentials
                return $this->sendCommand('Username', base64_encode($response), 235);
            default:
                $this->setError("Authentication method \"$authtype\" is not supported");
                return false;
        }
        return true;
    }

    /**
     * Calculate an MD5 HMAC hash.
     * Works like hash_hmac('md5', $data, $key)
     * in case that function is not available
     * @param string $data The data to hash
     * @param string $key The key to hash with
     * @access protected
     * @return string
     */
    protected function hmac($data, $key)
    {
        if (function_exists('hash_hmac')) {
            return hash_hmac('md5', $data, $key);
        }

        // The following borrowed from
        // http://php.net/manual/en/function.mhash.php#27225

        // RFC 2104 HMAC implementation for php.
        // Creates an md5 HMAC.
        // Eliminates the need to install mhash to compute a HMAC
        // by Lance Rushing

        $bytelen = 64; // byte length for md5
        if (strlen($key) > $bytelen) {
            $key = pack('H*', md5($key));
        }
        $key = str_pad($key, $bytelen, chr(0x00));
        $ipad = str_pad('', $bytelen, chr(0x36));
        $opad = str_pad('', $bytelen, chr(0x5c));
        $k_ipad = $key ^ $ipad;
        $k_opad = $key ^ $opad;

        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
    }

    /**
     * Check connection state.
     * @access public
     * @return boolean True if connected.
     */
    public function connected()
    {
        if (is_resource($this->smtp_conn)) {
            $sock_status = stream_get_meta_data($this->smtp_conn);
            if ($sock_status['eof']) {
                // The socket is valid but we are not connected
                $this->edebug(
                    'SMTP NOTICE: EOF caught while checking if connected',
                    self::DEBUG_CLIENT
                );
                $this->close();
                return false;
            }
            return true; // everything looks good
        }
        return false;
    }

    /**
     * Close the socket and clean up the state of the class.
     * Don't use this function without first trying to use QUIT.
     * @see quit()
     * @access public
     * @return void
     */
    public function close()
    {
        $this->setError('');
        $this->server_caps = null;
        $this->helo_rply = null;
        if (is_resource($this->smtp_conn)) {
            // close the connection and cleanup
            fclose($this->smtp_conn);
            $this->smtp_conn = null; //Makes for cleaner serialization
            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
        }
    }

    /**
     * Send an SMTP DATA command.
     * Issues a data command and sends the msg_data to the server,
     * finializing the mail transaction. $msg_data is the message
     * that is to be send with the headers. Each header needs to be
     * on a single line followed by a <CRLF> with the message headers
     * and the message body being separated by and additional <CRLF>.
     * Implements rfc 821: DATA <CRLF>
     * @param string $msg_data Message data to send
     * @access public
     * @return boolean
     */
    public function data($msg_data)
    {
        //This will use the standard timelimit
        if (!$this->sendCommand('DATA', 'DATA', 354)) {
            return false;
        }

        /* The server is ready to accept data!
         * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
         * smaller lines to fit within the limit.
         * We will also look for lines that start with a '.' and prepend an additional '.'.
         * NOTE: this does not count towards line-length limit.
         */

        // Normalize line breaks before exploding
        $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));

        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
         * process all lines before a blank line as headers.
         */

        $field = substr($lines[0], 0, strpos($lines[0], ':'));
        $in_headers = false;
        if (!empty($field) && strpos($field, ' ') === false) {
            $in_headers = true;
        }

        foreach ($lines as $line) {
            $lines_out = array();
            if ($in_headers and $line == '') {
                $in_headers = false;
            }
            //Break this line up into several smaller lines if it's too long
            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
            while (isset($line[self::MAX_LINE_LENGTH])) {
                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
                //so as to avoid breaking in the middle of a word
                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
                //Deliberately matches both false and 0
                if (!$pos) {
                    //No nice break found, add a hard break
                    $pos = self::MAX_LINE_LENGTH - 1;
                    $lines_out[] = substr($line, 0, $pos);
                    $line = substr($line, $pos);
                } else {
                    //Break at the found point
                    $lines_out[] = substr($line, 0, $pos);
                    //Move along by the amount we dealt with
                    $line = substr($line, $pos + 1);
                }
                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
                if ($in_headers) {
                    $line = "\t" . $line;
                }
            }
            $lines_out[] = $line;

            //Send the lines to the server
            foreach ($lines_out as $line_out) {
                //RFC2821 section 4.5.2
                if (!empty($line_out) and $line_out[0] == '.') {
                    $line_out = '.' . $line_out;
                }
                $this->client_send($line_out . self::CRLF);
            }
        }

        //Message data has been sent, complete the command
        //Increase timelimit for end of DATA command
        $savetimelimit = $this->Timelimit;
        $this->Timelimit = $this->Timelimit * 2;
        $result = $this->sendCommand('DATA END', '.', 250);
        $this->recordLastTransactionID();
        //Restore timelimit
        $this->Timelimit = $savetimelimit;
        return $result;
    }

    /**
     * Send an SMTP HELO or EHLO command.
     * Used to identify the sending server to the receiving server.
     * This makes sure that client and server are in a known state.
     * Implements RFC 821: HELO <SP> <domain> <CRLF>
     * and RFC 2821 EHLO.
     * @param string $host The host name or IP to connect to
     * @access public
     * @return boolean
     */
    public function hello($host = '')
    {
        //Try extended hello first (RFC 2821)
        return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
    }

    /**
     * Send an SMTP HELO or EHLO command.
     * Low-level implementation used by hello()
     * @see hello()
     * @param string $hello The HELO string
     * @param string $host The hostname to say we are
     * @access protected
     * @return boolean
     */
    protected function sendHello($hello, $host)
    {
        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
        $this->helo_rply = $this->last_reply;
        if ($noerror) {
            $this->parseHelloFields($hello);
        } else {
            $this->server_caps = null;
        }
        return $noerror;
    }

    /**
     * Parse a reply to HELO/EHLO command to discover server extensions.
     * In case of HELO, the only parameter that can be discovered is a server name.
     * @access protected
     * @param string $type - 'HELO' or 'EHLO'
     */
    protected function parseHelloFields($type)
    {
        $this->server_caps = array();
        $lines = explode("\n", $this->helo_rply);

        foreach ($lines as $n => $s) {
            //First 4 chars contain response code followed by - or space
            $s = trim(substr($s, 4));
            if (empty($s)) {
                continue;
            }
            $fields = explode(' ', $s);
            if (!empty($fields)) {
                if (!$n) {
                    $name = $type;
                    $fields = $fields[0];
                } else {
                    $name = array_shift($fields);
                    switch ($name) {
                        case 'SIZE':
                            $fields = ($fields ? $fields[0] : 0);
                            break;
                        case 'AUTH':
                            if (!is_array($fields)) {
                                $fields = array();
                            }
                            break;
                        default:
                            $fields = true;
                    }
                }
                $this->server_caps[$name] = $fields;
            }
        }
    }

    /**
     * Send an SMTP MAIL command.
     * Starts a mail transaction from the email address specified in
     * $from. Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more recipient
     * commands may be called followed by a data command.
     * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
     * @param string $from Source address of this message
     * @access public
     * @return boolean
     */
    public function mail($from)
    {
        $useVerp = ($this->do_verp ? ' XVERP' : '');
        return $this->sendCommand(
            'MAIL FROM',
            'MAIL FROM:<' . $from . '>' . $useVerp,
            250
        );
    }

    /**
     * Send an SMTP QUIT command.
     * Closes the socket if there is no error or the $close_on_error argument is true.
     * Implements from rfc 821: QUIT <CRLF>
     * @param boolean $close_on_error Should the connection close if an error occurs?
     * @access public
     * @return boolean
     */
    public function quit($close_on_error = true)
    {
        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
        $err = $this->error; //Save any error
        if ($noerror or $close_on_error) {
            $this->close();
            $this->error = $err; //Restore any error from the quit command
        }
        return $noerror;
    }

    /**
     * Send an SMTP RCPT command.
     * Sets the TO argument to $toaddr.
     * Returns true if the recipient was accepted false if it was rejected.
     * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
     * @param string $address The address the message is being sent to
     * @access public
     * @return boolean
     */
    public function recipient($address)
    {
        return $this->sendCommand(
            'RCPT TO',
            'RCPT TO:<' . $address . '>',
            array(250, 251)
        );
    }

    /**
     * Send an SMTP RSET command.
     * Abort any transaction that is currently in progress.
     * Implements rfc 821: RSET <CRLF>
     * @access public
     * @return boolean True on success.
     */
    public function reset()
    {
        return $this->sendCommand('RSET', 'RSET', 250);
    }

    /**
     * Send a command to an SMTP server and check its return code.
     * @param string $command The command name - not sent to the server
     * @param string $commandstring The actual command to send
     * @param integer|array $expect One or more expected integer success codes
     * @access protected
     * @return boolean True on success.
     */
    protected function sendCommand($command, $commandstring, $expect)
    {
        if (!$this->connected()) {
            $this->setError("Called $command without being connected");
            return false;
        }
        //Reject line breaks in all commands
        if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
            $this->setError("Command '$command' contained line breaks");
            return false;
        }
        $this->client_send($commandstring . self::CRLF);

        $this->last_reply = $this->get_lines();
        // Fetch SMTP code and possible error code explanation
        $matches = array();
        if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
            $code = $matches[1];
            $code_ex = (count($matches) > 2 ? $matches[2] : null);
            // Cut off error code from each response line
            $detail = preg_replace(
                "/{$code}[ -]" .
                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
                '',
                $this->last_reply
            );
        } else {
            // Fall back to simple parsing if regex fails
            $code = substr($this->last_reply, 0, 3);
            $code_ex = null;
            $detail = substr($this->last_reply, 4);
        }

        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);

        if (!in_array($code, (array)$expect)) {
            $this->setError(
                "$command command failed",
                $detail,
                $code,
                $code_ex
            );
            $this->edebug(
                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
                self::DEBUG_CLIENT
            );
            return false;
        }

        $this->setError('');
        return true;
    }

    /**
     * Send an SMTP SAML command.
     * Starts a mail transaction from the email address specified in $from.
     * Returns true if successful or false otherwise. If True
     * the mail transaction is started and then one or more recipient
     * commands may be called followed by a data command. This command
     * will send the message to the users terminal if they are logged
     * in and send them an email.
     * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
     * @param string $from The address the message is from
     * @access public
     * @return boolean
     */
    public function sendAndMail($from)
    {
        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
    }

    /**
     * Send an SMTP VRFY command.
     * @param string $name The name to verify
     * @access public
     * @return boolean
     */
    public function verify($name)
    {
        return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
    }

    /**
     * Send an SMTP NOOP command.
     * Used to keep keep-alives alive, doesn't actually do anything
     * @access public
     * @return boolean
     */
    public function noop()
    {
        return $this->sendCommand('NOOP', 'NOOP', 250);
    }

    /**
     * Send an SMTP TURN command.
     * This is an optional command for SMTP that this class does not support.
     * This method is here to make the RFC821 Definition complete for this class
     * and _may_ be implemented in future
     * Implements from rfc 821: TURN <CRLF>
     * @access public
     * @return boolean
     */
    public function turn()
    {
        $this->setError('The SMTP TURN command is not implemented');
        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
        return false;
    }

    /**
     * Send raw data to the server.
     * @param string $data The data to send
     * @access public
     * @return integer|boolean The number of bytes sent to the server or false on error
     */
    public function client_send($data)
    {
        $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
        set_error_handler(array($this, 'errorHandler'));
        $result = fwrite($this->smtp_conn, $data);
        restore_error_handler();
        return $result;
    }

    /**
     * Get the latest error.
     * @access public
     * @return array
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * Get SMTP extensions available on the server
     * @access public
     * @return array|null
     */
    public function getServerExtList()
    {
        return $this->server_caps;
    }

    /**
     * A multipurpose method
     * The method works in three ways, dependent on argument value and current state
     *   1. HELO/EHLO was not sent - returns null and set up $this->error
     *   2. HELO was sent
     *     $name = 'HELO': returns server name
     *     $name = 'EHLO': returns boolean false
     *     $name = any string: returns null and set up $this->error
     *   3. EHLO was sent
     *     $name = 'HELO'|'EHLO': returns server name
     *     $name = any string: if extension $name exists, returns boolean True
     *       or its options. Otherwise returns boolean False
     * In other words, one can use this method to detect 3 conditions:
     *  - null returned: handshake was not or we don't know about ext (refer to $this->error)
     *  - false returned: the requested feature exactly not exists
     *  - positive value returned: the requested feature exists
     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
     * @return mixed
     */
    public function getServerExt($name)
    {
        if (!$this->server_caps) {
            $this->setError('No HELO/EHLO was sent');
            return null;
        }

        // the tight logic knot ;)
        if (!array_key_exists($name, $this->server_caps)) {
            if ($name == 'HELO') {
                return $this->server_caps['EHLO'];
            }
            if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
                return false;
            }
            $this->setError('HELO handshake was used. Client knows nothing about server extensions');
            return null;
        }

        return $this->server_caps[$name];
    }

    /**
     * Get the last reply from the server.
     * @access public
     * @return string
     */
    public function getLastReply()
    {
        return $this->last_reply;
    }

    /**
     * Read the SMTP server's response.
     * Either before eof or socket timeout occurs on the operation.
     * With SMTP we can tell if we have more lines to read if the
     * 4th character is '-' symbol. If it is a space then we don't
     * need to read anything else.
     * @access protected
     * @return string
     */
    protected function get_lines()
    {
        // If the connection is bad, give up straight away
        if (!is_resource($this->smtp_conn)) {
            return '';
        }
        $data = '';
        $endtime = 0;
        stream_set_timeout($this->smtp_conn, $this->Timeout);
        if ($this->Timelimit > 0) {
            $endtime = time() + $this->Timelimit;
        }
        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
            $str = @fgets($this->smtp_conn, 515);
            $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
            $this->edebug("SMTP -> get_lines(): \$str is  \"$str\"", self::DEBUG_LOWLEVEL);
            $data .= $str;
            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
            // or 4th character is a space, we are done reading, break the loop,
            // string array access is a micro-optimisation over strlen
            if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
                break;
            }
            // Timed-out? Log and break
            $info = stream_get_meta_data($this->smtp_conn);
            if ($info['timed_out']) {
                $this->edebug(
                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
                    self::DEBUG_LOWLEVEL
                );
                break;
            }
            // Now check if reads took too long
            if ($endtime and time() > $endtime) {
                $this->edebug(
                    'SMTP -> get_lines(): timelimit reached (' .
                    $this->Timelimit . ' sec)',
                    self::DEBUG_LOWLEVEL
                );
                break;
            }
        }
        return $data;
    }

    /**
     * Enable or disable VERP address generation.
     * @param boolean $enabled
     */
    public function setVerp($enabled = false)
    {
        $this->do_verp = $enabled;
    }

    /**
     * Get VERP address generation mode.
     * @return boolean
     */
    public function getVerp()
    {
        return $this->do_verp;
    }

    /**
     * Set error messages and codes.
     * @param string $message The error message
     * @param string $detail Further detail on the error
     * @param string $smtp_code An associated SMTP error code
     * @param string $smtp_code_ex Extended SMTP code
     */
    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
    {
        $this->error = array(
            'error' => $message,
            'detail' => $detail,
            'smtp_code' => $smtp_code,
            'smtp_code_ex' => $smtp_code_ex
        );
    }

    /**
     * Set debug output method.
     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
     */
    public function setDebugOutput($method = 'echo')
    {
        $this->Debugoutput = $method;
    }

    /**
     * Get debug output method.
     * @return string
     */
    public function getDebugOutput()
    {
        return $this->Debugoutput;
    }

    /**
     * Set debug output level.
     * @param integer $level
     */
    public function setDebugLevel($level = 0)
    {
        $this->do_debug = $level;
    }

    /**
     * Get debug output level.
     * @return integer
     */
    public function getDebugLevel()
    {
        return $this->do_debug;
    }

    /**
     * Set SMTP timeout.
     * @param integer $timeout
     */
    public function setTimeout($timeout = 0)
    {
        $this->Timeout = $timeout;
    }

    /**
     * Get SMTP timeout.
     * @return integer
     */
    public function getTimeout()
    {
        return $this->Timeout;
    }

    /**
     * Reports an error number and string.
     * @param integer $errno The error number returned by PHP.
     * @param string $errmsg The error message returned by PHP.
     * @param string $errfile The file the error occurred in
     * @param integer $errline The line number the error occurred on
     */
    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
    {
        $notice = 'Connection failed.';
        $this->setError(
            $notice,
            $errno,
            $errmsg
        );
        $this->edebug(
            $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
            self::DEBUG_CONNECTION
        );
    }

    /**
     * Extract and return the ID of the last SMTP transaction based on
     * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
     * Relies on the host providing the ID in response to a DATA command.
     * If no reply has been received yet, it will return null.
     * If no pattern was matched, it will return false.
     * @return bool|null|string
     */
    protected function recordLastTransactionID()
    {
        $reply = $this->getLastReply();

        if (empty($reply)) {
            $this->last_smtp_transaction_id = null;
        } else {
            $this->last_smtp_transaction_id = false;
            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
                    $this->last_smtp_transaction_id = $matches[1];
                }
            }
        }

        return $this->last_smtp_transaction_id;
    }

    /**
     * Get the queue/transaction ID of the last SMTP transaction
     * If no reply has been received yet, it will return null.
     * If no pattern was matched, it will return false.
     * @return bool|null|string
     * @see recordLastTransactionID()
     */
    public function getLastTransactionID()
    {
        return $this->last_smtp_transaction_id;
    }
}
vendor/phpmailer/phpmailer/class.phpmaileroauth.php000064400000016060152177723700016660 0ustar00<?php
/**
 * PHPMailer - PHP email creation and transport class.
 * PHP Version 5.4
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailerOAuth - PHPMailer subclass adding OAuth support.
 * @package PHPMailer
 * @author @sherryl4george
 * @author Marcus Bointon (@Synchro) <phpmailer@synchromedia.co.uk>
 */
class PHPMailerOAuth extends PHPMailer
{
    /**
     * The OAuth user's email address
     * @var string
     */
    public $oauthUserEmail = '';

    /**
     * The OAuth refresh token
     * @var string
     */
    public $oauthRefreshToken = '';

    /**
     * The OAuth client ID
     * @var string
     */
    public $oauthClientId = '';

    /**
     * The OAuth client secret
     * @var string
     */
    public $oauthClientSecret = '';

    /**
     * An instance of the PHPMailerOAuthGoogle class.
     * @var PHPMailerOAuthGoogle
     * @access protected
     */
    protected $oauth = null;

    /**
     * Get a PHPMailerOAuthGoogle instance to use.
     * @return PHPMailerOAuthGoogle
     */
    public function getOAUTHInstance()
    {
        if (!is_object($this->oauth)) {
            $this->oauth = new PHPMailerOAuthGoogle(
                $this->oauthUserEmail,
                $this->oauthClientSecret,
                $this->oauthClientId,
                $this->oauthRefreshToken
            );
        }
        return $this->oauth;
    }

    /**
     * Initiate a connection to an SMTP server.
     * Overrides the original smtpConnect method to add support for OAuth.
     * @param array $options An array of options compatible with stream_context_create()
     * @uses SMTP
     * @access public
     * @return bool
     * @throws phpmailerException
     */
    public function smtpConnect($options = array())
    {
        if (is_null($this->smtp)) {
            $this->smtp = $this->getSMTPInstance();
        }

        if (is_null($this->oauth)) {
            $this->oauth = $this->getOAUTHInstance();
        }

        // Already connected?
        if ($this->smtp->connected()) {
            return true;
        }

        $this->smtp->setTimeout($this->Timeout);
        $this->smtp->setDebugLevel($this->SMTPDebug);
        $this->smtp->setDebugOutput($this->Debugoutput);
        $this->smtp->setVerp($this->do_verp);
        $hosts = explode(';', $this->Host);
        $lastexception = null;

        foreach ($hosts as $hostentry) {
            $hostinfo = array();
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
                // Not a valid host entry
                continue;
            }
            // $hostinfo[2]: optional ssl or tls prefix
            // $hostinfo[3]: the hostname
            // $hostinfo[4]: optional port number
            // The host string prefix can temporarily override the current setting for SMTPSecure
            // If it's not specified, the default value is used
            $prefix = '';
            $secure = $this->SMTPSecure;
            $tls = ($this->SMTPSecure == 'tls');
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
                $prefix = 'ssl://';
                $tls = false; // Can't have SSL and TLS at the same time
                $secure = 'ssl';
            } elseif ($hostinfo[2] == 'tls') {
                $tls = true;
                // tls doesn't use a prefix
                $secure = 'tls';
            }
            //Do we need the OpenSSL extension?
            $sslext = defined('OPENSSL_ALGO_SHA1');
            if ('tls' === $secure or 'ssl' === $secure) {
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
                if (!$sslext) {
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
                }
            }
            $host = $hostinfo[3];
            $port = $this->Port;
            $tport = (integer)$hostinfo[4];
            if ($tport > 0 and $tport < 65536) {
                $port = $tport;
            }
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
                try {
                    if ($this->Helo) {
                        $hello = $this->Helo;
                    } else {
                        $hello = $this->serverHostname();
                    }
                    $this->smtp->hello($hello);
                    //Automatically enable TLS encryption if:
                    // * it's not disabled
                    // * we have openssl extension
                    // * we are not already using SSL
                    // * the server offers STARTTLS
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
                        $tls = true;
                    }
                    if ($tls) {
                        if (!$this->smtp->startTLS()) {
                            throw new phpmailerException($this->lang('connect_host'));
                        }
                        // We must resend HELO after tls negotiation
                        $this->smtp->hello($hello);
                    }
                    if ($this->SMTPAuth) {
                        if (!$this->smtp->authenticate(
                            $this->Username,
                            $this->Password,
                            $this->AuthType,
                            $this->Realm,
                            $this->Workstation,
                            $this->oauth
                        )
                        ) {
                            throw new phpmailerException($this->lang('authenticate'));
                        }
                    }
                    return true;
                } catch (phpmailerException $exc) {
                    $lastexception = $exc;
                    $this->edebug($exc->getMessage());
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
                    $this->smtp->quit();
                }
            }
        }
        // If we get here, all connection attempts have failed, so close connection hard
        $this->smtp->close();
        // As we've caught all exceptions, just report whatever the last one was
        if ($this->exceptions and !is_null($lastexception)) {
            throw $lastexception;
        }
        return false;
    }
}
vendor/phpmailer/phpmailer/extras/htmlfilter.php000064400000114543152177723700016217 0ustar00<?php
/**
 * htmlfilter.inc
 * ---------------
 * This set of functions allows you to filter html in order to remove
 * any malicious tags from it. Useful in cases when you need to filter
 * user input for any cross-site-scripting attempts.
 *
 * Copyright (C) 2002-2004 by Duke University
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 *
 * @Author	Konstantin Riabitsev <icon@linux.duke.edu>
 * @Author  Jim Jagielski <jim@jaguNET.com / jimjag@gmail.com>
 * @Version 1.1 ($Date$)
 */

/**
 * This function returns the final tag out of the tag name, an array
 * of attributes, and the type of the tag. This function is called by
 * tln_sanitize internally.
 *
 * @param string $tagname the name of the tag.
 * @param array $attary the array of attributes and their values
 * @param integer $tagtype The type of the tag (see in comments).
 * @return string A string with the final tag representation.
 */
function tln_tagprint($tagname, $attary, $tagtype)
{
    if ($tagtype == 2) {
        $fulltag = '</' . $tagname . '>';
    } else {
        $fulltag = '<' . $tagname;
        if (is_array($attary) && sizeof($attary)) {
            $atts = array();
            foreach($attary as $attname => $attvalue) {
                array_push($atts, "$attname=$attvalue");
            }
            $fulltag .= ' ' . join(' ', $atts);
        }
        if ($tagtype == 3) {
            $fulltag .= ' /';
        }
        $fulltag .= '>';
    }
    return $fulltag;
}

/**
 * A small helper function to use with array_walk. Modifies a by-ref
 * value and makes it lowercase.
 *
 * @param string $val a value passed by-ref.
 * @return		void since it modifies a by-ref value.
 */
function tln_casenormalize(&$val)
{
    $val = strtolower($val);
}

/**
 * This function skips any whitespace from the current position within
 * a string and to the next non-whitespace value.
 *
 * @param string $body the string
 * @param integer $offset the offset within the string where we should start
 *				   looking for the next non-whitespace character.
 * @return integer          the location within the $body where the next
 *				   non-whitespace char is located.
 */
function tln_skipspace($body, $offset)
{
    preg_match('/^(\s*)/s', substr($body, $offset), $matches);
    if (sizeof($matches[1])) {
        $count = strlen($matches[1]);
        $offset += $count;
    }
    return $offset;
}

/**
 * This function looks for the next character within a string.	It's
 * really just a glorified "strpos", except it catches the failures
 * nicely.
 *
 * @param string $body   The string to look for needle in.
 * @param integer $offset Start looking from this position.
 * @param string $needle The character/string to look for.
 * @return integer           location of the next occurrence of the needle, or
 *				   strlen($body) if needle wasn't found.
 */
function tln_findnxstr($body, $offset, $needle)
{
    $pos = strpos($body, $needle, $offset);
    if ($pos === false) {
        $pos = strlen($body);
    }
    return $pos;
}

/**
 * This function takes a PCRE-style regexp and tries to match it
 * within the string.
 *
 * @param string $body   The string to look for needle in.
 * @param integer $offset Start looking from here.
 * @param string $reg       A PCRE-style regex to match.
 * @return array|boolean  Returns a false if no matches found, or an array
 *				   with the following members:
 *				   - integer with the location of the match within $body
 *				   - string with whatever content between offset and the match
 *				   - string with whatever it is we matched
 */
function tln_findnxreg($body, $offset, $reg)
{
    $matches = array();
    $retarr = array();
    $preg_rule = '%^(.*?)(' . $reg . ')%s';
    preg_match($preg_rule, substr($body, $offset), $matches);
    if (!isset($matches[0]) || !$matches[0]) {
        $retarr = false;
    } else {
        $retarr[0] = $offset + strlen($matches[1]);
        $retarr[1] = $matches[1];
        $retarr[2] = $matches[2];
    }
    return $retarr;
}

/**
 * This function looks for the next tag.
 *
 * @param string $body   String where to look for the next tag.
 * @param integer $offset Start looking from here.
 * @return array|boolean false if no more tags exist in the body, or
 *				   an array with the following members:
 *				   - string with the name of the tag
 *				   - array with attributes and their values
 *				   - integer with tag type (1, 2, or 3)
 *				   - integer where the tag starts (starting "<")
 *				   - integer where the tag ends (ending ">")
 *				   first three members will be false, if the tag is invalid.
 */
function tln_getnxtag($body, $offset)
{
    if ($offset > strlen($body)) {
        return false;
    }
    $lt = tln_findnxstr($body, $offset, '<');
    if ($lt == strlen($body)) {
        return false;
    }
    /**
     * We are here:
     * blah blah <tag attribute="value">
     * \---------^
     */
    $pos = tln_skipspace($body, $lt + 1);
    if ($pos >= strlen($body)) {
        return array(false, false, false, $lt, strlen($body));
    }
    /**
     * There are 3 kinds of tags:
     * 1. Opening tag, e.g.:
     *	  <a href="blah">
     * 2. Closing tag, e.g.:
     *	  </a>
     * 3. XHTML-style content-less tag, e.g.:
     *	  <img src="blah"/>
     */
    switch (substr($body, $pos, 1)) {
    case '/':
        $tagtype = 2;
        $pos++;
        break;
    case '!':
        /**
         * A comment or an SGML declaration.
         */
            if (substr($body, $pos + 1, 2) == '--') {
            $gt = strpos($body, '-->', $pos);
            if ($gt === false) {
                $gt = strlen($body);
            } else {
                $gt += 2;
            }
            return array(false, false, false, $lt, $gt);
        } else {
            $gt = tln_findnxstr($body, $pos, '>');
            return array(false, false, false, $lt, $gt);
        }
        break;
    default:
        /**
         * Assume tagtype 1 for now. If it's type 3, we'll switch values
         * later.
         */
        $tagtype = 1;
        break;
    }

    /**
     * Look for next [\W-_], which will indicate the end of the tag name.
     */
    $regary = tln_findnxreg($body, $pos, '[^\w\-_]');
    if ($regary == false) {
        return array(false, false, false, $lt, strlen($body));
    }
    list($pos, $tagname, $match) = $regary;
    $tagname = strtolower($tagname);

    /**
     * $match can be either of these:
     * '>'	indicating the end of the tag entirely.
     * '\s' indicating the end of the tag name.
     * '/'	indicating that this is type-3 xhtml tag.
     *
     * Whatever else we find there indicates an invalid tag.
     */
    switch ($match) {
    case '/':
        /**
         * This is an xhtml-style tag with a closing / at the
         * end, like so: <img src="blah"/>. Check if it's followed
         * by the closing bracket. If not, then this tag is invalid
         */
        if (substr($body, $pos, 2) == '/>') {
            $pos++;
            $tagtype = 3;
        } else {
            $gt = tln_findnxstr($body, $pos, '>');
            $retary = array(false, false, false, $lt, $gt);
            return $retary;
        }
            //intentional fall-through
    case '>':
        return array($tagname, false, $tagtype, $lt, $pos);
        break;
    default:
        /**
         * Check if it's whitespace
         */
        if (!preg_match('/\s/', $match)) {
            /**
             * This is an invalid tag! Look for the next closing ">".
             */
            $gt = tln_findnxstr($body, $lt, '>');
            return array(false, false, false, $lt, $gt);
        }
        break;
    }

    /**
     * At this point we're here:
     * <tagname	 attribute='blah'>
     * \-------^
     *
     * At this point we loop in order to find all attributes.
     */
    $attary = array();

    while ($pos <= strlen($body)) {
        $pos = tln_skipspace($body, $pos);
        if ($pos == strlen($body)) {
            /**
             * Non-closed tag.
             */
            return array(false, false, false, $lt, $pos);
        }
        /**
         * See if we arrived at a ">" or "/>", which means that we reached
         * the end of the tag.
         */
        $matches = array();
        if (preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches)) {
            /**
             * Yep. So we did.
             */
            $pos += strlen($matches[1]);
            if ($matches[2] == '/>') {
                $tagtype = 3;
                $pos++;
            }
            return array($tagname, $attary, $tagtype, $lt, $pos);
        }

        /**
         * There are several types of attributes, with optional
         * [:space:] between members.
         * Type 1:
         *	 attrname[:space:]=[:space:]'CDATA'
         * Type 2:
         *	 attrname[:space:]=[:space:]"CDATA"
         * Type 3:
         *	 attr[:space:]=[:space:]CDATA
         * Type 4:
         *	 attrname
         *
         * We leave types 1 and 2 the same, type 3 we check for
         * '"' and convert to "&quot" if needed, then wrap in
         * double quotes. Type 4 we convert into:
         * attrname="yes".
         */
        $regary = tln_findnxreg($body, $pos, '[^\w\-_]');
        if ($regary == false) {
            /**
             * Looks like body ended before the end of tag.
             */
            return array(false, false, false, $lt, strlen($body));
        }
        list($pos, $attname, $match) = $regary;
        $attname = strtolower($attname);
        /**
         * We arrived at the end of attribute name. Several things possible
         * here:
         * '>'	means the end of the tag and this is attribute type 4
         * '/'	if followed by '>' means the same thing as above
         * '\s' means a lot of things -- look what it's followed by.
         *		anything else means the attribute is invalid.
         */
        switch ($match) {
        case '/':
            /**
             * This is an xhtml-style tag with a closing / at the
             * end, like so: <img src="blah"/>. Check if it's followed
             * by the closing bracket. If not, then this tag is invalid
             */
            if (substr($body, $pos, 2) == '/>') {
                $pos++;
                $tagtype = 3;
            } else {
                $gt = tln_findnxstr($body, $pos, '>');
                $retary = array(false, false, false, $lt, $gt);
                return $retary;
            }
                //intentional fall-through
        case '>':
            $attary{$attname} = '"yes"';
            return array($tagname, $attary, $tagtype, $lt, $pos);
            break;
        default:
            /**
             * Skip whitespace and see what we arrive at.
             */
            $pos = tln_skipspace($body, $pos);
            $char = substr($body, $pos, 1);
            /**
             * Two things are valid here:
             * '=' means this is attribute type 1 2 or 3.
             * \w means this was attribute type 4.
             * anything else we ignore and re-loop. End of tag and
             * invalid stuff will be caught by our checks at the beginning
             * of the loop.
             */
            if ($char == '=') {
                $pos++;
                $pos = tln_skipspace($body, $pos);
                /**
                 * Here are 3 possibilities:
                 * "'"	attribute type 1
                 * '"'	attribute type 2
                 * everything else is the content of tag type 3
                 */
                $quot = substr($body, $pos, 1);
                if ($quot == '\'') {
                        $regary = tln_findnxreg($body, $pos + 1, '\'');
                    if ($regary == false) {
                        return array(false, false, false, $lt, strlen($body));
                    }
                    list($pos, $attval, $match) = $regary;
                    $pos++;
                    $attary{$attname} = '\'' . $attval . '\'';
                } elseif ($quot == '"') {
                    $regary = tln_findnxreg($body, $pos + 1, '\"');
                    if ($regary == false) {
                        return array(false, false, false, $lt, strlen($body));
                    }
                    list($pos, $attval, $match) = $regary;
                    $pos++;
                            $attary{$attname} = '"' . $attval . '"';
                } else {
                    /**
                     * These are hateful. Look for \s, or >.
                     */
                    $regary = tln_findnxreg($body, $pos, '[\s>]');
                    if ($regary == false) {
                        return array(false, false, false, $lt, strlen($body));
                    }
                    list($pos, $attval, $match) = $regary;
                    /**
                     * If it's ">" it will be caught at the top.
                     */
                    $attval = preg_replace('/\"/s', '&quot;', $attval);
                    $attary{$attname} = '"' . $attval . '"';
                }
            } elseif (preg_match('|[\w/>]|', $char)) {
                /**
                 * That was attribute type 4.
                 */
                $attary{$attname} = '"yes"';
            } else {
                /**
                 * An illegal character. Find next '>' and return.
                 */
                $gt = tln_findnxstr($body, $pos, '>');
                return array(false, false, false, $lt, $gt);
            }
            break;
        }
    }
    /**
     * The fact that we got here indicates that the tag end was never
     * found. Return invalid tag indication so it gets stripped.
     */
    return array(false, false, false, $lt, strlen($body));
}

/**
 * Translates entities into literal values so they can be checked.
 *
 * @param string $attvalue the by-ref value to check.
 * @param string $regex    the regular expression to check against.
 * @param boolean $hex        whether the entities are hexadecimal.
 * @return boolean            True or False depending on whether there were matches.
 */
function tln_deent(&$attvalue, $regex, $hex = false)
{
    preg_match_all($regex, $attvalue, $matches);
    if (is_array($matches) && sizeof($matches[0]) > 0) {
        $repl = array();
        for ($i = 0; $i < sizeof($matches[0]); $i++) {
            $numval = $matches[1][$i];
            if ($hex) {
                $numval = hexdec($numval);
            }
            $repl{$matches[0][$i]} = chr($numval);
        }
        $attvalue = strtr($attvalue, $repl);
        return true;
    } else {
        return false;
    }
}

/**
 * This function checks attribute values for entity-encoded values
 * and returns them translated into 8-bit strings so we can run
 * checks on them.
 *
 * @param string $attvalue A string to run entity check against.
 */
function tln_defang(&$attvalue)
{
    /**
     * Skip this if there aren't ampersands or backslashes.
     */
    if (strpos($attvalue, '&') === false
        && strpos($attvalue, '\\') === false
    ) {
        return;
    }
    do {
        $m = false;
        $m = $m || tln_deent($attvalue, '/\&#0*(\d+);*/s');
        $m = $m || tln_deent($attvalue, '/\&#x0*((\d|[a-f])+);*/si', true);
        $m = $m || tln_deent($attvalue, '/\\\\(\d+)/s', true);
    } while ($m == true);
    $attvalue = stripslashes($attvalue);
}

/**
 * Kill any tabs, newlines, or carriage returns. Our friends the
 * makers of the browser with 95% market value decided that it'd
 * be funny to make "java[tab]script" be just as good as "javascript".
 *
 * @param string $attvalue     The attribute value before extraneous spaces removed.
 */
function tln_unspace(&$attvalue)
{
    if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)) {
        $attvalue = str_replace(
            array("\t", "\r", "\n", "\0", " "),
            array('', '', '', '', ''),
            $attvalue
        );
    }
}

/**
 * This function runs various checks against the attributes.
 *
 * @param string $tagname            String with the name of the tag.
 * @param array $attary            Array with all tag attributes.
 * @param array $rm_attnames        See description for tln_sanitize
 * @param array $bad_attvals        See description for tln_sanitize
 * @param array $add_attr_to_tag See description for tln_sanitize
 * @param string $trans_image_path
 * @param boolean $block_external_images
 * @return array with modified attributes.
 */
function tln_fixatts(
    $tagname,
    $attary,
    $rm_attnames,
    $bad_attvals,
    $add_attr_to_tag,
    $trans_image_path,
    $block_external_images
) {
    foreach($attary as $attname => $attvalue) {
        /**
         * See if this attribute should be removed.
         */
        foreach ($rm_attnames as $matchtag => $matchattrs) {
            if (preg_match($matchtag, $tagname)) {
                foreach ($matchattrs as $matchattr) {
                    if (preg_match($matchattr, $attname)) {
                        unset($attary{$attname});
                        continue;
                    }
                }
            }
        }
        /**
         * Remove any backslashes, entities, or extraneous whitespace.
         */
        $oldattvalue = $attvalue;
        tln_defang($attvalue);
        if ($attname == 'style' && $attvalue !== $oldattvalue) {
            $attvalue = "idiocy";
            $attary{$attname} = $attvalue;
        }
        tln_unspace($attvalue);

        /**
         * Now let's run checks on the attvalues.
         * I don't expect anyone to comprehend this. If you do,
         * get in touch with me so I can drive to where you live and
         * shake your hand personally. :)
         */
        foreach ($bad_attvals as $matchtag => $matchattrs) {
            if (preg_match($matchtag, $tagname)) {
                foreach ($matchattrs as $matchattr => $valary) {
                    if (preg_match($matchattr, $attname)) {
                        /**
                         * There are two arrays in valary.
                         * First is matches.
                         * Second one is replacements
                         */
                        list($valmatch, $valrepl) = $valary;
                        $newvalue = preg_replace($valmatch, $valrepl, $attvalue);
                        if ($newvalue != $attvalue) {
                            $attary{$attname} = $newvalue;
                            $attvalue = $newvalue;
                        }
                    }
                }
            }
        }
        if ($attname == 'style') {
            if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) {
                $attary{$attname} = '"disallowed character"';
            }
            preg_match_all("/url\s*\((.+)\)/si", $attvalue, $aMatch);
            if (count($aMatch)) {
                foreach($aMatch[1] as $sMatch) {
                    $urlvalue = $sMatch;
                    tln_fixurl($attname, $urlvalue, $trans_image_path, $block_external_images);
                    $attary{$attname} = str_replace($sMatch, $urlvalue, $attvalue);
                }
            }
        }
     }
    /**
     * See if we need to append any attributes to this tag.
     */
    foreach ($add_attr_to_tag as $matchtag => $addattary) {
        if (preg_match($matchtag, $tagname)) {
            $attary = array_merge($attary, $addattary);
        }
    }
    return $attary;
}

function tln_fixurl($attname, &$attvalue, $trans_image_path, $block_external_images)
{
    $sQuote = '"';
    $attvalue = trim($attvalue);
    if ($attvalue && ($attvalue[0] =='"'|| $attvalue[0] == "'")) {
        // remove the double quotes
        $sQuote = $attvalue[0];
        $attvalue = trim(substr($attvalue,1,-1));
    }

    /**
     * Replace empty src tags with the blank image.  src is only used
     * for frames, images, and image inputs.  Doing a replace should
     * not affect them working as should be, however it will stop
     * IE from being kicked off when src for img tags are not set
     */
    if ($attvalue == '') {
        $attvalue = $sQuote . $trans_image_path . $sQuote;
    } else {
        // first, disallow 8 bit characters and control characters
        if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) {
            switch ($attname) {
                case 'href':
                    $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote;
                    break;
                default:
                    $attvalue = $sQuote . $trans_image_path . $sQuote;
                    break;
            }
        } else {
            $aUrl = parse_url($attvalue);
            if (isset($aUrl['scheme'])) {
                switch(strtolower($aUrl['scheme'])) {
                    case 'mailto':
                    case 'http':
                    case 'https':
                    case 'ftp':
                        if ($attname != 'href') {
                            if ($block_external_images == true) {
                                $attvalue = $sQuote . $trans_image_path . $sQuote;
                            } else {
                                if (!isset($aUrl['path'])) {
                                    $attvalue = $sQuote . $trans_image_path . $sQuote;
                                }
                            }
                        } else {
                            $attvalue = $sQuote . $attvalue . $sQuote;
                        }
                        break;
                    case 'outbind':
                        $attvalue = $sQuote . $attvalue . $sQuote;
                        break;
                    case 'cid':
                        $attvalue = $sQuote . $attvalue . $sQuote;
                        break;
                    default:
                        $attvalue = $sQuote . $trans_image_path . $sQuote;
                        break;
                }
            } else {
                if (!isset($aUrl['path']) || $aUrl['path'] != $trans_image_path) {
                    $$attvalue = $sQuote . $trans_image_path . $sQuote;
                }
            }
        }
    }
}

function tln_fixstyle($body, $pos, $trans_image_path, $block_external_images)
{
    // workaround for </style> in between comments
    $content = '';
    $sToken = '';
    $bSucces = false;
    $bEndTag = false;
    for ($i=$pos,$iCount=strlen($body);$i<$iCount;++$i) {
        $char = $body{$i};
        switch ($char) {
            case '<':
                $sToken = $char;
                break;
            case '/':
                 if ($sToken == '<') {
                    $sToken .= $char;
                    $bEndTag = true;
                 } else {
                    $content .= $char;
                 }
                 break;
            case '>':
                 if ($bEndTag) {
                    $sToken .= $char;
                    if (preg_match('/\<\/\s*style\s*\>/i',$sToken,$aMatch)) {
                        $newpos = $i + 1;
                        $bSucces = true;
                        break 2;
                    } else {
                        $content .= $sToken;
                    }
                    $bEndTag = false;
                 } else {
                    $content .= $char;
                 }
                 break;
            case '!':
                if ($sToken == '<') {
                    // possible comment
                    if (isset($body{$i+2}) && substr($body,$i,3) == '!--') {
                        $i = strpos($body,'-->',$i+3);
                        if ($i === false) { // no end comment
                            $i = strlen($body);
                        }
                        $sToken = '';
                    }
                } else {
                    $content .= $char;
                }
                break;
            default:
                if ($bEndTag) {
                    $sToken .= $char;
                } else {
                    $content .= $char;
                }
                break;
        }
    }
    if ($bSucces == FALSE){
        return array(FALSE, strlen($body));
    }



    /**
     * First look for general BODY style declaration, which would be
     * like so:
     * body {background: blah-blah}
     * and change it to .bodyclass so we can just assign it to a <div>
     */
    $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content);

    /**
    * Fix url('blah') declarations.
    */
    //   $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si",
    //                           "url(\\1$trans_image_path\\2)", $content);

    // first check for 8bit sequences and disallowed control characters
    if (preg_match('/[\16-\37\200-\377]+/',$content)) {
        $content = '<!-- style block removed by html filter due to presence of 8bit characters -->';
        return array($content, $newpos);
    }

    // remove @import line
    $content = preg_replace("/^\s*(@import.*)$/mi","\n<!-- @import rules forbidden -->\n",$content);

    $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content);
    preg_match_all("/url\s*\((.+)\)/si",$content,$aMatch);
    if (count($aMatch)) {
        $aValue = $aReplace = array();
        foreach($aMatch[1] as $sMatch) {
            // url value
            $urlvalue = $sMatch;
            tln_fixurl('style',$urlvalue, $trans_image_path, $block_external_images);
            $aValue[] = $sMatch;
            $aReplace[] = $urlvalue;
        }
        $content = str_replace($aValue,$aReplace,$content);
    }

    /**
     * Remove any backslashes, entities, and extraneous whitespace.
     */
    $contentTemp = $content;
    tln_defang($contentTemp);
    tln_unspace($contentTemp);

    $match   = array('/\/\*.*\*\//',
                    '/expression/i',
                    '/behaviou*r/i',
                    '/binding/i',
                    '/include-source/i',
                    '/javascript/i',
                    '/script/i',
                    '/position/i');
    $replace = array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', '');
    $contentNew = preg_replace($match, $replace, $contentTemp);
    if ($contentNew !== $contentTemp) {
        $content = $contentNew;
    }
    return array($content, $newpos);
}

function tln_body2div($attary, $trans_image_path)
{
    $divattary = array('class' => "'bodyclass'");
    $text = '#000000';
    $has_bgc_stl = $has_txt_stl = false;
    $styledef = '';
    if (is_array($attary) && sizeof($attary) > 0){
        foreach ($attary as $attname=>$attvalue){
            $quotchar = substr($attvalue, 0, 1);
            $attvalue = str_replace($quotchar, "", $attvalue);
            switch ($attname){
                case 'background':
                    $styledef .= "background-image: url('$trans_image_path'); ";
                    break;
                case 'bgcolor':
                    $has_bgc_stl = true;
                    $styledef .= "background-color: $attvalue; ";
                    break;
                case 'text':
                    $has_txt_stl = true;
                    $styledef .= "color: $attvalue; ";
                    break;
            }
        }
        // Outlook defines a white bgcolor and no text color. This can lead to
        // white text on a white bg with certain themes.
        if ($has_bgc_stl && !$has_txt_stl) {
            $styledef .= "color: $text; ";
        }
        if (strlen($styledef) > 0){
            $divattary{"style"} = "\"$styledef\"";
        }
    }
    return $divattary;
}

/**
 *
 * @param string $body                    The HTML you wish to filter
 * @param array $tag_list                see description above
 * @param array $rm_tags_with_content see description above
 * @param array $self_closing_tags    see description above
 * @param boolean $force_tag_closing    see description above
 * @param array $rm_attnames            see description above
 * @param array $bad_attvals            see description above
 * @param array $add_attr_to_tag        see description above
 * @param string $trans_image_path
 * @param boolean $block_external_images

 * @return string                       Sanitized html safe to show on your pages.
 */
function tln_sanitize(
    $body,
    $tag_list,
    $rm_tags_with_content,
    $self_closing_tags,
    $force_tag_closing,
    $rm_attnames,
    $bad_attvals,
    $add_attr_to_tag,
    $trans_image_path,
    $block_external_images
) {
    /**
     * Normalize rm_tags and rm_tags_with_content.
     */
    $rm_tags = array_shift($tag_list);
    @array_walk($tag_list, 'tln_casenormalize');
    @array_walk($rm_tags_with_content, 'tln_casenormalize');
    @array_walk($self_closing_tags, 'tln_casenormalize');
    /**
     * See if tag_list is of tags to remove or tags to allow.
     * false  means remove these tags
     * true	  means allow these tags
     */
    $curpos = 0;
    $open_tags = array();
    $trusted = "<!-- begin tln_sanitized html -->\n";
    $skip_content = false;
    /**
     * Take care of netscape's stupid javascript entities like
     * &{alert('boo')};
     */
    $body = preg_replace('/&(\{.*?\};)/si', '&amp;\\1', $body);
    while (($curtag = tln_getnxtag($body, $curpos)) != false) {
        list($tagname, $attary, $tagtype, $lt, $gt) = $curtag;
        $free_content = substr($body, $curpos, $lt-$curpos);
        /**
         * Take care of <style>
         */
        if ($tagname == "style" && $tagtype == 1){
            list($free_content, $curpos) =
                tln_fixstyle($body, $gt+1, $trans_image_path, $block_external_images);
            if ($free_content != FALSE){
                if ( !empty($attary) ) {
                    $attary = tln_fixatts($tagname,
                                         $attary,
                                         $rm_attnames,
                                         $bad_attvals,
                                         $add_attr_to_tag,
                                         $trans_image_path,
                                         $block_external_images
                                         );
                }
                $trusted .= tln_tagprint($tagname, $attary, $tagtype);
                $trusted .= $free_content;
                $trusted .= tln_tagprint($tagname, null, 2);
            }
            continue;
        }
        if ($skip_content == false){
            $trusted .= $free_content;
        }
        if ($tagname != false) {
            if ($tagtype == 2) {
                if ($skip_content == $tagname) {
                    /**
                     * Got to the end of tag we needed to remove.
                     */
                    $tagname = false;
                    $skip_content = false;
                } else {
                    if ($skip_content == false) {
                        if ($tagname == "body") {
                            $tagname = "div";
                        }
                        if (isset($open_tags{$tagname}) &&
                            $open_tags{$tagname} > 0
                        ) {
                            $open_tags{$tagname}--;
                        } else {
                            $tagname = false;
                        }
                    }
                }
            } else {
                /**
                 * $rm_tags_with_content
                 */
                if ($skip_content == false) {
                    /**
                     * See if this is a self-closing type and change
                     * tagtype appropriately.
                     */
                    if ($tagtype == 1
                        && in_array($tagname, $self_closing_tags)
                    ) {
                        $tagtype = 3;
                    }
                    /**
                     * See if we should skip this tag and any content
                     * inside it.
                     */
                    if ($tagtype == 1
                        && in_array($tagname, $rm_tags_with_content)
                    ) {
                        $skip_content = $tagname;
                    } else {
                        if (($rm_tags == false
                             && in_array($tagname, $tag_list)) ||
                            ($rm_tags == true
                                && !in_array($tagname, $tag_list))
                        ) {
                            $tagname = false;
                        } else {
                            /**
                             * Convert body into div.
                             */
                            if ($tagname == "body"){
                                $tagname = "div";
                                $attary = tln_body2div($attary, $trans_image_path);
                            }
                            if ($tagtype == 1) {
                                if (isset($open_tags{$tagname})) {
                                    $open_tags{$tagname}++;
                                } else {
                                    $open_tags{$tagname} = 1;
                                }
                            }
                            /**
                             * This is where we run other checks.
                             */
                            if (is_array($attary) && sizeof($attary) > 0) {
                                $attary = tln_fixatts(
                                    $tagname,
                                    $attary,
                                    $rm_attnames,
                                    $bad_attvals,
                                    $add_attr_to_tag,
                                    $trans_image_path,
                                    $block_external_images
                                );
                            }
                        }
                    }
                }
            }
            if ($tagname != false && $skip_content == false) {
                $trusted .= tln_tagprint($tagname, $attary, $tagtype);
            }
        }
        $curpos = $gt + 1;
    }
    $trusted .= substr($body, $curpos, strlen($body) - $curpos);
    if ($force_tag_closing == true) {
        foreach ($open_tags as $tagname => $opentimes) {
            while ($opentimes > 0) {
                $trusted .= '</' . $tagname . '>';
                $opentimes--;
            }
        }
        $trusted .= "\n";
    }
    $trusted .= "<!-- end tln_sanitized html -->\n";
    return $trusted;
}

//
// Use the nifty htmlfilter library
//


function HTMLFilter($body, $trans_image_path, $block_external_images = false)
{

    $tag_list = array(
        false,
        "object",
        "meta",
        "html",
        "head",
        "base",
        "link",
        "frame",
        "iframe",
        "plaintext",
        "marquee"
    );

    $rm_tags_with_content = array(
        "script",
        "applet",
        "embed",
        "title",
        "frameset",
        "xmp",
        "xml"
    );

    $self_closing_tags =  array(
        "img",
        "br",
        "hr",
        "input",
        "outbind"
    );

    $force_tag_closing = true;

    $rm_attnames = array(
        "/.*/" =>
            array(
                // "/target/i",
                "/^on.*/i",
                "/^dynsrc/i",
                "/^data.*/i",
                "/^lowsrc.*/i"
            )
    );

    $bad_attvals = array(
        "/.*/" =>
        array(
            "/^src|background/i" =>
            array(
                array(
                    '/^([\'"])\s*\S+script\s*:.*([\'"])/si',
                    '/^([\'"])\s*mocha\s*:*.*([\'"])/si',
                    '/^([\'"])\s*about\s*:.*([\'"])/si'
                ),
                array(
                    "\\1$trans_image_path\\2",
                    "\\1$trans_image_path\\2",
                    "\\1$trans_image_path\\2"
                )
            ),
            "/^href|action/i" =>
            array(
                array(
                    '/^([\'"])\s*\S+script\s*:.*([\'"])/si',
                    '/^([\'"])\s*mocha\s*:*.*([\'"])/si',
                    '/^([\'"])\s*about\s*:.*([\'"])/si'
                ),
                array(
                    "\\1#\\1",
                    "\\1#\\1",
                    "\\1#\\1"
                )
            ),
            "/^style/i" =>
            array(
                array(
                    "/\/\*.*\*\//",
                    "/expression/i",
                    "/binding/i",
                    "/behaviou*r/i",
                    "/include-source/i",
                    '/position\s*:/i',
                    '/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i',
                    '/url\s*\(\s*([\'"])\s*\S+script\s*:.*([\'"])\s*\)/si',
                    '/url\s*\(\s*([\'"])\s*mocha\s*:.*([\'"])\s*\)/si',
                    '/url\s*\(\s*([\'"])\s*about\s*:.*([\'"])\s*\)/si',
                    '/(.*)\s*:\s*url\s*\(\s*([\'"]*)\s*\S+script\s*:.*([\'"]*)\s*\)/si'
                ),
                array(
                    "",
                    "idiocy",
                    "idiocy",
                    "idiocy",
                    "idiocy",
                    "idiocy",
                    "url",
                    "url(\\1#\\1)",
                    "url(\\1#\\1)",
                    "url(\\1#\\1)",
                    "\\1:url(\\2#\\3)"
                )
            )
        )
    );

    if ($block_external_images) {
        array_push(
            $bad_attvals{'/.*/'}{'/^src|background/i'}[0],
            '/^([\'\"])\s*https*:.*([\'\"])/si'
        );
        array_push(
            $bad_attvals{'/.*/'}{'/^src|background/i'}[1],
            "\\1$trans_image_path\\1"
        );
        array_push(
            $bad_attvals{'/.*/'}{'/^style/i'}[0],
            '/url\(([\'\"])\s*https*:.*([\'\"])\)/si'
        );
        array_push(
            $bad_attvals{'/.*/'}{'/^style/i'}[1],
            "url(\\1$trans_image_path\\1)"
        );
    }

    $add_attr_to_tag = array(
        "/^a$/i" =>
            array('target' => '"_blank"')
    );

    $trusted = tln_sanitize(
        $body,
        $tag_list,
        $rm_tags_with_content,
        $self_closing_tags,
        $force_tag_closing,
        $rm_attnames,
        $bad_attvals,
        $add_attr_to_tag,
        $trans_image_path,
        $block_external_images
    );
    return $trusted;
}
vendor/phpmailer/phpmailer/extras/EasyPeasyICS.php000064400000007736152177723700016314 0ustar00<?php
/**
 * EasyPeasyICS Simple ICS/vCal data generator.
 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
 * @author Manuel Reinhard <manu@sprain.ch>
 *
 * Built with inspiration from
 * http://stackoverflow.com/questions/1463480/how-can-i-use-php-to-dynamically-publish-an-ical-file-to-be-read-by-google-calend/1464355#1464355
 * History:
 * 2010/12/17 - Manuel Reinhard - when it all started
 * 2014 PHPMailer project becomes maintainer
 */

/**
 * Class EasyPeasyICS.
 * Simple ICS data generator
 * @package phpmailer
 * @subpackage easypeasyics
 */
class EasyPeasyICS
{
    /**
     * The name of the calendar
     * @var string
     */
    protected $calendarName;
    /**
     * The array of events to add to this calendar
     * @var array
     */
    protected $events = array();

    /**
     * Constructor
     * @param string $calendarName
     */
    public function __construct($calendarName = "")
    {
        $this->calendarName = $calendarName;
    }

    /**
     * Add an event to this calendar.
     * @param string $start The start date and time as a unix timestamp
     * @param string $end The end date and time as a unix timestamp
     * @param string $summary A summary or title for the event
     * @param string $description A description of the event
     * @param string $url A URL for the event
     * @param string $uid A unique identifier for the event - generated automatically if not provided
     * @return array An array of event details, including any generated UID
     */
    public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '')
    {
        if (empty($uid)) {
            $uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS';
        }
        $event = array(
            'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z',
            'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z',
            'summary' => $summary,
            'description' => $description,
            'url' => $url,
            'uid' => $uid
        );
        $this->events[] = $event;
        return $event;
    }

    /**
     * @return array Get the array of events.
     */
    public function getEvents()
    {
        return $this->events;
    }

    /**
     * Clear all events.
     */
    public function clearEvents()
    {
        $this->events = array();
    }

    /**
     * Get the name of the calendar.
     * @return string
     */
    public function getName()
    {
        return $this->calendarName;
    }

    /**
     * Set the name of the calendar.
     * @param $name
     */
    public function setName($name)
    {
        $this->calendarName = $name;
    }

    /**
     * Render and optionally output a vcal string.
     * @param bool $output Whether to output the calendar data directly (the default).
     * @return string The complete rendered vlal
     */
    public function render($output = true)
    {
        //Add header
        $ics = 'BEGIN:VCALENDAR
METHOD:PUBLISH
VERSION:2.0
X-WR-CALNAME:' . $this->calendarName . '
PRODID:-//hacksw/handcal//NONSGML v1.0//EN';

        //Add events
        foreach ($this->events as $event) {
            $ics .= '
BEGIN:VEVENT
UID:' . $event['uid'] . '
DTSTAMP:' . gmdate('Ymd') . 'T' . gmdate('His') . 'Z
DTSTART:' . $event['start'] . '
DTEND:' . $event['end'] . '
SUMMARY:' . str_replace("\n", "\\n", $event['summary']) . '
DESCRIPTION:' . str_replace("\n", "\\n", $event['description']) . '
URL;VALUE=URI:' . $event['url'] . '
END:VEVENT';
        }

        //Add footer
        $ics .= '
END:VCALENDAR';

        if ($output) {
            //Output
            $filename = $this->calendarName;
            //Filename needs quoting if it contains spaces
            if (strpos($filename, ' ') !== false) {
                $filename = '"'.$filename.'"';
            }
            header('Content-type: text/calendar; charset=utf-8');
            header('Content-Disposition: inline; filename=' . $filename . '.ics');
            echo $ics;
        }
        return $ics;
    }
}
vendor/phpmailer/phpmailer/extras/ntlm_sasl_client.php000064400000014773152177723700017403 0ustar00<?php
/*
 * ntlm_sasl_client.php
 *
 * @(#) $Id: ntlm_sasl_client.php,v 1.3 2004/11/17 08:00:37 mlemos Exp $
 *
 */

define("SASL_NTLM_STATE_START", 0);
define("SASL_NTLM_STATE_IDENTIFY_DOMAIN", 1);
define("SASL_NTLM_STATE_RESPOND_CHALLENGE", 2);
define("SASL_NTLM_STATE_DONE", 3);
define("SASL_FAIL", -1);
define("SASL_CONTINUE", 1);

class ntlm_sasl_client_class
{
    public $credentials = array();
    public $state = SASL_NTLM_STATE_START;

    public function initialize(&$client)
    {
        if (!function_exists($function = "mcrypt_encrypt")
            || !function_exists($function = "mhash")
        ) {
            $extensions = array(
                "mcrypt_encrypt" => "mcrypt",
                "mhash" => "mhash"
            );
            $client->error = "the extension " . $extensions[$function] .
                " required by the NTLM SASL client class is not available in this PHP configuration";
            return (0);
        }
        return (1);
    }

    public function ASCIIToUnicode($ascii)
    {
        for ($unicode = "", $a = 0; $a < strlen($ascii); $a++) {
            $unicode .= substr($ascii, $a, 1) . chr(0);
        }
        return ($unicode);
    }

    public function typeMsg1($domain, $workstation)
    {
        $domain_length = strlen($domain);
        $workstation_length = strlen($workstation);
        $workstation_offset = 32;
        $domain_offset = $workstation_offset + $workstation_length;
        return (
            "NTLMSSP\0" .
            "\x01\x00\x00\x00" .
            "\x07\x32\x00\x00" .
            pack("v", $domain_length) .
            pack("v", $domain_length) .
            pack("V", $domain_offset) .
            pack("v", $workstation_length) .
            pack("v", $workstation_length) .
            pack("V", $workstation_offset) .
            $workstation .
            $domain
        );
    }

    public function NTLMResponse($challenge, $password)
    {
        $unicode = $this->ASCIIToUnicode($password);
        $md4 = mhash(MHASH_MD4, $unicode);
        $padded = $md4 . str_repeat(chr(0), 21 - strlen($md4));
        $iv_size = mcrypt_get_iv_size(MCRYPT_DES, MCRYPT_MODE_ECB);
        $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
        for ($response = "", $third = 0; $third < 21; $third += 7) {
            for ($packed = "", $p = $third; $p < $third + 7; $p++) {
                $packed .= str_pad(decbin(ord(substr($padded, $p, 1))), 8, "0", STR_PAD_LEFT);
            }
            for ($key = "", $p = 0; $p < strlen($packed); $p += 7) {
                $s = substr($packed, $p, 7);
                $b = $s . ((substr_count($s, "1") % 2) ? "0" : "1");
                $key .= chr(bindec($b));
            }
            $ciphertext = mcrypt_encrypt(MCRYPT_DES, $key, $challenge, MCRYPT_MODE_ECB, $iv);
            $response .= $ciphertext;
        }
        return $response;
    }

    public function typeMsg3($ntlm_response, $user, $domain, $workstation)
    {
        $domain_unicode = $this->ASCIIToUnicode($domain);
        $domain_length = strlen($domain_unicode);
        $domain_offset = 64;
        $user_unicode = $this->ASCIIToUnicode($user);
        $user_length = strlen($user_unicode);
        $user_offset = $domain_offset + $domain_length;
        $workstation_unicode = $this->ASCIIToUnicode($workstation);
        $workstation_length = strlen($workstation_unicode);
        $workstation_offset = $user_offset + $user_length;
        $lm = "";
        $lm_length = strlen($lm);
        $lm_offset = $workstation_offset + $workstation_length;
        $ntlm = $ntlm_response;
        $ntlm_length = strlen($ntlm);
        $ntlm_offset = $lm_offset + $lm_length;
        $session = "";
        $session_length = strlen($session);
        $session_offset = $ntlm_offset + $ntlm_length;
        return (
            "NTLMSSP\0" .
            "\x03\x00\x00\x00" .
            pack("v", $lm_length) .
            pack("v", $lm_length) .
            pack("V", $lm_offset) .
            pack("v", $ntlm_length) .
            pack("v", $ntlm_length) .
            pack("V", $ntlm_offset) .
            pack("v", $domain_length) .
            pack("v", $domain_length) .
            pack("V", $domain_offset) .
            pack("v", $user_length) .
            pack("v", $user_length) .
            pack("V", $user_offset) .
            pack("v", $workstation_length) .
            pack("v", $workstation_length) .
            pack("V", $workstation_offset) .
            pack("v", $session_length) .
            pack("v", $session_length) .
            pack("V", $session_offset) .
            "\x01\x02\x00\x00" .
            $domain_unicode .
            $user_unicode .
            $workstation_unicode .
            $lm .
            $ntlm
        );
    }

    public function start(&$client, &$message, &$interactions)
    {
        if ($this->state != SASL_NTLM_STATE_START) {
            $client->error = "NTLM authentication state is not at the start";
            return (SASL_FAIL);
        }
        $this->credentials = array(
            "user" => "",
            "password" => "",
            "realm" => "",
            "workstation" => ""
        );
        $defaults = array();
        $status = $client->GetCredentials($this->credentials, $defaults, $interactions);
        if ($status == SASL_CONTINUE) {
            $this->state = SASL_NTLM_STATE_IDENTIFY_DOMAIN;
        }
        unset($message);
        return ($status);
    }

    public function step(&$client, $response, &$message, &$interactions)
    {
        switch ($this->state) {
            case SASL_NTLM_STATE_IDENTIFY_DOMAIN:
                $message = $this->typeMsg1($this->credentials["realm"], $this->credentials["workstation"]);
                $this->state = SASL_NTLM_STATE_RESPOND_CHALLENGE;
                break;
            case SASL_NTLM_STATE_RESPOND_CHALLENGE:
                $ntlm_response = $this->NTLMResponse(substr($response, 24, 8), $this->credentials["password"]);
                $message = $this->typeMsg3(
                    $ntlm_response,
                    $this->credentials["user"],
                    $this->credentials["realm"],
                    $this->credentials["workstation"]
                );
                $this->state = SASL_NTLM_STATE_DONE;
                break;
            case SASL_NTLM_STATE_DONE:
                $client->error = "NTLM authentication was finished without success";
                return (SASL_FAIL);
            default:
                $client->error = "invalid NTLM authentication step state";
                return (SASL_FAIL);
        }
        return (SASL_CONTINUE);
    }
}
vendor/phpmailer/phpmailer/class.phpmailer.php000064400000440661152177723700015627 0ustar00<?php
/**
 * PHPMailer - PHP email creation and transport class.
 * PHP Version 5
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailer - PHP email creation and transport class.
 * @package PHPMailer
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 */
class PHPMailer
{
    /**
     * The PHPMailer Version number.
     * @var string
     */
    public $Version = '5.2.27';

    /**
     * Email priority.
     * Options: null (default), 1 = High, 3 = Normal, 5 = low.
     * When null, the header is not set at all.
     * @var integer
     */
    public $Priority = null;

    /**
     * The character set of the message.
     * @var string
     */
    public $CharSet = 'iso-8859-1';

    /**
     * The MIME Content-type of the message.
     * @var string
     */
    public $ContentType = 'text/plain';

    /**
     * The message encoding.
     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
     * @var string
     */
    public $Encoding = '8bit';

    /**
     * Holds the most recent mailer error message.
     * @var string
     */
    public $ErrorInfo = '';

    /**
     * The From email address for the message.
     * @var string
     */
    public $From = 'root@localhost';

    /**
     * The From name of the message.
     * @var string
     */
    public $FromName = 'Root User';

    /**
     * The Sender email (Return-Path) of the message.
     * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
     * @var string
     */
    public $Sender = '';

    /**
     * The Return-Path of the message.
     * If empty, it will be set to either From or Sender.
     * @var string
     * @deprecated Email senders should never set a return-path header;
     * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
     * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
     */
    public $ReturnPath = '';

    /**
     * The Subject of the message.
     * @var string
     */
    public $Subject = '';

    /**
     * An HTML or plain text message body.
     * If HTML then call isHTML(true).
     * @var string
     */
    public $Body = '';

    /**
     * The plain-text message body.
     * This body can be read by mail clients that do not have HTML email
     * capability such as mutt & Eudora.
     * Clients that can read HTML will view the normal Body.
     * @var string
     */
    public $AltBody = '';

    /**
     * An iCal message part body.
     * Only supported in simple alt or alt_inline message types
     * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
     * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
     * @link http://kigkonsult.se/iCalcreator/
     * @var string
     */
    public $Ical = '';

    /**
     * The complete compiled MIME message body.
     * @access protected
     * @var string
     */
    protected $MIMEBody = '';

    /**
     * The complete compiled MIME message headers.
     * @var string
     * @access protected
     */
    protected $MIMEHeader = '';

    /**
     * Extra headers that createHeader() doesn't fold in.
     * @var string
     * @access protected
     */
    protected $mailHeader = '';

    /**
     * Word-wrap the message body to this number of chars.
     * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
     * @var integer
     */
    public $WordWrap = 0;

    /**
     * Which method to use to send mail.
     * Options: "mail", "sendmail", or "smtp".
     * @var string
     */
    public $Mailer = 'mail';

    /**
     * The path to the sendmail program.
     * @var string
     */
    public $Sendmail = '/usr/sbin/sendmail';

    /**
     * Whether mail() uses a fully sendmail-compatible MTA.
     * One which supports sendmail's "-oi -f" options.
     * @var boolean
     */
    public $UseSendmailOptions = true;

    /**
     * Path to PHPMailer plugins.
     * Useful if the SMTP class is not in the PHP include path.
     * @var string
     * @deprecated Should not be needed now there is an autoloader.
     */
    public $PluginDir = '';

    /**
     * The email address that a reading confirmation should be sent to, also known as read receipt.
     * @var string
     */
    public $ConfirmReadingTo = '';

    /**
     * The hostname to use in the Message-ID header and as default HELO string.
     * If empty, PHPMailer attempts to find one with, in order,
     * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
     * 'localhost.localdomain'.
     * @var string
     */
    public $Hostname = '';

    /**
     * An ID to be used in the Message-ID header.
     * If empty, a unique id will be generated.
     * You can set your own, but it must be in the format "<id@domain>",
     * as defined in RFC5322 section 3.6.4 or it will be ignored.
     * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
     * @var string
     */
    public $MessageID = '';

    /**
     * The message Date to be used in the Date header.
     * If empty, the current date will be added.
     * @var string
     */
    public $MessageDate = '';

    /**
     * SMTP hosts.
     * Either a single hostname or multiple semicolon-delimited hostnames.
     * You can also specify a different port
     * for each host by using this format: [hostname:port]
     * (e.g. "smtp1.example.com:25;smtp2.example.com").
     * You can also specify encryption type, for example:
     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
     * Hosts will be tried in order.
     * @var string
     */
    public $Host = 'localhost';

    /**
     * The default SMTP server port.
     * @var integer
     * @TODO Why is this needed when the SMTP class takes care of it?
     */
    public $Port = 25;

    /**
     * The SMTP HELO of the message.
     * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
     * one with the same method described above for $Hostname.
     * @var string
     * @see PHPMailer::$Hostname
     */
    public $Helo = '';

    /**
     * What kind of encryption to use on the SMTP connection.
     * Options: '', 'ssl' or 'tls'
     * @var string
     */
    public $SMTPSecure = '';

    /**
     * Whether to enable TLS encryption automatically if a server supports it,
     * even if `SMTPSecure` is not set to 'tls'.
     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
     * @var boolean
     */
    public $SMTPAutoTLS = true;

    /**
     * Whether to use SMTP authentication.
     * Uses the Username and Password properties.
     * @var boolean
     * @see PHPMailer::$Username
     * @see PHPMailer::$Password
     */
    public $SMTPAuth = false;

    /**
     * Options array passed to stream_context_create when connecting via SMTP.
     * @var array
     */
    public $SMTPOptions = array();

    /**
     * SMTP username.
     * @var string
     */
    public $Username = '';

    /**
     * SMTP password.
     * @var string
     */
    public $Password = '';

    /**
     * SMTP auth type.
     * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
     * @var string
     */
    public $AuthType = '';

    /**
     * SMTP realm.
     * Used for NTLM auth
     * @var string
     */
    public $Realm = '';

    /**
     * SMTP workstation.
     * Used for NTLM auth
     * @var string
     */
    public $Workstation = '';

    /**
     * The SMTP server timeout in seconds.
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
     * @var integer
     */
    public $Timeout = 300;

    /**
     * SMTP class debug output mode.
     * Debug output level.
     * Options:
     * * `0` No output
     * * `1` Commands
     * * `2` Data and commands
     * * `3` As 2 plus connection status
     * * `4` Low-level data output
     * @var integer
     * @see SMTP::$do_debug
     */
    public $SMTPDebug = 0;

    /**
     * How to handle debug output.
     * Options:
     * * `echo` Output plain-text as-is, appropriate for CLI
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
     * * `error_log` Output to error log as configured in php.ini
     *
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
     * <code>
     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
     * </code>
     * @var string|callable
     * @see SMTP::$Debugoutput
     */
    public $Debugoutput = 'echo';

    /**
     * Whether to keep SMTP connection open after each message.
     * If this is set to true then to close the connection
     * requires an explicit call to smtpClose().
     * @var boolean
     */
    public $SMTPKeepAlive = false;

    /**
     * Whether to split multiple to addresses into multiple messages
     * or send them all in one message.
     * Only supported in `mail` and `sendmail` transports, not in SMTP.
     * @var boolean
     */
    public $SingleTo = false;

    /**
     * Storage for addresses when SingleTo is enabled.
     * @var array
     * @TODO This should really not be public
     */
    public $SingleToArray = array();

    /**
     * Whether to generate VERP addresses on send.
     * Only applicable when sending via SMTP.
     * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
     * @link http://www.postfix.org/VERP_README.html Postfix VERP info
     * @var boolean
     */
    public $do_verp = false;

    /**
     * Whether to allow sending messages with an empty body.
     * @var boolean
     */
    public $AllowEmpty = false;

    /**
     * The default line ending.
     * @note The default remains "\n". We force CRLF where we know
     *        it must be used via self::CRLF.
     * @var string
     */
    public $LE = "\n";

    /**
     * DKIM selector.
     * @var string
     */
    public $DKIM_selector = '';

    /**
     * DKIM Identity.
     * Usually the email address used as the source of the email.
     * @var string
     */
    public $DKIM_identity = '';

    /**
     * DKIM passphrase.
     * Used if your key is encrypted.
     * @var string
     */
    public $DKIM_passphrase = '';

    /**
     * DKIM signing domain name.
     * @example 'example.com'
     * @var string
     */
    public $DKIM_domain = '';

    /**
     * DKIM private key file path.
     * @var string
     */
    public $DKIM_private = '';

    /**
     * DKIM private key string.
     * If set, takes precedence over `$DKIM_private`.
     * @var string
     */
    public $DKIM_private_string = '';

    /**
     * Callback Action function name.
     *
     * The function that handles the result of the send email action.
     * It is called out by send() for each email sent.
     *
     * Value can be any php callable: http://www.php.net/is_callable
     *
     * Parameters:
     *   boolean $result        result of the send action
     *   array   $to            email addresses of the recipients
     *   array   $cc            cc email addresses
     *   array   $bcc           bcc email addresses
     *   string  $subject       the subject
     *   string  $body          the email body
     *   string  $from          email address of sender
     * @var string
     */
    public $action_function = '';

    /**
     * What to put in the X-Mailer header.
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
     * @var string
     */
    public $XMailer = '';

    /**
     * Which validator to use by default when validating email addresses.
     * May be a callable to inject your own validator, but there are several built-in validators.
     * @see PHPMailer::validateAddress()
     * @var string|callable
     * @static
     */
    public static $validator = 'auto';

    /**
     * An instance of the SMTP sender class.
     * @var SMTP
     * @access protected
     */
    protected $smtp = null;

    /**
     * The array of 'to' names and addresses.
     * @var array
     * @access protected
     */
    protected $to = array();

    /**
     * The array of 'cc' names and addresses.
     * @var array
     * @access protected
     */
    protected $cc = array();

    /**
     * The array of 'bcc' names and addresses.
     * @var array
     * @access protected
     */
    protected $bcc = array();

    /**
     * The array of reply-to names and addresses.
     * @var array
     * @access protected
     */
    protected $ReplyTo = array();

    /**
     * An array of all kinds of addresses.
     * Includes all of $to, $cc, $bcc
     * @var array
     * @access protected
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
     */
    protected $all_recipients = array();

    /**
     * An array of names and addresses queued for validation.
     * In send(), valid and non duplicate entries are moved to $all_recipients
     * and one of $to, $cc, or $bcc.
     * This array is used only for addresses with IDN.
     * @var array
     * @access protected
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
     * @see PHPMailer::$all_recipients
     */
    protected $RecipientsQueue = array();

    /**
     * An array of reply-to names and addresses queued for validation.
     * In send(), valid and non duplicate entries are moved to $ReplyTo.
     * This array is used only for addresses with IDN.
     * @var array
     * @access protected
     * @see PHPMailer::$ReplyTo
     */
    protected $ReplyToQueue = array();

    /**
     * The array of attachments.
     * @var array
     * @access protected
     */
    protected $attachment = array();

    /**
     * The array of custom headers.
     * @var array
     * @access protected
     */
    protected $CustomHeader = array();

    /**
     * The most recent Message-ID (including angular brackets).
     * @var string
     * @access protected
     */
    protected $lastMessageID = '';

    /**
     * The message's MIME type.
     * @var string
     * @access protected
     */
    protected $message_type = '';

    /**
     * The array of MIME boundary strings.
     * @var array
     * @access protected
     */
    protected $boundary = array();

    /**
     * The array of available languages.
     * @var array
     * @access protected
     */
    protected $language = array();

    /**
     * The number of errors encountered.
     * @var integer
     * @access protected
     */
    protected $error_count = 0;

    /**
     * The S/MIME certificate file path.
     * @var string
     * @access protected
     */
    protected $sign_cert_file = '';

    /**
     * The S/MIME key file path.
     * @var string
     * @access protected
     */
    protected $sign_key_file = '';

    /**
     * The optional S/MIME extra certificates ("CA Chain") file path.
     * @var string
     * @access protected
     */
    protected $sign_extracerts_file = '';

    /**
     * The S/MIME password for the key.
     * Used only if the key is encrypted.
     * @var string
     * @access protected
     */
    protected $sign_key_pass = '';

    /**
     * Whether to throw exceptions for errors.
     * @var boolean
     * @access protected
     */
    protected $exceptions = false;

    /**
     * Unique ID used for message ID and boundaries.
     * @var string
     * @access protected
     */
    protected $uniqueid = '';

    /**
     * Error severity: message only, continue processing.
     */
    const STOP_MESSAGE = 0;

    /**
     * Error severity: message, likely ok to continue processing.
     */
    const STOP_CONTINUE = 1;

    /**
     * Error severity: message, plus full stop, critical error reached.
     */
    const STOP_CRITICAL = 2;

    /**
     * SMTP RFC standard line ending.
     */
    const CRLF = "\r\n";

    /**
     * The maximum line length allowed by RFC 2822 section 2.1.1
     * @var integer
     */
    const MAX_LINE_LENGTH = 998;

    /**
     * Constructor.
     * @param boolean $exceptions Should we throw external exceptions?
     */
    public function __construct($exceptions = null)
    {
        if ($exceptions !== null) {
            $this->exceptions = (boolean)$exceptions;
        }
        //Pick an appropriate debug output format automatically
        $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
    }

    /**
     * Destructor.
     */
    public function __destruct()
    {
        //Close any open SMTP connection nicely
        $this->smtpClose();
    }

    /**
     * Call mail() in a safe_mode-aware fashion.
     * Also, unless sendmail_path points to sendmail (or something that
     * claims to be sendmail), don't pass params (not a perfect fix,
     * but it will do)
     * @param string $to To
     * @param string $subject Subject
     * @param string $body Message Body
     * @param string $header Additional Header(s)
     * @param string $params Params
     * @access private
     * @return boolean
     */
    private function mailPassthru($to, $subject, $body, $header, $params)
    {
        //Check overloading of mail function to avoid double-encoding
        if (ini_get('mbstring.func_overload') & 1) {
            $subject = $this->secureHeader($subject);
        } else {
            $subject = $this->encodeHeader($this->secureHeader($subject));
        }

        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
        //@link http://php.net/manual/en/function.mail.php
        if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
            $result = @mail($to, $subject, $body, $header);
        } else {
            $result = @mail($to, $subject, $body, $header, $params);
        }
        return $result;
    }
    /**
     * Output debugging info via user-defined method.
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
     * @see PHPMailer::$Debugoutput
     * @see PHPMailer::$SMTPDebug
     * @param string $str
     */
    protected function edebug($str)
    {
        if ($this->SMTPDebug <= 0) {
            return;
        }
        //Avoid clash with built-in function names
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
            return;
        }
        switch ($this->Debugoutput) {
            case 'error_log':
                //Don't output, just log
                error_log($str);
                break;
            case 'html':
                //Cleans up output a bit for a better looking, HTML-safe output
                echo htmlentities(
                    preg_replace('/[\r\n]+/', '', $str),
                    ENT_QUOTES,
                    'UTF-8'
                )
                . "<br>\n";
                break;
            case 'echo':
            default:
                //Normalize line breaks
                $str = preg_replace('/\r\n?/ms', "\n", $str);
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
                    "\n",
                    "\n                   \t                  ",
                    trim($str)
                ) . "\n";
        }
    }

    /**
     * Sets message type to HTML or plain.
     * @param boolean $isHtml True for HTML mode.
     * @return void
     */
    public function isHTML($isHtml = true)
    {
        if ($isHtml) {
            $this->ContentType = 'text/html';
        } else {
            $this->ContentType = 'text/plain';
        }
    }

    /**
     * Send messages using SMTP.
     * @return void
     */
    public function isSMTP()
    {
        $this->Mailer = 'smtp';
    }

    /**
     * Send messages using PHP's mail() function.
     * @return void
     */
    public function isMail()
    {
        $this->Mailer = 'mail';
    }

    /**
     * Send messages using $Sendmail.
     * @return void
     */
    public function isSendmail()
    {
        $ini_sendmail_path = ini_get('sendmail_path');

        if (!stristr($ini_sendmail_path, 'sendmail')) {
            $this->Sendmail = '/usr/sbin/sendmail';
        } else {
            $this->Sendmail = $ini_sendmail_path;
        }
        $this->Mailer = 'sendmail';
    }

    /**
     * Send messages using qmail.
     * @return void
     */
    public function isQmail()
    {
        $ini_sendmail_path = ini_get('sendmail_path');

        if (!stristr($ini_sendmail_path, 'qmail')) {
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
        } else {
            $this->Sendmail = $ini_sendmail_path;
        }
        $this->Mailer = 'qmail';
    }

    /**
     * Add a "To" address.
     * @param string $address The email address to send to
     * @param string $name
     * @return boolean true on success, false if address already used or invalid in some way
     */
    public function addAddress($address, $name = '')
    {
        return $this->addOrEnqueueAnAddress('to', $address, $name);
    }

    /**
     * Add a "CC" address.
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
     * @param string $address The email address to send to
     * @param string $name
     * @return boolean true on success, false if address already used or invalid in some way
     */
    public function addCC($address, $name = '')
    {
        return $this->addOrEnqueueAnAddress('cc', $address, $name);
    }

    /**
     * Add a "BCC" address.
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
     * @param string $address The email address to send to
     * @param string $name
     * @return boolean true on success, false if address already used or invalid in some way
     */
    public function addBCC($address, $name = '')
    {
        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
    }

    /**
     * Add a "Reply-To" address.
     * @param string $address The email address to reply to
     * @param string $name
     * @return boolean true on success, false if address already used or invalid in some way
     */
    public function addReplyTo($address, $name = '')
    {
        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
    }

    /**
     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
     * be modified after calling this function), addition of such addresses is delayed until send().
     * Addresses that have been added already return false, but do not throw exceptions.
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
     * @param string $address The email address to send, resp. to reply to
     * @param string $name
     * @throws phpmailerException
     * @return boolean true on success, false if address already used or invalid in some way
     * @access protected
     */
    protected function addOrEnqueueAnAddress($kind, $address, $name)
    {
        $address = trim($address);
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
        if (($pos = strrpos($address, '@')) === false) {
            // At-sign is misssing.
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
            $this->setError($error_message);
            $this->edebug($error_message);
            if ($this->exceptions) {
                throw new phpmailerException($error_message);
            }
            return false;
        }
        $params = array($kind, $address, $name);
        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
        if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
            if ($kind != 'Reply-To') {
                if (!array_key_exists($address, $this->RecipientsQueue)) {
                    $this->RecipientsQueue[$address] = $params;
                    return true;
                }
            } else {
                if (!array_key_exists($address, $this->ReplyToQueue)) {
                    $this->ReplyToQueue[$address] = $params;
                    return true;
                }
            }
            return false;
        }
        // Immediately add standard addresses without IDN.
        return call_user_func_array(array($this, 'addAnAddress'), $params);
    }

    /**
     * Add an address to one of the recipient arrays or to the ReplyTo array.
     * Addresses that have been added already return false, but do not throw exceptions.
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
     * @param string $address The email address to send, resp. to reply to
     * @param string $name
     * @throws phpmailerException
     * @return boolean true on success, false if address already used or invalid in some way
     * @access protected
     */
    protected function addAnAddress($kind, $address, $name = '')
    {
        if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
            $error_message = $this->lang('Invalid recipient kind: ') . $kind;
            $this->setError($error_message);
            $this->edebug($error_message);
            if ($this->exceptions) {
                throw new phpmailerException($error_message);
            }
            return false;
        }
        if (!$this->validateAddress($address)) {
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
            $this->setError($error_message);
            $this->edebug($error_message);
            if ($this->exceptions) {
                throw new phpmailerException($error_message);
            }
            return false;
        }
        if ($kind != 'Reply-To') {
            if (!array_key_exists(strtolower($address), $this->all_recipients)) {
                array_push($this->$kind, array($address, $name));
                $this->all_recipients[strtolower($address)] = true;
                return true;
            }
        } else {
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
                $this->ReplyTo[strtolower($address)] = array($address, $name);
                return true;
            }
        }
        return false;
    }

    /**
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
     * of the form "display name <address>" into an array of name/address pairs.
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
     * Note that quotes in the name part are removed.
     * @param string $addrstr The address list string
     * @param bool $useimap Whether to use the IMAP extension to parse the list
     * @return array
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
     */
    public function parseAddresses($addrstr, $useimap = true)
    {
        $addresses = array();
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
            //Use this built-in parser if it's available
            $list = imap_rfc822_parse_adrlist($addrstr, '');
            foreach ($list as $address) {
                if ($address->host != '.SYNTAX-ERROR.') {
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
                        $addresses[] = array(
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
                            'address' => $address->mailbox . '@' . $address->host
                        );
                    }
                }
            }
        } else {
            //Use this simpler parser
            $list = explode(',', $addrstr);
            foreach ($list as $address) {
                $address = trim($address);
                //Is there a separate name part?
                if (strpos($address, '<') === false) {
                    //No separate name, just use the whole thing
                    if ($this->validateAddress($address)) {
                        $addresses[] = array(
                            'name' => '',
                            'address' => $address
                        );
                    }
                } else {
                    list($name, $email) = explode('<', $address);
                    $email = trim(str_replace('>', '', $email));
                    if ($this->validateAddress($email)) {
                        $addresses[] = array(
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
                            'address' => $email
                        );
                    }
                }
            }
        }
        return $addresses;
    }

    /**
     * Set the From and FromName properties.
     * @param string $address
     * @param string $name
     * @param boolean $auto Whether to also set the Sender address, defaults to true
     * @throws phpmailerException
     * @return boolean
     */
    public function setFrom($address, $name = '', $auto = true)
    {
        $address = trim($address);
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
        // Don't validate now addresses with IDN. Will be done in send().
        if (($pos = strrpos($address, '@')) === false or
            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
            !$this->validateAddress($address)) {
            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
            $this->setError($error_message);
            $this->edebug($error_message);
            if ($this->exceptions) {
                throw new phpmailerException($error_message);
            }
            return false;
        }
        $this->From = $address;
        $this->FromName = $name;
        if ($auto) {
            if (empty($this->Sender)) {
                $this->Sender = $address;
            }
        }
        return true;
    }

    /**
     * Return the Message-ID header of the last email.
     * Technically this is the value from the last time the headers were created,
     * but it's also the message ID of the last sent message except in
     * pathological cases.
     * @return string
     */
    public function getLastMessageID()
    {
        return $this->lastMessageID;
    }

    /**
     * Check that a string looks like an email address.
     * @param string $address The email address to check
     * @param string|callable $patternselect A selector for the validation pattern to use :
     * * `auto` Pick best pattern automatically;
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
     * * `pcre` Use old PCRE implementation;
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
     * * `noregex` Don't use a regex: super fast, really dumb.
     * Alternatively you may pass in a callable to inject your own validator, for example:
     * PHPMailer::validateAddress('user@example.com', function($address) {
     *     return (strpos($address, '@') !== false);
     * });
     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
     * @return boolean
     * @static
     * @access public
     */
    public static function validateAddress($address, $patternselect = null)
    {
        if (is_null($patternselect)) {
            $patternselect = self::$validator;
        }
        if (is_callable($patternselect)) {
            return call_user_func($patternselect, $address);
        }
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
            return false;
        }
        if (!$patternselect or $patternselect == 'auto') {
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
            //Constant was added in PHP 5.2.4
            if (defined('PCRE_VERSION')) {
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
                    $patternselect = 'pcre8';
                } else {
                    $patternselect = 'pcre';
                }
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
                //Fall back to older PCRE
                $patternselect = 'pcre';
            } else {
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
                    $patternselect = 'php';
                } else {
                    $patternselect = 'noregex';
                }
            }
        }
        switch ($patternselect) {
            case 'pcre8':
                /**
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
                 * @copyright 2009-2010 Michael Rushton
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
                 */
                return (boolean)preg_match(
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
                    $address
                );
            case 'pcre':
                //An older regex that doesn't need a recent PCRE
                return (boolean)preg_match(
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
                    $address
                );
            case 'html5':
                /**
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
                 */
                return (boolean)preg_match(
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
                    $address
                );
            case 'noregex':
                //No PCRE! Do something _very_ approximate!
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
                return (strlen($address) >= 3
                    and strpos($address, '@') >= 1
                    and strpos($address, '@') != strlen($address) - 1);
            case 'php':
            default:
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
        }
    }

    /**
     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
     * "intl" and "mbstring" PHP extensions.
     * @return bool "true" if required functions for IDN support are present
     */
    public function idnSupported()
    {
        // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
        return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
    }

    /**
     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
     * This function silently returns unmodified address if:
     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
     *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
     * @see PHPMailer::$CharSet
     * @param string $address The email address to convert
     * @return string The encoded address in ASCII form
     */
    public function punyencodeAddress($address)
    {
        // Verify we have required functions, CharSet, and at-sign.
        if ($this->idnSupported() and
            !empty($this->CharSet) and
            ($pos = strrpos($address, '@')) !== false) {
            $domain = substr($address, ++$pos);
            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
            if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
                if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
                    idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
                    idn_to_ascii($domain)) !== false) {
                    return substr($address, 0, $pos) . $punycode;
                }
            }
        }
        return $address;
    }

    /**
     * Create a message and send it.
     * Uses the sending method specified by $Mailer.
     * @throws phpmailerException
     * @return boolean false on error - See the ErrorInfo property for details of the error.
     */
    public function send()
    {
        try {
            if (!$this->preSend()) {
                return false;
            }
            return $this->postSend();
        } catch (phpmailerException $exc) {
            $this->mailHeader = '';
            $this->setError($exc->getMessage());
            if ($this->exceptions) {
                throw $exc;
            }
            return false;
        }
    }

    /**
     * Prepare a message for sending.
     * @throws phpmailerException
     * @return boolean
     */
    public function preSend()
    {
        try {
            $this->error_count = 0; // Reset errors
            $this->mailHeader = '';

            // Dequeue recipient and Reply-To addresses with IDN
            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
                $params[1] = $this->punyencodeAddress($params[1]);
                call_user_func_array(array($this, 'addAnAddress'), $params);
            }
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
            }

            // Validate From, Sender, and ConfirmReadingTo addresses
            foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
                $this->$address_kind = trim($this->$address_kind);
                if (empty($this->$address_kind)) {
                    continue;
                }
                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
                if (!$this->validateAddress($this->$address_kind)) {
                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
                    $this->setError($error_message);
                    $this->edebug($error_message);
                    if ($this->exceptions) {
                        throw new phpmailerException($error_message);
                    }
                    return false;
                }
            }

            // Set whether the message is multipart/alternative
            if ($this->alternativeExists()) {
                $this->ContentType = 'multipart/alternative';
            }

            $this->setMessageType();
            // Refuse to send an empty message unless we are specifically allowing it
            if (!$this->AllowEmpty and empty($this->Body)) {
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
            }

            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
            $this->MIMEHeader = '';
            $this->MIMEBody = $this->createBody();
            // createBody may have added some headers, so retain them
            $tempheaders = $this->MIMEHeader;
            $this->MIMEHeader = $this->createHeader();
            $this->MIMEHeader .= $tempheaders;

            // To capture the complete message when using mail(), create
            // an extra header list which createHeader() doesn't fold in
            if ($this->Mailer == 'mail') {
                if (count($this->to) > 0) {
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
                } else {
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
                }
                $this->mailHeader .= $this->headerLine(
                    'Subject',
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
                );
            }

            // Sign with DKIM if enabled
            if (!empty($this->DKIM_domain)
                and !empty($this->DKIM_selector)
                and (!empty($this->DKIM_private_string)
                    or (!empty($this->DKIM_private)
                        and self::isPermittedPath($this->DKIM_private)
                        and file_exists($this->DKIM_private)
                    )
                )
            ) {
                $header_dkim = $this->DKIM_Add(
                    $this->MIMEHeader . $this->mailHeader,
                    $this->encodeHeader($this->secureHeader($this->Subject)),
                    $this->MIMEBody
                );
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
            }
            return true;
        } catch (phpmailerException $exc) {
            $this->setError($exc->getMessage());
            if ($this->exceptions) {
                throw $exc;
            }
            return false;
        }
    }

    /**
     * Actually send a message.
     * Send the email via the selected mechanism
     * @throws phpmailerException
     * @return boolean
     */
    public function postSend()
    {
        try {
            // Choose the mailer and send through it
            switch ($this->Mailer) {
                case 'sendmail':
                case 'qmail':
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
                case 'smtp':
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
                case 'mail':
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
                default:
                    $sendMethod = $this->Mailer.'Send';
                    if (method_exists($this, $sendMethod)) {
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
                    }

                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
            }
        } catch (phpmailerException $exc) {
            $this->setError($exc->getMessage());
            $this->edebug($exc->getMessage());
            if ($this->exceptions) {
                throw $exc;
            }
        }
        return false;
    }

    /**
     * Send mail using the $Sendmail program.
     * @param string $header The message headers
     * @param string $body The message body
     * @see PHPMailer::$Sendmail
     * @throws phpmailerException
     * @access protected
     * @return boolean
     */
    protected function sendmailSend($header, $body)
    {
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
        if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
            if ($this->Mailer == 'qmail') {
                $sendmailFmt = '%s -f%s';
            } else {
                $sendmailFmt = '%s -oi -f%s -t';
            }
        } else {
            if ($this->Mailer == 'qmail') {
                $sendmailFmt = '%s';
            } else {
                $sendmailFmt = '%s -oi -t';
            }
        }

        // TODO: If possible, this should be changed to escapeshellarg.  Needs thorough testing.
        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);

        if ($this->SingleTo) {
            foreach ($this->SingleToArray as $toAddr) {
                if (!@$mail = popen($sendmail, 'w')) {
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
                }
                fputs($mail, 'To: ' . $toAddr . "\n");
                fputs($mail, $header);
                fputs($mail, $body);
                $result = pclose($mail);
                $this->doCallback(
                    ($result == 0),
                    array($toAddr),
                    $this->cc,
                    $this->bcc,
                    $this->Subject,
                    $body,
                    $this->From
                );
                if ($result != 0) {
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
                }
            }
        } else {
            if (!@$mail = popen($sendmail, 'w')) {
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
            }
            fputs($mail, $header);
            fputs($mail, $body);
            $result = pclose($mail);
            $this->doCallback(
                ($result == 0),
                $this->to,
                $this->cc,
                $this->bcc,
                $this->Subject,
                $body,
                $this->From
            );
            if ($result != 0) {
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
            }
        }
        return true;
    }

    /**
     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
     *
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
     * @param string $string The string to be validated
     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
     * @access protected
     * @return boolean
     */
    protected static function isShellSafe($string)
    {
        // Future-proof
        if (escapeshellcmd($string) !== $string
            or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
        ) {
            return false;
        }

        $length = strlen($string);

        for ($i = 0; $i < $length; $i++) {
            $c = $string[$i];

            // All other characters have a special meaning in at least one common shell, including = and +.
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
                return false;
            }
        }

        return true;
    }

    /**
     * Check whether a file path is of a permitted type.
     * Used to reject URLs and phar files from functions that access local file paths,
     * such as addAttachment.
     * @param string $path A relative or absolute path to a file.
     * @return bool
     */
    protected static function isPermittedPath($path)
    {
        return !preg_match('#^[a-z]+://#i', $path);
    }

    /**
     * Send mail using the PHP mail() function.
     * @param string $header The message headers
     * @param string $body The message body
     * @link http://www.php.net/manual/en/book.mail.php
     * @throws phpmailerException
     * @access protected
     * @return boolean
     */
    protected function mailSend($header, $body)
    {
        $toArr = array();
        foreach ($this->to as $toaddr) {
            $toArr[] = $this->addrFormat($toaddr);
        }
        $to = implode(', ', $toArr);

        $params = null;
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
            // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
            if (self::isShellSafe($this->Sender)) {
                $params = sprintf('-f%s', $this->Sender);
            }
        }
        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
            $old_from = ini_get('sendmail_from');
            ini_set('sendmail_from', $this->Sender);
        }
        $result = false;
        if ($this->SingleTo and count($toArr) > 1) {
            foreach ($toArr as $toAddr) {
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
            }
        } else {
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
        }
        if (isset($old_from)) {
            ini_set('sendmail_from', $old_from);
        }
        if (!$result) {
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
        }
        return true;
    }

    /**
     * Get an instance to use for SMTP operations.
     * Override this function to load your own SMTP implementation
     * @return SMTP
     */
    public function getSMTPInstance()
    {
        if (!is_object($this->smtp)) {
            $this->smtp = new SMTP;
        }
        return $this->smtp;
    }

    /**
     * Send mail via SMTP.
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
     * Uses the PHPMailerSMTP class by default.
     * @see PHPMailer::getSMTPInstance() to use a different class.
     * @param string $header The message headers
     * @param string $body The message body
     * @throws phpmailerException
     * @uses SMTP
     * @access protected
     * @return boolean
     */
    protected function smtpSend($header, $body)
    {
        $bad_rcpt = array();
        if (!$this->smtpConnect($this->SMTPOptions)) {
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
        }
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
            $smtp_from = $this->Sender;
        } else {
            $smtp_from = $this->From;
        }
        if (!$this->smtp->mail($smtp_from)) {
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
        }

        // Attempt to send to all recipients
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
            foreach ($togroup as $to) {
                if (!$this->smtp->recipient($to[0])) {
                    $error = $this->smtp->getError();
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
                    $isSent = false;
                } else {
                    $isSent = true;
                }
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
            }
        }

        // Only send the DATA command if we have viable recipients
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
        }
        if ($this->SMTPKeepAlive) {
            $this->smtp->reset();
        } else {
            $this->smtp->quit();
            $this->smtp->close();
        }
        //Create error message for any bad addresses
        if (count($bad_rcpt) > 0) {
            $errstr = '';
            foreach ($bad_rcpt as $bad) {
                $errstr .= $bad['to'] . ': ' . $bad['error'];
            }
            throw new phpmailerException(
                $this->lang('recipients_failed') . $errstr,
                self::STOP_CONTINUE
            );
        }
        return true;
    }

    /**
     * Initiate a connection to an SMTP server.
     * Returns false if the operation failed.
     * @param array $options An array of options compatible with stream_context_create()
     * @uses SMTP
     * @access public
     * @throws phpmailerException
     * @return boolean
     */
    public function smtpConnect($options = null)
    {
        if (is_null($this->smtp)) {
            $this->smtp = $this->getSMTPInstance();
        }

        //If no options are provided, use whatever is set in the instance
        if (is_null($options)) {
            $options = $this->SMTPOptions;
        }

        // Already connected?
        if ($this->smtp->connected()) {
            return true;
        }

        $this->smtp->setTimeout($this->Timeout);
        $this->smtp->setDebugLevel($this->SMTPDebug);
        $this->smtp->setDebugOutput($this->Debugoutput);
        $this->smtp->setVerp($this->do_verp);
        $hosts = explode(';', $this->Host);
        $lastexception = null;

        foreach ($hosts as $hostentry) {
            $hostinfo = array();
            if (!preg_match(
                '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
                trim($hostentry),
                $hostinfo
            )) {
                // Not a valid host entry
                $this->edebug('Ignoring invalid host: ' . $hostentry);
                continue;
            }
            // $hostinfo[2]: optional ssl or tls prefix
            // $hostinfo[3]: the hostname
            // $hostinfo[4]: optional port number
            // The host string prefix can temporarily override the current setting for SMTPSecure
            // If it's not specified, the default value is used
            $prefix = '';
            $secure = $this->SMTPSecure;
            $tls = ($this->SMTPSecure == 'tls');
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
                $prefix = 'ssl://';
                $tls = false; // Can't have SSL and TLS at the same time
                $secure = 'ssl';
            } elseif ($hostinfo[2] == 'tls') {
                $tls = true;
                // tls doesn't use a prefix
                $secure = 'tls';
            }
            //Do we need the OpenSSL extension?
            $sslext = defined('OPENSSL_ALGO_SHA1');
            if ('tls' === $secure or 'ssl' === $secure) {
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
                if (!$sslext) {
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
                }
            }
            $host = $hostinfo[3];
            $port = $this->Port;
            $tport = (integer)$hostinfo[4];
            if ($tport > 0 and $tport < 65536) {
                $port = $tport;
            }
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
                try {
                    if ($this->Helo) {
                        $hello = $this->Helo;
                    } else {
                        $hello = $this->serverHostname();
                    }
                    $this->smtp->hello($hello);
                    //Automatically enable TLS encryption if:
                    // * it's not disabled
                    // * we have openssl extension
                    // * we are not already using SSL
                    // * the server offers STARTTLS
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
                        $tls = true;
                    }
                    if ($tls) {
                        if (!$this->smtp->startTLS()) {
                            throw new phpmailerException($this->lang('connect_host'));
                        }
                        // We must resend EHLO after TLS negotiation
                        $this->smtp->hello($hello);
                    }
                    if ($this->SMTPAuth) {
                        if (!$this->smtp->authenticate(
                            $this->Username,
                            $this->Password,
                            $this->AuthType,
                            $this->Realm,
                            $this->Workstation
                        )
                        ) {
                            throw new phpmailerException($this->lang('authenticate'));
                        }
                    }
                    return true;
                } catch (phpmailerException $exc) {
                    $lastexception = $exc;
                    $this->edebug($exc->getMessage());
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
                    $this->smtp->quit();
                }
            }
        }
        // If we get here, all connection attempts have failed, so close connection hard
        $this->smtp->close();
        // As we've caught all exceptions, just report whatever the last one was
        if ($this->exceptions and !is_null($lastexception)) {
            throw $lastexception;
        }
        return false;
    }

    /**
     * Close the active SMTP session if one exists.
     * @return void
     */
    public function smtpClose()
    {
        if (is_a($this->smtp, 'SMTP')) {
            if ($this->smtp->connected()) {
                $this->smtp->quit();
                $this->smtp->close();
            }
        }
    }

    /**
     * Set the language for error messages.
     * Returns false if it cannot load the language file.
     * The default language is English.
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
     * @return boolean
     * @access public
     */
    public function setLanguage($langcode = 'en', $lang_path = '')
    {
        // Backwards compatibility for renamed language codes
        $renamed_langcodes = array(
            'br' => 'pt_br',
            'cz' => 'cs',
            'dk' => 'da',
            'no' => 'nb',
            'se' => 'sv',
            'sr' => 'rs'
        );

        if (isset($renamed_langcodes[$langcode])) {
            $langcode = $renamed_langcodes[$langcode];
        }

        // Define full set of translatable strings in English
        $PHPMAILER_LANG = array(
            'authenticate' => 'SMTP Error: Could not authenticate.',
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
            'data_not_accepted' => 'SMTP Error: data not accepted.',
            'empty_message' => 'Message body empty',
            'encoding' => 'Unknown encoding: ',
            'execute' => 'Could not execute: ',
            'file_access' => 'Could not access file: ',
            'file_open' => 'File Error: Could not open file: ',
            'from_failed' => 'The following From address failed: ',
            'instantiate' => 'Could not instantiate mail function.',
            'invalid_address' => 'Invalid address: ',
            'mailer_not_supported' => ' mailer is not supported.',
            'provide_address' => 'You must provide at least one recipient email address.',
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
            'signing' => 'Signing Error: ',
            'smtp_connect_failed' => 'SMTP connect() failed.',
            'smtp_error' => 'SMTP server error: ',
            'variable_set' => 'Cannot set or reset variable: ',
            'extension_missing' => 'Extension missing: '
        );
        if (empty($lang_path)) {
            // Calculate an absolute path so it can work if CWD is not here
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
        }
        //Validate $langcode
        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
            $langcode = 'en';
        }
        $foundlang = true;
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
        // There is no English translation file
        if ($langcode != 'en') {
            // Make sure language file path is readable
            if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) {
                $foundlang = false;
            } else {
                // Overwrite language-specific strings.
                // This way we'll never have missing translation keys.
                $foundlang = include $lang_file;
            }
        }
        $this->language = $PHPMAILER_LANG;
        return (boolean)$foundlang; // Returns false if language not found
    }

    /**
     * Get the array of strings for the current language.
     * @return array
     */
    public function getTranslations()
    {
        return $this->language;
    }

    /**
     * Create recipient headers.
     * @access public
     * @param string $type
     * @param array $addr An array of recipient,
     * where each recipient is a 2-element indexed array with element 0 containing an address
     * and element 1 containing a name, like:
     * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
     * @return string
     */
    public function addrAppend($type, $addr)
    {
        $addresses = array();
        foreach ($addr as $address) {
            $addresses[] = $this->addrFormat($address);
        }
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
    }

    /**
     * Format an address for use in a message header.
     * @access public
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
     *      like array('joe@example.com', 'Joe User')
     * @return string
     */
    public function addrFormat($addr)
    {
        if (empty($addr[1])) { // No name provided
            return $this->secureHeader($addr[0]);
        } else {
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
                $addr[0]
            ) . '>';
        }
    }

    /**
     * Word-wrap message.
     * For use with mailers that do not automatically perform wrapping
     * and for quoted-printable encoded messages.
     * Original written by philippe.
     * @param string $message The message to wrap
     * @param integer $length The line length to wrap to
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
     * @access public
     * @return string
     */
    public function wrapText($message, $length, $qp_mode = false)
    {
        if ($qp_mode) {
            $soft_break = sprintf(' =%s', $this->LE);
        } else {
            $soft_break = $this->LE;
        }
        // If utf-8 encoding is used, we will need to make sure we don't
        // split multibyte characters when we wrap
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
        $lelen = strlen($this->LE);
        $crlflen = strlen(self::CRLF);

        $message = $this->fixEOL($message);
        //Remove a trailing line break
        if (substr($message, -$lelen) == $this->LE) {
            $message = substr($message, 0, -$lelen);
        }

        //Split message into lines
        $lines = explode($this->LE, $message);
        //Message will be rebuilt in here
        $message = '';
        foreach ($lines as $line) {
            $words = explode(' ', $line);
            $buf = '';
            $firstword = true;
            foreach ($words as $word) {
                if ($qp_mode and (strlen($word) > $length)) {
                    $space_left = $length - strlen($buf) - $crlflen;
                    if (!$firstword) {
                        if ($space_left > 20) {
                            $len = $space_left;
                            if ($is_utf8) {
                                $len = $this->utf8CharBoundary($word, $len);
                            } elseif (substr($word, $len - 1, 1) == '=') {
                                $len--;
                            } elseif (substr($word, $len - 2, 1) == '=') {
                                $len -= 2;
                            }
                            $part = substr($word, 0, $len);
                            $word = substr($word, $len);
                            $buf .= ' ' . $part;
                            $message .= $buf . sprintf('=%s', self::CRLF);
                        } else {
                            $message .= $buf . $soft_break;
                        }
                        $buf = '';
                    }
                    while (strlen($word) > 0) {
                        if ($length <= 0) {
                            break;
                        }
                        $len = $length;
                        if ($is_utf8) {
                            $len = $this->utf8CharBoundary($word, $len);
                        } elseif (substr($word, $len - 1, 1) == '=') {
                            $len--;
                        } elseif (substr($word, $len - 2, 1) == '=') {
                            $len -= 2;
                        }
                        $part = substr($word, 0, $len);
                        $word = substr($word, $len);

                        if (strlen($word) > 0) {
                            $message .= $part . sprintf('=%s', self::CRLF);
                        } else {
                            $buf = $part;
                        }
                    }
                } else {
                    $buf_o = $buf;
                    if (!$firstword) {
                        $buf .= ' ';
                    }
                    $buf .= $word;

                    if (strlen($buf) > $length and $buf_o != '') {
                        $message .= $buf_o . $soft_break;
                        $buf = $word;
                    }
                }
                $firstword = false;
            }
            $message .= $buf . self::CRLF;
        }

        return $message;
    }

    /**
     * Find the last character boundary prior to $maxLength in a utf-8
     * quoted-printable encoded string.
     * Original written by Colin Brown.
     * @access public
     * @param string $encodedText utf-8 QP text
     * @param integer $maxLength Find the last character boundary prior to this length
     * @return integer
     */
    public function utf8CharBoundary($encodedText, $maxLength)
    {
        $foundSplitPos = false;
        $lookBack = 3;
        while (!$foundSplitPos) {
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
            $encodedCharPos = strpos($lastChunk, '=');
            if (false !== $encodedCharPos) {
                // Found start of encoded character byte within $lookBack block.
                // Check the encoded byte value (the 2 chars after the '=')
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
                $dec = hexdec($hex);
                if ($dec < 128) {
                    // Single byte character.
                    // If the encoded char was found at pos 0, it will fit
                    // otherwise reduce maxLength to start of the encoded char
                    if ($encodedCharPos > 0) {
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
                    }
                    $foundSplitPos = true;
                } elseif ($dec >= 192) {
                    // First byte of a multi byte character
                    // Reduce maxLength to split at start of character
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
                    $foundSplitPos = true;
                } elseif ($dec < 192) {
                    // Middle byte of a multi byte character, look further back
                    $lookBack += 3;
                }
            } else {
                // No encoded character found
                $foundSplitPos = true;
            }
        }
        return $maxLength;
    }

    /**
     * Apply word wrapping to the message body.
     * Wraps the message body to the number of chars set in the WordWrap property.
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
     * This is called automatically by createBody(), so you don't need to call it yourself.
     * @access public
     * @return void
     */
    public function setWordWrap()
    {
        if ($this->WordWrap < 1) {
            return;
        }

        switch ($this->message_type) {
            case 'alt':
            case 'alt_inline':
            case 'alt_attach':
            case 'alt_inline_attach':
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
                break;
            default:
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
                break;
        }
    }

    /**
     * Assemble message headers.
     * @access public
     * @return string The assembled headers
     */
    public function createHeader()
    {
        $result = '';

        $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);

        // To be created automatically by mail()
        if ($this->SingleTo) {
            if ($this->Mailer != 'mail') {
                foreach ($this->to as $toaddr) {
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
                }
            }
        } else {
            if (count($this->to) > 0) {
                if ($this->Mailer != 'mail') {
                    $result .= $this->addrAppend('To', $this->to);
                }
            } elseif (count($this->cc) == 0) {
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
            }
        }

        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));

        // sendmail and mail() extract Cc from the header before sending
        if (count($this->cc) > 0) {
            $result .= $this->addrAppend('Cc', $this->cc);
        }

        // sendmail and mail() extract Bcc from the header before sending
        if ((
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
            )
            and count($this->bcc) > 0
        ) {
            $result .= $this->addrAppend('Bcc', $this->bcc);
        }

        if (count($this->ReplyTo) > 0) {
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
        }

        // mail() sets the subject itself
        if ($this->Mailer != 'mail') {
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
        }

        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
        // https://tools.ietf.org/html/rfc5322#section-3.6.4
        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
            $this->lastMessageID = $this->MessageID;
        } else {
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
        }
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
        if (!is_null($this->Priority)) {
            $result .= $this->headerLine('X-Priority', $this->Priority);
        }
        if ($this->XMailer == '') {
            $result .= $this->headerLine(
                'X-Mailer',
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
            );
        } else {
            $myXmailer = trim($this->XMailer);
            if ($myXmailer) {
                $result .= $this->headerLine('X-Mailer', $myXmailer);
            }
        }

        if ($this->ConfirmReadingTo != '') {
            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
        }

        // Add custom headers
        foreach ($this->CustomHeader as $header) {
            $result .= $this->headerLine(
                trim($header[0]),
                $this->encodeHeader(trim($header[1]))
            );
        }
        if (!$this->sign_key_file) {
            $result .= $this->headerLine('MIME-Version', '1.0');
            $result .= $this->getMailMIME();
        }

        return $result;
    }

    /**
     * Get the message MIME type headers.
     * @access public
     * @return string
     */
    public function getMailMIME()
    {
        $result = '';
        $ismultipart = true;
        switch ($this->message_type) {
            case 'inline':
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
                break;
            case 'attach':
            case 'inline_attach':
            case 'alt_attach':
            case 'alt_inline_attach':
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
                break;
            case 'alt':
            case 'alt_inline':
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
                break;
            default:
                // Catches case 'plain': and case '':
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
                $ismultipart = false;
                break;
        }
        // RFC1341 part 5 says 7bit is assumed if not specified
        if ($this->Encoding != '7bit') {
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
            if ($ismultipart) {
                if ($this->Encoding == '8bit') {
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
                }
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
            } else {
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
            }
        }

        if ($this->Mailer != 'mail') {
            $result .= $this->LE;
        }

        return $result;
    }

    /**
     * Returns the whole MIME message.
     * Includes complete headers and body.
     * Only valid post preSend().
     * @see PHPMailer::preSend()
     * @access public
     * @return string
     */
    public function getSentMIMEMessage()
    {
        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
    }

    /**
     * Create unique ID
     * @return string
     */
    protected function generateId() {
        return md5(uniqid(time()));
    }

    /**
     * Assemble the message body.
     * Returns an empty string on failure.
     * @access public
     * @throws phpmailerException
     * @return string The assembled message body
     */
    public function createBody()
    {
        $body = '';
        //Create unique IDs and preset boundaries
        $this->uniqueid = $this->generateId();
        $this->boundary[1] = 'b1_' . $this->uniqueid;
        $this->boundary[2] = 'b2_' . $this->uniqueid;
        $this->boundary[3] = 'b3_' . $this->uniqueid;

        if ($this->sign_key_file) {
            $body .= $this->getMailMIME() . $this->LE;
        }

        $this->setWordWrap();

        $bodyEncoding = $this->Encoding;
        $bodyCharSet = $this->CharSet;
        //Can we do a 7-bit downgrade?
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
            $bodyEncoding = '7bit';
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
            $bodyCharSet = 'us-ascii';
        }
        //If lines are too long, and we're not already using an encoding that will shorten them,
        //change to quoted-printable transfer encoding for the body part only
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
            $bodyEncoding = 'quoted-printable';
        }

        $altBodyEncoding = $this->Encoding;
        $altBodyCharSet = $this->CharSet;
        //Can we do a 7-bit downgrade?
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
            $altBodyEncoding = '7bit';
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
            $altBodyCharSet = 'us-ascii';
        }
        //If lines are too long, and we're not already using an encoding that will shorten them,
        //change to quoted-printable transfer encoding for the alt body part only
        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
            $altBodyEncoding = 'quoted-printable';
        }
        //Use this as a preamble in all multipart message types
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
        switch ($this->message_type) {
            case 'inline':
                $body .= $mimepre;
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->attachAll('inline', $this->boundary[1]);
                break;
            case 'attach':
                $body .= $mimepre;
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->attachAll('attachment', $this->boundary[1]);
                break;
            case 'inline_attach':
                $body .= $mimepre;
                $body .= $this->textLine('--' . $this->boundary[1]);
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
                $body .= $this->LE;
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->attachAll('inline', $this->boundary[2]);
                $body .= $this->LE;
                $body .= $this->attachAll('attachment', $this->boundary[1]);
                break;
            case 'alt':
                $body .= $mimepre;
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                if (!empty($this->Ical)) {
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
                    $body .= $this->LE . $this->LE;
                }
                $body .= $this->endBoundary($this->boundary[1]);
                break;
            case 'alt_inline':
                $body .= $mimepre;
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->textLine('--' . $this->boundary[1]);
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
                $body .= $this->LE;
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->attachAll('inline', $this->boundary[2]);
                $body .= $this->LE;
                $body .= $this->endBoundary($this->boundary[1]);
                break;
            case 'alt_attach':
                $body .= $mimepre;
                $body .= $this->textLine('--' . $this->boundary[1]);
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
                $body .= $this->LE;
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->endBoundary($this->boundary[2]);
                $body .= $this->LE;
                $body .= $this->attachAll('attachment', $this->boundary[1]);
                break;
            case 'alt_inline_attach':
                $body .= $mimepre;
                $body .= $this->textLine('--' . $this->boundary[1]);
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
                $body .= $this->LE;
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->textLine('--' . $this->boundary[2]);
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
                $body .= $this->LE;
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
                $body .= $this->encodeString($this->Body, $bodyEncoding);
                $body .= $this->LE . $this->LE;
                $body .= $this->attachAll('inline', $this->boundary[3]);
                $body .= $this->LE;
                $body .= $this->endBoundary($this->boundary[2]);
                $body .= $this->LE;
                $body .= $this->attachAll('attachment', $this->boundary[1]);
                break;
            default:
                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
                //Reset the `Encoding` property in case we changed it for line length reasons
                $this->Encoding = $bodyEncoding;
                $body .= $this->encodeString($this->Body, $this->Encoding);
                break;
        }

        if ($this->isError()) {
            $body = '';
        } elseif ($this->sign_key_file) {
            try {
                if (!defined('PKCS7_TEXT')) {
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
                }
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
                $file = tempnam(sys_get_temp_dir(), 'mail');
                if (false === file_put_contents($file, $body)) {
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
                }
                $signed = tempnam(sys_get_temp_dir(), 'signed');
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
                if (empty($this->sign_extracerts_file)) {
                    $sign = @openssl_pkcs7_sign(
                        $file,
                        $signed,
                        'file://' . realpath($this->sign_cert_file),
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
                        null
                    );
                } else {
                    $sign = @openssl_pkcs7_sign(
                        $file,
                        $signed,
                        'file://' . realpath($this->sign_cert_file),
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
                        null,
                        PKCS7_DETACHED,
                        $this->sign_extracerts_file
                    );
                }
                if ($sign) {
                    @unlink($file);
                    $body = file_get_contents($signed);
                    @unlink($signed);
                    //The message returned by openssl contains both headers and body, so need to split them up
                    $parts = explode("\n\n", $body, 2);
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
                    $body = $parts[1];
                } else {
                    @unlink($file);
                    @unlink($signed);
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
                }
            } catch (phpmailerException $exc) {
                $body = '';
                if ($this->exceptions) {
                    throw $exc;
                }
            }
        }
        return $body;
    }

    /**
     * Return the start of a message boundary.
     * @access protected
     * @param string $boundary
     * @param string $charSet
     * @param string $contentType
     * @param string $encoding
     * @return string
     */
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
    {
        $result = '';
        if ($charSet == '') {
            $charSet = $this->CharSet;
        }
        if ($contentType == '') {
            $contentType = $this->ContentType;
        }
        if ($encoding == '') {
            $encoding = $this->Encoding;
        }
        $result .= $this->textLine('--' . $boundary);
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
        $result .= $this->LE;
        // RFC1341 part 5 says 7bit is assumed if not specified
        if ($encoding != '7bit') {
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
        }
        $result .= $this->LE;

        return $result;
    }

    /**
     * Return the end of a message boundary.
     * @access protected
     * @param string $boundary
     * @return string
     */
    protected function endBoundary($boundary)
    {
        return $this->LE . '--' . $boundary . '--' . $this->LE;
    }

    /**
     * Set the message type.
     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
     * @access protected
     * @return void
     */
    protected function setMessageType()
    {
        $type = array();
        if ($this->alternativeExists()) {
            $type[] = 'alt';
        }
        if ($this->inlineImageExists()) {
            $type[] = 'inline';
        }
        if ($this->attachmentExists()) {
            $type[] = 'attach';
        }
        $this->message_type = implode('_', $type);
        if ($this->message_type == '') {
            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
            $this->message_type = 'plain';
        }
    }

    /**
     * Format a header line.
     * @access public
     * @param string $name
     * @param string $value
     * @return string
     */
    public function headerLine($name, $value)
    {
        return $name . ': ' . $value . $this->LE;
    }

    /**
     * Return a formatted mail line.
     * @access public
     * @param string $value
     * @return string
     */
    public function textLine($value)
    {
        return $value . $this->LE;
    }

    /**
     * Add an attachment from a path on the filesystem.
     * Never use a user-supplied path to a file!
     * Returns false if the file could not be found or read.
     * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
     * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
     * @param string $path Path to the attachment.
     * @param string $name Overrides the attachment name.
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type File extension (MIME) type.
     * @param string $disposition Disposition to use
     * @throws phpmailerException
     * @return boolean
     */
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
    {
        try {
            if (!self::isPermittedPath($path) or !@is_file($path)) {
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
            }

            // If a MIME type is not specified, try to work it out from the file name
            if ($type == '') {
                $type = self::filenameToType($path);
            }

            $filename = basename($path);
            if ($name == '') {
                $name = $filename;
            }

            $this->attachment[] = array(
                0 => $path,
                1 => $filename,
                2 => $name,
                3 => $encoding,
                4 => $type,
                5 => false, // isStringAttachment
                6 => $disposition,
                7 => 0
            );

        } catch (phpmailerException $exc) {
            $this->setError($exc->getMessage());
            $this->edebug($exc->getMessage());
            if ($this->exceptions) {
                throw $exc;
            }
            return false;
        }
        return true;
    }

    /**
     * Return the array of attachments.
     * @return array
     */
    public function getAttachments()
    {
        return $this->attachment;
    }

    /**
     * Attach all file, string, and binary attachments to the message.
     * Returns an empty string on failure.
     * @access protected
     * @param string $disposition_type
     * @param string $boundary
     * @return string
     */
    protected function attachAll($disposition_type, $boundary)
    {
        // Return text of body
        $mime = array();
        $cidUniq = array();
        $incl = array();

        // Add all attachments
        foreach ($this->attachment as $attachment) {
            // Check if it is a valid disposition_filter
            if ($attachment[6] == $disposition_type) {
                // Check for string attachment
                $string = '';
                $path = '';
                $bString = $attachment[5];
                if ($bString) {
                    $string = $attachment[0];
                } else {
                    $path = $attachment[0];
                }

                $inclhash = md5(serialize($attachment));
                if (in_array($inclhash, $incl)) {
                    continue;
                }
                $incl[] = $inclhash;
                $name = $attachment[2];
                $encoding = $attachment[3];
                $type = $attachment[4];
                $disposition = $attachment[6];
                $cid = $attachment[7];
                if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
                    continue;
                }
                $cidUniq[$cid] = true;

                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
                //Only include a filename property if we have one
                if (!empty($name)) {
                    $mime[] = sprintf(
                        'Content-Type: %s; name="%s"%s',
                        $type,
                        $this->encodeHeader($this->secureHeader($name)),
                        $this->LE
                    );
                } else {
                    $mime[] = sprintf(
                        'Content-Type: %s%s',
                        $type,
                        $this->LE
                    );
                }
                // RFC1341 part 5 says 7bit is assumed if not specified
                if ($encoding != '7bit') {
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
                }

                if ($disposition == 'inline') {
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
                }

                // If a filename contains any of these chars, it should be quoted,
                // but not otherwise: RFC2183 & RFC2045 5.1
                // Fixes a warning in IETF's msglint MIME checker
                // Allow for bypassing the Content-Disposition header totally
                if (!(empty($disposition))) {
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
                        $mime[] = sprintf(
                            'Content-Disposition: %s; filename="%s"%s',
                            $disposition,
                            $encoded_name,
                            $this->LE . $this->LE
                        );
                    } else {
                        if (!empty($encoded_name)) {
                            $mime[] = sprintf(
                                'Content-Disposition: %s; filename=%s%s',
                                $disposition,
                                $encoded_name,
                                $this->LE . $this->LE
                            );
                        } else {
                            $mime[] = sprintf(
                                'Content-Disposition: %s%s',
                                $disposition,
                                $this->LE . $this->LE
                            );
                        }
                    }
                } else {
                    $mime[] = $this->LE;
                }

                // Encode as string attachment
                if ($bString) {
                    $mime[] = $this->encodeString($string, $encoding);
                    if ($this->isError()) {
                        return '';
                    }
                    $mime[] = $this->LE . $this->LE;
                } else {
                    $mime[] = $this->encodeFile($path, $encoding);
                    if ($this->isError()) {
                        return '';
                    }
                    $mime[] = $this->LE . $this->LE;
                }
            }
        }

        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);

        return implode('', $mime);
    }

    /**
     * Encode a file attachment in requested format.
     * Returns an empty string on failure.
     * @param string $path The full path to the file
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     * @throws phpmailerException
     * @access protected
     * @return string
     */
    protected function encodeFile($path, $encoding = 'base64')
    {
        try {
            if (!self::isPermittedPath($path) or !file_exists($path)) {
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
            }
            $magic_quotes = get_magic_quotes_runtime();
            if ($magic_quotes) {
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
                    set_magic_quotes_runtime(false);
                } else {
                    //Doesn't exist in PHP 5.4, but we don't need to check because
                    //get_magic_quotes_runtime always returns false in 5.4+
                    //so it will never get here
                    ini_set('magic_quotes_runtime', false);
                }
            }
            $file_buffer = file_get_contents($path);
            $file_buffer = $this->encodeString($file_buffer, $encoding);
            if ($magic_quotes) {
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
                    set_magic_quotes_runtime($magic_quotes);
                } else {
                    ini_set('magic_quotes_runtime', $magic_quotes);
                }
            }
            return $file_buffer;
        } catch (Exception $exc) {
            $this->setError($exc->getMessage());
            return '';
        }
    }

    /**
     * Encode a string in requested format.
     * Returns an empty string on failure.
     * @param string $str The text to encode
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     * @access public
     * @return string
     */
    public function encodeString($str, $encoding = 'base64')
    {
        $encoded = '';
        switch (strtolower($encoding)) {
            case 'base64':
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
                break;
            case '7bit':
            case '8bit':
                $encoded = $this->fixEOL($str);
                // Make sure it ends with a line break
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
                    $encoded .= $this->LE;
                }
                break;
            case 'binary':
                $encoded = $str;
                break;
            case 'quoted-printable':
                $encoded = $this->encodeQP($str);
                break;
            default:
                $this->setError($this->lang('encoding') . $encoding);
                break;
        }
        return $encoded;
    }

    /**
     * Encode a header string optimally.
     * Picks shortest of Q, B, quoted-printable or none.
     * @access public
     * @param string $str
     * @param string $position
     * @return string
     */
    public function encodeHeader($str, $position = 'text')
    {
        $matchcount = 0;
        switch (strtolower($position)) {
            case 'phrase':
                if (!preg_match('/[\200-\377]/', $str)) {
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
                        return ($encoded);
                    } else {
                        return ("\"$encoded\"");
                    }
                }
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
                break;
            /** @noinspection PhpMissingBreakStatementInspection */
            case 'comment':
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
                // Intentional fall-through
            case 'text':
            default:
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
                break;
        }

        //There are no chars that need encoding
        if ($matchcount == 0) {
            return ($str);
        }

        $maxlen = 75 - 7 - strlen($this->CharSet);
        // Try to select the encoding which should produce the shortest output
        if ($matchcount > strlen($str) / 3) {
            // More than a third of the content will need encoding, so B encoding will be most efficient
            $encoding = 'B';
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
                // Use a custom function which correctly encodes and wraps long
                // multibyte strings without breaking lines within a character
                $encoded = $this->base64EncodeWrapMB($str, "\n");
            } else {
                $encoded = base64_encode($str);
                $maxlen -= $maxlen % 4;
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
            }
        } else {
            $encoding = 'Q';
            $encoded = $this->encodeQ($str, $position);
            $encoded = $this->wrapText($encoded, $maxlen, true);
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
        }

        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
        $encoded = trim(str_replace("\n", $this->LE, $encoded));

        return $encoded;
    }

    /**
     * Check if a string contains multi-byte characters.
     * @access public
     * @param string $str multi-byte text to wrap encode
     * @return boolean
     */
    public function hasMultiBytes($str)
    {
        if (function_exists('mb_strlen')) {
            return (strlen($str) > mb_strlen($str, $this->CharSet));
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
            return false;
        }
    }

    /**
     * Does a string contain any 8-bit chars (in any charset)?
     * @param string $text
     * @return boolean
     */
    public function has8bitChars($text)
    {
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
    }

    /**
     * Encode and wrap long multibyte strings for mail headers
     * without breaking lines within a character.
     * Adapted from a function by paravoid
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
     * @access public
     * @param string $str multi-byte text to wrap encode
     * @param string $linebreak string to use as linefeed/end-of-line
     * @return string
     */
    public function base64EncodeWrapMB($str, $linebreak = null)
    {
        $start = '=?' . $this->CharSet . '?B?';
        $end = '?=';
        $encoded = '';
        if ($linebreak === null) {
            $linebreak = $this->LE;
        }

        $mb_length = mb_strlen($str, $this->CharSet);
        // Each line must have length <= 75, including $start and $end
        $length = 75 - strlen($start) - strlen($end);
        // Average multi-byte ratio
        $ratio = $mb_length / strlen($str);
        // Base64 has a 4:3 ratio
        $avgLength = floor($length * $ratio * .75);

        for ($i = 0; $i < $mb_length; $i += $offset) {
            $lookBack = 0;
            do {
                $offset = $avgLength - $lookBack;
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
                $chunk = base64_encode($chunk);
                $lookBack++;
            } while (strlen($chunk) > $length);
            $encoded .= $chunk . $linebreak;
        }

        // Chomp the last linefeed
        $encoded = substr($encoded, 0, -strlen($linebreak));
        return $encoded;
    }

    /**
     * Encode a string in quoted-printable format.
     * According to RFC2045 section 6.7.
     * @access public
     * @param string $string The text to encode
     * @param integer $line_max Number of chars allowed on a line before wrapping
     * @return string
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
     */
    public function encodeQP($string, $line_max = 76)
    {
        // Use native function if it's available (>= PHP5.3)
        if (function_exists('quoted_printable_encode')) {
            return quoted_printable_encode($string);
        }
        // Fall back to a pure PHP implementation
        $string = str_replace(
            array('%20', '%0D%0A.', '%0D%0A', '%'),
            array(' ', "\r\n=2E", "\r\n", '='),
            rawurlencode($string)
        );
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
    }

    /**
     * Backward compatibility wrapper for an old QP encoding function that was removed.
     * @see PHPMailer::encodeQP()
     * @access public
     * @param string $string
     * @param integer $line_max
     * @param boolean $space_conv
     * @return string
     * @deprecated Use encodeQP instead.
     */
    public function encodeQPphp(
        $string,
        $line_max = 76,
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
    ) {
        return $this->encodeQP($string, $line_max);
    }

    /**
     * Encode a string using Q encoding.
     * @link http://tools.ietf.org/html/rfc2047
     * @param string $str the text to encode
     * @param string $position Where the text is going to be used, see the RFC for what that means
     * @access public
     * @return string
     */
    public function encodeQ($str, $position = 'text')
    {
        // There should not be any EOL in the string
        $pattern = '';
        $encoded = str_replace(array("\r", "\n"), '', $str);
        switch (strtolower($position)) {
            case 'phrase':
                // RFC 2047 section 5.3
                $pattern = '^A-Za-z0-9!*+\/ -';
                break;
            /** @noinspection PhpMissingBreakStatementInspection */
            case 'comment':
                // RFC 2047 section 5.2
                $pattern = '\(\)"';
                // intentional fall-through
                // for this reason we build the $pattern without including delimiters and []
            case 'text':
            default:
                // RFC 2047 section 5.1
                // Replace every high ascii, control, =, ? and _ characters
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
                break;
        }
        $matches = array();
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
            // If the string contains an '=', make sure it's the first thing we replace
            // so as to avoid double-encoding
            $eqkey = array_search('=', $matches[0]);
            if (false !== $eqkey) {
                unset($matches[0][$eqkey]);
                array_unshift($matches[0], '=');
            }
            foreach (array_unique($matches[0]) as $char) {
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
            }
        }
        // Replace every spaces to _ (more readable than =20)
        return str_replace(' ', '_', $encoded);
    }

    /**
     * Add a string or binary attachment (non-filesystem).
     * This method can be used to attach ascii or binary data,
     * such as a BLOB record from a database.
     * @param string $string String attachment data.
     * @param string $filename Name of the attachment.
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type File extension (MIME) type.
     * @param string $disposition Disposition to use
     * @return void
     */
    public function addStringAttachment(
        $string,
        $filename,
        $encoding = 'base64',
        $type = '',
        $disposition = 'attachment'
    ) {
        // If a MIME type is not specified, try to work it out from the file name
        if ($type == '') {
            $type = self::filenameToType($filename);
        }
        // Append to $attachment array
        $this->attachment[] = array(
            0 => $string,
            1 => $filename,
            2 => basename($filename),
            3 => $encoding,
            4 => $type,
            5 => true, // isStringAttachment
            6 => $disposition,
            7 => 0
        );
    }

    /**
     * Add an embedded (inline) attachment from a file.
     * This can include images, sounds, and just about any other document type.
     * These differ from 'regular' attachments in that they are intended to be
     * displayed inline with the message, not just attached for download.
     * This is used in HTML messages that embed the images
     * the HTML refers to using the $cid value.
     * Never use a user-supplied path to a file!
     * @param string $path Path to the attachment.
     * @param string $cid Content ID of the attachment; Use this to reference
     *        the content when using an embedded image in HTML.
     * @param string $name Overrides the attachment name.
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type File MIME type.
     * @param string $disposition Disposition to use
     * @return boolean True on successfully adding an attachment
     */
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
    {
        if (!self::isPermittedPath($path) or !@is_file($path)) {
            $this->setError($this->lang('file_access') . $path);
            return false;
        }

        // If a MIME type is not specified, try to work it out from the file name
        if ($type == '') {
            $type = self::filenameToType($path);
        }

        $filename = basename($path);
        if ($name == '') {
            $name = $filename;
        }

        // Append to $attachment array
        $this->attachment[] = array(
            0 => $path,
            1 => $filename,
            2 => $name,
            3 => $encoding,
            4 => $type,
            5 => false, // isStringAttachment
            6 => $disposition,
            7 => $cid
        );
        return true;
    }

    /**
     * Add an embedded stringified attachment.
     * This can include images, sounds, and just about any other document type.
     * Be sure to set the $type to an image type for images:
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
     * @param string $string The attachment binary data.
     * @param string $cid Content ID of the attachment; Use this to reference
     *        the content when using an embedded image in HTML.
     * @param string $name
     * @param string $encoding File encoding (see $Encoding).
     * @param string $type MIME type.
     * @param string $disposition Disposition to use
     * @return boolean True on successfully adding an attachment
     */
    public function addStringEmbeddedImage(
        $string,
        $cid,
        $name = '',
        $encoding = 'base64',
        $type = '',
        $disposition = 'inline'
    ) {
        // If a MIME type is not specified, try to work it out from the name
        if ($type == '' and !empty($name)) {
            $type = self::filenameToType($name);
        }

        // Append to $attachment array
        $this->attachment[] = array(
            0 => $string,
            1 => $name,
            2 => $name,
            3 => $encoding,
            4 => $type,
            5 => true, // isStringAttachment
            6 => $disposition,
            7 => $cid
        );
        return true;
    }

    /**
     * Check if an inline attachment is present.
     * @access public
     * @return boolean
     */
    public function inlineImageExists()
    {
        foreach ($this->attachment as $attachment) {
            if ($attachment[6] == 'inline') {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if an attachment (non-inline) is present.
     * @return boolean
     */
    public function attachmentExists()
    {
        foreach ($this->attachment as $attachment) {
            if ($attachment[6] == 'attachment') {
                return true;
            }
        }
        return false;
    }

    /**
     * Check if this message has an alternative body set.
     * @return boolean
     */
    public function alternativeExists()
    {
        return !empty($this->AltBody);
    }

    /**
     * Clear queued addresses of given kind.
     * @access protected
     * @param string $kind 'to', 'cc', or 'bcc'
     * @return void
     */
    public function clearQueuedAddresses($kind)
    {
        $RecipientsQueue = $this->RecipientsQueue;
        foreach ($RecipientsQueue as $address => $params) {
            if ($params[0] == $kind) {
                unset($this->RecipientsQueue[$address]);
            }
        }
    }

    /**
     * Clear all To recipients.
     * @return void
     */
    public function clearAddresses()
    {
        foreach ($this->to as $to) {
            unset($this->all_recipients[strtolower($to[0])]);
        }
        $this->to = array();
        $this->clearQueuedAddresses('to');
    }

    /**
     * Clear all CC recipients.
     * @return void
     */
    public function clearCCs()
    {
        foreach ($this->cc as $cc) {
            unset($this->all_recipients[strtolower($cc[0])]);
        }
        $this->cc = array();
        $this->clearQueuedAddresses('cc');
    }

    /**
     * Clear all BCC recipients.
     * @return void
     */
    public function clearBCCs()
    {
        foreach ($this->bcc as $bcc) {
            unset($this->all_recipients[strtolower($bcc[0])]);
        }
        $this->bcc = array();
        $this->clearQueuedAddresses('bcc');
    }

    /**
     * Clear all ReplyTo recipients.
     * @return void
     */
    public function clearReplyTos()
    {
        $this->ReplyTo = array();
        $this->ReplyToQueue = array();
    }

    /**
     * Clear all recipient types.
     * @return void
     */
    public function clearAllRecipients()
    {
        $this->to = array();
        $this->cc = array();
        $this->bcc = array();
        $this->all_recipients = array();
        $this->RecipientsQueue = array();
    }

    /**
     * Clear all filesystem, string, and binary attachments.
     * @return void
     */
    public function clearAttachments()
    {
        $this->attachment = array();
    }

    /**
     * Clear all custom headers.
     * @return void
     */
    public function clearCustomHeaders()
    {
        $this->CustomHeader = array();
    }

    /**
     * Add an error message to the error container.
     * @access protected
     * @param string $msg
     * @return void
     */
    protected function setError($msg)
    {
        $this->error_count++;
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
            $lasterror = $this->smtp->getError();
            if (!empty($lasterror['error'])) {
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
                if (!empty($lasterror['detail'])) {
                    $msg .= ' Detail: '. $lasterror['detail'];
                }
                if (!empty($lasterror['smtp_code'])) {
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
                }
                if (!empty($lasterror['smtp_code_ex'])) {
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
                }
            }
        }
        $this->ErrorInfo = $msg;
    }

    /**
     * Return an RFC 822 formatted date.
     * @access public
     * @return string
     * @static
     */
    public static function rfcDate()
    {
        // Set the time zone to whatever the default is to avoid 500 errors
        // Will default to UTC if it's not set properly in php.ini
        date_default_timezone_set(@date_default_timezone_get());
        return date('D, j M Y H:i:s O');
    }

    /**
     * Get the server hostname.
     * Returns 'localhost.localdomain' if unknown.
     * @access protected
     * @return string
     */
    protected function serverHostname()
    {
        $result = 'localhost.localdomain';
        if (!empty($this->Hostname)) {
            $result = $this->Hostname;
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
            $result = $_SERVER['SERVER_NAME'];
        } elseif (function_exists('gethostname') && gethostname() !== false) {
            $result = gethostname();
        } elseif (php_uname('n') !== false) {
            $result = php_uname('n');
        }
        return $result;
    }

    /**
     * Get an error message in the current language.
     * @access protected
     * @param string $key
     * @return string
     */
    protected function lang($key)
    {
        if (count($this->language) < 1) {
            $this->setLanguage('en'); // set the default language
        }

        if (array_key_exists($key, $this->language)) {
            if ($key == 'smtp_connect_failed') {
                //Include a link to troubleshooting docs on SMTP connection failure
                //this is by far the biggest cause of support questions
                //but it's usually not PHPMailer's fault.
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
            }
            return $this->language[$key];
        } else {
            //Return the key as a fallback
            return $key;
        }
    }

    /**
     * Check if an error occurred.
     * @access public
     * @return boolean True if an error did occur.
     */
    public function isError()
    {
        return ($this->error_count > 0);
    }

    /**
     * Ensure consistent line endings in a string.
     * Changes every end of line from CRLF, CR or LF to $this->LE.
     * @access public
     * @param string $str String to fixEOL
     * @return string
     */
    public function fixEOL($str)
    {
        // Normalise to \n
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
        // Now convert LE as needed
        if ($this->LE !== "\n") {
            $nstr = str_replace("\n", $this->LE, $nstr);
        }
        return $nstr;
    }

    /**
     * Add a custom header.
     * $name value can be overloaded to contain
     * both header name and value (name:value)
     * @access public
     * @param string $name Custom header name
     * @param string $value Header value
     * @return void
     */
    public function addCustomHeader($name, $value = null)
    {
        if ($value === null) {
            // Value passed in as name:value
            $this->CustomHeader[] = explode(':', $name, 2);
        } else {
            $this->CustomHeader[] = array($name, $value);
        }
    }

    /**
     * Returns all custom headers.
     * @return array
     */
    public function getCustomHeaders()
    {
        return $this->CustomHeader;
    }

    /**
     * Create a message body from an HTML string.
     * Automatically inlines images and creates a plain-text version by converting the HTML,
     * overwriting any existing values in Body and AltBody.
     * Do not source $message content from user input!
     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
     * will look for an image file in $basedir/images/a.png and convert it to inline.
     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
     * @access public
     * @param string $message HTML message string
     * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
     *    or your own custom converter @see PHPMailer::html2text()
     * @return string $message The transformed message Body
     */
    public function msgHTML($message, $basedir = '', $advanced = false)
    {
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
        if (array_key_exists(2, $images)) {
            if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
                // Ensure $basedir has a trailing /
                $basedir .= '/';
            }
            foreach ($images[2] as $imgindex => $url) {
                // Convert data URIs into embedded images
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
                    $data = substr($url, strpos($url, ','));
                    if ($match[2]) {
                        $data = base64_decode($data);
                    } else {
                        $data = rawurldecode($data);
                    }
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
                        $message = str_replace(
                            $images[0][$imgindex],
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
                            $message
                        );
                    }
                    continue;
                }
                if (
                    // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
                    !empty($basedir)
                    // Ignore URLs containing parent dir traversal (..)
                    && (strpos($url, '..') === false)
                    // Do not change urls that are already inline images
                    && substr($url, 0, 4) !== 'cid:'
                    // Do not change absolute URLs, including anonymous protocol
                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
                ) {
                    $filename = basename($url);
                    $directory = dirname($url);
                    if ($directory == '.') {
                        $directory = '';
                    }
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
                        $directory .= '/';
                    }
                    if ($this->addEmbeddedImage(
                        $basedir . $directory . $filename,
                        $cid,
                        $filename,
                        'base64',
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
                    )
                    ) {
                        $message = preg_replace(
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
                            $message
                        );
                    }
                }
            }
        }
        $this->isHTML(true);
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
        $this->Body = $this->normalizeBreaks($message);
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
        if (!$this->alternativeExists()) {
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
                self::CRLF . self::CRLF;
        }
        return $this->Body;
    }

    /**
     * Convert an HTML string into plain text.
     * This is used by msgHTML().
     * Note - older versions of this function used a bundled advanced converter
     * which was been removed for license reasons in #232.
     * Example usage:
     * <code>
     * // Use default conversion
     * $plain = $mail->html2text($html);
     * // Use your own custom converter
     * $plain = $mail->html2text($html, function($html) {
     *     $converter = new MyHtml2text($html);
     *     return $converter->get_text();
     * });
     * </code>
     * @param string $html The HTML text to convert
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
     *   or provide your own callable for custom conversion.
     * @return string
     */
    public function html2text($html, $advanced = false)
    {
        if (is_callable($advanced)) {
            return call_user_func($advanced, $html);
        }
        return html_entity_decode(
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
            ENT_QUOTES,
            $this->CharSet
        );
    }

    /**
     * Get the MIME type for a file extension.
     * @param string $ext File extension
     * @access public
     * @return string MIME type of file.
     * @static
     */
    public static function _mime_types($ext = '')
    {
        $mimes = array(
            'xl'    => 'application/excel',
            'js'    => 'application/javascript',
            'hqx'   => 'application/mac-binhex40',
            'cpt'   => 'application/mac-compactpro',
            'bin'   => 'application/macbinary',
            'doc'   => 'application/msword',
            'word'  => 'application/msword',
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
            'class' => 'application/octet-stream',
            'dll'   => 'application/octet-stream',
            'dms'   => 'application/octet-stream',
            'exe'   => 'application/octet-stream',
            'lha'   => 'application/octet-stream',
            'lzh'   => 'application/octet-stream',
            'psd'   => 'application/octet-stream',
            'sea'   => 'application/octet-stream',
            'so'    => 'application/octet-stream',
            'oda'   => 'application/oda',
            'pdf'   => 'application/pdf',
            'ai'    => 'application/postscript',
            'eps'   => 'application/postscript',
            'ps'    => 'application/postscript',
            'smi'   => 'application/smil',
            'smil'  => 'application/smil',
            'mif'   => 'application/vnd.mif',
            'xls'   => 'application/vnd.ms-excel',
            'ppt'   => 'application/vnd.ms-powerpoint',
            'wbxml' => 'application/vnd.wap.wbxml',
            'wmlc'  => 'application/vnd.wap.wmlc',
            'dcr'   => 'application/x-director',
            'dir'   => 'application/x-director',
            'dxr'   => 'application/x-director',
            'dvi'   => 'application/x-dvi',
            'gtar'  => 'application/x-gtar',
            'php3'  => 'application/x-httpd-php',
            'php4'  => 'application/x-httpd-php',
            'php'   => 'application/x-httpd-php',
            'phtml' => 'application/x-httpd-php',
            'phps'  => 'application/x-httpd-php-source',
            'swf'   => 'application/x-shockwave-flash',
            'sit'   => 'application/x-stuffit',
            'tar'   => 'application/x-tar',
            'tgz'   => 'application/x-tar',
            'xht'   => 'application/xhtml+xml',
            'xhtml' => 'application/xhtml+xml',
            'zip'   => 'application/zip',
            'mid'   => 'audio/midi',
            'midi'  => 'audio/midi',
            'mp2'   => 'audio/mpeg',
            'mp3'   => 'audio/mpeg',
            'mpga'  => 'audio/mpeg',
            'aif'   => 'audio/x-aiff',
            'aifc'  => 'audio/x-aiff',
            'aiff'  => 'audio/x-aiff',
            'ram'   => 'audio/x-pn-realaudio',
            'rm'    => 'audio/x-pn-realaudio',
            'rpm'   => 'audio/x-pn-realaudio-plugin',
            'ra'    => 'audio/x-realaudio',
            'wav'   => 'audio/x-wav',
            'bmp'   => 'image/bmp',
            'gif'   => 'image/gif',
            'jpeg'  => 'image/jpeg',
            'jpe'   => 'image/jpeg',
            'jpg'   => 'image/jpeg',
            'png'   => 'image/png',
            'tiff'  => 'image/tiff',
            'tif'   => 'image/tiff',
            'eml'   => 'message/rfc822',
            'css'   => 'text/css',
            'html'  => 'text/html',
            'htm'   => 'text/html',
            'shtml' => 'text/html',
            'log'   => 'text/plain',
            'text'  => 'text/plain',
            'txt'   => 'text/plain',
            'rtx'   => 'text/richtext',
            'rtf'   => 'text/rtf',
            'vcf'   => 'text/vcard',
            'vcard' => 'text/vcard',
            'xml'   => 'text/xml',
            'xsl'   => 'text/xml',
            'mpeg'  => 'video/mpeg',
            'mpe'   => 'video/mpeg',
            'mpg'   => 'video/mpeg',
            'mov'   => 'video/quicktime',
            'qt'    => 'video/quicktime',
            'rv'    => 'video/vnd.rn-realvideo',
            'avi'   => 'video/x-msvideo',
            'movie' => 'video/x-sgi-movie'
        );
        if (array_key_exists(strtolower($ext), $mimes)) {
            return $mimes[strtolower($ext)];
        }
        return 'application/octet-stream';
    }

    /**
     * Map a file name to a MIME type.
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
     * @param string $filename A file name or full path, does not need to exist as a file
     * @return string
     * @static
     */
    public static function filenameToType($filename)
    {
        // In case the path is a URL, strip any query string before getting extension
        $qpos = strpos($filename, '?');
        if (false !== $qpos) {
            $filename = substr($filename, 0, $qpos);
        }
        $pathinfo = self::mb_pathinfo($filename);
        return self::_mime_types($pathinfo['extension']);
    }

    /**
     * Multi-byte-safe pathinfo replacement.
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
     * Works similarly to the one in PHP >= 5.2.0
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
     * @param string $path A filename or path, does not need to exist as a file
     * @param integer|string $options Either a PATHINFO_* constant,
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
     * @return string|array
     * @static
     */
    public static function mb_pathinfo($path, $options = null)
    {
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
        $pathinfo = array();
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
            if (array_key_exists(1, $pathinfo)) {
                $ret['dirname'] = $pathinfo[1];
            }
            if (array_key_exists(2, $pathinfo)) {
                $ret['basename'] = $pathinfo[2];
            }
            if (array_key_exists(5, $pathinfo)) {
                $ret['extension'] = $pathinfo[5];
            }
            if (array_key_exists(3, $pathinfo)) {
                $ret['filename'] = $pathinfo[3];
            }
        }
        switch ($options) {
            case PATHINFO_DIRNAME:
            case 'dirname':
                return $ret['dirname'];
            case PATHINFO_BASENAME:
            case 'basename':
                return $ret['basename'];
            case PATHINFO_EXTENSION:
            case 'extension':
                return $ret['extension'];
            case PATHINFO_FILENAME:
            case 'filename':
                return $ret['filename'];
            default:
                return $ret;
        }
    }

    /**
     * Set or reset instance properties.
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
     * harder to debug than setting properties directly.
     * Usage Example:
     * `$mail->set('SMTPSecure', 'tls');`
     *   is the same as:
     * `$mail->SMTPSecure = 'tls';`
     * @access public
     * @param string $name The property name to set
     * @param mixed $value The value to set the property to
     * @return boolean
     * @TODO Should this not be using the __set() magic function?
     */
    public function set($name, $value = '')
    {
        if (property_exists($this, $name)) {
            $this->$name = $value;
            return true;
        } else {
            $this->setError($this->lang('variable_set') . $name);
            return false;
        }
    }

    /**
     * Strip newlines to prevent header injection.
     * @access public
     * @param string $str
     * @return string
     */
    public function secureHeader($str)
    {
        return trim(str_replace(array("\r", "\n"), '', $str));
    }

    /**
     * Normalize line breaks in a string.
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
     * @param string $text
     * @param string $breaktype What kind of line break to use, defaults to CRLF
     * @return string
     * @access public
     * @static
     */
    public static function normalizeBreaks($text, $breaktype = "\r\n")
    {
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
    }

    /**
     * Set the public and private key files and password for S/MIME signing.
     * @access public
     * @param string $cert_filename
     * @param string $key_filename
     * @param string $key_pass Password for private key
     * @param string $extracerts_filename Optional path to chain certificate
     */
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
    {
        $this->sign_cert_file = $cert_filename;
        $this->sign_key_file = $key_filename;
        $this->sign_key_pass = $key_pass;
        $this->sign_extracerts_file = $extracerts_filename;
    }

    /**
     * Quoted-Printable-encode a DKIM header.
     * @access public
     * @param string $txt
     * @return string
     */
    public function DKIM_QP($txt)
    {
        $line = '';
        for ($i = 0; $i < strlen($txt); $i++) {
            $ord = ord($txt[$i]);
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
                $line .= $txt[$i];
            } else {
                $line .= '=' . sprintf('%02X', $ord);
            }
        }
        return $line;
    }

    /**
     * Generate a DKIM signature.
     * @access public
     * @param string $signHeader
     * @throws phpmailerException
     * @return string The DKIM signature value
     */
    public function DKIM_Sign($signHeader)
    {
        if (!defined('PKCS7_TEXT')) {
            if ($this->exceptions) {
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
            }
            return '';
        }
        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
        if ('' != $this->DKIM_passphrase) {
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
        } else {
            $privKey = openssl_pkey_get_private($privKeyStr);
        }
        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
        //@link http://stackoverflow.com/a/11117338/333340
        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
            in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
                openssl_pkey_free($privKey);
                return base64_encode($signature);
            }
        } else {
            $pinfo = openssl_pkey_get_details($privKey);
            $hash = hash('sha256', $signHeader);
            //'Magic' constant for SHA256 from RFC3447
            //@link https://tools.ietf.org/html/rfc3447#page-43
            $t = '3031300d060960864801650304020105000420' . $hash;
            $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
            $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);

            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
                openssl_pkey_free($privKey);
                return base64_encode($signature);
            }
        }
        openssl_pkey_free($privKey);
        return '';
    }

    /**
     * Generate a DKIM canonicalization header.
     * @access public
     * @param string $signHeader Header
     * @return string
     */
    public function DKIM_HeaderC($signHeader)
    {
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
        $lines = explode("\r\n", $signHeader);
        foreach ($lines as $key => $line) {
            list($heading, $value) = explode(':', $line, 2);
            $heading = strtolower($heading);
            $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
        }
        $signHeader = implode("\r\n", $lines);
        return $signHeader;
    }

    /**
     * Generate a DKIM canonicalization body.
     * @access public
     * @param string $body Message Body
     * @return string
     */
    public function DKIM_BodyC($body)
    {
        if ($body == '') {
            return "\r\n";
        }
        // stabilize line endings
        $body = str_replace("\r\n", "\n", $body);
        $body = str_replace("\n", "\r\n", $body);
        // END stabilize line endings
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
            $body = substr($body, 0, strlen($body) - 2);
        }
        return $body;
    }

    /**
     * Create the DKIM header and body in a new message header.
     * @access public
     * @param string $headers_line Header lines
     * @param string $subject Subject
     * @param string $body Body
     * @return string
     */
    public function DKIM_Add($headers_line, $subject, $body)
    {
        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
        $DKIMquery = 'dns/txt'; // Query method
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
        $subject_header = "Subject: $subject";
        $headers = explode($this->LE, $headers_line);
        $from_header = '';
        $to_header = '';
        $date_header = '';
        $current = '';
        foreach ($headers as $header) {
            if (strpos($header, 'From:') === 0) {
                $from_header = $header;
                $current = 'from_header';
            } elseif (strpos($header, 'To:') === 0) {
                $to_header = $header;
                $current = 'to_header';
            } elseif (strpos($header, 'Date:') === 0) {
                $date_header = $header;
                $current = 'date_header';
            } else {
                if (!empty($$current) && strpos($header, ' =?') === 0) {
                    $$current .= $header;
                } else {
                    $current = '';
                }
            }
        }
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
        $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
        $subject = str_replace(
            '|',
            '=7C',
            $this->DKIM_QP($subject_header)
        ); // Copied header fields (dkim-quoted-printable)
        $body = $this->DKIM_BodyC($body);
        $DKIMlen = strlen($body); // Length of body
        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
        if ('' == $this->DKIM_identity) {
            $ident = '';
        } else {
            $ident = ' i=' . $this->DKIM_identity . ';';
        }
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
            $DKIMsignatureType . '; q=' .
            $DKIMquery . '; l=' .
            $DKIMlen . '; s=' .
            $this->DKIM_selector .
            ";\r\n" .
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
            "\th=From:To:Date:Subject;\r\n" .
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
            "\tz=$from\r\n" .
            "\t|$to\r\n" .
            "\t|$date\r\n" .
            "\t|$subject;\r\n" .
            "\tbh=" . $DKIMb64 . ";\r\n" .
            "\tb=";
        $toSign = $this->DKIM_HeaderC(
            $from_header . "\r\n" .
            $to_header . "\r\n" .
            $date_header . "\r\n" .
            $subject_header . "\r\n" .
            $dkimhdrs
        );
        $signed = $this->DKIM_Sign($toSign);
        return $dkimhdrs . $signed . "\r\n";
    }

    /**
     * Detect if a string contains a line longer than the maximum line length allowed.
     * @param string $str
     * @return boolean
     * @static
     */
    public static function hasLineLongerThanMax($str)
    {
        //+2 to include CRLF line break for a 1000 total
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
    }

    /**
     * Allows for public read access to 'to' property.
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     * @access public
     * @return array
     */
    public function getToAddresses()
    {
        return $this->to;
    }

    /**
     * Allows for public read access to 'cc' property.
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     * @access public
     * @return array
     */
    public function getCcAddresses()
    {
        return $this->cc;
    }

    /**
     * Allows for public read access to 'bcc' property.
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     * @access public
     * @return array
     */
    public function getBccAddresses()
    {
        return $this->bcc;
    }

    /**
     * Allows for public read access to 'ReplyTo' property.
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     * @access public
     * @return array
     */
    public function getReplyToAddresses()
    {
        return $this->ReplyTo;
    }

    /**
     * Allows for public read access to 'all_recipients' property.
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     * @access public
     * @return array
     */
    public function getAllRecipientAddresses()
    {
        return $this->all_recipients;
    }

    /**
     * Perform a callback.
     * @param boolean $isSent
     * @param array $to
     * @param array $cc
     * @param array $bcc
     * @param string $subject
     * @param string $body
     * @param string $from
     */
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
    {
        if (!empty($this->action_function) && is_callable($this->action_function)) {
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
            call_user_func_array($this->action_function, $params);
        }
    }
}

/**
 * PHPMailer exception handler
 * @package PHPMailer
 */
class phpmailerException extends Exception
{
    /**
     * Prettify error message output
     * @return string
     */
    public function errorMessage()
    {
        $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
        return $errorMsg;
    }
}
vendor/phpmailer/phpmailer/VERSION000064400000000006152177723700013062 0ustar005.2.27vendor/phpmailer/phpmailer/class.phpmaileroauthgoogle.php000064400000004640152177723700020056 0ustar00<?php
/**
 * PHPMailer - PHP email creation and transport class.
 * PHP Version 5.4
 * @package PHPMailer
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
 * @author Brent R. Matzelle (original founder)
 * @copyright 2012 - 2014 Marcus Bointon
 * @copyright 2010 - 2012 Jim Jagielski
 * @copyright 2004 - 2009 Andy Prevost
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @note This program is distributed in the hope that it will be useful - WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 */

/**
 * PHPMailerOAuthGoogle - Wrapper for League OAuth2 Google provider.
 * @package PHPMailer
 * @author @sherryl4george
 * @author Marcus Bointon (@Synchro) <phpmailer@synchromedia.co.uk>
 * @link https://github.com/thephpleague/oauth2-client
 */
class PHPMailerOAuthGoogle
{
    private $oauthUserEmail = '';
    private $oauthRefreshToken = '';
    private $oauthClientId = '';
    private $oauthClientSecret = '';

    /**
     * @param string $UserEmail
     * @param string $ClientSecret
     * @param string $ClientId
     * @param string $RefreshToken
     */
    public function __construct(
        $UserEmail,
        $ClientSecret,
        $ClientId,
        $RefreshToken
    ) {
        $this->oauthClientId = $ClientId;
        $this->oauthClientSecret = $ClientSecret;
        $this->oauthRefreshToken = $RefreshToken;
        $this->oauthUserEmail = $UserEmail;
    }

    private function getProvider()
    {
        return new League\OAuth2\Client\Provider\Google([
            'clientId' => $this->oauthClientId,
            'clientSecret' => $this->oauthClientSecret
        ]);
    }

    private function getGrant()
    {
        return new \League\OAuth2\Client\Grant\RefreshToken();
    }

    private function getToken()
    {
        $provider = $this->getProvider();
        $grant = $this->getGrant();
        return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]);
    }

    public function getOauth64()
    {
        $token = $this->getToken();
        return base64_encode("user=" . $this->oauthUserEmail . "\001auth=Bearer " . $token . "\001\001");
    }
}
vendor/joomla/input/LICENSE000064400000042630152177723700011506 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/input/src/Input.php000064400000022725152177723700013103 0ustar00<?php
/**
 * Part of the Joomla Framework Input Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Input;

use Joomla\Filter;

/**
 * Joomla! Input Base Class
 *
 * This is an abstracted input class used to manage retrieving data from the application environment.
 *
 * @since  1.0
 *
 * @property-read    Input   $get
 * @property-read    Input   $post
 * @property-read    Input   $request
 * @property-read    Input   $server
 * @property-read    Input   $env
 * @property-read    Files   $files
 * @property-read    Cookie  $cookie
 *
 * @method      integer  getInt($name, $default = null)       Get a signed integer.
 * @method      integer  getUint($name, $default = null)      Get an unsigned integer.
 * @method      float    getFloat($name, $default = null)     Get a floating-point number.
 * @method      boolean  getBool($name, $default = null)      Get a boolean value.
 * @method      string   getWord($name, $default = null)      Get a word.
 * @method      string   getAlnum($name, $default = null)     Get an alphanumeric string.
 * @method      string   getCmd($name, $default = null)       Get a CMD filtered string.
 * @method      string   getBase64($name, $default = null)    Get a base64 encoded string.
 * @method      string   getString($name, $default = null)    Get a string.
 * @method      string   getHtml($name, $default = null)      Get a HTML string.
 * @method      string   getPath($name, $default = null)      Get a file path.
 * @method      string   getUsername($name, $default = null)  Get a username.
 */
class Input implements \Serializable, \Countable
{
	/**
	 * Container with allowed superglobals
	 *
	 * @var    array
	 * @since  1.3.0
	 * @note   Once PHP 7.1 is the minimum supported version this should become a private constant
	 */
	private static $allowedGlobals = array('REQUEST', 'GET', 'POST', 'FILES', 'SERVER', 'ENV');

	/**
	 * Options array for the Input instance.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $options = array();

	/**
	 * Filter object to use.
	 *
	 * @var    Filter\InputFilter
	 * @since  1.0
	 */
	protected $filter;

	/**
	 * Input data.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $data = array();

	/**
	 * Input objects
	 *
	 * @var    Input[]
	 * @since  1.0
	 */
	protected $inputs = array();

	/**
	 * Is all GLOBAL added
	 *
	 * @var    boolean
	 * @since  1.1.4
	 */
	protected static $loaded = false;

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Optional source data. If omitted, a copy of the server variable '_REQUEST' is used.
	 * @param   array  $options  An optional associative array of configuration parameters:
	 *                           filter: An instance of Filter\Input. If omitted, a default filter is initialised.
	 *
	 * @since   1.0
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}

		if ($source === null)
		{
			$this->data = &$_REQUEST;
		}
		else
		{
			$this->data = $source;
		}

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Magic method to get an input object
	 *
	 * @param   mixed  $name  Name of the input object to retrieve.
	 *
	 * @return  Input  The request input object
	 *
	 * @since   1.0
	 */
	public function __get($name)
	{
		if (isset($this->inputs[$name]))
		{
			return $this->inputs[$name];
		}

		$className = '\\Joomla\\Input\\' . ucfirst($name);

		if (class_exists($className))
		{
			$this->inputs[$name] = new $className(null, $this->options);

			return $this->inputs[$name];
		}

		$superGlobal = '_' . strtoupper($name);

		if (\in_array(strtoupper($name), self::$allowedGlobals, true) && isset($GLOBALS[$superGlobal]))
		{
			$this->inputs[$name] = new Input($GLOBALS[$superGlobal], $this->options);

			return $this->inputs[$name];
		}

		// TODO throw an exception
	}

	/**
	 * Get the number of variables.
	 *
	 * @return  integer  The number of variables in the input.
	 *
	 * @since   1.0
	 * @see     Countable::count()
	 */
	public function count()
	{
		return \count($this->data);
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     Name of the value to get.
	 * @param   mixed   $default  Default value to return if variable does not exist.
	 * @param   string  $filter   Filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @see     \Joomla\Filter\InputFilter::clean()
	 * @since   1.0
	 */
	public function get($name, $default = null, $filter = 'cmd')
	{
		if (isset($this->data[$name]))
		{
			return $this->filter->clean($this->data[$name], $filter);
		}

		return $default;
	}

	/**
	 * Gets an array of values from the request.
	 *
	 * @param   array  $vars        Associative array of keys and filter types to apply.
	 *                              If empty and datasource is null, all the input data will be returned
	 *                              but filtered using the default case in JFilterInput::clean.
	 * @param   mixed  $datasource  Array to retrieve data from, or null
	 *
	 * @return  mixed  The filtered input data.
	 *
	 * @since   1.0
	 */
	public function getArray(array $vars = array(), $datasource = null)
	{
		if (empty($vars) && $datasource === null)
		{
			$vars = $this->data;
		}

		$results = array();

		foreach ($vars as $k => $v)
		{
			if (\is_array($v))
			{
				if ($datasource === null)
				{
					$results[$k] = $this->getArray($v, $this->get($k, null, 'array'));
				}
				else
				{
					$results[$k] = $this->getArray($v, $datasource[$k]);
				}
			}
			else
			{
				if ($datasource === null)
				{
					$results[$k] = $this->get($k, null, $v);
				}
				elseif (isset($datasource[$k]))
				{
					$results[$k] = $this->filter->clean($datasource[$k], $v);
				}
				else
				{
					$results[$k] = $this->filter->clean(null, $v);
				}
			}
		}

		return $results;
	}

	/**
	 * Get the Input instance holding the data for the current request method
	 *
	 * @return  Input
	 *
	 * @since   1.3.0
	 */
	public function getInputForRequestMethod()
	{
		switch (strtoupper($this->getMethod()))
		{
			case 'GET':
				return $this->get;

			case 'POST':
				return $this->post;

			default:
				// PUT, PATCH, etc. don't have superglobals
				return $this;
		}
	}

	/**
	 * Sets a value
	 *
	 * @param   string  $name   Name of the value to set.
	 * @param   mixed   $value  Value to assign to the input.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function set($name, $value)
	{
		$this->data[$name] = $value;
	}

	/**
	 * Define a value. The value will only be set if there's no value for the name or if it is null.
	 *
	 * @param   string  $name   Name of the value to define.
	 * @param   mixed   $value  Value to assign to the input.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function def($name, $value)
	{
		if (isset($this->data[$name]))
		{
			return;
		}

		$this->data[$name] = $value;
	}

	/**
	 * Check if a value name exists.
	 *
	 * @param   string  $name  Value name
	 *
	 * @return  boolean
	 *
	 * @since   1.2.0
	 */
	public function exists($name)
	{
		return isset($this->data[$name]);
	}

	/**
	 * Magic method to get filtered input data.
	 *
	 * @param   string  $name       Name of the filter type prefixed with 'get'.
	 * @param   array   $arguments  [0] The name of the variable [1] The default value.
	 *
	 * @return  mixed   The filtered input value.
	 *
	 * @since   1.0
	 */
	public function __call($name, $arguments)
	{
		if (substr($name, 0, 3) == 'get')
		{
			$filter = substr($name, 3);

			$default = null;

			if (isset($arguments[1]))
			{
				$default = $arguments[1];
			}

			return $this->get($arguments[0], $default, $filter);
		}
	}

	/**
	 * Gets the request method.
	 *
	 * @return  string   The request method.
	 *
	 * @since   1.0
	 */
	public function getMethod()
	{
		$method = strtoupper($_SERVER['REQUEST_METHOD']);

		return $method;
	}

	/**
	 * Method to serialize the input.
	 *
	 * @return  string  The serialized input.
	 *
	 * @since   1.0
	 */
	public function serialize()
	{
		// Load all of the inputs.
		$this->loadAllInputs();

		// Remove $_ENV and $_SERVER from the inputs.
		$inputs = $this->inputs;
		unset($inputs['env'], $inputs['server']);

		// Serialize the options, data, and inputs.
		return serialize(array($this->options, $this->data, $inputs));
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function unserialize($input)
	{
		// Unserialize the options, data, and inputs.
		list($this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}
	}

	/**
	 * Method to load all of the global inputs.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function loadAllInputs()
	{
		if (!self::$loaded)
		{
			// Load up all the globals.
			foreach ($GLOBALS as $global => $data)
			{
				// Check if the global starts with an underscore and is allowed.
				if (strpos($global, '_') === 0 && \in_array(substr($global, 1), self::$allowedGlobals, true))
				{
					// Convert global name to input name.
					$global = strtolower($global);
					$global = substr($global, 1);

					// Get the input.
					$this->$global;
				}
			}

			self::$loaded = true;
		}
	}
}
vendor/joomla/input/src/Cli.php000064400000010676152177723700012515 0ustar00<?php
/**
 * Part of the Joomla Framework Input Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Input;

use Joomla\Filter;

/**
 * Joomla! Input CLI Class
 *
 * @since       1.0
 * @deprecated  2.0  Use a Symfony\Component\Console\Input\InputInterface implementation when using the `joomla/console` package
 */
class Cli extends Input
{
	/**
	 * The executable that was called to run the CLI script.
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $executable;

	/**
	 * The additional arguments passed to the script that are not associated
	 * with a specific argument name.
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $args = array();

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is $_REQUEST)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.0
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}

		// Get the command line options
		$this->parseArguments();

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Method to serialize the input.
	 *
	 * @return  string  The serialized input.
	 *
	 * @since   1.0
	 */
	public function serialize()
	{
		// Load all of the inputs.
		$this->loadAllInputs();

		// Remove $_ENV and $_SERVER from the inputs.
		$inputs = $this->inputs;
		unset($inputs['env'], $inputs['server']);

		// Serialize the executable, args, options, data, and inputs.
		return serialize(array($this->executable, $this->args, $this->options, $this->data, $inputs));
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     Name of the value to get.
	 * @param   mixed   $default  Default value to return if variable does not exist.
	 * @param   string  $filter   Filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @since   1.0
	 */
	public function get($name, $default = null, $filter = 'string')
	{
		return parent::get($name, $default, $filter);
	}

	/**
	 * Method to unserialize the input.
	 *
	 * @param   string  $input  The serialized input.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function unserialize($input)
	{
		// Unserialize the executable, args, options, data, and inputs.
		list($this->executable, $this->args, $this->options, $this->data, $this->inputs) = unserialize($input);

		// Load the filter.
		if (isset($this->options['filter']))
		{
			$this->filter = $this->options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}
	}

	/**
	 * Initialise the options and arguments
	 *
	 * Not supported: -abc c-value
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function parseArguments()
	{
		$argv = $_SERVER['argv'];

		$this->executable = array_shift($argv);

		$out = array();

		for ($i = 0, $j = \count($argv); $i < $j; $i++)
		{
			$arg = $argv[$i];

			// --foo --bar=baz
			if (substr($arg, 0, 2) === '--')
			{
				$eqPos = strpos($arg, '=');

				// --foo
				if ($eqPos === false)
				{
					$key = substr($arg, 2);

					// --foo value
					if ($i + 1 < $j && $argv[$i + 1][0] !== '-')
					{
						$value          = $argv[$i + 1];
						$i++;
					}
					else
					{
						$value          = isset($out[$key]) ? $out[$key] : true;
					}

					$out[$key]          = $value;
				}

				// --bar=baz
				else
				{
					$key                = substr($arg, 2, $eqPos - 2);
					$value              = substr($arg, $eqPos + 1);
					$out[$key]          = $value;
				}
			}
			// -k=value -abc
			elseif (substr($arg, 0, 1) === '-')
			{
				// -k=value
				if (substr($arg, 2, 1) === '=')
				{
					$key                = substr($arg, 1, 1);
					$value              = substr($arg, 3);
					$out[$key]          = $value;
				}
				// -abc
				else
				{
					$chars              = str_split(substr($arg, 1));

					foreach ($chars as $char)
					{
						$key            = $char;
						$value          = isset($out[$key]) ? $out[$key] : true;
						$out[$key]      = $value;
					}

					// -a a-value
					if ((\count($chars) === 1) && ($i + 1 < $j) && ($argv[$i + 1][0] !== '-'))
					{
						$out[$key]      = $argv[$i + 1];
						$i++;
					}
				}
			}
			// Plain-arg
			else
			{
				$this->args[] = $arg;
			}
		}

		$this->data = $out;
	}
}
vendor/joomla/input/src/Json.php000064400000003421152177723700012705 0ustar00<?php
/**
 * Part of the Joomla Framework Input Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Input;

use Joomla\Filter;

/**
 * Joomla! Input JSON Class
 *
 * This class decodes a JSON string from the raw request data and makes it available via
 * the standard Input interface.
 *
 * @since  1.0
 */
class Json extends Input
{
	/**
	 * @var    string  The raw JSON string from the request.
	 * @since  1.0
	 */
	private $raw;

	/**
	 * Constructor.
	 *
	 * @param   array  $source   Source data (Optional, default is the raw HTTP input decoded from JSON)
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.0
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}

		if ($source === null)
		{
			$this->raw = file_get_contents('php://input');

			// This is a workaround for where php://input has already been read.
			// See note under php://input on https://www.php.net/manual/en/wrappers.php.php
			if (empty($this->raw) && isset($GLOBALS['HTTP_RAW_POST_DATA']))
			{
				$this->raw = $GLOBALS['HTTP_RAW_POST_DATA'];
			}

			$this->data = json_decode($this->raw, true);

			if (!\is_array($this->data))
			{
				$this->data = array();
			}
		}
		else
		{
			$this->data = $source;
		}

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets the raw JSON string from the request.
	 *
	 * @return  string  The raw JSON string from the request.
	 *
	 * @since   1.0
	 */
	public function getRaw()
	{
		return $this->raw;
	}
}
vendor/joomla/input/src/Files.php000064400000005367152177723700013051 0ustar00<?php
/**
 * Part of the Joomla Framework Input Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Input;

use Joomla\Filter;

/**
 * Joomla! Input Files Class
 *
 * @since  1.0
 */
class Files extends Input
{
	/**
	 * The pivoted data from a $_FILES or compatible array.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $decodedData = array();

	/**
	 * The class constructor.
	 *
	 * @param   array  $source   The source argument is ignored. $_FILES is always used.
	 * @param   array  $options  An optional array of configuration options:
	 *                           filter : a custom JFilterInput object.
	 *
	 * @since   1.0
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}

		// Set the data source.
		$this->data = & $_FILES;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Gets a value from the input data.
	 *
	 * @param   string  $name     The name of the input property (usually the name of the files INPUT tag) to get.
	 * @param   mixed   $default  The default value to return if the named property does not exist.
	 * @param   string  $filter   The filter to apply to the value.
	 *
	 * @return  mixed  The filtered input value.
	 *
	 * @see     \Joomla\Filter\InputFilter::clean()
	 * @since   1.0
	 */
	public function get($name, $default = null, $filter = 'cmd')
	{
		if (isset($this->data[$name]))
		{
			$results = $this->decodeData(
				array(
					$this->data[$name]['name'],
					$this->data[$name]['type'],
					$this->data[$name]['tmp_name'],
					$this->data[$name]['error'],
					$this->data[$name]['size'],
				)
			);

			return $results;
		}

		return $default;
	}

	/**
	 * Method to decode a data array.
	 *
	 * @param   array  $data  The data array to decode.
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	protected function decodeData(array $data)
	{
		$result = array();

		if (\is_array($data[0]))
		{
			foreach ($data[0] as $k => $v)
			{
				$result[$k] = $this->decodeData(array($data[0][$k], $data[1][$k], $data[2][$k], $data[3][$k], $data[4][$k]));
			}

			return $result;
		}

		return array('name' => $data[0], 'type' => $data[1], 'tmp_name' => $data[2], 'error' => $data[3], 'size' => $data[4]);
	}

	/**
	 * Sets a value.
	 *
	 * @param   string  $name   The name of the input property to set.
	 * @param   mixed   $value  The value to assign to the input property.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function set($name, $value)
	{
		// Restricts the usage of parent's set method.
	}
}
vendor/joomla/input/src/Cookie.php000064400000006617152177723700013217 0ustar00<?php
/**
 * Part of the Joomla Framework Input Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Input;

use Joomla\Filter;

/**
 * Joomla! Input Cookie Class
 *
 * @since  1.0
 */
class Cookie extends Input
{
	/**
	 * Constructor.
	 *
	 * @param   array  $source   Ignored.
	 * @param   array  $options  Array of configuration parameters (Optional)
	 *
	 * @since   1.0
	 */
	public function __construct($source = null, array $options = array())
	{
		if (isset($options['filter']))
		{
			$this->filter = $options['filter'];
		}
		else
		{
			$this->filter = new Filter\InputFilter;
		}

		// Set the data source.
		$this->data = & $_COOKIE;

		// Set the options for the class.
		$this->options = $options;
	}

	/**
	 * Sets a value
	 *
	 * @param   string   $name      Name of the value to set.
	 * @param   mixed    $value     Value to assign to the input.
	 * @param   array    $options   An associative array which may have any of the keys expires, path, domain,
	 *                              secure, httponly and samesite. The values have the same meaning as described
	 *                              for the parameters with the same name. The value of the samesite element
	 *                              should be either Lax or Strict. If any of the allowed options are not given,
	 *                              their default values are the same as the default values of the explicit
	 *                              parameters. If the samesite element is omitted, no SameSite cookie attribute
	 *                              is set.
	 *
	 * @return  void
	 *
	 * @link    https://www.ietf.org/rfc/rfc2109.txt
	 * @link    https://php.net/manual/en/function.setcookie.php
	 *
	 * @since   1.0
	 *
	 * @note    As of 1.4.0, the (name, value, expire, path, domain, secure, httpOnly) signature is deprecated and will not be supported
	 *          when support for PHP 7.2 and earlier is dropped
	 */
	public function set($name, $value, $options = array())
	{
		// BC layer to convert old method parameters.
		if (is_array($options) === false)
		{
			$argList = func_get_args();

			$options = array(
				'expires'  => isset($argList[2]) === true ? $argList[2] : 0,
				'path'     => isset($argList[3]) === true ? $argList[3] : '',
				'domain'   => isset($argList[4]) === true ? $argList[4] : '',
				'secure'   => isset($argList[5]) === true ? $argList[5] : false,
				'httponly' => isset($argList[6]) === true ? $argList[6] : false,
			);
		}

		// Set the cookie
		if (version_compare(PHP_VERSION, '7.3', '>='))
		{
			setcookie($name, $value, $options);
		}
		else
		{
			// Using the setcookie function before php 7.3, make sure we have default values.
			if (array_key_exists('expires', $options) === false)
			{
				$options['expires'] = 0;
			}

			if (array_key_exists('path', $options) === false)
			{
				$options['path'] = '';
			}

			if (array_key_exists('domain', $options) === false)
			{
				$options['domain'] = '';
			}

			if (array_key_exists('secure', $options) === false)
			{
				$options['secure'] = false;
			}

			if (array_key_exists('httponly', $options) === false)
			{
				$options['httponly'] = false;
			}

			setcookie($name, $value, $options['expires'], $options['path'], $options['domain'], $options['secure'], $options['httponly']);
		}

		$this->data[$name] = $value;
	}
}
vendor/joomla/data/LICENSE000064400000042630152177723700011260 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/data/src/DataObject.php000064400000021147152177723700013553 0ustar00<?php
/**
 * Part of the Joomla Framework Data Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Data;

use Joomla\Registry\Registry;

/**
 * DataObject is a class that is used to store data but allowing you to access the data
 * by mimicking the way PHP handles class properties.
 *
 * @since  1.0
 */
class DataObject implements DumpableInterface, \IteratorAggregate, \JsonSerializable, \Countable
{
	/**
	 * The data object properties.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $properties = array();

	/**
	 * The class constructor.
	 *
	 * @param   mixed  $properties  Either an associative array or another object
	 *                              by which to set the initial properties of the new object.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($properties = array())
	{
		// Check the properties input.
		if (!empty($properties))
		{
			// Bind the properties.
			$this->bind($properties);
		}
	}

	/**
	 * The magic get method is used to get a data property.
	 *
	 * This method is a public proxy for the protected getProperty method.
	 *
	 * Note: Magic __get does not allow recursive calls. This can be tricky because the error generated by recursing into
	 * __get is "Undefined property:  {CLASS}::{PROPERTY}" which is misleading. This is relevant for this class because
	 * requesting a non-visible property can trigger a call to a sub-function. If that references the property directly in
	 * the object, it will cause a recursion into __get.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  mixed  The value of the data property, or null if the data property does not exist.
	 *
	 * @see     DataObject::getProperty()
	 * @since   1.0
	 */
	public function __get($property)
	{
		return $this->getProperty($property);
	}

	/**
	 * The magic isset method is used to check the state of an object property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  boolean  True if set, otherwise false is returned.
	 *
	 * @since   1.0
	 */
	public function __isset($property)
	{
		return isset($this->properties[$property]);
	}

	/**
	 * The magic set method is used to set a data property.
	 *
	 * This is a public proxy for the protected setProperty method.
	 *
	 * @param   string  $property  The name of the data property.
	 * @param   mixed   $value     The value to give the data property.
	 *
	 * @return  void
	 *
	 * @see     DataObject::setProperty()
	 * @since   1.0
	 */
	public function __set($property, $value)
	{
		$this->setProperty($property, $value);
	}

	/**
	 * The magic unset method is used to unset a data property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function __unset($property)
	{
		unset($this->properties[$property]);
	}

	/**
	 * Binds an array or object to this object.
	 *
	 * @param   mixed    $properties   An associative array of properties or an object.
	 * @param   boolean  $updateNulls  True to bind null values, false to ignore null values.
	 *
	 * @return  DataObject  Returns itself to allow chaining.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function bind($properties, $updateNulls = true)
	{
		// Check the properties data type.
		if (!is_array($properties) && !is_object($properties))
		{
			throw new \InvalidArgumentException(sprintf('%s(%s)', __METHOD__, gettype($properties)));
		}

		// Check if the object is traversable.
		if ($properties instanceof \Traversable)
		{
			// Convert iterator to array.
			$properties = iterator_to_array($properties);
		}
		elseif (is_object($properties))
		// Check if the object needs to be converted to an array.
		{
			// Convert properties to an array.
			$properties = (array) $properties;
		}

		// Bind the properties.
		foreach ($properties as $property => $value)
		{
			// Check if the value is null and should be bound.
			if ($value === null && !$updateNulls)
			{
				continue;
			}

			// Set the property.
			$this->setProperty($property, $value);
		}

		return $this;
	}

	/**
	 * Dumps the data properties into a stdClass object, recursively if appropriate.
	 *
	 * @param   integer            $depth   The maximum depth of recursion (default = 3).
	 *                                      For example, a depth of 0 will return a stdClass with all the properties in native
	 *                                      form. A depth of 1 will recurse into the first level of properties only.
	 * @param   \SplObjectStorage  $dumped  An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  \stdClass  The data properties as a simple PHP stdClass object.
	 *
	 * @since   1.0
	 */
	public function dump($depth = 3, \SplObjectStorage $dumped = null)
	{
		// Check if we should initialise the recursion tracker.
		if ($dumped === null)
		{
			$dumped = new \SplObjectStorage;
		}

		// Add this object to the dumped stack.
		$dumped->attach($this);

		// Setup a container.
		$dump = new \stdClass;

		// Dump all object properties.
		foreach (array_keys($this->properties) as $property)
		{
			// Get the property.
			$dump->$property = $this->dumpProperty($property, $depth, $dumped);
		}

		return $dump;
	}

	/**
	 * Gets this object represented as an ArrayIterator.
	 *
	 * This allows the data properties to be access via a foreach statement.
	 *
	 * @return  \ArrayIterator  This object represented as an ArrayIterator.
	 *
	 * @see     IteratorAggregate::getIterator()
	 * @since   1.0
	 */
	public function getIterator()
	{
		return new \ArrayIterator($this->dump(0));
	}

	/**
	 * Gets the data properties in a form that can be serialised to JSON format.
	 *
	 * @return  string  An object that can be serialised by json_encode().
	 *
	 * @since   1.0
	 */
	public function jsonSerialize()
	{
		return $this->dump();
	}

	/**
	 * Dumps a data property.
	 *
	 * If recursion is set, this method will dump any object implementing Data\Dumpable (like Data\Object and Data\Set); it will
	 * convert a Date object to a string; and it will convert a Registry to an object.
	 *
	 * @param   string             $property  The name of the data property.
	 * @param   integer            $depth     The current depth of recursion (a value of 0 will ignore recursion).
	 * @param   \SplObjectStorage  $dumped    An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  mixed  The value of the dumped property.
	 *
	 * @since   1.0
	 */
	protected function dumpProperty($property, $depth, \SplObjectStorage $dumped)
	{
		$value = $this->getProperty($property);

		if ($depth > 0)
		{
			// Check if the object is also an dumpable object.
			if ($value instanceof DumpableInterface)
			{
				// Do not dump the property if it has already been dumped.
				if (!$dumped->contains($value))
				{
					$value = $value->dump($depth - 1, $dumped);
				}
			}

			// Check if the object is a date.
			if ($value instanceof \DateTime)
			{
				$value = $value->format('Y-m-d H:i:s');
			}
			elseif ($value instanceof Registry)
			// Check if the object is a registry.
			{
				$value = $value->toObject();
			}
		}

		return $value;
	}

	/**
	 * Gets a data property.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  mixed  The value of the data property.
	 *
	 * @see     DataObject::__get()
	 * @since   1.0
	 */
	protected function getProperty($property)
	{
		// Get the raw value.
		$value = array_key_exists($property, $this->properties) ? $this->properties[$property] : null;

		return $value;
	}

	/**
	 * Sets a data property.
	 *
	 * If the name of the property starts with a null byte, this method will return null.
	 *
	 * @param   string  $property  The name of the data property.
	 * @param   mixed   $value     The value to give the data property.
	 *
	 * @return  mixed  The value of the data property.
	 *
	 * @see     DataObject::__set()
	 * @since   1.0
	 */
	protected function setProperty($property, $value)
	{
		/*
		 * Check if the property starts with a null byte. If so, discard it because a later attempt to try to access it
		 * can cause a fatal error. See http://us3.php.net/manual/en/language.types.array.php#language.types.array.casting
		 */
		if (strpos($property, "\0") === 0)
		{
			return null;
		}

		// Set the value.
		$this->properties[$property] = $value;

		return $value;
	}

	/**
	 * Count the number of data properties.
	 *
	 * @return  integer  The number of data properties.
	 *
	 * @since   1.0
	 */
	public function count()
	{
		return count($this->properties);
	}
}
vendor/joomla/data/src/DataSet.php000064400000034774152177723700013112 0ustar00<?php
/**
 * Part of the Joomla Framework Data Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Data;

/**
 * DataSet is a collection class that allows the developer to operate on a set of DataObject objects as if they were in a
 * typical PHP array.
 *
 * @since  1.0
 */
class DataSet implements DumpableInterface, \ArrayAccess, \Countable, \Iterator
{
	/**
	 * The current position of the iterator.
	 *
	 * @var    integer
	 * @since  1.0
	 */
	private $current = false;

	/**
	 * The iterator objects.
	 *
	 * @var    DataObject[]
	 * @since  1.0
	 */
	private $objects = array();

	/**
	 * The class constructor.
	 *
	 * @param   DataObject[]  $objects  An array of DataObject objects to bind to the data set.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException if an object is not an instance of Data\Object.
	 */
	public function __construct(array $objects = array())
	{
		// Set the objects.
		$this->_initialise($objects);
	}

	/**
	 * The magic call method is used to call object methods using the iterator.
	 *
	 * Example: $array = $objectList->foo('bar');
	 *
	 * The object list will iterate over its objects and see if each object has a callable 'foo' method.
	 * If so, it will pass the argument list and assemble any return values. If an object does not have
	 * a callable method no return value is recorded.
	 * The keys of the objects and the result array are maintained.
	 *
	 * @param   string  $method     The name of the method called.
	 * @param   array   $arguments  The arguments of the method called.
	 *
	 * @return  array   An array of values returned by the methods called on the objects in the data set.
	 *
	 * @since   1.0
	 */
	public function __call($method, $arguments = array())
	{
		$return = array();

		// Iterate through the objects.
		foreach ($this->objects as $key => $object)
		{
			// Create the object callback.
			$callback = array($object, $method);

			// Check if the callback is callable.
			if (is_callable($callback))
			{
				// Call the method for the object.
				$return[$key] = call_user_func_array($callback, $arguments);
			}
		}

		return $return;
	}

	/**
	 * The magic get method is used to get a list of properties from the objects in the data set.
	 *
	 * Example: $array = $dataSet->foo;
	 *
	 * This will return a column of the values of the 'foo' property in all the objects
	 * (or values determined by custom property setters in the individual Data\Object's).
	 * The result array will contain an entry for each object in the list (compared to __call which may not).
	 * The keys of the objects and the result array are maintained.
	 *
	 * @param   string  $property  The name of the data property.
	 *
	 * @return  array  An associative array of the values.
	 *
	 * @since   1.0
	 */
	public function __get($property)
	{
		$return = array();

		// Iterate through the objects.
		foreach ($this->objects as $key => $object)
		{
			// Get the property.
			$return[$key] = $object->$property;
		}

		return $return;
	}

	/**
	 * The magic isset method is used to check the state of an object property using the iterator.
	 *
	 * Example: $array = isset($objectList->foo);
	 *
	 * @param   string  $property  The name of the property.
	 *
	 * @return  boolean  True if the property is set in any of the objects in the data set.
	 *
	 * @since   1.0
	 */
	public function __isset($property)
	{
		$return = array();

		// Iterate through the objects.
		foreach ($this->objects as $object)
		{
			// Check the property.
			$return[] = isset($object->$property);
		}

		return in_array(true, $return, true) ? true : false;
	}

	/**
	 * The magic set method is used to set an object property using the iterator.
	 *
	 * Example: $objectList->foo = 'bar';
	 *
	 * This will set the 'foo' property to 'bar' in all of the objects
	 * (or a value determined by custom property setters in the Data\Object).
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value to give the data property.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function __set($property, $value)
	{
		// Iterate through the objects.
		foreach ($this->objects as $object)
		{
			// Set the property.
			$object->$property = $value;
		}
	}

	/**
	 * The magic unset method is used to unset an object property using the iterator.
	 *
	 * Example: unset($objectList->foo);
	 *
	 * This will unset all of the 'foo' properties in the list of Data\Object's.
	 *
	 * @param   string  $property  The name of the property.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function __unset($property)
	{
		// Iterate through the objects.
		foreach ($this->objects as $object)
		{
			unset($object->$property);
		}
	}

	/**
	 * Gets an array of keys, existing in objects
	 *
	 * @param   string  $type  Selection type 'all' or 'common'
	 *
	 * @return  array   Array of keys
	 *
	 * @since   1.2.0
	 * @throws  \InvalidArgumentException
	 */
	public function getObjectsKeys($type = 'all')
	{
		$keys = null;

		if ($type == 'all')
		{
			$function = 'array_merge';
		}
		elseif ($type == 'common')
		{
			$function = 'array_intersect_key';
		}
		else
		{
			throw new \InvalidArgumentException("Unknown selection type: $type");
		}

		foreach ($this->objects as $object)
		{
			if (version_compare(PHP_VERSION, '5.4.0', '<'))
			{
				$object_vars = json_decode(json_encode($object->jsonSerialize()), true);
			}
			else
			{
				$object_vars = json_decode(json_encode($object), true);
			}

			$keys = (is_null($keys)) ? $object_vars : $function($keys, $object_vars);
		}

		return array_keys($keys);
	}

	/**
	 * Gets all objects as an array
	 *
	 * @param   boolean  $associative  Option to set return mode: associative or numeric array.
	 * @param   string   $k            Unlimited optional property names to extract from objects.
	 *
	 * @return  array    Returns an array according to defined options.
	 *
	 * @since   1.2.0
	 */
	public function toArray($associative = true, $k = null)
	{
		$keys        = func_get_args();
		$associative = array_shift($keys);

		if (empty($keys))
		{
			$keys = $this->getObjectsKeys();
		}

		$return = array();

		$i = 0;

		foreach ($this->objects as $key => $object)
		{
			$array_item = array();

			$key = ($associative) ? $key : $i++;

			$j = 0;

			foreach ($keys as $property)
			{
				$property_key              = ($associative) ? $property : $j++;
				$array_item[$property_key] = (isset($object->$property)) ? $object->$property : null;
			}

			$return[$key] = $array_item;
		}

		return $return;
	}

	/**
	 * Gets the number of data objects in the set.
	 *
	 * @return  integer  The number of objects.
	 *
	 * @since   1.0
	 */
	public function count()
	{
		return count($this->objects);
	}

	/**
	 * Clears the objects in the data set.
	 *
	 * @return  DataSet  Returns itself to allow chaining.
	 *
	 * @since   1.0
	 */
	public function clear()
	{
		$this->objects = array();
		$this->rewind();

		return $this;
	}

	/**
	 * Get the current data object in the set.
	 *
	 * @return  DataObject  The current object, or false if the array is empty or the pointer is beyond the end of the elements.
	 *
	 * @since   1.0
	 */
	public function current()
	{
		return is_scalar($this->current) ? $this->objects[$this->current] : false;
	}

	/**
	 * Dumps the data object in the set, recursively if appropriate.
	 *
	 * @param   integer            $depth   The maximum depth of recursion (default = 3).
	 *                                      For example, a depth of 0 will return a stdClass with all the properties in native
	 *                                      form. A depth of 1 will recurse into the first level of properties only.
	 * @param   \SplObjectStorage  $dumped  An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  array  An associative array of the data objects in the set, dumped as a simple PHP stdClass object.
	 *
	 * @see     DataObject::dump()
	 * @since   1.0
	 */
	public function dump($depth = 3, \SplObjectStorage $dumped = null)
	{
		// Check if we should initialise the recursion tracker.
		if ($dumped === null)
		{
			$dumped = new \SplObjectStorage;
		}

		// Add this object to the dumped stack.
		$dumped->attach($this);

		$objects = array();

		// Make sure that we have not reached our maximum depth.
		if ($depth > 0)
		{
			// Handle JSON serialization recursively.
			foreach ($this->objects as $key => $object)
			{
				$objects[$key] = $object->dump($depth, $dumped);
			}
		}

		return $objects;
	}

	/**
	 * Gets the data set in a form that can be serialised to JSON format.
	 *
	 * Note that this method will not return an associative array, otherwise it would be encoded into an object.
	 * JSON decoders do not consistently maintain the order of associative keys, whereas they do maintain the order of arrays.
	 *
	 * @param   mixed  $serialized  An array of objects that have already been serialized that is used to infinite loops
	 *                              (null on first call).
	 *
	 * @return  array  An array that can be serialised by json_encode().
	 *
	 * @since   1.0
	 */
	public function jsonSerialize($serialized = null)
	{
		// Check if we should initialise the recursion tracker.
		if ($serialized === null)
		{
			$serialized = array();
		}

		// Add this object to the serialized stack.
		$serialized[] = spl_object_hash($this);
		$return = array();

		// Iterate through the objects.
		foreach ($this->objects as $object)
		{
			// Call the method for the object.
			$return[] = $object->jsonSerialize($serialized);
		}

		return $return;
	}

	/**
	 * Gets the key of the current object in the iterator.
	 *
	 * @return  scalar  The object key on success; null on failure.
	 *
	 * @since   1.0
	 */
	public function key()
	{
		return $this->current;
	}

	/**
	 * Gets the array of keys for all the objects in the iterator (emulates array_keys).
	 *
	 * @return  array  The array of keys
	 *
	 * @since   1.0
	 */
	public function keys()
	{
		return array_keys($this->objects);
	}

	/**
	 * Applies a function to every object in the set (emulates array_walk).
	 * 
	 * @param   callable  $funcname  Callback function.  
	 * 
	 * @return  boolean
	 * 
	 * @since   1.2.0
	 * @throws  \InvalidArgumentException
	 */
	public function walk($funcname)
	{
		if (!is_callable($funcname))
		{
			$message = __METHOD__ . '() expects parameter 1 to be a valid callback';

			if (is_string($funcname))
			{
				$message .= sprintf(', function \'%s\' not found or invalid function name', $funcname);
			}

			throw new \InvalidArgumentException($message);
		}

		foreach ($this->objects as $key => $object)
		{
			$funcname($object, $key);
		}

		return true;
	}

	/**
	 * Advances the iterator to the next object in the iterator.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function next()
	{
		// Get the object offsets.
		$keys = $this->keys();

		// Check if _current has been set to false but offsetUnset.
		if ($this->current === false && isset($keys[0]))
		{
			// This is a special case where offsetUnset was used in a foreach loop and the first element was unset.
			$this->current = $keys[0];
		}
		else
		{
			// Get the current key.
			$position = array_search($this->current, $keys);

			// Check if there is an object after the current object.
			if ($position !== false && isset($keys[$position + 1]))
			{
				// Get the next id.
				$this->current = $keys[$position + 1];
			}
			else
			{
				// That was the last object or the internal properties have become corrupted.
				$this->current = null;
			}
		}
	}

	/**
	 * Checks whether an offset exists in the iterator.
	 *
	 * @param   mixed  $offset  The object offset.
	 *
	 * @return  boolean  True if the object exists, false otherwise.
	 *
	 * @since   1.0
	 */
	public function offsetExists($offset)
	{
		return isset($this->objects[$offset]);
	}

	/**
	 * Gets an offset in the iterator.
	 *
	 * @param   mixed  $offset  The object offset.
	 *
	 * @return  DataObject  The object if it exists, null otherwise.
	 *
	 * @since   1.0
	 */
	public function offsetGet($offset)
	{
		return isset($this->objects[$offset]) ? $this->objects[$offset] : null;
	}

	/**
	 * Sets an offset in the iterator.
	 *
	 * @param   mixed       $offset  The object offset.
	 * @param   DataObject  $object  The object object.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException if an object is not an instance of Data\Object.
	 */
	public function offsetSet($offset, $object)
	{
		if (!($object instanceof DataObject))
		{
			throw new \InvalidArgumentException(sprintf('%s("%s", *%s*)', __METHOD__, $offset, gettype($object)));
		}

		// Set the offset.
		$this->objects[$offset] = $object;
	}

	/**
	 * Unsets an offset in the iterator.
	 *
	 * @param   mixed  $offset  The object offset.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function offsetUnset($offset)
	{
		if (!$this->offsetExists($offset))
		{
			// Do nothing if the offset does not exist.
			return;
		}

		// Check for special handling of unsetting the current position.
		if ($offset == $this->current)
		{
			// Get the current position.
			$keys = $this->keys();
			$position = array_search($this->current, $keys);

			// Check if there is an object before the current object.
			if ($position > 0)
			{
				// Move the current position back one.
				$this->current = $keys[$position - 1];
			}
			else
			{
				// We are at the start of the keys AND let's assume we are in a foreach loop and `next` is going to be called.
				$this->current = false;
			}
		}

		unset($this->objects[$offset]);
	}

	/**
	 * Rewinds the iterator to the first object.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function rewind()
	{
		// Set the current position to the first object.
		if (empty($this->objects))
		{
			$this->current = false;
		}
		else
		{
			$keys = $this->keys();
			$this->current = array_shift($keys);
		}
	}

	/**
	 * Validates the iterator.
	 *
	 * @return  boolean  True if valid, false otherwise.
	 *
	 * @since   1.0
	 */
	public function valid()
	{
		// Check the current position.
		if (!is_scalar($this->current) || !isset($this->objects[$this->current]))
		{
			return false;
		}

		return true;
	}

	/**
	 * Initialises the list with an array of objects.
	 *
	 * @param   array  $input  An array of objects.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException if an object is not an instance of Data\DataObject.
	 */
	private function _initialise(array $input = array())
	{
		foreach ($input as $key => $object)
		{
			if (!is_null($object))
			{
				$this->offsetSet($key, $object);
			}
		}

		$this->rewind();
	}
}
vendor/joomla/data/src/DumpableInterface.php000064400000002041152177723700015115 0ustar00<?php
/**
 * Part of the Joomla Framework Data Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Data;

/**
 * An interface to define if an object is dumpable.
 *
 * @since  1.0
 */
interface DumpableInterface
{
	/**
	 * Dumps the object properties into a stdClass object, recursively if appropriate.
	 *
	 * @param   integer            $depth   The maximum depth of recursion.
	 *                                      For example, a depth of 0 will return a stdClass with all the properties in native
	 *                                      form. A depth of 1 will recurse into the first level of properties only.
	 * @param   \SplObjectStorage  $dumped  An array of already serialized objects that is used to avoid infinite loops.
	 *
	 * @return  \stdClass  The data properties as a simple PHP stdClass object.
	 *
	 * @since   1.0
	 */
	public function dump($depth = 3, \SplObjectStorage $dumped = null);
}
vendor/joomla/uri/LICENSE000064400000042630152177723700011146 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/uri/src/Uri.php000064400000006237152177723700012203 0ustar00<?php
/**
 * Part of the Joomla Framework Uri Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Uri;

/**
 * Uri Class
 *
 * This class parses a URI and provides a common interface for the Joomla Framework
 * to access and manipulate a URI.
 *
 * @since  1.0
 */
class Uri extends AbstractUri
{
	/**
	 * Adds a query variable and value, replacing the value if it
	 * already exists and returning the old value.
	 *
	 * @param   string  $name   Name of the query variable to set.
	 * @param   string  $value  Value of the query variable.
	 *
	 * @return  string  Previous value for the query variable.
	 *
	 * @since   1.0
	 */
	public function setVar($name, $value)
	{
		$tmp = isset($this->vars[$name]) ? $this->vars[$name] : null;

		$this->vars[$name] = $value;

		// Empty the query
		$this->query = null;

		return $tmp;
	}

	/**
	 * Removes an item from the query string variables if it exists.
	 *
	 * @param   string  $name  Name of variable to remove.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function delVar($name)
	{
		if (array_key_exists($name, $this->vars))
		{
			unset($this->vars[$name]);

			// Empty the query
			$this->query = null;
		}
	}

	/**
	 * Sets the query to a supplied string in format:
	 * foo=bar&x=y
	 *
	 * @param   mixed  $query  The query string or array.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setQuery($query)
	{
		if (\is_array($query))
		{
			$this->vars = $query;
		}
		else
		{
			if (strpos($query, '&amp;') !== false)
			{
				$query = str_replace('&amp;', '&', $query);
			}

			parse_str($query, $this->vars);
		}

		// Empty the query
		$this->query = null;
	}

	/**
	 * Set URI scheme (protocol)
	 * ie. http, https, ftp, etc...
	 *
	 * @param   string  $scheme  The URI scheme.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setScheme($scheme)
	{
		$this->scheme = $scheme;
	}

	/**
	 * Set URI username.
	 *
	 * @param   string  $user  The URI username.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setUser($user)
	{
		$this->user = $user;
	}

	/**
	 * Set URI password.
	 *
	 * @param   string  $pass  The URI password.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setPass($pass)
	{
		$this->pass = $pass;
	}

	/**
	 * Set URI host.
	 *
	 * @param   string  $host  The URI host.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setHost($host)
	{
		$this->host = $host;
	}

	/**
	 * Set URI port.
	 *
	 * @param   integer  $port  The URI port number.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setPort($port)
	{
		$this->port = $port;
	}

	/**
	 * Set the URI path string.
	 *
	 * @param   string  $path  The URI path string.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setPath($path)
	{
		$this->path = $this->cleanPath($path);
	}

	/**
	 * Set the URI anchor string
	 * everything after the "#".
	 *
	 * @param   string  $anchor  The URI anchor string.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setFragment($anchor)
	{
		$this->fragment = $anchor;
	}
}
vendor/joomla/uri/src/UriImmutable.php000064400000002572152177723700014041 0ustar00<?php
/**
 * Part of the Joomla Framework Uri Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Uri;

/**
 * Uri Class
 *
 * This is an immutable version of the uri class.
 *
 * @since  1.0
 */
final class UriImmutable extends AbstractUri
{
	/**
	 * @var    boolean  Has this class been instantiated yet.
	 * @since  1.0
	 */
	private $constructed = false;

	/**
	 * Prevent setting undeclared properties.
	 *
	 * @param   string  $name   This is an immutable object, setting $name is not allowed.
	 * @param   mixed   $value  This is an immutable object, setting $value is not allowed.
	 *
	 * @return  void  This method always throws an exception.
	 *
	 * @since   1.0
	 * @throws  \BadMethodCallException
	 */
	public function __set($name, $value)
	{
		throw new \BadMethodCallException('This is an immutable object');
	}

	/**
	 * This is a special constructor that prevents calling the __construct method again.
	 *
	 * @param   string  $uri  The optional URI string
	 *
	 * @since   1.0
	 * @throws  \BadMethodCallException
	 */
	public function __construct($uri = null)
	{
		if ($this->constructed === true)
		{
			throw new \BadMethodCallException('This is an immutable object');
		}

		$this->constructed = true;

		parent::__construct($uri);
	}
}
vendor/joomla/uri/src/UriInterface.php000064400000007534152177723700014025 0ustar00<?php
/**
 * Part of the Joomla Framework Uri Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Uri;

/**
 * Uri Interface
 *
 * Interface for read-only access to Uris.
 *
 * @since  1.0
 */
interface UriInterface
{
	/**
	 * Include the scheme (http, https, etc.)
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const SCHEME = 1;

	/**
	 * Include the user
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const USER = 2;

	/**
	 * Include the password
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const PASS = 4;

	/**
	 * Include the host
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const HOST = 8;

	/**
	 * Include the port
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const PORT = 16;

	/**
	 * Include the path
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const PATH = 32;

	/**
	 * Include the query string
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const QUERY = 64;

	/**
	 * Include the fragment
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const FRAGMENT = 128;

	/**
	 * Include all available url parts (scheme, user, pass, host, port, path, query, fragment)
	 *
	 * @var    integer
	 * @since  1.2.0
	 */
	const ALL = 255;

	/**
	 * Magic method to get the string representation of the URI object.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function __toString();

	/**
	 * Returns full uri string.
	 *
	 * @param   array  $parts  An array of strings specifying the parts to render.
	 *
	 * @return  string  The rendered URI string.
	 *
	 * @since   1.0
	 */
	public function toString(array $parts = array('scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment'));

	/**
	 * Checks if variable exists.
	 *
	 * @param   string  $name  Name of the query variable to check.
	 *
	 * @return  boolean  True if the variable exists.
	 *
	 * @since   1.0
	 */
	public function hasVar($name);

	/**
	 * Returns a query variable by name.
	 *
	 * @param   string  $name     Name of the query variable to get.
	 * @param   string  $default  Default value to return if the variable is not set.
	 *
	 * @return  array   Query variables.
	 *
	 * @since   1.0
	 */
	public function getVar($name, $default = null);

	/**
	 * Returns flat query string.
	 *
	 * @param   boolean  $toArray  True to return the query as a key => value pair array.
	 *
	 * @return  string   Query string.
	 *
	 * @since   1.0
	 */
	public function getQuery($toArray = false);

	/**
	 * Get URI scheme (protocol)
	 * ie. http, https, ftp, etc...
	 *
	 * @return  string  The URI scheme.
	 *
	 * @since   1.0
	 */
	public function getScheme();

	/**
	 * Get URI username
	 * Returns the username, or null if no username was specified.
	 *
	 * @return  string  The URI username.
	 *
	 * @since   1.0
	 */
	public function getUser();

	/**
	 * Get URI password
	 * Returns the password, or null if no password was specified.
	 *
	 * @return  string  The URI password.
	 *
	 * @since   1.0
	 */
	public function getPass();

	/**
	 * Get URI host
	 * Returns the hostname/ip or null if no hostname/ip was specified.
	 *
	 * @return  string  The URI host.
	 *
	 * @since   1.0
	 */
	public function getHost();

	/**
	 * Get URI port
	 * Returns the port number, or null if no port was specified.
	 *
	 * @return  integer  The URI port number.
	 *
	 * @since   1.0
	 */
	public function getPort();

	/**
	 * Gets the URI path string.
	 *
	 * @return  string  The URI path string.
	 *
	 * @since   1.0
	 */
	public function getPath();

	/**
	 * Get the URI archor string
	 * Everything after the "#".
	 *
	 * @return  string  The URI anchor string.
	 *
	 * @since   1.0
	 */
	public function getFragment();

	/**
	 * Checks whether the current URI is using HTTPS.
	 *
	 * @return  boolean  True if using SSL via HTTPS.
	 *
	 * @since   1.0
	 */
	public function isSsl();
}
vendor/joomla/uri/src/UriHelper.php000064400000002755152177723700013344 0ustar00<?php
/**
 * Part of the Joomla Framework Uri Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Uri;

/**
 * Uri Helper
 *
 * This class provides a UTF-8 safe version of parse_url().
 *
 * @since  1.0
 */
class UriHelper
{
	/**
	 * Does a UTF-8 safe version of PHP parse_url function
	 *
	 * @param   string  $url  URL to parse
	 *
	 * @return  mixed  Associative array or false if badly formed URL.
	 *
	 * @link    https://secure.php.net/manual/en/function.parse-url.php
	 * @since   1.0
	 */
	public static function parse_url($url)
	{
		$result = false;

		// Build arrays of values we need to decode before parsing
		$entities = array('%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D');
		$replacements = array('!', '*', "'", "(", ")", ";", ":", "@", "&", "=", "$", ",", "/", "?", "#", "[", "]");

		// Create encoded URL with special URL characters decoded so it can be parsed
		// All other characters will be encoded
		$encodedURL = str_replace($entities, $replacements, urlencode($url));

		// Parse the encoded URL
		$encodedParts = parse_url($encodedURL);

		// Now, decode each value of the resulting array
		if ($encodedParts)
		{
			foreach ($encodedParts as $key => $value)
			{
				$result[$key] = urldecode(str_replace($replacements, $entities, $value));
			}
		}

		return $result;
	}
}
vendor/joomla/uri/src/AbstractUri.php000064400000021257152177723700013666 0ustar00<?php
/**
 * Part of the Joomla Framework Uri Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Uri;

/**
 * Uri Class
 *
 * Abstract base for out uri classes.
 *
 * This class should be considered an implementation detail. Typehint against UriInterface.
 *
 * @since  1.0
 */
abstract class AbstractUri implements UriInterface
{
	/**
	 * @var    string  Original URI
	 * @since  1.0
	 */
	protected $uri = null;

	/**
	 * @var    string  Protocol
	 * @since  1.0
	 */
	protected $scheme = null;

	/**
	 * @var    string  Host
	 * @since  1.0
	 */
	protected $host = null;

	/**
	 * @var    integer  Port
	 * @since  1.0
	 */
	protected $port = null;

	/**
	 * @var    string  Username
	 * @since  1.0
	 */
	protected $user = null;

	/**
	 * @var    string  Password
	 * @since  1.0
	 */
	protected $pass = null;

	/**
	 * @var    string  Path
	 * @since  1.0
	 */
	protected $path = null;

	/**
	 * @var    string  Query
	 * @since  1.0
	 */
	protected $query = null;

	/**
	 * @var    string  Anchor
	 * @since  1.0
	 */
	protected $fragment = null;

	/**
	 * @var    array  Query variable hash
	 * @since  1.0
	 */
	protected $vars = array();

	/**
	 * Constructor.
	 * You can pass a URI string to the constructor to initialise a specific URI.
	 *
	 * @param   string  $uri  The optional URI string
	 *
	 * @since   1.0
	 */
	public function __construct($uri = null)
	{
		if (!\is_null($uri))
		{
			$this->parse($uri);
		}
	}

	/**
	 * Magic method to get the string representation of the URI object.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function __toString()
	{
		return $this->toString();
	}

	/**
	 * Returns full uri string.
	 *
	 * @param   array  $parts  An array of strings specifying the parts to render.
	 *
	 * @return  string  The rendered URI string.
	 *
	 * @since   1.0
	 */
	public function toString(array $parts = array('scheme', 'user', 'pass', 'host', 'port', 'path', 'query', 'fragment'))
	{
		$bitmask = 0;

		foreach ($parts as $part)
		{
			$const = 'static::' . strtoupper($part);

			if (\defined($const))
			{
				$bitmask |= constant($const);
			}
		}

		return $this->render($bitmask);
	}

	/**
	 * Returns full uri string.
	 *
	 * @param   integer  $parts  A bitmask specifying the parts to render.
	 *
	 * @return  string  The rendered URI string.
	 *
	 * @since   1.2.0
	 */
	public function render($parts = self::ALL)
	{
		// Make sure the query is created
		$query = $this->getQuery();

		$uri = '';
		$uri .= $parts & static::SCHEME ? (!empty($this->scheme) ? $this->scheme . '://' : '') : '';
		$uri .= $parts & static::USER ? $this->user : '';
		$uri .= $parts & static::PASS ? (!empty($this->pass) ? ':' : '') . $this->pass . (!empty($this->user) ? '@' : '') : '';
		$uri .= $parts & static::HOST ? $this->host : '';
		$uri .= $parts & static::PORT ? (!empty($this->port) ? ':' : '') . $this->port : '';
		$uri .= $parts & static::PATH ? $this->path : '';
		$uri .= $parts & static::QUERY ? (!empty($query) ? '?' . $query : '') : '';
		$uri .= $parts & static::FRAGMENT ? (!empty($this->fragment) ? '#' . $this->fragment : '') : '';

		return $uri;
	}

	/**
	 * Checks if variable exists.
	 *
	 * @param   string  $name  Name of the query variable to check.
	 *
	 * @return  boolean  True if the variable exists.
	 *
	 * @since   1.0
	 */
	public function hasVar($name)
	{
		return array_key_exists($name, $this->vars);
	}

	/**
	 * Returns a query variable by name.
	 *
	 * @param   string  $name     Name of the query variable to get.
	 * @param   string  $default  Default value to return if the variable is not set.
	 *
	 * @return  mixed   Value of the specified query variable.
	 *
	 * @since   1.0
	 */
	public function getVar($name, $default = null)
	{
		if (array_key_exists($name, $this->vars))
		{
			return $this->vars[$name];
		}

		return $default;
	}

	/**
	 * Returns flat query string.
	 *
	 * @param   boolean  $toArray  True to return the query as a key => value pair array.
	 *
	 * @return  string|array   Query string or Array of parts in query string depending on the function param
	 *
	 * @since   1.0
	 */
	public function getQuery($toArray = false)
	{
		if ($toArray)
		{
			return $this->vars;
		}

		// If the query is empty build it first
		if (\is_null($this->query))
		{
			$this->query = self::buildQuery($this->vars);
		}

		return $this->query;
	}

	/**
	 * Get URI scheme (protocol)
	 * ie. http, https, ftp, etc...
	 *
	 * @return  string  The URI scheme.
	 *
	 * @since   1.0
	 */
	public function getScheme()
	{
		return $this->scheme;
	}

	/**
	 * Get URI username
	 * Returns the username, or null if no username was specified.
	 *
	 * @return  string  The URI username.
	 *
	 * @since   1.0
	 */
	public function getUser()
	{
		return $this->user;
	}

	/**
	 * Get URI password
	 * Returns the password, or null if no password was specified.
	 *
	 * @return  string  The URI password.
	 *
	 * @since   1.0
	 */
	public function getPass()
	{
		return $this->pass;
	}

	/**
	 * Get URI host
	 * Returns the hostname/ip or null if no hostname/ip was specified.
	 *
	 * @return  string  The URI host.
	 *
	 * @since   1.0
	 */
	public function getHost()
	{
		return $this->host;
	}

	/**
	 * Get URI port
	 * Returns the port number, or null if no port was specified.
	 *
	 * @return  integer  The URI port number.
	 *
	 * @since   1.0
	 */
	public function getPort()
	{
		return (isset($this->port)) ? $this->port : null;
	}

	/**
	 * Gets the URI path string.
	 *
	 * @return  string  The URI path string.
	 *
	 * @since   1.0
	 */
	public function getPath()
	{
		return $this->path;
	}

	/**
	 * Get the URI anchor string
	 * Everything after the "#".
	 *
	 * @return  string  The URI anchor string.
	 *
	 * @since   1.0
	 */
	public function getFragment()
	{
		return $this->fragment;
	}

	/**
	 * Checks whether the current URI is using HTTPS.
	 *
	 * @return  boolean  True if using SSL via HTTPS.
	 *
	 * @since   1.0
	 */
	public function isSsl()
	{
		return $this->getScheme() == 'https' ? true : false;
	}

	/**
	 * Build a query from an array (reverse of the PHP parse_str()).
	 *
	 * @param   array  $params  The array of key => value pairs to return as a query string.
	 *
	 * @return  string  The resulting query string.
	 *
	 * @see     parse_str()
	 * @since   1.0
	 */
	protected static function buildQuery(array $params)
	{
		return urldecode(http_build_query($params, '', '&'));
	}

	/**
	 * Parse a given URI and populate the class fields.
	 *
	 * @param   string  $uri  The URI string to parse.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.0
	 */
	protected function parse($uri)
	{
		// Set the original URI to fall back on
		$this->uri = $uri;

		/*
		 * Parse the URI and populate the object fields. If URI is parsed properly,
		 * set method return value to true.
		 */

		$parts = UriHelper::parse_url($uri);

		$retval = ($parts) ? true : false;

		// We need to replace &amp; with & for parse_str to work right...
		if (isset($parts['query']) && strpos($parts['query'], '&amp;'))
		{
			$parts['query'] = str_replace('&amp;', '&', $parts['query']);
		}

		$this->scheme   = isset($parts['scheme']) ? $parts['scheme'] : null;
		$this->user     = isset($parts['user']) ? $parts['user'] : null;
		$this->pass     = isset($parts['pass']) ? $parts['pass'] : null;
		$this->host     = isset($parts['host']) ? $parts['host'] : null;
		$this->port     = isset($parts['port']) ? $parts['port'] : null;
		$this->path     = isset($parts['path']) ? $parts['path'] : null;
		$this->query    = isset($parts['query']) ? $parts['query'] : null;
		$this->fragment = isset($parts['fragment']) ? $parts['fragment'] : null;

		// Parse the query
		if (isset($parts['query']))
		{
			parse_str($parts['query'], $this->vars);
		}

		return $retval;
	}

	/**
	 * Resolves //, ../ and ./ from a path and returns
	 * the result. Eg:
	 *
	 * /foo/bar/../boo.php	=> /foo/boo.php
	 * /foo/bar/../../boo.php => /boo.php
	 * /foo/bar/.././/boo.php => /foo/boo.php
	 *
	 * @param   string  $path  The URI path to clean.
	 *
	 * @return  string  Cleaned and resolved URI path.
	 *
	 * @since   1.0
	 */
	protected function cleanPath($path)
	{
		$path = explode('/', preg_replace('#(/+)#', '/', $path));

		for ($i = 0, $n = \count($path); $i < $n; $i++)
		{
			if ($path[$i] == '.' || $path[$i] == '..')
			{
				if (($path[$i] == '.') || ($path[$i] == '..' && $i == 1 && $path[0] == ''))
				{
					unset($path[$i]);
					$path = array_values($path);
					$i--;
					$n--;
				}
				elseif ($path[$i] == '..' && ($i > 1 || ($i == 1 && $path[0] != '')))
				{
					unset($path[$i]);
					unset($path[$i - 1]);
					$path = array_values($path);
					$i -= 2;
					$n -= 2;
				}
			}
		}

		return implode('/', $path);
	}
}
vendor/joomla/di/LICENSE000064400000042630152177723700010743 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/di/src/ContainerAwareInterface.php000064400000001502152177723700015752 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI;

/**
 * Defines the interface for a Container Aware class.
 *
 * @since  1.0
 */
interface ContainerAwareInterface
{
	/**
	 * Get the DI container.
	 *
	 * @return  Container
	 *
	 * @since   1.0
	 * @throws  \UnexpectedValueException May be thrown if the container has not been set.
	 * @deprecated  2.0  The getter will no longer be part of the interface.
	 */
	public function getContainer();

	/**
	 * Set the DI container.
	 *
	 * @param   Container  $container  The DI container.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function setContainer(Container $container);
}
vendor/joomla/di/src/ServiceProviderInterface.php000064400000001077152177723700016172 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI;

/**
 * Defines the interface for a Service Provider.
 *
 * @since  1.0
 */
interface ServiceProviderInterface
{
	/**
	 * Registers the service provider with a DI container.
	 *
	 * @param   Container  $container  The DI container.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function register(Container $container);
}
vendor/joomla/di/src/ContainerAwareTrait.php000064400000002223152177723700015136 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI;

/**
 * Defines the trait for a Container Aware Class.
 *
 * @since  1.2
 * @note   Traits are available in PHP 5.4+
 */
trait ContainerAwareTrait
{
	/**
	 * DI Container
	 *
	 * @var    Container
	 * @since  1.2
	 */
	private $container;

	/**
	 * Get the DI container.
	 *
	 * @return  Container
	 *
	 * @since   1.2
	 * @throws  \UnexpectedValueException May be thrown if the container has not been set.
	 * @note    As of 2.0 this method will be protected.
	 */
	public function getContainer()
	{
		if ($this->container)
		{
			return $this->container;
		}

		throw new \UnexpectedValueException('Container not set in ' . __CLASS__);
	}

	/**
	 * Set the DI container.
	 *
	 * @param   Container  $container  The DI container.
	 *
	 * @return  mixed  Returns itself to support chaining.
	 *
	 * @since   1.2
	 */
	public function setContainer(Container $container)
	{
		$this->container = $container;

		return $this;
	}
}
vendor/joomla/di/src/Container.php000064400000032560152177723700013161 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI;

use Joomla\DI\Exception\DependencyResolutionException;
use Joomla\DI\Exception\KeyNotFoundException;
use Joomla\DI\Exception\ProtectedKeyException;
use Psr\Container\ContainerInterface;

/**
 * The Container class.
 *
 * @since  1.0
 */
class Container implements ContainerInterface
{
	/**
	 * Holds the key aliases.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $aliases = array();

	/**
	 * Holds the shared instances.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $instances = array();

	/**
	 * Holds the keys, their callbacks, and whether or not
	 * the item is meant to be a shared resource.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $dataStore = array();

	/**
	 * Parent for hierarchical containers.
	 *
	 * @var    Container|ContainerInterface
	 * @since  1.0
	 */
	protected $parent;

	/**
	 * Holds the service tag mapping.
	 *
	 * @var    array
	 * @since  1.5.0
	 */
	protected $tags = array();

	/**
	 * Constructor for the DI Container
	 *
	 * @param   ContainerInterface  $parent  Parent for hierarchical containers.
	 *
	 * @since   1.0
	 */
	public function __construct(ContainerInterface $parent = null)
	{
		$this->parent = $parent;
	}

	/**
	 * Create an alias for a given key for easy access.
	 *
	 * @param   string  $alias  The alias name
	 * @param   string  $key    The key to alias
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 */
	public function alias($alias, $key)
	{
		$this->aliases[$alias] = $key;

		return $this;
	}

	/**
	 * Search the aliases property for a matching alias key.
	 *
	 * @param   string  $key  The key to search for.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	protected function resolveAlias($key)
	{
		if (isset($this->aliases[$key]))
		{
			return $this->aliases[$key];
		}

		if ($this->parent instanceof Container)
		{
			return $this->parent->resolveAlias($key);
		}

		return $key;
	}

	/**
	 * Assign a tag to services.
	 *
	 * @param   string  $tag   The tag name
	 * @param   array   $keys  The service keys to tag
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.5.0
	 */
	public function tag($tag, array $keys)
	{
		foreach ($keys as $key)
		{
			$resolvedKey = $this->resolveAlias($key);

			if (!isset($this->tags[$tag]))
			{
				$this->tags[$tag] = array();
			}

			$this->tags[$tag][] = $resolvedKey;
		}

		// Prune duplicates
		$this->tags[$tag] = array_unique($this->tags[$tag]);

		return $this;
	}

	/**
	 * Fetch all services registered to the given tag.
	 *
	 * @param   string  $tag  The tag name
	 *
	 * @return  array  The resolved services for the given tag
	 *
	 * @since   1.5.0
	 */
	public function getTagged($tag)
	{
		$services = array();

		if (isset($this->tags[$tag]))
		{
			foreach ($this->tags[$tag] as $service)
			{
				$services[] = $this->get($service);
			}
		}

		return $services;
	}

	/**
	 * Build an object of class $key;
	 *
	 * @param   string   $key     The class name to build.
	 * @param   boolean  $shared  True to create a shared resource.
	 *
	 * @return  object|false  Instance of class specified by $key with all dependencies injected.
	 *                        Returns an object if the class exists and false otherwise
	 *
	 * @since   1.0
	 */
	public function buildObject($key, $shared = false)
	{
		static $buildStack = array();

		$resolvedKey = $this->resolveAlias($key);

		if (in_array($resolvedKey, $buildStack, true))
		{
			$buildStack = array();

			throw new DependencyResolutionException("Can't resolve circular dependency");
		}

		$buildStack[] = $resolvedKey;

		if ($this->has($resolvedKey))
		{
			$resource = $this->get($resolvedKey);
			array_pop($buildStack);

			return $resource;
		}

		try
		{
			$reflection = new \ReflectionClass($resolvedKey);
		}
		catch (\ReflectionException $e)
		{
			array_pop($buildStack);

			return false;
		}

		if (!$reflection->isInstantiable())
		{
			$buildStack = array();

			throw new DependencyResolutionException("$resolvedKey can not be instantiated.");
		}

		$constructor = $reflection->getConstructor();

		// If there are no parameters, just return a new object.
		if ($constructor === null)
		{
			$callback = function () use ($resolvedKey) {
				return new $resolvedKey;
			};
		}
		else
		{
			$newInstanceArgs = $this->getMethodArgs($constructor);

			// Create a callable for the dataStore
			$callback = function () use ($reflection, $newInstanceArgs) {
				return $reflection->newInstanceArgs($newInstanceArgs);
			};
		}

		$this->set($resolvedKey, $callback, $shared);

		$resource = $this->get($resolvedKey);
		array_pop($buildStack);

		return $resource;
	}

	/**
	 * Convenience method for building a shared object.
	 *
	 * @param   string  $key  The class name to build.
	 *
	 * @return  object|false  Instance of class specified by $key with all dependencies injected.
	 *                        Returns an object if the class exists and false otherwise
	 *
	 * @since   1.0
	 */
	public function buildSharedObject($key)
	{
		return $this->buildObject($key, true);
	}

	/**
	 * Create a child Container with a new property scope that
	 * that has the ability to access the parent scope when resolving.
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 */
	public function createChild()
	{
		return new static($this);
	}

	/**
	 * Extend a defined service Closure by wrapping the existing one with a new Closure.  This
	 * works very similar to a decorator pattern.  Note that this only works on service Closures
	 * that have been defined in the current Provider, not parent providers.
	 *
	 * @param   string    $key       The unique identifier for the Closure or property.
	 * @param   \Closure  $callable  A Closure to wrap the original service Closure.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  KeyNotFoundException
	 */
	public function extend($key, \Closure $callable)
	{
		$key = $this->resolveAlias($key);
		$raw = $this->getRaw($key);

		if ($raw === null)
		{
			throw new KeyNotFoundException(sprintf('The requested key %s does not exist to extend.', $key));
		}

		$closure = function ($c) use ($callable, $raw) {
			return $callable($raw['callback']($c), $c);
		};

		$this->set($key, $closure, $raw['shared']);
	}

	/**
	 * Build an array of constructor parameters.
	 *
	 * @param   \ReflectionMethod  $method  Method for which to build the argument array.
	 *
	 * @return  array  Array of arguments to pass to the method.
	 *
	 * @since   1.0
	 * @throws  DependencyResolutionException
	 */
	protected function getMethodArgs(\ReflectionMethod $method)
	{
		$methodArgs = array();

		foreach ($method->getParameters() as $param)
		{
			$dependency = $param->getClass();
			$dependencyVarName = $param->getName();

			// If we have a dependency, that means it has been type-hinted.
			if ($dependency !== null)
			{
				$dependencyClassName = $dependency->getName();

				// If the dependency class name is registered with this container or a parent, use it.
				if ($this->getRaw($dependencyClassName) !== null)
				{
					$depObject = $this->get($dependencyClassName);
				}
				else
				{
					$depObject = $this->buildObject($dependencyClassName);
				}

				if ($depObject instanceof $dependencyClassName)
				{
					$methodArgs[] = $depObject;
					continue;
				}
			}

			// Finally, if there is a default parameter, use it.
			if ($param->isOptional())
			{
				$methodArgs[] = $param->getDefaultValue();
				continue;
			}

			// Couldn't resolve dependency, and no default was provided.
			throw new DependencyResolutionException(sprintf('Could not resolve dependency: %s', $dependencyVarName));
		}

		return $methodArgs;
	}

	/**
	 * Method to set the key and callback to the dataStore array.
	 *
	 * @param   string   $key        Name of dataStore key to set.
	 * @param   mixed    $value      Callable function to run or string to retrive when requesting the specified $key.
	 * @param   boolean  $shared     True to create and store a shared instance.
	 * @param   boolean  $protected  True to protect this item from being overwritten. Useful for services.
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 * @throws  ProtectedKeyException  Thrown if the provided key is already set and is protected.
	 */
	public function set($key, $value, $shared = false, $protected = false)
	{
		if (isset($this->dataStore[$key]) && $this->dataStore[$key]['protected'] === true)
		{
			throw new ProtectedKeyException(sprintf("Key %s is protected and can't be overwritten.", $key));
		}

		// If the provided $value is not a closure, make it one now for easy resolution.
		if (!is_callable($value))
		{
			$value = function () use ($value) {
				return $value;
			};
		}

		$this->dataStore[$key] = array(
			'callback' => $value,
			'shared' => $shared,
			'protected' => $protected
		);

		return $this;
	}

	/**
	 * Convenience method for creating protected keys.
	 *
	 * @param   string   $key     Name of dataStore key to set.
	 * @param   mixed    $value   Callable function to run or string to retrive when requesting the specified $key.
	 * @param   boolean  $shared  True to create and store a shared instance.
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 */
	public function protect($key, $value, $shared = false)
	{
		return $this->set($key, $value, $shared, true);
	}

	/**
	 * Convenience method for creating shared keys.
	 *
	 * @param   string   $key        Name of dataStore key to set.
	 * @param   mixed    $value      Callable function to run or string to retrive when requesting the specified $key.
	 * @param   boolean  $protected  True to protect this item from being overwritten. Useful for services.
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 */
	public function share($key, $value, $protected = false)
	{
		return $this->set($key, $value, true, $protected);
	}

	/**
	 * Method to retrieve the results of running the $callback for the specified $key;
	 *
	 * @param   string   $key       Name of the dataStore key to get.
	 * @param   boolean  $forceNew  True to force creation and return of a new instance.
	 *
	 * @return  mixed   Results of running the $callback for the specified $key.
	 *
	 * @since   1.0
	 * @throws  KeyNotFoundException
	 */
	public function get($key, $forceNew = false)
	{
		$key = $this->resolveAlias($key);
		$raw = $this->getRaw($key);

		if ($raw === null)
		{
			throw new KeyNotFoundException(sprintf('Key %s has not been registered with the container.', $key));
		}

		if ($raw['shared'])
		{
			if ($forceNew || !isset($this->instances[$key]))
			{
				$this->instances[$key] = $raw['callback']($this);
			}

			return $this->instances[$key];
		}

		return call_user_func($raw['callback'], $this);
	}

	/**
	 * Method to check if specified dataStore key exists.
	 *
	 * @param   string  $key  Name of the dataStore key to check.
	 *
	 * @return  boolean  True for success
	 *
	 * @since   1.5.0
	 */
	public function has($key)
	{
		$key = $this->resolveAlias($key);

		$exists = (bool) $this->getRaw($key);

		if ($exists === false && $this->parent instanceof ContainerInterface)
		{
			$exists = $this->parent->has($key);
		}

		return $exists;
	}

	/**
	 * Method to check if specified dataStore key exists.
	 *
	 * @param   string  $key  Name of the dataStore key to check.
	 *
	 * @return  boolean  True for success
	 *
	 * @since   1.0
	 * @deprecated  3.0  Use ContainerInterface::has() instead
	 */
	public function exists($key)
	{
		return $this->has($key);
	}

	/**
	 * Get the raw data assigned to a key.
	 *
	 * @param   string  $key  The key for which to get the stored item.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	protected function getRaw($key)
	{
		if (isset($this->dataStore[$key]))
		{
			return $this->dataStore[$key];
		}

		$aliasKey = $this->resolveAlias($key);

		if ($aliasKey !== $key && isset($this->dataStore[$aliasKey]))
		{
			return $this->dataStore[$aliasKey];
		}

		if ($this->parent instanceof Container)
		{
			return $this->parent->getRaw($key);
		}

		if ($this->parent instanceof ContainerInterface && $this->parent->has($key))
		{
			$callback = $this->parent->get($key);

			if (!is_callable($callback))
			{
				$callback = function () use ($callback) {
					return $callback;
				};
			}

			return array(
				'callback'  => $callback,
				'shared'    => true,
				'protected' => true,
			);
		}

		return null;
	}

	/**
	 * Method to force the container to return a new instance
	 * of the results of the callback for requested $key.
	 *
	 * @param   string  $key  Name of the dataStore key to get.
	 *
	 * @return  mixed   Results of running the $callback for the specified $key.
	 *
	 * @since   1.0
	 */
	public function getNewInstance($key)
	{
		return $this->get($key, true);
	}

	/**
	 * Register a service provider to the container.
	 *
	 * @param   ServiceProviderInterface  $provider  The service provider to register.
	 *
	 * @return  Container  This object for chaining.
	 *
	 * @since   1.0
	 */
	public function registerServiceProvider(ServiceProviderInterface $provider)
	{
		$provider->register($this);

		return $this;
	}

	/**
	 * Retrieve the keys for services assigned to this container.
	 *
	 * @return  array
	 *
	 * @since   1.5.0
	 */
	public function getKeys()
	{
		return array_unique(array_merge(array_keys($this->aliases), array_keys($this->dataStore)));
	}
}
vendor/joomla/di/src/Exception/KeyNotFoundException.php000064400000000731152177723700017254 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI\Exception;

use Psr\Container\NotFoundExceptionInterface;

/**
 * No entry was found in the container.
 *
 * @since  1.5.0
 */
class KeyNotFoundException extends \InvalidArgumentException implements NotFoundExceptionInterface
{
}
vendor/joomla/di/src/Exception/ProtectedKeyException.php000064400000000765152177723700017460 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI\Exception;

use Psr\Container\ContainerExceptionInterface;

/**
 * Attempt to set the value of a protected key, which already is set
 *
 * @since  1.5.0
 */
class ProtectedKeyException extends \OutOfBoundsException implements ContainerExceptionInterface
{
}
vendor/joomla/di/src/Exception/DependencyResolutionException.php000064400000000763152177723700021216 0ustar00<?php
/**
 * Part of the Joomla Framework DI Package
 *
 * @copyright  Copyright (C) 2013 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\DI\Exception;

use Psr\Container\ContainerExceptionInterface;

/**
 * Exception class for handling errors in resolving a dependency
 *
 * @since  1.0
 */
class DependencyResolutionException extends \RuntimeException implements ContainerExceptionInterface
{
}
vendor/joomla/filter/LICENSE000064400000042630152177723700011634 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/filter/src/OutputFilter.php000064400000012703152177723700014573 0ustar00<?php
/**
 * Part of the Joomla Framework Filter Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filter;

use Joomla\Language\Language;
use Joomla\String\StringHelper;

/**
 * OutputFilter
 *
 * @since  1.0
 */
class OutputFilter
{
	/**
	 * Makes an object safe to display in forms
	 *
	 * Object parameters that are non-string, array, object or start with underscore
	 * will be converted
	 *
	 * @param   object   $mixed        An object to be parsed
	 * @param   integer  $quoteStyle   The optional quote style for the htmlspecialchars function
	 * @param   mixed    $excludeKeys  An optional string single field name or array of field names not to be parsed (eg, for a textarea)
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public static function objectHtmlSafe(&$mixed, $quoteStyle = ENT_QUOTES, $excludeKeys = '')
	{
		if (is_object($mixed))
		{
			foreach (get_object_vars($mixed) as $k => $v)
			{
				if (is_array($v) || is_object($v) || $v == null || substr($k, 1, 1) == '_')
				{
					continue;
				}

				if (is_string($excludeKeys) && $k == $excludeKeys)
				{
					continue;
				}
				elseif (is_array($excludeKeys) && in_array($k, $excludeKeys))
				{
					continue;
				}

				$mixed->$k = htmlspecialchars($v, $quoteStyle, 'UTF-8');
			}
		}
	}

	/**
	 * This method processes a string and replaces all instances of & with &amp; in links only.
	 *
	 * @param   string  $input  String to process
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.0
	 */
	public static function linkXhtmlSafe($input)
	{
		$regex = 'href="([^"]*(&(amp;){0})[^"]*)*?"';

		return preg_replace_callback(
			"#$regex#i",
			function ($m)
			{
				return preg_replace('#&(?!amp;)#', '&amp;', $m[0]);
			},
			$input
		);
	}

	/**
	 * This method processes a string and replaces all accented UTF-8 characters by unaccented
	 * ASCII-7 "equivalents", whitespaces are replaced by hyphens and the string is lowercase.
	 *
	 * @param   string  $string    String to process
	 * @param   string  $language  Language to transliterate to
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.0
	 */
	public static function stringUrlSafe($string, $language = '')
	{
		// Remove any '-' from the string since they will be used as concatenaters
		$str = str_replace('-', ' ', $string);

		// Transliterate on the language requested (fallback to current language if not specified)
		$lang = empty($language) ? Language::getInstance() : Language::getInstance($language);
		$str = $lang->transliterate($str);

		// Trim white spaces at beginning and end of alias and make lowercase
		$str = trim(StringHelper::strtolower($str));

		// Remove any duplicate whitespace, and ensure all characters are alphanumeric
		$str = preg_replace('/(\s|[^A-Za-z0-9\-])+/', '-', $str);

		// Trim dashes at beginning and end of alias
		$str = trim($str, '-');

		return $str;
	}

	/**
	 * This method implements unicode slugs instead of transliteration.
	 *
	 * @param   string  $string  String to process
	 *
	 * @return  string  Processed string
	 *
	 * @since   1.0
	 */
	public static function stringUrlUnicodeSlug($string)
	{
		// Replace double byte whitespaces by single byte (East Asian languages)
		$str = preg_replace('/\xE3\x80\x80/', ' ', $string);

		// Remove any '-' from the string as they will be used as concatenator.
		// Would be great to let the spaces in but only Firefox is friendly with this

		$str = str_replace('-', ' ', $str);

		// Replace forbidden characters by whitespaces
		$str = preg_replace('#[:\#\*"@+=;!><&\.%()\]\/\'\\\\|\[]#', "\x20", $str);

		// Delete all '?'
		$str = str_replace('?', '', $str);

		// Trim white spaces at beginning and end of alias and make lowercase
		$str = trim(StringHelper::strtolower($str));

		// Remove any duplicate whitespace and replace whitespaces by hyphens
		$str = preg_replace('#\x20+#', '-', $str);

		return $str;
	}

	/**
	 * Replaces &amp; with & for XHTML compliance
	 *
	 * @param   string  $text  Text to process
	 *
	 * @return  string  Processed string.
	 *
	 * @since   1.0
	 */
	public static function ampReplace($text)
	{
		return preg_replace('/(?<!&)&(?!&|#|[\w]+;)/', '&amp;', $text);
	}

	/**
	 * Cleans text of all formatting and scripting code
	 *
	 * @param   string  $text  Text to clean
	 *
	 * @return  string  Cleaned text.
	 *
	 * @since   1.0
	 */
	public static function cleanText(&$text)
	{
		$text = preg_replace("'<script[^>]*>.*?</script>'si", '', $text);
		$text = preg_replace('/<a\s+.*?href="([^"]+)"[^>]*>([^<]+)<\/a>/is', '\2 (\1)', $text);
		$text = preg_replace('/<!--.+?-->/', '', $text);
		$text = preg_replace('/{.+?}/', '', $text);
		$text = preg_replace('/&nbsp;/', ' ', $text);
		$text = preg_replace('/&amp;/', ' ', $text);
		$text = preg_replace('/&quot;/', ' ', $text);
		$text = strip_tags($text);
		$text = htmlspecialchars($text, ENT_COMPAT, 'UTF-8');

		return $text;
	}

	/**
	 * Strip img-tags from string
	 *
	 * @param   string  $string  Sting to be cleaned.
	 *
	 * @return  string  Cleaned string
	 *
	 * @since   1.0
	 */
	public static function stripImages($string)
	{
		return preg_replace('#(<[/]?img.*>)#U', '', $string);
	}

	/**
	 * Strip iframe-tags from string
	 *
	 * @param   string  $string  Sting to be cleaned.
	 *
	 * @return  string  Cleaned string
	 *
	 * @since   1.0
	 */
	public static function stripIframes($string)
	{
		return preg_replace('#(<[/]?iframe.*>)#U', '', $string);
	}
}
vendor/joomla/filter/src/InputFilter.php000064400000067233152177723700014402 0ustar00<?php
/**
 * Part of the Joomla Framework Filter Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filter;

use Joomla\String\StringHelper;

/**
 * InputFilter is a class for filtering input from any data source
 *
 * Forked from the php input filter library by: Daniel Morris <dan@rootcube.com>
 * Original Contributors: Gianpaolo Racca, Ghislain Picard, Marco Wandschneider, Chris Tobin and Andrew Eddie.
 *
 * @since  1.0
 */
class InputFilter
{
	/**
	 * Defines the InputFilter instance should use a whitelist method for sanitising tags.
	 *
	 * @var    integer
	 * @since  1.3.0
	 */
	const TAGS_WHITELIST = 0;

	/**
	 * Defines the InputFilter instance should use a blacklist method for sanitising tags.
	 *
	 * @var    integer
	 * @since  1.3.0
	 */
	const TAGS_BLACKLIST = 1;

	/**
	 * Defines the InputFilter instance should use a whitelist method for sanitising attributes.
	 *
	 * @var    integer
	 * @since  1.3.0
	 */
	const ATTR_WHITELIST = 0;

	/**
	 * Defines the InputFilter instance should use a blacklist method for sanitising attributes.
	 *
	 * @var    integer
	 * @since  1.3.0
	 */
	const ATTR_BLACKLIST = 1;

	/**
	 * A container for InputFilter instances.
	 *
	 * @var    InputFilter[]
	 * @since  1.0
	 * @deprecated  1.2.0
	 */
	protected static $instances = array();

	/**
	 * The array of permitted tags (whitelist).
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $tagsArray;

	/**
	 * The array of permitted tag attributes (whitelist).
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $attrArray;

	/**
	 * The method for sanitising tags
	 *
	 * @var    integer
	 * @since  1.0
	 */
	public $tagsMethod;

	/**
	 * The method for sanitising attributes
	 *
	 * @var    integer
	 * @since  1.0
	 */
	public $attrMethod;

	/**
	 * A flag for XSS checks. Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 *
	 * @var    integer
	 * @since  1.0
	 */
	public $xssAuto;

	/**
	 * The list of the default blacklisted tags.
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $tagBlacklist = array(
		'applet',
		'body',
		'bgsound',
		'base',
		'basefont',
		'canvas',
		'embed',
		'frame',
		'frameset',
		'head',
		'html',
		'id',
		'iframe',
		'ilayer',
		'layer',
		'link',
		'meta',
		'name',
		'object',
		'script',
		'style',
		'title',
		'xml',
	);

	/**
	 * The list of the default blacklisted tag attributes. All event handlers implicit.
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $attrBlacklist = array(
		'action',
		'background',
		'codebase',
		'dynsrc',
		'formaction',
		'lowsrc',
	);

	/**
	 * A special list of blacklisted chars
	 *
	 * @var    array
	 * @since  1.3.3
	 */
	private $blacklistedChars = array(
		'&tab;',
		'&space;',
		'&colon;',
		'&column;',
	);

	/**
	 * Constructor for InputFilter class.
	 *
	 * @param   array    $tagsArray   List of user-defined tags
	 * @param   array    $attrArray   List of user-defined attributes
	 * @param   integer  $tagsMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $attrMethod  WhiteList method = 0, BlackList method = 1
	 * @param   integer  $xssAuto     Only auto clean essentials = 0, Allow clean blacklisted tags/attr = 1
	 *
	 * @since   1.0
	 */
	public function __construct($tagsArray = array(), $attrArray = array(), $tagsMethod = self::TAGS_WHITELIST, $attrMethod = self::ATTR_WHITELIST,
		$xssAuto = 1
	)
	{
		// Make sure user defined arrays are in lowercase
		$tagsArray = array_map('strtolower', (array) $tagsArray);
		$attrArray = array_map('strtolower', (array) $attrArray);

		// Assign member variables
		$this->tagsArray  = $tagsArray;
		$this->attrArray  = $attrArray;
		$this->tagsMethod = $tagsMethod;
		$this->attrMethod = $attrMethod;
		$this->xssAuto    = $xssAuto;
	}

	/**
	 * Method to be called by another php script. Processes for XSS and
	 * specified bad code.
	 *
	 * @param   mixed   $source  Input string/array-of-string to be 'cleaned'
	 * @param   string  $type    The return type for the variable:
	 *                           INT:       An integer, or an array of integers,
	 *                           UINT:      An unsigned integer, or an array of unsigned integers,
	 *                           FLOAT:     A floating point number, or an array of floating point numbers,
	 *                           BOOLEAN:   A boolean value,
	 *                           WORD:      A string containing A-Z or underscores only (not case sensitive),
	 *                           ALNUM:     A string containing A-Z or 0-9 only (not case sensitive),
	 *                           CMD:       A string containing A-Z, 0-9, underscores, periods or hyphens (not case sensitive),
	 *                           BASE64:    A string containing A-Z, 0-9, forward slashes, plus or equals (not case sensitive),
	 *                           STRING:    A fully decoded and sanitised string (default),
	 *                           HTML:      A sanitised string,
	 *                           ARRAY:     An array,
	 *                           PATH:      A sanitised file path, or an array of sanitised file paths,
	 *                           TRIM:      A string trimmed from normal, non-breaking and multibyte spaces
	 *                           USERNAME:  Do not use (use an application specific filter),
	 *                           RAW:       The raw string is returned with no filtering,
	 *                           unknown:   An unknown filter will act like STRING. If the input is an array it will return an
	 *                                      array of fully decoded and sanitised strings.
	 *
	 * @return  mixed  'Cleaned' version of input parameter
	 *
	 * @since   1.0
	 */
	public function clean($source, $type = 'string')
	{
		// Handle the type constraint cases
		switch (strtoupper($type))
		{
			case 'INT':
			case 'INTEGER':
				$pattern = '/[-+]?[0-9]+/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (int) $matches[0] : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? (int) $matches[0] : 0;
				}

				break;

			case 'UINT':
				$pattern = '/[-+]?[0-9]+/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? abs((int) $matches[0]) : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? abs((int) $matches[0]) : 0;
				}

				break;

			case 'FLOAT':
			case 'DOUBLE':
				$pattern = '/[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (float) $matches[0] : 0;
					}
				}
				else
				{
					preg_match($pattern, (string) $source, $matches);
					$result = isset($matches[0]) ? (float) $matches[0] : 0;
				}

				break;

			case 'BOOL':
			case 'BOOLEAN':

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (bool) $eachString;
					}
				}
				else
				{
					$result = (bool) $source;
				}

				break;

			case 'WORD':
				$pattern = '/[^A-Z_]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;

			case 'ALNUM':
				$pattern = '/[^A-Z0-9]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;

			case 'CMD':
				$pattern = '/[^A-Z0-9_\.-]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$cleaned  = (string) preg_replace($pattern, '', $eachString);
						$result[] = ltrim($cleaned, '.');
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
					$result = ltrim($result, '.');
				}

				break;

			case 'BASE64':
				$pattern = '/[^A-Z0-9\/+=]/i';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;

			case 'STRING':
				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) $this->remove($this->decode((string) $eachString));
					}
				}
				else
				{
					$result = (string) $this->remove($this->decode((string) $source));
				}

				break;

			case 'HTML':
				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) $this->remove((string) $eachString);
					}
				}
				else
				{
					$result = (string) $this->remove((string) $source);
				}

				break;

			case 'ARRAY':
				$result = (array) $source;
				break;

			case 'PATH':
				$pattern = '/^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/][A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						preg_match($pattern, (string) $eachString, $matches);
						$result[] = isset($matches[0]) ? (string) $matches[0] : '';
					}
				}
				else
				{
					preg_match($pattern, $source, $matches);
					$result = isset($matches[0]) ? (string) $matches[0] : '';
				}

				break;

			case 'TRIM':
				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$cleaned  = (string) trim($eachString);
						$cleaned  = StringHelper::trim($cleaned, chr(0xE3) . chr(0x80) . chr(0x80));
						$result[] = StringHelper::trim($cleaned, chr(0xC2) . chr(0xA0));
					}
				}
				else
				{
					$result = (string) trim($source);
					$result = StringHelper::trim($result, chr(0xE3) . chr(0x80) . chr(0x80));
					$result = StringHelper::trim($result, chr(0xC2) . chr(0xA0));
				}

				break;

			case 'USERNAME':
				$pattern = '/[\x00-\x1F\x7F<>"\'%&]/';

				if (is_array($source))
				{
					$result = array();

					// Iterate through the array
					foreach ($source as $eachString)
					{
						$result[] = (string) preg_replace($pattern, '', $eachString);
					}
				}
				else
				{
					$result = (string) preg_replace($pattern, '', $source);
				}

				break;

			case 'RAW':
				$result = $source;
				break;

			default:
				// Are we dealing with an array?
				if (is_array($source))
				{
					foreach ($source as $key => $value)
					{
						// Filter element for XSS and other 'bad' code etc.
						if (is_string($value))
						{
							$source[$key] = $this->remove($this->decode($value));
						}
					}

					$result = $source;
				}
				else
				{
					// Or a string?
					if (is_string($source) && !empty($source))
					{
						// Filter source for XSS and other 'bad' code etc.
						$result = $this->remove($this->decode($source));
					}
					else
					{
						// Not an array or string... return the passed parameter
						$result = $source;
					}
				}

				break;
		}

		return $result;
	}

	/**
	 * Function to determine if contents of an attribute are safe
	 *
	 * @param   array  $attrSubSet  A 2 element array for attribute's name, value
	 *
	 * @return  boolean  True if bad code is detected
	 *
	 * @since   1.0
	 */
	public static function checkAttribute($attrSubSet)
	{
		$quoteStyle = version_compare(PHP_VERSION, '5.4', '>=') ? ENT_QUOTES | ENT_HTML401 : ENT_QUOTES;

		$attrSubSet[0] = strtolower($attrSubSet[0]);
		$attrSubSet[1] = html_entity_decode(strtolower($attrSubSet[1]), $quoteStyle, 'UTF-8');

		return ((strpos($attrSubSet[1], 'expression') !== false && $attrSubSet[0] === 'style')
			|| preg_match('/(?:(?:java|vb|live)script|behaviour|mocha)(?::|&colon;|&column;)/', $attrSubSet[1]) !== 0);
	}

	/**
	 * Internal method to iteratively remove all unwanted tags and attributes
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since   1.0
	 */
	protected function remove($source)
	{
		// Iteration provides nested tag protection
		do
		{
			$temp = $source;
			$source = $this->cleanTags($source);
		}
		while ($temp !== $source);

		return $source;
	}

	/**
	 * Internal method to strip a string of certain tags
	 *
	 * @param   string  $source  Input string to be 'cleaned'
	 *
	 * @return  string  'Cleaned' version of input parameter
	 *
	 * @since   1.0
	 */
	protected function cleanTags($source)
	{
		// First, pre-process this for illegal characters inside attribute values
		$source = $this->escapeAttributeValues($source);

		// In the beginning we don't really have a tag, so everything is postTag
		$preTag = null;
		$postTag = $source;
		$currentSpace = false;

		// Setting to null to deal with undefined variables
		$attr = '';

		// Is there a tag? If so it will certainly start with a '<'.
		$tagOpenStart = StringHelper::strpos($source, '<');

		while ($tagOpenStart !== false)
		{
			// Get some information about the tag we are processing
			$preTag .= StringHelper::substr($postTag, 0, $tagOpenStart);
			$postTag = StringHelper::substr($postTag, $tagOpenStart);
			$fromTagOpen = StringHelper::substr($postTag, 1);
			$tagOpenEnd = StringHelper::strpos($fromTagOpen, '>');

			// Check for mal-formed tag where we have a second '<' before the first '>'
			$nextOpenTag = (StringHelper::strlen($postTag) > $tagOpenStart) ? StringHelper::strpos($postTag, '<', $tagOpenStart + 1) : false;

			if (($nextOpenTag !== false) && ($nextOpenTag < $tagOpenEnd))
			{
				// At this point we have a mal-formed tag -- remove the offending open
				$postTag = StringHelper::substr($postTag, 0, $tagOpenStart) . StringHelper::substr($postTag, $tagOpenStart + 1);
				$tagOpenStart = StringHelper::strpos($postTag, '<');
				continue;
			}

			// Let's catch any non-terminated tags and skip over them
			if ($tagOpenEnd === false)
			{
				$postTag = StringHelper::substr($postTag, $tagOpenStart + 1);
				$tagOpenStart = StringHelper::strpos($postTag, '<');
				continue;
			}

			// Do we have a nested tag?
			$tagOpenNested = StringHelper::strpos($fromTagOpen, '<');

			if (($tagOpenNested !== false) && ($tagOpenNested < $tagOpenEnd))
			{
				$preTag .= StringHelper::substr($postTag, 0, ($tagOpenNested + 1));
				$postTag = StringHelper::substr($postTag, ($tagOpenNested + 1));
				$tagOpenStart = StringHelper::strpos($postTag, '<');
				continue;
			}

			// Let's get some information about our tag and setup attribute pairs
			$tagOpenNested = (StringHelper::strpos($fromTagOpen, '<') + $tagOpenStart + 1);
			$currentTag = StringHelper::substr($fromTagOpen, 0, $tagOpenEnd);
			$tagLength = StringHelper::strlen($currentTag);
			$tagLeft = $currentTag;
			$attrSet = array();
			$currentSpace = StringHelper::strpos($tagLeft, ' ');

			// Are we an open tag or a close tag?
			if (StringHelper::substr($currentTag, 0, 1) === '/')
			{
				// Close Tag
				$isCloseTag = true;
				list ($tagName) = explode(' ', $currentTag);
				$tagName = StringHelper::substr($tagName, 1);
			}
			else
			{
				// Open Tag
				$isCloseTag = false;
				list ($tagName) = explode(' ', $currentTag);
			}

			/*
			 * Exclude all "non-regular" tagnames
			 * OR no tagname
			 * OR remove if xssauto is on and tag is blacklisted
			 */
			if ((!preg_match("/^[a-z][a-z0-9]*$/i", $tagName))
				|| (!$tagName)
				|| ((in_array(strtolower($tagName), $this->tagBlacklist)) && ($this->xssAuto)))
			{
				$postTag = StringHelper::substr($postTag, ($tagLength + 2));
				$tagOpenStart = StringHelper::strpos($postTag, '<');

				// Strip tag
				continue;
			}

			/*
			 * Time to grab any attributes from the tag... need this section in
			 * case attributes have spaces in the values.
			 */
			while ($currentSpace !== false)
			{
				$attr = '';
				$fromSpace = StringHelper::substr($tagLeft, ($currentSpace + 1));
				$nextEqual = StringHelper::strpos($fromSpace, '=');
				$nextSpace = StringHelper::strpos($fromSpace, ' ');
				$openQuotes = StringHelper::strpos($fromSpace, '"');
				$closeQuotes = StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') + $openQuotes + 1;

				$startAtt = '';
				$startAttPosition = 0;

				// Find position of equal and open quotes ignoring
				if (preg_match('#\s*=\s*\"#', $fromSpace, $matches, PREG_OFFSET_CAPTURE))
				{
					// We have found an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr()
					$stringBeforeAttr = substr($fromSpace, 0, $matches[0][1]);
					$startAttPosition = StringHelper::strlen($stringBeforeAttr);
					$startAtt = $matches[0][0];
					$closeQuotePos = StringHelper::strpos(
						StringHelper::substr($fromSpace, ($startAttPosition + StringHelper::strlen($startAtt))), '"'
					);
					$closeQuotes = $closeQuotePos + $startAttPosition + StringHelper::strlen($startAtt);
					$nextEqual = $startAttPosition + StringHelper::strpos($startAtt, '=');
					$openQuotes = $startAttPosition + StringHelper::strpos($startAtt, '"');
					$nextSpace = StringHelper::strpos(StringHelper::substr($fromSpace, $closeQuotes), ' ') + $closeQuotes;
				}

				// Do we have an attribute to process? [check for equal sign]
				if ($fromSpace !== '/' && (($nextEqual && $nextSpace && $nextSpace < $nextEqual) || !$nextEqual))
				{
					if (!$nextEqual)
					{
						$attribEnd = StringHelper::strpos($fromSpace, '/') - 1;
					}
					else
					{
						$attribEnd = $nextSpace - 1;
					}

					// If there is an ending, use this, if not, do not worry.
					if ($attribEnd > 0)
					{
						$fromSpace = StringHelper::substr($fromSpace, $attribEnd + 1);
					}
				}

				if (StringHelper::strpos($fromSpace, '=') !== false)
				{
					/*
					 * If the attribute value is wrapped in quotes we need to grab the substring from the closing quote,
					 * otherwise grab until the next space.
					 */
					if (($openQuotes !== false)
						&& (StringHelper::strpos(StringHelper::substr($fromSpace, ($openQuotes + 1)), '"') !== false))
					{
						$attr = StringHelper::substr($fromSpace, 0, ($closeQuotes + 1));
					}
					else
					{
						$attr = StringHelper::substr($fromSpace, 0, $nextSpace);
					}
				}
				else
				// No more equal signs so add any extra text in the tag into the attribute array [eg. checked]
				{
					if ($fromSpace !== '/')
					{
						$attr = StringHelper::substr($fromSpace, 0, $nextSpace);
					}
				}

				// Last Attribute Pair
				if (!$attr && $fromSpace !== '/')
				{
					$attr = $fromSpace;
				}

				// Add attribute pair to the attribute array
				$attrSet[] = $attr;

				// Move search point and continue iteration
				$tagLeft = StringHelper::substr($fromSpace, StringHelper::strlen($attr));
				$currentSpace = StringHelper::strpos($tagLeft, ' ');
			}

			// Is our tag in the user input array?
			$tagFound = in_array(strtolower($tagName), $this->tagsArray);

			// If the tag is allowed let's append it to the output string.
			if ((!$tagFound && $this->tagsMethod) || ($tagFound && !$this->tagsMethod))
			{
				// Reconstruct tag with allowed attributes
				if (!$isCloseTag)
				{
					// Open or single tag
					$attrSet = $this->cleanAttributes($attrSet);
					$preTag .= '<' . $tagName;

					for ($i = 0, $count = count($attrSet); $i < $count; $i++)
					{
						$preTag .= ' ' . $attrSet[$i];
					}

					// Reformat single tags to XHTML
					if (StringHelper::strpos($fromTagOpen, '</' . $tagName))
					{
						$preTag .= '>';
					}
					else
					{
						$preTag .= ' />';
					}
				}
				else
				// Closing tag
				{
					$preTag .= '</' . $tagName . '>';
				}
			}

			// Find next tag's start and continue iteration
			$postTag = StringHelper::substr($postTag, ($tagLength + 2));
			$tagOpenStart = StringHelper::strpos($postTag, '<');
		}

		// Append any code after the end of tags and return
		if ($postTag !== '<')
		{
			$preTag .= $postTag;
		}

		return $preTag;
	}

	/**
	 * Internal method to strip a tag of certain attributes
	 *
	 * @param   array  $attrSet  Array of attribute pairs to filter
	 *
	 * @return  array  Filtered array of attribute pairs
	 *
	 * @since   1.0
	 */
	protected function cleanAttributes($attrSet)
	{
		$newSet = array();

		$count = count($attrSet);

		// Iterate through attribute pairs
		for ($i = 0; $i < $count; $i++)
		{
			// Skip blank spaces
			if (!$attrSet[$i])
			{
				continue;
			}

			// Split into name/value pairs
			$attrSubSet = explode('=', trim($attrSet[$i]), 2);

			// Take the last attribute in case there is an attribute with no value
			$attrSubSet0   = explode(' ', trim($attrSubSet[0]));
			$attrSubSet[0] = array_pop($attrSubSet0);

			$attrSubSet[0] = strtolower($attrSubSet[0]);
			$quoteStyle = version_compare(PHP_VERSION, '5.4', '>=') ? ENT_QUOTES | ENT_HTML401 : ENT_QUOTES;

			// Remove all spaces as valid attributes does not have spaces.
			$attrSubSet[0] = html_entity_decode($attrSubSet[0], $quoteStyle, 'UTF-8');
			$attrSubSet[0] = preg_replace('/^[\pZ\pC]+|[\pZ\pC]+$/u', '', $attrSubSet[0]);
			$attrSubSet[0] = preg_replace('/\s+/u', '', $attrSubSet[0]);

			// Remove blacklisted chars from the attribute name
			foreach ($this->blacklistedChars as $blacklistedChar)
			{
				$attrSubSet[0] = str_ireplace($blacklistedChar, '', $attrSubSet[0]);
			}

			// Remove all symbols
			$attrSubSet[0] = preg_replace('/[^\p{L}\p{N}\-\s]/u', '', $attrSubSet[0]);

			// Remove all "non-regular" attribute names
			// AND blacklisted attributes
			if ((!preg_match('/[a-z]*$/i', $attrSubSet[0]))
				|| (($this->xssAuto) && ((in_array(strtolower($attrSubSet[0]), $this->attrBlacklist))
				|| (substr($attrSubSet[0], 0, 2) == 'on'))))
			{
				continue;
			}

			// XSS attribute value filtering
			if (!isset($attrSubSet[1]))
			{
				continue;
			}

			// Remove blacklisted chars from the attribute value
			foreach ($this->blacklistedChars as $blacklistedChar)
			{
				$attrSubSet[1] = str_ireplace($blacklistedChar, '', $attrSubSet[1]);
			}

			// Trim leading and trailing spaces
			$attrSubSet[1] = trim($attrSubSet[1]);

			// Strips unicode, hex, etc
			$attrSubSet[1] = str_replace('&#', '', $attrSubSet[1]);

			// Strip normal newline within attr value
			$attrSubSet[1] = preg_replace('/[\n\r]/', '', $attrSubSet[1]);

			// Strip double quotes
			$attrSubSet[1] = str_replace('"', '', $attrSubSet[1]);

			// Convert single quotes from either side to doubles (Single quotes shouldn't be used to pad attr values)
			if ((substr($attrSubSet[1], 0, 1) == "'") && (substr($attrSubSet[1], (strlen($attrSubSet[1]) - 1), 1) == "'"))
			{
				$attrSubSet[1] = substr($attrSubSet[1], 1, (strlen($attrSubSet[1]) - 2));
			}

			// Strip slashes
			$attrSubSet[1] = stripslashes($attrSubSet[1]);

			// Autostrip script tags
			if (static::checkAttribute($attrSubSet))
			{
				continue;
			}

			// Is our attribute in the user input array?
			$attrFound = in_array(strtolower($attrSubSet[0]), $this->attrArray);

			// If the tag is allowed lets keep it
			if ((!$attrFound && $this->attrMethod) || ($attrFound && !$this->attrMethod))
			{
				// Does the attribute have a value?
				if (empty($attrSubSet[1]) === false)
				{
					$newSet[] = $attrSubSet[0] . '="' . $attrSubSet[1] . '"';
				}
				elseif ($attrSubSet[1] === "0")
				{
					// Special Case
					// Is the value 0?
					$newSet[] = $attrSubSet[0] . '="0"';
				}
				else
				{
					// Leave empty attributes alone
					$newSet[] = $attrSubSet[0] . '=""';
				}
			}
		}

		return $newSet;
	}

	/**
	 * Try to convert to plaintext
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Plaintext string
	 *
	 * @since   1.0
	 * @deprecated  This method will be removed once support for PHP 5.3 is discontinued.
	 */
	protected function decode($source)
	{
		return html_entity_decode($source, ENT_QUOTES, 'UTF-8');
	}

	/**
	 * Escape < > and " inside attribute values
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Filtered string
	 *
	 * @since   1.0
	 */
	protected function escapeAttributeValues($source)
	{
		$alreadyFiltered = '';
		$remainder = $source;
		$badChars = array('<', '"', '>');
		$escapedChars = array('&lt;', '&quot;', '&gt;');

		// Process each portion based on presence of =" and "<space>, "/>, or ">
		// See if there are any more attributes to process
		while (preg_match('#<[^>]*?=\s*?(\"|\')#s', $remainder, $matches, PREG_OFFSET_CAPTURE))
		{
			// We have found a tag with an attribute, convert its byte position to a UTF-8 string length, using non-multibyte substr()
			$stringBeforeTag = substr($remainder, 0, $matches[0][1]);
			$tagPosition = StringHelper::strlen($stringBeforeTag);

			// Get the character length before the attribute value
			$nextBefore = $tagPosition + StringHelper::strlen($matches[0][0]);

			// Figure out if we have a single or double quote and look for the matching closing quote
			// Closing quote should be "/>, ">, "<space>, or " at the end of the string
			$quote = StringHelper::substr($matches[0][0], -1);
			$pregMatch = ($quote == '"') ? '#(\"\s*/\s*>|\"\s*>|\"\s+|\"$)#' : "#(\'\s*/\s*>|\'\s*>|\'\s+|\'$)#";

			// Get the portion after attribute value
			$attributeValueRemainder = StringHelper::substr($remainder, $nextBefore);

			if (preg_match($pregMatch, $attributeValueRemainder, $matches, PREG_OFFSET_CAPTURE))
			{
				// We have a closing quote, convert its byte position to a UTF-8 string length, using non-multibyte substr()
				$stringBeforeQuote = substr($attributeValueRemainder, 0, $matches[0][1]);
				$closeQuoteChars = StringHelper::strlen($stringBeforeQuote);
				$nextAfter = $nextBefore + $matches[0][1];
			}
			else
			{
				// No closing quote
				$nextAfter = StringHelper::strlen($remainder);
			}

			// Get the actual attribute value
			$attributeValue = StringHelper::substr($remainder, $nextBefore, $nextAfter - $nextBefore);

			// Escape bad chars
			$attributeValue = str_replace($badChars, $escapedChars, $attributeValue);
			$attributeValue = $this->stripCssExpressions($attributeValue);
			$alreadyFiltered .= StringHelper::substr($remainder, 0, $nextBefore) . $attributeValue . $quote;
			$remainder = StringHelper::substr($remainder, $nextAfter + 1);
		}

		// At this point, we just have to return the $alreadyFiltered and the $remainder
		return $alreadyFiltered . $remainder;
	}

	/**
	 * Remove CSS Expressions in the form of <property>:expression(...)
	 *
	 * @param   string  $source  The source string.
	 *
	 * @return  string  Filtered string
	 *
	 * @since   1.0
	 */
	protected function stripCssExpressions($source)
	{
		// Strip any comments out (in the form of /*...*/)
		$test = preg_replace('#\/\*.*\*\/#U', '', $source);

		// Test for :expression
		if (!stripos($test, ':expression'))
		{
			// Not found, so we are done
			return $source;
		}

		// At this point, we have stripped out the comments and have found :expression
		// Test stripped string for :expression followed by a '('
		if (preg_match_all('#:expression\s*\(#', $test, $matches))
		{
			// If found, remove :expression
			return str_ireplace(':expression', '', $test);
		}

		return $source;
	}
}
vendor/joomla/filesystem/LICENSE000064400000042630152177723700012533 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/filesystem/src/Buffer.php000064400000010145152177723700014233 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

/**
 * Generic Buffer stream handler
 *
 * This class provides a generic buffer stream.  It can be used to store/retrieve/manipulate
 * string buffers with the standard PHP filesystem I/O methods.
 *
 * @since  1.0
 */
class Buffer
{
	/**
	 * Stream position
	 *
	 * @var    integer
	 * @since  1.0
	 */
	public $position = 0;

	/**
	 * Buffer name
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $name;

	/**
	 * Buffer hash
	 *
	 * @var    array
	 * @since  1.0
	 */
	public $buffers = array();

	/**
	 * Function to open file or url
	 *
	 * @param   string   $path        The URL that was passed
	 * @param   string   $mode        Mode used to open the file @see fopen
	 * @param   integer  $options     Flags used by the API, may be STREAM_USE_PATH and STREAM_REPORT_ERRORS
	 * @param   string   $openedPath  Full path of the resource. Used with STREAN_USE_PATH option
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @see     streamWrapper::stream_open
	 */
	public function stream_open($path, $mode, $options, &$openedPath)
	{
		$url                        = parse_url($path);
		$this->name                 = $url['host'];
		$this->buffers[$this->name] = null;
		$this->position             = 0;

		return true;
	}

	/**
	 * Read stream
	 *
	 * @param   integer  $count  How many bytes of data from the current position should be returned.
	 *
	 * @return  mixed    The data from the stream up to the specified number of bytes (all data if
	 *                   the total number of bytes in the stream is less than $count. Null if
	 *                   the stream is empty.
	 *
	 * @see     streamWrapper::stream_read
	 * @since   1.0
	 */
	public function stream_read($count)
	{
		$ret = substr($this->buffers[$this->name], $this->position, $count);
		$this->position += \strlen($ret);

		return $ret;
	}

	/**
	 * Write stream
	 *
	 * @param   string  $data  The data to write to the stream.
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_write
	 * @since   1.0
	 */
	public function stream_write($data)
	{
		$left                       = substr($this->buffers[$this->name], 0, $this->position);
		$right                      = substr($this->buffers[$this->name], $this->position + \strlen($data));
		$this->buffers[$this->name] = $left . $data . $right;
		$this->position += \strlen($data);

		return \strlen($data);
	}

	/**
	 * Function to get the current position of the stream
	 *
	 * @return  integer
	 *
	 * @see     streamWrapper::stream_tell
	 * @since   1.0
	 */
	public function stream_tell()
	{
		return $this->position;
	}

	/**
	 * Function to test for end of file pointer
	 *
	 * @return  boolean  True if the pointer is at the end of the stream
	 *
	 * @see     streamWrapper::stream_eof
	 * @since   1.0
	 */
	public function stream_eof()
	{
		return $this->position >= \strlen($this->buffers[$this->name]);
	}

	/**
	 * The read write position updates in response to $offset and $whence
	 *
	 * @param   integer  $offset  The offset in bytes
	 * @param   integer  $whence  Position the offset is added to
	 *                            Options are SEEK_SET, SEEK_CUR, and SEEK_END
	 *
	 * @return  boolean  True if updated
	 *
	 * @see     streamWrapper::stream_seek
	 * @since   1.0
	 */
	public function stream_seek($offset, $whence)
	{
		switch ($whence)
		{
			case SEEK_SET:
				if ($offset < \strlen($this->buffers[$this->name]) && $offset >= 0)
				{
					$this->position = $offset;

					return true;
				}

				return false;

			case SEEK_CUR:
				if ($offset >= 0)
				{
					$this->position += $offset;

					return true;
				}

				return false;

			case SEEK_END:
				if (\strlen($this->buffers[$this->name]) + $offset >= 0)
				{
					$this->position = \strlen($this->buffers[$this->name]) + $offset;

					return true;
				}

				return false;

			default:
				return false;
		}
	}
}

// Register the stream
stream_wrapper_register('buffer', 'Joomla\\Filesystem\\Buffer');
vendor/joomla/filesystem/src/Stream.php000064400000101251152177723700014254 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

use Joomla\Filesystem\Exception\FilesystemException;

/**
 * Joomla! Stream Interface
 *
 * The Joomla! stream interface is designed to handle files as streams
 * where as the legacy JFile static class treated files in a rather
 * atomic manner.
 *
 * This class adheres to the stream wrapper operations:
 *
 * @link   https://secure.php.net/manual/en/function.stream-get-wrappers.php
 * @link   https://secure.php.net/manual/en/intro.stream.php PHP Stream Manual
 * @link   https://secure.php.net/manual/en/wrappers.php Stream Wrappers
 * @link   https://secure.php.net/manual/en/filters.php Stream Filters
 * @link   https://secure.php.net/manual/en/transports.php Socket Transports (used by some options, particularly HTTP proxy)
 * @since  1.0
 */
class Stream
{
	/**
	 * File Mode
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $filemode = 0644;

	/**
	 * Directory Mode
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $dirmode = 0755;

	/**
	 * Default Chunk Size
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $chunksize = 8192;

	/**
	 * Filename
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $filename;

	/**
	 * Prefix of the connection for writing
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $writeprefix;

	/**
	 * Prefix of the connection for reading
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $readprefix;

	/**
	 * Read Processing method
	 *
	 * @var    string  gz, bz, f
	 * If a scheme is detected, fopen will be defaulted
	 * To use compression with a network stream use a filter
	 * @since  1.0
	 */
	protected $processingmethod = 'f';

	/**
	 * Filters applied to the current stream
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $filters = array();

	/**
	 * File Handle
	 *
	 * @var    resource
	 * @since  1.0
	 */
	protected $fh;

	/**
	 * File size
	 *
	 * @var    integer
	 * @since  1.0
	 */
	protected $filesize;

	/**
	 * Context to use when opening the connection
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $context;

	/**
	 * Context options; used to rebuild the context
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $contextOptions;

	/**
	 * The mode under which the file was opened
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $openmode;

	/**
	 * Constructor
	 *
	 * @param   string  $writeprefix  Prefix of the stream (optional). Unlike the JPATH_*, this has a final path separator!
	 * @param   string  $readprefix   The read prefix (optional).
	 * @param   array   $context      The context options (optional).
	 *
	 * @since   1.0
	 */
	public function __construct($writeprefix = '', $readprefix = '', $context = array())
	{
		$this->writeprefix    = $writeprefix;
		$this->readprefix     = $readprefix;
		$this->contextOptions = $context;
		$this->_buildContext();
	}

	/**
	 * Destructor
	 *
	 * @since   1.0
	 */
	public function __destruct()
	{
		// Attempt to close on destruction if there is a file handle
		if ($this->fh)
		{
			@$this->close();
		}
	}

	/**
	 * Creates a new stream object with appropriate prefix
	 *
	 * @param   boolean  $usePrefix  Prefix the connections for writing
	 * @param   string   $ua         UA User agent to use
	 * @param   boolean  $uamask     User agent masking (prefix Mozilla)
	 *
	 * @return  Stream
	 *
	 * @see     Stream
	 * @since   1.0
	 */
	public static function getStream($usePrefix = true, $ua = null, $uamask = false)
	{
		// Setup the context; Joomla! UA and overwrite
		$context = array();

		// Set the UA for HTTP
		$context['http']['user_agent'] = $ua ?: 'Joomla! Framework Stream';

		if ($usePrefix)
		{
			return new Stream(JPATH_ROOT . '/', JPATH_ROOT, $context);
		}

		return new Stream('', '', $context);
	}

	/**
	 * Generic File Operations
	 *
	 * Open a stream with some lazy loading smarts
	 *
	 * @param   string    $filename              Filename
	 * @param   string    $mode                  Mode string to use
	 * @param   boolean   $useIncludePath        Use the PHP include path
	 * @param   resource  $context               Context to use when opening
	 * @param   boolean   $usePrefix             Use a prefix to open the file
	 * @param   boolean   $relative              Filename is a relative path (if false, strips JPATH_ROOT to make it relative)
	 * @param   boolean   $detectprocessingmode  Detect the processing method for the file and use the appropriate function
	 *                                           to handle output automatically
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function open($filename, $mode = 'r', $useIncludePath = false, $context = null, $usePrefix = false, $relative = false,
		$detectprocessingmode = false
	)
	{
		$filename = $this->_getFilename($filename, $mode, $usePrefix, $relative);

		if (!$filename)
		{
			throw new FilesystemException('Filename not set');
		}

		$this->filename = $filename;
		$this->openmode = $mode;

		$url = parse_url($filename);

		if (isset($url['scheme']))
		{
			$scheme = ucfirst($url['scheme']);

			// If we're dealing with a Joomla! stream, load it
			if (Helper::isJoomlaStream($scheme))
			{
				// Map to StringWrapper if required
				if ($scheme === 'String')
				{
					$scheme = 'StringWrapper';
				}

				require_once __DIR__ . '/Stream/' . $scheme . '.php';
			}

			// We have a scheme! force the method to be f
			$this->processingmethod = 'f';
		}
		elseif ($detectprocessingmode)
		{
			$ext = strtolower(pathinfo($this->filename, PATHINFO_EXTENSION));

			switch ($ext)
			{
				case 'tgz':
				case 'gz':
				case 'gzip':
					$this->processingmethod = 'gz';

					break;

				case 'tbz2':
				case 'bz2':
				case 'bzip2':
					$this->processingmethod = 'bz';

					break;

				default:
					$this->processingmethod = 'f';

					break;
			}
		}

		// Capture PHP errors
		$php_errormsg = 'Error Unknown whilst opening a file';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		// Decide which context to use:
		switch ($this->processingmethod)
		{
			// Gzip doesn't support contexts or streams
			case 'gz':
				$this->fh = gzopen($filename, $mode, $useIncludePath);

				break;

			// Bzip2 is much like gzip except it doesn't use the include path
			case 'bz':
				$this->fh = bzopen($filename, $mode);

				break;

			// Fopen can handle streams
			case 'f':
			default:
				// One supplied at open; overrides everything
				if ($context)
				{
					$this->fh = @fopen($filename, $mode, $useIncludePath, $context);
				}
				elseif ($this->context)
				{
					// One provided at initialisation
					$this->fh = @fopen($filename, $mode, $useIncludePath, $this->context);
				}
				else
				{
					// No context; all defaults
					$this->fh = @fopen($filename, $mode, $useIncludePath);
				}

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if (!$this->fh)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return true;
	}

	/**
	 * Attempt to close a file handle
	 *
	 * Will return false if it failed and true on success
	 * If the file is not open the system will return true, this function destroys the file handle as well
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function close()
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzclose($this->fh);

				break;

			case 'bz':
				$res = bzclose($this->fh);

				break;

			case 'f':
			default:
				$res = fclose($this->fh);

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if (!$res)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Reset this
		$this->fh = null;

		// If we wrote, chmod the file after it's closed
		if ($this->openmode[0] == 'w')
		{
			$this->chmod();
		}

		// Return the result
		return true;
	}

	/**
	 * Work out if we're at the end of the file for a stream
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function eof()
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzeof($this->fh);

				break;

			case 'bz':
			case 'f':
			default:
				$res = feof($this->fh);

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if ($php_errormsg)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return $res;
	}

	/**
	 * Retrieve the file size of the path
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function filesize()
	{
		if (!$this->filename)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);
		$res = @filesize($this->filename);

		if (!$res)
		{
			$tmpError = '';

			if ($php_errormsg)
			{
				// Something went wrong.
				// Store the error in case we need it.
				$tmpError = $php_errormsg;
			}

			$res = Helper::remotefsize($this->filename);

			if (!$res)
			{
				// Restore error tracking to what it was before.
				ini_set('track_errors', $trackErrors);

				if ($tmpError)
				{
					// Use the php_errormsg from before
					throw new FilesystemException($tmpError);
				}

				// Error but nothing from php? How strange! Create our own
				throw new FilesystemException('Failed to get file size. This may not work for all streams.');
			}

			$this->filesize = $res;
			$retval         = $res;
		}
		else
		{
			$this->filesize = $res;
			$retval         = $res;
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		// Return the result
		return $retval;
	}

	/**
	 * Get a line from the stream source.
	 *
	 * @param   integer  $length  The number of bytes (optional) to read.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function gets($length = 0)
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = $length ? gzgets($this->fh, $length) : gzgets($this->fh);

				break;

			case 'bz':
			case 'f':
			default:
				$res = $length ? fgets($this->fh, $length) : fgets($this->fh);

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if (!$res)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return $res;
	}

	/**
	 * Read a file
	 *
	 * Handles user space streams appropriately otherwise any read will return 8192
	 *
	 * @param   integer  $length  Length of data to read
	 *
	 * @return  mixed
	 *
	 * @link    https://secure.php.net/manual/en/function.fread.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function read($length = 0)
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		if (!$this->filesize && !$length)
		{
			// Get the filesize
			$this->filesize();

			if (!$this->filesize)
			{
				// Set it to the biggest and then wait until eof
				$length = -1;
			}
			else
			{
				$length = $this->filesize;
			}
		}

		$retval = false;

		// Capture PHP errors
		$php_errormsg = 'Error Unknown';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);
		$remaining = $length;

		do
		{
			// Do chunked reads where relevant
			switch ($this->processingmethod)
			{
				case 'bz':
					$res = ($remaining > 0) ? bzread($this->fh, $remaining) : bzread($this->fh, $this->chunksize);

					break;

				case 'gz':
					$res = ($remaining > 0) ? gzread($this->fh, $remaining) : gzread($this->fh, $this->chunksize);

					break;

				case 'f':
				default:
					$res = ($remaining > 0) ? fread($this->fh, $remaining) : fread($this->fh, $this->chunksize);

					break;
			}

			if (!$res)
			{
				// Restore error tracking to what it was before
				ini_set('track_errors', $trackErrors);

				throw new FilesystemException($php_errormsg);
			}

			if (!$retval)
			{
				$retval = '';
			}

			$retval .= $res;

			if (!$this->eof())
			{
				$len = \strlen($res);
				$remaining -= $len;
			}
			else
			{
				// If it's the end of the file then we've nothing left to read; reset remaining and len
				$remaining = 0;
				$length    = \strlen($retval);
			}
		}
		while ($remaining || !$length);

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		// Return the result
		return $retval;
	}

	/**
	 * Seek the file
	 *
	 * Note: the return value is different to that of fseek
	 *
	 * @param   integer  $offset  Offset to use when seeking.
	 * @param   integer  $whence  Seek mode to use.
	 *
	 * @return  boolean  True on success, false on failure
	 *
	 * @link    https://secure.php.net/manual/en/function.fseek.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function seek($offset, $whence = SEEK_SET)
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gzseek($this->fh, $offset, $whence);

				break;

			case 'bz':
			case 'f':
			default:
				$res = fseek($this->fh, $offset, $whence);

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		// Seek, interestingly, returns 0 on success or -1 on failure.
		if ($res == -1)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return true;
	}

	/**
	 * Returns the current position of the file read/write pointer.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function tell()
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		switch ($this->processingmethod)
		{
			case 'gz':
				$res = gztell($this->fh);

				break;

			case 'bz':
			case 'f':
			default:
				$res = ftell($this->fh);

				break;
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		// May return 0 so check if it's really false
		if ($res === false)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return $res;
	}

	/**
	 * File write
	 *
	 * Whilst this function accepts a reference, the underlying fwrite
	 * will do a copy! This will roughly double the memory allocation for
	 * any write you do. Specifying chunked will get around this by only
	 * writing in specific chunk sizes. This defaults to 8192 which is a
	 * sane number to use most of the time (change the default with
	 * Stream::set('chunksize', newsize);)
	 * Note: This doesn't support gzip/bzip2 writing like reading does
	 *
	 * @param   string   $string  Reference to the string to write.
	 * @param   integer  $length  Length of the string to write.
	 * @param   integer  $chunk   Size of chunks to write in.
	 *
	 * @return  boolean
	 *
	 * @link    https://secure.php.net/manual/en/function.fwrite.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function write(&$string, $length = 0, $chunk = 0)
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		if ($this->openmode == 'r')
		{
			throw new \RuntimeException('File is in readonly mode');
		}

		// If the length isn't set, set it to the length of the string.
		if (!$length)
		{
			$length = \strlen($string);
		}

		// If the chunk isn't set, set it to the default.
		if (!$chunk)
		{
			$chunk = $this->chunksize;
		}

		$retval = true;

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);
		$remaining = $length;
		$start     = 0;

		do
		{
			// If the amount remaining is greater than the chunk size, then use the chunk
			$amount = ($remaining > $chunk) ? $chunk : $remaining;
			$res    = fwrite($this->fh, substr($string, $start), $amount);

			// Returns false on error or the number of bytes written
			if ($res === false)
			{
				// Restore error tracking to what it was before
				ini_set('track_errors', $trackErrors);

				// Returned error
				throw new FilesystemException($php_errormsg);
			}

			if ($res === 0)
			{
				// Restore error tracking to what it was before
				ini_set('track_errors', $trackErrors);

				// Wrote nothing?
				throw new FilesystemException('Warning: No data written');
			}

			// Wrote something
			$start += $amount;
			$remaining -= $res;
		}
		while ($remaining);

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		// Return the result
		return $retval;
	}

	/**
	 * Chmod wrapper
	 *
	 * @param   string  $filename  File name.
	 * @param   mixed   $mode      Mode to use.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function chmod($filename = '', $mode = 0)
	{
		if (!$filename)
		{
			if (!isset($this->filename) || !$this->filename)
			{
				throw new FilesystemException('Filename not set');
			}

			$filename = $this->filename;
		}

		// If no mode is set use the default
		if (!$mode)
		{
			$mode = $this->filemode;
		}

		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);
		$sch = parse_url($filename, PHP_URL_SCHEME);

		// Scheme specific options; ftp's chmod support is fun.
		switch ($sch)
		{
			case 'ftp':
			case 'ftps':
				$res = Helper::ftpChmod($filename, $mode);

				break;

			default:
				$res = chmod($filename, $mode);

				break;
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		// Seek, interestingly, returns 0 on success or -1 on failure
		if ($res === false)
		{
			throw new FilesystemException($php_errormsg);
		}

		// Return the result
		return true;
	}

	/**
	 * Get the stream metadata
	 *
	 * @return  array  header/metadata
	 *
	 * @link    https://secure.php.net/manual/en/function.stream-get-meta-data.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function get_meta_data()
	{
		if (!$this->fh)
		{
			throw new FilesystemException('File not open');
		}

		return stream_get_meta_data($this->fh);
	}

	/**
	 * Stream contexts
	 * Builds the context from the array
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function _buildContext()
	{
		// According to the manual this always works!
		if (\count($this->contextOptions))
		{
			$this->context = @stream_context_create($this->contextOptions);
		}
		else
		{
			$this->context = null;
		}
	}

	/**
	 * Updates the context to the array
	 *
	 * Format is the same as the options for stream_context_create
	 *
	 * @param   array  $context  Options to create the context with
	 *
	 * @return  void
	 *
	 * @link    https://secure.php.net/stream_context_create
	 * @since   1.0
	 */
	public function setContextOptions($context)
	{
		$this->contextOptions = $context;
		$this->_buildContext();
	}

	/**
	 * Adds a particular options to the context
	 *
	 * @param   string  $wrapper  The wrapper to use
	 * @param   string  $name     The option to set
	 * @param   string  $value    The value of the option
	 *
	 * @return  void
	 *
	 * @link    https://secure.php.net/stream_context_create Stream Context Creation
	 * @link    https://secure.php.net/manual/en/context.php Context Options for various streams
	 * @since   1.0
	 */
	public function addContextEntry($wrapper, $name, $value)
	{
		$this->contextOptions[$wrapper][$name] = $value;
		$this->_buildContext();
	}

	/**
	 * Deletes a particular setting from a context
	 *
	 * @param   string  $wrapper  The wrapper to use
	 * @param   string  $name     The option to unset
	 *
	 * @return  void
	 *
	 * @link    https://secure.php.net/stream_context_create
	 * @since   1.0
	 */
	public function deleteContextEntry($wrapper, $name)
	{
		// Check whether the wrapper is set
		if (isset($this->contextOptions[$wrapper]))
		{
			// Check that entry is set for that wrapper
			if (isset($this->contextOptions[$wrapper][$name]))
			{
				// Unset the item
				unset($this->contextOptions[$wrapper][$name]);

				// Check that there are still items there
				if (!\count($this->contextOptions[$wrapper]))
				{
					// Clean up an empty wrapper context option
					unset($this->contextOptions[$wrapper]);
				}
			}
		}

		// Rebuild the context and apply it to the stream
		$this->_buildContext();
	}

	/**
	 * Applies the current context to the stream
	 *
	 * Use this to change the values of the context after you've opened a stream
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function applyContextToStream()
	{
		$retval = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = 'Unknown error setting context option';
			$trackErrors  = ini_get('track_errors');
			ini_set('track_errors', true);
			$retval = @stream_context_set_option($this->fh, $this->contextOptions);

			// Restore error tracking to what it was before
			ini_set('track_errors', $trackErrors);

			if (!$retval)
			{
				throw new FilesystemException($php_errormsg);
			}
		}

		return $retval;
	}

	/**
	 * Stream filters
	 * Append a filter to the chain
	 *
	 * @param   string   $filtername  The key name of the filter.
	 * @param   integer  $readWrite   Optional. Defaults to STREAM_FILTER_READ.
	 * @param   array    $params      An array of params for the stream_filter_append call.
	 *
	 * @return  mixed
	 *
	 * @link    https://secure.php.net/manual/en/function.stream-filter-append.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function appendFilter($filtername, $readWrite = STREAM_FILTER_READ, $params = array())
	{
		$res = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = '';
			$trackErrors  = ini_get('track_errors');
			ini_set('track_errors', true);

			$res = @stream_filter_append($this->fh, $filtername, $readWrite, $params);

			// Restore error tracking to what it was before.
			ini_set('track_errors', $trackErrors);

			if (!$res && $php_errormsg)
			{
				throw new FilesystemException($php_errormsg);
			}

			$this->filters[] = &$res;
		}

		return $res;
	}

	/**
	 * Prepend a filter to the chain
	 *
	 * @param   string   $filtername  The key name of the filter.
	 * @param   integer  $readWrite   Optional. Defaults to STREAM_FILTER_READ.
	 * @param   array    $params      An array of params for the stream_filter_prepend call.
	 *
	 * @return  mixed
	 *
	 * @link    https://secure.php.net/manual/en/function.stream-filter-prepend.php
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function prependFilter($filtername, $readWrite = STREAM_FILTER_READ, $params = array())
	{
		$res = false;

		if ($this->fh)
		{
			// Capture PHP errors
			$php_errormsg = '';
			$trackErrors  = ini_get('track_errors');
			ini_set('track_errors', true);
			$res = @stream_filter_prepend($this->fh, $filtername, $readWrite, $params);

			// Restore error tracking to what it was before.
			ini_set('track_errors', $trackErrors);

			if (!$res && $php_errormsg)
			{
				// Set the error msg
				throw new FilesystemException($php_errormsg);
			}

			array_unshift($this->filters, '');
			$this->filters[0] = &$res;
		}

		return $res;
	}

	/**
	 * Remove a filter, either by resource (handed out from the append or prepend function)
	 * or via getting the filter list)
	 *
	 * @param   resource  $resource  The resource.
	 * @param   boolean   $byindex   The index of the filter.
	 *
	 * @return  boolean   Result of operation
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function removeFilter(&$resource, $byindex = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		if ($byindex)
		{
			$res = stream_filter_remove($this->filters[$resource]);
		}
		else
		{
			$res = stream_filter_remove($resource);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		if (!$res)
		{
			throw new FilesystemException($php_errormsg);
		}

		return $res;
	}

	/**
	 * Copy a file from src to dest
	 *
	 * @param   string    $src        The file path to copy from.
	 * @param   string    $dest       The file path to copy to.
	 * @param   resource  $context    A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $usePrefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative   Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function copy($src, $dest, $context = null, $usePrefix = true, $relative = false)
	{
		// Capture PHP errors
		$trackErrors = ini_get('track_errors');
		ini_set('track_errors', true);

		$chmodDest = $this->_getFilename($dest, 'w', $usePrefix, $relative);

		// Since we're going to open the file directly we need to get the filename.
		// We need to use the same prefix so force everything to write.
		$src  = $this->_getFilename($src, 'w', $usePrefix, $relative);
		$dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);

		// One supplied at copy; overrides everything
		if ($context)
		{
			// Use the provided context
			$res = @copy($src, $dest, $context);
		}
		elseif ($this->context)
		{
			// One provided at initialisation
			$res = @copy($src, $dest, $this->context);
		}
		else
		{
			// No context; all defaults
			$res = @copy($src, $dest);
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if (!$res && $php_errormsg)
		{
			throw new FilesystemException($php_errormsg);
		}

		$this->chmod($chmodDest);

		return $res;
	}

	/**
	 * Moves a file
	 *
	 * @param   string    $src        The file path to move from.
	 * @param   string    $dest       The file path to move to.
	 * @param   resource  $context    A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $usePrefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative   Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function move($src, $dest, $context = null, $usePrefix = true, $relative = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		$src  = $this->_getFilename($src, 'w', $usePrefix, $relative);
		$dest = $this->_getFilename($dest, 'w', $usePrefix, $relative);

		if ($context)
		{
			// Use the provided context
			$res = @rename($src, $dest, $context);
		}
		elseif ($this->context)
		{
			// Use the object's context
			$res = @rename($src, $dest, $this->context);
		}
		else
		{
			// Don't use any context
			$res = @rename($src, $dest);
		}

		// Restore error tracking to what it was before
		ini_set('track_errors', $trackErrors);

		if (!$res)
		{
			throw new FilesystemException($php_errormsg);
		}

		$this->chmod($dest);

		return $res;
	}

	/**
	 * Delete a file
	 *
	 * @param   string    $filename   The file path to delete.
	 * @param   resource  $context    A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $usePrefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative   Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function delete($filename, $context = null, $usePrefix = true, $relative = false)
	{
		// Capture PHP errors
		$php_errormsg = '';
		$trackErrors  = ini_get('track_errors');
		ini_set('track_errors', true);

		$filename = $this->_getFilename($filename, 'w', $usePrefix, $relative);

		if ($context)
		{
			// Use the provided context
			$res = @unlink($filename, $context);
		}
		elseif ($this->context)
		{
			// Use the object's context
			$res = @unlink($filename, $this->context);
		}
		else
		{
			// Don't use any context
			$res = @unlink($filename);
		}

		// Restore error tracking to what it was before.
		ini_set('track_errors', $trackErrors);

		if (!$res)
		{
			throw new FilesystemException($php_errormsg);
		}

		return $res;
	}

	/**
	 * Upload a file
	 *
	 * @param   string    $src        The file path to copy from (usually a temp folder).
	 * @param   string    $dest       The file path to copy to.
	 * @param   resource  $context    A valid context resource (optional) created with stream_context_create.
	 * @param   boolean   $usePrefix  Controls the use of a prefix (optional).
	 * @param   boolean   $relative   Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function upload($src, $dest, $context = null, $usePrefix = true, $relative = false)
	{
		if (is_uploaded_file($src))
		{
			// Make sure it's an uploaded file
			return $this->copy($src, $dest, $context, $usePrefix, $relative);
		}

		throw new FilesystemException('Not an uploaded file.');
	}

	/**
	 * Writes a chunk of data to a file.
	 *
	 * @param   string   $filename      The file name.
	 * @param   string   $buffer        The data to write to the file.
	 * @param   boolean  $appendToFile  Append to the file and not overwrite it.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function writeFile($filename, &$buffer, $appendToFile = false)
	{
		$fileMode = 'w';

		// Switch the filemode when we want to append to the file
		if ($appendToFile)
		{
			$fileMode = 'a';
		}

		if ($this->open($filename, $fileMode))
		{
			$result = $this->write($buffer);
			$this->chmod();
			$this->close();

			return $result;
		}

		return false;
	}

	/**
	 * Determine the appropriate 'filename' of a file
	 *
	 * @param   string   $filename   Original filename of the file
	 * @param   string   $mode       Mode string to retrieve the filename
	 * @param   boolean  $usePrefix  Controls the use of a prefix
	 * @param   boolean  $relative   Determines if the filename given is relative. Relative paths do not have JPATH_ROOT stripped.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function _getFilename($filename, $mode, $usePrefix, $relative)
	{
		if ($usePrefix)
		{
			// Get rid of binary or t, should be at the end of the string
			$tmode = trim($mode, 'btf123456789');

			$stream   = explode('://', $filename, 2);
			$scheme   = '';
			$filename = $stream[0];

			if (\count($stream) >= 2)
			{
				$scheme   = $stream[0] . '://';
				$filename = $stream[1];
			}

			// Check if it's a write mode then add the appropriate prefix
			if (\in_array($tmode, Helper::getWriteModes()))
			{
				$prefixToUse = $this->writeprefix;
			}
			else
			{
				$prefixToUse = $this->readprefix;
			}

			// Get rid of JPATH_ROOT (legacy compat)
			if (!$relative && $prefixToUse)
			{
				$pos = strpos($filename, JPATH_ROOT);

				if ($pos !== false)
				{
					$filename = substr_replace($filename, '', $pos, \strlen(JPATH_ROOT));
				}
			}

			$filename = ($prefixToUse ? $prefixToUse : '') . $filename;
		}

		return $filename;
	}

	/**
	 * Return the internal file handle
	 *
	 * @return  File handler
	 *
	 * @since   1.0
	 */
	public function getFileHandle()
	{
		return $this->fh;
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $value     The value of the property to set.
	 *
	 * @return  mixed  Previous value of the property.
	 *
	 * @since   1.0
	 */
	public function set($property, $value = null)
	{
		$previous        = isset($this->$property) ? $this->$property : null;
		$this->$property = $value;

		return $previous;
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $property  The name of the property.
	 * @param   mixed   $default   The default value.
	 *
	 * @return  mixed    The value of the property.
	 *
	 * @since   1.0
	 */
	public function get($property, $default = null)
	{
		if (isset($this->$property))
		{
			return $this->$property;
		}

		return $default;
	}
}
vendor/joomla/filesystem/src/Stream/String.php000064400000001022152177723700015515 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem\Stream;

/**
 * String Stream Wrapper
 *
 * This class allows you to use a PHP string in the same way that
 * you would normally use a regular stream wrapper
 *
 * @since       1.0
 * @deprecated  2.0  Use StringWrapper instead
 */
class String extends StringWrapper
{
}
vendor/joomla/filesystem/src/Stream/StringWrapper.php000064400000012416152177723700017067 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem\Stream;

use Joomla\Filesystem\Support\StringController;

/**
 * String Stream Wrapper
 *
 * This class allows you to use a PHP string in the same way that
 * you would normally use a regular stream wrapper
 *
 * @since  1.3.0
 */
class StringWrapper
{
	/**
	 * The current string
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $currentString;

	/**
	 * The path
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $path;

	/**
	 * The mode
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $mode;

	/**
	 * Enter description here ...
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $options;

	/**
	 * Enter description here ...
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $openedPath;

	/**
	 * Current position
	 *
	 * @var   integer
	 * @since  1.3.0
	 */
	protected $pos;

	/**
	 * Length of the string
	 *
	 * @var   string
	 * @since  1.3.0
	 */
	protected $len;

	/**
	 * Statistics for a file
	 *
	 * @var    array
	 * @since  1.3.0
	 * @link   https://secure.php.net/manual/en/function.stat.php
	 */
	protected $stat;

	/**
	 * Method to open a file or URL.
	 *
	 * @param   string   $path        The stream path.
	 * @param   string   $mode        Not used.
	 * @param   integer  $options     Not used.
	 * @param   string   $openedPath  Not used.
	 *
	 * @return  boolean
	 *
	 * @since   1.3.0
	 */
	public function stream_open($path, $mode, $options, &$openedPath)
	{
		$refPath = StringController::getRef(str_replace('string://', '', $path));

		$this->currentString = &$refPath;

		if ($this->currentString)
		{
			$this->len  = \strlen($this->currentString);
			$this->pos  = 0;
			$this->stat = $this->url_stat($path, 0);

			return true;
		}

		return false;
	}

	/**
	 * Method to retrieve information from a file resource
	 *
	 * @return  array
	 *
	 * @link    https://secure.php.net/manual/en/streamwrapper.stream-stat.php
	 * @since   1.3.0
	 */
	public function stream_stat()
	{
		return $this->stat;
	}

	/**
	 * Method to retrieve information about a file.
	 *
	 * @param   string   $path   File path or URL to stat
	 * @param   integer  $flags  Additional flags set by the streams API
	 *
	 * @return  array
	 *
	 * @link    https://secure.php.net/manual/en/streamwrapper.url-stat.php
	 * @since   1.3.0
	 */
	public function url_stat($path, $flags = 0)
	{
		$now     = time();
		$refPath = StringController::getRef(str_replace('string://', '', $path));
		$string  = &$refPath;
		$stat    = array(
			'dev'     => 0,
			'ino'     => 0,
			'mode'    => 0,
			'nlink'   => 1,
			'uid'     => 0,
			'gid'     => 0,
			'rdev'    => 0,
			'size'    => \strlen($string),
			'atime'   => $now,
			'mtime'   => $now,
			'ctime'   => $now,
			'blksize' => '512',
			'blocks'  => ceil(\strlen($string) / 512),
		);

		return $stat;
	}

	/**
	 * Method to read a given number of bytes starting at the current position
	 * and moving to the end of the string defined by the current position plus the
	 * given number.
	 *
	 * @param   integer  $count  Bytes of data from the current position should be returned.
	 *
	 * @return  string
	 *
	 * @link    https://secure.php.net/manual/en/streamwrapper.stream-read.php
	 * @since   1.3.0
	 */
	public function stream_read($count)
	{
		$result = substr($this->currentString, $this->pos, $count);
		$this->pos += $count;

		return $result;
	}

	/**
	 * Stream write, always returning false.
	 *
	 * @param   string  $data  The data to write.
	 *
	 * @return  boolean
	 *
	 * @since   1.3.0
	 * @note    Updating the string is not supported.
	 */
	public function stream_write($data)
	{
		// We don't support updating the string.
		return false;
	}

	/**
	 * Method to get the current position
	 *
	 * @return  integer  The position
	 *
	 * @since   1.3.0
	 */
	public function stream_tell()
	{
		return $this->pos;
	}

	/**
	 * End of field check
	 *
	 * @return  boolean  True if at end of field.
	 *
	 * @since   1.3.0
	 */
	public function stream_eof()
	{
		if ($this->pos >= $this->len)
		{
			return true;
		}

		return false;
	}

	/**
	 * Stream offset
	 *
	 * @param   integer  $offset  The starting offset.
	 * @param   integer  $whence  SEEK_SET, SEEK_CUR, SEEK_END
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.3.0
	 */
	public function stream_seek($offset, $whence)
	{
		// $whence: SEEK_SET, SEEK_CUR, SEEK_END
		if ($offset > $this->len)
		{
			// We can't seek beyond our len.
			return false;
		}

		switch ($whence)
		{
			case SEEK_SET:
				$this->pos = $offset;

				break;

			case SEEK_CUR:
				if (($this->pos + $offset) > $this->len)
				{
					return false;
				}

				$this->pos += $offset;

				break;

			case SEEK_END:
				$this->pos = $this->len - $offset;

				break;
		}

		return true;
	}

	/**
	 * Stream flush, always returns true.
	 *
	 * @return  boolean
	 *
	 * @since   1.3.0
	 * @note    Data storage is not supported
	 */
	public function stream_flush()
	{
		// We don't store data.
		return true;
	}
}

if (!stream_wrapper_register('string', '\\Joomla\\Filesystem\\Stream\\StringWrapper'))
{
	die('\\Joomla\\Filesystem\\Stream\\StringWrapper Wrapper Registration Failed');
}
vendor/joomla/filesystem/src/File.php000064400000015323152177723700013704 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

use Joomla\Filesystem\Exception\FilesystemException;

/**
 * A File handling class
 *
 * @since  1.0
 */
class File
{
	/**
	 * Strips the last extension off of a file name
	 *
	 * @param   string  $file  The file name
	 *
	 * @return  string  The file name without the extension
	 *
	 * @since   1.0
	 */
	public static function stripExt($file)
	{
		return preg_replace('#\.[^.]*$#', '', $file);
	}

	/**
	 * Makes the file name safe to use
	 *
	 * @param   string  $file        The name of the file [not full path]
	 * @param   array   $stripChars  Array of regex (by default will remove any leading periods)
	 *
	 * @return  string  The sanitised string
	 *
	 * @since   1.0
	 */
	public static function makeSafe($file, array $stripChars = array('#^\.#'))
	{
		$regex = array_merge(array('#(\.){2,}#', '#[^A-Za-z0-9\.\_\- ]#'), $stripChars);

		$file = preg_replace($regex, '', $file);

		// Remove any trailing dots, as those aren't ever valid file names.
		$file = rtrim($file, '.');

		return $file;
	}

	/**
	 * Copies a file
	 *
	 * @param   string   $src         The path to the source file
	 * @param   string   $dest        The path to the destination file
	 * @param   string   $path        An optional base path to prefix to the file names
	 * @param   boolean  $useStreams  True to use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 * @throws  \UnexpectedValueException
	 */
	public static function copy($src, $dest, $path = null, $useStreams = false)
	{
		// Prepend a base path if it exists
		if ($path)
		{
			$src  = Path::clean($path . '/' . $src);
			$dest = Path::clean($path . '/' . $dest);
		}

		// Check src path
		if (!is_readable($src))
		{
			throw new \UnexpectedValueException(__METHOD__ . ': Cannot find or read file: ' . $src);
		}

		if ($useStreams)
		{
			$stream = Stream::getStream();

			if (!$stream->copy($src, $dest, null, false))
			{
				throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
			}

			return true;
		}

		if (!@ copy($src, $dest))
		{
			throw new FilesystemException(__METHOD__ . ': Copy failed.');
		}

		return true;
	}

	/**
	 * Delete a file or array of files
	 *
	 * @param   mixed  $file  The file name or an array of file names
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function delete($file)
	{
		$files = (array) $file;

		foreach ($files as $file)
		{
			$file     = Path::clean($file);
			$filename = basename($file);

			if (!Path::canChmod($file))
			{
				throw new FilesystemException(__METHOD__ . ': Failed deleting inaccessible file ' . $filename);
			}

			// Try making the file writable first. If it's read-only, it can't be deleted
			// on Windows, even if the parent folder is writable
			@chmod($file, 0777);

			// In case of restricted permissions we zap it one way or the other
			// as long as the owner is either the webserver or the ftp
			if (!@ unlink($file))
			{
				throw new FilesystemException(__METHOD__ . ': Failed deleting ' . $filename);
			}
		}

		return true;
	}

	/**
	 * Moves a file
	 *
	 * @param   string   $src         The path to the source file
	 * @param   string   $dest        The path to the destination file
	 * @param   string   $path        An optional base path to prefix to the file names
	 * @param   boolean  $useStreams  True to use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function move($src, $dest, $path = '', $useStreams = false)
	{
		if ($path)
		{
			$src  = Path::clean($path . '/' . $src);
			$dest = Path::clean($path . '/' . $dest);
		}

		// Check src path
		if (!is_readable($src))
		{
			return 'Cannot find source file.';
		}

		if ($useStreams)
		{
			$stream = Stream::getStream();

			if (!$stream->move($src, $dest, null, false))
			{
				throw new FilesystemException(__METHOD__ . ': ' . $stream->getError());
			}

			return true;
		}

		if (!@ rename($src, $dest))
		{
			throw new FilesystemException(__METHOD__ . ': Rename failed.');
		}

		return true;
	}

	/**
	 * Write contents to a file
	 *
	 * @param   string   $file          The full file path
	 * @param   string   $buffer        The buffer to write
	 * @param   boolean  $useStreams    Use streams
	 * @param   boolean  $appendToFile  Append to the file and not overwrite it.
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 */
	public static function write($file, &$buffer, $useStreams = false, $appendToFile = false)
	{
		@set_time_limit(ini_get('max_execution_time'));

		// If the destination directory doesn't exist we need to create it
		if (!file_exists(\dirname($file)))
		{
			Folder::create(\dirname($file));
		}

		if ($useStreams)
		{
			$stream = Stream::getStream();

			// Beef up the chunk size to a meg
			$stream->set('chunksize', (1024 * 1024));
			$stream->writeFile($file, $buffer, $appendToFile);

			return true;
		}

		$file = Path::clean($file);

		// Set the required flag to only append to the file and not overwrite it
		if ($appendToFile === true)
		{
			return \is_int(file_put_contents($file, $buffer, FILE_APPEND));
		}

		return \is_int(file_put_contents($file, $buffer));
	}

	/**
	 * Moves an uploaded file to a destination folder
	 *
	 * @param   string   $src         The name of the php (temporary) uploaded file
	 * @param   string   $dest        The path (including filename) to move the uploaded file to
	 * @param   boolean  $useStreams  True to use streams
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function upload($src, $dest, $useStreams = false)
	{
		// Ensure that the path is valid and clean
		$dest = Path::clean($dest);

		// Create the destination directory if it does not exist
		$baseDir = \dirname($dest);

		if (!is_dir($baseDir))
		{
			Folder::create($baseDir);
		}

		if ($useStreams)
		{
			$stream = Stream::getStream();

			if (!$stream->upload($src, $dest, null, false))
			{
				throw new FilesystemException(sprintf('%1$s(%2$s, %3$s): %4$s', __METHOD__, $src, $dest, $stream->getError()));
			}

			return true;
		}

		if (is_writable($baseDir) && move_uploaded_file($src, $dest))
		{
			// Short circuit to prevent file permission errors
			if (Path::setPermissions($dest))
			{
				return true;
			}

			throw new FilesystemException(__METHOD__ . ': Failed to change file permissions.');
		}

		throw new FilesystemException(__METHOD__ . ': Failed to move file.');
	}
}
vendor/joomla/filesystem/src/Folder.php000064400000034323152177723700014241 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

use Joomla\Filesystem\Exception\FilesystemException;

/**
 * A Folder handling class
 *
 * @since  1.0
 */
abstract class Folder
{
	/**
	 * Copy a folder.
	 *
	 * @param   string   $src         The path to the source folder.
	 * @param   string   $dest        The path to the destination folder.
	 * @param   string   $path        An optional base path to prefix to the file names.
	 * @param   boolean  $force       Force copy.
	 * @param   boolean  $useStreams  Optionally force folder/file overwrites.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function copy($src, $dest, $path = '', $force = false, $useStreams = false)
	{
		@set_time_limit(ini_get('max_execution_time'));

		if ($path)
		{
			$src  = Path::clean($path . '/' . $src);
			$dest = Path::clean($path . '/' . $dest);
		}

		// Eliminate trailing directory separators, if any
		$src  = rtrim($src, DIRECTORY_SEPARATOR);
		$dest = rtrim($dest, DIRECTORY_SEPARATOR);

		if (!is_dir(Path::clean($src)))
		{
			throw new FilesystemException('Source folder not found', -1);
		}

		if (is_dir(Path::clean($dest)) && !$force)
		{
			throw new FilesystemException('Destination folder not found', -1);
		}

		// Make sure the destination exists
		if (!self::create($dest))
		{
			throw new FilesystemException('Cannot create destination folder', -1);
		}

		if (!($dh = @opendir($src)))
		{
			throw new FilesystemException('Cannot open source folder', -1);
		}

		// Walk through the directory copying files and recursing into folders.
		while (($file = readdir($dh)) !== false)
		{
			$sfid = $src . '/' . $file;
			$dfid = $dest . '/' . $file;

			switch (filetype($sfid))
			{
				case 'dir':
					if ($file != '.' && $file != '..')
					{
						$ret = self::copy($sfid, $dfid, null, $force, $useStreams);

						if ($ret !== true)
						{
							return $ret;
						}
					}

					break;

				case 'file':
					if ($useStreams)
					{
						Stream::getStream()->copy($sfid, $dfid);
					}
					else
					{
						if (!@copy($sfid, $dfid))
						{
							throw new FilesystemException('Copy file failed', -1);
						}
					}

					break;
			}
		}

		return true;
	}

	/**
	 * Create a folder -- and all necessary parent folders.
	 *
	 * @param   string   $path  A path to create from the base path.
	 * @param   integer  $mode  Directory permissions to set for folders created. 0755 by default.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function create($path = '', $mode = 0755)
	{
		static $nested = 0;

		// Check to make sure the path valid and clean
		$path = Path::clean($path);

		// Check if parent dir exists
		$parent = \dirname($path);

		if (!is_dir(Path::clean($parent)))
		{
			// Prevent infinite loops!
			$nested++;

			if (($nested > 20) || ($parent == $path))
			{
				throw new FilesystemException(__METHOD__ . ': Infinite loop detected');
			}

			try
			{
				// Create the parent directory
				if (self::create($parent, $mode) !== true)
				{
					// Folder::create throws an error
					$nested--;

					return false;
				}
			}
			catch (FilesystemException $exception)
			{
				$nested--;

				throw $exception;
			}

			// OK, parent directory has been created
			$nested--;
		}

		// Check if dir already exists
		if (is_dir(Path::clean($path)))
		{
			return true;
		}

		// We need to get and explode the open_basedir paths
		$obd = ini_get('open_basedir');

		// If open_basedir is set we need to get the open_basedir that the path is in
		if ($obd != null)
		{
			if (\defined('PHP_WINDOWS_VERSION_MAJOR'))
			{
				$obdSeparator = ';';
			}
			else
			{
				$obdSeparator = ':';
			}

			// Create the array of open_basedir paths
			$obdArray  = explode($obdSeparator, $obd);
			$inBaseDir = false;

			// Iterate through open_basedir paths looking for a match
			foreach ($obdArray as $test)
			{
				$test = Path::clean($test);

				if (strpos($path, $test) === 0 || strpos($path, realpath($test)) === 0)
				{
					$inBaseDir = true;

					break;
				}
			}

			if ($inBaseDir == false)
			{
				// Throw a FilesystemException because the path to be created is not in open_basedir
				throw new FilesystemException(__METHOD__ . ': Path not in open_basedir paths');
			}
		}

		// First set umask
		$origmask = @umask(0);

		// Create the path
		if (!$ret = @mkdir($path, $mode))
		{
			@umask($origmask);

			throw new FilesystemException(__METHOD__ . ': Could not create directory.  Path: ' . $path);
		}

		// Reset umask
		@umask($origmask);

		return $ret;
	}

	/**
	 * Delete a folder.
	 *
	 * @param   string  $path  The path to the folder to delete.
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 * @throws  \UnexpectedValueException
	 */
	public static function delete($path)
	{
		@set_time_limit(ini_get('max_execution_time'));

		// Sanity check
		if (!$path)
		{
			// Bad programmer! Bad Bad programmer!
			throw new FilesystemException(__METHOD__ . ': You can not delete a base directory.');
		}

		// Check to make sure the path valid and clean
		$path = Path::clean($path);

		// Is this really a folder?
		if (!is_dir($path))
		{
			throw new \UnexpectedValueException(sprintf('%1$s: Path is not a folder. Path: %2$s', __METHOD__, $path));
		}

		// Remove all the files in folder if they exist; disable all filtering
		$files = self::files($path, '.', false, true, array(), array());

		if (!empty($files))
		{
			if (File::delete($files) !== true)
			{
				// File::delete throws an error
				return false;
			}
		}

		// Remove sub-folders of folder; disable all filtering
		$folders = self::folders($path, '.', false, true, array(), array());

		foreach ($folders as $folder)
		{
			if (is_link($folder))
			{
				// Don't descend into linked directories, just delete the link.
				if (File::delete($folder) !== true)
				{
					// File::delete throws an error
					return false;
				}
			}
			elseif (self::delete($folder) !== true)
			{
				// Folder::delete throws an error
				return false;
			}
		}

		// In case of restricted permissions we zap it one way or the other as long as the owner is either the webserver or the ftp.
		if (@rmdir($path))
		{
			return true;
		}

		throw new FilesystemException(sprintf('%1$s: Could not delete folder. Path: %2$s', __METHOD__, $path));
	}

	/**
	 * Moves a folder.
	 *
	 * @param   string   $src         The path to the source folder.
	 * @param   string   $dest        The path to the destination folder.
	 * @param   string   $path        An optional base path to prefix to the file names.
	 * @param   boolean  $useStreams  Optionally use streams.
	 *
	 * @return  mixed  Error message on false or boolean true on success.
	 *
	 * @since   1.0
	 */
	public static function move($src, $dest, $path = '', $useStreams = false)
	{
		if ($path)
		{
			$src  = Path::clean($path . '/' . $src);
			$dest = Path::clean($path . '/' . $dest);
		}

		if (!is_dir(Path::clean($src)))
		{
			return 'Cannot find source folder';
		}

		if (is_dir(Path::clean($dest)))
		{
			return 'Folder already exists';
		}

		if ($useStreams)
		{
			Stream::getStream()->move($src, $dest);

			return true;
		}

		if (!@rename($src, $dest))
		{
			return 'Rename failed';
		}

		return true;
	}

	/**
	 * Utility function to read the files in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for file names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the file.
	 * @param   array    $exclude        Array with names of files which should not be shown in the result.
	 * @param   array    $excludeFilter  Array of filter to exclude
	 *
	 * @return  array  Files in the given folder.
	 *
	 * @since   1.0
	 * @throws  \UnexpectedValueException
	 */
	public static function files($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludeFilter = array('^\..*', '.*~')
	)
	{
		// Check to make sure the path valid and clean
		$path = Path::clean($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			throw new \UnexpectedValueException(sprintf('%1$s: Path is not a folder. Path: %2$s', __METHOD__, $path));
		}

		// Compute the excludefilter string
		if (\count($excludeFilter))
		{
			$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
		}
		else
		{
			$excludeFilterString = '';
		}

		// Get the files
		$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, true);

		// Sort the files
		asort($arr);

		return array_values($arr);
	}

	/**
	 * Utility function to read the folders in a folder.
	 *
	 * @param   string   $path           The path of the folder to read.
	 * @param   string   $filter         A filter for folder names.
	 * @param   mixed    $recurse        True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full           True to return the full path to the folders.
	 * @param   array    $exclude        Array with names of folders which should not be shown in the result.
	 * @param   array    $excludeFilter  Array with regular expressions matching folders which should not be shown in the result.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @since   1.0
	 * @throws  \UnexpectedValueException
	 */
	public static function folders($path, $filter = '.', $recurse = false, $full = false, $exclude = array('.svn', 'CVS', '.DS_Store', '__MACOSX'),
		$excludeFilter = array('^\..*')
	)
	{
		// Check to make sure the path valid and clean
		$path = Path::clean($path);

		// Is the path a folder?
		if (!is_dir($path))
		{
			throw new \UnexpectedValueException(sprintf('%1$s: Path is not a folder. Path: %2$s', __METHOD__, $path));
		}

		// Compute the excludefilter string
		if (\count($excludeFilter))
		{
			$excludeFilterString = '/(' . implode('|', $excludeFilter) . ')/';
		}
		else
		{
			$excludeFilterString = '';
		}

		// Get the folders
		$arr = self::_items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, false);

		// Sort the folders
		asort($arr);

		return array_values($arr);
	}

	/**
	 * Function to read the files/folders in a folder.
	 *
	 * @param   string   $path                 The path of the folder to read.
	 * @param   string   $filter               A filter for file names.
	 * @param   mixed    $recurse              True to recursively search into sub-folders, or an integer to specify the maximum depth.
	 * @param   boolean  $full                 True to return the full path to the file.
	 * @param   array    $exclude              Array with names of files which should not be shown in the result.
	 * @param   string   $excludeFilterString  Regexp of files to exclude
	 * @param   boolean  $findfiles            True to read the files, false to read the folders
	 *
	 * @return  array  Files.
	 *
	 * @since   1.0
	 */
	protected static function _items($path, $filter, $recurse, $full, $exclude, $excludeFilterString, $findfiles)
	{
		@set_time_limit(ini_get('max_execution_time'));

		$arr = array();

		// Read the source directory
		if (!($handle = @opendir($path)))
		{
			return $arr;
		}

		while (($file = readdir($handle)) !== false)
		{
			if ($file != '.' && $file != '..' && !\in_array($file, $exclude)
				&& (empty($excludeFilterString) || !preg_match($excludeFilterString, $file)))
			{
				// Compute the fullpath
				$fullpath = Path::clean($path . '/' . $file);

				// Compute the isDir flag
				$isDir = is_dir($fullpath);

				if (($isDir xor $findfiles) && preg_match("/$filter/", $file))
				{
					// (fullpath is dir and folders are searched or fullpath is not dir and files are searched) and file matches the filter
					if ($full)
					{
						// Full path is requested
						$arr[] = $fullpath;
					}
					else
					{
						// Filename is requested
						$arr[] = $file;
					}
				}

				if ($isDir && $recurse)
				{
					// Search recursively
					if (\is_int($recurse))
					{
						// Until depth 0 is reached
						$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse - 1, $full, $exclude, $excludeFilterString, $findfiles));
					}
					else
					{
						$arr = array_merge($arr, self::_items($fullpath, $filter, $recurse, $full, $exclude, $excludeFilterString, $findfiles));
					}
				}
			}
		}

		closedir($handle);

		return $arr;
	}

	/**
	 * Lists folder in format suitable for tree display.
	 *
	 * @param   string   $path      The path of the folder to read.
	 * @param   string   $filter    A filter for folder names.
	 * @param   integer  $maxLevel  The maximum number of levels to recursively read, defaults to three.
	 * @param   integer  $level     The current level, optional.
	 * @param   integer  $parent    Unique identifier of the parent folder, if any.
	 *
	 * @return  array  Folders in the given folder.
	 *
	 * @since   1.0
	 */
	public static function listFolderTree($path, $filter, $maxLevel = 3, $level = 0, $parent = 0)
	{
		$dirs = array();

		if ($level == 0)
		{
			$GLOBALS['_JFolder_folder_tree_index'] = 0;
		}

		if ($level < $maxLevel)
		{
			$folders = self::folders($path, $filter);

			// First path, index foldernames
			foreach ($folders as $name)
			{
				$id       = ++$GLOBALS['_JFolder_folder_tree_index'];
				$fullName = Path::clean($path . '/' . $name);
				$dirs[]   = array(
					'id'       => $id,
					'parent'   => $parent,
					'name'     => $name,
					'fullname' => $fullName,
					'relname'  => str_replace(JPATH_ROOT, '', $fullName),
				);
				$dirs2 = self::listFolderTree($fullName, $filter, $maxLevel, $level + 1, $id);
				$dirs  = array_merge($dirs, $dirs2);
			}
		}

		return $dirs;
	}

	/**
	 * Makes path name safe to use.
	 *
	 * @param   string  $path  The full path to sanitise.
	 *
	 * @return  string  The sanitised string.
	 *
	 * @since   1.0
	 */
	public static function makeSafe($path)
	{
		$regex = array('#[^A-Za-z0-9_\\\/\(\)\[\]\{\}\#\$\^\+\.\'~`!@&=;,-]#');

		return preg_replace($regex, '', $path);
	}
}
vendor/joomla/filesystem/src/Path.php000064400000016201152177723700013715 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

use Joomla\Filesystem\Exception\FilesystemException;

if (!\defined('JPATH_ROOT'))
{
	throw new \LogicException('The "JPATH_ROOT" constant must be defined for your application.');
}

/**
 * A Path handling class
 *
 * @since  1.0
 */
class Path
{
	/**
	 * Checks if a path's permissions can be changed.
	 *
	 * @param   string  $path  Path to check.
	 *
	 * @return  boolean  True if path can have mode changed.
	 *
	 * @since   1.0
	 */
	public static function canChmod($path)
	{
		if (!file_exists($path))
		{
			return false;
		}

		$perms = @fileperms($path);

		if ($perms !== false)
		{
			if (@chmod($path, $perms ^ 0001))
			{
				@chmod($path, $perms);

				return true;
			}
		}

		return false;
	}

	/**
	 * Chmods files and directories recursively to given permissions.
	 *
	 * @param   string  $path        Root path to begin changing mode [without trailing slash].
	 * @param   string  $filemode    Octal representation of the value to change file mode to [null = no change].
	 * @param   string  $foldermode  Octal representation of the value to change folder mode to [null = no change].
	 *
	 * @return  boolean  True if successful [one fail means the whole operation failed].
	 *
	 * @since   1.0
	 */
	public static function setPermissions($path, $filemode = '0644', $foldermode = '0755')
	{
		// Initialise return value
		$ret = true;

		if (is_dir($path))
		{
			$dh = @opendir($path);

			if ($dh)
			{
				while ($file = readdir($dh))
				{
					if ($file != '.' && $file != '..')
					{
						$fullpath = $path . '/' . $file;

						if (is_dir($fullpath))
						{
							if (!static::setPermissions($fullpath, $filemode, $foldermode))
							{
								$ret = false;
							}
						}
						else
						{
							if (isset($filemode))
							{
								if (!static::canChmod($fullpath) || !@ chmod($fullpath, octdec($filemode)))
								{
									$ret = false;
								}
							}
						}
					}
				}

				closedir($dh);
			}

			if (isset($foldermode))
			{
				if (!static::canChmod($path) || !@ chmod($path, octdec($foldermode)))
				{
					$ret = false;
				}
			}
		}
		else
		{
			if (isset($filemode))
			{
				if (!static::canChmod($path) || !@ chmod($path, octdec($filemode)))
				{
					$ret = false;
				}
			}
		}

		return $ret;
	}

	/**
	 * Get the permissions of the file/folder at a give path.
	 *
	 * @param   string  $path  The path of a file/folder.
	 *
	 * @return  string  Filesystem permissions.
	 *
	 * @since   1.0
	 */
	public static function getPermissions($path)
	{
		$path = self::clean($path);
		$mode = @ decoct(@ fileperms($path) & 0777);

		if (\strlen($mode) < 3)
		{
			return '---------';
		}

		$parsedMode = '';

		for ($i = 0; $i < 3; $i++)
		{
			// Read
			$parsedMode .= ($mode{$i} & 04) ? 'r' : '-';

			// Write
			$parsedMode .= ($mode{$i} & 02) ? 'w' : '-';

			// Execute
			$parsedMode .= ($mode{$i} & 01) ? 'x' : '-';
		}

		return $parsedMode;
	}

	/**
	 * Checks for snooping outside of the file system root.
	 *
	 * @param   string  $path  A file system path to check.
	 *
	 * @return  string  A cleaned version of the path or exit on error.
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public static function check($path)
	{
		if (strpos($path, '..') !== false)
		{
			throw new FilesystemException(
				sprintf(
					'%s() - Use of relative paths not permitted',
					__METHOD__
				),
				20
			);
		}

		$path = static::clean($path);

		if ((JPATH_ROOT != '') && strpos($path, static::clean(JPATH_ROOT)) !== 0)
		{
			throw new FilesystemException(
				sprintf(
					'%1$s() - Snooping out of bounds @ %2$s',
					__METHOD__,
					$path
				),
				20
			);
		}

		return $path;
	}

	/**
	 * Function to strip additional / or \ in a path name.
	 *
	 * @param   string  $path  The path to clean.
	 * @param   string  $ds    Directory separator (optional).
	 *
	 * @return  string  The cleaned path.
	 *
	 * @since   1.0
	 * @throws  \UnexpectedValueException If $path is not a string.
	 */
	public static function clean($path, $ds = DIRECTORY_SEPARATOR)
	{
		if (!\is_string($path))
		{
			throw new \UnexpectedValueException('JPath::clean $path is not a string.');
		}

		$stream = explode('://', $path, 2);
		$scheme = '';
		$path   = $stream[0];

		if (\count($stream) >= 2)
		{
			$scheme = $stream[0] . '://';
			$path   = $stream[1];
		}

		$path = trim($path);

		if (empty($path))
		{
			$path = JPATH_ROOT;
		}
		elseif (($ds == '\\') && ($path[0] == '\\') && ($path[1] == '\\'))
		{
			// Remove double slashes and backslashes and convert all slashes and backslashes to DIRECTORY_SEPARATOR
			// If dealing with a UNC path don't forget to prepend the path with a backslash.
			$path = '\\' . preg_replace('#[/\\\\]+#', $ds, $path);
		}
		else
		{
			$path = preg_replace('#[/\\\\]+#', $ds, $path);
		}

		return $scheme . $path;
	}

	/**
	 * Method to determine if script owns the path.
	 *
	 * @param   string  $path  Path to check ownership.
	 *
	 * @return  boolean  True if the php script owns the path passed.
	 *
	 * @since   1.0
	 */
	public static function isOwner($path)
	{
		$tmp = md5(random_bytes(16));
		$ssp = ini_get('session.save_path');
		$jtp = JPATH_ROOT;

		// Try to find a writable directory
		$dir = is_writable('/tmp') ? '/tmp' : false;
		$dir = (!$dir && is_writable($ssp)) ? $ssp : $dir;
		$dir = (!$dir && is_writable($jtp)) ? $jtp : $dir;

		if ($dir)
		{
			$test = $dir . '/' . $tmp;

			// Create the test file
			$blank = '';
			File::write($test, $blank, false);

			// Test ownership
			$return = (fileowner($test) == fileowner($path));

			// Delete the test file
			File::delete($test);

			return $return;
		}

		return false;
	}

	/**
	 * Searches the directory paths for a given file.
	 *
	 * @param   mixed   $paths  A path string or array of path strings to search in
	 * @param   string  $file   The file name to look for.
	 *
	 * @return  mixed   The full path and file name for the target file, or boolean false if the file is not found in any of the paths.
	 *
	 * @since   1.0
	 */
	public static function find($paths, $file)
	{
		// Force to array
		if (!\is_array($paths) && !($paths instanceof \Iterator))
		{
			settype($paths, 'array');
		}

		// Start looping through the path set
		foreach ($paths as $path)
		{
			// Get the path to the file
			$fullname = $path . '/' . $file;

			// Is the path based on a stream?
			if (strpos($path, '://') === false)
			{
				// Not a stream, so do a realpath() to avoid directory
				// traversal attempts on the local file system.

				// Needed for substr() later
				$path     = realpath($path);
				$fullname = realpath($fullname);
			}

			/*
			 * The substr() check added to make sure that the realpath()
			 * results in a directory registered so that
			 * non-registered directories are not accessible via directory
			 * traversal attempts.
			 */
			if (file_exists($fullname) && substr($fullname, 0, \strlen($path)) == $path)
			{
				return $fullname;
			}
		}

		// Could not find the file in the set of paths
		return false;
	}
}
vendor/joomla/filesystem/src/Clients/FtpClient.php000064400000122020152177723700016307 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem\Clients;

use Joomla\Filesystem\Exception\FilesystemException;

/*
 * Error Codes:
 * - 30 : Unable to connect to host
 * - 31 : Not connected
 * - 32 : Unable to send command to server
 * - 33 : Bad username
 * - 34 : Bad password
 * - 35 : Bad response
 * - 36 : Passive mode failed
 * - 37 : Data transfer error
 * - 38 : Local filesystem error
 */

if (!\defined('CRLF'))
{
	\define('CRLF', "\r\n");
}

if (!\defined('FTP_AUTOASCII'))
{
	\define('FTP_AUTOASCII', -1);
}

if (!\defined('FTP_BINARY'))
{
	\define('FTP_BINARY', 1);
}

if (!\defined('FTP_ASCII'))
{
	\define('FTP_ASCII', 0);
}

if (!\defined('FTP_NATIVE'))
{
	\define('FTP_NATIVE', (\function_exists('ftp_connect')) ? 1 : 0);
}

/**
 * FTP client class
 *
 * @since  1.0
 */
class FtpClient
{
	/**
	 * @var    resource  Socket resource
	 * @since  1.0
	 */
	private $conn;

	/**
	 * @var    resource  Data port connection resource
	 * @since  1.0
	 */
	private $dataconn;

	/**
	 * @var    array  Passive connection information
	 * @since  1.0
	 */
	private $pasv;

	/**
	 * @var    string  Response Message
	 * @since  1.0
	 */
	private $response;

	/**
	 * @var    integer  Response Code
	 * @since  1.0
	 */
	private $responseCode;

	/**
	 * @var    string  Response Message
	 * @since  1.0
	 */
	private $responseMsg;

	/**
	 * @var    integer  Timeout limit
	 * @since  1.0
	 */
	private $timeout = 15;

	/**
	 * @var    integer  Transfer Type
	 * @since  1.0
	 */
	private $type;

	/**
	 * @var    array  Array to hold ascii format file extensions
	 * @since   1.0
	 */
	private $autoAscii = array(
		'asp',
		'bat',
		'c',
		'cpp',
		'csv',
		'h',
		'htm',
		'html',
		'shtml',
		'ini',
		'inc',
		'log',
		'php',
		'php3',
		'pl',
		'perl',
		'sh',
		'sql',
		'txt',
		'xhtml',
		'xml',
	);

	/**
	 * Array to hold native line ending characters
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $lineEndings = array('UNIX' => "\n", 'WIN' => "\r\n");

	/**
	 * @var    FtpClient[]  FtpClient instances container.
	 * @since  1.0
	 */
	protected static $instances = array();

	/**
	 * FtpClient object constructor
	 *
	 * @param   array  $options  Associative array of options to set
	 *
	 * @since   1.0
	 */
	public function __construct(array $options = array())
	{
		// If default transfer type is not set, set it to autoascii detect
		if (!isset($options['type']))
		{
			$options['type'] = FTP_BINARY;
		}

		$this->setOptions($options);

		if (FTP_NATIVE)
		{
			// Autoloading fails for Buffer as the class is used as a stream handler
			class_exists('Joomla\\Filesystem\\Buffer');
		}
	}

	/**
	 * FtpClient object destructor
	 *
	 * Closes an existing connection, if we have one
	 *
	 * @since   1.0
	 */
	public function __destruct()
	{
		if (\is_resource($this->conn))
		{
			$this->quit();
		}
	}

	/**
	 * Returns the global FTP connector object, only creating it
	 * if it doesn't already exist.
	 *
	 * You may optionally specify a username and password in the parameters. If you do so,
	 * you may not login() again with different credentials using the same object.
	 * If you do not use this option, you must quit() the current connection when you
	 * are done, to free it for use by others.
	 *
	 * @param   string  $host     Host to connect to
	 * @param   string  $port     Port to connect to
	 * @param   array   $options  Array with any of these options: type=>[FTP_AUTOASCII|FTP_ASCII|FTP_BINARY], timeout=>(int)
	 * @param   string  $user     Username to use for a connection
	 * @param   string  $pass     Password to use for a connection
	 *
	 * @return  FtpClient  The FTP Client object.
	 *
	 * @since   1.0
	 */
	public static function getInstance($host = '127.0.0.1', $port = '21', array $options = array(), $user = null, $pass = null)
	{
		$signature = $user . ':' . $pass . '@' . $host . ':' . $port;

		// Create a new instance, or set the options of an existing one
		if (!isset(self::$instances[$signature]) || !\is_object(self::$instances[$signature]))
		{
			self::$instances[$signature] = new static($options);
		}
		else
		{
			self::$instances[$signature]->setOptions($options);
		}

		// Connect to the server, and login, if requested
		if (!self::$instances[$signature]->isConnected())
		{
			$return = self::$instances[$signature]->connect($host, $port);

			if ($return && $user !== null && $pass !== null)
			{
				self::$instances[$signature]->login($user, $pass);
			}
		}

		return self::$instances[$signature];
	}

	/**
	 * Set client options
	 *
	 * @param   array  $options  Associative array of options to set
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 */
	public function setOptions(array $options)
	{
		if (isset($options['type']))
		{
			$this->type = $options['type'];
		}

		if (isset($options['timeout']))
		{
			$this->timeout = $options['timeout'];
		}

		return true;
	}

	/**
	 * Method to connect to a FTP server
	 *
	 * @param   string   $host  Host to connect to [Default: 127.0.0.1]
	 * @param   integer  $port  Port to connect on [Default: port 21]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function connect($host = '127.0.0.1', $port = 21)
	{
		$errno = null;
		$err   = null;

		// If already connected, return
		if (\is_resource($this->conn))
		{
			return true;
		}

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			$this->conn = @ftp_connect($host, $port, $this->timeout);

			if ($this->conn === false)
			{
				throw new FilesystemException(sprintf('%1$s: Could not connect to host " %2$s " on port " %3$s "', __METHOD__, $host, $port));
			}

			// Set the timeout for this connection
			ftp_set_option($this->conn, FTP_TIMEOUT_SEC, $this->timeout);

			return true;
		}

		// Connect to the FTP server.
		$this->conn = @ fsockopen($host, $port, $errno, $err, $this->timeout);

		if (!$this->conn)
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Could not connect to host " %2$s " on port " %3$s ". Socket error number: %4$s and error message: %5$s',
					__METHOD__,
					$host,
					$port,
					$errno,
					$err
				)
			);
		}

		// Set the timeout for this connection
		socket_set_timeout($this->conn, $this->timeout, 0);

		// Check for welcome response code
		if (!$this->_verifyResponse(220))
		{
			throw new FilesystemException(sprintf('%1$s: Bad response. Server response: %2$s [Expected: 220]', __METHOD__, $this->response));
		}

		return true;
	}

	/**
	 * Method to determine if the object is connected to an FTP server
	 *
	 * @return  boolean  True if connected
	 *
	 * @since   1.0
	 */
	public function isConnected()
	{
		return \is_resource($this->conn);
	}

	/**
	 * Method to login to a server once connected
	 *
	 * @param   string  $user  Username to login to the server
	 * @param   string  $pass  Password to login to the server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function login($user = 'anonymous', $pass = 'jftp@joomla.org')
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_login($this->conn, $user, $pass) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to login');
			}

			return true;
		}

		// Send the username
		if (!$this->_putCmd('USER ' . $user, array(331, 503)))
		{
			throw new FilesystemException(
				sprintf('%1$s: Bad Username. Server response: %2$s [Expected: 331]. Username sent: %3$s', __METHOD__, $this->response, $user)
			);
		}

		// If we are already logged in, continue :)
		if ($this->responseCode == 503)
		{
			return true;
		}

		// Send the password
		if (!$this->_putCmd('PASS ' . $pass, 230))
		{
			throw new FilesystemException(sprintf('%1$s: Bad Password. Server response: %2$s [Expected: 230].', __METHOD__, $this->response));
		}

		return true;
	}

	/**
	 * Method to quit and close the connection
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 */
	public function quit()
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			@ftp_close($this->conn);

			return true;
		}

		// Logout and close connection
		@fwrite($this->conn, "QUIT\r\n");
		@fclose($this->conn);

		return true;
	}

	/**
	 * Method to retrieve the current working directory on the FTP server
	 *
	 * @return  string   Current working directory
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function pwd()
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (($ret = @ftp_pwd($this->conn)) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return $ret;
		}

		$match = array(null);

		// Send print working directory command and verify success
		if (!$this->_putCmd('PWD', 257))
		{
			throw new FilesystemException(sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 257]', __METHOD__, $this->response));
		}

		// Match just the path
		preg_match('/"[^"\r\n]*"/', $this->response, $match);

		// Return the cleaned path
		return preg_replace('/"/', '', $match[0]);
	}

	/**
	 * Method to system string from the FTP server
	 *
	 * @return  string   System identifier string
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function syst()
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			if (($ret = @ftp_systype($this->conn)) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}
		}
		else
		{
			// Send print working directory command and verify success
			if (!$this->_putCmd('SYST', 215))
			{
				throw new FilesystemException(sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 215]', __METHOD__, $this->response));
			}

			$ret = $this->response;
		}

		// Match the system string to an OS
		if (strpos(strtoupper($ret), 'MAC') !== false)
		{
			$ret = 'MAC';
		}
		elseif (strpos(strtoupper($ret), 'WIN') !== false)
		{
			$ret = 'WIN';
		}
		else
		{
			$ret = 'UNIX';
		}

		// Return the os type
		return $ret;
	}

	/**
	 * Method to change the current working directory on the FTP server
	 *
	 * @param   string  $path  Path to change into on the server
	 *
	 * @return  boolean True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function chdir($path)
	{
		// If native FTP support is enabled lets use it...
		if (FTP_NATIVE)
		{
			if (@ftp_chdir($this->conn, $path) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		// Send change directory command and verify success
		if (!$this->_putCmd('CWD ' . $path, 250))
		{
			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 250].  Sent path: %3$s', __METHOD__, $this->response, $path)
			);
		}

		return true;
	}

	/**
	 * Method to reinitialise the server, ie. need to login again
	 *
	 * NOTE: This command not available on all servers
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function reinit()
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->conn, 'REIN') === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		// Send reinitialise command to the server
		if (!$this->_putCmd('REIN', 220))
		{
			throw new FilesystemException(sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 220]', __METHOD__, $this->response));
		}

		return true;
	}

	/**
	 * Method to rename a file/folder on the FTP server
	 *
	 * @param   string  $from  Path to change file/folder from
	 * @param   string  $to    Path to change file/folder to
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function rename($from, $to)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_rename($this->conn, $from, $to) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		// Send rename from command to the server
		if (!$this->_putCmd('RNFR ' . $from, 350))
		{
			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 350].  From path sent: %3$s', __METHOD__, $this->response, $from)
			);
		}

		// Send rename to command to the server
		if (!$this->_putCmd('RNTO ' . $to, 250))
		{
			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 250].  To path sent: %3$s', __METHOD__, $this->response, $to)
			);
		}

		return true;
	}

	/**
	 * Method to change mode for a path on the FTP server
	 *
	 * @param   string  $path  Path to change mode on
	 * @param   mixed   $mode  Octal value to change mode to, e.g. '0777', 0777 or 511 (string or integer)
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function chmod($path, $mode)
	{
		// If no filename is given, we assume the current directory is the target
		if ($path == '')
		{
			$path = '.';
		}

		// Convert the mode to a string
		if (\is_int($mode))
		{
			$mode = decoct($mode);
		}

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->conn, 'CHMOD ' . $mode . ' ' . $path) === false)
			{
				if (!\defined('PHP_WINDOWS_VERSION_MAJOR'))
				{
					throw new FilesystemException(__METHOD__ . 'Bad response.');
				}

				return false;
			}

			return true;
		}

		// Send change mode command and verify success [must convert mode from octal]
		if (!$this->_putCmd('SITE CHMOD ' . $mode . ' ' . $path, array(200, 250)))
		{
			if (!\defined('PHP_WINDOWS_VERSION_MAJOR'))
			{
				throw new FilesystemException(
					sprintf(
						'%1$s: Bad response.  Server response: %2$s [Expected: 250].  Path sent: %3$s.  Mode sent: %4$s',
						__METHOD__,
						$this->response,
						$path,
						$mode
					)
				);
			}

			return false;
		}

		return true;
	}

	/**
	 * Method to delete a path [file/folder] on the FTP server
	 *
	 * @param   string  $path  Path to delete
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function delete($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_delete($this->conn, $path) === false)
			{
				if (@ftp_rmdir($this->conn, $path) === false)
				{
					throw new FilesystemException(__METHOD__ . 'Bad response.');
				}
			}

			return true;
		}

		// Send delete file command and if that doesn't work, try to remove a directory
		if (!$this->_putCmd('DELE ' . $path, 250))
		{
			if (!$this->_putCmd('RMD ' . $path, 250))
			{
				throw new FilesystemException(
					sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 250].  Path sent: %3$s', __METHOD__, $this->response, $path)
				);
			}
		}

		return true;
	}

	/**
	 * Method to create a directory on the FTP server
	 *
	 * @param   string  $path  Directory to create
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function mkdir($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_mkdir($this->conn, $path) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		// Send change directory command and verify success
		if (!$this->_putCmd('MKD ' . $path, 257))
		{
			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 257].  Path sent: %3$s', __METHOD__, $this->response, $path)
			);
		}

		return true;
	}

	/**
	 * Method to restart data transfer at a given byte
	 *
	 * @param   integer  $point  Byte to restart transfer at
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function restart($point)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			if (@ftp_site($this->conn, 'REST ' . $point) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		// Send restart command and verify success
		if (!$this->_putCmd('REST ' . $point, 350))
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Bad response.  Server response: %2$s [Expected: 350].  Restart point sent: %3$s', __METHOD__, $this->response, $point
				)
			);
		}

		return true;
	}

	/**
	 * Method to create an empty file on the FTP server
	 *
	 * @param   string  $path  Path local file to store on the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function create($path)
	{
		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			$buffer = fopen('buffer://tmp', 'r');

			if (@ftp_fput($this->conn, $path, $buffer, FTP_ASCII) === false)
			{
				fclose($buffer);

				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			fclose($buffer);

			return true;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		if (!$this->_putCmd('STOR ' . $path, array(150, 125)))
		{
			@ fclose($this->dataconn);

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $path)
			);
		}

		// To create a zero byte upload close the data port connection
		fclose($this->dataconn);

		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $path)
			);
		}

		return true;
	}

	/**
	 * Method to read a file from the FTP server's contents into a buffer
	 *
	 * @param   string  $remote  Path to remote file to read on the FTP server
	 * @param   string  $buffer  Buffer variable to read file contents into
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function read($remote, &$buffer)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			$tmp = fopen('buffer://tmp', 'br+');

			if (@ftp_fget($this->conn, $tmp, $remote, $mode) === false)
			{
				fclose($tmp);

				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			// Read tmp buffer contents
			rewind($tmp);
			$buffer = '';

			while (!feof($tmp))
			{
				$buffer .= fread($tmp, 8192);
			}

			fclose($tmp);

			return true;
		}

		$this->_mode($mode);

		// Start passive mode
		if (!$this->_passive())
		{
			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
		{
			@ fclose($this->dataconn);

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		// Read data from data port connection and add to the buffer
		$buffer = '';

		while (!feof($this->dataconn))
		{
			$buffer .= fread($this->dataconn, 4096);
		}

		// Close the data port connection
		fclose($this->dataconn);

		// Let's try to cleanup some line endings if it is ascii
		if ($mode == FTP_ASCII)
		{
			$os = 'UNIX';

			if (\defined('PHP_WINDOWS_VERSION_MAJOR'))
			{
				$os = 'WIN';
			}

			$buffer = preg_replace('/' . CRLF . '/', $this->lineEndings[$os], $buffer);
		}

		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Restart point sent: %3$s',
					__METHOD__, $this->response, $remote
				)
			);
		}

		return true;
	}

	/**
	 * Method to get a file from the FTP server and save it to a local file
	 *
	 * @param   string  $local   Local path to save remote file to
	 * @param   string  $remote  Path to remote file to get on the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function get($local, $remote)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			if (@ftp_get($this->conn, $local, $remote, $mode) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		$this->_mode($mode);

		// Check to see if the local file can be opened for writing
		$fp = fopen($local, 'wb');

		if (!$fp)
		{
			throw new FilesystemException(sprintf('%1$s: Unable to open local file for writing.  Local path: %2$s', __METHOD__, $local));
		}

		// Start passive mode
		if (!$this->_passive())
		{
			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		if (!$this->_putCmd('RETR ' . $remote, array(150, 125)))
		{
			@ fclose($this->dataconn);

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		// Read data from data port connection and add to the buffer
		while (!feof($this->dataconn))
		{
			$buffer = fread($this->dataconn, 4096);
			fwrite($fp, $buffer, 4096);
		}

		// Close the data port connection and file pointer
		fclose($this->dataconn);
		fclose($fp);

		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		return true;
	}

	/**
	 * Method to store a file to the FTP server
	 *
	 * @param   string  $local   Path to local file to store on the FTP server
	 * @param   string  $remote  FTP path to file to create
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function store($local, $remote = null)
	{
		// If remote file is not given, use the filename of the local file in the current
		// working directory.
		if ($remote == null)
		{
			$remote = basename($local);
		}

		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			if (@ftp_put($this->conn, $remote, $local, $mode) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			return true;
		}

		$this->_mode($mode);

		// Check to see if the local file exists and if so open it for reading
		if (@ file_exists($local))
		{
			$fp = fopen($local, 'rb');

			if (!$fp)
			{
				throw new FilesystemException(sprintf('%1$s: Unable to open local file for reading. Local path: %2$s', __METHOD__, $local));
			}
		}
		else
		{
			throw new FilesystemException(sprintf('%1$s: Unable to find local file. Local path: %2$s', __METHOD__, $local));
		}

		// Start passive mode
		if (!$this->_passive())
		{
			@ fclose($fp);

			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		// Send store command to the FTP server
		if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
		{
			@ fclose($fp);
			@ fclose($this->dataconn);

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		// Do actual file transfer, read local file and write to data port connection
		while (!feof($fp))
		{
			$line = fread($fp, 4096);

			do
			{
				if (($result = @ fwrite($this->dataconn, $line)) === false)
				{
					throw new FilesystemException(__METHOD__ . ': Unable to write to data port socket');
				}

				$line = substr($line, $result);
			}
			while ($line != '');
		}

		fclose($fp);
		fclose($this->dataconn);

		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		return true;
	}

	/**
	 * Method to write a string to the FTP server
	 *
	 * @param   string  $remote  FTP path to file to write to
	 * @param   string  $buffer  Contents to write to the FTP server
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function write($remote, $buffer)
	{
		// Determine file type
		$mode = $this->_findMode($remote);

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			$tmp = fopen('buffer://tmp', 'br+');
			fwrite($tmp, $buffer);
			rewind($tmp);

			if (@ftp_fput($this->conn, $remote, $tmp, $mode) === false)
			{
				fclose($tmp);

				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			fclose($tmp);

			return true;
		}

		// First we need to set the transfer mode
		$this->_mode($mode);

		// Start passive mode
		if (!$this->_passive())
		{
			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		// Send store command to the FTP server
		if (!$this->_putCmd('STOR ' . $remote, array(150, 125)))
		{
			@ fclose($this->dataconn);

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		// Write buffer to the data connection port
		do
		{
			if (($result = @ fwrite($this->dataconn, $buffer)) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to write to data port socket.');
			}

			$buffer = substr($buffer, $result);
		}
		while ($buffer != '');

		// Close the data connection port [Data transfer complete]
		fclose($this->dataconn);

		// Verify that the server recieved the transfer
		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $remote)
			);
		}

		return true;
	}

	/**
	 * Method to list the filenames of the contents of a directory on the FTP server
	 *
	 * Note: Some servers also return folder names. However, to be sure to list folders on all
	 * servers, you should use listDetails() instead if you also need to deal with folders
	 *
	 * @param   string  $path  Path local file to store on the FTP server
	 *
	 * @return  string  Directory listing
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function listNames($path = null)
	{
		$data = null;

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			if (($list = @ftp_nlist($this->conn, $path)) === false)
			{
				// Workaround for empty directories on some servers
				if ($this->listDetails($path, 'files') === array())
				{
					return array();
				}

				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}

			$list = preg_replace('#^' . preg_quote($path, '#') . '[/\\\\]?#', '', $list);

			if ($keys = array_merge(array_keys($list, '.'), array_keys($list, '..')))
			{
				foreach ($keys as $key)
				{
					unset($list[$key]);
				}
			}

			return $list;
		}

		// If a path exists, prepend a space
		if ($path != null)
		{
			$path = ' ' . $path;
		}

		// Start passive mode
		if (!$this->_passive())
		{
			throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
		}

		if (!$this->_putCmd('NLST' . $path, array(150, 125)))
		{
			@ fclose($this->dataconn);

			// Workaround for empty directories on some servers
			if ($this->listDetails($path, 'files') === array())
			{
				return array();
			}

			throw new FilesystemException(
				sprintf('%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s', __METHOD__, $this->response, $path)
			);
		}

		// Read in the file listing.
		while (!feof($this->dataconn))
		{
			$data .= fread($this->dataconn, 4096);
		}

		fclose($this->dataconn);

		// Everything go okay?
		if (!$this->_verifyResponse(226))
		{
			throw new FilesystemException(
				sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $path)
			);
		}

		$data = preg_split('/[' . CRLF . ']+/', $data, -1, PREG_SPLIT_NO_EMPTY);
		$data = preg_replace('#^' . preg_quote(substr($path, 1), '#') . '[/\\\\]?#', '', $data);

		if ($keys = array_merge(array_keys($data, '.'), array_keys($data, '..')))
		{
			foreach ($keys as $key)
			{
				unset($data[$key]);
			}
		}

		return $data;
	}

	/**
	 * Method to list the contents of a directory on the FTP server
	 *
	 * @param   string  $path  Path to the local file to be stored on the FTP server
	 * @param   string  $type  Return type [raw|all|folders|files]
	 *
	 * @return  mixed  If $type is raw: string Directory listing, otherwise array of string with file-names
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	public function listDetails($path = null, $type = 'all')
	{
		$dirList = array();
		$data    = null;
		$regs    = null;

		// TODO: Deal with recurse -- nightmare
		// For now we will just set it to false
		$recurse = false;

		// If native FTP support is enabled let's use it...
		if (FTP_NATIVE)
		{
			// Turn passive mode on
			if (@ftp_pasv($this->conn, true) === false)
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			if (($contents = @ftp_rawlist($this->conn, $path)) === false)
			{
				throw new FilesystemException(__METHOD__ . 'Bad response.');
			}
		}
		else
		{
			// Non Native mode

			// Start passive mode
			if (!$this->_passive())
			{
				throw new FilesystemException(__METHOD__ . ': Unable to use passive mode.');
			}

			// If a path exists, prepend a space
			if ($path != null)
			{
				$path = ' ' . $path;
			}

			// Request the file listing
			if (!$this->_putCmd(($recurse == true) ? 'LIST -R' : 'LIST' . $path, array(150, 125)))
			{
				@ fclose($this->dataconn);

				throw new FilesystemException(
					sprintf(
						'%1$s: Bad response.  Server response: %2$s [Expected: 150 or 125].  Path sent: %3$s',
						__METHOD__, $this->response, $path
					)
				);
			}

			// Read in the file listing.
			while (!feof($this->dataconn))
			{
				$data .= fread($this->dataconn, 4096);
			}

			fclose($this->dataconn);

			// Everything go okay?
			if (!$this->_verifyResponse(226))
			{
				throw new FilesystemException(
					sprintf('%1$s: Transfer failed.  Server response: %2$s [Expected: 226].  Path sent: %3$s', __METHOD__, $this->response, $path)
				);
			}

			$contents = explode(CRLF, $data);
		}

		// If only raw output is requested we are done
		if ($type == 'raw')
		{
			return $data;
		}

		// If we received the listing of an empty directory, we are done as well
		if (empty($contents[0]))
		{
			return $dirList;
		}

		// If the server returned the number of results in the first response, let's dump it
		if (strtolower(substr($contents[0], 0, 6)) == 'total ')
		{
			array_shift($contents);

			if (!isset($contents[0]) || empty($contents[0]))
			{
				return $dirList;
			}
		}

		// Regular expressions for the directory listing parsing.
		$regexps = array(
			'UNIX' => '#([-dl][rwxstST-]+).* ([0-9]*) ([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
				. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{1,2}:[0-9]{2})|[0-9]{4}) (.+)#',
			'MAC' => '#([-dl][rwxstST-]+).* ?([0-9 ]*)?([a-zA-Z0-9]+).* ([a-zA-Z0-9]+).* ([0-9]*)'
				. ' ([a-zA-Z]+[0-9: ]*[0-9])[ ]+(([0-9]{2}:[0-9]{2})|[0-9]{4}) (.+)#',
			'WIN' => '#([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)#',
		);

		// Find out the format of the directory listing by matching one of the regexps
		$osType = null;

		foreach ($regexps as $k => $v)
		{
			if (@preg_match($v, $contents[0]))
			{
				$osType = $k;
				$regexp = $v;

				break;
			}
		}

		if (!$osType)
		{
			throw new FilesystemException(__METHOD__ . ': Unrecognised directory listing format.');
		}

		/*
		 * Here is where it is going to get dirty....
		 */
		if ($osType == 'UNIX' || $osType == 'MAC')
		{
			foreach ($contents as $file)
			{
				$tmpArray = null;

				if (@preg_match($regexp, $file, $regs))
				{
					$fType = (int) strpos('-dl', $regs[1]{0});

					// $tmpArray['line'] = $regs[0];
					$tmpArray['type']   = $fType;
					$tmpArray['rights'] = $regs[1];

					// $tmpArray['number'] = $regs[2];
					$tmpArray['user']  = $regs[3];
					$tmpArray['group'] = $regs[4];
					$tmpArray['size']  = $regs[5];
					$tmpArray['date']  = @date('m-d', strtotime($regs[6]));
					$tmpArray['time']  = $regs[7];
					$tmpArray['name']  = $regs[9];
				}

				// If we just want files, do not add a folder
				if ($type == 'files' && $tmpArray['type'] == 1)
				{
					continue;
				}

				// If we just want folders, do not add a file
				if ($type == 'folders' && $tmpArray['type'] == 0)
				{
					continue;
				}

				if (\is_array($tmpArray) && $tmpArray['name'] != '.' && $tmpArray['name'] != '..')
				{
					$dirList[] = $tmpArray;
				}
			}
		}
		else
		{
			foreach ($contents as $file)
			{
				$tmpArray = null;

				if (@preg_match($regexp, $file, $regs))
				{
					$fType     = (int) ($regs[7] == '<DIR>');
					$timestamp = strtotime("$regs[3]-$regs[1]-$regs[2] $regs[4]:$regs[5]$regs[6]");

					// $tmpArray['line'] = $regs[0];
					$tmpArray['type']   = $fType;
					$tmpArray['rights'] = '';

					// $tmpArray['number'] = 0;
					$tmpArray['user']  = '';
					$tmpArray['group'] = '';
					$tmpArray['size']  = (int) $regs[7];
					$tmpArray['date']  = date('m-d', $timestamp);
					$tmpArray['time']  = date('H:i', $timestamp);
					$tmpArray['name']  = $regs[8];
				}

				// If we just want files, do not add a folder
				if ($type == 'files' && $tmpArray['type'] == 1)
				{
					continue;
				}

				// If we just want folders, do not add a file
				if ($type == 'folders' && $tmpArray['type'] == 0)
				{
					continue;
				}

				if (\is_array($tmpArray) && $tmpArray['name'] != '.' && $tmpArray['name'] != '..')
				{
					$dirList[] = $tmpArray;
				}
			}
		}

		return $dirList;
	}

	/**
	 * Send command to the FTP server and validate an expected response code
	 *
	 * @param   string  $cmd               Command to send to the FTP server
	 * @param   mixed   $expectedResponse  Integer response code or array of integer response codes
	 *
	 * @return  boolean  True if command executed successfully
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	protected function _putCmd($cmd, $expectedResponse)
	{
		// Make sure we have a connection to the server
		if (!\is_resource($this->conn))
		{
			throw new FilesystemException(__METHOD__ . ': Not connected to the control port.');
		}

		// Send the command to the server
		if (!fwrite($this->conn, $cmd . "\r\n"))
		{
			throw new FilesystemException(sprintf('%1$s: Unable to send command: %2$s', __METHOD__, $cmd));
		}

		return $this->_verifyResponse($expectedResponse);
	}

	/**
	 * Verify the response code from the server and log response if flag is set
	 *
	 * @param   mixed  $expected  Integer response code or array of integer response codes
	 *
	 * @return  boolean  True if response code from the server is expected
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	protected function _verifyResponse($expected)
	{
		$parts = null;

		// Wait for a response from the server, but timeout after the set time limit
		$endTime        = time() + $this->timeout;
		$this->response = '';

		do
		{
			$this->response .= fgets($this->conn, 4096);
		}
		while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\\1)? [^' . CRLF . ']+' . CRLF . '$/', $this->response, $parts) && time() < $endTime);

		// Catch a timeout or bad response
		if (!isset($parts[1]))
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Timeout or unrecognised response while waiting for a response from the server. Server response: %2$s',
					__METHOD__, $this->response
				)
			);
		}

		// Separate the code from the message
		$this->responseCode = $parts[1];
		$this->responseMsg  = $parts[0];

		// Did the server respond with the code we wanted?
		if (\is_array($expected))
		{
			if (\in_array($this->responseCode, $expected))
			{
				$retval = true;
			}
			else
			{
				$retval = false;
			}
		}
		else
		{
			if ($this->responseCode == $expected)
			{
				$retval = true;
			}
			else
			{
				$retval = false;
			}
		}

		return $retval;
	}

	/**
	 * Set server to passive mode and open a data port connection
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	protected function _passive()
	{
		$match = array();
		$parts = array();
		$errno = null;
		$err   = null;

		// Make sure we have a connection to the server
		if (!\is_resource($this->conn))
		{
			throw new FilesystemException(__METHOD__ . ': Not connected to the control port.');
		}

		// Request a passive connection - this means, we'll talk to you, you don't talk to us.
		@ fwrite($this->conn, "PASV\r\n");

		// Wait for a response from the server, but timeout after the set time limit
		$endTime        = time() + $this->timeout;
		$this->response = '';

		do
		{
			$this->response .= fgets($this->conn, 4096);
		}
		while (!preg_match('/^([0-9]{3})(-(.*' . CRLF . ')+\\1)? [^' . CRLF . ']+' . CRLF . '$/', $this->response, $parts) && time() < $endTime);

		// Catch a timeout or bad response
		if (!isset($parts[1]))
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Timeout or unrecognised response while waiting for a response from the server. Server response: %2$s',
					__METHOD__, $this->response
				)
			);
		}

		// Separate the code from the message
		$this->responseCode = $parts[1];
		$this->responseMsg  = $parts[0];

		// If it's not 227, we weren't given an IP and port, which means it failed.
		if ($this->responseCode != 227)
		{
			throw new FilesystemException(
				sprintf('%1$s: Unable to obtain IP and port for data transfer. Server response: %2$s', __METHOD__, $this->responseMsg)
			);
		}

		// Snatch the IP and port information, or die horribly trying...
		if (preg_match('~\((\d+),\s*(\d+),\s*(\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+))\)~', $this->responseMsg, $match) == 0)
		{
			throw new FilesystemException(
				sprintf('%1$s: IP and port for data transfer not valid. Server response: %2$s', __METHOD__, $this->responseMsg)
			);
		}

		// This is pretty simple - store it for later use ;).
		$this->pasv = array('ip' => $match[1] . '.' . $match[2] . '.' . $match[3] . '.' . $match[4], 'port' => $match[5] * 256 + $match[6]);

		// Connect, assuming we've got a connection.
		$this->dataconn = @fsockopen($this->pasv['ip'], $this->pasv['port'], $errno, $err, $this->timeout);

		if (!$this->dataconn)
		{
			throw new FilesystemException(
				sprintf(
					'%1$s: Could not connect to host %2$s on port %3$s. Socket error number: %4$s and error message: %5$s',
					__METHOD__,
					$this->pasv['ip'],
					$this->pasv['port'],
					$errno,
					$err
				)
			);
		}

		// Set the timeout for this connection
		socket_set_timeout($this->conn, $this->timeout, 0);

		return true;
	}

	/**
	 * Method to find out the correct transfer mode for a specific file
	 *
	 * @param   string  $fileName  Name of the file
	 *
	 * @return  integer Transfer-mode for this filetype [FTP_ASCII|FTP_BINARY]
	 *
	 * @since   1.0
	 */
	protected function _findMode($fileName)
	{
		if ($this->type == FTP_AUTOASCII)
		{
			$dot = strrpos($fileName, '.') + 1;
			$ext = substr($fileName, $dot);

			if (\in_array($ext, $this->autoAscii))
			{
				$mode = FTP_ASCII;
			}
			else
			{
				$mode = FTP_BINARY;
			}
		}
		elseif ($this->type == FTP_ASCII)
		{
			$mode = FTP_ASCII;
		}
		else
		{
			$mode = FTP_BINARY;
		}

		return $mode;
	}

	/**
	 * Set transfer mode
	 *
	 * @param   integer  $mode  Integer representation of data transfer mode [1:Binary|0:Ascii]
	 *                          Defined constants can also be used [FTP_BINARY|FTP_ASCII]
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  FilesystemException
	 */
	protected function _mode($mode)
	{
		if ($mode == FTP_BINARY)
		{
			if (!$this->_putCmd('TYPE I', 200))
			{
				throw new FilesystemException(
					sprintf('%1$s: Bad response. Server response: %2$s [Expected: 200]. Mode sent: Binary', __METHOD__, $this->response)
				);
			}
		}
		else
		{
			if (!$this->_putCmd('TYPE A', 200))
			{
				throw new FilesystemException(
					sprintf('%1$s: Bad response. Server response: %2$s [Expected: 200]. Mode sent: ASCII', __METHOD__, $this->response)
				);
			}
		}

		return true;
	}
}
vendor/joomla/filesystem/src/Patcher.php000064400000026145152177723700014417 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

/**
 * A Unified Diff Format Patcher class
 *
 * @link   http://sourceforge.net/projects/phppatcher/ This has been derived from the PhpPatcher version 0.1.1 written by Giuseppe Mazzotta
 * @since  1.0
 */
class Patcher
{
	/**
	 * Regular expression for searching source files
	 */
	const SRC_FILE = '/^---\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';

	/**
	 * Regular expression for searching destination files
	 */
	const DST_FILE = '/^\\+\\+\\+\\s+(\\S+)\s+\\d{1,4}-\\d{1,2}-\\d{1,2}\\s+\\d{1,2}:\\d{1,2}:\\d{1,2}(\\.\\d+)?\\s+(\+|-)\\d{4}/A';

	/**
	 * Regular expression for searching hunks of differences
	 */
	const HUNK = '/@@ -(\\d+)(,(\\d+))?\\s+\\+(\\d+)(,(\\d+))?\\s+@@($)/A';

	/**
	 * Regular expression for splitting lines
	 */
	const SPLIT = '/(\r\n)|(\r)|(\n)/';

	/**
	 * @var    array  sources files
	 * @since  1.0
	 */
	protected $sources = array();

	/**
	 * @var    array  destination files
	 * @since  1.0
	 */
	protected $destinations = array();

	/**
	 * @var    array  removal files
	 * @since  1.0
	 */
	protected $removals = array();

	/**
	 * @var    array  patches
	 * @since  1.0
	 */
	protected $patches = array();

	/**
	 * @var    array  instance of this class
	 * @since  1.0
	 */
	protected static $instance;

	/**
	 * Constructor
	 *
	 * The constructor is protected to force the use of Patcher::getInstance()
	 *
	 * @since   1.0
	 */
	protected function __construct()
	{
	}

	/**
	 * Method to get a patcher
	 *
	 * @return  Patcher  an instance of the patcher
	 *
	 * @since   1.0
	 */
	public static function getInstance()
	{
		if (!isset(static::$instance))
		{
			static::$instance = new static;
		}

		return static::$instance;
	}

	/**
	 * Reset the pacher
	 *
	 * @return  Patcher  This object for chaining
	 *
	 * @since   1.0
	 */
	public function reset()
	{
		$this->sources      = array();
		$this->destinations = array();
		$this->removals     = array();
		$this->patches      = array();

		return $this;
	}

	/**
	 * Apply the patches
	 *
	 * @return  integer  The number of files patched
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function apply()
	{
		foreach ($this->patches as $patch)
		{
			// Separate the input into lines
			$lines = self::splitLines($patch['udiff']);

			// Loop for each header
			while (self::findHeader($lines, $src, $dst))
			{
				$done = false;

				if ($patch['strip'] === null)
				{
					$src = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $src);
					$dst = $patch['root'] . preg_replace('#^([^/]*/)*#', '', $dst);
				}
				else
				{
					$src = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $src);
					$dst = $patch['root'] . preg_replace('#^([^/]*/){' . (int) $patch['strip'] . '}#', '', $dst);
				}

				// Loop for each hunk of differences
				while (self::findHunk($lines, $srcLine, $srcSize, $dstLine, $dstSize))
				{
					$done = true;

					// Apply the hunk of differences
					$this->applyHunk($lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize);
				}

				// If no modifications were found, throw an exception
				if (!$done)
				{
					throw new \RuntimeException('Invalid Diff');
				}
			}
		}

		// Initialize the counter
		$done = 0;

		// Patch each destination file
		foreach ($this->destinations as $file => $content)
		{
			$content = implode("\n", $content);

			if (File::write($file, $content))
			{
				if (isset($this->sources[$file]))
				{
					$this->sources[$file] = $content;
				}

				$done++;
			}
		}

		// Remove each removed file
		foreach ($this->removals as $file)
		{
			if (File::delete($file))
			{
				if (isset($this->sources[$file]))
				{
					unset($this->sources[$file]);
				}

				$done++;
			}
		}

		// Clear the destinations cache
		$this->destinations = array();

		// Clear the removals
		$this->removals = array();

		// Clear the patches
		$this->patches = array();

		return $done;
	}

	/**
	 * Add a unified diff file to the patcher
	 *
	 * @param   string   $filename  Path to the unified diff file
	 * @param   string   $root      The files root path
	 * @param   integer  $strip     The number of '/' to strip
	 *
	 * @return	Patcher  $this for chaining
	 *
	 * @since   1.0
	 */
	public function addFile($filename, $root = JPATH_ROOT, $strip = 0)
	{
		return $this->add(file_get_contents($filename), $root, $strip);
	}

	/**
	 * Add a unified diff string to the patcher
	 *
	 * @param   string   $udiff  Unified diff input string
	 * @param   string   $root   The files root path
	 * @param   integer  $strip  The number of '/' to strip
	 *
	 * @return	Patcher  $this for chaining
	 *
	 * @since   1.0
	 */
	public function add($udiff, $root = JPATH_ROOT, $strip = 0)
	{
		$this->patches[] = array(
			'udiff' => $udiff,
			'root'  => isset($root) ? rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : '',
			'strip' => $strip,
		);

		return $this;
	}

	/**
	 * Separate CR or CRLF lines
	 *
	 * @param   string  $data  Input string
	 *
	 * @return  array  The lines of the input destination file
	 *
	 * @since   1.0
	 */
	protected static function splitLines($data)
	{
		return preg_split(self::SPLIT, $data);
	}

	/**
	 * Find the diff header
	 *
	 * The internal array pointer of $lines is on the next line after the finding
	 *
	 * @param   array   $lines  The udiff array of lines
	 * @param   string  $src    The source file
	 * @param   string  $dst    The destination file
	 *
	 * @return  boolean  TRUE in case of success, FALSE in case of failure
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected static function findHeader(&$lines, &$src, &$dst)
	{
		// Get the current line
		$line = current($lines);

		// Search for the header
		while ($line !== false && !preg_match(self::SRC_FILE, $line, $m))
		{
			$line = next($lines);
		}

		if ($line === false)
		{
			// No header found, return false
			return false;
		}

		// Set the source file
		$src = $m[1];

		// Advance to the next line
		$line = next($lines);

		if ($line === false)
		{
			throw new \RuntimeException('Unexpected EOF');
		}

		// Search the destination file
		if (!preg_match(self::DST_FILE, $line, $m))
		{
			throw new \RuntimeException('Invalid Diff file');
		}

		// Set the destination file
		$dst = $m[1];

		// Advance to the next line
		if (next($lines) === false)
		{
			throw new \RuntimeException('Unexpected EOF');
		}

		return true;
	}

	/**
	 * Find the next hunk of difference
	 *
	 * The internal array pointer of $lines is on the next line after the finding
	 *
	 * @param   array   $lines    The udiff array of lines
	 * @param   string  $srcLine  The beginning of the patch for the source file
	 * @param   string  $srcSize  The size of the patch for the source file
	 * @param   string  $dstLine  The beginning of the patch for the destination file
	 * @param   string  $dstSize  The size of the patch for the destination file
	 *
	 * @return  boolean  TRUE in case of success, false in case of failure
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected static function findHunk(&$lines, &$srcLine, &$srcSize, &$dstLine, &$dstSize)
	{
		$line = current($lines);

		if (preg_match(self::HUNK, $line, $m))
		{
			$srcLine = (int) $m[1];

			if ($m[3] === '')
			{
				$srcSize = 1;
			}
			else
			{
				$srcSize = (int) $m[3];
			}

			$dstLine = (int) $m[4];

			if ($m[6] === '')
			{
				$dstSize = 1;
			}
			else
			{
				$dstSize = (int) $m[6];
			}

			if (next($lines) === false)
			{
				throw new \RuntimeException('Unexpected EOF');
			}

			return true;
		}

		return false;
	}

	/**
	 * Apply the patch
	 *
	 * @param   array   $lines    The udiff array of lines
	 * @param   string  $src      The source file
	 * @param   string  $dst      The destination file
	 * @param   string  $srcLine  The beginning of the patch for the source file
	 * @param   string  $srcSize  The size of the patch for the source file
	 * @param   string  $dstLine  The beginning of the patch for the destination file
	 * @param   string  $dstSize  The size of the patch for the destination file
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function applyHunk(&$lines, $src, $dst, $srcLine, $srcSize, $dstLine, $dstSize)
	{
		$srcLine--;
		$dstLine--;
		$line = current($lines);

		// Source lines (old file)
		$source = array();

		// New lines (new file)
		$destin  = array();
		$srcLeft = $srcSize;
		$dstLeft = $dstSize;

		do
		{
			if (!isset($line[0]))
			{
				$source[] = '';
				$destin[] = '';
				$srcLeft--;
				$dstLeft--;
			}
			elseif ($line[0] == '-')
			{
				if ($srcLeft == 0)
				{
					throw new \RuntimeException('Unexpected remove line at line ' . key($lines));
				}

				$source[] = substr($line, 1);
				$srcLeft--;
			}
			elseif ($line[0] == '+')
			{
				if ($dstLeft == 0)
				{
					throw new \RuntimeException('Unexpected add line at line ' . key($lines));
				}

				$destin[] = substr($line, 1);
				$dstLeft--;
			}
			elseif ($line != '\\ No newline at end of file')
			{
				$line     = substr($line, 1);
				$source[] = $line;
				$destin[] = $line;
				$srcLeft--;
				$dstLeft--;
			}

			if ($srcLeft == 0 && $dstLeft == 0)
			{
				// Now apply the patch, finally!
				if ($srcSize > 0)
				{
					$srcLines = & $this->getSource($src);

					if (!isset($srcLines))
					{
						throw new \RuntimeException('Unexisting source file: ' . $src);
					}
				}

				if ($dstSize > 0)
				{
					if ($srcSize > 0)
					{
						$dstLines  = & $this->getDestination($dst, $src);
						$srcBottom = $srcLine + \count($source);

						for ($l = $srcLine; $l < $srcBottom; $l++)
						{
							if ($srcLines[$l] != $source[$l - $srcLine])
							{
								throw new \RuntimeException(sprintf('Failed source verification of file %1$s at line %2$s', $src, $l));
							}
						}

						array_splice($dstLines, $dstLine, \count($source), $destin);
					}
					else
					{
						$this->destinations[$dst] = $destin;
					}
				}
				else
				{
					$this->removals[] = $src;
				}

				next($lines);

				return;
			}

			$line = next($lines);
		}
		while ($line !== false);

		throw new \RuntimeException('Unexpected EOF');
	}

	/**
	 * Get the lines of a source file
	 *
	 * @param   string  $src  The path of a file
	 *
	 * @return  array  The lines of the source file
	 *
	 * @since   1.0
	 */
	protected function &getSource($src)
	{
		if (!isset($this->sources[$src]))
		{
			if (is_readable($src))
			{
				$this->sources[$src] = self::splitLines(file_get_contents($src));
			}
			else
			{
				$this->sources[$src] = null;
			}
		}

		return $this->sources[$src];
	}

	/**
	 * Get the lines of a destination file
	 *
	 * @param   string  $dst  The path of a destination file
	 * @param   string  $src  The path of a source file
	 *
	 * @return  array  The lines of the destination file
	 *
	 * @since   1.0
	 */
	protected function &getDestination($dst, $src)
	{
		if (!isset($this->destinations[$dst]))
		{
			$this->destinations[$dst] = $this->getSource($src);
		}

		return $this->destinations[$dst];
	}
}
vendor/joomla/filesystem/src/Support/StringController.php000064400000002622152177723700020011 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem\Support;

/**
 * String Controller
 *
 * @since  1.0
 */
class StringController
{
	/**
	 * Internal string references
	 *
	 * @var     array
	 * @ssince  1.4.0
	 */
	private static $strings = array();

	/**
	 * Defines a variable as an array
	 *
	 * @return  array
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use `getArray` instead.
	 */
	public static function _getArray()
	{
		return self::getArray();
	}

	/**
	 * Defines a variable as an array
	 *
	 * @return  array
	 *
	 * @since   1.4.0
	 */
	public static function getArray()
	{
		return self::$strings;
	}

	/**
	 * Create a reference
	 *
	 * @param   string  $reference  The key
	 * @param   string  $string     The value
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public static function createRef($reference, &$string)
	{
		self::$strings[$reference] = & $string;
	}

	/**
	 * Get reference
	 *
	 * @param   string  $reference  The key for the reference.
	 *
	 * @return  mixed  False if not set, reference if it exists
	 *
	 * @since   1.0
	 */
	public static function getRef($reference)
	{
		if (isset(self::$strings[$reference]))
		{
			return self::$strings[$reference];
		}

		return false;
	}
}
vendor/joomla/filesystem/src/Exception/FilesystemException.php000064400000000644152177723700020766 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem\Exception;

/**
 * Exception class for handling errors in the Filesystem package
 *
 * @since  1.2.0
 */
class FilesystemException extends \RuntimeException
{
}
vendor/joomla/filesystem/src/Helper.php000064400000012143152177723700014241 0ustar00<?php
/**
 * Part of the Joomla Framework Filesystem Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Filesystem;

/**
 * File system helper
 *
 * Holds support functions for the filesystem, particularly the stream
 *
 * @since  1.0
 */
class Helper
{
	/**
	 * Remote file size function for streams that don't support it
	 *
	 * @param   string  $url  TODO Add text
	 *
	 * @return  mixed
	 *
	 * @link    https://secure.php.net/manual/en/function.filesize.php#71098
	 * @since   1.0
	 */
	public static function remotefsize($url)
	{
		$sch = parse_url($url, PHP_URL_SCHEME);

		if (!\in_array($sch, array('http', 'https', 'ftp', 'ftps'), true))
		{
			return false;
		}

		if (\in_array($sch, array('http', 'https'), true))
		{
			$headers = @ get_headers($url, 1);

			if (!$headers || (!array_key_exists('Content-Length', $headers)))
			{
				return false;
			}

			return $headers['Content-Length'];
		}

		if (\in_array($sch, array('ftp', 'ftps'), true))
		{
			$server = parse_url($url, PHP_URL_HOST);
			$port   = parse_url($url, PHP_URL_PORT);
			$path   = parse_url($url, PHP_URL_PATH);
			$user   = parse_url($url, PHP_URL_USER);
			$pass   = parse_url($url, PHP_URL_PASS);

			if ((!$server) || (!$path))
			{
				return false;
			}

			if (!$port)
			{
				$port = 21;
			}

			if (!$user)
			{
				$user = 'anonymous';
			}

			if (!$pass)
			{
				$pass = '';
			}

			$ftpid = null;

			switch ($sch)
			{
				case 'ftp':
					$ftpid = @ftp_connect($server, $port);

					break;

				case 'ftps':
					$ftpid = @ftp_ssl_connect($server, $port);

					break;
			}

			if (!$ftpid)
			{
				return false;
			}

			$login = @ftp_login($ftpid, $user, $pass);

			if (!$login)
			{
				return false;
			}

			$ftpsize = ftp_size($ftpid, $path);
			ftp_close($ftpid);

			if ($ftpsize == -1)
			{
				return false;
			}

			return $ftpsize;
		}
	}

	/**
	 * Quick FTP chmod
	 *
	 * @param   string   $url   Link identifier
	 * @param   integer  $mode  The new permissions, given as an octal value.
	 *
	 * @return  mixed
	 *
	 * @link    https://secure.php.net/manual/en/function.ftp-chmod.php
	 * @since   1.0
	 */
	public static function ftpChmod($url, $mode)
	{
		$sch = parse_url($url, PHP_URL_SCHEME);

		if (($sch != 'ftp') && ($sch != 'ftps'))
		{
			return false;
		}

		$server = parse_url($url, PHP_URL_HOST);
		$port   = parse_url($url, PHP_URL_PORT);
		$path   = parse_url($url, PHP_URL_PATH);
		$user   = parse_url($url, PHP_URL_USER);
		$pass   = parse_url($url, PHP_URL_PASS);

		if ((!$server) || (!$path))
		{
			return false;
		}

		if (!$port)
		{
			$port = 21;
		}

		if (!$user)
		{
			$user = 'anonymous';
		}

		if (!$pass)
		{
			$pass = '';
		}

		$ftpid = null;

		switch ($sch)
		{
			case 'ftp':
				$ftpid = @ftp_connect($server, $port);

				break;

			case 'ftps':
				$ftpid = @ftp_ssl_connect($server, $port);

				break;
		}

		if (!$ftpid)
		{
			return false;
		}

		$login = @ftp_login($ftpid, $user, $pass);

		if (!$login)
		{
			return false;
		}

		$res = @ftp_chmod($ftpid, $mode, $path);
		ftp_close($ftpid);

		return $res;
	}

	/**
	 * Modes that require a write operation
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function getWriteModes()
	{
		return array('w', 'w+', 'a', 'a+', 'r+', 'x', 'x+');
	}

	/**
	 * Stream and Filter Support Operations
	 *
	 * Returns the supported streams, in addition to direct file access
	 * Also includes Joomla! streams as well as PHP streams
	 *
	 * @return  array  Streams
	 *
	 * @since   1.0
	 */
	public static function getSupported()
	{
		// Really quite cool what php can do with arrays when you let it...
		static $streams;

		if (!$streams)
		{
			$streams = array_merge(stream_get_wrappers(), self::getJStreams());
		}

		return $streams;
	}

	/**
	 * Returns a list of transports
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function getTransports()
	{
		// Is this overkill?
		return stream_get_transports();
	}

	/**
	 * Returns a list of filters
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function getFilters()
	{
		// Note: This will look like the getSupported() function with J! filters.
		// TODO: add user space filter loading like user space stream loading
		return stream_get_filters();
	}

	/**
	 * Returns a list of J! streams
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function getJStreams()
	{
		static $streams = array();

		if (!$streams)
		{
			$files = new \DirectoryIterator(__DIR__ . '/Stream');

			/** @var $file \DirectoryIterator */
			foreach ($files as $file)
			{
				// Only load for php files.
				if (!$file->isFile() || $file->getExtension() != 'php')
				{
					continue;
				}

				$streams[] = $file->getBasename('.php');
			}
		}

		return $streams;
	}

	/**
	 * Determine if a stream is a Joomla stream.
	 *
	 * @param   string  $streamname  The name of a stream
	 *
	 * @return  boolean  True for a Joomla Stream
	 *
	 * @since   1.0
	 */
	public static function isJoomlaStream($streamname)
	{
		return \in_array($streamname, self::getJStreams());
	}
}
vendor/joomla/filesystem/meta/language/en-GB/en-GB.lib_joomla_filesystem_patcher.ini000064400000001342152177723700024573 0ustar00; Joomla! Project
; Copyright (C) 2005 - 2016 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
; Note : All ini files need to be saved as UTF-8 - No BOM

JLIB_FILESYSTEM_PATCHER_FAILED_VERIFY="Failed source verification of file %s at line %d"
JLIB_FILESYSTEM_PATCHER_INVALID_DIFF="Invalid unified diff block"
JLIB_FILESYSTEM_PATCHER_INVALID_INPUT="Invalid input"
JLIB_FILESYSTEM_PATCHER_UNEXISING_SOURCE="Unexisting source file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_ADD_LINE="Unexpected add line at line %d'"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_EOF="Unexpected end of file"
JLIB_FILESYSTEM_PATCHER_UNEXPECTED_REMOVE_LINE="Unexpected remove line at line %d"

vendor/joomla/registry/LICENSE000064400000042630152177723700012217 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/registry/src/Format/Php.php000064400000005027152177723700014470 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry\Format;

use Joomla\Registry\AbstractRegistryFormat;

/**
 * PHP class format handler for Registry
 *
 * @since  1.0
 */
class Php extends AbstractRegistryFormat
{
	/**
	 * Converts an object into a php class string.
	 * - NOTE: Only one depth level is supported.
	 *
	 * @param   object  $object  Data Source Object
	 * @param   array   $params  Parameters used by the formatter
	 *
	 * @return  string  Config class formatted string
	 *
	 * @since   1.0
	 */
	public function objectToString($object, $params = array())
	{
		// A class must be provided
		$class = !empty($params['class']) ? $params['class'] : 'Registry';

		// Build the object variables string
		$vars = '';

		foreach (get_object_vars($object) as $k => $v)
		{
			if (is_scalar($v))
			{
				$vars .= "\tpublic $" . $k . " = '" . addcslashes($v, '\\\'') . "';\n";
			}
			elseif (is_array($v) || is_object($v))
			{
				$vars .= "\tpublic $" . $k . ' = ' . $this->getArrayString((array) $v) . ";\n";
			}
		}

		$str = "<?php\n";

		// If supplied, add a namespace to the class object
		if (isset($params['namespace']) && $params['namespace'] !== '')
		{
			$str .= 'namespace ' . $params['namespace'] . ";\n\n";
		}

		$str .= 'class ' . $class . " {\n";
		$str .= $vars;
		$str .= '}';

		// Use the closing tag if it not set to false in parameters.
		if (!isset($params['closingtag']) || $params['closingtag'] !== false)
		{
			$str .= "\n?>";
		}

		return $str;
	}

	/**
	 * Parse a PHP class formatted string and convert it into an object.
	 *
	 * @param   string  $data     PHP Class formatted string to convert.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  object   Data object.
	 *
	 * @since   1.0
	 */
	public function stringToObject($data, array $options = array())
	{
		return new \stdClass;
	}

	/**
	 * Method to get an array as an exported string.
	 *
	 * @param   array  $a  The array to get as a string.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	protected function getArrayString($a)
	{
		$s = 'array(';
		$i = 0;

		foreach ($a as $k => $v)
		{
			$s .= $i ? ', ' : '';
			$s .= '"' . $k . '" => ';

			if (is_array($v) || is_object($v))
			{
				$s .= $this->getArrayString((array) $v);
			}
			else
			{
				$s .= '"' . addslashes($v) . '"';
			}

			$i++;
		}

		$s .= ')';

		return $s;
	}
}
vendor/joomla/registry/src/Format/Yaml.php000064400000003637152177723700014650 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry\Format;

use Joomla\Registry\AbstractRegistryFormat;
use Symfony\Component\Yaml\Parser as SymfonyYamlParser;
use Symfony\Component\Yaml\Dumper as SymfonyYamlDumper;

/**
 * YAML format handler for Registry.
 *
 * @since  1.0
 */
class Yaml extends AbstractRegistryFormat
{
	/**
	 * The YAML parser class.
	 *
	 * @var    \Symfony\Component\Yaml\Parser
	 * @since  1.0
	 */
	private $parser;

	/**
	 * The YAML dumper class.
	 *
	 * @var    \Symfony\Component\Yaml\Dumper
	 * @since  1.0
	 */
	private $dumper;

	/**
	 * Construct to set up the parser and dumper
	 *
	 * @since   1.0
	 */
	public function __construct()
	{
		$this->parser = new SymfonyYamlParser;
		$this->dumper = new SymfonyYamlDumper;
	}

	/**
	 * Converts an object into a YAML formatted string.
	 * We use json_* to convert the passed object to an array.
	 *
	 * @param   object  $object   Data source object.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  string  YAML formatted string.
	 *
	 * @since   1.0
	 */
	public function objectToString($object, $options = array())
	{
		$array = json_decode(json_encode($object), true);

		return $this->dumper->dump($array, 2, 0);
	}

	/**
	 * Parse a YAML formatted string and convert it into an object.
	 * We use the json_* methods to convert the parsed YAML array to an object.
	 *
	 * @param   string  $data     YAML formatted string to convert.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  object  Data object.
	 *
	 * @since   1.0
	 */
	public function stringToObject($data, array $options = array())
	{
		$array = $this->parser->parse(trim($data));

		return (object) json_decode(json_encode($array));
	}
}
vendor/joomla/registry/src/Format/Json.php000064400000004172152177723700014652 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry\Format;

use Joomla\Registry\AbstractRegistryFormat;

/**
 * JSON format handler for Registry.
 *
 * @since  1.0
 */
class Json extends AbstractRegistryFormat
{
	/**
	 * Converts an object into a JSON formatted string.
	 *
	 * @param   object  $object   Data source object.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  string  JSON formatted string.
	 *
	 * @since   1.0
	 */
	public function objectToString($object, $options = array())
	{
		$bitMask = isset($options['bitmask']) ? $options['bitmask'] : 0;

		// The depth parameter is only present as of PHP 5.5
		if (version_compare(PHP_VERSION, '5.5', '>='))
		{
			$depth = isset($options['depth']) ? $options['depth'] : 512;

			return json_encode($object, $bitMask, $depth);
		}

		return json_encode($object, $bitMask);
	}

	/**
	 * Parse a JSON formatted string and convert it into an object.
	 *
	 * If the string is not in JSON format, this method will attempt to parse it as INI format.
	 *
	 * @param   string  $data     JSON formatted string to convert.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  object   Data object.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function stringToObject($data, array $options = array('processSections' => false))
	{
		$data = trim($data);

		// Because developers are clearly not validating their data before pushing it into a Registry, we'll do it for them
		if (empty($data))
		{
			return new \stdClass;
		}

		if ($data !== '' && $data[0] !== '{')
		{
			return AbstractRegistryFormat::getInstance('Ini')->stringToObject($data, $options);
		}

		$decoded = json_decode($data);

		// Check for an error decoding the data
		if ($decoded === null && json_last_error() !== JSON_ERROR_NONE)
		{
			throw new \RuntimeException(sprintf('Error decoding JSON data: %s', json_last_error_msg()));
		}

		return (object) $decoded;
	}
}
vendor/joomla/registry/src/Format/Xml.php000064400000007207152177723700014503 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry\Format;

use Joomla\Registry\AbstractRegistryFormat;
use SimpleXMLElement;
use stdClass;

/**
 * XML format handler for Registry.
 *
 * @since  1.0
 */
class Xml extends AbstractRegistryFormat
{
	/**
	 * Converts an object into an XML formatted string.
	 * -	If more than two levels of nested groups are necessary, since INI is not
	 * useful, XML or another format should be used.
	 *
	 * @param   object  $object   Data source object.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  string  XML formatted string.
	 *
	 * @since   1.0
	 */
	public function objectToString($object, $options = array())
	{
		$rootName = isset($options['name']) ? $options['name'] : 'registry';
		$nodeName = isset($options['nodeName']) ? $options['nodeName'] : 'node';

		// Create the root node.
		$root = simplexml_load_string('<' . $rootName . ' />');

		// Iterate over the object members.
		$this->getXmlChildren($root, $object, $nodeName);

		return $root->asXML();
	}

	/**
	 * Parse a XML formatted string and convert it into an object.
	 *
	 * @param   string  $data     XML formatted string to convert.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  object   Data object.
	 *
	 * @since   1.0
	 */
	public function stringToObject($data, array $options = array())
	{
		$obj = new stdClass;

		// Parse the XML string.
		$xml = simplexml_load_string($data);

		foreach ($xml->children() as $node)
		{
			$obj->{$node['name']} = $this->getValueFromNode($node);
		}

		return $obj;
	}

	/**
	 * Method to get a PHP native value for a SimpleXMLElement object. -- called recursively
	 *
	 * @param   object  $node  SimpleXMLElement object for which to get the native value.
	 *
	 * @return  mixed  Native value of the SimpleXMLElement object.
	 *
	 * @since   1.0
	 */
	protected function getValueFromNode($node)
	{
		switch ($node['type'])
		{
			case 'integer':
				$value = (string) $node;

				return (int) $value;
				break;

			case 'string':
				return (string) $node;
				break;

			case 'boolean':
				$value = (string) $node;

				return (bool) $value;
				break;

			case 'double':
				$value = (string) $node;

				return (float) $value;
				break;

			case 'array':
				$value = array();

				foreach ($node->children() as $child)
				{
					$value[(string) $child['name']] = $this->getValueFromNode($child);
				}

				break;

			default:
				$value = new stdClass;

				foreach ($node->children() as $child)
				{
					$value->{$child['name']} = $this->getValueFromNode($child);
				}

				break;
		}

		return $value;
	}

	/**
	 * Method to build a level of the XML string -- called recursively
	 *
	 * @param   SimpleXMLElement  $node      SimpleXMLElement object to attach children.
	 * @param   object            $var       Object that represents a node of the XML document.
	 * @param   string            $nodeName  The name to use for node elements.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function getXmlChildren(SimpleXMLElement $node, $var, $nodeName)
	{
		// Iterate over the object members.
		foreach ((array) $var as $k => $v)
		{
			if (is_scalar($v))
			{
				$n = $node->addChild($nodeName, $v);
				$n->addAttribute('name', $k);
				$n->addAttribute('type', gettype($v));
			}
			else
			{
				$n = $node->addChild($nodeName);
				$n->addAttribute('name', $k);
				$n->addAttribute('type', gettype($v));

				$this->getXmlChildren($n, $v, $nodeName);
			}
		}
	}
}
vendor/joomla/registry/src/Format/Ini.php000064400000017721152177723700014464 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry\Format;

use Joomla\Registry\AbstractRegistryFormat;
use Joomla\Utilities\ArrayHelper;
use stdClass;

/**
 * INI format handler for Registry.
 *
 * @since  1.0
 */
class Ini extends AbstractRegistryFormat
{
	/**
	 * Default options array
	 *
	 * @var    array
	 * @since  1.3.0
	 */
	protected static $options = array(
		'supportArrayValues' => false,
		'parseBooleanWords'  => false,
		'processSections'    => false,
	);

	/**
	 * A cache used by stringToObject.
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected static $cache = array();

	/**
	 * Converts an object into an INI formatted string
	 * - Unfortunately, there is no way to have ini values nested further than two
	 * levels deep.  Therefore we will only go through the first two levels of
	 * the object.
	 *
	 * @param   object  $object   Data source object.
	 * @param   array   $options  Options used by the formatter.
	 *
	 * @return  string  INI formatted string.
	 *
	 * @since   1.0
	 */
	public function objectToString($object, $options = array())
	{
		$options            = array_merge(self::$options, $options);
		$supportArrayValues = $options['supportArrayValues'];

		$local  = array();
		$global = array();

		$variables = get_object_vars($object);

		$last = count($variables);

		// Assume that the first element is in section
		$inSection = true;

		// Iterate over the object to set the properties.
		foreach ($variables as $key => $value)
		{
			// If the value is an object then we need to put it in a local section.
			if (is_object($value))
			{
				// Add an empty line if previous string wasn't in a section
				if (!$inSection)
				{
					$local[] = '';
				}

				// Add the section line.
				$local[] = '[' . $key . ']';

				// Add the properties for this section.
				foreach (get_object_vars($value) as $k => $v)
				{
					if (is_array($v) && $supportArrayValues)
					{
						$assoc = ArrayHelper::isAssociative($v);

						foreach ($v as $arrayKey => $item)
						{
							$arrayKey = $assoc ? $arrayKey : '';
							$local[]  = $k . '[' . $arrayKey . ']=' . $this->getValueAsIni($item);
						}
					}
					else
					{
						$local[] = $k . '=' . $this->getValueAsIni($v);
					}
				}

				// Add empty line after section if it is not the last one
				if (0 !== --$last)
				{
					$local[] = '';
				}
			}
			elseif (is_array($value) && $supportArrayValues)
			{
				$assoc = ArrayHelper::isAssociative($value);

				foreach ($value as $arrayKey => $item)
				{
					$arrayKey = $assoc ? $arrayKey : '';
					$global[] = $key . '[' . $arrayKey . ']=' . $this->getValueAsIni($item);
				}
			}
			else
			{
				// Not in a section so add the property to the global array.
				$global[]  = $key . '=' . $this->getValueAsIni($value);
				$inSection = false;
			}
		}

		return implode("\n", array_merge($global, $local));
	}

	/**
	 * Parse an INI formatted string and convert it into an object.
	 *
	 * @param   string  $data     INI formatted string to convert.
	 * @param   array   $options  An array of options used by the formatter, or a boolean setting to process sections.
	 *
	 * @return  object   Data object.
	 *
	 * @since   1.0
	 */
	public function stringToObject($data, array $options = array())
	{
		$options = array_merge(self::$options, $options);

		// Check the memory cache for already processed strings.
		$hash = md5($data . ':' . (int) $options['processSections']);

		if (isset(self::$cache[$hash]))
		{
			return self::$cache[$hash];
		}

		// If no lines present just return the object.
		if (empty($data))
		{
			return new stdClass;
		}

		$obj     = new stdClass;
		$section = false;
		$array   = false;
		$lines   = explode("\n", $data);

		// Process the lines.
		foreach ($lines as $line)
		{
			// Trim any unnecessary whitespace.
			$line = trim($line);

			// Ignore empty lines and comments.
			if (empty($line) || ($line[0] === ';'))
			{
				continue;
			}

			if ($options['processSections'])
			{
				$length = strlen($line);

				// If we are processing sections and the line is a section add the object and continue.
				if ($line[0] === '[' && ($line[$length - 1] === ']'))
				{
					$section       = substr($line, 1, $length - 2);
					$obj->$section = new stdClass;
					continue;
				}
			}
			elseif ($line[0] === '[')
			{
				continue;
			}

			// Check that an equal sign exists and is not the first character of the line.
			if (!strpos($line, '='))
			{
				// Maybe throw exception?
				continue;
			}

			// Get the key and value for the line.
			list ($key, $value) = explode('=', $line, 2);

			// If we have an array item
			if (substr($key, -1) === ']' && ($openBrace = strpos($key, '[', 1)) !== false)
			{
				if ($options['supportArrayValues'])
				{
					$array    = true;
					$arrayKey = substr($key, $openBrace + 1, -1);

					// If we have a multi-dimensional array or malformed key
					if (strpos($arrayKey, '[') !== false || strpos($arrayKey, ']') !== false)
					{
						// Maybe throw exception?
						continue;
					}

					$key = substr($key, 0, $openBrace);
				}
				else
				{
					continue;
				}
			}

			// Validate the key.
			if (preg_match('/[^A-Z0-9_]/i', $key))
			{
				// Maybe throw exception?
				continue;
			}

			// If the value is quoted then we assume it is a string.
			$length = strlen($value);

			if ($length && ($value[0] === '"') && ($value[$length - 1] === '"'))
			{
				// Strip the quotes and Convert the new line characters.
				$value = stripcslashes(substr($value, 1, $length - 2));
				$value = str_replace('\n', "\n", $value);
			}
			else
			{
				// If the value is not quoted, we assume it is not a string.

				// If the value is 'false' assume boolean false.
				if ($value === 'false')
				{
					$value = false;
				}
				elseif ($value === 'true')
					// If the value is 'true' assume boolean true.
				{
					$value = true;
				}
				elseif ($options['parseBooleanWords'] && in_array(strtolower($value), array('yes', 'no'), true))
					// If the value is 'yes' or 'no' and option is enabled assume appropriate boolean
				{
					$value = (strtolower($value) === 'yes');
				}
				elseif (is_numeric($value))
					// If the value is numeric than it is either a float or int.
				{
					// If there is a period then we assume a float.
					if (strpos($value, '.') !== false)
					{
						$value = (float) $value;
					}
					else
					{
						$value = (int) $value;
					}
				}
			}

			// If a section is set add the key/value to the section, otherwise top level.
			if ($section)
			{
				if ($array)
				{
					if (!isset($obj->$section->$key))
					{
						$obj->$section->$key = array();
					}

					if (!empty($arrayKey))
					{
						$obj->$section->{$key}[$arrayKey] = $value;
					}
					else
					{
						$obj->$section->{$key}[] = $value;
					}
				}
				else
				{
					$obj->$section->$key = $value;
				}
			}
			else
			{
				if ($array)
				{
					if (!isset($obj->$key))
					{
						$obj->$key = array();
					}

					if (!empty($arrayKey))
					{
						$obj->{$key}[$arrayKey] = $value;
					}
					else
					{
						$obj->{$key}[] = $value;
					}
				}
				else
				{
					$obj->$key = $value;
				}
			}

			$array = false;
		}

		// Cache the string to save cpu cycles -- thus the world :)
		self::$cache[$hash] = clone $obj;

		return $obj;
	}

	/**
	 * Method to get a value in an INI format.
	 *
	 * @param   mixed  $value  The value to convert to INI format.
	 *
	 * @return  string  The value in INI format.
	 *
	 * @since   1.0
	 */
	protected function getValueAsIni($value)
	{
		$string = '';

		switch (gettype($value))
		{
			case 'integer':
			case 'double':
				$string = $value;
				break;

			case 'boolean':
				$string = $value ? 'true' : 'false';
				break;

			case 'string':
				// Sanitize any CRLF characters..
				$string = '"' . str_replace(array("\r\n", "\n"), '\\n', $value) . '"';
				break;
		}

		return $string;
	}
}
vendor/joomla/registry/src/AbstractRegistryFormat.php000064400000002262152177723700017154 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry;

/**
 * Abstract Format for Registry
 *
 * @since       1.0
 * @deprecated  2.0  Format objects should directly implement the FormatInterface
 */
abstract class AbstractRegistryFormat implements FormatInterface
{
	/**
	 * @var    AbstractRegistryFormat[]  Format instances container.
	 * @since  1.0
	 * @deprecated  2.0  Object caching will no longer be supported
	 */
	protected static $instances = array();

	/**
	 * Returns a reference to a Format object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $type     The format to load
	 * @param   array   $options  Additional options to configure the object
	 *
	 * @return  AbstractRegistryFormat  Registry format handler
	 *
	 * @deprecated  2.0  Use Factory::getFormat() instead
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public static function getInstance($type, array $options = array())
	{
		return Factory::getFormat($type, $options);
	}
}
vendor/joomla/registry/src/Registry.php000064400000045313152177723700014323 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry;

use Joomla\Utilities\ArrayHelper;

/**
 * Registry class
 *
 * @since  1.0
 */
class Registry implements \JsonSerializable, \ArrayAccess, \IteratorAggregate, \Countable
{
	/**
	 * Registry Object
	 *
	 * @var    \stdClass
	 * @since  1.0
	 */
	protected $data;

	/**
	 * Flag if the Registry data object has been initialized
	 *
	 * @var    boolean
	 * @since  1.5.2
	 */
	protected $initialized = false;

	/**
	 * Registry instances container.
	 *
	 * @var    Registry[]
	 * @since  1.0
	 * @deprecated  2.0  Object caching will no longer be supported
	 */
	protected static $instances = array();

	/**
	 * Path separator
	 *
	 * @var    string
	 * @since  1.4.0
	 */
	public $separator = '.';

	/**
	 * Constructor
	 *
	 * @param   mixed  $data  The data to bind to the new Registry object.
	 *
	 * @since   1.0
	 */
	public function __construct($data = null)
	{
		// Instantiate the internal data object.
		$this->data = new \stdClass;

		// Optionally load supplied data.
		if ($data instanceof Registry)
		{
			$this->merge($data);
		}
		elseif (is_array($data) || is_object($data))
		{
			$this->bindData($this->data, $data);
		}
		elseif (!empty($data) && is_string($data))
		{
			$this->loadString($data);
		}
	}

	/**
	 * Magic function to clone the registry object.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function __clone()
	{
		$this->data = unserialize(serialize($this->data));
	}

	/**
	 * Magic function to render this object as a string using default args of toString method.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function __toString()
	{
		return $this->toString();
	}

	/**
	 * Count elements of the data object
	 *
	 * @return  integer  The custom count as an integer.
	 *
	 * @link    https://secure.php.net/manual/en/countable.count.php
	 * @since   1.3.0
	 */
	public function count()
	{
		return count(get_object_vars($this->data));
	}

	/**
	 * Implementation for the JsonSerializable interface.
	 * Allows us to pass Registry objects to json_encode.
	 *
	 * @return  object
	 *
	 * @since   1.0
	 * @note    The interface is only present in PHP 5.4 and up.
	 */
	public function jsonSerialize()
	{
		return $this->data;
	}

	/**
	 * Sets a default value if not already assigned.
	 *
	 * @param   string  $key      The name of the parameter.
	 * @param   mixed   $default  An optional value for the parameter.
	 *
	 * @return  mixed  The value set, or the default if the value was not previously set (or null).
	 *
	 * @since   1.0
	 */
	public function def($key, $default = '')
	{
		$value = $this->get($key, $default);
		$this->set($key, $value);

		return $value;
	}

	/**
	 * Check if a registry path exists.
	 *
	 * @param   string  $path  Registry path (e.g. joomla.content.showauthor)
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function exists($path)
	{
		// Return default value if path is empty
		if (empty($path))
		{
			return false;
		}

		// Explode the registry path into an array
		$nodes = explode($this->separator, $path);

		// Initialize the current node to be the registry root.
		$node  = $this->data;
		$found = false;

		// Traverse the registry to find the correct node for the result.
		foreach ($nodes as $n)
		{
			if (is_array($node) && isset($node[$n]))
			{
				$node  = $node[$n];
				$found = true;
				continue;
			}

			if (!isset($node->$n))
			{
				return false;
			}

			$node  = $node->$n;
			$found = true;
		}

		return $found;
	}

	/**
	 * Get a registry value.
	 *
	 * @param   string  $path     Registry path (e.g. joomla.content.showauthor)
	 * @param   mixed   $default  Optional default value, returned if the internal value is null.
	 *
	 * @return  mixed  Value of entry or null
	 *
	 * @since   1.0
	 */
	public function get($path, $default = null)
	{
		// Return default value if path is empty
		if (empty($path))
		{
			return $default;
		}

		if (!strpos($path, $this->separator))
		{
			return (isset($this->data->$path) && $this->data->$path !== null && $this->data->$path !== '') ? $this->data->$path : $default;
		}

		// Explode the registry path into an array
		$nodes = explode($this->separator, trim($path));

		// Initialize the current node to be the registry root.
		$node  = $this->data;
		$found = false;

		// Traverse the registry to find the correct node for the result.
		foreach ($nodes as $n)
		{
			if (is_array($node) && isset($node[$n]))
			{
				$node  = $node[$n];
				$found = true;

				continue;
			}

			if (!isset($node->$n))
			{
				return $default;
			}

			$node  = $node->$n;
			$found = true;
		}

		if (!$found || $node === null || $node === '')
		{
			return $default;
		}

		return $node;
	}

	/**
	 * Returns a reference to a global Registry object, only creating it
	 * if it doesn't already exist.
	 *
	 * This method must be invoked as:
	 * <pre>$registry = Registry::getInstance($id);</pre>
	 *
	 * @param   string  $id  An ID for the registry instance
	 *
	 * @return  Registry  The Registry object.
	 *
	 * @since   1.0
	 * @deprecated  2.0  Instantiate a new Registry instance instead
	 */
	public static function getInstance($id)
	{
		if (empty(self::$instances[$id]))
		{
			self::$instances[$id] = new self;
		}

		return self::$instances[$id];
	}

	/**
	 * Gets this object represented as an ArrayIterator.
	 *
	 * This allows the data properties to be accessed via a foreach statement.
	 *
	 * @return  \ArrayIterator  This object represented as an ArrayIterator.
	 *
	 * @see     IteratorAggregate::getIterator()
	 * @since   1.3.0
	 */
	public function getIterator()
	{
		return new \ArrayIterator($this->data);
	}

	/**
	 * Load an associative array of values into the default namespace
	 *
	 * @param   array    $array      Associative array of value to load
	 * @param   boolean  $flattened  Load from a one-dimensional array
	 * @param   string   $separator  The key separator
	 *
	 * @return  Registry  Return this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function loadArray($array, $flattened = false, $separator = null)
	{
		if (!$flattened)
		{
			$this->bindData($this->data, $array);

			return $this;
		}

		foreach ($array as $k => $v)
		{
			$this->set($k, $v, $separator);
		}

		return $this;
	}

	/**
	 * Load the public variables of the object into the default namespace.
	 *
	 * @param   object  $object  The object holding the publics to load
	 *
	 * @return  Registry  Return this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function loadObject($object)
	{
		$this->bindData($this->data, $object);

		return $this;
	}

	/**
	 * Load the contents of a file into the registry
	 *
	 * @param   string  $file     Path to file to load
	 * @param   string  $format   Format of the file [optional: defaults to JSON]
	 * @param   array   $options  Options used by the formatter
	 *
	 * @return  Registry  Return this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function loadFile($file, $format = 'JSON', $options = array())
	{
		$data = file_get_contents($file);

		return $this->loadString($data, $format, $options);
	}

	/**
	 * Load a string into the registry
	 *
	 * @param   string  $data     String to load into the registry
	 * @param   string  $format   Format of the string
	 * @param   array   $options  Options used by the formatter
	 *
	 * @return  Registry  Return this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function loadString($data, $format = 'JSON', $options = array())
	{
		// Load a string into the given namespace [or default namespace if not given]
		$handler = AbstractRegistryFormat::getInstance($format, $options);

		$obj = $handler->stringToObject($data, $options);

		// If the data object has not yet been initialized, direct assign the object
		if (!$this->initialized)
		{
			$this->data        = $obj;
			$this->initialized = true;

			return $this;
		}

		$this->loadObject($obj);

		return $this;
	}

	/**
	 * Merge a Registry object into this one
	 *
	 * @param   Registry  $source     Source Registry object to merge.
	 * @param   boolean   $recursive  True to support recursive merge the children values.
	 *
	 * @return  Registry|false  Return this object to support chaining or false if $source is not an instance of Registry.
	 *
	 * @since   1.0
	 */
	public function merge($source, $recursive = false)
	{
		if (!$source instanceof Registry)
		{
			return false;
		}

		$this->bindData($this->data, $source->toArray(), $recursive, false);

		return $this;
	}

	/**
	 * Method to extract a sub-registry from path
	 *
	 * @param   string  $path  Registry path (e.g. joomla.content.showauthor)
	 *
	 * @return  Registry|null  Registry object if data is present
	 *
	 * @since   1.2.0
	 */
	public function extract($path)
	{
		$data = $this->get($path);

		if ($data === null)
		{
			return null;
		}

		return new Registry($data);
	}

	/**
	 * Checks whether an offset exists in the iterator.
	 *
	 * @param   mixed  $offset  The array offset.
	 *
	 * @return  boolean  True if the offset exists, false otherwise.
	 *
	 * @since   1.0
	 */
	public function offsetExists($offset)
	{
		return (boolean) ($this->get($offset) !== null);
	}

	/**
	 * Gets an offset in the iterator.
	 *
	 * @param   mixed  $offset  The array offset.
	 *
	 * @return  mixed  The array value if it exists, null otherwise.
	 *
	 * @since   1.0
	 */
	public function offsetGet($offset)
	{
		return $this->get($offset);
	}

	/**
	 * Sets an offset in the iterator.
	 *
	 * @param   mixed  $offset  The array offset.
	 * @param   mixed  $value   The array value.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function offsetSet($offset, $value)
	{
		$this->set($offset, $value);
	}

	/**
	 * Unsets an offset in the iterator.
	 *
	 * @param   mixed  $offset  The array offset.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function offsetUnset($offset)
	{
		$this->remove($offset);
	}

	/**
	 * Set a registry value.
	 *
	 * @param   string  $path       Registry Path (e.g. joomla.content.showauthor)
	 * @param   mixed   $value      Value of entry
	 * @param   string  $separator  The key separator
	 *
	 * @return  mixed  The value of the that has been set.
	 *
	 * @since   1.0
	 */
	public function set($path, $value, $separator = null)
	{
		if (empty($separator))
		{
			$separator = $this->separator;
		}

		/*
		 * Explode the registry path into an array and remove empty
		 * nodes that occur as a result of a double separator. ex: joomla..test
		 * Finally, re-key the array so they are sequential.
		 */
		$nodes = array_values(array_filter(explode($separator, $path), 'strlen'));

		if (!$nodes)
		{
			return null;
		}

		// Initialize the current node to be the registry root.
		$node = $this->data;

		// Traverse the registry to find the correct node for the result.
		for ($i = 0, $n = count($nodes) - 1; $i < $n; $i++)
		{
			if (is_object($node))
			{
				if (!isset($node->{$nodes[$i]}) && ($i !== $n))
				{
					$node->{$nodes[$i]} = new \stdClass;
				}

				// Pass the child as pointer in case it is an object
				$node = &$node->{$nodes[$i]};

				continue;
			}

			if (is_array($node))
			{
				if (($i !== $n) && !isset($node[$nodes[$i]]))
				{
					$node[$nodes[$i]] = new \stdClass;
				}

				// Pass the child as pointer in case it is an array
				$node = &$node[$nodes[$i]];
			}
		}

		// Get the old value if exists so we can return it
		switch (true)
		{
			case (is_object($node)):
				$result = $node->{$nodes[$i]} = $value;
				break;

			case (is_array($node)):
				$result = $node[$nodes[$i]] = $value;
				break;

			default:
				$result = null;
				break;
		}

		return $result;
	}

	/**
	 * Append value to a path in registry
	 *
	 * @param   string  $path   Parent registry Path (e.g. joomla.content.showauthor)
	 * @param   mixed   $value  Value of entry
	 *
	 * @return  mixed  The value of the that has been set.
	 *
	 * @since   1.4.0
	 */
	public function append($path, $value)
	{
		$result = null;

		/*
		 * Explode the registry path into an array and remove empty
		 * nodes that occur as a result of a double dot. ex: joomla..test
		 * Finally, re-key the array so they are sequential.
		 */
		$nodes = array_values(array_filter(explode('.', $path), 'strlen'));

		if ($nodes)
		{
			// Initialize the current node to be the registry root.
			$node = $this->data;

			// Traverse the registry to find the correct node for the result.
			// TODO Create a new private method from part of code below, as it is almost equal to 'set' method
			for ($i = 0, $n = count($nodes) - 1; $i <= $n; $i++)
			{
				if (is_object($node))
				{
					if (!isset($node->{$nodes[$i]}) && ($i !== $n))
					{
						$node->{$nodes[$i]} = new \stdClass;
					}

					// Pass the child as pointer in case it is an array
					$node = &$node->{$nodes[$i]};
				}
				elseif (is_array($node))
				{
					if (($i !== $n) && !isset($node[$nodes[$i]]))
					{
						$node[$nodes[$i]] = new \stdClass;
					}

					// Pass the child as pointer in case it is an array
					$node = &$node[$nodes[$i]];
				}
			}

			if (!is_array($node))
				// Convert the node to array to make append possible
			{
				$node = get_object_vars($node);
			}

			$node[] = $value;
			$result = $value;
		}

		return $result;
	}

	/**
	 * Delete a registry value
	 *
	 * @param   string  $path  Registry Path (e.g. joomla.content.showauthor)
	 *
	 * @return  mixed  The value of the removed node or null if not set
	 *
	 * @since   1.6.0
	 */
	public function remove($path)
	{
		// Cheap optimisation to direct remove the node if there is no separator
		if (!strpos($path, $this->separator))
		{
			$result = (isset($this->data->$path) && $this->data->$path !== null && $this->data->$path !== '') ? $this->data->$path : null;

			unset($this->data->$path);

			return $result;
		}

		/*
		 * Explode the registry path into an array and remove empty
		 * nodes that occur as a result of a double separator. ex: joomla..test
		 * Finally, re-key the array so they are sequential.
		 */
		$nodes = array_values(array_filter(explode($this->separator, $path), 'strlen'));

		if (!$nodes)
		{
			return null;
		}

		// Initialize the current node to be the registry root.
		$node   = $this->data;
		$parent = null;

		// Traverse the registry to find the correct node for the result.
		for ($i = 0, $n = count($nodes) - 1; $i < $n; $i++)
		{
			if (is_object($node))
			{
				if (!isset($node->{$nodes[$i]}) && ($i !== $n))
				{
					continue;
				}

				$parent = &$node;
				$node   = $node->{$nodes[$i]};

				continue;
			}

			if (is_array($node))
			{
				if (($i !== $n) && !isset($node[$nodes[$i]]))
				{
					continue;
				}

				$parent = &$node;
				$node   = $node[$nodes[$i]];

				continue;
			}
		}

		// Get the old value if exists so we can return it
		switch (true)
		{
			case (is_object($node)):
				$result = isset($node->{$nodes[$i]}) ? $node->{$nodes[$i]} : null;
				unset($parent->{$nodes[$i]});
				break;

			case (is_array($node)):
				$result = isset($node[$nodes[$i]]) ? $node[$nodes[$i]] : null;
				unset($parent[$nodes[$i]]);
				break;

			default:
				$result = null;
				break;
		}

		return $result;
	}

	/**
	 * Transforms a namespace to an array
	 *
	 * @return  array  An associative array holding the namespace data
	 *
	 * @since   1.0
	 */
	public function toArray()
	{
		return (array) $this->asArray($this->data);
	}

	/**
	 * Transforms a namespace to an object
	 *
	 * @return  object   An an object holding the namespace data
	 *
	 * @since   1.0
	 */
	public function toObject()
	{
		return $this->data;
	}

	/**
	 * Get a namespace in a given string format
	 *
	 * @param   string  $format   Format to return the string in
	 * @param   mixed   $options  Parameters used by the formatter, see formatters for more info
	 *
	 * @return  string   Namespace in string format
	 *
	 * @since   1.0
	 */
	public function toString($format = 'JSON', $options = array())
	{
		// Return a namespace in a given format
		$handler = AbstractRegistryFormat::getInstance($format, $options);

		return $handler->objectToString($this->data, $options);
	}

	/**
	 * Method to recursively bind data to a parent object.
	 *
	 * @param   object   $parent     The parent object on which to attach the data values.
	 * @param   mixed    $data       An array or object of data to bind to the parent object.
	 * @param   boolean  $recursive  True to support recursive bindData.
	 * @param   boolean  $allowNull  True to allow null values.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function bindData($parent, $data, $recursive = true, $allowNull = true)
	{
		// The data object is now initialized
		$this->initialized = true;

		// Ensure the input data is an array.
		$data = is_object($data) ? get_object_vars($data) : (array) $data;

		foreach ($data as $k => $v)
		{
			if (!$allowNull && !(($v !== null) && ($v !== '')))
			{
				continue;
			}

			if ($recursive && ((is_array($v) && ArrayHelper::isAssociative($v)) || is_object($v)))
			{
				if (!isset($parent->$k))
				{
					$parent->$k = new \stdClass;
				}

				$this->bindData($parent->$k, $v);

				continue;
			}

			$parent->$k = $v;
		}
	}

	/**
	 * Method to recursively convert an object of data to an array.
	 *
	 * @param   object  $data  An object of data to return as an array.
	 *
	 * @return  array  Array representation of the input object.
	 *
	 * @since   1.0
	 */
	protected function asArray($data)
	{
		$array = array();

		if (is_object($data))
		{
			$data = get_object_vars($data);
		}

		foreach ($data as $k => $v)
		{
			if (is_object($v) || is_array($v))
			{
				$array[$k] = $this->asArray($v);

				continue;
			}

			$array[$k] = $v;
		}

		return $array;
	}

	/**
	 * Dump to one dimension array.
	 *
	 * @param   string  $separator  The key separator.
	 *
	 * @return  string[]  Dumped array.
	 *
	 * @since   1.3.0
	 */
	public function flatten($separator = null)
	{
		$array = array();

		if (empty($separator))
		{
			$separator = $this->separator;
		}

		$this->toFlatten($separator, $this->data, $array);

		return $array;
	}

	/**
	 * Method to recursively convert data to one dimension array.
	 *
	 * @param   string        $separator  The key separator.
	 * @param   array|object  $data       Data source of this scope.
	 * @param   array         $array      The result array, it is passed by reference.
	 * @param   string        $prefix     Last level key prefix.
	 *
	 * @return  void
	 *
	 * @since   1.3.0
	 */
	protected function toFlatten($separator = null, $data = null, &$array = array(), $prefix = '')
	{
		$data = (array) $data;

		if (empty($separator))
		{
			$separator = $this->separator;
		}

		foreach ($data as $k => $v)
		{
			$key = $prefix ? $prefix . $separator . $k : $k;

			if (is_object($v) || is_array($v))
			{
				$this->toFlatten($separator, $v, $array, $key);

				continue;
			}

			$array[$key] = $v;
		}
	}
}
vendor/joomla/registry/src/FormatInterface.php000064400000001710152177723700015555 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry;

/**
 * Interface defining a format object
 *
 * @since  1.5.0
 */
interface FormatInterface
{
	/**
	 * Converts an object into a formatted string.
	 *
	 * @param   object  $object   Data Source Object.
	 * @param   array   $options  An array of options for the formatter.
	 *
	 * @return  string  Formatted string.
	 *
	 * @since   1.5.0
	 */
	public function objectToString($object, $options = null);

	/**
	 * Converts a formatted string into an object.
	 *
	 * @param   string  $data     Formatted string
	 * @param   array   $options  An array of options for the formatter.
	 *
	 * @return  object  Data Object
	 *
	 * @since   1.5.0
	 */
	public function stringToObject($data, array $options = array());
}
vendor/joomla/registry/src/Factory.php000064400000004166152177723700014123 0ustar00<?php
/**
 * Part of the Joomla Framework Registry Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Registry;

/**
 * Factory class to fetch Registry objects
 *
 * @since  1.5.0
 */
class Factory
{
	/**
	 * Format instances container - for backward compatibility with AbstractRegistryFormat::getInstance().
	 *
	 * @var    FormatInterface[]
	 * @since  1.5.0
	 * @deprecated  2.0  Object caching will no longer be supported
	 */
	protected static $formatInstances = array();

	/**
	 * Returns an AbstractRegistryFormat object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $type     The format to load
	 * @param   array   $options  Additional options to configure the object
	 *
	 * @return  FormatInterface  Registry format handler
	 *
	 * @since   1.5.0
	 * @throws  \InvalidArgumentException
	 */
	public static function getFormat($type, array $options = array())
	{
		// Sanitize format type.
		$type = strtolower(preg_replace('/[^A-Z0-9_]/i', '', $type));

		/*
		 * Only instantiate the object if it doesn't already exist.
		 * @deprecated 2.0 Object caching will no longer be supported, a new instance will be returned every time
		 */
		if (!isset(self::$formatInstances[$type]))
		{
			$localNamespace = __NAMESPACE__ . '\\Format';
			$namespace      = isset($options['format_namespace']) ? $options['format_namespace'] : $localNamespace;
			$class          = $namespace . '\\' . ucfirst($type);

			if (!class_exists($class))
			{
				// Were we given a custom namespace?  If not, there's nothing else we can do
				if ($namespace === $localNamespace)
				{
					throw new \InvalidArgumentException(sprintf('Unable to load format class for type "%s".', $type), 500);
				}

				$class = $localNamespace . '\\' . ucfirst($type);

				if (!class_exists($class))
				{
					throw new \InvalidArgumentException(sprintf('Unable to load format class for type "%s".', $type), 500);
				}
			}

			self::$formatInstances[$type] = new $class;
		}

		return self::$formatInstances[$type];
	}
}
vendor/joomla/utilities/LICENSE000064400000042630152177723700012362 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/utilities/src/ArrayHelper.php000064400000041675152177723700015103 0ustar00<?php
/**
 * Part of the Joomla Framework Utilities Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Utilities;

use Joomla\String\StringHelper;

/**
 * ArrayHelper is an array utility class for doing all sorts of odds and ends with arrays.
 *
 * @since  1.0
 */
final class ArrayHelper
{
	/**
	 * Private constructor to prevent instantiation of this class
	 *
	 * @since   1.0
	 */
	private function __construct()
	{
	}

	/**
	 * Function to convert array to integer values
	 *
	 * @param   array      $array    The source array to convert
	 * @param   int|array  $default  A default value to assign if $array is not an array
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function toInteger($array, $default = null)
	{
		if (\is_array($array))
		{
			return array_map('intval', $array);
		}

		if ($default === null)
		{
			return array();
		}

		if (\is_array($default))
		{
			return static::toInteger($default, null);
		}

		return array((int) $default);
	}

	/**
	 * Utility function to map an array to a stdClass object.
	 *
	 * @param   array    $array      The array to map.
	 * @param   string   $class      Name of the class to create
	 * @param   boolean  $recursive  Convert also any array inside the main array
	 *
	 * @return  object
	 *
	 * @since   1.0
	 */
	public static function toObject(array $array, $class = 'stdClass', $recursive = true)
	{
		$obj = new $class;

		foreach ($array as $k => $v)
		{
			if ($recursive && \is_array($v))
			{
				$obj->$k = static::toObject($v, $class);
			}
			else
			{
				$obj->$k = $v;
			}
		}

		return $obj;
	}

	/**
	 * Utility function to map an array to a string.
	 *
	 * @param   array    $array         The array to map.
	 * @param   string   $innerGlue     The glue (optional, defaults to '=') between the key and the value.
	 * @param   string   $outerGlue     The glue (optional, defaults to ' ') between array elements.
	 * @param   boolean  $keepOuterKey  True if final key should be kept.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public static function toString(array $array, $innerGlue = '=', $outerGlue = ' ', $keepOuterKey = false)
	{
		$output = array();

		foreach ($array as $key => $item)
		{
			if (\is_array($item))
			{
				if ($keepOuterKey)
				{
					$output[] = $key;
				}

				// This is value is an array, go and do it again!
				$output[] = static::toString($item, $innerGlue, $outerGlue, $keepOuterKey);
			}
			else
			{
				$output[] = $key . $innerGlue . '"' . $item . '"';
			}
		}

		return implode($outerGlue, $output);
	}

	/**
	 * Utility function to map an object to an array
	 *
	 * @param   object   $source   The source object
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function fromObject($source, $recurse = true, $regex = null)
	{
		if (\is_object($source) || \is_array($source))
		{
			return self::arrayFromObject($source, $recurse, $regex);
		}

		return array();
	}

	/**
	 * Utility function to map an object or array to an array
	 *
	 * @param   mixed    $item     The source object or array
	 * @param   boolean  $recurse  True to recurse through multi-level objects
	 * @param   string   $regex    An optional regular expression to match on field names
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	private static function arrayFromObject($item, $recurse, $regex)
	{
		if (\is_object($item))
		{
			$result = array();

			foreach (get_object_vars($item) as $k => $v)
			{
				if (!$regex || preg_match($regex, $k))
				{
					if ($recurse)
					{
						$result[$k] = self::arrayFromObject($v, $recurse, $regex);
					}
					else
					{
						$result[$k] = $v;
					}
				}
			}

			return $result;
		}

		if (\is_array($item))
		{
			$result = array();

			foreach ($item as $k => $v)
			{
				$result[$k] = self::arrayFromObject($v, $recurse, $regex);
			}

			return $result;
		}

		return $item;
	}

	/**
	 * Adds a column to an array of arrays or objects
	 *
	 * @param   array   $array    The source array
	 * @param   array   $column   The array to be used as new column
	 * @param   string  $colName  The index of the new column or name of the new object property
	 * @param   string  $keyCol   The index of the column or name of object property to be used for mapping with the new column
	 *
	 * @return  array  An array with the new column added to the source array
	 *
	 * @since   1.5.0
	 * @see     https://secure.php.net/manual/en/language.types.array.php
	 */
	public static function addColumn(array $array, array $column, $colName, $keyCol = null)
	{
		$result = array();

		foreach ($array as $i => $item)
		{
			$value = null;

			if (!isset($keyCol))
			{
				$value = static::getValue($column, $i);
			}
			else
			{
				// Convert object to array
				$subject = \is_object($item) ? static::fromObject($item) : $item;

				if (isset($subject[$keyCol]) && is_scalar($subject[$keyCol]))
				{
					$value = static::getValue($column, $subject[$keyCol]);
				}
			}

			// Add the column
			if (\is_object($item))
			{
				if (isset($colName))
				{
					$item->$colName = $value;
				}
			}
			else
			{
				if (isset($colName))
				{
					$item[$colName] = $value;
				}
				else
				{
					$item[] = $value;
				}
			}

			$result[$i] = $item;
		}

		return $result;
	}

	/**
	 * Remove a column from an array of arrays or objects
	 *
	 * @param   array   $array    The source array
	 * @param   string  $colName  The index of the column or name of object property to be removed
	 *
	 * @return  array  Column of values from the source array
	 *
	 * @since   1.5.0
	 * @see     https://secure.php.net/manual/en/language.types.array.php
	 */
	public static function dropColumn(array $array, $colName)
	{
		$result = array();

		foreach ($array as $i => $item)
		{
			if (\is_object($item) && isset($item->$colName))
			{
				unset($item->$colName);
			}
			elseif (\is_array($item) && isset($item[$colName]))
			{
				unset($item[$colName]);
			}

			$result[$i] = $item;
		}

		return $result;
	}

	/**
	 * Extracts a column from an array of arrays or objects
	 *
	 * @param   array   $array     The source array
	 * @param   string  $valueCol  The index of the column or name of object property to be used as value
	 *                             It may also be NULL to return complete arrays or objects (this is
	 *                             useful together with <var>$keyCol</var> to reindex the array).
	 * @param   string  $keyCol    The index of the column or name of object property to be used as key
	 *
	 * @return  array  Column of values from the source array
	 *
	 * @since   1.0
	 * @see     https://secure.php.net/manual/en/language.types.array.php
	 * @see     https://secure.php.net/manual/en/function.array-column.php
	 */
	public static function getColumn(array $array, $valueCol, $keyCol = null)
	{
		// As of PHP 7, array_column() supports an array of objects so we'll use that
		if (PHP_VERSION_ID >= 70000)
		{
			return array_column($array, $valueCol, $keyCol);
		}

		$result = array();

		foreach ($array as $item)
		{
			// Convert object to array
			$subject = \is_object($item) ? static::fromObject($item) : $item;

			/*
			 * We process arrays (and objects already converted to array)
			 * Only if the value column (if required) exists in this item
			 */
			if (\is_array($subject) && (!isset($valueCol) || isset($subject[$valueCol])))
			{
				// Use whole $item if valueCol is null, else use the value column.
				$value = isset($valueCol) ? $subject[$valueCol] : $item;

				// Array keys can only be integer or string. Casting will occur as per the PHP Manual.
				if (isset($keyCol, $subject[$keyCol]) && is_scalar($subject[$keyCol]))
				{
					$key          = $subject[$keyCol];
					$result[$key] = $value;
				}
				else
				{
					$result[] = $value;
				}
			}
		}

		return $result;
	}

	/**
	 * Utility function to return a value from a named array or a specified default
	 *
	 * @param   array|\ArrayAccess  $array    A named array or object that implements ArrayAccess
	 * @param   string              $name     The key to search for (this can be an array index or a dot separated key sequence as in Registry)
	 * @param   mixed               $default  The default value to give if no key found
	 * @param   string              $type     Return type for the variable (INT, FLOAT, STRING, WORD, BOOLEAN, ARRAY)
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public static function getValue($array, $name, $default = null, $type = '')
	{
		if (!\is_array($array) && !($array instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException('The object must be an array or an object that implements ArrayAccess');
		}

		$result = null;

		if (isset($array[$name]))
		{
			$result = $array[$name];
		}
		elseif (strpos($name, '.'))
		{
			list($name, $subset) = explode('.', $name, 2);

			if (isset($array[$name]) && \is_array($array[$name]))
			{
				return static::getValue($array[$name], $subset, $default, $type);
			}
		}

		// Handle the default case
		if ($result === null)
		{
			$result = $default;
		}

		// Handle the type constraint
		switch (strtoupper($type))
		{
			case 'INT':
			case 'INTEGER':
				// Only use the first integer value
				@preg_match('/-?[0-9]+/', $result, $matches);
				$result = @(int) $matches[0];

				break;

			case 'FLOAT':
			case 'DOUBLE':
				// Only use the first floating point value
				@preg_match('/-?[0-9]+(\.[0-9]+)?/', $result, $matches);
				$result = @(float) $matches[0];

				break;

			case 'BOOL':
			case 'BOOLEAN':
				$result = (bool) $result;

				break;

			case 'ARRAY':
				if (!\is_array($result))
				{
					$result = array($result);
				}

				break;

			case 'STRING':
				$result = (string) $result;

				break;

			case 'WORD':
				$result = (string) preg_replace('#\W#', '', $result);

				break;

			case 'NONE':
			default:
				// No casting necessary
				break;
		}

		return $result;
	}

	/**
	 * Takes an associative array of arrays and inverts the array keys to values using the array values as keys.
	 *
	 * Example:
	 * $input = array(
	 *     'New' => array('1000', '1500', '1750'),
	 *     'Used' => array('3000', '4000', '5000', '6000')
	 * );
	 * $output = ArrayHelper::invert($input);
	 *
	 * Output would be equal to:
	 * $output = array(
	 *     '1000' => 'New',
	 *     '1500' => 'New',
	 *     '1750' => 'New',
	 *     '3000' => 'Used',
	 *     '4000' => 'Used',
	 *     '5000' => 'Used',
	 *     '6000' => 'Used'
	 * );
	 *
	 * @param   array  $array  The source array.
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function invert(array $array)
	{
		$return = array();

		foreach ($array as $base => $values)
		{
			if (!\is_array($values))
			{
				continue;
			}

			foreach ($values as $key)
			{
				// If the key isn't scalar then ignore it.
				if (is_scalar($key))
				{
					$return[$key] = $base;
				}
			}
		}

		return $return;
	}

	/**
	 * Method to determine if an array is an associative array.
	 *
	 * @param   array  $array  An array to test.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public static function isAssociative($array)
	{
		if (\is_array($array))
		{
			foreach (array_keys($array) as $k => $v)
			{
				if ($k !== $v)
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Pivots an array to create a reverse lookup of an array of scalars, arrays or objects.
	 *
	 * @param   array   $source  The source array.
	 * @param   string  $key     Where the elements of the source array are objects or arrays, the key to pivot on.
	 *
	 * @return  array  An array of arrays pivoted either on the value of the keys, or an individual key of an object or array.
	 *
	 * @since   1.0
	 */
	public static function pivot(array $source, $key = null)
	{
		$result  = array();
		$counter = array();

		foreach ($source as $index => $value)
		{
			// Determine the name of the pivot key, and its value.
			if (\is_array($value))
			{
				// If the key does not exist, ignore it.
				if (!isset($value[$key]))
				{
					continue;
				}

				$resultKey   = $value[$key];
				$resultValue = $source[$index];
			}
			elseif (\is_object($value))
			{
				// If the key does not exist, ignore it.
				if (!isset($value->$key))
				{
					continue;
				}

				$resultKey   = $value->$key;
				$resultValue = $source[$index];
			}
			else
			{
				// Just a scalar value.
				$resultKey   = $value;
				$resultValue = $index;
			}

			// The counter tracks how many times a key has been used.
			if (empty($counter[$resultKey]))
			{
				// The first time around we just assign the value to the key.
				$result[$resultKey]  = $resultValue;
				$counter[$resultKey] = 1;
			}
			elseif ($counter[$resultKey] == 1)
			{
				// If there is a second time, we convert the value into an array.
				$result[$resultKey] = array(
					$result[$resultKey],
					$resultValue,
				);
				$counter[$resultKey]++;
			}
			else
			{
				// After the second time, no need to track any more. Just append to the existing array.
				$result[$resultKey][] = $resultValue;
			}
		}

		unset($counter);

		return $result;
	}

	/**
	 * Utility function to sort an array of objects on a given field
	 *
	 * @param   array  $a              An array of objects
	 * @param   mixed  $k              The key (string) or an array of keys to sort on
	 * @param   mixed  $direction      Direction (integer) or an array of direction to sort in [1 = Ascending] [-1 = Descending]
	 * @param   mixed  $caseSensitive  Boolean or array of booleans to let sort occur case sensitive or insensitive
	 * @param   mixed  $locale         Boolean or array of booleans to let sort occur using the locale language or not
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public static function sortObjects(array $a, $k, $direction = 1, $caseSensitive = true, $locale = false)
	{
		if (!\is_array($locale) || !\is_array($locale[0]))
		{
			$locale = array($locale);
		}

		$sortCase      = (array) $caseSensitive;
		$sortDirection = (array) $direction;
		$key           = (array) $k;
		$sortLocale    = $locale;

		usort(
			$a, function ($a, $b) use ($sortCase, $sortDirection, $key, $sortLocale)
			{
				for ($i = 0, $count = \count($key); $i < $count; $i++)
				{
					if (isset($sortDirection[$i]))
					{
						$direction = $sortDirection[$i];
					}

					if (isset($sortCase[$i]))
					{
						$caseSensitive = $sortCase[$i];
					}

					if (isset($sortLocale[$i]))
					{
						$locale = $sortLocale[$i];
					}

					$va = $a->{$key[$i]};
					$vb = $b->{$key[$i]};

					if ((\is_bool($va) || is_numeric($va)) && (\is_bool($vb) || is_numeric($vb)))
					{
						$cmp = $va - $vb;
					}
					elseif ($caseSensitive)
					{
						$cmp = StringHelper::strcmp($va, $vb, $locale);
					}
					else
					{
						$cmp = StringHelper::strcasecmp($va, $vb, $locale);
					}

					if ($cmp > 0)
					{
						return $direction;
					}

					if ($cmp < 0)
					{
						return -$direction;
					}
				}

				return 0;
			}
		);

		return $a;
	}

	/**
	 * Multidimensional array safe unique test
	 *
	 * @param   array  $array  The array to make unique.
	 *
	 * @return  array
	 *
	 * @see     https://secure.php.net/manual/en/function.array-unique.php
	 * @since   1.0
	 */
	public static function arrayUnique(array $array)
	{
		$array = array_map('serialize', $array);
		$array = array_unique($array);
		$array = array_map('unserialize', $array);

		return $array;
	}

	/**
	 * An improved array_search that allows for partial matching of strings values in associative arrays.
	 *
	 * @param   string   $needle         The text to search for within the array.
	 * @param   array    $haystack       Associative array to search in to find $needle.
	 * @param   boolean  $caseSensitive  True to search case sensitive, false otherwise.
	 *
	 * @return  mixed    Returns the matching array $key if found, otherwise false.
	 *
	 * @since   1.0
	 */
	public static function arraySearch($needle, array $haystack, $caseSensitive = true)
	{
		foreach ($haystack as $key => $value)
		{
			$searchFunc = ($caseSensitive) ? 'strpos' : 'stripos';

			if ($searchFunc($value, $needle) === 0)
			{
				return $key;
			}
		}

		return false;
	}

	/**
	 * Method to recursively convert data to a one dimension array.
	 *
	 * @param   array|object  $array      The array or object to convert.
	 * @param   string        $separator  The key separator.
	 * @param   string        $prefix     Last level key prefix.
	 *
	 * @return  array
	 *
	 * @since   1.3.0
	 */
	public static function flatten($array, $separator = '.', $prefix = '')
	{
		if ($array instanceof \Traversable)
		{
			$array = iterator_to_array($array);
		}
		elseif (\is_object($array))
		{
			$array = get_object_vars($array);
		}

		foreach ($array as $k => $v)
		{
			$key = $prefix ? $prefix . $separator . $k : $k;

			if (\is_object($v) || \is_array($v))
			{
				$array = array_merge($array, static::flatten($v, $separator, $key));
			}
			else
			{
				$array[$key] = $v;
			}
		}

		return $array;
	}
}
vendor/joomla/utilities/src/IpHelper.php000064400000027671152177723700014375 0ustar00<?php
/**
 * Part of the Joomla Framework Utilities Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Utilities;

/**
 * IpHelper is a utility class for processing IP addresses
 *
 * This class is adapted from the `FOFUtilsIp` class distributed with the Joomla! CMS as part of the FOF library by Akeeba Ltd.
 * The original class is copyright of Nicholas K. Dionysopoulos / Akeeba Ltd.
 *
 * @since  1.6.0
 */
final class IpHelper
{
	/**
	 * The IP address of the current visitor
	 *
	 * @var    string
	 * @since  1.6.0
	 */
	private static $ip = null;

	/**
	 * Should I allow IP overrides through X-Forwarded-For or Client-Ip HTTP headers?
	 *
	 * @var    boolean
	 * @since  1.6.0
	 */
	private static $allowIpOverrides = true;

	/**
	 * Private constructor to prevent instantiation of this class
	 *
	 * @since   1.6.0
	 */
	private function __construct()
	{
	}

	/**
	 * Get the current visitor's IP address
	 *
	 * @return  string
	 *
	 * @since   1.6.0
	 */
	public static function getIp()
	{
		if (self::$ip === null)
		{
			$ip = self::detectAndCleanIP();

			if (!empty($ip) && ($ip != '0.0.0.0') && \function_exists('inet_pton') && \function_exists('inet_ntop'))
			{
				$myIP = @inet_pton($ip);

				if ($myIP !== false)
				{
					$ip = inet_ntop($myIP);
				}
			}

			self::setIp($ip);
		}

		return self::$ip;
	}

	/**
	 * Set the IP address of the current visitor
	 *
	 * @param   string  $ip  The visitor's IP address
	 *
	 * @return  void
	 *
	 * @since   1.6.0
	 */
	public static function setIp($ip)
	{
		self::$ip = $ip;
	}

	/**
	 * Is it an IPv6 IP address?
	 *
	 * @param   string   $ip  An IPv4 or IPv6 address
	 *
	 * @return  boolean
	 *
	 * @since   1.6.0
	 */
	public static function isIPv6($ip)
	{
		return strstr($ip, ':');
	}

	/**
	 * Checks if an IP is contained in a list of IPs or IP expressions
	 *
	 * @param   string        $ip       The IPv4/IPv6 address to check
	 * @param   array|string  $ipTable  An IP expression (or a comma-separated or array list of IP expressions) to check against
	 *
	 * @return  boolean
	 *
	 * @since   1.6.0
	 */
	public static function IPinList($ip, $ipTable = '')
	{
		// No point proceeding with an empty IP list
		if (empty($ipTable))
		{
			return false;
		}

		// If the IP list is not an array, convert it to an array
		if (!\is_array($ipTable))
		{
			if (strpos($ipTable, ',') !== false)
			{
				$ipTable = explode(',', $ipTable);
				$ipTable = array_map('trim', $ipTable);
			}
			else
			{
				$ipTable = trim($ipTable);
				$ipTable = array($ipTable);
			}
		}

		// If no IP address is found, return false
		if ($ip === '0.0.0.0')
		{
			return false;
		}

		// If no IP is given, return false
		if (empty($ip))
		{
			return false;
		}

		// Sanity check
		if (!\function_exists('inet_pton'))
		{
			return false;
		}

		// Get the IP's in_adds representation
		$myIP = @inet_pton($ip);

		// If the IP is in an unrecognisable format, quite
		if ($myIP === false)
		{
			return false;
		}

		$ipv6 = self::isIPv6($ip);

		foreach ($ipTable as $ipExpression)
		{
			$ipExpression = trim($ipExpression);

			// Inclusive IP range, i.e. 123.123.123.123-124.125.126.127
			if (strstr($ipExpression, '-'))
			{
				list($from, $to) = explode('-', $ipExpression, 2);

				if ($ipv6 && (!self::isIPv6($from) || !self::isIPv6($to)))
				{
					// Do not apply IPv4 filtering on an IPv6 address
					continue;
				}

				if (!$ipv6 && (self::isIPv6($from) || self::isIPv6($to)))
				{
					// Do not apply IPv6 filtering on an IPv4 address
					continue;
				}

				$from = @inet_pton(trim($from));
				$to   = @inet_pton(trim($to));

				// Sanity check
				if (($from === false) || ($to === false))
				{
					continue;
				}

				// Swap from/to if they're in the wrong order
				if ($from > $to)
				{
					list($from, $to) = array($to, $from);
				}

				if (($myIP >= $from) && ($myIP <= $to))
				{
					return true;
				}
			}
			// Netmask or CIDR provided
			elseif (strstr($ipExpression, '/'))
			{
				$binaryip = self::inetToBits($myIP);

				list($net, $maskbits) = explode('/', $ipExpression, 2);

				if ($ipv6 && !self::isIPv6($net))
				{
					// Do not apply IPv4 filtering on an IPv6 address
					continue;
				}

				if (!$ipv6 && self::isIPv6($net))
				{
					// Do not apply IPv6 filtering on an IPv4 address
					continue;
				}

				if ($ipv6 && strstr($maskbits, ':'))
				{
					// Perform an IPv6 CIDR check
					if (self::checkIPv6CIDR($myIP, $ipExpression))
					{
						return true;
					}

					// If we didn't match it proceed to the next expression
					continue;
				}

				if (!$ipv6 && strstr($maskbits, '.'))
				{
					// Convert IPv4 netmask to CIDR
					$long     = ip2long($maskbits);
					$base     = ip2long('255.255.255.255');
					$maskbits = 32 - log(($long ^ $base) + 1, 2);
				}

				// Convert network IP to in_addr representation
				$net = @inet_pton($net);

				// Sanity check
				if ($net === false)
				{
					continue;
				}

				// Get the network's binary representation
				$expectedNumberOfBits = $ipv6 ? 128 : 24;
				$binarynet            = str_pad(self::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);

				// Check the corresponding bits of the IP and the network
				$ipNetBits = substr($binaryip, 0, $maskbits);
				$netBits   = substr($binarynet, 0, $maskbits);

				if ($ipNetBits === $netBits)
				{
					return true;
				}
			}
			else
			{
				// IPv6: Only single IPs are supported
				if ($ipv6)
				{
					$ipExpression = trim($ipExpression);

					if (!self::isIPv6($ipExpression))
					{
						continue;
					}

					$ipCheck = @inet_pton($ipExpression);

					if ($ipCheck === false)
					{
						continue;
					}

					if ($ipCheck == $myIP)
					{
						return true;
					}
				}
				else
				{
					// Standard IPv4 address, i.e. 123.123.123.123 or partial IP address, i.e. 123.[123.][123.][123]
					$dots = 0;

					if (substr($ipExpression, -1) == '.')
					{
						// Partial IP address. Convert to CIDR and re-match
						foreach (count_chars($ipExpression, 1) as $i => $val)
						{
							if ($i == 46)
							{
								$dots = $val;
							}
						}

						switch ($dots)
						{
							case 1:
								$netmask = '255.0.0.0';
								$ipExpression .= '0.0.0';

								break;

							case 2:
								$netmask = '255.255.0.0';
								$ipExpression .= '0.0';

								break;

							case 3:
								$netmask = '255.255.255.0';
								$ipExpression .= '0';

								break;

							default:
								$dots = 0;
						}

						if ($dots)
						{
							$binaryip = self::inetToBits($myIP);

							// Convert netmask to CIDR
							$long     = ip2long($netmask);
							$base     = ip2long('255.255.255.255');
							$maskbits = 32 - log(($long ^ $base) + 1, 2);

							$net = @inet_pton($ipExpression);

							// Sanity check
							if ($net === false)
							{
								continue;
							}

							// Get the network's binary representation
							$expectedNumberOfBits = $ipv6 ? 128 : 24;
							$binarynet            = str_pad(self::inetToBits($net), $expectedNumberOfBits, '0', STR_PAD_RIGHT);

							// Check the corresponding bits of the IP and the network
							$ipNetBits = substr($binaryip, 0, $maskbits);
							$netBits   = substr($binarynet, 0, $maskbits);

							if ($ipNetBits === $netBits)
							{
								return true;
							}
						}
					}

					if (!$dots)
					{
						$ip = @inet_pton(trim($ipExpression));

						if ($ip == $myIP)
						{
							return true;
						}
					}
				}
			}
		}

		return false;
	}

	/**
	 * Works around the REMOTE_ADDR not containing the user's IP
	 *
	 * @return  void
	 *
	 * @since   1.6.0
	 */
	public static function workaroundIPIssues()
	{
		$ip = self::getIp();

		if ($_SERVER['REMOTE_ADDR'] === $ip)
		{
			return;
		}

		if (array_key_exists('REMOTE_ADDR', $_SERVER))
		{
			$_SERVER['JOOMLA_REMOTE_ADDR'] = $_SERVER['REMOTE_ADDR'];
		}
		elseif (\function_exists('getenv'))
		{
			if (getenv('REMOTE_ADDR'))
			{
				$_SERVER['JOOMLA_REMOTE_ADDR'] = getenv('REMOTE_ADDR');
			}
		}

		$_SERVER['REMOTE_ADDR'] = $ip;
	}

	/**
	 * Should I allow the remote client's IP to be overridden by an X-Forwarded-For or Client-Ip HTTP header?
	 *
	 * @param   boolean  $newState  True to allow the override
	 *
	 * @return  void
	 *
	 * @since   1.6.0
	 */
	public static function setAllowIpOverrides($newState)
	{
		self::$allowIpOverrides = $newState ? true : false;
	}

	/**
	 * Gets the visitor's IP address.
	 *
	 * Automatically handles reverse proxies reporting the IPs of intermediate devices, like load balancers. Examples:
	 *
	 * - https://www.akeebabackup.com/support/admin-tools/13743-double-ip-adresses-in-security-exception-log-warnings.html
	 * - https://stackoverflow.com/questions/2422395/why-is-request-envremote-addr-returning-two-ips
	 *
	 * The solution used is assuming that the last IP address is the external one.
	 *
	 * @return  string
	 *
	 * @since   1.6.0
	 */
	protected static function detectAndCleanIP()
	{
		$ip = self::detectIP();

		if (strstr($ip, ',') !== false || strstr($ip, ' ') !== false)
		{
			$ip  = str_replace(' ', ',', $ip);
			$ip  = str_replace(',,', ',', $ip);
			$ips = explode(',', $ip);
			$ip  = '';

			while (empty($ip) && !empty($ips))
			{
				$ip = array_pop($ips);
				$ip = trim($ip);
			}
		}
		else
		{
			$ip = trim($ip);
		}

		return $ip;
	}

	/**
	 * Gets the visitor's IP address
	 *
	 * @return  string
	 *
	 * @since   1.6.0
	 */
	protected static function detectIP()
	{
		// Normally the $_SERVER superglobal is set
		if (isset($_SERVER))
		{
			// Do we have an x-forwarded-for HTTP header (e.g. NginX)?
			if (self::$allowIpOverrides && array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER))
			{
				return $_SERVER['HTTP_X_FORWARDED_FOR'];
			}

			// Do we have a client-ip header (e.g. non-transparent proxy)?
			if (self::$allowIpOverrides && array_key_exists('HTTP_CLIENT_IP', $_SERVER))
			{
				return $_SERVER['HTTP_CLIENT_IP'];
			}

			// Normal, non-proxied server or server behind a transparent proxy
			return $_SERVER['REMOTE_ADDR'];
		}

		/*
		 * This part is executed on PHP running as CGI, or on SAPIs which do not set the $_SERVER superglobal
		 * If getenv() is disabled, you're screwed
		 */
		if (!\function_exists('getenv'))
		{
			return '';
		}

		// Do we have an x-forwarded-for HTTP header?
		if (self::$allowIpOverrides && getenv('HTTP_X_FORWARDED_FOR'))
		{
			return getenv('HTTP_X_FORWARDED_FOR');
		}

		// Do we have a client-ip header?
		if (self::$allowIpOverrides && getenv('HTTP_CLIENT_IP'))
		{
			return getenv('HTTP_CLIENT_IP');
		}

		// Normal, non-proxied server or server behind a transparent proxy
		if (getenv('REMOTE_ADDR'))
		{
			return getenv('REMOTE_ADDR');
		}

		// Catch-all case for broken servers, apparently
		return '';
	}

	/**
	 * Converts inet_pton output to bits string
	 *
	 * @param   string  $inet  The in_addr representation of an IPv4 or IPv6 address
	 *
	 * @return  string
	 *
	 * @since   1.6.0
	 */
	protected static function inetToBits($inet)
	{
		if (\strlen($inet) == 4)
		{
			$unpacked = unpack('A4', $inet);
		}
		else
		{
			$unpacked = unpack('A16', $inet);
		}

		$unpacked = str_split($unpacked[1]);
		$binaryip = '';

		foreach ($unpacked as $char)
		{
			$binaryip .= str_pad(decbin(\ord($char)), 8, '0', STR_PAD_LEFT);
		}

		return $binaryip;
	}

	/**
	 * Checks if an IPv6 address $ip is part of the IPv6 CIDR block $cidrnet
	 *
	 * @param   string  $ip       The IPv6 address to check, e.g. 21DA:00D3:0000:2F3B:02AC:00FF:FE28:9C5A
	 * @param   string  $cidrnet  The IPv6 CIDR block, e.g. 21DA:00D3:0000:2F3B::/64
	 *
	 * @return  boolean
	 *
	 * @since   1.6.0
	 */
	protected static function checkIPv6CIDR($ip, $cidrnet)
	{
		$ip       = inet_pton($ip);
		$binaryip = self::inetToBits($ip);

		list($net, $maskbits) = explode('/', $cidrnet);
		$net                  = inet_pton($net);
		$binarynet            = self::inetToBits($net);

		$ipNetBits = substr($binaryip, 0, $maskbits);
		$netBits   = substr($binarynet, 0, $maskbits);

		return $ipNetBits === $netBits;
	}
}
vendor/joomla/ldap/LICENSE000064400000042630152177723700011267 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/ldap/src/LdapClient.php000064400000037330152177723700013602 0ustar00<?php
/**
 * Part of the Joomla Framework LDAP Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Ldap;

/**
 * LDAP client class
 *
 * @since       1.0
 * @deprecated  The joomla/ldap package is deprecated
 */
class LdapClient
{
	/**
	 * Hostname of LDAP server
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $host;

	/**
	 * Authorization Method to use
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	public $auth_method;

	/**
	 * Port of LDAP server
	 *
	 * @var    integer
	 * @since  1.0
	 */
	public $port;

	/**
	 * Base DN (e.g. o=MyDir)
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $base_dn;

	/**
	 * User DN (e.g. cn=Users,o=MyDir)
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $users_dn;

	/**
	 * Search String
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $search_string;

	/**
	 * Use LDAP Version 3
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	public $use_ldapV3;

	/**
	 * No referrals (server transfers)
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	public $no_referrals;

	/**
	 * Negotiate TLS (encrypted communications)
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	public $negotiate_tls;

	/**
	 * Ignore TLS Certificate (encrypted communications)
	 *
	 * @var    boolean
	 * @since  1.5.0
	 */
	public $ignore_reqcert_tls;

	/**
	 * Enable LDAP debug
	 *
	 * @var    boolean
	 * @since  1.5.0
	 */
	public $ldap_debug;

	/**
	 * Username to connect to server
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $username;

	/**
	 * Password to connect to server
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $password;

	/**
	 * LDAP Resource Identifier
	 *
	 * @var    resource
	 * @since  1.0
	 */
	private $resource;

	/**
	 * Current DN
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $dn;

	/**
	 * Flag tracking whether the connection has been bound
	 *
	 * @var    boolean
	 * @since  1.3.0
	 */
	private $isBound = false;

	/**
	 * Constructor
	 *
	 * @param   object  $configObj  An object of configuration variables
	 *
	 * @since   1.0
	 */
	public function __construct($configObj = null)
	{
		if (\is_object($configObj))
		{
			$vars = get_class_vars(\get_class($this));

			foreach (array_keys($vars) as $var)
			{
				if (substr($var, 0, 1) != '_')
				{
					$param = $configObj->get($var);

					if ($param)
					{
						$this->$var = $param;
					}
				}
			}
		}
	}

	/**
	 * Class destructor.
	 *
	 * @since   1.3.0
	 */
	public function __destruct()
	{
		$this->close();
	}

	/**
	 * Connect to an LDAP server
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function connect()
	{
		if ($this->host == '')
		{
			return false;
		}

		if ($this->ignore_reqcert_tls)
		{
			putenv('LDAPTLS_REQCERT=never');
		}

		if ($this->ldap_debug)
		{
			ldap_set_option(null, LDAP_OPT_DEBUG_LEVEL, 7);
		}

		$this->resource = ldap_connect($this->host, $this->port);

		if (!$this->resource)
		{
			return false;
		}

		if ($this->use_ldapV3 && !ldap_set_option($this->resource, LDAP_OPT_PROTOCOL_VERSION, 3))
		{
			return false;
		}

		if (!ldap_set_option($this->resource, LDAP_OPT_REFERRALS, (int) $this->no_referrals))
		{
			return false;
		}

		if ($this->negotiate_tls && !ldap_start_tls($this->resource))
		{
			return false;
		}

		return true;
	}

	/**
	 * Close the connection
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function close()
	{
		if ($this->isConnected())
		{
			$this->unbind();
		}

		$this->resource = null;
	}

	/**
	 * Sets the DN with some template replacements
	 *
	 * @param   string  $username  The username
	 * @param   string  $nosub     ...
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function setDn($username, $nosub = 0)
	{
		if ($this->users_dn == '' || $nosub)
		{
			$this->dn = $username;
		}
		elseif (\strlen($username))
		{
			$this->dn = str_replace('[username]', $username, $this->users_dn);
		}
		else
		{
			$this->dn = '';
		}
	}

	/**
	 * Get the configured DN
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function getDn()
	{
		return $this->dn;
	}

	/**
	 * Anonymously binds to LDAP directory
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function anonymous_bind()
	{
		if (!$this->isConnected())
		{
			if (!$this->connect())
			{
				return false;
			}
		}

		$this->isBound = ldap_bind($this->resource);

		return $this->isBound;
	}

	/**
	 * Binds to the LDAP directory
	 *
	 * @param   string  $username  The username
	 * @param   string  $password  The password
	 * @param   string  $nosub     ...
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function bind($username = null, $password = null, $nosub = 0)
	{
		if (!$this->isConnected())
		{
			if (!$this->connect())
			{
				return false;
			}
		}

		if ($username === null)
		{
			$username = $this->username;
		}

		if ($password === null)
		{
			$password = $this->password;
		}

		$this->setDn($username, $nosub);

		$this->isBound = ldap_bind($this->resource, $this->getDn(), $password);

		return $this->isBound;
	}

	/**
	 * Unbinds from the LDAP directory
	 *
	 * @return  boolean
	 *
	 * @since   1.3.0
	 */
	public function unbind()
	{
		if ($this->isBound && $this->resource && \is_resource($this->resource))
		{
			return ldap_unbind($this->resource);
		}

		return true;
	}

	/**
	 * Perform an LDAP search using comma separated search strings
	 *
	 * @param   string  $search  search string of search values
	 *
	 * @return  array  Search results
	 *
	 * @since   1.0
	 */
	public function simple_search($search)
	{
		$results = explode(';', $search);

		foreach ($results as $key => $result)
		{
			$results[$key] = '(' . $result . ')';
		}

		return $this->search($results);
	}

	/**
	 * Performs an LDAP search
	 *
	 * @param   array   $filters     Search Filters (array of strings)
	 * @param   string  $dnoverride  DN Override
	 * @param   array   $attributes  An array of attributes to return (if empty, all fields are returned).
	 *
	 * @return  array  Multidimensional array of results
	 *
	 * @since   1.0
	 */
	public function search(array $filters, $dnoverride = null, array $attributes = array())
	{
		$result = array();

		if (!$this->isBound || !$this->isConnected())
		{
			return $result;
		}

		if ($dnoverride)
		{
			$dn = $dnoverride;
		}
		else
		{
			$dn = $this->base_dn;
		}

		foreach ($filters as $searchFilter)
		{
			$searchResult = ldap_search($this->resource, $dn, $searchFilter, $attributes);

			if ($searchResult && ($count = ldap_count_entries($this->resource, $searchResult)) > 0)
			{
				for ($i = 0; $i < $count; $i++)
				{
					$result[$i] = array();

					if (!$i)
					{
						$firstentry = ldap_first_entry($this->resource, $searchResult);
					}
					else
					{
						$firstentry = ldap_next_entry($this->resource, $firstentry);
					}

					// Load user-specified attributes
					$attributeResult = ldap_get_attributes($this->resource, $firstentry);

					// LDAP returns an array of arrays, fit this into attributes result array
					foreach ($attributeResult as $ki => $ai)
					{
						if (\is_array($ai))
						{
							$subcount        = $ai['count'];
							$result[$i][$ki] = array();

							for ($k = 0; $k < $subcount; $k++)
							{
								$result[$i][$ki][$k] = $ai[$k];
							}
						}
					}

					$result[$i]['dn'] = ldap_get_dn($this->resource, $firstentry);
				}
			}
		}

		return $result;
	}

	/**
	 * Replace attribute values with new ones
	 *
	 * @param   string  $dn         The DN which contains the attribute you want to replace
	 * @param   string  $attribute  The attribute values you want to replace
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function replace($dn, $attribute)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_mod_replace($this->resource, $dn, $attribute);
	}

	/**
	 * Modify an LDAP entry
	 *
	 * @param   string  $dn         The DN which contains the attribute you want to modify
	 * @param   string  $attribute  The attribute values you want to modify
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function modify($dn, $attribute)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_modify($this->resource, $dn, $attribute);
	}

	/**
	 * Delete attribute values from current attributes
	 *
	 * @param   string  $dn         The DN which contains the attribute you want to remove
	 * @param   string  $attribute  The attribute values you want to remove
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function remove($dn, $attribute)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_mod_del($this->resource, $dn, $attribute);
	}

	/**
	 * Compare value of attribute found in entry specified with DN
	 *
	 * @param   string  $dn         The DN which contains the attribute you want to compare
	 * @param   string  $attribute  The attribute whose value you want to compare
	 * @param   string  $value      The value you want to check against the LDAP attribute
	 *
	 * @return  boolean|integer  Boolean result of the comparison or -1 on error
	 *
	 * @since   1.0
	 */
	public function compare($dn, $attribute, $value)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_compare($this->resource, $dn, $attribute, $value);
	}

	/**
	 * Read attributes of a given DN
	 *
	 * @param   string  $dn  The DN of the object you want to read
	 *
	 * @return  array|boolean  Array of attributes for the given DN or boolean false on failure
	 *
	 * @since   1.0
	 */
	public function read($dn)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		$base   = substr($dn, strpos($dn, ',') + 1);
		$cn     = substr($dn, 0, strpos($dn, ','));
		$result = ldap_read($this->resource, $base, $cn);

		if ($result === false)
		{
			return false;
		}

		return ldap_get_entries($this->resource, $result);
	}

	/**
	 * Delete an entry from a directory
	 *
	 * @param   string  $dn  The DN of the object you want to delete
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function delete($dn)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_delete($this->resource, $dn);
	}

	/**
	 * Add entries to LDAP directory
	 *
	 * @param   string  $dn       The DN where you want to put the object
	 * @param   array   $entries  An array of arrays describing the object to add
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function create($dn, array $entries)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_add($this->resource, $dn, $entries);
	}

	/**
	 * Add attribute values to current attributes
	 *
	 * @param   string  $dn     The DN of the entry to add the attribute
	 * @param   array   $entry  An array of arrays with attributes to add
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function add($dn, array $entry)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_mod_add($this->resource, $dn, $entry);
	}

	/**
	 * Modify the name of an entry
	 *
	 * @param   string   $dn           The DN of the entry at the moment
	 * @param   string   $newdn        The DN of the entry should be (only cn=newvalue)
	 * @param   string   $newparent    The full DN of the parent (null by default)
	 * @param   boolean  $deleteolddn  Delete the old values (default)
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function rename($dn, $newdn, $newparent, $deleteolddn)
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return false;
		}

		return ldap_rename($this->resource, $dn, $newdn, $newparent, $deleteolddn);
	}

	/**
	 * Escape a string
	 *
	 * @param   string   $value   The subject string
	 * @param   string   $ignore  Characters to ignore when escaping.
	 * @param   integer  $flags   The context the escaped string will be used in LDAP_ESCAPE_FILTER or LDAP_ESCAPE_DN
	 *
	 * @return  string
	 *
	 * @since   1.2.0
	 */
	public function escape($value, $ignore = '', $flags = 0)
	{
		return ldap_escape($value, $ignore, $flags);
	}

	/**
	 * Return the LDAP error message of the last LDAP command
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function getErrorMsg()
	{
		if (!$this->isBound || !$this->isConnected())
		{
			return '';
		}

		return ldap_error($this->resource);
	}

	/**
	 * Check if the connection is established
	 *
	 * @return  boolean
	 *
	 * @since   1.3.0
	 */
	public function isConnected()
	{
		return $this->resource && \is_resource($this->resource);
	}

	/**
	 * Converts a dot notation IP address to net address (e.g. for Netware, etc)
	 *
	 * @param   string  $ip  IP Address (e.g. xxx.xxx.xxx.xxx)
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public static function ipToNetAddress($ip)
	{
		$parts   = explode('.', $ip);
		$address = '1#';

		foreach ($parts as $int)
		{
			$tmp = dechex($int);

			if (\strlen($tmp) != 2)
			{
				$tmp = '0' . $tmp;
			}

			$address .= '\\' . $tmp;
		}

		return $address;
	}

	/**
	 * Extract readable network address from the LDAP encoded networkAddress attribute.
	 *
	 * Please keep this document block and author attribution in place.
	 *
	 * Novell Docs, see: http://developer.novell.com/ndk/doc/ndslib/schm_enu/data/sdk5624.html#sdk5624
	 * for Address types: http://developer.novell.com/ndk/doc/ndslib/index.html?page=/ndk/doc/ndslib/schm_enu/data/sdk4170.html
	 * LDAP Format, String:
	 * taggedData = uint32String "#" octetstring
	 * byte 0 = uint32String = Address Type: 0= IPX Address; 1 = IP Address
	 * byte 1 = char = "#" - separator
	 * byte 2+ = octetstring - the ordinal value of the address
	 * Note: with eDirectory 8.6.2, the IP address (type 1) returns
	 * correctly, however, an IPX address does not seem to.  eDir 8.7 may correct this.
	 * Enhancement made by Merijn van de Schoot:
	 * If addresstype is 8 (UDP) or 9 (TCP) do some additional parsing like still returning the IP address
	 *
	 * @param   string  $networkaddress  The network address
	 *
	 * @return  array
	 *
	 * @author  Jay Burrell, Systems & Networks, Mississippi State University
	 * @since   1.0
	 */
	public static function ldapNetAddr($networkaddress)
	{
		$addr     = '';
		$addrtype = (int) substr($networkaddress, 0, 1);

		// Throw away bytes 0 and 1 which should be the addrtype and the "#" separator
		$networkaddress = substr($networkaddress, 2);

		if (($addrtype == 8) || ($addrtype = 9))
		{
			// TODO 1.6: If UDP or TCP, (TODO fill addrport and) strip portnumber information from address
			$networkaddress = substr($networkaddress, (\strlen($networkaddress) - 4));
		}

		$addrtypes = array(
			'IPX',
			'IP',
			'SDLC',
			'Token Ring',
			'OSI',
			'AppleTalk',
			'NetBEUI',
			'Socket',
			'UDP',
			'TCP',
			'UDP6',
			'TCP6',
			'Reserved (12)',
			'URL',
			'Count',
		);

		$len = \strlen($networkaddress);

		if ($len > 0)
		{
			for ($i = 0; $i < $len; $i++)
			{
				$byte = substr($networkaddress, $i, 1);
				$addr .= \ord($byte);

				if (($addrtype == 1) || ($addrtype == 8) || ($addrtype = 9))
				{
					// Dot separate IP addresses...
					$addr .= '.';
				}
			}

			if (($addrtype == 1) || ($addrtype == 8) || ($addrtype = 9))
			{
				// Strip last period from end of $addr
				$addr = substr($addr, 0, \strlen($addr) - 1);
			}
		}
		else
		{
			$addr .= 'Address not available.';
		}

		return array('protocol' => $addrtypes[$addrtype], 'address' => $addr);
	}

	/**
	 * Generates a LDAP compatible password
	 *
	 * @param   string  $password  Clear text password to encrypt
	 * @param   string  $type      Type of password hash, either md5 or SHA
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public static function generatePassword($password, $type = 'md5')
	{
		switch (strtolower($type))
		{
			case 'sha':
				return '{SHA}' . base64_encode(pack('H*', sha1($password)));

			case 'md5':
			default:
				return '{MD5}' . base64_encode(pack('H*', md5($password)));
		}
	}
}
vendor/joomla/image/LICENSE000064400000042630152177723700011431 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/image/src/ImageFilter.php000064400000004631152177723700014113 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image;

use Psr\Log\NullLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;

/**
 * Class to manipulate an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
abstract class ImageFilter implements LoggerAwareInterface
{
	/**
	 * @var    resource  The image resource handle.
	 * @since  1.0
	 */
	protected $handle;

	/**
	 * @var    LoggerInterface  Logger object
	 * @since  1.0
	 */
	protected $logger = null;

	/**
	 * Class constructor.
	 *
	 * @param   resource  $handle  The image resource on which to apply the filter.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public function __construct($handle)
	{
		// Verify that image filter support for PHP is available.
		if (!function_exists('imagefilter'))
		{
			// @codeCoverageIgnoreStart
			$this->getLogger()->error('The imagefilter function for PHP is not available.');

			throw new \RuntimeException('The imagefilter function for PHP is not available.');

			// @codeCoverageIgnoreEnd
		}

		// Make sure the file handle is valid.
		if (!is_resource($handle) || (get_resource_type($handle) != 'gd'))
		{
			$this->getLogger()->error('The image handle is invalid for the image filter.');

			throw new \InvalidArgumentException('The image handle is invalid for the image filter.');
		}

		$this->handle = $handle;
	}

	/**
	 * Get the logger.
	 *
	 * @return  LoggerInterface
	 *
	 * @since   1.0
	 */
	public function getLogger()
	{
		// If a logger hasn't been set, use NullLogger
		if (! ($this->logger instanceof LoggerInterface))
		{
			$this->logger = new NullLogger;
		}

		return $this->logger;
	}

	/**
	 * Sets a logger instance on the object
	 *
	 * @param   LoggerInterface  $logger  A PSR-3 compliant logger.
	 *
	 * @return  Image  This object for message chaining.
	 *
	 * @since   1.0
	 */
	public function setLogger(LoggerInterface $logger)
	{
		$this->logger = $logger;

		return $this;
	}

	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	abstract public function execute(array $options = array());
}
vendor/joomla/image/src/Image.php000064400000075472152177723700012760 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image;

use Psr\Log\NullLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerAwareInterface;

/**
 * Class to manipulate an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Image implements LoggerAwareInterface
{
	/**
	 * @const  integer
	 * @since  1.0
	 */
	const SCALE_FILL = 1;

	/**
	 * @const  integer
	 * @since  1.0
	 */
	const SCALE_INSIDE = 2;

	/**
	 * @const  integer
	 * @since  1.0
	 */
	const SCALE_OUTSIDE = 3;

	/**
	 * @const  integer
	 * @since  1.0
	 */
	const CROP = 4;

	/**
	 * @const  integer
	 * @since  1.0
	 */
	const CROP_RESIZE = 5;

	/**
	 * @const  integer
	 * @since  1.0
	 */
	const SCALE_FIT = 6;

	/**
	 * @const  string
	 * @since  1.2.0
	 */
	const ORIENTATION_LANDSCAPE = 'landscape';

	/**
	 * @const  string
	 * @since  1.2.0
	 */
	const ORIENTATION_PORTRAIT = 'portrait';

	/**
	 * @const  string
	 * @since  1.2.0
	 */
	const ORIENTATION_SQUARE = 'square';

	/**
	 * @var    resource  The image resource handle.
	 * @since  1.0
	 */
	protected $handle;

	/**
	 * @var    string  The source image path.
	 * @since  1.0
	 */
	protected $path = null;

	/**
	 * @var    array  Whether or not different image formats are supported.
	 * @since  1.0
	 */
	protected static $formats = array();

	/**
	 * @var    LoggerInterface  Logger object
	 * @since  1.0
	 */
	protected $logger = null;

	/**
	 * @var    boolean  Flag if an image should use the best quality available.  Disable for improved performance.
	 * @since  1.4.0
	 */
	protected $generateBestQuality = true;

	/**
	 * Class constructor.
	 *
	 * @param   mixed  $source  Either a file path for a source image or a GD resource handler for an image.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function __construct($source = null)
	{
		// Verify that GD support for PHP is available.
		if (!extension_loaded('gd'))
		{
			// @codeCoverageIgnoreStart
			throw new \RuntimeException('The GD extension for PHP is not available.');

			// @codeCoverageIgnoreEnd
		}

		// Determine which image types are supported by GD, but only once.
		if (!isset(static::$formats[IMAGETYPE_JPEG]))
		{
			$info = gd_info();
			static::$formats[IMAGETYPE_JPEG] = ($info['JPEG Support']) ? true : false;
			static::$formats[IMAGETYPE_PNG] = ($info['PNG Support']) ? true : false;
			static::$formats[IMAGETYPE_GIF] = ($info['GIF Read Support']) ? true : false;
		}

		// If the source input is a resource, set it as the image handle.
		if (is_resource($source) && (get_resource_type($source) == 'gd'))
		{
			$this->handle = &$source;
		}
		elseif (!empty($source) && is_string($source))
		{
			// If the source input is not empty, assume it is a path and populate the image handle.
			$this->loadFile($source);
		}
	}

	/**
	 * Get the image resource handle
	 *
	 * @return  resource
	 *
	 * @since   1.3.0
	 * @throws  \LogicException if an image has not been loaded into the instance
	 */
	public function getHandle()
	{
		// Make sure the resource handle is valid.
		if (!$this->isLoaded())
		{
			throw new \LogicException('No valid image was loaded.');
		}

		return $this->handle;
	}

	/**
	 * Get the logger.
	 *
	 * @return  LoggerInterface
	 *
	 * @since   1.0
	 */
	public function getLogger()
	{
		// If a logger hasn't been set, use NullLogger
		if (! ($this->logger instanceof LoggerInterface))
		{
			$this->logger = new NullLogger;
		}

		return $this->logger;
	}

	/**
	 * Sets a logger instance on the object
	 *
	 * @param   LoggerInterface  $logger  A PSR-3 compliant logger.
	 *
	 * @return  Image  This object for message chaining.
	 *
	 * @since   1.0
	 */
	public function setLogger(LoggerInterface $logger)
	{
		$this->logger = $logger;

		return $this;
	}

	/**
	 * Method to return a properties object for an image given a filesystem path.
	 *
	 * The result object has values for image width, height, type, attributes, mime type, bits, and channels.
	 *
	 * @param   string  $path  The filesystem path to the image for which to get properties.
	 *
	 * @return  \stdClass
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public static function getImageFileProperties($path)
	{
		// Make sure the file exists.
		if (!file_exists($path))
		{
			throw new \InvalidArgumentException('The image file does not exist.');
		}

		// Get the image file information.
		$info = getimagesize($path);

		if (!$info)
		{
			// @codeCoverageIgnoreStart
			throw new \RuntimeException('Unable to get properties for the image.');

			// @codeCoverageIgnoreEnd
		}

		// Build the response object.
		$properties = (object) array(
			'width'       => $info[0],
			'height'      => $info[1],
			'type'        => $info[2],
			'attributes'  => $info[3],
			'bits'        => isset($info['bits']) ? $info['bits'] : null,
			'channels'    => isset($info['channels']) ? $info['channels'] : null,
			'mime'        => $info['mime'],
			'filesize'    => filesize($path),
			'orientation' => self::getOrientationString((int) $info[0], (int) $info[1]),
		);

		return $properties;
	}

	/**
	 * Method to detect whether an image's orientation is landscape, portrait or square.
	 *
	 * The orientation will be returned as a string.
	 *
	 * @return  mixed   Orientation string or null.
	 *
	 * @since   1.2.0
	 */
	public function getOrientation()
	{
		if ($this->isLoaded())
		{
			return self::getOrientationString($this->getWidth(), $this->getHeight());
		}

		return null;
	}

	/**
	 * Compare width and height integers to determine image orientation.
	 *
	 * @param   integer  $width   The width value to use for calculation
	 * @param   integer  $height  The height value to use for calculation
	 *
	 * @return  string   Orientation string
	 *
	 * @since   1.2.0
	 */
	private static function getOrientationString($width, $height)
	{
		switch (true)
		{
			case ($width > $height) :
				return self::ORIENTATION_LANDSCAPE;

			case ($width < $height) :
				return self::ORIENTATION_PORTRAIT;

			default:
				return self::ORIENTATION_SQUARE;
		}
	}

	/**
	 * Method to generate thumbnails from the current image. It allows
	 * creation by resizing or cropping the original image.
	 *
	 * @param   mixed    $thumbSizes      String or array of strings. Example: $thumbSizes = array('150x75','250x150');
	 * @param   integer  $creationMethod  1-3 resize $scaleMethod | 4 create cropping | 5 resize then crop
	 *
	 * @return  array
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 * @throws  \InvalidArgumentException
	 */
	public function generateThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE)
	{
		// Make sure the resource handle is valid.
		if (!$this->isLoaded())
		{
			throw new \LogicException('No valid image was loaded.');
		}

		// Accept a single thumbsize string as parameter
		if (!is_array($thumbSizes))
		{
			$thumbSizes = array($thumbSizes);
		}

		// Process thumbs
		$generated = array();

		if (!empty($thumbSizes))
		{
			foreach ($thumbSizes as $thumbSize)
			{
				// Desired thumbnail size
				$size = explode('x', strtolower($thumbSize));

				if (count($size) != 2)
				{
					throw new \InvalidArgumentException('Invalid thumb size received: ' . $thumbSize);
				}

				$thumbWidth  = $size[0];
				$thumbHeight = $size[1];

				switch ($creationMethod)
				{
					// Case for self::CROP
					case 4:
						$thumb = $this->crop($thumbWidth, $thumbHeight, null, null, true);
						break;

					// Case for self::CROP_RESIZE
					case 5:
						$thumb = $this->cropResize($thumbWidth, $thumbHeight, true);
						break;

					default:
						$thumb = $this->resize($thumbWidth, $thumbHeight, true, $creationMethod);
						break;
				}

				// Store the thumb in the results array
				$generated[] = $thumb;
			}
		}

		return $generated;
	}

	/**
	 * Method to create thumbnails from the current image and save them to disk. It allows creation by resizing
	 * or cropping the original image.
	 *
	 * @param   mixed    $thumbSizes      string or array of strings. Example: $thumbSizes = array('150x75','250x150');
	 * @param   integer  $creationMethod  1-3 resize $scaleMethod | 4 create cropping
	 * @param   string   $thumbsFolder    destination thumbs folder. null generates a thumbs folder in the image folder
	 *
	 * @return  array
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 * @throws  \InvalidArgumentException
	 */
	public function createThumbs($thumbSizes, $creationMethod = self::SCALE_INSIDE, $thumbsFolder = null)
	{
		// Make sure the resource handle is valid.
		if (!$this->isLoaded())
		{
			throw new \LogicException('No valid image was loaded.');
		}

		// No thumbFolder set -> we will create a thumbs folder in the current image folder
		if (is_null($thumbsFolder))
		{
			$thumbsFolder = dirname($this->getPath()) . '/thumbs';
		}

		// Check destination
		if (!is_dir($thumbsFolder) && (!is_dir(dirname($thumbsFolder)) || !@mkdir($thumbsFolder)))
		{
			throw new \InvalidArgumentException('Folder does not exist and cannot be created: ' . $thumbsFolder);
		}

		// Process thumbs
		$thumbsCreated = array();

		if ($thumbs = $this->generateThumbs($thumbSizes, $creationMethod))
		{
			// Parent image properties
			$imgProperties = static::getImageFileProperties($this->getPath());

			foreach ($thumbs as $thumb)
			{
				// Get thumb properties
				$thumbWidth     = $thumb->getWidth();
				$thumbHeight    = $thumb->getHeight();

				// Generate thumb name
				$filename       = pathinfo($this->getPath(), PATHINFO_FILENAME);
				$fileExtension  = pathinfo($this->getPath(), PATHINFO_EXTENSION);
				$thumbFileName  = $filename . '_' . $thumbWidth . 'x' . $thumbHeight . '.' . $fileExtension;

				// Save thumb file to disk
				$thumbFileName = $thumbsFolder . '/' . $thumbFileName;

				if ($thumb->toFile($thumbFileName, $imgProperties->type))
				{
					// Return Image object with thumb path to ease further manipulation
					$thumb->path = $thumbFileName;
					$thumbsCreated[] = $thumb;
				}
			}
		}

		return $thumbsCreated;
	}

	/**
	 * Method to crop the current image.
	 *
	 * @param   mixed    $width      The width of the image section to crop in pixels or a percentage.
	 * @param   mixed    $height     The height of the image section to crop in pixels or a percentage.
	 * @param   integer  $left       The number of pixels from the left to start cropping.
	 * @param   integer  $top        The number of pixels from the top to start cropping.
	 * @param   boolean  $createNew  If true the current image will be cloned, cropped and returned; else
	 *                               the current image will be cropped and returned.
	 *
	 * @return  Image
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function crop($width, $height, $left = null, $top = null, $createNew = true)
	{
		// Sanitize width.
		$width = $this->sanitizeWidth($width, $height);

		// Sanitize height.
		$height = $this->sanitizeHeight($height, $width);

		// Autocrop offsets
		if (is_null($left))
		{
			$left = round(($this->getWidth() - $width) / 2);
		}

		if (is_null($top))
		{
			$top = round(($this->getHeight() - $height) / 2);
		}

		// Sanitize left.
		$left = $this->sanitizeOffset($left);

		// Sanitize top.
		$top = $this->sanitizeOffset($top);

		// Create the new truecolor image handle.
		$handle = imagecreatetruecolor($width, $height);

		// Allow transparency for the new image handle.
		imagealphablending($handle, false);
		imagesavealpha($handle, true);

		if ($this->isTransparent())
		{
			// Get the transparent color values for the current image.
			$rgba  = imagecolorsforindex($this->getHandle(), imagecolortransparent($this->getHandle()));
			$color = imagecolorallocatealpha($handle, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);

			// Set the transparent color values for the new image.
			imagecolortransparent($handle, $color);
			imagefill($handle, 0, 0, $color);
		}

		if (!$this->generateBestQuality)
		{
			imagecopyresized($handle, $this->getHandle(), 0, 0, $left, $top, $width, $height, $width, $height);
		}
		else
		{
			imagecopyresampled($handle, $this->getHandle(), 0, 0, $left, $top, $width, $height, $width, $height);
		}

		// If we are cropping to a new image, create a new Image object.
		if ($createNew)
		{
			// @codeCoverageIgnoreStart
			return new static($handle);

			// @codeCoverageIgnoreEnd
		}

		// Swap out the current handle for the new image handle.
		$this->destroy();

		$this->handle = $handle;

		return $this;
	}

	/**
	 * Method to apply a filter to the image by type.  Two examples are: grayscale and sketchy.
	 *
	 * @param   string  $type     The name of the image filter to apply.
	 * @param   array   $options  An array of options for the filter.
	 *
	 * @return  Image
	 *
	 * @since   1.0
	 * @see     Joomla\Image\Filter
	 * @throws  \LogicException
	 */
	public function filter($type, array $options = array())
	{
		// Make sure the resource handle is valid.
		if (!$this->isLoaded())
		{
			throw new \LogicException('No valid image was loaded.');
		}

		// Get the image filter instance.
		$filter = $this->getFilterInstance($type);

		// Execute the image filter.
		$filter->execute($options);

		return $this;
	}

	/**
	 * Method to get the height of the image in pixels.
	 *
	 * @return  integer
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function getHeight()
	{
		return imagesy($this->getHandle());
	}

	/**
	 * Method to get the width of the image in pixels.
	 *
	 * @return  integer
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function getWidth()
	{
		return imagesx($this->getHandle());
	}

	/**
	 * Method to return the path
	 *
	 * @return	string
	 *
	 * @since	1.0
	 */
	public function getPath()
	{
		return $this->path;
	}

	/**
	 * Method to determine whether or not an image has been loaded into the object.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function isLoaded()
	{
		// Make sure the resource handle is valid.
		if (!is_resource($this->handle) || (get_resource_type($this->handle) != 'gd'))
		{
			return false;
		}

		return true;
	}

	/**
	 * Method to determine whether or not the image has transparency.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function isTransparent()
	{
		return imagecolortransparent($this->getHandle()) >= 0;
	}

	/**
	 * Method to load a file into the Image object as the resource.
	 *
	 * @param   string  $path  The filesystem path to load as an image.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 * @throws  \RuntimeException
	 */
	public function loadFile($path)
	{
		// Destroy the current image handle if it exists
		$this->destroy();

		// Make sure the file exists.
		if (!file_exists($path))
		{
			throw new \InvalidArgumentException('The image file does not exist.');
		}

		// Get the image properties.
		$properties = static::getImageFileProperties($path);

		// Attempt to load the image based on the MIME-Type
		switch ($properties->mime)
		{
			case 'image/gif':
				// Make sure the image type is supported.
				if (empty(static::$formats[IMAGETYPE_GIF]))
				{
					// @codeCoverageIgnoreStart
					$this->getLogger()->error('Attempting to load an image of unsupported type GIF.');

					throw new \RuntimeException('Attempting to load an image of unsupported type GIF.');

					// @codeCoverageIgnoreEnd
				}

				// Attempt to create the image handle.
				$handle = imagecreatefromgif($path);

				if (!is_resource($handle))
				{
					// @codeCoverageIgnoreStart
					throw new \RuntimeException('Unable to process GIF image.');

					// @codeCoverageIgnoreEnd
				}

				$this->handle = $handle;
				break;

			case 'image/jpeg':
				// Make sure the image type is supported.
				if (empty(static::$formats[IMAGETYPE_JPEG]))
				{
					// @codeCoverageIgnoreStart
					$this->getLogger()->error('Attempting to load an image of unsupported type JPG.');

					throw new \RuntimeException('Attempting to load an image of unsupported type JPG.');

					// @codeCoverageIgnoreEnd
				}

				// Attempt to create the image handle.
				$handle = imagecreatefromjpeg($path);

				if (!is_resource($handle))
				{
					// @codeCoverageIgnoreStart
					throw new \RuntimeException('Unable to process JPG image.');

					// @codeCoverageIgnoreEnd
				}

				$this->handle = $handle;
				break;

			case 'image/png':
				// Make sure the image type is supported.
				if (empty(static::$formats[IMAGETYPE_PNG]))
				{
					// @codeCoverageIgnoreStart
					$this->getLogger()->error('Attempting to load an image of unsupported type PNG.');

					throw new \RuntimeException('Attempting to load an image of unsupported type PNG.');

					// @codeCoverageIgnoreEnd
				}

				// Attempt to create the image handle.
				$handle = imagecreatefrompng($path);

				if (!is_resource($handle))
				{
					// @codeCoverageIgnoreStart
					throw new \RuntimeException('Unable to process PNG image.');

					// @codeCoverageIgnoreEnd
				}

				$this->handle = $handle;

				break;

			default:
				$this->getLogger()->error('Attempting to load an image of unsupported type ' . $properties->mime);

				throw new \InvalidArgumentException('Attempting to load an image of unsupported type ' . $properties->mime);
		}

		// Set the filesystem path to the source image.
		$this->path = $path;
	}

	/**
	 * Method to resize the current image.
	 *
	 * @param   mixed    $width        The width of the resized image in pixels or a percentage.
	 * @param   mixed    $height       The height of the resized image in pixels or a percentage.
	 * @param   boolean  $createNew    If true the current image will be cloned, resized and returned; else
	 *                                 the current image will be resized and returned.
	 * @param   integer  $scaleMethod  Which method to use for scaling
	 *
	 * @return  Image
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function resize($width, $height, $createNew = true, $scaleMethod = self::SCALE_INSIDE)
	{
		// Sanitize width.
		$width = $this->sanitizeWidth($width, $height);

		// Sanitize height.
		$height = $this->sanitizeHeight($height, $width);

		// Prepare the dimensions for the resize operation.
		$dimensions = $this->prepareDimensions($width, $height, $scaleMethod);

		// Instantiate offset.
		$offset = new \stdClass;
		$offset->x = $offset->y = 0;

		// Center image if needed and create the new truecolor image handle.
		if ($scaleMethod == self::SCALE_FIT)
		{
			// Get the offsets
			$offset->x	= round(($width - $dimensions->width) / 2);
			$offset->y	= round(($height - $dimensions->height) / 2);

			$handle = imagecreatetruecolor($width, $height);

			// Make image transparent, otherwise canvas outside initial image would default to black
			if (!$this->isTransparent())
			{
				$transparency = imagecolorallocatealpha($this->getHandle(), 0, 0, 0, 127);
				imagecolortransparent($this->getHandle(), $transparency);
			}
		}
		else
		{
			$handle = imagecreatetruecolor($dimensions->width, $dimensions->height);
		}

		// Allow transparency for the new image handle.
		imagealphablending($handle, false);
		imagesavealpha($handle, true);

		if ($this->isTransparent())
		{
			// Get the transparent color values for the current image.
			$rgba = imagecolorsforindex($this->getHandle(), imagecolortransparent($this->getHandle()));
			$color = imagecolorallocatealpha($handle, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);

			// Set the transparent color values for the new image.
			imagecolortransparent($handle, $color);
			imagefill($handle, 0, 0, $color);
		}

		if (!$this->generateBestQuality)
		{
			imagecopyresized(
				$handle,
				$this->getHandle(),
				$offset->x,
				$offset->y,
				0,
				0,
				$dimensions->width,
				$dimensions->height,
				$this->getWidth(),
				$this->getHeight()
			);
		}
		else
		{
			// Use resampling for better quality
			imagecopyresampled(
				$handle,
				$this->getHandle(),
				$offset->x,
				$offset->y,
				0,
				0,
				$dimensions->width,
				$dimensions->height,
				$this->getWidth(),
				$this->getHeight()
			);
		}

		// If we are resizing to a new image, create a new JImage object.
		if ($createNew)
		{
			// @codeCoverageIgnoreStart
			return new static($handle);

			// @codeCoverageIgnoreEnd
		}

		// Swap out the current handle for the new image handle.
		$this->destroy();

		$this->handle = $handle;

		return $this;
	}

	/**
	 * Method to crop an image after resizing it to maintain
	 * proportions without having to do all the set up work.
	 *
	 * @param   integer  $width      The desired width of the image in pixels or a percentage.
	 * @param   integer  $height     The desired height of the image in pixels or a percentage.
	 * @param   integer  $createNew  If true the current image will be cloned, resized, cropped and returned.
	 *
	 * @return  Image
	 *
	 * @since   1.0
	 */
	public function cropResize($width, $height, $createNew = true)
	{
		$width   = $this->sanitizeWidth($width, $height);
		$height  = $this->sanitizeHeight($height, $width);

		$resizewidth = $width;
		$resizeheight = $height;

		if (($this->getWidth() / $width) < ($this->getHeight() / $height))
		{
			$resizeheight = 0;
		}
		else
		{
			$resizewidth = 0;
		}

		return $this->resize($resizewidth, $resizeheight, $createNew)->crop($width, $height, null, null, false);
	}

	/**
	 * Method to rotate the current image.
	 *
	 * @param   mixed    $angle       The angle of rotation for the image
	 * @param   integer  $background  The background color to use when areas are added due to rotation
	 * @param   boolean  $createNew   If true the current image will be cloned, rotated and returned; else
	 *                                the current image will be rotated and returned.
	 *
	 * @return  Image
	 *
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function rotate($angle, $background = -1, $createNew = true)
	{
		// Sanitize input
		$angle = (float) $angle;

		// Create the new truecolor image handle.
		$handle = imagecreatetruecolor($this->getWidth(), $this->getHeight());

		// Make background transparent if no external background color is provided.
		if ($background == -1)
		{
			// Allow transparency for the new image handle.
			imagealphablending($handle, false);
			imagesavealpha($handle, true);

			$background = imagecolorallocatealpha($handle, 0, 0, 0, 127);
		}

		// Copy the image
		imagecopy($handle, $this->getHandle(), 0, 0, 0, 0, $this->getWidth(), $this->getHeight());

		// Rotate the image
		$handle = imagerotate($handle, $angle, $background);

		// If we are resizing to a new image, create a new Image object.
		if ($createNew)
		{
			// @codeCoverageIgnoreStart
			return new static($handle);

			// @codeCoverageIgnoreEnd
		}

		// Swap out the current handle for the new image handle.
		$this->destroy();

		$this->handle = $handle;

		return $this;
	}

	/**
	 * Method to flip the current image.
	 *
	 * @param   integer  $mode       The flip mode for flipping the image {@link http://php.net/imageflip#refsect1-function.imageflip-parameters}
	 * @param   boolean  $createNew  If true the current image will be cloned, flipped and returned; else
	 *                               the current image will be flipped and returned.
	 *
	 * @return  Image
	 *
	 * @since   1.2.0
	 * @throws  \LogicException
	 */
	public function flip($mode, $createNew = true)
	{
		// Create the new truecolor image handle.
		$handle = imagecreatetruecolor($this->getWidth(), $this->getHeight());

		// Copy the image
		imagecopy($handle, $this->getHandle(), 0, 0, 0, 0, $this->getWidth(), $this->getHeight());

		// Flip the image
		if (!imageflip($handle, $mode))
		{
			throw new \LogicException('Unable to flip the image.');
		}

		// If we are resizing to a new image, create a new Image object.
		if ($createNew)
		{
			// @codeCoverageIgnoreStart
			return new static($handle);

			// @codeCoverageIgnoreEnd
		}

		// Free the memory from the current handle
		$this->destroy();

		// Swap out the current handle for the new image handle.
		$this->handle = $handle;

		return $this;
	}

	/**
	 * Watermark the image
	 *
	 * @param   Image    $watermark     The Image object containing the watermark graphic
	 * @param   integer  $transparency  The transparency to use for the watermark graphic
	 * @param   integer  $bottomMargin  The margin from the bottom of this image
	 * @param   integer  $rightMargin   The margin from the right side of this image
	 *
	 * @return  Image
	 *
	 * @since   1.3.0
	 * @link    https://secure.php.net/manual/en/image.examples-watermark.php
	 */
	public function watermark(Image $watermark, $transparency = 50, $bottomMargin = 0, $rightMargin = 0)
	{
		imagecopymerge(
			$this->getHandle(),
			$watermark->getHandle(),
			$this->getWidth() - $watermark->getWidth() - $rightMargin,
			$this->getHeight() - $watermark->getHeight() - $bottomMargin,
			0,
			0,
			$watermark->getWidth(),
			$watermark->getHeight(),
			$transparency
		);

		return $this;
	}

	/**
	 * Method to write the current image out to a file or output directly.
	 *
	 * @param   mixed    $path     The filesystem path to save the image.
	 *                             When null, the raw image stream will be outputted directly.
	 * @param   integer  $type     The image type to save the file as.
	 * @param   array    $options  The image type options to use in saving the file.
	 *                             For PNG and JPEG formats use `quality` key to set compression level (0..9 and 0..100)
	 *
	 * @return  boolean
	 *
	 * @link    http://www.php.net/manual/image.constants.php
	 * @since   1.0
	 * @throws  \LogicException
	 */
	public function toFile($path, $type = IMAGETYPE_JPEG, array $options = array())
	{
		switch ($type)
		{
			case IMAGETYPE_GIF:
				return imagegif($this->getHandle(), $path);
				break;

			case IMAGETYPE_PNG:
				return imagepng($this->getHandle(), $path, (array_key_exists('quality', $options)) ? $options['quality'] : 0);
				break;
		}

		// Case IMAGETYPE_JPEG & default
		return imagejpeg($this->getHandle(), $path, (array_key_exists('quality', $options)) ? $options['quality'] : 100);
	}

	/**
	 * Method to get an image filter instance of a specified type.
	 *
	 * @param   string  $type  The image filter type to get.
	 *
	 * @return  ImageFilter
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function getFilterInstance($type)
	{
		// Sanitize the filter type.
		$type = strtolower(preg_replace('#[^A-Z0-9_]#i', '', $type));

		// Verify that the filter type exists.
		$className = 'Joomla\\Image\\Filter\\' . ucfirst($type);

		if (!class_exists($className))
		{
			$this->getLogger()->error('The ' . ucfirst($type) . ' image filter is not available.');

			throw new \RuntimeException('The ' . ucfirst($type) . ' image filter is not available.');
		}

		// Instantiate the filter object.
		$instance = new $className($this->getHandle());

		// Verify that the filter type is valid.
		if (!($instance instanceof ImageFilter))
		{
			// @codeCoverageIgnoreStart
			$this->getLogger()->error('The ' . ucfirst($type) . ' image filter is not valid.');

			throw new \RuntimeException('The ' . ucfirst($type) . ' image filter is not valid.');

			// @codeCoverageIgnoreEnd
		}

		return $instance;
	}

	/**
	 * Method to get the new dimensions for a resized image.
	 *
	 * @param   integer  $width        The width of the resized image in pixels.
	 * @param   integer  $height       The height of the resized image in pixels.
	 * @param   integer  $scaleMethod  The method to use for scaling
	 *
	 * @return  \stdClass
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException  If width, height or both given as zero
	 */
	protected function prepareDimensions($width, $height, $scaleMethod)
	{
		// Instantiate variables.
		$dimensions = new \stdClass;

		switch ($scaleMethod)
		{
			case self::SCALE_FILL:
				$dimensions->width = (int) round($width);
				$dimensions->height = (int) round($height);
				break;

			case self::SCALE_INSIDE:
			case self::SCALE_OUTSIDE:
			case self::SCALE_FIT:
				$rx = ($width > 0) ? ($this->getWidth() / $width) : 0;
				$ry = ($height > 0) ? ($this->getHeight() / $height) : 0;

				if ($scaleMethod != self::SCALE_OUTSIDE)
				{
					$ratio = max($rx, $ry);
				}
				else
				{
					$ratio = min($rx, $ry);
				}

				$dimensions->width = (int) round($this->getWidth() / $ratio);
				$dimensions->height = (int) round($this->getHeight() / $ratio);
				break;

			default:
				throw new \InvalidArgumentException('Invalid scale method.');
				break;
		}

		return $dimensions;
	}

	/**
	 * Method to sanitize a height value.
	 *
	 * @param   mixed  $height  The input height value to sanitize.
	 * @param   mixed  $width   The input width value for reference.
	 *
	 * @return  integer
	 *
	 * @since   1.0
	 */
	protected function sanitizeHeight($height, $width)
	{
		// If no height was given we will assume it is a square and use the width.
		$height = ($height === null) ? $width : $height;

		// If we were given a percentage, calculate the integer value.
		if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $height))
		{
			$height = (int) round($this->getHeight() * (float) str_replace('%', '', $height) / 100);
		}
		else
		// Else do some rounding so we come out with a sane integer value.
		{
			$height = (int) round((float) $height);
		}

		return $height;
	}

	/**
	 * Method to sanitize an offset value like left or top.
	 *
	 * @param   mixed  $offset  An offset value.
	 *
	 * @return  integer
	 *
	 * @since   1.0
	 */
	protected function sanitizeOffset($offset)
	{
		return (int) round((float) $offset);
	}

	/**
	 * Method to sanitize a width value.
	 *
	 * @param   mixed  $width   The input width value to sanitize.
	 * @param   mixed  $height  The input height value for reference.
	 *
	 * @return  integer
	 *
	 * @since   1.0
	 */
	protected function sanitizeWidth($width, $height)
	{
		// If no width was given we will assume it is a square and use the height.
		$width = ($width === null) ? $height : $width;

		// If we were given a percentage, calculate the integer value.
		if (preg_match('/^[0-9]+(\.[0-9]+)?\%$/', $width))
		{
			$width = (int) round($this->getWidth() * (float) str_replace('%', '', $width) / 100);
		}
		else
		// Else do some rounding so we come out with a sane integer value.
		{
			$width = (int) round((float) $width);
		}

		return $width;
	}

	/**
	 * Method to destroy an image handle and
	 * free the memory associated with the handle
	 *
	 * @return  boolean  True on success, false on failure or if no image is loaded
	 *
	 * @since   1.0
	 */
	public function destroy()
	{
		if ($this->isLoaded())
		{
			return imagedestroy($this->getHandle());
		}

		return false;
	}

	/**
	 * Method to call the destroy() method one last time
	 * to free any memory when the object is unset
	 *
	 * @see    Image::destroy()
	 * @since  1.0
	 */
	public function __destruct()
	{
		$this->destroy();
	}

	/**
	 * Method for set option of generate thumbnail method
	 *
	 * @param   boolean  $quality  True for best quality. False for best speed.
	 *
	 * @return  void
	 *
	 * @since   1.4.0
	 */
	public function setThumbnailGenerate($quality = true)
	{
		$this->generateBestQuality = (boolean) $quality;
	}
}
vendor/joomla/image/src/Filter/Brightness.php000064400000002206152177723700015254 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;
use InvalidArgumentException;

/**
 * Image Filter class adjust the brightness of an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Brightness extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  InvalidArgumentException
	 */
	public function execute(array $options = array())
	{
		// Validate that the brightness value exists and is an integer.
		if (!isset($options[IMG_FILTER_BRIGHTNESS]) || !is_int($options[IMG_FILTER_BRIGHTNESS]))
		{
			throw new InvalidArgumentException('No valid brightness value was given.  Expected integer.');
		}

		// Perform the brightness filter.
		imagefilter($this->handle, IMG_FILTER_BRIGHTNESS, $options[IMG_FILTER_BRIGHTNESS]);
	}
}
vendor/joomla/image/src/Filter/Emboss.php000064400000001376152177723700014403 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;

/**
 * Image Filter class to emboss an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Emboss extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute(array $options = array())
	{
		// Perform the emboss filter.
		imagefilter($this->handle, IMG_FILTER_EMBOSS);
	}
}
vendor/joomla/image/src/Filter/Sketchy.php000064400000001425152177723700014560 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;

/**
 * Image Filter class to make an image appear "sketchy".
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Sketchy extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute(array $options = array())
	{
		// Perform the sketchy filter.
		imagefilter($this->handle, IMG_FILTER_MEAN_REMOVAL);
	}
}
vendor/joomla/image/src/Filter/Backgroundfill.php000064400000006650152177723700016101 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;
use InvalidArgumentException;

/**
 * Image Filter class fill background with color;
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Backgroundfill extends ImageFilter
{
	/**
	 * Method to apply a background color to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *                           color  Background matte color
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  InvalidArgumentException
	 */
	public function execute(array $options = array())
	{
		// Validate that the color value exists and is an integer.
		if (!isset($options['color']))
		{
			throw new InvalidArgumentException('No color value was given. Expected string or array.');
		}

		$colorCode = (!empty($options['color'])) ? $options['color'] : null;

		// Get resource dimensions
		$width = imagesX($this->handle);
		$height = imagesY($this->handle);

		// Sanitize color
		$rgba = $this->sanitizeColor($colorCode);

		// Enforce alpha on source image
		if (imageIsTrueColor($this->handle))
		{
			imageAlphaBlending($this->handle, false);
			imageSaveAlpha($this->handle, true);
		}

		// Create background
		$bg = imageCreateTruecolor($width, $height);
		imageSaveAlpha($bg, empty($rgba['alpha']));

		// Allocate background color.
		$color = imageColorAllocateAlpha($bg, $rgba['red'], $rgba['green'], $rgba['blue'], $rgba['alpha']);

		// Fill background
		imageFill($bg, 0, 0, $color);

		// Apply image over background
		imageCopy($bg, $this->handle, 0, 0, 0, 0, $width, $height);

		// Move flattened result onto curent handle.
		// If handle was palette-based, it'll stay like that.
		imageCopy($this->handle, $bg, 0, 0, 0, 0, $width, $height);

		// Free up memory
		imageDestroy($bg);

		return;
	}

	/**
	 * Method to sanitize color values
	 * and/or convert to an array
	 *
	 * @param   mixed  $input  Associative array of colors and alpha,
	 *                         or hex RGBA string when alpha FF is opaque.
	 *                         Defaults to black and opaque alpha
	 *
	 * @return  array  Associative array of red, green, blue and alpha
	 *
	 * @since   1.0
	 *
	 * @note    '#FF0000FF' returns an array with alpha of 0 (opaque)
	 */
	protected function sanitizeColor($input)
	{
		// Construct default values
		$colors = array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0);

		// Make sure all values are in
		if (is_array($input))
		{
			$colors = array_merge($colors, $input);
		}
		elseif (is_string($input))
		// Convert RGBA 6-9 char string
		{
			$hex = ltrim($input, '#');

			$hexValues = array(
				'red' => substr($hex, 0, 2),
				'green' => substr($hex, 2, 2),
				'blue' => substr($hex, 4, 2),
				'alpha' => substr($hex, 6, 2),
			);

			$colors = array_map('hexdec', $hexValues);

			// Convert Alpha to 0..127 when provided
			if (strlen($hex) > 6)
			{
				$colors['alpha'] = floor((255 - $colors['alpha']) / 2);
			}
		}
		else
		// Cannot sanitize such type
		{
			return $colors;
		}

		// Make sure each value is within the allowed range
		foreach ($colors as &$value)
		{
			$value = max(0, min(255, (int) $value));
		}

		$colors['alpha'] = min(127, $colors['alpha']);

		return $colors;
	}
}
vendor/joomla/image/src/Filter/Grayscale.php000064400000001427152177723700015062 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;

/**
 * Image Filter class to transform an image to grayscale.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Grayscale extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute(array $options = array())
	{
		// Perform the grayscale filter.
		imagefilter($this->handle, IMG_FILTER_GRAYSCALE);
	}
}
vendor/joomla/image/src/Filter/Smooth.php000064400000002157152177723700014422 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;
use InvalidArgumentException;

/**
 * Image Filter class adjust the smoothness of an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Smooth extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  InvalidArgumentException
	 */
	public function execute(array $options = array())
	{
		// Validate that the smoothing value exists and is an integer.
		if (!isset($options[IMG_FILTER_SMOOTH]) || !is_int($options[IMG_FILTER_SMOOTH]))
		{
			throw new InvalidArgumentException('No valid smoothing value was given.  Expected integer.');
		}

		// Perform the smoothing filter.
		imagefilter($this->handle, IMG_FILTER_SMOOTH, $options[IMG_FILTER_SMOOTH]);
	}
}
vendor/joomla/image/src/Filter/Negate.php000064400000001416152177723700014351 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;

/**
 * Image Filter class to negate the colors of an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Negate extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute(array $options = array())
	{
		// Perform the negative filter.
		imagefilter($this->handle, IMG_FILTER_NEGATE);
	}
}
vendor/joomla/image/src/Filter/Contrast.php000064400000002164152177723700014744 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;
use InvalidArgumentException;

/**
 * Image Filter class adjust the contrast of an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Contrast extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  InvalidArgumentException
	 */
	public function execute(array $options = array())
	{
		// Validate that the contrast value exists and is an integer.
		if (!isset($options[IMG_FILTER_CONTRAST]) || !is_int($options[IMG_FILTER_CONTRAST]))
		{
			throw new InvalidArgumentException('No valid contrast value was given.  Expected integer.');
		}

		// Perform the contrast filter.
		imagefilter($this->handle, IMG_FILTER_CONTRAST, $options[IMG_FILTER_CONTRAST]);
	}
}
vendor/joomla/image/src/Filter/Edgedetect.php000064400000001444152177723700015204 0ustar00<?php
/**
 * Part of the Joomla Framework Image Package
 *
 * @copyright  Copyright (C) 2005 - 2018 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Image\Filter;

use Joomla\Image\ImageFilter;

/**
 * Image Filter class to add an edge detect effect to an image.
 *
 * @since       1.0
 * @deprecated  The joomla/image package is deprecated
 */
class Edgedetect extends ImageFilter
{
	/**
	 * Method to apply a filter to an image resource.
	 *
	 * @param   array  $options  An array of options for the filter.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute(array $options = array())
	{
		// Perform the edge detection filter.
		imagefilter($this->handle, IMG_FILTER_EDGEDETECT);
	}
}
vendor/joomla/session/Joomla/Session/LICENSE000064400000042630152177723700014676 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/session/Joomla/Session/Storage/Memcache.php000064400000003554152177723700017512 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * Memcache session storage handler for PHP
 *
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class Memcache extends Storage
{
	/**
	 * Container for server data
	 *
	 * @var    array
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $_servers = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('Memcache Extension is not available', 404);
		}

		// This will be an array of loveliness
		// @todo: multiple servers
		$this->_servers = array(
			array(
				'host' => isset($options['memcache_server_host']) ? $options['memcache_server_host'] : 'localhost',
				'port' => isset($options['memcache_server_port']) ? $options['memcache_server_port'] : 11211,
			),
		);

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function register()
	{
		if (!headers_sent())
		{
			ini_set('session.save_path', $this->_servers[0]['host'] . ':' . $this->_servers[0]['port']);
			ini_set('session.save_handler', 'memcache');
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('memcache') && class_exists('Memcache');
	}
}
vendor/joomla/session/Joomla/Session/Storage/Xcache.php000064400000004421152177723700017175 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * XCache session storage handler
 *
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class Xcache extends Storage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('XCache Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		// Check if id exists
		if (!xcache_isset($sess_id))
		{
			return '';
		}

		return (string) xcache_get($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return xcache_set($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		if (!xcache_isset($sess_id))
		{
			return true;
		}

		return xcache_unset($sess_id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('xcache');
	}
}
vendor/joomla/session/Joomla/Session/Storage/None.php000064400000001331152177723700016676 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * Default PHP configured session handler for Joomla!
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class None extends Storage
{
	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function register()
	{
	}
}
vendor/joomla/session/Joomla/Session/Storage/Wincache.php000064400000002605152177723700017525 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * WINCACHE session storage handler for PHP
 *
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class Wincache extends Storage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('Wincache Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function register()
	{
		if (!headers_sent())
		{
			ini_set('session.save_handler', 'wincache');
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('wincache') && \function_exists('wincache_ucache_get') && !strcmp(ini_get('wincache.ucenabled'), '1');
	}
}
vendor/joomla/session/Joomla/Session/Storage/Apc.php000064400000004332152177723700016506 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * APC session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed.
 */
class Apc extends Storage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('APC Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		return (string) apc_fetch($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return apc_store($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		return apc_delete($sess_id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('apc');
	}
}
vendor/joomla/session/Joomla/Session/Storage/Apcu.php000064400000004205152177723700016672 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * APCU session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.4.0
 * @deprecated  2.0  The Storage class chain will be removed.
 */
class Apcu extends Storage
{
	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters
	 *
	 * @since   1.4.0
	 * @throws  \RuntimeException
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('APCU Extension is not available', 404);
		}

		parent::__construct($options);
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.4.0
	 */
	public function read($id)
	{
		$sess_id = 'sess_' . $id;

		return (string) apcu_fetch($sess_id);
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.4.0
	 */
	public function write($id, $session_data)
	{
		$sess_id = 'sess_' . $id;

		return apcu_store($sess_id, $session_data, ini_get('session.gc_maxlifetime'));
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.4.0
	 */
	public function destroy($id)
	{
		$sess_id = 'sess_' . $id;

		return apcu_delete($sess_id);
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.4.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('apcu');
	}
}
vendor/joomla/session/Joomla/Session/Storage/Memcached.php000064400000004147152177723700017655 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Session\Storage;

/**
 * Memcached session storage handler for PHP
 *
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class Memcached extends Storage
{
	/**
	 * Container for server data
	 *
	 * @var    array
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $_servers = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (!self::isSupported())
		{
			throw new \RuntimeException('Memcached Extension is not available', 404);
		}

		// This will be an array of loveliness
		// @todo: multiple servers
		$this->_servers = array(
			array(
				'host' => isset($options['memcache_server_host']) ? $options['memcache_server_host'] : 'localhost',
				'port' => isset($options['memcache_server_port']) ? $options['memcache_server_port'] : 11211,
			),
		);

		// Only construct parent AFTER host and port are sent, otherwise when register is called this will fail.
		parent::__construct($options);
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function register()
	{
		if (!headers_sent())
		{
			ini_set('session.save_path', $this->_servers[0]['host'] . ':' . $this->_servers[0]['port']);
			ini_set('session.save_handler', 'memcached');
		}
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		/*
		 * GAE and HHVM have both had instances where Memcached the class was defined but no extension was loaded.
		 * If the class is there, we can assume it works.
		 */
		return class_exists('Memcached');
	}
}
vendor/joomla/session/Joomla/Session/Storage/Database.php000064400000010500152177723700017501 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session\Storage;

use Joomla\Database\DatabaseDriver;
use Joomla\Session\Storage;

/**
 * Database session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed
 */
class Database extends Storage
{
	/**
	 * The DatabaseDriver to use when querying.
	 *
	 * @var    DatabaseDriver
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $db;

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters. A `dbo` options is required.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		if (isset($options['db']) && ($options['db'] instanceof DatabaseDriver))
		{
			parent::__construct($options);
			$this->db = $options['db'];
		}
		else
		{
			throw new \RuntimeException(
				sprintf('The %s storage engine requires a `db` option that is an instance of Joomla\\Database\\DatabaseDriver.', __CLASS__)
			);
		}
	}

	/**
	 * Read the data for a particular session identifier from the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function read($id)
	{
		try
		{
			// Get the session data from the database table.
			$query = $this->db->getQuery(true);
			$query->select($this->db->quoteName('data'))
				->from($this->db->quoteName('#__session'))
				->where($this->db->quoteName('session_id') . ' = ' . $this->db->quote($id));

			$this->db->setQuery($query);

			return (string) $this->db->loadResult();
		}
		catch (\Exception $e)
		{
			return false;
		}
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id    The session identifier.
	 * @param   string  $data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function write($id, $data)
	{
		try
		{
			$query = $this->db->getQuery(true);
			$query->update($this->db->quoteName('#__session'))
				->set($this->db->quoteName('data') . ' = ' . $this->db->quote($data))
				->set($this->db->quoteName('time') . ' = ' . $this->db->quote((int) time()))
				->where($this->db->quoteName('session_id') . ' = ' . $this->db->quote($id));

			// Try to update the session data in the database table.
			$this->db->setQuery($query);

			if (!$this->db->execute())
			{
				return false;
			}

			// Since $this->db->execute did not throw an exception the query was successful.
			// Either the data changed, or the data was identical. In either case we are done.

			return true;
		}
		catch (\Exception $e)
		{
			return false;
		}
	}

	/**
	 * Destroy the data for a particular session identifier in the SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function destroy($id)
	{
		try
		{
			$query = $this->db->getQuery(true);
			$query->delete($this->db->quoteName('#__session'))
				->where($this->db->quoteName('session_id') . ' = ' . $this->db->quote($id));

			// Remove a session from the database.
			$this->db->setQuery($query);

			return (boolean) $this->db->execute();
		}
		catch (\Exception $e)
		{
			return false;
		}
	}

	/**
	 * Garbage collect stale sessions from the SessionHandler backend.
	 *
	 * @param   integer  $lifetime  The maximum age of a session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function gc($lifetime = 1440)
	{
		// Determine the timestamp threshold with which to purge old sessions.
		$past = time() - $lifetime;

		try
		{
			$query = $this->db->getQuery(true);
			$query->delete($this->db->quoteName('#__session'))
				->where($this->db->quoteName('time') . ' < ' . $this->db->quote((int) $past));

			// Remove expired sessions from the database.
			$this->db->setQuery($query);

			return (boolean) $this->db->execute();
		}
		catch (\Exception $e)
		{
			return false;
		}
	}
}
vendor/joomla/session/Joomla/Session/Session.php000064400000055670152177723700016035 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session;

use Joomla\Event\DispatcherInterface;
use Joomla\Input\Input;

/**
 * Class for managing HTTP sessions
 *
 * Provides access to session-state values as well as session-level
 * settings and lifetime management methods.
 * Based on the standard PHP session handling mechanism it provides
 * more advanced features such as expire timeouts.
 *
 * @since  1.0
 */
class Session implements \IteratorAggregate
{
	/**
	 * Internal state.
	 * One of 'inactive'|'active'|'expired'|'destroyed'|'error'
	 *
	 * @var    string
	 * @see    getState()
	 * @since  1.0
	 */
	protected $state = 'inactive';

	/**
	 * Maximum age of unused session in minutes
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $expire = 15;

	/**
	 * The session store object.
	 *
	 * @var    Storage
	 * @since  1.0
	 */
	protected $store;

	/**
	 * Security policy.
	 * List of checks that will be done.
	 *
	 * Default values:
	 * - fix_browser
	 * - fix_adress
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $security = array('fix_browser');

	/**
	 * Force cookies to be SSL only
	 * Default  false
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	protected $force_ssl = false;

	/**
	 * The domain to use when setting cookies.
	 *
	 * @var    mixed
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $cookie_domain;

	/**
	 * The path to use when setting cookies.
	 *
	 * @var    mixed
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $cookie_path;

	/**
	 * The configuration of the HttpOnly cookie.
	 *
	 * @var    mixed
	 * @since  1.5.0
	 * @deprecated  2.0
	 */
	protected $cookie_httponly = true;

	/**
	 * The configuration of the SameSite cookie.
	 *
	 * @var    mixed
	 * @since  1.5.0
	 * @deprecated  2.0
	 */
	protected $cookie_samesite;

	/**
	 * Session instances container.
	 *
	 * @var    Session
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected static $instance;

	/**
	 * The type of storage for the session.
	 *
	 * @var    string
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected $storeName;

	/**
	 * Holds the Input object
	 *
	 * @var    Input
	 * @since  1.0
	 */
	private $input;

	/**
	 * Holds the Dispatcher object
	 *
	 * @var    DispatcherInterface
	 * @since  1.0
	 */
	private $dispatcher;

	/**
	 * Constructor
	 *
	 * @param   string  $store    The type of storage for the session.
	 * @param   array   $options  Optional parameters
	 *
	 * @since   1.0
	 */
	public function __construct($store = 'none', array $options = array())
	{
		// Need to destroy any existing sessions started with session.auto_start
		if (session_id())
		{
			session_unset();
			session_destroy();
		}

		// Disable transparent sid support
		ini_set('session.use_trans_sid', '0');

		// Only allow the session ID to come from cookies and nothing else.
		ini_set('session.use_only_cookies', '1');

		// Create handler
		$this->store = Storage::getInstance($store, $options);

		$this->storeName = $store;

		// Set options
		$this->_setOptions($options);

		$this->_setCookieParams();

		$this->setState('inactive');
	}

	/**
	 * Magic method to get read-only access to properties.
	 *
	 * @param   string  $name  Name of property to retrieve
	 *
	 * @return  mixed   The value of the property
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use get methods for non-deprecated properties
	 */
	public function __get($name)
	{
		if ($name === 'storeName' || $name === 'state' || $name === 'expire')
		{
			return $this->$name;
		}
	}

	/**
	 * Returns the global Session object, only creating it
	 * if it doesn't already exist.
	 *
	 * @param   string  $handler  The type of session handler.
	 * @param   array   $options  An array of configuration options (for new sessions only).
	 *
	 * @return  Session  The Session object.
	 *
	 * @since   1.0
	 * @deprecated  2.0  A singleton object store will no longer be supported
	 */
	public static function getInstance($handler, array $options = array())
	{
		if (!\is_object(self::$instance))
		{
			self::$instance = new self($handler, $options);
		}

		return self::$instance;
	}

	/**
	 * Get current state of session
	 *
	 * @return  string  The session state
	 *
	 * @since   1.0
	 */
	public function getState()
	{
		return $this->state;
	}

	/**
	 * Get expiration time in minutes
	 *
	 * @return  integer  The session expiration time in minutes
	 *
	 * @since   1.0
	 */
	public function getExpire()
	{
		return $this->expire;
	}

	/**
	 * Get a session token, if a token isn't set yet one will be generated.
	 *
	 * Tokens are used to secure forms from spamming attacks. Once a token
	 * has been generated the system will check the post request to see if
	 * it is present, if not it will invalidate the session.
	 *
	 * @param   boolean  $forceNew  If true, force a new token to be created
	 *
	 * @return  string  The session token
	 *
	 * @since   1.0
	 */
	public function getToken($forceNew = false)
	{
		$token = $this->get('session.token');

		// Create a token
		if ($token === null || $forceNew)
		{
			$token = $this->_createToken();
			$this->set('session.token', $token);
		}

		return $token;
	}

	/**
	 * Method to determine if a token exists in the session. If not the
	 * session will be set to expired
	 *
	 * @param   string   $tCheck       Hashed token to be verified
	 * @param   boolean  $forceExpire  If true, expires the session
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function hasToken($tCheck, $forceExpire = true)
	{
		// Check if a token exists in the session
		$tStored = $this->get('session.token');

		// Check token
		if (($tStored !== $tCheck))
		{
			if ($forceExpire)
			{
				$this->setState('expired');
			}

			return false;
		}

		return true;
	}

	/**
	 * Retrieve an external iterator.
	 *
	 * @return  \ArrayIterator  Return an ArrayIterator of $_SESSION.
	 *
	 * @since   1.0
	 */
	public function getIterator()
	{
		return new \ArrayIterator($_SESSION);
	}

	/**
	 * Get session name
	 *
	 * @return  string  The session name
	 *
	 * @since   1.0
	 */
	public function getName()
	{
		if ($this->getState() === 'destroyed')
		{
			// @codingStandardsIgnoreLine
			return;
		}

		return session_name();
	}

	/**
	 * Get session id
	 *
	 * @return  string  The session name
	 *
	 * @since   1.0
	 */
	public function getId()
	{
		if ($this->getState() === 'destroyed')
		{
			// @codingStandardsIgnoreLine
			return;
		}

		return session_id();
	}

	/**
	 * Get the session handlers
	 *
	 * @return  array  An array of available session handlers
	 *
	 * @since   1.0
	 * @deprecated  2.0  The Storage class chain will be removed
	 */
	public static function getStores()
	{
		$connectors = array();

		// Get an iterator and loop trough the driver classes.
		$iterator = new \DirectoryIterator(__DIR__ . '/Storage');

		foreach ($iterator as $file)
		{
			$fileName = $file->getFilename();

			// Only load for php files.
			if (!$file->isFile() || $file->getExtension() != 'php')
			{
				continue;
			}

			// Derive the class name from the type.
			$class = str_ireplace('.php', '', '\\Joomla\\Session\\Storage\\' . ucfirst(trim($fileName)));

			// If the class doesn't exist we have nothing left to do but look at the next type. We did our best.
			if (!class_exists($class))
			{
				continue;
			}

			// Sweet!  Our class exists, so now we just need to know if it passes its test method.
			if ($class::isSupported())
			{
				// Connector names should not have file extensions.
				$connectors[] = str_ireplace('.php', '', $fileName);
			}
		}

		return $connectors;
	}

	/**
	 * Shorthand to check if the session is active
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function isActive()
	{
		return (bool) ($this->getState() == 'active');
	}

	/**
	 * Check whether this session is currently created
	 *
	 * @return  boolean  True on success.
	 *
	 * @since   1.0
	 */
	public function isNew()
	{
		$counter = $this->get('session.counter');

		return (bool) ($counter === 1);
	}

	/**
	 * Check whether this session is currently created
	 *
	 * @param   Input                $input       Input object for the session to use.
	 * @param   DispatcherInterface  $dispatcher  Dispatcher object for the session to use.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0  In 2.0 the DispatcherInterface should be injected via the object constructor
	 */
	public function initialise(Input $input, DispatcherInterface $dispatcher = null)
	{
		$this->input      = $input;
		$this->dispatcher = $dispatcher;
	}

	/**
	 * Get data from the session store
	 *
	 * @param   string  $name       Name of a variable
	 * @param   mixed   $default    Default value of a variable if not set
	 * @param   string  $namespace  Namespace to use, default to 'default' {@deprecated 2.0 Namespace support will be removed.}
	 *
	 * @return  mixed  Value of a variable
	 *
	 * @since   1.0
	 */
	public function get($name, $default = null, $namespace = 'default')
	{
		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active' && $this->getState() !== 'expired')
		{
			return;
		}

		if (isset($_SESSION[$namespace][$name]))
		{
			return $_SESSION[$namespace][$name];
		}

		return $default;
	}

	/**
	 * Set data into the session store.
	 *
	 * @param   string  $name       Name of a variable.
	 * @param   mixed   $value      Value of a variable.
	 * @param   string  $namespace  Namespace to use, default to 'default' {@deprecated 2.0 Namespace support will be removed.}
	 *
	 * @return  mixed  Old value of a variable.
	 *
	 * @since   1.0
	 */
	public function set($name, $value = null, $namespace = 'default')
	{
		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			return;
		}

		$old = isset($_SESSION[$namespace][$name]) ? $_SESSION[$namespace][$name] : null;

		if ($value === null)
		{
			unset($_SESSION[$namespace][$name]);
		}
		else
		{
			$_SESSION[$namespace][$name] = $value;
		}

		return $old;
	}

	/**
	 * Check whether data exists in the session store
	 *
	 * @param   string  $name       Name of variable
	 * @param   string  $namespace  Namespace to use, default to 'default' {@deprecated 2.0 Namespace support will be removed.}
	 *
	 * @return  boolean  True if the variable exists
	 *
	 * @since   1.0
	 */
	public function has($name, $namespace = 'default')
	{
		// Add prefix to namespace to avoid collisions.
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			// @codingStandardsIgnoreLine
			return;
		}

		return isset($_SESSION[$namespace][$name]);
	}

	/**
	 * Unset data from the session store
	 *
	 * @param   string  $name       Name of variable
	 * @param   string  $namespace  Namespace to use, default to 'default' {@deprecated 2.0 Namespace support will be removed.}
	 *
	 * @return  mixed   The value from session or NULL if not set
	 *
	 * @since   1.0
	 */
	public function clear($name, $namespace = 'default')
	{
		// Add prefix to namespace to avoid collisions
		$namespace = '__' . $namespace;

		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return;
		}

		$value = null;

		if (isset($_SESSION[$namespace][$name]))
		{
			$value = $_SESSION[$namespace][$name];
			unset($_SESSION[$namespace][$name]);
		}

		return $value;
	}

	/**
	 * Start a session.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function start()
	{
		if ($this->getState() === 'active')
		{
			return;
		}

		$this->_start();

		$this->setState('active');

		// Initialise the session
		$this->_setCounter();
		$this->_setTimers();

		// Perform security checks
		$this->_validate();

		if ($this->dispatcher instanceof DispatcherInterface)
		{
			$this->dispatcher->triggerEvent('onAfterSessionStart');
		}
	}

	/**
	 * Start a session.
	 *
	 * Creates a session (or resumes the current one based on the state of the session)
	 *
	 * @return  boolean  true on success
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	protected function _start()
	{
		// Start session if not started
		if ($this->getState() === 'restart')
		{
			session_regenerate_id(true);
		}
		else
		{
			$session_name = session_name();

			// Get the Joomla\Input\Cookie object
			$cookie = $this->input->cookie;

			if ($cookie->get($session_name) === null)
			{
				$session_clean = $this->input->get($session_name, false, 'string');

				if ($session_clean)
				{
					session_id($session_clean);
					$cookie->set($session_name, '', array('expires' => 1));
				}
			}
		}

		/**
		 * Write and Close handlers are called after destructing objects since PHP 5.0.5.
		 * Thus destructors can use sessions but session handler can't use objects.
		 * So we are moving session closure before destructing objects.
		 *
		 * Replace with session_register_shutdown() when dropping compatibility with PHP 5.3
		 */
		register_shutdown_function('session_write_close');

		session_cache_limiter('none');
		session_start();

		return true;
	}

	/**
	 * Frees all session variables and destroys all data registered to a session
	 *
	 * This method resets the $_SESSION variable and destroys all of the data associated
	 * with the current session in its storage (file or DB). It forces new session to be
	 * started after this method is called. It does not unset the session cookie.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     session_destroy()
	 * @see     session_unset()
	 * @since   1.0
	 */
	public function destroy()
	{
		// Session was already destroyed
		if ($this->getState() === 'destroyed')
		{
			return true;
		}

		/*
		 * In order to kill the session altogether, such as to log the user out, the session id
		 * must also be unset. If a cookie is used to propagate the session id (default behavior),
		 * then the session cookie must be deleted.
		 */
		$cookie = session_get_cookie_params();

		$cookieOptions = array(
			'expires'  => 1,
			'path'     => $cookie['path'],
			'domain'   => $cookie['domain'],
			'secure'   => $cookie['secure'],
			'httponly' => true,
		);

		if (isset($cookie['samesite']))
		{
			$cookieOptions['samesite'] = $cookie['samesite'];
		}

		$this->input->cookie->set($this->getName(), '', $cookieOptions);

		session_unset();
		session_destroy();

		$this->setState('destroyed');

		return true;
	}

	/**
	 * Restart an expired or locked session.
	 *
	 * @return  boolean  True on success
	 *
	 * @see     destroy
	 * @since   1.0
	 */
	public function restart()
	{
		$this->destroy();

		if ($this->getState() !== 'destroyed')
		{
			// @TODO :: generated error here
			return false;
		}

		// Re-register the session handler after a session has been destroyed, to avoid PHP bug
		$this->store->register();

		$this->setState('restart');

		// Regenerate session id
		session_regenerate_id(true);
		$this->_start();
		$this->setState('active');

		$this->_validate();
		$this->_setCounter();

		return true;
	}

	/**
	 * Create a new session and copy variables from the old one
	 *
	 * @return  boolean $result true on success
	 *
	 * @since   1.0
	 */
	public function fork()
	{
		if ($this->getState() !== 'active')
		{
			// @TODO :: generated error here
			return false;
		}

		// Keep session config
		$cookie = session_get_cookie_params();

		// Kill session
		session_destroy();

		// Re-register the session store after a session has been destroyed, to avoid PHP bug
		$this->store->register();

		// Restore config
		if (version_compare(PHP_VERSION, '7.3', '>='))
		{
			session_set_cookie_params($cookie);
		}
		else
		{
			session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
		}

		// Restart session with new id
		session_regenerate_id(true);
		session_start();

		return true;
	}

	/**
	 * Writes session data and ends session
	 *
	 * Session data is usually stored after your script terminated without the need
	 * to call JSession::close(), but as session data is locked to prevent concurrent
	 * writes only one script may operate on a session at any time. When using
	 * framesets together with sessions you will experience the frames loading one
	 * by one due to this locking. You can reduce the time needed to load all the
	 * frames by ending the session as soon as all changes to session variables are
	 * done.
	 *
	 * @return  void
	 *
	 * @see     session_write_close()
	 * @since   1.0
	 */
	public function close()
	{
		session_write_close();
	}

	/**
	 * Set the session expiration
	 *
	 * @param   integer  $expire  Maximum age of unused session in minutes
	 *
	 * @return  $this
	 *
	 * @since   1.3.0
	 */
	protected function setExpire($expire)
	{
		$this->expire = $expire;

		return $this;
	}

	/**
	 * Set the session state
	 *
	 * @param   string  $state  Internal state
	 *
	 * @return  $this
	 *
	 * @since   1.3.0
	 */
	protected function setState($state)
	{
		$this->state = $state;

		return $this;
	}

	/**
	 * Set session cookie parameters
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	protected function _setCookieParams()
	{
		$cookie = session_get_cookie_params();

		if ($this->force_ssl)
		{
			$cookie['secure'] = true;
		}

		if ($this->cookie_domain)
		{
			$cookie['domain'] = $this->cookie_domain;
		}

		if ($this->cookie_path)
		{
			$cookie['path'] = $this->cookie_path;
		}

		$cookie['httponly'] = $this->cookie_httponly;

		if ($this->cookie_samesite)
		{
			$cookie['samesite'] = $this->cookie_samesite;
		}

		if (version_compare(PHP_VERSION, '7.3', '>='))
		{
			session_set_cookie_params($cookie);
		}
		else
		{
			session_set_cookie_params($cookie['lifetime'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly']);
		}
	}

	/**
	 * Create a token-string
	 *
	 * @param   integer  $length  Length of string
	 *
	 * @return  string  Generated token
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use createToken instead
	 */
	protected function _createToken($length = 32)
	{
		return $this->createToken($length);
	}

	/**
	 * Create a token-string
	 *
	 * @param   integer  $length  Length of string
	 *
	 * @return  string  Generated token
	 *
	 * @since   1.3.1
	 */
	protected function createToken($length = 32)
	{
		return bin2hex(random_bytes($length));
	}

	/**
	 * Set counter of session usage
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use setCounter instead
	 */
	protected function _setCounter()
	{
		return $this->setCounter();
	}

	/**
	 * Set counter of session usage
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.3.0
	 */
	protected function setCounter()
	{
		$counter = $this->get('session.counter', 0);
		++$counter;

		$this->set('session.counter', $counter);

		return true;
	}

	/**
	 * Set the session timers
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use setTimers instead
	 */
	protected function _setTimers()
	{
		return $this->setTimers();
	}

	/**
	 * Set the session timers
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.3.0
	 */
	protected function setTimers()
	{
		if (!$this->has('session.timer.start'))
		{
			$start = time();

			$this->set('session.timer.start', $start);
			$this->set('session.timer.last', $start);
			$this->set('session.timer.now', $start);
		}

		$this->set('session.timer.last', $this->get('session.timer.now'));
		$this->set('session.timer.now', time());

		return true;
	}

	/**
	 * Set additional session options
	 *
	 * @param   array  $options  List of parameter
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @deprecated  2.0  Use setOptions instead
	 */
	protected function _setOptions(array $options)
	{
		return $this->setOptions($options);
	}

	/**
	 * Set additional session options
	 *
	 * @param   array  $options  List of parameter
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.3.0
	 */
	protected function setOptions(array $options)
	{
		// Set name
		if (isset($options['name']))
		{
			session_name(md5($options['name']));
		}

		// Set id
		if (isset($options['id']))
		{
			session_id($options['id']);
		}

		// Set expire time
		if (isset($options['expire']))
		{
			$this->setExpire($options['expire']);
		}

		// Get security options
		if (isset($options['security']))
		{
			$this->security = explode(',', $options['security']);
		}

		if (isset($options['force_ssl']))
		{
			$this->force_ssl = (bool) $options['force_ssl'];
		}

		if (isset($options['cookie_domain']))
		{
			$this->cookie_domain = $options['cookie_domain'];
		}

		if (isset($options['cookie_path']))
		{
			$this->cookie_path = $options['cookie_path'];
		}

		if (isset($options['cookie_httponly']))
		{
			$this->cookie_httponly = (bool) $options['cookie_httponly'];
		}

		if (isset($options['cookie_samesite']))
		{
			$this->cookie_samesite = $options['cookie_samesite'];
		}

		// Sync the session maxlifetime
		if (!headers_sent())
		{
			ini_set('session.gc_maxlifetime', $this->getExpire());
		}

		return true;
	}

	/**
	 * Do some checks for security reason
	 *
	 * - timeout check (expire)
	 * - ip-fixiation
	 * - browser-fixiation
	 *
	 * If one check failed, session data has to be cleaned.
	 *
	 * @param   boolean  $restart  Reactivate session
	 *
	 * @return  boolean  True on success
	 *
	 * @link    http://shiflett.org/articles/the-truth-about-sessions
	 * @since   1.0
	 * @deprecated  2.0  Use validate instead
	 */
	protected function _validate($restart = false)
	{
		return $this->validate($restart);
	}

	/**
	 * Do some checks for security reason
	 *
	 * - timeout check (expire)
	 * - ip-fixiation
	 * - browser-fixiation
	 *
	 * If one check failed, session data has to be cleaned.
	 *
	 * @param   boolean  $restart  Reactivate session
	 *
	 * @return  boolean  True on success
	 *
	 * @link    http://shiflett.org/articles/the-truth-about-sessions
	 * @since   1.3.0
	 */
	protected function validate($restart = false)
	{
		// Allow to restart a session
		if ($restart)
		{
			$this->setState('active');

			$this->set('session.client.address', null);
			$this->set('session.client.forwarded', null);
			$this->set('session.token', null);
		}

		// Check if session has expired
		if ($this->getExpire())
		{
			$curTime = $this->get('session.timer.now', 0);
			$maxTime = $this->get('session.timer.last', 0) + $this->getExpire();

			// Empty session variables
			if ($maxTime < $curTime)
			{
				$this->setState('expired');

				return false;
			}
		}

		$remoteAddr = $this->input->server->getString('REMOTE_ADDR', '');

		// Check for client address
		if (\in_array('fix_adress', $this->security) && !empty($remoteAddr) && filter_var($remoteAddr, FILTER_VALIDATE_IP) !== false)
		{
			$ip = $this->get('session.client.address');

			if ($ip === null)
			{
				$this->set('session.client.address', $remoteAddr);
			}
			elseif ($remoteAddr !== $ip)
			{
				$this->setState('error');

				return false;
			}
		}

		$xForwardedFor = $this->input->server->getString('HTTP_X_FORWARDED_FOR', '');

		// Record proxy forwarded for in the session in case we need it later
		if (!empty($xForwardedFor) && filter_var($xForwardedFor, FILTER_VALIDATE_IP) !== false)
		{
			$this->set('session.client.forwarded', $xForwardedFor);
		}

		return true;
	}
}
vendor/joomla/session/Joomla/Session/Storage.php000064400000010277152177723700016010 0ustar00<?php
/**
 * Part of the Joomla Framework Session Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Session;

use Joomla\Filter\InputFilter;

/**
 * Custom session storage handler for PHP
 *
 * @link        https://www.php.net/manual/en/function.session-set-save-handler.php
 * @since       1.0
 * @deprecated  2.0  The Storage class chain will be removed.
 */
abstract class Storage
{
	/**
	 * @var    Storage[]  Storage instances container.
	 * @since  1.0
	 * @deprecated  2.0
	 */
	protected static $instances = array();

	/**
	 * Constructor
	 *
	 * @param   array  $options  Optional parameters.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function __construct($options = array())
	{
		$this->register($options);
	}

	/**
	 * Returns a session storage handler object, only creating it if it doesn't already exist.
	 *
	 * @param   string  $name     The session store to instantiate
	 * @param   array   $options  Array of options
	 *
	 * @return  Storage
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function getInstance($name = 'none', $options = array())
	{
		$filter = new InputFilter;
		$name   = strtolower($filter->clean($name, 'word'));

		if (empty(self::$instances[$name]))
		{
			$class = '\\Joomla\\Session\\Storage\\' . ucfirst($name);

			if (!class_exists($class))
			{
				$path = __DIR__ . '/storage/' . $name . '.php';

				if (file_exists($path))
				{
					require_once $path;
				}
				else
				{
					// No attempt to die gracefully here, as it tries to close the non-existing session
					exit('Unable to load session storage class: ' . $name);
				}
			}

			self::$instances[$name] = new $class($options);
		}

		return self::$instances[$name];
	}

	/**
	 * Register the functions of this class with PHP's session handler
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function register()
	{
		if (!headers_sent())
		{
			session_set_save_handler(
				array($this, 'open'),
				array($this, 'close'),
				array($this, 'read'),
				array($this, 'write'),
				array($this, 'destroy'),
				array($this, 'gc')
			);
		}
	}

	/**
	 * Open the SessionHandler backend.
	 *
	 * @param   string  $save_path     The path to the session object.
	 * @param   string  $session_name  The name of the session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function open($save_path, $session_name)
	{
		return true;
	}

	/**
	 * Close the SessionHandler backend.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function close()
	{
		return true;
	}

	/**
	 * Read the data for a particular session identifier from the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  string  The session data.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function read($id)
	{
		return '';
	}

	/**
	 * Write session data to the SessionHandler backend.
	 *
	 * @param   string  $id            The session identifier.
	 * @param   string  $session_data  The session data.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function write($id, $session_data)
	{
		return true;
	}

	/**
	 * Destroy the data for a particular session identifier in the
	 * SessionHandler backend.
	 *
	 * @param   string  $id  The session identifier.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function destroy($id)
	{
		return true;
	}

	/**
	 * Garbage collect stale sessions from the SessionHandler backend.
	 *
	 * @param   integer  $maxlifetime  The maximum age of a session.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public function gc($maxlifetime = null)
	{
		return true;
	}

	/**
	 * Test to see if the SessionHandler is available.
	 *
	 * @return  boolean  True on success, false otherwise.
	 *
	 * @since   1.0
	 * @deprecated  2.0
	 */
	public static function isSupported()
	{
		return true;
	}
}
vendor/joomla/compat/LICENSE000064400000042630152177723700011632 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/compat/src/CallbackFilterIterator.php000064400000004314152177723700016476 0ustar00<?php
/**
 * Part of the Joomla Framework Compat Package
 *
 * @copyright  Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

/**
 * CallbackFilterIterator using the callback to determine which items are accepted or rejected.
 *
 * @link   http://php.net/manual/en/class.callbackfilteriterator.php
 * @since  1.2.0
 */
class CallbackFilterIterator extends \FilterIterator
{
	/**
	 * The callback to check value.
	 *
	 * @var    callable
	 *
	 * @since  1.2.0
	 */
	protected $callback = null;

	/**
	 * Creates a filtered iterator using the callback to determine
	 * which items are accepted or rejected.
	 *
	 * @param   \Iterator  $iterator  The iterator to be filtered.
	 * @param   callable   $callback  The callback, which should return TRUE to accept the current item
	 *                                or FALSE otherwise. May be any valid callable value.
	 *                                The callback should accept up to three arguments: the current item,
	 *                                the current key and the iterator, respectively.
	 *                                ``` php
	 *                                function my_callback($current, $key, $iterator)
	 *                                ```
	 *
	 * @throws  InvalidArgumentException
	 *
	 * @since   1.2.0
	 */
	public function __construct(\Iterator $iterator, $callback)
	{
		if (!is_callable($callback))
		{
			throw new \InvalidArgumentException("Argument 2 of CallbackFilterIterator should be callable.");
		}

		$this->callback = $callback;

		parent::__construct($iterator);
	}

	/**
	 * This method calls the callback with the current value, current key and the inner iterator.
	 * The callback is expected to return TRUE if the current item is to be accepted, or FALSE otherwise.
	 *
	 * @link    http://www.php.net/manual/en/callbackfilteriterator.accept.php
	 *
	 * @return  boolean  True if the current element is acceptable, otherwise false.
	 *
	 * @since   1.2.0
	 */
	public function accept()
	{
		$inner = $this->getInnerIterator();

		return call_user_func_array(
			$this->callback,
			array(
				$inner->current(),
				$inner->key(),
				$inner
			)
		);
	}
}
vendor/joomla/compat/src/JsonSerializable.php000064400000001251152177723700015357 0ustar00<?php
/**
 * Part of the Joomla Framework Compat Package
 *
 * @copyright  Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

/**
 * JsonSerializable interface. This file provides backwards compatibility to PHP 5.3 and ensures
 * the interface is present in systems where JSON related code was removed.
 *
 * @link   http://www.php.net/manual/en/jsonserializable.jsonserialize.php
 * @since  1.0
 */
interface JsonSerializable
{
	/**
	 * Return data which should be serialized by json_encode().
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	public function jsonSerialize();
}
vendor/joomla/event/LICENSE000064400000042630152177723700011470 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/event/src/DispatcherInterface.php000064400000001126152177723700015665 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * Interface for event dispatchers.
 *
 * @since  1.0
 */
interface DispatcherInterface
{
	/**
	 * Trigger an event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  EventInterface  The event after being passed through all listeners.
	 *
	 * @since   1.0
	 */
	public function triggerEvent($event);
}
vendor/joomla/event/src/Priority.php000064400000001062152177723700013576 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * An enumeration of priorities for event listeners,
 * that you are encouraged to use when adding them in the Dispatcher.
 *
 * @since  1.0
 */
final class Priority
{
	const MIN = -3;
	const LOW = -2;
	const BELOW_NORMAL = -1;
	const NORMAL = 0;
	const ABOVE_NORMAL = 1;
	const HIGH = 2;
	const MAX = 3;
}
vendor/joomla/event/src/EventInterface.php000064400000001316152177723700014661 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * Interface for events.
 * An event has a name and its propagation can be stopped (if the implementation supports it).
 *
 * @since  1.0
 */
interface EventInterface
{
	/**
	 * Get the event name.
	 *
	 * @return  string  The event name.
	 *
	 * @since   1.0
	 */
	public function getName();

	/**
	 * Tell if the event propagation is stopped.
	 *
	 * @return  boolean  True if stopped, false otherwise.
	 *
	 * @since   1.0
	 */
	public function isStopped();
}
vendor/joomla/event/src/AbstractEvent.php000064400000006743152177723700014535 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

use Serializable;
use ArrayAccess;
use Countable;

/**
 * Implementation of EventInterface.
 *
 * @since  1.0
 */
abstract class AbstractEvent implements EventInterface, ArrayAccess, Serializable, Countable
{
	/**
	 * The event name.
	 *
	 * @var    string
	 *
	 * @since  1.0
	 */
	protected $name;

	/**
	 * The event arguments.
	 *
	 * @var    array
	 *
	 * @since  1.0
	 */
	protected $arguments;

	/**
	 * A flag to see if the event propagation is stopped.
	 *
	 * @var    boolean
	 *
	 * @since  1.0
	 */
	protected $stopped = false;

	/**
	 * Constructor.
	 *
	 * @param   string  $name       The event name.
	 * @param   array   $arguments  The event arguments.
	 *
	 * @since   1.0
	 */
	public function __construct($name, array $arguments = array())
	{
		$this->name = $name;
		$this->arguments = $arguments;
	}

	/**
	 * Get the event name.
	 *
	 * @return  string  The event name.
	 *
	 * @since   1.0
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * Get an event argument value.
	 *
	 * @param   string  $name     The argument name.
	 * @param   mixed   $default  The default value if not found.
	 *
	 * @return  mixed  The argument value or the default value.
	 *
	 * @since   1.0
	 */
	public function getArgument($name, $default = null)
	{
		if (isset($this->arguments[$name]))
		{
			return $this->arguments[$name];
		}

		return $default;
	}

	/**
	 * Tell if the given event argument exists.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  boolean  True if it exists, false otherwise.
	 *
	 * @since   1.0
	 */
	public function hasArgument($name)
	{
		return isset($this->arguments[$name]);
	}

	/**
	 * Get all event arguments.
	 *
	 * @return  array  An associative array of argument names as keys
	 *                 and their values as values.
	 *
	 * @since   1.0
	 */
	public function getArguments()
	{
		return $this->arguments;
	}

	/**
	 * Tell if the event propagation is stopped.
	 *
	 * @return  boolean  True if stopped, false otherwise.
	 *
	 * @since   1.0
	 */
	public function isStopped()
	{
		return true === $this->stopped;
	}

	/**
	 * Count the number of arguments.
	 *
	 * @return  integer  The number of arguments.
	 *
	 * @since   1.0
	 */
	public function count()
	{
		return count($this->arguments);
	}

	/**
	 * Serialize the event.
	 *
	 * @return  string  The serialized event.
	 *
	 * @since   1.0
	 */
	public function serialize()
	{
		return serialize(array($this->name, $this->arguments, $this->stopped));
	}

	/**
	 * Unserialize the event.
	 *
	 * @param   string  $serialized  The serialized event.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function unserialize($serialized)
	{
		list($this->name, $this->arguments, $this->stopped) = unserialize($serialized);
	}

	/**
	 * Tell if the given event argument exists.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  boolean  True if it exists, false otherwise.
	 *
	 * @since   1.0
	 */
	public function offsetExists($name)
	{
		return $this->hasArgument($name);
	}

	/**
	 * Get an event argument value.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  mixed  The argument value or null if not existing.
	 *
	 * @since   1.0
	 */
	public function offsetGet($name)
	{
		return $this->getArgument($name);
	}
}
vendor/joomla/event/src/DispatcherAwareInterface.php000064400000001217152177723700016646 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * Interface to be implemented by classes depending on a dispatcher.
 *
 * @since  1.0
 */
interface DispatcherAwareInterface
{
	/**
	 * Set the dispatcher to use.
	 *
	 * @param   DispatcherInterface  $dispatcher  The dispatcher to use.
	 *
	 * @return  DispatcherAwareInterface  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function setDispatcher(DispatcherInterface $dispatcher);
}
vendor/joomla/event/src/Event.php000064400000005021152177723700013035 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

use InvalidArgumentException;

/**
 * Default Event class.
 *
 * @since  1.0
 */
class Event extends AbstractEvent
{
	/**
	 * Add an event argument, only if it is not existing.
	 *
	 * @param   string  $name   The argument name.
	 * @param   mixed   $value  The argument value.
	 *
	 * @return  Event  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function addArgument($name, $value)
	{
		if (!isset($this->arguments[$name]))
		{
			$this->arguments[$name] = $value;
		}

		return $this;
	}

	/**
	 * Set the value of an event argument.
	 * If the argument already exists, it will be overridden.
	 *
	 * @param   string  $name   The argument name.
	 * @param   mixed   $value  The argument value.
	 *
	 * @return  Event  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function setArgument($name, $value)
	{
		$this->arguments[$name] = $value;

		return $this;
	}

	/**
	 * Remove an event argument.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  mixed  The old argument value or null if it is not existing.
	 *
	 * @since   1.0
	 */
	public function removeArgument($name)
	{
		$return = null;

		if (isset($this->arguments[$name]))
		{
			$return = $this->arguments[$name];
			unset($this->arguments[$name]);
		}

		return $return;
	}

	/**
	 * Clear all event arguments.
	 *
	 * @return  array  The old arguments.
	 *
	 * @since   1.0
	 */
	public function clearArguments()
	{
		$arguments = $this->arguments;
		$this->arguments = array();

		return $arguments;
	}

	/**
	 * Stop the event propagation.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function stop()
	{
		$this->stopped = true;
	}

	/**
	 * Set the value of an event argument.
	 *
	 * @param   string  $name   The argument name.
	 * @param   mixed   $value  The argument value.
	 *
	 * @return  void
	 *
	 * @throws  InvalidArgumentException  If the argument name is null.
	 *
	 * @since   1.0
	 */
	public function offsetSet($name, $value)
	{
		if (is_null($name))
		{
			throw new InvalidArgumentException('The argument name cannot be null.');
		}

		$this->setArgument($name, $value);
	}

	/**
	 * Remove an event argument.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function offsetUnset($name)
	{
		$this->removeArgument($name);
	}
}
vendor/joomla/event/src/EventImmutable.php000064400000004011152177723700014673 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

use BadMethodCallException;

/**
 * Implementation of an immutable Event.
 * An immutable event cannot be modified after instanciation :
 *
 * - its propagation cannot be stopped
 * - its arguments cannot be modified
 *
 * You may want to use this event when you want to ensure that
 * the listeners won't manipulate it.
 *
 * @since  1.0
 */
final class EventImmutable extends AbstractEvent
{
	/**
	 * A flag to see if the constructor has been
	 * already called.
	 *
	 * @var  boolean
	 */
	private $constructed = false;

	/**
	 * Constructor.
	 *
	 * @param   string  $name       The event name.
	 * @param   array   $arguments  The event arguments.
	 *
	 * @throws  BadMethodCallException
	 *
	 * @since   1.0
	 */
	public function __construct($name, array $arguments = array())
	{
		if ($this->constructed)
		{
			throw new BadMethodCallException(
				sprintf('Cannot reconstruct the EventImmutable %s.', $this->name)
			);
		}

		$this->constructed = true;

		parent::__construct($name, $arguments);
	}

	/**
	 * Set the value of an event argument.
	 *
	 * @param   string  $name   The argument name.
	 * @param   mixed   $value  The argument value.
	 *
	 * @return  void
	 *
	 * @throws  BadMethodCallException
	 *
	 * @since   1.0
	 */
	public function offsetSet($name, $value)
	{
		throw new BadMethodCallException(
			sprintf(
				'Cannot set the argument %s of the immutable event %s.',
				$name,
				$this->name
			)
		);
	}

	/**
	 * Remove an event argument.
	 *
	 * @param   string  $name  The argument name.
	 *
	 * @return  void
	 *
	 * @throws  BadMethodCallException
	 *
	 * @since   1.0
	 */
	public function offsetUnset($name)
	{
		throw new BadMethodCallException(
			sprintf(
				'Cannot remove the argument %s of the immutable event %s.',
				$name,
				$this->name
			)
		);
	}
}
vendor/joomla/event/src/DelegatingDispatcher.php000064400000002046152177723700016032 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * A dispatcher delegating its methods to an other dispatcher.
 *
 * @since  1.0
 */
final class DelegatingDispatcher implements DispatcherInterface
{
	/**
	 * The delegated dispatcher.
	 *
	 * @var    DispatcherInterface
	 *
	 * @since  1.0
	 */
	private $dispatcher;

	/**
	 * Constructor.
	 *
	 * @param   DispatcherInterface  $dispatcher  The delegated dispatcher.
	 *
	 * @since   1.0
	 */
	public function __construct(DispatcherInterface $dispatcher)
	{
		$this->dispatcher = $dispatcher;
	}

	/**
	 * Trigger an event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  EventInterface  The event after being passed through all listeners.
	 *
	 * @since   1.0
	 */
	public function triggerEvent($event)
	{
		return $this->dispatcher->triggerEvent($event);
	}
}
vendor/joomla/event/src/ListenersPriorityQueue.php000064400000010163152177723700016476 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

use SplPriorityQueue;
use SplObjectStorage;
use IteratorAggregate;
use Countable;

/**
 * A class containing an inner listeners priority queue that can be iterated multiple times.
 * One instance of ListenersPriorityQueue is used per Event in the Dispatcher.
 *
 * @since  1.0
 */
class ListenersPriorityQueue implements IteratorAggregate, Countable
{
	/**
	 * The inner priority queue.
	 *
	 * @var    SplPriorityQueue
	 *
	 * @since  1.0
	 */
	protected $queue;

	/**
	 * A copy of the listeners contained in the queue
	 * that is used when detaching them to
	 * recreate the queue or to see if the queue contains
	 * a given listener.
	 *
	 * @var    SplObjectStorage
	 *
	 * @since  1.0
	 */
	protected $storage;

	/**
	 * A decreasing counter used to compute
	 * the internal priority as an array because
	 * SplPriorityQueue dequeues elements with the same priority.
	 *
	 * @var    integer
	 *
	 * @since  1.0
	 */
	private $counter = PHP_INT_MAX;

	/**
	 * Constructor.
	 *
	 * @since  1.0
	 */
	public function __construct()
	{
		$this->queue = new SplPriorityQueue;
		$this->storage = new SplObjectStorage;
	}

	/**
	 * Add a listener with the given priority only if not already present.
	 *
	 * @param   \Closure|object  $listener  The listener.
	 * @param   integer          $priority  The listener priority.
	 *
	 * @return  ListenersPriorityQueue  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function add($listener, $priority)
	{
		if (!$this->storage->contains($listener))
		{
			// Compute the internal priority as an array.
			$priority = array($priority, $this->counter--);

			$this->storage->attach($listener, $priority);
			$this->queue->insert($listener, $priority);
		}

		return $this;
	}

	/**
	 * Remove a listener from the queue.
	 *
	 * @param   \Closure|object  $listener  The listener.
	 *
	 * @return  ListenersPriorityQueue  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function remove($listener)
	{
		if ($this->storage->contains($listener))
		{
			$this->storage->detach($listener);
			$this->storage->rewind();

			$this->queue = new SplPriorityQueue;

			foreach ($this->storage as $listener)
			{
				$priority = $this->storage->getInfo();
				$this->queue->insert($listener, $priority);
			}
		}

		return $this;
	}

	/**
	 * Tell if the listener exists in the queue.
	 *
	 * @param   \Closure|object  $listener  The listener.
	 *
	 * @return  boolean  True if it exists, false otherwise.
	 *
	 * @since   1.0
	 */
	public function has($listener)
	{
		return $this->storage->contains($listener);
	}

	/**
	 * Get the priority of the given listener.
	 *
	 * @param   \Closure|object  $listener  The listener.
	 * @param   mixed            $default   The default value to return if the listener doesn't exist.
	 *
	 * @return  mixed  The listener priority if it exists, null otherwise.
	 *
	 * @since   1.0
	 */
	public function getPriority($listener, $default = null)
	{
		if ($this->storage->contains($listener))
		{
			return $this->storage[$listener][0];
		}

		return $default;
	}

	/**
	 * Get all listeners contained in this queue, sorted according to their priority.
	 *
	 * @return  object[]  An array of listeners.
	 *
	 * @since   1.0
	 */
	public function getAll()
	{
		$listeners = array();

		// Get a clone of the queue.
		$queue = $this->getIterator();

		foreach ($queue as $listener)
		{
			$listeners[] = $listener;
		}

		return $listeners;
	}

	/**
	 * Get the inner queue with its cursor on top of the heap.
	 *
	 * @return  SplPriorityQueue  The inner queue.
	 *
	 * @since   1.0
	 */
	public function getIterator()
	{
		// SplPriorityQueue queue is a heap.
		$queue = clone $this->queue;

		if (!$queue->isEmpty())
		{
			$queue->top();
		}

		return $queue;
	}

	/**
	 * Count the number of listeners in the queue.
	 *
	 * @return  integer  The number of listeners in the queue.
	 *
	 * @since   1.0
	 */
	public function count()
	{
		return count($this->queue);
	}
}
vendor/joomla/event/src/DispatcherAwareTrait.php000064400000002141152177723700016026 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

/**
 * Defines the trait for a Dispatcher Aware Class.
 *
 * @since  1.2.0
 */
trait DispatcherAwareTrait
{
	/**
	 * Event Dispatcher
	 *
	 * @var    DispatcherInterface
	 * @since  1.2.0
	 */
	private $dispatcher;

	/**
	 * Get the event dispatcher.
	 *
	 * @return  DispatcherInterface
	 *
	 * @since   1.2.0
	 * @throws  \UnexpectedValueException May be thrown if the dispatcher has not been set.
	 */
	public function getDispatcher()
	{
		if ($this->dispatcher)
		{
			return $this->dispatcher;
		}

		throw new \UnexpectedValueException('Dispatcher not set in ' . __CLASS__);
	}

	/**
	 * Set the dispatcher to use.
	 *
	 * @param   DispatcherInterface  $dispatcher  The dispatcher to use.
	 *
	 * @return  $this
	 *
	 * @since   1.2.0
	 */
	public function setDispatcher(DispatcherInterface $dispatcher)
	{
		$this->dispatcher = $dispatcher;

		return $this;
	}
}
vendor/joomla/event/src/Dispatcher.php000064400000024524152177723700014053 0ustar00<?php
/**
 * Part of the Joomla Framework Event Package
 *
 * @copyright  Copyright (C) 2005 - 2016 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Event;

use InvalidArgumentException;
use Closure;

/**
 * Implementation of a DispatcherInterface supporting
 * prioritized listeners.
 *
 * @since  1.0
 */
class Dispatcher implements DispatcherInterface
{
	/**
	 * An array of registered events indexed by
	 * the event names.
	 *
	 * @var    EventInterface[]
	 *
	 * @since  1.0
	 */
	protected $events = array();

	/**
	 * A regular expression that will filter listener method names.
	 *
	 * @var    string
	 * @since  1.0
	 * @deprecated
	 */
	protected $listenerFilter;

	/**
	 * An array of ListenersPriorityQueue indexed
	 * by the event names.
	 *
	 * @var    ListenersPriorityQueue[]
	 *
	 * @since  1.0
	 */
	protected $listeners = array();

	/**
	 * Set an event to the dispatcher.
	 * It will replace any event with the same name.
	 *
	 * @param   EventInterface  $event  The event.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function setEvent(EventInterface $event)
	{
		$this->events[$event->getName()] = $event;

		return $this;
	}

	/**
	 * Sets a regular expression to filter the class methods when adding a listener.
	 *
	 * @param   string  $regex  A regular expression (for example '^on' will only register methods starting with "on").
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since       1.0
	 * @deprecated  Incorporate a method in your listener object such as `getEvents` to feed into the `setListener` method.
	 */
	public function setListenerFilter($regex)
	{
		$this->listenerFilter = $regex;

		return $this;
	}

	/**
	 * Add an event to this dispatcher, only if it is not existing.
	 *
	 * @param   EventInterface  $event  The event.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function addEvent(EventInterface $event)
	{
		if (!isset($this->events[$event->getName()]))
		{
			$this->events[$event->getName()] = $event;
		}

		return $this;
	}

	/**
	 * Tell if the given event has been added to this dispatcher.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  boolean  True if the listener has the given event, false otherwise.
	 *
	 * @since   1.0
	 */
	public function hasEvent($event)
	{
		if ($event instanceof EventInterface)
		{
			$event = $event->getName();
		}

		return isset($this->events[$event]);
	}

	/**
	 * Get the event object identified by the given name.
	 *
	 * @param   string  $name     The event name.
	 * @param   mixed   $default  The default value if the event was not registered.
	 *
	 * @return  EventInterface|mixed  The event of the default value.
	 *
	 * @since   1.0
	 */
	public function getEvent($name, $default = null)
	{
		if (isset($this->events[$name]))
		{
			return $this->events[$name];
		}

		return $default;
	}

	/**
	 * Remove an event from this dispatcher.
	 * The registered listeners will remain.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function removeEvent($event)
	{
		if ($event instanceof EventInterface)
		{
			$event = $event->getName();
		}

		if (isset($this->events[$event]))
		{
			unset($this->events[$event]);
		}

		return $this;
	}

	/**
	 * Get the registered events.
	 *
	 * @return  EventInterface[]  The registered event.
	 *
	 * @since   1.0
	 */
	public function getEvents()
	{
		return $this->events;
	}

	/**
	 * Clear all events.
	 *
	 * @return  EventInterface[]  The old events.
	 *
	 * @since   1.0
	 */
	public function clearEvents()
	{
		$events = $this->events;
		$this->events = array();

		return $events;
	}

	/**
	 * Count the number of registered event.
	 *
	 * @return  integer  The numer of registered events.
	 *
	 * @since   1.0
	 */
	public function countEvents()
	{
		return count($this->events);
	}

	/**
	 * Add a listener to this dispatcher, only if not already registered to these events.
	 * If no events are specified, it will be registered to all events matching it's methods name.
	 * In the case of a closure, you must specify at least one event name.
	 *
	 * @param   object|Closure  $listener  The listener
	 * @param   array           $events    An associative array of event names as keys
	 *                                     and the corresponding listener priority as values.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @throws  InvalidArgumentException
	 *
	 * @since   1.0
	 */
	public function addListener($listener, array $events = array())
	{
		if (!is_object($listener))
		{
			throw new InvalidArgumentException('The given listener is not an object.');
		}

		// We deal with a closure.
		if ($listener instanceof Closure)
		{
			if (empty($events))
			{
				throw new InvalidArgumentException('No event name(s) and priority
				specified for the Closure listener.');
			}

			foreach ($events as $name => $priority)
			{
				if (!isset($this->listeners[$name]))
				{
					$this->listeners[$name] = new ListenersPriorityQueue;
				}

				$this->listeners[$name]->add($listener, $priority);
			}

			return $this;
		}

		// We deal with a "normal" object.
		$methods = get_class_methods($listener);

		if (!empty($events))
		{
			$methods = array_intersect($methods, array_keys($events));
		}

		// @deprecated
		$regex = $this->listenerFilter ?: '.*';

		foreach ($methods as $event)
		{
			// @deprecated - this outer `if` is deprecated.
			if (preg_match("#$regex#", $event))
			{
				// Retain this inner code after removal of the outer `if`.
				if (!isset($this->listeners[$event]))
				{
					$this->listeners[$event] = new ListenersPriorityQueue;
				}

				$priority = isset($events[$event]) ? $events[$event] : Priority::NORMAL;

				$this->listeners[$event]->add($listener, $priority);
			}
		}

		return $this;
	}

	/**
	 * Get the priority of the given listener for the given event.
	 *
	 * @param   object|Closure         $listener  The listener.
	 * @param   EventInterface|string  $event     The event object or name.
	 *
	 * @return  mixed  The listener priority or null if the listener doesn't exist.
	 *
	 * @since   1.0
	 */
	public function getListenerPriority($listener, $event)
	{
		if ($event instanceof EventInterface)
		{
			$event = $event->getName();
		}

		if (isset($this->listeners[$event]))
		{
			return $this->listeners[$event]->getPriority($listener);
		}

		return null;
	}

	/**
	 * Get the listeners registered to the given event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  object[]  An array of registered listeners sorted according to their priorities.
	 *
	 * @since   1.0
	 */
	public function getListeners($event)
	{
		if ($event instanceof EventInterface)
		{
			$event = $event->getName();
		}

		if (isset($this->listeners[$event]))
		{
			return $this->listeners[$event]->getAll();
		}

		return array();
	}

	/**
	 * Tell if the given listener has been added.
	 * If an event is specified, it will tell if the listener is registered for that event.
	 *
	 * @param   object|Closure         $listener  The listener.
	 * @param   EventInterface|string  $event     The event object or name.
	 *
	 * @return  boolean  True if the listener is registered, false otherwise.
	 *
	 * @since   1.0
	 */
	public function hasListener($listener, $event = null)
	{
		if ($event)
		{
			if ($event instanceof EventInterface)
			{
				$event = $event->getName();
			}

			if (isset($this->listeners[$event]))
			{
				return $this->listeners[$event]->has($listener);
			}
		}
		else
		{
			foreach ($this->listeners as $queue)
			{
				if ($queue->has($listener))
				{
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * Remove the given listener from this dispatcher.
	 * If no event is specified, it will be removed from all events it is listening to.
	 *
	 * @param   object|Closure         $listener  The listener to remove.
	 * @param   EventInterface|string  $event     The event object or name.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function removeListener($listener, $event = null)
	{
		if ($event)
		{
			if ($event instanceof EventInterface)
			{
				$event = $event->getName();
			}

			if (isset($this->listeners[$event]))
			{
				$this->listeners[$event]->remove($listener);
			}
		}

		else
		{
			foreach ($this->listeners as $queue)
			{
				$queue->remove($listener);
			}
		}

		return $this;
	}

	/**
	 * Clear the listeners in this dispatcher.
	 * If an event is specified, the listeners will be cleared only for that event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  Dispatcher  This method is chainable.
	 *
	 * @since   1.0
	 */
	public function clearListeners($event = null)
	{
		if ($event)
		{
			if ($event instanceof EventInterface)
			{
				$event = $event->getName();
			}

			if (isset($this->listeners[$event]))
			{
				unset($this->listeners[$event]);
			}
		}

		else
		{
			$this->listeners = array();
		}

		return $this;
	}

	/**
	 * Count the number of registered listeners for the given event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  integer  The number of registered listeners for the given event.
	 *
	 * @since   1.0
	 */
	public function countListeners($event)
	{
		if ($event instanceof EventInterface)
		{
			$event = $event->getName();
		}

		return isset($this->listeners[$event]) ? count($this->listeners[$event]) : 0;
	}

	/**
	 * Trigger an event.
	 *
	 * @param   EventInterface|string  $event  The event object or name.
	 *
	 * @return  EventInterface  The event after being passed through all listeners.
	 *
	 * @since   1.0
	 */
	public function triggerEvent($event)
	{
		if (!($event instanceof EventInterface))
		{
			if (isset($this->events[$event]))
			{
				$event = $this->events[$event];
			}

			else
			{
				$event = new Event($event);
			}
		}

		if (isset($this->listeners[$event->getName()]))
		{
			foreach ($this->listeners[$event->getName()] as $listener)
			{
				if ($event->isStopped())
				{
					return $event;
				}

				if ($listener instanceof Closure)
				{
					call_user_func($listener, $event);
				}

				else
				{
					call_user_func(array($listener, $event->getName()), $event);
				}
			}
		}

		return $event;
	}
}
vendor/joomla/archive/LICENSE000064400000042630152177723700011770 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/archive/src/Bzip2.php000064400000006012152177723700013243 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

use Joomla\Filesystem\File;
use Joomla\Filesystem\Stream;

/**
 * Bzip2 format adapter for the Archive package
 *
 * @since  1.0
 */
class Bzip2 implements ExtractableInterface
{
	/**
	 * Bzip2 file data buffer
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $data;

	/**
	 * Holds the options array.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options = array();

	/**
	 * Create a new Archive object.
	 *
	 * @param   array|\ArrayAccess  $options  An array of options
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array())
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Extract a Bzip2 compressed file to a given path
	 *
	 * @param   string  $archive      Path to Bzip2 archive to extract
	 * @param   string  $destination  Path to extract archive to
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function extract($archive, $destination)
	{
		$this->data = null;

		if (!isset($this->options['use_streams']) || $this->options['use_streams'] == false)
		{
			// Old style: read the whole file and then parse it
			$this->data = file_get_contents($archive);

			if (!$this->data)
			{
				throw new \RuntimeException('Unable to read archive');
			}

			$buffer = bzdecompress($this->data);
			unset($this->data);

			if (empty($buffer))
			{
				throw new \RuntimeException('Unable to decompress data');
			}

			if (!File::write($destination, $buffer))
			{
				throw new \RuntimeException('Unable to write archive to file ' . $destination);
			}
		}
		else
		{
			// New style! streams!
			$input = Stream::getStream();

			// Use bzip
			$input->set('processingmethod', 'bz');

			if (!$input->open($archive))
			{
				throw new \RuntimeException('Unable to read archive');
			}

			$output = Stream::getStream();

			if (!$output->open($destination, 'w'))
			{
				$input->close();

				throw new \RuntimeException('Unable to open file "' . $destination . '" for writing');
			}

			do
			{
				$this->data = $input->read($input->get('chunksize', 8196));

				if ($this->data)
				{
					if (!$output->write($this->data))
					{
						$input->close();

						throw new \RuntimeException('Unable to write archive to file ' . $destination);
					}
				}
			}
			while ($this->data);

			$output->close();
			$input->close();
		}

		return true;
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('bz2');
	}
}
vendor/joomla/archive/src/Gzip.php000064400000011032152177723700013164 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

use Joomla\Filesystem\File;
use Joomla\Filesystem\Stream;

/**
 * Gzip format adapter for the Archive package
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <http://www.horde.org>
 *
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since  1.0
 */
class Gzip implements ExtractableInterface
{
	/**
	 * Gzip file flags.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $flags = array('FTEXT' => 0x01, 'FHCRC' => 0x02, 'FEXTRA' => 0x04, 'FNAME' => 0x08, 'FCOMMENT' => 0x10);

	/**
	 * Gzip file data buffer
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $data;

	/**
	 * Holds the options array.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options = array();

	/**
	 * Create a new Archive object.
	 *
	 * @param   array|\ArrayAccess  $options  An array of options
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array())
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Extract a Gzip compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive to
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function extract($archive, $destination)
	{
		$this->data = null;

		if (!isset($this->options['use_streams']) || $this->options['use_streams'] == false)
		{
			$this->data = file_get_contents($archive);

			if (!$this->data)
			{
				throw new \RuntimeException('Unable to read archive');
			}

			$position = $this->getFilePosition();
			$buffer   = gzinflate(substr($this->data, $position, \strlen($this->data) - $position));

			if (empty($buffer))
			{
				throw new \RuntimeException('Unable to decompress data');
			}

			if (!File::write($destination, $buffer))
			{
				throw new \RuntimeException('Unable to write archive to file ' . $destination);
			}
		}
		else
		{
			// New style! streams!
			$input = Stream::getStream();

			// Use gz
			$input->set('processingmethod', 'gz');

			if (!$input->open($archive))
			{
				throw new \RuntimeException('Unable to read archive');
			}

			$output = Stream::getStream();

			if (!$output->open($destination, 'w'))
			{
				$input->close();

				throw new \RuntimeException('Unable to open file "' . $destination . '" for writing');
			}

			do
			{
				$this->data = $input->read($input->get('chunksize', 8196));

				if ($this->data)
				{
					if (!$output->write($this->data))
					{
						$input->close();

						throw new \RuntimeException('Unable to write archive to file ' . $destination);
					}
				}
			}
			while ($this->data);

			$output->close();
			$input->close();
		}

		return true;
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return \extension_loaded('zlib');
	}

	/**
	 * Get file data offset for archive
	 *
	 * @return  integer  Data position marker for archive
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function getFilePosition()
	{
		// Gzipped file... unpack it first
		$position = 0;
		$info     = @ unpack('CCM/CFLG/VTime/CXFL/COS', substr($this->data, $position + 2));

		if (!$info)
		{
			throw new \RuntimeException('Unable to decompress data.');
		}

		$position += 10;

		if ($info['FLG'] & $this->flags['FEXTRA'])
		{
			$XLEN = unpack('vLength', substr($this->data, $position + 0, 2));
			$XLEN = $XLEN['Length'];
			$position += $XLEN + 2;
		}

		if ($info['FLG'] & $this->flags['FNAME'])
		{
			$filenamePos = strpos($this->data, "\x0", $position);
			$position    = $filenamePos + 1;
		}

		if ($info['FLG'] & $this->flags['FCOMMENT'])
		{
			$commentPos = strpos($this->data, "\x0", $position);
			$position   = $commentPos + 1;
		}

		if ($info['FLG'] & $this->flags['FHCRC'])
		{
			$hcrc = unpack('vCRC', substr($this->data, $position + 0, 2));
			$hcrc = $hcrc['CRC'];
			$position += 2;
		}

		return $position;
	}
}
vendor/joomla/archive/src/ExtractableInterface.php000064400000001527152177723700016342 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

/**
 * Archive class interface
 *
 * @since  1.0
 */
interface ExtractableInterface
{
	/**
	 * Extract a compressed file to a given path
	 *
	 * @param   string  $archive      Path to archive to extract
	 * @param   string  $destination  Path to extract archive to
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function extract($archive, $destination);

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   1.0
	 */
	public static function isSupported();
}
vendor/joomla/archive/src/Zip.php000064400000040314152177723700013022 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;

/**
 * ZIP format adapter for the Archive package
 *
 * The ZIP compression code is partially based on code from:
 * Eric Mueller <eric@themepark.com>
 * http://www.zend.com/codex.php?id=535&single=1
 *
 * Deins125 <webmaster@atlant.ru>
 * http://www.zend.com/codex.php?id=470&single=1
 *
 * The ZIP compression date code is partially based on code from
 * Peter Listiak <mlady@users.sourceforge.net>
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <http://www.horde.org>
 *
 * @contributor  Chuck Hagenbuch <chuck@horde.org>
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since  1.0
 */
class Zip implements ExtractableInterface
{
	/**
	 * ZIP compression methods.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $methods = array(
		0x0 => 'None',
		0x1 => 'Shrunk',
		0x2 => 'Super Fast',
		0x3 => 'Fast',
		0x4 => 'Normal',
		0x5 => 'Maximum',
		0x6 => 'Imploded',
		0x8 => 'Deflated',
	);

	/**
	 * Beginning of central directory record.
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $ctrlDirHeader = "\x50\x4b\x01\x02";

	/**
	 * End of central directory record.
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $ctrlDirEnd = "\x50\x4b\x05\x06\x00\x00\x00\x00";

	/**
	 * Beginning of file contents.
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $fileHeader = "\x50\x4b\x03\x04";

	/**
	 * ZIP file data buffer
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $data;

	/**
	 * ZIP file metadata array
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $metadata;

	/**
	 * Holds the options array.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options = array();

	/**
	 * Create a new Archive object.
	 *
	 * @param   array|\ArrayAccess  $options  An array of options or an object that implements \ArrayAccess
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array())
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Create a ZIP compressed file from an array of file data.
	 *
	 * @param   string  $archive  Path to save archive.
	 * @param   array   $files    Array of files to add to archive.
	 *
	 * @return  boolean  True if successful.
	 *
	 * @since   1.0
	 * @todo    Finish Implementation
	 */
	public function create($archive, $files)
	{
		$contents = array();
		$ctrldir  = array();

		foreach ($files as $file)
		{
			$this->addToZipFile($file, $contents, $ctrldir);
		}

		return $this->createZipFile($contents, $ctrldir, $archive);
	}

	/**
	 * Extract a ZIP compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function extract($archive, $destination)
	{
		if (!is_file($archive))
		{
			throw new \RuntimeException('Archive does not exist at ' . $archive);
		}

		if (static::hasNativeSupport())
		{
			return $this->extractNative($archive, $destination);
		}

		return $this->extractCustom($archive, $destination);
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return self::hasNativeSupport() || \extension_loaded('zlib');
	}

	/**
	 * Method to determine if the server has native zip support for faster handling
	 *
	 * @return  boolean  True if php has native ZIP support
	 *
	 * @since   1.0
	 */
	public static function hasNativeSupport()
	{
		return \extension_loaded('zip');
	}

	/**
	 * Checks to see if the data is a valid ZIP file.
	 *
	 * @param   string  $data  ZIP archive data buffer.
	 *
	 * @return  boolean  True if valid, false if invalid.
	 *
	 * @since   1.0
	 */
	public function checkZipData(&$data)
	{
		return strpos($data, $this->fileHeader) !== false;
	}

	/**
	 * Extract a ZIP compressed file to a given path using a php based algorithm that only requires zlib support
	 *
	 * @param   string  $archive      Path to ZIP archive to extract.
	 * @param   string  $destination  Path to extract archive into.
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function extractCustom($archive, $destination)
	{
		$this->data     = null;
		$this->metadata = null;

		$this->data = file_get_contents($archive);

		if (!$this->data)
		{
			throw new \RuntimeException('Unable to read archive');
		}

		if (!$this->readZipInfo($this->data))
		{
			throw new \RuntimeException('Get ZIP Information failed');
		}

		for ($i = 0, $n = \count($this->metadata); $i < $n; $i++)
		{
			$lastPathCharacter = substr($this->metadata[$i]['name'], -1, 1);

			if ($lastPathCharacter !== '/' && $lastPathCharacter !== '\\')
			{
				$buffer = $this->getFileData($i);
				$path   = Path::clean($destination . '/' . $this->metadata[$i]['name']);

				// Make sure the destination folder exists
				if (!Folder::create(\dirname($path)))
				{
					throw new \RuntimeException('Unable to create destination folder ' . \dirname($path));
				}

				if (!File::write($path, $buffer))
				{
					throw new \RuntimeException('Unable to write entry to file ' . $path);
				}
			}
		}

		return true;
	}

	/**
	 * Extract a ZIP compressed file to a given path using native php api calls for speed
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 *
	 * @return  boolean  True on success
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function extractNative($archive, $destination)
	{
		$zip = zip_open($archive);

		if (!\is_resource($zip))
		{
			throw new \RuntimeException('Unable to open archive');
		}

		// Make sure the destination folder exists
		if (!Folder::create($destination))
		{
			throw new \RuntimeException('Unable to create destination folder ' . \dirname($path));
		}

		// Read files in the archive
		while ($file = @zip_read($zip))
		{
			if (!zip_entry_open($zip, $file, 'r'))
			{
				throw new \RuntimeException('Unable to read ZIP entry');
			}

			if (substr(zip_entry_name($file), \strlen(zip_entry_name($file)) - 1) != '/')
			{
				$buffer = zip_entry_read($file, zip_entry_filesize($file));

				if (File::write($destination . '/' . zip_entry_name($file), $buffer) === false)
				{
					throw new \RuntimeException('Unable to write ZIP entry to file ' . $destination . '/' . zip_entry_name($file));
				}

				zip_entry_close($file);
			}
		}

		@zip_close($zip);

		return true;
	}

	/**
	 * Get the list of files/data from a ZIP archive buffer.
	 *
	 * <pre>
	 * KEY: Position in zipfile
	 * VALUES: 'attr'  --  File attributes
	 * 'crc'   --  CRC checksum
	 * 'csize' --  Compressed file size
	 * 'date'  --  File modification time
	 * 'name'  --  Filename
	 * 'method'--  Compression method
	 * 'size'  --  Original file size
	 * 'type'  --  File type
	 * </pre>
	 *
	 * @param   string  $data  The ZIP archive buffer.
	 *
	 * @return  boolean True on success
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	private function readZipInfo(&$data)
	{
		$entries = array();

		// Find the last central directory header entry
		$fhLast = strpos($data, $this->ctrlDirEnd);

		do
		{
			$last = $fhLast;
		}
		while (($fhLast = strpos($data, $this->ctrlDirEnd, $fhLast + 1)) !== false);

		// Find the central directory offset
		$offset = 0;

		if ($last)
		{
			$endOfCentralDirectory = unpack(
				'vNumberOfDisk/vNoOfDiskWithStartOfCentralDirectory/vNoOfCentralDirectoryEntriesOnDisk/' .
				'vTotalCentralDirectoryEntries/VSizeOfCentralDirectory/VCentralDirectoryOffset/vCommentLength',
				substr($data, $last + 4)
			);
			$offset = $endOfCentralDirectory['CentralDirectoryOffset'];
		}

		// Get details from central directory structure.
		$fhStart    = strpos($data, $this->ctrlDirHeader, $offset);
		$dataLength = \strlen($data);

		do
		{
			if ($dataLength < $fhStart + 31)
			{
				throw new \RuntimeException('Invalid ZIP Data');
			}

			$info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength', substr($data, $fhStart + 10, 20));
			$name = substr($data, $fhStart + 46, $info['Length']);

			$entries[$name] = array(
				'attr'       => null,
				'crc'        => sprintf('%08s', dechex($info['CRC32'])),
				'csize'      => $info['Compressed'],
				'date'       => null,
				'_dataStart' => null,
				'name'       => $name,
				'method'     => $this->methods[$info['Method']],
				'_method'    => $info['Method'],
				'size'       => $info['Uncompressed'],
				'type'       => null,
			);

			$entries[$name]['date'] = mktime(
				($info['Time'] >> 11) & 0x1f,
				($info['Time'] >> 5) & 0x3f,
				($info['Time'] << 1) & 0x3e,
				($info['Time'] >> 21) & 0x07,
				($info['Time'] >> 16) & 0x1f,
				(($info['Time'] >> 25) & 0x7f) + 1980
			);

			if ($dataLength < $fhStart + 43)
			{
				throw new \RuntimeException('Invalid ZIP data');
			}

			$info = unpack('vInternal/VExternal/VOffset', substr($data, $fhStart + 36, 10));

			$entries[$name]['type'] = ($info['Internal'] & 0x01) ? 'text' : 'binary';
			$entries[$name]['attr'] = (($info['External'] & 0x10) ? 'D' : '-') . (($info['External'] & 0x20) ? 'A' : '-')
				. (($info['External'] & 0x03) ? 'S' : '-') . (($info['External'] & 0x02) ? 'H' : '-') . (($info['External'] & 0x01) ? 'R' : '-');
			$entries[$name]['offset'] = $info['Offset'];

			// Get details from local file header since we have the offset
			$lfhStart = strpos($data, $this->fileHeader, $entries[$name]['offset']);

			if ($dataLength < $lfhStart + 34)
			{
				throw new \RuntimeException('Invalid ZIP Data');
			}

			$info                         = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength', substr($data, $lfhStart + 8, 25));
			$name                         = substr($data, $lfhStart + 30, $info['Length']);
			$entries[$name]['_dataStart'] = $lfhStart + 30 + $info['Length'] + $info['ExtraLength'];

			// Bump the max execution time because not using the built in php zip libs makes this process slow.
			@set_time_limit(ini_get('max_execution_time'));
		}
		while (($fhStart = strpos($data, $this->ctrlDirHeader, $fhStart + 46)) !== false);

		$this->metadata = array_values($entries);

		return true;
	}

	/**
	 * Returns the file data for a file by offsest in the ZIP archive
	 *
	 * @param   integer  $key  The position of the file in the archive.
	 *
	 * @return  string  Uncompressed file data buffer.
	 *
	 * @since   1.0
	 */
	private function getFileData($key)
	{
		if ($this->metadata[$key]['_method'] == 0x8)
		{
			return gzinflate(substr($this->data, $this->metadata[$key]['_dataStart'], $this->metadata[$key]['csize']));
		}

		if ($this->metadata[$key]['_method'] == 0x0)
		{
			// Files that aren't compressed.
			return substr($this->data, $this->metadata[$key]['_dataStart'], $this->metadata[$key]['csize']);
		}

		if ($this->metadata[$key]['_method'] == 0x12)
		{
			// If bz2 extension is loaded use it
			if (\extension_loaded('bz2'))
			{
				return bzdecompress(substr($this->data, $this->metadata[$key]['_dataStart'], $this->metadata[$key]['csize']));
			}
		}

		return '';
	}

	/**
	 * Converts a UNIX timestamp to a 4-byte DOS date and time format
	 * (date in high 2-bytes, time in low 2-bytes allowing magnitude
	 * comparison).
	 *
	 * @param   integer  $unixtime  The current UNIX timestamp.
	 *
	 * @return  integer  The current date in a 4-byte DOS format.
	 *
	 * @since   1.0
	 */
	protected function unix2DosTime($unixtime = null)
	{
		$timearray = $unixtime === null ? getdate() : getdate($unixtime);

		if ($timearray['year'] < 1980)
		{
			$timearray['year']    = 1980;
			$timearray['mon']     = 1;
			$timearray['mday']    = 1;
			$timearray['hours']   = 0;
			$timearray['minutes'] = 0;
			$timearray['seconds'] = 0;
		}

		return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) | ($timearray['hours'] << 11) |
			($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
	}

	/**
	 * Adds a "file" to the ZIP archive.
	 *
	 * @param   array  $file      File data array to add
	 * @param   array  $contents  An array of existing zipped files.
	 * @param   array  $ctrldir   An array of central directory information.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @todo    Review and finish implementation
	 */
	private function addToZipFile(array &$file, array &$contents, array &$ctrldir)
	{
		$data = &$file['data'];
		$name = str_replace('\\', '/', $file['name']);

		// See if time/date information has been provided.
		$ftime = null;

		if (isset($file['time']))
		{
			$ftime = $file['time'];
		}

		// Get the hex time.
		$dtime    = dechex($this->unix2DosTime($ftime));
		$hexdtime = \chr(hexdec($dtime[6] . $dtime[7])) . \chr(hexdec($dtime[4] . $dtime[5])) . \chr(hexdec($dtime[2] . $dtime[3]))
			. \chr(hexdec($dtime[0] . $dtime[1]));

		// Begin creating the ZIP data.
		$fr = $this->fileHeader;

		// Version needed to extract.
		$fr .= "\x14\x00";

		// General purpose bit flag.
		$fr .= "\x00\x00";

		// Compression method.
		$fr .= "\x08\x00";

		// Last modification time/date.
		$fr .= $hexdtime;

		// "Local file header" segment.
		$uncLen = \strlen($data);
		$crc    = crc32($data);
		$zdata  = gzcompress($data);
		$zdata  = substr(substr($zdata, 0, \strlen($zdata) - 4), 2);
		$cLen   = \strlen($zdata);

		// CRC 32 information.
		$fr .= pack('V', $crc);

		// Compressed filesize.
		$fr .= pack('V', $cLen);

		// Uncompressed filesize.
		$fr .= pack('V', $uncLen);

		// Length of filename.
		$fr .= pack('v', \strlen($name));

		// Extra field length.
		$fr .= pack('v', 0);

		// File name.
		$fr .= $name;

		// "File data" segment.
		$fr .= $zdata;

		// Add this entry to array.
		$oldOffset  = \strlen(implode('', $contents));
		$contents[] = &$fr;

		// Add to central directory record.
		$cdrec = $this->ctrlDirHeader;

		// Version made by.
		$cdrec .= "\x00\x00";

		// Version needed to extract
		$cdrec .= "\x14\x00";

		// General purpose bit flag
		$cdrec .= "\x00\x00";

		// Compression method
		$cdrec .= "\x08\x00";

		// Last mod time/date.
		$cdrec .= $hexdtime;

		// CRC 32 information.
		$cdrec .= pack('V', $crc);

		// Compressed filesize.
		$cdrec .= pack('V', $cLen);

		// Uncompressed filesize.
		$cdrec .= pack('V', $uncLen);

		// Length of filename.
		$cdrec .= pack('v', \strlen($name));

		// Extra field length.
		$cdrec .= pack('v', 0);

		// File comment length.
		$cdrec .= pack('v', 0);

		// Disk number start.
		$cdrec .= pack('v', 0);

		// Internal file attributes.
		$cdrec .= pack('v', 0);

		// External file attributes -'archive' bit set.
		$cdrec .= pack('V', 32);

		// Relative offset of local header.
		$cdrec .= pack('V', $oldOffset);

		// File name.
		$cdrec .= $name;

		// Save to central directory array.
		$ctrldir[] = &$cdrec;
	}

	/**
	 * Creates the ZIP file.
	 *
	 * Official ZIP file format: http://www.pkware.com/appnote.txt
	 *
	 * @param   array   $contents  An array of existing zipped files.
	 * @param   array   $ctrlDir   An array of central directory information.
	 * @param   string  $path      The path to store the archive.
	 *
	 * @return  boolean  True if successful
	 *
	 * @since   1.0
	 * @todo	Review and finish implementation
	 */
	private function createZipFile(array &$contents, array &$ctrlDir, $path)
	{
		$data = implode('', $contents);
		$dir  = implode('', $ctrlDir);

		/*
		 * Buffer data:
		 * Total # of entries "on this disk".
		 * Total # of entries overall.
		 * Size of central directory.
		 * Offset to start of central dir.
		 * ZIP file comment length.
		 */
		$buffer = $data . $dir . $this->ctrlDirEnd .
		pack('v', \count($ctrlDir)) .
		pack('v', \count($ctrlDir)) .
		pack('V', \strlen($dir)) .
		pack('V', \strlen($data)) .
		"\x00\x00";

		return File::write($path, $buffer);
	}
}
vendor/joomla/archive/src/Tar.php000064400000014211152177723700013003 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;
use Joomla\Filesystem\Path;

/**
 * Tar format adapter for the Archive package
 *
 * This class is inspired from and draws heavily in code and concept from the Compress package of
 * The Horde Project <http://www.horde.org>
 *
 * @contributor  Michael Slusarz <slusarz@horde.org>
 * @contributor  Michael Cochrane <mike@graftonhall.co.nz>
 *
 * @since  1.0
 */
class Tar implements ExtractableInterface
{
	/**
	 * Tar file types.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $types = array(
		0x0  => 'Unix file',
		0x30 => 'File',
		0x31 => 'Link',
		0x32 => 'Symbolic link',
		0x33 => 'Character special file',
		0x34 => 'Block special file',
		0x35 => 'Directory',
		0x36 => 'FIFO special file',
		0x37 => 'Contiguous file',
	);

	/**
	 * Tar file data buffer
	 *
	 * @var    string
	 * @since  1.0
	 */
	private $data;

	/**
	 * Tar file metadata array
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $metadata;

	/**
	 * Holds the options array.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	protected $options = array();

	/**
	 * Create a new Archive object.
	 *
	 * @param   array|\ArrayAccess  $options  An array of options or an object that implements \ArrayAccess
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array())
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		$this->options = $options;
	}

	/**
	 * Extract a ZIP compressed file to a given path
	 *
	 * @param   string  $archive      Path to ZIP archive to extract
	 * @param   string  $destination  Path to extract archive into
	 *
	 * @return  boolean True if successful
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function extract($archive, $destination)
	{
		$this->data     = null;
		$this->metadata = null;

		$this->data = file_get_contents($archive);

		if (!$this->data)
		{
			throw new \RuntimeException('Unable to read archive');
		}

		$this->getTarInfo($this->data);

		for ($i = 0, $n = \count($this->metadata); $i < $n; $i++)
		{
			$type = strtolower($this->metadata[$i]['type']);

			if ($type == 'file' || $type == 'unix file')
			{
				$buffer = $this->metadata[$i]['data'];
				$path   = Path::clean($destination . '/' . $this->metadata[$i]['name']);

				// Make sure the destination folder exists
				if (!Folder::create(\dirname($path)))
				{
					throw new \RuntimeException('Unable to create destination folder ' . \dirname($path));
				}

				if (!File::write($path, $buffer))
				{
					throw new \RuntimeException('Unable to write entry to file ' . $path);
				}
			}
		}

		return true;
	}

	/**
	 * Tests whether this adapter can unpack files on this computer.
	 *
	 * @return  boolean  True if supported
	 *
	 * @since   1.0
	 */
	public static function isSupported()
	{
		return true;
	}

	/**
	 * Get the list of files/data from a Tar archive buffer.
	 *
	 * @param   string  $data  The Tar archive buffer.
	 *
	 * @return  array  Archive metadata array
	 * <pre>
	 * KEY: Position in the array
	 * VALUES: 'attr'  --  File attributes
	 * 'data'  --  Raw file contents
	 * 'date'  --  File modification time
	 * 'name'  --  Filename
	 * 'size'  --  Original file size
	 * 'type'  --  File type
	 * </pre>
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function getTarInfo(&$data)
	{
		$position    = 0;
		$returnArray = array();

		while ($position < \strlen($data))
		{
			if (version_compare(PHP_VERSION, '5.5', '>='))
			{
				$info = @unpack(
					'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Ctypeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor',
					substr($data, $position)
				);
			}
			else
			{
				$info = @unpack(
					'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/Ctypeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor',
					substr($data, $position)
				);
			}

			/*
			 * This variable has been set in the previous loop, meaning that the filename was present in the previous block
			 * to allow more than 100 characters - see below
			 */
			if (isset($longlinkfilename))
			{
				$info['filename'] = $longlinkfilename;
				unset($longlinkfilename);
			}

			if (!$info)
			{
				throw new \RuntimeException('Unable to decompress data');
			}

			$position += 512;
			$contents = substr($data, $position, octdec($info['size']));
			$position += ceil(octdec($info['size']) / 512) * 512;

			if ($info['filename'])
			{
				$file = array(
					'attr' => null,
					'data' => null,
					'date' => octdec($info['mtime']),
					'name' => trim($info['filename']),
					'size' => octdec($info['size']),
					'type' => isset($this->types[$info['typeflag']]) ? $this->types[$info['typeflag']] : null,
				);

				if (($info['typeflag'] == 0) || ($info['typeflag'] == 0x30) || ($info['typeflag'] == 0x35))
				{
					// File or folder.
					$file['data'] = $contents;

					$mode         = hexdec(substr($info['mode'], 4, 3));
					$file['attr'] = (($info['typeflag'] == 0x35) ? 'd' : '-')
						. (($mode & 0x400) ? 'r' : '-')
						. (($mode & 0x200) ? 'w' : '-')
						. (($mode & 0x100) ? 'x' : '-')
						. (($mode & 0x040) ? 'r' : '-')
						. (($mode & 0x020) ? 'w' : '-')
						. (($mode & 0x010) ? 'x' : '-')
						. (($mode & 0x004) ? 'r' : '-')
						. (($mode & 0x002) ? 'w' : '-')
						. (($mode & 0x001) ? 'x' : '-');
				}
				elseif (\chr($info['typeflag']) == 'L' && $info['filename'] == '././@LongLink')
				{
					// GNU tar ././@LongLink support - the filename is actually in the contents, set a variable here so we can test in the next loop
					$longlinkfilename = $contents;

					// And the file contents are in the next block so we'll need to skip this
					continue;
				}

				$returnArray[] = $file;
			}
		}

		$this->metadata = $returnArray;

		return true;
	}
}
vendor/joomla/archive/src/Archive.php000064400000012540152177723700013641 0ustar00<?php
/**
 * Part of the Joomla Framework Archive Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Archive;

use Joomla\Filesystem\File;
use Joomla\Filesystem\Folder;

/**
 * An Archive handling class
 *
 * @since  1.0
 */
class Archive
{
	/**
	 * The array of instantiated archive adapters.
	 *
	 * @var    ExtractableInterface[]
	 * @since  1.0
	 */
	protected $adapters = array();

	/**
	 * Holds the options array.
	 *
	 * @var    array|\ArrayAccess
	 * @since  1.0
	 */
	public $options = array();

	/**
	 * Create a new Archive object.
	 *
	 * @param   array|\ArrayAccess  $options  An array of options
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($options = array())
	{
		if (!\is_array($options) && !($options instanceof \ArrayAccess))
		{
			throw new \InvalidArgumentException(
				'The options param must be an array or implement the ArrayAccess interface.'
			);
		}

		// Make sure we have a tmp directory.
		isset($options['tmp_path']) || $options['tmp_path'] = realpath(sys_get_temp_dir());

		$this->options = $options;
	}

	/**
	 * Extract an archive file to a directory.
	 *
	 * @param   string  $archivename  The name of the archive file
	 * @param   string  $extractdir   Directory to unpack into
	 *
	 * @return  boolean  True for success
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function extract($archivename, $extractdir)
	{
		$ext      = pathinfo($archivename, PATHINFO_EXTENSION);
		$path     = pathinfo($archivename, PATHINFO_DIRNAME);
		$filename = pathinfo($archivename, PATHINFO_FILENAME);

		switch (strtolower($ext))
		{
			case 'zip':
				$result = $this->getAdapter('zip')->extract($archivename, $extractdir);

				break;

			case 'tar':
				$result = $this->getAdapter('tar')->extract($archivename, $extractdir);

				break;

			case 'tgz':
			case 'gz':
			case 'gzip':
				// This may just be an individual file (e.g. sql script)
				$tmpfname = $this->options['tmp_path'] . '/' . uniqid('gzip');

				try
				{
					$this->getAdapter('gzip')->extract($archivename, $tmpfname);
				}
				catch (\RuntimeException $exception)
				{
					@unlink($tmpfname);

					return false;
				}

				if ($ext === 'tgz' || stripos($filename, '.tar') !== false)
				{
					$result = $this->getAdapter('tar')->extract($tmpfname, $extractdir);
				}
				else
				{
					Folder::create($extractdir);
					$result = File::copy($tmpfname, $extractdir . '/' . $filename, null, 0);
				}

				@unlink($tmpfname);

				break;

			case 'tbz2':
			case 'bz2':
			case 'bzip2':
				// This may just be an individual file (e.g. sql script)
				$tmpfname = $this->options['tmp_path'] . '/' . uniqid('bzip2');

				try
				{
					$this->getAdapter('bzip2')->extract($archivename, $tmpfname);
				}
				catch (\RuntimeException $exception)
				{
					@unlink($tmpfname);

					return false;
				}

				if ($ext === 'tbz2' || stripos($filename, '.tar') !== false)
				{
					$result = $this->getAdapter('tar')->extract($tmpfname, $extractdir);
				}
				else
				{
					Folder::create($extractdir);
					$result = File::copy($tmpfname, $extractdir . '/' . $filename, null, 0);
				}

				@unlink($tmpfname);

				break;

			default:
				throw new \InvalidArgumentException(sprintf('Unknown archive type: %s', $ext));
		}

		return $result;
	}

	/**
	 * Method to override the provided adapter with your own implementation.
	 *
	 * @param   string   $type      Name of the adapter to set.
	 * @param   string   $class     FQCN of your class which implements ExtractableInterface.
	 * @param   boolean  $override  True to force override the adapter type.
	 *
	 * @return  Archive  This object for chaining.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function setAdapter($type, $class, $override = true)
	{
		if ($override || !isset($this->adapters[$type]))
		{
			$error = !\is_object($class) && !class_exists($class)
					? 'Archive adapter "%s" (class "%s") not found.'
					: '';

			$error = $error == '' && !($class instanceof ExtractableInterface)
					? 'The provided adapter "%s" (class "%s") must implement Joomla\\Archive\\ExtractableInterface'
					: $error;

			$error = $error == '' && !$class::isSupported()
					? 'Archive adapter "%s" (class "%s") not supported.'
					: $error;

			if ($error != '')
			{
				throw new \InvalidArgumentException(
					sprintf($error, $type, $class)
				);
			}

			$this->adapters[$type] = new $class($this->options);
		}

		return $this;
	}

	/**
	 * Get a file compression adapter.
	 *
	 * @param   string  $type  The type of adapter (bzip2|gzip|tar|zip).
	 *
	 * @return  ExtractableInterface  Adapter for the requested type
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function getAdapter($type)
	{
		$type = strtolower($type);

		if (!isset($this->adapters[$type]))
		{
			// Try to load the adapter object
			/** @var ExtractableInterface $class */
			$class = 'Joomla\\Archive\\' . ucfirst($type);

			if (!class_exists($class) || !$class::isSupported())
			{
				throw new \InvalidArgumentException(
					sprintf(
						'Archive adapter "%s" (class "%s") not found or supported.',
						$type,
						$class
					)
				);
			}

			$this->adapters[$type] = new $class($this->options);
		}

		return $this->adapters[$type];
	}
}
vendor/joomla/string/LICENSE000064400000042630152177723700011655 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/string/src/StringHelper.php000064400000053437152177723700014565 0ustar00<?php
/**
 * Part of the Joomla Framework String Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\String;

// PHP mbstring and iconv local configuration
if (version_compare(PHP_VERSION, '5.6', '>='))
{
	@ini_set('default_charset', 'UTF-8');
}
else
{
	// Check if mbstring extension is loaded and attempt to load it if not present except for windows
	if (\extension_loaded('mbstring'))
	{
		@ini_set('mbstring.internal_encoding', 'UTF-8');
		@ini_set('mbstring.http_input', 'UTF-8');
		@ini_set('mbstring.http_output', 'UTF-8');
	}

	// Same for iconv
	if (\function_exists('iconv'))
	{
		iconv_set_encoding('internal_encoding', 'UTF-8');
		iconv_set_encoding('input_encoding', 'UTF-8');
		iconv_set_encoding('output_encoding', 'UTF-8');
	}
}

/**
 * String handling class for UTF-8 data wrapping the phputf8 library. All functions assume the validity of UTF-8 strings.
 *
 * @since  1.3.0
 */
abstract class StringHelper
{
	/**
	 * Increment styles.
	 *
	 * @var    array
	 * @since  1.3.0
	 */
	protected static $incrementStyles = array(
		'dash' => array(
			'#-(\d+)$#',
			'-%d',
		),
		'default' => array(
			array('#\((\d+)\)$#', '#\(\d+\)$#'),
			array(' (%d)', '(%d)'),
		),
	);

	/**
	 * Increments a trailing number in a string.
	 *
	 * Used to easily create distinct labels when copying objects. The method has the following styles:
	 *
	 * default: "Label" becomes "Label (2)"
	 * dash:    "Label" becomes "Label-2"
	 *
	 * @param   string   $string  The source string.
	 * @param   string   $style   The the style (default|dash).
	 * @param   integer  $n       If supplied, this number is used for the copy, otherwise it is the 'next' number.
	 *
	 * @return  string  The incremented string.
	 *
	 * @since   1.3.0
	 */
	public static function increment($string, $style = 'default', $n = 0)
	{
		$styleSpec = isset(static::$incrementStyles[$style]) ? static::$incrementStyles[$style] : static::$incrementStyles['default'];

		// Regular expression search and replace patterns.
		if (\is_array($styleSpec[0]))
		{
			$rxSearch  = $styleSpec[0][0];
			$rxReplace = $styleSpec[0][1];
		}
		else
		{
			$rxSearch = $rxReplace = $styleSpec[0];
		}

		// New and old (existing) sprintf formats.
		if (\is_array($styleSpec[1]))
		{
			$newFormat = $styleSpec[1][0];
			$oldFormat = $styleSpec[1][1];
		}
		else
		{
			$newFormat = $oldFormat = $styleSpec[1];
		}

		// Check if we are incrementing an existing pattern, or appending a new one.
		if (preg_match($rxSearch, $string, $matches))
		{
			$n      = empty($n) ? ($matches[1] + 1) : $n;
			$string = preg_replace($rxReplace, sprintf($oldFormat, $n), $string);
		}
		else
		{
			$n = empty($n) ? 2 : $n;
			$string .= sprintf($newFormat, $n);
		}

		return $string;
	}

	/**
	 * Tests whether a string contains only 7bit ASCII bytes.
	 *
	 * You might use this to conditionally check whether a string needs handling as UTF-8 or not, potentially offering performance
	 * benefits by using the native PHP equivalent if it's just ASCII e.g.;
	 *
	 * <code>
	 * if (StringHelper::is_ascii($someString))
	 * {
	 *     // It's just ASCII - use the native PHP version
	 *     $someString = strtolower($someString);
	 * }
	 * else
	 * {
	 *     $someString = StringHelper::strtolower($someString);
	 * }
	 * </code>
	 *
	 * @param   string  $str  The string to test.
	 *
	 * @return  boolean True if the string is all ASCII
	 *
	 * @since   1.3.0
	 */
	public static function is_ascii($str)
	{
		return utf8_is_ascii($str);
	}

	/**
	 * UTF-8 aware alternative to ord()
	 *
	 * Returns the unicode ordinal for a character.
	 *
	 * @param   string  $chr  UTF-8 encoded character
	 *
	 * @return  integer Unicode ordinal for the character
	 *
	 * @link    https://www.php.net/ord
	 * @since   1.4.0
	 */
	public static function ord($chr)
	{
		return utf8_ord($chr);
	}

	/**
	 * UTF-8 aware alternative to strpos()
	 *
	 * Find position of first occurrence of a string.
	 *
	 * @param   string   $str     String being examined
	 * @param   string   $search  String being searched for
	 * @param   integer  $offset  Optional, specifies the position from which the search should be performed
	 *
	 * @return  integer|boolean  Number of characters before the first match or FALSE on failure
	 *
	 * @link    https://www.php.net/strpos
	 * @since   1.3.0
	 */
	public static function strpos($str, $search, $offset = false)
	{
		if ($offset === false)
		{
			return utf8_strpos($str, $search);
		}

		return utf8_strpos($str, $search, $offset);
	}

	/**
	 * UTF-8 aware alternative to strrpos()
	 *
	 * Finds position of last occurrence of a string.
	 *
	 * @param   string   $str     String being examined.
	 * @param   string   $search  String being searched for.
	 * @param   integer  $offset  Offset from the left of the string.
	 *
	 * @return  integer|boolean  Number of characters before the last match or false on failure
	 *
	 * @link    https://www.php.net/strrpos
	 * @since   1.3.0
	 */
	public static function strrpos($str, $search, $offset = 0)
	{
		return utf8_strrpos($str, $search, $offset);
	}

	/**
	 * UTF-8 aware alternative to substr()
	 *
	 * Return part of a string given character offset (and optionally length).
	 *
	 * @param   string   $str     String being processed
	 * @param   integer  $offset  Number of UTF-8 characters offset (from left)
	 * @param   integer  $length  Optional length in UTF-8 characters from offset
	 *
	 * @return  string|boolean
	 *
	 * @link    https://www.php.net/substr
	 * @since   1.3.0
	 */
	public static function substr($str, $offset, $length = false)
	{
		if ($length === false)
		{
			return utf8_substr($str, $offset);
		}

		return utf8_substr($str, $offset, $length);
	}

	/**
	 * UTF-8 aware alternative to strtolower()
	 *
	 * Make a string lowercase
	 *
	 * Note: The concept of a characters "case" only exists is some alphabets such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
	 * not exist in the Chinese alphabet, for example. See Unicode Standard Annex #21: Case Mappings
	 *
	 * @param   string  $str  String being processed
	 *
	 * @return  string|boolean  Either string in lowercase or FALSE is UTF-8 invalid
	 *
	 * @link    https://www.php.net/strtolower
	 * @since   1.3.0
	 */
	public static function strtolower($str)
	{
		return utf8_strtolower($str);
	}

	/**
	 * UTF-8 aware alternative to strtoupper()
	 *
	 * Make a string uppercase
	 *
	 * Note: The concept of a characters "case" only exists is some alphabets such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
	 * not exist in the Chinese alphabet, for example. See Unicode Standard Annex #21: Case Mappings
	 *
	 * @param   string  $str  String being processed
	 *
	 * @return  string|boolean  Either string in uppercase or FALSE is UTF-8 invalid
	 *
	 * @link    https://www.php.net/strtoupper
	 * @since   1.3.0
	 */
	public static function strtoupper($str)
	{
		return utf8_strtoupper($str);
	}

	/**
	 * UTF-8 aware alternative to strlen()
	 *
	 * Returns the number of characters in the string (NOT THE NUMBER OF BYTES).
	 *
	 * @param   string  $str  UTF-8 string.
	 *
	 * @return  integer  Number of UTF-8 characters in string.
	 *
	 * @link    https://www.php.net/strlen
	 * @since   1.3.0
	 */
	public static function strlen($str)
	{
		return utf8_strlen($str);
	}

	/**
	 * UTF-8 aware alternative to str_ireplace()
	 *
	 * Case-insensitive version of str_replace()
	 *
	 * @param   string   $search   String to search
	 * @param   string   $replace  Existing string to replace
	 * @param   string   $str      New string to replace with
	 * @param   integer  $count    Optional count value to be passed by referene
	 *
	 * @return  string  UTF-8 String
	 *
	 * @link    https://www.php.net/str_ireplace
	 * @since   1.3.0
	 */
	public static function str_ireplace($search, $replace, $str, $count = null)
	{
		if ($count === false)
		{
			return utf8_ireplace($search, $replace, $str);
		}

		return utf8_ireplace($search, $replace, $str, $count);
	}

	/**
	 * UTF-8 aware alternative to str_pad()
	 *
	 * Pad a string to a certain length with another string.
	 * $padStr may contain multi-byte characters.
	 *
	 * @param   string   $input   The input string.
	 * @param   integer  $length  If the value is negative, less than, or equal to the length of the input string, no padding takes place.
	 * @param   string   $padStr  The string may be truncated if the number of padding characters can't be evenly divided by the string's length.
	 * @param   integer  $type    The type of padding to apply
	 *
	 * @return  string
	 *
	 * @link    https://www.php.net/str_pad
	 * @since   1.4.0
	 */
	public static function str_pad($input, $length, $padStr = ' ', $type = STR_PAD_RIGHT)
	{
		return utf8_str_pad($input, $length, $padStr, $type);
	}

	/**
	 * UTF-8 aware alternative to str_split()
	 *
	 * Convert a string to an array.
	 *
	 * @param   string   $str       UTF-8 encoded string to process
	 * @param   integer  $splitLen  Number to characters to split string by
	 *
	 * @return  array
	 *
	 * @link    https://www.php.net/str_split
	 * @since   1.3.0
	 */
	public static function str_split($str, $splitLen = 1)
	{
		return utf8_str_split($str, $splitLen);
	}

	/**
	 * UTF-8/LOCALE aware alternative to strcasecmp()
	 *
	 * A case insensitive string comparison.
	 *
	 * @param   string  $str1    string 1 to compare
	 * @param   string  $str2    string 2 to compare
	 * @param   mixed   $locale  The locale used by strcoll or false to use classical comparison
	 *
	 * @return  integer   < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.
	 *
	 * @link    https://www.php.net/strcasecmp
	 * @link    https://www.php.net/strcoll
	 * @link    https://www.php.net/setlocale
	 * @since   1.3.0
	 */
	public static function strcasecmp($str1, $str2, $locale = false)
	{
		if ($locale)
		{
			// Get current locale
			$locale0 = setlocale(LC_COLLATE, 0);

			if (!$locale = setlocale(LC_COLLATE, $locale))
			{
				$locale = $locale0;
			}

			// See if we have successfully set locale to UTF-8
			if (!stristr($locale, 'UTF-8') && stristr($locale, '_') && preg_match('~\.(\d+)$~', $locale, $m))
			{
				$encoding = 'CP' . $m[1];
			}
			elseif (stristr($locale, 'UTF-8') || stristr($locale, 'utf8'))
			{
				$encoding = 'UTF-8';
			}
			else
			{
				$encoding = 'nonrecodable';
			}

			// If we successfully set encoding it to utf-8 or encoding is sth weird don't recode
			if ($encoding == 'UTF-8' || $encoding == 'nonrecodable')
			{
				return strcoll(utf8_strtolower($str1), utf8_strtolower($str2));
			}

			return strcoll(
				static::transcode(utf8_strtolower($str1), 'UTF-8', $encoding),
				static::transcode(utf8_strtolower($str2), 'UTF-8', $encoding)
			);
		}

		return utf8_strcasecmp($str1, $str2);
	}

	/**
	 * UTF-8/LOCALE aware alternative to strcmp()
	 *
	 * A case sensitive string comparison.
	 *
	 * @param   string  $str1    string 1 to compare
	 * @param   string  $str2    string 2 to compare
	 * @param   mixed   $locale  The locale used by strcoll or false to use classical comparison
	 *
	 * @return  integer  < 0 if str1 is less than str2; > 0 if str1 is greater than str2, and 0 if they are equal.
	 *
	 * @link    https://www.php.net/strcmp
	 * @link    https://www.php.net/strcoll
	 * @link    https://www.php.net/setlocale
	 * @since   1.3.0
	 */
	public static function strcmp($str1, $str2, $locale = false)
	{
		if ($locale)
		{
			// Get current locale
			$locale0 = setlocale(LC_COLLATE, 0);

			if (!$locale = setlocale(LC_COLLATE, $locale))
			{
				$locale = $locale0;
			}

			// See if we have successfully set locale to UTF-8
			if (!stristr($locale, 'UTF-8') && stristr($locale, '_') && preg_match('~\.(\d+)$~', $locale, $m))
			{
				$encoding = 'CP' . $m[1];
			}
			elseif (stristr($locale, 'UTF-8') || stristr($locale, 'utf8'))
			{
				$encoding = 'UTF-8';
			}
			else
			{
				$encoding = 'nonrecodable';
			}

			// If we successfully set encoding it to utf-8 or encoding is sth weird don't recode
			if ($encoding == 'UTF-8' || $encoding == 'nonrecodable')
			{
				return strcoll($str1, $str2);
			}

			return strcoll(static::transcode($str1, 'UTF-8', $encoding), static::transcode($str2, 'UTF-8', $encoding));
		}

		return strcmp($str1, $str2);
	}

	/**
	 * UTF-8 aware alternative to strcspn()
	 *
	 * Find length of initial segment not matching mask.
	 *
	 * @param   string   $str     The string to process
	 * @param   string   $mask    The mask
	 * @param   integer  $start   Optional starting character position (in characters)
	 * @param   integer  $length  Optional length
	 *
	 * @return  integer  The length of the initial segment of str1 which does not contain any of the characters in str2
	 *
	 * @link    https://www.php.net/strcspn
	 * @since   1.3.0
	 */
	public static function strcspn($str, $mask, $start = null, $length = null)
	{
		if ($start === false && $length === false)
		{
			return utf8_strcspn($str, $mask);
		}

		if ($length === false)
		{
			return utf8_strcspn($str, $mask, $start);
		}

		return utf8_strcspn($str, $mask, $start, $length);
	}

	/**
	 * UTF-8 aware alternative to stristr()
	 *
	 * Returns all of haystack from the first occurrence of needle to the end. Needle and haystack are examined in a case-insensitive manner to
	 * find the first occurrence of a string using case insensitive comparison.
	 *
	 * @param   string  $str     The haystack
	 * @param   string  $search  The needle
	 *
	 * @return string the sub string
	 *
	 * @link    https://www.php.net/stristr
	 * @since   1.3.0
	 */
	public static function stristr($str, $search)
	{
		return utf8_stristr($str, $search);
	}

	/**
	 * UTF-8 aware alternative to strrev()
	 *
	 * Reverse a string.
	 *
	 * @param   string  $str  String to be reversed
	 *
	 * @return  string   The string in reverse character order
	 *
	 * @link    https://www.php.net/strrev
	 * @since   1.3.0
	 */
	public static function strrev($str)
	{
		return utf8_strrev($str);
	}

	/**
	 * UTF-8 aware alternative to strspn()
	 *
	 * Find length of initial segment matching mask.
	 *
	 * @param   string   $str     The haystack
	 * @param   string   $mask    The mask
	 * @param   integer  $start   Start optional
	 * @param   integer  $length  Length optional
	 *
	 * @return  integer
	 *
	 * @link    https://www.php.net/strspn
	 * @since   1.3.0
	 */
	public static function strspn($str, $mask, $start = null, $length = null)
	{
		if ($start === null && $length === null)
		{
			return utf8_strspn($str, $mask);
		}

		if ($length === null)
		{
			return utf8_strspn($str, $mask, $start);
		}

		return utf8_strspn($str, $mask, $start, $length);
	}

	/**
	 * UTF-8 aware alternative to substr_replace()
	 *
	 * Replace text within a portion of a string.
	 *
	 * @param   string   $str     The haystack
	 * @param   string   $repl    The replacement string
	 * @param   integer  $start   Start
	 * @param   integer  $length  Length (optional)
	 *
	 * @return  string
	 *
	 * @link    https://www.php.net/substr_replace
	 * @since   1.3.0
	 */
	public static function substr_replace($str, $repl, $start, $length = null)
	{
		// Loaded by library loader
		if ($length === false)
		{
			return utf8_substr_replace($str, $repl, $start);
		}

		return utf8_substr_replace($str, $repl, $start, $length);
	}

	/**
	 * UTF-8 aware replacement for ltrim()
	 *
	 * Strip whitespace (or other characters) from the beginning of a string. You only need to use this if you are supplying the charlist
	 * optional arg and it contains UTF-8 characters. Otherwise ltrim will work normally on a UTF-8 string.
	 *
	 * @param   string  $str       The string to be trimmed
	 * @param   string  $charlist  The optional charlist of additional characters to trim
	 *
	 * @return  string  The trimmed string
	 *
	 * @link    https://www.php.net/ltrim
	 * @since   1.3.0
	 */
	public static function ltrim($str, $charlist = false)
	{
		if (empty($charlist) && $charlist !== false)
		{
			return $str;
		}

		if ($charlist === false)
		{
			return utf8_ltrim($str);
		}

		return utf8_ltrim($str, $charlist);
	}

	/**
	 * UTF-8 aware replacement for rtrim()
	 *
	 * Strip whitespace (or other characters) from the end of a string. You only need to use this if you are supplying the charlist
	 * optional arg and it contains UTF-8 characters. Otherwise rtrim will work normally on a UTF-8 string.
	 *
	 * @param   string  $str       The string to be trimmed
	 * @param   string  $charlist  The optional charlist of additional characters to trim
	 *
	 * @return  string  The trimmed string
	 *
	 * @link    https://www.php.net/rtrim
	 * @since   1.3.0
	 */
	public static function rtrim($str, $charlist = false)
	{
		if (empty($charlist) && $charlist !== false)
		{
			return $str;
		}

		if ($charlist === false)
		{
			return utf8_rtrim($str);
		}

		return utf8_rtrim($str, $charlist);
	}

	/**
	 * UTF-8 aware replacement for trim()
	 *
	 * Strip whitespace (or other characters) from the beginning and end of a string. You only need to use this if you are supplying the charlist
	 * optional arg and it contains UTF-8 characters. Otherwise trim will work normally on a UTF-8 string
	 *
	 * @param   string  $str       The string to be trimmed
	 * @param   string  $charlist  The optional charlist of additional characters to trim
	 *
	 * @return  string  The trimmed string
	 *
	 * @link    https://www.php.net/trim
	 * @since   1.3.0
	 */
	public static function trim($str, $charlist = false)
	{
		if (empty($charlist) && $charlist !== false)
		{
			return $str;
		}

		if ($charlist === false)
		{
			return utf8_trim($str);
		}

		return utf8_trim($str, $charlist);
	}

	/**
	 * UTF-8 aware alternative to ucfirst()
	 *
	 * Make a string's first character uppercase or all words' first character uppercase.
	 *
	 * @param   string  $str           String to be processed
	 * @param   string  $delimiter     The words delimiter (null means do not split the string)
	 * @param   string  $newDelimiter  The new words delimiter (null means equal to $delimiter)
	 *
	 * @return  string  If $delimiter is null, return the string with first character as upper case (if applicable)
	 *                  else consider the string of words separated by the delimiter, apply the ucfirst to each words
	 *                  and return the string with the new delimiter
	 *
	 * @link    https://www.php.net/ucfirst
	 * @since   1.3.0
	 */
	public static function ucfirst($str, $delimiter = null, $newDelimiter = null)
	{
		if ($delimiter === null)
		{
			return utf8_ucfirst($str);
		}

		if ($newDelimiter === null)
		{
			$newDelimiter = $delimiter;
		}

		return implode($newDelimiter, array_map('utf8_ucfirst', explode($delimiter, $str)));
	}

	/**
	 * UTF-8 aware alternative to ucwords()
	 *
	 * Uppercase the first character of each word in a string.
	 *
	 * @param   string  $str  String to be processed
	 *
	 * @return  string  String with first char of each word uppercase
	 *
	 * @link    https://www.php.net/ucwords
	 * @since   1.3.0
	 */
	public static function ucwords($str)
	{
		return utf8_ucwords($str);
	}

	/**
	 * Transcode a string.
	 *
	 * @param   string  $source        The string to transcode.
	 * @param   string  $fromEncoding  The source encoding.
	 * @param   string  $toEncoding    The target encoding.
	 *
	 * @return  mixed  The transcoded string, or null if the source was not a string.
	 *
	 * @link    https://bugs.php.net/bug.php?id=48147
	 *
	 * @since   1.3.0
	 */
	public static function transcode($source, $fromEncoding, $toEncoding)
	{
		if (\is_string($source))
		{
			switch (ICONV_IMPL)
			{
				case 'glibc':
					return @iconv($fromEncoding, $toEncoding . '//TRANSLIT,IGNORE', $source);

				case 'libiconv':
				default:
					return iconv($fromEncoding, $toEncoding . '//IGNORE//TRANSLIT', $source);
			}
		}
	}

	/**
	 * Tests a string as to whether it's valid UTF-8 and supported by the Unicode standard.
	 *
	 * Note: this function has been modified to simple return true or false.
	 *
	 * @param   string  $str  UTF-8 encoded string.
	 *
	 * @return  boolean  true if valid
	 *
	 * @author  <hsivonen@iki.fi>
	 * @link    https://hsivonen.fi/php-utf8/
	 * @see     compliant
	 * @since   1.3.0
	 */
	public static function valid($str)
	{
		return utf8_is_valid($str);
	}

	/**
	 * Tests whether a string complies as UTF-8.
	 *
	 * This will be much faster than StringHelper::valid() but will pass five and six octet UTF-8 sequences, which are not supported by Unicode and
	 * so cannot be displayed correctly in a browser. In other words it is not as strict as StringHelper::valid() but it's faster. If you use it to
	 * validate user input, you place yourself at the risk that attackers will be able to inject 5 and 6 byte sequences (which may or may not be a
	 * significant risk, depending on what you are are doing).
	 *
	 * @param   string  $str  UTF-8 string to check
	 *
	 * @return  boolean  TRUE if string is valid UTF-8
	 *
	 * @see     StringHelper::valid
	 * @link    https://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805
	 * @since   1.3.0
	 */
	public static function compliant($str)
	{
		return utf8_compliant($str);
	}

	/**
	 * Converts Unicode sequences to UTF-8 string.
	 *
	 * @param   string  $str  Unicode string to convert
	 *
	 * @return  string  UTF-8 string
	 *
	 * @since   1.3.0
	 */
	public static function unicode_to_utf8($str)
	{
		if (\extension_loaded('mbstring'))
		{
			return preg_replace_callback(
				'/\\\\u([0-9a-fA-F]{4})/',
				function ($match)
				{
					return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UCS-2BE');
				},
				$str
			);
		}

		return $str;
	}

	/**
	 * Converts Unicode sequences to UTF-16 string.
	 *
	 * @param   string  $str  Unicode string to convert
	 *
	 * @return  string  UTF-16 string
	 *
	 * @since   1.3.0
	 */
	public static function unicode_to_utf16($str)
	{
		if (\extension_loaded('mbstring'))
		{
			return preg_replace_callback(
				'/\\\\u([0-9a-fA-F]{4})/',
				function ($match)
				{
					return mb_convert_encoding(pack('H*', $match[1]), 'UTF-8', 'UTF-16BE');
				},
				$str
			);
		}

		return $str;
	}
}
vendor/joomla/string/src/Inflector.php000064400000026632152177723700014101 0ustar00<?php
/**
 * Part of the Joomla Framework String Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\String;

use InvalidArgumentException;

/**
 * Joomla Framework String Inflector Class
 *
 * The Inflector transforms words
 *
 * @since  1.0
 */
class Inflector
{
	/**
	 * The singleton instance.
	 *
	 * @var    Inflector
	 * @since  1.0
	 */
	private static $instance;

	/**
	 * The inflector rules for singularisation, pluralisation and countability.
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $rules = array(
		'singular' => array(
			'/(matr)ices$/i'                                                          => '\1ix',
			'/(vert|ind)ices$/i'                                                      => '\1ex',
			'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us',
			'/([ftw]ax)es/i'                                                          => '\1',
			'/(cris|ax|test)es$/i'                                                    => '\1is',
			'/(shoe|slave)s$/i'                                                       => '\1',
			'/(o)es$/i'                                                               => '\1',
			'/([^aeiouy]|qu)ies$/i'                                                   => '\1y',
			'/$1ses$/i'                                                               => '\s',
			'/ses$/i'                                                                 => '\s',
			'/eaus$/'                                                                 => 'eau',
			'/^(.*us)$/'                                                              => '\\1',
			'/s$/i'                                                                   => '',
		),
		'plural' => array(
			'/([m|l])ouse$/i'                                                        => '\1ice',
			'/(matr|vert|ind)(ix|ex)$/i'                                             => '\1ices',
			'/(x|ch|ss|sh)$/i'                                                       => '\1es',
			'/([^aeiouy]|qu)y$/i'                                                    => '\1ies',
			'/([^aeiouy]|qu)ies$/i'                                                  => '\1y',
			'/(?:([^f])fe|([lr])f)$/i'                                               => '\1\2ves',
			'/sis$/i'                                                                => 'ses',
			'/([ti])um$/i'                                                           => '\1a',
			'/(buffal|tomat)o$/i'                                                    => '\1\2oes',
			'/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i',
			'/us$/i'                                                                 => 'uses',
			'/(ax|cris|test)is$/i'                                                   => '\1es',
			'/s$/i'                                                                  => 's',
			'/$/'                                                                    => 's',
		),
		'countable' => array(
			'id',
			'hits',
			'clicks',
		),
	);

	/**
	 * Cached inflections.
	 *
	 * The array is in the form [singular => plural]
	 *
	 * @var    string[]
	 * @since  1.0
	 */
	private $cache = array();

	/**
	 * Protected constructor.
	 *
	 * @since  1.0
	 */
	protected function __construct()
	{
		// Pre=populate the irregual singular/plural.
		$this
			->addWord('deer')
			->addWord('moose')
			->addWord('sheep')
			->addWord('bison')
			->addWord('salmon')
			->addWord('pike')
			->addWord('trout')
			->addWord('fish')
			->addWord('swine')

			->addWord('alias', 'aliases')
			->addWord('bus', 'buses')
			->addWord('foot', 'feet')
			->addWord('goose', 'geese')
			->addWord('hive', 'hives')
			->addWord('louse', 'lice')
			->addWord('man', 'men')
			->addWord('mouse', 'mice')
			->addWord('ox', 'oxen')
			->addWord('quiz', 'quizes')
			->addWord('status', 'statuses')
			->addWord('tooth', 'teeth')
			->addWord('woman', 'women');
	}

	/**
	 * Adds inflection regex rules to the inflector.
	 *
	 * @param   mixed   $data      A string or an array of strings or regex rules to add.
	 * @param   string  $ruleType  The rule type: singular | plural | countable
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  InvalidArgumentException
	 */
	private function addRule($data, $ruleType)
	{
		if (\is_string($data))
		{
			$data = array($data);
		}
		elseif (!\is_array($data))
		{
			// Do not translate.
			throw new InvalidArgumentException('Invalid inflector rule data.');
		}

		foreach ($data as $rule)
		{
			// Ensure a string is pushed.
			array_push($this->rules[$ruleType], (string) $rule);
		}
	}

	/**
	 * Gets an inflected word from the cache where the singular form is supplied.
	 *
	 * @param   string  $singular  A singular form of a word.
	 *
	 * @return  string|boolean  The cached inflection or false if none found.
	 *
	 * @since   1.0
	 */
	private function getCachedPlural($singular)
	{
		$singular = StringHelper::strtolower($singular);

		// Check if the word is in cache.
		if (isset($this->cache[$singular]))
		{
			return $this->cache[$singular];
		}

		return false;
	}

	/**
	 * Gets an inflected word from the cache where the plural form is supplied.
	 *
	 * @param   string  $plural  A plural form of a word.
	 *
	 * @return  string|boolean  The cached inflection or false if none found.
	 *
	 * @since   1.0
	 */
	private function getCachedSingular($plural)
	{
		$plural = StringHelper::strtolower($plural);

		return array_search($plural, $this->cache);
	}

	/**
	 * Execute a regex from rules.
	 *
	 * The 'plural' rule type expects a singular word.
	 * The 'singular' rule type expects a plural word.
	 *
	 * @param   string  $word      The string input.
	 * @param   string  $ruleType  String (eg, singular|plural)
	 *
	 * @return  string|boolean  An inflected string, or false if no rule could be applied.
	 *
	 * @since   1.0
	 */
	private function matchRegexRule($word, $ruleType)
	{
		// Cycle through the regex rules.
		foreach ($this->rules[$ruleType] as $regex => $replacement)
		{
			$matches     = 0;
			$matchedWord = preg_replace($regex, $replacement, $word, -1, $matches);

			if ($matches > 0)
			{
				return $matchedWord;
			}
		}

		return false;
	}

	/**
	 * Sets an inflected word in the cache.
	 *
	 * @param   string  $singular  The singular form of the word.
	 * @param   string  $plural    The plural form of the word. If omitted, it is assumed the singular and plural are identical.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	private function setCache($singular, $plural = null)
	{
		$singular = StringHelper::strtolower($singular);

		if ($plural === null)
		{
			$plural = $singular;
		}
		else
		{
			$plural = StringHelper::strtolower($plural);
		}

		$this->cache[$singular] = $plural;
	}

	/**
	 * Adds a countable word.
	 *
	 * @param   mixed  $data  A string or an array of strings to add.
	 *
	 * @return  Inflector  Returns this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function addCountableRule($data)
	{
		$this->addRule($data, 'countable');

		return $this;
	}

	/**
	 * Adds a specific singular-plural pair for a word.
	 *
	 * @param   string  $singular  The singular form of the word.
	 * @param   string  $plural    The plural form of the word. If omitted, it is assumed the singular and plural are identical.
	 *
	 * @return  Inflector  Returns this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function addWord($singular, $plural =null)
	{
		$this->setCache($singular, $plural);

		return $this;
	}

	/**
	 * Adds a pluralisation rule.
	 *
	 * @param   mixed  $data  A string or an array of regex rules to add.
	 *
	 * @return  Inflector  Returns this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function addPluraliseRule($data)
	{
		$this->addRule($data, 'plural');

		return $this;
	}

	/**
	 * Adds a singularisation rule.
	 *
	 * @param   mixed  $data  A string or an array of regex rules to add.
	 *
	 * @return  Inflector  Returns this object to support chaining.
	 *
	 * @since   1.0
	 */
	public function addSingulariseRule($data)
	{
		$this->addRule($data, 'singular');

		return $this;
	}

	/**
	 * Gets an instance of the JStringInflector singleton.
	 *
	 * @param   boolean  $new  If true (default is false), returns a new instance regardless if one exists.
	 *                         This argument is mainly used for testing.
	 *
	 * @return  Inflector
	 *
	 * @since   1.0
	 */
	public static function getInstance($new = false)
	{
		if ($new)
		{
			return new static;
		}

		if (!\is_object(self::$instance))
		{
			self::$instance = new static;
		}

		return self::$instance;
	}

	/**
	 * Checks if a word is countable.
	 *
	 * @param   string  $word  The string input.
	 *
	 * @return  boolean  True if word is countable, false otherwise.
	 *
	 * @since   1.0
	 */
	public function isCountable($word)
	{
		return \in_array($word, $this->rules['countable']);
	}

	/**
	 * Checks if a word is in a plural form.
	 *
	 * @param   string  $word  The string input.
	 *
	 * @return  boolean  True if word is plural, false if not.
	 *
	 * @since   1.0
	 */
	public function isPlural($word)
	{
		// Try the cache for a known inflection.
		$inflection = $this->getCachedSingular($word);

		if ($inflection !== false)
		{
			return true;
		}

		$singularWord = $this->toSingular($word);

		if ($singularWord === false)
		{
			return false;
		}

		// Compute the inflection to cache the values, and compare.
		return $this->toPlural($singularWord) == $word;
	}

	/**
	 * Checks if a word is in a singular form.
	 *
	 * @param   string  $word  The string input.
	 *
	 * @return  boolean  True if word is singular, false if not.
	 *
	 * @since   1.0
	 */
	public function isSingular($word)
	{
		// Try the cache for a known inflection.
		$inflection = $this->getCachedPlural($word);

		if ($inflection !== false)
		{
			return true;
		}

		$pluralWord = $this->toPlural($word);

		if ($pluralWord === false)
		{
			return false;
		}

		// Compute the inflection to cache the values, and compare.
		return $this->toSingular($pluralWord) == $word;
	}

	/**
	 * Converts a word into its plural form.
	 *
	 * @param   string  $word  The singular word to pluralise.
	 *
	 * @return  string|boolean  An inflected string, or false if no rule could be applied.
	 *
	 * @since   1.0
	 */
	public function toPlural($word)
	{
		// Try to get the cached plural form from the singular.
		$cache = $this->getCachedPlural($word);

		if ($cache !== false)
		{
			return $cache;
		}

		// Check if the word is a known singular.
		if ($this->getCachedSingular($word))
		{
			return false;
		}

		// Compute the inflection.
		$inflected = $this->matchRegexRule($word, 'plural');

		if ($inflected !== false)
		{
			$this->setCache($word, $inflected);

			return $inflected;
		}

		// Dead code
		return false;
	}

	/**
	 * Converts a word into its singular form.
	 *
	 * @param   string  $word  The plural word to singularise.
	 *
	 * @return  string|boolean  An inflected string, or false if no rule could be applied.
	 *
	 * @since   1.0
	 */
	public function toSingular($word)
	{
		// Try to get the cached singular form from the plural.
		$cache = $this->getCachedSingular($word);

		if ($cache !== false)
		{
			return $cache;
		}

		// Check if the word is a known plural.
		if ($this->getCachedPlural($word))
		{
			return false;
		}

		// Compute the inflection.
		$inflected = $this->matchRegexRule($word, 'singular');

		if ($inflected !== false)
		{
			$this->setCache($inflected, $word);

			return $inflected;
		}

		return false;
	}
}
vendor/joomla/string/src/phputf8/substr_replace.php000064400000001062152177723700016555 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware substr_replace.
* Note: requires utf8_substr to be loaded
* @see http://www.php.net/substr_replace
* @see utf8_strlen
* @see utf8_substr
*/
function utf8_substr_replace($str, $repl, $start , $length = NULL ) {
    preg_match_all('/./us', $str, $ar);
    preg_match_all('/./us', $repl, $rar);
    if( $length === NULL ) {
        $length = utf8_strlen($str);
    }
    array_splice( $ar[0], $start, $length, $rar[0] );
    return join('',$ar[0]);
}
vendor/joomla/string/src/phputf8/LICENSE000064400000063476152177723700014055 0ustar00		  GNU LESSER GENERAL PUBLIC LICENSE
		       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

			    Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

		  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

			    NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

		     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!


vendor/joomla/string/src/phputf8/utf8.php000064400000005274152177723700014437 0ustar00<?php
/**
* This is the dynamic loader for the library. It checks whether you have
* the mbstring extension available and includes relevant files
* on that basis, falling back to the native (as in written in PHP) version
* if mbstring is unavailabe.
*
* It's probably easiest to use this, if you don't want to understand
* the dependencies involved, in conjunction with PHP versions etc. At
* the same time, you might get better performance by managing loading
* yourself. The smartest way to do this, bearing in mind performance,
* is probably to "load on demand" - i.e. just before you use these
* functions in your code, load the version you need.
*
* It makes sure the the following functions are available;
* utf8_strlen, utf8_strpos, utf8_strrpos, utf8_substr,
* utf8_strtolower, utf8_strtoupper
* Other functions in the ./native directory depend on these
* six functions being available
* @package utf8
*/

/**
* Put the current directory in this constant
*/
if ( !defined('UTF8') ) {
    define('UTF8',dirname(__FILE__));
}

/**
* If string overloading is active, it will break many of the
* native implementations. mbstring.func_overload must be set
* to 0, 1 or 4 in php.ini (string overloading disabled).
* Also need to check we have the correct internal mbstring
* encoding
*/
if ( extension_loaded('mbstring')) {
    /*
     * Joomla modification - As of PHP 8, the `mbstring.func_overload` configuration has been removed and the
     * MB_OVERLOAD_STRING constant will no longer be present, so this check only runs for PHP 7 and older
     * See https://github.com/php/php-src/commit/331e56ce38a91e87a6fb8e88154bb5bde445b132
     * and https://github.com/php/php-src/commit/97df99a6d7d96a886ac143337fecad775907589a
     * for additional references
     */
    if ( PHP_VERSION_ID < 80000 && ((int) ini_get('mbstring.func_overload')) & MB_OVERLOAD_STRING ) {
        trigger_error('String functions are overloaded by mbstring',E_USER_ERROR);
    }
    mb_internal_encoding('UTF-8');
}

/**
* Check whether PCRE has been compiled with UTF-8 support
*/
$UTF8_ar = array();
if ( preg_match('/^.{1}$/u',"ñ",$UTF8_ar) != 1 ) {
    trigger_error('PCRE is not compiled with UTF-8 support',E_USER_ERROR);
}
unset($UTF8_ar);


/**
* Load the smartest implementations of utf8_strpos, utf8_strrpos
* and utf8_substr
*/
if ( !defined('UTF8_CORE') ) {
    if ( function_exists('mb_substr') ) {
        require_once UTF8 . '/mbstring/core.php';
    } else {
        require_once UTF8 . '/utils/unicode.php';
        require_once UTF8 . '/native/core.php';
    }
}

/**
* Load the native implementation of utf8_substr_replace
*/
require_once UTF8 . '/substr_replace.php';

/**
* You should now be able to use all the other utf_* string functions
*/
vendor/joomla/string/src/phputf8/trim.php000064400000004122152177723700014513 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware replacement for ltrim()
* Note: you only need to use this if you are supplying the charlist
* optional arg and it contains UTF-8 characters. Otherwise ltrim will
* work normally on a UTF-8 string
* @author Andreas Gohr <andi@splitbrain.org>
* @see http://www.php.net/ltrim
* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
* @return string
* @package utf8
*/
function utf8_ltrim( $str, $charlist = FALSE ) {
    if($charlist === FALSE) return ltrim($str);

    //quote charlist for use in a characterclass
    $charlist = preg_replace('!([\\\\\\-\\]\\[/^])!','\\\${1}',$charlist);

    return preg_replace('/^['.$charlist.']+/u','',$str);
}

//---------------------------------------------------------------
/**
* UTF-8 aware replacement for rtrim()
* Note: you only need to use this if you are supplying the charlist
* optional arg and it contains UTF-8 characters. Otherwise rtrim will
* work normally on a UTF-8 string
* @author Andreas Gohr <andi@splitbrain.org>
* @see http://www.php.net/rtrim
* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
* @return string
* @package utf8
*/
function utf8_rtrim( $str, $charlist = FALSE ) {
    if($charlist === FALSE) return rtrim($str);

    //quote charlist for use in a characterclass
    $charlist = preg_replace('!([\\\\\\-\\]\\[/^])!','\\\${1}',$charlist);

    return preg_replace('/['.$charlist.']+$/u','',$str);
}

//---------------------------------------------------------------
/**
* UTF-8 aware replacement for trim()
* Note: you only need to use this if you are supplying the charlist
* optional arg and it contains UTF-8 characters. Otherwise trim will
* work normally on a UTF-8 string
* @author Andreas Gohr <andi@splitbrain.org>
* @see http://www.php.net/trim
* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
* @return string
* @package utf8
*/
function utf8_trim( $str, $charlist = FALSE ) {
    if($charlist === FALSE) return trim($str);
    return utf8_ltrim(utf8_rtrim($str, $charlist), $charlist);
}
vendor/joomla/string/src/phputf8/ord.php000064400000004475152177723700014337 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to ord
* Returns the unicode ordinal for a character
* @param string UTF-8 encoded character
* @return int unicode ordinal for the character
* @see http://www.php.net/ord
* @see http://www.php.net/manual/en/function.ord.php#46267
*/
function utf8_ord($chr) {

    $ord0 = ord($chr);

    if ( $ord0 >= 0 && $ord0 <= 127 ) {
        return $ord0;
    }

    if ( !isset($chr{1}) ) {
        trigger_error('Short sequence - at least 2 bytes expected, only 1 seen');
        return FALSE;
    }

    $ord1 = ord($chr{1});
    if ( $ord0 >= 192 && $ord0 <= 223 ) {
        return ( $ord0 - 192 ) * 64
            + ( $ord1 - 128 );
    }

    if ( !isset($chr{2}) ) {
        trigger_error('Short sequence - at least 3 bytes expected, only 2 seen');
        return FALSE;
    }
    $ord2 = ord($chr{2});
    if ( $ord0 >= 224 && $ord0 <= 239 ) {
        return ($ord0-224)*4096
            + ($ord1-128)*64
                + ($ord2-128);
    }

    if ( !isset($chr{3}) ) {
        trigger_error('Short sequence - at least 4 bytes expected, only 3 seen');
        return FALSE;
    }
    $ord3 = ord($chr{3});
    if ($ord0>=240 && $ord0<=247) {
        return ($ord0-240)*262144
            + ($ord1-128)*4096
                + ($ord2-128)*64
                    + ($ord3-128);

    }

    if ( !isset($chr{4}) ) {
        trigger_error('Short sequence - at least 5 bytes expected, only 4 seen');
        return FALSE;
    }
    $ord4 = ord($chr{4});
    if ($ord0>=248 && $ord0<=251) {
        return ($ord0-248)*16777216
            + ($ord1-128)*262144
                + ($ord2-128)*4096
                    + ($ord3-128)*64
                        + ($ord4-128);
    }

    if ( !isset($chr{5}) ) {
        trigger_error('Short sequence - at least 6 bytes expected, only 5 seen');
        return FALSE;
    }
    if ($ord0>=252 && $ord0<=253) {
        return ($ord0-252) * 1073741824
            + ($ord1-128)*16777216
                + ($ord2-128)*262144
                    + ($ord3-128)*4096
                        + ($ord4-128)*64
                            + (ord($chr{5})-128);
    }

    if ( $ord0 >= 254 && $ord0 <= 255 ) {
        trigger_error('Invalid UTF-8 with surrogate ordinal '.$ord0);
        return FALSE;
    }

}

vendor/joomla/string/src/phputf8/str_ireplace.php000064400000003614152177723700016221 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to str_ireplace
* Case-insensitive version of str_replace
* Note: requires utf8_strtolower
* Note: it's not fast and gets slower if $search / $replace is array
* Notes: it's based on the assumption that the lower and uppercase
* versions of a UTF-8 character will have the same length in bytes
* which is currently true given the hash table to strtolower
* @param string
* @return string
* @see http://www.php.net/str_ireplace
* @see utf8_strtolower
* @package utf8
*/
function utf8_ireplace($search, $replace, $str, $count = NULL){

    if ( !is_array($search) ) {

        $slen = strlen($search);
        if ( $slen == 0 ) {
            return $str;
        }

        $lendif = strlen($replace) - strlen($search);
        $search = utf8_strtolower($search);

        $search = preg_quote($search, '/');
        $lstr = utf8_strtolower($str);
        $i = 0;
        $matched = 0;
        while ( preg_match('/(.*)'.$search.'/Us',$lstr, $matches) ) {
            if ( $i === $count ) {
                break;
            }
            $mlen = strlen($matches[0]);
            $lstr = substr($lstr, $mlen);
            $str = substr_replace($str, $replace, $matched+strlen($matches[1]), $slen);
            $matched += $mlen + $lendif;
            $i++;
        }
        return $str;

    } else {

        foreach ( array_keys($search) as $k ) {

            if ( is_array($replace) ) {

                if ( array_key_exists($k,$replace) ) {

                    $str = utf8_ireplace($search[$k], $replace[$k], $str, $count);

                } else {

                    $str = utf8_ireplace($search[$k], '', $str, $count);

                }

            } else {

                $str = utf8_ireplace($search[$k], $replace, $str, $count);

            }
        }
        return $str;

    }

}


vendor/joomla/string/src/phputf8/mbstring/core.php000064400000007701152177723700016323 0ustar00<?php
/**
* @package utf8
*/

/**
* Define UTF8_CORE as required
*/
if ( !defined('UTF8_CORE') ) {
    define('UTF8_CORE',TRUE);
}

//--------------------------------------------------------------------
/**
* Wrapper round mb_strlen
* Assumes you have mb_internal_encoding to UTF-8 already
* Note: this function does not count bad bytes in the string - these
* are simply ignored
* @param string UTF-8 string
* @return int number of UTF-8 characters in string
* @package utf8
*/
function utf8_strlen($str){
    return mb_strlen($str);
}


//--------------------------------------------------------------------
/**
* Assumes mbstring internal encoding is set to UTF-8
* Wrapper around mb_strpos
* Find position of first occurrence of a string
* @param string haystack
* @param string needle (you should validate this with utf8_is_valid)
* @param integer offset in characters (from left)
* @return mixed integer position or FALSE on failure
* @package utf8
*/
function utf8_strpos($str, $search, $offset = FALSE){
    if ( $offset === FALSE ) {
        return mb_strpos($str, $search);
    } else {
        return mb_strpos($str, $search, $offset);
    }
}

//--------------------------------------------------------------------
/**
* Assumes mbstring internal encoding is set to UTF-8
* Wrapper around mb_strrpos
* Find position of last occurrence of a char in a string
* @param string haystack
* @param string needle (you should validate this with utf8_is_valid)
* @param integer (optional) offset (from left)
* @return mixed integer position or FALSE on failure
* @package utf8
*/
function utf8_strrpos($str, $search, $offset = FALSE){
    if ( $offset === FALSE ) {
        # Emulate behaviour of strrpos rather than raising warning
        if ( empty($str) ) {
            return FALSE;
        }
        return mb_strrpos($str, $search);
    } else {
        if ( !is_int($offset) ) {
            trigger_error('utf8_strrpos expects parameter 3 to be long',E_USER_WARNING);
            return FALSE;
        }

        $str = mb_substr($str, $offset);

        if ( FALSE !== ( $pos = mb_strrpos($str, $search) ) ) {
            return $pos + $offset;
        }

        return FALSE;
    }
}

//--------------------------------------------------------------------
/**
* Assumes mbstring internal encoding is set to UTF-8
* Wrapper around mb_substr
* Return part of a string given character offset (and optionally length)
* @param string
* @param integer number of UTF-8 characters offset (from left)
* @param integer (optional) length in UTF-8 characters from offset
* @return mixed string or FALSE if failure
* @package utf8
*/
function utf8_substr($str, $offset, $length = FALSE){
    if ( $length === FALSE ) {
        return mb_substr($str, $offset);
    } else {
        return mb_substr($str, $offset, $length);
    }
}

//--------------------------------------------------------------------
/**
* Assumes mbstring internal encoding is set to UTF-8
* Wrapper around mb_strtolower
* Make a string lowercase
* Note: The concept of a characters "case" only exists is some alphabets
* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
* not exist in the Chinese alphabet, for example. See Unicode Standard
* Annex #21: Case Mappings
* @param string
* @return mixed either string in lowercase or FALSE is UTF-8 invalid
* @package utf8
*/
function utf8_strtolower($str){
    return mb_strtolower($str);
}

//--------------------------------------------------------------------
/**
* Assumes mbstring internal encoding is set to UTF-8
* Wrapper around mb_strtoupper
* Make a string uppercase
* Note: The concept of a characters "case" only exists is some alphabets
* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
* not exist in the Chinese alphabet, for example. See Unicode Standard
* Annex #21: Case Mappings
* @param string
* @return mixed either string in lowercase or FALSE is UTF-8 invalid
* @package utf8
*/
function utf8_strtoupper($str){
    return mb_strtoupper($str);
}
vendor/joomla/string/src/phputf8/utils/specials.php000064400000015502152177723700016507 0ustar00<?php
/**
* Utilities for processing "special" characters in UTF-8. "Special" largely means anything which would
* be regarded as a non-word character, like ASCII control characters and punctuation. This has a "Roman"
* bias - it would be unaware of modern Chinese "punctuation" characters for example.
* Note: requires utils/unicode.php to be loaded
* @package utf8
* @see utf8_is_valid
*/

//--------------------------------------------------------------------
/**
* Used internally. Builds a PCRE pattern from the $UTF8_SPECIAL_CHARS
* array defined in this file
* The $UTF8_SPECIAL_CHARS should contain all special characters (non-letter/non-digit)
* defined in the various local charsets - it's not a complete list of
* non-alphanum characters in UTF-8. It's not perfect but should match most
* cases of special chars.
* This function adds the control chars 0x00 to 0x19 to the array of
* special chars (they are not included in $UTF8_SPECIAL_CHARS)
* @package utf8
* @return string
* @see utf8_from_unicode
* @see utf8_is_word_chars
* @see utf8_strip_specials
*/
function utf8_specials_pattern() {
    static $pattern = NULL;

    if ( !$pattern ) {
        $UTF8_SPECIAL_CHARS = array(
    0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023,
    0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c,
    0x002f,         0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x005b,
    0x005c, 0x005d, 0x005e,         0x0060, 0x007b, 0x007c, 0x007d, 0x007e,
    0x007f, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, 0x0088,
    0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, 0x0090, 0x0091, 0x0092,
    0x0093, 0x0094, 0x0095, 0x0096, 0x0097, 0x0098, 0x0099, 0x009a, 0x009b, 0x009c,
    0x009d, 0x009e, 0x009f, 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6,
    0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af, 0x00b0,
    0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba,
    0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf, 0x00d7, 0x00f7, 0x02c7, 0x02d8, 0x02d9,
    0x02da, 0x02db, 0x02dc, 0x02dd, 0x0300, 0x0301, 0x0303, 0x0309, 0x0323, 0x0384,
    0x0385, 0x0387, 0x03b2, 0x03c6, 0x03d1, 0x03d2, 0x03d5, 0x03d6, 0x05b0, 0x05b1,
    0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7, 0x05b8, 0x05b9, 0x05bb, 0x05bc,
    0x05bd, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05f3, 0x05f4, 0x060c,
    0x061b, 0x061f, 0x0640, 0x064b, 0x064c, 0x064d, 0x064e, 0x064f, 0x0650, 0x0651,
    0x0652, 0x066a, 0x0e3f, 0x200c, 0x200d, 0x200e, 0x200f, 0x2013, 0x2014, 0x2015,
    0x2017, 0x2018, 0x2019, 0x201a, 0x201c, 0x201d, 0x201e, 0x2020, 0x2021, 0x2022,
    0x2026, 0x2030, 0x2032, 0x2033, 0x2039, 0x203a, 0x2044, 0x20a7, 0x20aa, 0x20ab,
    0x20ac, 0x2116, 0x2118, 0x2122, 0x2126, 0x2135, 0x2190, 0x2191, 0x2192, 0x2193,
    0x2194, 0x2195, 0x21b5, 0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x2200, 0x2202,
    0x2203, 0x2205, 0x2206, 0x2207, 0x2208, 0x2209, 0x220b, 0x220f, 0x2211, 0x2212,
    0x2215, 0x2217, 0x2219, 0x221a, 0x221d, 0x221e, 0x2220, 0x2227, 0x2228, 0x2229,
    0x222a, 0x222b, 0x2234, 0x223c, 0x2245, 0x2248, 0x2260, 0x2261, 0x2264, 0x2265,
    0x2282, 0x2283, 0x2284, 0x2286, 0x2287, 0x2295, 0x2297, 0x22a5, 0x22c5, 0x2310,
    0x2320, 0x2321, 0x2329, 0x232a, 0x2469, 0x2500, 0x2502, 0x250c, 0x2510, 0x2514,
    0x2518, 0x251c, 0x2524, 0x252c, 0x2534, 0x253c, 0x2550, 0x2551, 0x2552, 0x2553,
    0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559, 0x255a, 0x255b, 0x255c, 0x255d,
    0x255e, 0x255f, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567,
    0x2568, 0x2569, 0x256a, 0x256b, 0x256c, 0x2580, 0x2584, 0x2588, 0x258c, 0x2590,
    0x2591, 0x2592, 0x2593, 0x25a0, 0x25b2, 0x25bc, 0x25c6, 0x25ca, 0x25cf, 0x25d7,
    0x2605, 0x260e, 0x261b, 0x261e, 0x2660, 0x2663, 0x2665, 0x2666, 0x2701, 0x2702,
    0x2703, 0x2704, 0x2706, 0x2707, 0x2708, 0x2709, 0x270c, 0x270d, 0x270e, 0x270f,
    0x2710, 0x2711, 0x2712, 0x2713, 0x2714, 0x2715, 0x2716, 0x2717, 0x2718, 0x2719,
    0x271a, 0x271b, 0x271c, 0x271d, 0x271e, 0x271f, 0x2720, 0x2721, 0x2722, 0x2723,
    0x2724, 0x2725, 0x2726, 0x2727, 0x2729, 0x272a, 0x272b, 0x272c, 0x272d, 0x272e,
    0x272f, 0x2730, 0x2731, 0x2732, 0x2733, 0x2734, 0x2735, 0x2736, 0x2737, 0x2738,
    0x2739, 0x273a, 0x273b, 0x273c, 0x273d, 0x273e, 0x273f, 0x2740, 0x2741, 0x2742,
    0x2743, 0x2744, 0x2745, 0x2746, 0x2747, 0x2748, 0x2749, 0x274a, 0x274b, 0x274d,
    0x274f, 0x2750, 0x2751, 0x2752, 0x2756, 0x2758, 0x2759, 0x275a, 0x275b, 0x275c,
    0x275d, 0x275e, 0x2761, 0x2762, 0x2763, 0x2764, 0x2765, 0x2766, 0x2767, 0x277f,
    0x2789, 0x2793, 0x2794, 0x2798, 0x2799, 0x279a, 0x279b, 0x279c, 0x279d, 0x279e,
    0x279f, 0x27a0, 0x27a1, 0x27a2, 0x27a3, 0x27a4, 0x27a5, 0x27a6, 0x27a7, 0x27a8,
    0x27a9, 0x27aa, 0x27ab, 0x27ac, 0x27ad, 0x27ae, 0x27af, 0x27b1, 0x27b2, 0x27b3,
    0x27b4, 0x27b5, 0x27b6, 0x27b7, 0x27b8, 0x27b9, 0x27ba, 0x27bb, 0x27bc, 0x27bd,
    0x27be, 0xf6d9, 0xf6da, 0xf6db, 0xf8d7, 0xf8d8, 0xf8d9, 0xf8da, 0xf8db, 0xf8dc,
    0xf8dd, 0xf8de, 0xf8df, 0xf8e0, 0xf8e1, 0xf8e2, 0xf8e3, 0xf8e4, 0xf8e5, 0xf8e6,
    0xf8e7, 0xf8e8, 0xf8e9, 0xf8ea, 0xf8eb, 0xf8ec, 0xf8ed, 0xf8ee, 0xf8ef, 0xf8f0,
    0xf8f1, 0xf8f2, 0xf8f3, 0xf8f4, 0xf8f5, 0xf8f6, 0xf8f7, 0xf8f8, 0xf8f9, 0xf8fa,
    0xf8fb, 0xf8fc, 0xf8fd, 0xf8fe, 0xfe7c, 0xfe7d,
            );
        $pattern = preg_quote(utf8_from_unicode($UTF8_SPECIAL_CHARS), '/');
        $pattern = '/[\x00-\x19'.$pattern.']/u';
    }

    return $pattern;
}

//--------------------------------------------------------------------
/**
* Checks a string for whether it contains only word characters. This
* is logically equivalent to the \w PCRE meta character. Note that
* this is not a 100% guarantee that the string only contains alpha /
* numeric characters but just that common non-alphanumeric are not
* in the string, including ASCII device control characters.
* @package utf8
* @param string to check
* @return boolean TRUE if the string only contains word characters
* @see utf8_specials_pattern
*/
function utf8_is_word_chars($str) {
    return !(bool)preg_match(utf8_specials_pattern(),$str);
}

//--------------------------------------------------------------------
/**
* Removes special characters (nonalphanumeric) from a UTF-8 string
*
* This can be useful as a helper for sanitizing a string for use as
* something like a file name or a unique identifier. Be warned though
* it does not handle all possible non-alphanumeric characters and is
* not intended is some kind of security / injection filter.
*
* @package utf8
* @author Andreas Gohr <andi@splitbrain.org>
* @param string $string The UTF8 string to strip of special chars
* @param string (optional) $repl   Replace special with this string
* @return string with common non-alphanumeric characters removed
* @see utf8_specials_pattern
*/
function utf8_strip_specials($string, $repl=''){
    return preg_replace(utf8_specials_pattern(), $repl, $string);
}


vendor/joomla/string/src/phputf8/utils/unicode.php000064400000022042152177723700016327 0ustar00<?php
/**
* Tools for conversion between UTF-8 and unicode
* The Original Code is Mozilla Communicator client code.
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
*/

//--------------------------------------------------------------------
/**
* Takes an UTF-8 string and returns an array of ints representing the
* Unicode characters. Astral planes are supported ie. the ints in the
* output can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
* are not allowed.
* Returns false if the input string isn't a valid UTF-8 octet sequence
* and raises a PHP error at level E_USER_WARNING
* Note: this function has been modified slightly in this library to
* trigger errors on encountering bad bytes
* @author <hsivonen@iki.fi>
* @param string UTF-8 encoded string
* @return mixed array of unicode code points or FALSE if UTF-8 invalid
* @see utf8_from_unicode
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
*/
function utf8_to_unicode($str) {
    $mState = 0;     // cached expected number of octets after the current octet
                     // until the beginning of the next UTF8 character sequence
    $mUcs4  = 0;     // cached Unicode character
    $mBytes = 1;     // cached expected number of octets in the current sequence

    $out = array();

    $len = strlen($str);

    for($i = 0; $i < $len; $i++) {

        $in = ord($str{$i});

        if ( $mState == 0) {

            // When mState is zero we expect either a US-ASCII character or a
            // multi-octet sequence.
            if (0 == (0x80 & ($in))) {
                // US-ASCII, pass straight through.
                $out[] = $in;
                $mBytes = 1;

            } else if (0xC0 == (0xE0 & ($in))) {
                // First octet of 2 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x1F) << 6;
                $mState = 1;
                $mBytes = 2;

            } else if (0xE0 == (0xF0 & ($in))) {
                // First octet of 3 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x0F) << 12;
                $mState = 2;
                $mBytes = 3;

            } else if (0xF0 == (0xF8 & ($in))) {
                // First octet of 4 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x07) << 18;
                $mState = 3;
                $mBytes = 4;

            } else if (0xF8 == (0xFC & ($in))) {
                /* First octet of 5 octet sequence.
                *
                * This is illegal because the encoded codepoint must be either
                * (a) not the shortest form or
                * (b) outside the Unicode range of 0-0x10FFFF.
                * Rather than trying to resynchronize, we will carry on until the end
                * of the sequence and let the later error handling code catch it.
                */
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x03) << 24;
                $mState = 4;
                $mBytes = 5;

            } else if (0xFC == (0xFE & ($in))) {
                // First octet of 6 octet sequence, see comments for 5 octet sequence.
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 1) << 30;
                $mState = 5;
                $mBytes = 6;

            } else {
                /* Current octet is neither in the US-ASCII range nor a legal first
                 * octet of a multi-octet sequence.
                 */
                trigger_error(
                        'utf8_to_unicode: Illegal sequence identifier '.
                            'in UTF-8 at byte '.$i,
                        E_USER_WARNING
                    );
                return FALSE;

            }

        } else {

            // When mState is non-zero, we expect a continuation of the multi-octet
            // sequence
            if (0x80 == (0xC0 & ($in))) {

                // Legal continuation.
                $shift = ($mState - 1) * 6;
                $tmp = $in;
                $tmp = ($tmp & 0x0000003F) << $shift;
                $mUcs4 |= $tmp;

                /**
                * End of the multi-octet sequence. mUcs4 now contains the final
                * Unicode codepoint to be output
                */
                if (0 == --$mState) {

                    /*
                    * Check for illegal sequences and codepoints.
                    */
                    // From Unicode 3.1, non-shortest form is illegal
                    if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                        ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                        ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
                        (4 < $mBytes) ||
                        // From Unicode 3.2, surrogate characters are illegal
                        (($mUcs4 & 0xFFFFF800) == 0xD800) ||
                        // Codepoints outside the Unicode range are illegal
                        ($mUcs4 > 0x10FFFF)) {

                        trigger_error(
                                'utf8_to_unicode: Illegal sequence or codepoint '.
                                    'in UTF-8 at byte '.$i,
                                E_USER_WARNING
                            );

                        return FALSE;

                    }

                    if (0xFEFF != $mUcs4) {
                        // BOM is legal but we don't want to output it
                        $out[] = $mUcs4;
                    }

                    //initialize UTF8 cache
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                }

            } else {
                /**
                *((0xC0 & (*in) != 0x80) && (mState != 0))
                * Incomplete multi-octet sequence.
                */
                trigger_error(
                        'utf8_to_unicode: Incomplete multi-octet '.
                        '   sequence in UTF-8 at byte '.$i,
                        E_USER_WARNING
                    );

                return FALSE;
            }
        }
    }
    return $out;
}

//--------------------------------------------------------------------
/**
* Takes an array of ints representing the Unicode characters and returns
* a UTF-8 string. Astral planes are supported ie. the ints in the
* input can be > 0xFFFF. Occurrances of the BOM are ignored. Surrogates
* are not allowed.
* Returns false if the input array contains ints that represent
* surrogates or are outside the Unicode range
* and raises a PHP error at level E_USER_WARNING
* Note: this function has been modified slightly in this library to use
* output buffering to concatenate the UTF-8 string (faster) as well as
* reference the array by it's keys
* @param array of unicode code points representing a string
* @return mixed UTF-8 string or FALSE if array contains invalid code points
* @author <hsivonen@iki.fi>
* @see utf8_to_unicode
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
*/
function utf8_from_unicode($arr) {
    ob_start();

    foreach (array_keys($arr) as $k) {

        # ASCII range (including control chars)
        if ( ($arr[$k] >= 0) && ($arr[$k] <= 0x007f) ) {

            echo chr($arr[$k]);

        # 2 byte sequence
        } else if ($arr[$k] <= 0x07ff) {

            echo chr(0xc0 | ($arr[$k] >> 6));
            echo chr(0x80 | ($arr[$k] & 0x003f));

        # Byte order mark (skip)
        } else if($arr[$k] == 0xFEFF) {

            // nop -- zap the BOM

        # Test for illegal surrogates
        } else if ($arr[$k] >= 0xD800 && $arr[$k] <= 0xDFFF) {

            // found a surrogate
            trigger_error(
                'utf8_from_unicode: Illegal surrogate '.
                    'at index: '.$k.', value: '.$arr[$k],
                E_USER_WARNING
                );

            return FALSE;

        # 3 byte sequence
        } else if ($arr[$k] <= 0xffff) {

            echo chr(0xe0 | ($arr[$k] >> 12));
            echo chr(0x80 | (($arr[$k] >> 6) & 0x003f));
            echo chr(0x80 | ($arr[$k] & 0x003f));

        # 4 byte sequence
        } else if ($arr[$k] <= 0x10ffff) {

            echo chr(0xf0 | ($arr[$k] >> 18));
            echo chr(0x80 | (($arr[$k] >> 12) & 0x3f));
            echo chr(0x80 | (($arr[$k] >> 6) & 0x3f));
            echo chr(0x80 | ($arr[$k] & 0x3f));

        } else {

            trigger_error(
                'utf8_from_unicode: Codepoint out of Unicode range '.
                    'at index: '.$k.', value: '.$arr[$k],
                E_USER_WARNING
                );

            // out of range
            return FALSE;
        }
    }

    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}
vendor/joomla/string/src/phputf8/utils/bad.php000064400000032710152177723700015432 0ustar00<?php
/**
* Tools for locating / replacing bad bytes in UTF-8 strings
* The Original Code is Mozilla Communicator client code.
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
* @see utf8_is_valid
*/

//--------------------------------------------------------------------
/**
* Locates the first bad byte in a UTF-8 string returning it's
* byte index in the string
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @param string
* @return mixed integer byte index or FALSE if no bad found
* @package utf8
*/
function utf8_bad_find($str) {
    $UTF8_BAD =
    '([\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.               # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.           # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.           # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.            # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # plane 16
    '|(.{1}))';                              # invalid byte
    $pos = 0;
    $badList = array();
    while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) {
        $bytes = strlen($matches[0]);
        if ( isset($matches[2])) {
            return $pos;
        }
        $pos += $bytes;
        $str = substr($str,$bytes);
    }
    return FALSE;
}

//--------------------------------------------------------------------
/**
* Locates all bad bytes in a UTF-8 string and returns a list of their
* byte index in the string
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @param string
* @return mixed array of integers or FALSE if no bad found
* @package utf8
*/
function utf8_bad_findall($str) {
    $UTF8_BAD =
    '([\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.               # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.           # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.           # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.            # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # plane 16
    '|(.{1}))';                              # invalid byte
    $pos = 0;
    $badList = array();
    while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) {
        $bytes = strlen($matches[0]);
        if ( isset($matches[2])) {
            $badList[] = $pos;
        }
        $pos += $bytes;
        $str = substr($str,$bytes);
    }
    if ( count($badList) > 0 ) {
        return $badList;
    }
    return FALSE;
}

//--------------------------------------------------------------------
/**
* Strips out any bad bytes from a UTF-8 string and returns the rest
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @param string
* @return string
* @package utf8
*/
function utf8_bad_strip($str) {
    $UTF8_BAD =
    '([\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.               # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.           # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.           # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.            # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # plane 16
    '|(.{1}))';                              # invalid byte
    ob_start();
    while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) {
        if ( !isset($matches[2])) {
            echo $matches[0];
        }
        $str = substr($str,strlen($matches[0]));
    }
    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}

//--------------------------------------------------------------------
/**
* Replace bad bytes with an alternative character - ASCII character
* recommended is replacement char
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @param string to search
* @param string to replace bad bytes with (defaults to '?') - use ASCII
* @return string
* @package utf8
*/
function utf8_bad_replace($str, $replace = '?') {
    $UTF8_BAD =
    '([\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.               # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.           # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.           # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.            # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # plane 16
    '|(.{1}))';                              # invalid byte
    ob_start();
    while (preg_match('/'.$UTF8_BAD.'/S', $str, $matches)) {
        if ( !isset($matches[2])) {
            echo $matches[0];
        } else {
            echo $replace;
        }
        $str = substr($str,strlen($matches[0]));
    }
    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}

//--------------------------------------------------------------------
/**
* Return code from utf8_bad_identify() when a five octet sequence is detected.
* Note: 5 octets sequences are valid UTF-8 but are not supported by Unicode so
* do not represent a useful character
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_5OCTET',1);

/**
* Return code from utf8_bad_identify() when a six octet sequence is detected.
* Note: 6 octets sequences are valid UTF-8 but are not supported by Unicode so
* do not represent a useful character
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_6OCTET',2);

/**
* Return code from utf8_bad_identify().
* Invalid octet for use as start of multi-byte UTF-8 sequence
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_SEQID',3);

/**
* Return code from utf8_bad_identify().
* From Unicode 3.1, non-shortest form is illegal
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_NONSHORT',4);

/**
* Return code from utf8_bad_identify().
* From Unicode 3.2, surrogate characters are illegal
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_SURROGATE',5);

/**
* Return code from utf8_bad_identify().
* Codepoints outside the Unicode range are illegal
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_UNIOUTRANGE',6);

/**
* Return code from utf8_bad_identify().
* Incomplete multi-octet sequence
* Note: this is kind of a "catch-all"
* @see utf8_bad_identify
* @package utf8
*/
define('UTF8_BAD_SEQINCOMPLETE',7);

//--------------------------------------------------------------------
/**
* Reports on the type of bad byte found in a UTF-8 string. Returns a
* status code on the first bad byte found
* @author <hsivonen@iki.fi>
* @param string UTF-8 encoded string
* @return mixed integer constant describing problem or FALSE if valid UTF-8
* @see utf8_bad_explain
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
*/
function utf8_bad_identify($str, &$i) {

    $mState = 0;     // cached expected number of octets after the current octet
                     // until the beginning of the next UTF8 character sequence
    $mUcs4  = 0;     // cached Unicode character
    $mBytes = 1;     // cached expected number of octets in the current sequence

    $len = strlen($str);

    for($i = 0; $i < $len; $i++) {

        $in = ord($str{$i});

        if ( $mState == 0) {

            // When mState is zero we expect either a US-ASCII character or a
            // multi-octet sequence.
            if (0 == (0x80 & ($in))) {
                // US-ASCII, pass straight through.
                $mBytes = 1;

            } else if (0xC0 == (0xE0 & ($in))) {
                // First octet of 2 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x1F) << 6;
                $mState = 1;
                $mBytes = 2;

            } else if (0xE0 == (0xF0 & ($in))) {
                // First octet of 3 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x0F) << 12;
                $mState = 2;
                $mBytes = 3;

            } else if (0xF0 == (0xF8 & ($in))) {
                // First octet of 4 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x07) << 18;
                $mState = 3;
                $mBytes = 4;

            } else if (0xF8 == (0xFC & ($in))) {

                /* First octet of 5 octet sequence.
                *
                * This is illegal because the encoded codepoint must be either
                * (a) not the shortest form or
                * (b) outside the Unicode range of 0-0x10FFFF.
                */

                return UTF8_BAD_5OCTET;

            } else if (0xFC == (0xFE & ($in))) {

                // First octet of 6 octet sequence, see comments for 5 octet sequence.
                return UTF8_BAD_6OCTET;

            } else {
                // Current octet is neither in the US-ASCII range nor a legal first
                // octet of a multi-octet sequence.
                return UTF8_BAD_SEQID;

            }

        } else {

            // When mState is non-zero, we expect a continuation of the multi-octet
            // sequence
            if (0x80 == (0xC0 & ($in))) {

                // Legal continuation.
                $shift = ($mState - 1) * 6;
                $tmp = $in;
                $tmp = ($tmp & 0x0000003F) << $shift;
                $mUcs4 |= $tmp;

                /**
                * End of the multi-octet sequence. mUcs4 now contains the final
                * Unicode codepoint to be output
                */
                if (0 == --$mState) {

                    // From Unicode 3.1, non-shortest form is illegal
                    if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                        ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                        ((4 == $mBytes) && ($mUcs4 < 0x10000)) ) {
                        return UTF8_BAD_NONSHORT;

                    // From Unicode 3.2, surrogate characters are illegal
                    } else if (($mUcs4 & 0xFFFFF800) == 0xD800) {
                        return UTF8_BAD_SURROGATE;

                    // Codepoints outside the Unicode range are illegal
                    } else if ($mUcs4 > 0x10FFFF) {
                        return UTF8_BAD_UNIOUTRANGE;
                    }

                    //initialize UTF8 cache
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                }

            } else {
                // ((0xC0 & (*in) != 0x80) && (mState != 0))
                // Incomplete multi-octet sequence.
                $i--;
                return UTF8_BAD_SEQINCOMPLETE;
            }
        }
    }

    if ( $mState != 0 ) {
        // Incomplete multi-octet sequence.
        $i--;
        return UTF8_BAD_SEQINCOMPLETE;
    }

    // No bad octets found
    $i = NULL;
    return FALSE;
}

//--------------------------------------------------------------------
/**
* Takes a return code from utf8_bad_identify() are returns a message
* (in English) explaining what the problem is.
* @param int return code from utf8_bad_identify
* @return mixed string message or FALSE if return code unknown
* @see utf8_bad_identify
* @package utf8
*/
function utf8_bad_explain($code) {

    switch ($code) {

        case UTF8_BAD_5OCTET:
            return 'Five octet sequences are valid UTF-8 but are not supported by Unicode';
        break;

        case UTF8_BAD_6OCTET:
            return 'Six octet sequences are valid UTF-8 but are not supported by Unicode';
        break;

        case UTF8_BAD_SEQID:
            return 'Invalid octet for use as start of multi-byte UTF-8 sequence';
        break;

        case UTF8_BAD_NONSHORT:
            return 'From Unicode 3.1, non-shortest form is illegal';
        break;

        case UTF8_BAD_SURROGATE:
            return 'From Unicode 3.2, surrogate characters are illegal';
        break;

        case UTF8_BAD_UNIOUTRANGE:
            return 'Codepoints outside the Unicode range are illegal';
        break;

        case UTF8_BAD_SEQINCOMPLETE:
            return 'Incomplete multi-octet sequence';
        break;

    }

    trigger_error('Unknown error code: '.$code,E_USER_WARNING);
    return FALSE;

}
vendor/joomla/string/src/phputf8/utils/ascii.php000064400000020433152177723700015773 0ustar00<?php
/**
* Tools to help with ASCII in UTF-8
*
* @package utf8
*/

//--------------------------------------------------------------------
/**
* Tests whether a string contains only 7bit ASCII bytes.
* You might use this to conditionally check whether a string
* needs handling as UTF-8 or not, potentially offering performance
* benefits by using the native PHP equivalent if it's just ASCII e.g.;
*
* <code>
* if ( utf8_is_ascii($someString) ) {
*     // It's just ASCII - use the native PHP version
*     $someString = strtolower($someString);
* } else {
*     $someString = utf8_strtolower($someString);
* }
* </code>
*
* @param string
* @return boolean TRUE if it's all ASCII
* @package utf8
* @see utf8_is_ascii_ctrl
*/
function utf8_is_ascii($str) {
    // Search for any bytes which are outside the ASCII range...
    return (preg_match('/(?:[^\x00-\x7F])/',$str) !== 1);
}

//--------------------------------------------------------------------
/**
* Tests whether a string contains only 7bit ASCII bytes with device
* control codes omitted. The device control codes can be found on the
* second table here: http://www.w3schools.com/tags/ref_ascii.asp
*
* @param string
* @return boolean TRUE if it's all ASCII without device control codes
* @package utf8
* @see utf8_is_ascii
*/
function utf8_is_ascii_ctrl($str) {
    if ( strlen($str) > 0 ) {
        // Search for any bytes which are outside the ASCII range,
        // or are device control codes
        return (preg_match('/[^\x09\x0A\x0D\x20-\x7E]/',$str) !== 1);
    }
    return FALSE;
}

//--------------------------------------------------------------------
/**
* Strip out all non-7bit ASCII bytes
* If you need to transmit a string to system which you know can only
* support 7bit ASCII, you could use this function.
* @param string
* @return string with non ASCII bytes removed
* @package utf8
* @see utf8_strip_non_ascii_ctrl
*/
function utf8_strip_non_ascii($str) {
    ob_start();
    while ( preg_match(
        '/^([\x00-\x7F]+)|([^\x00-\x7F]+)/S',
            $str, $matches) ) {
        if ( !isset($matches[2]) ) {
            echo $matches[0];
        }
        $str = substr($str, strlen($matches[0]));
    }
    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}

//--------------------------------------------------------------------
/**
* Strip out device control codes in the ASCII range
* which are not permitted in XML. Note that this leaves
* multi-byte characters untouched - it only removes device
* control codes
* @see http://hsivonen.iki.fi/producing-xml/#controlchar
* @param string
* @return string control codes removed
*/
function utf8_strip_ascii_ctrl($str) {
    ob_start();
    while ( preg_match(
        '/^([^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)|([\x00-\x08\x0B\x0C\x0E-\x1F\x7F]+)/S',
            $str, $matches) ) {
        if ( !isset($matches[2]) ) {
            echo $matches[0];
        }
        $str = substr($str, strlen($matches[0]));
    }
    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}

//--------------------------------------------------------------------
/**
* Strip out all non 7bit ASCII bytes and ASCII device control codes.
* For a list of ASCII device control codes see the 2nd table here:
* http://www.w3schools.com/tags/ref_ascii.asp
*
* @param string
* @return boolean TRUE if it's all ASCII
* @package utf8
*/
function utf8_strip_non_ascii_ctrl($str) {
    ob_start();
    while ( preg_match(
        '/^([\x09\x0A\x0D\x20-\x7E]+)|([^\x09\x0A\x0D\x20-\x7E]+)/S',
            $str, $matches) ) {
        if ( !isset($matches[2]) ) {
            echo $matches[0];
        }
        $str = substr($str, strlen($matches[0]));
    }
    $result = ob_get_contents();
    ob_end_clean();
    return $result;
}

//---------------------------------------------------------------
/**
* Replace accented UTF-8 characters by unaccented ASCII-7 "equivalents".
* The purpose of this function is to replace characters commonly found in Latin
* alphabets with something more or less equivalent from the ASCII range. This can
* be useful for converting a UTF-8 to something ready for a filename, for example.
* Following the use of this function, you would probably also pass the string
* through utf8_strip_non_ascii to clean out any other non-ASCII chars
* Use the optional parameter to just deaccent lower ($case = -1) or upper ($case = 1)
* letters. Default is to deaccent both cases ($case = 0)
*
* For a more complete implementation of transliteration, see the utf8_to_ascii package
* available from the phputf8 project downloads:
* http://prdownloads.sourceforge.net/phputf8
*
* @param string UTF-8 string
* @param int (optional) -1 lowercase only, +1 uppercase only, 1 both cases
* @param string UTF-8 with accented characters replaced by ASCII chars
* @return string accented chars replaced with ascii equivalents
* @author Andreas Gohr <andi@splitbrain.org>
* @package utf8
*/
function utf8_accents_to_ascii( $str, $case=0 ){

    static $UTF8_LOWER_ACCENTS = NULL;
    static $UTF8_UPPER_ACCENTS = NULL;

    if($case <= 0){

        if ( is_null($UTF8_LOWER_ACCENTS) ) {
            $UTF8_LOWER_ACCENTS = array(
  'à' => 'a', 'ô' => 'o', 'ď' => 'd', 'ḟ' => 'f', 'ë' => 'e', 'š' => 's', 'ơ' => 'o',
  'ß' => 'ss', 'ă' => 'a', 'ř' => 'r', 'ț' => 't', 'ň' => 'n', 'ā' => 'a', 'ķ' => 'k',
  'ŝ' => 's', 'ỳ' => 'y', 'ņ' => 'n', 'ĺ' => 'l', 'ħ' => 'h', 'ṗ' => 'p', 'ó' => 'o',
  'ú' => 'u', 'ě' => 'e', 'é' => 'e', 'ç' => 'c', 'ẁ' => 'w', 'ċ' => 'c', 'õ' => 'o',
  'ṡ' => 's', 'ø' => 'o', 'ģ' => 'g', 'ŧ' => 't', 'ș' => 's', 'ė' => 'e', 'ĉ' => 'c',
  'ś' => 's', 'î' => 'i', 'ű' => 'u', 'ć' => 'c', 'ę' => 'e', 'ŵ' => 'w', 'ṫ' => 't',
  'ū' => 'u', 'č' => 'c', 'ö' => 'oe', 'è' => 'e', 'ŷ' => 'y', 'ą' => 'a', 'ł' => 'l',
  'ų' => 'u', 'ů' => 'u', 'ş' => 's', 'ğ' => 'g', 'ļ' => 'l', 'ƒ' => 'f', 'ž' => 'z',
  'ẃ' => 'w', 'ḃ' => 'b', 'å' => 'a', 'ì' => 'i', 'ï' => 'i', 'ḋ' => 'd', 'ť' => 't',
  'ŗ' => 'r', 'ä' => 'ae', 'í' => 'i', 'ŕ' => 'r', 'ê' => 'e', 'ü' => 'ue', 'ò' => 'o',
  'ē' => 'e', 'ñ' => 'n', 'ń' => 'n', 'ĥ' => 'h', 'ĝ' => 'g', 'đ' => 'd', 'ĵ' => 'j',
  'ÿ' => 'y', 'ũ' => 'u', 'ŭ' => 'u', 'ư' => 'u', 'ţ' => 't', 'ý' => 'y', 'ő' => 'o',
  'â' => 'a', 'ľ' => 'l', 'ẅ' => 'w', 'ż' => 'z', 'ī' => 'i', 'ã' => 'a', 'ġ' => 'g',
  'ṁ' => 'm', 'ō' => 'o', 'ĩ' => 'i', 'ù' => 'u', 'į' => 'i', 'ź' => 'z', 'á' => 'a',
  'û' => 'u', 'þ' => 'th', 'ð' => 'dh', 'æ' => 'ae', 'µ' => 'u', 'ĕ' => 'e',
            );
        }

        $str = str_replace(
                array_keys($UTF8_LOWER_ACCENTS),
                array_values($UTF8_LOWER_ACCENTS),
                $str
            );
    }

    if($case >= 0){
        if ( is_null($UTF8_UPPER_ACCENTS) ) {
            $UTF8_UPPER_ACCENTS = array(
  'À' => 'A', 'Ô' => 'O', 'Ď' => 'D', 'Ḟ' => 'F', 'Ë' => 'E', 'Š' => 'S', 'Ơ' => 'O',
  'Ă' => 'A', 'Ř' => 'R', 'Ț' => 'T', 'Ň' => 'N', 'Ā' => 'A', 'Ķ' => 'K',
  'Ŝ' => 'S', 'Ỳ' => 'Y', 'Ņ' => 'N', 'Ĺ' => 'L', 'Ħ' => 'H', 'Ṗ' => 'P', 'Ó' => 'O',
  'Ú' => 'U', 'Ě' => 'E', 'É' => 'E', 'Ç' => 'C', 'Ẁ' => 'W', 'Ċ' => 'C', 'Õ' => 'O',
  'Ṡ' => 'S', 'Ø' => 'O', 'Ģ' => 'G', 'Ŧ' => 'T', 'Ș' => 'S', 'Ė' => 'E', 'Ĉ' => 'C',
  'Ś' => 'S', 'Î' => 'I', 'Ű' => 'U', 'Ć' => 'C', 'Ę' => 'E', 'Ŵ' => 'W', 'Ṫ' => 'T',
  'Ū' => 'U', 'Č' => 'C', 'Ö' => 'Oe', 'È' => 'E', 'Ŷ' => 'Y', 'Ą' => 'A', 'Ł' => 'L',
  'Ų' => 'U', 'Ů' => 'U', 'Ş' => 'S', 'Ğ' => 'G', 'Ļ' => 'L', 'Ƒ' => 'F', 'Ž' => 'Z',
  'Ẃ' => 'W', 'Ḃ' => 'B', 'Å' => 'A', 'Ì' => 'I', 'Ï' => 'I', 'Ḋ' => 'D', 'Ť' => 'T',
  'Ŗ' => 'R', 'Ä' => 'Ae', 'Í' => 'I', 'Ŕ' => 'R', 'Ê' => 'E', 'Ü' => 'Ue', 'Ò' => 'O',
  'Ē' => 'E', 'Ñ' => 'N', 'Ń' => 'N', 'Ĥ' => 'H', 'Ĝ' => 'G', 'Đ' => 'D', 'Ĵ' => 'J',
  'Ÿ' => 'Y', 'Ũ' => 'U', 'Ŭ' => 'U', 'Ư' => 'U', 'Ţ' => 'T', 'Ý' => 'Y', 'Ő' => 'O',
  'Â' => 'A', 'Ľ' => 'L', 'Ẅ' => 'W', 'Ż' => 'Z', 'Ī' => 'I', 'Ã' => 'A', 'Ġ' => 'G',
  'Ṁ' => 'M', 'Ō' => 'O', 'Ĩ' => 'I', 'Ù' => 'U', 'Į' => 'I', 'Ź' => 'Z', 'Á' => 'A',
  'Û' => 'U', 'Þ' => 'Th', 'Ð' => 'Dh', 'Æ' => 'Ae', 'Ĕ' => 'E',
            );
        }
        $str = str_replace(
                array_keys($UTF8_UPPER_ACCENTS),
                array_values($UTF8_UPPER_ACCENTS),
                $str
            );
    }

    return $str;

}
vendor/joomla/string/src/phputf8/utils/position.php000064400000011765152177723700016557 0ustar00<?php
/**
* Locate a byte index given a UTF-8 character index
* @package utf8
*/

//--------------------------------------------------------------------
/**
* Given a string and a character index in the string, in
* terms of the UTF-8 character position, returns the byte
* index of that character. Can be useful when you want to
* PHP's native string functions but we warned, locating
* the byte can be expensive
* Takes variable number of parameters - first must be
* the search string then 1 to n UTF-8 character positions
* to obtain byte indexes for - it is more efficient to search
* the string for multiple characters at once, than make
* repeated calls to this function
*
* @author Chris Smith<chris@jalakai.co.uk>
* @param string string to locate index in
* @param int (n times)
* @return mixed - int if only one input int, array if more
* @return boolean TRUE if it's all ASCII
* @package utf8
*/
function utf8_byte_position() {

    $args = func_get_args();
    $str =& array_shift($args);
    if (!is_string($str)) return false;

    $result = array();

    // trivial byte index, character offset pair
    $prev = array(0,0);

    // use a short piece of str to estimate bytes per character
    // $i (& $j) -> byte indexes into $str
    $i = utf8_locate_next_chr($str, 300);

    // $c -> character offset into $str
    $c = strlen(utf8_decode(substr($str,0,$i)));

    // deal with arguments from lowest to highest
    sort($args);

    foreach ($args as $offset) {
        // sanity checks FIXME

        // 0 is an easy check
        if ($offset == 0) { $result[] = 0; continue; }

        // ensure no endless looping
        $safety_valve = 50;

        do {

            if ( ($c - $prev[1]) == 0 ) {
                // Hack: gone past end of string
                $error = 0;
                $i = strlen($str);
                break;
            }

            $j = $i + (int)(($offset-$c) * ($i - $prev[0]) / ($c - $prev[1]));

            // correct to utf8 character boundary
            $j = utf8_locate_next_chr($str, $j);

            // save the index, offset for use next iteration
            $prev = array($i,$c);

            if ($j > $i) {
                // determine new character offset
                $c += strlen(utf8_decode(substr($str,$i,$j-$i)));
            } else {
                // ditto
                $c -= strlen(utf8_decode(substr($str,$j,$i-$j)));
            }

            $error = abs($c-$offset);

            // ready for next time around
            $i = $j;

        // from 7 it is faster to iterate over the string
        } while ( ($error > 7) && --$safety_valve) ;

        if ($error && $error <= 7) {

            if ($c < $offset) {
                // move up
                while ($error--) { $i = utf8_locate_next_chr($str,++$i); }
            } else {
                // move down
                while ($error--) { $i = utf8_locate_current_chr($str,--$i); }
            }

            // ready for next arg
            $c = $offset;
        }
        $result[] = $i;
    }

    if ( count($result) == 1 ) {
        return $result[0];
    }

    return $result;
}

//--------------------------------------------------------------------
/**
* Given a string and any byte index, returns the byte index
* of the start of the current UTF-8 character, relative to supplied
* position. If the current character begins at the same place as the
* supplied byte index, that byte index will be returned. Otherwise
* this function will step backwards, looking for the index where
* curent UTF-8 character begins
* @author Chris Smith<chris@jalakai.co.uk>
* @param string
* @param int byte index in the string
* @return int byte index of start of next UTF-8 character
* @package utf8
*/
function utf8_locate_current_chr( &$str, $idx ) {

    if ($idx <= 0) return 0;

    $limit = strlen($str);
    if ($idx >= $limit) return $limit;

    // Binary value for any byte after the first in a multi-byte UTF-8 character
    // will be like 10xxxxxx so & 0xC0 can be used to detect this kind
    // of byte - assuming well formed UTF-8
    while ($idx && ((ord($str[$idx]) & 0xC0) == 0x80)) $idx--;

    return $idx;
}

//--------------------------------------------------------------------
/**
* Given a string and any byte index, returns the byte index
* of the start of the next UTF-8 character, relative to supplied
* position. If the next character begins at the same place as the
* supplied byte index, that byte index will be returned.
* @author Chris Smith<chris@jalakai.co.uk>
* @param string
* @param int byte index in the string
* @return int byte index of start of next UTF-8 character
* @package utf8
*/
function utf8_locate_next_chr( &$str, $idx ) {

    if ($idx <= 0) return 0;

    $limit = strlen($str);
    if ($idx >= $limit) return $limit;

    // Binary value for any byte after the first in a multi-byte UTF-8 character
    // will be like 10xxxxxx so & 0xC0 can be used to detect this kind
    // of byte - assuming well formed UTF-8
    while (($idx < $limit) && ((ord($str[$idx]) & 0xC0) == 0x80)) $idx++;

    return $idx;
}

vendor/joomla/string/src/phputf8/utils/validation.php000064400000014617152177723700017044 0ustar00<?php
/**
* Tools for validing a UTF-8 string is well formed.
* The Original Code is Mozilla Communicator client code.
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
* Ported to PHP by Henri Sivonen (http://hsivonen.iki.fi)
* Slight modifications to fit with phputf8 library by Harry Fuecks (hfuecks gmail com)
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUTF8ToUnicode.cpp
* @see http://lxr.mozilla.org/seamonkey/source/intl/uconv/src/nsUnicodeToUTF8.cpp
* @see http://hsivonen.iki.fi/php-utf8/
* @package utf8
*/

//--------------------------------------------------------------------
/**
* Tests a string as to whether it's valid UTF-8 and supported by the
* Unicode standard
* Note: this function has been modified to simple return true or false
* @author <hsivonen@iki.fi>
* @param string UTF-8 encoded string
* @return boolean true if valid
* @see http://hsivonen.iki.fi/php-utf8/
* @see utf8_compliant
* @package utf8
*/
function utf8_is_valid($str) {

    $mState = 0;     // cached expected number of octets after the current octet
                     // until the beginning of the next UTF8 character sequence
    $mUcs4  = 0;     // cached Unicode character
    $mBytes = 1;     // cached expected number of octets in the current sequence

    $len = strlen($str);

    for($i = 0; $i < $len; $i++) {

        $in = ord($str{$i});

        if ( $mState == 0) {

            // When mState is zero we expect either a US-ASCII character or a
            // multi-octet sequence.
            if (0 == (0x80 & ($in))) {
                // US-ASCII, pass straight through.
                $mBytes = 1;

            } else if (0xC0 == (0xE0 & ($in))) {
                // First octet of 2 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x1F) << 6;
                $mState = 1;
                $mBytes = 2;

            } else if (0xE0 == (0xF0 & ($in))) {
                // First octet of 3 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x0F) << 12;
                $mState = 2;
                $mBytes = 3;

            } else if (0xF0 == (0xF8 & ($in))) {
                // First octet of 4 octet sequence
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x07) << 18;
                $mState = 3;
                $mBytes = 4;

            } else if (0xF8 == (0xFC & ($in))) {
                /* First octet of 5 octet sequence.
                *
                * This is illegal because the encoded codepoint must be either
                * (a) not the shortest form or
                * (b) outside the Unicode range of 0-0x10FFFF.
                * Rather than trying to resynchronize, we will carry on until the end
                * of the sequence and let the later error handling code catch it.
                */
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 0x03) << 24;
                $mState = 4;
                $mBytes = 5;

            } else if (0xFC == (0xFE & ($in))) {
                // First octet of 6 octet sequence, see comments for 5 octet sequence.
                $mUcs4 = ($in);
                $mUcs4 = ($mUcs4 & 1) << 30;
                $mState = 5;
                $mBytes = 6;

            } else {
                /* Current octet is neither in the US-ASCII range nor a legal first
                 * octet of a multi-octet sequence.
                 */
                return FALSE;

            }

        } else {

            // When mState is non-zero, we expect a continuation of the multi-octet
            // sequence
            if (0x80 == (0xC0 & ($in))) {

                // Legal continuation.
                $shift = ($mState - 1) * 6;
                $tmp = $in;
                $tmp = ($tmp & 0x0000003F) << $shift;
                $mUcs4 |= $tmp;

                /**
                * End of the multi-octet sequence. mUcs4 now contains the final
                * Unicode codepoint to be output
                */
                if (0 == --$mState) {

                    /*
                    * Check for illegal sequences and codepoints.
                    */
                    // From Unicode 3.1, non-shortest form is illegal
                    if (((2 == $mBytes) && ($mUcs4 < 0x0080)) ||
                        ((3 == $mBytes) && ($mUcs4 < 0x0800)) ||
                        ((4 == $mBytes) && ($mUcs4 < 0x10000)) ||
                        (4 < $mBytes) ||
                        // From Unicode 3.2, surrogate characters are illegal
                        (($mUcs4 & 0xFFFFF800) == 0xD800) ||
                        // Codepoints outside the Unicode range are illegal
                        ($mUcs4 > 0x10FFFF)) {

                        return FALSE;

                    }

                    //initialize UTF8 cache
                    $mState = 0;
                    $mUcs4  = 0;
                    $mBytes = 1;
                }

            } else {
                /**
                *((0xC0 & (*in) != 0x80) && (mState != 0))
                * Incomplete multi-octet sequence.
                */

                return FALSE;
            }
        }
    }
    return TRUE;
}

//--------------------------------------------------------------------
/**
* Tests whether a string complies as UTF-8. This will be much
* faster than utf8_is_valid but will pass five and six octet
* UTF-8 sequences, which are not supported by Unicode and
* so cannot be displayed correctly in a browser. In other words
* it is not as strict as utf8_is_valid but it's faster. If you use
* is to validate user input, you place yourself at the risk that
* attackers will be able to inject 5 and 6 byte sequences (which
* may or may not be a significant risk, depending on what you are
* are doing)
* @see utf8_is_valid
* @see http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php#54805
* @param string UTF-8 string to check
* @return boolean TRUE if string is valid UTF-8
* @package utf8
*/
function utf8_compliant($str) {
    if ( strlen($str) == 0 ) {
        return TRUE;
    }
    // If even just the first character can be matched, when the /u
    // modifier is used, then it's valid UTF-8. If the UTF-8 is somehow
    // invalid, nothing at all will match, even if the string contains
    // some valid sequences
    return (preg_match('/^.{1}/us',$str,$ar) == 1);
}

vendor/joomla/string/src/phputf8/utils/patterns.php000064400000005502152177723700016543 0ustar00<?php
/**
* PCRE Regular expressions for UTF-8. Note this file is not actually used by
* the rest of the library but these regular expressions can be useful to have
* available.
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @package utf8
*/

//--------------------------------------------------------------------
/**
* PCRE Pattern to check a UTF-8 string is valid
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @package utf8
*/
$UTF8_VALID = '^('.
    '[\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.              # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.          # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.   # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.          # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.       # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.           # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.       # plane 16
    ')*$';

//--------------------------------------------------------------------
/**
* PCRE Pattern to match single UTF-8 characters
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @package utf8
*/
$UTF8_MATCH =
    '([\x00-\x7F])'.                          # ASCII (including control chars)
    '|([\xC2-\xDF][\x80-\xBF])'.              # non-overlong 2-byte
    '|(\xE0[\xA0-\xBF][\x80-\xBF])'.          # excluding overlongs
    '|([\xE1-\xEC\xEE\xEF][\x80-\xBF]{2})'.   # straight 3-byte
    '|(\xED[\x80-\x9F][\x80-\xBF])'.          # excluding surrogates
    '|(\xF0[\x90-\xBF][\x80-\xBF]{2})'.       # planes 1-3
    '|([\xF1-\xF3][\x80-\xBF]{3})'.           # planes 4-15
    '|(\xF4[\x80-\x8F][\x80-\xBF]{2})';       # plane 16

//--------------------------------------------------------------------
/**
* PCRE Pattern to locate bad bytes in a UTF-8 string
* Comes from W3 FAQ: Multilingual Forms
* Note: modified to include full ASCII range including control chars
* @see http://www.w3.org/International/questions/qa-forms-utf-8
* @package utf8
*/
$UTF8_BAD =
    '([\x00-\x7F]'.                          # ASCII (including control chars)
    '|[\xC2-\xDF][\x80-\xBF]'.               # non-overlong 2-byte
    '|\xE0[\xA0-\xBF][\x80-\xBF]'.           # excluding overlongs
    '|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}'.    # straight 3-byte
    '|\xED[\x80-\x9F][\x80-\xBF]'.           # excluding surrogates
    '|\xF0[\x90-\xBF][\x80-\xBF]{2}'.        # planes 1-3
    '|[\xF1-\xF3][\x80-\xBF]{3}'.            # planes 4-15
    '|\xF4[\x80-\x8F][\x80-\xBF]{2}'.        # plane 16
    '|(.{1}))';                              # invalid byte
vendor/joomla/string/src/phputf8/README000064400000006330152177723700013712 0ustar00++PHP UTF-8++

Version 0.5

++DOCUMENTATION++

Documentation in progress in ./docs dir

http://www.phpwact.org/php/i18n/charsets
http://www.phpwact.org/php/i18n/utf-8

Important Note: DO NOT use these functions without understanding WHY
you are using them. In particular, do not blindly replace all use of PHP's
string functions which functions found here - most of the time you will
not need to, and you will be introducing a significant performance
overhead to your application. You can get a good idea of when to use what
from reading: http://www.phpwact.org/php/i18n/utf-8

Important Note: For sake of performance most of the functions here are
not "defensive" (e.g. there is not extensive parameter checking, well
formed UTF-8 is assumed). This is particularily relevant when is comes to
catching badly formed UTF-8 - you should screen input on the "outer
perimeter" with help from functions in the utf8_validation.php and
utf8_bad.php files.

Important Note: this library treats ALL ASCII characters as valid, including ASCII control characters. But if you use some ASCII control characters in XML, it will render the XML ill-formed. Don't be a bozo: http://hsivonen.iki.fi/producing-xml/#controlchar

++BUGS / SUPPORT / FEATURE REQUESTS ++

Please report bugs to:
http://sourceforge.net/tracker/?group_id=142846&atid=753842
- if you are able, please submit a failing unit test
(http://www.lastcraft.com/simple_test.php) with your bug report.

For feature requests / faster implementation of functions found here,
please drop them in via the RFE tracker: http://sourceforge.net/tracker/?group_id=142846&atid=753845
Particularily interested in faster implementations!

For general support / help, use:
http://sourceforge.net/tracker/?group_id=142846&atid=753843

In the VERY WORST case, you can email me: hfuecks gmail com - I tend to be slow to respond though so be warned.

Important Note: when reporting bugs, please provide the following
information;

PHP version, whether the iconv extension is loaded (in PHP5 it's
there by default), whether the mbstring extension is loaded. The
following PHP script can be used to determine this information;

<?php
print "PHP Version: " .phpversion()."<br>";
if ( extension_loaded('mbstring') ) {
    print "mbstring available<br>";
} else {
    print "mbstring not available<br>";
}
if ( extension_loaded('iconv') ) {
    print "iconv available<br>";
} else {
    print "iconv not available<br>";
}
?>

++LICENSING++

Parts of the code in this library come from other places, under different
licenses.
The authors involved have been contacted (see below). Attribution for
which code came from elsewhere can be found in the source code itself.

+Andreas Gohr / Chris Smith - Dokuwiki
There is a fair degree of collaboration / exchange of ideas and code
beteen Dokuwiki's UTF-8 library;
http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
and phputf8. Although Dokuwiki is released under GPL, its UTF-8
library is released under LGPL, hence no conflict with phputf8

+Henri Sivonen (http://hsivonen.iki.fi/php-utf8/ /
http://hsivonen.iki.fi/php-utf8/) has also given permission for his
code to be released under the terms of the LGPL. He ported a Unicode / UTF-8
converter from the Mozilla codebase to PHP, which is re-used in phputf8
vendor/joomla/string/src/phputf8/ucwords.php000064400000002564152177723700015236 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to ucwords
* Uppercase the first character of each word in a string
* Note: requires utf8_substr_replace and utf8_strtoupper
* @param string
* @return string with first char of each word uppercase
* @see http://www.php.net/ucwords
* @package utf8
*/
function utf8_ucwords($str) {
    // Note: [\x0c\x09\x0b\x0a\x0d\x20] matches;
    // form feeds, horizontal tabs, vertical tabs, linefeeds and carriage returns
    // This corresponds to the definition of a "word" defined at http://www.php.net/ucwords
    $pattern = '/(^|([\x0c\x09\x0b\x0a\x0d\x20]+))([^\x0c\x09\x0b\x0a\x0d\x20]{1})[^\x0c\x09\x0b\x0a\x0d\x20]*/u';
    return preg_replace_callback($pattern, 'utf8_ucwords_callback',$str);
}

//---------------------------------------------------------------
/**
* Callback function for preg_replace_callback call in utf8_ucwords
* You don't need to call this yourself
* @param array of matches corresponding to a single word
* @return string with first char of the word in uppercase
* @see utf8_ucwords
* @see utf8_strtoupper
* @package utf8
*/
function utf8_ucwords_callback($matches) {
    $leadingws = $matches[2];
    $ucfirst = utf8_strtoupper($matches[3]);
    $ucword = utf8_substr_replace(ltrim($matches[0]),$ucfirst,0,1);
    return $leadingws . $ucword;
}

vendor/joomla/string/src/phputf8/stristr.php000064400000001433152177723700015254 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to stristr
* Find first occurrence of a string using case insensitive comparison
* Note: requires utf8_strtolower
* @param string
* @param string
* @return int
* @see http://www.php.net/strcasecmp
* @see utf8_strtolower
* @package utf8
*/
function utf8_stristr($str, $search) {

    if ( strlen($search) == 0 ) {
        return $str;
    }

    $lstr = utf8_strtolower($str);
    $lsearch = utf8_strtolower($search);
    //JOOMLA SPECIFIC FIX - BEGIN
    preg_match('/^(.*)'.preg_quote($lsearch, '/').'/Us',$lstr, $matches);
    //JOOMLA SPECIFIC FIX - END

    if ( count($matches) == 2 ) {
        return substr($str, strlen($matches[1]));
    }

    return FALSE;
}
vendor/joomla/string/src/phputf8/str_split.php000064400000001374152177723700015571 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to str_split
* Convert a string to an array
* Note: requires utf8_strlen to be loaded
* @param string UTF-8 encoded
* @param int number to characters to split string by
* @return string characters in string reverses
* @see http://www.php.net/str_split
* @see utf8_strlen
* @package utf8
*/
function utf8_str_split($str, $split_len = 1) {

    if ( !preg_match('/^[0-9]+$/',$split_len) || $split_len < 1 ) {
        return FALSE;
    }

    $len = utf8_strlen($str);
    if ( $len <= $split_len ) {
        return array($str);
    }

    preg_match_all('/.{'.$split_len.'}|[^\x00]{1,'.$split_len.'}$/us', $str, $ar);
    return $ar[0];

}
vendor/joomla/string/src/phputf8/strcspn.php000064400000001502152177723700015233 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strcspn
* Find length of initial segment not matching mask
* Note: requires utf8_strlen and utf8_substr (if start, length are used)
* @param string
* @return int
* @see http://www.php.net/strcspn
* @see utf8_strlen
* @package utf8
*/
function utf8_strcspn($str, $mask, $start = NULL, $length = NULL) {

    if ( empty($mask) || strlen($mask) == 0 ) {
        return NULL;
    }

    $mask = preg_replace('!([\\\\\\-\\]\\[/^])!','\\\${1}',$mask);

    if ( $start !== NULL || $length !== NULL ) {
        $str = utf8_substr($str, $start, $length);
    }

    preg_match('/^[^'.$mask.']+/u',$str, $matches);

    if ( isset($matches[0]) ) {
        return utf8_strlen($matches[0]);
    }

    return 0;

}

vendor/joomla/string/src/phputf8/str_pad.php000064400000003167152177723700015204 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* Replacement for str_pad. $padStr may contain multi-byte characters.
*
* @author Oliver Saunders <oliver (a) osinternetservices.com>
* @param string $input
* @param int $length
* @param string $padStr
* @param int $type ( same constants as str_pad )
* @return string
* @see http://www.php.net/str_pad
* @see utf8_substr
* @package utf8
*/
function utf8_str_pad($input, $length, $padStr = ' ', $type = STR_PAD_RIGHT) {

    $inputLen = utf8_strlen($input);
    if ($length <= $inputLen) {
        return $input;
    }

    $padStrLen = utf8_strlen($padStr);
    $padLen = $length - $inputLen;

    if ($type == STR_PAD_RIGHT) {
        $repeatTimes = ceil($padLen / $padStrLen);
        return utf8_substr($input . str_repeat($padStr, $repeatTimes), 0, $length);
    }

    if ($type == STR_PAD_LEFT) {
        $repeatTimes = ceil($padLen / $padStrLen);
        return utf8_substr(str_repeat($padStr, $repeatTimes), 0, floor($padLen)) . $input;
    }

    if ($type == STR_PAD_BOTH) {

        $padLen/= 2;
        $padAmountLeft = floor($padLen);
        $padAmountRight = ceil($padLen);
        $repeatTimesLeft = ceil($padAmountLeft / $padStrLen);
        $repeatTimesRight = ceil($padAmountRight / $padStrLen);

        $paddingLeft = utf8_substr(str_repeat($padStr, $repeatTimesLeft), 0, $padAmountLeft);
        $paddingRight = utf8_substr(str_repeat($padStr, $repeatTimesRight), 0, $padAmountLeft);
        return $paddingLeft . $input . $paddingRight;
    }

    trigger_error('utf8_str_pad: Unknown padding type (' . $type . ')',E_USER_ERROR);
}
vendor/joomla/string/src/phputf8/native/core.php000064400000040673152177723700015771 0ustar00<?php
/**
* @package utf8
*/

/**
* Define UTF8_CORE as required
*/
if ( !defined('UTF8_CORE') ) {
    define('UTF8_CORE',TRUE);
}

//--------------------------------------------------------------------
/**
* Unicode aware replacement for strlen(). Returns the number
* of characters in the string (not the number of bytes), replacing
* multibyte characters with a single byte equivalent
* utf8_decode() converts characters that are not in ISO-8859-1
* to '?', which, for the purpose of counting, is alright - It's
* much faster than iconv_strlen
* Note: this function does not count bad UTF-8 bytes in the string
* - these are simply ignored
* @author <chernyshevsky at hotmail dot com>
* @link   http://www.php.net/manual/en/function.strlen.php
* @link   http://www.php.net/manual/en/function.utf8-decode.php
* @param string UTF-8 string
* @return int number of UTF-8 characters in string
* @package utf8
*/
function utf8_strlen($str){
    return strlen(utf8_decode($str));
}


//--------------------------------------------------------------------
/**
* UTF-8 aware alternative to strpos
* Find position of first occurrence of a string
* Note: This will get alot slower if offset is used
* Note: requires utf8_strlen amd utf8_substr to be loaded
* @param string haystack
* @param string needle (you should validate this with utf8_is_valid)
* @param integer offset in characters (from left)
* @return mixed integer position or FALSE on failure
* @see http://www.php.net/strpos
* @see utf8_strlen
* @see utf8_substr
* @package utf8
*/
function utf8_strpos($str, $needle, $offset = NULL) {

    if ( is_null($offset) ) {

        $ar = explode($needle, $str, 2);
        if ( count($ar) > 1 ) {
            return utf8_strlen($ar[0]);
        }
        return FALSE;

    } else {

        if ( !is_int($offset) ) {
            trigger_error('utf8_strpos: Offset must be an integer',E_USER_ERROR);
            return FALSE;
        }

        $str = utf8_substr($str, $offset);

        if ( FALSE !== ( $pos = utf8_strpos($str, $needle) ) ) {
            return $pos + $offset;
        }

        return FALSE;
    }

}

//--------------------------------------------------------------------
/**
* UTF-8 aware alternative to strrpos
* Find position of last occurrence of a char in a string
* Note: This will get alot slower if offset is used
* Note: requires utf8_substr and utf8_strlen to be loaded
* @param string haystack
* @param string needle (you should validate this with utf8_is_valid)
* @param integer (optional) offset (from left)
* @return mixed integer position or FALSE on failure
* @see http://www.php.net/strrpos
* @see utf8_substr
* @see utf8_strlen
* @package utf8
*/
function utf8_strrpos($str, $needle, $offset = NULL) {

    if ( is_null($offset) ) {

        $ar = explode($needle, $str);

        if ( count($ar) > 1 ) {
            // Pop off the end of the string where the last match was made
            array_pop($ar);
            $str = join($needle,$ar);
            return utf8_strlen($str);
        }
        return FALSE;

    } else {

        if ( !is_int($offset) ) {
            trigger_error('utf8_strrpos expects parameter 3 to be long',E_USER_WARNING);
            return FALSE;
        }

        $str = utf8_substr($str, $offset);

        if ( FALSE !== ( $pos = utf8_strrpos($str, $needle) ) ) {
            return $pos + $offset;
        }

        return FALSE;
    }

}

//--------------------------------------------------------------------
/**
* UTF-8 aware alternative to substr
* Return part of a string given character offset (and optionally length)
*
* Note arguments: comparied to substr - if offset or length are
* not integers, this version will not complain but rather massages them
* into an integer.
*
* Note on returned values: substr documentation states false can be
* returned in some cases (e.g. offset > string length)
* mb_substr never returns false, it will return an empty string instead.
* This adopts the mb_substr approach
*
* Note on implementation: PCRE only supports repetitions of less than
* 65536, in order to accept up to MAXINT values for offset and length,
* we'll repeat a group of 65535 characters when needed.
*
* Note on implementation: calculating the number of characters in the
* string is a relatively expensive operation, so we only carry it out when
* necessary. It isn't necessary for +ve offsets and no specified length
*
* @author Chris Smith<chris@jalakai.co.uk>
* @param string
* @param integer number of UTF-8 characters offset (from left)
* @param integer (optional) length in UTF-8 characters from offset
* @return mixed string or FALSE if failure
* @package utf8
*/
function utf8_substr($str, $offset, $length = NULL) {

    // generates E_NOTICE
    // for PHP4 objects, but not PHP5 objects
    $str = (string)$str;
    $offset = (int)$offset;
    if (!is_null($length)) $length = (int)$length;

    // handle trivial cases
    if ($length === 0) return '';
    if ($offset < 0 && $length < 0 && $length < $offset)
        return '';

    // normalise negative offsets (we could use a tail
    // anchored pattern, but they are horribly slow!)
    if ($offset < 0) {

        // see notes
        $strlen = strlen(utf8_decode($str));
        $offset = $strlen + $offset;
        if ($offset < 0) $offset = 0;

    }

    $Op = '';
    $Lp = '';

    // establish a pattern for offset, a
    // non-captured group equal in length to offset
    if ($offset > 0) {

        $Ox = (int)($offset/65535);
        $Oy = $offset%65535;

        if ($Ox) {
            $Op = '(?:.{65535}){'.$Ox.'}';
        }

        $Op = '^(?:'.$Op.'.{'.$Oy.'})';

    } else {

        // offset == 0; just anchor the pattern
        $Op = '^';

    }

    // establish a pattern for length
    if (is_null($length)) {

        // the rest of the string
        $Lp = '(.*)$';

    } else {

        if (!isset($strlen)) {
            // see notes
            $strlen = strlen(utf8_decode($str));
        }

        // another trivial case
        if ($offset > $strlen) return '';

        if ($length > 0) {

            // reduce any length that would
            // go passed the end of the string
            $length = min($strlen-$offset, $length);

            $Lx = (int)( $length / 65535 );
            $Ly = $length % 65535;

            // negative length requires a captured group
            // of length characters
            if ($Lx) $Lp = '(?:.{65535}){'.$Lx.'}';
            $Lp = '('.$Lp.'.{'.$Ly.'})';

        } else if ($length < 0) {

            if ( $length < ($offset - $strlen) ) {
                return '';
            }

            $Lx = (int)((-$length)/65535);
            $Ly = (-$length)%65535;

            // negative length requires ... capture everything
            // except a group of  -length characters
            // anchored at the tail-end of the string
            if ($Lx) $Lp = '(?:.{65535}){'.$Lx.'}';
            $Lp = '(.*)(?:'.$Lp.'.{'.$Ly.'})$';

        }

    }

    if (!preg_match( '#'.$Op.$Lp.'#us',$str, $match )) {
        return '';
    }

    return $match[1];

}

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strtolower
* Make a string lowercase
* Note: The concept of a characters "case" only exists is some alphabets
* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
* not exist in the Chinese alphabet, for example. See Unicode Standard
* Annex #21: Case Mappings
* Note: requires utf8_to_unicode and utf8_from_unicode
* @author Andreas Gohr <andi@splitbrain.org>
* @param string
* @return mixed either string in lowercase or FALSE is UTF-8 invalid
* @see http://www.php.net/strtolower
* @see utf8_to_unicode
* @see utf8_from_unicode
* @see http://www.unicode.org/reports/tr21/tr21-5.html
* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
* @package utf8
*/
function utf8_strtolower($string){

    static $UTF8_UPPER_TO_LOWER = NULL;

    if ( is_null($UTF8_UPPER_TO_LOWER) ) {
        $UTF8_UPPER_TO_LOWER = array(
    0x0041=>0x0061, 0x03A6=>0x03C6, 0x0162=>0x0163, 0x00C5=>0x00E5, 0x0042=>0x0062,
    0x0139=>0x013A, 0x00C1=>0x00E1, 0x0141=>0x0142, 0x038E=>0x03CD, 0x0100=>0x0101,
    0x0490=>0x0491, 0x0394=>0x03B4, 0x015A=>0x015B, 0x0044=>0x0064, 0x0393=>0x03B3,
    0x00D4=>0x00F4, 0x042A=>0x044A, 0x0419=>0x0439, 0x0112=>0x0113, 0x041C=>0x043C,
    0x015E=>0x015F, 0x0143=>0x0144, 0x00CE=>0x00EE, 0x040E=>0x045E, 0x042F=>0x044F,
    0x039A=>0x03BA, 0x0154=>0x0155, 0x0049=>0x0069, 0x0053=>0x0073, 0x1E1E=>0x1E1F,
    0x0134=>0x0135, 0x0427=>0x0447, 0x03A0=>0x03C0, 0x0418=>0x0438, 0x00D3=>0x00F3,
    0x0420=>0x0440, 0x0404=>0x0454, 0x0415=>0x0435, 0x0429=>0x0449, 0x014A=>0x014B,
    0x0411=>0x0431, 0x0409=>0x0459, 0x1E02=>0x1E03, 0x00D6=>0x00F6, 0x00D9=>0x00F9,
    0x004E=>0x006E, 0x0401=>0x0451, 0x03A4=>0x03C4, 0x0423=>0x0443, 0x015C=>0x015D,
    0x0403=>0x0453, 0x03A8=>0x03C8, 0x0158=>0x0159, 0x0047=>0x0067, 0x00C4=>0x00E4,
    0x0386=>0x03AC, 0x0389=>0x03AE, 0x0166=>0x0167, 0x039E=>0x03BE, 0x0164=>0x0165,
    0x0116=>0x0117, 0x0108=>0x0109, 0x0056=>0x0076, 0x00DE=>0x00FE, 0x0156=>0x0157,
    0x00DA=>0x00FA, 0x1E60=>0x1E61, 0x1E82=>0x1E83, 0x00C2=>0x00E2, 0x0118=>0x0119,
    0x0145=>0x0146, 0x0050=>0x0070, 0x0150=>0x0151, 0x042E=>0x044E, 0x0128=>0x0129,
    0x03A7=>0x03C7, 0x013D=>0x013E, 0x0422=>0x0442, 0x005A=>0x007A, 0x0428=>0x0448,
    0x03A1=>0x03C1, 0x1E80=>0x1E81, 0x016C=>0x016D, 0x00D5=>0x00F5, 0x0055=>0x0075,
    0x0176=>0x0177, 0x00DC=>0x00FC, 0x1E56=>0x1E57, 0x03A3=>0x03C3, 0x041A=>0x043A,
    0x004D=>0x006D, 0x016A=>0x016B, 0x0170=>0x0171, 0x0424=>0x0444, 0x00CC=>0x00EC,
    0x0168=>0x0169, 0x039F=>0x03BF, 0x004B=>0x006B, 0x00D2=>0x00F2, 0x00C0=>0x00E0,
    0x0414=>0x0434, 0x03A9=>0x03C9, 0x1E6A=>0x1E6B, 0x00C3=>0x00E3, 0x042D=>0x044D,
    0x0416=>0x0436, 0x01A0=>0x01A1, 0x010C=>0x010D, 0x011C=>0x011D, 0x00D0=>0x00F0,
    0x013B=>0x013C, 0x040F=>0x045F, 0x040A=>0x045A, 0x00C8=>0x00E8, 0x03A5=>0x03C5,
    0x0046=>0x0066, 0x00DD=>0x00FD, 0x0043=>0x0063, 0x021A=>0x021B, 0x00CA=>0x00EA,
    0x0399=>0x03B9, 0x0179=>0x017A, 0x00CF=>0x00EF, 0x01AF=>0x01B0, 0x0045=>0x0065,
    0x039B=>0x03BB, 0x0398=>0x03B8, 0x039C=>0x03BC, 0x040C=>0x045C, 0x041F=>0x043F,
    0x042C=>0x044C, 0x00DE=>0x00FE, 0x00D0=>0x00F0, 0x1EF2=>0x1EF3, 0x0048=>0x0068,
    0x00CB=>0x00EB, 0x0110=>0x0111, 0x0413=>0x0433, 0x012E=>0x012F, 0x00C6=>0x00E6,
    0x0058=>0x0078, 0x0160=>0x0161, 0x016E=>0x016F, 0x0391=>0x03B1, 0x0407=>0x0457,
    0x0172=>0x0173, 0x0178=>0x00FF, 0x004F=>0x006F, 0x041B=>0x043B, 0x0395=>0x03B5,
    0x0425=>0x0445, 0x0120=>0x0121, 0x017D=>0x017E, 0x017B=>0x017C, 0x0396=>0x03B6,
    0x0392=>0x03B2, 0x0388=>0x03AD, 0x1E84=>0x1E85, 0x0174=>0x0175, 0x0051=>0x0071,
    0x0417=>0x0437, 0x1E0A=>0x1E0B, 0x0147=>0x0148, 0x0104=>0x0105, 0x0408=>0x0458,
    0x014C=>0x014D, 0x00CD=>0x00ED, 0x0059=>0x0079, 0x010A=>0x010B, 0x038F=>0x03CE,
    0x0052=>0x0072, 0x0410=>0x0430, 0x0405=>0x0455, 0x0402=>0x0452, 0x0126=>0x0127,
    0x0136=>0x0137, 0x012A=>0x012B, 0x038A=>0x03AF, 0x042B=>0x044B, 0x004C=>0x006C,
    0x0397=>0x03B7, 0x0124=>0x0125, 0x0218=>0x0219, 0x00DB=>0x00FB, 0x011E=>0x011F,
    0x041E=>0x043E, 0x1E40=>0x1E41, 0x039D=>0x03BD, 0x0106=>0x0107, 0x03AB=>0x03CB,
    0x0426=>0x0446, 0x00DE=>0x00FE, 0x00C7=>0x00E7, 0x03AA=>0x03CA, 0x0421=>0x0441,
    0x0412=>0x0432, 0x010E=>0x010F, 0x00D8=>0x00F8, 0x0057=>0x0077, 0x011A=>0x011B,
    0x0054=>0x0074, 0x004A=>0x006A, 0x040B=>0x045B, 0x0406=>0x0456, 0x0102=>0x0103,
    0x039B=>0x03BB, 0x00D1=>0x00F1, 0x041D=>0x043D, 0x038C=>0x03CC, 0x00C9=>0x00E9,
    0x00D0=>0x00F0, 0x0407=>0x0457, 0x0122=>0x0123,
            );
    }

    $uni = utf8_to_unicode($string);

    if ( !$uni ) {
        return FALSE;
    }

    $cnt = count($uni);
    for ($i=0; $i < $cnt; $i++){
        if ( isset($UTF8_UPPER_TO_LOWER[$uni[$i]]) ) {
            $uni[$i] = $UTF8_UPPER_TO_LOWER[$uni[$i]];
        }
    }

    return utf8_from_unicode($uni);
}

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strtoupper
* Make a string uppercase
* Note: The concept of a characters "case" only exists is some alphabets
* such as Latin, Greek, Cyrillic, Armenian and archaic Georgian - it does
* not exist in the Chinese alphabet, for example. See Unicode Standard
* Annex #21: Case Mappings
* Note: requires utf8_to_unicode and utf8_from_unicode
* @author Andreas Gohr <andi@splitbrain.org>
* @param string
* @return mixed either string in lowercase or FALSE is UTF-8 invalid
* @see http://www.php.net/strtoupper
* @see utf8_to_unicode
* @see utf8_from_unicode
* @see http://www.unicode.org/reports/tr21/tr21-5.html
* @see http://dev.splitbrain.org/view/darcs/dokuwiki/inc/utf8.php
* @package utf8
*/
function utf8_strtoupper($string){

    static $UTF8_LOWER_TO_UPPER = NULL;

    if ( is_null($UTF8_LOWER_TO_UPPER) ) {
        $UTF8_LOWER_TO_UPPER = array(
    0x0061=>0x0041, 0x03C6=>0x03A6, 0x0163=>0x0162, 0x00E5=>0x00C5, 0x0062=>0x0042,
    0x013A=>0x0139, 0x00E1=>0x00C1, 0x0142=>0x0141, 0x03CD=>0x038E, 0x0101=>0x0100,
    0x0491=>0x0490, 0x03B4=>0x0394, 0x015B=>0x015A, 0x0064=>0x0044, 0x03B3=>0x0393,
    0x00F4=>0x00D4, 0x044A=>0x042A, 0x0439=>0x0419, 0x0113=>0x0112, 0x043C=>0x041C,
    0x015F=>0x015E, 0x0144=>0x0143, 0x00EE=>0x00CE, 0x045E=>0x040E, 0x044F=>0x042F,
    0x03BA=>0x039A, 0x0155=>0x0154, 0x0069=>0x0049, 0x0073=>0x0053, 0x1E1F=>0x1E1E,
    0x0135=>0x0134, 0x0447=>0x0427, 0x03C0=>0x03A0, 0x0438=>0x0418, 0x00F3=>0x00D3,
    0x0440=>0x0420, 0x0454=>0x0404, 0x0435=>0x0415, 0x0449=>0x0429, 0x014B=>0x014A,
    0x0431=>0x0411, 0x0459=>0x0409, 0x1E03=>0x1E02, 0x00F6=>0x00D6, 0x00F9=>0x00D9,
    0x006E=>0x004E, 0x0451=>0x0401, 0x03C4=>0x03A4, 0x0443=>0x0423, 0x015D=>0x015C,
    0x0453=>0x0403, 0x03C8=>0x03A8, 0x0159=>0x0158, 0x0067=>0x0047, 0x00E4=>0x00C4,
    0x03AC=>0x0386, 0x03AE=>0x0389, 0x0167=>0x0166, 0x03BE=>0x039E, 0x0165=>0x0164,
    0x0117=>0x0116, 0x0109=>0x0108, 0x0076=>0x0056, 0x00FE=>0x00DE, 0x0157=>0x0156,
    0x00FA=>0x00DA, 0x1E61=>0x1E60, 0x1E83=>0x1E82, 0x00E2=>0x00C2, 0x0119=>0x0118,
    0x0146=>0x0145, 0x0070=>0x0050, 0x0151=>0x0150, 0x044E=>0x042E, 0x0129=>0x0128,
    0x03C7=>0x03A7, 0x013E=>0x013D, 0x0442=>0x0422, 0x007A=>0x005A, 0x0448=>0x0428,
    0x03C1=>0x03A1, 0x1E81=>0x1E80, 0x016D=>0x016C, 0x00F5=>0x00D5, 0x0075=>0x0055,
    0x0177=>0x0176, 0x00FC=>0x00DC, 0x1E57=>0x1E56, 0x03C3=>0x03A3, 0x043A=>0x041A,
    0x006D=>0x004D, 0x016B=>0x016A, 0x0171=>0x0170, 0x0444=>0x0424, 0x00EC=>0x00CC,
    0x0169=>0x0168, 0x03BF=>0x039F, 0x006B=>0x004B, 0x00F2=>0x00D2, 0x00E0=>0x00C0,
    0x0434=>0x0414, 0x03C9=>0x03A9, 0x1E6B=>0x1E6A, 0x00E3=>0x00C3, 0x044D=>0x042D,
    0x0436=>0x0416, 0x01A1=>0x01A0, 0x010D=>0x010C, 0x011D=>0x011C, 0x00F0=>0x00D0,
    0x013C=>0x013B, 0x045F=>0x040F, 0x045A=>0x040A, 0x00E8=>0x00C8, 0x03C5=>0x03A5,
    0x0066=>0x0046, 0x00FD=>0x00DD, 0x0063=>0x0043, 0x021B=>0x021A, 0x00EA=>0x00CA,
    0x03B9=>0x0399, 0x017A=>0x0179, 0x00EF=>0x00CF, 0x01B0=>0x01AF, 0x0065=>0x0045,
    0x03BB=>0x039B, 0x03B8=>0x0398, 0x03BC=>0x039C, 0x045C=>0x040C, 0x043F=>0x041F,
    0x044C=>0x042C, 0x00FE=>0x00DE, 0x00F0=>0x00D0, 0x1EF3=>0x1EF2, 0x0068=>0x0048,
    0x00EB=>0x00CB, 0x0111=>0x0110, 0x0433=>0x0413, 0x012F=>0x012E, 0x00E6=>0x00C6,
    0x0078=>0x0058, 0x0161=>0x0160, 0x016F=>0x016E, 0x03B1=>0x0391, 0x0457=>0x0407,
    0x0173=>0x0172, 0x00FF=>0x0178, 0x006F=>0x004F, 0x043B=>0x041B, 0x03B5=>0x0395,
    0x0445=>0x0425, 0x0121=>0x0120, 0x017E=>0x017D, 0x017C=>0x017B, 0x03B6=>0x0396,
    0x03B2=>0x0392, 0x03AD=>0x0388, 0x1E85=>0x1E84, 0x0175=>0x0174, 0x0071=>0x0051,
    0x0437=>0x0417, 0x1E0B=>0x1E0A, 0x0148=>0x0147, 0x0105=>0x0104, 0x0458=>0x0408,
    0x014D=>0x014C, 0x00ED=>0x00CD, 0x0079=>0x0059, 0x010B=>0x010A, 0x03CE=>0x038F,
    0x0072=>0x0052, 0x0430=>0x0410, 0x0455=>0x0405, 0x0452=>0x0402, 0x0127=>0x0126,
    0x0137=>0x0136, 0x012B=>0x012A, 0x03AF=>0x038A, 0x044B=>0x042B, 0x006C=>0x004C,
    0x03B7=>0x0397, 0x0125=>0x0124, 0x0219=>0x0218, 0x00FB=>0x00DB, 0x011F=>0x011E,
    0x043E=>0x041E, 0x1E41=>0x1E40, 0x03BD=>0x039D, 0x0107=>0x0106, 0x03CB=>0x03AB,
    0x0446=>0x0426, 0x00FE=>0x00DE, 0x00E7=>0x00C7, 0x03CA=>0x03AA, 0x0441=>0x0421,
    0x0432=>0x0412, 0x010F=>0x010E, 0x00F8=>0x00D8, 0x0077=>0x0057, 0x011B=>0x011A,
    0x0074=>0x0054, 0x006A=>0x004A, 0x045B=>0x040B, 0x0456=>0x0406, 0x0103=>0x0102,
    0x03BB=>0x039B, 0x00F1=>0x00D1, 0x043D=>0x041D, 0x03CC=>0x038C, 0x00E9=>0x00C9,
    0x00F0=>0x00D0, 0x0457=>0x0407, 0x0123=>0x0122,
            );
    }

    $uni = utf8_to_unicode($string);

    if ( !$uni ) {
        return FALSE;
    }

    $cnt = count($uni);
    for ($i=0; $i < $cnt; $i++){
        if( isset($UTF8_LOWER_TO_UPPER[$uni[$i]]) ) {
            $uni[$i] = $UTF8_LOWER_TO_UPPER[$uni[$i]];
        }
    }

    return utf8_from_unicode($uni);
}
vendor/joomla/string/src/phputf8/strrev.php000064400000000616152177723700015071 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strrev
* Reverse a string
* @param string UTF-8 encoded
* @return string characters in string reverses
* @see http://www.php.net/strrev
* @package utf8
*/
function utf8_strrev($str){
    preg_match_all('/./us', $str, $ar);
    return join('',array_reverse($ar[0]));
}

vendor/joomla/string/src/phputf8/ucfirst.php000064400000001327152177723700015223 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to ucfirst
* Make a string's first character uppercase
* Note: requires utf8_strtoupper
* @param string
* @return string with first character as upper case (if applicable)
* @see http://www.php.net/ucfirst
* @see utf8_strtoupper
* @package utf8
*/
function utf8_ucfirst($str){
    switch ( utf8_strlen($str) ) {
        case 0:
            return '';
        break;
        case 1:
            return utf8_strtoupper($str);
        break;
        default:
            preg_match('/^(.{1})(.*)$/us', $str, $matches);
            return utf8_strtoupper($matches[1]).$matches[2];
        break;
    }
}

vendor/joomla/string/src/phputf8/strcasecmp.php000064400000000746152177723700015714 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strcasecmp
* A case insensivite string comparison
* Note: requires utf8_strtolower
* @param string
* @param string
* @return int
* @see http://www.php.net/strcasecmp
* @see utf8_strtolower
* @package utf8
*/
function utf8_strcasecmp($strX, $strY) {
    $strX = utf8_strtolower($strX);
    $strY = utf8_strtolower($strY);
    return strcmp($strX, $strY);
}

vendor/joomla/string/src/phputf8/strspn.php000064400000001537152177723700015100 0ustar00<?php
/**
* @package utf8
*/

//---------------------------------------------------------------
/**
* UTF-8 aware alternative to strspn
* Find length of initial segment matching mask
* Note: requires utf8_strlen and utf8_substr (if start, length are used)
* @param string
* @return int
* @see http://www.php.net/strspn
* @package utf8
*/
function utf8_strspn($str, $mask, $start = NULL, $length = NULL) {

    $mask = preg_replace('!([\\\\\\-\\]\\[/^])!','\\\${1}',$mask);

	// Fix for $start but no $length argument.
    if ($start !== null && $length === null) {
    	$length = utf8_strlen($str);
    }

    if ( $start !== NULL || $length !== NULL ) {
        $str = utf8_substr($str, $start, $length);
    }

    preg_match('/^['.$mask.']+/u',$str, $matches);

    if ( isset($matches[0]) ) {
        return utf8_strlen($matches[0]);
    }

    return 0;

}

vendor/joomla/string/src/Normalise.php000064400000010146152177723700014076 0ustar00<?php
/**
 * Part of the Joomla Framework String Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\String;

/**
 * Joomla Framework String Normalise Class
 *
 * @since  1.0
 */
abstract class Normalise
{
	/**
	 * Method to convert a string from camel case.
	 *
	 * This method offers two modes. Grouped allows for splitting on groups of uppercase characters as follows:
	 *
	 * "FooBarABCDef"            becomes  array("Foo", "Bar", "ABC", "Def")
	 * "JFooBar"                 becomes  array("J", "Foo", "Bar")
	 * "J001FooBar002"           becomes  array("J001", "Foo", "Bar002")
	 * "abcDef"                  becomes  array("abc", "Def")
	 * "abc_defGhi_Jkl"          becomes  array("abc_def", "Ghi_Jkl")
	 * "ThisIsA_NASAAstronaut"   becomes  array("This", "Is", "A_NASA", "Astronaut"))
	 * "JohnFitzgerald_Kennedy"  becomes  array("John", "Fitzgerald_Kennedy"))
	 *
	 * Non-grouped will split strings at each uppercase character.
	 *
	 * @param   string   $input    The string input (ASCII only).
	 * @param   boolean  $grouped  Optionally allows splitting on groups of uppercase characters.
	 *
	 * @return  string  The space separated string.
	 *
	 * @since   1.0
	 */
	public static function fromCamelCase($input, $grouped = false)
	{
		return $grouped
			? preg_split('/(?<=[^A-Z_])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][^A-Z_])/x', $input)
			: trim(preg_replace('#([A-Z])#', ' $1', $input));
	}

	/**
	 * Method to convert a string into camel case.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The camel case string.
	 *
	 * @since   1.0
	 */
	public static function toCamelCase($input)
	{
		// Convert words to uppercase and then remove spaces.
		$input = self::toSpaceSeparated($input);
		$input = ucwords($input);
		$input = str_ireplace(' ', '', $input);

		return $input;
	}

	/**
	 * Method to convert a string into dash separated form.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The dash separated string.
	 *
	 * @since   1.0
	 */
	public static function toDashSeparated($input)
	{
		// Convert spaces and underscores to dashes.
		$input = preg_replace('#[ \-_]+#', '-', $input);

		return $input;
	}

	/**
	 * Method to convert a string into space separated form.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The space separated string.
	 *
	 * @since   1.0
	 */
	public static function toSpaceSeparated($input)
	{
		// Convert underscores and dashes to spaces.
		$input = preg_replace('#[ \-_]+#', ' ', $input);

		return $input;
	}

	/**
	 * Method to convert a string into underscore separated form.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The underscore separated string.
	 *
	 * @since   1.0
	 */
	public static function toUnderscoreSeparated($input)
	{
		// Convert spaces and dashes to underscores.
		$input = preg_replace('#[ \-_]+#', '_', $input);

		return $input;
	}

	/**
	 * Method to convert a string into variable form.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The variable string.
	 *
	 * @since   1.0
	 */
	public static function toVariable($input)
	{
		// Remove dashes and underscores, then convert to camel case.
		$input = self::toSpaceSeparated($input);
		$input = self::toCamelCase($input);

		// Remove leading digits.
		$input = preg_replace('#^[0-9]+#', '', $input);

		// Lowercase the first character.
		$first = substr($input, 0, 1);
		$first = strtolower($first);

		// Replace the first character with the lowercase character.
		$input = substr_replace($input, $first, 0, 1);

		return $input;
	}

	/**
	 * Method to convert a string into key form.
	 *
	 * @param   string  $input  The string input (ASCII only).
	 *
	 * @return  string  The key string.
	 *
	 * @since   1.0
	 */
	public static function toKey($input)
	{
		// Remove spaces and dashes, then convert to lower case.
		$input = self::toUnderscoreSeparated($input);
		$input = strtolower($input);

		return $input;
	}
}
vendor/joomla/string/src/String.php000064400000000765152177723700013421 0ustar00<?php
/**
 * Part of the Joomla Framework String Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\String;

/**
 * String handling class for utf-8 data
 * Wraps the phputf8 library
 * All functions assume the validity of utf-8 strings.
 *
 * @since       1.0
 * @deprecated  2.0  Use StringHelper instead
 */
abstract class String extends StringHelper
{
}
vendor/joomla/application/LICENSE000064400000042630152177723700012652 0ustar00GNU GENERAL PUBLIC LICENSE
				Version 2, June 1991

 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

				Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users.  This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it.  (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.)  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.

  To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have.  You must make sure that they, too, receive or can get the
source code.  And you must show them these terms so they know their
rights.

  We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.

  Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software.  If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.

  Finally, any free program is threatened constantly by software
patents.  We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary.  To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.

  The precise terms and conditions for copying, distribution and
modification follow.

			GNU GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License.  The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language.  (Hereinafter, translation is included without limitation in
the term "modification".)  Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.

  1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.

You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.

  2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

	a) You must cause the modified files to carry prominent notices
	stating that you changed the files and the date of any change.

	b) You must cause any work that you distribute or publish, that in
	whole or in part contains or is derived from the Program or any
	part thereof, to be licensed as a whole at no charge to all third
	parties under the terms of this License.

	c) If the modified program normally reads commands interactively
	when run, you must cause it, when started running for such
	interactive use in the most ordinary way, to print or display an
	announcement including an appropriate copyright notice and a
	notice that there is no warranty (or else, saying that you provide
	a warranty) and that users may redistribute the program under
	these conditions, and telling the user how to view a copy of this
	License.  (Exception: if the Program itself is interactive but
	does not normally print such an announcement, your work based on
	the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.

In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:

	a) Accompany it with the complete corresponding machine-readable
	source code, which must be distributed under the terms of Sections
	1 and 2 above on a medium customarily used for software interchange; or,

	b) Accompany it with a written offer, valid for at least three
	years, to give any third party, for a charge no more than your
	cost of physically performing source distribution, a complete
	machine-readable copy of the corresponding source code, to be
	distributed under the terms of Sections 1 and 2 above on a medium
	customarily used for software interchange; or,

	c) Accompany it with the information you received as to the offer
	to distribute corresponding source code.  (This alternative is
	allowed only for noncommercial distribution and only if you
	received the program in object code or executable form with such
	an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for
making modifications to it.  For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable.  However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.

If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.

  4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License.  Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.

  5. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Program or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.

  6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.

  7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all.  For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded.  In such case, this License incorporates
the limitation as if written in the body of this License.

  9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

Each version is given a distinguishing version number.  If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation.  If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.

  10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission.  For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this.  Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.

				NO WARRANTY

  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

			 END OF TERMS AND CONDITIONS

		How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

	<one line to give the program's name and a brief idea of what it does.>
	Copyright (C) <year>  <name of author>

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

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

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:

	Gnomovision version 69, Copyright (C) year name of author
	Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
	This is free software, and you are welcome to redistribute it
	under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
  `Gnomovision' (which makes passes at compilers) written by James Hacker.

  <signature of Ty Coon>, 1 April 1989
  Ty Coon, President of Vice

This General Public License does not permit incorporating your program into
proprietary programs.  If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library.  If this is what you want to do, use the GNU Library General
Public License instead of this License.
vendor/joomla/application/src/AbstractDaemonApplication.php000064400000060461152177723700020222 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application;

use Joomla\Input;
use Joomla\Registry\Registry;
use Psr\Log\LoggerAwareInterface;

/**
 * Class to turn Cli applications into daemons.  It requires CLI and PCNTL support built into PHP.
 *
 * @link        https://secure.php.net/manual/en/book.pcntl.php
 * @link        https://secure.php.net/manual/en/features.commandline.php
 * @since       1.0
 * @deprecated  2.0  Deprecated without replacement
 */
abstract class AbstractDaemonApplication extends AbstractCliApplication implements LoggerAwareInterface
{
	/**
	 * @var    array  The available POSIX signals to be caught by default.
	 * @link   https://secure.php.net/manual/pcntl.constants.php
	 * @since  1.0
	 */
	protected static $signals = array(
		'SIGHUP',
		'SIGINT',
		'SIGQUIT',
		'SIGILL',
		'SIGTRAP',
		'SIGABRT',
		'SIGIOT',
		'SIGBUS',
		'SIGFPE',
		'SIGUSR1',
		'SIGSEGV',
		'SIGUSR2',
		'SIGPIPE',
		'SIGALRM',
		'SIGTERM',
		'SIGSTKFLT',
		'SIGCLD',
		'SIGCHLD',
		'SIGCONT',
		'SIGTSTP',
		'SIGTTIN',
		'SIGTTOU',
		'SIGURG',
		'SIGXCPU',
		'SIGXFSZ',
		'SIGVTALRM',
		'SIGPROF',
		'SIGWINCH',
		'SIGPOLL',
		'SIGIO',
		'SIGPWR',
		'SIGSYS',
		'SIGBABY',
		'SIG_BLOCK',
		'SIG_UNBLOCK',
		'SIG_SETMASK',
	);

	/**
	 * @var    boolean  True if the daemon is in the process of exiting.
	 * @since  1.0
	 */
	protected $exiting = false;

	/**
	 * @var    integer  The parent process id.
	 * @since  1.0
	 */
	protected $parentId = 0;

	/**
	 * @var    integer  The process id of the daemon.
	 * @since  1.0
	 */
	protected $processId = 0;

	/**
	 * @var    boolean  True if the daemon is currently running.
	 * @since  1.0
	 */
	protected $running = false;

	/**
	 * Class constructor.
	 *
	 * @param   Input\Cli      $input     An optional argument to provide dependency injection for the application's input object.  If the
	 *                                    argument is an Input\Cli object that object will become the application's input object, otherwise
	 *                                    a default input object is created.
	 * @param   Registry       $config    An optional argument to provide dependency injection for the application's config object.  If the
	 *                                    argument is a Registry object that object will become the application's config object, otherwise
	 *                                    a default config object is created.
	 * @param   Cli\CliOutput  $output    An optional argument to provide dependency injection for the application's output object.  If the
	 *                                    argument is a Cli\CliOutput object that object will become the application's input object, otherwise
	 *                                    a default output object is created.
	 * @param   Cli\CliInput   $cliInput  An optional argument to provide dependency injection for the application's CLI input object.  If the
	 *                                    argument is a Cli\CliInput object that object will become the application's input object, otherwise
	 *                                    a default input object is created.
	 *
	 * @since   1.0
	 */
	public function __construct(Cli $input = null, Registry $config = null, Cli\CliOutput $output = null, Cli\CliInput $cliInput = null)
	{
		// Verify that the process control extension for PHP is available.
		// @codeCoverageIgnoreStart
		if (!\defined('SIGHUP'))
		{
			$this->getLogger()->error('The PCNTL extension for PHP is not available.');

			throw new \RuntimeException('The PCNTL extension for PHP is not available.');
		}

		// Verify that POSIX support for PHP is available.
		if (!\function_exists('posix_getpid'))
		{
			$this->getLogger()->error('The POSIX extension for PHP is not available.');

			throw new \RuntimeException('The POSIX extension for PHP is not available.');
		}

		// @codeCoverageIgnoreEnd

		// Call the parent constructor.
		parent::__construct($input, $config, $output, $cliInput);

		// Set some system limits.
		@set_time_limit($this->get('max_execution_time', 0));

		if ($this->get('max_memory_limit') !== null)
		{
			ini_set('memory_limit', $this->get('max_memory_limit', '256M'));
		}

		// Flush content immediately.
		ob_implicit_flush();
	}

	/**
	 * Method to handle POSIX signals.
	 *
	 * @param   integer  $signal  The received POSIX signal.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @see     pcntl_signal()
	 * @throws  \RuntimeException
	 */
	public function signal($signal)
	{
		// Log all signals sent to the daemon.
		$this->getLogger()->debug('Received signal: ' . $signal);

		// Let's make sure we have an application instance.
		if (!is_subclass_of($this, __CLASS__))
		{
			$this->getLogger()->emergency('Cannot find the application instance.');

			throw new \RuntimeException('Cannot find the application instance.');
		}

		// @event onReceiveSignal

		switch ($signal)
		{
			case SIGINT:
			case SIGTERM:
				// Handle shutdown tasks
				if ($this->running && $this->isActive())
				{
					$this->shutdown();
				}
				else
				{
					$this->close();
				}

				break;

			case SIGHUP:
				// Handle restart tasks
				if ($this->running && $this->isActive())
				{
					$this->shutdown(true);
				}
				else
				{
					$this->close();
				}

				break;

			case SIGCHLD:
				// A child process has died
				while ($this->pcntlWait($signal, WNOHANG || WUNTRACED) > 0)
				{
					usleep(1000);
				}

				break;

			case SIGCLD:
				while ($this->pcntlWait($signal, WNOHANG) > 0)
				{
					$signal = $this->pcntlChildExitStatus($signal);
				}

				break;

			default:
				break;
		}
	}

	/**
	 * Check to see if the daemon is active.  This does not assume that $this daemon is active, but
	 * only if an instance of the application is active as a daemon.
	 *
	 * @return  boolean  True if daemon is active.
	 *
	 * @since   1.0
	 */
	public function isActive()
	{
		// Get the process id file location for the application.
		$pidFile = $this->get('application_pid_file');

		// If the process id file doesn't exist then the daemon is obviously not running.
		if (!is_file($pidFile))
		{
			return false;
		}

		// Read the contents of the process id file as an integer.
		$fp  = fopen($pidFile, 'r');
		$pid = fread($fp, filesize($pidFile));
		$pid = (int) $pid;
		fclose($fp);

		// Check to make sure that the process id exists as a positive integer.
		if (!$pid)
		{
			return false;
		}

		// Check to make sure the process is active by pinging it and ensure it responds.
		if (!posix_kill($pid, 0))
		{
			// No response so remove the process id file and log the situation.
			@ unlink($pidFile);

			$this->getLogger()->warning('The process found based on PID file was unresponsive.');

			return false;
		}

		return true;
	}

	/**
	 * Load an object or array into the application configuration object.
	 *
	 * @param   mixed  $data  Either an array or object to be loaded into the configuration object.
	 *
	 * @return  AbstractDaemonApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function loadConfiguration($data)
	{
		/*
		 * Setup some application metadata options.  This is useful if we ever want to write out startup scripts
		 * or just have some sort of information available to share about things.
		 */

		// The application author name.  This string is used in generating startup scripts and has
		// a maximum of 50 characters.
		$tmp = (string) $this->get('author_name', 'Joomla Framework');
		$this->set('author_name', (\strlen($tmp) > 50) ? substr($tmp, 0, 50) : $tmp);

		// The application author email.  This string is used in generating startup scripts.
		$tmp = (string) $this->get('author_email', 'admin@joomla.org');
		$this->set('author_email', filter_var($tmp, FILTER_VALIDATE_EMAIL));

		// The application name.  This string is used in generating startup scripts.
		$tmp = (string) $this->get('application_name', 'JApplicationDaemon');
		$this->set('application_name', (string) preg_replace('/[^A-Z0-9_-]/i', '', $tmp));

		// The application description.  This string is used in generating startup scripts.
		$tmp = (string) $this->get('application_description', 'A generic Joomla Framework application.');
		$this->set('application_description', filter_var($tmp, FILTER_SANITIZE_STRING));

		/*
		 * Setup the application path options.  This defines the default executable name, executable directory,
		 * and also the path to the daemon process id file.
		 */

		// The application executable daemon.  This string is used in generating startup scripts.
		$tmp = (string) $this->get('application_executable', basename($this->input->executable));
		$this->set('application_executable', $tmp);

		// The home directory of the daemon.
		$tmp = (string) $this->get('application_directory', \dirname($this->input->executable));
		$this->set('application_directory', $tmp);

		// The pid file location.  This defaults to a path inside the /tmp directory.
		$name = $this->get('application_name');
		$tmp  = (string) $this->get('application_pid_file', strtolower('/tmp/' . $name . '/' . $name . '.pid'));
		$this->set('application_pid_file', $tmp);

		/*
		 * Setup the application identity options.  It is important to remember if the default of 0 is set for
		 * either UID or GID then changing that setting will not be attempted as there is no real way to "change"
		 * the identity of a process from some user to root.
		 */

		// The user id under which to run the daemon.
		$tmp     = (int) $this->get('application_uid', 0);
		$options = array('options' => array('min_range' => 0, 'max_range' => 65000));
		$this->set('application_uid', filter_var($tmp, FILTER_VALIDATE_INT, $options));

		// The group id under which to run the daemon.
		$tmp     = (int) $this->get('application_gid', 0);
		$options = array('options' => array('min_range' => 0, 'max_range' => 65000));
		$this->set('application_gid', filter_var($tmp, FILTER_VALIDATE_INT, $options));

		// Option to kill the daemon if it cannot switch to the chosen identity.
		$tmp = (bool) $this->get('application_require_identity', 1);
		$this->set('application_require_identity', $tmp);

		/*
		 * Setup the application runtime options.  By default our execution time limit is infinite obviously
		 * because a daemon should be constantly running unless told otherwise.  The default limit for memory
		 * usage is 128M, which admittedly is a little high, but remember it is a "limit" and PHP's memory
		 * management leaves a bit to be desired :-)
		 */

		// The maximum execution time of the application in seconds.  Zero is infinite.
		$tmp = $this->get('max_execution_time');

		if ($tmp !== null)
		{
			$this->set('max_execution_time', (int) $tmp);
		}

		// The maximum amount of memory the application can use.
		$tmp = $this->get('max_memory_limit', '256M');

		if ($tmp !== null)
		{
			$this->set('max_memory_limit', (string) $tmp);
		}

		return $this;
	}

	/**
	 * Execute the daemon.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute()
	{
		// @event onBeforeExecute

		// Enable basic garbage collection.
		gc_enable();

		$this->getLogger()->info('Starting ' . $this->name);

		// Set off the process for becoming a daemon.
		if ($this->daemonize())
		{
			// Declare ticks to start signal monitoring. When you declare ticks, PCNTL will monitor
			// incoming signals after each tick and call the relevant signal handler automatically.
			declare(ticks = 1);

			// Start the main execution loop.
			while (true)
			{
				// Perform basic garbage collection.
				$this->gc();

				// Don't completely overload the CPU.
				usleep(1000);

				// Execute the main application logic.
				$this->doExecute();
			}
		}
		else
		{
			// We were not able to daemonize the application so log the failure and die gracefully.
			$this->getLogger()->info('Starting ' . $this->name . ' failed');
		}

		// @event onAfterExecute
	}

	/**
	 * Restart daemon process.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	public function restart()
	{
		$this->getLogger()->info('Stopping ' . $this->name);

		$this->shutdown(true);
	}

	/**
	 * Stop daemon process.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	public function stop()
	{
		$this->getLogger()->info('Stopping ' . $this->name);

		$this->shutdown();
	}

	/**
	 * Method to change the identity of the daemon process and resources.
	 *
	 * @return  boolean  True if identity successfully changed
	 *
	 * @since   1.0
	 * @see     posix_setuid()
	 */
	protected function changeIdentity()
	{
		// Get the group and user ids to set for the daemon.
		$uid = (int) $this->get('application_uid', 0);
		$gid = (int) $this->get('application_gid', 0);

		// Get the application process id file path.
		$file = $this->get('application_pid_file');

		// Change the user id for the process id file if necessary.
		if ($uid && (fileowner($file) != $uid) && (!@ chown($file, $uid)))
		{
			$this->getLogger()->error('Unable to change user ownership of the process id file.');

			return false;
		}

		// Change the group id for the process id file if necessary.
		if ($gid && (filegroup($file) != $gid) && (!@ chgrp($file, $gid)))
		{
			$this->getLogger()->error('Unable to change group ownership of the process id file.');

			return false;
		}

		// Set the correct home directory for the process.
		if ($uid && ($info = posix_getpwuid($uid)) && is_dir($info['dir']))
		{
			system('export HOME="' . $info['dir'] . '"');
		}

		// Change the user id for the process necessary.
		if ($uid && (posix_getuid($file) != $uid) && (!@ posix_setuid($uid)))
		{
			$this->getLogger()->error('Unable to change user ownership of the proccess.');

			return false;
		}

		// Change the group id for the process necessary.
		if ($gid && (posix_getgid($file) != $gid) && (!@ posix_setgid($gid)))
		{
			$this->getLogger()->error('Unable to change group ownership of the proccess.');

			return false;
		}

		// Get the user and group information based on uid and gid.
		$user  = posix_getpwuid($uid);
		$group = posix_getgrgid($gid);

		$this->getLogger()->info('Changed daemon identity to ' . $user['name'] . ':' . $group['name']);

		return true;
	}

	/**
	 * Method to put the application into the background.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function daemonize()
	{
		// Is there already an active daemon running?
		if ($this->isActive())
		{
			$this->getLogger()->emergency($this->name . ' daemon is still running. Exiting the application.');

			return false;
		}

		// Reset Process Information
		$this->safeMode  = !!@ ini_get('safe_mode');
		$this->processId = 0;
		$this->running   = false;

		// Detach process!
		try
		{
			// Check if we should run in the foreground.
			if (!$this->input->get('f'))
			{
				// Detach from the terminal.
				$this->detach();
			}
			else
			{
				// Setup running values.
				$this->exiting = false;
				$this->running = true;

				// Set the process id.
				$this->processId = (int) posix_getpid();
				$this->parentId  = $this->processId;
			}
		}
		catch (\RuntimeException $e)
		{
			$this->getLogger()->emergency('Unable to fork.');

			return false;
		}

		// Verify the process id is valid.
		if ($this->processId < 1)
		{
			$this->getLogger()->emergency('The process id is invalid; the fork failed.');

			return false;
		}

		// Clear the umask.
		@ umask(0);

		// Write out the process id file for concurrency management.
		if (!$this->writeProcessIdFile())
		{
			$this->getLogger()->emergency('Unable to write the pid file at: ' . $this->get('application_pid_file'));

			return false;
		}

		// Attempt to change the identity of user running the process.
		if (!$this->changeIdentity())
		{
			// If the identity change was required then we need to return false.
			if ($this->get('application_require_identity'))
			{
				$this->getLogger()->critical('Unable to change process owner.');

				return false;
			}

			$this->getLogger()->warning('Unable to change process owner.');
		}

		// Setup the signal handlers for the daemon.
		if (!$this->setupSignalHandlers())
		{
			return false;
		}

		// Change the current working directory to the application working directory.
		@ chdir($this->get('application_directory'));

		return true;
	}

	/**
	 * This is truly where the magic happens.  This is where we fork the process and kill the parent
	 * process, which is essentially what turns the application into a daemon.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function detach()
	{
		$this->getLogger()->debug('Detaching the ' . $this->name . ' daemon.');

		// Attempt to fork the process.
		$pid = $this->fork();

		// If the pid is positive then we successfully forked, and can close this application.
		if ($pid)
		{
			// Add the log entry for debugging purposes and exit gracefully.
			$this->getLogger()->debug('Ending ' . $this->name . ' parent process');

			$this->close();
		}
		else
		{
			// We are in the forked child process.
			// Setup some protected values.
			$this->exiting = false;
			$this->running = true;

			// Set the parent to self.
			$this->parentId = $this->processId;
		}
	}

	/**
	 * Method to fork the process.
	 *
	 * @return  integer  The child process id to the parent process, zero to the child process.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	protected function fork()
	{
		// Attempt to fork the process.
		$pid = $this->pcntlFork();

		// If the fork failed, throw an exception.
		if ($pid === -1)
		{
			throw new \RuntimeException('The process could not be forked.');
		}

		if ($pid === 0)
		{
			// Update the process id for the child.
			$this->processId = (int) posix_getpid();
		}
		else
		{
			// Log the fork in the parent.
			$this->getLogger()->debug('Process forked ' . $pid);
		}

		// Trigger the onFork event.
		$this->postFork();

		return $pid;
	}

	/**
	 * Method to perform basic garbage collection and memory management in the sense of clearing the
	 * stat cache.  We will probably call this method pretty regularly in our main loop.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	protected function gc()
	{
		// Perform generic garbage collection.
		gc_collect_cycles();

		// Clear the stat cache so it doesn't blow up memory.
		clearstatcache();
	}

	/**
	 * Method to attach the AbstractDaemonApplication signal handler to the known signals.  Applications
	 * can override these handlers by using the pcntl_signal() function and attaching a different
	 * callback method.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 * @see     pcntl_signal()
	 */
	protected function setupSignalHandlers()
	{
		// We add the error suppression for the loop because on some platforms some constants are not defined.
		foreach (self::$signals as $signal)
		{
			// Ignore signals that are not defined.
			if (!\defined($signal) || !\is_int(\constant($signal)) || (\constant($signal) === 0))
			{
				// Define the signal to avoid notices.
				$this->getLogger()->debug('Signal "' . $signal . '" not defined. Defining it as null.');

				\define($signal, null);

				// Don't listen for signal.
				continue;
			}

			// Attach the signal handler for the signal.
			if (!$this->pcntlSignal(\constant($signal), array($this, 'signal')))
			{
				$this->getLogger()->emergency(sprintf('Unable to reroute signal handler: %s', $signal));

				return false;
			}
		}

		return true;
	}

	/**
	 * Method to shut down the daemon and optionally restart it.
	 *
	 * @param   boolean  $restart  True to restart the daemon on exit.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function shutdown($restart = false)
	{
		// If we are already exiting, chill.
		if ($this->exiting)
		{
			return;
		}

		// If not, now we are.
		$this->exiting = true;

		// If we aren't already daemonized then just kill the application.
		if (!$this->running && !$this->isActive())
		{
			$this->getLogger()->info('Process was not daemonized yet, just halting current process');

			$this->close();
		}

		// Only read the pid for the parent file.
		if ($this->parentId == $this->processId)
		{
			// Read the contents of the process id file as an integer.
			$fp  = fopen($this->get('application_pid_file'), 'r');
			$pid = fread($fp, filesize($this->get('application_pid_file')));
			$pid = (int) $pid;
			fclose($fp);

			// Remove the process id file.
			@ unlink($this->get('application_pid_file'));

			// If we are supposed to restart the daemon we need to execute the same command.
			if ($restart)
			{
				$this->close(exec(implode(' ', $GLOBALS['argv']) . ' > /dev/null &'));
			}
			else
			{
				// If we are not supposed to restart the daemon let's just kill -9.
				passthru('kill -9 ' . $pid);
				$this->close();
			}
		}
	}

	/**
	 * Method to write the process id file out to disk.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	protected function writeProcessIdFile()
	{
		// Verify the process id is valid.
		if ($this->processId < 1)
		{
			$this->getLogger()->emergency('The process id is invalid.');

			return false;
		}

		// Get the application process id file path.
		$file = $this->get('application_pid_file');

		if (empty($file))
		{
			$this->getLogger()->error('The process id file path is empty.');

			return false;
		}

		// Make sure that the folder where we are writing the process id file exists.
		$folder = \dirname($file);

		if (!is_dir($folder) && !@ mkdir($folder, $this->get('folder_permission', 0755)))
		{
			$this->getLogger()->error('Unable to create directory: ' . $folder);

			return false;
		}

		// Write the process id file out to disk.
		if (!file_put_contents($file, $this->processId))
		{
			$this->getLogger()->error('Unable to write proccess id file: ' . $file);

			return false;
		}

		// Make sure the permissions for the proccess id file are accurate.
		if (!chmod($file, $this->get('file_permission', 0644)))
		{
			$this->getLogger()->error('Unable to adjust permissions for the proccess id file: ' . $file);

			return false;
		}

		return true;
	}

	/**
	 * Method to handle post-fork triggering of the onFork event.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function postFork()
	{
		// @event onFork
	}

	/**
	 * Method to return the exit code of a terminated child process.
	 *
	 * @param   integer  $status  The status parameter is the status parameter supplied to a successful call to pcntl_waitpid().
	 *
	 * @return  integer  The child process exit code.
	 *
	 * @codeCoverageIgnore
	 * @see     pcntl_wexitstatus()
	 * @since   1.0
	 */
	protected function pcntlChildExitStatus($status)
	{
		return pcntl_wexitstatus($status);
	}

	/**
	 * Method to return the exit code of a terminated child process.
	 *
	 * @return  integer  On success, the PID of the child process is returned in the parent's thread
	 *                   of execution, and a 0 is returned in the child's thread of execution. On
	 *                   failure, a -1 will be returned in the parent's context, no child process
	 *                   will be created, and a PHP error is raised.
	 *
	 * @codeCoverageIgnore
	 * @see     pcntl_fork()
	 * @since   1.0
	 */
	protected function pcntlFork()
	{
		return pcntl_fork();
	}

	/**
	 * Method to install a signal handler.
	 *
	 * @param   integer   $signal   The signal number.
	 * @param   callable  $handler  The signal handler which may be the name of a user created function,
	 *                              or method, or either of the two global constants SIG_IGN or SIG_DFL.
	 * @param   boolean   $restart  Specifies whether system call restarting should be used when this
	 *                              signal arrives.
	 *
	 * @return  boolean  True on success.
	 *
	 * @codeCoverageIgnore
	 * @see     pcntl_signal()
	 * @since   1.0
	 */
	protected function pcntlSignal($signal, $handler, $restart = true)
	{
		return pcntl_signal($signal, $handler, $restart);
	}

	/**
	 * Method to wait on or return the status of a forked child.
	 *
	 * @param   integer  $status   Status information.
	 * @param   integer  $options  If wait3 is available on your system (mostly BSD-style systems),
	 *                             you can provide the optional options parameter.
	 *
	 * @return  integer  The process ID of the child which exited, -1 on error or zero if WNOHANG
	 *                   was provided as an option (on wait3-available systems) and no child was available.
	 *
	 * @codeCoverageIgnore
	 * @see     pcntl_wait()
	 * @since   1.0
	 */
	protected function pcntlWait(&$status, $options = 0)
	{
		return pcntl_wait($status, $options);
	}
}
vendor/joomla/application/src/Cli/ColorStyle.php000064400000010452152177723700015750 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli;

/**
 * Class ColorStyle
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
final class ColorStyle
{
	/**
	 * Known colors
	 *
	 * @var    array
	 * @since  1.0
	 */
	private static $knownColors = array(
		'black'   => 0,
		'red'     => 1,
		'green'   => 2,
		'yellow'  => 3,
		'blue'    => 4,
		'magenta' => 5,
		'cyan'    => 6,
		'white'   => 7,
	);

	/**
	 * Known styles
	 *
	 * @var    array
	 * @since  1.0
	 */
	private static $knownOptions = array(
		'bold'       => 1,
		'underscore' => 4,
		'blink'      => 5,
		'reverse'    => 7,
	);

	/**
	 * Foreground base value
	 *
	 * @var    integer
	 * @since  1.0
	 */
	private static $fgBase = 30;

	/**
	 * Background base value
	 *
	 * @var    integer
	 * @since  1.0
	 */
	private static $bgBase = 40;

	/**
	 * Foreground color
	 *
	 * @var    integer
	 * @since  1.0
	 */
	private $fgColor = 0;

	/**
	 * Background color
	 *
	 * @var    integer
	 * @since  1.0
	 */
	private $bgColor = 0;

	/**
	 * Array of style options
	 *
	 * @var    array
	 * @since  1.0
	 */
	private $options = array();

	/**
	 * Constructor
	 *
	 * @param   string  $fg       Foreground color.
	 * @param   string  $bg       Background color.
	 * @param   array   $options  Style options.
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function __construct($fg = '', $bg = '', $options = array())
	{
		if ($fg)
		{
			if (array_key_exists($fg, static::$knownColors) == false)
			{
				throw new \InvalidArgumentException(
					sprintf('Invalid foreground color "%1$s" [%2$s]',
						$fg,
						implode(', ', $this->getKnownColors())
					)
				);
			}

			$this->fgColor = static::$fgBase + static::$knownColors[$fg];
		}

		if ($bg)
		{
			if (array_key_exists($bg, static::$knownColors) == false)
			{
				throw new \InvalidArgumentException(
					sprintf('Invalid background color "%1$s" [%2$s]',
						$bg,
						implode(', ', $this->getKnownColors())
					)
				);
			}

			$this->bgColor = static::$bgBase + static::$knownColors[$bg];
		}

		foreach ($options as $option)
		{
			if (array_key_exists($option, static::$knownOptions) == false)
			{
				throw new \InvalidArgumentException(
					sprintf('Invalid option "%1$s" [%2$s]',
						$option,
						implode(', ', $this->getKnownOptions())
					)
				);
			}

			$this->options[] = $option;
		}
	}

	/**
	 * Convert to a string.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function __toString()
	{
		return $this->getStyle();
	}

	/**
	 * Create a color style from a parameter string.
	 *
	 * Example: fg=red;bg=blue;options=bold,blink
	 *
	 * @param   string  $string  The parameter string.
	 *
	 * @return  ColorStyle  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public static function fromString($string)
	{
		$fg      = '';
		$bg      = '';
		$options = array();

		$parts = explode(';', $string);

		foreach ($parts as $part)
		{
			$subParts = explode('=', $part);

			if (\count($subParts) < 2)
			{
				continue;
			}

			switch ($subParts[0])
			{
				case 'fg':
					$fg = $subParts[1];

					break;

				case 'bg':
					$bg = $subParts[1];

					break;

				case 'options':
					$options = explode(',', $subParts[1]);

					break;

				default:
					throw new \RuntimeException('Invalid option');

					break;
			}
		}

		return new self($fg, $bg, $options);
	}

	/**
	 * Get the translated color code.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function getStyle()
	{
		$values = array();

		if ($this->fgColor)
		{
			$values[] = $this->fgColor;
		}

		if ($this->bgColor)
		{
			$values[] = $this->bgColor;
		}

		foreach ($this->options as $option)
		{
			$values[] = static::$knownOptions[$option];
		}

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

	/**
	 * Get the known colors.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function getKnownColors()
	{
		return array_keys(static::$knownColors);
	}

	/**
	 * Get the known options.
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public function getKnownOptions()
	{
		return array_keys(static::$knownOptions);
	}
}
vendor/joomla/application/src/Cli/CliInput.php000064400000001164152177723700015400 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli;

/**
 * Class CliInput
 *
 * @since       1.6.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
class CliInput
{
	/**
	 * Get a value from standard input.
	 *
	 * @return  string  The input string from standard input.
	 *
	 * @codeCoverageIgnore
	 * @since   1.6.0
	 */
	public function in()
	{
		return rtrim(fread(STDIN, 8192), "\n\r");
	}
}
vendor/joomla/application/src/Cli/ColorProcessor.php000064400000001011152177723700016616 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli;

use \Joomla\Application\Cli\Output\Processor\ColorProcessor as RealColorProcessor;

/**
 * Class ColorProcessor.
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
class ColorProcessor extends RealColorProcessor
{
}
vendor/joomla/application/src/Cli/Output/Processor/ColorProcessor.php000064400000007546152177723700022120 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli\Output\Processor;

use Joomla\Application\Cli\ColorStyle;
use Joomla\Application\Cli\Output\Stdout;

/**
 * Class ColorProcessor.
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
class ColorProcessor implements ProcessorInterface
{
	/**
	 * Flag to remove color codes from the output
	 *
	 * @var    boolean
	 * @since  1.0
	 */
	public $noColors = false;

	/**
	 * Regex to match tags
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected $tagFilter = '/<([a-z=;]+)>(.*?)<\/\\1>/s';

	/**
	 * Regex used for removing color codes
	 *
	 * @var    string
	 * @since  1.0
	 */
	protected static $stripFilter = '/<[\/]?[a-z=;]+>/';

	/**
	 * Array of ColorStyle objects
	 *
	 * @var    array
	 * @since  1.0
	 */
	protected $styles = array();

	/**
	 * Class constructor
	 *
	 * @param   boolean  $noColors  Defines non-colored mode on construct
	 *
	 * @since  1.1.0
	 */
	public function __construct($noColors = null)
	{
		if ($noColors === null)
		{
			/*
			 * By default windows cmd.exe and PowerShell does not support ANSI-colored output
			 * if the variable is not set explicitly colors should be disabled on Windows
			 */
			$noColors = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
		}

		$this->noColors = $noColors;

		$this->addPredefinedStyles();
	}

	/**
	 * Add a style.
	 *
	 * @param   string      $name   The style name.
	 * @param   ColorStyle  $style  The color style.
	 *
	 * @return  ColorProcessor  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function addStyle($name, ColorStyle $style)
	{
		$this->styles[$name] = $style;

		return $this;
	}

	/**
	 * Strip color tags from a string.
	 *
	 * @param   string  $string  The string.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public static function stripColors($string)
	{
		return preg_replace(static::$stripFilter, '', $string);
	}

	/**
	 * Process a string.
	 *
	 * @param   string  $string  The string to process.
	 *
	 * @return  string
	 *
	 * @since   1.0
	 */
	public function process($string)
	{
		preg_match_all($this->tagFilter, $string, $matches);

		if (!$matches)
		{
			return $string;
		}

		foreach ($matches[0] as $i => $m)
		{
			if (array_key_exists($matches[1][$i], $this->styles))
			{
				$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], $this->styles[$matches[1][$i]]);
			}
			// Custom format
			elseif (strpos($matches[1][$i], '='))
			{
				$string = $this->replaceColors($string, $matches[1][$i], $matches[2][$i], ColorStyle::fromString($matches[1][$i]));
			}
		}

		return $string;
	}

	/**
	 * Replace color tags in a string.
	 *
	 * @param   string      $text   The original text.
	 * @param   string      $tag    The matched tag.
	 * @param   string      $match  The match.
	 * @param   ColorStyle  $style  The color style to apply.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	private function replaceColors($text, $tag, $match, Colorstyle $style)
	{
		$replace = $this->noColors
			? $match
			: "\033[" . $style . 'm' . $match . "\033[0m";

		return str_replace('<' . $tag . '>' . $match . '</' . $tag . '>', $replace, $text);
	}

	/**
	 * Adds predefined color styles to the ColorProcessor object
	 *
	 * @return  Stdout  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	private function addPredefinedStyles()
	{
		$this->addStyle(
			'info',
			new ColorStyle('green', '', array('bold'))
		);

		$this->addStyle(
			'comment',
			new ColorStyle('yellow', '', array('bold'))
		);

		$this->addStyle(
			'question',
			new ColorStyle('black', 'cyan')
		);

		$this->addStyle(
			'error',
			new ColorStyle('white', 'red')
		);

		return $this;
	}
}
vendor/joomla/application/src/Cli/Output/Processor/ProcessorInterface.php000064400000001175152177723700022732 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli\Output\Processor;

/**
 * Class ProcessorInterface.
 *
 * @since       1.1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
interface ProcessorInterface
{
	/**
	 * Process the provided output into a string.
	 *
	 * @param   string  $output  The string to process.
	 *
	 * @return  string
	 *
	 * @since   1.1.0
	 */
	public function process($output);
}
vendor/joomla/application/src/Cli/Output/Stdout.php000064400000001620152177723700016430 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli\Output;

use Joomla\Application\Cli\CliOutput;

/**
 * Class Stdout.
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
class Stdout extends CliOutput
{
	/**
	 * Write a string to standard output
	 *
	 * @param   string   $text  The text to display.
	 * @param   boolean  $nl    True (default) to append a new line at the end of the output string.
	 *
	 * @return  Stdout  Instance of $this to allow chaining.
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	public function out($text = '', $nl = true)
	{
		fwrite(STDOUT, $this->getProcessor()->process($text) . ($nl ? "\n" : null));

		return $this;
	}
}
vendor/joomla/application/src/Cli/Output/Xml.php000064400000001521152177723700015706 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli\Output;

use Joomla\Application\Cli\CliOutput;

/**
 * Class Xml.
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
class Xml extends CliOutput
{
	/**
	 * Write a string to standard output.
	 *
	 * @param   string   $text  The text to display.
	 * @param   boolean  $nl    True (default) to append a new line at the end of the output string.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 * @codeCoverageIgnore
	 */
	public function out($text = '', $nl = true)
	{
		fwrite(STDOUT, $text . ($nl ? "\n" : null));
	}
}
vendor/joomla/application/src/Cli/CliOutput.php000064400000003437152177723700015606 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Cli;

use Joomla\Application\Cli\Output\Processor\ProcessorInterface;

/**
 * Class CliOutput
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
abstract class CliOutput
{
	/**
	 * Color processing object
	 *
	 * @var    ProcessorInterface
	 * @since  1.0
	 */
	protected $processor;

	/**
	 * Constructor
	 *
	 * @param   ProcessorInterface  $processor  The output processor.
	 *
	 * @since   1.1.2
	 */
	public function __construct(ProcessorInterface $processor = null)
	{
		$this->setProcessor(($processor instanceof ProcessorInterface) ? $processor : new Output\Processor\ColorProcessor);
	}

	/**
	 * Set a processor
	 *
	 * @param   ProcessorInterface  $processor  The output processor.
	 *
	 * @return  Stdout  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function setProcessor(ProcessorInterface $processor)
	{
		$this->processor = $processor;

		return $this;
	}

	/**
	 * Get a processor
	 *
	 * @return  ProcessorInterface
	 *
	 * @since   1.0
	 * @throws  \RuntimeException
	 */
	public function getProcessor()
	{
		if ($this->processor)
		{
			return $this->processor;
		}

		throw new \RuntimeException('A ProcessorInterface object has not been set.');
	}

	/**
	 * Write a string to an output handler.
	 *
	 * @param   string   $text  The text to display.
	 * @param   boolean  $nl    True (default) to append a new line at the end of the output string.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @codeCoverageIgnore
	 */
	abstract public function out($text = '', $nl = true);
}
vendor/joomla/application/src/Web/WebClient.php000064400000041072152177723700015535 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application\Web;

/**
 * Class to model a Web Client.
 *
 * @property-read  integer  $platform        The detected platform on which the web client runs.
 * @property-read  boolean  $mobile          True if the web client is a mobile device.
 * @property-read  integer  $engine          The detected rendering engine used by the web client.
 * @property-read  integer  $browser         The detected browser used by the web client.
 * @property-read  string   $browserVersion  The detected browser version used by the web client.
 * @property-read  array    $languages       The priority order detected accepted languages for the client.
 * @property-read  array    $encodings       The priority order detected accepted encodings for the client.
 * @property-read  string   $userAgent       The web client's user agent string.
 * @property-read  string   $acceptEncoding  The web client's accepted encoding string.
 * @property-read  string   $acceptLanguage  The web client's accepted languages string.
 * @property-read  array    $detection       An array of flags determining whether or not a detection routine has been run.
 * @property-read  boolean  $robot           True if the web client is a robot
 * @property-read  array    $headers         An array of all headers sent by client
 *
 * @since  1.0
 */
class WebClient
{
	const WINDOWS       = 1;
	const WINDOWS_PHONE = 2;
	const WINDOWS_CE    = 3;
	const IPHONE        = 4;
	const IPAD          = 5;
	const IPOD          = 6;
	const MAC           = 7;
	const BLACKBERRY    = 8;
	const ANDROID       = 9;
	const LINUX         = 10;
	const TRIDENT       = 11;
	const WEBKIT        = 12;
	const GECKO         = 13;
	const PRESTO        = 14;
	const KHTML         = 15;
	const AMAYA         = 16;
	const IE            = 17;
	const FIREFOX       = 18;
	const CHROME        = 19;
	const SAFARI        = 20;
	const OPERA         = 21;
	const ANDROIDTABLET = 22;
	const EDGE          = 23;
	const BLINK         = 24;
	const EDG           = 25;

	/**
	 * @var    integer  The detected platform on which the web client runs.
	 * @since  1.0
	 */
	protected $platform;

	/**
	 * @var    boolean  True if the web client is a mobile device.
	 * @since  1.0
	 */
	protected $mobile = false;

	/**
	 * @var    integer  The detected rendering engine used by the web client.
	 * @since  1.0
	 */
	protected $engine;

	/**
	 * @var    integer  The detected browser used by the web client.
	 * @since  1.0
	 */
	protected $browser;

	/**
	 * @var    string  The detected browser version used by the web client.
	 * @since  1.0
	 */
	protected $browserVersion;

	/**
	 * @var    array  The priority order detected accepted languages for the client.
	 * @since  1.0
	 */
	protected $languages = array();

	/**
	 * @var    array  The priority order detected accepted encodings for the client.
	 * @since  1.0
	 */
	protected $encodings = array();

	/**
	 * @var    string  The web client's user agent string.
	 * @since  1.0
	 */
	protected $userAgent;

	/**
	 * @var    string  The web client's accepted encoding string.
	 * @since  1.0
	 */
	protected $acceptEncoding;

	/**
	 * @var    string  The web client's accepted languages string.
	 * @since  1.0
	 */
	protected $acceptLanguage;

	/**
	 * @var    boolean  True if the web client is a robot.
	 * @since  1.0
	 */
	protected $robot = false;

	/**
	 * @var    array  An array of flags determining whether or not a detection routine has been run.
	 * @since  1.0
	 */
	protected $detection = array();

	/**
	 * @var    array  An array of headers sent by client
	 * @since  1.3.0
	 */
	protected $headers;

	/**
	 * Class constructor.
	 *
	 * @param   string  $userAgent       The optional user-agent string to parse.
	 * @param   string  $acceptEncoding  The optional client accept encoding string to parse.
	 * @param   string  $acceptLanguage  The optional client accept language string to parse.
	 *
	 * @since   1.0
	 */
	public function __construct($userAgent = null, $acceptEncoding = null, $acceptLanguage = null)
	{
		// If no explicit user agent string was given attempt to use the implicit one from server environment.
		if (empty($userAgent) && isset($_SERVER['HTTP_USER_AGENT']))
		{
			$this->userAgent = $_SERVER['HTTP_USER_AGENT'];
		}
		else
		{
			$this->userAgent = $userAgent;
		}

		// If no explicit acceptable encoding string was given attempt to use the implicit one from server environment.
		if (empty($acceptEncoding) && isset($_SERVER['HTTP_ACCEPT_ENCODING']))
		{
			$this->acceptEncoding = $_SERVER['HTTP_ACCEPT_ENCODING'];
		}
		else
		{
			$this->acceptEncoding = $acceptEncoding;
		}

		// If no explicit acceptable languages string was given attempt to use the implicit one from server environment.
		if (empty($acceptLanguage) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE']))
		{
			$this->acceptLanguage = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
		}
		else
		{
			$this->acceptLanguage = $acceptLanguage;
		}
	}

	/**
	 * Magic method to get an object property's value by name.
	 *
	 * @param   string  $name  Name of the property for which to return a value.
	 *
	 * @return  mixed  The requested value if it exists.
	 *
	 * @since   1.0
	 */
	public function __get($name)
	{
		switch ($name)
		{
			case 'mobile':
			case 'platform':
				if (empty($this->detection['platform']))
				{
					$this->detectPlatform($this->userAgent);
				}

				break;

			case 'engine':
				if (empty($this->detection['engine']))
				{
					$this->detectEngine($this->userAgent);
				}

				break;

			case 'browser':
			case 'browserVersion':
				if (empty($this->detection['browser']))
				{
					$this->detectBrowser($this->userAgent);
				}

				break;

			case 'languages':
				if (empty($this->detection['acceptLanguage']))
				{
					$this->detectLanguage($this->acceptLanguage);
				}

				break;

			case 'encodings':
				if (empty($this->detection['acceptEncoding']))
				{
					$this->detectEncoding($this->acceptEncoding);
				}

				break;

			case 'robot':
				if (empty($this->detection['robot']))
				{
					$this->detectRobot($this->userAgent);
				}

				break;

			case 'headers':
				if (empty($this->detection['headers']))
				{
					$this->detectHeaders();
				}

				break;
		}

		// Return the property if it exists.
		if (isset($this->$name))
		{
			return $this->$name;
		}
	}

	/**
	 * Detects the client browser and version in a user agent string.
	 *
	 * @param   string  $userAgent  The user-agent string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectBrowser($userAgent)
	{
		// Attempt to detect the browser type.  Obviously we are only worried about major browsers.
		if ((stripos($userAgent, 'MSIE') !== false) && (stripos($userAgent, 'Opera') === false))
		{
			$this->browser  = self::IE;
			$patternBrowser = 'MSIE';
		}
		elseif (stripos($userAgent, 'Trident') !== false)
		{
			$this->browser  = self::IE;
			$patternBrowser = ' rv';
		}
		elseif (stripos($userAgent, 'Edge') !== false)
		{
			$this->browser  = self::EDGE;
			$patternBrowser = 'Edge';
		}
		elseif (stripos($userAgent, 'Edg') !== false)
		{
			$this->browser  = self::EDG;
			$patternBrowser = 'Edg';
		}
		elseif ((stripos($userAgent, 'Firefox') !== false) && (stripos($userAgent, 'like Firefox') === false))
		{
			$this->browser  = self::FIREFOX;
			$patternBrowser = 'Firefox';
		}
		elseif (stripos($userAgent, 'OPR') !== false)
		{
			$this->browser  = self::OPERA;
			$patternBrowser = 'OPR';
		}
		elseif (stripos($userAgent, 'Chrome') !== false)
		{
			$this->browser  = self::CHROME;
			$patternBrowser = 'Chrome';
		}
		elseif (stripos($userAgent, 'Safari') !== false)
		{
			$this->browser  = self::SAFARI;
			$patternBrowser = 'Safari';
		}
		elseif (stripos($userAgent, 'Opera') !== false)
		{
			$this->browser  = self::OPERA;
			$patternBrowser = 'Opera';
		}

		// If we detected a known browser let's attempt to determine the version.
		if ($this->browser)
		{
			// Build the REGEX pattern to match the browser version string within the user agent string.
			$pattern = '#(?<browser>Version|' . $patternBrowser . ')[/ :]+(?<version>[0-9.|a-zA-Z.]*)#';

			// Attempt to find version strings in the user agent string.
			$matches = array();

			if (preg_match_all($pattern, $userAgent, $matches))
			{
				// Do we have both a Version and browser match?
				if (\count($matches['browser']) == 2)
				{
					// See whether Version or browser came first, and use the number accordingly.
					if (strripos($userAgent, 'Version') < strripos($userAgent, $patternBrowser))
					{
						$this->browserVersion = $matches['version'][0];
					}
					else
					{
						$this->browserVersion = $matches['version'][1];
					}
				}
				elseif (\count($matches['browser']) > 2)
				{
					$key = array_search('Version', $matches['browser']);

					if ($key)
					{
						$this->browserVersion = $matches['version'][$key];
					}
				}
				else
				{
					// We only have a Version or a browser so use what we have.
					$this->browserVersion = $matches['version'][0];
				}
			}
		}

		// Mark this detection routine as run.
		$this->detection['browser'] = true;
	}

	/**
	 * Method to detect the accepted response encoding by the client.
	 *
	 * @param   string  $acceptEncoding  The client accept encoding string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectEncoding($acceptEncoding)
	{
		// Parse the accepted encodings.
		$this->encodings = array_map('trim', (array) explode(',', $acceptEncoding));

		// Mark this detection routine as run.
		$this->detection['acceptEncoding'] = true;
	}

	/**
	 * Detects the client rendering engine in a user agent string.
	 *
	 * @param   string  $userAgent  The user-agent string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectEngine($userAgent)
	{
		if (stripos($userAgent, 'MSIE') !== false || stripos($userAgent, 'Trident') !== false)
		{
			// Attempt to detect the client engine -- starting with the most popular ... for now.
			$this->engine = self::TRIDENT;
		}
		elseif (stripos($userAgent, 'Edge') !== false || stripos($userAgent, 'EdgeHTML') !== false)
		{
			$this->engine = self::EDGE;
		}
		elseif (stripos($userAgent, 'Edg') !== false)
		{
			$this->engine = self::BLINK;
		}
		elseif (stripos($userAgent, 'Chrome') !== false)
		{
			$result  = explode('/', stristr($userAgent, 'Chrome'));
			$version = explode(' ', $result[1]);

			if ($version[0] >= 28)
			{
				$this->engine = self::BLINK;
			}
			else
			{
				$this->engine = self::WEBKIT;
			}
		}
		elseif (stripos($userAgent, 'AppleWebKit') !== false || stripos($userAgent, 'blackberry') !== false)
		{
			if (stripos($userAgent, 'AppleWebKit') !== false)
			{
				$result  = explode('/', stristr($userAgent, 'AppleWebKit'));
				$version = explode(' ', $result[1]);

				if ($version[0] === 537.36)
				{
					// AppleWebKit/537.36 is Blink engine specific, exception is Blink emulated IEMobile, Trident or Edge
					$this->engine = self::BLINK;
				}
			}

			// Evidently blackberry uses WebKit and doesn't necessarily report it.  Bad RIM.
			$this->engine = self::WEBKIT;
		}
		elseif (stripos($userAgent, 'Gecko') !== false && stripos($userAgent, 'like Gecko') === false)
		{
			// We have to check for like Gecko because some other browsers spoof Gecko.
			$this->engine = self::GECKO;
		}
		elseif (stripos($userAgent, 'Opera') !== false || stripos($userAgent, 'Presto') !== false)
		{
			$version = false;

			if (preg_match('/Opera[\/| ]?([0-9.]+)/u', $userAgent, $match))
			{
				$version = \floatval($match[1]);
			}

			if (preg_match('/Version\/([0-9.]+)/u', $userAgent, $match))
			{
				if (\floatval($match[1]) >= 10)
				{
					$version = \floatval($match[1]);
				}
			}

			if ($version !== false && $version >= 15)
			{
				$this->engine = self::BLINK;
			}
			else
			{
				$this->engine = self::PRESTO;
			}
		}
		elseif (stripos($userAgent, 'KHTML') !== false)
		{
			// *sigh*
			$this->engine = self::KHTML;
		}
		elseif (stripos($userAgent, 'Amaya') !== false)
		{
			// Lesser known engine but it finishes off the major list from Wikipedia :-)
			$this->engine = self::AMAYA;
		}

		// Mark this detection routine as run.
		$this->detection['engine'] = true;
	}

	/**
	 * Method to detect the accepted languages by the client.
	 *
	 * @param   mixed  $acceptLanguage  The client accept language string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectLanguage($acceptLanguage)
	{
		// Parse the accepted encodings.
		$this->languages = array_map('trim', (array) explode(',', $acceptLanguage));

		// Mark this detection routine as run.
		$this->detection['acceptLanguage'] = true;
	}

	/**
	 * Detects the client platform in a user agent string.
	 *
	 * @param   string  $userAgent  The user-agent string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectPlatform($userAgent)
	{
		// Attempt to detect the client platform.
		if (stripos($userAgent, 'Windows') !== false)
		{
			$this->platform = self::WINDOWS;

			// Let's look at the specific mobile options in the Windows space.
			if (stripos($userAgent, 'Windows Phone') !== false)
			{
				$this->mobile   = true;
				$this->platform = self::WINDOWS_PHONE;
			}
			elseif (stripos($userAgent, 'Windows CE') !== false)
			{
				$this->mobile   = true;
				$this->platform = self::WINDOWS_CE;
			}
		}
		elseif (stripos($userAgent, 'iPhone') !== false)
		{
			// Interestingly 'iPhone' is present in all iOS devices so far including iPad and iPods.
			$this->mobile   = true;
			$this->platform = self::IPHONE;

			// Let's look at the specific mobile options in the iOS space.
			if (stripos($userAgent, 'iPad') !== false)
			{
				$this->platform = self::IPAD;
			}
			elseif (stripos($userAgent, 'iPod') !== false)
			{
				$this->platform = self::IPOD;
			}
		}
		elseif (stripos($userAgent, 'iPad') !== false)
		{
			// In case where iPhone is not mentioed in iPad user agent string
			$this->mobile   = true;
			$this->platform = self::IPAD;
		}
		elseif (stripos($userAgent, 'iPod') !== false)
		{
			// In case where iPhone is not mentioed in iPod user agent string
			$this->mobile   = true;
			$this->platform = self::IPOD;
		}
		elseif (preg_match('/macintosh|mac os x/i', $userAgent))
		{
			// This has to come after the iPhone check because mac strings are also present in iOS devices.
			$this->platform = self::MAC;
		}
		elseif (stripos($userAgent, 'Blackberry') !== false)
		{
			$this->mobile   = true;
			$this->platform = self::BLACKBERRY;
		}
		elseif (stripos($userAgent, 'Android') !== false)
		{
			$this->mobile   = true;
			$this->platform = self::ANDROID;
			/**
			 * Attempt to distinguish between Android phones and tablets
			 * There is no totally foolproof method but certain rules almost always hold
			 *   Android 3.x is only used for tablets
			 *   Some devices and browsers encourage users to change their UA string to include Tablet.
			 *   Google encourages manufacturers to exclude the string Mobile from tablet device UA strings.
			 *   In some modes Kindle Android devices include the string Mobile but they include the string Silk.
			 */
			if (stripos($userAgent, 'Android 3') !== false || stripos($userAgent, 'Tablet') !== false
				|| stripos($userAgent, 'Mobile') === false || stripos($userAgent, 'Silk') !== false)
			{
				$this->platform = self::ANDROIDTABLET;
			}
		}
		elseif (stripos($userAgent, 'Linux') !== false)
		{
			$this->platform = self::LINUX;
		}

		// Mark this detection routine as run.
		$this->detection['platform'] = true;
	}

	/**
	 * Determines if the browser is a robot or not.
	 *
	 * @param   string  $userAgent  The user-agent string to parse.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function detectRobot($userAgent)
	{
		if (preg_match('/http|bot|bingbot|googlebot|robot|spider|slurp|crawler|curl|^$/i', $userAgent))
		{
			$this->robot = true;
		}
		else
		{
			$this->robot = false;
		}

		$this->detection['robot'] = true;
	}

	/**
	 * Fills internal array of headers
	 *
	 * @return  void
	 *
	 * @since   1.3.0
	 */
	protected function detectHeaders()
	{
		if (\function_exists('getallheaders'))
		{
			// If php is working under Apache, there is a special function
			$this->headers = getallheaders();
		}
		else
		{
			// Else we fill headers from $_SERVER variable
			$this->headers = array();

			foreach ($_SERVER as $name => $value)
			{
				if (substr($name, 0, 5) == 'HTTP_')
				{
					$this->headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
				}
			}
		}

		// Mark this detection routine as run.
		$this->detection['headers'] = true;
	}
}
vendor/joomla/application/src/AbstractCliApplication.php000064400000007531152177723700017525 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application;

use Joomla\Input;
use Joomla\Registry\Registry;

/**
 * Base class for a Joomla! command line application.
 *
 * @since       1.0
 * @deprecated  2.0  Use the `joomla/console` package instead
 */
abstract class AbstractCliApplication extends AbstractApplication
{
	/**
	 * Output object
	 *
	 * @var    Cli\CliOutput
	 * @since  1.0
	 */
	protected $output;

	/**
	 * CLI Input object
	 *
	 * @var    Cli\CliInput
	 * @since  1.6.0
	 */
	protected $cliInput;

	/**
	 * Class constructor.
	 *
	 * @param   Input\Cli      $input     An optional argument to provide dependency injection for the application's input object.  If the
	 *                                    argument is an Input\Cli object that object will become the application's input object, otherwise
	 *                                    a default input object is created.
	 * @param   Registry       $config    An optional argument to provide dependency injection for the application's config object.  If the
	 *                                    argument is a Registry object that object will become the application's config object, otherwise
	 *                                    a default config object is created.
	 * @param   Cli\CliOutput  $output    An optional argument to provide dependency injection for the application's output object.  If the
	 *                                    argument is a Cli\CliOutput object that object will become the application's input object, otherwise
	 *                                    a default output object is created.
	 * @param   Cli\CliInput   $cliInput  An optional argument to provide dependency injection for the application's CLI input object.  If the
	 *                                    argument is a Cli\CliInput object that object will become the application's input object, otherwise
	 *                                    a default input object is created.
	 *
	 * @since   1.0
	 */
	public function __construct(Input\Cli $input = null, Registry $config = null, Cli\CliOutput $output = null, Cli\CliInput $cliInput = null)
	{
		// Close the application if we are not executed from the command line.
		// @codeCoverageIgnoreStart
		if (!\defined('STDOUT') || !\defined('STDIN') || !isset($_SERVER['argv']))
		{
			$this->close();
		}

		// @codeCoverageIgnoreEnd

		$this->output = ($output instanceof Cli\CliOutput) ? $output : new Cli\Output\Stdout;

		// Set the CLI input object.
		$this->cliInput = ($cliInput instanceof Cli\CliInput) ? $cliInput : new Cli\CliInput;

		// Call the constructor as late as possible (it runs `initialise`).
		parent::__construct($input instanceof Input\Input ? $input : new Input\Cli, $config);

		// Set the current directory.
		$this->set('cwd', getcwd());
	}

	/**
	 * Get an output object.
	 *
	 * @return  Cli\CliOutput
	 *
	 * @since   1.0
	 */
	public function getOutput()
	{
		return $this->output;
	}

	/**
	 * Get a CLI input object.
	 *
	 * @return  Cli\CliInput
	 *
	 * @since   1.6.0
	 */
	public function getCliInput()
	{
		return $this->cliInput;
	}

	/**
	 * Write a string to standard output.
	 *
	 * @param   string   $text  The text to display.
	 * @param   boolean  $nl    True (default) to append a new line at the end of the output string.
	 *
	 * @return  AbstractCliApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function out($text = '', $nl = true)
	{
		$this->getOutput()->out($text, $nl);

		return $this;
	}

	/**
	 * Get a value from standard input.
	 *
	 * @return  string  The input string from standard input.
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	public function in()
	{
		return $this->getCliInput()->in();
	}
}
vendor/joomla/application/src/AbstractApplication.php000064400000011254152177723700017072 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application;

use Joomla\Input\Input;
use Joomla\Registry\Registry;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
 * Joomla Framework Base Application Class
 *
 * @since  1.0
 */
abstract class AbstractApplication implements LoggerAwareInterface
{
	/**
	 * The application configuration object.
	 *
	 * @var    Registry
	 * @since  1.0
	 */
	protected $config;

	/**
	 * The application input object.
	 *
	 * @var    Input
	 * @since  1.0
	 */
	public $input;

	/**
	 * A logger.
	 *
	 * @var    LoggerInterface
	 * @since  1.0
	 */
	private $logger;

	/**
	 * Class constructor.
	 *
	 * @param   Input     $input   An optional argument to provide dependency injection for the application's input object.  If the argument is an
	 *                             Input object that object will become the application's input object, otherwise a default input object is created.
	 * @param   Registry  $config  An optional argument to provide dependency injection for the application's config object.  If the argument
	 *                             is a Registry object that object will become the application's config object, otherwise a default config
	 *                             object is created.
	 *
	 * @since   1.0
	 */
	public function __construct(Input $input = null, Registry $config = null)
	{
		$this->input  = $input instanceof Input ? $input : new Input;
		$this->config = $config instanceof Registry ? $config : new Registry;

		// Set the execution datetime and timestamp;
		$this->set('execution.datetime', gmdate('Y-m-d H:i:s'));
		$this->set('execution.timestamp', time());
		$this->set('execution.microtimestamp', microtime(true));

		$this->initialise();
	}

	/**
	 * Method to close the application.
	 *
	 * @param   integer  $code  The exit code (optional; default is 0).
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	public function close($code = 0)
	{
		exit($code);
	}

	/**
	 * Method to run the application routines.  Most likely you will want to instantiate a controller
	 * and execute it, or perform some sort of task directly.
	 *
	 * @return  mixed
	 *
	 * @since   1.0
	 */
	abstract protected function doExecute();

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute()
	{
		// @event onBeforeExecute

		// Perform application routines.
		$this->doExecute();

		// @event onAfterExecute
	}

	/**
	 * Returns a property of the object or the default value if the property is not set.
	 *
	 * @param   string  $key      The name of the property.
	 * @param   mixed   $default  The default value (optional) if none is set.
	 *
	 * @return  mixed   The value of the configuration.
	 *
	 * @since   1.0
	 */
	public function get($key, $default = null)
	{
		return $this->config->get($key, $default);
	}

	/**
	 * Get the logger.
	 *
	 * @return  LoggerInterface
	 *
	 * @since   1.0
	 */
	public function getLogger()
	{
		// If a logger hasn't been set, use NullLogger
		if (! ($this->logger instanceof LoggerInterface))
		{
			$this->logger = new NullLogger;
		}

		return $this->logger;
	}

	/**
	 * Custom initialisation method.
	 *
	 * Called at the end of the AbstractApplication::__construct method.
	 * This is for developers to inject initialisation code for their application classes.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @since   1.0
	 */
	protected function initialise()
	{
	}

	/**
	 * Modifies a property of the object, creating it if it does not already exist.
	 *
	 * @param   string  $key    The name of the property.
	 * @param   mixed   $value  The value of the property to set (optional).
	 *
	 * @return  mixed   Previous value of the property
	 *
	 * @since   1.0
	 */
	public function set($key, $value = null)
	{
		$previous = $this->config->get($key);
		$this->config->set($key, $value);

		return $previous;
	}

	/**
	 * Sets the configuration for the application.
	 *
	 * @param   Registry  $config  A registry object holding the configuration.
	 *
	 * @return  AbstractApplication  Returns itself to support chaining.
	 *
	 * @since   1.0
	 */
	public function setConfiguration(Registry $config)
	{
		$this->config = $config;

		return $this;
	}

	/**
	 * Set the logger.
	 *
	 * @param   LoggerInterface  $logger  The logger.
	 *
	 * @return  AbstractApplication  Returns itself to support chaining.
	 *
	 * @since   1.0
	 */
	public function setLogger(LoggerInterface $logger)
	{
		$this->logger = $logger;

		return $this;
	}
}
vendor/joomla/application/src/AbstractWebApplication.php000064400000066023152177723700017534 0ustar00<?php
/**
 * Part of the Joomla Framework Application Package
 *
 * @copyright  Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license    GNU General Public License version 2 or later; see LICENSE
 */

namespace Joomla\Application;

use Joomla\Input\Input;
use Joomla\Registry\Registry;
use Joomla\Session\Session;
use Joomla\Uri\Uri;

/**
 * Base class for a Joomla! Web application.
 *
 * @since  1.0
 */
abstract class AbstractWebApplication extends AbstractApplication
{
	/**
	 * Character encoding string.
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $charSet = 'utf-8';

	/**
	 * Response mime type.
	 *
	 * @var    string
	 * @since  1.0
	 */
	public $mimeType = 'text/html';

	/**
	 * HTTP protocol version.
	 *
	 * @var    string
	 * @since  1.9.0
	 */
	public $httpVersion = '1.1';

	/**
	 * The body modified date for response headers.
	 *
	 * @var    \DateTime
	 * @since  1.0
	 */
	public $modifiedDate;

	/**
	 * The application client object.
	 *
	 * @var    Web\WebClient
	 * @since  1.0
	 */
	public $client;

	/**
	 * The application response object.
	 *
	 * @var    object
	 * @since  1.0
	 */
	protected $response;

	/**
	 * The application session object.
	 *
	 * @var    Session
	 * @since  1.0
	 */
	private $session;

	/**
	 * A map of integer HTTP response codes to the full HTTP Status for the headers.
	 *
	 * @var    array
	 * @since  1.6.0
	 * @link   https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
	 */
	private $responseMap = array(
		100 => 'HTTP/{version} 100 Continue',
		101 => 'HTTP/{version} 101 Switching Protocols',
		102 => 'HTTP/{version} 102 Processing',
		200 => 'HTTP/{version} 200 OK',
		201 => 'HTTP/{version} 201 Created',
		202 => 'HTTP/{version} 202 Accepted',
		203 => 'HTTP/{version} 203 Non-Authoritative Information',
		204 => 'HTTP/{version} 204 No Content',
		205 => 'HTTP/{version} 205 Reset Content',
		206 => 'HTTP/{version} 206 Partial Content',
		207 => 'HTTP/{version} 207 Multi-Status',
		208 => 'HTTP/{version} 208 Already Reported',
		226 => 'HTTP/{version} 226 IM Used',
		300 => 'HTTP/{version} 300 Multiple Choices',
		301 => 'HTTP/{version} 301 Moved Permanently',
		302 => 'HTTP/{version} 302 Found',
		303 => 'HTTP/{version} 303 See other',
		304 => 'HTTP/{version} 304 Not Modified',
		305 => 'HTTP/{version} 305 Use Proxy',
		306 => 'HTTP/{version} 306 (Unused)',
		307 => 'HTTP/{version} 307 Temporary Redirect',
		308 => 'HTTP/{version} 308 Permanent Redirect',
		400 => 'HTTP/{version} 400 Bad Request',
		401 => 'HTTP/{version} 401 Unauthorized',
		402 => 'HTTP/{version} 402 Payment Required',
		403 => 'HTTP/{version} 403 Forbidden',
		404 => 'HTTP/{version} 404 Not Found',
		405 => 'HTTP/{version} 405 Method Not Allowed',
		406 => 'HTTP/{version} 406 Not Acceptable',
		407 => 'HTTP/{version} 407 Proxy Authentication Required',
		408 => 'HTTP/{version} 408 Request Timeout',
		409 => 'HTTP/{version} 409 Conflict',
		410 => 'HTTP/{version} 410 Gone',
		411 => 'HTTP/{version} 411 Length Required',
		412 => 'HTTP/{version} 412 Precondition Failed',
		413 => 'HTTP/{version} 413 Payload Too Large',
		414 => 'HTTP/{version} 414 URI Too Long',
		415 => 'HTTP/{version} 415 Unsupported Media Type',
		416 => 'HTTP/{version} 416 Range Not Satisfiable',
		417 => 'HTTP/{version} 417 Expectation Failed',
		418 => 'HTTP/{version} 418 I\'m a teapot',
		421 => 'HTTP/{version} 421 Misdirected Request',
		422 => 'HTTP/{version} 422 Unprocessable Entity',
		423 => 'HTTP/{version} 423 Locked',
		424 => 'HTTP/{version} 424 Failed Dependency',
		426 => 'HTTP/{version} 426 Upgrade Required',
		428 => 'HTTP/{version} 428 Precondition Required',
		429 => 'HTTP/{version} 429 Too Many Requests',
		431 => 'HTTP/{version} 431 Request Header Fields Too Large',
		451 => 'HTTP/{version} 451 Unavailable For Legal Reasons',
		500 => 'HTTP/{version} 500 Internal Server Error',
		501 => 'HTTP/{version} 501 Not Implemented',
		502 => 'HTTP/{version} 502 Bad Gateway',
		503 => 'HTTP/{version} 503 Service Unavailable',
		504 => 'HTTP/{version} 504 Gateway Timeout',
		505 => 'HTTP/{version} 505 HTTP Version Not Supported',
		506 => 'HTTP/{version} 506 Variant Also Negotiates',
		507 => 'HTTP/{version} 507 Insufficient Storage',
		508 => 'HTTP/{version} 508 Loop Detected',
		510 => 'HTTP/{version} 510 Not Extended',
		511 => 'HTTP/{version} 511 Network Authentication Required',
	);

	/**
	 * Class constructor.
	 *
	 * @param   Input          $input   An optional argument to provide dependency injection for the application's input object.  If the argument
	 *                                  is an Input object that object will become the application's input object, otherwise a default input
	 *                                  object is created.
	 * @param   Registry       $config  An optional argument to provide dependency injection for the application's config object.  If the argument
	 *                                  is a Registry object that object will become the application's config object, otherwise a default config
	 *                                  object is created.
	 * @param   Web\WebClient  $client  An optional argument to provide dependency injection for the application's client object.  If the argument
	 *                                  is a Web\WebClient object that object will become the application's client object, otherwise a default client
	 *                                  object is created.
	 *
	 * @since   1.0
	 */
	public function __construct(Input $input = null, Registry $config = null, Web\WebClient $client = null)
	{
		$this->client = $client instanceof Web\WebClient ? $client : new Web\WebClient;

		// Setup the response object.
		$this->response           = new \stdClass;
		$this->response->cachable = false;
		$this->response->headers  = array();
		$this->response->body     = array();

		// Call the constructor as late as possible (it runs `initialise`).
		parent::__construct($input, $config);

		// Set the system URIs.
		$this->loadSystemUris();
	}

	/**
	 * Execute the application.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	public function execute()
	{
		// @event onBeforeExecute

		// Perform application routines.
		$this->doExecute();

		// @event onAfterExecute

		// If gzip compression is enabled in configuration and the server is compliant, compress the output.
		if ($this->get('gzip') && !ini_get('zlib.output_compression') && (ini_get('output_handler') != 'ob_gzhandler'))
		{
			$this->compress();
		}

		// @event onBeforeRespond

		// Send the application response.
		$this->respond();

		// @event onAfterRespond
	}

	/**
	 * Checks the accept encoding of the browser and compresses the data before
	 * sending it to the client if possible.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function compress()
	{
		// Supported compression encodings.
		$supported = array(
			'x-gzip'  => 'gz',
			'gzip'    => 'gz',
			'deflate' => 'deflate',
		);

		// Get the supported encoding.
		$encodings = array_intersect($this->client->encodings, array_keys($supported));

		// If no supported encoding is detected do nothing and return.
		if (empty($encodings))
		{
			return;
		}

		// Verify that headers have not yet been sent, and that our connection is still alive.
		if ($this->checkHeadersSent() || !$this->checkConnectionAlive())
		{
			return;
		}

		// Iterate through the encodings and attempt to compress the data using any found supported encodings.
		foreach ($encodings as $encoding)
		{
			if (($supported[$encoding] == 'gz') || ($supported[$encoding] == 'deflate'))
			{
				// Verify that the server supports gzip compression before we attempt to gzip encode the data.
				// @codeCoverageIgnoreStart
				if (!\extension_loaded('zlib') || ini_get('zlib.output_compression'))
				{
					continue;
				}

				// @codeCoverageIgnoreEnd

				// Attempt to gzip encode the data with an optimal level 4.
				$data   = $this->getBody();
				$gzdata = gzencode($data, 4, ($supported[$encoding] == 'gz') ? FORCE_GZIP : FORCE_DEFLATE);

				// If there was a problem encoding the data just try the next encoding scheme.
				// @codeCoverageIgnoreStart
				if ($gzdata === false)
				{
					continue;
				}

				// @codeCoverageIgnoreEnd

				// Set the encoding headers.
				$this->setHeader('Content-Encoding', $encoding);
				$this->setHeader('Vary', 'Accept-Encoding');
				$this->setHeader('X-Content-Encoded-By', 'Joomla');

				// Replace the output with the encoded data.
				$this->setBody($gzdata);

				// Compression complete, let's break out of the loop.
				break;
			}
		}
	}

	/**
	 * Method to send the application response to the client.  All headers will be sent prior to the main
	 * application output data.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function respond()
	{
		// Send the content-type header.
		$this->setHeader('Content-Type', $this->mimeType . '; charset=' . $this->charSet);

		// If the response is set to uncachable, we need to set some appropriate headers so browsers don't cache the response.
		if (!$this->allowCache())
		{
			// Expires in the past.
			$this->setHeader('Expires', 'Wed, 17 Aug 2005 00:00:00 GMT', true);

			// Always modified.
			$this->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT', true);
			$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', false);

			// HTTP 1.0
			$this->setHeader('Pragma', 'no-cache');
		}
		else
		{
			// Expires.
			$this->setHeader('Expires', gmdate('D, d M Y H:i:s', time() + 900) . ' GMT');

			// Last modified.
			if ($this->modifiedDate instanceof \DateTime)
			{
				$this->modifiedDate->setTimezone(new \DateTimeZone('UTC'));
				$this->setHeader('Last-Modified', $this->modifiedDate->format('D, d M Y H:i:s') . ' GMT');
			}
		}

		$this->sendHeaders();

		echo $this->getBody();
	}

	/**
	 * Redirect to another URL.
	 *
	 * If the headers have not been sent the redirect will be accomplished using a "301 Moved Permanently"
	 * or "303 See Other" code in the header pointing to the new location. If the headers have already been
	 * sent this will be accomplished using a JavaScript statement.
	 *
	 * @param   string   $url     The URL to redirect to. Can only be http/https URL
	 * @param   integer  $status  The HTTP status code to be provided. 303 is assumed by default.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 * @throws  \InvalidArgumentException
	 */
	public function redirect($url, $status = 303)
	{
		// Check for relative internal links.
		if (preg_match('#^index\.php#', $url))
		{
			$url = $this->get('uri.base.full') . $url;
		}

		// Perform a basic sanity check to make sure we don't have any CRLF garbage.
		$url = preg_split("/[\r\n]/", $url);
		$url = $url[0];

		/*
		 * Here we need to check and see if the URL is relative or absolute.  Essentially, do we need to
		 * prepend the URL with our base URL for a proper redirect.  The rudimentary way we are looking
		 * at this is to simply check whether or not the URL string has a valid scheme or not.
		 */
		if (!preg_match('#^[a-z]+\://#i', $url))
		{
			// Get a Uri instance for the requested URI.
			$uri = new Uri($this->get('uri.request'));

			// Get a base URL to prepend from the requested URI.
			$prefix = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port'));

			// We just need the prefix since we have a path relative to the root.
			if ($url[0] == '/')
			{
				$url = $prefix . $url;
			}
			else
			{
				// It's relative to where we are now, so lets add that.
				$parts = explode('/', $uri->toString(array('path')));
				array_pop($parts);
				$path = implode('/', $parts) . '/';
				$url  = $prefix . $path . $url;
			}
		}

		// If the headers have already been sent we need to send the redirect statement via JavaScript.
		if ($this->checkHeadersSent())
		{
			echo '<script>document.location.href=' . json_encode($url) . ";</script>\n";
		}
		else
		{
			// We have to use a JavaScript redirect here because MSIE doesn't play nice with utf-8 URLs.
			if (($this->client->engine == Web\WebClient::TRIDENT) && !static::isAscii($url))
			{
				$html = '<html><head>';
				$html .= '<meta http-equiv="content-type" content="text/html; charset=' . $this->charSet . '" />';
				$html .= '<script>document.location.href=' . json_encode($url) . ';</script>';
				$html .= '</head><body></body></html>';

				echo $html;
			}
			else
			{
				// Check if we have a boolean for the status variable for compatability with v1 of the framework
				// @deprecated 3.0
				if (\is_bool($status))
				{
					$status = $status ? 301 : 303;
				}

				if (!\is_int($status) && !$this->isRedirectState($status))
				{
					throw new \InvalidArgumentException('You have not supplied a valid HTTP status code');
				}

				// All other cases use the more efficient HTTP header for redirection.
				$this->setHeader('Status', $status, true);
				$this->setHeader('Location', $url, true);
			}
		}

		// Set appropriate headers
		$this->respond();

		// Close the application after the redirect.
		$this->close();
	}

	/**
	 * Set/get cachable state for the response.  If $allow is set, sets the cachable state of the
	 * response.  Always returns the current state.
	 *
	 * @param   boolean  $allow  True to allow browser caching.
	 *
	 * @return  boolean
	 *
	 * @since   1.0
	 */
	public function allowCache($allow = null)
	{
		if ($allow !== null)
		{
			$this->response->cachable = (bool) $allow;
		}

		return $this->response->cachable;
	}

	/**
	 * Method to set a response header.  If the replace flag is set then all headers
	 * with the given name will be replaced by the new one.  The headers are stored
	 * in an internal array to be sent when the site is sent to the browser.
	 *
	 * @param   string   $name     The name of the header to set.
	 * @param   string   $value    The value of the header to set.
	 * @param   boolean  $replace  True to replace any headers with the same name.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function setHeader($name, $value, $replace = false)
	{
		// Sanitize the input values.
		$name  = (string) $name;
		$value = (string) $value;

		// If the replace flag is set, unset all known headers with the given name.
		if ($replace)
		{
			foreach ($this->response->headers as $key => $header)
			{
				if ($name == $header['name'])
				{
					unset($this->response->headers[$key]);
				}
			}

			// Clean up the array as unsetting nested arrays leaves some junk.
			$this->response->headers = array_values($this->response->headers);
		}

		// Add the header to the internal array.
		$this->response->headers[] = array('name' => $name, 'value' => $value);

		return $this;
	}

	/**
	 * Method to get the array of response headers to be sent when the response is sent
	 * to the client.
	 *
	 * @return  array
	 *
	 * @since   1.0
	 */
	public function getHeaders()
	{
		return $this->response->headers;
	}

	/**
	 * Method to clear any set response headers.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function clearHeaders()
	{
		$this->response->headers = array();

		return $this;
	}

	/**
	 * Send the response headers.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function sendHeaders()
	{
		if (!$this->checkHeadersSent())
		{
			foreach ($this->response->headers as $header)
			{
				if (strtolower($header['name']) == 'status')
				{
					// 'status' headers indicate an HTTP status, and need to be handled slightly differently
					$status = $this->getHttpStatusValue($header['value']);

					$this->header($status, true, (int) $header['value']);
				}
				else
				{
					$this->header($header['name'] . ': ' . $header['value']);
				}
			}
		}

		return $this;
	}

	/**
	 * Set body content.  If body content already defined, this will replace it.
	 *
	 * @param   string  $content  The content to set as the response body.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function setBody($content)
	{
		$this->response->body = array((string) $content);

		return $this;
	}

	/**
	 * Prepend content to the body content
	 *
	 * @param   string  $content  The content to prepend to the response body.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function prependBody($content)
	{
		array_unshift($this->response->body, (string) $content);

		return $this;
	}

	/**
	 * Append content to the body content
	 *
	 * @param   string  $content  The content to append to the response body.
	 *
	 * @return  AbstractWebApplication  Instance of $this to allow chaining.
	 *
	 * @since   1.0
	 */
	public function appendBody($content)
	{
		$this->response->body[] = (string) $content;

		return $this;
	}

	/**
	 * Return the body content
	 *
	 * @param   boolean  $asArray  True to return the body as an array of strings.
	 *
	 * @return  mixed  The response body either as an array or concatenated string.
	 *
	 * @since   1.0
	 */
	public function getBody($asArray = false)
	{
		return $asArray ? $this->response->body : implode((array) $this->response->body);
	}

	/**
	 * Method to get the application session object.
	 *
	 * @return  Session  The session object
	 *
	 * @since   1.0
	 */
	public function getSession()
	{
		if ($this->session === null)
		{
			throw new \RuntimeException('A \Joomla\Session\Session object has not been set.');
		}

		return $this->session;
	}

	/**
	 * Check if a given value can be successfully mapped to a valid http status value
	 *
	 * @param   string|int  $value  The given status as int or string
	 *
	 * @return  string
	 *
	 * @since   1.8.0
	 */
	protected function getHttpStatusValue($value)
	{
		$code = (int) $value;

		if (array_key_exists($code, $this->responseMap))
		{
			$value = $this->responseMap[$code];
		}
		else
		{
			$value = 'HTTP/{version} ' . $code;
		}

		return str_replace('{version}', $this->httpVersion, $value);
	}

	/**
	 * Check if the value is a valid HTTP status code
	 *
	 * @param   integer  $code  The potential status code
	 *
	 * @return  boolean
	 *
	 * @since   1.8.1
	 */
	public function isValidHttpStatus($code)
	{
		return array_key_exists($code, $this->responseMap);
	}

	/**
	 * Method to check the current client connection status to ensure that it is alive.  We are
	 * wrapping this to isolate the connection_status() function from our code base for testing reasons.
	 *
	 * @return  boolean  True if the connection is valid and normal.
	 *
	 * @codeCoverageIgnore
	 * @see     connection_status()
	 * @since   1.0
	 */
	protected function checkConnectionAlive()
	{
		return connection_status() === CONNECTION_NORMAL;
	}

	/**
	 * Method to check to see if headers have already been sent.  We are wrapping this to isolate the
	 * headers_sent() function from our code base for testing reasons.
	 *
	 * @return  boolean  True if the headers have already been sent.
	 *
	 * @codeCoverageIgnore
	 * @see     headers_sent()
	 * @since   1.0
	 */
	protected function checkHeadersSent()
	{
		return headers_sent();
	}

	/**
	 * Method to detect the requested URI from server environment variables.
	 *
	 * @return  string  The requested URI
	 *
	 * @since   1.0
	 */
	protected function detectRequestUri()
	{
		// First we need to detect the URI scheme.
		if ($this->isSslConnection())
		{
			$scheme = 'https://';
		}
		else
		{
			$scheme = 'http://';
		}

		/*
		 * There are some differences in the way that Apache and IIS populate server environment variables.  To
		 * properly detect the requested URI we need to adjust our algorithm based on whether or not we are getting
		 * information from Apache or IIS.
		 */

		$phpSelf    = $this->input->server->getString('PHP_SELF', '');
		$requestUri = $this->input->server->getString('REQUEST_URI', '');

		// If PHP_SELF and REQUEST_URI are both populated then we will assume "Apache Mode".
		if (!empty($phpSelf) && !empty($requestUri))
		{
			// The URI is built from the HTTP_HOST and REQUEST_URI environment variables in an Apache environment.
			$uri = $scheme . $this->input->server->getString('HTTP_HOST') . $requestUri;
		}
		else
		{
			// If not in "Apache Mode" we will assume that we are in an IIS environment and proceed.
			// IIS uses the SCRIPT_NAME variable instead of a REQUEST_URI variable... thanks, MS
			$uri       = $scheme . $this->input->server->getString('HTTP_HOST') . $this->input->server->getString('SCRIPT_NAME');
			$queryHost = $this->input->server->getString('QUERY_STRING', '');

			// If the QUERY_STRING variable exists append it to the URI string.
			if (!empty($queryHost))
			{
				$uri .= '?' . $queryHost;
			}
		}

		return trim($uri);
	}

	/**
	 * Method to send a header to the client.  We are wrapping this to isolate the header() function
	 * from our code base for testing reasons.
	 *
	 * @param   string   $string   The header string.
	 * @param   boolean  $replace  The optional replace parameter indicates whether the header should
	 *                             replace a previous similar header, or add a second header of the same type.
	 * @param   integer  $code     Forces the HTTP response code to the specified value. Note that
	 *                             this parameter only has an effect if the string is not empty.
	 *
	 * @return  void
	 *
	 * @codeCoverageIgnore
	 * @see     header()
	 * @since   1.0
	 */
	protected function header($string, $replace = true, $code = null)
	{
		header(str_replace(\chr(0), '', $string), $replace, $code);
	}

	/**
	 * Checks if a state is a redirect state
	 *
	 * @param   integer  $state  The HTTP status code.
	 *
	 * @return  boolean
	 *
	 * @since   1.8.0
	 */
	protected function isRedirectState($state)
	{
		$state = (int) $state;

		return $state > 299 && $state < 400 && array_key_exists($state, $this->responseMap);
	}

	/**
	 * Determine if we are using a secure (SSL) connection.
	 *
	 * @return  boolean  True if using SSL, false if not.
	 *
	 * @since   1.0
	 */
	public function isSslConnection()
	{
		$serverSSLVar = $this->input->server->getString('HTTPS', '');

		if (!empty($serverSSLVar) && strtolower($serverSSLVar) !== 'off')
		{
			return true;
		}

		$serverForwarderProtoVar = $this->input->server->getString('HTTP_X_FORWARDED_PROTO', '');

		return !empty($serverForwarderProtoVar) && strtolower($serverForwarderProtoVar) === 'https';
	}

	/**
	 * Sets the session for the application to use, if required.
	 *
	 * @param   Session  $session  A session object.
	 *
	 * @return  AbstractWebApplication  Returns itself to support chaining.
	 *
	 * @since   1.0
	 */
	public function setSession(Session $session)
	{
		$this->session = $session;

		return $this;
	}

	/**
	 * Method to load the system URI strings for the application.
	 *
	 * @param   string  $requestUri  An optional request URI to use instead of detecting one from the
	 *                               server environment variables.
	 *
	 * @return  void
	 *
	 * @since   1.0
	 */
	protected function loadSystemUris($requestUri = null)
	{
		// Set the request URI.
		// @codeCoverageIgnoreStart
		if (!empty($requestUri))
		{
			$this->set('uri.request', $requestUri);
		}
		else
		{
			$this->set('uri.request', $this->detectRequestUri());
		}

		// @codeCoverageIgnoreEnd

		// Check to see if an explicit base URI has been set.
		$siteUri = trim($this->get('site_uri'));

		if ($siteUri != '')
		{
			$uri  = new Uri($siteUri);
			$path = $uri->toString(array('path'));
		}
		else
		{
			// No explicit base URI was set so we need to detect it. Start with the requested URI.
			$uri = new Uri($this->get('uri.request'));

			$requestUri = $this->input->server->getString('REQUEST_URI', '');

			// If we are working from a CGI SAPI with the 'cgi.fix_pathinfo' directive disabled we use PHP_SELF.
			if (strpos(PHP_SAPI, 'cgi') !== false && !ini_get('cgi.fix_pathinfo') && !empty($requestUri))
			{
				// We aren't expecting PATH_INFO within PHP_SELF so this should work.
				$path = \dirname($this->input->server->getString('PHP_SELF', ''));
			}
			else
			{
				// Pretty much everything else should be handled with SCRIPT_NAME.
				$path = \dirname($this->input->server->getString('SCRIPT_NAME', ''));
			}
		}

		// Get the host from the URI.
		$host = $uri->toString(array('scheme', 'user', 'pass', 'host', 'port'));

		// Check if the path includes "index.php".
		if (strpos($path, 'index.php') !== false)
		{
			// Remove the index.php portion of the path.
			$path = substr_replace($path, '', strpos($path, 'index.php'), 9);
		}

		$path = rtrim($path, '/\\');

		// Set the base URI both as just a path and as the full URI.
		$this->set('uri.base.full', $host . $path . '/');
		$this->set('uri.base.host', $host);
		$this->set('uri.base.path', $path . '/');

		// Set the extended (non-base) part of the request URI as the route.
		if (stripos($this->get('uri.request'), $this->get('uri.base.full')) === 0)
		{
			$this->set('uri.route', substr_replace($this->get('uri.request'), '', 0, \strlen($this->get('uri.base.full'))));
		}

		// Get an explicitly set media URI is present.
		$mediaURI = trim($this->get('media_uri'));

		if ($mediaURI)
		{
			if (strpos($mediaURI, '://') !== false)
			{
				$this->set('uri.media.full', $mediaURI);
				$this->set('uri.media.path', $mediaURI);
			}
			else
			{
				// Normalise slashes.
				$mediaURI = trim($mediaURI, '/\\');
				$mediaURI = !empty($mediaURI) ? '/' . $mediaURI . '/' : '/';
				$this->set('uri.media.full', $this->get('uri.base.host') . $mediaURI);
				$this->set('uri.media.path', $mediaURI);
			}
		}
		else
		{
			// No explicit media URI was set, build it dynamically from the base uri.
			$this->set('uri.media.full', $this->get('uri.base.full') . 'media/');
			$this->set('uri.media.path', $this->get('uri.base.path') . 'media/');
		}
	}

	/**
	 * Checks for a form token in the request.
	 *
	 * Use in conjunction with getFormToken.
	 *
	 * @param   string  $method  The request method in which to look for the token key.
	 *
	 * @return  boolean  True if found and valid, false otherwise.
	 *
	 * @since   1.0
	 */
	public function checkToken($method = 'post')
	{
		$token = $this->getFormToken();

		if (!$this->input->$method->get($token, '', 'alnum'))
		{
			if ($this->getSession()->isNew())
			{
				// Redirect to login screen.
				$this->redirect('index.php');
				$this->close();
			}
			else
			{
				return false;
			}
		}
		else
		{
			return true;
		}
	}

	/**
	 * Method to determine a hash for anti-spoofing variable names
	 *
	 * @param   boolean  $forceNew  If true, force a new token to be created
	 *
	 * @return  string  Hashed var name
	 *
	 * @since   1.0
	 */
	public function getFormToken($forceNew = false)
	{
		// @todo we need the user id somehow here
		$userId  = 0;

		return md5($this->get('secret') . $userId . $this->getSession()->getToken($forceNew));
	}

	/**
	 * Tests whether a string contains only 7bit ASCII bytes.
	 *
	 * You might use this to conditionally check whether a string
	 * needs handling as UTF-8 or not, potentially offering performance
	 * benefits by using the native PHP equivalent if it's just ASCII e.g.;
	 *
	 * @param   string  $str  The string to test.
	 *
	 * @return  boolean True if the string is all ASCII
	 *
	 * @since   1.4.0
	 */
	public static function isAscii($str)
	{
		// Search for any bytes which are outside the ASCII range...
		return preg_match('/(?:[^\x00-\x7F])/', $str) !== 1;
	}
}
vendor/simplepie/simplepie/library/SimplePie/Restriction.php000064400000007330152177723700020406 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles `<media:restriction>` as defined in Media RSS
 *
 * Used by {@see SimplePie_Enclosure::get_restriction()} and {@see SimplePie_Enclosure::get_restrictions()}
 *
 * This class can be overloaded with {@see SimplePie::set_restriction_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Restriction
{
	/**
	 * Relationship ('allow'/'deny')
	 *
	 * @var string
	 * @see get_relationship()
	 */
	var $relationship;

	/**
	 * Type of restriction
	 *
	 * @var string
	 * @see get_type()
	 */
	var $type;

	/**
	 * Restricted values
	 *
	 * @var string
	 * @see get_value()
	 */
	var $value;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 */
	public function __construct($relationship = null, $type = null, $value = null)
	{
		$this->relationship = $relationship;
		$this->type = $type;
		$this->value = $value;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the relationship
	 *
	 * @return string|null Either 'allow' or 'deny'
	 */
	public function get_relationship()
	{
		if ($this->relationship !== null)
		{
			return $this->relationship;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the type
	 *
	 * @return string|null
	 */
	public function get_type()
	{
		if ($this->type !== null)
		{
			return $this->type;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the list of restricted things
	 *
	 * @return string|null
	 */
	public function get_value()
	{
		if ($this->value !== null)
		{
			return $this->value;
		}
		else
		{
			return null;
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Item.php000064400000277773152177723700017023 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Manages all item-related data
 *
 * Used by {@see SimplePie::get_item()} and {@see SimplePie::get_items()}
 *
 * This class can be overloaded with {@see SimplePie::set_item_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Item
{
	/**
	 * Parent feed
	 *
	 * @access private
	 * @var SimplePie
	 */
	var $feed;

	/**
	 * Raw data
	 *
	 * @access private
	 * @var array
	 */
	var $data = array();

	/**
	 * Registry object
	 *
	 * @see set_registry
	 * @var SimplePie_Registry
	 */
	protected $registry;

	/**
	 * Create a new item object
	 *
	 * This is usually used by {@see SimplePie::get_items} and
	 * {@see SimplePie::get_item}. Avoid creating this manually.
	 *
	 * @param SimplePie $feed Parent feed
	 * @param array $data Raw data
	 */
	public function __construct($feed, $data)
	{
		$this->feed = $feed;
		$this->data = $data;
	}

	/**
	 * Set the registry handler
	 *
	 * This is usually used by {@see SimplePie_Registry::create}
	 *
	 * @since 1.3
	 * @param SimplePie_Registry $registry
	 */
	public function set_registry(SimplePie_Registry $registry)
	{
		$this->registry = $registry;
	}

	/**
	 * Get a string representation of the item
	 *
	 * @return string
	 */
	public function __toString()
	{
		return md5(serialize($this->data));
	}

	/**
	 * Remove items that link back to this before destroying this object
	 */
	public function __destruct()
	{
		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
		{
			unset($this->feed);
		}
	}

	/**
	 * Get data for an item-level element
	 *
	 * This method allows you to get access to ANY element/attribute that is a
	 * sub-element of the item/entry tag.
	 *
	 * See {@see SimplePie::get_feed_tags()} for a description of the return value
	 *
	 * @since 1.0
	 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
	 * @param string $namespace The URL of the XML namespace of the elements you're trying to access
	 * @param string $tag Tag name
	 * @return array
	 */
	public function get_item_tags($namespace, $tag)
	{
		if (isset($this->data['child'][$namespace][$tag]))
		{
			return $this->data['child'][$namespace][$tag];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the base URL value from the parent feed
	 *
	 * Uses `<xml:base>`
	 *
	 * @param array $element
	 * @return string
	 */
	public function get_base($element = array())
	{
		return $this->feed->get_base($element);
	}

	/**
	 * Sanitize feed data
	 *
	 * @access private
	 * @see SimplePie::sanitize()
	 * @param string $data Data to sanitize
	 * @param int $type One of the SIMPLEPIE_CONSTRUCT_* constants
	 * @param string $base Base URL to resolve URLs against
	 * @return string Sanitized data
	 */
	public function sanitize($data, $type, $base = '')
	{
		return $this->feed->sanitize($data, $type, $base);
	}

	/**
	 * Get the parent feed
	 *
	 * Note: this may not work as you think for multifeeds!
	 *
	 * @link http://simplepie.org/faq/typical_multifeed_gotchas#missing_data_from_feed
	 * @since 1.0
	 * @return SimplePie
	 */
	public function get_feed()
	{
		return $this->feed;
	}

	/**
	 * Get the unique identifier for the item
	 *
	 * This is usually used when writing code to check for new items in a feed.
	 *
	 * Uses `<atom:id>`, `<guid>`, `<dc:identifier>` or the `about` attribute
	 * for RDF. If none of these are supplied (or `$hash` is true), creates an
	 * MD5 hash based on the permalink and title. If either of those are not
	 * supplied, creates a hash based on the full feed data.
	 *
	 * @since Beta 2
	 * @param boolean $hash Should we force using a hash instead of the supplied ID?
	 * @return string
	 */
	public function get_id($hash = false)
	{
		if (!$hash)
		{
			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'id'))
			{
				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'id'))
			{
				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
			{
				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'identifier'))
			{
				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'identifier'))
			{
				return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif (isset($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about']))
			{
				return $this->sanitize($this->data['attribs'][SIMPLEPIE_NAMESPACE_RDF]['about'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif (($return = $this->get_permalink()) !== null)
			{
				return $return;
			}
			elseif (($return = $this->get_title()) !== null)
			{
				return $return;
			}
		}
		if ($this->get_permalink() !== null || $this->get_title() !== null)
		{
			return md5($this->get_permalink() . $this->get_title());
		}
		else
		{
			return md5(serialize($this->data));
		}
	}

	/**
	 * Get the title of the item
	 *
	 * Uses `<atom:title>`, `<title>` or `<dc:title>`
	 *
	 * @since Beta 2 (previously called `get_item_title` since 0.8)
	 * @return string|null
	 */
	public function get_title()
	{
		if (!isset($this->data['title']))
		{
			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
			{
				$this->data['title'] = $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			else
			{
				$this->data['title'] = null;
			}
		}
		return $this->data['title'];
	}

	/**
	 * Get the content for the item
	 *
	 * Prefers summaries over full content , but will return full content if a
	 * summary does not exist.
	 *
	 * To prefer full content instead, use {@see get_content}
	 *
	 * Uses `<atom:summary>`, `<description>`, `<dc:description>` or
	 * `<itunes:subtitle>`
	 *
	 * @since 0.8
	 * @param boolean $description_only Should we avoid falling back to the content?
	 * @return string|null
	 */
	public function get_description($description_only = false)
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'summary'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'summary'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML);
		}

		elseif (!$description_only)
		{
			return $this->get_content(true);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the content for the item
	 *
	 * Prefers full content over summaries, but will return a summary if full
	 * content does not exist.
	 *
	 * To prefer summaries instead, use {@see get_description}
	 *
	 * Uses `<atom:content>` or `<content:encoded>` (RSS 1.0 Content Module)
	 *
	 * @since 1.0
	 * @param boolean $content_only Should we avoid falling back to the description?
	 * @return string|null
	 */
	public function get_content($content_only = false)
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'content'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_content_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'content'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT, 'encoded'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif (!$content_only)
		{
			return $this->get_description(true);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a category for the item
	 *
	 * @since Beta 3 (previously called `get_categories()` since Beta 2)
	 * @param int $key The category that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Category|null
	 */
	public function get_category($key = 0)
	{
		$categories = $this->get_categories();
		if (isset($categories[$key]))
		{
			return $categories[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all categories for the item
	 *
	 * Uses `<atom:category>`, `<category>` or `<dc:subject>`
	 *
	 * @since Beta 3
	 * @return array|null List of {@see SimplePie_Category} objects
	 */
	public function get_categories()
	{
		$categories = array();

		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
		{
			$term = null;
			$scheme = null;
			$label = null;
			if (isset($category['attribs']['']['term']))
			{
				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['scheme']))
			{
				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['label']))
			{
				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
		{
			// This is really the label, but keep this as the term also for BC.
			// Label will also work on retrieving because that falls back to term.
			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			if (isset($category['attribs']['']['domain']))
			{
				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			else
			{
				$scheme = null;
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, null));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($categories))
		{
			return array_unique($categories);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get an author for the item
	 *
	 * @since Beta 2
	 * @param int $key The author that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Author|null
	 */
	public function get_author($key = 0)
	{
		$authors = $this->get_authors();
		if (isset($authors[$key]))
		{
			return $authors[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a contributor for the item
	 *
	 * @since 1.1
	 * @param int $key The contrbutor that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Author|null
	 */
	public function get_contributor($key = 0)
	{
		$contributors = $this->get_contributors();
		if (isset($contributors[$key]))
		{
			return $contributors[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all contributors for the item
	 *
	 * Uses `<atom:contributor>`
	 *
	 * @since 1.1
	 * @return array|null List of {@see SimplePie_Author} objects
	 */
	public function get_contributors()
	{
		$contributors = array();
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}

		if (!empty($contributors))
		{
			return array_unique($contributors);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all authors for the item
	 *
	 * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>`
	 *
	 * @since Beta 2
	 * @return array|null List of {@see SimplePie_Author} objects
	 */
	public function get_authors()
	{
		$authors = array();
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}
		if ($author = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'author'))
		{
			$authors[] = $this->registry->create('Author', array(null, null, $this->sanitize($author[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT)));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($authors))
		{
			return array_unique($authors);
		}
		elseif (($source = $this->get_source()) && ($authors = $source->get_authors()))
		{
			return $authors;
		}
		elseif ($authors = $this->feed->get_authors())
		{
			return $authors;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the copyright info for the item
	 *
	 * Uses `<atom:rights>` or `<dc:rights>`
	 *
	 * @since 1.1
	 * @return string
	 */
	public function get_copyright()
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the posting date/time for the item
	 *
	 * Uses `<atom:published>`, `<atom:updated>`, `<atom:issued>`,
	 * `<atom:modified>`, `<pubDate>` or `<dc:date>`
	 *
	 * Note: obeys PHP's timezone setting. To get a UTC date/time, use
	 * {@see get_gmdate}
	 *
	 * @since Beta 2 (previously called `get_item_date` since 0.8)
	 *
	 * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data)
	 * @return int|string|null
	 */
	public function get_date($date_format = 'j F Y, g:i a')
	{
		if (!isset($this->data['date']))
		{
			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}
			elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date'))
			{
				$this->data['date']['raw'] = $return[0]['data'];
			}

			if (!empty($this->data['date']['raw']))
			{
				$parser = $this->registry->call('Parse_Date', 'get');
				$this->data['date']['parsed'] = $parser->parse($this->data['date']['raw']);
			}
			else
			{
				$this->data['date'] = null;
			}
		}
		if ($this->data['date'])
		{
			$date_format = (string) $date_format;
			switch ($date_format)
			{
				case '':
					return $this->sanitize($this->data['date']['raw'], SIMPLEPIE_CONSTRUCT_TEXT);

				case 'U':
					return $this->data['date']['parsed'];

				default:
					return date($date_format, $this->data['date']['parsed']);
			}
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the update date/time for the item
	 *
	 * Uses `<atom:updated>`
	 *
	 * Note: obeys PHP's timezone setting. To get a UTC date/time, use
	 * {@see get_gmdate}
	 *
	 * @param string $date_format Supports any PHP date format from {@see http://php.net/date} (empty for the raw data)
	 * @return int|string|null
	 */
	public function get_updated_date($date_format = 'j F Y, g:i a')
	{
		if (!isset($this->data['updated']))
		{
			if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
			{
				$this->data['updated']['raw'] = $return[0]['data'];
			}

			if (!empty($this->data['updated']['raw']))
			{
				$parser = $this->registry->call('Parse_Date', 'get');
				$this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']);
			}
			else
			{
				$this->data['updated'] = null;
			}
		}
		if ($this->data['updated'])
		{
			$date_format = (string) $date_format;
			switch ($date_format)
			{
				case '':
					return $this->sanitize($this->data['updated']['raw'], SIMPLEPIE_CONSTRUCT_TEXT);

				case 'U':
					return $this->data['updated']['parsed'];

				default:
					return date($date_format, $this->data['updated']['parsed']);
			}
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the localized posting date/time for the item
	 *
	 * Returns the date formatted in the localized language. To display in
	 * languages other than the server's default, you need to change the locale
	 * with {@link http://php.net/setlocale setlocale()}. The available
	 * localizations depend on which ones are installed on your web server.
	 *
	 * @since 1.0
	 *
	 * @param string $date_format Supports any PHP date format from {@see http://php.net/strftime} (empty for the raw data)
	 * @return int|string|null
	 */
	public function get_local_date($date_format = '%c')
	{
		if (!$date_format)
		{
			return $this->sanitize($this->get_date(''), SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (($date = $this->get_date('U')) !== null && $date !== false)
		{
			return strftime($date_format, $date);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the posting date/time for the item (UTC time)
	 *
	 * @see get_date
	 * @param string $date_format Supports any PHP date format from {@see http://php.net/date}
	 * @return int|string|null
	 */
	public function get_gmdate($date_format = 'j F Y, g:i a')
	{
		$date = $this->get_date('U');
		if ($date === null)
		{
			return null;
		}

		return gmdate($date_format, $date);
	}

	/**
	 * Get the update date/time for the item (UTC time)
	 *
	 * @see get_updated_date
	 * @param string $date_format Supports any PHP date format from {@see http://php.net/date}
	 * @return int|string|null
	 */
	public function get_updated_gmdate($date_format = 'j F Y, g:i a')
	{
		$date = $this->get_updated_date('U');
		if ($date === null)
		{
			return null;
		}

		return gmdate($date_format, $date);
	}

	/**
	 * Get the permalink for the item
	 *
	 * Returns the first link available with a relationship of "alternate".
	 * Identical to {@see get_link()} with key 0
	 *
	 * @see get_link
	 * @since 0.8
	 * @return string|null Permalink URL
	 */
	public function get_permalink()
	{
		$link = $this->get_link();
		$enclosure = $this->get_enclosure(0);
		if ($link !== null)
		{
			return $link;
		}
		elseif ($enclosure !== null)
		{
			return $enclosure->get_link();
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single link for the item
	 *
	 * @since Beta 3
	 * @param int $key The link that you want to return.  Remember that arrays begin with 0, not 1
	 * @param string $rel The relationship of the link to return
	 * @return string|null Link URL
	 */
	public function get_link($key = 0, $rel = 'alternate')
	{
		$links = $this->get_links($rel);
		if ($links[$key] !== null)
		{
			return $links[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all links for the item
	 *
	 * Uses `<atom:link>`, `<link>` or `<guid>`
	 *
	 * @since Beta 2
	 * @param string $rel The relationship of links to return
	 * @return array|null Links found for the item (strings)
	 */
	public function get_links($rel = 'alternate')
	{
		if (!isset($this->data['links']))
		{
			$this->data['links'] = array();
			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
			{
				if (isset($link['attribs']['']['href']))
				{
					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));

				}
			}
			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
			{
				if (isset($link['attribs']['']['href']))
				{
					$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
					$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
				}
			}
			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'guid'))
			{
				if (!isset($links[0]['attribs']['']['isPermaLink']) || strtolower(trim($links[0]['attribs']['']['isPermaLink'])) === 'true')
				{
					$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
				}
			}

			$keys = array_keys($this->data['links']);
			foreach ($keys as $key)
			{
				if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key)))
				{
					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
					}
					else
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
					}
				}
				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
				{
					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
				}
				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
			}
		}
		if (isset($this->data['links'][$rel]))
		{
			return $this->data['links'][$rel];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get an enclosure from the item
	 *
	 * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS.
	 *
	 * @since Beta 2
	 * @todo Add ability to prefer one type of content over another (in a media group).
	 * @param int $key The enclosure that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Enclosure|null
	 */
	public function get_enclosure($key = 0, $prefer = null)
	{
		$enclosures = $this->get_enclosures();
		if (isset($enclosures[$key]))
		{
			return $enclosures[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all available enclosures (podcasts, etc.)
	 *
	 * Supports the <enclosure> RSS tag, as well as Media RSS and iTunes RSS.
	 *
	 * At this point, we're pretty much assuming that all enclosures for an item
	 * are the same content.  Anything else is too complicated to
	 * properly support.
	 *
	 * @since Beta 2
	 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4).
	 * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists).
	 * @return array|null List of SimplePie_Enclosure items
	 */
	public function get_enclosures()
	{
		if (!isset($this->data['enclosures']))
		{
			$this->data['enclosures'] = array();

			// Elements
			$captions_parent = null;
			$categories_parent = null;
			$copyrights_parent = null;
			$credits_parent = null;
			$description_parent = null;
			$duration_parent = null;
			$hashes_parent = null;
			$keywords_parent = null;
			$player_parent = null;
			$ratings_parent = null;
			$restrictions_parent = null;
			$thumbnails_parent = null;
			$title_parent = null;

			// Let's do the channel and item-level ones first, and just re-use them if we need to.
			$parent = $this->get_feed();

			// CAPTIONS
			if ($captions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
			{
				foreach ($captions as $caption)
				{
					$caption_type = null;
					$caption_lang = null;
					$caption_startTime = null;
					$caption_endTime = null;
					$caption_text = null;
					if (isset($caption['attribs']['']['type']))
					{
						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['lang']))
					{
						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['start']))
					{
						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['end']))
					{
						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['data']))
					{
						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text));
				}
			}
			elseif ($captions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'text'))
			{
				foreach ($captions as $caption)
				{
					$caption_type = null;
					$caption_lang = null;
					$caption_startTime = null;
					$caption_endTime = null;
					$caption_text = null;
					if (isset($caption['attribs']['']['type']))
					{
						$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['lang']))
					{
						$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['start']))
					{
						$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['attribs']['']['end']))
					{
						$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($caption['data']))
					{
						$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$captions_parent[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text));
				}
			}
			if (is_array($captions_parent))
			{
				$captions_parent = array_values(array_unique($captions_parent));
			}

			// CATEGORIES
			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
			{
				$term = null;
				$scheme = null;
				$label = null;
				if (isset($category['data']))
				{
					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				if (isset($category['attribs']['']['scheme']))
				{
					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				else
				{
					$scheme = 'http://search.yahoo.com/mrss/category_schema';
				}
				if (isset($category['attribs']['']['label']))
				{
					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				$categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label));
			}
			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'category') as $category)
			{
				$term = null;
				$scheme = null;
				$label = null;
				if (isset($category['data']))
				{
					$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				if (isset($category['attribs']['']['scheme']))
				{
					$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				else
				{
					$scheme = 'http://search.yahoo.com/mrss/category_schema';
				}
				if (isset($category['attribs']['']['label']))
				{
					$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				$categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label));
			}
			foreach ((array) $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'category') as $category)
			{
				$term = null;
				$scheme = 'http://www.itunes.com/dtds/podcast-1.0.dtd';
				$label = null;
				if (isset($category['attribs']['']['text']))
				{
					$label = $this->sanitize($category['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				$categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label));

				if (isset($category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category']))
				{
					foreach ((array) $category['child'][SIMPLEPIE_NAMESPACE_ITUNES]['category'] as $subcategory)
					{
						if (isset($subcategory['attribs']['']['text']))
						{
							$label = $this->sanitize($subcategory['attribs']['']['text'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						$categories_parent[] = $this->registry->create('Category', array($term, $scheme, $label));
					}
				}
			}
			if (is_array($categories_parent))
			{
				$categories_parent = array_values(array_unique($categories_parent));
			}

			// COPYRIGHT
			if ($copyright = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
			{
				$copyright_url = null;
				$copyright_label = null;
				if (isset($copyright[0]['attribs']['']['url']))
				{
					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				if (isset($copyright[0]['data']))
				{
					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				$copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label));
			}
			elseif ($copyright = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'copyright'))
			{
				$copyright_url = null;
				$copyright_label = null;
				if (isset($copyright[0]['attribs']['']['url']))
				{
					$copyright_url = $this->sanitize($copyright[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				if (isset($copyright[0]['data']))
				{
					$copyright_label = $this->sanitize($copyright[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
				$copyrights_parent = $this->registry->create('Copyright', array($copyright_url, $copyright_label));
			}

			// CREDITS
			if ($credits = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
			{
				foreach ($credits as $credit)
				{
					$credit_role = null;
					$credit_scheme = null;
					$credit_name = null;
					if (isset($credit['attribs']['']['role']))
					{
						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($credit['attribs']['']['scheme']))
					{
						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$credit_scheme = 'urn:ebu';
					}
					if (isset($credit['data']))
					{
						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name));
				}
			}
			elseif ($credits = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'credit'))
			{
				foreach ($credits as $credit)
				{
					$credit_role = null;
					$credit_scheme = null;
					$credit_name = null;
					if (isset($credit['attribs']['']['role']))
					{
						$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($credit['attribs']['']['scheme']))
					{
						$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$credit_scheme = 'urn:ebu';
					}
					if (isset($credit['data']))
					{
						$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$credits_parent[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name));
				}
			}
			if (is_array($credits_parent))
			{
				$credits_parent = array_values(array_unique($credits_parent));
			}

			// DESCRIPTION
			if ($description_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
			{
				if (isset($description_parent[0]['data']))
				{
					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
			}
			elseif ($description_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'description'))
			{
				if (isset($description_parent[0]['data']))
				{
					$description_parent = $this->sanitize($description_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
			}

			// DURATION
			if ($duration_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'duration'))
			{
				$seconds = null;
				$minutes = null;
				$hours = null;
				if (isset($duration_parent[0]['data']))
				{
					$temp = explode(':', $this->sanitize($duration_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
					if (sizeof($temp) > 0)
					{
						$seconds = (int) array_pop($temp);
					}
					if (sizeof($temp) > 0)
					{
						$minutes = (int) array_pop($temp);
						$seconds += $minutes * 60;
					}
					if (sizeof($temp) > 0)
					{
						$hours = (int) array_pop($temp);
						$seconds += $hours * 3600;
					}
					unset($temp);
					$duration_parent = $seconds;
				}
			}

			// HASHES
			if ($hashes_iterator = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
			{
				foreach ($hashes_iterator as $hash)
				{
					$value = null;
					$algo = null;
					if (isset($hash['data']))
					{
						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($hash['attribs']['']['algo']))
					{
						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$algo = 'md5';
					}
					$hashes_parent[] = $algo.':'.$value;
				}
			}
			elseif ($hashes_iterator = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'hash'))
			{
				foreach ($hashes_iterator as $hash)
				{
					$value = null;
					$algo = null;
					if (isset($hash['data']))
					{
						$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($hash['attribs']['']['algo']))
					{
						$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$algo = 'md5';
					}
					$hashes_parent[] = $algo.':'.$value;
				}
			}
			if (is_array($hashes_parent))
			{
				$hashes_parent = array_values(array_unique($hashes_parent));
			}

			// KEYWORDS
			if ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
			{
				if (isset($keywords[0]['data']))
				{
					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
					foreach ($temp as $word)
					{
						$keywords_parent[] = trim($word);
					}
				}
				unset($temp);
			}
			elseif ($keywords = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
			{
				if (isset($keywords[0]['data']))
				{
					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
					foreach ($temp as $word)
					{
						$keywords_parent[] = trim($word);
					}
				}
				unset($temp);
			}
			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'keywords'))
			{
				if (isset($keywords[0]['data']))
				{
					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
					foreach ($temp as $word)
					{
						$keywords_parent[] = trim($word);
					}
				}
				unset($temp);
			}
			elseif ($keywords = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'keywords'))
			{
				if (isset($keywords[0]['data']))
				{
					$temp = explode(',', $this->sanitize($keywords[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
					foreach ($temp as $word)
					{
						$keywords_parent[] = trim($word);
					}
				}
				unset($temp);
			}
			if (is_array($keywords_parent))
			{
				$keywords_parent = array_values(array_unique($keywords_parent));
			}

			// PLAYER
			if ($player_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
			{
				if (isset($player_parent[0]['attribs']['']['url']))
				{
					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
				}
			}
			elseif ($player_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'player'))
			{
				if (isset($player_parent[0]['attribs']['']['url']))
				{
					$player_parent = $this->sanitize($player_parent[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
				}
			}

			// RATINGS
			if ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
			{
				foreach ($ratings as $rating)
				{
					$rating_scheme = null;
					$rating_value = null;
					if (isset($rating['attribs']['']['scheme']))
					{
						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$rating_scheme = 'urn:simple';
					}
					if (isset($rating['data']))
					{
						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
				}
			}
			elseif ($ratings = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
			{
				foreach ($ratings as $rating)
				{
					$rating_scheme = 'urn:itunes';
					$rating_value = null;
					if (isset($rating['data']))
					{
						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
				}
			}
			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'rating'))
			{
				foreach ($ratings as $rating)
				{
					$rating_scheme = null;
					$rating_value = null;
					if (isset($rating['attribs']['']['scheme']))
					{
						$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					else
					{
						$rating_scheme = 'urn:simple';
					}
					if (isset($rating['data']))
					{
						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
				}
			}
			elseif ($ratings = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'explicit'))
			{
				foreach ($ratings as $rating)
				{
					$rating_scheme = 'urn:itunes';
					$rating_value = null;
					if (isset($rating['data']))
					{
						$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$ratings_parent[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
				}
			}
			if (is_array($ratings_parent))
			{
				$ratings_parent = array_values(array_unique($ratings_parent));
			}

			// RESTRICTIONS
			if ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
			{
				foreach ($restrictions as $restriction)
				{
					$restriction_relationship = null;
					$restriction_type = null;
					$restriction_value = null;
					if (isset($restriction['attribs']['']['relationship']))
					{
						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($restriction['attribs']['']['type']))
					{
						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($restriction['data']))
					{
						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
				}
			}
			elseif ($restrictions = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
			{
				foreach ($restrictions as $restriction)
				{
					$restriction_relationship = 'allow';
					$restriction_type = null;
					$restriction_value = 'itunes';
					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
					{
						$restriction_relationship = 'deny';
					}
					$restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
				}
			}
			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'restriction'))
			{
				foreach ($restrictions as $restriction)
				{
					$restriction_relationship = null;
					$restriction_type = null;
					$restriction_value = null;
					if (isset($restriction['attribs']['']['relationship']))
					{
						$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($restriction['attribs']['']['type']))
					{
						$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($restriction['data']))
					{
						$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					$restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
				}
			}
			elseif ($restrictions = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'block'))
			{
				foreach ($restrictions as $restriction)
				{
					$restriction_relationship = 'allow';
					$restriction_type = null;
					$restriction_value = 'itunes';
					if (isset($restriction['data']) && strtolower($restriction['data']) === 'yes')
					{
						$restriction_relationship = 'deny';
					}
					$restrictions_parent[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
				}
			}
			if (is_array($restrictions_parent))
			{
				$restrictions_parent = array_values(array_unique($restrictions_parent));
			}
			else
			{
				$restrictions_parent = array(new SimplePie_Restriction('allow', null, 'default'));
			}

			// THUMBNAILS
			if ($thumbnails = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
			{
				foreach ($thumbnails as $thumbnail)
				{
					if (isset($thumbnail['attribs']['']['url']))
					{
						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
					}
				}
			}
			elseif ($thumbnails = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
			{
				foreach ($thumbnails as $thumbnail)
				{
					if (isset($thumbnail['attribs']['']['url']))
					{
						$thumbnails_parent[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
					}
				}
			}

			// TITLES
			if ($title_parent = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
			{
				if (isset($title_parent[0]['data']))
				{
					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
			}
			elseif ($title_parent = $parent->get_channel_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'title'))
			{
				if (isset($title_parent[0]['data']))
				{
					$title_parent = $this->sanitize($title_parent[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
				}
			}

			// Clear the memory
			unset($parent);

			// Attributes
			$bitrate = null;
			$channels = null;
			$duration = null;
			$expression = null;
			$framerate = null;
			$height = null;
			$javascript = null;
			$lang = null;
			$length = null;
			$medium = null;
			$samplingrate = null;
			$type = null;
			$url = null;
			$width = null;

			// Elements
			$captions = null;
			$categories = null;
			$copyrights = null;
			$credits = null;
			$description = null;
			$hashes = null;
			$keywords = null;
			$player = null;
			$ratings = null;
			$restrictions = null;
			$thumbnails = null;
			$title = null;

			// If we have media:group tags, loop through them.
			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group') as $group)
			{
				if(isset($group['child']) && isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']))
				{
					// If we have media:content tags, loop through them.
					foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
					{
						if (isset($content['attribs']['']['url']))
						{
							// Attributes
							$bitrate = null;
							$channels = null;
							$duration = null;
							$expression = null;
							$framerate = null;
							$height = null;
							$javascript = null;
							$lang = null;
							$length = null;
							$medium = null;
							$samplingrate = null;
							$type = null;
							$url = null;
							$width = null;

							// Elements
							$captions = null;
							$categories = null;
							$copyrights = null;
							$credits = null;
							$description = null;
							$hashes = null;
							$keywords = null;
							$player = null;
							$ratings = null;
							$restrictions = null;
							$thumbnails = null;
							$title = null;

							// Start checking the attributes of media:content
							if (isset($content['attribs']['']['bitrate']))
							{
								$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['channels']))
							{
								$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['duration']))
							{
								$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							else
							{
								$duration = $duration_parent;
							}
							if (isset($content['attribs']['']['expression']))
							{
								$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['framerate']))
							{
								$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['height']))
							{
								$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['lang']))
							{
								$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['fileSize']))
							{
								$length = ceil($content['attribs']['']['fileSize']);
							}
							if (isset($content['attribs']['']['medium']))
							{
								$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['samplingrate']))
							{
								$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['type']))
							{
								$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['attribs']['']['width']))
							{
								$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);

							// Checking the other optional media: elements. Priority: media:content, media:group, item, channel

							// CAPTIONS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
								{
									$caption_type = null;
									$caption_lang = null;
									$caption_startTime = null;
									$caption_endTime = null;
									$caption_text = null;
									if (isset($caption['attribs']['']['type']))
									{
										$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['lang']))
									{
										$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['start']))
									{
										$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['end']))
									{
										$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['data']))
									{
										$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text));
								}
								if (is_array($captions))
								{
									$captions = array_values(array_unique($captions));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
								{
									$caption_type = null;
									$caption_lang = null;
									$caption_startTime = null;
									$caption_endTime = null;
									$caption_text = null;
									if (isset($caption['attribs']['']['type']))
									{
										$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['lang']))
									{
										$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['start']))
									{
										$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['attribs']['']['end']))
									{
										$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($caption['data']))
									{
										$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text));
								}
								if (is_array($captions))
								{
									$captions = array_values(array_unique($captions));
								}
							}
							else
							{
								$captions = $captions_parent;
							}

							// CATEGORIES
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
							{
								foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
								{
									$term = null;
									$scheme = null;
									$label = null;
									if (isset($category['data']))
									{
										$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($category['attribs']['']['scheme']))
									{
										$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$scheme = 'http://search.yahoo.com/mrss/category_schema';
									}
									if (isset($category['attribs']['']['label']))
									{
										$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
								}
							}
							if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
							{
								foreach ((array) $group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
								{
									$term = null;
									$scheme = null;
									$label = null;
									if (isset($category['data']))
									{
										$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($category['attribs']['']['scheme']))
									{
										$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$scheme = 'http://search.yahoo.com/mrss/category_schema';
									}
									if (isset($category['attribs']['']['label']))
									{
										$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
								}
							}
							if (is_array($categories) && is_array($categories_parent))
							{
								$categories = array_values(array_unique(array_merge($categories, $categories_parent)));
							}
							elseif (is_array($categories))
							{
								$categories = array_values(array_unique($categories));
							}
							elseif (is_array($categories_parent))
							{
								$categories = array_values(array_unique($categories_parent));
							}

							// COPYRIGHTS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
							{
								$copyright_url = null;
								$copyright_label = null;
								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
								{
									$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
								{
									$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label));
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
							{
								$copyright_url = null;
								$copyright_label = null;
								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
								{
									$copyright_url = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
								{
									$copyright_label = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label));
							}
							else
							{
								$copyrights = $copyrights_parent;
							}

							// CREDITS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
								{
									$credit_role = null;
									$credit_scheme = null;
									$credit_name = null;
									if (isset($credit['attribs']['']['role']))
									{
										$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($credit['attribs']['']['scheme']))
									{
										$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$credit_scheme = 'urn:ebu';
									}
									if (isset($credit['data']))
									{
										$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name));
								}
								if (is_array($credits))
								{
									$credits = array_values(array_unique($credits));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
								{
									$credit_role = null;
									$credit_scheme = null;
									$credit_name = null;
									if (isset($credit['attribs']['']['role']))
									{
										$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($credit['attribs']['']['scheme']))
									{
										$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$credit_scheme = 'urn:ebu';
									}
									if (isset($credit['data']))
									{
										$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name));
								}
								if (is_array($credits))
								{
									$credits = array_values(array_unique($credits));
								}
							}
							else
							{
								$credits = $credits_parent;
							}

							// DESCRIPTION
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
							{
								$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
							{
								$description = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							else
							{
								$description = $description_parent;
							}

							// HASHES
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
								{
									$value = null;
									$algo = null;
									if (isset($hash['data']))
									{
										$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($hash['attribs']['']['algo']))
									{
										$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$algo = 'md5';
									}
									$hashes[] = $algo.':'.$value;
								}
								if (is_array($hashes))
								{
									$hashes = array_values(array_unique($hashes));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
								{
									$value = null;
									$algo = null;
									if (isset($hash['data']))
									{
										$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($hash['attribs']['']['algo']))
									{
										$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$algo = 'md5';
									}
									$hashes[] = $algo.':'.$value;
								}
								if (is_array($hashes))
								{
									$hashes = array_values(array_unique($hashes));
								}
							}
							else
							{
								$hashes = $hashes_parent;
							}

							// KEYWORDS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
							{
								if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
								{
									$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
									foreach ($temp as $word)
									{
										$keywords[] = trim($word);
									}
									unset($temp);
								}
								if (is_array($keywords))
								{
									$keywords = array_values(array_unique($keywords));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
							{
								if (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
								{
									$temp = explode(',', $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
									foreach ($temp as $word)
									{
										$keywords[] = trim($word);
									}
									unset($temp);
								}
								if (is_array($keywords))
								{
									$keywords = array_values(array_unique($keywords));
								}
							}
							else
							{
								$keywords = $keywords_parent;
							}

							// PLAYER
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
							{
								$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
							{
								$player = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
							}
							else
							{
								$player = $player_parent;
							}

							// RATINGS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
								{
									$rating_scheme = null;
									$rating_value = null;
									if (isset($rating['attribs']['']['scheme']))
									{
										$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$rating_scheme = 'urn:simple';
									}
									if (isset($rating['data']))
									{
										$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
								}
								if (is_array($ratings))
								{
									$ratings = array_values(array_unique($ratings));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
								{
									$rating_scheme = null;
									$rating_value = null;
									if (isset($rating['attribs']['']['scheme']))
									{
										$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									else
									{
										$rating_scheme = 'urn:simple';
									}
									if (isset($rating['data']))
									{
										$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
								}
								if (is_array($ratings))
								{
									$ratings = array_values(array_unique($ratings));
								}
							}
							else
							{
								$ratings = $ratings_parent;
							}

							// RESTRICTIONS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
								{
									$restriction_relationship = null;
									$restriction_type = null;
									$restriction_value = null;
									if (isset($restriction['attribs']['']['relationship']))
									{
										$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($restriction['attribs']['']['type']))
									{
										$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($restriction['data']))
									{
										$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
								}
								if (is_array($restrictions))
								{
									$restrictions = array_values(array_unique($restrictions));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
								{
									$restriction_relationship = null;
									$restriction_type = null;
									$restriction_value = null;
									if (isset($restriction['attribs']['']['relationship']))
									{
										$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($restriction['attribs']['']['type']))
									{
										$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									if (isset($restriction['data']))
									{
										$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
									}
									$restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
								}
								if (is_array($restrictions))
								{
									$restrictions = array_values(array_unique($restrictions));
								}
							}
							else
							{
								$restrictions = $restrictions_parent;
							}

							// THUMBNAILS
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
							{
								foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
								{
									$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
								}
								if (is_array($thumbnails))
								{
									$thumbnails = array_values(array_unique($thumbnails));
								}
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
							{
								foreach ($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
								{
									$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
								}
								if (is_array($thumbnails))
								{
									$thumbnails = array_values(array_unique($thumbnails));
								}
							}
							else
							{
								$thumbnails = $thumbnails_parent;
							}

							// TITLES
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
							{
								$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							elseif (isset($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
							{
								$title = $this->sanitize($group['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							else
							{
								$title = $title_parent;
							}

							$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width));
						}
					}
				}
			}

			// If we have standalone media:content tags, loop through them.
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content']))
			{
				foreach ((array) $this->data['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'] as $content)
				{
					if (isset($content['attribs']['']['url']) || isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
					{
						// Attributes
						$bitrate = null;
						$channels = null;
						$duration = null;
						$expression = null;
						$framerate = null;
						$height = null;
						$javascript = null;
						$lang = null;
						$length = null;
						$medium = null;
						$samplingrate = null;
						$type = null;
						$url = null;
						$width = null;

						// Elements
						$captions = null;
						$categories = null;
						$copyrights = null;
						$credits = null;
						$description = null;
						$hashes = null;
						$keywords = null;
						$player = null;
						$ratings = null;
						$restrictions = null;
						$thumbnails = null;
						$title = null;

						// Start checking the attributes of media:content
						if (isset($content['attribs']['']['bitrate']))
						{
							$bitrate = $this->sanitize($content['attribs']['']['bitrate'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['channels']))
						{
							$channels = $this->sanitize($content['attribs']['']['channels'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['duration']))
						{
							$duration = $this->sanitize($content['attribs']['']['duration'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						else
						{
							$duration = $duration_parent;
						}
						if (isset($content['attribs']['']['expression']))
						{
							$expression = $this->sanitize($content['attribs']['']['expression'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['framerate']))
						{
							$framerate = $this->sanitize($content['attribs']['']['framerate'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['height']))
						{
							$height = $this->sanitize($content['attribs']['']['height'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['lang']))
						{
							$lang = $this->sanitize($content['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['fileSize']))
						{
							$length = ceil($content['attribs']['']['fileSize']);
						}
						if (isset($content['attribs']['']['medium']))
						{
							$medium = $this->sanitize($content['attribs']['']['medium'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['samplingrate']))
						{
							$samplingrate = $this->sanitize($content['attribs']['']['samplingrate'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['type']))
						{
							$type = $this->sanitize($content['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['width']))
						{
							$width = $this->sanitize($content['attribs']['']['width'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						if (isset($content['attribs']['']['url']))
						{
							$url = $this->sanitize($content['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
						}
						// Checking the other optional media: elements. Priority: media:content, media:group, item, channel

						// CAPTIONS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['text'] as $caption)
							{
								$caption_type = null;
								$caption_lang = null;
								$caption_startTime = null;
								$caption_endTime = null;
								$caption_text = null;
								if (isset($caption['attribs']['']['type']))
								{
									$caption_type = $this->sanitize($caption['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($caption['attribs']['']['lang']))
								{
									$caption_lang = $this->sanitize($caption['attribs']['']['lang'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($caption['attribs']['']['start']))
								{
									$caption_startTime = $this->sanitize($caption['attribs']['']['start'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($caption['attribs']['']['end']))
								{
									$caption_endTime = $this->sanitize($caption['attribs']['']['end'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($caption['data']))
								{
									$caption_text = $this->sanitize($caption['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$captions[] = $this->registry->create('Caption', array($caption_type, $caption_lang, $caption_startTime, $caption_endTime, $caption_text));
							}
							if (is_array($captions))
							{
								$captions = array_values(array_unique($captions));
							}
						}
						else
						{
							$captions = $captions_parent;
						}

						// CATEGORIES
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category']))
						{
							foreach ((array) $content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['category'] as $category)
							{
								$term = null;
								$scheme = null;
								$label = null;
								if (isset($category['data']))
								{
									$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($category['attribs']['']['scheme']))
								{
									$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								else
								{
									$scheme = 'http://search.yahoo.com/mrss/category_schema';
								}
								if (isset($category['attribs']['']['label']))
								{
									$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
							}
						}
						if (is_array($categories) && is_array($categories_parent))
						{
							$categories = array_values(array_unique(array_merge($categories, $categories_parent)));
						}
						elseif (is_array($categories))
						{
							$categories = array_values(array_unique($categories));
						}
						elseif (is_array($categories_parent))
						{
							$categories = array_values(array_unique($categories_parent));
						}
						else
						{
							$categories = null;
						}

						// COPYRIGHTS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright']))
						{
							$copyright_url = null;
							$copyright_label = null;
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url']))
							{
								$copyright_url = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data']))
							{
								$copyright_label = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['copyright'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
							}
							$copyrights = $this->registry->create('Copyright', array($copyright_url, $copyright_label));
						}
						else
						{
							$copyrights = $copyrights_parent;
						}

						// CREDITS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['credit'] as $credit)
							{
								$credit_role = null;
								$credit_scheme = null;
								$credit_name = null;
								if (isset($credit['attribs']['']['role']))
								{
									$credit_role = $this->sanitize($credit['attribs']['']['role'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($credit['attribs']['']['scheme']))
								{
									$credit_scheme = $this->sanitize($credit['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								else
								{
									$credit_scheme = 'urn:ebu';
								}
								if (isset($credit['data']))
								{
									$credit_name = $this->sanitize($credit['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$credits[] = $this->registry->create('Credit', array($credit_role, $credit_scheme, $credit_name));
							}
							if (is_array($credits))
							{
								$credits = array_values(array_unique($credits));
							}
						}
						else
						{
							$credits = $credits_parent;
						}

						// DESCRIPTION
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description']))
						{
							$description = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['description'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						else
						{
							$description = $description_parent;
						}

						// HASHES
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['hash'] as $hash)
							{
								$value = null;
								$algo = null;
								if (isset($hash['data']))
								{
									$value = $this->sanitize($hash['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($hash['attribs']['']['algo']))
								{
									$algo = $this->sanitize($hash['attribs']['']['algo'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								else
								{
									$algo = 'md5';
								}
								$hashes[] = $algo.':'.$value;
							}
							if (is_array($hashes))
							{
								$hashes = array_values(array_unique($hashes));
							}
						}
						else
						{
							$hashes = $hashes_parent;
						}

						// KEYWORDS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords']))
						{
							if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data']))
							{
								$temp = explode(',', $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['keywords'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT));
								foreach ($temp as $word)
								{
									$keywords[] = trim($word);
								}
								unset($temp);
							}
							if (is_array($keywords))
							{
								$keywords = array_values(array_unique($keywords));
							}
						}
						else
						{
							$keywords = $keywords_parent;
						}

						// PLAYER
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player']))
						{
							$player = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['player'][0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
						}
						else
						{
							$player = $player_parent;
						}

						// RATINGS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['rating'] as $rating)
							{
								$rating_scheme = null;
								$rating_value = null;
								if (isset($rating['attribs']['']['scheme']))
								{
									$rating_scheme = $this->sanitize($rating['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								else
								{
									$rating_scheme = 'urn:simple';
								}
								if (isset($rating['data']))
								{
									$rating_value = $this->sanitize($rating['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$ratings[] = $this->registry->create('Rating', array($rating_scheme, $rating_value));
							}
							if (is_array($ratings))
							{
								$ratings = array_values(array_unique($ratings));
							}
						}
						else
						{
							$ratings = $ratings_parent;
						}

						// RESTRICTIONS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['restriction'] as $restriction)
							{
								$restriction_relationship = null;
								$restriction_type = null;
								$restriction_value = null;
								if (isset($restriction['attribs']['']['relationship']))
								{
									$restriction_relationship = $this->sanitize($restriction['attribs']['']['relationship'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($restriction['attribs']['']['type']))
								{
									$restriction_type = $this->sanitize($restriction['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								if (isset($restriction['data']))
								{
									$restriction_value = $this->sanitize($restriction['data'], SIMPLEPIE_CONSTRUCT_TEXT);
								}
								$restrictions[] = $this->registry->create('Restriction', array($restriction_relationship, $restriction_type, $restriction_value));
							}
							if (is_array($restrictions))
							{
								$restrictions = array_values(array_unique($restrictions));
							}
						}
						else
						{
							$restrictions = $restrictions_parent;
						}

						// THUMBNAILS
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail']))
						{
							foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
							{
								$thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
							}
							if (is_array($thumbnails))
							{
								$thumbnails = array_values(array_unique($thumbnails));
							}
						}
						else
						{
							$thumbnails = $thumbnails_parent;
						}

						// TITLES
						if (isset($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title']))
						{
							$title = $this->sanitize($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['title'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
						}
						else
						{
							$title = $title_parent;
						}

						$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions, $categories, $channels, $copyrights, $credits, $description, $duration, $expression, $framerate, $hashes, $height, $keywords, $lang, $medium, $player, $ratings, $restrictions, $samplingrate, $thumbnails, $title, $width));
					}
				}
			}

			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link') as $link)
			{
				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
				{
					// Attributes
					$bitrate = null;
					$channels = null;
					$duration = null;
					$expression = null;
					$framerate = null;
					$height = null;
					$javascript = null;
					$lang = null;
					$length = null;
					$medium = null;
					$samplingrate = null;
					$type = null;
					$url = null;
					$width = null;

					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
					if (isset($link['attribs']['']['type']))
					{
						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($link['attribs']['']['length']))
					{
						$length = ceil($link['attribs']['']['length']);
					}

					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
					$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width));
				}
			}

			foreach ((array) $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link') as $link)
			{
				if (isset($link['attribs']['']['href']) && !empty($link['attribs']['']['rel']) && $link['attribs']['']['rel'] === 'enclosure')
				{
					// Attributes
					$bitrate = null;
					$channels = null;
					$duration = null;
					$expression = null;
					$framerate = null;
					$height = null;
					$javascript = null;
					$lang = null;
					$length = null;
					$medium = null;
					$samplingrate = null;
					$type = null;
					$url = null;
					$width = null;

					$url = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
					if (isset($link['attribs']['']['type']))
					{
						$type = $this->sanitize($link['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($link['attribs']['']['length']))
					{
						$length = ceil($link['attribs']['']['length']);
					}

					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
					$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width));
				}
			}

			if ($enclosure = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'enclosure'))
			{
				if (isset($enclosure[0]['attribs']['']['url']))
				{
					// Attributes
					$bitrate = null;
					$channels = null;
					$duration = null;
					$expression = null;
					$framerate = null;
					$height = null;
					$javascript = null;
					$lang = null;
					$length = null;
					$medium = null;
					$samplingrate = null;
					$type = null;
					$url = null;
					$width = null;

					$url = $this->sanitize($enclosure[0]['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($enclosure[0]));
					if (isset($enclosure[0]['attribs']['']['type']))
					{
						$type = $this->sanitize($enclosure[0]['attribs']['']['type'], SIMPLEPIE_CONSTRUCT_TEXT);
					}
					if (isset($enclosure[0]['attribs']['']['length']))
					{
						$length = ceil($enclosure[0]['attribs']['']['length']);
					}

					// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
					$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width));
				}
			}

			if (sizeof($this->data['enclosures']) === 0 && ($url || $type || $length || $bitrate || $captions_parent || $categories_parent || $channels || $copyrights_parent || $credits_parent || $description_parent || $duration_parent || $expression || $framerate || $hashes_parent || $height || $keywords_parent || $lang || $medium || $player_parent || $ratings_parent || $restrictions_parent || $samplingrate || $thumbnails_parent || $title_parent || $width))
			{
				// Since we don't have group or content for these, we'll just pass the '*_parent' variables directly to the constructor
				$this->data['enclosures'][] = $this->registry->create('Enclosure', array($url, $type, $length, null, $bitrate, $captions_parent, $categories_parent, $channels, $copyrights_parent, $credits_parent, $description_parent, $duration_parent, $expression, $framerate, $hashes_parent, $height, $keywords_parent, $lang, $medium, $player_parent, $ratings_parent, $restrictions_parent, $samplingrate, $thumbnails_parent, $title_parent, $width));
			}

			$this->data['enclosures'] = array_values(array_unique($this->data['enclosures']));
		}
		if (!empty($this->data['enclosures']))
		{
			return $this->data['enclosures'];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the latitude coordinates for the item
	 *
	 * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
	 *
	 * Uses `<geo:lat>` or `<georss:point>`
	 *
	 * @since 1.0
	 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
	 * @link http://www.georss.org/ GeoRSS
	 * @return string|null
	 */
	public function get_latitude()
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[1];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the longitude coordinates for the item
	 *
	 * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
	 *
	 * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>`
	 *
	 * @since 1.0
	 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
	 * @link http://www.georss.org/ GeoRSS
	 * @return string|null
	 */
	public function get_longitude()
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
		{
			return (float) $return[0]['data'];
		}
		elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[2];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the `<atom:source>` for the item
	 *
	 * @since 1.1
	 * @return SimplePie_Source|null
	 */
	public function get_source()
	{
		if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'source'))
		{
			return $this->registry->create('Source', array($this, $return[0]));
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Caption.php000064400000010636152177723700017501 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Handles `<media:text>` captions as defined in Media RSS.
 *
 * Used by {@see SimplePie_Enclosure::get_caption()} and {@see SimplePie_Enclosure::get_captions()}
 *
 * This class can be overloaded with {@see SimplePie::set_caption_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Caption
{
	/**
	 * Content type
	 *
	 * @var string
	 * @see get_type()
	 */
	var $type;

	/**
	 * Language
	 *
	 * @var string
	 * @see get_language()
	 */
	var $lang;

	/**
	 * Start time
	 *
	 * @var string
	 * @see get_starttime()
	 */
	var $startTime;

	/**
	 * End time
	 *
	 * @var string
	 * @see get_endtime()
	 */
	var $endTime;

	/**
	 * Caption text
	 *
	 * @var string
	 * @see get_text()
	 */
	var $text;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 */
	public function __construct($type = null, $lang = null, $startTime = null, $endTime = null, $text = null)
	{
		$this->type = $type;
		$this->lang = $lang;
		$this->startTime = $startTime;
		$this->endTime = $endTime;
		$this->text = $text;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the end time
	 *
	 * @return string|null Time in the format 'hh:mm:ss.SSS'
	 */
	public function get_endtime()
	{
		if ($this->endTime !== null)
		{
			return $this->endTime;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the language
	 *
	 * @link http://tools.ietf.org/html/rfc3066
	 * @return string|null Language code as per RFC 3066
	 */
	public function get_language()
	{
		if ($this->lang !== null)
		{
			return $this->lang;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the start time
	 *
	 * @return string|null Time in the format 'hh:mm:ss.SSS'
	 */
	public function get_starttime()
	{
		if ($this->startTime !== null)
		{
			return $this->startTime;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the text of the caption
	 *
	 * @return string|null
	 */
	public function get_text()
	{
		if ($this->text !== null)
		{
			return $this->text;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the content type (not MIME type)
	 *
	 * @return string|null Either 'text' or 'html'
	 */
	public function get_type()
	{
		if ($this->type !== null)
		{
			return $this->type;
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Decode/HTML/Entities.php000064400000041651152177723700021620 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Decode HTML Entities
 *
 * This implements HTML5 as of revision 967 (2007-06-28)
 *
 * @deprecated Use DOMDocument instead!
 * @package SimplePie
 */
class SimplePie_Decode_HTML_Entities
{
	/**
	 * Data to be parsed
	 *
	 * @access private
	 * @var string
	 */
	var $data = '';

	/**
	 * Currently consumed bytes
	 *
	 * @access private
	 * @var string
	 */
	var $consumed = '';

	/**
	 * Position of the current byte being parsed
	 *
	 * @access private
	 * @var int
	 */
	var $position = 0;

	/**
	 * Create an instance of the class with the input data
	 *
	 * @access public
	 * @param string $data Input data
	 */
	public function __construct($data)
	{
		$this->data = $data;
	}

	/**
	 * Parse the input data
	 *
	 * @access public
	 * @return string Output data
	 */
	public function parse()
	{
		while (($this->position = strpos($this->data, '&', $this->position)) !== false)
		{
			$this->consume();
			$this->entity();
			$this->consumed = '';
		}
		return $this->data;
	}

	/**
	 * Consume the next byte
	 *
	 * @access private
	 * @return mixed The next byte, or false, if there is no more data
	 */
	public function consume()
	{
		if (isset($this->data[$this->position]))
		{
			$this->consumed .= $this->data[$this->position];
			return $this->data[$this->position++];
		}
		else
		{
			return false;
		}
	}

	/**
	 * Consume a range of characters
	 *
	 * @access private
	 * @param string $chars Characters to consume
	 * @return mixed A series of characters that match the range, or false
	 */
	public function consume_range($chars)
	{
		if ($len = strspn($this->data, $chars, $this->position))
		{
			$data = substr($this->data, $this->position, $len);
			$this->consumed .= $data;
			$this->position += $len;
			return $data;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Unconsume one byte
	 *
	 * @access private
	 */
	public function unconsume()
	{
		$this->consumed = substr($this->consumed, 0, -1);
		$this->position--;
	}

	/**
	 * Decode an entity
	 *
	 * @access private
	 */
	public function entity()
	{
		switch ($this->consume())
		{
			case "\x09":
			case "\x0A":
			case "\x0B":
			case "\x0B":
			case "\x0C":
			case "\x20":
			case "\x3C":
			case "\x26":
			case false:
				break;

			case "\x23":
				switch ($this->consume())
				{
					case "\x78":
					case "\x58":
						$range = '0123456789ABCDEFabcdef';
						$hex = true;
						break;

					default:
						$range = '0123456789';
						$hex = false;
						$this->unconsume();
						break;
				}

				if ($codepoint = $this->consume_range($range))
				{
					static $windows_1252_specials = array(0x0D => "\x0A", 0x80 => "\xE2\x82\xAC", 0x81 => "\xEF\xBF\xBD", 0x82 => "\xE2\x80\x9A", 0x83 => "\xC6\x92", 0x84 => "\xE2\x80\x9E", 0x85 => "\xE2\x80\xA6", 0x86 => "\xE2\x80\xA0", 0x87 => "\xE2\x80\xA1", 0x88 => "\xCB\x86", 0x89 => "\xE2\x80\xB0", 0x8A => "\xC5\xA0", 0x8B => "\xE2\x80\xB9", 0x8C => "\xC5\x92", 0x8D => "\xEF\xBF\xBD", 0x8E => "\xC5\xBD", 0x8F => "\xEF\xBF\xBD", 0x90 => "\xEF\xBF\xBD", 0x91 => "\xE2\x80\x98", 0x92 => "\xE2\x80\x99", 0x93 => "\xE2\x80\x9C", 0x94 => "\xE2\x80\x9D", 0x95 => "\xE2\x80\xA2", 0x96 => "\xE2\x80\x93", 0x97 => "\xE2\x80\x94", 0x98 => "\xCB\x9C", 0x99 => "\xE2\x84\xA2", 0x9A => "\xC5\xA1", 0x9B => "\xE2\x80\xBA", 0x9C => "\xC5\x93", 0x9D => "\xEF\xBF\xBD", 0x9E => "\xC5\xBE", 0x9F => "\xC5\xB8");

					if ($hex)
					{
						$codepoint = hexdec($codepoint);
					}
					else
					{
						$codepoint = intval($codepoint);
					}

					if (isset($windows_1252_specials[$codepoint]))
					{
						$replacement = $windows_1252_specials[$codepoint];
					}
					else
					{
						$replacement = SimplePie_Misc::codepoint_to_utf8($codepoint);
					}

					if (!in_array($this->consume(), array(';', false), true))
					{
						$this->unconsume();
					}

					$consumed_length = strlen($this->consumed);
					$this->data = substr_replace($this->data, $replacement, $this->position - $consumed_length, $consumed_length);
					$this->position += strlen($replacement) - $consumed_length;
				}
				break;

			default:
				static $entities = array(
					'Aacute' => "\xC3\x81",
					'aacute' => "\xC3\xA1",
					'Aacute;' => "\xC3\x81",
					'aacute;' => "\xC3\xA1",
					'Acirc' => "\xC3\x82",
					'acirc' => "\xC3\xA2",
					'Acirc;' => "\xC3\x82",
					'acirc;' => "\xC3\xA2",
					'acute' => "\xC2\xB4",
					'acute;' => "\xC2\xB4",
					'AElig' => "\xC3\x86",
					'aelig' => "\xC3\xA6",
					'AElig;' => "\xC3\x86",
					'aelig;' => "\xC3\xA6",
					'Agrave' => "\xC3\x80",
					'agrave' => "\xC3\xA0",
					'Agrave;' => "\xC3\x80",
					'agrave;' => "\xC3\xA0",
					'alefsym;' => "\xE2\x84\xB5",
					'Alpha;' => "\xCE\x91",
					'alpha;' => "\xCE\xB1",
					'AMP' => "\x26",
					'amp' => "\x26",
					'AMP;' => "\x26",
					'amp;' => "\x26",
					'and;' => "\xE2\x88\xA7",
					'ang;' => "\xE2\x88\xA0",
					'apos;' => "\x27",
					'Aring' => "\xC3\x85",
					'aring' => "\xC3\xA5",
					'Aring;' => "\xC3\x85",
					'aring;' => "\xC3\xA5",
					'asymp;' => "\xE2\x89\x88",
					'Atilde' => "\xC3\x83",
					'atilde' => "\xC3\xA3",
					'Atilde;' => "\xC3\x83",
					'atilde;' => "\xC3\xA3",
					'Auml' => "\xC3\x84",
					'auml' => "\xC3\xA4",
					'Auml;' => "\xC3\x84",
					'auml;' => "\xC3\xA4",
					'bdquo;' => "\xE2\x80\x9E",
					'Beta;' => "\xCE\x92",
					'beta;' => "\xCE\xB2",
					'brvbar' => "\xC2\xA6",
					'brvbar;' => "\xC2\xA6",
					'bull;' => "\xE2\x80\xA2",
					'cap;' => "\xE2\x88\xA9",
					'Ccedil' => "\xC3\x87",
					'ccedil' => "\xC3\xA7",
					'Ccedil;' => "\xC3\x87",
					'ccedil;' => "\xC3\xA7",
					'cedil' => "\xC2\xB8",
					'cedil;' => "\xC2\xB8",
					'cent' => "\xC2\xA2",
					'cent;' => "\xC2\xA2",
					'Chi;' => "\xCE\xA7",
					'chi;' => "\xCF\x87",
					'circ;' => "\xCB\x86",
					'clubs;' => "\xE2\x99\xA3",
					'cong;' => "\xE2\x89\x85",
					'COPY' => "\xC2\xA9",
					'copy' => "\xC2\xA9",
					'COPY;' => "\xC2\xA9",
					'copy;' => "\xC2\xA9",
					'crarr;' => "\xE2\x86\xB5",
					'cup;' => "\xE2\x88\xAA",
					'curren' => "\xC2\xA4",
					'curren;' => "\xC2\xA4",
					'Dagger;' => "\xE2\x80\xA1",
					'dagger;' => "\xE2\x80\xA0",
					'dArr;' => "\xE2\x87\x93",
					'darr;' => "\xE2\x86\x93",
					'deg' => "\xC2\xB0",
					'deg;' => "\xC2\xB0",
					'Delta;' => "\xCE\x94",
					'delta;' => "\xCE\xB4",
					'diams;' => "\xE2\x99\xA6",
					'divide' => "\xC3\xB7",
					'divide;' => "\xC3\xB7",
					'Eacute' => "\xC3\x89",
					'eacute' => "\xC3\xA9",
					'Eacute;' => "\xC3\x89",
					'eacute;' => "\xC3\xA9",
					'Ecirc' => "\xC3\x8A",
					'ecirc' => "\xC3\xAA",
					'Ecirc;' => "\xC3\x8A",
					'ecirc;' => "\xC3\xAA",
					'Egrave' => "\xC3\x88",
					'egrave' => "\xC3\xA8",
					'Egrave;' => "\xC3\x88",
					'egrave;' => "\xC3\xA8",
					'empty;' => "\xE2\x88\x85",
					'emsp;' => "\xE2\x80\x83",
					'ensp;' => "\xE2\x80\x82",
					'Epsilon;' => "\xCE\x95",
					'epsilon;' => "\xCE\xB5",
					'equiv;' => "\xE2\x89\xA1",
					'Eta;' => "\xCE\x97",
					'eta;' => "\xCE\xB7",
					'ETH' => "\xC3\x90",
					'eth' => "\xC3\xB0",
					'ETH;' => "\xC3\x90",
					'eth;' => "\xC3\xB0",
					'Euml' => "\xC3\x8B",
					'euml' => "\xC3\xAB",
					'Euml;' => "\xC3\x8B",
					'euml;' => "\xC3\xAB",
					'euro;' => "\xE2\x82\xAC",
					'exist;' => "\xE2\x88\x83",
					'fnof;' => "\xC6\x92",
					'forall;' => "\xE2\x88\x80",
					'frac12' => "\xC2\xBD",
					'frac12;' => "\xC2\xBD",
					'frac14' => "\xC2\xBC",
					'frac14;' => "\xC2\xBC",
					'frac34' => "\xC2\xBE",
					'frac34;' => "\xC2\xBE",
					'frasl;' => "\xE2\x81\x84",
					'Gamma;' => "\xCE\x93",
					'gamma;' => "\xCE\xB3",
					'ge;' => "\xE2\x89\xA5",
					'GT' => "\x3E",
					'gt' => "\x3E",
					'GT;' => "\x3E",
					'gt;' => "\x3E",
					'hArr;' => "\xE2\x87\x94",
					'harr;' => "\xE2\x86\x94",
					'hearts;' => "\xE2\x99\xA5",
					'hellip;' => "\xE2\x80\xA6",
					'Iacute' => "\xC3\x8D",
					'iacute' => "\xC3\xAD",
					'Iacute;' => "\xC3\x8D",
					'iacute;' => "\xC3\xAD",
					'Icirc' => "\xC3\x8E",
					'icirc' => "\xC3\xAE",
					'Icirc;' => "\xC3\x8E",
					'icirc;' => "\xC3\xAE",
					'iexcl' => "\xC2\xA1",
					'iexcl;' => "\xC2\xA1",
					'Igrave' => "\xC3\x8C",
					'igrave' => "\xC3\xAC",
					'Igrave;' => "\xC3\x8C",
					'igrave;' => "\xC3\xAC",
					'image;' => "\xE2\x84\x91",
					'infin;' => "\xE2\x88\x9E",
					'int;' => "\xE2\x88\xAB",
					'Iota;' => "\xCE\x99",
					'iota;' => "\xCE\xB9",
					'iquest' => "\xC2\xBF",
					'iquest;' => "\xC2\xBF",
					'isin;' => "\xE2\x88\x88",
					'Iuml' => "\xC3\x8F",
					'iuml' => "\xC3\xAF",
					'Iuml;' => "\xC3\x8F",
					'iuml;' => "\xC3\xAF",
					'Kappa;' => "\xCE\x9A",
					'kappa;' => "\xCE\xBA",
					'Lambda;' => "\xCE\x9B",
					'lambda;' => "\xCE\xBB",
					'lang;' => "\xE3\x80\x88",
					'laquo' => "\xC2\xAB",
					'laquo;' => "\xC2\xAB",
					'lArr;' => "\xE2\x87\x90",
					'larr;' => "\xE2\x86\x90",
					'lceil;' => "\xE2\x8C\x88",
					'ldquo;' => "\xE2\x80\x9C",
					'le;' => "\xE2\x89\xA4",
					'lfloor;' => "\xE2\x8C\x8A",
					'lowast;' => "\xE2\x88\x97",
					'loz;' => "\xE2\x97\x8A",
					'lrm;' => "\xE2\x80\x8E",
					'lsaquo;' => "\xE2\x80\xB9",
					'lsquo;' => "\xE2\x80\x98",
					'LT' => "\x3C",
					'lt' => "\x3C",
					'LT;' => "\x3C",
					'lt;' => "\x3C",
					'macr' => "\xC2\xAF",
					'macr;' => "\xC2\xAF",
					'mdash;' => "\xE2\x80\x94",
					'micro' => "\xC2\xB5",
					'micro;' => "\xC2\xB5",
					'middot' => "\xC2\xB7",
					'middot;' => "\xC2\xB7",
					'minus;' => "\xE2\x88\x92",
					'Mu;' => "\xCE\x9C",
					'mu;' => "\xCE\xBC",
					'nabla;' => "\xE2\x88\x87",
					'nbsp' => "\xC2\xA0",
					'nbsp;' => "\xC2\xA0",
					'ndash;' => "\xE2\x80\x93",
					'ne;' => "\xE2\x89\xA0",
					'ni;' => "\xE2\x88\x8B",
					'not' => "\xC2\xAC",
					'not;' => "\xC2\xAC",
					'notin;' => "\xE2\x88\x89",
					'nsub;' => "\xE2\x8A\x84",
					'Ntilde' => "\xC3\x91",
					'ntilde' => "\xC3\xB1",
					'Ntilde;' => "\xC3\x91",
					'ntilde;' => "\xC3\xB1",
					'Nu;' => "\xCE\x9D",
					'nu;' => "\xCE\xBD",
					'Oacute' => "\xC3\x93",
					'oacute' => "\xC3\xB3",
					'Oacute;' => "\xC3\x93",
					'oacute;' => "\xC3\xB3",
					'Ocirc' => "\xC3\x94",
					'ocirc' => "\xC3\xB4",
					'Ocirc;' => "\xC3\x94",
					'ocirc;' => "\xC3\xB4",
					'OElig;' => "\xC5\x92",
					'oelig;' => "\xC5\x93",
					'Ograve' => "\xC3\x92",
					'ograve' => "\xC3\xB2",
					'Ograve;' => "\xC3\x92",
					'ograve;' => "\xC3\xB2",
					'oline;' => "\xE2\x80\xBE",
					'Omega;' => "\xCE\xA9",
					'omega;' => "\xCF\x89",
					'Omicron;' => "\xCE\x9F",
					'omicron;' => "\xCE\xBF",
					'oplus;' => "\xE2\x8A\x95",
					'or;' => "\xE2\x88\xA8",
					'ordf' => "\xC2\xAA",
					'ordf;' => "\xC2\xAA",
					'ordm' => "\xC2\xBA",
					'ordm;' => "\xC2\xBA",
					'Oslash' => "\xC3\x98",
					'oslash' => "\xC3\xB8",
					'Oslash;' => "\xC3\x98",
					'oslash;' => "\xC3\xB8",
					'Otilde' => "\xC3\x95",
					'otilde' => "\xC3\xB5",
					'Otilde;' => "\xC3\x95",
					'otilde;' => "\xC3\xB5",
					'otimes;' => "\xE2\x8A\x97",
					'Ouml' => "\xC3\x96",
					'ouml' => "\xC3\xB6",
					'Ouml;' => "\xC3\x96",
					'ouml;' => "\xC3\xB6",
					'para' => "\xC2\xB6",
					'para;' => "\xC2\xB6",
					'part;' => "\xE2\x88\x82",
					'permil;' => "\xE2\x80\xB0",
					'perp;' => "\xE2\x8A\xA5",
					'Phi;' => "\xCE\xA6",
					'phi;' => "\xCF\x86",
					'Pi;' => "\xCE\xA0",
					'pi;' => "\xCF\x80",
					'piv;' => "\xCF\x96",
					'plusmn' => "\xC2\xB1",
					'plusmn;' => "\xC2\xB1",
					'pound' => "\xC2\xA3",
					'pound;' => "\xC2\xA3",
					'Prime;' => "\xE2\x80\xB3",
					'prime;' => "\xE2\x80\xB2",
					'prod;' => "\xE2\x88\x8F",
					'prop;' => "\xE2\x88\x9D",
					'Psi;' => "\xCE\xA8",
					'psi;' => "\xCF\x88",
					'QUOT' => "\x22",
					'quot' => "\x22",
					'QUOT;' => "\x22",
					'quot;' => "\x22",
					'radic;' => "\xE2\x88\x9A",
					'rang;' => "\xE3\x80\x89",
					'raquo' => "\xC2\xBB",
					'raquo;' => "\xC2\xBB",
					'rArr;' => "\xE2\x87\x92",
					'rarr;' => "\xE2\x86\x92",
					'rceil;' => "\xE2\x8C\x89",
					'rdquo;' => "\xE2\x80\x9D",
					'real;' => "\xE2\x84\x9C",
					'REG' => "\xC2\xAE",
					'reg' => "\xC2\xAE",
					'REG;' => "\xC2\xAE",
					'reg;' => "\xC2\xAE",
					'rfloor;' => "\xE2\x8C\x8B",
					'Rho;' => "\xCE\xA1",
					'rho;' => "\xCF\x81",
					'rlm;' => "\xE2\x80\x8F",
					'rsaquo;' => "\xE2\x80\xBA",
					'rsquo;' => "\xE2\x80\x99",
					'sbquo;' => "\xE2\x80\x9A",
					'Scaron;' => "\xC5\xA0",
					'scaron;' => "\xC5\xA1",
					'sdot;' => "\xE2\x8B\x85",
					'sect' => "\xC2\xA7",
					'sect;' => "\xC2\xA7",
					'shy' => "\xC2\xAD",
					'shy;' => "\xC2\xAD",
					'Sigma;' => "\xCE\xA3",
					'sigma;' => "\xCF\x83",
					'sigmaf;' => "\xCF\x82",
					'sim;' => "\xE2\x88\xBC",
					'spades;' => "\xE2\x99\xA0",
					'sub;' => "\xE2\x8A\x82",
					'sube;' => "\xE2\x8A\x86",
					'sum;' => "\xE2\x88\x91",
					'sup;' => "\xE2\x8A\x83",
					'sup1' => "\xC2\xB9",
					'sup1;' => "\xC2\xB9",
					'sup2' => "\xC2\xB2",
					'sup2;' => "\xC2\xB2",
					'sup3' => "\xC2\xB3",
					'sup3;' => "\xC2\xB3",
					'supe;' => "\xE2\x8A\x87",
					'szlig' => "\xC3\x9F",
					'szlig;' => "\xC3\x9F",
					'Tau;' => "\xCE\xA4",
					'tau;' => "\xCF\x84",
					'there4;' => "\xE2\x88\xB4",
					'Theta;' => "\xCE\x98",
					'theta;' => "\xCE\xB8",
					'thetasym;' => "\xCF\x91",
					'thinsp;' => "\xE2\x80\x89",
					'THORN' => "\xC3\x9E",
					'thorn' => "\xC3\xBE",
					'THORN;' => "\xC3\x9E",
					'thorn;' => "\xC3\xBE",
					'tilde;' => "\xCB\x9C",
					'times' => "\xC3\x97",
					'times;' => "\xC3\x97",
					'TRADE;' => "\xE2\x84\xA2",
					'trade;' => "\xE2\x84\xA2",
					'Uacute' => "\xC3\x9A",
					'uacute' => "\xC3\xBA",
					'Uacute;' => "\xC3\x9A",
					'uacute;' => "\xC3\xBA",
					'uArr;' => "\xE2\x87\x91",
					'uarr;' => "\xE2\x86\x91",
					'Ucirc' => "\xC3\x9B",
					'ucirc' => "\xC3\xBB",
					'Ucirc;' => "\xC3\x9B",
					'ucirc;' => "\xC3\xBB",
					'Ugrave' => "\xC3\x99",
					'ugrave' => "\xC3\xB9",
					'Ugrave;' => "\xC3\x99",
					'ugrave;' => "\xC3\xB9",
					'uml' => "\xC2\xA8",
					'uml;' => "\xC2\xA8",
					'upsih;' => "\xCF\x92",
					'Upsilon;' => "\xCE\xA5",
					'upsilon;' => "\xCF\x85",
					'Uuml' => "\xC3\x9C",
					'uuml' => "\xC3\xBC",
					'Uuml;' => "\xC3\x9C",
					'uuml;' => "\xC3\xBC",
					'weierp;' => "\xE2\x84\x98",
					'Xi;' => "\xCE\x9E",
					'xi;' => "\xCE\xBE",
					'Yacute' => "\xC3\x9D",
					'yacute' => "\xC3\xBD",
					'Yacute;' => "\xC3\x9D",
					'yacute;' => "\xC3\xBD",
					'yen' => "\xC2\xA5",
					'yen;' => "\xC2\xA5",
					'yuml' => "\xC3\xBF",
					'Yuml;' => "\xC5\xB8",
					'yuml;' => "\xC3\xBF",
					'Zeta;' => "\xCE\x96",
					'zeta;' => "\xCE\xB6",
					'zwj;' => "\xE2\x80\x8D",
					'zwnj;' => "\xE2\x80\x8C"
				);

				for ($i = 0, $match = null; $i < 9 && $this->consume() !== false; $i++)
				{
					$consumed = substr($this->consumed, 1);
					if (isset($entities[$consumed]))
					{
						$match = $consumed;
					}
				}

				if ($match !== null)
				{
 					$this->data = substr_replace($this->data, $entities[$match], $this->position - strlen($consumed) - 1, strlen($match) + 1);
					$this->position += strlen($entities[$match]) - strlen($consumed) - 1;
				}
				break;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/XML/Declaration/Parser.php000064400000015755152177723700022234 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Parses the XML Declaration
 *
 * @package SimplePie
 * @subpackage Parsing
 */
class SimplePie_XML_Declaration_Parser
{
	/**
	 * XML Version
	 *
	 * @access public
	 * @var string
	 */
	var $version = '1.0';

	/**
	 * Encoding
	 *
	 * @access public
	 * @var string
	 */
	var $encoding = 'UTF-8';

	/**
	 * Standalone
	 *
	 * @access public
	 * @var bool
	 */
	var $standalone = false;

	/**
	 * Current state of the state machine
	 *
	 * @access private
	 * @var string
	 */
	var $state = 'before_version_name';

	/**
	 * Input data
	 *
	 * @access private
	 * @var string
	 */
	var $data = '';

	/**
	 * Input data length (to avoid calling strlen() everytime this is needed)
	 *
	 * @access private
	 * @var int
	 */
	var $data_length = 0;

	/**
	 * Current position of the pointer
	 *
	 * @var int
	 * @access private
	 */
	var $position = 0;

	/**
	 * Create an instance of the class with the input data
	 *
	 * @access public
	 * @param string $data Input data
	 */
	public function __construct($data)
	{
		$this->data = $data;
		$this->data_length = strlen($this->data);
	}

	/**
	 * Parse the input data
	 *
	 * @access public
	 * @return bool true on success, false on failure
	 */
	public function parse()
	{
		while ($this->state && $this->state !== 'emit' && $this->has_data())
		{
			$state = $this->state;
			$this->$state();
		}
		$this->data = '';
		if ($this->state === 'emit')
		{
			return true;
		}
		else
		{
			$this->version = '';
			$this->encoding = '';
			$this->standalone = '';
			return false;
		}
	}

	/**
	 * Check whether there is data beyond the pointer
	 *
	 * @access private
	 * @return bool true if there is further data, false if not
	 */
	public function has_data()
	{
		return (bool) ($this->position < $this->data_length);
	}

	/**
	 * Advance past any whitespace
	 *
	 * @return int Number of whitespace characters passed
	 */
	public function skip_whitespace()
	{
		$whitespace = strspn($this->data, "\x09\x0A\x0D\x20", $this->position);
		$this->position += $whitespace;
		return $whitespace;
	}

	/**
	 * Read value
	 */
	public function get_value()
	{
		$quote = substr($this->data, $this->position, 1);
		if ($quote === '"' || $quote === "'")
		{
			$this->position++;
			$len = strcspn($this->data, $quote, $this->position);
			if ($this->has_data())
			{
				$value = substr($this->data, $this->position, $len);
				$this->position += $len + 1;
				return $value;
			}
		}
		return false;
	}

	public function before_version_name()
	{
		if ($this->skip_whitespace())
		{
			$this->state = 'version_name';
		}
		else
		{
			$this->state = false;
		}
	}

	public function version_name()
	{
		if (substr($this->data, $this->position, 7) === 'version')
		{
			$this->position += 7;
			$this->skip_whitespace();
			$this->state = 'version_equals';
		}
		else
		{
			$this->state = false;
		}
	}

	public function version_equals()
	{
		if (substr($this->data, $this->position, 1) === '=')
		{
			$this->position++;
			$this->skip_whitespace();
			$this->state = 'version_value';
		}
		else
		{
			$this->state = false;
		}
	}

	public function version_value()
	{
		if ($this->version = $this->get_value())
		{
			$this->skip_whitespace();
			if ($this->has_data())
			{
				$this->state = 'encoding_name';
			}
			else
			{
				$this->state = 'emit';
			}
		}
		else
		{
			$this->state = false;
		}
	}

	public function encoding_name()
	{
		if (substr($this->data, $this->position, 8) === 'encoding')
		{
			$this->position += 8;
			$this->skip_whitespace();
			$this->state = 'encoding_equals';
		}
		else
		{
			$this->state = 'standalone_name';
		}
	}

	public function encoding_equals()
	{
		if (substr($this->data, $this->position, 1) === '=')
		{
			$this->position++;
			$this->skip_whitespace();
			$this->state = 'encoding_value';
		}
		else
		{
			$this->state = false;
		}
	}

	public function encoding_value()
	{
		if ($this->encoding = $this->get_value())
		{
			$this->skip_whitespace();
			if ($this->has_data())
			{
				$this->state = 'standalone_name';
			}
			else
			{
				$this->state = 'emit';
			}
		}
		else
		{
			$this->state = false;
		}
	}

	public function standalone_name()
	{
		if (substr($this->data, $this->position, 10) === 'standalone')
		{
			$this->position += 10;
			$this->skip_whitespace();
			$this->state = 'standalone_equals';
		}
		else
		{
			$this->state = false;
		}
	}

	public function standalone_equals()
	{
		if (substr($this->data, $this->position, 1) === '=')
		{
			$this->position++;
			$this->skip_whitespace();
			$this->state = 'standalone_value';
		}
		else
		{
			$this->state = false;
		}
	}

	public function standalone_value()
	{
		if ($standalone = $this->get_value())
		{
			switch ($standalone)
			{
				case 'yes':
					$this->standalone = true;
					break;

				case 'no':
					$this->standalone = false;
					break;

				default:
					$this->state = false;
					return;
			}

			$this->skip_whitespace();
			if ($this->has_data())
			{
				$this->state = false;
			}
			else
			{
				$this->state = 'emit';
			}
		}
		else
		{
			$this->state = false;
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Misc.php000064400000144547152177723700017010 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Miscellanous utilities
 *
 * @package SimplePie
 */
class SimplePie_Misc
{
	public static function time_hms($seconds)
	{
		$time = '';

		$hours = floor($seconds / 3600);
		$remainder = $seconds % 3600;
		if ($hours > 0)
		{
			$time .= $hours.':';
		}

		$minutes = floor($remainder / 60);
		$seconds = $remainder % 60;
		if ($minutes < 10 && $hours > 0)
		{
			$minutes = '0' . $minutes;
		}
		if ($seconds < 10)
		{
			$seconds = '0' . $seconds;
		}

		$time .= $minutes.':';
		$time .= $seconds;

		return $time;
	}

	public static function absolutize_url($relative, $base)
	{
		$iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative);
		if ($iri === false)
		{
			return false;
		}
		return $iri->get_uri();
	}

	/**
	 * Get a HTML/XML element from a HTML string
	 *
	 * @deprecated Use DOMDocument instead (parsing HTML with regex is bad!)
	 * @param string $realname Element name (including namespace prefix if applicable)
	 * @param string $string HTML document
	 * @return array
	 */
	public static function get_element($realname, $string)
	{
		$return = array();
		$name = preg_quote($realname, '/');
		if (preg_match_all("/<($name)" . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . "(>(.*)<\/$name>|(\/)?>)/siU", $string, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE))
		{
			for ($i = 0, $total_matches = count($matches); $i < $total_matches; $i++)
			{
				$return[$i]['tag'] = $realname;
				$return[$i]['full'] = $matches[$i][0][0];
				$return[$i]['offset'] = $matches[$i][0][1];
				if (strlen($matches[$i][3][0]) <= 2)
				{
					$return[$i]['self_closing'] = true;
				}
				else
				{
					$return[$i]['self_closing'] = false;
					$return[$i]['content'] = $matches[$i][4][0];
				}
				$return[$i]['attribs'] = array();
				if (isset($matches[$i][2][0]) && preg_match_all('/[\x09\x0A\x0B\x0C\x0D\x20]+([^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*)(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"([^"]*)"|\'([^\']*)\'|([^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?/', ' ' . $matches[$i][2][0] . ' ', $attribs, PREG_SET_ORDER))
				{
					for ($j = 0, $total_attribs = count($attribs); $j < $total_attribs; $j++)
					{
						if (count($attribs[$j]) === 2)
						{
							$attribs[$j][2] = $attribs[$j][1];
						}
						$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
					}
				}
			}
		}
		return $return;
	}

	public static function element_implode($element)
	{
		$full = "<$element[tag]";
		foreach ($element['attribs'] as $key => $value)
		{
			$key = strtolower($key);
			$full .= " $key=\"" . htmlspecialchars($value['data']) . '"';
		}
		if ($element['self_closing'])
		{
			$full .= ' />';
		}
		else
		{
			$full .= ">$element[content]</$element[tag]>";
		}
		return $full;
	}

	public static function error($message, $level, $file, $line)
	{
		if ((ini_get('error_reporting') & $level) > 0)
		{
			switch ($level)
			{
				case E_USER_ERROR:
					$note = 'PHP Error';
					break;
				case E_USER_WARNING:
					$note = 'PHP Warning';
					break;
				case E_USER_NOTICE:
					$note = 'PHP Notice';
					break;
				default:
					$note = 'Unknown Error';
					break;
			}

			$log_error = true;
			if (!function_exists('error_log'))
			{
				$log_error = false;
			}

			$log_file = @ini_get('error_log');
			if (!empty($log_file) && ('syslog' !== $log_file) && !@is_writable($log_file))
			{
				$log_error = false;
			}

			if ($log_error)
			{
				@error_log("$note: $message in $file on line $line", 0);
			}
		}

		return $message;
	}

	public static function fix_protocol($url, $http = 1)
	{
		$url = SimplePie_Misc::normalize_url($url);
		$parsed = SimplePie_Misc::parse_url($url);
		if ($parsed['scheme'] !== '' && $parsed['scheme'] !== 'http' && $parsed['scheme'] !== 'https')
		{
			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['authority'], $parsed['path'], $parsed['query'], $parsed['fragment']), $http);
		}

		if ($parsed['scheme'] === '' && $parsed['authority'] === '' && !file_exists($url))
		{
			return SimplePie_Misc::fix_protocol(SimplePie_Misc::compress_parse_url('http', $parsed['path'], '', $parsed['query'], $parsed['fragment']), $http);
		}

		if ($http === 2 && $parsed['scheme'] !== '')
		{
			return "feed:$url";
		}
		elseif ($http === 3 && strtolower($parsed['scheme']) === 'http')
		{
			return substr_replace($url, 'podcast', 0, 4);
		}
		elseif ($http === 4 && strtolower($parsed['scheme']) === 'http')
		{
			return substr_replace($url, 'itpc', 0, 4);
		}
		else
		{
			return $url;
		}
	}

	public static function parse_url($url)
	{
		$iri = new SimplePie_IRI($url);
		return array(
			'scheme' => (string) $iri->scheme,
			'authority' => (string) $iri->authority,
			'path' => (string) $iri->path,
			'query' => (string) $iri->query,
			'fragment' => (string) $iri->fragment
		);
	}

	public static function compress_parse_url($scheme = '', $authority = '', $path = '', $query = '', $fragment = '')
	{
		$iri = new SimplePie_IRI('');
		$iri->scheme = $scheme;
		$iri->authority = $authority;
		$iri->path = $path;
		$iri->query = $query;
		$iri->fragment = $fragment;
		return $iri->get_uri();
	}

	public static function normalize_url($url)
	{
		$iri = new SimplePie_IRI($url);
		return $iri->get_uri();
	}

	public static function percent_encoding_normalization($match)
	{
		$integer = hexdec($match[1]);
		if ($integer >= 0x41 && $integer <= 0x5A || $integer >= 0x61 && $integer <= 0x7A || $integer >= 0x30 && $integer <= 0x39 || $integer === 0x2D || $integer === 0x2E || $integer === 0x5F || $integer === 0x7E)
		{
			return chr($integer);
		}
		else
		{
			return strtoupper($match[0]);
		}
	}

	/**
	 * Converts a Windows-1252 encoded string to a UTF-8 encoded string
	 *
	 * @static
	 * @param string $string Windows-1252 encoded string
	 * @return string UTF-8 encoded string
	 */
	public static function windows_1252_to_utf8($string)
	{
		static $convert_table = array("\x80" => "\xE2\x82\xAC", "\x81" => "\xEF\xBF\xBD", "\x82" => "\xE2\x80\x9A", "\x83" => "\xC6\x92", "\x84" => "\xE2\x80\x9E", "\x85" => "\xE2\x80\xA6", "\x86" => "\xE2\x80\xA0", "\x87" => "\xE2\x80\xA1", "\x88" => "\xCB\x86", "\x89" => "\xE2\x80\xB0", "\x8A" => "\xC5\xA0", "\x8B" => "\xE2\x80\xB9", "\x8C" => "\xC5\x92", "\x8D" => "\xEF\xBF\xBD", "\x8E" => "\xC5\xBD", "\x8F" => "\xEF\xBF\xBD", "\x90" => "\xEF\xBF\xBD", "\x91" => "\xE2\x80\x98", "\x92" => "\xE2\x80\x99", "\x93" => "\xE2\x80\x9C", "\x94" => "\xE2\x80\x9D", "\x95" => "\xE2\x80\xA2", "\x96" => "\xE2\x80\x93", "\x97" => "\xE2\x80\x94", "\x98" => "\xCB\x9C", "\x99" => "\xE2\x84\xA2", "\x9A" => "\xC5\xA1", "\x9B" => "\xE2\x80\xBA", "\x9C" => "\xC5\x93", "\x9D" => "\xEF\xBF\xBD", "\x9E" => "\xC5\xBE", "\x9F" => "\xC5\xB8", "\xA0" => "\xC2\xA0", "\xA1" => "\xC2\xA1", "\xA2" => "\xC2\xA2", "\xA3" => "\xC2\xA3", "\xA4" => "\xC2\xA4", "\xA5" => "\xC2\xA5", "\xA6" => "\xC2\xA6", "\xA7" => "\xC2\xA7", "\xA8" => "\xC2\xA8", "\xA9" => "\xC2\xA9", "\xAA" => "\xC2\xAA", "\xAB" => "\xC2\xAB", "\xAC" => "\xC2\xAC", "\xAD" => "\xC2\xAD", "\xAE" => "\xC2\xAE", "\xAF" => "\xC2\xAF", "\xB0" => "\xC2\xB0", "\xB1" => "\xC2\xB1", "\xB2" => "\xC2\xB2", "\xB3" => "\xC2\xB3", "\xB4" => "\xC2\xB4", "\xB5" => "\xC2\xB5", "\xB6" => "\xC2\xB6", "\xB7" => "\xC2\xB7", "\xB8" => "\xC2\xB8", "\xB9" => "\xC2\xB9", "\xBA" => "\xC2\xBA", "\xBB" => "\xC2\xBB", "\xBC" => "\xC2\xBC", "\xBD" => "\xC2\xBD", "\xBE" => "\xC2\xBE", "\xBF" => "\xC2\xBF", "\xC0" => "\xC3\x80", "\xC1" => "\xC3\x81", "\xC2" => "\xC3\x82", "\xC3" => "\xC3\x83", "\xC4" => "\xC3\x84", "\xC5" => "\xC3\x85", "\xC6" => "\xC3\x86", "\xC7" => "\xC3\x87", "\xC8" => "\xC3\x88", "\xC9" => "\xC3\x89", "\xCA" => "\xC3\x8A", "\xCB" => "\xC3\x8B", "\xCC" => "\xC3\x8C", "\xCD" => "\xC3\x8D", "\xCE" => "\xC3\x8E", "\xCF" => "\xC3\x8F", "\xD0" => "\xC3\x90", "\xD1" => "\xC3\x91", "\xD2" => "\xC3\x92", "\xD3" => "\xC3\x93", "\xD4" => "\xC3\x94", "\xD5" => "\xC3\x95", "\xD6" => "\xC3\x96", "\xD7" => "\xC3\x97", "\xD8" => "\xC3\x98", "\xD9" => "\xC3\x99", "\xDA" => "\xC3\x9A", "\xDB" => "\xC3\x9B", "\xDC" => "\xC3\x9C", "\xDD" => "\xC3\x9D", "\xDE" => "\xC3\x9E", "\xDF" => "\xC3\x9F", "\xE0" => "\xC3\xA0", "\xE1" => "\xC3\xA1", "\xE2" => "\xC3\xA2", "\xE3" => "\xC3\xA3", "\xE4" => "\xC3\xA4", "\xE5" => "\xC3\xA5", "\xE6" => "\xC3\xA6", "\xE7" => "\xC3\xA7", "\xE8" => "\xC3\xA8", "\xE9" => "\xC3\xA9", "\xEA" => "\xC3\xAA", "\xEB" => "\xC3\xAB", "\xEC" => "\xC3\xAC", "\xED" => "\xC3\xAD", "\xEE" => "\xC3\xAE", "\xEF" => "\xC3\xAF", "\xF0" => "\xC3\xB0", "\xF1" => "\xC3\xB1", "\xF2" => "\xC3\xB2", "\xF3" => "\xC3\xB3", "\xF4" => "\xC3\xB4", "\xF5" => "\xC3\xB5", "\xF6" => "\xC3\xB6", "\xF7" => "\xC3\xB7", "\xF8" => "\xC3\xB8", "\xF9" => "\xC3\xB9", "\xFA" => "\xC3\xBA", "\xFB" => "\xC3\xBB", "\xFC" => "\xC3\xBC", "\xFD" => "\xC3\xBD", "\xFE" => "\xC3\xBE", "\xFF" => "\xC3\xBF");

		return strtr($string, $convert_table);
	}

	/**
	 * Change a string from one encoding to another
	 *
	 * @param string $data Raw data in $input encoding
	 * @param string $input Encoding of $data
	 * @param string $output Encoding you want
	 * @return string|boolean False if we can't convert it
	 */
	public static function change_encoding($data, $input, $output)
	{
		$input = SimplePie_Misc::encoding($input);
		$output = SimplePie_Misc::encoding($output);

		// We fail to fail on non US-ASCII bytes
		if ($input === 'US-ASCII')
		{
			static $non_ascii_octects = '';
			if (!$non_ascii_octects)
			{
				for ($i = 0x80; $i <= 0xFF; $i++)
				{
					$non_ascii_octects .= chr($i);
				}
			}
			$data = substr($data, 0, strcspn($data, $non_ascii_octects));
		}

		// This is first, as behaviour of this is completely predictable
		if ($input === 'windows-1252' && $output === 'UTF-8')
		{
			return SimplePie_Misc::windows_1252_to_utf8($data);
		}
		// This is second, as behaviour of this varies only with PHP version (the middle part of this expression checks the encoding is supported).
		elseif (function_exists('mb_convert_encoding') && ($return = SimplePie_Misc::change_encoding_mbstring($data, $input, $output)))
		{
			return $return;
 		}
		// This is last, as behaviour of this varies with OS userland and PHP version
		elseif (function_exists('iconv') && ($return = SimplePie_Misc::change_encoding_iconv($data, $input, $output)))
		{
			return $return;
		}
		// If we can't do anything, just fail
		else
		{
			return false;
		}
	}

	protected static function change_encoding_mbstring($data, $input, $output)
	{
		if ($input === 'windows-949')
		{
			$input = 'EUC-KR';
		}
		if ($output === 'windows-949')
		{
			$output = 'EUC-KR';
		}
		if ($input === 'Windows-31J')
		{
			$input = 'SJIS';
		}
		if ($output === 'Windows-31J')
		{
			$output = 'SJIS';
		}

		// Check that the encoding is supported
		if (@mb_convert_encoding("\x80", 'UTF-16BE', $input) === "\x00\x80")
		{
			return false;
		}
		if (!in_array($input, mb_list_encodings()))
		{
			return false;
		}

		// Let's do some conversion
		if ($return = @mb_convert_encoding($data, $output, $input))
		{
			return $return;
		}

		return false;
	}

	protected static function change_encoding_iconv($data, $input, $output)
	{
		return @iconv($input, $output, $data);
	}

	/**
	 * Normalize an encoding name
	 *
	 * This is automatically generated by create.php
	 *
	 * To generate it, run `php create.php` on the command line, and copy the
	 * output to replace this function.
	 *
	 * @param string $charset Character set to standardise
	 * @return string Standardised name
	 */
	public static function encoding($charset)
	{
		// Normalization from UTS #22
		switch (strtolower(preg_replace('/(?:[^a-zA-Z0-9]+|([^0-9])0+)/', '\1', $charset)))
		{
			case 'adobestandardencoding':
			case 'csadobestandardencoding':
				return 'Adobe-Standard-Encoding';

			case 'adobesymbolencoding':
			case 'cshppsmath':
				return 'Adobe-Symbol-Encoding';

			case 'ami1251':
			case 'amiga1251':
				return 'Amiga-1251';

			case 'ansix31101983':
			case 'csat5001983':
			case 'csiso99naplps':
			case 'isoir99':
			case 'naplps':
				return 'ANSI_X3.110-1983';

			case 'arabic7':
			case 'asmo449':
			case 'csiso89asmo449':
			case 'iso9036':
			case 'isoir89':
				return 'ASMO_449';

			case 'big5':
			case 'csbig5':
				return 'Big5';

			case 'big5hkscs':
				return 'Big5-HKSCS';

			case 'bocu1':
			case 'csbocu1':
				return 'BOCU-1';

			case 'brf':
			case 'csbrf':
				return 'BRF';

			case 'bs4730':
			case 'csiso4unitedkingdom':
			case 'gb':
			case 'iso646gb':
			case 'isoir4':
			case 'uk':
				return 'BS_4730';

			case 'bsviewdata':
			case 'csiso47bsviewdata':
			case 'isoir47':
				return 'BS_viewdata';

			case 'cesu8':
			case 'cscesu8':
				return 'CESU-8';

			case 'ca':
			case 'csa71':
			case 'csaz243419851':
			case 'csiso121canadian1':
			case 'iso646ca':
			case 'isoir121':
				return 'CSA_Z243.4-1985-1';

			case 'csa72':
			case 'csaz243419852':
			case 'csiso122canadian2':
			case 'iso646ca2':
			case 'isoir122':
				return 'CSA_Z243.4-1985-2';

			case 'csaz24341985gr':
			case 'csiso123csaz24341985gr':
			case 'isoir123':
				return 'CSA_Z243.4-1985-gr';

			case 'csiso139csn369103':
			case 'csn369103':
			case 'isoir139':
				return 'CSN_369103';

			case 'csdecmcs':
			case 'dec':
			case 'decmcs':
				return 'DEC-MCS';

			case 'csiso21german':
			case 'de':
			case 'din66003':
			case 'iso646de':
			case 'isoir21':
				return 'DIN_66003';

			case 'csdkus':
			case 'dkus':
				return 'dk-us';

			case 'csiso646danish':
			case 'dk':
			case 'ds2089':
			case 'iso646dk':
				return 'DS_2089';

			case 'csibmebcdicatde':
			case 'ebcdicatde':
				return 'EBCDIC-AT-DE';

			case 'csebcdicatdea':
			case 'ebcdicatdea':
				return 'EBCDIC-AT-DE-A';

			case 'csebcdiccafr':
			case 'ebcdiccafr':
				return 'EBCDIC-CA-FR';

			case 'csebcdicdkno':
			case 'ebcdicdkno':
				return 'EBCDIC-DK-NO';

			case 'csebcdicdknoa':
			case 'ebcdicdknoa':
				return 'EBCDIC-DK-NO-A';

			case 'csebcdices':
			case 'ebcdices':
				return 'EBCDIC-ES';

			case 'csebcdicesa':
			case 'ebcdicesa':
				return 'EBCDIC-ES-A';

			case 'csebcdicess':
			case 'ebcdicess':
				return 'EBCDIC-ES-S';

			case 'csebcdicfise':
			case 'ebcdicfise':
				return 'EBCDIC-FI-SE';

			case 'csebcdicfisea':
			case 'ebcdicfisea':
				return 'EBCDIC-FI-SE-A';

			case 'csebcdicfr':
			case 'ebcdicfr':
				return 'EBCDIC-FR';

			case 'csebcdicit':
			case 'ebcdicit':
				return 'EBCDIC-IT';

			case 'csebcdicpt':
			case 'ebcdicpt':
				return 'EBCDIC-PT';

			case 'csebcdicuk':
			case 'ebcdicuk':
				return 'EBCDIC-UK';

			case 'csebcdicus':
			case 'ebcdicus':
				return 'EBCDIC-US';

			case 'csiso111ecmacyrillic':
			case 'ecmacyrillic':
			case 'isoir111':
			case 'koi8e':
				return 'ECMA-cyrillic';

			case 'csiso17spanish':
			case 'es':
			case 'iso646es':
			case 'isoir17':
				return 'ES';

			case 'csiso85spanish2':
			case 'es2':
			case 'iso646es2':
			case 'isoir85':
				return 'ES2';

			case 'cseucpkdfmtjapanese':
			case 'eucjp':
			case 'extendedunixcodepackedformatforjapanese':
				return 'EUC-JP';

			case 'cseucfixwidjapanese':
			case 'extendedunixcodefixedwidthforjapanese':
				return 'Extended_UNIX_Code_Fixed_Width_for_Japanese';

			case 'gb18030':
				return 'GB18030';

			case 'chinese':
			case 'cp936':
			case 'csgb2312':
			case 'csiso58gb231280':
			case 'gb2312':
			case 'gb231280':
			case 'gbk':
			case 'isoir58':
			case 'ms936':
			case 'windows936':
				return 'GBK';

			case 'cn':
			case 'csiso57gb1988':
			case 'gb198880':
			case 'iso646cn':
			case 'isoir57':
				return 'GB_1988-80';

			case 'csiso153gost1976874':
			case 'gost1976874':
			case 'isoir153':
			case 'stsev35888':
				return 'GOST_19768-74';

			case 'csiso150':
			case 'csiso150greekccitt':
			case 'greekccitt':
			case 'isoir150':
				return 'greek-ccitt';

			case 'csiso88greek7':
			case 'greek7':
			case 'isoir88':
				return 'greek7';

			case 'csiso18greek7old':
			case 'greek7old':
			case 'isoir18':
				return 'greek7-old';

			case 'cshpdesktop':
			case 'hpdesktop':
				return 'HP-DeskTop';

			case 'cshplegal':
			case 'hplegal':
				return 'HP-Legal';

			case 'cshpmath8':
			case 'hpmath8':
				return 'HP-Math8';

			case 'cshppifont':
			case 'hppifont':
				return 'HP-Pi-font';

			case 'cshproman8':
			case 'hproman8':
			case 'r8':
			case 'roman8':
				return 'hp-roman8';

			case 'hzgb2312':
				return 'HZ-GB-2312';

			case 'csibmsymbols':
			case 'ibmsymbols':
				return 'IBM-Symbols';

			case 'csibmthai':
			case 'ibmthai':
				return 'IBM-Thai';

			case 'cp37':
			case 'csibm37':
			case 'ebcdiccpca':
			case 'ebcdiccpnl':
			case 'ebcdiccpus':
			case 'ebcdiccpwt':
			case 'ibm37':
				return 'IBM037';

			case 'cp38':
			case 'csibm38':
			case 'ebcdicint':
			case 'ibm38':
				return 'IBM038';

			case 'cp273':
			case 'csibm273':
			case 'ibm273':
				return 'IBM273';

			case 'cp274':
			case 'csibm274':
			case 'ebcdicbe':
			case 'ibm274':
				return 'IBM274';

			case 'cp275':
			case 'csibm275':
			case 'ebcdicbr':
			case 'ibm275':
				return 'IBM275';

			case 'csibm277':
			case 'ebcdiccpdk':
			case 'ebcdiccpno':
			case 'ibm277':
				return 'IBM277';

			case 'cp278':
			case 'csibm278':
			case 'ebcdiccpfi':
			case 'ebcdiccpse':
			case 'ibm278':
				return 'IBM278';

			case 'cp280':
			case 'csibm280':
			case 'ebcdiccpit':
			case 'ibm280':
				return 'IBM280';

			case 'cp281':
			case 'csibm281':
			case 'ebcdicjpe':
			case 'ibm281':
				return 'IBM281';

			case 'cp284':
			case 'csibm284':
			case 'ebcdiccpes':
			case 'ibm284':
				return 'IBM284';

			case 'cp285':
			case 'csibm285':
			case 'ebcdiccpgb':
			case 'ibm285':
				return 'IBM285';

			case 'cp290':
			case 'csibm290':
			case 'ebcdicjpkana':
			case 'ibm290':
				return 'IBM290';

			case 'cp297':
			case 'csibm297':
			case 'ebcdiccpfr':
			case 'ibm297':
				return 'IBM297';

			case 'cp420':
			case 'csibm420':
			case 'ebcdiccpar1':
			case 'ibm420':
				return 'IBM420';

			case 'cp423':
			case 'csibm423':
			case 'ebcdiccpgr':
			case 'ibm423':
				return 'IBM423';

			case 'cp424':
			case 'csibm424':
			case 'ebcdiccphe':
			case 'ibm424':
				return 'IBM424';

			case '437':
			case 'cp437':
			case 'cspc8codepage437':
			case 'ibm437':
				return 'IBM437';

			case 'cp500':
			case 'csibm500':
			case 'ebcdiccpbe':
			case 'ebcdiccpch':
			case 'ibm500':
				return 'IBM500';

			case 'cp775':
			case 'cspc775baltic':
			case 'ibm775':
				return 'IBM775';

			case '850':
			case 'cp850':
			case 'cspc850multilingual':
			case 'ibm850':
				return 'IBM850';

			case '851':
			case 'cp851':
			case 'csibm851':
			case 'ibm851':
				return 'IBM851';

			case '852':
			case 'cp852':
			case 'cspcp852':
			case 'ibm852':
				return 'IBM852';

			case '855':
			case 'cp855':
			case 'csibm855':
			case 'ibm855':
				return 'IBM855';

			case '857':
			case 'cp857':
			case 'csibm857':
			case 'ibm857':
				return 'IBM857';

			case 'ccsid858':
			case 'cp858':
			case 'ibm858':
			case 'pcmultilingual850euro':
				return 'IBM00858';

			case '860':
			case 'cp860':
			case 'csibm860':
			case 'ibm860':
				return 'IBM860';

			case '861':
			case 'cp861':
			case 'cpis':
			case 'csibm861':
			case 'ibm861':
				return 'IBM861';

			case '862':
			case 'cp862':
			case 'cspc862latinhebrew':
			case 'ibm862':
				return 'IBM862';

			case '863':
			case 'cp863':
			case 'csibm863':
			case 'ibm863':
				return 'IBM863';

			case 'cp864':
			case 'csibm864':
			case 'ibm864':
				return 'IBM864';

			case '865':
			case 'cp865':
			case 'csibm865':
			case 'ibm865':
				return 'IBM865';

			case '866':
			case 'cp866':
			case 'csibm866':
			case 'ibm866':
				return 'IBM866';

			case 'cp868':
			case 'cpar':
			case 'csibm868':
			case 'ibm868':
				return 'IBM868';

			case '869':
			case 'cp869':
			case 'cpgr':
			case 'csibm869':
			case 'ibm869':
				return 'IBM869';

			case 'cp870':
			case 'csibm870':
			case 'ebcdiccproece':
			case 'ebcdiccpyu':
			case 'ibm870':
				return 'IBM870';

			case 'cp871':
			case 'csibm871':
			case 'ebcdiccpis':
			case 'ibm871':
				return 'IBM871';

			case 'cp880':
			case 'csibm880':
			case 'ebcdiccyrillic':
			case 'ibm880':
				return 'IBM880';

			case 'cp891':
			case 'csibm891':
			case 'ibm891':
				return 'IBM891';

			case 'cp903':
			case 'csibm903':
			case 'ibm903':
				return 'IBM903';

			case '904':
			case 'cp904':
			case 'csibbm904':
			case 'ibm904':
				return 'IBM904';

			case 'cp905':
			case 'csibm905':
			case 'ebcdiccptr':
			case 'ibm905':
				return 'IBM905';

			case 'cp918':
			case 'csibm918':
			case 'ebcdiccpar2':
			case 'ibm918':
				return 'IBM918';

			case 'ccsid924':
			case 'cp924':
			case 'ebcdiclatin9euro':
			case 'ibm924':
				return 'IBM00924';

			case 'cp1026':
			case 'csibm1026':
			case 'ibm1026':
				return 'IBM1026';

			case 'ibm1047':
				return 'IBM1047';

			case 'ccsid1140':
			case 'cp1140':
			case 'ebcdicus37euro':
			case 'ibm1140':
				return 'IBM01140';

			case 'ccsid1141':
			case 'cp1141':
			case 'ebcdicde273euro':
			case 'ibm1141':
				return 'IBM01141';

			case 'ccsid1142':
			case 'cp1142':
			case 'ebcdicdk277euro':
			case 'ebcdicno277euro':
			case 'ibm1142':
				return 'IBM01142';

			case 'ccsid1143':
			case 'cp1143':
			case 'ebcdicfi278euro':
			case 'ebcdicse278euro':
			case 'ibm1143':
				return 'IBM01143';

			case 'ccsid1144':
			case 'cp1144':
			case 'ebcdicit280euro':
			case 'ibm1144':
				return 'IBM01144';

			case 'ccsid1145':
			case 'cp1145':
			case 'ebcdices284euro':
			case 'ibm1145':
				return 'IBM01145';

			case 'ccsid1146':
			case 'cp1146':
			case 'ebcdicgb285euro':
			case 'ibm1146':
				return 'IBM01146';

			case 'ccsid1147':
			case 'cp1147':
			case 'ebcdicfr297euro':
			case 'ibm1147':
				return 'IBM01147';

			case 'ccsid1148':
			case 'cp1148':
			case 'ebcdicinternational500euro':
			case 'ibm1148':
				return 'IBM01148';

			case 'ccsid1149':
			case 'cp1149':
			case 'ebcdicis871euro':
			case 'ibm1149':
				return 'IBM01149';

			case 'csiso143iecp271':
			case 'iecp271':
			case 'isoir143':
				return 'IEC_P27-1';

			case 'csiso49inis':
			case 'inis':
			case 'isoir49':
				return 'INIS';

			case 'csiso50inis8':
			case 'inis8':
			case 'isoir50':
				return 'INIS-8';

			case 'csiso51iniscyrillic':
			case 'iniscyrillic':
			case 'isoir51':
				return 'INIS-cyrillic';

			case 'csinvariant':
			case 'invariant':
				return 'INVARIANT';

			case 'iso2022cn':
				return 'ISO-2022-CN';

			case 'iso2022cnext':
				return 'ISO-2022-CN-EXT';

			case 'csiso2022jp':
			case 'iso2022jp':
				return 'ISO-2022-JP';

			case 'csiso2022jp2':
			case 'iso2022jp2':
				return 'ISO-2022-JP-2';

			case 'csiso2022kr':
			case 'iso2022kr':
				return 'ISO-2022-KR';

			case 'cswindows30latin1':
			case 'iso88591windows30latin1':
				return 'ISO-8859-1-Windows-3.0-Latin-1';

			case 'cswindows31latin1':
			case 'iso88591windows31latin1':
				return 'ISO-8859-1-Windows-3.1-Latin-1';

			case 'csisolatin2':
			case 'iso88592':
			case 'iso885921987':
			case 'isoir101':
			case 'l2':
			case 'latin2':
				return 'ISO-8859-2';

			case 'cswindows31latin2':
			case 'iso88592windowslatin2':
				return 'ISO-8859-2-Windows-Latin-2';

			case 'csisolatin3':
			case 'iso88593':
			case 'iso885931988':
			case 'isoir109':
			case 'l3':
			case 'latin3':
				return 'ISO-8859-3';

			case 'csisolatin4':
			case 'iso88594':
			case 'iso885941988':
			case 'isoir110':
			case 'l4':
			case 'latin4':
				return 'ISO-8859-4';

			case 'csisolatincyrillic':
			case 'cyrillic':
			case 'iso88595':
			case 'iso885951988':
			case 'isoir144':
				return 'ISO-8859-5';

			case 'arabic':
			case 'asmo708':
			case 'csisolatinarabic':
			case 'ecma114':
			case 'iso88596':
			case 'iso885961987':
			case 'isoir127':
				return 'ISO-8859-6';

			case 'csiso88596e':
			case 'iso88596e':
				return 'ISO-8859-6-E';

			case 'csiso88596i':
			case 'iso88596i':
				return 'ISO-8859-6-I';

			case 'csisolatingreek':
			case 'ecma118':
			case 'elot928':
			case 'greek':
			case 'greek8':
			case 'iso88597':
			case 'iso885971987':
			case 'isoir126':
				return 'ISO-8859-7';

			case 'csisolatinhebrew':
			case 'hebrew':
			case 'iso88598':
			case 'iso885981988':
			case 'isoir138':
				return 'ISO-8859-8';

			case 'csiso88598e':
			case 'iso88598e':
				return 'ISO-8859-8-E';

			case 'csiso88598i':
			case 'iso88598i':
				return 'ISO-8859-8-I';

			case 'cswindows31latin5':
			case 'iso88599windowslatin5':
				return 'ISO-8859-9-Windows-Latin-5';

			case 'csisolatin6':
			case 'iso885910':
			case 'iso8859101992':
			case 'isoir157':
			case 'l6':
			case 'latin6':
				return 'ISO-8859-10';

			case 'iso885913':
				return 'ISO-8859-13';

			case 'iso885914':
			case 'iso8859141998':
			case 'isoceltic':
			case 'isoir199':
			case 'l8':
			case 'latin8':
				return 'ISO-8859-14';

			case 'iso885915':
			case 'latin9':
				return 'ISO-8859-15';

			case 'iso885916':
			case 'iso8859162001':
			case 'isoir226':
			case 'l10':
			case 'latin10':
				return 'ISO-8859-16';

			case 'iso10646j1':
				return 'ISO-10646-J-1';

			case 'csunicode':
			case 'iso10646ucs2':
				return 'ISO-10646-UCS-2';

			case 'csucs4':
			case 'iso10646ucs4':
				return 'ISO-10646-UCS-4';

			case 'csunicodeascii':
			case 'iso10646ucsbasic':
				return 'ISO-10646-UCS-Basic';

			case 'csunicodelatin1':
			case 'iso10646':
			case 'iso10646unicodelatin1':
				return 'ISO-10646-Unicode-Latin1';

			case 'csiso10646utf1':
			case 'iso10646utf1':
				return 'ISO-10646-UTF-1';

			case 'csiso115481':
			case 'iso115481':
			case 'isotr115481':
				return 'ISO-11548-1';

			case 'csiso90':
			case 'isoir90':
				return 'iso-ir-90';

			case 'csunicodeibm1261':
			case 'isounicodeibm1261':
				return 'ISO-Unicode-IBM-1261';

			case 'csunicodeibm1264':
			case 'isounicodeibm1264':
				return 'ISO-Unicode-IBM-1264';

			case 'csunicodeibm1265':
			case 'isounicodeibm1265':
				return 'ISO-Unicode-IBM-1265';

			case 'csunicodeibm1268':
			case 'isounicodeibm1268':
				return 'ISO-Unicode-IBM-1268';

			case 'csunicodeibm1276':
			case 'isounicodeibm1276':
				return 'ISO-Unicode-IBM-1276';

			case 'csiso646basic1983':
			case 'iso646basic1983':
			case 'ref':
				return 'ISO_646.basic:1983';

			case 'csiso2intlrefversion':
			case 'irv':
			case 'iso646irv1983':
			case 'isoir2':
				return 'ISO_646.irv:1983';

			case 'csiso2033':
			case 'e13b':
			case 'iso20331983':
			case 'isoir98':
				return 'ISO_2033-1983';

			case 'csiso5427cyrillic':
			case 'iso5427':
			case 'isoir37':
				return 'ISO_5427';

			case 'iso5427cyrillic1981':
			case 'iso54271981':
			case 'isoir54':
				return 'ISO_5427:1981';

			case 'csiso5428greek':
			case 'iso54281980':
			case 'isoir55':
				return 'ISO_5428:1980';

			case 'csiso6937add':
			case 'iso6937225':
			case 'isoir152':
				return 'ISO_6937-2-25';

			case 'csisotextcomm':
			case 'iso69372add':
			case 'isoir142':
				return 'ISO_6937-2-add';

			case 'csiso8859supp':
			case 'iso8859supp':
			case 'isoir154':
			case 'latin125':
				return 'ISO_8859-supp';

			case 'csiso10367box':
			case 'iso10367box':
			case 'isoir155':
				return 'ISO_10367-box';

			case 'csiso15italian':
			case 'iso646it':
			case 'isoir15':
			case 'it':
				return 'IT';

			case 'csiso13jisc6220jp':
			case 'isoir13':
			case 'jisc62201969':
			case 'jisc62201969jp':
			case 'katakana':
			case 'x2017':
				return 'JIS_C6220-1969-jp';

			case 'csiso14jisc6220ro':
			case 'iso646jp':
			case 'isoir14':
			case 'jisc62201969ro':
			case 'jp':
				return 'JIS_C6220-1969-ro';

			case 'csiso42jisc62261978':
			case 'isoir42':
			case 'jisc62261978':
				return 'JIS_C6226-1978';

			case 'csiso87jisx208':
			case 'isoir87':
			case 'jisc62261983':
			case 'jisx2081983':
			case 'x208':
				return 'JIS_C6226-1983';

			case 'csiso91jisc62291984a':
			case 'isoir91':
			case 'jisc62291984a':
			case 'jpocra':
				return 'JIS_C6229-1984-a';

			case 'csiso92jisc62991984b':
			case 'iso646jpocrb':
			case 'isoir92':
			case 'jisc62291984b':
			case 'jpocrb':
				return 'JIS_C6229-1984-b';

			case 'csiso93jis62291984badd':
			case 'isoir93':
			case 'jisc62291984badd':
			case 'jpocrbadd':
				return 'JIS_C6229-1984-b-add';

			case 'csiso94jis62291984hand':
			case 'isoir94':
			case 'jisc62291984hand':
			case 'jpocrhand':
				return 'JIS_C6229-1984-hand';

			case 'csiso95jis62291984handadd':
			case 'isoir95':
			case 'jisc62291984handadd':
			case 'jpocrhandadd':
				return 'JIS_C6229-1984-hand-add';

			case 'csiso96jisc62291984kana':
			case 'isoir96':
			case 'jisc62291984kana':
				return 'JIS_C6229-1984-kana';

			case 'csjisencoding':
			case 'jisencoding':
				return 'JIS_Encoding';

			case 'cshalfwidthkatakana':
			case 'jisx201':
			case 'x201':
				return 'JIS_X0201';

			case 'csiso159jisx2121990':
			case 'isoir159':
			case 'jisx2121990':
			case 'x212':
				return 'JIS_X0212-1990';

			case 'csiso141jusib1002':
			case 'iso646yu':
			case 'isoir141':
			case 'js':
			case 'jusib1002':
			case 'yu':
				return 'JUS_I.B1.002';

			case 'csiso147macedonian':
			case 'isoir147':
			case 'jusib1003mac':
			case 'macedonian':
				return 'JUS_I.B1.003-mac';

			case 'csiso146serbian':
			case 'isoir146':
			case 'jusib1003serb':
			case 'serbian':
				return 'JUS_I.B1.003-serb';

			case 'koi7switched':
				return 'KOI7-switched';

			case 'cskoi8r':
			case 'koi8r':
				return 'KOI8-R';

			case 'koi8u':
				return 'KOI8-U';

			case 'csksc5636':
			case 'iso646kr':
			case 'ksc5636':
				return 'KSC5636';

			case 'cskz1048':
			case 'kz1048':
			case 'rk1048':
			case 'strk10482002':
				return 'KZ-1048';

			case 'csiso19latingreek':
			case 'isoir19':
			case 'latingreek':
				return 'latin-greek';

			case 'csiso27latingreek1':
			case 'isoir27':
			case 'latingreek1':
				return 'Latin-greek-1';

			case 'csiso158lap':
			case 'isoir158':
			case 'lap':
			case 'latinlap':
				return 'latin-lap';

			case 'csmacintosh':
			case 'mac':
			case 'macintosh':
				return 'macintosh';

			case 'csmicrosoftpublishing':
			case 'microsoftpublishing':
				return 'Microsoft-Publishing';

			case 'csmnem':
			case 'mnem':
				return 'MNEM';

			case 'csmnemonic':
			case 'mnemonic':
				return 'MNEMONIC';

			case 'csiso86hungarian':
			case 'hu':
			case 'iso646hu':
			case 'isoir86':
			case 'msz77953':
				return 'MSZ_7795.3';

			case 'csnatsdano':
			case 'isoir91':
			case 'natsdano':
				return 'NATS-DANO';

			case 'csnatsdanoadd':
			case 'isoir92':
			case 'natsdanoadd':
				return 'NATS-DANO-ADD';

			case 'csnatssefi':
			case 'isoir81':
			case 'natssefi':
				return 'NATS-SEFI';

			case 'csnatssefiadd':
			case 'isoir82':
			case 'natssefiadd':
				return 'NATS-SEFI-ADD';

			case 'csiso151cuba':
			case 'cuba':
			case 'iso646cu':
			case 'isoir151':
			case 'ncnc1081':
				return 'NC_NC00-10:81';

			case 'csiso69french':
			case 'fr':
			case 'iso646fr':
			case 'isoir69':
			case 'nfz62010':
				return 'NF_Z_62-010';

			case 'csiso25french':
			case 'iso646fr1':
			case 'isoir25':
			case 'nfz620101973':
				return 'NF_Z_62-010_(1973)';

			case 'csiso60danishnorwegian':
			case 'csiso60norwegian1':
			case 'iso646no':
			case 'isoir60':
			case 'no':
			case 'ns45511':
				return 'NS_4551-1';

			case 'csiso61norwegian2':
			case 'iso646no2':
			case 'isoir61':
			case 'no2':
			case 'ns45512':
				return 'NS_4551-2';

			case 'osdebcdicdf3irv':
				return 'OSD_EBCDIC_DF03_IRV';

			case 'osdebcdicdf41':
				return 'OSD_EBCDIC_DF04_1';

			case 'osdebcdicdf415':
				return 'OSD_EBCDIC_DF04_15';

			case 'cspc8danishnorwegian':
			case 'pc8danishnorwegian':
				return 'PC8-Danish-Norwegian';

			case 'cspc8turkish':
			case 'pc8turkish':
				return 'PC8-Turkish';

			case 'csiso16portuguese':
			case 'iso646pt':
			case 'isoir16':
			case 'pt':
				return 'PT';

			case 'csiso84portuguese2':
			case 'iso646pt2':
			case 'isoir84':
			case 'pt2':
				return 'PT2';

			case 'cp154':
			case 'csptcp154':
			case 'cyrillicasian':
			case 'pt154':
			case 'ptcp154':
				return 'PTCP154';

			case 'scsu':
				return 'SCSU';

			case 'csiso10swedish':
			case 'fi':
			case 'iso646fi':
			case 'iso646se':
			case 'isoir10':
			case 'se':
			case 'sen850200b':
				return 'SEN_850200_B';

			case 'csiso11swedishfornames':
			case 'iso646se2':
			case 'isoir11':
			case 'se2':
			case 'sen850200c':
				return 'SEN_850200_C';

			case 'csiso102t617bit':
			case 'isoir102':
			case 't617bit':
				return 'T.61-7bit';

			case 'csiso103t618bit':
			case 'isoir103':
			case 't61':
			case 't618bit':
				return 'T.61-8bit';

			case 'csiso128t101g2':
			case 'isoir128':
			case 't101g2':
				return 'T.101-G2';

			case 'cstscii':
			case 'tscii':
				return 'TSCII';

			case 'csunicode11':
			case 'unicode11':
				return 'UNICODE-1-1';

			case 'csunicode11utf7':
			case 'unicode11utf7':
				return 'UNICODE-1-1-UTF-7';

			case 'csunknown8bit':
			case 'unknown8bit':
				return 'UNKNOWN-8BIT';

			case 'ansix341968':
			case 'ansix341986':
			case 'ascii':
			case 'cp367':
			case 'csascii':
			case 'ibm367':
			case 'iso646irv1991':
			case 'iso646us':
			case 'isoir6':
			case 'us':
			case 'usascii':
				return 'US-ASCII';

			case 'csusdk':
			case 'usdk':
				return 'us-dk';

			case 'utf7':
				return 'UTF-7';

			case 'utf8':
				return 'UTF-8';

			case 'utf16':
				return 'UTF-16';

			case 'utf16be':
				return 'UTF-16BE';

			case 'utf16le':
				return 'UTF-16LE';

			case 'utf32':
				return 'UTF-32';

			case 'utf32be':
				return 'UTF-32BE';

			case 'utf32le':
				return 'UTF-32LE';

			case 'csventurainternational':
			case 'venturainternational':
				return 'Ventura-International';

			case 'csventuramath':
			case 'venturamath':
				return 'Ventura-Math';

			case 'csventuraus':
			case 'venturaus':
				return 'Ventura-US';

			case 'csiso70videotexsupp1':
			case 'isoir70':
			case 'videotexsuppl':
				return 'videotex-suppl';

			case 'csviqr':
			case 'viqr':
				return 'VIQR';

			case 'csviscii':
			case 'viscii':
				return 'VISCII';

			case 'csshiftjis':
			case 'cswindows31j':
			case 'mskanji':
			case 'shiftjis':
			case 'windows31j':
				return 'Windows-31J';

			case 'iso885911':
			case 'tis620':
				return 'windows-874';

			case 'cseuckr':
			case 'csksc56011987':
			case 'euckr':
			case 'isoir149':
			case 'korean':
			case 'ksc5601':
			case 'ksc56011987':
			case 'ksc56011989':
			case 'windows949':
				return 'windows-949';

			case 'windows1250':
				return 'windows-1250';

			case 'windows1251':
				return 'windows-1251';

			case 'cp819':
			case 'csisolatin1':
			case 'ibm819':
			case 'iso88591':
			case 'iso885911987':
			case 'isoir100':
			case 'l1':
			case 'latin1':
			case 'windows1252':
				return 'windows-1252';

			case 'windows1253':
				return 'windows-1253';

			case 'csisolatin5':
			case 'iso88599':
			case 'iso885991989':
			case 'isoir148':
			case 'l5':
			case 'latin5':
			case 'windows1254':
				return 'windows-1254';

			case 'windows1255':
				return 'windows-1255';

			case 'windows1256':
				return 'windows-1256';

			case 'windows1257':
				return 'windows-1257';

			case 'windows1258':
				return 'windows-1258';

			default:
				return $charset;
		}
	}

	public static function get_curl_version()
	{
		if (is_array($curl = curl_version()))
		{
			$curl = $curl['version'];
		}
		elseif (substr($curl, 0, 5) === 'curl/')
		{
			$curl = substr($curl, 5, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 5));
		}
		elseif (substr($curl, 0, 8) === 'libcurl/')
		{
			$curl = substr($curl, 8, strcspn($curl, "\x09\x0A\x0B\x0C\x0D", 8));
		}
		else
		{
			$curl = 0;
		}
		return $curl;
	}

	/**
	 * Strip HTML comments
	 *
	 * @param string $data Data to strip comments from
	 * @return string Comment stripped string
	 */
	public static function strip_comments($data)
	{
		$output = '';
		while (($start = strpos($data, '<!--')) !== false)
		{
			$output .= substr($data, 0, $start);
			if (($end = strpos($data, '-->', $start)) !== false)
			{
				$data = substr_replace($data, '', 0, $end + 3);
			}
			else
			{
				$data = '';
			}
		}
		return $output . $data;
	}

	public static function parse_date($dt)
	{
		$parser = SimplePie_Parse_Date::get();
		return $parser->parse($dt);
	}

	/**
	 * Decode HTML entities
	 *
	 * @deprecated Use DOMDocument instead
	 * @param string $data Input data
	 * @return string Output data
	 */
	public static function entities_decode($data)
	{
		$decoder = new SimplePie_Decode_HTML_Entities($data);
		return $decoder->parse();
	}

	/**
	 * Remove RFC822 comments
	 *
	 * @param string $data Data to strip comments from
	 * @return string Comment stripped string
	 */
	public static function uncomment_rfc822($string)
	{
		$string = (string) $string;
		$position = 0;
		$length = strlen($string);
		$depth = 0;

		$output = '';

		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
		{
			$output .= substr($string, $position, $pos - $position);
			$position = $pos + 1;
			if ($string[$pos - 1] !== '\\')
			{
				$depth++;
				while ($depth && $position < $length)
				{
					$position += strcspn($string, '()', $position);
					if ($string[$position - 1] === '\\')
					{
						$position++;
						continue;
					}
					elseif (isset($string[$position]))
					{
						switch ($string[$position])
						{
							case '(':
								$depth++;
								break;

							case ')':
								$depth--;
								break;
						}
						$position++;
					}
					else
					{
						break;
					}
				}
			}
			else
			{
				$output .= '(';
			}
		}
		$output .= substr($string, $position);

		return $output;
	}

	public static function parse_mime($mime)
	{
		if (($pos = strpos($mime, ';')) === false)
		{
			return trim($mime);
		}
		else
		{
			return trim(substr($mime, 0, $pos));
		}
	}

	public static function atom_03_construct_type($attribs)
	{
		if (isset($attribs['']['mode']) && strtolower(trim($attribs['']['mode']) === 'base64'))
		{
			$mode = SIMPLEPIE_CONSTRUCT_BASE64;
		}
		else
		{
			$mode = SIMPLEPIE_CONSTRUCT_NONE;
		}
		if (isset($attribs['']['type']))
		{
			switch (strtolower(trim($attribs['']['type'])))
			{
				case 'text':
				case 'text/plain':
					return SIMPLEPIE_CONSTRUCT_TEXT | $mode;

				case 'html':
				case 'text/html':
					return SIMPLEPIE_CONSTRUCT_HTML | $mode;

				case 'xhtml':
				case 'application/xhtml+xml':
					return SIMPLEPIE_CONSTRUCT_XHTML | $mode;

				default:
					return SIMPLEPIE_CONSTRUCT_NONE | $mode;
			}
		}
		else
		{
			return SIMPLEPIE_CONSTRUCT_TEXT | $mode;
		}
	}

	public static function atom_10_construct_type($attribs)
	{
		if (isset($attribs['']['type']))
		{
			switch (strtolower(trim($attribs['']['type'])))
			{
				case 'text':
					return SIMPLEPIE_CONSTRUCT_TEXT;

				case 'html':
					return SIMPLEPIE_CONSTRUCT_HTML;

				case 'xhtml':
					return SIMPLEPIE_CONSTRUCT_XHTML;

				default:
					return SIMPLEPIE_CONSTRUCT_NONE;
			}
		}
		return SIMPLEPIE_CONSTRUCT_TEXT;
	}

	public static function atom_10_content_construct_type($attribs)
	{
		if (isset($attribs['']['type']))
		{
			$type = strtolower(trim($attribs['']['type']));
			switch ($type)
			{
				case 'text':
					return SIMPLEPIE_CONSTRUCT_TEXT;

				case 'html':
					return SIMPLEPIE_CONSTRUCT_HTML;

				case 'xhtml':
					return SIMPLEPIE_CONSTRUCT_XHTML;
			}
			if (in_array(substr($type, -4), array('+xml', '/xml')) || substr($type, 0, 5) === 'text/')
			{
				return SIMPLEPIE_CONSTRUCT_NONE;
			}
			else
			{
				return SIMPLEPIE_CONSTRUCT_BASE64;
			}
		}
		else
		{
			return SIMPLEPIE_CONSTRUCT_TEXT;
		}
	}

	public static function is_isegment_nz_nc($string)
	{
		return (bool) preg_match('/^([A-Za-z0-9\-._~\x{A0}-\x{D7FF}\x{F900}-\x{FDCF}\x{FDF0}-\x{FFEF}\x{10000}-\x{1FFFD}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}\x{40000}-\x{4FFFD}\x{50000}-\x{5FFFD}\x{60000}-\x{6FFFD}\x{70000}-\x{7FFFD}\x{80000}-\x{8FFFD}\x{90000}-\x{9FFFD}\x{A0000}-\x{AFFFD}\x{B0000}-\x{BFFFD}\x{C0000}-\x{CFFFD}\x{D0000}-\x{DFFFD}\x{E1000}-\x{EFFFD}!$&\'()*+,;=@]|(%[0-9ABCDEF]{2}))+$/u', $string);
	}

	public static function space_seperated_tokens($string)
	{
		$space_characters = "\x20\x09\x0A\x0B\x0C\x0D";
		$string_length = strlen($string);

		$position = strspn($string, $space_characters);
		$tokens = array();

		while ($position < $string_length)
		{
			$len = strcspn($string, $space_characters, $position);
			$tokens[] = substr($string, $position, $len);
			$position += $len;
			$position += strspn($string, $space_characters, $position);
		}

		return $tokens;
	}

	/**
	 * Converts a unicode codepoint to a UTF-8 character
	 *
	 * @static
	 * @param int $codepoint Unicode codepoint
	 * @return string UTF-8 character
	 */
	public static function codepoint_to_utf8($codepoint)
	{
		$codepoint = (int) $codepoint;
		if ($codepoint < 0)
		{
			return false;
		}
		else if ($codepoint <= 0x7f)
		{
			return chr($codepoint);
		}
		else if ($codepoint <= 0x7ff)
		{
			return chr(0xc0 | ($codepoint >> 6)) . chr(0x80 | ($codepoint & 0x3f));
		}
		else if ($codepoint <= 0xffff)
		{
			return chr(0xe0 | ($codepoint >> 12)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
		}
		else if ($codepoint <= 0x10ffff)
		{
			return chr(0xf0 | ($codepoint >> 18)) . chr(0x80 | (($codepoint >> 12) & 0x3f)) . chr(0x80 | (($codepoint >> 6) & 0x3f)) . chr(0x80 | ($codepoint & 0x3f));
		}
		else
		{
			// U+FFFD REPLACEMENT CHARACTER
			return "\xEF\xBF\xBD";
		}
	}

	/**
	 * Similar to parse_str()
	 *
	 * Returns an associative array of name/value pairs, where the value is an
	 * array of values that have used the same name
	 *
	 * @static
	 * @param string $str The input string.
	 * @return array
	 */
	public static function parse_str($str)
	{
		$return = array();
		$str = explode('&', $str);

		foreach ($str as $section)
		{
			if (strpos($section, '=') !== false)
			{
				list($name, $value) = explode('=', $section, 2);
				$return[urldecode($name)][] = urldecode($value);
			}
			else
			{
				$return[urldecode($section)][] = null;
			}
		}

		return $return;
	}

	/**
	 * Detect XML encoding, as per XML 1.0 Appendix F.1
	 *
	 * @todo Add support for EBCDIC
	 * @param string $data XML data
	 * @param SimplePie_Registry $registry Class registry
	 * @return array Possible encodings
	 */
	public static function xml_encoding($data, $registry)
	{
		// UTF-32 Big Endian BOM
		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
		{
			$encoding[] = 'UTF-32BE';
		}
		// UTF-32 Little Endian BOM
		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
		{
			$encoding[] = 'UTF-32LE';
		}
		// UTF-16 Big Endian BOM
		elseif (substr($data, 0, 2) === "\xFE\xFF")
		{
			$encoding[] = 'UTF-16BE';
		}
		// UTF-16 Little Endian BOM
		elseif (substr($data, 0, 2) === "\xFF\xFE")
		{
			$encoding[] = 'UTF-16LE';
		}
		// UTF-8 BOM
		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
		{
			$encoding[] = 'UTF-8';
		}
		// UTF-32 Big Endian Without BOM
		elseif (substr($data, 0, 20) === "\x00\x00\x00\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C")
		{
			if ($pos = strpos($data, "\x00\x00\x00\x3F\x00\x00\x00\x3E"))
			{
				$parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32BE', 'UTF-8')));
				if ($parser->parse())
				{
					$encoding[] = $parser->encoding;
				}
			}
			$encoding[] = 'UTF-32BE';
		}
		// UTF-32 Little Endian Without BOM
		elseif (substr($data, 0, 20) === "\x3C\x00\x00\x00\x3F\x00\x00\x00\x78\x00\x00\x00\x6D\x00\x00\x00\x6C\x00\x00\x00")
		{
			if ($pos = strpos($data, "\x3F\x00\x00\x00\x3E\x00\x00\x00"))
			{
				$parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 20), 'UTF-32LE', 'UTF-8')));
				if ($parser->parse())
				{
					$encoding[] = $parser->encoding;
				}
			}
			$encoding[] = 'UTF-32LE';
		}
		// UTF-16 Big Endian Without BOM
		elseif (substr($data, 0, 10) === "\x00\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C")
		{
			if ($pos = strpos($data, "\x00\x3F\x00\x3E"))
			{
				$parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16BE', 'UTF-8')));
				if ($parser->parse())
				{
					$encoding[] = $parser->encoding;
				}
			}
			$encoding[] = 'UTF-16BE';
		}
		// UTF-16 Little Endian Without BOM
		elseif (substr($data, 0, 10) === "\x3C\x00\x3F\x00\x78\x00\x6D\x00\x6C\x00")
		{
			if ($pos = strpos($data, "\x3F\x00\x3E\x00"))
			{
				$parser = $registry->create('XML_Declaration_Parser', array(SimplePie_Misc::change_encoding(substr($data, 20, $pos - 10), 'UTF-16LE', 'UTF-8')));
				if ($parser->parse())
				{
					$encoding[] = $parser->encoding;
				}
			}
			$encoding[] = 'UTF-16LE';
		}
		// US-ASCII (or superset)
		elseif (substr($data, 0, 5) === "\x3C\x3F\x78\x6D\x6C")
		{
			if ($pos = strpos($data, "\x3F\x3E"))
			{
				$parser = $registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5)));
				if ($parser->parse())
				{
					$encoding[] = $parser->encoding;
				}
			}
			$encoding[] = 'UTF-8';
		}
		// Fallback to UTF-8
		else
		{
			$encoding[] = 'UTF-8';
		}
		return $encoding;
	}

	public static function output_javascript()
	{
		if (function_exists('ob_gzhandler'))
		{
			ob_start('ob_gzhandler');
		}
		header('Content-type: text/javascript; charset: UTF-8');
		header('Cache-Control: must-revalidate');
		header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 604800) . ' GMT'); // 7 days
		?>
function embed_quicktime(type, bgcolor, width, height, link, placeholder, loop) {
	if (placeholder != '') {
		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" href="'+link+'" src="'+placeholder+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="false" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
	}
	else {
		document.writeln('<embed type="'+type+'" style="cursor:hand; cursor:pointer;" src="'+link+'" width="'+width+'" height="'+height+'" autoplay="false" target="myself" controller="true" loop="'+loop+'" scale="aspect" bgcolor="'+bgcolor+'" pluginspage="http://www.apple.com/quicktime/download/"></embed>');
	}
}

function embed_flash(bgcolor, width, height, link, loop, type) {
	document.writeln('<embed src="'+link+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="'+type+'" quality="high" width="'+width+'" height="'+height+'" bgcolor="'+bgcolor+'" loop="'+loop+'"></embed>');
}

function embed_flv(width, height, link, placeholder, loop, player) {
	document.writeln('<embed src="'+player+'" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" quality="high" width="'+width+'" height="'+height+'" wmode="transparent" flashvars="file='+link+'&autostart=false&repeat='+loop+'&showdigits=true&showfsbutton=false"></embed>');
}

function embed_wmedia(width, height, link) {
	document.writeln('<embed type="application/x-mplayer2" src="'+link+'" autosize="1" width="'+width+'" height="'+height+'" showcontrols="1" showstatusbar="0" showdisplay="0" autostart="0"></embed>');
}
		<?php
	}

	/**
	 * Get the SimplePie build timestamp
	 *
	 * Uses the git index if it exists, otherwise uses the modification time
	 * of the newest file.
	 */
	public static function get_build()
	{
		$root = dirname(dirname(__FILE__));
		if (file_exists($root . '/.git/index'))
		{
			return filemtime($root . '/.git/index');
		}
		elseif (file_exists($root . '/SimplePie'))
		{
			$time = 0;
			foreach (glob($root . '/SimplePie/*.php') as $file)
			{
				if (($mtime = filemtime($file)) > $time)
				{
					$time = $mtime;
				}
			}
			return $time;
		}
		elseif (file_exists(dirname(__FILE__) . '/Core.php'))
		{
			return filemtime(dirname(__FILE__) . '/Core.php');
		}
		else
		{
			return filemtime(__FILE__);
		}
	}

	/**
	 * Format debugging information
	 */
	public static function debug(&$sp)
	{
		$info = 'SimplePie ' . SIMPLEPIE_VERSION . ' Build ' . SIMPLEPIE_BUILD . "\n";
		$info .= 'PHP ' . PHP_VERSION . "\n";
		if ($sp->error() !== null)
		{
			$info .= 'Error occurred: ' . $sp->error() . "\n";
		}
		else
		{
			$info .= "No error found.\n";
		}
		$info .= "Extensions:\n";
		$extensions = array('pcre', 'curl', 'zlib', 'mbstring', 'iconv', 'xmlreader', 'xml');
		foreach ($extensions as $ext)
		{
			if (extension_loaded($ext))
			{
				$info .= "    $ext loaded\n";
				switch ($ext)
				{
					case 'pcre':
						$info .= '      Version ' . PCRE_VERSION . "\n";
						break;
					case 'curl':
						$version = curl_version();
						$info .= '      Version ' . $version['version'] . "\n";
						break;
					case 'mbstring':
						$info .= '      Overloading: ' . mb_get_info('func_overload') . "\n";
						break;
					case 'iconv':
						$info .= '      Version ' . ICONV_VERSION . "\n";
						break;
					case 'xml':
						$info .= '      Version ' . LIBXML_DOTTED_VERSION . "\n";
						break;
				}
			}
			else
			{
				$info .= "    $ext not loaded\n";
			}
		}
		return $info;
	}

	public static function silence_errors($num, $str)
	{
		// No-op
	}
}

vendor/simplepie/simplepie/library/SimplePie/Locator.php000064400000025614152177723700017511 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Used for feed auto-discovery
 *
 *
 * This class can be overloaded with {@see SimplePie::set_locator_class()}
 *
 * @package SimplePie
 */
class SimplePie_Locator
{
	var $useragent;
	var $timeout;
	var $file;
	var $local = array();
	var $elsewhere = array();
	var $cached_entities = array();
	var $http_base;
	var $base;
	var $base_location = 0;
	var $checked_feeds = 0;
	var $max_checked_feeds = 10;
	protected $registry;

	public function __construct(SimplePie_File $file, $timeout = 10, $useragent = null, $max_checked_feeds = 10)
	{
		$this->file = $file;
		$this->useragent = $useragent;
		$this->timeout = $timeout;
		$this->max_checked_feeds = $max_checked_feeds;

		if (class_exists('DOMDocument'))
		{
			$this->dom = new DOMDocument();

			set_error_handler(array('SimplePie_Misc', 'silence_errors'));
			$this->dom->loadHTML($this->file->body);
			restore_error_handler();
		}
		else
		{
			$this->dom = null;
		}
	}

	public function set_registry(SimplePie_Registry $registry)
	{
		$this->registry = $registry;
	}

	public function find($type = SIMPLEPIE_LOCATOR_ALL, &$working)
	{
		if ($this->is_feed($this->file))
		{
			return $this->file;
		}

		if ($this->file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
		{
			$sniffer = $this->registry->create('Content_Type_Sniffer', array($this->file));
			if ($sniffer->get_type() !== 'text/html')
			{
				return null;
			}
		}

		if ($type & ~SIMPLEPIE_LOCATOR_NONE)
		{
			$this->get_base();
		}

		if ($type & SIMPLEPIE_LOCATOR_AUTODISCOVERY && $working = $this->autodiscovery())
		{
			return $working[0];
		}

		if ($type & (SIMPLEPIE_LOCATOR_LOCAL_EXTENSION | SIMPLEPIE_LOCATOR_LOCAL_BODY | SIMPLEPIE_LOCATOR_REMOTE_EXTENSION | SIMPLEPIE_LOCATOR_REMOTE_BODY) && $this->get_links())
		{
			if ($type & SIMPLEPIE_LOCATOR_LOCAL_EXTENSION && $working = $this->extension($this->local))
			{
				return $working;
			}

			if ($type & SIMPLEPIE_LOCATOR_LOCAL_BODY && $working = $this->body($this->local))
			{
				return $working;
			}

			if ($type & SIMPLEPIE_LOCATOR_REMOTE_EXTENSION && $working = $this->extension($this->elsewhere))
			{
				return $working;
			}

			if ($type & SIMPLEPIE_LOCATOR_REMOTE_BODY && $working = $this->body($this->elsewhere))
			{
				return $working;
			}
		}
		return null;
	}

	public function is_feed($file)
	{
		if ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE)
		{
			$sniffer = $this->registry->create('Content_Type_Sniffer', array($file));
			$sniffed = $sniffer->get_type();
			if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml')))
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		elseif ($file->method & SIMPLEPIE_FILE_SOURCE_LOCAL)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	public function get_base()
	{
		if ($this->dom === null)
		{
			throw new SimplePie_Exception('DOMDocument not found, unable to use locator');
		}
		$this->http_base = $this->file->url;
		$this->base = $this->http_base;
		$elements = $this->dom->getElementsByTagName('base');
		foreach ($elements as $element)
		{
			if ($element->hasAttribute('href'))
			{
				$base = $this->registry->call('Misc', 'absolutize_url', array(trim($element->getAttribute('href')), $this->http_base));
				if ($base === false)
				{
					continue;
				}
				$this->base = $base;
				$this->base_location = method_exists($element, 'getLineNo') ? $element->getLineNo() : 0;
				break;
			}
		}
	}

	public function autodiscovery()
	{
		$done = array();
		$feeds = array();
		$feeds = array_merge($feeds, $this->search_elements_by_tag('link', $done, $feeds));
		$feeds = array_merge($feeds, $this->search_elements_by_tag('a', $done, $feeds));
		$feeds = array_merge($feeds, $this->search_elements_by_tag('area', $done, $feeds));

		if (!empty($feeds))
		{
			return array_values($feeds);
		}
		else
		{
			return null;
		}
	}

	protected function search_elements_by_tag($name, &$done, $feeds)
	{
		if ($this->dom === null)
		{
			throw new SimplePie_Exception('DOMDocument not found, unable to use locator');
		}

		$links = $this->dom->getElementsByTagName($name);
		foreach ($links as $link)
		{
			if ($this->checked_feeds === $this->max_checked_feeds)
			{
				break;
			}
			if ($link->hasAttribute('href') && $link->hasAttribute('rel'))
			{
				$rel = array_unique($this->registry->call('Misc', 'space_seperated_tokens', array(strtolower($link->getAttribute('rel')))));
				$line = method_exists($link, 'getLineNo') ? $link->getLineNo() : 1;

				if ($this->base_location < $line)
				{
					$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base));
				}
				else
				{
					$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base));
				}
				if ($href === false)
				{
					continue;
				}

				if (!in_array($href, $done) && in_array('feed', $rel) || (in_array('alternate', $rel) && !in_array('stylesheet', $rel) && $link->hasAttribute('type') && in_array(strtolower($this->registry->call('Misc', 'parse_mime', array($link->getAttribute('type')))), array('application/rss+xml', 'application/atom+xml'))) && !isset($feeds[$href]))
				{
					$this->checked_feeds++;
					$headers = array(
						'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
					);
					$feed = $this->registry->create('File', array($href, $this->timeout, 5, $headers, $this->useragent));
					if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
					{
						$feeds[$href] = $feed;
					}
				}
				$done[] = $href;
			}
		}

		return $feeds;
	}

	public function get_links()
	{
		if ($this->dom === null)
		{
			throw new SimplePie_Exception('DOMDocument not found, unable to use locator');
		}

		$links = $this->dom->getElementsByTagName('a');
		foreach ($links as $link)
		{
			if ($link->hasAttribute('href'))
			{
				$href = trim($link->getAttribute('href'));
				$parsed = $this->registry->call('Misc', 'parse_url', array($href));
				if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme']))
				{
					if ($this->base_location < $link->getLineNo())
					{
						$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base));
					}
					else
					{
						$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->http_base));
					}
					if ($href === false)
					{
						continue;
					}

					$current = $this->registry->call('Misc', 'parse_url', array($this->file->url));

					if ($parsed['authority'] === '' || $parsed['authority'] === $current['authority'])
					{
						$this->local[] = $href;
					}
					else
					{
						$this->elsewhere[] = $href;
					}
				}
			}
		}
		$this->local = array_unique($this->local);
		$this->elsewhere = array_unique($this->elsewhere);
		if (!empty($this->local) || !empty($this->elsewhere))
		{
			return true;
		}
		return null;
	}

	public function extension(&$array)
	{
		foreach ($array as $key => $value)
		{
			if ($this->checked_feeds === $this->max_checked_feeds)
			{
				break;
			}
			if (in_array(strtolower(strrchr($value, '.')), array('.rss', '.rdf', '.atom', '.xml')))
			{
				$this->checked_feeds++;

				$headers = array(
					'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
				);
				$feed = $this->registry->create('File', array($value, $this->timeout, 5, $headers, $this->useragent));
				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
				{
					return $feed;
				}
				else
				{
					unset($array[$key]);
				}
			}
		}
		return null;
	}

	public function body(&$array)
	{
		foreach ($array as $key => $value)
		{
			if ($this->checked_feeds === $this->max_checked_feeds)
			{
				break;
			}
			if (preg_match('/(rss|rdf|atom|xml)/i', $value))
			{
				$this->checked_feeds++;
				$headers = array(
					'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
				);
				$feed = $this->registry->create('File', array($value, $this->timeout, 5, null, $this->useragent));
				if ($feed->success && ($feed->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($feed->status_code === 200 || $feed->status_code > 206 && $feed->status_code < 300)) && $this->is_feed($feed))
				{
					return $feed;
				}
				else
				{
					unset($array[$key]);
				}
			}
		}
		return null;
	}
}

vendor/simplepie/simplepie/library/SimplePie/HTTP/Parser.php000064400000025202152177723700020112 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * HTTP Response Parser
 *
 * @package SimplePie
 * @subpackage HTTP
 */
class SimplePie_HTTP_Parser
{
	/**
	 * HTTP Version
	 *
	 * @var float
	 */
	public $http_version = 0.0;

	/**
	 * Status code
	 *
	 * @var int
	 */
	public $status_code = 0;

	/**
	 * Reason phrase
	 *
	 * @var string
	 */
	public $reason = '';

	/**
	 * Key/value pairs of the headers
	 *
	 * @var array
	 */
	public $headers = array();

	/**
	 * Body of the response
	 *
	 * @var string
	 */
	public $body = '';

	/**
	 * Current state of the state machine
	 *
	 * @var string
	 */
	protected $state = 'http_version';

	/**
	 * Input data
	 *
	 * @var string
	 */
	protected $data = '';

	/**
	 * Input data length (to avoid calling strlen() everytime this is needed)
	 *
	 * @var int
	 */
	protected $data_length = 0;

	/**
	 * Current position of the pointer
	 *
	 * @var int
	 */
	protected $position = 0;

	/**
	 * Name of the hedaer currently being parsed
	 *
	 * @var string
	 */
	protected $name = '';

	/**
	 * Value of the hedaer currently being parsed
	 *
	 * @var string
	 */
	protected $value = '';

	/**
	 * Create an instance of the class with the input data
	 *
	 * @param string $data Input data
	 */
	public function __construct($data)
	{
		$this->data = $data;
		$this->data_length = strlen($this->data);
	}

	/**
	 * Parse the input data
	 *
	 * @return bool true on success, false on failure
	 */
	public function parse()
	{
		while ($this->state && $this->state !== 'emit' && $this->has_data())
		{
			$state = $this->state;
			$this->$state();
		}
		$this->data = '';
		if ($this->state === 'emit' || $this->state === 'body')
		{
			return true;
		}
		else
		{
			$this->http_version = '';
			$this->status_code = '';
			$this->reason = '';
			$this->headers = array();
			$this->body = '';
			return false;
		}
	}

	/**
	 * Check whether there is data beyond the pointer
	 *
	 * @return bool true if there is further data, false if not
	 */
	protected function has_data()
	{
		return (bool) ($this->position < $this->data_length);
	}

	/**
	 * See if the next character is LWS
	 *
	 * @return bool true if the next character is LWS, false if not
	 */
	protected function is_linear_whitespace()
	{
		return (bool) ($this->data[$this->position] === "\x09"
			|| $this->data[$this->position] === "\x20"
			|| ($this->data[$this->position] === "\x0A"
				&& isset($this->data[$this->position + 1])
				&& ($this->data[$this->position + 1] === "\x09" || $this->data[$this->position + 1] === "\x20")));
	}

	/**
	 * Parse the HTTP version
	 */
	protected function http_version()
	{
		if (strpos($this->data, "\x0A") !== false && strtoupper(substr($this->data, 0, 5)) === 'HTTP/')
		{
			$len = strspn($this->data, '0123456789.', 5);
			$this->http_version = substr($this->data, 5, $len);
			$this->position += 5 + $len;
			if (substr_count($this->http_version, '.') <= 1)
			{
				$this->http_version = (float) $this->http_version;
				$this->position += strspn($this->data, "\x09\x20", $this->position);
				$this->state = 'status';
			}
			else
			{
				$this->state = false;
			}
		}
		else
		{
			$this->state = false;
		}
	}

	/**
	 * Parse the status code
	 */
	protected function status()
	{
		if ($len = strspn($this->data, '0123456789', $this->position))
		{
			$this->status_code = (int) substr($this->data, $this->position, $len);
			$this->position += $len;
			$this->state = 'reason';
		}
		else
		{
			$this->state = false;
		}
	}

	/**
	 * Parse the reason phrase
	 */
	protected function reason()
	{
		$len = strcspn($this->data, "\x0A", $this->position);
		$this->reason = trim(substr($this->data, $this->position, $len), "\x09\x0D\x20");
		$this->position += $len + 1;
		$this->state = 'new_line';
	}

	/**
	 * Deal with a new line, shifting data around as needed
	 */
	protected function new_line()
	{
		$this->value = trim($this->value, "\x0D\x20");
		if ($this->name !== '' && $this->value !== '')
		{
			$this->name = strtolower($this->name);
			// We should only use the last Content-Type header. c.f. issue #1
			if (isset($this->headers[$this->name]) && $this->name !== 'content-type')
			{
				$this->headers[$this->name] .= ', ' . $this->value;
			}
			else
			{
				$this->headers[$this->name] = $this->value;
			}
		}
		$this->name = '';
		$this->value = '';
		if (substr($this->data[$this->position], 0, 2) === "\x0D\x0A")
		{
			$this->position += 2;
			$this->state = 'body';
		}
		elseif ($this->data[$this->position] === "\x0A")
		{
			$this->position++;
			$this->state = 'body';
		}
		else
		{
			$this->state = 'name';
		}
	}

	/**
	 * Parse a header name
	 */
	protected function name()
	{
		$len = strcspn($this->data, "\x0A:", $this->position);
		if (isset($this->data[$this->position + $len]))
		{
			if ($this->data[$this->position + $len] === "\x0A")
			{
				$this->position += $len;
				$this->state = 'new_line';
			}
			else
			{
				$this->name = substr($this->data, $this->position, $len);
				$this->position += $len + 1;
				$this->state = 'value';
			}
		}
		else
		{
			$this->state = false;
		}
	}

	/**
	 * Parse LWS, replacing consecutive LWS characters with a single space
	 */
	protected function linear_whitespace()
	{
		do
		{
			if (substr($this->data, $this->position, 2) === "\x0D\x0A")
			{
				$this->position += 2;
			}
			elseif ($this->data[$this->position] === "\x0A")
			{
				$this->position++;
			}
			$this->position += strspn($this->data, "\x09\x20", $this->position);
		} while ($this->has_data() && $this->is_linear_whitespace());
		$this->value .= "\x20";
	}

	/**
	 * See what state to move to while within non-quoted header values
	 */
	protected function value()
	{
		if ($this->is_linear_whitespace())
		{
			$this->linear_whitespace();
		}
		else
		{
			switch ($this->data[$this->position])
			{
				case '"':
					// Workaround for ETags: we have to include the quotes as
					// part of the tag.
					if (strtolower($this->name) === 'etag')
					{
						$this->value .= '"';
						$this->position++;
						$this->state = 'value_char';
						break;
					}
					$this->position++;
					$this->state = 'quote';
					break;

				case "\x0A":
					$this->position++;
					$this->state = 'new_line';
					break;

				default:
					$this->state = 'value_char';
					break;
			}
		}
	}

	/**
	 * Parse a header value while outside quotes
	 */
	protected function value_char()
	{
		$len = strcspn($this->data, "\x09\x20\x0A\"", $this->position);
		$this->value .= substr($this->data, $this->position, $len);
		$this->position += $len;
		$this->state = 'value';
	}

	/**
	 * See what state to move to while within quoted header values
	 */
	protected function quote()
	{
		if ($this->is_linear_whitespace())
		{
			$this->linear_whitespace();
		}
		else
		{
			switch ($this->data[$this->position])
			{
				case '"':
					$this->position++;
					$this->state = 'value';
					break;

				case "\x0A":
					$this->position++;
					$this->state = 'new_line';
					break;

				case '\\':
					$this->position++;
					$this->state = 'quote_escaped';
					break;

				default:
					$this->state = 'quote_char';
					break;
			}
		}
	}

	/**
	 * Parse a header value while within quotes
	 */
	protected function quote_char()
	{
		$len = strcspn($this->data, "\x09\x20\x0A\"\\", $this->position);
		$this->value .= substr($this->data, $this->position, $len);
		$this->position += $len;
		$this->state = 'value';
	}

	/**
	 * Parse an escaped character within quotes
	 */
	protected function quote_escaped()
	{
		$this->value .= $this->data[$this->position];
		$this->position++;
		$this->state = 'quote';
	}

	/**
	 * Parse the body
	 */
	protected function body()
	{
		$this->body = substr($this->data, $this->position);
		if (!empty($this->headers['transfer-encoding']))
		{
			unset($this->headers['transfer-encoding']);
			$this->state = 'chunked';
		}
		else
		{
			$this->state = 'emit';
		}
	}

	/**
	 * Parsed a "Transfer-Encoding: chunked" body
	 */
	protected function chunked()
	{
		if (!preg_match('/^([0-9a-f]+)[^\r\n]*\r\n/i', trim($this->body)))
		{
			$this->state = 'emit';
			return;
		}

		$decoded = '';
		$encoded = $this->body;

		while (true)
		{
			$is_chunked = (bool) preg_match( '/^([0-9a-f]+)[^\r\n]*\r\n/i', $encoded, $matches );
			if (!$is_chunked)
			{
				// Looks like it's not chunked after all
				$this->state = 'emit';
				return;
			}

			$length = hexdec(trim($matches[1]));
			if ($length === 0)
			{
				// Ignore trailer headers
				$this->state = 'emit';
				$this->body = $decoded;
				return;
			}

			$chunk_length = strlen($matches[0]);
			$decoded .= $part = substr($encoded, $chunk_length, $length);
			$encoded = substr($encoded, $chunk_length + $length + 2);

			if (trim($encoded) === '0' || empty($encoded))
			{
				$this->state = 'emit';
				$this->body = $decoded;
				return;
			}
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Source.php000064400000050073152177723700017343 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles `<atom:source>`
 *
 * Used by {@see SimplePie_Item::get_source()}
 *
 * This class can be overloaded with {@see SimplePie::set_source_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Source
{
	var $item;
	var $data = array();
	protected $registry;

	public function __construct($item, $data)
	{
		$this->item = $item;
		$this->data = $data;
	}

	public function set_registry(SimplePie_Registry $registry)
	{
		$this->registry = $registry;
	}

	public function __toString()
	{
		return md5(serialize($this->data));
	}

	public function get_source_tags($namespace, $tag)
	{
		if (isset($this->data['child'][$namespace][$tag]))
		{
			return $this->data['child'][$namespace][$tag];
		}
		else
		{
			return null;
		}
	}

	public function get_base($element = array())
	{
		return $this->item->get_base($element);
	}

	public function sanitize($data, $type, $base = '')
	{
		return $this->item->sanitize($data, $type, $base);
	}

	public function get_item()
	{
		return $this->item;
	}

	public function get_title()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	public function get_category($key = 0)
	{
		$categories = $this->get_categories();
		if (isset($categories[$key]))
		{
			return $categories[$key];
		}
		else
		{
			return null;
		}
	}

	public function get_categories()
	{
		$categories = array();

		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
		{
			$term = null;
			$scheme = null;
			$label = null;
			if (isset($category['attribs']['']['term']))
			{
				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['scheme']))
			{
				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['label']))
			{
				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
		{
			// This is really the label, but keep this as the term also for BC.
			// Label will also work on retrieving because that falls back to term.
			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			if (isset($category['attribs']['']['domain']))
			{
				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			else
			{
				$scheme = null;
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, null));
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($categories))
		{
			return array_unique($categories);
		}
		else
		{
			return null;
		}
	}

	public function get_author($key = 0)
	{
		$authors = $this->get_authors();
		if (isset($authors[$key]))
		{
			return $authors[$key];
		}
		else
		{
			return null;
		}
	}

	public function get_authors()
	{
		$authors = array();
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		if ($author = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($authors))
		{
			return array_unique($authors);
		}
		else
		{
			return null;
		}
	}

	public function get_contributor($key = 0)
	{
		$contributors = $this->get_contributors();
		if (isset($contributors[$key]))
		{
			return $contributors[$key];
		}
		else
		{
			return null;
		}
	}

	public function get_contributors()
	{
		$contributors = array();
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		foreach ((array) $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}

		if (!empty($contributors))
		{
			return array_unique($contributors);
		}
		else
		{
			return null;
		}
	}

	public function get_link($key = 0, $rel = 'alternate')
	{
		$links = $this->get_links($rel);
		if (isset($links[$key]))
		{
			return $links[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Added for parity between the parent-level and the item/entry-level.
	 */
	public function get_permalink()
	{
		return $this->get_link(0);
	}

	public function get_links($rel = 'alternate')
	{
		if (!isset($this->data['links']))
		{
			$this->data['links'] = array();
			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
			{
				foreach ($links as $link)
				{
					if (isset($link['attribs']['']['href']))
					{
						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
					}
				}
			}
			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
			{
				foreach ($links as $link)
				{
					if (isset($link['attribs']['']['href']))
					{
						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));

					}
				}
			}
			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}

			$keys = array_keys($this->data['links']);
			foreach ($keys as $key)
			{
				if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key)))
				{
					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
					}
					else
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
					}
				}
				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
				{
					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
				}
				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
			}
		}

		if (isset($this->data['links'][$rel]))
		{
			return $this->data['links'][$rel];
		}
		else
		{
			return null;
		}
	}

	public function get_description()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		else
		{
			return null;
		}
	}

	public function get_copyright()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	public function get_language()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (isset($this->data['xml_lang']))
		{
			return $this->sanitize($this->data['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	public function get_latitude()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[1];
		}
		else
		{
			return null;
		}
	}

	public function get_longitude()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
		{
			return (float) $return[0]['data'];
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[2];
		}
		else
		{
			return null;
		}
	}

	public function get_image_url()
	{
		if ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
		{
			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_source_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/File.php000064400000022716152177723700016765 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Used for fetching remote files and reading local files
 *
 * Supports HTTP 1.0 via cURL or fsockopen, with spotty HTTP 1.1 support
 *
 * This class can be overloaded with {@see SimplePie::set_file_class()}
 *
 * @package SimplePie
 * @subpackage HTTP
 * @todo Move to properly supporting RFC2616 (HTTP/1.1)
 */
class SimplePie_File
{
	var $url;
	var $useragent;
	var $success = true;
	var $headers = array();
	var $body;
	var $status_code;
	var $redirects = 0;
	var $error;
	var $method = SIMPLEPIE_FILE_SOURCE_NONE;

	public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false)
	{
		if (class_exists('idna_convert'))
		{
			$idn = new idna_convert();
			$parsed = SimplePie_Misc::parse_url($url);
			$url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
		}
		$this->url = $url;
		$this->useragent = $useragent;
		if (preg_match('/^http(s)?:\/\//i', $url))
		{
			if ($useragent === null)
			{
				$useragent = ini_get('user_agent');
				$this->useragent = $useragent;
			}
			if (!is_array($headers))
			{
				$headers = array();
			}
			if (!$force_fsockopen && function_exists('curl_exec'))
			{
				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_CURL;
				$fp = curl_init();
				$headers2 = array();
				foreach ($headers as $key => $value)
				{
					$headers2[] = "$key: $value";
				}
				if (version_compare(SimplePie_Misc::get_curl_version(), '7.10.5', '>='))
				{
					curl_setopt($fp, CURLOPT_ENCODING, '');
				}
				curl_setopt($fp, CURLOPT_URL, $url);
				curl_setopt($fp, CURLOPT_HEADER, 1);
				curl_setopt($fp, CURLOPT_RETURNTRANSFER, 1);
				curl_setopt($fp, CURLOPT_TIMEOUT, $timeout);
				curl_setopt($fp, CURLOPT_CONNECTTIMEOUT, $timeout);
				curl_setopt($fp, CURLOPT_REFERER, $url);
				curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
				curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
				if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
				{
					curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
					curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects);
				}

				$this->headers = curl_exec($fp);
				if (curl_errno($fp) === 23 || curl_errno($fp) === 61)
				{
					curl_setopt($fp, CURLOPT_ENCODING, 'none');
					$this->headers = curl_exec($fp);
				}
				if (curl_errno($fp))
				{
					$this->error = 'cURL error ' . curl_errno($fp) . ': ' . curl_error($fp);
					$this->success = false;
				}
				else
				{
					$info = curl_getinfo($fp);
					curl_close($fp);
					$this->headers = explode("\r\n\r\n", $this->headers, $info['redirect_count'] + 1);
					$this->headers = array_pop($this->headers);
					$parser = new SimplePie_HTTP_Parser($this->headers);
					if ($parser->parse())
					{
						$this->headers = $parser->headers;
						$this->body = $parser->body;
						$this->status_code = $parser->status_code;
						if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
						{
							$this->redirects++;
							$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
							return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
						}
					}
				}
			}
			else
			{
				$this->method = SIMPLEPIE_FILE_SOURCE_REMOTE | SIMPLEPIE_FILE_SOURCE_FSOCKOPEN;
				$url_parts = parse_url($url);
				$socket_host = $url_parts['host'];
				if (isset($url_parts['scheme']) && strtolower($url_parts['scheme']) === 'https')
				{
					$socket_host = "ssl://$url_parts[host]";
					$url_parts['port'] = 443;
				}
				if (!isset($url_parts['port']))
				{
					$url_parts['port'] = 80;
				}
				$fp = @fsockopen($socket_host, $url_parts['port'], $errno, $errstr, $timeout);
				if (!$fp)
				{
					$this->error = 'fsockopen error: ' . $errstr;
					$this->success = false;
				}
				else
				{
					stream_set_timeout($fp, $timeout);
					if (isset($url_parts['path']))
					{
						if (isset($url_parts['query']))
						{
							$get = "$url_parts[path]?$url_parts[query]";
						}
						else
						{
							$get = $url_parts['path'];
						}
					}
					else
					{
						$get = '/';
					}
					$out = "GET $get HTTP/1.1\r\n";
					$out .= "Host: $url_parts[host]\r\n";
					$out .= "User-Agent: $useragent\r\n";
					if (extension_loaded('zlib'))
					{
						$out .= "Accept-Encoding: x-gzip,gzip,deflate\r\n";
					}

					if (isset($url_parts['user']) && isset($url_parts['pass']))
					{
						$out .= "Authorization: Basic " . base64_encode("$url_parts[user]:$url_parts[pass]") . "\r\n";
					}
					foreach ($headers as $key => $value)
					{
						$out .= "$key: $value\r\n";
					}
					$out .= "Connection: Close\r\n\r\n";
					fwrite($fp, $out);

					$info = stream_get_meta_data($fp);

					$this->headers = '';
					while (!$info['eof'] && !$info['timed_out'])
					{
						$this->headers .= fread($fp, 1160);
						$info = stream_get_meta_data($fp);
					}
					if (!$info['timed_out'])
					{
						$parser = new SimplePie_HTTP_Parser($this->headers);
						if ($parser->parse())
						{
							$this->headers = $parser->headers;
							$this->body = $parser->body;
							$this->status_code = $parser->status_code;
							if ((in_array($this->status_code, array(300, 301, 302, 303, 307)) || $this->status_code > 307 && $this->status_code < 400) && isset($this->headers['location']) && $this->redirects < $redirects)
							{
								$this->redirects++;
								$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
								return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
							}
							if (isset($this->headers['content-encoding']))
							{
								// Hey, we act dumb elsewhere, so let's do that here too
								switch (strtolower(trim($this->headers['content-encoding'], "\x09\x0A\x0D\x20")))
								{
									case 'gzip':
									case 'x-gzip':
										$decoder = new SimplePie_gzdecode($this->body);
										if (!$decoder->parse())
										{
											$this->error = 'Unable to decode HTTP "gzip" stream';
											$this->success = false;
										}
										else
										{
											$this->body = $decoder->data;
										}
										break;

									case 'deflate':
										if (($decompressed = gzinflate($this->body)) !== false)
										{
											$this->body = $decompressed;
										}
										else if (($decompressed = gzuncompress($this->body)) !== false)
										{
											$this->body = $decompressed;
										}
										else if (function_exists('gzdecode') && ($decompressed = gzdecode($this->body)) !== false)
										{
											$this->body = $decompressed;
										}
										else
										{
											$this->error = 'Unable to decode HTTP "deflate" stream';
											$this->success = false;
										}
										break;

									default:
										$this->error = 'Unknown content coding';
										$this->success = false;
								}
							}
						}
					}
					else
					{
						$this->error = 'fsocket timed out';
						$this->success = false;
					}
					fclose($fp);
				}
			}
		}
		else
		{
			$this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS;
			if (!$this->body = file_get_contents($url))
			{
				$this->error = 'file_get_contents could not read the file';
				$this->success = false;
			}
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Net/IPv6.php000064400000016630152177723700017416 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Class to validate and to work with IPv6 addresses.
 *
 * @package SimplePie
 * @subpackage HTTP
 * @copyright 2003-2005 The PHP Group
 * @license http://www.opensource.org/licenses/bsd-license.php
 * @link http://pear.php.net/package/Net_IPv6
 * @author Alexander Merz <alexander.merz@web.de>
 * @author elfrink at introweb dot nl
 * @author Josh Peck <jmp at joshpeck dot org>
 * @author Geoffrey Sneddon <geoffers@gmail.com>
 */
class SimplePie_Net_IPv6
{
	/**
	 * Uncompresses an IPv6 address
	 *
	 * RFC 4291 allows you to compress concecutive zero pieces in an address to
	 * '::'. This method expects a valid IPv6 address and expands the '::' to
	 * the required number of zero pieces.
	 *
	 * Example:  FF01::101   ->  FF01:0:0:0:0:0:0:101
	 *           ::1         ->  0:0:0:0:0:0:0:1
	 *
	 * @author Alexander Merz <alexander.merz@web.de>
	 * @author elfrink at introweb dot nl
	 * @author Josh Peck <jmp at joshpeck dot org>
	 * @copyright 2003-2005 The PHP Group
	 * @license http://www.opensource.org/licenses/bsd-license.php
	 * @param string $ip An IPv6 address
	 * @return string The uncompressed IPv6 address
	 */
	public static function uncompress($ip)
	{
		$c1 = -1;
		$c2 = -1;
		if (substr_count($ip, '::') === 1)
		{
			list($ip1, $ip2) = explode('::', $ip);
			if ($ip1 === '')
			{
				$c1 = -1;
			}
			else
			{
				$c1 = substr_count($ip1, ':');
			}
			if ($ip2 === '')
			{
				$c2 = -1;
			}
			else
			{
				$c2 = substr_count($ip2, ':');
			}
			if (strpos($ip2, '.') !== false)
			{
				$c2++;
			}
			// ::
			if ($c1 === -1 && $c2 === -1)
			{
				$ip = '0:0:0:0:0:0:0:0';
			}
			// ::xxx
			else if ($c1 === -1)
			{
				$fill = str_repeat('0:', 7 - $c2);
				$ip = str_replace('::', $fill, $ip);
			}
			// xxx::
			else if ($c2 === -1)
			{
				$fill = str_repeat(':0', 7 - $c1);
				$ip = str_replace('::', $fill, $ip);
			}
			// xxx::xxx
			else
			{
				$fill = ':' . str_repeat('0:', 6 - $c2 - $c1);
				$ip = str_replace('::', $fill, $ip);
			}
		}
		return $ip;
	}

	/**
	 * Compresses an IPv6 address
	 *
	 * RFC 4291 allows you to compress concecutive zero pieces in an address to
	 * '::'. This method expects a valid IPv6 address and compresses consecutive
	 * zero pieces to '::'.
	 *
	 * Example:  FF01:0:0:0:0:0:0:101   ->  FF01::101
	 *           0:0:0:0:0:0:0:1        ->  ::1
	 *
	 * @see uncompress()
	 * @param string $ip An IPv6 address
	 * @return string The compressed IPv6 address
	 */
	public static function compress($ip)
	{
		// Prepare the IP to be compressed
		$ip = self::uncompress($ip);
		$ip_parts = self::split_v6_v4($ip);

		// Replace all leading zeros
		$ip_parts[0] = preg_replace('/(^|:)0+([0-9])/', '\1\2', $ip_parts[0]);

		// Find bunches of zeros
		if (preg_match_all('/(?:^|:)(?:0(?::|$))+/', $ip_parts[0], $matches, PREG_OFFSET_CAPTURE))
		{
			$max = 0;
			$pos = null;
			foreach ($matches[0] as $match)
			{
				if (strlen($match[0]) > $max)
				{
					$max = strlen($match[0]);
					$pos = $match[1];
				}
			}

			$ip_parts[0] = substr_replace($ip_parts[0], '::', $pos, $max);
		}

		if ($ip_parts[1] !== '')
		{
			return implode(':', $ip_parts);
		}
		else
		{
			return $ip_parts[0];
		}
	}

	/**
	 * Splits an IPv6 address into the IPv6 and IPv4 representation parts
	 *
	 * RFC 4291 allows you to represent the last two parts of an IPv6 address
	 * using the standard IPv4 representation
	 *
	 * Example:  0:0:0:0:0:0:13.1.68.3
	 *           0:0:0:0:0:FFFF:129.144.52.38
	 *
	 * @param string $ip An IPv6 address
	 * @return array [0] contains the IPv6 represented part, and [1] the IPv4 represented part
	 */
	private static function split_v6_v4($ip)
	{
		if (strpos($ip, '.') !== false)
		{
			$pos = strrpos($ip, ':');
			$ipv6_part = substr($ip, 0, $pos);
			$ipv4_part = substr($ip, $pos + 1);
			return array($ipv6_part, $ipv4_part);
		}
		else
		{
			return array($ip, '');
		}
	}

	/**
	 * Checks an IPv6 address
	 *
	 * Checks if the given IP is a valid IPv6 address
	 *
	 * @param string $ip An IPv6 address
	 * @return bool true if $ip is a valid IPv6 address
	 */
	public static function check_ipv6($ip)
	{
		$ip = self::uncompress($ip);
		list($ipv6, $ipv4) = self::split_v6_v4($ip);
		$ipv6 = explode(':', $ipv6);
		$ipv4 = explode('.', $ipv4);
		if (count($ipv6) === 8 && count($ipv4) === 1 || count($ipv6) === 6 && count($ipv4) === 4)
		{
			foreach ($ipv6 as $ipv6_part)
			{
				// The section can't be empty
				if ($ipv6_part === '')
					return false;

				// Nor can it be over four characters
				if (strlen($ipv6_part) > 4)
					return false;

				// Remove leading zeros (this is safe because of the above)
				$ipv6_part = ltrim($ipv6_part, '0');
				if ($ipv6_part === '')
					$ipv6_part = '0';

				// Check the value is valid
				$value = hexdec($ipv6_part);
				if (dechex($value) !== strtolower($ipv6_part) || $value < 0 || $value > 0xFFFF)
					return false;
			}
			if (count($ipv4) === 4)
			{
				foreach ($ipv4 as $ipv4_part)
				{
					$value = (int) $ipv4_part;
					if ((string) $value !== $ipv4_part || $value < 0 || $value > 0xFF)
						return false;
				}
			}
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Checks if the given IP is a valid IPv6 address
	 *
	 * @codeCoverageIgnore
	 * @deprecated Use {@see SimplePie_Net_IPv6::check_ipv6()} instead
	 * @see check_ipv6
	 * @param string $ip An IPv6 address
	 * @return bool true if $ip is a valid IPv6 address
	 */
	public static function checkIPv6($ip)
	{
		return self::check_ipv6($ip);
	}
}
vendor/simplepie/simplepie/library/SimplePie/Registry.php000064400000013547152177723700017720 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles creating objects and calling methods
 *
 * Access this via {@see SimplePie::get_registry()}
 *
 * @package SimplePie
 */
class SimplePie_Registry
{
	/**
	 * Default class mapping
	 *
	 * Overriding classes *must* subclass these.
	 *
	 * @var array
	 */
	protected $default = array(
		'Cache' => 'SimplePie_Cache',
		'Locator' => 'SimplePie_Locator',
		'Parser' => 'SimplePie_Parser',
		'File' => 'SimplePie_File',
		'Sanitize' => 'SimplePie_Sanitize',
		'Item' => 'SimplePie_Item',
		'Author' => 'SimplePie_Author',
		'Category' => 'SimplePie_Category',
		'Enclosure' => 'SimplePie_Enclosure',
		'Caption' => 'SimplePie_Caption',
		'Copyright' => 'SimplePie_Copyright',
		'Credit' => 'SimplePie_Credit',
		'Rating' => 'SimplePie_Rating',
		'Restriction' => 'SimplePie_Restriction',
		'Content_Type_Sniffer' => 'SimplePie_Content_Type_Sniffer',
		'Source' => 'SimplePie_Source',
		'Misc' => 'SimplePie_Misc',
		'XML_Declaration_Parser' => 'SimplePie_XML_Declaration_Parser',
		'Parse_Date' => 'SimplePie_Parse_Date',
	);

	/**
	 * Class mapping
	 *
	 * @see register()
	 * @var array
	 */
	protected $classes = array();

	/**
	 * Legacy classes
	 *
	 * @see register()
	 * @var array
	 */
	protected $legacy = array();

	/**
	 * Constructor
	 *
	 * No-op
	 */
	public function __construct() { }

	/**
	 * Register a class
	 *
	 * @param string $type See {@see $default} for names
	 * @param string $class Class name, must subclass the corresponding default
	 * @param bool $legacy Whether to enable legacy support for this class
	 * @return bool Successfulness
	 */
	public function register($type, $class, $legacy = false)
	{
		if (!is_subclass_of($class, $this->default[$type]))
		{
			return false;
		}

		$this->classes[$type] = $class;

		if ($legacy)
		{
			$this->legacy[] = $class;
		}

		return true;
	}

	/**
	 * Get the class registered for a type
	 *
	 * Where possible, use {@see create()} or {@see call()} instead
	 *
	 * @param string $type
	 * @return string|null
	 */
	public function get_class($type)
	{
		if (!empty($this->classes[$type]))
		{
			return $this->classes[$type];
		}
		if (!empty($this->default[$type]))
		{
			return $this->default[$type];
		}

		return null;
	}

	/**
	 * Create a new instance of a given type
	 *
	 * @param string $type
	 * @param array $parameters Parameters to pass to the constructor
	 * @return object Instance of class
	 */
	public function &create($type, $parameters = array())
	{
		$class = $this->get_class($type);

		if (in_array($class, $this->legacy))
		{
			switch ($type)
			{
				case 'locator':
					// Legacy: file, timeout, useragent, file_class, max_checked_feeds, content_type_sniffer_class
					// Specified: file, timeout, useragent, max_checked_feeds
					$replacement = array($this->get_class('file'), $parameters[3], $this->get_class('content_type_sniffer'));
					array_splice($parameters, 3, 1, $replacement);
					break;
			}
		}

		if (!method_exists($class, '__construct'))
		{
			$instance = new $class;
		}
		else
		{
			$reflector = new ReflectionClass($class);
			$instance = $reflector->newInstanceArgs($parameters);
		}

		if (method_exists($instance, 'set_registry'))
		{
			$instance->set_registry($this);
		}
		return $instance;
	}

	/**
	 * Call a static method for a type
	 *
	 * @param string $type
	 * @param string $method
	 * @param array $parameters
	 * @return mixed
	 */
	public function &call($type, $method, $parameters = array())
	{
		$class = $this->get_class($type);

		if (in_array($class, $this->legacy))
		{
			switch ($type)
			{
				case 'Cache':
					// For backwards compatibility with old non-static
					// Cache::create() methods
					if ($method === 'get_handler')
					{
						$result = @call_user_func_array(array($class, 'create'), $parameters);
						return $result;
					}
					break;
			}
		}

		$result = call_user_func_array(array($class, $method), $parameters);
		return $result;
	}
}vendor/simplepie/simplepie/library/SimplePie/Rating.php000064400000006573152177723700017335 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles `<media:rating>` or `<itunes:explicit>` tags as defined in Media RSS and iTunes RSS respectively
 *
 * Used by {@see SimplePie_Enclosure::get_rating()} and {@see SimplePie_Enclosure::get_ratings()}
 *
 * This class can be overloaded with {@see SimplePie::set_rating_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Rating
{
	/**
	 * Rating scheme
	 *
	 * @var string
	 * @see get_scheme()
	 */
	var $scheme;

	/**
	 * Rating value
	 *
	 * @var string
	 * @see get_value()
	 */
	var $value;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 */
	public function __construct($scheme = null, $value = null)
	{
		$this->scheme = $scheme;
		$this->value = $value;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the organizational scheme for the rating
	 *
	 * @return string|null
	 */
	public function get_scheme()
	{
		if ($this->scheme !== null)
		{
			return $this->scheme;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the value of the rating
	 *
	 * @return string|null
	 */
	public function get_value()
	{
		if ($this->value !== null)
		{
			return $this->value;
		}
		else
		{
			return null;
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Core.php000064400000004334152177723700016772 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * SimplePie class.
 *
 * Class for backward compatibility.
 *
 * @deprecated Use {@see SimplePie} directly
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Core extends SimplePie
{

}vendor/simplepie/simplepie/library/SimplePie/IRI.php000064400000067307152177723700016536 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * IRI parser/serialiser/normaliser
 *
 * @package SimplePie
 * @subpackage HTTP
 * @author Geoffrey Sneddon
 * @author Steve Minutillo
 * @author Ryan McCue
 * @copyright 2007-2012 Geoffrey Sneddon, Steve Minutillo, Ryan McCue
 * @license http://www.opensource.org/licenses/bsd-license.php
 */
class SimplePie_IRI
{
	/**
	 * Scheme
	 *
	 * @var string
	 */
	protected $scheme = null;

	/**
	 * User Information
	 *
	 * @var string
	 */
	protected $iuserinfo = null;

	/**
	 * ihost
	 *
	 * @var string
	 */
	protected $ihost = null;

	/**
	 * Port
	 *
	 * @var string
	 */
	protected $port = null;

	/**
	 * ipath
	 *
	 * @var string
	 */
	protected $ipath = '';

	/**
	 * iquery
	 *
	 * @var string
	 */
	protected $iquery = null;

	/**
	 * ifragment
	 *
	 * @var string
	 */
	protected $ifragment = null;

	/**
	 * Normalization database
	 *
	 * Each key is the scheme, each value is an array with each key as the IRI
	 * part and value as the default value for that part.
	 */
	protected $normalization = array(
		'acap' => array(
			'port' => 674
		),
		'dict' => array(
			'port' => 2628
		),
		'file' => array(
			'ihost' => 'localhost'
		),
		'http' => array(
			'port' => 80,
			'ipath' => '/'
		),
		'https' => array(
			'port' => 443,
			'ipath' => '/'
		),
	);

	/**
	 * Return the entire IRI when you try and read the object as a string
	 *
	 * @return string
	 */
	public function __toString()
	{
		return $this->get_iri();
	}

	/**
	 * Overload __set() to provide access via properties
	 *
	 * @param string $name Property name
	 * @param mixed $value Property value
	 */
	public function __set($name, $value)
	{
		if (method_exists($this, 'set_' . $name))
		{
			call_user_func(array($this, 'set_' . $name), $value);
		}
		elseif (
			   $name === 'iauthority'
			|| $name === 'iuserinfo'
			|| $name === 'ihost'
			|| $name === 'ipath'
			|| $name === 'iquery'
			|| $name === 'ifragment'
		)
		{
			call_user_func(array($this, 'set_' . substr($name, 1)), $value);
		}
	}

	/**
	 * Overload __get() to provide access via properties
	 *
	 * @param string $name Property name
	 * @return mixed
	 */
	public function __get($name)
	{
		// isset() returns false for null, we don't want to do that
		// Also why we use array_key_exists below instead of isset()
		$props = get_object_vars($this);

		if (
			$name === 'iri' ||
			$name === 'uri' ||
			$name === 'iauthority' ||
			$name === 'authority'
		)
		{
			$return = $this->{"get_$name"}();
		}
		elseif (array_key_exists($name, $props))
		{
			$return = $this->$name;
		}
		// host -> ihost
		elseif (($prop = 'i' . $name) && array_key_exists($prop, $props))
		{
			$name = $prop;
			$return = $this->$prop;
		}
		// ischeme -> scheme
		elseif (($prop = substr($name, 1)) && array_key_exists($prop, $props))
		{
			$name = $prop;
			$return = $this->$prop;
		}
		else
		{
			trigger_error('Undefined property: ' . get_class($this) . '::' . $name, E_USER_NOTICE);
			$return = null;
		}

		if ($return === null && isset($this->normalization[$this->scheme][$name]))
		{
			return $this->normalization[$this->scheme][$name];
		}
		else
		{
			return $return;
		}
	}

	/**
	 * Overload __isset() to provide access via properties
	 *
	 * @param string $name Property name
	 * @return bool
	 */
	public function __isset($name)
	{
		if (method_exists($this, 'get_' . $name) || isset($this->$name))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Overload __unset() to provide access via properties
	 *
	 * @param string $name Property name
	 */
	public function __unset($name)
	{
		if (method_exists($this, 'set_' . $name))
		{
			call_user_func(array($this, 'set_' . $name), '');
		}
	}

	/**
	 * Create a new IRI object, from a specified string
	 *
	 * @param string $iri
	 */
	public function __construct($iri = null)
	{
		$this->set_iri($iri);
	}

	/**
	 * Create a new IRI object by resolving a relative IRI
	 *
	 * Returns false if $base is not absolute, otherwise an IRI.
	 *
	 * @param IRI|string $base (Absolute) Base IRI
	 * @param IRI|string $relative Relative IRI
	 * @return IRI|false
	 */
	public static function absolutize($base, $relative)
	{
		if (!($relative instanceof SimplePie_IRI))
		{
			$relative = new SimplePie_IRI($relative);
		}
		if (!$relative->is_valid())
		{
			return false;
		}
		elseif ($relative->scheme !== null)
		{
			return clone $relative;
		}
		else
		{
			if (!($base instanceof SimplePie_IRI))
			{
				$base = new SimplePie_IRI($base);
			}
			if ($base->scheme !== null && $base->is_valid())
			{
				if ($relative->get_iri() !== '')
				{
					if ($relative->iuserinfo !== null || $relative->ihost !== null || $relative->port !== null)
					{
						$target = clone $relative;
						$target->scheme = $base->scheme;
					}
					else
					{
						$target = new SimplePie_IRI;
						$target->scheme = $base->scheme;
						$target->iuserinfo = $base->iuserinfo;
						$target->ihost = $base->ihost;
						$target->port = $base->port;
						if ($relative->ipath !== '')
						{
							if ($relative->ipath[0] === '/')
							{
								$target->ipath = $relative->ipath;
							}
							elseif (($base->iuserinfo !== null || $base->ihost !== null || $base->port !== null) && $base->ipath === '')
							{
								$target->ipath = '/' . $relative->ipath;
							}
							elseif (($last_segment = strrpos($base->ipath, '/')) !== false)
							{
								$target->ipath = substr($base->ipath, 0, $last_segment + 1) . $relative->ipath;
							}
							else
							{
								$target->ipath = $relative->ipath;
							}
							$target->ipath = $target->remove_dot_segments($target->ipath);
							$target->iquery = $relative->iquery;
						}
						else
						{
							$target->ipath = $base->ipath;
							if ($relative->iquery !== null)
							{
								$target->iquery = $relative->iquery;
							}
							elseif ($base->iquery !== null)
							{
								$target->iquery = $base->iquery;
							}
						}
						$target->ifragment = $relative->ifragment;
					}
				}
				else
				{
					$target = clone $base;
					$target->ifragment = null;
				}
				$target->scheme_normalization();
				return $target;
			}
			else
			{
				return false;
			}
		}
	}

	/**
	 * Parse an IRI into scheme/authority/path/query/fragment segments
	 *
	 * @param string $iri
	 * @return array
	 */
	protected function parse_iri($iri)
	{
		$iri = trim($iri, "\x20\x09\x0A\x0C\x0D");
		if (preg_match('/^((?P<scheme>[^:\/?#]+):)?(\/\/(?P<authority>[^\/?#]*))?(?P<path>[^?#]*)(\?(?P<query>[^#]*))?(#(?P<fragment>.*))?$/', $iri, $match))
		{
			if ($match[1] === '')
			{
				$match['scheme'] = null;
			}
			if (!isset($match[3]) || $match[3] === '')
			{
				$match['authority'] = null;
			}
			if (!isset($match[5]))
			{
				$match['path'] = '';
			}
			if (!isset($match[6]) || $match[6] === '')
			{
				$match['query'] = null;
			}
			if (!isset($match[8]) || $match[8] === '')
			{
				$match['fragment'] = null;
			}
			return $match;
		}
		else
		{
			// This can occur when a paragraph is accidentally parsed as a URI
			return false;
		}
	}

	/**
	 * Remove dot segments from a path
	 *
	 * @param string $input
	 * @return string
	 */
	protected function remove_dot_segments($input)
	{
		$output = '';
		while (strpos($input, './') !== false || strpos($input, '/.') !== false || $input === '.' || $input === '..')
		{
			// A: If the input buffer begins with a prefix of "../" or "./", then remove that prefix from the input buffer; otherwise,
			if (strpos($input, '../') === 0)
			{
				$input = substr($input, 3);
			}
			elseif (strpos($input, './') === 0)
			{
				$input = substr($input, 2);
			}
			// B: if the input buffer begins with a prefix of "/./" or "/.", where "." is a complete path segment, then replace that prefix with "/" in the input buffer; otherwise,
			elseif (strpos($input, '/./') === 0)
			{
				$input = substr($input, 2);
			}
			elseif ($input === '/.')
			{
				$input = '/';
			}
			// C: if the input buffer begins with a prefix of "/../" or "/..", where ".." is a complete path segment, then replace that prefix with "/" in the input buffer and remove the last segment and its preceding "/" (if any) from the output buffer; otherwise,
			elseif (strpos($input, '/../') === 0)
			{
				$input = substr($input, 3);
				$output = substr_replace($output, '', strrpos($output, '/'));
			}
			elseif ($input === '/..')
			{
				$input = '/';
				$output = substr_replace($output, '', strrpos($output, '/'));
			}
			// D: if the input buffer consists only of "." or "..", then remove that from the input buffer; otherwise,
			elseif ($input === '.' || $input === '..')
			{
				$input = '';
			}
			// E: move the first path segment in the input buffer to the end of the output buffer, including the initial "/" character (if any) and any subsequent characters up to, but not including, the next "/" character or the end of the input buffer
			elseif (($pos = strpos($input, '/', 1)) !== false)
			{
				$output .= substr($input, 0, $pos);
				$input = substr_replace($input, '', 0, $pos);
			}
			else
			{
				$output .= $input;
				$input = '';
			}
		}
		return $output . $input;
	}

	/**
	 * Replace invalid character with percent encoding
	 *
	 * @param string $string Input string
	 * @param string $extra_chars Valid characters not in iunreserved or
	 *                            iprivate (this is ASCII-only)
	 * @param bool $iprivate Allow iprivate
	 * @return string
	 */
	protected function replace_invalid_with_pct_encoding($string, $extra_chars, $iprivate = false)
	{
		// Normalize as many pct-encoded sections as possible
		$string = preg_replace_callback('/(?:%[A-Fa-f0-9]{2})+/', array($this, 'remove_iunreserved_percent_encoded'), $string);

		// Replace invalid percent characters
		$string = preg_replace('/%(?![A-Fa-f0-9]{2})/', '%25', $string);

		// Add unreserved and % to $extra_chars (the latter is safe because all
		// pct-encoded sections are now valid).
		$extra_chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~%';

		// Now replace any bytes that aren't allowed with their pct-encoded versions
		$position = 0;
		$strlen = strlen($string);
		while (($position += strspn($string, $extra_chars, $position)) < $strlen)
		{
			$value = ord($string[$position]);

			// Start position
			$start = $position;

			// By default we are valid
			$valid = true;

			// No one byte sequences are valid due to the while.
			// Two byte sequence:
			if (($value & 0xE0) === 0xC0)
			{
				$character = ($value & 0x1F) << 6;
				$length = 2;
				$remaining = 1;
			}
			// Three byte sequence:
			elseif (($value & 0xF0) === 0xE0)
			{
				$character = ($value & 0x0F) << 12;
				$length = 3;
				$remaining = 2;
			}
			// Four byte sequence:
			elseif (($value & 0xF8) === 0xF0)
			{
				$character = ($value & 0x07) << 18;
				$length = 4;
				$remaining = 3;
			}
			// Invalid byte:
			else
			{
				$valid = false;
				$length = 1;
				$remaining = 0;
			}

			if ($remaining)
			{
				if ($position + $length <= $strlen)
				{
					for ($position++; $remaining; $position++)
					{
						$value = ord($string[$position]);

						// Check that the byte is valid, then add it to the character:
						if (($value & 0xC0) === 0x80)
						{
							$character |= ($value & 0x3F) << (--$remaining * 6);
						}
						// If it is invalid, count the sequence as invalid and reprocess the current byte:
						else
						{
							$valid = false;
							$position--;
							break;
						}
					}
				}
				else
				{
					$position = $strlen - 1;
					$valid = false;
				}
			}

			// Percent encode anything invalid or not in ucschar
			if (
				// Invalid sequences
				!$valid
				// Non-shortest form sequences are invalid
				|| $length > 1 && $character <= 0x7F
				|| $length > 2 && $character <= 0x7FF
				|| $length > 3 && $character <= 0xFFFF
				// Outside of range of ucschar codepoints
				// Noncharacters
				|| ($character & 0xFFFE) === 0xFFFE
				|| $character >= 0xFDD0 && $character <= 0xFDEF
				|| (
					// Everything else not in ucschar
					   $character > 0xD7FF && $character < 0xF900
					|| $character < 0xA0
					|| $character > 0xEFFFD
				)
				&& (
					// Everything not in iprivate, if it applies
					   !$iprivate
					|| $character < 0xE000
					|| $character > 0x10FFFD
				)
			)
			{
				// If we were a character, pretend we weren't, but rather an error.
				if ($valid)
					$position--;

				for ($j = $start; $j <= $position; $j++)
				{
					$string = substr_replace($string, sprintf('%%%02X', ord($string[$j])), $j, 1);
					$j += 2;
					$position += 2;
					$strlen += 2;
				}
			}
		}

		return $string;
	}

	/**
	 * Callback function for preg_replace_callback.
	 *
	 * Removes sequences of percent encoded bytes that represent UTF-8
	 * encoded characters in iunreserved
	 *
	 * @param array $match PCRE match
	 * @return string Replacement
	 */
	protected function remove_iunreserved_percent_encoded($match)
	{
		// As we just have valid percent encoded sequences we can just explode
		// and ignore the first member of the returned array (an empty string).
		$bytes = explode('%', $match[0]);

		// Initialize the new string (this is what will be returned) and that
		// there are no bytes remaining in the current sequence (unsurprising
		// at the first byte!).
		$string = '';
		$remaining = 0;

		// Loop over each and every byte, and set $value to its value
		for ($i = 1, $len = count($bytes); $i < $len; $i++)
		{
			$value = hexdec($bytes[$i]);

			// If we're the first byte of sequence:
			if (!$remaining)
			{
				// Start position
				$start = $i;

				// By default we are valid
				$valid = true;

				// One byte sequence:
				if ($value <= 0x7F)
				{
					$character = $value;
					$length = 1;
				}
				// Two byte sequence:
				elseif (($value & 0xE0) === 0xC0)
				{
					$character = ($value & 0x1F) << 6;
					$length = 2;
					$remaining = 1;
				}
				// Three byte sequence:
				elseif (($value & 0xF0) === 0xE0)
				{
					$character = ($value & 0x0F) << 12;
					$length = 3;
					$remaining = 2;
				}
				// Four byte sequence:
				elseif (($value & 0xF8) === 0xF0)
				{
					$character = ($value & 0x07) << 18;
					$length = 4;
					$remaining = 3;
				}
				// Invalid byte:
				else
				{
					$valid = false;
					$remaining = 0;
				}
			}
			// Continuation byte:
			else
			{
				// Check that the byte is valid, then add it to the character:
				if (($value & 0xC0) === 0x80)
				{
					$remaining--;
					$character |= ($value & 0x3F) << ($remaining * 6);
				}
				// If it is invalid, count the sequence as invalid and reprocess the current byte as the start of a sequence:
				else
				{
					$valid = false;
					$remaining = 0;
					$i--;
				}
			}

			// If we've reached the end of the current byte sequence, append it to Unicode::$data
			if (!$remaining)
			{
				// Percent encode anything invalid or not in iunreserved
				if (
					// Invalid sequences
					!$valid
					// Non-shortest form sequences are invalid
					|| $length > 1 && $character <= 0x7F
					|| $length > 2 && $character <= 0x7FF
					|| $length > 3 && $character <= 0xFFFF
					// Outside of range of iunreserved codepoints
					|| $character < 0x2D
					|| $character > 0xEFFFD
					// Noncharacters
					|| ($character & 0xFFFE) === 0xFFFE
					|| $character >= 0xFDD0 && $character <= 0xFDEF
					// Everything else not in iunreserved (this is all BMP)
					|| $character === 0x2F
					|| $character > 0x39 && $character < 0x41
					|| $character > 0x5A && $character < 0x61
					|| $character > 0x7A && $character < 0x7E
					|| $character > 0x7E && $character < 0xA0
					|| $character > 0xD7FF && $character < 0xF900
				)
				{
					for ($j = $start; $j <= $i; $j++)
					{
						$string .= '%' . strtoupper($bytes[$j]);
					}
				}
				else
				{
					for ($j = $start; $j <= $i; $j++)
					{
						$string .= chr(hexdec($bytes[$j]));
					}
				}
			}
		}

		// If we have any bytes left over they are invalid (i.e., we are
		// mid-way through a multi-byte sequence)
		if ($remaining)
		{
			for ($j = $start; $j < $len; $j++)
			{
				$string .= '%' . strtoupper($bytes[$j]);
			}
		}

		return $string;
	}

	protected function scheme_normalization()
	{
		if (isset($this->normalization[$this->scheme]['iuserinfo']) && $this->iuserinfo === $this->normalization[$this->scheme]['iuserinfo'])
		{
			$this->iuserinfo = null;
		}
		if (isset($this->normalization[$this->scheme]['ihost']) && $this->ihost === $this->normalization[$this->scheme]['ihost'])
		{
			$this->ihost = null;
		}
		if (isset($this->normalization[$this->scheme]['port']) && $this->port === $this->normalization[$this->scheme]['port'])
		{
			$this->port = null;
		}
		if (isset($this->normalization[$this->scheme]['ipath']) && $this->ipath === $this->normalization[$this->scheme]['ipath'])
		{
			$this->ipath = '';
		}
		if (isset($this->normalization[$this->scheme]['iquery']) && $this->iquery === $this->normalization[$this->scheme]['iquery'])
		{
			$this->iquery = null;
		}
		if (isset($this->normalization[$this->scheme]['ifragment']) && $this->ifragment === $this->normalization[$this->scheme]['ifragment'])
		{
			$this->ifragment = null;
		}
	}

	/**
	 * Check if the object represents a valid IRI. This needs to be done on each
	 * call as some things change depending on another part of the IRI.
	 *
	 * @return bool
	 */
	public function is_valid()
	{
		$isauthority = $this->iuserinfo !== null || $this->ihost !== null || $this->port !== null;
		if ($this->ipath !== '' &&
			(
				$isauthority && (
					$this->ipath[0] !== '/' ||
					substr($this->ipath, 0, 2) === '//'
				) ||
				(
					$this->scheme === null &&
					!$isauthority &&
					strpos($this->ipath, ':') !== false &&
					(strpos($this->ipath, '/') === false ? true : strpos($this->ipath, ':') < strpos($this->ipath, '/'))
				)
			)
		)
		{
			return false;
		}

		return true;
	}

	/**
	 * Set the entire IRI. Returns true on success, false on failure (if there
	 * are any invalid characters).
	 *
	 * @param string $iri
	 * @return bool
	 */
	public function set_iri($iri)
	{
		static $cache;
		if (!$cache)
		{
			$cache = array();
		}

		if ($iri === null)
		{
			return true;
		}
		elseif (isset($cache[$iri]))
		{
			list($this->scheme,
				 $this->iuserinfo,
				 $this->ihost,
				 $this->port,
				 $this->ipath,
				 $this->iquery,
				 $this->ifragment,
				 $return) = $cache[$iri];
			return $return;
		}
		else
		{
			$parsed = $this->parse_iri((string) $iri);
			if (!$parsed)
			{
				return false;
			}

			$return = $this->set_scheme($parsed['scheme'])
				&& $this->set_authority($parsed['authority'])
				&& $this->set_path($parsed['path'])
				&& $this->set_query($parsed['query'])
				&& $this->set_fragment($parsed['fragment']);

			$cache[$iri] = array($this->scheme,
								 $this->iuserinfo,
								 $this->ihost,
								 $this->port,
								 $this->ipath,
								 $this->iquery,
								 $this->ifragment,
								 $return);
			return $return;
		}
	}

	/**
	 * Set the scheme. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $scheme
	 * @return bool
	 */
	public function set_scheme($scheme)
	{
		if ($scheme === null)
		{
			$this->scheme = null;
		}
		elseif (!preg_match('/^[A-Za-z][0-9A-Za-z+\-.]*$/', $scheme))
		{
			$this->scheme = null;
			return false;
		}
		else
		{
			$this->scheme = strtolower($scheme);
		}
		return true;
	}

	/**
	 * Set the authority. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $authority
	 * @return bool
	 */
	public function set_authority($authority)
	{
		static $cache;
		if (!$cache)
			$cache = array();

		if ($authority === null)
		{
			$this->iuserinfo = null;
			$this->ihost = null;
			$this->port = null;
			return true;
		}
		elseif (isset($cache[$authority]))
		{
			list($this->iuserinfo,
				 $this->ihost,
				 $this->port,
				 $return) = $cache[$authority];

			return $return;
		}
		else
		{
			$remaining = $authority;
			if (($iuserinfo_end = strrpos($remaining, '@')) !== false)
			{
				$iuserinfo = substr($remaining, 0, $iuserinfo_end);
				$remaining = substr($remaining, $iuserinfo_end + 1);
			}
			else
			{
				$iuserinfo = null;
			}
			if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false)
			{
				if (($port = substr($remaining, $port_start + 1)) === false)
				{
					$port = null;
				}
				$remaining = substr($remaining, 0, $port_start);
			}
			else
			{
				$port = null;
			}

			$return = $this->set_userinfo($iuserinfo) &&
					  $this->set_host($remaining) &&
					  $this->set_port($port);

			$cache[$authority] = array($this->iuserinfo,
									   $this->ihost,
									   $this->port,
									   $return);

			return $return;
		}
	}

	/**
	 * Set the iuserinfo.
	 *
	 * @param string $iuserinfo
	 * @return bool
	 */
	public function set_userinfo($iuserinfo)
	{
		if ($iuserinfo === null)
		{
			$this->iuserinfo = null;
		}
		else
		{
			$this->iuserinfo = $this->replace_invalid_with_pct_encoding($iuserinfo, '!$&\'()*+,;=:');
			$this->scheme_normalization();
		}

		return true;
	}

	/**
	 * Set the ihost. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $ihost
	 * @return bool
	 */
	public function set_host($ihost)
	{
		if ($ihost === null)
		{
			$this->ihost = null;
			return true;
		}
		elseif (substr($ihost, 0, 1) === '[' && substr($ihost, -1) === ']')
		{
			if (SimplePie_Net_IPv6::check_ipv6(substr($ihost, 1, -1)))
			{
				$this->ihost = '[' . SimplePie_Net_IPv6::compress(substr($ihost, 1, -1)) . ']';
			}
			else
			{
				$this->ihost = null;
				return false;
			}
		}
		else
		{
			$ihost = $this->replace_invalid_with_pct_encoding($ihost, '!$&\'()*+,;=');

			// Lowercase, but ignore pct-encoded sections (as they should
			// remain uppercase). This must be done after the previous step
			// as that can add unescaped characters.
			$position = 0;
			$strlen = strlen($ihost);
			while (($position += strcspn($ihost, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ%', $position)) < $strlen)
			{
				if ($ihost[$position] === '%')
				{
					$position += 3;
				}
				else
				{
					$ihost[$position] = strtolower($ihost[$position]);
					$position++;
				}
			}

			$this->ihost = $ihost;
		}

		$this->scheme_normalization();

		return true;
	}

	/**
	 * Set the port. Returns true on success, false on failure (if there are
	 * any invalid characters).
	 *
	 * @param string $port
	 * @return bool
	 */
	public function set_port($port)
	{
		if ($port === null)
		{
			$this->port = null;
			return true;
		}
		elseif (strspn($port, '0123456789') === strlen($port))
		{
			$this->port = (int) $port;
			$this->scheme_normalization();
			return true;
		}
		else
		{
			$this->port = null;
			return false;
		}
	}

	/**
	 * Set the ipath.
	 *
	 * @param string $ipath
	 * @return bool
	 */
	public function set_path($ipath)
	{
		static $cache;
		if (!$cache)
		{
			$cache = array();
		}

		$ipath = (string) $ipath;

		if (isset($cache[$ipath]))
		{
			$this->ipath = $cache[$ipath][(int) ($this->scheme !== null)];
		}
		else
		{
			$valid = $this->replace_invalid_with_pct_encoding($ipath, '!$&\'()*+,;=@:/');
			$removed = $this->remove_dot_segments($valid);

			$cache[$ipath] = array($valid, $removed);
			$this->ipath =  ($this->scheme !== null) ? $removed : $valid;
		}

		$this->scheme_normalization();
		return true;
	}

	/**
	 * Set the iquery.
	 *
	 * @param string $iquery
	 * @return bool
	 */
	public function set_query($iquery)
	{
		if ($iquery === null)
		{
			$this->iquery = null;
		}
		else
		{
			$this->iquery = $this->replace_invalid_with_pct_encoding($iquery, '!$&\'()*+,;=:@/?', true);
			$this->scheme_normalization();
		}
		return true;
	}

	/**
	 * Set the ifragment.
	 *
	 * @param string $ifragment
	 * @return bool
	 */
	public function set_fragment($ifragment)
	{
		if ($ifragment === null)
		{
			$this->ifragment = null;
		}
		else
		{
			$this->ifragment = $this->replace_invalid_with_pct_encoding($ifragment, '!$&\'()*+,;=:@/?');
			$this->scheme_normalization();
		}
		return true;
	}

	/**
	 * Convert an IRI to a URI (or parts thereof)
	 *
	 * @return string
	 */
	public function to_uri($string)
	{
		static $non_ascii;
		if (!$non_ascii)
		{
			$non_ascii = implode('', range("\x80", "\xFF"));
		}

		$position = 0;
		$strlen = strlen($string);
		while (($position += strcspn($string, $non_ascii, $position)) < $strlen)
		{
			$string = substr_replace($string, sprintf('%%%02X', ord($string[$position])), $position, 1);
			$position += 3;
			$strlen += 2;
		}

		return $string;
	}

	/**
	 * Get the complete IRI
	 *
	 * @return string
	 */
	public function get_iri()
	{
		if (!$this->is_valid())
		{
			return false;
		}

		$iri = '';
		if ($this->scheme !== null)
		{
			$iri .= $this->scheme . ':';
		}
		if (($iauthority = $this->get_iauthority()) !== null)
		{
			$iri .= '//' . $iauthority;
		}
		if ($this->ipath !== '')
		{
			$iri .= $this->ipath;
		}
		elseif (!empty($this->normalization[$this->scheme]['ipath']) && $iauthority !== null && $iauthority !== '')
		{
			$iri .= $this->normalization[$this->scheme]['ipath'];
		}
		if ($this->iquery !== null)
		{
			$iri .= '?' . $this->iquery;
		}
		if ($this->ifragment !== null)
		{
			$iri .= '#' . $this->ifragment;
		}

		return $iri;
	}

	/**
	 * Get the complete URI
	 *
	 * @return string
	 */
	public function get_uri()
	{
		return $this->to_uri($this->get_iri());
	}

	/**
	 * Get the complete iauthority
	 *
	 * @return string
	 */
	protected function get_iauthority()
	{
		if ($this->iuserinfo !== null || $this->ihost !== null || $this->port !== null)
		{
			$iauthority = '';
			if ($this->iuserinfo !== null)
			{
				$iauthority .= $this->iuserinfo . '@';
			}
			if ($this->ihost !== null)
			{
				$iauthority .= $this->ihost;
			}
			if ($this->port !== null)
			{
				$iauthority .= ':' . $this->port;
			}
			return $iauthority;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the complete authority
	 *
	 * @return string
	 */
	protected function get_authority()
	{
		$iauthority = $this->get_iauthority();
		if (is_string($iauthority))
			return $this->to_uri($iauthority);
		else
			return $iauthority;
	}
}
vendor/simplepie/simplepie/library/SimplePie/Enclosure.php000064400000065537152177723700020055 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles everything related to enclosures (including Media RSS and iTunes RSS)
 *
 * Used by {@see SimplePie_Item::get_enclosure()} and {@see SimplePie_Item::get_enclosures()}
 *
 * This class can be overloaded with {@see SimplePie::set_enclosure_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Enclosure
{
	/**
	 * @var string
	 * @see get_bitrate()
	 */
	var $bitrate;

	/**
	 * @var array
	 * @see get_captions()
	 */
	var $captions;

	/**
	 * @var array
	 * @see get_categories()
	 */
	var $categories;

	/**
	 * @var int
	 * @see get_channels()
	 */
	var $channels;

	/**
	 * @var SimplePie_Copyright
	 * @see get_copyright()
	 */
	var $copyright;

	/**
	 * @var array
	 * @see get_credits()
	 */
	var $credits;

	/**
	 * @var string
	 * @see get_description()
	 */
	var $description;

	/**
	 * @var int
	 * @see get_duration()
	 */
	var $duration;

	/**
	 * @var string
	 * @see get_expression()
	 */
	var $expression;

	/**
	 * @var string
	 * @see get_framerate()
	 */
	var $framerate;

	/**
	 * @var string
	 * @see get_handler()
	 */
	var $handler;

	/**
	 * @var array
	 * @see get_hashes()
	 */
	var $hashes;

	/**
	 * @var string
	 * @see get_height()
	 */
	var $height;

	/**
	 * @deprecated
	 * @var null
	 */
	var $javascript;

	/**
	 * @var array
	 * @see get_keywords()
	 */
	var $keywords;

	/**
	 * @var string
	 * @see get_language()
	 */
	var $lang;

	/**
	 * @var string
	 * @see get_length()
	 */
	var $length;

	/**
	 * @var string
	 * @see get_link()
	 */
	var $link;

	/**
	 * @var string
	 * @see get_medium()
	 */
	var $medium;

	/**
	 * @var string
	 * @see get_player()
	 */
	var $player;

	/**
	 * @var array
	 * @see get_ratings()
	 */
	var $ratings;

	/**
	 * @var array
	 * @see get_restrictions()
	 */
	var $restrictions;

	/**
	 * @var string
	 * @see get_sampling_rate()
	 */
	var $samplingrate;

	/**
	 * @var array
	 * @see get_thumbnails()
	 */
	var $thumbnails;

	/**
	 * @var string
	 * @see get_title()
	 */
	var $title;

	/**
	 * @var string
	 * @see get_type()
	 */
	var $type;

	/**
	 * @var string
	 * @see get_width()
	 */
	var $width;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 *
	 * @uses idna_convert If available, this will convert an IDN
	 */
	public function __construct($link = null, $type = null, $length = null, $javascript = null, $bitrate = null, $captions = null, $categories = null, $channels = null, $copyright = null, $credits = null, $description = null, $duration = null, $expression = null, $framerate = null, $hashes = null, $height = null, $keywords = null, $lang = null, $medium = null, $player = null, $ratings = null, $restrictions = null, $samplingrate = null, $thumbnails = null, $title = null, $width = null)
	{
		$this->bitrate = $bitrate;
		$this->captions = $captions;
		$this->categories = $categories;
		$this->channels = $channels;
		$this->copyright = $copyright;
		$this->credits = $credits;
		$this->description = $description;
		$this->duration = $duration;
		$this->expression = $expression;
		$this->framerate = $framerate;
		$this->hashes = $hashes;
		$this->height = $height;
		$this->keywords = $keywords;
		$this->lang = $lang;
		$this->length = $length;
		$this->link = $link;
		$this->medium = $medium;
		$this->player = $player;
		$this->ratings = $ratings;
		$this->restrictions = $restrictions;
		$this->samplingrate = $samplingrate;
		$this->thumbnails = $thumbnails;
		$this->title = $title;
		$this->type = $type;
		$this->width = $width;

		if (class_exists('idna_convert'))
		{
			$idn = new idna_convert();
			$parsed = SimplePie_Misc::parse_url($link);
			$this->link = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
		}
		$this->handler = $this->get_handler(); // Needs to load last
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the bitrate
	 *
	 * @return string|null
	 */
	public function get_bitrate()
	{
		if ($this->bitrate !== null)
		{
			return $this->bitrate;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single caption
	 *
	 * @param int $key
	 * @return SimplePie_Caption|null
	 */
	public function get_caption($key = 0)
	{
		$captions = $this->get_captions();
		if (isset($captions[$key]))
		{
			return $captions[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all captions
	 *
	 * @return array|null Array of {@see SimplePie_Caption} objects
	 */
	public function get_captions()
	{
		if ($this->captions !== null)
		{
			return $this->captions;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single category
	 *
	 * @param int $key
	 * @return SimplePie_Category|null
	 */
	public function get_category($key = 0)
	{
		$categories = $this->get_categories();
		if (isset($categories[$key]))
		{
			return $categories[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all categories
	 *
	 * @return array|null Array of {@see SimplePie_Category} objects
	 */
	public function get_categories()
	{
		if ($this->categories !== null)
		{
			return $this->categories;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the number of audio channels
	 *
	 * @return int|null
	 */
	public function get_channels()
	{
		if ($this->channels !== null)
		{
			return $this->channels;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the copyright information
	 *
	 * @return SimplePie_Copyright|null
	 */
	public function get_copyright()
	{
		if ($this->copyright !== null)
		{
			return $this->copyright;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single credit
	 *
	 * @param int $key
	 * @return SimplePie_Credit|null
	 */
	public function get_credit($key = 0)
	{
		$credits = $this->get_credits();
		if (isset($credits[$key]))
		{
			return $credits[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all credits
	 *
	 * @return array|null Array of {@see SimplePie_Credit} objects
	 */
	public function get_credits()
	{
		if ($this->credits !== null)
		{
			return $this->credits;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the description of the enclosure
	 *
	 * @return string|null
	 */
	public function get_description()
	{
		if ($this->description !== null)
		{
			return $this->description;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the duration of the enclosure
	 *
	 * @param string $convert Convert seconds into hh:mm:ss
	 * @return string|int|null 'hh:mm:ss' string if `$convert` was specified, otherwise integer (or null if none found)
	 */
	public function get_duration($convert = false)
	{
		if ($this->duration !== null)
		{
			if ($convert)
			{
				$time = SimplePie_Misc::time_hms($this->duration);
				return $time;
			}
			else
			{
				return $this->duration;
			}
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the expression
	 *
	 * @return string Probably one of 'sample', 'full', 'nonstop', 'clip'. Defaults to 'full'
	 */
	public function get_expression()
	{
		if ($this->expression !== null)
		{
			return $this->expression;
		}
		else
		{
			return 'full';
		}
	}

	/**
	 * Get the file extension
	 *
	 * @return string|null
	 */
	public function get_extension()
	{
		if ($this->link !== null)
		{
			$url = SimplePie_Misc::parse_url($this->link);
			if ($url['path'] !== '')
			{
				return pathinfo($url['path'], PATHINFO_EXTENSION);
			}
		}
		return null;
	}

	/**
	 * Get the framerate (in frames-per-second)
	 *
	 * @return string|null
	 */
	public function get_framerate()
	{
		if ($this->framerate !== null)
		{
			return $this->framerate;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the preferred handler
	 *
	 * @return string|null One of 'flash', 'fmedia', 'quicktime', 'wmedia', 'mp3'
	 */
	public function get_handler()
	{
		return $this->get_real_type(true);
	}

	/**
	 * Get a single hash
	 *
	 * @link http://www.rssboard.org/media-rss#media-hash
	 * @param int $key
	 * @return string|null Hash as per `media:hash`, prefixed with "$algo:"
	 */
	public function get_hash($key = 0)
	{
		$hashes = $this->get_hashes();
		if (isset($hashes[$key]))
		{
			return $hashes[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all credits
	 *
	 * @return array|null Array of strings, see {@see get_hash()}
	 */
	public function get_hashes()
	{
		if ($this->hashes !== null)
		{
			return $this->hashes;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the height
	 *
	 * @return string|null
	 */
	public function get_height()
	{
		if ($this->height !== null)
		{
			return $this->height;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the language
	 *
	 * @link http://tools.ietf.org/html/rfc3066
	 * @return string|null Language code as per RFC 3066
	 */
	public function get_language()
	{
		if ($this->lang !== null)
		{
			return $this->lang;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single keyword
	 *
	 * @param int $key
	 * @return string|null
	 */
	public function get_keyword($key = 0)
	{
		$keywords = $this->get_keywords();
		if (isset($keywords[$key]))
		{
			return $keywords[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all keywords
	 *
	 * @return array|null Array of strings
	 */
	public function get_keywords()
	{
		if ($this->keywords !== null)
		{
			return $this->keywords;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get length
	 *
	 * @return float Length in bytes
	 */
	public function get_length()
	{
		if ($this->length !== null)
		{
			return $this->length;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the URL
	 *
	 * @return string|null
	 */
	public function get_link()
	{
		if ($this->link !== null)
		{
			return urldecode($this->link);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the medium
	 *
	 * @link http://www.rssboard.org/media-rss#media-content
	 * @return string|null Should be one of 'image', 'audio', 'video', 'document', 'executable'
	 */
	public function get_medium()
	{
		if ($this->medium !== null)
		{
			return $this->medium;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the player URL
	 *
	 * Typically the same as {@see get_permalink()}
	 * @return string|null Player URL
	 */
	public function get_player()
	{
		if ($this->player !== null)
		{
			return $this->player;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single rating
	 *
	 * @param int $key
	 * @return SimplePie_Rating|null
	 */
	public function get_rating($key = 0)
	{
		$ratings = $this->get_ratings();
		if (isset($ratings[$key]))
		{
			return $ratings[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all ratings
	 *
	 * @return array|null Array of {@see SimplePie_Rating} objects
	 */
	public function get_ratings()
	{
		if ($this->ratings !== null)
		{
			return $this->ratings;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single restriction
	 *
	 * @param int $key
	 * @return SimplePie_Restriction|null
	 */
	public function get_restriction($key = 0)
	{
		$restrictions = $this->get_restrictions();
		if (isset($restrictions[$key]))
		{
			return $restrictions[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all restrictions
	 *
	 * @return array|null Array of {@see SimplePie_Restriction} objects
	 */
	public function get_restrictions()
	{
		if ($this->restrictions !== null)
		{
			return $this->restrictions;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the sampling rate (in kHz)
	 *
	 * @return string|null
	 */
	public function get_sampling_rate()
	{
		if ($this->samplingrate !== null)
		{
			return $this->samplingrate;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the file size (in MiB)
	 *
	 * @return float|null File size in mebibytes (1048 bytes)
	 */
	public function get_size()
	{
		$length = $this->get_length();
		if ($length !== null)
		{
			return round($length/1048576, 2);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single thumbnail
	 *
	 * @param int $key
	 * @return string|null Thumbnail URL
	 */
	public function get_thumbnail($key = 0)
	{
		$thumbnails = $this->get_thumbnails();
		if (isset($thumbnails[$key]))
		{
			return $thumbnails[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all thumbnails
	 *
	 * @return array|null Array of thumbnail URLs
	 */
	public function get_thumbnails()
	{
		if ($this->thumbnails !== null)
		{
			return $this->thumbnails;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the title
	 *
	 * @return string|null
	 */
	public function get_title()
	{
		if ($this->title !== null)
		{
			return $this->title;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get mimetype of the enclosure
	 *
	 * @see get_real_type()
	 * @return string|null MIME type
	 */
	public function get_type()
	{
		if ($this->type !== null)
		{
			return $this->type;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the width
	 *
	 * @return string|null
	 */
	public function get_width()
	{
		if ($this->width !== null)
		{
			return $this->width;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Embed the enclosure using `<embed>`
	 *
	 * @deprecated Use the second parameter to {@see embed} instead
	 *
	 * @param array|string $options See first paramter to {@see embed}
	 * @return string HTML string to output
	 */
	public function native_embed($options='')
	{
		return $this->embed($options, true);
	}

	/**
	 * Embed the enclosure using Javascript
	 *
	 * `$options` is an array or comma-separated key:value string, with the
	 * following properties:
	 *
	 * - `alt` (string): Alternate content for when an end-user does not have
	 *    the appropriate handler installed or when a file type is
	 *    unsupported. Can be any text or HTML. Defaults to blank.
	 * - `altclass` (string): If a file type is unsupported, the end-user will
	 *    see the alt text (above) linked directly to the content. That link
	 *    will have this value as its class name. Defaults to blank.
	 * - `audio` (string): This is an image that should be used as a
	 *    placeholder for audio files before they're loaded (QuickTime-only).
	 *    Can be any relative or absolute URL. Defaults to blank.
	 * - `bgcolor` (string): The background color for the media, if not
	 *    already transparent. Defaults to `#ffffff`.
	 * - `height` (integer): The height of the embedded media. Accepts any
	 *    numeric pixel value (such as `360`) or `auto`. Defaults to `auto`,
	 *    and it is recommended that you use this default.
	 * - `loop` (boolean): Do you want the media to loop when its done?
	 *    Defaults to `false`.
	 * - `mediaplayer` (string): The location of the included
	 *    `mediaplayer.swf` file. This allows for the playback of Flash Video
	 *    (`.flv`) files, and is the default handler for non-Odeo MP3's.
	 *    Defaults to blank.
	 * - `video` (string): This is an image that should be used as a
	 *    placeholder for video files before they're loaded (QuickTime-only).
	 *    Can be any relative or absolute URL. Defaults to blank.
	 * - `width` (integer): The width of the embedded media. Accepts any
	 *    numeric pixel value (such as `480`) or `auto`. Defaults to `auto`,
	 *    and it is recommended that you use this default.
	 * - `widescreen` (boolean): Is the enclosure widescreen or standard?
	 *    This applies only to video enclosures, and will automatically resize
	 *    the content appropriately.  Defaults to `false`, implying 4:3 mode.
	 *
	 * Note: Non-widescreen (4:3) mode with `width` and `height` set to `auto`
	 * will default to 480x360 video resolution.  Widescreen (16:9) mode with
	 * `width` and `height` set to `auto` will default to 480x270 video resolution.
	 *
	 * @todo If the dimensions for media:content are defined, use them when width/height are set to 'auto'.
	 * @param array|string $options Comma-separated key:value list, or array
	 * @param bool $native Use `<embed>`
	 * @return string HTML string to output
	 */
	public function embed($options = '', $native = false)
	{
		// Set up defaults
		$audio = '';
		$video = '';
		$alt = '';
		$altclass = '';
		$loop = 'false';
		$width = 'auto';
		$height = 'auto';
		$bgcolor = '#ffffff';
		$mediaplayer = '';
		$widescreen = false;
		$handler = $this->get_handler();
		$type = $this->get_real_type();

		// Process options and reassign values as necessary
		if (is_array($options))
		{
			extract($options);
		}
		else
		{
			$options = explode(',', $options);
			foreach($options as $option)
			{
				$opt = explode(':', $option, 2);
				if (isset($opt[0], $opt[1]))
				{
					$opt[0] = trim($opt[0]);
					$opt[1] = trim($opt[1]);
					switch ($opt[0])
					{
						case 'audio':
							$audio = $opt[1];
							break;

						case 'video':
							$video = $opt[1];
							break;

						case 'alt':
							$alt = $opt[1];
							break;

						case 'altclass':
							$altclass = $opt[1];
							break;

						case 'loop':
							$loop = $opt[1];
							break;

						case 'width':
							$width = $opt[1];
							break;

						case 'height':
							$height = $opt[1];
							break;

						case 'bgcolor':
							$bgcolor = $opt[1];
							break;

						case 'mediaplayer':
							$mediaplayer = $opt[1];
							break;

						case 'widescreen':
							$widescreen = $opt[1];
							break;
					}
				}
			}
		}

		$mime = explode('/', $type, 2);
		$mime = $mime[0];

		// Process values for 'auto'
		if ($width === 'auto')
		{
			if ($mime === 'video')
			{
				if ($height === 'auto')
				{
					$width = 480;
				}
				elseif ($widescreen)
				{
					$width = round((intval($height)/9)*16);
				}
				else
				{
					$width = round((intval($height)/3)*4);
				}
			}
			else
			{
				$width = '100%';
			}
		}

		if ($height === 'auto')
		{
			if ($mime === 'audio')
			{
				$height = 0;
			}
			elseif ($mime === 'video')
			{
				if ($width === 'auto')
				{
					if ($widescreen)
					{
						$height = 270;
					}
					else
					{
						$height = 360;
					}
				}
				elseif ($widescreen)
				{
					$height = round((intval($width)/16)*9);
				}
				else
				{
					$height = round((intval($width)/4)*3);
				}
			}
			else
			{
				$height = 376;
			}
		}
		elseif ($mime === 'audio')
		{
			$height = 0;
		}

		// Set proper placeholder value
		if ($mime === 'audio')
		{
			$placeholder = $audio;
		}
		elseif ($mime === 'video')
		{
			$placeholder = $video;
		}

		$embed = '';

		// Flash
		if ($handler === 'flash')
		{
			if ($native)
			{
				$embed .= "<embed src=\"" . $this->get_link() . "\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"$type\" quality=\"high\" width=\"$width\" height=\"$height\" bgcolor=\"$bgcolor\" loop=\"$loop\"></embed>";
			}
			else
			{
				$embed .= "<script type='text/javascript'>embed_flash('$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$loop', '$type');</script>";
			}
		}

		// Flash Media Player file types.
		// Preferred handler for MP3 file types.
		elseif ($handler === 'fmedia' || ($handler === 'mp3' && $mediaplayer !== ''))
		{
			$height += 20;
			if ($native)
			{
				$embed .= "<embed src=\"$mediaplayer\" pluginspage=\"http://adobe.com/go/getflashplayer\" type=\"application/x-shockwave-flash\" quality=\"high\" width=\"$width\" height=\"$height\" wmode=\"transparent\" flashvars=\"file=" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "&autostart=false&repeat=$loop&showdigits=true&showfsbutton=false\"></embed>";
			}
			else
			{
				$embed .= "<script type='text/javascript'>embed_flv('$width', '$height', '" . rawurlencode($this->get_link().'?file_extension=.'.$this->get_extension()) . "', '$placeholder', '$loop', '$mediaplayer');</script>";
			}
		}

		// QuickTime 7 file types.  Need to test with QuickTime 6.
		// Only handle MP3's if the Flash Media Player is not present.
		elseif ($handler === 'quicktime' || ($handler === 'mp3' && $mediaplayer === ''))
		{
			$height += 16;
			if ($native)
			{
				if ($placeholder !== '')
				{
					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" href=\"" . $this->get_link() . "\" src=\"$placeholder\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"false\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
				}
				else
				{
					$embed .= "<embed type=\"$type\" style=\"cursor:hand; cursor:pointer;\" src=\"" . $this->get_link() . "\" width=\"$width\" height=\"$height\" autoplay=\"false\" target=\"myself\" controller=\"true\" loop=\"$loop\" scale=\"aspect\" bgcolor=\"$bgcolor\" pluginspage=\"http://apple.com/quicktime/download/\"></embed>";
				}
			}
			else
			{
				$embed .= "<script type='text/javascript'>embed_quicktime('$type', '$bgcolor', '$width', '$height', '" . $this->get_link() . "', '$placeholder', '$loop');</script>";
			}
		}

		// Windows Media
		elseif ($handler === 'wmedia')
		{
			$height += 45;
			if ($native)
			{
				$embed .= "<embed type=\"application/x-mplayer2\" src=\"" . $this->get_link() . "\" autosize=\"1\" width=\"$width\" height=\"$height\" showcontrols=\"1\" showstatusbar=\"0\" showdisplay=\"0\" autostart=\"0\"></embed>";
			}
			else
			{
				$embed .= "<script type='text/javascript'>embed_wmedia('$width', '$height', '" . $this->get_link() . "');</script>";
			}
		}

		// Everything else
		else $embed .= '<a href="' . $this->get_link() . '" class="' . $altclass . '">' . $alt . '</a>';

		return $embed;
	}

	/**
	 * Get the real media type
	 *
	 * Often, feeds lie to us, necessitating a bit of deeper inspection. This
	 * converts types to their canonical representations based on the file
	 * extension
	 *
	 * @see get_type()
	 * @param bool $find_handler Internal use only, use {@see get_handler()} instead
	 * @return string MIME type
	 */
	public function get_real_type($find_handler = false)
	{
		// Mime-types by handler.
		$types_flash = array('application/x-shockwave-flash', 'application/futuresplash'); // Flash
		$types_fmedia = array('video/flv', 'video/x-flv','flv-application/octet-stream'); // Flash Media Player
		$types_quicktime = array('audio/3gpp', 'audio/3gpp2', 'audio/aac', 'audio/x-aac', 'audio/aiff', 'audio/x-aiff', 'audio/mid', 'audio/midi', 'audio/x-midi', 'audio/mp4', 'audio/m4a', 'audio/x-m4a', 'audio/wav', 'audio/x-wav', 'video/3gpp', 'video/3gpp2', 'video/m4v', 'video/x-m4v', 'video/mp4', 'video/mpeg', 'video/x-mpeg', 'video/quicktime', 'video/sd-video'); // QuickTime
		$types_wmedia = array('application/asx', 'application/x-mplayer2', 'audio/x-ms-wma', 'audio/x-ms-wax', 'video/x-ms-asf-plugin', 'video/x-ms-asf', 'video/x-ms-wm', 'video/x-ms-wmv', 'video/x-ms-wvx'); // Windows Media
		$types_mp3 = array('audio/mp3', 'audio/x-mp3', 'audio/mpeg', 'audio/x-mpeg'); // MP3

		if ($this->get_type() !== null)
		{
			$type = strtolower($this->type);
		}
		else
		{
			$type = null;
		}

		// If we encounter an unsupported mime-type, check the file extension and guess intelligently.
		if (!in_array($type, array_merge($types_flash, $types_fmedia, $types_quicktime, $types_wmedia, $types_mp3)))
		{
			switch (strtolower($this->get_extension()))
			{
				// Audio mime-types
				case 'aac':
				case 'adts':
					$type = 'audio/acc';
					break;

				case 'aif':
				case 'aifc':
				case 'aiff':
				case 'cdda':
					$type = 'audio/aiff';
					break;

				case 'bwf':
					$type = 'audio/wav';
					break;

				case 'kar':
				case 'mid':
				case 'midi':
				case 'smf':
					$type = 'audio/midi';
					break;

				case 'm4a':
					$type = 'audio/x-m4a';
					break;

				case 'mp3':
				case 'swa':
					$type = 'audio/mp3';
					break;

				case 'wav':
					$type = 'audio/wav';
					break;

				case 'wax':
					$type = 'audio/x-ms-wax';
					break;

				case 'wma':
					$type = 'audio/x-ms-wma';
					break;

				// Video mime-types
				case '3gp':
				case '3gpp':
					$type = 'video/3gpp';
					break;

				case '3g2':
				case '3gp2':
					$type = 'video/3gpp2';
					break;

				case 'asf':
					$type = 'video/x-ms-asf';
					break;

				case 'flv':
					$type = 'video/x-flv';
					break;

				case 'm1a':
				case 'm1s':
				case 'm1v':
				case 'm15':
				case 'm75':
				case 'mp2':
				case 'mpa':
				case 'mpeg':
				case 'mpg':
				case 'mpm':
				case 'mpv':
					$type = 'video/mpeg';
					break;

				case 'm4v':
					$type = 'video/x-m4v';
					break;

				case 'mov':
				case 'qt':
					$type = 'video/quicktime';
					break;

				case 'mp4':
				case 'mpg4':
					$type = 'video/mp4';
					break;

				case 'sdv':
					$type = 'video/sd-video';
					break;

				case 'wm':
					$type = 'video/x-ms-wm';
					break;

				case 'wmv':
					$type = 'video/x-ms-wmv';
					break;

				case 'wvx':
					$type = 'video/x-ms-wvx';
					break;

				// Flash mime-types
				case 'spl':
					$type = 'application/futuresplash';
					break;

				case 'swf':
					$type = 'application/x-shockwave-flash';
					break;
			}
		}

		if ($find_handler)
		{
			if (in_array($type, $types_flash))
			{
				return 'flash';
			}
			elseif (in_array($type, $types_fmedia))
			{
				return 'fmedia';
			}
			elseif (in_array($type, $types_quicktime))
			{
				return 'quicktime';
			}
			elseif (in_array($type, $types_wmedia))
			{
				return 'wmedia';
			}
			elseif (in_array($type, $types_mp3))
			{
				return 'mp3';
			}
			else
			{
				return null;
			}
		}
		else
		{
			return $type;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Category.php000064400000007173152177723700017663 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Manages all category-related data
 *
 * Used by {@see SimplePie_Item::get_category()} and {@see SimplePie_Item::get_categories()}
 *
 * This class can be overloaded with {@see SimplePie::set_category_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Category
{
	/**
	 * Category identifier
	 *
	 * @var string
	 * @see get_term
	 */
	var $term;

	/**
	 * Categorization scheme identifier
	 *
	 * @var string
	 * @see get_scheme()
	 */
	var $scheme;

	/**
	 * Human readable label
	 *
	 * @var string
	 * @see get_label()
	 */
	var $label;

	/**
	 * Constructor, used to input the data
	 *
	 * @param string $term
	 * @param string $scheme
	 * @param string $label
	 */
	public function __construct($term = null, $scheme = null, $label = null)
	{
		$this->term = $term;
		$this->scheme = $scheme;
		$this->label = $label;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the category identifier
	 *
	 * @return string|null
	 */
	public function get_term()
	{
		if ($this->term !== null)
		{
			return $this->term;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the categorization scheme identifier
	 *
	 * @return string|null
	 */
	public function get_scheme()
	{
		if ($this->scheme !== null)
		{
			return $this->scheme;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the human readable label
	 *
	 * @return string|null
	 */
	public function get_label()
	{
		if ($this->label !== null)
		{
			return $this->label;
		}
		else
		{
			return $this->get_term();
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Sanitize.php000064400000036213152177723700017671 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Used for data cleanup and post-processing
 *
 *
 * This class can be overloaded with {@see SimplePie::set_sanitize_class()}
 *
 * @package SimplePie
 * @todo Move to using an actual HTML parser (this will allow tags to be properly stripped, and to switch between HTML and XHTML), this will also make it easier to shorten a string while preserving HTML tags
 */
class SimplePie_Sanitize
{
	// Private vars
	var $base;

	// Options
	var $remove_div = true;
	var $image_handler = '';
	var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');
	var $encode_instead_of_strip = false;
	var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
	var $strip_comments = false;
	var $output_encoding = 'UTF-8';
	var $enable_cache = true;
	var $cache_location = './cache';
	var $cache_name_function = 'md5';
	var $timeout = 10;
	var $useragent = '';
	var $force_fsockopen = false;
	var $replace_url_attributes = null;

	public function __construct()
	{
		// Set defaults
		$this->set_url_replacements(null);
	}

	public function remove_div($enable = true)
	{
		$this->remove_div = (bool) $enable;
	}

	public function set_image_handler($page = false)
	{
		if ($page)
		{
			$this->image_handler = (string) $page;
		}
		else
		{
			$this->image_handler = false;
		}
	}

	public function set_registry(SimplePie_Registry $registry)
	{
		$this->registry = $registry;
	}

	public function pass_cache_data($enable_cache = true, $cache_location = './cache', $cache_name_function = 'md5', $cache_class = 'SimplePie_Cache')
	{
		if (isset($enable_cache))
		{
			$this->enable_cache = (bool) $enable_cache;
		}

		if ($cache_location)
		{
			$this->cache_location = (string) $cache_location;
		}

		if ($cache_name_function)
		{
			$this->cache_name_function = (string) $cache_name_function;
		}
	}

	public function pass_file_data($file_class = 'SimplePie_File', $timeout = 10, $useragent = '', $force_fsockopen = false)
	{
		if ($timeout)
		{
			$this->timeout = (string) $timeout;
		}

		if ($useragent)
		{
			$this->useragent = (string) $useragent;
		}

		if ($force_fsockopen)
		{
			$this->force_fsockopen = (string) $force_fsockopen;
		}
	}

	public function strip_htmltags($tags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'))
	{
		if ($tags)
		{
			if (is_array($tags))
			{
				$this->strip_htmltags = $tags;
			}
			else
			{
				$this->strip_htmltags = explode(',', $tags);
			}
		}
		else
		{
			$this->strip_htmltags = false;
		}
	}

	public function encode_instead_of_strip($encode = false)
	{
		$this->encode_instead_of_strip = (bool) $encode;
	}

	public function strip_attributes($attribs = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'))
	{
		if ($attribs)
		{
			if (is_array($attribs))
			{
				$this->strip_attributes = $attribs;
			}
			else
			{
				$this->strip_attributes = explode(',', $attribs);
			}
		}
		else
		{
			$this->strip_attributes = false;
		}
	}

	public function strip_comments($strip = false)
	{
		$this->strip_comments = (bool) $strip;
	}

	public function set_output_encoding($encoding = 'UTF-8')
	{
		$this->output_encoding = (string) $encoding;
	}

	/**
	 * Set element/attribute key/value pairs of HTML attributes
	 * containing URLs that need to be resolved relative to the feed
	 *
	 * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite,
	 * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite,
	 * |q|@cite
	 *
	 * @since 1.0
	 * @param array|null $element_attribute Element/attribute key/value pairs, null for default
	 */
	public function set_url_replacements($element_attribute = null)
	{
		if ($element_attribute === null)
		{
			$element_attribute = array(
				'a' => 'href',
				'area' => 'href',
				'blockquote' => 'cite',
				'del' => 'cite',
				'form' => 'action',
				'img' => array(
					'longdesc',
					'src'
				),
				'input' => 'src',
				'ins' => 'cite',
				'q' => 'cite'
			);
		}
		$this->replace_url_attributes = (array) $element_attribute;
	}

	public function sanitize($data, $type, $base = '')
	{
		$data = trim($data);
		if ($data !== '' || $type & SIMPLEPIE_CONSTRUCT_IRI)
		{
			if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML)
			{
				if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data))
				{
					$type |= SIMPLEPIE_CONSTRUCT_HTML;
				}
				else
				{
					$type |= SIMPLEPIE_CONSTRUCT_TEXT;
				}
			}

			if ($type & SIMPLEPIE_CONSTRUCT_BASE64)
			{
				$data = base64_decode($data);
			}

			if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML))
			{

				$document = new DOMDocument();
				$document->encoding = 'UTF-8';
				$data = $this->preprocess($data, $type);

				set_error_handler(array('SimplePie_Misc', 'silence_errors'));
				$document->loadHTML($data);
				restore_error_handler();

				// Strip comments
				if ($this->strip_comments)
				{
					$xpath = new DOMXPath($document);
					$comments = $xpath->query('//comment()');

					foreach ($comments as $comment)
					{
						$comment->parentNode->removeChild($comment);
					}
				}

				// Strip out HTML tags and attributes that might cause various security problems.
				// Based on recommendations by Mark Pilgrim at:
				// http://diveintomark.org/archives/2003/06/12/how_to_consume_rss_safely
				if ($this->strip_htmltags)
				{
					foreach ($this->strip_htmltags as $tag)
					{
						$this->strip_tag($tag, $document, $type);
					}
				}

				if ($this->strip_attributes)
				{
					foreach ($this->strip_attributes as $attrib)
					{
						$this->strip_attr($attrib, $document);
					}
				}

				// Replace relative URLs
				$this->base = $base;
				foreach ($this->replace_url_attributes as $element => $attributes)
				{
					$this->replace_urls($document, $element, $attributes);
				}

				// If image handling (caching, etc.) is enabled, cache and rewrite all the image tags.
				if (isset($this->image_handler) && ((string) $this->image_handler) !== '' && $this->enable_cache)
				{
					$images = $document->getElementsByTagName('img');
					foreach ($images as $img)
					{
						if ($img->hasAttribute('src'))
						{
							$image_url = call_user_func($this->cache_name_function, $img->getAttribute('src'));
							$cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, $image_url, 'spi'));

							if ($cache->load())
							{
								$img->setAttribute('src', $this->image_handler . $image_url);
							}
							else
							{
								$file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen));
								$headers = $file->headers;

								if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
								{
									if ($cache->save(array('headers' => $file->headers, 'body' => $file->body)))
									{
										$img->setAttribute('src', $this->image_handler . $image_url);
									}
									else
									{
										trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
									}
								}
							}
						}
					}
				}

				// Remove the DOCTYPE
				// Seems to cause segfaulting if we don't do this
				if ($document->firstChild instanceof DOMDocumentType)
				{
					$document->removeChild($document->firstChild);
				}

				// Move everything from the body to the root
				$real_body = $document->getElementsByTagName('body')->item(0)->childNodes->item(0);
				$document->replaceChild($real_body, $document->firstChild);

				// Finally, convert to a HTML string
				$data = trim($document->saveHTML());

				if ($this->remove_div)
				{
					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '', $data);
					$data = preg_replace('/<\/div>$/', '', $data);
				}
				else
				{
					$data = preg_replace('/^<div' . SIMPLEPIE_PCRE_XML_ATTRIBUTE . '>/', '<div>', $data);
				}
			}

			if ($type & SIMPLEPIE_CONSTRUCT_IRI)
			{
				$absolute = $this->registry->call('Misc', 'absolutize_url', array($data, $base));
				if ($absolute !== false)
				{
					$data = $absolute;
				}
			}

			if ($type & (SIMPLEPIE_CONSTRUCT_TEXT | SIMPLEPIE_CONSTRUCT_IRI))
			{
				$data = htmlspecialchars($data, ENT_COMPAT, 'UTF-8');
			}

			if ($this->output_encoding !== 'UTF-8')
			{
				$data = $this->registry->call('Misc', 'change_encoding', array($data, 'UTF-8', $this->output_encoding));
			}
		}
		return $data;
	}

	protected function preprocess($html, $type)
	{
		$ret = '';
		if ($type & ~SIMPLEPIE_CONSTRUCT_XHTML)
		{
			// Atom XHTML constructs are wrapped with a div by default
			// Note: No protection if $html contains a stray </div>!
			$html = '<div>' . $html . '</div>';
			$ret .= '<!DOCTYPE html>';
			$content_type = 'text/html';
		}
		else
		{
			$ret .= '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
			$content_type = 'application/xhtml+xml';
		}

		$ret .= '<html><head>';
		$ret .= '<meta http-equiv="Content-Type" content="' . $content_type . '; charset=utf-8" />';
		$ret .= '</head><body>' . $html . '</body></html>';
		return $ret;
	}

	public function replace_urls($document, $tag, $attributes)
	{
		if (!is_array($attributes))
		{
			$attributes = array($attributes);
		}

		if (!is_array($this->strip_htmltags) || !in_array($tag, $this->strip_htmltags))
		{
			$elements = $document->getElementsByTagName($tag);
			foreach ($elements as $element)
			{
				foreach ($attributes as $attribute)
				{
					if ($element->hasAttribute($attribute))
					{
						$value = $this->registry->call('Misc', 'absolutize_url', array($element->getAttribute($attribute), $this->base));
						if ($value !== false)
						{
							$element->setAttribute($attribute, $value);
						}
					}
				}
			}
		}
	}

	public function do_strip_htmltags($match)
	{
		if ($this->encode_instead_of_strip)
		{
			if (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
			{
				$match[1] = htmlspecialchars($match[1], ENT_COMPAT, 'UTF-8');
				$match[2] = htmlspecialchars($match[2], ENT_COMPAT, 'UTF-8');
				return "&lt;$match[1]$match[2]&gt;$match[3]&lt;/$match[1]&gt;";
			}
			else
			{
				return htmlspecialchars($match[0], ENT_COMPAT, 'UTF-8');
			}
		}
		elseif (isset($match[4]) && !in_array(strtolower($match[1]), array('script', 'style')))
		{
			return $match[4];
		}
		else
		{
			return '';
		}
	}

	protected function strip_tag($tag, $document, $type)
	{
		$xpath = new DOMXPath($document);
		$elements = $xpath->query('body//' . $tag);
		if ($this->encode_instead_of_strip)
		{
			foreach ($elements as $element)
			{
				$fragment = $document->createDocumentFragment();

				// For elements which aren't script or style, include the tag itself
				if (!in_array($tag, array('script', 'style')))
				{
					$text = '<' . $tag;
					if ($element->hasAttributes())
					{
						$attrs = array();
						foreach ($element->attributes as $name => $attr)
						{
							$value = $attr->value;

							// In XHTML, empty values should never exist, so we repeat the value
							if (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_XHTML))
							{
								$value = $name;
							}
							// For HTML, empty is fine
							elseif (empty($value) && ($type & SIMPLEPIE_CONSTRUCT_HTML))
							{
								$attrs[] = $name;
								continue;
							}

							// Standard attribute text
							$attrs[] = $name . '="' . $attr->value . '"';
						}
						$text .= ' ' . implode(' ', $attrs);
					}
					$text .= '>';
					$fragment->appendChild(new DOMText($text));
				}

				$number = $element->childNodes->length;
				for ($i = $number; $i > 0; $i--)
				{
					$child = $element->childNodes->item(0);
					$fragment->appendChild($child);
				}

				if (!in_array($tag, array('script', 'style')))
				{
					$fragment->appendChild(new DOMText('</' . $tag . '>'));
				}

				$element->parentNode->replaceChild($fragment, $element);
			}

			return;
		}
		elseif (in_array($tag, array('script', 'style')))
		{
			foreach ($elements as $element)
			{
				$element->parentNode->removeChild($element);
			}

			return;
		}
		else
		{
			foreach ($elements as $element)
			{
				$fragment = $document->createDocumentFragment();
				$number = $element->childNodes->length;
				for ($i = $number; $i > 0; $i--)
				{
					$child = $element->childNodes->item(0);
					$fragment->appendChild($child);
				}

				$element->parentNode->replaceChild($fragment, $element);
			}
		}
	}

	protected function strip_attr($attrib, $document)
	{
		$xpath = new DOMXPath($document);
		$elements = $xpath->query('//*[@' . $attrib . ']');

		foreach ($elements as $element)
		{
			$element->removeAttribute($attrib);
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Cache.php000064400000010310152177723700017074 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Used to create cache objects
 *
 * This class can be overloaded with {@see SimplePie::set_cache_class()},
 * although the preferred way is to create your own handler
 * via {@see register()}
 *
 * @package SimplePie
 * @subpackage Caching
 */
class SimplePie_Cache
{
	/**
	 * Cache handler classes
	 *
	 * These receive 3 parameters to their constructor, as documented in
	 * {@see register()}
	 * @var array
	 */
	protected static $handlers = array(
		'mysql' => 'SimplePie_Cache_MySQL',
		'memcache' => 'SimplePie_Cache_Memcache',
	);

	/**
	 * Don't call the constructor. Please.
	 */
	private function __construct() { }

	/**
	 * Create a new SimplePie_Cache object
	 *
	 * @param string $location URL location (scheme is used to determine handler)
	 * @param string $filename Unique identifier for cache object
	 * @param string $extension 'spi' or 'spc'
	 * @return SimplePie_Cache_Base Type of object depends on scheme of `$location`
	 */
	public static function get_handler($location, $filename, $extension)
	{
		$type = explode(':', $location, 2);
		$type = $type[0];
		if (!empty(self::$handlers[$type]))
		{
			$class = self::$handlers[$type];
			return new $class($location, $filename, $extension);
		}

		return new SimplePie_Cache_File($location, $filename, $extension);
	}

	/**
	 * Create a new SimplePie_Cache object
	 *
	 * @deprecated Use {@see get_handler} instead
	 */
	public function create($location, $filename, $extension)
	{
		trigger_error('Cache::create() has been replaced with Cache::get_handler(). Switch to the registry system to use this.', E_USER_DEPRECATED);
		return self::get_handler($location, $filename, $extension);
	}

	/**
	 * Register a handler
	 *
	 * @param string $type DSN type to register for
	 * @param string $class Name of handler class. Must implement SimplePie_Cache_Base
	 */
	public static function register($type, $class)
	{
		self::$handlers[$type] = $class;
	}

	/**
	 * Parse a URL into an array
	 *
	 * @param string $url
	 * @return array
	 */
	public static function parse_URL($url)
	{
		$params = parse_url($url);
		$params['extras'] = array();
		if (isset($params['query']))
		{
			parse_str($params['query'], $params['extras']);
		}
		return $params;
	}
}
vendor/simplepie/simplepie/library/SimplePie/Author.php000064400000007010152177723700017336 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Manages all author-related data
 *
 * Used by {@see SimplePie_Item::get_author()} and {@see SimplePie::get_authors()}
 *
 * This class can be overloaded with {@see SimplePie::set_author_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Author
{
	/**
	 * Author's name
	 *
	 * @var string
	 * @see get_name()
	 */
	var $name;

	/**
	 * Author's link
	 *
	 * @var string
	 * @see get_link()
	 */
	var $link;

	/**
	 * Author's email address
	 *
	 * @var string
	 * @see get_email()
	 */
	var $email;

	/**
	 * Constructor, used to input the data
	 *
	 * @param string $name
	 * @param string $link
	 * @param string $email
	 */
	public function __construct($name = null, $link = null, $email = null)
	{
		$this->name = $name;
		$this->link = $link;
		$this->email = $email;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Author's name
	 *
	 * @return string|null
	 */
	public function get_name()
	{
		if ($this->name !== null)
		{
			return $this->name;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Author's link
	 *
	 * @return string|null
	 */
	public function get_link()
	{
		if ($this->link !== null)
		{
			return $this->link;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Author's email address
	 *
	 * @return string|null
	 */
	public function get_email()
	{
		if ($this->email !== null)
		{
			return $this->email;
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Parse/Date.php000064400000046343152177723700020037 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Date Parser
 *
 * @package SimplePie
 * @subpackage Parsing
 */
class SimplePie_Parse_Date
{
	/**
	 * Input data
	 *
	 * @access protected
	 * @var string
	 */
	var $date;

	/**
	 * List of days, calendar day name => ordinal day number in the week
	 *
	 * @access protected
	 * @var array
	 */
	var $day = array(
		// English
		'mon' => 1,
		'monday' => 1,
		'tue' => 2,
		'tuesday' => 2,
		'wed' => 3,
		'wednesday' => 3,
		'thu' => 4,
		'thursday' => 4,
		'fri' => 5,
		'friday' => 5,
		'sat' => 6,
		'saturday' => 6,
		'sun' => 7,
		'sunday' => 7,
		// Dutch
		'maandag' => 1,
		'dinsdag' => 2,
		'woensdag' => 3,
		'donderdag' => 4,
		'vrijdag' => 5,
		'zaterdag' => 6,
		'zondag' => 7,
		// French
		'lundi' => 1,
		'mardi' => 2,
		'mercredi' => 3,
		'jeudi' => 4,
		'vendredi' => 5,
		'samedi' => 6,
		'dimanche' => 7,
		// German
		'montag' => 1,
		'dienstag' => 2,
		'mittwoch' => 3,
		'donnerstag' => 4,
		'freitag' => 5,
		'samstag' => 6,
		'sonnabend' => 6,
		'sonntag' => 7,
		// Italian
		'lunedì' => 1,
		'martedì' => 2,
		'mercoledì' => 3,
		'giovedì' => 4,
		'venerdì' => 5,
		'sabato' => 6,
		'domenica' => 7,
		// Spanish
		'lunes' => 1,
		'martes' => 2,
		'miércoles' => 3,
		'jueves' => 4,
		'viernes' => 5,
		'sábado' => 6,
		'domingo' => 7,
		// Finnish
		'maanantai' => 1,
		'tiistai' => 2,
		'keskiviikko' => 3,
		'torstai' => 4,
		'perjantai' => 5,
		'lauantai' => 6,
		'sunnuntai' => 7,
		// Hungarian
		'hétfő' => 1,
		'kedd' => 2,
		'szerda' => 3,
		'csütörtok' => 4,
		'péntek' => 5,
		'szombat' => 6,
		'vasárnap' => 7,
		// Greek
		'Δευ' => 1,
		'Τρι' => 2,
		'Τετ' => 3,
		'Πεμ' => 4,
		'Παρ' => 5,
		'Σαβ' => 6,
		'Κυρ' => 7,
	);

	/**
	 * List of months, calendar month name => calendar month number
	 *
	 * @access protected
	 * @var array
	 */
	var $month = array(
		// English
		'jan' => 1,
		'january' => 1,
		'feb' => 2,
		'february' => 2,
		'mar' => 3,
		'march' => 3,
		'apr' => 4,
		'april' => 4,
		'may' => 5,
		// No long form of May
		'jun' => 6,
		'june' => 6,
		'jul' => 7,
		'july' => 7,
		'aug' => 8,
		'august' => 8,
		'sep' => 9,
		'september' => 8,
		'oct' => 10,
		'october' => 10,
		'nov' => 11,
		'november' => 11,
		'dec' => 12,
		'december' => 12,
		// Dutch
		'januari' => 1,
		'februari' => 2,
		'maart' => 3,
		'april' => 4,
		'mei' => 5,
		'juni' => 6,
		'juli' => 7,
		'augustus' => 8,
		'september' => 9,
		'oktober' => 10,
		'november' => 11,
		'december' => 12,
		// French
		'janvier' => 1,
		'février' => 2,
		'mars' => 3,
		'avril' => 4,
		'mai' => 5,
		'juin' => 6,
		'juillet' => 7,
		'août' => 8,
		'septembre' => 9,
		'octobre' => 10,
		'novembre' => 11,
		'décembre' => 12,
		// German
		'januar' => 1,
		'februar' => 2,
		'märz' => 3,
		'april' => 4,
		'mai' => 5,
		'juni' => 6,
		'juli' => 7,
		'august' => 8,
		'september' => 9,
		'oktober' => 10,
		'november' => 11,
		'dezember' => 12,
		// Italian
		'gennaio' => 1,
		'febbraio' => 2,
		'marzo' => 3,
		'aprile' => 4,
		'maggio' => 5,
		'giugno' => 6,
		'luglio' => 7,
		'agosto' => 8,
		'settembre' => 9,
		'ottobre' => 10,
		'novembre' => 11,
		'dicembre' => 12,
		// Spanish
		'enero' => 1,
		'febrero' => 2,
		'marzo' => 3,
		'abril' => 4,
		'mayo' => 5,
		'junio' => 6,
		'julio' => 7,
		'agosto' => 8,
		'septiembre' => 9,
		'setiembre' => 9,
		'octubre' => 10,
		'noviembre' => 11,
		'diciembre' => 12,
		// Finnish
		'tammikuu' => 1,
		'helmikuu' => 2,
		'maaliskuu' => 3,
		'huhtikuu' => 4,
		'toukokuu' => 5,
		'kesäkuu' => 6,
		'heinäkuu' => 7,
		'elokuu' => 8,
		'suuskuu' => 9,
		'lokakuu' => 10,
		'marras' => 11,
		'joulukuu' => 12,
		// Hungarian
		'január' => 1,
		'február' => 2,
		'március' => 3,
		'április' => 4,
		'május' => 5,
		'június' => 6,
		'július' => 7,
		'augusztus' => 8,
		'szeptember' => 9,
		'október' => 10,
		'november' => 11,
		'december' => 12,
		// Greek
		'Ιαν' => 1,
		'Φεβ' => 2,
		'Μάώ' => 3,
		'Μαώ' => 3,
		'Απρ' => 4,
		'Μάι' => 5,
		'Μαϊ' => 5,
		'Μαι' => 5,
		'Ιούν' => 6,
		'Ιον' => 6,
		'Ιούλ' => 7,
		'Ιολ' => 7,
		'Αύγ' => 8,
		'Αυγ' => 8,
		'Σεπ' => 9,
		'Οκτ' => 10,
		'Νοέ' => 11,
		'Δεκ' => 12,
	);

	/**
	 * List of timezones, abbreviation => offset from UTC
	 *
	 * @access protected
	 * @var array
	 */
	var $timezone = array(
		'ACDT' => 37800,
		'ACIT' => 28800,
		'ACST' => 34200,
		'ACT' => -18000,
		'ACWDT' => 35100,
		'ACWST' => 31500,
		'AEDT' => 39600,
		'AEST' => 36000,
		'AFT' => 16200,
		'AKDT' => -28800,
		'AKST' => -32400,
		'AMDT' => 18000,
		'AMT' => -14400,
		'ANAST' => 46800,
		'ANAT' => 43200,
		'ART' => -10800,
		'AZOST' => -3600,
		'AZST' => 18000,
		'AZT' => 14400,
		'BIOT' => 21600,
		'BIT' => -43200,
		'BOT' => -14400,
		'BRST' => -7200,
		'BRT' => -10800,
		'BST' => 3600,
		'BTT' => 21600,
		'CAST' => 18000,
		'CAT' => 7200,
		'CCT' => 23400,
		'CDT' => -18000,
		'CEDT' => 7200,
		'CET' => 3600,
		'CGST' => -7200,
		'CGT' => -10800,
		'CHADT' => 49500,
		'CHAST' => 45900,
		'CIST' => -28800,
		'CKT' => -36000,
		'CLDT' => -10800,
		'CLST' => -14400,
		'COT' => -18000,
		'CST' => -21600,
		'CVT' => -3600,
		'CXT' => 25200,
		'DAVT' => 25200,
		'DTAT' => 36000,
		'EADT' => -18000,
		'EAST' => -21600,
		'EAT' => 10800,
		'ECT' => -18000,
		'EDT' => -14400,
		'EEST' => 10800,
		'EET' => 7200,
		'EGT' => -3600,
		'EKST' => 21600,
		'EST' => -18000,
		'FJT' => 43200,
		'FKDT' => -10800,
		'FKST' => -14400,
		'FNT' => -7200,
		'GALT' => -21600,
		'GEDT' => 14400,
		'GEST' => 10800,
		'GFT' => -10800,
		'GILT' => 43200,
		'GIT' => -32400,
		'GST' => 14400,
		'GST' => -7200,
		'GYT' => -14400,
		'HAA' => -10800,
		'HAC' => -18000,
		'HADT' => -32400,
		'HAE' => -14400,
		'HAP' => -25200,
		'HAR' => -21600,
		'HAST' => -36000,
		'HAT' => -9000,
		'HAY' => -28800,
		'HKST' => 28800,
		'HMT' => 18000,
		'HNA' => -14400,
		'HNC' => -21600,
		'HNE' => -18000,
		'HNP' => -28800,
		'HNR' => -25200,
		'HNT' => -12600,
		'HNY' => -32400,
		'IRDT' => 16200,
		'IRKST' => 32400,
		'IRKT' => 28800,
		'IRST' => 12600,
		'JFDT' => -10800,
		'JFST' => -14400,
		'JST' => 32400,
		'KGST' => 21600,
		'KGT' => 18000,
		'KOST' => 39600,
		'KOVST' => 28800,
		'KOVT' => 25200,
		'KRAST' => 28800,
		'KRAT' => 25200,
		'KST' => 32400,
		'LHDT' => 39600,
		'LHST' => 37800,
		'LINT' => 50400,
		'LKT' => 21600,
		'MAGST' => 43200,
		'MAGT' => 39600,
		'MAWT' => 21600,
		'MDT' => -21600,
		'MESZ' => 7200,
		'MEZ' => 3600,
		'MHT' => 43200,
		'MIT' => -34200,
		'MNST' => 32400,
		'MSDT' => 14400,
		'MSST' => 10800,
		'MST' => -25200,
		'MUT' => 14400,
		'MVT' => 18000,
		'MYT' => 28800,
		'NCT' => 39600,
		'NDT' => -9000,
		'NFT' => 41400,
		'NMIT' => 36000,
		'NOVST' => 25200,
		'NOVT' => 21600,
		'NPT' => 20700,
		'NRT' => 43200,
		'NST' => -12600,
		'NUT' => -39600,
		'NZDT' => 46800,
		'NZST' => 43200,
		'OMSST' => 25200,
		'OMST' => 21600,
		'PDT' => -25200,
		'PET' => -18000,
		'PETST' => 46800,
		'PETT' => 43200,
		'PGT' => 36000,
		'PHOT' => 46800,
		'PHT' => 28800,
		'PKT' => 18000,
		'PMDT' => -7200,
		'PMST' => -10800,
		'PONT' => 39600,
		'PST' => -28800,
		'PWT' => 32400,
		'PYST' => -10800,
		'PYT' => -14400,
		'RET' => 14400,
		'ROTT' => -10800,
		'SAMST' => 18000,
		'SAMT' => 14400,
		'SAST' => 7200,
		'SBT' => 39600,
		'SCDT' => 46800,
		'SCST' => 43200,
		'SCT' => 14400,
		'SEST' => 3600,
		'SGT' => 28800,
		'SIT' => 28800,
		'SRT' => -10800,
		'SST' => -39600,
		'SYST' => 10800,
		'SYT' => 7200,
		'TFT' => 18000,
		'THAT' => -36000,
		'TJT' => 18000,
		'TKT' => -36000,
		'TMT' => 18000,
		'TOT' => 46800,
		'TPT' => 32400,
		'TRUT' => 36000,
		'TVT' => 43200,
		'TWT' => 28800,
		'UYST' => -7200,
		'UYT' => -10800,
		'UZT' => 18000,
		'VET' => -14400,
		'VLAST' => 39600,
		'VLAT' => 36000,
		'VOST' => 21600,
		'VUT' => 39600,
		'WAST' => 7200,
		'WAT' => 3600,
		'WDT' => 32400,
		'WEST' => 3600,
		'WFT' => 43200,
		'WIB' => 25200,
		'WIT' => 32400,
		'WITA' => 28800,
		'WKST' => 18000,
		'WST' => 28800,
		'YAKST' => 36000,
		'YAKT' => 32400,
		'YAPT' => 36000,
		'YEKST' => 21600,
		'YEKT' => 18000,
	);

	/**
	 * Cached PCRE for SimplePie_Parse_Date::$day
	 *
	 * @access protected
	 * @var string
	 */
	var $day_pcre;

	/**
	 * Cached PCRE for SimplePie_Parse_Date::$month
	 *
	 * @access protected
	 * @var string
	 */
	var $month_pcre;

	/**
	 * Array of user-added callback methods
	 *
	 * @access private
	 * @var array
	 */
	var $built_in = array();

	/**
	 * Array of user-added callback methods
	 *
	 * @access private
	 * @var array
	 */
	var $user = array();

	/**
	 * Create new SimplePie_Parse_Date object, and set self::day_pcre,
	 * self::month_pcre, and self::built_in
	 *
	 * @access private
	 */
	public function __construct()
	{
		$this->day_pcre = '(' . implode(array_keys($this->day), '|') . ')';
		$this->month_pcre = '(' . implode(array_keys($this->month), '|') . ')';

		static $cache;
		if (!isset($cache[get_class($this)]))
		{
			$all_methods = get_class_methods($this);

			foreach ($all_methods as $method)
			{
				if (strtolower(substr($method, 0, 5)) === 'date_')
				{
					$cache[get_class($this)][] = $method;
				}
			}
		}

		foreach ($cache[get_class($this)] as $method)
		{
			$this->built_in[] = $method;
		}
	}

	/**
	 * Get the object
	 *
	 * @access public
	 */
	public static function get()
	{
		static $object;
		if (!$object)
		{
			$object = new SimplePie_Parse_Date;
		}
		return $object;
	}

	/**
	 * Parse a date
	 *
	 * @final
	 * @access public
	 * @param string $date Date to parse
	 * @return int Timestamp corresponding to date string, or false on failure
	 */
	public function parse($date)
	{
		foreach ($this->user as $method)
		{
			if (($returned = call_user_func($method, $date)) !== false)
			{
				return $returned;
			}
		}

		foreach ($this->built_in as $method)
		{
			if (($returned = call_user_func(array($this, $method), $date)) !== false)
			{
				return $returned;
			}
		}

		return false;
	}

	/**
	 * Add a callback method to parse a date
	 *
	 * @final
	 * @access public
	 * @param callback $callback
	 */
	public function add_callback($callback)
	{
		if (is_callable($callback))
		{
			$this->user[] = $callback;
		}
		else
		{
			trigger_error('User-supplied function must be a valid callback', E_USER_WARNING);
		}
	}

	/**
	 * Parse a superset of W3C-DTF (allows hyphens and colons to be omitted, as
	 * well as allowing any of upper or lower case "T", horizontal tabs, or
	 * spaces to be used as the time seperator (including more than one))
	 *
	 * @access protected
	 * @return int Timestamp
	 */
	public function date_w3cdtf($date)
	{
		static $pcre;
		if (!$pcre)
		{
			$year = '([0-9]{4})';
			$month = $day = $hour = $minute = $second = '([0-9]{2})';
			$decimal = '([0-9]*)';
			$zone = '(?:(Z)|([+\-])([0-9]{1,2}):?([0-9]{1,2}))';
			$pcre = '/^' . $year . '(?:-?' . $month . '(?:-?' . $day . '(?:[Tt\x09\x20]+' . $hour . '(?::?' . $minute . '(?::?' . $second . '(?:.' . $decimal . ')?)?)?' . $zone . ')?)?)?$/';
		}
		if (preg_match($pcre, $date, $match))
		{
			/*
			Capturing subpatterns:
			1: Year
			2: Month
			3: Day
			4: Hour
			5: Minute
			6: Second
			7: Decimal fraction of a second
			8: Zulu
			9: Timezone ±
			10: Timezone hours
			11: Timezone minutes
			*/

			// Fill in empty matches
			for ($i = count($match); $i <= 3; $i++)
			{
				$match[$i] = '1';
			}

			for ($i = count($match); $i <= 7; $i++)
			{
				$match[$i] = '0';
			}

			// Numeric timezone
			if (isset($match[9]) && $match[9] !== '')
			{
				$timezone = $match[10] * 3600;
				$timezone += $match[11] * 60;
				if ($match[9] === '-')
				{
					$timezone = 0 - $timezone;
				}
			}
			else
			{
				$timezone = 0;
			}

			// Convert the number of seconds to an integer, taking decimals into account
			$second = round($match[6] + $match[7] / pow(10, strlen($match[7])));

			return gmmktime($match[4], $match[5], $second, $match[2], $match[3], $match[1]) - $timezone;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Remove RFC822 comments
	 *
	 * @access protected
	 * @param string $data Data to strip comments from
	 * @return string Comment stripped string
	 */
	public function remove_rfc2822_comments($string)
	{
		$string = (string) $string;
		$position = 0;
		$length = strlen($string);
		$depth = 0;

		$output = '';

		while ($position < $length && ($pos = strpos($string, '(', $position)) !== false)
		{
			$output .= substr($string, $position, $pos - $position);
			$position = $pos + 1;
			if ($string[$pos - 1] !== '\\')
			{
				$depth++;
				while ($depth && $position < $length)
				{
					$position += strcspn($string, '()', $position);
					if ($string[$position - 1] === '\\')
					{
						$position++;
						continue;
					}
					elseif (isset($string[$position]))
					{
						switch ($string[$position])
						{
							case '(':
								$depth++;
								break;

							case ')':
								$depth--;
								break;
						}
						$position++;
					}
					else
					{
						break;
					}
				}
			}
			else
			{
				$output .= '(';
			}
		}
		$output .= substr($string, $position);

		return $output;
	}

	/**
	 * Parse RFC2822's date format
	 *
	 * @access protected
	 * @return int Timestamp
	 */
	public function date_rfc2822($date)
	{
		static $pcre;
		if (!$pcre)
		{
			$wsp = '[\x09\x20]';
			$fws = '(?:' . $wsp . '+|' . $wsp . '*(?:\x0D\x0A' . $wsp . '+)+)';
			$optional_fws = $fws . '?';
			$day_name = $this->day_pcre;
			$month = $this->month_pcre;
			$day = '([0-9]{1,2})';
			$hour = $minute = $second = '([0-9]{2})';
			$year = '([0-9]{2,4})';
			$num_zone = '([+\-])([0-9]{2})([0-9]{2})';
			$character_zone = '([A-Z]{1,5})';
			$zone = '(?:' . $num_zone . '|' . $character_zone . ')';
			$pcre = '/(?:' . $optional_fws . $day_name . $optional_fws . ',)?' . $optional_fws . $day . $fws . $month . $fws . $year . $fws . $hour . $optional_fws . ':' . $optional_fws . $minute . '(?:' . $optional_fws . ':' . $optional_fws . $second . ')?' . $fws . $zone . '/i';
		}
		if (preg_match($pcre, $this->remove_rfc2822_comments($date), $match))
		{
			/*
			Capturing subpatterns:
			1: Day name
			2: Day
			3: Month
			4: Year
			5: Hour
			6: Minute
			7: Second
			8: Timezone ±
			9: Timezone hours
			10: Timezone minutes
			11: Alphabetic timezone
			*/

			// Find the month number
			$month = $this->month[strtolower($match[3])];

			// Numeric timezone
			if ($match[8] !== '')
			{
				$timezone = $match[9] * 3600;
				$timezone += $match[10] * 60;
				if ($match[8] === '-')
				{
					$timezone = 0 - $timezone;
				}
			}
			// Character timezone
			elseif (isset($this->timezone[strtoupper($match[11])]))
			{
				$timezone = $this->timezone[strtoupper($match[11])];
			}
			// Assume everything else to be -0000
			else
			{
				$timezone = 0;
			}

			// Deal with 2/3 digit years
			if ($match[4] < 50)
			{
				$match[4] += 2000;
			}
			elseif ($match[4] < 1000)
			{
				$match[4] += 1900;
			}

			// Second is optional, if it is empty set it to zero
			if ($match[7] !== '')
			{
				$second = $match[7];
			}
			else
			{
				$second = 0;
			}

			return gmmktime($match[5], $match[6], $second, $month, $match[2], $match[4]) - $timezone;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Parse RFC850's date format
	 *
	 * @access protected
	 * @return int Timestamp
	 */
	public function date_rfc850($date)
	{
		static $pcre;
		if (!$pcre)
		{
			$space = '[\x09\x20]+';
			$day_name = $this->day_pcre;
			$month = $this->month_pcre;
			$day = '([0-9]{1,2})';
			$year = $hour = $minute = $second = '([0-9]{2})';
			$zone = '([A-Z]{1,5})';
			$pcre = '/^' . $day_name . ',' . $space . $day . '-' . $month . '-' . $year . $space . $hour . ':' . $minute . ':' . $second . $space . $zone . '$/i';
		}
		if (preg_match($pcre, $date, $match))
		{
			/*
			Capturing subpatterns:
			1: Day name
			2: Day
			3: Month
			4: Year
			5: Hour
			6: Minute
			7: Second
			8: Timezone
			*/

			// Month
			$month = $this->month[strtolower($match[3])];

			// Character timezone
			if (isset($this->timezone[strtoupper($match[8])]))
			{
				$timezone = $this->timezone[strtoupper($match[8])];
			}
			// Assume everything else to be -0000
			else
			{
				$timezone = 0;
			}

			// Deal with 2 digit year
			if ($match[4] < 50)
			{
				$match[4] += 2000;
			}
			else
			{
				$match[4] += 1900;
			}

			return gmmktime($match[5], $match[6], $match[7], $month, $match[2], $match[4]) - $timezone;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Parse C99's asctime()'s date format
	 *
	 * @access protected
	 * @return int Timestamp
	 */
	public function date_asctime($date)
	{
		static $pcre;
		if (!$pcre)
		{
			$space = '[\x09\x20]+';
			$wday_name = $this->day_pcre;
			$mon_name = $this->month_pcre;
			$day = '([0-9]{1,2})';
			$hour = $sec = $min = '([0-9]{2})';
			$year = '([0-9]{4})';
			$terminator = '\x0A?\x00?';
			$pcre = '/^' . $wday_name . $space . $mon_name . $space . $day . $space . $hour . ':' . $min . ':' . $sec . $space . $year . $terminator . '$/i';
		}
		if (preg_match($pcre, $date, $match))
		{
			/*
			Capturing subpatterns:
			1: Day name
			2: Month
			3: Day
			4: Hour
			5: Minute
			6: Second
			7: Year
			*/

			$month = $this->month[strtolower($match[2])];
			return gmmktime($match[4], $match[5], $match[6], $month, $match[3], $match[7]);
		}
		else
		{
			return false;
		}
	}

	/**
	 * Parse dates using strtotime()
	 *
	 * @access protected
	 * @return int Timestamp
	 */
	public function date_strtotime($date)
	{
		$strtotime = strtotime($date);
		if ($strtotime === -1 || $strtotime === false)
		{
			return false;
		}
		else
		{
			return $strtotime;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Parser.php000064400000027113152177723700017336 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Parses XML into something sane
 *
 *
 * This class can be overloaded with {@see SimplePie::set_parser_class()}
 *
 * @package SimplePie
 * @subpackage Parsing
 */
class SimplePie_Parser
{
	var $error_code;
	var $error_string;
	var $current_line;
	var $current_column;
	var $current_byte;
	var $separator = ' ';
	var $namespace = array('');
	var $element = array('');
	var $xml_base = array('');
	var $xml_base_explicit = array(false);
	var $xml_lang = array('');
	var $data = array();
	var $datas = array(array());
	var $current_xhtml_construct = -1;
	var $encoding;
	protected $registry;

	public function set_registry(SimplePie_Registry $registry)
	{
		$this->registry = $registry;
	}

	public function parse(&$data, $encoding)
	{
		// Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character
		if (strtoupper($encoding) === 'US-ASCII')
		{
			$this->encoding = 'UTF-8';
		}
		else
		{
			$this->encoding = $encoding;
		}

		// Strip BOM:
		// UTF-32 Big Endian BOM
		if (substr($data, 0, 4) === "\x00\x00\xFE\xFF")
		{
			$data = substr($data, 4);
		}
		// UTF-32 Little Endian BOM
		elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00")
		{
			$data = substr($data, 4);
		}
		// UTF-16 Big Endian BOM
		elseif (substr($data, 0, 2) === "\xFE\xFF")
		{
			$data = substr($data, 2);
		}
		// UTF-16 Little Endian BOM
		elseif (substr($data, 0, 2) === "\xFF\xFE")
		{
			$data = substr($data, 2);
		}
		// UTF-8 BOM
		elseif (substr($data, 0, 3) === "\xEF\xBB\xBF")
		{
			$data = substr($data, 3);
		}

		if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false)
		{
			$declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5)));
			if ($declaration->parse())
			{
				$data = substr($data, $pos + 2);
				$data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data;
			}
			else
			{
				$this->error_string = 'SimplePie bug! Please report this!';
				return false;
			}
		}

		$return = true;

		static $xml_is_sane = null;
		if ($xml_is_sane === null)
		{
			$parser_check = xml_parser_create();
			xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
			xml_parser_free($parser_check);
			$xml_is_sane = isset($values[0]['value']);
		}

		// Create the parser
		if ($xml_is_sane)
		{
			$xml = xml_parser_create_ns($this->encoding, $this->separator);
			xml_parser_set_option($xml, XML_OPTION_SKIP_WHITE, 1);
			xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, 0);
			xml_set_object($xml, $this);
			xml_set_character_data_handler($xml, 'cdata');
			xml_set_element_handler($xml, 'tag_open', 'tag_close');

			// Parse!
			if (!xml_parse($xml, $data, true))
			{
				$this->error_code = xml_get_error_code($xml);
				$this->error_string = xml_error_string($this->error_code);
				$return = false;
			}
			$this->current_line = xml_get_current_line_number($xml);
			$this->current_column = xml_get_current_column_number($xml);
			$this->current_byte = xml_get_current_byte_index($xml);
			xml_parser_free($xml);
			return $return;
		}
		else
		{
			libxml_clear_errors();
			$xml = new XMLReader();
			$xml->xml($data);
			while (@$xml->read())
			{
				switch ($xml->nodeType)
				{

					case constant('XMLReader::END_ELEMENT'):
						if ($xml->namespaceURI !== '')
						{
							$tagName = $xml->namespaceURI . $this->separator . $xml->localName;
						}
						else
						{
							$tagName = $xml->localName;
						}
						$this->tag_close(null, $tagName);
						break;
					case constant('XMLReader::ELEMENT'):
						$empty = $xml->isEmptyElement;
						if ($xml->namespaceURI !== '')
						{
							$tagName = $xml->namespaceURI . $this->separator . $xml->localName;
						}
						else
						{
							$tagName = $xml->localName;
						}
						$attributes = array();
						while ($xml->moveToNextAttribute())
						{
							if ($xml->namespaceURI !== '')
							{
								$attrName = $xml->namespaceURI . $this->separator . $xml->localName;
							}
							else
							{
								$attrName = $xml->localName;
							}
							$attributes[$attrName] = $xml->value;
						}
						$this->tag_open(null, $tagName, $attributes);
						if ($empty)
						{
							$this->tag_close(null, $tagName);
						}
						break;
					case constant('XMLReader::TEXT'):

					case constant('XMLReader::CDATA'):
						$this->cdata(null, $xml->value);
						break;
				}
			}
			if ($error = libxml_get_last_error())
			{
				$this->error_code = $error->code;
				$this->error_string = $error->message;
				$this->current_line = $error->line;
				$this->current_column = $error->column;
				return false;
			}
			else
			{
				return true;
			}
		}
	}

	public function get_error_code()
	{
		return $this->error_code;
	}

	public function get_error_string()
	{
		return $this->error_string;
	}

	public function get_current_line()
	{
		return $this->current_line;
	}

	public function get_current_column()
	{
		return $this->current_column;
	}

	public function get_current_byte()
	{
		return $this->current_byte;
	}

	public function get_data()
	{
		return $this->data;
	}

	public function tag_open($parser, $tag, $attributes)
	{
		list($this->namespace[], $this->element[]) = $this->split_ns($tag);

		$attribs = array();
		foreach ($attributes as $name => $value)
		{
			list($attrib_namespace, $attribute) = $this->split_ns($name);
			$attribs[$attrib_namespace][$attribute] = $value;
		}

		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['base']))
		{
			$base = $this->registry->call('Misc', 'absolutize_url', array($attribs[SIMPLEPIE_NAMESPACE_XML]['base'], end($this->xml_base)));
			if ($base !== false)
			{
				$this->xml_base[] = $base;
				$this->xml_base_explicit[] = true;
			}
		}
		else
		{
			$this->xml_base[] = end($this->xml_base);
			$this->xml_base_explicit[] = end($this->xml_base_explicit);
		}

		if (isset($attribs[SIMPLEPIE_NAMESPACE_XML]['lang']))
		{
			$this->xml_lang[] = $attribs[SIMPLEPIE_NAMESPACE_XML]['lang'];
		}
		else
		{
			$this->xml_lang[] = end($this->xml_lang);
		}

		if ($this->current_xhtml_construct >= 0)
		{
			$this->current_xhtml_construct++;
			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML)
			{
				$this->data['data'] .= '<' . end($this->element);
				if (isset($attribs['']))
				{
					foreach ($attribs[''] as $name => $value)
					{
						$this->data['data'] .= ' ' . $name . '="' . htmlspecialchars($value, ENT_COMPAT, $this->encoding) . '"';
					}
				}
				$this->data['data'] .= '>';
			}
		}
		else
		{
			$this->datas[] =& $this->data;
			$this->data =& $this->data['child'][end($this->namespace)][end($this->element)][];
			$this->data = array('data' => '', 'attribs' => $attribs, 'xml_base' => end($this->xml_base), 'xml_base_explicit' => end($this->xml_base_explicit), 'xml_lang' => end($this->xml_lang));
			if ((end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_03 && in_array(end($this->element), array('title', 'tagline', 'copyright', 'info', 'summary', 'content')) && isset($attribs['']['mode']) && $attribs['']['mode'] === 'xml')
			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_ATOM_10 && in_array(end($this->element), array('rights', 'subtitle', 'summary', 'info', 'title', 'content')) && isset($attribs['']['type']) && $attribs['']['type'] === 'xhtml')
			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_20 && in_array(end($this->element), array('title')))
			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_090 && in_array(end($this->element), array('title')))
			|| (end($this->namespace) === SIMPLEPIE_NAMESPACE_RSS_10 && in_array(end($this->element), array('title'))))
			{
				$this->current_xhtml_construct = 0;
			}
		}
	}

	public function cdata($parser, $cdata)
	{
		if ($this->current_xhtml_construct >= 0)
		{
			$this->data['data'] .= htmlspecialchars($cdata, ENT_QUOTES, $this->encoding);
		}
		else
		{
			$this->data['data'] .= $cdata;
		}
	}

	public function tag_close($parser, $tag)
	{
		if ($this->current_xhtml_construct >= 0)
		{
			$this->current_xhtml_construct--;
			if (end($this->namespace) === SIMPLEPIE_NAMESPACE_XHTML && !in_array(end($this->element), array('area', 'base', 'basefont', 'br', 'col', 'frame', 'hr', 'img', 'input', 'isindex', 'link', 'meta', 'param')))
			{
				$this->data['data'] .= '</' . end($this->element) . '>';
			}
		}
		if ($this->current_xhtml_construct === -1)
		{
			$this->data =& $this->datas[count($this->datas) - 1];
			array_pop($this->datas);
		}

		array_pop($this->element);
		array_pop($this->namespace);
		array_pop($this->xml_base);
		array_pop($this->xml_base_explicit);
		array_pop($this->xml_lang);
	}

	public function split_ns($string)
	{
		static $cache = array();
		if (!isset($cache[$string]))
		{
			if ($pos = strpos($string, $this->separator))
			{
				static $separator_length;
				if (!$separator_length)
				{
					$separator_length = strlen($this->separator);
				}
				$namespace = substr($string, 0, $pos);
				$local_name = substr($string, $pos + $separator_length);
				if (strtolower($namespace) === SIMPLEPIE_NAMESPACE_ITUNES)
				{
					$namespace = SIMPLEPIE_NAMESPACE_ITUNES;
				}

				// Normalize the Media RSS namespaces
				if ($namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG ||
					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2 ||
					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3 ||
					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4 ||
					$namespace === SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5 )
				{
					$namespace = SIMPLEPIE_NAMESPACE_MEDIARSS;
				}
				$cache[$string] = array($namespace, $local_name);
			}
			else
			{
				$cache[$string] = array('', $string);
			}
		}
		return $cache[$string];
	}
}
vendor/simplepie/simplepie/library/SimplePie/Content/Type/Sniffer.php000064400000017711152177723700022034 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Content-type sniffing
 *
 * Based on the rules in http://tools.ietf.org/html/draft-abarth-mime-sniff-06
 *
 * This is used since we can't always trust Content-Type headers, and is based
 * upon the HTML5 parsing rules.
 *
 *
 * This class can be overloaded with {@see SimplePie::set_content_type_sniffer_class()}
 *
 * @package SimplePie
 * @subpackage HTTP
 */
class SimplePie_Content_Type_Sniffer
{
	/**
	 * File object
	 *
	 * @var SimplePie_File
	 */
	var $file;

	/**
	 * Create an instance of the class with the input file
	 *
	 * @param SimplePie_Content_Type_Sniffer $file Input file
	 */
	public function __construct($file)
	{
		$this->file = $file;
	}

	/**
	 * Get the Content-Type of the specified file
	 *
	 * @return string Actual Content-Type
	 */
	public function get_type()
	{
		if (isset($this->file->headers['content-type']))
		{
			if (!isset($this->file->headers['content-encoding'])
				&& ($this->file->headers['content-type'] === 'text/plain'
					|| $this->file->headers['content-type'] === 'text/plain; charset=ISO-8859-1'
					|| $this->file->headers['content-type'] === 'text/plain; charset=iso-8859-1'
					|| $this->file->headers['content-type'] === 'text/plain; charset=UTF-8'))
			{
				return $this->text_or_binary();
			}

			if (($pos = strpos($this->file->headers['content-type'], ';')) !== false)
			{
				$official = substr($this->file->headers['content-type'], 0, $pos);
			}
			else
			{
				$official = $this->file->headers['content-type'];
			}
			$official = trim(strtolower($official));

			if ($official === 'unknown/unknown'
				|| $official === 'application/unknown')
			{
				return $this->unknown();
			}
			elseif (substr($official, -4) === '+xml'
				|| $official === 'text/xml'
				|| $official === 'application/xml')
			{
				return $official;
			}
			elseif (substr($official, 0, 6) === 'image/')
			{
				if ($return = $this->image())
				{
					return $return;
				}
				else
				{
					return $official;
				}
			}
			elseif ($official === 'text/html')
			{
				return $this->feed_or_html();
			}
			else
			{
				return $official;
			}
		}
		else
		{
			return $this->unknown();
		}
	}

	/**
	 * Sniff text or binary
	 *
	 * @return string Actual Content-Type
	 */
	public function text_or_binary()
	{
		if (substr($this->file->body, 0, 2) === "\xFE\xFF"
			|| substr($this->file->body, 0, 2) === "\xFF\xFE"
			|| substr($this->file->body, 0, 4) === "\x00\x00\xFE\xFF"
			|| substr($this->file->body, 0, 3) === "\xEF\xBB\xBF")
		{
			return 'text/plain';
		}
		elseif (preg_match('/[\x00-\x08\x0E-\x1A\x1C-\x1F]/', $this->file->body))
		{
			return 'application/octect-stream';
		}
		else
		{
			return 'text/plain';
		}
	}

	/**
	 * Sniff unknown
	 *
	 * @return string Actual Content-Type
	 */
	public function unknown()
	{
		$ws = strspn($this->file->body, "\x09\x0A\x0B\x0C\x0D\x20");
		if (strtolower(substr($this->file->body, $ws, 14)) === '<!doctype html'
			|| strtolower(substr($this->file->body, $ws, 5)) === '<html'
			|| strtolower(substr($this->file->body, $ws, 7)) === '<script')
		{
			return 'text/html';
		}
		elseif (substr($this->file->body, 0, 5) === '%PDF-')
		{
			return 'application/pdf';
		}
		elseif (substr($this->file->body, 0, 11) === '%!PS-Adobe-')
		{
			return 'application/postscript';
		}
		elseif (substr($this->file->body, 0, 6) === 'GIF87a'
			|| substr($this->file->body, 0, 6) === 'GIF89a')
		{
			return 'image/gif';
		}
		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
		{
			return 'image/png';
		}
		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
		{
			return 'image/jpeg';
		}
		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
		{
			return 'image/bmp';
		}
		elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00")
		{
			return 'image/vnd.microsoft.icon';
		}
		else
		{
			return $this->text_or_binary();
		}
	}

	/**
	 * Sniff images
	 *
	 * @return string Actual Content-Type
	 */
	public function image()
	{
		if (substr($this->file->body, 0, 6) === 'GIF87a'
			|| substr($this->file->body, 0, 6) === 'GIF89a')
		{
			return 'image/gif';
		}
		elseif (substr($this->file->body, 0, 8) === "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A")
		{
			return 'image/png';
		}
		elseif (substr($this->file->body, 0, 3) === "\xFF\xD8\xFF")
		{
			return 'image/jpeg';
		}
		elseif (substr($this->file->body, 0, 2) === "\x42\x4D")
		{
			return 'image/bmp';
		}
		elseif (substr($this->file->body, 0, 4) === "\x00\x00\x01\x00")
		{
			return 'image/vnd.microsoft.icon';
		}
		else
		{
			return false;
		}
	}

	/**
	 * Sniff HTML
	 *
	 * @return string Actual Content-Type
	 */
	public function feed_or_html()
	{
		$len = strlen($this->file->body);
		$pos = strspn($this->file->body, "\x09\x0A\x0D\x20");

		while ($pos < $len)
		{
			switch ($this->file->body[$pos])
			{
				case "\x09":
				case "\x0A":
				case "\x0D":
				case "\x20":
					$pos += strspn($this->file->body, "\x09\x0A\x0D\x20", $pos);
					continue 2;

				case '<':
					$pos++;
					break;

				default:
					return 'text/html';
			}

			if (substr($this->file->body, $pos, 3) === '!--')
			{
				$pos += 3;
				if ($pos < $len && ($pos = strpos($this->file->body, '-->', $pos)) !== false)
				{
					$pos += 3;
				}
				else
				{
					return 'text/html';
				}
			}
			elseif (substr($this->file->body, $pos, 1) === '!')
			{
				if ($pos < $len && ($pos = strpos($this->file->body, '>', $pos)) !== false)
				{
					$pos++;
				}
				else
				{
					return 'text/html';
				}
			}
			elseif (substr($this->file->body, $pos, 1) === '?')
			{
				if ($pos < $len && ($pos = strpos($this->file->body, '?>', $pos)) !== false)
				{
					$pos += 2;
				}
				else
				{
					return 'text/html';
				}
			}
			elseif (substr($this->file->body, $pos, 3) === 'rss'
				|| substr($this->file->body, $pos, 7) === 'rdf:RDF')
			{
				return 'application/rss+xml';
			}
			elseif (substr($this->file->body, $pos, 4) === 'feed')
			{
				return 'application/atom+xml';
			}
			else
			{
				return 'text/html';
			}
		}

		return 'text/html';
	}
}

vendor/simplepie/simplepie/library/SimplePie/Cache/Base.php000064400000006574152177723700017767 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Base for cache objects
 *
 * Classes to be used with {@see SimplePie_Cache::register()} are expected
 * to implement this interface.
 *
 * @package SimplePie
 * @subpackage Caching
 */
interface SimplePie_Cache_Base
{
	/**
	 * Feed cache type
	 *
	 * @var string
	 */
	const TYPE_FEED = 'spc';

	/**
	 * Image cache type
	 *
	 * @var string
	 */
	const TYPE_IMAGE = 'spi';

	/**
	 * Create a new cache object
	 *
	 * @param string $location Location string (from SimplePie::$cache_location)
	 * @param string $name Unique ID for the cache
	 * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
	 */
	public function __construct($location, $name, $type);

	/**
	 * Save data to the cache
	 *
	 * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
	 * @return bool Successfulness
	 */
	public function save($data);

	/**
	 * Retrieve the data saved to the cache
	 *
	 * @return array Data for SimplePie::$data
	 */
	public function load();

	/**
	 * Retrieve the last modified time for the cache
	 *
	 * @return int Timestamp
	 */
	public function mtime();

	/**
	 * Set the last modified time to the current time
	 *
	 * @return bool Success status
	 */
	public function touch();

	/**
	 * Remove the cache
	 *
	 * @return bool Success status
	 */
	public function unlink();
}
vendor/simplepie/simplepie/library/SimplePie/Cache/Memcache.php000064400000012025152177723700020603 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Caches data to memcache
 *
 * Registered for URLs with the "memcache" protocol
 *
 * For example, `memcache://localhost:11211/?timeout=3600&prefix=sp_` will
 * connect to memcache on `localhost` on port 11211. All tables will be
 * prefixed with `sp_` and data will expire after 3600 seconds
 *
 * @package SimplePie
 * @subpackage Caching
 * @uses Memcache
 */
class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
{
	/**
	 * Memcache instance
	 *
	 * @var Memcache
	 */
	protected $cache;

	/**
	 * Options
	 *
	 * @var array
	 */
	protected $options;

	/**
	 * Cache name
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * Create a new cache object
	 *
	 * @param string $location Location string (from SimplePie::$cache_location)
	 * @param string $name Unique ID for the cache
	 * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
	 */
	public function __construct($location, $name, $type)
	{
		$this->options = array(
			'host' => '127.0.0.1',
			'port' => 11211,
			'extras' => array(
				'timeout' => 3600, // one hour
				'prefix' => 'simplepie_',
			),
		);
		$parsed = SimplePie_Cache::parse_URL($location);
		$this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host'];
		$this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port'];
		$this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']);
		$this->name = $this->options['extras']['prefix'] . md5("$name:$type");

		$this->cache = new Memcache();
		$this->cache->addServer($this->options['host'], (int) $this->options['port']);
	}

	/**
	 * Save data to the cache
	 *
	 * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
	 * @return bool Successfulness
	 */
	public function save($data)
	{
		if ($data instanceof SimplePie)
		{
			$data = $data->data;
		}
		return $this->cache->set($this->name, serialize($data), MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']);
	}

	/**
	 * Retrieve the data saved to the cache
	 *
	 * @return array Data for SimplePie::$data
	 */
	public function load()
	{
		$data = $this->cache->get($this->name);

		if ($data !== false)
		{
			return unserialize($data);
		}
		return false;
	}

	/**
	 * Retrieve the last modified time for the cache
	 *
	 * @return int Timestamp
	 */
	public function mtime()
	{
		$data = $this->cache->get($this->name);

		if ($data !== false)
		{
			// essentially ignore the mtime because Memcache expires on it's own
			return time();
		}

		return false;
	}

	/**
	 * Set the last modified time to the current time
	 *
	 * @return bool Success status
	 */
	public function touch()
	{
		$data = $this->cache->get($this->name);

		if ($data !== false)
		{
			return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration);
		}

		return false;
	}

	/**
	 * Remove the cache
	 *
	 * @return bool Success status
	 */
	public function unlink()
	{
		return $this->cache->delete($this->name, 0);
	}
}
vendor/simplepie/simplepie/library/SimplePie/Cache/DB.php000064400000011166152177723700017373 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Base class for database-based caches
 *
 * @package SimplePie
 * @subpackage Caching
 */
abstract class SimplePie_Cache_DB implements SimplePie_Cache_Base
{
	/**
	 * Helper for database conversion
	 *
	 * Converts a given {@see SimplePie} object into data to be stored
	 *
	 * @param SimplePie $data
	 * @return array First item is the serialized data for storage, second item is the unique ID for this item
	 */
	protected static function prepare_simplepie_object_for_cache($data)
	{
		$items = $data->get_items();
		$items_by_id = array();

		if (!empty($items))
		{
			foreach ($items as $item)
			{
				$items_by_id[$item->get_id()] = $item;
			}

			if (count($items_by_id) !== count($items))
			{
				$items_by_id = array();
				foreach ($items as $item)
				{
					$items_by_id[$item->get_id(true)] = $item;
				}
			}

			if (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
			{
				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
			}
			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
			{
				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
			}
			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
			{
				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
			}
			elseif (isset($data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0]))
			{
				$channel =& $data->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['channel'][0];
			}
			else
			{
				$channel = null;
			}

			if ($channel !== null)
			{
				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']))
				{
					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry']);
				}
				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']))
				{
					unset($channel['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['entry']);
				}
				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']))
				{
					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item']);
				}
				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']))
				{
					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item']);
				}
				if (isset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']))
				{
					unset($channel['child'][SIMPLEPIE_NAMESPACE_RSS_20]['item']);
				}
			}
			if (isset($data->data['items']))
			{
				unset($data->data['items']);
			}
			if (isset($data->data['ordered_items']))
			{
				unset($data->data['ordered_items']);
			}
		}
		return array(serialize($data->data), $items_by_id);
	}
}
vendor/simplepie/simplepie/library/SimplePie/Cache/MySQL.php000064400000027770152177723700020063 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Caches data to a MySQL database
 *
 * Registered for URLs with the "mysql" protocol
 *
 * For example, `mysql://root:password@localhost:3306/mydb?prefix=sp_` will
 * connect to the `mydb` database on `localhost` on port 3306, with the user
 * `root` and the password `password`. All tables will be prefixed with `sp_`
 *
 * @package SimplePie
 * @subpackage Caching
 */
class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
{
	/**
	 * PDO instance
	 *
	 * @var PDO
	 */
	protected $mysql;

	/**
	 * Options
	 *
	 * @var array
	 */
	protected $options;

	/**
	 * Cache ID
	 *
	 * @var string
	 */
	protected $id;

	/**
	 * Create a new cache object
	 *
	 * @param string $location Location string (from SimplePie::$cache_location)
	 * @param string $name Unique ID for the cache
	 * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
	 */
	public function __construct($location, $name, $type)
	{
		$this->options = array(
			'user' => null,
			'pass' => null,
			'host' => '127.0.0.1',
			'port' => '3306',
			'path' => '',
			'extras' => array(
				'prefix' => '',
			),
		);
		$this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));

		// Path is prefixed with a "/"
		$this->options['dbname'] = substr($this->options['path'], 1);

		try
		{
			$this->mysql = new PDO("mysql:dbname={$this->options['dbname']};host={$this->options['host']};port={$this->options['port']}", $this->options['user'], $this->options['pass'], array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
		}
		catch (PDOException $e)
		{
			$this->mysql = null;
			return;
		}

		$this->id = $name . $type;

		if (!$query = $this->mysql->query('SHOW TABLES'))
		{
			$this->mysql = null;
			return;
		}

		$db = array();
		while ($row = $query->fetchColumn())
		{
			$db[] = $row;
		}

		if (!in_array($this->options['extras']['prefix'] . 'cache_data', $db))
		{
			$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'cache_data` (`id` TEXT CHARACTER SET utf8 NOT NULL, `items` SMALLINT NOT NULL DEFAULT 0, `data` BLOB NOT NULL, `mtime` INT UNSIGNED NOT NULL, UNIQUE (`id`(125)))');
			if ($query === false)
			{
				$this->mysql = null;
			}
		}

		if (!in_array($this->options['extras']['prefix'] . 'items', $db))
		{
			$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
			if ($query === false)
			{
				$this->mysql = null;
			}
		}
	}

	/**
	 * Save data to the cache
	 *
	 * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
	 * @return bool Successfulness
	 */
	public function save($data)
	{
		if ($this->mysql === null)
		{
			return false;
		}

		if ($data instanceof SimplePie)
		{
			$data = clone $data;

			$prepared = self::prepare_simplepie_object_for_cache($data);

			$query = $this->mysql->prepare('SELECT COUNT(*) FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
			$query->bindValue(':feed', $this->id);
			if ($query->execute())
			{
				if ($query->fetchColumn() > 0)
				{
					$items = count($prepared[1]);
					if ($items)
					{
						$sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = :items, `data` = :data, `mtime` = :time WHERE `id` = :feed';
						$query = $this->mysql->prepare($sql);
						$query->bindValue(':items', $items);
					}
					else
					{
						$sql = 'UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `data` = :data, `mtime` = :time WHERE `id` = :feed';
						$query = $this->mysql->prepare($sql);
					}

					$query->bindValue(':data', $prepared[0]);
					$query->bindValue(':time', time());
					$query->bindValue(':feed', $this->id);
					if (!$query->execute())
					{
						return false;
					}
				}
				else
				{
					$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:feed, :count, :data, :time)');
					$query->bindValue(':feed', $this->id);
					$query->bindValue(':count', count($prepared[1]));
					$query->bindValue(':data', $prepared[0]);
					$query->bindValue(':time', time());
					if (!$query->execute())
					{
						return false;
					}
				}

				$ids = array_keys($prepared[1]);
				if (!empty($ids))
				{
					foreach ($ids as $id)
					{
						$database_ids[] = $this->mysql->quote($id);
					}

					$query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `id` = ' . implode(' OR `id` = ', $database_ids) . ' AND `feed_id` = :feed');
					$query->bindValue(':feed', $this->id);

					if ($query->execute())
					{
						$existing_ids = array();
						while ($row = $query->fetchColumn())
						{
							$existing_ids[] = $row;
						}

						$new_ids = array_diff($ids, $existing_ids);

						foreach ($new_ids as $new_id)
						{
							if (!($date = $prepared[1][$new_id]->get_date('U')))
							{
								$date = time();
							}

							$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'items` (`feed_id`, `id`, `data`, `posted`) VALUES(:feed, :id, :data, :date)');
							$query->bindValue(':feed', $this->id);
							$query->bindValue(':id', $new_id);
							$query->bindValue(':data', serialize($prepared[1][$new_id]->data));
							$query->bindValue(':date', $date);
							if (!$query->execute())
							{
								return false;
							}
						}
						return true;
					}
				}
				else
				{
					return true;
				}
			}
		}
		else
		{
			$query = $this->mysql->prepare('SELECT `id` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :feed');
			$query->bindValue(':feed', $this->id);
			if ($query->execute())
			{
				if ($query->rowCount() > 0)
				{
					$query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `items` = 0, `data` = :data, `mtime` = :time WHERE `id` = :feed');
					$query->bindValue(':data', serialize($data));
					$query->bindValue(':time', time());
					$query->bindValue(':feed', $this->id);
					if ($this->execute())
					{
						return true;
					}
				}
				else
				{
					$query = $this->mysql->prepare('INSERT INTO `' . $this->options['extras']['prefix'] . 'cache_data` (`id`, `items`, `data`, `mtime`) VALUES(:id, 0, :data, :time)');
					$query->bindValue(':id', $this->id);
					$query->bindValue(':data', serialize($data));
					$query->bindValue(':time', time());
					if ($query->execute())
					{
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Retrieve the data saved to the cache
	 *
	 * @return array Data for SimplePie::$data
	 */
	public function load()
	{
		if ($this->mysql === null)
		{
			return false;
		}

		$query = $this->mysql->prepare('SELECT `items`, `data` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
		$query->bindValue(':id', $this->id);
		if ($query->execute() && ($row = $query->fetch()))
		{
			$data = unserialize($row[1]);

			if (isset($this->options['items'][0]))
			{
				$items = (int) $this->options['items'][0];
			}
			else
			{
				$items = (int) $row[0];
			}

			if ($items !== 0)
			{
				if (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]))
				{
					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0];
				}
				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]))
				{
					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0];
				}
				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]))
				{
					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0];
				}
				elseif (isset($data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]))
				{
					$feed =& $data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0];
				}
				else
				{
					$feed = null;
				}

				if ($feed !== null)
				{
					$sql = 'SELECT `data` FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :feed ORDER BY `posted` DESC';
					if ($items > 0)
					{
						$sql .= ' LIMIT ' . $items;
					}

					$query = $this->mysql->prepare($sql);
					$query->bindValue(':feed', $this->id);
					if ($query->execute())
					{
						while ($row = $query->fetchColumn())
						{
							$feed['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['entry'][] = unserialize($row);
						}
					}
					else
					{
						return false;
					}
				}
			}
			return $data;
		}
		return false;
	}

	/**
	 * Retrieve the last modified time for the cache
	 *
	 * @return int Timestamp
	 */
	public function mtime()
	{
		if ($this->mysql === null)
		{
			return false;
		}

		$query = $this->mysql->prepare('SELECT `mtime` FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
		$query->bindValue(':id', $this->id);
		if ($query->execute() && ($time = $query->fetchColumn()))
		{
			return $time;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Set the last modified time to the current time
	 *
	 * @return bool Success status
	 */
	public function touch()
	{
		if ($this->mysql === null)
		{
			return false;
		}

		$query = $this->mysql->prepare('UPDATE `' . $this->options['extras']['prefix'] . 'cache_data` SET `mtime` = :time WHERE `id` = :id');
		$query->bindValue(':time', time());
		$query->bindValue(':id', $this->id);
		if ($query->execute() && $query->rowCount() > 0)
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	/**
	 * Remove the cache
	 *
	 * @return bool Success status
	 */
	public function unlink()
	{
		if ($this->mysql === null)
		{
			return false;
		}

		$query = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'cache_data` WHERE `id` = :id');
		$query->bindValue(':id', $this->id);
		$query2 = $this->mysql->prepare('DELETE FROM `' . $this->options['extras']['prefix'] . 'items` WHERE `feed_id` = :id');
		$query2->bindValue(':id', $this->id);
		if ($query->execute() && $query2->execute())
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie/Cache/File.php000064400000010510152177723700017755 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Caches data to the filesystem
 *
 * @package SimplePie
 * @subpackage Caching
 */
class SimplePie_Cache_File implements SimplePie_Cache_Base
{
	/**
	 * Location string
	 *
	 * @see SimplePie::$cache_location
	 * @var string
	 */
	protected $location;

	/**
	 * Filename
	 *
	 * @var string
	 */
	protected $filename;

	/**
	 * File extension
	 *
	 * @var string
	 */
	protected $extension;

	/**
	 * File path
	 *
	 * @var string
	 */
	protected $name;

	/**
	 * Create a new cache object
	 *
	 * @param string $location Location string (from SimplePie::$cache_location)
	 * @param string $name Unique ID for the cache
	 * @param string $type Either TYPE_FEED for SimplePie data, or TYPE_IMAGE for image data
	 */
	public function __construct($location, $name, $type)
	{
		$this->location = $location;
		$this->filename = $name;
		$this->extension = $type;
		$this->name = "$this->location/$this->filename.$this->extension";
	}

	/**
	 * Save data to the cache
	 *
	 * @param array|SimplePie $data Data to store in the cache. If passed a SimplePie object, only cache the $data property
	 * @return bool Successfulness
	 */
	public function save($data)
	{
		if (file_exists($this->name) && is_writeable($this->name) || file_exists($this->location) && is_writeable($this->location))
		{
			if ($data instanceof SimplePie)
			{
				$data = $data->data;
			}

			$data = serialize($data);
			return (bool) file_put_contents($this->name, $data);
		}
		return false;
	}

	/**
	 * Retrieve the data saved to the cache
	 *
	 * @return array Data for SimplePie::$data
	 */
	public function load()
	{
		if (file_exists($this->name) && is_readable($this->name))
		{
			return unserialize(file_get_contents($this->name));
		}
		return false;
	}

	/**
	 * Retrieve the last modified time for the cache
	 *
	 * @return int Timestamp
	 */
	public function mtime()
	{
		if (file_exists($this->name))
		{
			return filemtime($this->name);
		}
		return false;
	}

	/**
	 * Set the last modified time to the current time
	 *
	 * @return bool Success status
	 */
	public function touch()
	{
		if (file_exists($this->name))
		{
			return touch($this->name);
		}
		return false;
	}

	/**
	 * Remove the cache
	 *
	 * @return bool Success status
	 */
	public function unlink()
	{
		if (file_exists($this->name))
		{
			return unlink($this->name);
		}
		return false;
	}
}
vendor/simplepie/simplepie/library/SimplePie/Copyright.php000064400000006447152177723700020061 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Manages `<media:copyright>` copyright tags as defined in Media RSS
 *
 * Used by {@see SimplePie_Enclosure::get_copyright()}
 *
 * This class can be overloaded with {@see SimplePie::set_copyright_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Copyright
{
	/**
	 * Copyright URL
	 *
	 * @var string
	 * @see get_url()
	 */
	var $url;

	/**
	 * Attribution
	 *
	 * @var string
	 * @see get_attribution()
	 */
	var $label;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 */
	public function __construct($url = null, $label = null)
	{
		$this->url = $url;
		$this->label = $label;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the copyright URL
	 *
	 * @return string|null URL to copyright information
	 */
	public function get_url()
	{
		if ($this->url !== null)
		{
			return $this->url;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the attribution text
	 *
	 * @return string|null
	 */
	public function get_attribution()
	{
		if ($this->label !== null)
		{
			return $this->label;
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/Exception.php000064400000004213152177723700020034 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.4-dev
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * General SimplePie exception class
 *
 * @package SimplePie
 */
class SimplePie_Exception extends Exception
{
}vendor/simplepie/simplepie/library/SimplePie/Credit.php000064400000007211152177723700017311 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * Handles `<media:credit>` as defined in Media RSS
 *
 * Used by {@see SimplePie_Enclosure::get_credit()} and {@see SimplePie_Enclosure::get_credits()}
 *
 * This class can be overloaded with {@see SimplePie::set_credit_class()}
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Credit
{
	/**
	 * Credited role
	 *
	 * @var string
	 * @see get_role()
	 */
	var $role;

	/**
	 * Organizational scheme
	 *
	 * @var string
	 * @see get_scheme()
	 */
	var $scheme;

	/**
	 * Credited name
	 *
	 * @var string
	 * @see get_name()
	 */
	var $name;

	/**
	 * Constructor, used to input the data
	 *
	 * For documentation on all the parameters, see the corresponding
	 * properties and their accessors
	 */
	public function __construct($role = null, $scheme = null, $name = null)
	{
		$this->role = $role;
		$this->scheme = $scheme;
		$this->name = $name;
	}

	/**
	 * String-ified version
	 *
	 * @return string
	 */
	public function __toString()
	{
		// There is no $this->data here
		return md5(serialize($this));
	}

	/**
	 * Get the role of the person receiving credit
	 *
	 * @return string|null
	 */
	public function get_role()
	{
		if ($this->role !== null)
		{
			return $this->role;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the organizational scheme
	 *
	 * @return string|null
	 */
	public function get_scheme()
	{
		if ($this->scheme !== null)
		{
			return $this->scheme;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the credited person/entity's name
	 *
	 * @return string|null
	 */
	public function get_name()
	{
		if ($this->name !== null)
		{
			return $this->name;
		}
		else
		{
			return null;
		}
	}
}

vendor/simplepie/simplepie/library/SimplePie/gzdecode.php000064400000020574152177723700017672 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


/**
 * Decode 'gzip' encoded HTTP data
 *
 * @package SimplePie
 * @subpackage HTTP
 * @link http://www.gzip.org/format.txt
 */
class SimplePie_gzdecode
{
	/**
	 * Compressed data
	 *
	 * @access private
	 * @var string
	 * @see gzdecode::$data
	 */
	var $compressed_data;

	/**
	 * Size of compressed data
	 *
	 * @access private
	 * @var int
	 */
	var $compressed_size;

	/**
	 * Minimum size of a valid gzip string
	 *
	 * @access private
	 * @var int
	 */
	var $min_compressed_size = 18;

	/**
	 * Current position of pointer
	 *
	 * @access private
	 * @var int
	 */
	var $position = 0;

	/**
	 * Flags (FLG)
	 *
	 * @access private
	 * @var int
	 */
	var $flags;

	/**
	 * Uncompressed data
	 *
	 * @access public
	 * @see gzdecode::$compressed_data
	 * @var string
	 */
	var $data;

	/**
	 * Modified time
	 *
	 * @access public
	 * @var int
	 */
	var $MTIME;

	/**
	 * Extra Flags
	 *
	 * @access public
	 * @var int
	 */
	var $XFL;

	/**
	 * Operating System
	 *
	 * @access public
	 * @var int
	 */
	var $OS;

	/**
	 * Subfield ID 1
	 *
	 * @access public
	 * @see gzdecode::$extra_field
	 * @see gzdecode::$SI2
	 * @var string
	 */
	var $SI1;

	/**
	 * Subfield ID 2
	 *
	 * @access public
	 * @see gzdecode::$extra_field
	 * @see gzdecode::$SI1
	 * @var string
	 */
	var $SI2;

	/**
	 * Extra field content
	 *
	 * @access public
	 * @see gzdecode::$SI1
	 * @see gzdecode::$SI2
	 * @var string
	 */
	var $extra_field;

	/**
	 * Original filename
	 *
	 * @access public
	 * @var string
	 */
	var $filename;

	/**
	 * Human readable comment
	 *
	 * @access public
	 * @var string
	 */
	var $comment;

	/**
	 * Don't allow anything to be set
	 *
	 * @param string $name
	 * @param mixed $value
	 */
	public function __set($name, $value)
	{
		trigger_error("Cannot write property $name", E_USER_ERROR);
	}

	/**
	 * Set the compressed string and related properties
	 *
	 * @param string $data
	 */
	public function __construct($data)
	{
		$this->compressed_data = $data;
		$this->compressed_size = strlen($data);
	}

	/**
	 * Decode the GZIP stream
	 *
	 * @return bool Successfulness
	 */
	public function parse()
	{
		if ($this->compressed_size >= $this->min_compressed_size)
		{
			// Check ID1, ID2, and CM
			if (substr($this->compressed_data, 0, 3) !== "\x1F\x8B\x08")
			{
				return false;
			}

			// Get the FLG (FLaGs)
			$this->flags = ord($this->compressed_data[3]);

			// FLG bits above (1 << 4) are reserved
			if ($this->flags > 0x1F)
			{
				return false;
			}

			// Advance the pointer after the above
			$this->position += 4;

			// MTIME
			$mtime = substr($this->compressed_data, $this->position, 4);
			// Reverse the string if we're on a big-endian arch because l is the only signed long and is machine endianness
			if (current(unpack('S', "\x00\x01")) === 1)
			{
				$mtime = strrev($mtime);
			}
			$this->MTIME = current(unpack('l', $mtime));
			$this->position += 4;

			// Get the XFL (eXtra FLags)
			$this->XFL = ord($this->compressed_data[$this->position++]);

			// Get the OS (Operating System)
			$this->OS = ord($this->compressed_data[$this->position++]);

			// Parse the FEXTRA
			if ($this->flags & 4)
			{
				// Read subfield IDs
				$this->SI1 = $this->compressed_data[$this->position++];
				$this->SI2 = $this->compressed_data[$this->position++];

				// SI2 set to zero is reserved for future use
				if ($this->SI2 === "\x00")
				{
					return false;
				}

				// Get the length of the extra field
				$len = current(unpack('v', substr($this->compressed_data, $this->position, 2)));
				$this->position += 2;

				// Check the length of the string is still valid
				$this->min_compressed_size += $len + 4;
				if ($this->compressed_size >= $this->min_compressed_size)
				{
					// Set the extra field to the given data
					$this->extra_field = substr($this->compressed_data, $this->position, $len);
					$this->position += $len;
				}
				else
				{
					return false;
				}
			}

			// Parse the FNAME
			if ($this->flags & 8)
			{
				// Get the length of the filename
				$len = strcspn($this->compressed_data, "\x00", $this->position);

				// Check the length of the string is still valid
				$this->min_compressed_size += $len + 1;
				if ($this->compressed_size >= $this->min_compressed_size)
				{
					// Set the original filename to the given string
					$this->filename = substr($this->compressed_data, $this->position, $len);
					$this->position += $len + 1;
				}
				else
				{
					return false;
				}
			}

			// Parse the FCOMMENT
			if ($this->flags & 16)
			{
				// Get the length of the comment
				$len = strcspn($this->compressed_data, "\x00", $this->position);

				// Check the length of the string is still valid
				$this->min_compressed_size += $len + 1;
				if ($this->compressed_size >= $this->min_compressed_size)
				{
					// Set the original comment to the given string
					$this->comment = substr($this->compressed_data, $this->position, $len);
					$this->position += $len + 1;
				}
				else
				{
					return false;
				}
			}

			// Parse the FHCRC
			if ($this->flags & 2)
			{
				// Check the length of the string is still valid
				$this->min_compressed_size += $len + 2;
				if ($this->compressed_size >= $this->min_compressed_size)
				{
					// Read the CRC
					$crc = current(unpack('v', substr($this->compressed_data, $this->position, 2)));

					// Check the CRC matches
					if ((crc32(substr($this->compressed_data, 0, $this->position)) & 0xFFFF) === $crc)
					{
						$this->position += 2;
					}
					else
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}

			// Decompress the actual data
			if (($this->data = gzinflate(substr($this->compressed_data, $this->position, -8))) === false)
			{
				return false;
			}
			else
			{
				$this->position = $this->compressed_size - 8;
			}

			// Check CRC of data
			$crc = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
			$this->position += 4;
			/*if (extension_loaded('hash') && sprintf('%u', current(unpack('V', hash('crc32b', $this->data)))) !== sprintf('%u', $crc))
			{
				return false;
			}*/

			// Check ISIZE of data
			$isize = current(unpack('V', substr($this->compressed_data, $this->position, 4)));
			$this->position += 4;
			if (sprintf('%u', strlen($this->data) & 0xFFFFFFFF) !== sprintf('%u', $isize))
			{
				return false;
			}

			// Wow, against all odds, we've actually got a valid gzip string
			return true;
		}
		else
		{
			return false;
		}
	}
}
vendor/simplepie/simplepie/library/SimplePie.php000064400000254075152177723700016113 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2012, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */

/**
 * SimplePie Name
 */
define('SIMPLEPIE_NAME', 'SimplePie');

/**
 * SimplePie Version
 */
define('SIMPLEPIE_VERSION', '1.3.1');

/**
 * SimplePie Build
 * @todo Hardcode for release (there's no need to have to call SimplePie_Misc::get_build() only every load of simplepie.inc)
 */
define('SIMPLEPIE_BUILD', gmdate('YmdHis', SimplePie_Misc::get_build()));

/**
 * SimplePie Website URL
 */
define('SIMPLEPIE_URL', 'http://simplepie.org');

/**
 * SimplePie Useragent
 * @see SimplePie::set_useragent()
 */
define('SIMPLEPIE_USERAGENT', SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION . ' (Feed Parser; ' . SIMPLEPIE_URL . '; Allow like Gecko) Build/' . SIMPLEPIE_BUILD);

/**
 * SimplePie Linkback
 */
define('SIMPLEPIE_LINKBACK', '<a href="' . SIMPLEPIE_URL . '" title="' . SIMPLEPIE_NAME . ' ' . SIMPLEPIE_VERSION . '">' . SIMPLEPIE_NAME . '</a>');

/**
 * No Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_NONE', 0);

/**
 * Feed Link Element Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_AUTODISCOVERY', 1);

/**
 * Local Feed Extension Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_LOCAL_EXTENSION', 2);

/**
 * Local Feed Body Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_LOCAL_BODY', 4);

/**
 * Remote Feed Extension Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_REMOTE_EXTENSION', 8);

/**
 * Remote Feed Body Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_REMOTE_BODY', 16);

/**
 * All Feed Autodiscovery
 * @see SimplePie::set_autodiscovery_level()
 */
define('SIMPLEPIE_LOCATOR_ALL', 31);

/**
 * No known feed type
 */
define('SIMPLEPIE_TYPE_NONE', 0);

/**
 * RSS 0.90
 */
define('SIMPLEPIE_TYPE_RSS_090', 1);

/**
 * RSS 0.91 (Netscape)
 */
define('SIMPLEPIE_TYPE_RSS_091_NETSCAPE', 2);

/**
 * RSS 0.91 (Userland)
 */
define('SIMPLEPIE_TYPE_RSS_091_USERLAND', 4);

/**
 * RSS 0.91 (both Netscape and Userland)
 */
define('SIMPLEPIE_TYPE_RSS_091', 6);

/**
 * RSS 0.92
 */
define('SIMPLEPIE_TYPE_RSS_092', 8);

/**
 * RSS 0.93
 */
define('SIMPLEPIE_TYPE_RSS_093', 16);

/**
 * RSS 0.94
 */
define('SIMPLEPIE_TYPE_RSS_094', 32);

/**
 * RSS 1.0
 */
define('SIMPLEPIE_TYPE_RSS_10', 64);

/**
 * RSS 2.0
 */
define('SIMPLEPIE_TYPE_RSS_20', 128);

/**
 * RDF-based RSS
 */
define('SIMPLEPIE_TYPE_RSS_RDF', 65);

/**
 * Non-RDF-based RSS (truly intended as syndication format)
 */
define('SIMPLEPIE_TYPE_RSS_SYNDICATION', 190);

/**
 * All RSS
 */
define('SIMPLEPIE_TYPE_RSS_ALL', 255);

/**
 * Atom 0.3
 */
define('SIMPLEPIE_TYPE_ATOM_03', 256);

/**
 * Atom 1.0
 */
define('SIMPLEPIE_TYPE_ATOM_10', 512);

/**
 * All Atom
 */
define('SIMPLEPIE_TYPE_ATOM_ALL', 768);

/**
 * All feed types
 */
define('SIMPLEPIE_TYPE_ALL', 1023);

/**
 * No construct
 */
define('SIMPLEPIE_CONSTRUCT_NONE', 0);

/**
 * Text construct
 */
define('SIMPLEPIE_CONSTRUCT_TEXT', 1);

/**
 * HTML construct
 */
define('SIMPLEPIE_CONSTRUCT_HTML', 2);

/**
 * XHTML construct
 */
define('SIMPLEPIE_CONSTRUCT_XHTML', 4);

/**
 * base64-encoded construct
 */
define('SIMPLEPIE_CONSTRUCT_BASE64', 8);

/**
 * IRI construct
 */
define('SIMPLEPIE_CONSTRUCT_IRI', 16);

/**
 * A construct that might be HTML
 */
define('SIMPLEPIE_CONSTRUCT_MAYBE_HTML', 32);

/**
 * All constructs
 */
define('SIMPLEPIE_CONSTRUCT_ALL', 63);

/**
 * Don't change case
 */
define('SIMPLEPIE_SAME_CASE', 1);

/**
 * Change to lowercase
 */
define('SIMPLEPIE_LOWERCASE', 2);

/**
 * Change to uppercase
 */
define('SIMPLEPIE_UPPERCASE', 4);

/**
 * PCRE for HTML attributes
 */
define('SIMPLEPIE_PCRE_HTML_ATTRIBUTE', '((?:[\x09\x0A\x0B\x0C\x0D\x20]+[^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3D\x3E]*(?:[\x09\x0A\x0B\x0C\x0D\x20]*=[\x09\x0A\x0B\x0C\x0D\x20]*(?:"(?:[^"]*)"|\'(?:[^\']*)\'|(?:[^\x09\x0A\x0B\x0C\x0D\x20\x22\x27\x3E][^\x09\x0A\x0B\x0C\x0D\x20\x3E]*)?))?)*)[\x09\x0A\x0B\x0C\x0D\x20]*');

/**
 * PCRE for XML attributes
 */
define('SIMPLEPIE_PCRE_XML_ATTRIBUTE', '((?:\s+(?:(?:[^\s:]+:)?[^\s:]+)\s*=\s*(?:"(?:[^"]*)"|\'(?:[^\']*)\'))*)\s*');

/**
 * XML Namespace
 */
define('SIMPLEPIE_NAMESPACE_XML', 'http://www.w3.org/XML/1998/namespace');

/**
 * Atom 1.0 Namespace
 */
define('SIMPLEPIE_NAMESPACE_ATOM_10', 'http://www.w3.org/2005/Atom');

/**
 * Atom 0.3 Namespace
 */
define('SIMPLEPIE_NAMESPACE_ATOM_03', 'http://purl.org/atom/ns#');

/**
 * RDF Namespace
 */
define('SIMPLEPIE_NAMESPACE_RDF', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#');

/**
 * RSS 0.90 Namespace
 */
define('SIMPLEPIE_NAMESPACE_RSS_090', 'http://my.netscape.com/rdf/simple/0.9/');

/**
 * RSS 1.0 Namespace
 */
define('SIMPLEPIE_NAMESPACE_RSS_10', 'http://purl.org/rss/1.0/');

/**
 * RSS 1.0 Content Module Namespace
 */
define('SIMPLEPIE_NAMESPACE_RSS_10_MODULES_CONTENT', 'http://purl.org/rss/1.0/modules/content/');

/**
 * RSS 2.0 Namespace
 * (Stupid, I know, but I'm certain it will confuse people less with support.)
 */
define('SIMPLEPIE_NAMESPACE_RSS_20', '');

/**
 * DC 1.0 Namespace
 */
define('SIMPLEPIE_NAMESPACE_DC_10', 'http://purl.org/dc/elements/1.0/');

/**
 * DC 1.1 Namespace
 */
define('SIMPLEPIE_NAMESPACE_DC_11', 'http://purl.org/dc/elements/1.1/');

/**
 * W3C Basic Geo (WGS84 lat/long) Vocabulary Namespace
 */
define('SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO', 'http://www.w3.org/2003/01/geo/wgs84_pos#');

/**
 * GeoRSS Namespace
 */
define('SIMPLEPIE_NAMESPACE_GEORSS', 'http://www.georss.org/georss');

/**
 * Media RSS Namespace
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS', 'http://search.yahoo.com/mrss/');

/**
 * Wrong Media RSS Namespace. Caused by a long-standing typo in the spec.
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG', 'http://search.yahoo.com/mrss');

/**
 * Wrong Media RSS Namespace #2. New namespace introduced in Media RSS 1.5.
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG2', 'http://video.search.yahoo.com/mrss');

/**
 * Wrong Media RSS Namespace #3. A possible typo of the Media RSS 1.5 namespace.
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG3', 'http://video.search.yahoo.com/mrss/');

/**
 * Wrong Media RSS Namespace #4. New spec location after the RSS Advisory Board takes it over, but not a valid namespace.
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG4', 'http://www.rssboard.org/media-rss');

/**
 * Wrong Media RSS Namespace #5. A possible typo of the RSS Advisory Board URL.
 */
define('SIMPLEPIE_NAMESPACE_MEDIARSS_WRONG5', 'http://www.rssboard.org/media-rss/');

/**
 * iTunes RSS Namespace
 */
define('SIMPLEPIE_NAMESPACE_ITUNES', 'http://www.itunes.com/dtds/podcast-1.0.dtd');

/**
 * XHTML Namespace
 */
define('SIMPLEPIE_NAMESPACE_XHTML', 'http://www.w3.org/1999/xhtml');

/**
 * IANA Link Relations Registry
 */
define('SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY', 'http://www.iana.org/assignments/relation/');

/**
 * No file source
 */
define('SIMPLEPIE_FILE_SOURCE_NONE', 0);

/**
 * Remote file source
 */
define('SIMPLEPIE_FILE_SOURCE_REMOTE', 1);

/**
 * Local file source
 */
define('SIMPLEPIE_FILE_SOURCE_LOCAL', 2);

/**
 * fsockopen() file source
 */
define('SIMPLEPIE_FILE_SOURCE_FSOCKOPEN', 4);

/**
 * cURL file source
 */
define('SIMPLEPIE_FILE_SOURCE_CURL', 8);

/**
 * file_get_contents() file source
 */
define('SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS', 16);



/**
 * SimplePie
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie
{
	/**
	 * @var array Raw data
	 * @access private
	 */
	public $data = array();

	/**
	 * @var mixed Error string
	 * @access private
	 */
	public $error;

	/**
	 * @var object Instance of SimplePie_Sanitize (or other class)
	 * @see SimplePie::set_sanitize_class()
	 * @access private
	 */
	public $sanitize;

	/**
	 * @var string SimplePie Useragent
	 * @see SimplePie::set_useragent()
	 * @access private
	 */
	public $useragent = SIMPLEPIE_USERAGENT;

	/**
	 * @var string Feed URL
	 * @see SimplePie::set_feed_url()
	 * @access private
	 */
	public $feed_url;

	/**
	 * @var object Instance of SimplePie_File to use as a feed
	 * @see SimplePie::set_file()
	 * @access private
	 */
	public $file;

	/**
	 * @var string Raw feed data
	 * @see SimplePie::set_raw_data()
	 * @access private
	 */
	public $raw_data;

	/**
	 * @var int Timeout for fetching remote files
	 * @see SimplePie::set_timeout()
	 * @access private
	 */
	public $timeout = 10;

	/**
	 * @var bool Forces fsockopen() to be used for remote files instead
	 * of cURL, even if a new enough version is installed
	 * @see SimplePie::force_fsockopen()
	 * @access private
	 */
	public $force_fsockopen = false;

	/**
	 * @var bool Force the given data/URL to be treated as a feed no matter what
	 * it appears like
	 * @see SimplePie::force_feed()
	 * @access private
	 */
	public $force_feed = false;

	/**
	 * @var bool Enable/Disable Caching
	 * @see SimplePie::enable_cache()
	 * @access private
	 */
	public $cache = true;

	/**
	 * @var int Cache duration (in seconds)
	 * @see SimplePie::set_cache_duration()
	 * @access private
	 */
	public $cache_duration = 3600;

	/**
	 * @var int Auto-discovery cache duration (in seconds)
	 * @see SimplePie::set_autodiscovery_cache_duration()
	 * @access private
	 */
	public $autodiscovery_cache_duration = 604800; // 7 Days.

	/**
	 * @var string Cache location (relative to executing script)
	 * @see SimplePie::set_cache_location()
	 * @access private
	 */
	public $cache_location = './cache';

	/**
	 * @var string Function that creates the cache filename
	 * @see SimplePie::set_cache_name_function()
	 * @access private
	 */
	public $cache_name_function = 'md5';

	/**
	 * @var bool Reorder feed by date descending
	 * @see SimplePie::enable_order_by_date()
	 * @access private
	 */
	public $order_by_date = true;

	/**
	 * @var mixed Force input encoding to be set to the follow value
	 * (false, or anything type-cast to false, disables this feature)
	 * @see SimplePie::set_input_encoding()
	 * @access private
	 */
	public $input_encoding = false;

	/**
	 * @var int Feed Autodiscovery Level
	 * @see SimplePie::set_autodiscovery_level()
	 * @access private
	 */
	public $autodiscovery = SIMPLEPIE_LOCATOR_ALL;

	/**
	 * Class registry object
	 *
	 * @var SimplePie_Registry
	 */
	public $registry;

	/**
	 * @var int Maximum number of feeds to check with autodiscovery
	 * @see SimplePie::set_max_checked_feeds()
	 * @access private
	 */
	public $max_checked_feeds = 10;

	/**
	 * @var array All the feeds found during the autodiscovery process
	 * @see SimplePie::get_all_discovered_feeds()
	 * @access private
	 */
	public $all_discovered_feeds = array();

	/**
	 * @var string Web-accessible path to the handler_image.php file.
	 * @see SimplePie::set_image_handler()
	 * @access private
	 */
	public $image_handler = '';

	/**
	 * @var array Stores the URLs when multiple feeds are being initialized.
	 * @see SimplePie::set_feed_url()
	 * @access private
	 */
	public $multifeed_url = array();

	/**
	 * @var array Stores SimplePie objects when multiple feeds initialized.
	 * @access private
	 */
	public $multifeed_objects = array();

	/**
	 * @var array Stores the get_object_vars() array for use with multifeeds.
	 * @see SimplePie::set_feed_url()
	 * @access private
	 */
	public $config_settings = null;

	/**
	 * @var integer Stores the number of items to return per-feed with multifeeds.
	 * @see SimplePie::set_item_limit()
	 * @access private
	 */
	public $item_limit = 0;

	/**
	 * @var array Stores the default attributes to be stripped by strip_attributes().
	 * @see SimplePie::strip_attributes()
	 * @access private
	 */
	public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');

	/**
	 * @var array Stores the default tags to be stripped by strip_htmltags().
	 * @see SimplePie::strip_htmltags()
	 * @access private
	 */
	public $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style');

	/**
	 * The SimplePie class contains feed level data and options
	 *
	 * To use SimplePie, create the SimplePie object with no parameters. You can
	 * then set configuration options using the provided methods. After setting
	 * them, you must initialise the feed using $feed->init(). At that point the
	 * object's methods and properties will be available to you.
	 *
	 * Previously, it was possible to pass in the feed URL along with cache
	 * options directly into the constructor. This has been removed as of 1.3 as
	 * it caused a lot of confusion.
	 *
	 * @since 1.0 Preview Release
	 */
	public function __construct()
	{
		if (version_compare(PHP_VERSION, '5.2', '<'))
		{
			trigger_error('PHP 4.x, 5.0 and 5.1 are no longer supported. Please upgrade to PHP 5.2 or newer.');
			die();
		}

		// Other objects, instances created here so we can set options on them
		$this->sanitize = new SimplePie_Sanitize();
		$this->registry = new SimplePie_Registry();

		if (func_num_args() > 0)
		{
			$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
			trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level);

			$args = func_get_args();
			switch (count($args)) {
				case 3:
					$this->set_cache_duration($args[2]);
				case 2:
					$this->set_cache_location($args[1]);
				case 1:
					$this->set_feed_url($args[0]);
					$this->init();
			}
		}
	}

	/**
	 * Used for converting object to a string
	 */
	public function __toString()
	{
		return md5(serialize($this->data));
	}

	/**
	 * Remove items that link back to this before destroying this object
	 */
	public function __destruct()
	{
		if ((version_compare(PHP_VERSION, '5.3', '<') || !gc_enabled()) && !ini_get('zend.ze1_compatibility_mode'))
		{
			if (!empty($this->data['items']))
			{
				foreach ($this->data['items'] as $item)
				{
					$item->__destruct();
				}
				unset($item, $this->data['items']);
			}
			if (!empty($this->data['ordered_items']))
			{
				foreach ($this->data['ordered_items'] as $item)
				{
					$item->__destruct();
				}
				unset($item, $this->data['ordered_items']);
			}
		}
	}

	/**
	 * Force the given data/URL to be treated as a feed
	 *
	 * This tells SimplePie to ignore the content-type provided by the server.
	 * Be careful when using this option, as it will also disable autodiscovery.
	 *
	 * @since 1.1
	 * @param bool $enable Force the given data/URL to be treated as a feed
	 */
	public function force_feed($enable = false)
	{
		$this->force_feed = (bool) $enable;
	}

	/**
	 * Set the URL of the feed you want to parse
	 *
	 * This allows you to enter the URL of the feed you want to parse, or the
	 * website you want to try to use auto-discovery on. This takes priority
	 * over any set raw data.
	 *
	 * You can set multiple feeds to mash together by passing an array instead
	 * of a string for the $url. Remember that with each additional feed comes
	 * additional processing and resources.
	 *
	 * @since 1.0 Preview Release
	 * @see set_raw_data()
	 * @param string|array $url This is the URL (or array of URLs) that you want to parse.
	 */
	public function set_feed_url($url)
	{
		$this->multifeed_url = array();
		if (is_array($url))
		{
			foreach ($url as $value)
			{
				$this->multifeed_url[] = $this->registry->call('Misc', 'fix_protocol', array($value, 1));
			}
		}
		else
		{
			$this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1));
		}
	}

	/**
	 * Set an instance of {@see SimplePie_File} to use as a feed
	 *
	 * @param SimplePie_File &$file
	 * @return bool True on success, false on failure
	 */
	public function set_file(&$file)
	{
		if ($file instanceof SimplePie_File)
		{
			$this->feed_url = $file->url;
			$this->file =& $file;
			return true;
		}
		return false;
	}

	/**
	 * Set the raw XML data to parse
	 *
	 * Allows you to use a string of RSS/Atom data instead of a remote feed.
	 *
	 * If you have a feed available as a string in PHP, you can tell SimplePie
	 * to parse that data string instead of a remote feed. Any set feed URL
	 * takes precedence.
	 *
	 * @since 1.0 Beta 3
	 * @param string $data RSS or Atom data as a string.
	 * @see set_feed_url()
	 */
	public function set_raw_data($data)
	{
		$this->raw_data = $data;
	}

	/**
	 * Set the the default timeout for fetching remote feeds
	 *
	 * This allows you to change the maximum time the feed's server to respond
	 * and send the feed back.
	 *
	 * @since 1.0 Beta 3
	 * @param int $timeout The maximum number of seconds to spend waiting to retrieve a feed.
	 */
	public function set_timeout($timeout = 10)
	{
		$this->timeout = (int) $timeout;
	}

	/**
	 * Force SimplePie to use fsockopen() instead of cURL
	 *
	 * @since 1.0 Beta 3
	 * @param bool $enable Force fsockopen() to be used
	 */
	public function force_fsockopen($enable = false)
	{
		$this->force_fsockopen = (bool) $enable;
	}

	/**
	 * Enable/disable caching in SimplePie.
	 *
	 * This option allows you to disable caching all-together in SimplePie.
	 * However, disabling the cache can lead to longer load times.
	 *
	 * @since 1.0 Preview Release
	 * @param bool $enable Enable caching
	 */
	public function enable_cache($enable = true)
	{
		$this->cache = (bool) $enable;
	}

	/**
	 * Set the length of time (in seconds) that the contents of a feed will be
	 * cached
	 *
	 * @param int $seconds The feed content cache duration
	 */
	public function set_cache_duration($seconds = 3600)
	{
		$this->cache_duration = (int) $seconds;
	}

	/**
	 * Set the length of time (in seconds) that the autodiscovered feed URL will
	 * be cached
	 *
	 * @param int $seconds The autodiscovered feed URL cache duration.
	 */
	public function set_autodiscovery_cache_duration($seconds = 604800)
	{
		$this->autodiscovery_cache_duration = (int) $seconds;
	}

	/**
	 * Set the file system location where the cached files should be stored
	 *
	 * @param string $location The file system location.
	 */
	public function set_cache_location($location = './cache')
	{
		$this->cache_location = (string) $location;
	}

	/**
	 * Set whether feed items should be sorted into reverse chronological order
	 *
	 * @param bool $enable Sort as reverse chronological order.
	 */
	public function enable_order_by_date($enable = true)
	{
		$this->order_by_date = (bool) $enable;
	}

	/**
	 * Set the character encoding used to parse the feed
	 *
	 * This overrides the encoding reported by the feed, however it will fall
	 * back to the normal encoding detection if the override fails
	 *
	 * @param string $encoding Character encoding
	 */
	public function set_input_encoding($encoding = false)
	{
		if ($encoding)
		{
			$this->input_encoding = (string) $encoding;
		}
		else
		{
			$this->input_encoding = false;
		}
	}

	/**
	 * Set how much feed autodiscovery to do
	 *
	 * @see SIMPLEPIE_LOCATOR_NONE
	 * @see SIMPLEPIE_LOCATOR_AUTODISCOVERY
	 * @see SIMPLEPIE_LOCATOR_LOCAL_EXTENSION
	 * @see SIMPLEPIE_LOCATOR_LOCAL_BODY
	 * @see SIMPLEPIE_LOCATOR_REMOTE_EXTENSION
	 * @see SIMPLEPIE_LOCATOR_REMOTE_BODY
	 * @see SIMPLEPIE_LOCATOR_ALL
	 * @param int $level Feed Autodiscovery Level (level can be a combination of the above constants, see bitwise OR operator)
	 */
	public function set_autodiscovery_level($level = SIMPLEPIE_LOCATOR_ALL)
	{
		$this->autodiscovery = (int) $level;
	}

	/**
	 * Get the class registry
	 *
	 * Use this to override SimplePie's default classes
	 * @see SimplePie_Registry
	 * @return SimplePie_Registry
	 */
	public function &get_registry()
	{
		return $this->registry;
	}

	/**#@+
	 * Useful when you are overloading or extending SimplePie's default classes.
	 *
	 * @deprecated Use {@see get_registry()} instead
	 * @link http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.extends PHP5 extends documentation
	 * @param string $class Name of custom class
	 * @return boolean True on success, false otherwise
	 */
	/**
	 * Set which class SimplePie uses for caching
	 */
	public function set_cache_class($class = 'SimplePie_Cache')
	{
		return $this->registry->register('Cache', $class, true);
	}

	/**
	 * Set which class SimplePie uses for auto-discovery
	 */
	public function set_locator_class($class = 'SimplePie_Locator')
	{
		return $this->registry->register('Locator', $class, true);
	}

	/**
	 * Set which class SimplePie uses for XML parsing
	 */
	public function set_parser_class($class = 'SimplePie_Parser')
	{
		return $this->registry->register('Parser', $class, true);
	}

	/**
	 * Set which class SimplePie uses for remote file fetching
	 */
	public function set_file_class($class = 'SimplePie_File')
	{
		return $this->registry->register('File', $class, true);
	}

	/**
	 * Set which class SimplePie uses for data sanitization
	 */
	public function set_sanitize_class($class = 'SimplePie_Sanitize')
	{
		return $this->registry->register('Sanitize', $class, true);
	}

	/**
	 * Set which class SimplePie uses for handling feed items
	 */
	public function set_item_class($class = 'SimplePie_Item')
	{
		return $this->registry->register('Item', $class, true);
	}

	/**
	 * Set which class SimplePie uses for handling author data
	 */
	public function set_author_class($class = 'SimplePie_Author')
	{
		return $this->registry->register('Author', $class, true);
	}

	/**
	 * Set which class SimplePie uses for handling category data
	 */
	public function set_category_class($class = 'SimplePie_Category')
	{
		return $this->registry->register('Category', $class, true);
	}

	/**
	 * Set which class SimplePie uses for feed enclosures
	 */
	public function set_enclosure_class($class = 'SimplePie_Enclosure')
	{
		return $this->registry->register('Enclosure', $class, true);
	}

	/**
	 * Set which class SimplePie uses for `<media:text>` captions
	 */
	public function set_caption_class($class = 'SimplePie_Caption')
	{
		return $this->registry->register('Caption', $class, true);
	}

	/**
	 * Set which class SimplePie uses for `<media:copyright>`
	 */
	public function set_copyright_class($class = 'SimplePie_Copyright')
	{
		return $this->registry->register('Copyright', $class, true);
	}

	/**
	 * Set which class SimplePie uses for `<media:credit>`
	 */
	public function set_credit_class($class = 'SimplePie_Credit')
	{
		return $this->registry->register('Credit', $class, true);
	}

	/**
	 * Set which class SimplePie uses for `<media:rating>`
	 */
	public function set_rating_class($class = 'SimplePie_Rating')
	{
		return $this->registry->register('Rating', $class, true);
	}

	/**
	 * Set which class SimplePie uses for `<media:restriction>`
	 */
	public function set_restriction_class($class = 'SimplePie_Restriction')
	{
		return $this->registry->register('Restriction', $class, true);
	}

	/**
	 * Set which class SimplePie uses for content-type sniffing
	 */
	public function set_content_type_sniffer_class($class = 'SimplePie_Content_Type_Sniffer')
	{
		return $this->registry->register('Content_Type_Sniffer', $class, true);
	}

	/**
	 * Set which class SimplePie uses item sources
	 */
	public function set_source_class($class = 'SimplePie_Source')
	{
		return $this->registry->register('Source', $class, true);
	}
	/**#@-*/

	/**
	 * Set the user agent string
	 *
	 * @param string $ua New user agent string.
	 */
	public function set_useragent($ua = SIMPLEPIE_USERAGENT)
	{
		$this->useragent = (string) $ua;
	}

	/**
	 * Set callback function to create cache filename with
	 *
	 * @param mixed $function Callback function
	 */
	public function set_cache_name_function($function = 'md5')
	{
		if (is_callable($function))
		{
			$this->cache_name_function = $function;
		}
	}

	/**
	 * Set options to make SP as fast as possible
	 *
	 * Forgoes a substantial amount of data sanitization in favor of speed. This
	 * turns SimplePie into a dumb parser of feeds.
	 *
	 * @param bool $set Whether to set them or not
	 */
	public function set_stupidly_fast($set = false)
	{
		if ($set)
		{
			$this->enable_order_by_date(false);
			$this->remove_div(false);
			$this->strip_comments(false);
			$this->strip_htmltags(false);
			$this->strip_attributes(false);
			$this->set_image_handler(false);
		}
	}

	/**
	 * Set maximum number of feeds to check with autodiscovery
	 *
	 * @param int $max Maximum number of feeds to check
	 */
	public function set_max_checked_feeds($max = 10)
	{
		$this->max_checked_feeds = (int) $max;
	}

	public function remove_div($enable = true)
	{
		$this->sanitize->remove_div($enable);
	}

	public function strip_htmltags($tags = '', $encode = null)
	{
		if ($tags === '')
		{
			$tags = $this->strip_htmltags;
		}
		$this->sanitize->strip_htmltags($tags);
		if ($encode !== null)
		{
			$this->sanitize->encode_instead_of_strip($tags);
		}
	}

	public function encode_instead_of_strip($enable = true)
	{
		$this->sanitize->encode_instead_of_strip($enable);
	}

	public function strip_attributes($attribs = '')
	{
		if ($attribs === '')
		{
			$attribs = $this->strip_attributes;
		}
		$this->sanitize->strip_attributes($attribs);
	}

	/**
	 * Set the output encoding
	 *
	 * Allows you to override SimplePie's output to match that of your webpage.
	 * This is useful for times when your webpages are not being served as
	 * UTF-8.  This setting will be obeyed by {@see handle_content_type()}, and
	 * is similar to {@see set_input_encoding()}.
	 *
	 * It should be noted, however, that not all character encodings can support
	 * all characters.  If your page is being served as ISO-8859-1 and you try
	 * to display a Japanese feed, you'll likely see garbled characters.
	 * Because of this, it is highly recommended to ensure that your webpages
	 * are served as UTF-8.
	 *
	 * The number of supported character encodings depends on whether your web
	 * host supports {@link http://php.net/mbstring mbstring},
	 * {@link http://php.net/iconv iconv}, or both. See
	 * {@link http://simplepie.org/wiki/faq/Supported_Character_Encodings} for
	 * more information.
	 *
	 * @param string $encoding
	 */
	public function set_output_encoding($encoding = 'UTF-8')
	{
		$this->sanitize->set_output_encoding($encoding);
	}

	public function strip_comments($strip = false)
	{
		$this->sanitize->strip_comments($strip);
	}

	/**
	 * Set element/attribute key/value pairs of HTML attributes
	 * containing URLs that need to be resolved relative to the feed
	 *
	 * Defaults to |a|@href, |area|@href, |blockquote|@cite, |del|@cite,
	 * |form|@action, |img|@longdesc, |img|@src, |input|@src, |ins|@cite,
	 * |q|@cite
	 *
	 * @since 1.0
	 * @param array|null $element_attribute Element/attribute key/value pairs, null for default
	 */
	public function set_url_replacements($element_attribute = null)
	{
		$this->sanitize->set_url_replacements($element_attribute);
	}

	/**
	 * Set the handler to enable the display of cached images.
	 *
	 * @param str $page Web-accessible path to the handler_image.php file.
	 * @param str $qs The query string that the value should be passed to.
	 */
	public function set_image_handler($page = false, $qs = 'i')
	{
		if ($page !== false)
		{
			$this->sanitize->set_image_handler($page . '?' . $qs . '=');
		}
		else
		{
			$this->image_handler = '';
		}
	}

	/**
	 * Set the limit for items returned per-feed with multifeeds
	 *
	 * @param integer $limit The maximum number of items to return.
	 */
	public function set_item_limit($limit = 0)
	{
		$this->item_limit = (int) $limit;
	}

	/**
	 * Initialize the feed object
	 *
	 * This is what makes everything happen.  Period.  This is where all of the
	 * configuration options get processed, feeds are fetched, cached, and
	 * parsed, and all of that other good stuff.
	 *
	 * @return boolean True if successful, false otherwise
	 */
	public function init()
	{
		// Check absolute bare minimum requirements.
		if (!extension_loaded('xml') || !extension_loaded('pcre'))
		{
			return false;
		}
		// Then check the xml extension is sane (i.e., libxml 2.7.x issue on PHP < 5.2.9 and libxml 2.7.0 to 2.7.2 on any version) if we don't have xmlreader.
		elseif (!extension_loaded('xmlreader'))
		{
			static $xml_is_sane = null;
			if ($xml_is_sane === null)
			{
				$parser_check = xml_parser_create();
				xml_parse_into_struct($parser_check, '<foo>&amp;</foo>', $values);
				xml_parser_free($parser_check);
				$xml_is_sane = isset($values[0]['value']);
			}
			if (!$xml_is_sane)
			{
				return false;
			}
		}

		if (method_exists($this->sanitize, 'set_registry'))
		{
			$this->sanitize->set_registry($this->registry);
		}

		// Pass whatever was set with config options over to the sanitizer.
		// Pass the classes in for legacy support; new classes should use the registry instead
		$this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->registry->get_class('Cache'));
		$this->sanitize->pass_file_data($this->registry->get_class('File'), $this->timeout, $this->useragent, $this->force_fsockopen);

		if (!empty($this->multifeed_url))
		{
			$i = 0;
			$success = 0;
			$this->multifeed_objects = array();
			$this->error = array();
			foreach ($this->multifeed_url as $url)
			{
				$this->multifeed_objects[$i] = clone $this;
				$this->multifeed_objects[$i]->set_feed_url($url);
				$single_success = $this->multifeed_objects[$i]->init();
				$success |= $single_success;
				if (!$single_success)
				{
					$this->error[$i] = $this->multifeed_objects[$i]->error();
				}
				$i++;
			}
			return (bool) $success;
		}
		elseif ($this->feed_url === null && $this->raw_data === null)
		{
			return false;
		}

		$this->error = null;
		$this->data = array();
		$this->multifeed_objects = array();
		$cache = false;

		if ($this->feed_url !== null)
		{
			$parsed_feed_url = $this->registry->call('Misc', 'parse_url', array($this->feed_url));

			// Decide whether to enable caching
			if ($this->cache && $parsed_feed_url['scheme'] !== '')
			{
				$cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $this->feed_url), 'spc'));
			}

			// Fetch the data via SimplePie_File into $this->raw_data
			if (($fetched = $this->fetch_data($cache)) === true)
			{
				return true;
			}
			elseif ($fetched === false) {
				return false;
			}

			list($headers, $sniffed) = $fetched;
		}

		// Set up array of possible encodings
		$encodings = array();

		// First check to see if input has been overridden.
		if ($this->input_encoding !== false)
		{
			$encodings[] = $this->input_encoding;
		}

		$application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity');
		$text_types = array('text/xml', 'text/xml-external-parsed-entity');

		// RFC 3023 (only applies to sniffed content)
		if (isset($sniffed))
		{
			if (in_array($sniffed, $application_types) || substr($sniffed, 0, 12) === 'application/' && substr($sniffed, -4) === '+xml')
			{
				if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
				{
					$encodings[] = strtoupper($charset[1]);
				}
				$encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry)));
				$encodings[] = 'UTF-8';
			}
			elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml')
			{
				if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
				{
					$encodings[] = $charset[1];
				}
				$encodings[] = 'US-ASCII';
			}
			// Text MIME-type default
			elseif (substr($sniffed, 0, 5) === 'text/')
			{
				$encodings[] = 'US-ASCII';
			}
		}

		// Fallback to XML 1.0 Appendix F.1/UTF-8/ISO-8859-1
		$encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry)));
		$encodings[] = 'UTF-8';
		$encodings[] = 'ISO-8859-1';

		// There's no point in trying an encoding twice
		$encodings = array_unique($encodings);

		// Loop through each possible encoding, till we return something, or run out of possibilities
		foreach ($encodings as $encoding)
		{
			// Change the encoding to UTF-8 (as we always use UTF-8 internally)
			if ($utf8_data = $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8')))
			{
				// Create new parser
				$parser = $this->registry->create('Parser');

				// If it's parsed fine
				if ($parser->parse($utf8_data, 'UTF-8'))
				{
					$this->data = $parser->get_data();
					if (!($this->get_type() & ~SIMPLEPIE_TYPE_NONE))
					{
						$this->error = "A feed could not be found at $this->feed_url. This does not appear to be a valid RSS or Atom feed.";
						$this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__));
						return false;
					}

					if (isset($headers))
					{
						$this->data['headers'] = $headers;
					}
					$this->data['build'] = SIMPLEPIE_BUILD;

					// Cache the file if caching is enabled
					if ($cache && !$cache->save($this))
					{
						trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
					}
					return true;
				}
			}
		}

		if (isset($parser))
		{
			// We have an error, just set SimplePie_Misc::error to it and quit
			$this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column());
		}
		else
		{
			$this->error = 'The data could not be converted to UTF-8. You MUST have either the iconv or mbstring extension installed. Upgrading to PHP 5.x (which includes iconv) is highly recommended.';
		}

		$this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__));

		return false;
	}

	/**
	 * Fetch the data via SimplePie_File
	 *
	 * If the data is already cached, attempt to fetch it from there instead
	 * @param SimplePie_Cache|false $cache Cache handler, or false to not load from the cache
	 * @return array|true Returns true if the data was loaded from the cache, or an array of HTTP headers and sniffed type
	 */
	protected function fetch_data(&$cache)
	{
		// If it's enabled, use the cache
		if ($cache)
		{
			// Load the Cache
			$this->data = $cache->load();
			if (!empty($this->data))
			{
				// If the cache is for an outdated build of SimplePie
				if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD)
				{
					$cache->unlink();
					$this->data = array();
				}
				// If we've hit a collision just rerun it with caching disabled
				elseif (isset($this->data['url']) && $this->data['url'] !== $this->feed_url)
				{
					$cache = false;
					$this->data = array();
				}
				// If we've got a non feed_url stored (if the page isn't actually a feed, or is a redirect) use that URL.
				elseif (isset($this->data['feed_url']))
				{
					// If the autodiscovery cache is still valid use it.
					if ($cache->mtime() + $this->autodiscovery_cache_duration > time())
					{
						// Do not need to do feed autodiscovery yet.
						if ($this->data['feed_url'] !== $this->data['url'])
						{
							$this->set_feed_url($this->data['feed_url']);
							return $this->init();
						}

						$cache->unlink();
						$this->data = array();
					}
				}
				// Check if the cache has been updated
				elseif ($cache->mtime() + $this->cache_duration < time())
				{
					// If we have last-modified and/or etag set
					if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))
					{
						$headers = array(
							'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
						);
						if (isset($this->data['headers']['last-modified']))
						{
							$headers['if-modified-since'] = $this->data['headers']['last-modified'];
						}
						if (isset($this->data['headers']['etag']))
						{
							$headers['if-none-match'] = $this->data['headers']['etag'];
						}

						$file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen));

						if ($file->success)
						{
							if ($file->status_code === 304)
							{
								$cache->touch();
								return true;
							}
						}
						else
						{
							unset($file);
						}
					}
				}
				// If the cache is still valid, just return true
				else
				{
					$this->raw_data = false;
					return true;
				}
			}
			// If the cache is empty, delete it
			else
			{
				$cache->unlink();
				$this->data = array();
			}
		}
		// If we don't already have the file (it'll only exist if we've opened it to check if the cache has been modified), open it.
		if (!isset($file))
		{
			if ($this->file instanceof SimplePie_File && $this->file->url === $this->feed_url)
			{
				$file =& $this->file;
			}
			else
			{
				$headers = array(
					'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
				);
				$file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen));
			}
		}
		// If the file connection has an error, set SimplePie::error to that and quit
		if (!$file->success && !($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))
		{
			$this->error = $file->error;
			return !empty($this->data);
		}

		if (!$this->force_feed)
		{
			// Check if the supplied URL is a feed, if it isn't, look for it.
			$locate = $this->registry->create('Locator', array(&$file, $this->timeout, $this->useragent, $this->max_checked_feeds));

			if (!$locate->is_feed($file))
			{
				// We need to unset this so that if SimplePie::set_file() has been called that object is untouched
				unset($file);
				try
				{
					if (!($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds)))
					{
						$this->error = "A feed could not be found at $this->feed_url. A feed with an invalid mime type may fall victim to this error, or " . SIMPLEPIE_NAME . " was unable to auto-discover it.. Use force_feed() if you are certain this URL is a real feed.";
						$this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__));
						return false;
					}
				}
				catch (SimplePie_Exception $e)
				{
					// This is usually because DOMDocument doesn't exist
					$this->error = $e->getMessage();
					$this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, $e->getFile(), $e->getLine()));
					return false;
				}
				if ($cache)
				{
					$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
					if (!$cache->save($this))
					{
						trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
					}
					$cache = $this->registry->call('Cache', 'get_handler', array($this->cache_location, call_user_func($this->cache_name_function, $file->url), 'spc'));
				}
				$this->feed_url = $file->url;
			}
			$locate = null;
		}

		$this->raw_data = $file->body;

		$headers = $file->headers;
		$sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file));
		$sniffed = $sniffer->get_type();

		return array($headers, $sniffed);
	}

	/**
	 * Get the error message for the occured error
	 *
	 * @return string|array Error message, or array of messages for multifeeds
	 */
	public function error()
	{
		return $this->error;
	}

	/**
	 * Get the raw XML
	 *
	 * This is the same as the old `$feed->enable_xml_dump(true)`, but returns
	 * the data instead of printing it.
	 *
	 * @return string|boolean Raw XML data, false if the cache is used
	 */
	public function get_raw_data()
	{
		return $this->raw_data;
	}

	/**
	 * Get the character encoding used for output
	 *
	 * @since Preview Release
	 * @return string
	 */
	public function get_encoding()
	{
		return $this->sanitize->output_encoding;
	}

	/**
	 * Send the content-type header with correct encoding
	 *
	 * This method ensures that the SimplePie-enabled page is being served with
	 * the correct {@link http://www.iana.org/assignments/media-types/ mime-type}
	 * and character encoding HTTP headers (character encoding determined by the
	 * {@see set_output_encoding} config option).
	 *
	 * This won't work properly if any content or whitespace has already been
	 * sent to the browser, because it relies on PHP's
	 * {@link http://php.net/header header()} function, and these are the
	 * circumstances under which the function works.
	 *
	 * Because it's setting these settings for the entire page (as is the nature
	 * of HTTP headers), this should only be used once per page (again, at the
	 * top).
	 *
	 * @param string $mime MIME type to serve the page as
	 */
	public function handle_content_type($mime = 'text/html')
	{
		if (!headers_sent())
		{
			$header = "Content-type: $mime;";
			if ($this->get_encoding())
			{
				$header .= ' charset=' . $this->get_encoding();
			}
			else
			{
				$header .= ' charset=UTF-8';
			}
			header($header);
		}
	}

	/**
	 * Get the type of the feed
	 *
	 * This returns a SIMPLEPIE_TYPE_* constant, which can be tested against
	 * using {@link http://php.net/language.operators.bitwise bitwise operators}
	 *
	 * @since 0.8 (usage changed to using constants in 1.0)
	 * @see SIMPLEPIE_TYPE_NONE Unknown.
	 * @see SIMPLEPIE_TYPE_RSS_090 RSS 0.90.
	 * @see SIMPLEPIE_TYPE_RSS_091_NETSCAPE RSS 0.91 (Netscape).
	 * @see SIMPLEPIE_TYPE_RSS_091_USERLAND RSS 0.91 (Userland).
	 * @see SIMPLEPIE_TYPE_RSS_091 RSS 0.91.
	 * @see SIMPLEPIE_TYPE_RSS_092 RSS 0.92.
	 * @see SIMPLEPIE_TYPE_RSS_093 RSS 0.93.
	 * @see SIMPLEPIE_TYPE_RSS_094 RSS 0.94.
	 * @see SIMPLEPIE_TYPE_RSS_10 RSS 1.0.
	 * @see SIMPLEPIE_TYPE_RSS_20 RSS 2.0.x.
	 * @see SIMPLEPIE_TYPE_RSS_RDF RDF-based RSS.
	 * @see SIMPLEPIE_TYPE_RSS_SYNDICATION Non-RDF-based RSS (truly intended as syndication format).
	 * @see SIMPLEPIE_TYPE_RSS_ALL Any version of RSS.
	 * @see SIMPLEPIE_TYPE_ATOM_03 Atom 0.3.
	 * @see SIMPLEPIE_TYPE_ATOM_10 Atom 1.0.
	 * @see SIMPLEPIE_TYPE_ATOM_ALL Any version of Atom.
	 * @see SIMPLEPIE_TYPE_ALL Any known/supported feed type.
	 * @return int SIMPLEPIE_TYPE_* constant
	 */
	public function get_type()
	{
		if (!isset($this->data['type']))
		{
			$this->data['type'] = SIMPLEPIE_TYPE_ALL;
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed']))
			{
				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_10;
			}
			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed']))
			{
				$this->data['type'] &= SIMPLEPIE_TYPE_ATOM_03;
			}
			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF']))
			{
				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['channel'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['image'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['item'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_10]['textinput']))
				{
					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_10;
				}
				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['channel'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['image'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['item'])
				|| isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_090]['textinput']))
				{
					$this->data['type'] &= SIMPLEPIE_TYPE_RSS_090;
				}
			}
			elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss']))
			{
				$this->data['type'] &= SIMPLEPIE_TYPE_RSS_ALL;
				if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
				{
					switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['attribs']['']['version']))
					{
						case '0.91':
							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091;
							if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
							{
								switch (trim($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][SIMPLEPIE_NAMESPACE_RSS_20]['skiphours']['hour'][0]['data']))
								{
									case '0':
										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_NETSCAPE;
										break;

									case '24':
										$this->data['type'] &= SIMPLEPIE_TYPE_RSS_091_USERLAND;
										break;
								}
							}
							break;

						case '0.92':
							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_092;
							break;

						case '0.93':
							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_093;
							break;

						case '0.94':
							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_094;
							break;

						case '2.0':
							$this->data['type'] &= SIMPLEPIE_TYPE_RSS_20;
							break;
					}
				}
			}
			else
			{
				$this->data['type'] = SIMPLEPIE_TYPE_NONE;
			}
		}
		return $this->data['type'];
	}

	/**
	 * Get the URL for the feed
	 *
	 * May or may not be different from the URL passed to {@see set_feed_url()},
	 * depending on whether auto-discovery was used.
	 *
	 * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.)
	 * @todo If we have a perm redirect we should return the new URL
	 * @todo When we make the above change, let's support <itunes:new-feed-url> as well
	 * @todo Also, |atom:link|@rel=self
	 * @return string|null
	 */
	public function subscribe_url()
	{
		if ($this->feed_url !== null)
		{
			return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get data for an feed-level element
	 *
	 * This method allows you to get access to ANY element/attribute that is a
	 * sub-element of the opening feed tag.
	 *
	 * The return value is an indexed array of elements matching the given
	 * namespace and tag name. Each element has `attribs`, `data` and `child`
	 * subkeys. For `attribs` and `child`, these contain namespace subkeys.
	 * `attribs` then has one level of associative name => value data (where
	 * `value` is a string) after the namespace. `child` has tag-indexed keys
	 * after the namespace, each member of which is an indexed array matching
	 * this same format.
	 *
	 * For example:
	 * <pre>
	 * // This is probably a bad example because we already support
	 * // <media:content> natively, but it shows you how to parse through
	 * // the nodes.
	 * $group = $item->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'group');
	 * $content = $group[0]['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['content'];
	 * $file = $content[0]['attribs']['']['url'];
	 * echo $file;
	 * </pre>
	 *
	 * @since 1.0
	 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
	 * @param string $namespace The URL of the XML namespace of the elements you're trying to access
	 * @param string $tag Tag name
	 * @return array
	 */
	public function get_feed_tags($namespace, $tag)
	{
		$type = $this->get_type();
		if ($type & SIMPLEPIE_TYPE_ATOM_10)
		{
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag]))
			{
				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['child'][$namespace][$tag];
			}
		}
		if ($type & SIMPLEPIE_TYPE_ATOM_03)
		{
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag]))
			{
				return $this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['child'][$namespace][$tag];
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_RDF)
		{
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag]))
			{
				return $this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['child'][$namespace][$tag];
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
		{
			if (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag]))
			{
				return $this->data['child'][SIMPLEPIE_NAMESPACE_RSS_20]['rss'][0]['child'][$namespace][$tag];
			}
		}
		return null;
	}

	/**
	 * Get data for an channel-level element
	 *
	 * This method allows you to get access to ANY element/attribute in the
	 * channel/header section of the feed.
	 *
	 * See {@see SimplePie::get_feed_tags()} for a description of the return value
	 *
	 * @since 1.0
	 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
	 * @param string $namespace The URL of the XML namespace of the elements you're trying to access
	 * @param string $tag Tag name
	 * @return array
	 */
	public function get_channel_tags($namespace, $tag)
	{
		$type = $this->get_type();
		if ($type & SIMPLEPIE_TYPE_ATOM_ALL)
		{
			if ($return = $this->get_feed_tags($namespace, $tag))
			{
				return $return;
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_10)
		{
			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'channel'))
			{
				if (isset($channel[0]['child'][$namespace][$tag]))
				{
					return $channel[0]['child'][$namespace][$tag];
				}
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_090)
		{
			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'channel'))
			{
				if (isset($channel[0]['child'][$namespace][$tag]))
				{
					return $channel[0]['child'][$namespace][$tag];
				}
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
		{
			if ($channel = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'channel'))
			{
				if (isset($channel[0]['child'][$namespace][$tag]))
				{
					return $channel[0]['child'][$namespace][$tag];
				}
			}
		}
		return null;
	}

	/**
	 * Get data for an channel-level element
	 *
	 * This method allows you to get access to ANY element/attribute in the
	 * image/logo section of the feed.
	 *
	 * See {@see SimplePie::get_feed_tags()} for a description of the return value
	 *
	 * @since 1.0
	 * @see http://simplepie.org/wiki/faq/supported_xml_namespaces
	 * @param string $namespace The URL of the XML namespace of the elements you're trying to access
	 * @param string $tag Tag name
	 * @return array
	 */
	public function get_image_tags($namespace, $tag)
	{
		$type = $this->get_type();
		if ($type & SIMPLEPIE_TYPE_RSS_10)
		{
			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'image'))
			{
				if (isset($image[0]['child'][$namespace][$tag]))
				{
					return $image[0]['child'][$namespace][$tag];
				}
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_090)
		{
			if ($image = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'image'))
			{
				if (isset($image[0]['child'][$namespace][$tag]))
				{
					return $image[0]['child'][$namespace][$tag];
				}
			}
		}
		if ($type & SIMPLEPIE_TYPE_RSS_SYNDICATION)
		{
			if ($image = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'image'))
			{
				if (isset($image[0]['child'][$namespace][$tag]))
				{
					return $image[0]['child'][$namespace][$tag];
				}
			}
		}
		return null;
	}

	/**
	 * Get the base URL value from the feed
	 *
	 * Uses `<xml:base>` if available, otherwise uses the first link in the
	 * feed, or failing that, the URL of the feed itself.
	 *
	 * @see get_link
	 * @see subscribe_url
	 *
	 * @param array $element
	 * @return string
	 */
	public function get_base($element = array())
	{
		if (!($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION) && !empty($element['xml_base_explicit']) && isset($element['xml_base']))
		{
			return $element['xml_base'];
		}
		elseif ($this->get_link() !== null)
		{
			return $this->get_link();
		}
		else
		{
			return $this->subscribe_url();
		}
	}

	/**
	 * Sanitize feed data
	 *
	 * @access private
	 * @see SimplePie_Sanitize::sanitize()
	 * @param string $data Data to sanitize
	 * @param int $type One of the SIMPLEPIE_CONSTRUCT_* constants
	 * @param string $base Base URL to resolve URLs against
	 * @return string Sanitized data
	 */
	public function sanitize($data, $type, $base = '')
	{
		return $this->sanitize->sanitize($data, $type, $base);
	}

	/**
	 * Get the title of the feed
	 *
	 * Uses `<atom:title>`, `<title>` or `<dc:title>`
	 *
	 * @since 1.0 (previously called `get_feed_title` since 0.8)
	 * @return string|null
	 */
	public function get_title()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'title'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a category for the feed
	 *
	 * @since Unknown
	 * @param int $key The category that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Category|null
	 */
	public function get_category($key = 0)
	{
		$categories = $this->get_categories();
		if (isset($categories[$key]))
		{
			return $categories[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all categories for the feed
	 *
	 * Uses `<atom:category>`, `<category>` or `<dc:subject>`
	 *
	 * @since Unknown
	 * @return array|null List of {@see SimplePie_Category} objects
	 */
	public function get_categories()
	{
		$categories = array();

		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'category') as $category)
		{
			$term = null;
			$scheme = null;
			$label = null;
			if (isset($category['attribs']['']['term']))
			{
				$term = $this->sanitize($category['attribs']['']['term'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['scheme']))
			{
				$scheme = $this->sanitize($category['attribs']['']['scheme'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($category['attribs']['']['label']))
			{
				$label = $this->sanitize($category['attribs']['']['label'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, $label));
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'category') as $category)
		{
			// This is really the label, but keep this as the term also for BC.
			// Label will also work on retrieving because that falls back to term.
			$term = $this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			if (isset($category['attribs']['']['domain']))
			{
				$scheme = $this->sanitize($category['attribs']['']['domain'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			else
			{
				$scheme = null;
			}
			$categories[] = $this->registry->create('Category', array($term, $scheme, null));
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'subject') as $category)
		{
			$categories[] = $this->registry->create('Category', array($this->sanitize($category['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($categories))
		{
			return array_unique($categories);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get an author for the feed
	 *
	 * @since 1.1
	 * @param int $key The author that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Author|null
	 */
	public function get_author($key = 0)
	{
		$authors = $this->get_authors();
		if (isset($authors[$key]))
		{
			return $authors[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all authors for the feed
	 *
	 * Uses `<atom:author>`, `<author>`, `<dc:creator>` or `<itunes:author>`
	 *
	 * @since 1.1
	 * @return array|null List of {@see SimplePie_Author} objects
	 */
	public function get_authors()
	{
		$authors = array();
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author') as $author)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($author['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		if ($author = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'author'))
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($author[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$authors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'creator') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'author') as $author)
		{
			$authors[] = $this->registry->create('Author', array($this->sanitize($author['data'], SIMPLEPIE_CONSTRUCT_TEXT), null, null));
		}

		if (!empty($authors))
		{
			return array_unique($authors);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a contributor for the feed
	 *
	 * @since 1.1
	 * @param int $key The contrbutor that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Author|null
	 */
	public function get_contributor($key = 0)
	{
		$contributors = $this->get_contributors();
		if (isset($contributors[$key]))
		{
			return $contributors[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all contributors for the feed
	 *
	 * Uses `<atom:contributor>`
	 *
	 * @since 1.1
	 * @return array|null List of {@see SimplePie_Author} objects
	 */
	public function get_contributors()
	{
		$contributors = array();
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'contributor') as $contributor)
		{
			$name = null;
			$uri = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']))
			{
				$uri = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $uri !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $uri, $email));
			}
		}
		foreach ((array) $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'contributor') as $contributor)
		{
			$name = null;
			$url = null;
			$email = null;
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data']))
			{
				$name = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['name'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data']))
			{
				$url = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['url'][0]));
			}
			if (isset($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data']))
			{
				$email = $this->sanitize($contributor['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['email'][0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
			}
			if ($name !== null || $email !== null || $url !== null)
			{
				$contributors[] = $this->registry->create('Author', array($name, $url, $email));
			}
		}

		if (!empty($contributors))
		{
			return array_unique($contributors);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get a single link for the feed
	 *
	 * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8)
	 * @param int $key The link that you want to return.  Remember that arrays begin with 0, not 1
	 * @param string $rel The relationship of the link to return
	 * @return string|null Link URL
	 */
	public function get_link($key = 0, $rel = 'alternate')
	{
		$links = $this->get_links($rel);
		if (isset($links[$key]))
		{
			return $links[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the permalink for the item
	 *
	 * Returns the first link available with a relationship of "alternate".
	 * Identical to {@see get_link()} with key 0
	 *
	 * @see get_link
	 * @since 1.0 (previously called `get_feed_link` since Preview Release, `get_feed_permalink()` since 0.8)
	 * @internal Added for parity between the parent-level and the item/entry-level.
	 * @return string|null Link URL
	 */
	public function get_permalink()
	{
		return $this->get_link(0);
	}

	/**
	 * Get all links for the feed
	 *
	 * Uses `<atom:link>` or `<link>`
	 *
	 * @since Beta 2
	 * @param string $rel The relationship of links to return
	 * @return array|null Links found for the feed (strings)
	 */
	public function get_links($rel = 'alternate')
	{
		if (!isset($this->data['links']))
		{
			$this->data['links'] = array();
			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'link'))
			{
				foreach ($links as $link)
				{
					if (isset($link['attribs']['']['href']))
					{
						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));
					}
				}
			}
			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'link'))
			{
				foreach ($links as $link)
				{
					if (isset($link['attribs']['']['href']))
					{
						$link_rel = (isset($link['attribs']['']['rel'])) ? $link['attribs']['']['rel'] : 'alternate';
						$this->data['links'][$link_rel][] = $this->sanitize($link['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($link));

					}
				}
			}
			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}
			if ($links = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
			{
				$this->data['links']['alternate'][] = $this->sanitize($links[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($links[0]));
			}

			$keys = array_keys($this->data['links']);
			foreach ($keys as $key)
			{
				if ($this->registry->call('Misc', 'is_isegment_nz_nc', array($key)))
				{
					if (isset($this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]))
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] = array_merge($this->data['links'][$key], $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key]);
						$this->data['links'][$key] =& $this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key];
					}
					else
					{
						$this->data['links'][SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY . $key] =& $this->data['links'][$key];
					}
				}
				elseif (substr($key, 0, 41) === SIMPLEPIE_IANA_LINK_RELATIONS_REGISTRY)
				{
					$this->data['links'][substr($key, 41)] =& $this->data['links'][$key];
				}
				$this->data['links'][$key] = array_unique($this->data['links'][$key]);
			}
		}

		if (isset($this->data['links'][$rel]))
		{
			return $this->data['links'][$rel];
		}
		else
		{
			return null;
		}
	}

	public function get_all_discovered_feeds()
	{
		return $this->all_discovered_feeds;
	}

	/**
	 * Get the content for the item
	 *
	 * Uses `<atom:subtitle>`, `<atom:tagline>`, `<description>`,
	 * `<dc:description>`, `<itunes:summary>` or `<itunes:subtitle>`
	 *
	 * @since 1.0 (previously called `get_feed_description()` since 0.8)
	 * @return string|null
	 */
	public function get_description()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'subtitle'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'tagline'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_MAYBE_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'description'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'summary'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'subtitle'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_HTML, $this->get_base($return[0]));
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the copyright info for the feed
	 *
	 * Uses `<atom:rights>`, `<atom:copyright>` or `<dc:rights>`
	 *
	 * @since 1.0 (previously called `get_feed_copyright()` since 0.8)
	 * @return string|null
	 */
	public function get_copyright()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_10_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'copyright'))
		{
			return $this->sanitize($return[0]['data'], $this->registry->call('Misc', 'atom_03_construct_type', array($return[0]['attribs'])), $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'copyright'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'rights'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the language for the feed
	 *
	 * Uses `<language>`, `<dc:language>`, or @xml_lang
	 *
	 * @since 1.0 (previously called `get_feed_language()` since 0.8)
	 * @return string|null
	 */
	public function get_language()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_11, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_DC_10, 'language'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang']))
		{
			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang']))
		{
			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_ATOM_03]['feed'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (isset($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang']))
		{
			return $this->sanitize($this->data['child'][SIMPLEPIE_NAMESPACE_RDF]['RDF'][0]['xml_lang'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif (isset($this->data['headers']['content-language']))
		{
			return $this->sanitize($this->data['headers']['content-language'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the latitude coordinates for the item
	 *
	 * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
	 *
	 * Uses `<geo:lat>` or `<georss:point>`
	 *
	 * @since 1.0
	 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
	 * @link http://www.georss.org/ GeoRSS
	 * @return string|null
	 */
	public function get_latitude()
	{

		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lat'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[1];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the longitude coordinates for the feed
	 *
	 * Compatible with the W3C WGS84 Basic Geo and GeoRSS specifications
	 *
	 * Uses `<geo:long>`, `<geo:lon>` or `<georss:point>`
	 *
	 * @since 1.0
	 * @link http://www.w3.org/2003/01/geo/ W3C WGS84 Basic Geo
	 * @link http://www.georss.org/ GeoRSS
	 * @return string|null
	 */
	public function get_longitude()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'long'))
		{
			return (float) $return[0]['data'];
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_W3C_BASIC_GEO, 'lon'))
		{
			return (float) $return[0]['data'];
		}
		elseif (($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_GEORSS, 'point')) && preg_match('/^((?:-)?[0-9]+(?:\.[0-9]+)) ((?:-)?[0-9]+(?:\.[0-9]+))$/', trim($return[0]['data']), $match))
		{
			return (float) $match[2];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the feed logo's title
	 *
	 * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" title.
	 *
	 * Uses `<image><title>` or `<image><dc:title>`
	 *
	 * @return string|null
	 */
	public function get_image_title()
	{
		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_11, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_DC_10, 'title'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_TEXT);
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the feed logo's URL
	 *
	 * RSS 0.9.0, 2.0, Atom 1.0, and feeds with iTunes RSS tags are allowed to
	 * have a "feed logo" URL. This points directly to the image itself.
	 *
	 * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`,
	 * `<image><title>` or `<image><dc:title>`
	 *
	 * @return string|null
	 */
	public function get_image_url()
	{
		if ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ITUNES, 'image'))
		{
			return $this->sanitize($return[0]['attribs']['']['href'], SIMPLEPIE_CONSTRUCT_IRI);
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'logo'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'icon'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'url'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'url'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		else
		{
			return null;
		}
	}


	/**
	 * Get the feed logo's link
	 *
	 * RSS 0.9.0, 1.0 and 2.0 feeds are allowed to have a "feed logo" link. This
	 * points to a human-readable page that the image should link to.
	 *
	 * Uses `<itunes:image>`, `<atom:logo>`, `<atom:icon>`,
	 * `<image><title>` or `<image><dc:title>`
	 *
	 * @return string|null
	 */
	public function get_image_link()
	{
		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'link'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'link'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		elseif ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'link'))
		{
			return $this->sanitize($return[0]['data'], SIMPLEPIE_CONSTRUCT_IRI, $this->get_base($return[0]));
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the feed logo's link
	 *
	 * RSS 2.0 feeds are allowed to have a "feed logo" width.
	 *
	 * Uses `<image><width>` or defaults to 88.0 if no width is specified and
	 * the feed is an RSS 2.0 feed.
	 *
	 * @return int|float|null
	 */
	public function get_image_width()
	{
		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'width'))
		{
			return round($return[0]['data']);
		}
		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
		{
			return 88.0;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the feed logo's height
	 *
	 * RSS 2.0 feeds are allowed to have a "feed logo" height.
	 *
	 * Uses `<image><height>` or defaults to 31.0 if no height is specified and
	 * the feed is an RSS 2.0 feed.
	 *
	 * @return int|float|null
	 */
	public function get_image_height()
	{
		if ($return = $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'height'))
		{
			return round($return[0]['data']);
		}
		elseif ($this->get_type() & SIMPLEPIE_TYPE_RSS_SYNDICATION && $this->get_image_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'url'))
		{
			return 31.0;
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get the number of items in the feed
	 *
	 * This is well-suited for {@link http://php.net/for for()} loops with
	 * {@see get_item()}
	 *
	 * @param int $max Maximum value to return. 0 for no limit
	 * @return int Number of items in the feed
	 */
	public function get_item_quantity($max = 0)
	{
		$max = (int) $max;
		$qty = count($this->get_items());
		if ($max === 0)
		{
			return $qty;
		}
		else
		{
			return ($qty > $max) ? $max : $qty;
		}
	}

	/**
	 * Get a single item from the feed
	 *
	 * This is better suited for {@link http://php.net/for for()} loops, whereas
	 * {@see get_items()} is better suited for
	 * {@link http://php.net/foreach foreach()} loops.
	 *
	 * @see get_item_quantity()
	 * @since Beta 2
	 * @param int $key The item that you want to return.  Remember that arrays begin with 0, not 1
	 * @return SimplePie_Item|null
	 */
	public function get_item($key = 0)
	{
		$items = $this->get_items();
		if (isset($items[$key]))
		{
			return $items[$key];
		}
		else
		{
			return null;
		}
	}

	/**
	 * Get all items from the feed
	 *
	 * This is better suited for {@link http://php.net/for for()} loops, whereas
	 * {@see get_items()} is better suited for
	 * {@link http://php.net/foreach foreach()} loops.
	 *
	 * @see get_item_quantity
	 * @since Beta 2
	 * @param int $start Index to start at
	 * @param int $end Number of items to return. 0 for all items after `$start`
	 * @return array|null List of {@see SimplePie_Item} objects
	 */
	public function get_items($start = 0, $end = 0)
	{
		if (!isset($this->data['items']))
		{
			if (!empty($this->multifeed_objects))
			{
				$this->data['items'] = SimplePie::merge_items($this->multifeed_objects, $start, $end, $this->item_limit);
			}
			else
			{
				$this->data['items'] = array();
				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'entry'))
				{
					$keys = array_keys($items);
					foreach ($keys as $key)
					{
						$this->data['items'][] = $this->registry->create('Item', array($this, $items[$key]));
					}
				}
				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'entry'))
				{
					$keys = array_keys($items);
					foreach ($keys as $key)
					{
						$this->data['items'][] = $this->registry->create('Item', array($this, $items[$key]));
					}
				}
				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_10, 'item'))
				{
					$keys = array_keys($items);
					foreach ($keys as $key)
					{
						$this->data['items'][] = $this->registry->create('Item', array($this, $items[$key]));
					}
				}
				if ($items = $this->get_feed_tags(SIMPLEPIE_NAMESPACE_RSS_090, 'item'))
				{
					$keys = array_keys($items);
					foreach ($keys as $key)
					{
						$this->data['items'][] = $this->registry->create('Item', array($this, $items[$key]));
					}
				}
				if ($items = $this->get_channel_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'item'))
				{
					$keys = array_keys($items);
					foreach ($keys as $key)
					{
						$this->data['items'][] = $this->registry->create('Item', array($this, $items[$key]));
					}
				}
			}
		}

		if (!empty($this->data['items']))
		{
			// If we want to order it by date, check if all items have a date, and then sort it
			if ($this->order_by_date && empty($this->multifeed_objects))
			{
				if (!isset($this->data['ordered_items']))
				{
					$do_sort = true;
					foreach ($this->data['items'] as $item)
					{
						if (!$item->get_date('U'))
						{
							$do_sort = false;
							break;
						}
					}
					$item = null;
					$this->data['ordered_items'] = $this->data['items'];
					if ($do_sort)
					{
						usort($this->data['ordered_items'], array(get_class($this), 'sort_items'));
					}
				}
				$items = $this->data['ordered_items'];
			}
			else
			{
				$items = $this->data['items'];
			}

			// Slice the data as desired
			if ($end === 0)
			{
				return array_slice($items, $start);
			}
			else
			{
				return array_slice($items, $start, $end);
			}
		}
		else
		{
			return array();
		}
	}

	/**
	 * Set the favicon handler
	 *
	 * @deprecated Use your own favicon handling instead
	 */
	public function set_favicon_handler($page = false, $qs = 'i')
	{
		$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
		trigger_error('Favicon handling has been removed, please use your own handling', $level);
		return false;
	}

	/**
	 * Get the favicon for the current feed
	 *
	 * @deprecated Use your own favicon handling instead
	 */
	public function get_favicon()
	{
		$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
		trigger_error('Favicon handling has been removed, please use your own handling', $level);

		if (($url = $this->get_link()) !== null)
		{
			return 'http://g.etfv.co/' . urlencode($url);
		}

		return false;
	}

	/**
	 * Magic method handler
	 *
	 * @param string $method Method name
	 * @param array $args Arguments to the method
	 * @return mixed
	 */
	public function __call($method, $args)
	{
		if (strpos($method, 'subscribe_') === 0)
		{
			$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
			trigger_error('subscribe_*() has been deprecated, implement the callback yourself', $level);
			return '';
		}
		if ($method === 'enable_xml_dump')
		{
			$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
			trigger_error('enable_xml_dump() has been deprecated, use get_raw_data() instead', $level);
			return false;
		}

		$class = get_class($this);
		$trace = debug_backtrace();
		$file = $trace[0]['file'];
		$line = $trace[0]['line'];
		trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
	}

	/**
	 * Sorting callback for items
	 *
	 * @access private
	 * @param SimplePie $a
	 * @param SimplePie $b
	 * @return boolean
	 */
	public static function sort_items($a, $b)
	{
		return $a->get_date('U') <= $b->get_date('U');
	}

	/**
	 * Merge items from several feeds into one
	 *
	 * If you're merging multiple feeds together, they need to all have dates
	 * for the items or else SimplePie will refuse to sort them.
	 *
	 * @link http://simplepie.org/wiki/tutorial/sort_multiple_feeds_by_time_and_date#if_feeds_require_separate_per-feed_settings
	 * @param array $urls List of SimplePie feed objects to merge
	 * @param int $start Starting item
	 * @param int $end Number of items to return
	 * @param int $limit Maximum number of items per feed
	 * @return array
	 */
	public static function merge_items($urls, $start = 0, $end = 0, $limit = 0)
	{
		if (is_array($urls) && sizeof($urls) > 0)
		{
			$items = array();
			foreach ($urls as $arg)
			{
				if ($arg instanceof SimplePie)
				{
					$items = array_merge($items, $arg->get_items(0, $limit));
				}
				else
				{
					trigger_error('Arguments must be SimplePie objects', E_USER_WARNING);
				}
			}

			$do_sort = true;
			foreach ($items as $item)
			{
				if (!$item->get_date('U'))
				{
					$do_sort = false;
					break;
				}
			}
			$item = null;
			if ($do_sort)
			{
				usort($items, array(get_class($urls[0]), 'sort_items'));
			}

			if ($end === 0)
			{
				return array_slice($items, $start);
			}
			else
			{
				return array_slice($items, $start, $end);
			}
		}
		else
		{
			trigger_error('Cannot merge zero SimplePie objects', E_USER_WARNING);
			return array();
		}
	}
}
vendor/simplepie/simplepie/autoloader.php000064400000005471152177723700014711 0ustar00<?php
/**
 * SimplePie
 *
 * A PHP-Based RSS and Atom Feed Framework.
 * Takes the hard work out of managing a complete RSS/Atom solution.
 *
 * Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 * 	* Redistributions of source code must retain the above copyright notice, this list of
 * 	  conditions and the following disclaimer.
 *
 * 	* Redistributions in binary form must reproduce the above copyright notice, this list
 * 	  of conditions and the following disclaimer in the documentation and/or other materials
 * 	  provided with the distribution.
 *
 * 	* Neither the name of the SimplePie Team nor the names of its contributors may be used
 * 	  to endorse or promote products derived from this software without specific prior
 * 	  written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS
 * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * @package SimplePie
 * @version 1.3.1
 * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
 * @author Ryan Parman
 * @author Geoffrey Sneddon
 * @author Ryan McCue
 * @link http://simplepie.org/ SimplePie
 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
 */


// autoloader
spl_autoload_register(array(new SimplePie_Autoloader(), 'autoload'));

if (!class_exists('SimplePie'))
{
	trigger_error('Autoloader not registered properly', E_USER_ERROR);
}

/**
 * Autoloader class
 *
 * @package SimplePie
 * @subpackage API
 */
class SimplePie_Autoloader
{
	/**
	 * Constructor
	 */
	public function __construct()
	{
		$this->path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'library';
	}

	/**
	 * Autoloader
	 *
	 * @param string $class The name of the class to attempt to load.
	 */
	public function autoload($class)
	{
		// Only load the class if it starts with "SimplePie"
		if (strpos($class, 'SimplePie') !== 0)
		{
			return;
		}

		$filename = $this->path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php';
		include $filename;
	}
}vendor/simplepie/simplepie/idn/idna_convert.class.php000064400000113046152177723700017101 0ustar00<?php
// {{{ license

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
//
// +----------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU Lesser General Public License as       |
// | published by the Free Software Foundation; either version 2.1 of the |
// | License, or (at your option) any later version.                      |
// |                                                                      |
// | This library is distributed in the hope that it will be useful, but  |
// | WITHOUT ANY WARRANTY; without even the implied warranty of           |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
// | Lesser General Public License for more details.                      |
// |                                                                      |
// | You should have received a copy of the GNU Lesser General Public     |
// | License along with this library; if not, write to the Free Software  |
// | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
// | USA.                                                                 |
// +----------------------------------------------------------------------+
//

// }}}

/**
 * Encode/decode Internationalized Domain Names.
 *
 * The class allows to convert internationalized domain names
 * (see RFC 3490 for details) as they can be used with various registries worldwide
 * to be translated between their original (localized) form and their encoded form
 * as it will be used in the DNS (Domain Name System).
 *
 * The class provides two public methods, encode() and decode(), which do exactly
 * what you would expect them to do. You are allowed to use complete domain names,
 * simple strings and complete email addresses as well. That means, that you might
 * use any of the following notations:
 *
 * - www.nörgler.com
 * - xn--nrgler-wxa
 * - xn--brse-5qa.xn--knrz-1ra.info
 *
 * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4
 * array. Unicode output is available in the same formats.
 * You can select your preferred format via {@link set_paramter()}.
 *
 * ACE input and output is always expected to be ASCII.
 *
 * @author  Matthias Sommerfeld <mso@phlylabs.de>
 * @copyright 2004-2007 phlyLabs Berlin, http://phlylabs.de
 * @version 0.5.1
 *
 */
class idna_convert
{
    /**
     * Holds all relevant mapping tables, loaded from a seperate file on construct
     * See RFC3454 for details
     *
     * @var array
     * @access private
     */
    var $NP = array();

    // Internal settings, do not mess with them
    var $_punycode_prefix = 'xn--';
    var $_invalid_ucs =     0x80000000;
    var $_max_ucs =         0x10FFFF;
    var $_base =            36;
    var $_tmin =            1;
    var $_tmax =            26;
    var $_skew =            38;
    var $_damp =            700;
    var $_initial_bias =    72;
    var $_initial_n =       0x80;
    var $_sbase =           0xAC00;
    var $_lbase =           0x1100;
    var $_vbase =           0x1161;
    var $_tbase =           0x11A7;
    var $_lcount =          19;
    var $_vcount =          21;
    var $_tcount =          28;
    var $_ncount =          588;   // _vcount * _tcount
    var $_scount =          11172; // _lcount * _tcount * _vcount
    var $_error =           false;

    // See {@link set_paramter()} for details of how to change the following
    // settings from within your script / application
    var $_api_encoding   =  'utf8'; // Default input charset is UTF-8
    var $_allow_overlong =  false;  // Overlong UTF-8 encodings are forbidden
    var $_strict_mode    =  false;  // Behave strict or not

    // The constructor
    function idna_convert($options = false)
    {
        $this->slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount;
        if (function_exists('file_get_contents')) {
            $this->NP = unserialize(file_get_contents(dirname(__FILE__).'/npdata.ser'));
        } else {
            $this->NP = unserialize(join('', file(dirname(__FILE__).'/npdata.ser')));
        }
        // If parameters are given, pass these to the respective method
        if (is_array($options)) {
            return $this->set_parameter($options);
        }
        return true;
    }

    /**
     * Sets a new option value. Available options and values:
     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
     *             to allow this, set this parameter to true, else to false;
     *             default is false.]
     * [strict - true: strict mode, good for registration purposes - Causes errors
     *           on failures; false: loose mode, ideal for "wildlife" applications
     *           by silently ignoring errors and returning the original input instead
     *
     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
     * @param    string    Value to use (if parameter 1 is a string)
     * @return   boolean   true on success, false otherwise
     * @access   public
     */
    function set_parameter($option, $value = false)
    {
        if (!is_array($option)) {
            $option = array($option => $value);
        }
        foreach ($option as $k => $v) {
            switch ($k) {
            case 'encoding':
                switch ($v) {
                case 'utf8':
                case 'ucs4_string':
                case 'ucs4_array':
                    $this->_api_encoding = $v;
                    break;
                default:
                    $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
                    return false;
                }
                break;
            case 'overlong':
                $this->_allow_overlong = ($v) ? true : false;
                break;
            case 'strict':
                $this->_strict_mode = ($v) ? true : false;
                break;
            default:
                $this->_error('Set Parameter: Unknown option '.$k);
                return false;
            }
        }
        return true;
    }

    /**
     * Decode a given ACE domain name
     * @param    string   Domain name (ACE string)
     * [@param    string   Desired output encoding, see {@link set_parameter}]
     * @return   string   Decoded Domain name (UTF-8 or UCS-4)
     * @access   public
     */
    function decode($input, $one_time_encoding = false)
    {
        // Optionally set
        if ($one_time_encoding) {
            switch ($one_time_encoding) {
            case 'utf8':
            case 'ucs4_string':
            case 'ucs4_array':
                break;
            default:
                $this->_error('Unknown encoding '.$one_time_encoding);
                return false;
            }
        }
        // Make sure to drop any newline characters around
        $input = trim($input);

        // Negotiate input and try to determine, whether it is a plain string,
        // an email address or something like a complete URL
        if (strpos($input, '@')) { // Maybe it is an email address
            // No no in strict mode
            if ($this->_strict_mode) {
                $this->_error('Only simple domain name parts can be handled in strict mode');
                return false;
            }
            list ($email_pref, $input) = explode('@', $input, 2);
            $arr = explode('.', $input);
            foreach ($arr as $k => $v) {
                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
            }
            $input = join('.', $arr);
            $arr = explode('.', $email_pref);
            foreach ($arr as $k => $v) {
                if (preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $v)) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
            }
            $email_pref = join('.', $arr);
            $return = $email_pref . '@' . $input;
        } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters)
            // No no in strict mode
            if ($this->_strict_mode) {
                $this->_error('Only simple domain name parts can be handled in strict mode');
                return false;
            }
            $parsed = parse_url($input);
            if (isset($parsed['host'])) {
                $arr = explode('.', $parsed['host']);
                foreach ($arr as $k => $v) {
                    $conv = $this->_decode($v);
                    if ($conv) $arr[$k] = $conv;
                }
                $parsed['host'] = join('.', $arr);
                $return =
                        (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
                        .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
                        .$parsed['host']
                        .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
                        .(empty($parsed['path']) ? '' : $parsed['path'])
                        .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
                        .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
            } else { // parse_url seems to have failed, try without it
                $arr = explode('.', $input);
                foreach ($arr as $k => $v) {
                    $conv = $this->_decode($v);
                    $arr[$k] = ($conv) ? $conv : $v;
                }
                $return = join('.', $arr);
            }
        } else { // Otherwise we consider it being a pure domain name string
            $return = $this->_decode($input);
            if (!$return) $return = $input;
        }
        // The output is UTF-8 by default, other output formats need conversion here
        // If one time encoding is given, use this, else the objects property
        switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
        case 'utf8':
            return $return;
            break;
        case 'ucs4_string':
           return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return));
           break;
        case 'ucs4_array':
            return $this->_utf8_to_ucs4($return);
            break;
        default:
            $this->_error('Unsupported output format');
            return false;
        }
    }

    /**
     * Encode a given UTF-8 domain name
     * @param    string   Domain name (UTF-8 or UCS-4)
     * [@param    string   Desired input encoding, see {@link set_parameter}]
     * @return   string   Encoded Domain name (ACE string)
     * @access   public
     */
    function encode($decoded, $one_time_encoding = false)
    {
        // Forcing conversion of input to UCS4 array
        // If one time encoding is given, use this, else the objects property
        switch ($one_time_encoding ? $one_time_encoding : $this->_api_encoding) {
        case 'utf8':
            $decoded = $this->_utf8_to_ucs4($decoded);
            break;
        case 'ucs4_string':
           $decoded = $this->_ucs4_string_to_ucs4($decoded);
        case 'ucs4_array':
           break;
        default:
            $this->_error('Unsupported input format: '.($one_time_encoding ? $one_time_encoding : $this->_api_encoding));
            return false;
        }

        // No input, no output, what else did you expect?
        if (empty($decoded)) return '';

        // Anchors for iteration
        $last_begin = 0;
        // Output string
        $output = '';
        foreach ($decoded as $k => $v) {
            // Make sure to use just the plain dot
            switch($v) {
            case 0x3002:
            case 0xFF0E:
            case 0xFF61:
                $decoded[$k] = 0x2E;
                // Right, no break here, the above are converted to dots anyway
            // Stumbling across an anchoring character
            case 0x2E:
            case 0x2F:
            case 0x3A:
            case 0x3F:
            case 0x40:
                // Neither email addresses nor URLs allowed in strict mode
                if ($this->_strict_mode) {
                   $this->_error('Neither email addresses nor URLs are allowed in strict mode.');
                   return false;
                } else {
                    // Skip first char
                    if ($k) {
                        $encoded = '';
                        $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin)));
                        if ($encoded) {
                            $output .= $encoded;
                        } else {
                            $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin)));
                        }
                        $output .= chr($decoded[$k]);
                    }
                    $last_begin = $k + 1;
                }
            }
        }
        // Catch the rest of the string
        if ($last_begin) {
            $inp_len = sizeof($decoded);
            $encoded = '';
            $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
            if ($encoded) {
                $output .= $encoded;
            } else {
                $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
            }
            return $output;
        } else {
            if ($output = $this->_encode($decoded)) {
                return $output;
            } else {
                return $this->_ucs4_to_utf8($decoded);
            }
        }
    }

    /**
     * Use this method to get the last error ocurred
     * @param    void
     * @return   string   The last error, that occured
     * @access   public
     */
    function get_last_error()
    {
        return $this->_error;
    }

    /**
     * The actual decoding algorithm
     * @access   private
     */
    function _decode($encoded)
    {
        // We do need to find the Punycode prefix
        if (!preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $encoded)) {
            $this->_error('This is not a punycode string');
            return false;
        }
        $encode_test = preg_replace('!^'.preg_quote($this->_punycode_prefix, '!').'!', '', $encoded);
        // If nothing left after removing the prefix, it is hopeless
        if (!$encode_test) {
            $this->_error('The given encoded string was empty');
            return false;
        }
        // Find last occurence of the delimiter
        $delim_pos = strrpos($encoded, '-');
        if ($delim_pos > strlen($this->_punycode_prefix)) {
            for ($k = strlen($this->_punycode_prefix); $k < $delim_pos; ++$k) {
                $decoded[] = ord($encoded{$k});
            }
        } else {
            $decoded = array();
        }
        $deco_len = count($decoded);
        $enco_len = strlen($encoded);

        // Wandering through the strings; init
        $is_first = true;
        $bias     = $this->_initial_bias;
        $idx      = 0;
        $char     = $this->_initial_n;

        for ($enco_idx = ($delim_pos) ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
            for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) {
                $digit = $this->_decode_digit($encoded{$enco_idx++});
                $idx += $digit * $w;
                $t = ($k <= $bias) ? $this->_tmin :
                        (($k >= $bias + $this->_tmax) ? $this->_tmax : ($k - $bias));
                if ($digit < $t) break;
                $w = (int) ($w * ($this->_base - $t));
            }
            $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first);
            $is_first = false;
            $char += (int) ($idx / ($deco_len + 1));
            $idx %= ($deco_len + 1);
            if ($deco_len > 0) {
                // Make room for the decoded char
                for ($i = $deco_len; $i > $idx; $i--) {
                    $decoded[$i] = $decoded[($i - 1)];
                }
            }
            $decoded[$idx++] = $char;
        }
        return $this->_ucs4_to_utf8($decoded);
    }

    /**
     * The actual encoding algorithm
     * @access   private
     */
    function _encode($decoded)
    {
        // We cannot encode a domain name containing the Punycode prefix
        $extract = strlen($this->_punycode_prefix);
        $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix);
        $check_deco = array_slice($decoded, 0, $extract);

        if ($check_pref == $check_deco) {
            $this->_error('This is already a punycode string');
            return false;
        }
        // We will not try to encode strings consisting of basic code points only
        $encodable = false;
        foreach ($decoded as $k => $v) {
            if ($v > 0x7a) {
                $encodable = true;
                break;
            }
        }
        if (!$encodable) {
            $this->_error('The given string does not contain encodable chars');
            return false;
        }

        // Do NAMEPREP
        $decoded = $this->_nameprep($decoded);
        if (!$decoded || !is_array($decoded)) return false; // NAMEPREP failed

        $deco_len  = count($decoded);
        if (!$deco_len) return false; // Empty array

        $codecount = 0; // How many chars have been consumed

        $encoded = '';
        // Copy all basic code points to output
        for ($i = 0; $i < $deco_len; ++$i) {
            $test = $decoded[$i];
            // Will match [-0-9a-zA-Z]
            if ((0x2F < $test && $test < 0x40) || (0x40 < $test && $test < 0x5B)
                    || (0x60 < $test && $test <= 0x7B) || (0x2D == $test)) {
                $encoded .= chr($decoded[$i]);
                $codecount++;
            }
        }
        if ($codecount == $deco_len) return $encoded; // All codepoints were basic ones

        // Start with the prefix; copy it to output
        $encoded = $this->_punycode_prefix.$encoded;

        // If we have basic code points in output, add an hyphen to the end
        if ($codecount) $encoded .= '-';

        // Now find and encode all non-basic code points
        $is_first  = true;
        $cur_code  = $this->_initial_n;
        $bias      = $this->_initial_bias;
        $delta     = 0;
        while ($codecount < $deco_len) {
            // Find the smallest code point >= the current code point and
            // remember the last ouccrence of it in the input
            for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) {
                if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) {
                    $next_code = $decoded[$i];
                }
            }

            $delta += ($next_code - $cur_code) * ($codecount + 1);
            $cur_code = $next_code;

            // Scan input again and encode all characters whose code point is $cur_code
            for ($i = 0; $i < $deco_len; $i++) {
                if ($decoded[$i] < $cur_code) {
                    $delta++;
                } elseif ($decoded[$i] == $cur_code) {
                    for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) {
                        $t = ($k <= $bias) ? $this->_tmin :
                                (($k >= $bias + $this->_tmax) ? $this->_tmax : $k - $bias);
                        if ($q < $t) break;
                        $encoded .= $this->_encode_digit(intval($t + (($q - $t) % ($this->_base - $t)))); //v0.4.5 Changed from ceil() to intval()
                        $q = (int) (($q - $t) / ($this->_base - $t));
                    }
                    $encoded .= $this->_encode_digit($q);
                    $bias = $this->_adapt($delta, $codecount+1, $is_first);
                    $codecount++;
                    $delta = 0;
                    $is_first = false;
                }
            }
            $delta++;
            $cur_code++;
        }
        return $encoded;
    }

    /**
     * Adapt the bias according to the current code point and position
     * @access   private
     */
    function _adapt($delta, $npoints, $is_first)
    {
        $delta = intval($is_first ? ($delta / $this->_damp) : ($delta / 2));
        $delta += intval($delta / $npoints);
        for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) {
            $delta = intval($delta / ($this->_base - $this->_tmin));
        }
        return intval($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew));
    }

    /**
     * Encoding a certain digit
     * @access   private
     */
    function _encode_digit($d)
    {
        return chr($d + 22 + 75 * ($d < 26));
    }

    /**
     * Decode a certain digit
     * @access   private
     */
    function _decode_digit($cp)
    {
        $cp = ord($cp);
        return ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $this->_base));
    }

    /**
     * Internal error handling method
     * @access   private
     */
    function _error($error = '')
    {
        $this->_error = $error;
    }

    /**
     * Do Nameprep according to RFC3491 and RFC3454
     * @param    array    Unicode Characters
     * @return   string   Unicode Characters, Nameprep'd
     * @access   private
     */
    function _nameprep($input)
    {
        $output = array();
        $error = false;
        //
        // Mapping
        // Walking through the input array, performing the required steps on each of
        // the input chars and putting the result into the output array
        // While mapping required chars we apply the cannonical ordering
        foreach ($input as $v) {
            // Map to nothing == skip that code point
            if (in_array($v, $this->NP['map_nothing'])) continue;

            // Try to find prohibited input
            if (in_array($v, $this->NP['prohibit']) || in_array($v, $this->NP['general_prohibited'])) {
                $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
                return false;
            }
            foreach ($this->NP['prohibit_ranges'] as $range) {
                if ($range[0] <= $v && $v <= $range[1]) {
                    $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
                    return false;
                }
            }
            //
            // Hangul syllable decomposition
            if (0xAC00 <= $v && $v <= 0xD7AF) {
                foreach ($this->_hangul_decompose($v) as $out) {
                    $output[] = (int) $out;
                }
            // There's a decomposition mapping for that code point
            } elseif (isset($this->NP['replacemaps'][$v])) {
                foreach ($this->_apply_cannonical_ordering($this->NP['replacemaps'][$v]) as $out) {
                    $output[] = (int) $out;
                }
            } else {
                $output[] = (int) $v;
            }
        }
        // Before applying any Combining, try to rearrange any Hangul syllables
        $output = $this->_hangul_compose($output);
        //
        // Combine code points
        //
        $last_class   = 0;
        $last_starter = 0;
        $out_len      = count($output);
        for ($i = 0; $i < $out_len; ++$i) {
            $class = $this->_get_combining_class($output[$i]);
            if ((!$last_class || $last_class > $class) && $class) {
                // Try to match
                $seq_len = $i - $last_starter;
                $out = $this->_combine(array_slice($output, $last_starter, $seq_len));
                // On match: Replace the last starter with the composed character and remove
                // the now redundant non-starter(s)
                if ($out) {
                    $output[$last_starter] = $out;
                    if (count($out) != $seq_len) {
                        for ($j = $i+1; $j < $out_len; ++$j) {
                            $output[$j-1] = $output[$j];
                        }
                        unset($output[$out_len]);
                    }
                    // Rewind the for loop by one, since there can be more possible compositions
                    $i--;
                    $out_len--;
                    $last_class = ($i == $last_starter) ? 0 : $this->_get_combining_class($output[$i-1]);
                    continue;
                }
            }
            // The current class is 0
            if (!$class) $last_starter = $i;
            $last_class = $class;
        }
        return $output;
    }

    /**
     * Decomposes a Hangul syllable
     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
     * @param    integer  32bit UCS4 code point
     * @return   array    Either Hangul Syllable decomposed or original 32bit value as one value array
     * @access   private
     */
    function _hangul_decompose($char)
    {
        $sindex = (int) $char - $this->_sbase;
        if ($sindex < 0 || $sindex >= $this->_scount) {
            return array($char);
        }
        $result = array();
        $result[] = (int) $this->_lbase + $sindex / $this->_ncount;
        $result[] = (int) $this->_vbase + ($sindex % $this->_ncount) / $this->_tcount;
        $T = intval($this->_tbase + $sindex % $this->_tcount);
        if ($T != $this->_tbase) $result[] = $T;
        return $result;
    }
    /**
     * Ccomposes a Hangul syllable
     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
     * @param    array    Decomposed UCS4 sequence
     * @return   array    UCS4 sequence with syllables composed
     * @access   private
     */
    function _hangul_compose($input)
    {
        $inp_len = count($input);
        if (!$inp_len) return array();
        $result = array();
        $last = (int) $input[0];
        $result[] = $last; // copy first char from input to output

        for ($i = 1; $i < $inp_len; ++$i) {
            $char = (int) $input[$i];
            $sindex = $last - $this->_sbase;
            $lindex = $last - $this->_lbase;
            $vindex = $char - $this->_vbase;
            $tindex = $char - $this->_tbase;
            // Find out, whether two current characters are LV and T
            if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount == 0)
                    && 0 <= $tindex && $tindex <= $this->_tcount) {
                // create syllable of form LVT
                $last += $tindex;
                $result[(count($result) - 1)] = $last; // reset last
                continue; // discard char
            }
            // Find out, whether two current characters form L and V
            if (0 <= $lindex && $lindex < $this->_lcount && 0 <= $vindex && $vindex < $this->_vcount) {
                // create syllable of form LV
                $last = (int) $this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount;
                $result[(count($result) - 1)] = $last; // reset last
                continue; // discard char
            }
            // if neither case was true, just add the character
            $last = $char;
            $result[] = $char;
        }
        return $result;
    }

    /**
     * Returns the combining class of a certain wide char
     * @param    integer    Wide char to check (32bit integer)
     * @return   integer    Combining class if found, else 0
     * @access   private
     */
    function _get_combining_class($char)
    {
        return isset($this->NP['norm_combcls'][$char]) ? $this->NP['norm_combcls'][$char] : 0;
    }

    /**
     * Apllies the cannonical ordering of a decomposed UCS4 sequence
     * @param    array      Decomposed UCS4 sequence
     * @return   array      Ordered USC4 sequence
     * @access   private
     */
    function _apply_cannonical_ordering($input)
    {
        $swap = true;
        $size = count($input);
        while ($swap) {
            $swap = false;
            $last = $this->_get_combining_class(intval($input[0]));
            for ($i = 0; $i < $size-1; ++$i) {
                $next = $this->_get_combining_class(intval($input[$i+1]));
                if ($next != 0 && $last > $next) {
                    // Move item leftward until it fits
                    for ($j = $i + 1; $j > 0; --$j) {
                        if ($this->_get_combining_class(intval($input[$j-1])) <= $next) break;
                        $t = intval($input[$j]);
                        $input[$j] = intval($input[$j-1]);
                        $input[$j-1] = $t;
                        $swap = true;
                    }
                    // Reentering the loop looking at the old character again
                    $next = $last;
                }
                $last = $next;
            }
        }
        return $input;
    }

    /**
     * Do composition of a sequence of starter and non-starter
     * @param    array      UCS4 Decomposed sequence
     * @return   array      Ordered USC4 sequence
     * @access   private
     */
    function _combine($input)
    {
        $inp_len = count($input);
        foreach ($this->NP['replacemaps'] as $np_src => $np_target) {
            if ($np_target[0] != $input[0]) continue;
            if (count($np_target) != $inp_len) continue;
            $hit = false;
            foreach ($input as $k2 => $v2) {
                if ($v2 == $np_target[$k2]) {
                    $hit = true;
                } else {
                    $hit = false;
                    break;
                }
            }
            if ($hit) return $np_src;
        }
        return false;
    }

    /**
     * This converts an UTF-8 encoded string to its UCS-4 representation
     * By talking about UCS-4 "strings" we mean arrays of 32bit integers representing
     * each of the "chars". This is due to PHP not being able to handle strings with
     * bit depth different from 8. This apllies to the reverse method _ucs4_to_utf8(), too.
     * The following UTF-8 encodings are supported:
     * bytes bits  representation
     * 1        7  0xxxxxxx
     * 2       11  110xxxxx 10xxxxxx
     * 3       16  1110xxxx 10xxxxxx 10xxxxxx
     * 4       21  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
     * 5       26  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
     * 6       31  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
     * Each x represents a bit that can be used to store character data.
     * The five and six byte sequences are part of Annex D of ISO/IEC 10646-1:2000
     * @access   private
     */
    function _utf8_to_ucs4($input)
    {
        $output = array();
        $out_len = 0;
        $inp_len = strlen($input);
        $mode = 'next';
        $test = 'none';
        for ($k = 0; $k < $inp_len; ++$k) {
            $v = ord($input{$k}); // Extract byte from input string

            if ($v < 128) { // We found an ASCII char - put into stirng as is
                $output[$out_len] = $v;
                ++$out_len;
                if ('add' == $mode) {
                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    return false;
                }
                continue;
            }
            if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
                $start_byte = $v;
                $mode = 'add';
                $test = 'range';
                if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
                    $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
                    $v = ($v - 192) << 6;
                } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
                    $next_byte = 1;
                    $v = ($v - 224) << 12;
                } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 2;
                    $v = ($v - 240) << 18;
                } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 3;
                    $v = ($v - 248) << 24;
                } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
                    $next_byte = 4;
                    $v = ($v - 252) << 30;
                } else {
                    $this->_error('This might be UTF-8, but I don\'t understand it at byte '.$k);
                    return false;
                }
                if ('add' == $mode) {
                    $output[$out_len] = (int) $v;
                    ++$out_len;
                    continue;
                }
            }
            if ('add' == $mode) {
                if (!$this->_allow_overlong && $test == 'range') {
                    $test = 'none';
                    if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
                        $this->_error('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
                        return false;
                    }
                }
                if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
                    $v = ($v - 128) << ($next_byte * 6);
                    $output[($out_len - 1)] += $v;
                    --$next_byte;
                } else {
                    $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
                    return false;
                }
                if ($next_byte < 0) {
                    $mode = 'next';
                }
            }
        } // for
        return $output;
    }

    /**
     * Convert UCS-4 string into UTF-8 string
     * See _utf8_to_ucs4() for details
     * @access   private
     */
    function _ucs4_to_utf8($input)
    {
        $output = '';
        $k = 0;
        foreach ($input as $v) {
            ++$k;
            // $v = ord($v);
            if ($v < 128) { // 7bit are transferred literally
                $output .= chr($v);
            } elseif ($v < (1 << 11)) { // 2 bytes
                $output .= chr(192 + ($v >> 6)) . chr(128 + ($v & 63));
            } elseif ($v < (1 << 16)) { // 3 bytes
                $output .= chr(224 + ($v >> 12)) . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
            } elseif ($v < (1 << 21)) { // 4 bytes
                $output .= chr(240 + ($v >> 18)) . chr(128 + (($v >> 12) & 63))
                         . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
            } elseif ($v < (1 << 26)) { // 5 bytes
                $output .= chr(248 + ($v >> 24)) . chr(128 + (($v >> 18) & 63))
                         . chr(128 + (($v >> 12) & 63)) . chr(128 + (($v >> 6) & 63))
                         . chr(128 + ($v & 63));
            } elseif ($v < (1 << 31)) { // 6 bytes
                $output .= chr(252 + ($v >> 30)) . chr(128 + (($v >> 24) & 63))
                         . chr(128 + (($v >> 18) & 63)) . chr(128 + (($v >> 12) & 63))
                         . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
            } else {
                $this->_error('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k);
                return false;
            }
        }
        return $output;
    }

    /**
      * Convert UCS-4 array into UCS-4 string
      *
      * @access   private
      */
    function _ucs4_to_ucs4_string($input)
    {
        $output = '';
        // Take array values and split output to 4 bytes per value
        // The bit mask is 255, which reads &11111111
        foreach ($input as $v) {
            $output .= chr(($v >> 24) & 255).chr(($v >> 16) & 255).chr(($v >> 8) & 255).chr($v & 255);
        }
        return $output;
    }

    /**
      * Convert UCS-4 strin into UCS-4 garray
      *
      * @access   private
      */
    function _ucs4_string_to_ucs4($input)
    {
        $output = array();
        $inp_len = strlen($input);
        // Input length must be dividable by 4
        if ($inp_len % 4) {
            $this->_error('Input UCS4 string is broken');
            return false;
        }
        // Empty input - return empty output
        if (!$inp_len) return $output;
        for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
            // Increment output position every 4 input bytes
            if (!($i % 4)) {
                $out_len++;
                $output[$out_len] = 0;
            }
            $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
        }
        return $output;
    }
}

/**
* Adapter class for aligning the API of idna_convert with that of Net_IDNA
* @author  Matthias Sommerfeld <mso@phlylabs.de>
*/
class Net_IDNA_php4 extends idna_convert
{
    /**
     * Sets a new option value. Available options and values:
     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
     *             to allow this, set this parameter to true, else to false;
     *             default is false.]
     * [strict - true: strict mode, good for registration purposes - Causes errors
     *           on failures; false: loose mode, ideal for "wildlife" applications
     *           by silently ignoring errors and returning the original input instead
     *
     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
     * @param    string    Value to use (if parameter 1 is a string)
     * @return   boolean   true on success, false otherwise
     * @access   public
     */
    function setParams($option, $param = false)
    {
        return $this->IC->set_parameters($option, $param);
    }
}

?>vendor/simplepie/simplepie/idn/LICENCE000064400000063623152177723700013603 0ustar00                  GNU LESSER GENERAL PUBLIC LICENSE
                       Version 2.1, February 1999

 Copyright (C) 1991, 1999 Free Software Foundation, Inc.
     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts
 as the successor of the GNU Library Public License, version 2, hence
 the version number 2.1.]

                            Preamble

  The licenses for most software are designed to take away your
freedom to share and change it.  By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.

  This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it.  You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.

  When we speak of free software, we are referring to freedom of use,
not price.  Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.

  To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights.  These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.

  For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you.  You must make sure that they, too, receive or can get the source
code.  If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it.  And you must show them these terms so they know their rights.

  We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.

  To protect each distributor, we want to make it very clear that
there is no warranty for the free library.  Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.

  Finally, software patents pose a constant threat to the existence of
any free program.  We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder.  Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.

  Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License.  This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License.  We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.

  When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library.  The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom.  The Lesser General
Public License permits more lax criteria for linking other code with
the library.

  We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License.  It also provides other free software developers Less
of an advantage over competing non-free programs.  These disadvantages
are the reason we use the ordinary General Public License for many
libraries.  However, the Lesser license provides advantages in certain
special circumstances.

  For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard.  To achieve this, non-free programs must be
allowed to use the library.  A more frequent case is that a free
library does the same job as widely used non-free libraries.  In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.

  In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software.  For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.

  Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.

  The precise terms and conditions for copying, distribution and
modification follow.  Pay close attention to the difference between a
"work based on the library" and a "work that uses the library".  The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.

                  GNU LESSER GENERAL PUBLIC LICENSE
   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

  0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".

  A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.

  The "Library", below, refers to any such software library or work
which has been distributed under these terms.  A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language.  (Hereinafter, translation is
included without limitation in the term "modification".)

  "Source code" for a work means the preferred form of the work for
making modifications to it.  For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.

  Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope.  The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it).  Whether that is true depends on what the Library does
and what the program that uses the Library does.

  1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.

  You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.

  2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:

    a) The modified work must itself be a software library.

    b) You must cause the files modified to carry prominent notices
    stating that you changed the files and the date of any change.

    c) You must cause the whole of the work to be licensed at no
    charge to all third parties under the terms of this License.

    d) If a facility in the modified Library refers to a function or a
    table of data to be supplied by an application program that uses
    the facility, other than as an argument passed when the facility
    is invoked, then you must make a good faith effort to ensure that,
    in the event an application does not supply such function or
    table, the facility still operates, and performs whatever part of
    its purpose remains meaningful.

    (For example, a function in a library to compute square roots has
    a purpose that is entirely well-defined independent of the
    application.  Therefore, Subsection 2d requires that any
    application-supplied function or table used by this function must
    be optional: if the application does not supply it, the square
    root function must still compute square roots.)

These requirements apply to the modified work as a whole.  If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works.  But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.

Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.

In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.

  3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library.  To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License.  (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.)  Do not make any other change in
these notices.

  Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.

  This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.

  4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.

  If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.

  5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library".  Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.

  However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library".  The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.

  When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library.  The
threshold for this to be true is not precisely defined by law.

  If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work.  (Executables containing this object code plus portions of the
Library will still fall under Section 6.)

  Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.

  6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.

  You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License.  You must supply a copy of this License.  If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License.  Also, you must do one
of these things:

    a) Accompany the work with the complete corresponding
    machine-readable source code for the Library including whatever
    changes were used in the work (which must be distributed under
    Sections 1 and 2 above); and, if the work is an executable linked
    with the Library, with the complete machine-readable "work that
    uses the Library", as object code and/or source code, so that the
    user can modify the Library and then relink to produce a modified
    executable containing the modified Library.  (It is understood
    that the user who changes the contents of definitions files in the
    Library will not necessarily be able to recompile the application
    to use the modified definitions.)

    b) Use a suitable shared library mechanism for linking with the
    Library.  A suitable mechanism is one that (1) uses at run time a
    copy of the library already present on the user's computer system,
    rather than copying library functions into the executable, and (2)
    will operate properly with a modified version of the library, if
    the user installs one, as long as the modified version is
    interface-compatible with the version that the work was made with.

    c) Accompany the work with a written offer, valid for at
    least three years, to give the same user the materials
    specified in Subsection 6a, above, for a charge no more
    than the cost of performing this distribution.

    d) If distribution of the work is made by offering access to copy
    from a designated place, offer equivalent access to copy the above
    specified materials from the same place.

    e) Verify that the user has already received a copy of these
    materials or that you have already sent this user a copy.

  For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it.  However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.

  It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system.  Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.

  7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:

    a) Accompany the combined library with a copy of the same work
    based on the Library, uncombined with any other library
    facilities.  This must be distributed under the terms of the
    Sections above.

    b) Give prominent notice with the combined library of the fact
    that part of it is a work based on the Library, and explaining
    where to find the accompanying uncombined form of the same work.

  8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License.  Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License.  However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.

  9. You are not required to accept this License, since you have not
signed it.  However, nothing else grants you permission to modify or
distribute the Library or its derivative works.  These actions are
prohibited by law if you do not accept this License.  Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.

  10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions.  You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.

  11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all.  For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices.  Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.

This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.

  12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded.  In such case, this License incorporates the limitation as if
written in the body of this License.

  13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number.  If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation.  If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.

  14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission.  For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this.  Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.

                            NO WARRANTY

  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.

                     END OF TERMS AND CONDITIONS

           How to Apply These Terms to Your New Libraries

  If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change.  You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).

  To apply these terms, attach the following notices to the library.  It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.

    <one line to give the library's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

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

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

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary.  Here is a sample; alter the names:

  Yoyodyne, Inc., hereby disclaims all copyright interest in the
  library `Frob' (a library for tweaking knobs) written by James Random Hacker.

  <signature of Ty Coon>, 1 April 1990
  Ty Coon, President of Vice

That's all there is to it!
vendor/simplepie/simplepie/idn/npdata.ser000064400000122601152177723700014570 0ustar00a:6:{s:11:"map_nothing";a:27:{i:0;i:173;i:1;i:847;i:2;i:6150;i:3;i:6155;i:4;i:6156;i:5;i:6157;i:6;i:8203;i:7;i:8204;i:8;i:8205;i:9;i:8288;i:10;i:65024;i:11;i:65025;i:12;i:65026;i:13;i:65027;i:14;i:65028;i:15;i:65029;i:16;i:65030;i:17;i:65031;i:18;i:65032;i:19;i:65033;i:20;i:65034;i:21;i:65035;i:22;i:65036;i:23;i:65037;i:24;i:65038;i:25;i:65039;i:26;i:65279;}s:18:"general_prohibited";a:64:{i:0;i:0;i:1;i:1;i:2;i:2;i:3;i:3;i:4;i:4;i:5;i:5;i:6;i:6;i:7;i:7;i:8;i:8;i:9;i:9;i:10;i:10;i:11;i:11;i:12;i:12;i:13;i:13;i:14;i:14;i:15;i:15;i:16;i:16;i:17;i:17;i:18;i:18;i:19;i:19;i:20;i:20;i:21;i:21;i:22;i:22;i:23;i:23;i:24;i:24;i:25;i:25;i:26;i:26;i:27;i:27;i:28;i:28;i:29;i:29;i:30;i:30;i:31;i:31;i:32;i:32;i:33;i:33;i:34;i:34;i:35;i:35;i:36;i:36;i:37;i:37;i:38;i:38;i:39;i:39;i:40;i:40;i:41;i:41;i:42;i:42;i:43;i:43;i:44;i:44;i:45;i:47;i:46;i:59;i:47;i:60;i:48;i:61;i:49;i:62;i:50;i:63;i:51;i:64;i:52;i:91;i:53;i:92;i:54;i:93;i:55;i:94;i:56;i:95;i:57;i:96;i:58;i:123;i:59;i:124;i:60;i:125;i:61;i:126;i:62;i:127;i:63;i:12290;}s:8:"prohibit";a:84:{i:0;i:160;i:1;i:5760;i:2;i:8192;i:3;i:8193;i:4;i:8194;i:5;i:8195;i:6;i:8196;i:7;i:8197;i:8;i:8198;i:9;i:8199;i:10;i:8200;i:11;i:8201;i:12;i:8202;i:13;i:8203;i:14;i:8239;i:15;i:8287;i:16;i:12288;i:17;i:1757;i:18;i:1807;i:19;i:6158;i:20;i:8204;i:21;i:8205;i:22;i:8232;i:23;i:8233;i:24;i:65279;i:25;i:65529;i:26;i:65530;i:27;i:65531;i:28;i:65532;i:29;i:65534;i:30;i:65535;i:31;i:131070;i:32;i:131071;i:33;i:196606;i:34;i:196607;i:35;i:262142;i:36;i:262143;i:37;i:327678;i:38;i:327679;i:39;i:393214;i:40;i:393215;i:41;i:458750;i:42;i:458751;i:43;i:524286;i:44;i:524287;i:45;i:589822;i:46;i:589823;i:47;i:655358;i:48;i:655359;i:49;i:720894;i:50;i:720895;i:51;i:786430;i:52;i:786431;i:53;i:851966;i:54;i:851967;i:55;i:917502;i:56;i:917503;i:57;i:983038;i:58;i:983039;i:59;i:1048574;i:60;i:1048575;i:61;i:1114110;i:62;i:1114111;i:63;i:65529;i:64;i:65530;i:65;i:65531;i:66;i:65532;i:67;i:65533;i:68;i:832;i:69;i:833;i:70;i:8206;i:71;i:8207;i:72;i:8234;i:73;i:8235;i:74;i:8236;i:75;i:8237;i:76;i:8238;i:77;i:8298;i:78;i:8299;i:79;i:8300;i:80;i:8301;i:81;i:8302;i:82;i:8303;i:83;i:917505;}s:15:"prohibit_ranges";a:10:{i:0;a:2:{i:0;i:128;i:1;i:159;}i:1;a:2:{i:0;i:8288;i:1;i:8303;}i:2;a:2:{i:0;i:119155;i:1;i:119162;}i:3;a:2:{i:0;i:57344;i:1;i:63743;}i:4;a:2:{i:0;i:983040;i:1;i:1048573;}i:5;a:2:{i:0;i:1048576;i:1;i:1114109;}i:6;a:2:{i:0;i:64976;i:1;i:65007;}i:7;a:2:{i:0;i:55296;i:1;i:57343;}i:8;a:2:{i:0;i:12272;i:1;i:12283;}i:9;a:2:{i:0;i:917536;i:1;i:917631;}}s:11:"replacemaps";a:1401:{i:65;a:1:{i:0;i:97;}i:66;a:1:{i:0;i:98;}i:67;a:1:{i:0;i:99;}i:68;a:1:{i:0;i:100;}i:69;a:1:{i:0;i:101;}i:70;a:1:{i:0;i:102;}i:71;a:1:{i:0;i:103;}i:72;a:1:{i:0;i:104;}i:73;a:1:{i:0;i:105;}i:74;a:1:{i:0;i:106;}i:75;a:1:{i:0;i:107;}i:76;a:1:{i:0;i:108;}i:77;a:1:{i:0;i:109;}i:78;a:1:{i:0;i:110;}i:79;a:1:{i:0;i:111;}i:80;a:1:{i:0;i:112;}i:81;a:1:{i:0;i:113;}i:82;a:1:{i:0;i:114;}i:83;a:1:{i:0;i:115;}i:84;a:1:{i:0;i:116;}i:85;a:1:{i:0;i:117;}i:86;a:1:{i:0;i:118;}i:87;a:1:{i:0;i:119;}i:88;a:1:{i:0;i:120;}i:89;a:1:{i:0;i:121;}i:90;a:1:{i:0;i:122;}i:181;a:1:{i:0;i:956;}i:192;a:1:{i:0;i:224;}i:193;a:1:{i:0;i:225;}i:194;a:1:{i:0;i:226;}i:195;a:1:{i:0;i:227;}i:196;a:1:{i:0;i:228;}i:197;a:1:{i:0;i:229;}i:198;a:1:{i:0;i:230;}i:199;a:1:{i:0;i:231;}i:200;a:1:{i:0;i:232;}i:201;a:1:{i:0;i:233;}i:202;a:1:{i:0;i:234;}i:203;a:1:{i:0;i:235;}i:204;a:1:{i:0;i:236;}i:205;a:1:{i:0;i:237;}i:206;a:1:{i:0;i:238;}i:207;a:1:{i:0;i:239;}i:208;a:1:{i:0;i:240;}i:209;a:1:{i:0;i:241;}i:210;a:1:{i:0;i:242;}i:211;a:1:{i:0;i:243;}i:212;a:1:{i:0;i:244;}i:213;a:1:{i:0;i:245;}i:214;a:1:{i:0;i:246;}i:216;a:1:{i:0;i:248;}i:217;a:1:{i:0;i:249;}i:218;a:1:{i:0;i:250;}i:219;a:1:{i:0;i:251;}i:220;a:1:{i:0;i:252;}i:221;a:1:{i:0;i:253;}i:222;a:1:{i:0;i:254;}i:223;a:2:{i:0;i:115;i:1;i:115;}i:256;a:1:{i:0;i:257;}i:258;a:1:{i:0;i:259;}i:260;a:1:{i:0;i:261;}i:262;a:1:{i:0;i:263;}i:264;a:1:{i:0;i:265;}i:266;a:1:{i:0;i:267;}i:268;a:1:{i:0;i:269;}i:270;a:1:{i:0;i:271;}i:272;a:1:{i:0;i:273;}i:274;a:1:{i:0;i:275;}i:276;a:1:{i:0;i:277;}i:278;a:1:{i:0;i:279;}i:280;a:1:{i:0;i:281;}i:282;a:1:{i:0;i:283;}i:284;a:1:{i:0;i:285;}i:286;a:1:{i:0;i:287;}i:288;a:1:{i:0;i:289;}i:290;a:1:{i:0;i:291;}i:292;a:1:{i:0;i:293;}i:294;a:1:{i:0;i:295;}i:296;a:1:{i:0;i:297;}i:298;a:1:{i:0;i:299;}i:300;a:1:{i:0;i:301;}i:302;a:1:{i:0;i:303;}i:304;a:2:{i:0;i:105;i:1;i:775;}i:306;a:1:{i:0;i:307;}i:308;a:1:{i:0;i:309;}i:310;a:1:{i:0;i:311;}i:313;a:1:{i:0;i:314;}i:315;a:1:{i:0;i:316;}i:317;a:1:{i:0;i:318;}i:319;a:1:{i:0;i:320;}i:321;a:1:{i:0;i:322;}i:323;a:1:{i:0;i:324;}i:325;a:1:{i:0;i:326;}i:327;a:1:{i:0;i:328;}i:329;a:2:{i:0;i:700;i:1;i:110;}i:330;a:1:{i:0;i:331;}i:332;a:1:{i:0;i:333;}i:334;a:1:{i:0;i:335;}i:336;a:1:{i:0;i:337;}i:338;a:1:{i:0;i:339;}i:340;a:1:{i:0;i:341;}i:342;a:1:{i:0;i:343;}i:344;a:1:{i:0;i:345;}i:346;a:1:{i:0;i:347;}i:348;a:1:{i:0;i:349;}i:350;a:1:{i:0;i:351;}i:352;a:1:{i:0;i:353;}i:354;a:1:{i:0;i:355;}i:356;a:1:{i:0;i:357;}i:358;a:1:{i:0;i:359;}i:360;a:1:{i:0;i:361;}i:362;a:1:{i:0;i:363;}i:364;a:1:{i:0;i:365;}i:366;a:1:{i:0;i:367;}i:368;a:1:{i:0;i:369;}i:370;a:1:{i:0;i:371;}i:372;a:1:{i:0;i:373;}i:374;a:1:{i:0;i:375;}i:376;a:1:{i:0;i:255;}i:377;a:1:{i:0;i:378;}i:379;a:1:{i:0;i:380;}i:381;a:1:{i:0;i:382;}i:383;a:1:{i:0;i:115;}i:385;a:1:{i:0;i:595;}i:386;a:1:{i:0;i:387;}i:388;a:1:{i:0;i:389;}i:390;a:1:{i:0;i:596;}i:391;a:1:{i:0;i:392;}i:393;a:1:{i:0;i:598;}i:394;a:1:{i:0;i:599;}i:395;a:1:{i:0;i:396;}i:398;a:1:{i:0;i:477;}i:399;a:1:{i:0;i:601;}i:400;a:1:{i:0;i:603;}i:401;a:1:{i:0;i:402;}i:403;a:1:{i:0;i:608;}i:404;a:1:{i:0;i:611;}i:406;a:1:{i:0;i:617;}i:407;a:1:{i:0;i:616;}i:408;a:1:{i:0;i:409;}i:412;a:1:{i:0;i:623;}i:413;a:1:{i:0;i:626;}i:415;a:1:{i:0;i:629;}i:416;a:1:{i:0;i:417;}i:418;a:1:{i:0;i:419;}i:420;a:1:{i:0;i:421;}i:422;a:1:{i:0;i:640;}i:423;a:1:{i:0;i:424;}i:425;a:1:{i:0;i:643;}i:428;a:1:{i:0;i:429;}i:430;a:1:{i:0;i:648;}i:431;a:1:{i:0;i:432;}i:433;a:1:{i:0;i:650;}i:434;a:1:{i:0;i:651;}i:435;a:1:{i:0;i:436;}i:437;a:1:{i:0;i:438;}i:439;a:1:{i:0;i:658;}i:440;a:1:{i:0;i:441;}i:444;a:1:{i:0;i:445;}i:452;a:1:{i:0;i:454;}i:453;a:1:{i:0;i:454;}i:455;a:1:{i:0;i:457;}i:456;a:1:{i:0;i:457;}i:458;a:1:{i:0;i:460;}i:459;a:1:{i:0;i:460;}i:461;a:1:{i:0;i:462;}i:463;a:1:{i:0;i:464;}i:465;a:1:{i:0;i:466;}i:467;a:1:{i:0;i:468;}i:469;a:1:{i:0;i:470;}i:471;a:1:{i:0;i:472;}i:473;a:1:{i:0;i:474;}i:475;a:1:{i:0;i:476;}i:478;a:1:{i:0;i:479;}i:480;a:1:{i:0;i:481;}i:482;a:1:{i:0;i:483;}i:484;a:1:{i:0;i:485;}i:486;a:1:{i:0;i:487;}i:488;a:1:{i:0;i:489;}i:490;a:1:{i:0;i:491;}i:492;a:1:{i:0;i:493;}i:494;a:1:{i:0;i:495;}i:496;a:2:{i:0;i:106;i:1;i:780;}i:497;a:1:{i:0;i:499;}i:498;a:1:{i:0;i:499;}i:500;a:1:{i:0;i:501;}i:502;a:1:{i:0;i:405;}i:503;a:1:{i:0;i:447;}i:504;a:1:{i:0;i:505;}i:506;a:1:{i:0;i:507;}i:508;a:1:{i:0;i:509;}i:510;a:1:{i:0;i:511;}i:512;a:1:{i:0;i:513;}i:514;a:1:{i:0;i:515;}i:516;a:1:{i:0;i:517;}i:518;a:1:{i:0;i:519;}i:520;a:1:{i:0;i:521;}i:522;a:1:{i:0;i:523;}i:524;a:1:{i:0;i:525;}i:526;a:1:{i:0;i:527;}i:528;a:1:{i:0;i:529;}i:530;a:1:{i:0;i:531;}i:532;a:1:{i:0;i:533;}i:534;a:1:{i:0;i:535;}i:536;a:1:{i:0;i:537;}i:538;a:1:{i:0;i:539;}i:540;a:1:{i:0;i:541;}i:542;a:1:{i:0;i:543;}i:544;a:1:{i:0;i:414;}i:546;a:1:{i:0;i:547;}i:548;a:1:{i:0;i:549;}i:550;a:1:{i:0;i:551;}i:552;a:1:{i:0;i:553;}i:554;a:1:{i:0;i:555;}i:556;a:1:{i:0;i:557;}i:558;a:1:{i:0;i:559;}i:560;a:1:{i:0;i:561;}i:562;a:1:{i:0;i:563;}i:837;a:1:{i:0;i:953;}i:890;a:2:{i:0;i:32;i:1;i:953;}i:902;a:1:{i:0;i:940;}i:904;a:1:{i:0;i:941;}i:905;a:1:{i:0;i:942;}i:906;a:1:{i:0;i:943;}i:908;a:1:{i:0;i:972;}i:910;a:1:{i:0;i:973;}i:911;a:1:{i:0;i:974;}i:912;a:3:{i:0;i:953;i:1;i:776;i:2;i:769;}i:913;a:1:{i:0;i:945;}i:914;a:1:{i:0;i:946;}i:915;a:1:{i:0;i:947;}i:916;a:1:{i:0;i:948;}i:917;a:1:{i:0;i:949;}i:918;a:1:{i:0;i:950;}i:919;a:1:{i:0;i:951;}i:920;a:1:{i:0;i:952;}i:921;a:1:{i:0;i:953;}i:922;a:1:{i:0;i:954;}i:923;a:1:{i:0;i:955;}i:924;a:1:{i:0;i:956;}i:925;a:1:{i:0;i:957;}i:926;a:1:{i:0;i:958;}i:927;a:1:{i:0;i:959;}i:928;a:1:{i:0;i:960;}i:929;a:1:{i:0;i:961;}i:931;a:1:{i:0;i:963;}i:932;a:1:{i:0;i:964;}i:933;a:1:{i:0;i:965;}i:934;a:1:{i:0;i:966;}i:935;a:1:{i:0;i:967;}i:936;a:1:{i:0;i:968;}i:937;a:1:{i:0;i:969;}i:938;a:1:{i:0;i:970;}i:939;a:1:{i:0;i:971;}i:944;a:3:{i:0;i:965;i:1;i:776;i:2;i:769;}i:962;a:1:{i:0;i:963;}i:976;a:1:{i:0;i:946;}i:977;a:1:{i:0;i:952;}i:978;a:1:{i:0;i:965;}i:979;a:1:{i:0;i:973;}i:980;a:1:{i:0;i:971;}i:981;a:1:{i:0;i:966;}i:982;a:1:{i:0;i:960;}i:984;a:1:{i:0;i:985;}i:986;a:1:{i:0;i:987;}i:988;a:1:{i:0;i:989;}i:990;a:1:{i:0;i:991;}i:992;a:1:{i:0;i:993;}i:994;a:1:{i:0;i:995;}i:996;a:1:{i:0;i:997;}i:998;a:1:{i:0;i:999;}i:1000;a:1:{i:0;i:1001;}i:1002;a:1:{i:0;i:1003;}i:1004;a:1:{i:0;i:1005;}i:1006;a:1:{i:0;i:1007;}i:1008;a:1:{i:0;i:954;}i:1009;a:1:{i:0;i:961;}i:1010;a:1:{i:0;i:963;}i:1012;a:1:{i:0;i:952;}i:1013;a:1:{i:0;i:949;}i:1024;a:1:{i:0;i:1104;}i:1025;a:1:{i:0;i:1105;}i:1026;a:1:{i:0;i:1106;}i:1027;a:1:{i:0;i:1107;}i:1028;a:1:{i:0;i:1108;}i:1029;a:1:{i:0;i:1109;}i:1030;a:1:{i:0;i:1110;}i:1031;a:1:{i:0;i:1111;}i:1032;a:1:{i:0;i:1112;}i:1033;a:1:{i:0;i:1113;}i:1034;a:1:{i:0;i:1114;}i:1035;a:1:{i:0;i:1115;}i:1036;a:1:{i:0;i:1116;}i:1037;a:1:{i:0;i:1117;}i:1038;a:1:{i:0;i:1118;}i:1039;a:1:{i:0;i:1119;}i:1040;a:1:{i:0;i:1072;}i:1041;a:1:{i:0;i:1073;}i:1042;a:1:{i:0;i:1074;}i:1043;a:1:{i:0;i:1075;}i:1044;a:1:{i:0;i:1076;}i:1045;a:1:{i:0;i:1077;}i:1046;a:1:{i:0;i:1078;}i:1047;a:1:{i:0;i:1079;}i:1048;a:1:{i:0;i:1080;}i:1049;a:1:{i:0;i:1081;}i:1050;a:1:{i:0;i:1082;}i:1051;a:1:{i:0;i:1083;}i:1052;a:1:{i:0;i:1084;}i:1053;a:1:{i:0;i:1085;}i:1054;a:1:{i:0;i:1086;}i:1055;a:1:{i:0;i:1087;}i:1056;a:1:{i:0;i:1088;}i:1057;a:1:{i:0;i:1089;}i:1058;a:1:{i:0;i:1090;}i:1059;a:1:{i:0;i:1091;}i:1060;a:1:{i:0;i:1092;}i:1061;a:1:{i:0;i:1093;}i:1062;a:1:{i:0;i:1094;}i:1063;a:1:{i:0;i:1095;}i:1064;a:1:{i:0;i:1096;}i:1065;a:1:{i:0;i:1097;}i:1066;a:1:{i:0;i:1098;}i:1067;a:1:{i:0;i:1099;}i:1068;a:1:{i:0;i:1100;}i:1069;a:1:{i:0;i:1101;}i:1070;a:1:{i:0;i:1102;}i:1071;a:1:{i:0;i:1103;}i:1120;a:1:{i:0;i:1121;}i:1122;a:1:{i:0;i:1123;}i:1124;a:1:{i:0;i:1125;}i:1126;a:1:{i:0;i:1127;}i:1128;a:1:{i:0;i:1129;}i:1130;a:1:{i:0;i:1131;}i:1132;a:1:{i:0;i:1133;}i:1134;a:1:{i:0;i:1135;}i:1136;a:1:{i:0;i:1137;}i:1138;a:1:{i:0;i:1139;}i:1140;a:1:{i:0;i:1141;}i:1142;a:1:{i:0;i:1143;}i:1144;a:1:{i:0;i:1145;}i:1146;a:1:{i:0;i:1147;}i:1148;a:1:{i:0;i:1149;}i:1150;a:1:{i:0;i:1151;}i:1152;a:1:{i:0;i:1153;}i:1162;a:1:{i:0;i:1163;}i:1164;a:1:{i:0;i:1165;}i:1166;a:1:{i:0;i:1167;}i:1168;a:1:{i:0;i:1169;}i:1170;a:1:{i:0;i:1171;}i:1172;a:1:{i:0;i:1173;}i:1174;a:1:{i:0;i:1175;}i:1176;a:1:{i:0;i:1177;}i:1178;a:1:{i:0;i:1179;}i:1180;a:1:{i:0;i:1181;}i:1182;a:1:{i:0;i:1183;}i:1184;a:1:{i:0;i:1185;}i:1186;a:1:{i:0;i:1187;}i:1188;a:1:{i:0;i:1189;}i:1190;a:1:{i:0;i:1191;}i:1192;a:1:{i:0;i:1193;}i:1194;a:1:{i:0;i:1195;}i:1196;a:1:{i:0;i:1197;}i:1198;a:1:{i:0;i:1199;}i:1200;a:1:{i:0;i:1201;}i:1202;a:1:{i:0;i:1203;}i:1204;a:1:{i:0;i:1205;}i:1206;a:1:{i:0;i:1207;}i:1208;a:1:{i:0;i:1209;}i:1210;a:1:{i:0;i:1211;}i:1212;a:1:{i:0;i:1213;}i:1214;a:1:{i:0;i:1215;}i:1217;a:1:{i:0;i:1218;}i:1219;a:1:{i:0;i:1220;}i:1221;a:1:{i:0;i:1222;}i:1223;a:1:{i:0;i:1224;}i:1225;a:1:{i:0;i:1226;}i:1227;a:1:{i:0;i:1228;}i:1229;a:1:{i:0;i:1230;}i:1232;a:1:{i:0;i:1233;}i:1234;a:1:{i:0;i:1235;}i:1236;a:1:{i:0;i:1237;}i:1238;a:1:{i:0;i:1239;}i:1240;a:1:{i:0;i:1241;}i:1242;a:1:{i:0;i:1243;}i:1244;a:1:{i:0;i:1245;}i:1246;a:1:{i:0;i:1247;}i:1248;a:1:{i:0;i:1249;}i:1250;a:1:{i:0;i:1251;}i:1252;a:1:{i:0;i:1253;}i:1254;a:1:{i:0;i:1255;}i:1256;a:1:{i:0;i:1257;}i:1258;a:1:{i:0;i:1259;}i:1260;a:1:{i:0;i:1261;}i:1262;a:1:{i:0;i:1263;}i:1264;a:1:{i:0;i:1265;}i:1266;a:1:{i:0;i:1267;}i:1268;a:1:{i:0;i:1269;}i:1272;a:1:{i:0;i:1273;}i:1280;a:1:{i:0;i:1281;}i:1282;a:1:{i:0;i:1283;}i:1284;a:1:{i:0;i:1285;}i:1286;a:1:{i:0;i:1287;}i:1288;a:1:{i:0;i:1289;}i:1290;a:1:{i:0;i:1291;}i:1292;a:1:{i:0;i:1293;}i:1294;a:1:{i:0;i:1295;}i:1329;a:1:{i:0;i:1377;}i:1330;a:1:{i:0;i:1378;}i:1331;a:1:{i:0;i:1379;}i:1332;a:1:{i:0;i:1380;}i:1333;a:1:{i:0;i:1381;}i:1334;a:1:{i:0;i:1382;}i:1335;a:1:{i:0;i:1383;}i:1336;a:1:{i:0;i:1384;}i:1337;a:1:{i:0;i:1385;}i:1338;a:1:{i:0;i:1386;}i:1339;a:1:{i:0;i:1387;}i:1340;a:1:{i:0;i:1388;}i:1341;a:1:{i:0;i:1389;}i:1342;a:1:{i:0;i:1390;}i:1343;a:1:{i:0;i:1391;}i:1344;a:1:{i:0;i:1392;}i:1345;a:1:{i:0;i:1393;}i:1346;a:1:{i:0;i:1394;}i:1347;a:1:{i:0;i:1395;}i:1348;a:1:{i:0;i:1396;}i:1349;a:1:{i:0;i:1397;}i:1350;a:1:{i:0;i:1398;}i:1351;a:1:{i:0;i:1399;}i:1352;a:1:{i:0;i:1400;}i:1353;a:1:{i:0;i:1401;}i:1354;a:1:{i:0;i:1402;}i:1355;a:1:{i:0;i:1403;}i:1356;a:1:{i:0;i:1404;}i:1357;a:1:{i:0;i:1405;}i:1358;a:1:{i:0;i:1406;}i:1359;a:1:{i:0;i:1407;}i:1360;a:1:{i:0;i:1408;}i:1361;a:1:{i:0;i:1409;}i:1362;a:1:{i:0;i:1410;}i:1363;a:1:{i:0;i:1411;}i:1364;a:1:{i:0;i:1412;}i:1365;a:1:{i:0;i:1413;}i:1366;a:1:{i:0;i:1414;}i:1415;a:2:{i:0;i:1381;i:1;i:1410;}i:7680;a:1:{i:0;i:7681;}i:7682;a:1:{i:0;i:7683;}i:7684;a:1:{i:0;i:7685;}i:7686;a:1:{i:0;i:7687;}i:7688;a:1:{i:0;i:7689;}i:7690;a:1:{i:0;i:7691;}i:7692;a:1:{i:0;i:7693;}i:7694;a:1:{i:0;i:7695;}i:7696;a:1:{i:0;i:7697;}i:7698;a:1:{i:0;i:7699;}i:7700;a:1:{i:0;i:7701;}i:7702;a:1:{i:0;i:7703;}i:7704;a:1:{i:0;i:7705;}i:7706;a:1:{i:0;i:7707;}i:7708;a:1:{i:0;i:7709;}i:7710;a:1:{i:0;i:7711;}i:7712;a:1:{i:0;i:7713;}i:7714;a:1:{i:0;i:7715;}i:7716;a:1:{i:0;i:7717;}i:7718;a:1:{i:0;i:7719;}i:7720;a:1:{i:0;i:7721;}i:7722;a:1:{i:0;i:7723;}i:7724;a:1:{i:0;i:7725;}i:7726;a:1:{i:0;i:7727;}i:7728;a:1:{i:0;i:7729;}i:7730;a:1:{i:0;i:7731;}i:7732;a:1:{i:0;i:7733;}i:7734;a:1:{i:0;i:7735;}i:7736;a:1:{i:0;i:7737;}i:7738;a:1:{i:0;i:7739;}i:7740;a:1:{i:0;i:7741;}i:7742;a:1:{i:0;i:7743;}i:7744;a:1:{i:0;i:7745;}i:7746;a:1:{i:0;i:7747;}i:7748;a:1:{i:0;i:7749;}i:7750;a:1:{i:0;i:7751;}i:7752;a:1:{i:0;i:7753;}i:7754;a:1:{i:0;i:7755;}i:7756;a:1:{i:0;i:7757;}i:7758;a:1:{i:0;i:7759;}i:7760;a:1:{i:0;i:7761;}i:7762;a:1:{i:0;i:7763;}i:7764;a:1:{i:0;i:7765;}i:7766;a:1:{i:0;i:7767;}i:7768;a:1:{i:0;i:7769;}i:7770;a:1:{i:0;i:7771;}i:7772;a:1:{i:0;i:7773;}i:7774;a:1:{i:0;i:7775;}i:7776;a:1:{i:0;i:7777;}i:7778;a:1:{i:0;i:7779;}i:7780;a:1:{i:0;i:7781;}i:7782;a:1:{i:0;i:7783;}i:7784;a:1:{i:0;i:7785;}i:7786;a:1:{i:0;i:7787;}i:7788;a:1:{i:0;i:7789;}i:7790;a:1:{i:0;i:7791;}i:7792;a:1:{i:0;i:7793;}i:7794;a:1:{i:0;i:7795;}i:7796;a:1:{i:0;i:7797;}i:7798;a:1:{i:0;i:7799;}i:7800;a:1:{i:0;i:7801;}i:7802;a:1:{i:0;i:7803;}i:7804;a:1:{i:0;i:7805;}i:7806;a:1:{i:0;i:7807;}i:7808;a:1:{i:0;i:7809;}i:7810;a:1:{i:0;i:7811;}i:7812;a:1:{i:0;i:7813;}i:7814;a:1:{i:0;i:7815;}i:7816;a:1:{i:0;i:7817;}i:7818;a:1:{i:0;i:7819;}i:7820;a:1:{i:0;i:7821;}i:7822;a:1:{i:0;i:7823;}i:7824;a:1:{i:0;i:7825;}i:7826;a:1:{i:0;i:7827;}i:7828;a:1:{i:0;i:7829;}i:7830;a:2:{i:0;i:104;i:1;i:817;}i:7831;a:2:{i:0;i:116;i:1;i:776;}i:7832;a:2:{i:0;i:119;i:1;i:778;}i:7833;a:2:{i:0;i:121;i:1;i:778;}i:7834;a:2:{i:0;i:97;i:1;i:702;}i:7835;a:1:{i:0;i:7777;}i:7840;a:1:{i:0;i:7841;}i:7842;a:1:{i:0;i:7843;}i:7844;a:1:{i:0;i:7845;}i:7846;a:1:{i:0;i:7847;}i:7848;a:1:{i:0;i:7849;}i:7850;a:1:{i:0;i:7851;}i:7852;a:1:{i:0;i:7853;}i:7854;a:1:{i:0;i:7855;}i:7856;a:1:{i:0;i:7857;}i:7858;a:1:{i:0;i:7859;}i:7860;a:1:{i:0;i:7861;}i:7862;a:1:{i:0;i:7863;}i:7864;a:1:{i:0;i:7865;}i:7866;a:1:{i:0;i:7867;}i:7868;a:1:{i:0;i:7869;}i:7870;a:1:{i:0;i:7871;}i:7872;a:1:{i:0;i:7873;}i:7874;a:1:{i:0;i:7875;}i:7876;a:1:{i:0;i:7877;}i:7878;a:1:{i:0;i:7879;}i:7880;a:1:{i:0;i:7881;}i:7882;a:1:{i:0;i:7883;}i:7884;a:1:{i:0;i:7885;}i:7886;a:1:{i:0;i:7887;}i:7888;a:1:{i:0;i:7889;}i:7890;a:1:{i:0;i:7891;}i:7892;a:1:{i:0;i:7893;}i:7894;a:1:{i:0;i:7895;}i:7896;a:1:{i:0;i:7897;}i:7898;a:1:{i:0;i:7899;}i:7900;a:1:{i:0;i:7901;}i:7902;a:1:{i:0;i:7903;}i:7904;a:1:{i:0;i:7905;}i:7906;a:1:{i:0;i:7907;}i:7908;a:1:{i:0;i:7909;}i:7910;a:1:{i:0;i:7911;}i:7912;a:1:{i:0;i:7913;}i:7914;a:1:{i:0;i:7915;}i:7916;a:1:{i:0;i:7917;}i:7918;a:1:{i:0;i:7919;}i:7920;a:1:{i:0;i:7921;}i:7922;a:1:{i:0;i:7923;}i:7924;a:1:{i:0;i:7925;}i:7926;a:1:{i:0;i:7927;}i:7928;a:1:{i:0;i:7929;}i:7944;a:1:{i:0;i:7936;}i:7945;a:1:{i:0;i:7937;}i:7946;a:1:{i:0;i:7938;}i:7947;a:1:{i:0;i:7939;}i:7948;a:1:{i:0;i:7940;}i:7949;a:1:{i:0;i:7941;}i:7950;a:1:{i:0;i:7942;}i:7951;a:1:{i:0;i:7943;}i:7960;a:1:{i:0;i:7952;}i:7961;a:1:{i:0;i:7953;}i:7962;a:1:{i:0;i:7954;}i:7963;a:1:{i:0;i:7955;}i:7964;a:1:{i:0;i:7956;}i:7965;a:1:{i:0;i:7957;}i:7976;a:1:{i:0;i:7968;}i:7977;a:1:{i:0;i:7969;}i:7978;a:1:{i:0;i:7970;}i:7979;a:1:{i:0;i:7971;}i:7980;a:1:{i:0;i:7972;}i:7981;a:1:{i:0;i:7973;}i:7982;a:1:{i:0;i:7974;}i:7983;a:1:{i:0;i:7975;}i:7992;a:1:{i:0;i:7984;}i:7993;a:1:{i:0;i:7985;}i:7994;a:1:{i:0;i:7986;}i:7995;a:1:{i:0;i:7987;}i:7996;a:1:{i:0;i:7988;}i:7997;a:1:{i:0;i:7989;}i:7998;a:1:{i:0;i:7990;}i:7999;a:1:{i:0;i:7991;}i:8008;a:1:{i:0;i:8000;}i:8009;a:1:{i:0;i:8001;}i:8010;a:1:{i:0;i:8002;}i:8011;a:1:{i:0;i:8003;}i:8012;a:1:{i:0;i:8004;}i:8013;a:1:{i:0;i:8005;}i:8016;a:2:{i:0;i:965;i:1;i:787;}i:8018;a:3:{i:0;i:965;i:1;i:787;i:2;i:768;}i:8020;a:3:{i:0;i:965;i:1;i:787;i:2;i:769;}i:8022;a:3:{i:0;i:965;i:1;i:787;i:2;i:834;}i:8025;a:1:{i:0;i:8017;}i:8027;a:1:{i:0;i:8019;}i:8029;a:1:{i:0;i:8021;}i:8031;a:1:{i:0;i:8023;}i:8040;a:1:{i:0;i:8032;}i:8041;a:1:{i:0;i:8033;}i:8042;a:1:{i:0;i:8034;}i:8043;a:1:{i:0;i:8035;}i:8044;a:1:{i:0;i:8036;}i:8045;a:1:{i:0;i:8037;}i:8046;a:1:{i:0;i:8038;}i:8047;a:1:{i:0;i:8039;}i:8064;a:2:{i:0;i:7936;i:1;i:953;}i:8065;a:2:{i:0;i:7937;i:1;i:953;}i:8066;a:2:{i:0;i:7938;i:1;i:953;}i:8067;a:2:{i:0;i:7939;i:1;i:953;}i:8068;a:2:{i:0;i:7940;i:1;i:953;}i:8069;a:2:{i:0;i:7941;i:1;i:953;}i:8070;a:2:{i:0;i:7942;i:1;i:953;}i:8071;a:2:{i:0;i:7943;i:1;i:953;}i:8072;a:2:{i:0;i:7936;i:1;i:953;}i:8073;a:2:{i:0;i:7937;i:1;i:953;}i:8074;a:2:{i:0;i:7938;i:1;i:953;}i:8075;a:2:{i:0;i:7939;i:1;i:953;}i:8076;a:2:{i:0;i:7940;i:1;i:953;}i:8077;a:2:{i:0;i:7941;i:1;i:953;}i:8078;a:2:{i:0;i:7942;i:1;i:953;}i:8079;a:2:{i:0;i:7943;i:1;i:953;}i:8080;a:2:{i:0;i:7968;i:1;i:953;}i:8081;a:2:{i:0;i:7969;i:1;i:953;}i:8082;a:2:{i:0;i:7970;i:1;i:953;}i:8083;a:2:{i:0;i:7971;i:1;i:953;}i:8084;a:2:{i:0;i:7972;i:1;i:953;}i:8085;a:2:{i:0;i:7973;i:1;i:953;}i:8086;a:2:{i:0;i:7974;i:1;i:953;}i:8087;a:2:{i:0;i:7975;i:1;i:953;}i:8088;a:2:{i:0;i:7968;i:1;i:953;}i:8089;a:2:{i:0;i:7969;i:1;i:953;}i:8090;a:2:{i:0;i:7970;i:1;i:953;}i:8091;a:2:{i:0;i:7971;i:1;i:953;}i:8092;a:2:{i:0;i:7972;i:1;i:953;}i:8093;a:2:{i:0;i:7973;i:1;i:953;}i:8094;a:2:{i:0;i:7974;i:1;i:953;}i:8095;a:2:{i:0;i:7975;i:1;i:953;}i:8096;a:2:{i:0;i:8032;i:1;i:953;}i:8097;a:2:{i:0;i:8033;i:1;i:953;}i:8098;a:2:{i:0;i:8034;i:1;i:953;}i:8099;a:2:{i:0;i:8035;i:1;i:953;}i:8100;a:2:{i:0;i:8036;i:1;i:953;}i:8101;a:2:{i:0;i:8037;i:1;i:953;}i:8102;a:2:{i:0;i:8038;i:1;i:953;}i:8103;a:2:{i:0;i:8039;i:1;i:953;}i:8104;a:2:{i:0;i:8032;i:1;i:953;}i:8105;a:2:{i:0;i:8033;i:1;i:953;}i:8106;a:2:{i:0;i:8034;i:1;i:953;}i:8107;a:2:{i:0;i:8035;i:1;i:953;}i:8108;a:2:{i:0;i:8036;i:1;i:953;}i:8109;a:2:{i:0;i:8037;i:1;i:953;}i:8110;a:2:{i:0;i:8038;i:1;i:953;}i:8111;a:2:{i:0;i:8039;i:1;i:953;}i:8114;a:2:{i:0;i:8048;i:1;i:953;}i:8115;a:2:{i:0;i:945;i:1;i:953;}i:8116;a:2:{i:0;i:940;i:1;i:953;}i:8118;a:2:{i:0;i:945;i:1;i:834;}i:8119;a:3:{i:0;i:945;i:1;i:834;i:2;i:953;}i:8120;a:1:{i:0;i:8112;}i:8121;a:1:{i:0;i:8113;}i:8122;a:1:{i:0;i:8048;}i:8123;a:1:{i:0;i:8049;}i:8124;a:2:{i:0;i:945;i:1;i:953;}i:8126;a:1:{i:0;i:953;}i:8130;a:2:{i:0;i:8052;i:1;i:953;}i:8131;a:2:{i:0;i:951;i:1;i:953;}i:8132;a:2:{i:0;i:942;i:1;i:953;}i:8134;a:2:{i:0;i:951;i:1;i:834;}i:8135;a:3:{i:0;i:951;i:1;i:834;i:2;i:953;}i:8136;a:1:{i:0;i:8050;}i:8137;a:1:{i:0;i:8051;}i:8138;a:1:{i:0;i:8052;}i:8139;a:1:{i:0;i:8053;}i:8140;a:2:{i:0;i:951;i:1;i:953;}i:8146;a:3:{i:0;i:953;i:1;i:776;i:2;i:768;}i:8147;a:3:{i:0;i:953;i:1;i:776;i:2;i:769;}i:8150;a:2:{i:0;i:953;i:1;i:834;}i:8151;a:3:{i:0;i:953;i:1;i:776;i:2;i:834;}i:8152;a:1:{i:0;i:8144;}i:8153;a:1:{i:0;i:8145;}i:8154;a:1:{i:0;i:8054;}i:8155;a:1:{i:0;i:8055;}i:8162;a:3:{i:0;i:965;i:1;i:776;i:2;i:768;}i:8163;a:3:{i:0;i:965;i:1;i:776;i:2;i:769;}i:8164;a:2:{i:0;i:961;i:1;i:787;}i:8166;a:2:{i:0;i:965;i:1;i:834;}i:8167;a:3:{i:0;i:965;i:1;i:776;i:2;i:834;}i:8168;a:1:{i:0;i:8160;}i:8169;a:1:{i:0;i:8161;}i:8170;a:1:{i:0;i:8058;}i:8171;a:1:{i:0;i:8059;}i:8172;a:1:{i:0;i:8165;}i:8178;a:2:{i:0;i:8060;i:1;i:953;}i:8179;a:2:{i:0;i:969;i:1;i:953;}i:8180;a:2:{i:0;i:974;i:1;i:953;}i:8182;a:2:{i:0;i:969;i:1;i:834;}i:8183;a:3:{i:0;i:969;i:1;i:834;i:2;i:953;}i:8184;a:1:{i:0;i:8056;}i:8185;a:1:{i:0;i:8057;}i:8186;a:1:{i:0;i:8060;}i:8187;a:1:{i:0;i:8061;}i:8188;a:2:{i:0;i:969;i:1;i:953;}i:8360;a:2:{i:0;i:114;i:1;i:115;}i:8450;a:1:{i:0;i:99;}i:8451;a:2:{i:0;i:176;i:1;i:99;}i:8455;a:1:{i:0;i:603;}i:8457;a:2:{i:0;i:176;i:1;i:102;}i:8459;a:1:{i:0;i:104;}i:8460;a:1:{i:0;i:104;}i:8461;a:1:{i:0;i:104;}i:8464;a:1:{i:0;i:105;}i:8465;a:1:{i:0;i:105;}i:8466;a:1:{i:0;i:108;}i:8469;a:1:{i:0;i:110;}i:8470;a:2:{i:0;i:110;i:1;i:111;}i:8473;a:1:{i:0;i:112;}i:8474;a:1:{i:0;i:113;}i:8475;a:1:{i:0;i:114;}i:8476;a:1:{i:0;i:114;}i:8477;a:1:{i:0;i:114;}i:8480;a:2:{i:0;i:115;i:1;i:109;}i:8481;a:3:{i:0;i:116;i:1;i:101;i:2;i:108;}i:8482;a:2:{i:0;i:116;i:1;i:109;}i:8484;a:1:{i:0;i:122;}i:8486;a:1:{i:0;i:969;}i:8488;a:1:{i:0;i:122;}i:8490;a:1:{i:0;i:107;}i:8491;a:1:{i:0;i:229;}i:8492;a:1:{i:0;i:98;}i:8493;a:1:{i:0;i:99;}i:8496;a:1:{i:0;i:101;}i:8497;a:1:{i:0;i:102;}i:8499;a:1:{i:0;i:109;}i:8510;a:1:{i:0;i:947;}i:8511;a:1:{i:0;i:960;}i:8517;a:1:{i:0;i:100;}i:8544;a:1:{i:0;i:8560;}i:8545;a:1:{i:0;i:8561;}i:8546;a:1:{i:0;i:8562;}i:8547;a:1:{i:0;i:8563;}i:8548;a:1:{i:0;i:8564;}i:8549;a:1:{i:0;i:8565;}i:8550;a:1:{i:0;i:8566;}i:8551;a:1:{i:0;i:8567;}i:8552;a:1:{i:0;i:8568;}i:8553;a:1:{i:0;i:8569;}i:8554;a:1:{i:0;i:8570;}i:8555;a:1:{i:0;i:8571;}i:8556;a:1:{i:0;i:8572;}i:8557;a:1:{i:0;i:8573;}i:8558;a:1:{i:0;i:8574;}i:8559;a:1:{i:0;i:8575;}i:9398;a:1:{i:0;i:9424;}i:9399;a:1:{i:0;i:9425;}i:9400;a:1:{i:0;i:9426;}i:9401;a:1:{i:0;i:9427;}i:9402;a:1:{i:0;i:9428;}i:9403;a:1:{i:0;i:9429;}i:9404;a:1:{i:0;i:9430;}i:9405;a:1:{i:0;i:9431;}i:9406;a:1:{i:0;i:9432;}i:9407;a:1:{i:0;i:9433;}i:9408;a:1:{i:0;i:9434;}i:9409;a:1:{i:0;i:9435;}i:9410;a:1:{i:0;i:9436;}i:9411;a:1:{i:0;i:9437;}i:9412;a:1:{i:0;i:9438;}i:9413;a:1:{i:0;i:9439;}i:9414;a:1:{i:0;i:9440;}i:9415;a:1:{i:0;i:9441;}i:9416;a:1:{i:0;i:9442;}i:9417;a:1:{i:0;i:9443;}i:9418;a:1:{i:0;i:9444;}i:9419;a:1:{i:0;i:9445;}i:9420;a:1:{i:0;i:9446;}i:9421;a:1:{i:0;i:9447;}i:9422;a:1:{i:0;i:9448;}i:9423;a:1:{i:0;i:9449;}i:13169;a:3:{i:0;i:104;i:1;i:112;i:2;i:97;}i:13171;a:2:{i:0;i:97;i:1;i:117;}i:13173;a:2:{i:0;i:111;i:1;i:118;}i:13184;a:2:{i:0;i:112;i:1;i:97;}i:13185;a:2:{i:0;i:110;i:1;i:97;}i:13186;a:2:{i:0;i:956;i:1;i:97;}i:13187;a:2:{i:0;i:109;i:1;i:97;}i:13188;a:2:{i:0;i:107;i:1;i:97;}i:13189;a:2:{i:0;i:107;i:1;i:98;}i:13190;a:2:{i:0;i:109;i:1;i:98;}i:13191;a:2:{i:0;i:103;i:1;i:98;}i:13194;a:2:{i:0;i:112;i:1;i:102;}i:13195;a:2:{i:0;i:110;i:1;i:102;}i:13196;a:2:{i:0;i:956;i:1;i:102;}i:13200;a:2:{i:0;i:104;i:1;i:122;}i:13201;a:3:{i:0;i:107;i:1;i:104;i:2;i:122;}i:13202;a:3:{i:0;i:109;i:1;i:104;i:2;i:122;}i:13203;a:3:{i:0;i:103;i:1;i:104;i:2;i:122;}i:13204;a:3:{i:0;i:116;i:1;i:104;i:2;i:122;}i:13225;a:2:{i:0;i:112;i:1;i:97;}i:13226;a:3:{i:0;i:107;i:1;i:112;i:2;i:97;}i:13227;a:3:{i:0;i:109;i:1;i:112;i:2;i:97;}i:13228;a:3:{i:0;i:103;i:1;i:112;i:2;i:97;}i:13236;a:2:{i:0;i:112;i:1;i:118;}i:13237;a:2:{i:0;i:110;i:1;i:118;}i:13238;a:2:{i:0;i:956;i:1;i:118;}i:13239;a:2:{i:0;i:109;i:1;i:118;}i:13240;a:2:{i:0;i:107;i:1;i:118;}i:13241;a:2:{i:0;i:109;i:1;i:118;}i:13242;a:2:{i:0;i:112;i:1;i:119;}i:13243;a:2:{i:0;i:110;i:1;i:119;}i:13244;a:2:{i:0;i:956;i:1;i:119;}i:13245;a:2:{i:0;i:109;i:1;i:119;}i:13246;a:2:{i:0;i:107;i:1;i:119;}i:13247;a:2:{i:0;i:109;i:1;i:119;}i:13248;a:2:{i:0;i:107;i:1;i:969;}i:13249;a:2:{i:0;i:109;i:1;i:969;}i:13251;a:2:{i:0;i:98;i:1;i:113;}i:13254;a:4:{i:0;i:99;i:1;i:8725;i:2;i:107;i:3;i:103;}i:13255;a:3:{i:0;i:99;i:1;i:111;i:2;i:46;}i:13256;a:2:{i:0;i:100;i:1;i:98;}i:13257;a:2:{i:0;i:103;i:1;i:121;}i:13259;a:2:{i:0;i:104;i:1;i:112;}i:13261;a:2:{i:0;i:107;i:1;i:107;}i:13262;a:2:{i:0;i:107;i:1;i:109;}i:13271;a:2:{i:0;i:112;i:1;i:104;}i:13273;a:3:{i:0;i:112;i:1;i:112;i:2;i:109;}i:13274;a:2:{i:0;i:112;i:1;i:114;}i:13276;a:2:{i:0;i:115;i:1;i:118;}i:13277;a:2:{i:0;i:119;i:1;i:98;}i:64256;a:2:{i:0;i:102;i:1;i:102;}i:64257;a:2:{i:0;i:102;i:1;i:105;}i:64258;a:2:{i:0;i:102;i:1;i:108;}i:64259;a:3:{i:0;i:102;i:1;i:102;i:2;i:105;}i:64260;a:3:{i:0;i:102;i:1;i:102;i:2;i:108;}i:64261;a:2:{i:0;i:115;i:1;i:116;}i:64262;a:2:{i:0;i:115;i:1;i:116;}i:64275;a:2:{i:0;i:1396;i:1;i:1398;}i:64276;a:2:{i:0;i:1396;i:1;i:1381;}i:64277;a:2:{i:0;i:1396;i:1;i:1387;}i:64278;a:2:{i:0;i:1406;i:1;i:1398;}i:64279;a:2:{i:0;i:1396;i:1;i:1389;}i:65313;a:1:{i:0;i:65345;}i:65314;a:1:{i:0;i:65346;}i:65315;a:1:{i:0;i:65347;}i:65316;a:1:{i:0;i:65348;}i:65317;a:1:{i:0;i:65349;}i:65318;a:1:{i:0;i:65350;}i:65319;a:1:{i:0;i:65351;}i:65320;a:1:{i:0;i:65352;}i:65321;a:1:{i:0;i:65353;}i:65322;a:1:{i:0;i:65354;}i:65323;a:1:{i:0;i:65355;}i:65324;a:1:{i:0;i:65356;}i:65325;a:1:{i:0;i:65357;}i:65326;a:1:{i:0;i:65358;}i:65327;a:1:{i:0;i:65359;}i:65328;a:1:{i:0;i:65360;}i:65329;a:1:{i:0;i:65361;}i:65330;a:1:{i:0;i:65362;}i:65331;a:1:{i:0;i:65363;}i:65332;a:1:{i:0;i:65364;}i:65333;a:1:{i:0;i:65365;}i:65334;a:1:{i:0;i:65366;}i:65335;a:1:{i:0;i:65367;}i:65336;a:1:{i:0;i:65368;}i:65337;a:1:{i:0;i:65369;}i:65338;a:1:{i:0;i:65370;}i:66560;a:1:{i:0;i:66600;}i:66561;a:1:{i:0;i:66601;}i:66562;a:1:{i:0;i:66602;}i:66563;a:1:{i:0;i:66603;}i:66564;a:1:{i:0;i:66604;}i:66565;a:1:{i:0;i:66605;}i:66566;a:1:{i:0;i:66606;}i:66567;a:1:{i:0;i:66607;}i:66568;a:1:{i:0;i:66608;}i:66569;a:1:{i:0;i:66609;}i:66570;a:1:{i:0;i:66610;}i:66571;a:1:{i:0;i:66611;}i:66572;a:1:{i:0;i:66612;}i:66573;a:1:{i:0;i:66613;}i:66574;a:1:{i:0;i:66614;}i:66575;a:1:{i:0;i:66615;}i:66576;a:1:{i:0;i:66616;}i:66577;a:1:{i:0;i:66617;}i:66578;a:1:{i:0;i:66618;}i:66579;a:1:{i:0;i:66619;}i:66580;a:1:{i:0;i:66620;}i:66581;a:1:{i:0;i:66621;}i:66582;a:1:{i:0;i:66622;}i:66583;a:1:{i:0;i:66623;}i:66584;a:1:{i:0;i:66624;}i:66585;a:1:{i:0;i:66625;}i:66586;a:1:{i:0;i:66626;}i:66587;a:1:{i:0;i:66627;}i:66588;a:1:{i:0;i:66628;}i:66589;a:1:{i:0;i:66629;}i:66590;a:1:{i:0;i:66630;}i:66591;a:1:{i:0;i:66631;}i:66592;a:1:{i:0;i:66632;}i:66593;a:1:{i:0;i:66633;}i:66594;a:1:{i:0;i:66634;}i:66595;a:1:{i:0;i:66635;}i:66596;a:1:{i:0;i:66636;}i:66597;a:1:{i:0;i:66637;}i:119808;a:1:{i:0;i:97;}i:119809;a:1:{i:0;i:98;}i:119810;a:1:{i:0;i:99;}i:119811;a:1:{i:0;i:100;}i:119812;a:1:{i:0;i:101;}i:119813;a:1:{i:0;i:102;}i:119814;a:1:{i:0;i:103;}i:119815;a:1:{i:0;i:104;}i:119816;a:1:{i:0;i:105;}i:119817;a:1:{i:0;i:106;}i:119818;a:1:{i:0;i:107;}i:119819;a:1:{i:0;i:108;}i:119820;a:1:{i:0;i:109;}i:119821;a:1:{i:0;i:110;}i:119822;a:1:{i:0;i:111;}i:119823;a:1:{i:0;i:112;}i:119824;a:1:{i:0;i:113;}i:119825;a:1:{i:0;i:114;}i:119826;a:1:{i:0;i:115;}i:119827;a:1:{i:0;i:116;}i:119828;a:1:{i:0;i:117;}i:119829;a:1:{i:0;i:118;}i:119830;a:1:{i:0;i:119;}i:119831;a:1:{i:0;i:120;}i:119832;a:1:{i:0;i:121;}i:119833;a:1:{i:0;i:122;}i:119860;a:1:{i:0;i:97;}i:119861;a:1:{i:0;i:98;}i:119862;a:1:{i:0;i:99;}i:119863;a:1:{i:0;i:100;}i:119864;a:1:{i:0;i:101;}i:119865;a:1:{i:0;i:102;}i:119866;a:1:{i:0;i:103;}i:119867;a:1:{i:0;i:104;}i:119868;a:1:{i:0;i:105;}i:119869;a:1:{i:0;i:106;}i:119870;a:1:{i:0;i:107;}i:119871;a:1:{i:0;i:108;}i:119872;a:1:{i:0;i:109;}i:119873;a:1:{i:0;i:110;}i:119874;a:1:{i:0;i:111;}i:119875;a:1:{i:0;i:112;}i:119876;a:1:{i:0;i:113;}i:119877;a:1:{i:0;i:114;}i:119878;a:1:{i:0;i:115;}i:119879;a:1:{i:0;i:116;}i:119880;a:1:{i:0;i:117;}i:119881;a:1:{i:0;i:118;}i:119882;a:1:{i:0;i:119;}i:119883;a:1:{i:0;i:120;}i:119884;a:1:{i:0;i:121;}i:119885;a:1:{i:0;i:122;}i:119912;a:1:{i:0;i:97;}i:119913;a:1:{i:0;i:98;}i:119914;a:1:{i:0;i:99;}i:119915;a:1:{i:0;i:100;}i:119916;a:1:{i:0;i:101;}i:119917;a:1:{i:0;i:102;}i:119918;a:1:{i:0;i:103;}i:119919;a:1:{i:0;i:104;}i:119920;a:1:{i:0;i:105;}i:119921;a:1:{i:0;i:106;}i:119922;a:1:{i:0;i:107;}i:119923;a:1:{i:0;i:108;}i:119924;a:1:{i:0;i:109;}i:119925;a:1:{i:0;i:110;}i:119926;a:1:{i:0;i:111;}i:119927;a:1:{i:0;i:112;}i:119928;a:1:{i:0;i:113;}i:119929;a:1:{i:0;i:114;}i:119930;a:1:{i:0;i:115;}i:119931;a:1:{i:0;i:116;}i:119932;a:1:{i:0;i:117;}i:119933;a:1:{i:0;i:118;}i:119934;a:1:{i:0;i:119;}i:119935;a:1:{i:0;i:120;}i:119936;a:1:{i:0;i:121;}i:119937;a:1:{i:0;i:122;}i:119964;a:1:{i:0;i:97;}i:119966;a:1:{i:0;i:99;}i:119967;a:1:{i:0;i:100;}i:119970;a:1:{i:0;i:103;}i:119973;a:1:{i:0;i:106;}i:119974;a:1:{i:0;i:107;}i:119977;a:1:{i:0;i:110;}i:119978;a:1:{i:0;i:111;}i:119979;a:1:{i:0;i:112;}i:119980;a:1:{i:0;i:113;}i:119982;a:1:{i:0;i:115;}i:119983;a:1:{i:0;i:116;}i:119984;a:1:{i:0;i:117;}i:119985;a:1:{i:0;i:118;}i:119986;a:1:{i:0;i:119;}i:119987;a:1:{i:0;i:120;}i:119988;a:1:{i:0;i:121;}i:119989;a:1:{i:0;i:122;}i:120016;a:1:{i:0;i:97;}i:120017;a:1:{i:0;i:98;}i:120018;a:1:{i:0;i:99;}i:120019;a:1:{i:0;i:100;}i:120020;a:1:{i:0;i:101;}i:120021;a:1:{i:0;i:102;}i:120022;a:1:{i:0;i:103;}i:120023;a:1:{i:0;i:104;}i:120024;a:1:{i:0;i:105;}i:120025;a:1:{i:0;i:106;}i:120026;a:1:{i:0;i:107;}i:120027;a:1:{i:0;i:108;}i:120028;a:1:{i:0;i:109;}i:120029;a:1:{i:0;i:110;}i:120030;a:1:{i:0;i:111;}i:120031;a:1:{i:0;i:112;}i:120032;a:1:{i:0;i:113;}i:120033;a:1:{i:0;i:114;}i:120034;a:1:{i:0;i:115;}i:120035;a:1:{i:0;i:116;}i:120036;a:1:{i:0;i:117;}i:120037;a:1:{i:0;i:118;}i:120038;a:1:{i:0;i:119;}i:120039;a:1:{i:0;i:120;}i:120040;a:1:{i:0;i:121;}i:120041;a:1:{i:0;i:122;}i:120068;a:1:{i:0;i:97;}i:120069;a:1:{i:0;i:98;}i:120071;a:1:{i:0;i:100;}i:120072;a:1:{i:0;i:101;}i:120073;a:1:{i:0;i:102;}i:120074;a:1:{i:0;i:103;}i:120077;a:1:{i:0;i:106;}i:120078;a:1:{i:0;i:107;}i:120079;a:1:{i:0;i:108;}i:120080;a:1:{i:0;i:109;}i:120081;a:1:{i:0;i:110;}i:120082;a:1:{i:0;i:111;}i:120083;a:1:{i:0;i:112;}i:120084;a:1:{i:0;i:113;}i:120086;a:1:{i:0;i:115;}i:120087;a:1:{i:0;i:116;}i:120088;a:1:{i:0;i:117;}i:120089;a:1:{i:0;i:118;}i:120090;a:1:{i:0;i:119;}i:120091;a:1:{i:0;i:120;}i:120092;a:1:{i:0;i:121;}i:120120;a:1:{i:0;i:97;}i:120121;a:1:{i:0;i:98;}i:120123;a:1:{i:0;i:100;}i:120124;a:1:{i:0;i:101;}i:120125;a:1:{i:0;i:102;}i:120126;a:1:{i:0;i:103;}i:120128;a:1:{i:0;i:105;}i:120129;a:1:{i:0;i:106;}i:120130;a:1:{i:0;i:107;}i:120131;a:1:{i:0;i:108;}i:120132;a:1:{i:0;i:109;}i:120134;a:1:{i:0;i:111;}i:120138;a:1:{i:0;i:115;}i:120139;a:1:{i:0;i:116;}i:120140;a:1:{i:0;i:117;}i:120141;a:1:{i:0;i:118;}i:120142;a:1:{i:0;i:119;}i:120143;a:1:{i:0;i:120;}i:120144;a:1:{i:0;i:121;}i:120172;a:1:{i:0;i:97;}i:120173;a:1:{i:0;i:98;}i:120174;a:1:{i:0;i:99;}i:120175;a:1:{i:0;i:100;}i:120176;a:1:{i:0;i:101;}i:120177;a:1:{i:0;i:102;}i:120178;a:1:{i:0;i:103;}i:120179;a:1:{i:0;i:104;}i:120180;a:1:{i:0;i:105;}i:120181;a:1:{i:0;i:106;}i:120182;a:1:{i:0;i:107;}i:120183;a:1:{i:0;i:108;}i:120184;a:1:{i:0;i:109;}i:120185;a:1:{i:0;i:110;}i:120186;a:1:{i:0;i:111;}i:120187;a:1:{i:0;i:112;}i:120188;a:1:{i:0;i:113;}i:120189;a:1:{i:0;i:114;}i:120190;a:1:{i:0;i:115;}i:120191;a:1:{i:0;i:116;}i:120192;a:1:{i:0;i:117;}i:120193;a:1:{i:0;i:118;}i:120194;a:1:{i:0;i:119;}i:120195;a:1:{i:0;i:120;}i:120196;a:1:{i:0;i:121;}i:120197;a:1:{i:0;i:122;}i:120224;a:1:{i:0;i:97;}i:120225;a:1:{i:0;i:98;}i:120226;a:1:{i:0;i:99;}i:120227;a:1:{i:0;i:100;}i:120228;a:1:{i:0;i:101;}i:120229;a:1:{i:0;i:102;}i:120230;a:1:{i:0;i:103;}i:120231;a:1:{i:0;i:104;}i:120232;a:1:{i:0;i:105;}i:120233;a:1:{i:0;i:106;}i:120234;a:1:{i:0;i:107;}i:120235;a:1:{i:0;i:108;}i:120236;a:1:{i:0;i:109;}i:120237;a:1:{i:0;i:110;}i:120238;a:1:{i:0;i:111;}i:120239;a:1:{i:0;i:112;}i:120240;a:1:{i:0;i:113;}i:120241;a:1:{i:0;i:114;}i:120242;a:1:{i:0;i:115;}i:120243;a:1:{i:0;i:116;}i:120244;a:1:{i:0;i:117;}i:120245;a:1:{i:0;i:118;}i:120246;a:1:{i:0;i:119;}i:120247;a:1:{i:0;i:120;}i:120248;a:1:{i:0;i:121;}i:120249;a:1:{i:0;i:122;}i:120276;a:1:{i:0;i:97;}i:120277;a:1:{i:0;i:98;}i:120278;a:1:{i:0;i:99;}i:120279;a:1:{i:0;i:100;}i:120280;a:1:{i:0;i:101;}i:120281;a:1:{i:0;i:102;}i:120282;a:1:{i:0;i:103;}i:120283;a:1:{i:0;i:104;}i:120284;a:1:{i:0;i:105;}i:120285;a:1:{i:0;i:106;}i:120286;a:1:{i:0;i:107;}i:120287;a:1:{i:0;i:108;}i:120288;a:1:{i:0;i:109;}i:120289;a:1:{i:0;i:110;}i:120290;a:1:{i:0;i:111;}i:120291;a:1:{i:0;i:112;}i:120292;a:1:{i:0;i:113;}i:120293;a:1:{i:0;i:114;}i:120294;a:1:{i:0;i:115;}i:120295;a:1:{i:0;i:116;}i:120296;a:1:{i:0;i:117;}i:120297;a:1:{i:0;i:118;}i:120298;a:1:{i:0;i:119;}i:120299;a:1:{i:0;i:120;}i:120300;a:1:{i:0;i:121;}i:120301;a:1:{i:0;i:122;}i:120328;a:1:{i:0;i:97;}i:120329;a:1:{i:0;i:98;}i:120330;a:1:{i:0;i:99;}i:120331;a:1:{i:0;i:100;}i:120332;a:1:{i:0;i:101;}i:120333;a:1:{i:0;i:102;}i:120334;a:1:{i:0;i:103;}i:120335;a:1:{i:0;i:104;}i:120336;a:1:{i:0;i:105;}i:120337;a:1:{i:0;i:106;}i:120338;a:1:{i:0;i:107;}i:120339;a:1:{i:0;i:108;}i:120340;a:1:{i:0;i:109;}i:120341;a:1:{i:0;i:110;}i:120342;a:1:{i:0;i:111;}i:120343;a:1:{i:0;i:112;}i:120344;a:1:{i:0;i:113;}i:120345;a:1:{i:0;i:114;}i:120346;a:1:{i:0;i:115;}i:120347;a:1:{i:0;i:116;}i:120348;a:1:{i:0;i:117;}i:120349;a:1:{i:0;i:118;}i:120350;a:1:{i:0;i:119;}i:120351;a:1:{i:0;i:120;}i:120352;a:1:{i:0;i:121;}i:120353;a:1:{i:0;i:122;}i:120380;a:1:{i:0;i:97;}i:120381;a:1:{i:0;i:98;}i:120382;a:1:{i:0;i:99;}i:120383;a:1:{i:0;i:100;}i:120384;a:1:{i:0;i:101;}i:120385;a:1:{i:0;i:102;}i:120386;a:1:{i:0;i:103;}i:120387;a:1:{i:0;i:104;}i:120388;a:1:{i:0;i:105;}i:120389;a:1:{i:0;i:106;}i:120390;a:1:{i:0;i:107;}i:120391;a:1:{i:0;i:108;}i:120392;a:1:{i:0;i:109;}i:120393;a:1:{i:0;i:110;}i:120394;a:1:{i:0;i:111;}i:120395;a:1:{i:0;i:112;}i:120396;a:1:{i:0;i:113;}i:120397;a:1:{i:0;i:114;}i:120398;a:1:{i:0;i:115;}i:120399;a:1:{i:0;i:116;}i:120400;a:1:{i:0;i:117;}i:120401;a:1:{i:0;i:118;}i:120402;a:1:{i:0;i:119;}i:120403;a:1:{i:0;i:120;}i:120404;a:1:{i:0;i:121;}i:120405;a:1:{i:0;i:122;}i:120432;a:1:{i:0;i:97;}i:120433;a:1:{i:0;i:98;}i:120434;a:1:{i:0;i:99;}i:120435;a:1:{i:0;i:100;}i:120436;a:1:{i:0;i:101;}i:120437;a:1:{i:0;i:102;}i:120438;a:1:{i:0;i:103;}i:120439;a:1:{i:0;i:104;}i:120440;a:1:{i:0;i:105;}i:120441;a:1:{i:0;i:106;}i:120442;a:1:{i:0;i:107;}i:120443;a:1:{i:0;i:108;}i:120444;a:1:{i:0;i:109;}i:120445;a:1:{i:0;i:110;}i:120446;a:1:{i:0;i:111;}i:120447;a:1:{i:0;i:112;}i:120448;a:1:{i:0;i:113;}i:120449;a:1:{i:0;i:114;}i:120450;a:1:{i:0;i:115;}i:120451;a:1:{i:0;i:116;}i:120452;a:1:{i:0;i:117;}i:120453;a:1:{i:0;i:118;}i:120454;a:1:{i:0;i:119;}i:120455;a:1:{i:0;i:120;}i:120456;a:1:{i:0;i:121;}i:120457;a:1:{i:0;i:122;}i:120488;a:1:{i:0;i:945;}i:120489;a:1:{i:0;i:946;}i:120490;a:1:{i:0;i:947;}i:120491;a:1:{i:0;i:948;}i:120492;a:1:{i:0;i:949;}i:120493;a:1:{i:0;i:950;}i:120494;a:1:{i:0;i:951;}i:120495;a:1:{i:0;i:952;}i:120496;a:1:{i:0;i:953;}i:120497;a:1:{i:0;i:954;}i:120498;a:1:{i:0;i:955;}i:120499;a:1:{i:0;i:956;}i:120500;a:1:{i:0;i:957;}i:120501;a:1:{i:0;i:958;}i:120502;a:1:{i:0;i:959;}i:120503;a:1:{i:0;i:960;}i:120504;a:1:{i:0;i:961;}i:120505;a:1:{i:0;i:952;}i:120506;a:1:{i:0;i:963;}i:120507;a:1:{i:0;i:964;}i:120508;a:1:{i:0;i:965;}i:120509;a:1:{i:0;i:966;}i:120510;a:1:{i:0;i:967;}i:120511;a:1:{i:0;i:968;}i:120512;a:1:{i:0;i:969;}i:120531;a:1:{i:0;i:963;}i:120546;a:1:{i:0;i:945;}i:120547;a:1:{i:0;i:946;}i:120548;a:1:{i:0;i:947;}i:120549;a:1:{i:0;i:948;}i:120550;a:1:{i:0;i:949;}i:120551;a:1:{i:0;i:950;}i:120552;a:1:{i:0;i:951;}i:120553;a:1:{i:0;i:952;}i:120554;a:1:{i:0;i:953;}i:120555;a:1:{i:0;i:954;}i:120556;a:1:{i:0;i:955;}i:120557;a:1:{i:0;i:956;}i:120558;a:1:{i:0;i:957;}i:120559;a:1:{i:0;i:958;}i:120560;a:1:{i:0;i:959;}i:120561;a:1:{i:0;i:960;}i:120562;a:1:{i:0;i:961;}i:120563;a:1:{i:0;i:952;}i:120564;a:1:{i:0;i:963;}i:120565;a:1:{i:0;i:964;}i:120566;a:1:{i:0;i:965;}i:120567;a:1:{i:0;i:966;}i:120568;a:1:{i:0;i:967;}i:120569;a:1:{i:0;i:968;}i:120570;a:1:{i:0;i:969;}i:120589;a:1:{i:0;i:963;}i:120604;a:1:{i:0;i:945;}i:120605;a:1:{i:0;i:946;}i:120606;a:1:{i:0;i:947;}i:120607;a:1:{i:0;i:948;}i:120608;a:1:{i:0;i:949;}i:120609;a:1:{i:0;i:950;}i:120610;a:1:{i:0;i:951;}i:120611;a:1:{i:0;i:952;}i:120612;a:1:{i:0;i:953;}i:120613;a:1:{i:0;i:954;}i:120614;a:1:{i:0;i:955;}i:120615;a:1:{i:0;i:956;}i:120616;a:1:{i:0;i:957;}i:120617;a:1:{i:0;i:958;}i:120618;a:1:{i:0;i:959;}i:120619;a:1:{i:0;i:960;}i:120620;a:1:{i:0;i:961;}i:120621;a:1:{i:0;i:952;}i:120622;a:1:{i:0;i:963;}i:120623;a:1:{i:0;i:964;}i:120624;a:1:{i:0;i:965;}i:120625;a:1:{i:0;i:966;}i:120626;a:1:{i:0;i:967;}i:120627;a:1:{i:0;i:968;}i:120628;a:1:{i:0;i:969;}i:120647;a:1:{i:0;i:963;}i:120662;a:1:{i:0;i:945;}i:120663;a:1:{i:0;i:946;}i:120664;a:1:{i:0;i:947;}i:120665;a:1:{i:0;i:948;}i:120666;a:1:{i:0;i:949;}i:120667;a:1:{i:0;i:950;}i:120668;a:1:{i:0;i:951;}i:120669;a:1:{i:0;i:952;}i:120670;a:1:{i:0;i:953;}i:120671;a:1:{i:0;i:954;}i:120672;a:1:{i:0;i:955;}i:120673;a:1:{i:0;i:956;}i:120674;a:1:{i:0;i:957;}i:120675;a:1:{i:0;i:958;}i:120676;a:1:{i:0;i:959;}i:120677;a:1:{i:0;i:960;}i:120678;a:1:{i:0;i:961;}i:120679;a:1:{i:0;i:952;}i:120680;a:1:{i:0;i:963;}i:120681;a:1:{i:0;i:964;}i:120682;a:1:{i:0;i:965;}i:120683;a:1:{i:0;i:966;}i:120684;a:1:{i:0;i:967;}i:120685;a:1:{i:0;i:968;}i:120686;a:1:{i:0;i:969;}i:120705;a:1:{i:0;i:963;}i:120720;a:1:{i:0;i:945;}i:120721;a:1:{i:0;i:946;}i:120722;a:1:{i:0;i:947;}i:120723;a:1:{i:0;i:948;}i:120724;a:1:{i:0;i:949;}i:120725;a:1:{i:0;i:950;}i:120726;a:1:{i:0;i:951;}i:120727;a:1:{i:0;i:952;}i:120728;a:1:{i:0;i:953;}i:120729;a:1:{i:0;i:954;}i:120730;a:1:{i:0;i:955;}i:120731;a:1:{i:0;i:956;}i:120732;a:1:{i:0;i:957;}i:120733;a:1:{i:0;i:958;}i:120734;a:1:{i:0;i:959;}i:120735;a:1:{i:0;i:960;}i:120736;a:1:{i:0;i:961;}i:120737;a:1:{i:0;i:952;}i:120738;a:1:{i:0;i:963;}i:120739;a:1:{i:0;i:964;}i:120740;a:1:{i:0;i:965;}i:120741;a:1:{i:0;i:966;}i:120742;a:1:{i:0;i:967;}i:120743;a:1:{i:0;i:968;}i:120744;a:1:{i:0;i:969;}i:120763;a:1:{i:0;i:963;}i:1017;a:1:{i:0;i:963;}i:7468;a:1:{i:0;i:97;}i:7469;a:1:{i:0;i:230;}i:7470;a:1:{i:0;i:98;}i:7472;a:1:{i:0;i:100;}i:7473;a:1:{i:0;i:101;}i:7474;a:1:{i:0;i:477;}i:7475;a:1:{i:0;i:103;}i:7476;a:1:{i:0;i:104;}i:7477;a:1:{i:0;i:105;}i:7478;a:1:{i:0;i:106;}i:7479;a:1:{i:0;i:107;}i:7480;a:1:{i:0;i:108;}i:7481;a:1:{i:0;i:109;}i:7482;a:1:{i:0;i:110;}i:7484;a:1:{i:0;i:111;}i:7485;a:1:{i:0;i:547;}i:7486;a:1:{i:0;i:112;}i:7487;a:1:{i:0;i:114;}i:7488;a:1:{i:0;i:116;}i:7489;a:1:{i:0;i:117;}i:7490;a:1:{i:0;i:119;}i:8507;a:3:{i:0;i:102;i:1;i:97;i:2;i:120;}i:12880;a:3:{i:0;i:112;i:1;i:116;i:2;i:101;}i:13004;a:2:{i:0;i:104;i:1;i:103;}i:13006;a:2:{i:0;i:101;i:1;i:118;}i:13007;a:3:{i:0;i:108;i:1;i:116;i:2;i:100;}i:13178;a:2:{i:0;i:105;i:1;i:117;}i:13278;a:3:{i:0;i:118;i:1;i:8725;i:2;i:109;}i:13279;a:3:{i:0;i:97;i:1;i:8725;i:2;i:109;}}s:12:"norm_combcls";a:341:{i:820;i:1;i:821;i:1;i:822;i:1;i:823;i:1;i:824;i:1;i:2364;i:7;i:2492;i:7;i:2620;i:7;i:2748;i:7;i:2876;i:7;i:3260;i:7;i:4151;i:7;i:12441;i:8;i:12442;i:8;i:2381;i:9;i:2509;i:9;i:2637;i:9;i:2765;i:9;i:2893;i:9;i:3021;i:9;i:3149;i:9;i:3277;i:9;i:3405;i:9;i:3530;i:9;i:3642;i:9;i:3972;i:9;i:4153;i:9;i:5908;i:9;i:5940;i:9;i:6098;i:9;i:1456;i:10;i:1457;i:11;i:1458;i:12;i:1459;i:13;i:1460;i:14;i:1461;i:15;i:1462;i:16;i:1463;i:17;i:1464;i:18;i:1465;i:19;i:1467;i:20;i:1468;i:21;i:1469;i:22;i:1471;i:23;i:1473;i:24;i:1474;i:25;i:64286;i:26;i:1611;i:27;i:1612;i:28;i:1613;i:29;i:1614;i:30;i:1615;i:31;i:1616;i:32;i:1617;i:33;i:1618;i:34;i:1648;i:35;i:1809;i:36;i:3157;i:84;i:3158;i:91;i:3640;i:103;i:3641;i:103;i:3656;i:107;i:3657;i:107;i:3658;i:107;i:3659;i:107;i:3768;i:118;i:3769;i:118;i:3784;i:122;i:3785;i:122;i:3786;i:122;i:3787;i:122;i:3953;i:129;i:3954;i:130;i:3962;i:130;i:3963;i:130;i:3964;i:130;i:3965;i:130;i:3968;i:130;i:3956;i:132;i:801;i:202;i:802;i:202;i:807;i:202;i:808;i:202;i:795;i:216;i:3897;i:216;i:119141;i:216;i:119142;i:216;i:119150;i:216;i:119151;i:216;i:119152;i:216;i:119153;i:216;i:119154;i:216;i:12330;i:218;i:790;i:220;i:791;i:220;i:792;i:220;i:793;i:220;i:796;i:220;i:797;i:220;i:798;i:220;i:799;i:220;i:800;i:220;i:803;i:220;i:804;i:220;i:805;i:220;i:806;i:220;i:809;i:220;i:810;i:220;i:811;i:220;i:812;i:220;i:813;i:220;i:814;i:220;i:815;i:220;i:816;i:220;i:817;i:220;i:818;i:220;i:819;i:220;i:825;i:220;i:826;i:220;i:827;i:220;i:828;i:220;i:839;i:220;i:840;i:220;i:841;i:220;i:845;i:220;i:846;i:220;i:851;i:220;i:852;i:220;i:853;i:220;i:854;i:220;i:1425;i:220;i:1430;i:220;i:1435;i:220;i:1443;i:220;i:1444;i:220;i:1445;i:220;i:1446;i:220;i:1447;i:220;i:1450;i:220;i:1621;i:220;i:1622;i:220;i:1763;i:220;i:1770;i:220;i:1773;i:220;i:1841;i:220;i:1844;i:220;i:1847;i:220;i:1848;i:220;i:1849;i:220;i:1851;i:220;i:1852;i:220;i:1854;i:220;i:1858;i:220;i:1860;i:220;i:1862;i:220;i:1864;i:220;i:2386;i:220;i:3864;i:220;i:3865;i:220;i:3893;i:220;i:3895;i:220;i:4038;i:220;i:6459;i:220;i:8424;i:220;i:119163;i:220;i:119164;i:220;i:119165;i:220;i:119166;i:220;i:119167;i:220;i:119168;i:220;i:119169;i:220;i:119170;i:220;i:119178;i:220;i:119179;i:220;i:1434;i:222;i:1453;i:222;i:6441;i:222;i:12333;i:222;i:12334;i:224;i:12335;i:224;i:119149;i:226;i:1454;i:228;i:6313;i:228;i:12331;i:228;i:768;i:230;i:769;i:230;i:770;i:230;i:771;i:230;i:772;i:230;i:773;i:230;i:774;i:230;i:775;i:230;i:776;i:230;i:777;i:230;i:778;i:230;i:779;i:230;i:780;i:230;i:781;i:230;i:782;i:230;i:783;i:230;i:784;i:230;i:785;i:230;i:786;i:230;i:787;i:230;i:788;i:230;i:829;i:230;i:830;i:230;i:831;i:230;i:832;i:230;i:833;i:230;i:834;i:230;i:835;i:230;i:836;i:230;i:838;i:230;i:842;i:230;i:843;i:230;i:844;i:230;i:848;i:230;i:849;i:230;i:850;i:230;i:855;i:230;i:867;i:230;i:868;i:230;i:869;i:230;i:870;i:230;i:871;i:230;i:872;i:230;i:873;i:230;i:874;i:230;i:875;i:230;i:876;i:230;i:877;i:230;i:878;i:230;i:879;i:230;i:1155;i:230;i:1156;i:230;i:1157;i:230;i:1158;i:230;i:1426;i:230;i:1427;i:230;i:1428;i:230;i:1429;i:230;i:1431;i:230;i:1432;i:230;i:1433;i:230;i:1436;i:230;i:1437;i:230;i:1438;i:230;i:1439;i:230;i:1440;i:230;i:1441;i:230;i:1448;i:230;i:1449;i:230;i:1451;i:230;i:1452;i:230;i:1455;i:230;i:1476;i:230;i:1552;i:230;i:1553;i:230;i:1554;i:230;i:1555;i:230;i:1556;i:230;i:1557;i:230;i:1619;i:230;i:1620;i:230;i:1623;i:230;i:1624;i:230;i:1750;i:230;i:1751;i:230;i:1752;i:230;i:1753;i:230;i:1754;i:230;i:1755;i:230;i:1756;i:230;i:1759;i:230;i:1760;i:230;i:1761;i:230;i:1762;i:230;i:1764;i:230;i:1767;i:230;i:1768;i:230;i:1771;i:230;i:1772;i:230;i:1840;i:230;i:1842;i:230;i:1843;i:230;i:1845;i:230;i:1846;i:230;i:1850;i:230;i:1853;i:230;i:1855;i:230;i:1856;i:230;i:1857;i:230;i:1859;i:230;i:1861;i:230;i:1863;i:230;i:1865;i:230;i:1866;i:230;i:2385;i:230;i:2387;i:230;i:2388;i:230;i:3970;i:230;i:3971;i:230;i:3974;i:230;i:3975;i:230;i:5901;i:230;i:6458;i:230;i:8400;i:230;i:8401;i:230;i:8404;i:230;i:8405;i:230;i:8406;i:230;i:8407;i:230;i:8411;i:230;i:8412;i:230;i:8417;i:230;i:8423;i:230;i:8425;i:230;i:65056;i:230;i:65057;i:230;i:65058;i:230;i:65059;i:230;i:119173;i:230;i:119174;i:230;i:119175;i:230;i:119177;i:230;i:119176;i:230;i:119210;i:230;i:119211;i:230;i:119212;i:230;i:119213;i:230;i:789;i:232;i:794;i:232;i:12332;i:232;i:863;i:233;i:866;i:233;i:861;i:234;i:862;i:234;i:864;i:234;i:865;i:234;i:837;i:240;}}vendor/simplepie/simplepie/LICENSE.txt000064400000003001152177723700013647 0ustar00Copyright (c) 2004-2007, Ryan Parman and Geoffrey Sneddon.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are 
permitted provided that the following conditions are met:

	* Redistributions of source code must retain the above copyright notice, this list of 
	  conditions and the following disclaimer.

	* Redistributions in binary form must reproduce the above copyright notice, this list 
	  of conditions and the following disclaimer in the documentation and/or other materials 
	  provided with the distribution.

	* Neither the name of the SimplePie Team nor the names of its contributors may be used 
	  to endorse or promote products derived from this software without specific prior 
	  written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS 
AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.vendor/simplepie/simplepie/db.sql000064400000001420152177723700013135 0ustar00/* SQLite */
CREATE TABLE cache_data (
	id TEXT NOT NULL,
	items SMALLINT NOT NULL DEFAULT 0,
	data BLOB NOT NULL,
	mtime INTEGER UNSIGNED NOT NULL
);
CREATE UNIQUE INDEX id ON cache_data(id);

CREATE TABLE items (
	feed_id TEXT NOT NULL,
	id TEXT NOT NULL,
	data TEXT NOT NULL,
	posted INTEGER UNSIGNED NOT NULL
);
CREATE INDEX feed_id ON items(feed_id);


/* MySQL */
CREATE TABLE `cache_data` (
	`id` TEXT CHARACTER SET utf8 NOT NULL,
	`items` SMALLINT NOT NULL DEFAULT 0,
	`data` BLOB NOT NULL,
	`mtime` INT UNSIGNED NOT NULL,
	UNIQUE (
		`id`(125)
	)
);

CREATE TABLE `items` (
	`feed_id` TEXT CHARACTER SET utf8 NOT NULL,
	`id` TEXT CHARACTER SET utf8 NOT NULL,
	`data` TEXT CHARACTER SET utf8 NOT NULL,
	`posted` INT UNSIGNED NOT NULL,
	INDEX `feed_id` (
		`feed_id`(125)
	)
);vendor/symfony/polyfill-php71/LICENSE000064400000002051152177723700013352 0ustar00Copyright (c) 2015-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-php71/bootstrap.php000064400000000653152177723700015101 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php71 as p;

if (PHP_VERSION_ID < 70100) {
    if (!function_exists('is_iterable')) {
        function is_iterable($var) { return p\Php71::is_iterable($var); }
    }
}
vendor/symfony/polyfill-php71/Php71.php000064400000000762152177723700013764 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php71;

/**
 * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
 *
 * @internal
 */
final class Php71
{
    public static function is_iterable($var)
    {
        return \is_array($var) || $var instanceof \Traversable;
    }
}
vendor/symfony/polyfill-php73/Resources/stubs/JsonException.php000064400000000430152177723700020761 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

class JsonException extends Exception
{
}
vendor/symfony/polyfill-php73/LICENSE000064400000002051152177723700013354 0ustar00Copyright (c) 2018-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-php73/bootstrap.php000064400000001725152177723700015104 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php73 as p;

if (PHP_VERSION_ID < 70300) {
    if (!function_exists('is_countable')) {
        function is_countable($var) { return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; }
    }

    if (!function_exists('hrtime')) {
        p\Php73::$startAt = (int) microtime(true);
        function hrtime($asNum = false) { return p\Php73::hrtime($asNum); }
    }

    if (!function_exists('array_key_first')) {
        function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
    }

    if (!function_exists('array_key_last')) {
        function array_key_last(array $array) { end($array); return key($array); }
    }
}
vendor/symfony/polyfill-php73/Php73.php000064400000001202152177723700013756 0ustar00<?php

namespace Symfony\Polyfill\Php73;

/**
 * @author Gabriel Caruso <carusogabriel34@gmail.com>
 * @author Ion Bazan <ion.bazan@gmail.com>
 *
 * @internal
 */
final class Php73
{
    public static $startAt = 1533462603;

    /**
     * @param bool $asNum
     *
     * @return array|float|int
     */
    public static function hrtime($asNum = false)
    {
        $ns = \microtime(false);
        $s = \substr($ns, 11) - self::$startAt;
        $ns = 1E9 * (float) $ns;

        if ($asNum) {
            $ns += $s * 1E9;

            return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
        }

        return array($s, (int) $ns);
    }
}
vendor/symfony/polyfill-php56/LICENSE000064400000002051152177723700013355 0ustar00Copyright (c) 2015-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-php56/bootstrap.php000064400000002752152177723700015106 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php56 as p;

if (PHP_VERSION_ID < 50600) {
    if (!function_exists('hash_equals')) {
        function hash_equals($knownString, $userInput) { return p\Php56::hash_equals($knownString, $userInput); }
    }
    if (extension_loaded('ldap') && !function_exists('ldap_escape')) {
        define('LDAP_ESCAPE_FILTER', 1);
        define('LDAP_ESCAPE_DN', 2);

        function ldap_escape($subject, $ignore = '', $flags = 0) { return p\Php56::ldap_escape($subject, $ignore, $flags); }
    }

    if (50509 === PHP_VERSION_ID && 4 === PHP_INT_SIZE) {
        // Missing functions in PHP 5.5.9 - affects 32 bit builds of Ubuntu 14.04LTS
        // See https://bugs.launchpad.net/ubuntu/+source/php5/+bug/1315888
        if (!function_exists('gzopen') && function_exists('gzopen64')) {
            function gzopen($filename, $mode, $use_include_path = 0) { return gzopen64($filename, $mode, $use_include_path); }
        }
        if (!function_exists('gzseek') && function_exists('gzseek64')) {
            function gzseek($zp, $offset, $whence = SEEK_SET) { return gzseek64($zp, $offset, $whence); }
        }
        if (!function_exists('gztell') && function_exists('gztell64')) {
            function gztell($zp) { return gztell64($zp); }
        }
    }
}
vendor/symfony/polyfill-php56/Php56.php000064400000007467152177723700014003 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php56;

use Symfony\Polyfill\Util\Binary;

/**
 * @internal
 */
final class Php56
{
    const LDAP_ESCAPE_FILTER = 1;
    const LDAP_ESCAPE_DN = 2;

    public static function hash_equals($knownString, $userInput)
    {
        if (!\is_string($knownString)) {
            trigger_error('Expected known_string to be a string, '.\gettype($knownString).' given', E_USER_WARNING);

            return false;
        }

        if (!\is_string($userInput)) {
            trigger_error('Expected user_input to be a string, '.\gettype($userInput).' given', E_USER_WARNING);

            return false;
        }

        $knownLen = Binary::strlen($knownString);
        $userLen = Binary::strlen($userInput);

        if ($knownLen !== $userLen) {
            return false;
        }

        $result = 0;

        for ($i = 0; $i < $knownLen; ++$i) {
            $result |= \ord($knownString[$i]) ^ \ord($userInput[$i]);
        }

        return 0 === $result;
    }

    /**
     * Stub implementation of the {@link ldap_escape()} function of the ldap
     * extension.
     *
     * Escape strings for safe use in LDAP filters and DNs.
     *
     * @author Chris Wright <ldapi@daverandom.com>
     *
     * @param string $subject
     * @param string $ignore
     * @param int    $flags
     *
     * @return string
     *
     * @see http://stackoverflow.com/a/8561604
     */
    public static function ldap_escape($subject, $ignore = '', $flags = 0)
    {
        static $charMaps = null;

        if (null === $charMaps) {
            $charMaps = array(
                self::LDAP_ESCAPE_FILTER => array('\\', '*', '(', ')', "\x00"),
                self::LDAP_ESCAPE_DN => array('\\', ',', '=', '+', '<', '>', ';', '"', '#', "\r"),
            );

            $charMaps[0] = array();

            for ($i = 0; $i < 256; ++$i) {
                $charMaps[0][\chr($i)] = sprintf('\\%02x', $i);
            }

            for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_FILTER]); $i < $l; ++$i) {
                $chr = $charMaps[self::LDAP_ESCAPE_FILTER][$i];
                unset($charMaps[self::LDAP_ESCAPE_FILTER][$i]);
                $charMaps[self::LDAP_ESCAPE_FILTER][$chr] = $charMaps[0][$chr];
            }

            for ($i = 0, $l = \count($charMaps[self::LDAP_ESCAPE_DN]); $i < $l; ++$i) {
                $chr = $charMaps[self::LDAP_ESCAPE_DN][$i];
                unset($charMaps[self::LDAP_ESCAPE_DN][$i]);
                $charMaps[self::LDAP_ESCAPE_DN][$chr] = $charMaps[0][$chr];
            }
        }

        // Create the base char map to escape
        $flags = (int) $flags;
        $charMap = array();

        if ($flags & self::LDAP_ESCAPE_FILTER) {
            $charMap += $charMaps[self::LDAP_ESCAPE_FILTER];
        }

        if ($flags & self::LDAP_ESCAPE_DN) {
            $charMap += $charMaps[self::LDAP_ESCAPE_DN];
        }

        if (!$charMap) {
            $charMap = $charMaps[0];
        }

        // Remove any chars to ignore from the list
        $ignore = (string) $ignore;

        for ($i = 0, $l = \strlen($ignore); $i < $l; ++$i) {
            unset($charMap[$ignore[$i]]);
        }

        // Do the main replacement
        $result = strtr($subject, $charMap);

        // Encode leading/trailing spaces if self::LDAP_ESCAPE_DN is passed
        if ($flags & self::LDAP_ESCAPE_DN) {
            if (' ' === $result[0]) {
                $result = '\\20'.substr($result, 1);
            }

            if (' ' === $result[\strlen($result) - 1]) {
                $result = substr($result, 0, -1).'\\20';
            }
        }

        return $result;
    }
}
vendor/symfony/yaml/Inline.php000064400000053711152177723700012460 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;

/**
 * Inline implements a YAML parser/dumper for the YAML inline syntax.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Inline
{
    const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';

    private static $exceptionOnInvalidType = false;
    private static $objectSupport = false;
    private static $objectForMap = false;

    /**
     * Converts a YAML string to a PHP value.
     *
     * @param string $value                  A YAML string
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
     * @param array  $references             Mapping of variable names to values
     *
     * @return mixed A PHP value
     *
     * @throws ParseException
     */
    public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
    {
        self::$exceptionOnInvalidType = $exceptionOnInvalidType;
        self::$objectSupport = $objectSupport;
        self::$objectForMap = $objectForMap;

        $value = trim($value);

        if ('' === $value) {
            return '';
        }

        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
            $mbEncoding = mb_internal_encoding();
            mb_internal_encoding('ASCII');
        }

        $i = 0;
        switch ($value[0]) {
            case '[':
                $result = self::parseSequence($value, $i, $references);
                ++$i;
                break;
            case '{':
                $result = self::parseMapping($value, $i, $references);
                ++$i;
                break;
            default:
                $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
        }

        // some comments are allowed at the end
        if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
            throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
        }

        if (isset($mbEncoding)) {
            mb_internal_encoding($mbEncoding);
        }

        return $result;
    }

    /**
     * Dumps a given PHP variable to a YAML string.
     *
     * @param mixed $value                  The PHP variable to convert
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
     *
     * @return string The YAML string representing the PHP value
     *
     * @throws DumpException When trying to dump PHP resource
     */
    public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        switch (true) {
            case \is_resource($value):
                if ($exceptionOnInvalidType) {
                    throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
                }

                return 'null';
            case \is_object($value):
                if ($objectSupport) {
                    return '!php/object:'.serialize($value);
                }

                if ($exceptionOnInvalidType) {
                    throw new DumpException('Object support when dumping a YAML file has been disabled.');
                }

                return 'null';
            case \is_array($value):
                return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
            case null === $value:
                return 'null';
            case true === $value:
                return 'true';
            case false === $value:
                return 'false';
            case ctype_digit($value):
                return \is_string($value) ? "'$value'" : (int) $value;
            case is_numeric($value):
                $locale = setlocale(LC_NUMERIC, 0);
                if (false !== $locale) {
                    setlocale(LC_NUMERIC, 'C');
                }
                if (\is_float($value)) {
                    $repr = (string) $value;
                    if (is_infinite($value)) {
                        $repr = str_ireplace('INF', '.Inf', $repr);
                    } elseif (floor($value) == $value && $repr == $value) {
                        // Preserve float data type since storing a whole number will result in integer value.
                        $repr = '!!float '.$repr;
                    }
                } else {
                    $repr = \is_string($value) ? "'$value'" : (string) $value;
                }
                if (false !== $locale) {
                    setlocale(LC_NUMERIC, $locale);
                }

                return $repr;
            case '' == $value:
                return "''";
            case Escaper::requiresDoubleQuoting($value):
                return Escaper::escapeWithDoubleQuotes($value);
            case Escaper::requiresSingleQuoting($value):
            case Parser::preg_match(self::getHexRegex(), $value):
            case Parser::preg_match(self::getTimestampRegex(), $value):
                return Escaper::escapeWithSingleQuotes($value);
            default:
                return $value;
        }
    }

    /**
     * Check if given array is hash or just normal indexed array.
     *
     * @internal
     *
     * @param array $value The PHP array to check
     *
     * @return bool true if value is hash array, false otherwise
     */
    public static function isHash(array $value)
    {
        $expectedKey = 0;

        foreach ($value as $key => $val) {
            if ($key !== $expectedKey++) {
                return true;
            }
        }

        return false;
    }

    /**
     * Dumps a PHP array to a YAML string.
     *
     * @param array $value                  The PHP array to dump
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
     *
     * @return string The YAML string representing the PHP array
     */
    private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
    {
        // array
        if ($value && !self::isHash($value)) {
            $output = array();
            foreach ($value as $val) {
                $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
            }

            return sprintf('[%s]', implode(', ', $output));
        }

        // hash
        $output = array();
        foreach ($value as $key => $val) {
            $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
        }

        return sprintf('{ %s }', implode(', ', $output));
    }

    /**
     * Parses a YAML scalar.
     *
     * @param string   $scalar
     * @param string[] $delimiters
     * @param string[] $stringDelimiters
     * @param int      &$i
     * @param bool     $evaluate
     * @param array    $references
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     *
     * @internal
     */
    public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
    {
        if (\in_array($scalar[$i], $stringDelimiters)) {
            // quoted scalar
            $output = self::parseQuotedScalar($scalar, $i);

            if (null !== $delimiters) {
                $tmp = ltrim(substr($scalar, $i), ' ');
                if ('' === $tmp) {
                    throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)));
                }
                if (!\in_array($tmp[0], $delimiters)) {
                    throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
                }
            }
        } else {
            // "normal" string
            if (!$delimiters) {
                $output = substr($scalar, $i);
                $i += \strlen($output);

                // remove comments
                if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
                    $output = substr($output, 0, $match[0][1]);
                }
            } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
                $output = $match[1];
                $i += \strlen($output);
            } else {
                throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
            }

            // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
            if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
                @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);

                // to be thrown in 3.0
                // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
            }

            if ($evaluate) {
                $output = self::evaluateScalar($output, $references);
            }
        }

        return $output;
    }

    /**
     * Parses a YAML quoted scalar.
     *
     * @param string $scalar
     * @param int    &$i
     *
     * @return string
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseQuotedScalar($scalar, &$i)
    {
        if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
            throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
        }

        $output = substr($match[0], 1, \strlen($match[0]) - 2);

        $unescaper = new Unescaper();
        if ('"' == $scalar[$i]) {
            $output = $unescaper->unescapeDoubleQuotedString($output);
        } else {
            $output = $unescaper->unescapeSingleQuotedString($output);
        }

        $i += \strlen($match[0]);

        return $output;
    }

    /**
     * Parses a YAML sequence.
     *
     * @param string $sequence
     * @param int    &$i
     * @param array  $references
     *
     * @return array
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseSequence($sequence, &$i = 0, $references = array())
    {
        $output = array();
        $len = \strlen($sequence);
        ++$i;

        // [foo, bar, ...]
        while ($i < $len) {
            switch ($sequence[$i]) {
                case '[':
                    // nested sequence
                    $output[] = self::parseSequence($sequence, $i, $references);
                    break;
                case '{':
                    // nested mapping
                    $output[] = self::parseMapping($sequence, $i, $references);
                    break;
                case ']':
                    return $output;
                case ',':
                case ' ':
                    break;
                default:
                    $isQuoted = \in_array($sequence[$i], array('"', "'"));
                    $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);

                    // the value can be an array if a reference has been resolved to an array var
                    if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
                        // embedded mapping?
                        try {
                            $pos = 0;
                            $value = self::parseMapping('{'.$value.'}', $pos, $references);
                        } catch (\InvalidArgumentException $e) {
                            // no, it's not
                        }
                    }

                    $output[] = $value;

                    --$i;
            }

            ++$i;
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
    }

    /**
     * Parses a YAML mapping.
     *
     * @param string $mapping
     * @param int    &$i
     * @param array  $references
     *
     * @return array|\stdClass
     *
     * @throws ParseException When malformed inline YAML string is parsed
     */
    private static function parseMapping($mapping, &$i = 0, $references = array())
    {
        $output = array();
        $len = \strlen($mapping);
        ++$i;
        $allowOverwrite = false;

        // {foo: bar, bar:foo, ...}
        while ($i < $len) {
            switch ($mapping[$i]) {
                case ' ':
                case ',':
                    ++$i;
                    continue 2;
                case '}':
                    if (self::$objectForMap) {
                        return (object) $output;
                    }

                    return $output;
            }

            // key
            $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);

            if ('<<' === $key) {
                $allowOverwrite = true;
            }

            // value
            $done = false;

            while ($i < $len) {
                switch ($mapping[$i]) {
                    case '[':
                        // nested sequence
                        $value = self::parseSequence($mapping, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            foreach ($value as $parsedValue) {
                                $output += $parsedValue;
                            }
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        break;
                    case '{':
                        // nested mapping
                        $value = self::parseMapping($mapping, $i, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            $output += $value;
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        break;
                    case ':':
                    case ' ':
                        break;
                    default:
                        $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
                        // Spec: Keys MUST be unique; first one wins.
                        // Parser cannot abort this mapping earlier, since lines
                        // are processed sequentially.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ('<<' === $key) {
                            $output += $value;
                        } elseif ($allowOverwrite || !isset($output[$key])) {
                            $output[$key] = $value;
                        }
                        $done = true;
                        --$i;
                }

                ++$i;

                if ($done) {
                    continue 2;
                }
            }
        }

        throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
    }

    /**
     * Evaluates scalars and replaces magic values.
     *
     * @param string $scalar
     * @param array  $references
     *
     * @return mixed The evaluated YAML string
     *
     * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
     */
    private static function evaluateScalar($scalar, $references = array())
    {
        $scalar = trim($scalar);
        $scalarLower = strtolower($scalar);

        if (0 === strpos($scalar, '*')) {
            if (false !== $pos = strpos($scalar, '#')) {
                $value = substr($scalar, 1, $pos - 2);
            } else {
                $value = substr($scalar, 1);
            }

            // an unquoted *
            if (false === $value || '' === $value) {
                throw new ParseException('A reference must contain at least one character.');
            }

            if (!array_key_exists($value, $references)) {
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
            }

            return $references[$value];
        }

        switch (true) {
            case 'null' === $scalarLower:
            case '' === $scalar:
            case '~' === $scalar:
                return;
            case 'true' === $scalarLower:
                return true;
            case 'false' === $scalarLower:
                return false;
            // Optimise for returning strings.
            case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
                switch (true) {
                    case 0 === strpos($scalar, '!str'):
                        return (string) substr($scalar, 5);
                    case 0 === strpos($scalar, '! '):
                        return (int) self::parseScalar(substr($scalar, 2));
                    case 0 === strpos($scalar, '!php/object:'):
                        if (self::$objectSupport) {
                            return unserialize(substr($scalar, 12));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
                        }

                        return;
                    case 0 === strpos($scalar, '!!php/object:'):
                        if (self::$objectSupport) {
                            return unserialize(substr($scalar, 13));
                        }

                        if (self::$exceptionOnInvalidType) {
                            throw new ParseException('Object support when parsing a YAML file has been disabled.');
                        }

                        return;
                    case 0 === strpos($scalar, '!!float '):
                        return (float) substr($scalar, 8);
                    case ctype_digit($scalar):
                        $raw = $scalar;
                        $cast = (int) $scalar;

                        return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
                    case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
                        $raw = $scalar;
                        $cast = (int) $scalar;

                        return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
                    case is_numeric($scalar):
                    case Parser::preg_match(self::getHexRegex(), $scalar):
                        return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
                    case '.inf' === $scalarLower:
                    case '.nan' === $scalarLower:
                        return -log(0);
                    case '-.inf' === $scalarLower:
                        return log(0);
                    case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
                        return (float) str_replace(',', '', $scalar);
                    case Parser::preg_match(self::getTimestampRegex(), $scalar):
                        $timeZone = date_default_timezone_get();
                        date_default_timezone_set('UTC');
                        $time = strtotime($scalar);
                        date_default_timezone_set($timeZone);

                        return $time;
                }
                // no break
            default:
                return (string) $scalar;
        }
    }

    /**
     * Gets a regex that matches a YAML date.
     *
     * @return string The regular expression
     *
     * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
     */
    private static function getTimestampRegex()
    {
        return <<<EOF
        ~^
        (?P<year>[0-9][0-9][0-9][0-9])
        -(?P<month>[0-9][0-9]?)
        -(?P<day>[0-9][0-9]?)
        (?:(?:[Tt]|[ \t]+)
        (?P<hour>[0-9][0-9]?)
        :(?P<minute>[0-9][0-9])
        :(?P<second>[0-9][0-9])
        (?:\.(?P<fraction>[0-9]*))?
        (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
        (?::(?P<tz_minute>[0-9][0-9]))?))?)?
        $~x
EOF;
    }

    /**
     * Gets a regex that matches a YAML number in hexadecimal notation.
     *
     * @return string
     */
    private static function getHexRegex()
    {
        return '~^0x[0-9a-f]++$~i';
    }
}
vendor/symfony/yaml/Unescaper.php000064400000010516152177723700013163 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

/**
 * Unescaper encapsulates unescaping rules for single and double-quoted
 * YAML strings.
 *
 * @author Matthew Lewinski <matthew@lewinski.org>
 *
 * @internal
 */
class Unescaper
{
    /**
     * Parser and Inline assume UTF-8 encoding, so escaped Unicode characters
     * must be converted to that encoding.
     *
     * @deprecated since version 2.5, to be removed in 3.0
     *
     * @internal
     */
    const ENCODING = 'UTF-8';

    /**
     * Regex fragment that matches an escaped character in a double quoted string.
     */
    const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)';

    /**
     * Unescapes a single quoted string.
     *
     * @param string $value A single quoted string
     *
     * @return string The unescaped string
     */
    public function unescapeSingleQuotedString($value)
    {
        return str_replace('\'\'', '\'', $value);
    }

    /**
     * Unescapes a double quoted string.
     *
     * @param string $value A double quoted string
     *
     * @return string The unescaped string
     */
    public function unescapeDoubleQuotedString($value)
    {
        $self = $this;
        $callback = function ($match) use ($self) {
            return $self->unescapeCharacter($match[0]);
        };

        // evaluate the string
        return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
    }

    /**
     * Unescapes a character that was found in a double-quoted string.
     *
     * @param string $value An escaped character
     *
     * @return string The unescaped character
     *
     * @internal This method is public to be usable as callback. It should not
     *           be used in user code. Should be changed in 3.0.
     */
    public function unescapeCharacter($value)
    {
        switch ($value[1]) {
            case '0':
                return "\x0";
            case 'a':
                return "\x7";
            case 'b':
                return "\x8";
            case 't':
                return "\t";
            case "\t":
                return "\t";
            case 'n':
                return "\n";
            case 'v':
                return "\xB";
            case 'f':
                return "\xC";
            case 'r':
                return "\r";
            case 'e':
                return "\x1B";
            case ' ':
                return ' ';
            case '"':
                return '"';
            case '/':
                return '/';
            case '\\':
                return '\\';
            case 'N':
                // U+0085 NEXT LINE
                return "\xC2\x85";
            case '_':
                // U+00A0 NO-BREAK SPACE
                return "\xC2\xA0";
            case 'L':
                // U+2028 LINE SEPARATOR
                return "\xE2\x80\xA8";
            case 'P':
                // U+2029 PARAGRAPH SEPARATOR
                return "\xE2\x80\xA9";
            case 'x':
                return self::utf8chr(hexdec(substr($value, 2, 2)));
            case 'u':
                return self::utf8chr(hexdec(substr($value, 2, 4)));
            case 'U':
                return self::utf8chr(hexdec(substr($value, 2, 8)));
            default:
                @trigger_error('Not escaping a backslash in a double-quoted string is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', E_USER_DEPRECATED);

                return $value;
        }
    }

    /**
     * Get the UTF-8 character for the given code point.
     *
     * @param int $c The unicode code point
     *
     * @return string The corresponding UTF-8 character
     */
    private static function utf8chr($c)
    {
        if (0x80 > $c %= 0x200000) {
            return \chr($c);
        }
        if (0x800 > $c) {
            return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F);
        }
        if (0x10000 > $c) {
            return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
        }

        return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
    }
}
vendor/symfony/yaml/LICENSE000064400000002051152177723700011525 0ustar00Copyright (c) 2004-2018 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/yaml/Dumper.php000064400000004750152177723700012475 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

/**
 * Dumper dumps PHP variables to YAML strings.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Dumper
{
    /**
     * The amount of spaces to use for indentation of nested nodes.
     *
     * @var int
     */
    protected $indentation = 4;

    /**
     * Sets the indentation.
     *
     * @param int $num The amount of spaces to use for indentation of nested nodes
     */
    public function setIndentation($num)
    {
        if ($num < 1) {
            throw new \InvalidArgumentException('The indentation must be greater than zero.');
        }

        $this->indentation = (int) $num;
    }

    /**
     * Dumps a PHP value to YAML.
     *
     * @param mixed $input                  The PHP value
     * @param int   $inline                 The level where you switch to inline YAML
     * @param int   $indent                 The level of indentation (used internally)
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
     *
     * @return string The YAML representation of the PHP value
     */
    public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        $output = '';
        $prefix = $indent ? str_repeat(' ', $indent) : '';

        if ($inline <= 0 || !\is_array($input) || empty($input)) {
            $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport);
        } else {
            $isAHash = Inline::isHash($input);

            foreach ($input as $key => $value) {
                $willBeInlined = $inline - 1 <= 0 || !\is_array($value) || empty($value);

                $output .= sprintf('%s%s%s%s',
                    $prefix,
                    $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-',
                    $willBeInlined ? ' ' : "\n",
                    $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport)
                ).($willBeInlined ? "\n" : '');
            }
        }

        return $output;
    }
}
vendor/symfony/yaml/Yaml.php000064400000007327152177723700012146 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\ParseException;

/**
 * Yaml offers convenience methods to load and dump YAML.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Yaml
{
    /**
     * Parses YAML into a PHP value.
     *
     * Usage:
     *
     *     $array = Yaml::parse(file_get_contents('config.yml'));
     *     print_r($array);
     *
     * As this method accepts both plain strings and file names as an input,
     * you must validate the input before calling this method. Passing a file
     * as an input is a deprecated feature and will be removed in 3.0.
     *
     * Note: the ability to pass file names to the Yaml::parse method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.
     *
     * @param string $input                  Path to a YAML file or a string containing YAML
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
     *
     * @return mixed The YAML converted to a PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        // if input is a file, process it
        $file = '';
        if (false === strpos($input, "\n") && is_file($input)) {
            @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED);

            if (false === is_readable($input)) {
                throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input));
            }

            $file = $input;
            $input = file_get_contents($file);
        }

        $yaml = new Parser();

        try {
            return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport, $objectForMap);
        } catch (ParseException $e) {
            if ($file) {
                $e->setParsedFile($file);
            }

            throw $e;
        }
    }

    /**
     * Dumps a PHP value to a YAML string.
     *
     * The dump method, when supplied with an array, will do its best
     * to convert the array into friendly YAML.
     *
     * @param mixed $input                  The PHP value
     * @param int   $inline                 The level where you switch to inline YAML
     * @param int   $indent                 The amount of spaces to use for indentation of nested nodes
     * @param bool  $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool  $objectSupport          True if object support is enabled, false otherwise
     *
     * @return string A YAML string representing the original PHP value
     */
    public static function dump($input, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false)
    {
        if ($indent < 1) {
            throw new \InvalidArgumentException('The indentation must be greater than zero.');
        }

        $yaml = new Dumper();
        $yaml->setIndentation($indent);

        return $yaml->dump($input, $inline, 0, $exceptionOnInvalidType, $objectSupport);
    }
}
vendor/symfony/yaml/Escaper.php000064400000007550152177723700012624 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

/**
 * Escaper encapsulates escaping rules for single and double-quoted
 * YAML strings.
 *
 * @author Matthew Lewinski <matthew@lewinski.org>
 *
 * @internal
 */
class Escaper
{
    // Characters that would cause a dumped string to require double quoting.
    const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";

    // Mapping arrays for escaping a double quoted string. The backslash is
    // first to ensure proper escaping because str_replace operates iteratively
    // on the input arrays. This ordering of the characters avoids the use of strtr,
    // which performs more slowly.
    private static $escapees = array('\\', '\\\\', '\\"', '"',
                                     "\x00",  "\x01",  "\x02",  "\x03",  "\x04",  "\x05",  "\x06",  "\x07",
                                     "\x08",  "\x09",  "\x0a",  "\x0b",  "\x0c",  "\x0d",  "\x0e",  "\x0f",
                                     "\x10",  "\x11",  "\x12",  "\x13",  "\x14",  "\x15",  "\x16",  "\x17",
                                     "\x18",  "\x19",  "\x1a",  "\x1b",  "\x1c",  "\x1d",  "\x1e",  "\x1f",
                                     "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
                               );
    private static $escaped = array('\\\\', '\\"', '\\\\', '\\"',
                                     '\\0',   '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a',
                                     '\\b',   '\\t',   '\\n',   '\\v',   '\\f',   '\\r',   '\\x0e', '\\x0f',
                                     '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17',
                                     '\\x18', '\\x19', '\\x1a', '\\e',   '\\x1c', '\\x1d', '\\x1e', '\\x1f',
                                     '\\N', '\\_', '\\L', '\\P',
                              );

    /**
     * Determines if a PHP value would require double quoting in YAML.
     *
     * @param string $value A PHP value
     *
     * @return bool True if the value would require double quotes
     */
    public static function requiresDoubleQuoting($value)
    {
        return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
    }

    /**
     * Escapes and surrounds a PHP value with double quotes.
     *
     * @param string $value A PHP value
     *
     * @return string The quoted, escaped string
     */
    public static function escapeWithDoubleQuotes($value)
    {
        return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value));
    }

    /**
     * Determines if a PHP value would require single quoting in YAML.
     *
     * @param string $value A PHP value
     *
     * @return bool True if the value would require single quotes
     */
    public static function requiresSingleQuoting($value)
    {
        // Determines if a PHP value is entirely composed of a value that would
        // require single quoting in YAML.
        if (\in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) {
            return true;
        }

        // Determines if the PHP value contains any single characters that would
        // cause it to require single quoting in YAML.
        return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value);
    }

    /**
     * Escapes and surrounds a PHP value with single quotes.
     *
     * @param string $value A PHP value
     *
     * @return string The quoted, escaped string
     */
    public static function escapeWithSingleQuotes($value)
    {
        return sprintf("'%s'", str_replace('\'', '\'\'', $value));
    }
}
vendor/symfony/yaml/Parser.php000064400000100467152177723700012477 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml;

use Symfony\Component\Yaml\Exception\ParseException;

/**
 * Parser parses YAML strings to convert them to PHP arrays.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class Parser
{
    const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
    // BC - wrongly named
    const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;

    private $offset = 0;
    private $totalNumberOfLines;
    private $lines = array();
    private $currentLineNb = -1;
    private $currentLine = '';
    private $refs = array();
    private $skippedLineNumbers = array();
    private $locallySkippedLineNumbers = array();

    /**
     * @param int      $offset             The offset of YAML document (used for line numbers in error messages)
     * @param int|null $totalNumberOfLines The overall number of lines being parsed
     * @param int[]    $skippedLineNumbers Number of comment lines that have been skipped by the parser
     */
    public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
    {
        $this->offset = $offset;
        $this->totalNumberOfLines = $totalNumberOfLines;
        $this->skippedLineNumbers = $skippedLineNumbers;
    }

    /**
     * Parses a YAML string to a PHP value.
     *
     * @param string $value                  A YAML string
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
     *
     * @return mixed A PHP value
     *
     * @throws ParseException If the YAML is not valid
     */
    public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        if (false === preg_match('//u', $value)) {
            throw new ParseException('The YAML value does not appear to be valid UTF-8.');
        }

        $this->refs = array();

        $mbEncoding = null;
        $e = null;
        $data = null;

        if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
            $mbEncoding = mb_internal_encoding();
            mb_internal_encoding('UTF-8');
        }

        try {
            $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
        } catch (\Exception $e) {
        } catch (\Throwable $e) {
        }

        if (null !== $mbEncoding) {
            mb_internal_encoding($mbEncoding);
        }

        $this->lines = array();
        $this->currentLine = '';
        $this->refs = array();
        $this->skippedLineNumbers = array();
        $this->locallySkippedLineNumbers = array();

        if (null !== $e) {
            throw $e;
        }

        return $data;
    }

    private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
    {
        $this->currentLineNb = -1;
        $this->currentLine = '';
        $value = $this->cleanup($value);
        $this->lines = explode("\n", $value);
        $this->locallySkippedLineNumbers = array();

        if (null === $this->totalNumberOfLines) {
            $this->totalNumberOfLines = \count($this->lines);
        }

        $data = array();
        $context = null;
        $allowOverwrite = false;

        while ($this->moveToNextLine()) {
            if ($this->isCurrentLineEmpty()) {
                continue;
            }

            // tab?
            if ("\t" === $this->currentLine[0]) {
                throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }

            $isRef = $mergeNode = false;
            if (self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
                if ($context && 'mapping' == $context) {
                    throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                }
                $context = 'sequence';

                if (isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $values['value'] = $matches['value'];
                }

                // array
                if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
                    $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
                } else {
                    if (isset($values['leadspaces'])
                        && self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($values['value']), $matches)
                    ) {
                        // this is a compact notation element, add to next block and parse
                        $block = $values['value'];
                        if ($this->isNextLineIndented()) {
                            $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
                        }

                        $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
                    } else {
                        $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = end($data);
                }
            } elseif (
                self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
                && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], array('"', "'")))
            ) {
                if ($context && 'sequence' == $context) {
                    throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
                }
                $context = 'mapping';

                // force correct settings
                Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
                try {
                    $key = Inline::parseScalar($values['key']);
                } catch (ParseException $e) {
                    $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                    $e->setSnippet($this->currentLine);

                    throw $e;
                }

                // Convert float keys to strings, to avoid being converted to integers by PHP
                if (\is_float($key)) {
                    $key = (string) $key;
                }

                if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
                    $mergeNode = true;
                    $allowOverwrite = true;
                    if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
                        $refName = substr($values['value'], 1);
                        if (!array_key_exists($refName, $this->refs)) {
                            throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        $refValue = $this->refs[$refName];

                        if (!\is_array($refValue)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        $data += $refValue; // array union
                    } else {
                        if (isset($values['value']) && '' !== $values['value']) {
                            $value = $values['value'];
                        } else {
                            $value = $this->getNextEmbedBlock();
                        }
                        $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);

                        if (!\is_array($parsed)) {
                            throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
                        }

                        if (isset($parsed[0])) {
                            // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
                            // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
                            // in the sequence override keys specified in later mapping nodes.
                            foreach ($parsed as $parsedItem) {
                                if (!\is_array($parsedItem)) {
                                    throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
                                }

                                $data += $parsedItem; // array union
                            }
                        } else {
                            // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
                            // current mapping, unless the key already exists in it.
                            $data += $parsed; // array union
                        }
                    }
                } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
                    $isRef = $matches['ref'];
                    $values['value'] = $matches['value'];
                }

                if ($mergeNode) {
                    // Merge keys
                } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) {
                    // hash
                    // if next line is less indented or equal, then it means that the current value is null
                    if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
                        // Spec: Keys MUST be unique; first one wins.
                        // But overwriting is allowed when a merge node is used in current block.
                        if ($allowOverwrite || !isset($data[$key])) {
                            $data[$key] = null;
                        }
                    } else {
                        $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);

                        if ('<<' === $key) {
                            $this->refs[$refMatches['ref']] = $value;
                            $data += $value;
                        } elseif ($allowOverwrite || !isset($data[$key])) {
                            // Spec: Keys MUST be unique; first one wins.
                            // But overwriting is allowed when a merge node is used in current block.
                            $data[$key] = $value;
                        }
                    }
                } else {
                    $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
                    // Spec: Keys MUST be unique; first one wins.
                    // But overwriting is allowed when a merge node is used in current block.
                    if ($allowOverwrite || !isset($data[$key])) {
                        $data[$key] = $value;
                    }
                }
                if ($isRef) {
                    $this->refs[$isRef] = $data[$key];
                }
            } else {
                // multiple documents are not supported
                if ('---' === $this->currentLine) {
                    throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
                }

                // 1-liner optionally followed by newline(s)
                if (\is_string($value) && $this->lines[0] === trim($value)) {
                    try {
                        $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
                    } catch (ParseException $e) {
                        $e->setParsedLine($this->getRealCurrentLineNb() + 1);
                        $e->setSnippet($this->currentLine);

                        throw $e;
                    }

                    return $value;
                }

                throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        }

        if ($objectForMap && !\is_object($data) && 'mapping' === $context) {
            $object = new \stdClass();

            foreach ($data as $key => $value) {
                $object->$key = $value;
            }

            $data = $object;
        }

        return empty($data) ? null : $data;
    }

    private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
    {
        $skippedLineNumbers = $this->skippedLineNumbers;

        foreach ($this->locallySkippedLineNumbers as $lineNumber) {
            if ($lineNumber < $offset) {
                continue;
            }

            $skippedLineNumbers[] = $lineNumber;
        }

        $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
        $parser->refs = &$this->refs;

        return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
    }

    /**
     * Returns the current line number (takes the offset into account).
     *
     * @return int The current line number
     */
    private function getRealCurrentLineNb()
    {
        $realCurrentLineNumber = $this->currentLineNb + $this->offset;

        foreach ($this->skippedLineNumbers as $skippedLineNumber) {
            if ($skippedLineNumber > $realCurrentLineNumber) {
                break;
            }

            ++$realCurrentLineNumber;
        }

        return $realCurrentLineNumber;
    }

    /**
     * Returns the current line indentation.
     *
     * @return int The current line indentation
     */
    private function getCurrentLineIndentation()
    {
        return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
    }

    /**
     * Returns the next embed block of YAML.
     *
     * @param int  $indentation The indent level at which the block is to be read, or null for default
     * @param bool $inSequence  True if the enclosing data structure is a sequence
     *
     * @return string A YAML string
     *
     * @throws ParseException When indentation problem are detected
     */
    private function getNextEmbedBlock($indentation = null, $inSequence = false)
    {
        $oldLineIndentation = $this->getCurrentLineIndentation();
        $blockScalarIndentations = array();

        if ($this->isBlockScalarHeader()) {
            $blockScalarIndentations[] = $this->getCurrentLineIndentation();
        }

        if (!$this->moveToNextLine()) {
            return;
        }

        if (null === $indentation) {
            $newIndent = $this->getCurrentLineIndentation();

            $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();

            if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        } else {
            $newIndent = $indentation;
        }

        $data = array();
        if ($this->getCurrentLineIndentation() >= $newIndent) {
            $data[] = substr($this->currentLine, $newIndent);
        } else {
            $this->moveToPreviousLine();

            return;
        }

        if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
            // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
            // and therefore no nested list or mapping
            $this->moveToPreviousLine();

            return;
        }

        $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();

        if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
            $blockScalarIndentations[] = $this->getCurrentLineIndentation();
        }

        $previousLineIndentation = $this->getCurrentLineIndentation();

        while ($this->moveToNextLine()) {
            $indent = $this->getCurrentLineIndentation();

            // terminate all block scalars that are more indented than the current line
            if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
                foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
                    if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
                        unset($blockScalarIndentations[$key]);
                    }
                }
            }

            if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
                $blockScalarIndentations[] = $this->getCurrentLineIndentation();
            }

            $previousLineIndentation = $indent;

            if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
                $this->moveToPreviousLine();
                break;
            }

            if ($this->isCurrentLineBlank()) {
                $data[] = substr($this->currentLine, $newIndent);
                continue;
            }

            // we ignore "comment" lines only when we are not inside a scalar block
            if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
                // remember ignored comment lines (they are used later in nested
                // parser calls to determine real line numbers)
                //
                // CAUTION: beware to not populate the global property here as it
                // will otherwise influence the getRealCurrentLineNb() call here
                // for consecutive comment lines and subsequent embedded blocks
                $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();

                continue;
            }

            if ($indent >= $newIndent) {
                $data[] = substr($this->currentLine, $newIndent);
            } elseif (0 == $indent) {
                $this->moveToPreviousLine();

                break;
            } else {
                throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
            }
        }

        return implode("\n", $data);
    }

    /**
     * Moves the parser to the next line.
     *
     * @return bool
     */
    private function moveToNextLine()
    {
        if ($this->currentLineNb >= \count($this->lines) - 1) {
            return false;
        }

        $this->currentLine = $this->lines[++$this->currentLineNb];

        return true;
    }

    /**
     * Moves the parser to the previous line.
     *
     * @return bool
     */
    private function moveToPreviousLine()
    {
        if ($this->currentLineNb < 1) {
            return false;
        }

        $this->currentLine = $this->lines[--$this->currentLineNb];

        return true;
    }

    /**
     * Parses a YAML value.
     *
     * @param string $value                  A YAML value
     * @param bool   $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
     * @param bool   $objectSupport          True if object support is enabled, false otherwise
     * @param bool   $objectForMap           True if maps should return a stdClass instead of array()
     * @param string $context                The parser context (either sequence or mapping)
     *
     * @return mixed A PHP value
     *
     * @throws ParseException When reference does not exist
     */
    private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
    {
        if (0 === strpos($value, '*')) {
            if (false !== $pos = strpos($value, '#')) {
                $value = substr($value, 1, $pos - 2);
            } else {
                $value = substr($value, 1);
            }

            if (!array_key_exists($value, $this->refs)) {
                throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
            }

            return $this->refs[$value];
        }

        if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
            $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';

            return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
        }

        try {
            $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);

            if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
                @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);

                // to be thrown in 3.0
                // throw new ParseException('A colon cannot be used in an unquoted mapping value.');
            }

            return $parsedValue;
        } catch (ParseException $e) {
            $e->setParsedLine($this->getRealCurrentLineNb() + 1);
            $e->setSnippet($this->currentLine);

            throw $e;
        }
    }

    /**
     * Parses a block scalar.
     *
     * @param string $style       The style indicator that was used to begin this block scalar (| or >)
     * @param string $chomping    The chomping indicator that was used to begin this block scalar (+ or -)
     * @param int    $indentation The indentation indicator that was used to begin this block scalar
     *
     * @return string The text value
     */
    private function parseBlockScalar($style, $chomping = '', $indentation = 0)
    {
        $notEOF = $this->moveToNextLine();
        if (!$notEOF) {
            return '';
        }

        $isCurrentLineBlank = $this->isCurrentLineBlank();
        $blockLines = array();

        // leading blank lines are consumed before determining indentation
        while ($notEOF && $isCurrentLineBlank) {
            // newline only if not EOF
            if ($notEOF = $this->moveToNextLine()) {
                $blockLines[] = '';
                $isCurrentLineBlank = $this->isCurrentLineBlank();
            }
        }

        // determine indentation if not specified
        if (0 === $indentation) {
            if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
                $indentation = \strlen($matches[0]);
            }
        }

        if ($indentation > 0) {
            $pattern = sprintf('/^ {%d}(.*)$/', $indentation);

            while (
                $notEOF && (
                    $isCurrentLineBlank ||
                    self::preg_match($pattern, $this->currentLine, $matches)
                )
            ) {
                if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
                    $blockLines[] = substr($this->currentLine, $indentation);
                } elseif ($isCurrentLineBlank) {
                    $blockLines[] = '';
                } else {
                    $blockLines[] = $matches[1];
                }

                // newline only if not EOF
                if ($notEOF = $this->moveToNextLine()) {
                    $isCurrentLineBlank = $this->isCurrentLineBlank();
                }
            }
        } elseif ($notEOF) {
            $blockLines[] = '';
        }

        if ($notEOF) {
            $blockLines[] = '';
            $this->moveToPreviousLine();
        } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
            $blockLines[] = '';
        }

        // folded style
        if ('>' === $style) {
            $text = '';
            $previousLineIndented = false;
            $previousLineBlank = false;

            for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
                if ('' === $blockLines[$i]) {
                    $text .= "\n";
                    $previousLineIndented = false;
                    $previousLineBlank = true;
                } elseif (' ' === $blockLines[$i][0]) {
                    $text .= "\n".$blockLines[$i];
                    $previousLineIndented = true;
                    $previousLineBlank = false;
                } elseif ($previousLineIndented) {
                    $text .= "\n".$blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                } elseif ($previousLineBlank || 0 === $i) {
                    $text .= $blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                } else {
                    $text .= ' '.$blockLines[$i];
                    $previousLineIndented = false;
                    $previousLineBlank = false;
                }
            }
        } else {
            $text = implode("\n", $blockLines);
        }

        // deal with trailing newlines
        if ('' === $chomping) {
            $text = preg_replace('/\n+$/', "\n", $text);
        } elseif ('-' === $chomping) {
            $text = preg_replace('/\n+$/', '', $text);
        }

        return $text;
    }

    /**
     * Returns true if the next line is indented.
     *
     * @return bool Returns true if the next line is indented, false otherwise
     */
    private function isNextLineIndented()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $EOF = !$this->moveToNextLine();

        while (!$EOF && $this->isCurrentLineEmpty()) {
            $EOF = !$this->moveToNextLine();
        }

        if ($EOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() > $currentIndentation;

        $this->moveToPreviousLine();

        return $ret;
    }

    /**
     * Returns true if the current line is blank or if it is a comment line.
     *
     * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
     */
    private function isCurrentLineEmpty()
    {
        return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
    }

    /**
     * Returns true if the current line is blank.
     *
     * @return bool Returns true if the current line is blank, false otherwise
     */
    private function isCurrentLineBlank()
    {
        return '' == trim($this->currentLine, ' ');
    }

    /**
     * Returns true if the current line is a comment line.
     *
     * @return bool Returns true if the current line is a comment line, false otherwise
     */
    private function isCurrentLineComment()
    {
        //checking explicitly the first char of the trim is faster than loops or strpos
        $ltrimmedLine = ltrim($this->currentLine, ' ');

        return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
    }

    private function isCurrentLineLastLineInDocument()
    {
        return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
    }

    /**
     * Cleanups a YAML string to be parsed.
     *
     * @param string $value The input YAML string
     *
     * @return string A cleaned up YAML string
     */
    private function cleanup($value)
    {
        $value = str_replace(array("\r\n", "\r"), "\n", $value);

        // strip YAML header
        $count = 0;
        $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
        $this->offset += $count;

        // remove leading comments
        $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
        if (1 == $count) {
            // items have been removed, update the offset
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
            $value = $trimmedValue;
        }

        // remove start of the document marker (---)
        $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
        if (1 == $count) {
            // items have been removed, update the offset
            $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
            $value = $trimmedValue;

            // remove end of the document marker (...)
            $value = preg_replace('#\.\.\.\s*$#', '', $value);
        }

        return $value;
    }

    /**
     * Returns true if the next line starts unindented collection.
     *
     * @return bool Returns true if the next line starts unindented collection, false otherwise
     */
    private function isNextLineUnIndentedCollection()
    {
        $currentIndentation = $this->getCurrentLineIndentation();
        $notEOF = $this->moveToNextLine();

        while ($notEOF && $this->isCurrentLineEmpty()) {
            $notEOF = $this->moveToNextLine();
        }

        if (false === $notEOF) {
            return false;
        }

        $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();

        $this->moveToPreviousLine();

        return $ret;
    }

    /**
     * Returns true if the string is un-indented collection item.
     *
     * @return bool Returns true if the string is un-indented collection item, false otherwise
     */
    private function isStringUnIndentedCollectionItem()
    {
        return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
    }

    /**
     * Tests whether or not the current line is the header of a block scalar.
     *
     * @return bool
     */
    private function isBlockScalarHeader()
    {
        return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
    }

    /**
     * A local wrapper for `preg_match` which will throw a ParseException if there
     * is an internal error in the PCRE engine.
     *
     * This avoids us needing to check for "false" every time PCRE is used
     * in the YAML engine
     *
     * @throws ParseException on a PCRE internal error
     *
     * @see preg_last_error()
     *
     * @internal
     */
    public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
    {
        if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
            switch (preg_last_error()) {
                case PREG_INTERNAL_ERROR:
                    $error = 'Internal PCRE error.';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $error = 'pcre.backtrack_limit reached.';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $error = 'pcre.recursion_limit reached.';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $error = 'Malformed UTF-8 data.';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
                    break;
                default:
                    $error = 'Error.';
            }

            throw new ParseException($error);
        }

        return $ret;
    }
}
vendor/symfony/yaml/Exception/RuntimeException.php000064400000000745152177723700016501 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during parsing.
 *
 * @author Romain Neutron <imprec@gmail.com>
 */
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
vendor/symfony/yaml/Exception/ParseException.php000064400000006771152177723700016135 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during parsing.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class ParseException extends RuntimeException
{
    private $parsedFile;
    private $parsedLine;
    private $snippet;
    private $rawMessage;

    /**
     * @param string          $message    The error message
     * @param int             $parsedLine The line where the error occurred
     * @param string|null     $snippet    The snippet of code near the problem
     * @param string|null     $parsedFile The file name where the error occurred
     * @param \Exception|null $previous   The previous exception
     */
    public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null)
    {
        $this->parsedFile = $parsedFile;
        $this->parsedLine = $parsedLine;
        $this->snippet = $snippet;
        $this->rawMessage = $message;

        $this->updateRepr();

        parent::__construct($this->message, 0, $previous);
    }

    /**
     * Gets the snippet of code near the error.
     *
     * @return string The snippet of code
     */
    public function getSnippet()
    {
        return $this->snippet;
    }

    /**
     * Sets the snippet of code near the error.
     *
     * @param string $snippet The code snippet
     */
    public function setSnippet($snippet)
    {
        $this->snippet = $snippet;

        $this->updateRepr();
    }

    /**
     * Gets the filename where the error occurred.
     *
     * This method returns null if a string is parsed.
     *
     * @return string The filename
     */
    public function getParsedFile()
    {
        return $this->parsedFile;
    }

    /**
     * Sets the filename where the error occurred.
     *
     * @param string $parsedFile The filename
     */
    public function setParsedFile($parsedFile)
    {
        $this->parsedFile = $parsedFile;

        $this->updateRepr();
    }

    /**
     * Gets the line where the error occurred.
     *
     * @return int The file line
     */
    public function getParsedLine()
    {
        return $this->parsedLine;
    }

    /**
     * Sets the line where the error occurred.
     *
     * @param int $parsedLine The file line
     */
    public function setParsedLine($parsedLine)
    {
        $this->parsedLine = $parsedLine;

        $this->updateRepr();
    }

    private function updateRepr()
    {
        $this->message = $this->rawMessage;

        $dot = false;
        if ('.' === substr($this->message, -1)) {
            $this->message = substr($this->message, 0, -1);
            $dot = true;
        }

        if (null !== $this->parsedFile) {
            if (\PHP_VERSION_ID >= 50400) {
                $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
            } else {
                $jsonOptions = 0;
            }
            $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions));
        }

        if ($this->parsedLine >= 0) {
            $this->message .= sprintf(' at line %d', $this->parsedLine);
        }

        if ($this->snippet) {
            $this->message .= sprintf(' (near "%s")', $this->snippet);
        }

        if ($dot) {
            $this->message .= '.';
        }
    }
}
vendor/symfony/yaml/Exception/DumpException.php000064400000000707152177723700015761 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception class thrown when an error occurs during dumping.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class DumpException extends RuntimeException
{
}
vendor/symfony/yaml/Exception/ExceptionInterface.php000064400000000673152177723700016756 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Component\Yaml\Exception;

/**
 * Exception interface for all exceptions thrown by the component.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
interface ExceptionInterface
{
}
vendor/symfony/polyfill-ctype/LICENSE000064400000002051152177723700013537 0ustar00Copyright (c) 2018-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-ctype/bootstrap.php000064400000002123152177723700015260 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Ctype as p;

if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
    function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
    function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
    function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
    function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
    function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
    function ctype_print($text) { return p\Ctype::ctype_print($text); }
    function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
    function ctype_space($text) { return p\Ctype::ctype_space($text); }
    function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
    function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
vendor/symfony/polyfill-ctype/Ctype.php000064400000014175152177723700014341 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Ctype;

/**
 * Ctype implementation through regex.
 *
 * @internal
 *
 * @author Gert de Pagter <BackEndTea@gmail.com>
 */
final class Ctype
{
    /**
     * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-alnum
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alnum($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a letter, FALSE otherwise.
     *
     * @see https://php.net/ctype-alpha
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_alpha($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
     *
     * @see https://php.net/ctype-cntrl
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_cntrl($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
    }

    /**
     * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
     *
     * @see https://php.net/ctype-digit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_digit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
     *
     * @see https://php.net/ctype-graph
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_graph($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a lowercase letter.
     *
     * @see https://php.net/ctype-lower
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_lower($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
    }

    /**
     * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
     *
     * @see https://php.net/ctype-print
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_print($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
    }

    /**
     * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
     *
     * @see https://php.net/ctype-punct
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_punct($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
    }

    /**
     * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
     *
     * @see https://php.net/ctype-space
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_space($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
    }

    /**
     * Returns TRUE if every character in text is an uppercase letter.
     *
     * @see https://php.net/ctype-upper
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_upper($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
    }

    /**
     * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
     *
     * @see https://php.net/ctype-xdigit
     *
     * @param string|int $text
     *
     * @return bool
     */
    public static function ctype_xdigit($text)
    {
        $text = self::convert_int_to_char_for_ctype($text);

        return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
    }

    /**
     * Converts integers to their char versions according to normal ctype behaviour, if needed.
     *
     * If an integer between -128 and 255 inclusive is provided,
     * it is interpreted as the ASCII value of a single character
     * (negative values have 256 added in order to allow characters in the Extended ASCII range).
     * Any other integer is interpreted as a string containing the decimal digits of the integer.
     *
     * @param string|int $int
     *
     * @return mixed
     */
    private static function convert_int_to_char_for_ctype($int)
    {
        if (!\is_int($int)) {
            return $int;
        }

        if ($int < -128 || $int > 255) {
            return (string) $int;
        }

        if ($int < 0) {
            $int += 256;
        }

        return \chr($int);
    }
}
vendor/symfony/polyfill-php55/Php55ArrayColumn.php000064400000004211152177723700016136 0ustar00<?php

/*
 *   Copyright (c) 2013 Ben Ramsey <http://benramsey.com>
 *
 *   Permission is hereby granted, free of charge, to any person obtaining a
 *   copy of this software and associated documentation files (the "Software"),
 *   to deal in the Software without restriction, including without limitation
 *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *   and/or sell copies of the Software, and to permit persons to whom the
 *   Software is furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *   DEALINGS IN THE SOFTWARE.
 */

namespace Symfony\Polyfill\Php55;

/**
 * @internal
 */
final class Php55ArrayColumn
{
    public static function array_column(array $input, $columnKey, $indexKey = null)
    {
        $output = array();

        foreach ($input as $row) {
            $key = $value = null;
            $keySet = $valueSet = false;

            if (null !== $indexKey && array_key_exists($indexKey, $row)) {
                $keySet = true;
                $key = (string) $row[$indexKey];
            }

            if (null === $columnKey) {
                $valueSet = true;
                $value = $row;
            } elseif (\is_array($row) && \array_key_exists($columnKey, $row)) {
                $valueSet = true;
                $value = $row[$columnKey];
            }

            if ($valueSet) {
                if ($keySet) {
                    $output[$key] = $value;
                } else {
                    $output[] = $value;
                }
            }
        }

        return $output;
    }
}
vendor/symfony/polyfill-php55/LICENSE000064400000002051152177723700013354 0ustar00Copyright (c) 2015-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-php55/bootstrap.php000064400000001747152177723700015110 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

use Symfony\Polyfill\Php55 as p;

if (PHP_VERSION_ID < 50500) {
    if (!function_exists('boolval')) {
        function boolval($val) { return p\Php55::boolval($val); }
    }
    if (!function_exists('json_last_error_msg')) {
        function json_last_error_msg() { return p\Php55::json_last_error_msg(); }
    }
    if (!function_exists('array_column')) {
        function array_column($array, $columnKey, $indexKey = null) { return p\Php55ArrayColumn::array_column($array, $columnKey, $indexKey); }
    }
    if (!function_exists('hash_pbkdf2')) {
        function hash_pbkdf2($algorithm, $password, $salt, $iterations, $length = 0, $rawOutput = false) { return p\Php55::hash_pbkdf2($algorithm, $password, $salt, $iterations, $length, $rawOutput); }
    }
}
vendor/symfony/polyfill-php55/Php55.php000064400000005076152177723700013773 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Php55;

/**
 * @internal
 */
final class Php55
{
    public static function boolval($val)
    {
        return (bool) $val;
    }

    public static function json_last_error_msg()
    {
        switch (json_last_error()) {
            case JSON_ERROR_NONE: return 'No error';
            case JSON_ERROR_DEPTH: return 'Maximum stack depth exceeded';
            case JSON_ERROR_STATE_MISMATCH: return 'State mismatch (invalid or malformed JSON)';
            case JSON_ERROR_CTRL_CHAR: return 'Control character error, possibly incorrectly encoded';
            case JSON_ERROR_SYNTAX: return 'Syntax error';
            case JSON_ERROR_UTF8: return 'Malformed UTF-8 characters, possibly incorrectly encoded';
            default: return 'Unknown error';
        }
    }

    /**
     * @author Sebastiaan Stok <s.stok@rollerscapes.net>
     * @author Scott <scott@paragonie.com>
     */
    public static function hash_pbkdf2($algorithm, $password, $salt, $iterations, $length = 0, $rawOutput = false)
    {
        // Pre-hash for optimization if password length > hash length
        $hashLength = \strlen(hash($algorithm, '', true));
        switch ($algorithm) {
            case 'sha224':
            case 'sha256':
                $blockSize = 64;
                break;
            case 'sha384':
            case 'sha512':
                $blockSize = 128;
                break;
            default:
                $blockSize = $hashLength;
                break;
        }
        if ($length < 1) {
            $length = $hashLength;
            if (!$rawOutput) {
                $length <<= 1;
            }
        }

        // Number of blocks needed to create the derived key
        $blocks = ceil($length / $hashLength);
        $digest = '';
        if (\strlen($password) > $blockSize) {
            $password = hash($algorithm, $password, true);
        }

        for ($i = 1; $i <= $blocks; ++$i) {
            $ib = $block = hash_hmac($algorithm, $salt.pack('N', $i), $password, true);

            // Iterations
            for ($j = 1; $j < $iterations; ++$j) {
                $ib ^= ($block = hash_hmac($algorithm, $block, $password, true));
            }

            $digest .= $ib;
        }

        if (!$rawOutput) {
            $digest = bin2hex($digest);
        }

        return substr($digest, 0, $length);
    }
}
vendor/symfony/polyfill-util/LICENSE000064400000002051152177723700013370 0ustar00Copyright (c) 2015-2019 Fabien Potencier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
vendor/symfony/polyfill-util/Binary.php000064400000000664152177723700014330 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Util;

if (\extension_loaded('mbstring')) {
    class Binary extends BinaryOnFuncOverload
    {
    }
} else {
    class Binary extends BinaryNoFuncOverload
    {
    }
}
vendor/symfony/polyfill-util/BinaryNoFuncOverload.php000064400000002651152177723700017133 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Util;

/**
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
class BinaryNoFuncOverload
{
    public static function strlen($s)
    {
        return \strlen($s);
    }

    public static function strpos($haystack, $needle, $offset = 0)
    {
        return strpos($haystack, $needle, $offset);
    }

    public static function strrpos($haystack, $needle, $offset = 0)
    {
        return strrpos($haystack, $needle, $offset);
    }

    public static function substr($string, $start, $length = PHP_INT_MAX)
    {
        return substr($string, $start, $length);
    }

    public static function stripos($s, $needle, $offset = 0)
    {
        return stripos($s, $needle, $offset);
    }

    public static function stristr($s, $needle, $part = false)
    {
        return stristr($s, $needle, $part);
    }

    public static function strrchr($s, $needle, $part = false)
    {
        return strrchr($s, $needle, $part);
    }

    public static function strripos($s, $needle, $offset = 0)
    {
        return strripos($s, $needle, $offset);
    }

    public static function strstr($s, $needle, $part = false)
    {
        return strstr($s, $needle, $part);
    }
}
vendor/symfony/polyfill-util/BinaryOnFuncOverload.php000064400000003147152177723700017134 0ustar00<?php

/*
 * This file is part of the Symfony package.
 *
 * (c) Fabien Potencier <fabien@symfony.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Symfony\Polyfill\Util;

/**
 * Binary safe version of string functions overloaded when MB_OVERLOAD_STRING is enabled.
 *
 * @author Nicolas Grekas <p@tchwork.com>
 *
 * @internal
 */
class BinaryOnFuncOverload
{
    public static function strlen($s)
    {
        return mb_strlen($s, '8bit');
    }

    public static function strpos($haystack, $needle, $offset = 0)
    {
        return mb_strpos($haystack, $needle, $offset, '8bit');
    }

    public static function strrpos($haystack, $needle, $offset = 0)
    {
        return mb_strrpos($haystack, $needle, $offset, '8bit');
    }

    public static function substr($string, $start, $length = 2147483647)
    {
        return mb_substr($string, $start, $length, '8bit');
    }

    public static function stripos($s, $needle, $offset = 0)
    {
        return mb_stripos($s, $needle, $offset, '8bit');
    }

    public static function stristr($s, $needle, $part = false)
    {
        return mb_stristr($s, $needle, $part, '8bit');
    }

    public static function strrchr($s, $needle, $part = false)
    {
        return mb_strrchr($s, $needle, $part, '8bit');
    }

    public static function strripos($s, $needle, $offset = 0)
    {
        return mb_strripos($s, $needle, $offset, '8bit');
    }

    public static function strstr($s, $needle, $part = false)
    {
        return mb_strstr($s, $needle, $part, '8bit');
    }
}
vendor/ircmaxell/password-compat/lib/password.php000064400000030213152177723700016277 0ustar00<?php
/**
 * A Compatibility library with PHP 5.5's simplified password hashing API.
 *
 * @author Anthony Ferrara <ircmaxell@php.net>
 * @license http://www.opensource.org/licenses/mit-license.html MIT License
 * @copyright 2012 The Authors
 */

namespace {

    if (!defined('PASSWORD_BCRYPT')) {
        /**
         * PHPUnit Process isolation caches constants, but not function declarations.
         * So we need to check if the constants are defined separately from 
         * the functions to enable supporting process isolation in userland
         * code.
         */
        define('PASSWORD_BCRYPT', 1);
        define('PASSWORD_DEFAULT', PASSWORD_BCRYPT);
        define('PASSWORD_BCRYPT_DEFAULT_COST', 10);
    }

    if (!function_exists('password_hash')) {

        /**
         * Hash the password using the specified algorithm
         *
         * @param string $password The password to hash
         * @param int    $algo     The algorithm to use (Defined by PASSWORD_* constants)
         * @param array  $options  The options for the algorithm to use
         *
         * @return string|false The hashed password, or false on error.
         */
        function password_hash($password, $algo, array $options = array()) {
            if (!function_exists('crypt')) {
                trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING);
                return null;
            }
            if (is_null($password) || is_int($password)) {
                $password = (string) $password;
            }
            if (!is_string($password)) {
                trigger_error("password_hash(): Password must be a string", E_USER_WARNING);
                return null;
            }
            if (!is_int($algo)) {
                trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING);
                return null;
            }
            $resultLength = 0;
            switch ($algo) {
                case PASSWORD_BCRYPT:
                    $cost = PASSWORD_BCRYPT_DEFAULT_COST;
                    if (isset($options['cost'])) {
                        $cost = $options['cost'];
                        if ($cost < 4 || $cost > 31) {
                            trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING);
                            return null;
                        }
                    }
                    // The length of salt to generate
                    $raw_salt_len = 16;
                    // The length required in the final serialization
                    $required_salt_len = 22;
                    $hash_format = sprintf("$2y$%02d$", $cost);
                    // The expected length of the final crypt() output
                    $resultLength = 60;
                    break;
                default:
                    trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING);
                    return null;
            }
            $salt_requires_encoding = false;
            if (isset($options['salt'])) {
                switch (gettype($options['salt'])) {
                    case 'NULL':
                    case 'boolean':
                    case 'integer':
                    case 'double':
                    case 'string':
                        $salt = (string) $options['salt'];
                        break;
                    case 'object':
                        if (method_exists($options['salt'], '__tostring')) {
                            $salt = (string) $options['salt'];
                            break;
                        }
                    case 'array':
                    case 'resource':
                    default:
                        trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING);
                        return null;
                }
                if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) {
                    trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING);
                    return null;
                } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) {
                    $salt_requires_encoding = true;
                }
            } else {
                $buffer = '';
                $buffer_valid = false;
                if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) {
                    $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM);
                    if ($buffer) {
                        $buffer_valid = true;
                    }
                }
                if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) {
                    $buffer = openssl_random_pseudo_bytes($raw_salt_len);
                    if ($buffer) {
                        $buffer_valid = true;
                    }
                }
                if (!$buffer_valid && @is_readable('/dev/urandom')) {
                    $f = fopen('/dev/urandom', 'r');
                    $read = PasswordCompat\binary\_strlen($buffer);
                    while ($read < $raw_salt_len) {
                        $buffer .= fread($f, $raw_salt_len - $read);
                        $read = PasswordCompat\binary\_strlen($buffer);
                    }
                    fclose($f);
                    if ($read >= $raw_salt_len) {
                        $buffer_valid = true;
                    }
                }
                if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) {
                    $bl = PasswordCompat\binary\_strlen($buffer);
                    for ($i = 0; $i < $raw_salt_len; $i++) {
                        if ($i < $bl) {
                            $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255));
                        } else {
                            $buffer .= chr(mt_rand(0, 255));
                        }
                    }
                }
                $salt = $buffer;
                $salt_requires_encoding = true;
            }
            if ($salt_requires_encoding) {
                // encode string with the Base64 variant used by crypt
                $base64_digits =
                    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
                $bcrypt64_digits =
                    './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

                $base64_string = base64_encode($salt);
                $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits);
            }
            $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len);

            $hash = $hash_format . $salt;

            $ret = crypt($password, $hash);

            if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) {
                return false;
            }

            return $ret;
        }

        /**
         * Get information about the password hash. Returns an array of the information
         * that was used to generate the password hash.
         *
         * array(
         *    'algo' => 1,
         *    'algoName' => 'bcrypt',
         *    'options' => array(
         *        'cost' => PASSWORD_BCRYPT_DEFAULT_COST,
         *    ),
         * )
         *
         * @param string $hash The password hash to extract info from
         *
         * @return array The array of information about the hash.
         */
        function password_get_info($hash) {
            $return = array(
                'algo' => 0,
                'algoName' => 'unknown',
                'options' => array(),
            );
            if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) {
                $return['algo'] = PASSWORD_BCRYPT;
                $return['algoName'] = 'bcrypt';
                list($cost) = sscanf($hash, "$2y$%d$");
                $return['options']['cost'] = $cost;
            }
            return $return;
        }

        /**
         * Determine if the password hash needs to be rehashed according to the options provided
         *
         * If the answer is true, after validating the password using password_verify, rehash it.
         *
         * @param string $hash    The hash to test
         * @param int    $algo    The algorithm used for new password hashes
         * @param array  $options The options array passed to password_hash
         *
         * @return boolean True if the password needs to be rehashed.
         */
        function password_needs_rehash($hash, $algo, array $options = array()) {
            $info = password_get_info($hash);
            if ($info['algo'] != $algo) {
                return true;
            }
            switch ($algo) {
                case PASSWORD_BCRYPT:
                    $cost = isset($options['cost']) ? $options['cost'] : PASSWORD_BCRYPT_DEFAULT_COST;
                    if ($cost != $info['options']['cost']) {
                        return true;
                    }
                    break;
            }
            return false;
        }

        /**
         * Verify a password against a hash using a timing attack resistant approach
         *
         * @param string $password The password to verify
         * @param string $hash     The hash to verify against
         *
         * @return boolean If the password matches the hash
         */
        function password_verify($password, $hash) {
            if (!function_exists('crypt')) {
                trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING);
                return false;
            }
            $ret = crypt($password, $hash);
            if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) {
                return false;
            }

            $status = 0;
            for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) {
                $status |= (ord($ret[$i]) ^ ord($hash[$i]));
            }

            return $status === 0;
        }
    }

}

namespace PasswordCompat\binary {

    if (!function_exists('PasswordCompat\\binary\\_strlen')) {

        /**
         * Count the number of bytes in a string
         *
         * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension.
         * In this case, strlen() will count the number of *characters* based on the internal encoding. A
         * sequence of bytes might be regarded as a single multibyte character.
         *
         * @param string $binary_string The input string
         *
         * @internal
         * @return int The number of bytes
         */
        function _strlen($binary_string) {
            if (function_exists('mb_strlen')) {
                return mb_strlen($binary_string, '8bit');
            }
            return strlen($binary_string);
        }

        /**
         * Get a substring based on byte limits
         *
         * @see _strlen()
         *
         * @param string $binary_string The input string
         * @param int    $start
         * @param int    $length
         *
         * @internal
         * @return string The substring
         */
        function _substr($binary_string, $start, $length) {
            if (function_exists('mb_substr')) {
                return mb_substr($binary_string, $start, $length, '8bit');
            }
            return substr($binary_string, $start, $length);
        }

        /**
         * Check if current PHP version is compatible with the library
         *
         * @return boolean the check result
         */
        function check() {
            static $pass = NULL;

            if (is_null($pass)) {
                if (function_exists('crypt')) {
                    $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
                    $test = crypt("password", $hash);
                    $pass = $test == $hash;
                } else {
                    $pass = false;
                }
            }
            return $pass;
        }

    }
}vendor/ircmaxell/password-compat/LICENSE.md000064400000002042152177723700014561 0ustar00Copyright (c) 2012 Anthony Ferrara

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.vendor/composer/LICENSE000064400000002056152177723700010713 0ustar00
Copyright (c) Nils Adermann, Jordi Boggiano

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

vendor/composer/autoload_static.php000064400000077164152177723700013612 0ustar00<?php

// autoload_static.php @generated by Composer

namespace Composer\Autoload;

class ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe
{
    public static $files = array (
        '2fb9d6f23c8e8faefc193a4cde0cab4f' => __DIR__ . '/..' . '/joomla/string/src/phputf8/utf8.php',
        'e6851e0ae7328fe5412fcec73928f3d9' => __DIR__ . '/..' . '/joomla/string/src/phputf8/ord.php',
        'd9ad1b7c85c100a18c404a13824b846e' => __DIR__ . '/..' . '/joomla/string/src/phputf8/str_ireplace.php',
        '62bad9b6730d2f83493d2337bf61519d' => __DIR__ . '/..' . '/joomla/string/src/phputf8/str_pad.php',
        'c4d521b8d54308532dce032713d4eec0' => __DIR__ . '/..' . '/joomla/string/src/phputf8/str_split.php',
        'fa973e71cace925de2afdc692b861b1d' => __DIR__ . '/..' . '/joomla/string/src/phputf8/strcasecmp.php',
        '0c98c2f1295d9f4d093cc77d5834bb04' => __DIR__ . '/..' . '/joomla/string/src/phputf8/strcspn.php',
        'a52639d843b4094945115c178a91ca86' => __DIR__ . '/..' . '/joomla/string/src/phputf8/stristr.php',
        '73ee7d0297e683c4c2e7798ef040fb2f' => __DIR__ . '/..' . '/joomla/string/src/phputf8/strrev.php',
        'd55633c05ddb996e0005f35debaa7b5b' => __DIR__ . '/..' . '/joomla/string/src/phputf8/strspn.php',
        '944e69d23b93558fc0714353cf0c8beb' => __DIR__ . '/..' . '/joomla/string/src/phputf8/trim.php',
        '31264bab20f14a8fc7a9d4265d91ee98' => __DIR__ . '/..' . '/joomla/string/src/phputf8/ucfirst.php',
        '05d739a990f75f0c44ebe1f032b33148' => __DIR__ . '/..' . '/joomla/string/src/phputf8/ucwords.php',
        '4292e2fa66516089e6006723267587b4' => __DIR__ . '/..' . '/joomla/string/src/phputf8/utils/ascii.php',
        '87465e33b7551b401bf051928f220e9a' => __DIR__ . '/..' . '/joomla/string/src/phputf8/utils/validation.php',
        '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
        'e40631d46120a9c38ea139981f8dab26' => __DIR__ . '/..' . '/ircmaxell/password-compat/lib/password.php',
        '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php',
        'edc6464955a37aa4d5fbf39d40fb6ee7' => __DIR__ . '/..' . '/symfony/polyfill-php55/bootstrap.php',
        'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php',
        '3109cb1a231dcd04bee1f9f620d46975' => __DIR__ . '/..' . '/paragonie/sodium_compat/autoload.php',
        'e277be14c90068cf94faed2c43dbe6d8' => __DIR__ . '/..' . '/symfony/polyfill-php71/bootstrap.php',
        '0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
    );

    public static $prefixLengthsPsr4 = array (
        'T' => 
        array (
            'TYPO3\\PharStreamWrapper\\' => 24,
        ),
        'S' => 
        array (
            'Symfony\\Polyfill\\Util\\' => 22,
            'Symfony\\Polyfill\\Php73\\' => 23,
            'Symfony\\Polyfill\\Php71\\' => 23,
            'Symfony\\Polyfill\\Php56\\' => 23,
            'Symfony\\Polyfill\\Php55\\' => 23,
            'Symfony\\Polyfill\\Ctype\\' => 23,
            'Symfony\\Component\\Yaml\\' => 23,
        ),
        'R' => 
        array (
            'ReCaptcha\\' => 10,
        ),
        'P' => 
        array (
            'Psr\\Log\\' => 8,
            'Psr\\Container\\' => 14,
        ),
        'J' => 
        array (
            'Joomla\\Utilities\\' => 17,
            'Joomla\\Uri\\' => 11,
            'Joomla\\String\\' => 14,
            'Joomla\\Registry\\' => 16,
            'Joomla\\Ldap\\' => 12,
            'Joomla\\Input\\' => 13,
            'Joomla\\Image\\' => 13,
            'Joomla\\Filter\\' => 14,
            'Joomla\\Filesystem\\' => 18,
            'Joomla\\Event\\Tests\\' => 19,
            'Joomla\\Event\\' => 13,
            'Joomla\\Data\\Tests\\' => 18,
            'Joomla\\Data\\' => 12,
            'Joomla\\DI\\' => 10,
            'Joomla\\Archive\\' => 15,
            'Joomla\\Application\\' => 19,
        ),
        'B' => 
        array (
            'Brumann\\Polyfill\\' => 17,
        ),
    );

    public static $prefixDirsPsr4 = array (
        'TYPO3\\PharStreamWrapper\\' => 
        array (
            0 => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src',
        ),
        'Symfony\\Polyfill\\Util\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-util',
        ),
        'Symfony\\Polyfill\\Php73\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
        ),
        'Symfony\\Polyfill\\Php71\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php71',
        ),
        'Symfony\\Polyfill\\Php56\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php56',
        ),
        'Symfony\\Polyfill\\Php55\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-php55',
        ),
        'Symfony\\Polyfill\\Ctype\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
        ),
        'Symfony\\Component\\Yaml\\' => 
        array (
            0 => __DIR__ . '/..' . '/symfony/yaml',
        ),
        'ReCaptcha\\' => 
        array (
            0 => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha',
        ),
        'Psr\\Log\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
        ),
        'Psr\\Container\\' => 
        array (
            0 => __DIR__ . '/..' . '/psr/container/src',
        ),
        'Joomla\\Utilities\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/utilities/src',
        ),
        'Joomla\\Uri\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/uri/src',
        ),
        'Joomla\\String\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/string/src',
        ),
        'Joomla\\Registry\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/registry/src',
        ),
        'Joomla\\Ldap\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/ldap/src',
        ),
        'Joomla\\Input\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/input/src',
        ),
        'Joomla\\Image\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/image/src',
        ),
        'Joomla\\Filter\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/filter/src',
        ),
        'Joomla\\Filesystem\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/filesystem/src',
        ),
        'Joomla\\Event\\Tests\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/event/Tests',
        ),
        'Joomla\\Event\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/event/src',
        ),
        'Joomla\\Data\\Tests\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/data/Tests',
        ),
        'Joomla\\Data\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/data/src',
        ),
        'Joomla\\DI\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/di/src',
        ),
        'Joomla\\Archive\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/archive/src',
        ),
        'Joomla\\Application\\' => 
        array (
            0 => __DIR__ . '/..' . '/joomla/application/src',
        ),
        'Brumann\\Polyfill\\' => 
        array (
            0 => __DIR__ . '/..' . '/brumann/polyfill-unserialize/src',
        ),
    );

    public static $prefixesPsr0 = array (
        'S' => 
        array (
            'SimplePie' => 
            array (
                0 => __DIR__ . '/..' . '/simplepie/simplepie/library',
            ),
        ),
        'J' => 
        array (
            'Joomla\\Session' => 
            array (
                0 => __DIR__ . '/..' . '/joomla/session',
            ),
        ),
    );

    public static $classMap = array (
        'Brumann\\Polyfill\\Unserialize' => __DIR__ . '/..' . '/brumann/polyfill-unserialize/src/Unserialize.php',
        'CallbackFilterIterator' => __DIR__ . '/..' . '/joomla/compat/src/CallbackFilterIterator.php',
        'EasyPeasyICS' => __DIR__ . '/..' . '/phpmailer/phpmailer/extras/EasyPeasyICS.php',
        'Joomla\\Application\\AbstractApplication' => __DIR__ . '/..' . '/joomla/application/src/AbstractApplication.php',
        'Joomla\\Application\\AbstractCliApplication' => __DIR__ . '/..' . '/joomla/application/src/AbstractCliApplication.php',
        'Joomla\\Application\\AbstractDaemonApplication' => __DIR__ . '/..' . '/joomla/application/src/AbstractDaemonApplication.php',
        'Joomla\\Application\\AbstractWebApplication' => __DIR__ . '/..' . '/joomla/application/src/AbstractWebApplication.php',
        'Joomla\\Application\\Cli\\CliInput' => __DIR__ . '/..' . '/joomla/application/src/Cli/CliInput.php',
        'Joomla\\Application\\Cli\\CliOutput' => __DIR__ . '/..' . '/joomla/application/src/Cli/CliOutput.php',
        'Joomla\\Application\\Cli\\ColorProcessor' => __DIR__ . '/..' . '/joomla/application/src/Cli/ColorProcessor.php',
        'Joomla\\Application\\Cli\\ColorStyle' => __DIR__ . '/..' . '/joomla/application/src/Cli/ColorStyle.php',
        'Joomla\\Application\\Cli\\Output\\Processor\\ColorProcessor' => __DIR__ . '/..' . '/joomla/application/src/Cli/Output/Processor/ColorProcessor.php',
        'Joomla\\Application\\Cli\\Output\\Processor\\ProcessorInterface' => __DIR__ . '/..' . '/joomla/application/src/Cli/Output/Processor/ProcessorInterface.php',
        'Joomla\\Application\\Cli\\Output\\Stdout' => __DIR__ . '/..' . '/joomla/application/src/Cli/Output/Stdout.php',
        'Joomla\\Application\\Cli\\Output\\Xml' => __DIR__ . '/..' . '/joomla/application/src/Cli/Output/Xml.php',
        'Joomla\\Application\\Web\\WebClient' => __DIR__ . '/..' . '/joomla/application/src/Web/WebClient.php',
        'Joomla\\Archive\\Archive' => __DIR__ . '/..' . '/joomla/archive/src/Archive.php',
        'Joomla\\Archive\\Bzip2' => __DIR__ . '/..' . '/joomla/archive/src/Bzip2.php',
        'Joomla\\Archive\\ExtractableInterface' => __DIR__ . '/..' . '/joomla/archive/src/ExtractableInterface.php',
        'Joomla\\Archive\\Gzip' => __DIR__ . '/..' . '/joomla/archive/src/Gzip.php',
        'Joomla\\Archive\\Tar' => __DIR__ . '/..' . '/joomla/archive/src/Tar.php',
        'Joomla\\Archive\\Zip' => __DIR__ . '/..' . '/joomla/archive/src/Zip.php',
        'Joomla\\DI\\Container' => __DIR__ . '/..' . '/joomla/di/src/Container.php',
        'Joomla\\DI\\ContainerAwareInterface' => __DIR__ . '/..' . '/joomla/di/src/ContainerAwareInterface.php',
        'Joomla\\DI\\ContainerAwareTrait' => __DIR__ . '/..' . '/joomla/di/src/ContainerAwareTrait.php',
        'Joomla\\DI\\Exception\\DependencyResolutionException' => __DIR__ . '/..' . '/joomla/di/src/Exception/DependencyResolutionException.php',
        'Joomla\\DI\\Exception\\KeyNotFoundException' => __DIR__ . '/..' . '/joomla/di/src/Exception/KeyNotFoundException.php',
        'Joomla\\DI\\Exception\\ProtectedKeyException' => __DIR__ . '/..' . '/joomla/di/src/Exception/ProtectedKeyException.php',
        'Joomla\\DI\\ServiceProviderInterface' => __DIR__ . '/..' . '/joomla/di/src/ServiceProviderInterface.php',
        'Joomla\\Data\\DataObject' => __DIR__ . '/..' . '/joomla/data/src/DataObject.php',
        'Joomla\\Data\\DataSet' => __DIR__ . '/..' . '/joomla/data/src/DataSet.php',
        'Joomla\\Data\\DumpableInterface' => __DIR__ . '/..' . '/joomla/data/src/DumpableInterface.php',
        'Joomla\\Event\\AbstractEvent' => __DIR__ . '/..' . '/joomla/event/src/AbstractEvent.php',
        'Joomla\\Event\\DelegatingDispatcher' => __DIR__ . '/..' . '/joomla/event/src/DelegatingDispatcher.php',
        'Joomla\\Event\\Dispatcher' => __DIR__ . '/..' . '/joomla/event/src/Dispatcher.php',
        'Joomla\\Event\\DispatcherAwareInterface' => __DIR__ . '/..' . '/joomla/event/src/DispatcherAwareInterface.php',
        'Joomla\\Event\\DispatcherAwareTrait' => __DIR__ . '/..' . '/joomla/event/src/DispatcherAwareTrait.php',
        'Joomla\\Event\\DispatcherInterface' => __DIR__ . '/..' . '/joomla/event/src/DispatcherInterface.php',
        'Joomla\\Event\\Event' => __DIR__ . '/..' . '/joomla/event/src/Event.php',
        'Joomla\\Event\\EventImmutable' => __DIR__ . '/..' . '/joomla/event/src/EventImmutable.php',
        'Joomla\\Event\\EventInterface' => __DIR__ . '/..' . '/joomla/event/src/EventInterface.php',
        'Joomla\\Event\\ListenersPriorityQueue' => __DIR__ . '/..' . '/joomla/event/src/ListenersPriorityQueue.php',
        'Joomla\\Event\\Priority' => __DIR__ . '/..' . '/joomla/event/src/Priority.php',
        'Joomla\\Filesystem\\Buffer' => __DIR__ . '/..' . '/joomla/filesystem/src/Buffer.php',
        'Joomla\\Filesystem\\Clients\\FtpClient' => __DIR__ . '/..' . '/joomla/filesystem/src/Clients/FtpClient.php',
        'Joomla\\Filesystem\\Exception\\FilesystemException' => __DIR__ . '/..' . '/joomla/filesystem/src/Exception/FilesystemException.php',
        'Joomla\\Filesystem\\File' => __DIR__ . '/..' . '/joomla/filesystem/src/File.php',
        'Joomla\\Filesystem\\Folder' => __DIR__ . '/..' . '/joomla/filesystem/src/Folder.php',
        'Joomla\\Filesystem\\Helper' => __DIR__ . '/..' . '/joomla/filesystem/src/Helper.php',
        'Joomla\\Filesystem\\Patcher' => __DIR__ . '/..' . '/joomla/filesystem/src/Patcher.php',
        'Joomla\\Filesystem\\Path' => __DIR__ . '/..' . '/joomla/filesystem/src/Path.php',
        'Joomla\\Filesystem\\Stream' => __DIR__ . '/..' . '/joomla/filesystem/src/Stream.php',
        'Joomla\\Filesystem\\Stream\\String' => __DIR__ . '/..' . '/joomla/filesystem/src/Stream/String.php',
        'Joomla\\Filesystem\\Stream\\StringWrapper' => __DIR__ . '/..' . '/joomla/filesystem/src/Stream/StringWrapper.php',
        'Joomla\\Filesystem\\Support\\StringController' => __DIR__ . '/..' . '/joomla/filesystem/src/Support/StringController.php',
        'Joomla\\Filter\\InputFilter' => __DIR__ . '/..' . '/joomla/filter/src/InputFilter.php',
        'Joomla\\Filter\\OutputFilter' => __DIR__ . '/..' . '/joomla/filter/src/OutputFilter.php',
        'Joomla\\Image\\Filter\\Backgroundfill' => __DIR__ . '/..' . '/joomla/image/src/Filter/Backgroundfill.php',
        'Joomla\\Image\\Filter\\Brightness' => __DIR__ . '/..' . '/joomla/image/src/Filter/Brightness.php',
        'Joomla\\Image\\Filter\\Contrast' => __DIR__ . '/..' . '/joomla/image/src/Filter/Contrast.php',
        'Joomla\\Image\\Filter\\Edgedetect' => __DIR__ . '/..' . '/joomla/image/src/Filter/Edgedetect.php',
        'Joomla\\Image\\Filter\\Emboss' => __DIR__ . '/..' . '/joomla/image/src/Filter/Emboss.php',
        'Joomla\\Image\\Filter\\Grayscale' => __DIR__ . '/..' . '/joomla/image/src/Filter/Grayscale.php',
        'Joomla\\Image\\Filter\\Negate' => __DIR__ . '/..' . '/joomla/image/src/Filter/Negate.php',
        'Joomla\\Image\\Filter\\Sketchy' => __DIR__ . '/..' . '/joomla/image/src/Filter/Sketchy.php',
        'Joomla\\Image\\Filter\\Smooth' => __DIR__ . '/..' . '/joomla/image/src/Filter/Smooth.php',
        'Joomla\\Image\\Image' => __DIR__ . '/..' . '/joomla/image/src/Image.php',
        'Joomla\\Image\\ImageFilter' => __DIR__ . '/..' . '/joomla/image/src/ImageFilter.php',
        'Joomla\\Input\\Cli' => __DIR__ . '/..' . '/joomla/input/src/Cli.php',
        'Joomla\\Input\\Cookie' => __DIR__ . '/..' . '/joomla/input/src/Cookie.php',
        'Joomla\\Input\\Files' => __DIR__ . '/..' . '/joomla/input/src/Files.php',
        'Joomla\\Input\\Input' => __DIR__ . '/..' . '/joomla/input/src/Input.php',
        'Joomla\\Input\\Json' => __DIR__ . '/..' . '/joomla/input/src/Json.php',
        'Joomla\\Ldap\\LdapClient' => __DIR__ . '/..' . '/joomla/ldap/src/LdapClient.php',
        'Joomla\\Registry\\AbstractRegistryFormat' => __DIR__ . '/..' . '/joomla/registry/src/AbstractRegistryFormat.php',
        'Joomla\\Registry\\Factory' => __DIR__ . '/..' . '/joomla/registry/src/Factory.php',
        'Joomla\\Registry\\FormatInterface' => __DIR__ . '/..' . '/joomla/registry/src/FormatInterface.php',
        'Joomla\\Registry\\Format\\Ini' => __DIR__ . '/..' . '/joomla/registry/src/Format/Ini.php',
        'Joomla\\Registry\\Format\\Json' => __DIR__ . '/..' . '/joomla/registry/src/Format/Json.php',
        'Joomla\\Registry\\Format\\Php' => __DIR__ . '/..' . '/joomla/registry/src/Format/Php.php',
        'Joomla\\Registry\\Format\\Xml' => __DIR__ . '/..' . '/joomla/registry/src/Format/Xml.php',
        'Joomla\\Registry\\Format\\Yaml' => __DIR__ . '/..' . '/joomla/registry/src/Format/Yaml.php',
        'Joomla\\Registry\\Registry' => __DIR__ . '/..' . '/joomla/registry/src/Registry.php',
        'Joomla\\Session\\Session' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Session.php',
        'Joomla\\Session\\Storage' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage.php',
        'Joomla\\Session\\Storage\\Apc' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Apc.php',
        'Joomla\\Session\\Storage\\Apcu' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Apcu.php',
        'Joomla\\Session\\Storage\\Database' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Database.php',
        'Joomla\\Session\\Storage\\Memcache' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Memcache.php',
        'Joomla\\Session\\Storage\\Memcached' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Memcached.php',
        'Joomla\\Session\\Storage\\None' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/None.php',
        'Joomla\\Session\\Storage\\Wincache' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Wincache.php',
        'Joomla\\Session\\Storage\\Xcache' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Storage/Xcache.php',
        'Joomla\\Session\\Tests\\StorageCase' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/StorageCase.php',
        'Joomla\\Session\\Tests\\StorageTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/StorageTest.php',
        'Joomla\\Session\\Tests\\Storage\\ApcTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/ApcTest.php',
        'Joomla\\Session\\Tests\\Storage\\DatabaseTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/DatabaseTest.php',
        'Joomla\\Session\\Tests\\Storage\\MemcacheTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/MemcacheTest.php',
        'Joomla\\Session\\Tests\\Storage\\MemcachedTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/MemcachedTest.php',
        'Joomla\\Session\\Tests\\Storage\\NoneTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/NoneTest.php',
        'Joomla\\Session\\Tests\\Storage\\WincacheTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/WincacheTest.php',
        'Joomla\\Session\\Tests\\Storage\\XcacheTest' => __DIR__ . '/..' . '/joomla/session/Joomla/Session/Tests/Storage/XcacheTest.php',
        'Joomla\\String\\Inflector' => __DIR__ . '/..' . '/joomla/string/src/Inflector.php',
        'Joomla\\String\\Normalise' => __DIR__ . '/..' . '/joomla/string/src/Normalise.php',
        'Joomla\\String\\String' => __DIR__ . '/..' . '/joomla/string/src/String.php',
        'Joomla\\String\\StringHelper' => __DIR__ . '/..' . '/joomla/string/src/StringHelper.php',
        'Joomla\\Uri\\AbstractUri' => __DIR__ . '/..' . '/joomla/uri/src/AbstractUri.php',
        'Joomla\\Uri\\Uri' => __DIR__ . '/..' . '/joomla/uri/src/Uri.php',
        'Joomla\\Uri\\UriHelper' => __DIR__ . '/..' . '/joomla/uri/src/UriHelper.php',
        'Joomla\\Uri\\UriImmutable' => __DIR__ . '/..' . '/joomla/uri/src/UriImmutable.php',
        'Joomla\\Uri\\UriInterface' => __DIR__ . '/..' . '/joomla/uri/src/UriInterface.php',
        'Joomla\\Utilities\\ArrayHelper' => __DIR__ . '/..' . '/joomla/utilities/src/ArrayHelper.php',
        'Joomla\\Utilities\\IpHelper' => __DIR__ . '/..' . '/joomla/utilities/src/IpHelper.php',
        'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
        'JsonSerializable' => __DIR__ . '/..' . '/joomla/compat/src/JsonSerializable.php',
        'PHPMailer' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmailer.php',
        'PHPMailerOAuth' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmaileroauth.php',
        'PHPMailerOAuthGoogle' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmaileroauthgoogle.php',
        'POP3' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.pop3.php',
        'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php',
        'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php',
        'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php',
        'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php',
        'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php',
        'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php',
        'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php',
        'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php',
        'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
        'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
        'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
        'ReCaptcha\\ReCaptcha' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php',
        'ReCaptcha\\RequestMethod' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod.php',
        'ReCaptcha\\RequestMethod\\Curl' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php',
        'ReCaptcha\\RequestMethod\\CurlPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php',
        'ReCaptcha\\RequestMethod\\Post' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php',
        'ReCaptcha\\RequestMethod\\Socket' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php',
        'ReCaptcha\\RequestMethod\\SocketPost' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php',
        'ReCaptcha\\RequestParameters' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/RequestParameters.php',
        'ReCaptcha\\Response' => __DIR__ . '/..' . '/google/recaptcha/src/ReCaptcha/Response.php',
        'SMTP' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.smtp.php',
        'SimplePie' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie.php',
        'SimplePie_Author' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Author.php',
        'SimplePie_Cache' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache.php',
        'SimplePie_Cache_Base' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache/Base.php',
        'SimplePie_Cache_DB' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache/DB.php',
        'SimplePie_Cache_File' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache/File.php',
        'SimplePie_Cache_Memcache' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache/Memcache.php',
        'SimplePie_Cache_MySQL' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Cache/MySQL.php',
        'SimplePie_Caption' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Caption.php',
        'SimplePie_Category' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Category.php',
        'SimplePie_Content_Type_Sniffer' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Content/Type/Sniffer.php',
        'SimplePie_Copyright' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Copyright.php',
        'SimplePie_Core' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Core.php',
        'SimplePie_Credit' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Credit.php',
        'SimplePie_Decode_HTML_Entities' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Decode/HTML/Entities.php',
        'SimplePie_Enclosure' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Enclosure.php',
        'SimplePie_Exception' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Exception.php',
        'SimplePie_File' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/File.php',
        'SimplePie_HTTP_Parser' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/HTTP/Parser.php',
        'SimplePie_IRI' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/IRI.php',
        'SimplePie_Item' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Item.php',
        'SimplePie_Locator' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Locator.php',
        'SimplePie_Misc' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Misc.php',
        'SimplePie_Net_IPv6' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Net/IPv6.php',
        'SimplePie_Parse_Date' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Parse/Date.php',
        'SimplePie_Parser' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Parser.php',
        'SimplePie_Rating' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Rating.php',
        'SimplePie_Registry' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Registry.php',
        'SimplePie_Restriction' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Restriction.php',
        'SimplePie_Sanitize' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Sanitize.php',
        'SimplePie_Source' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/Source.php',
        'SimplePie_XML_Declaration_Parser' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/XML/Declaration/Parser.php',
        'SimplePie_gzdecode' => __DIR__ . '/..' . '/simplepie/simplepie/library/SimplePie/gzdecode.php',
        'Symfony\\Component\\Yaml\\Dumper' => __DIR__ . '/..' . '/symfony/yaml/Dumper.php',
        'Symfony\\Component\\Yaml\\Escaper' => __DIR__ . '/..' . '/symfony/yaml/Escaper.php',
        'Symfony\\Component\\Yaml\\Exception\\DumpException' => __DIR__ . '/..' . '/symfony/yaml/Exception/DumpException.php',
        'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => __DIR__ . '/..' . '/symfony/yaml/Exception/ExceptionInterface.php',
        'Symfony\\Component\\Yaml\\Exception\\ParseException' => __DIR__ . '/..' . '/symfony/yaml/Exception/ParseException.php',
        'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => __DIR__ . '/..' . '/symfony/yaml/Exception/RuntimeException.php',
        'Symfony\\Component\\Yaml\\Inline' => __DIR__ . '/..' . '/symfony/yaml/Inline.php',
        'Symfony\\Component\\Yaml\\Parser' => __DIR__ . '/..' . '/symfony/yaml/Parser.php',
        'Symfony\\Component\\Yaml\\Unescaper' => __DIR__ . '/..' . '/symfony/yaml/Unescaper.php',
        'Symfony\\Component\\Yaml\\Yaml' => __DIR__ . '/..' . '/symfony/yaml/Yaml.php',
        'Symfony\\Polyfill\\Ctype\\Ctype' => __DIR__ . '/..' . '/symfony/polyfill-ctype/Ctype.php',
        'Symfony\\Polyfill\\Php55\\Php55' => __DIR__ . '/..' . '/symfony/polyfill-php55/Php55.php',
        'Symfony\\Polyfill\\Php55\\Php55ArrayColumn' => __DIR__ . '/..' . '/symfony/polyfill-php55/Php55ArrayColumn.php',
        'Symfony\\Polyfill\\Php56\\Php56' => __DIR__ . '/..' . '/symfony/polyfill-php56/Php56.php',
        'Symfony\\Polyfill\\Php71\\Php71' => __DIR__ . '/..' . '/symfony/polyfill-php71/Php71.php',
        'Symfony\\Polyfill\\Php73\\Php73' => __DIR__ . '/..' . '/symfony/polyfill-php73/Php73.php',
        'Symfony\\Polyfill\\Util\\Binary' => __DIR__ . '/..' . '/symfony/polyfill-util/Binary.php',
        'Symfony\\Polyfill\\Util\\BinaryNoFuncOverload' => __DIR__ . '/..' . '/symfony/polyfill-util/BinaryNoFuncOverload.php',
        'Symfony\\Polyfill\\Util\\BinaryOnFuncOverload' => __DIR__ . '/..' . '/symfony/polyfill-util/BinaryOnFuncOverload.php',
        'TYPO3\\PharStreamWrapper\\Assertable' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Assertable.php',
        'TYPO3\\PharStreamWrapper\\Behavior' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Behavior.php',
        'TYPO3\\PharStreamWrapper\\Collectable' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Collectable.php',
        'TYPO3\\PharStreamWrapper\\Exception' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Exception.php',
        'TYPO3\\PharStreamWrapper\\Helper' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Helper.php',
        'TYPO3\\PharStreamWrapper\\Interceptor\\ConjunctionInterceptor' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Interceptor/ConjunctionInterceptor.php',
        'TYPO3\\PharStreamWrapper\\Interceptor\\PharExtensionInterceptor' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php',
        'TYPO3\\PharStreamWrapper\\Interceptor\\PharMetaDataInterceptor' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php',
        'TYPO3\\PharStreamWrapper\\Manager' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Manager.php',
        'TYPO3\\PharStreamWrapper\\PharStreamWrapper' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/PharStreamWrapper.php',
        'TYPO3\\PharStreamWrapper\\Phar\\Container' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/Container.php',
        'TYPO3\\PharStreamWrapper\\Phar\\DeserializationException' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php',
        'TYPO3\\PharStreamWrapper\\Phar\\Manifest' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/Manifest.php',
        'TYPO3\\PharStreamWrapper\\Phar\\Reader' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/Reader.php',
        'TYPO3\\PharStreamWrapper\\Phar\\ReaderException' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/ReaderException.php',
        'TYPO3\\PharStreamWrapper\\Phar\\Stub' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Phar/Stub.php',
        'TYPO3\\PharStreamWrapper\\Resolvable' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Resolvable.php',
        'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocation' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocation.php',
        'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocationCollection' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php',
        'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocationResolver' => __DIR__ . '/..' . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php',
        'lessc' => __DIR__ . '/..' . '/leafo/lessphp/lessc.inc.php',
        'lessc_formatter_classic' => __DIR__ . '/..' . '/leafo/lessphp/lessc.inc.php',
        'lessc_formatter_compressed' => __DIR__ . '/..' . '/leafo/lessphp/lessc.inc.php',
        'lessc_formatter_lessjs' => __DIR__ . '/..' . '/leafo/lessphp/lessc.inc.php',
        'lessc_parser' => __DIR__ . '/..' . '/leafo/lessphp/lessc.inc.php',
        'ntlm_sasl_client_class' => __DIR__ . '/..' . '/phpmailer/phpmailer/extras/ntlm_sasl_client.php',
        'phpmailerException' => __DIR__ . '/..' . '/phpmailer/phpmailer/class.phpmailer.php',
    );

    public static function getInitializer(ClassLoader $loader)
    {
        return \Closure::bind(function () use ($loader) {
            $loader->prefixLengthsPsr4 = ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::$prefixLengthsPsr4;
            $loader->prefixDirsPsr4 = ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::$prefixDirsPsr4;
            $loader->prefixesPsr0 = ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::$prefixesPsr0;
            $loader->classMap = ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::$classMap;

        }, null, ClassLoader::class);
    }
}
vendor/composer/autoload_real.php000064400000004556152177723700013241 0ustar00<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit205c915b9c7d3e718e7c95793ee67ffe
{
    private static $loader;

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInit205c915b9c7d3e718e7c95793ee67ffe', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInit205c915b9c7d3e718e7c95793ee67ffe', 'loadClassLoader'));

        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

        $loader->register(true);

        if ($useStaticLoader) {
            $includeFiles = Composer\Autoload\ComposerStaticInit205c915b9c7d3e718e7c95793ee67ffe::$files;
        } else {
            $includeFiles = require __DIR__ . '/autoload_files.php';
        }
        foreach ($includeFiles as $fileIdentifier => $file) {
            composerRequire205c915b9c7d3e718e7c95793ee67ffe($fileIdentifier, $file);
        }

        return $loader;
    }
}

function composerRequire205c915b9c7d3e718e7c95793ee67ffe($fileIdentifier, $file)
{
    if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
        require $file;

        $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
    }
}
vendor/composer/autoload_namespaces.php000064400000000445152177723700014426 0ustar00<?php

// autoload_namespaces.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));

return array(
    'SimplePie' => array($vendorDir . '/simplepie/simplepie/library'),
    'Joomla\\Session' => array($vendorDir . '/joomla/session'),
);
vendor/composer/autoload_files.php000064400000004533152177723700013413 0ustar00<?php

// autoload_files.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));

return array(
    '2fb9d6f23c8e8faefc193a4cde0cab4f' => $vendorDir . '/joomla/string/src/phputf8/utf8.php',
    'e6851e0ae7328fe5412fcec73928f3d9' => $vendorDir . '/joomla/string/src/phputf8/ord.php',
    'd9ad1b7c85c100a18c404a13824b846e' => $vendorDir . '/joomla/string/src/phputf8/str_ireplace.php',
    '62bad9b6730d2f83493d2337bf61519d' => $vendorDir . '/joomla/string/src/phputf8/str_pad.php',
    'c4d521b8d54308532dce032713d4eec0' => $vendorDir . '/joomla/string/src/phputf8/str_split.php',
    'fa973e71cace925de2afdc692b861b1d' => $vendorDir . '/joomla/string/src/phputf8/strcasecmp.php',
    '0c98c2f1295d9f4d093cc77d5834bb04' => $vendorDir . '/joomla/string/src/phputf8/strcspn.php',
    'a52639d843b4094945115c178a91ca86' => $vendorDir . '/joomla/string/src/phputf8/stristr.php',
    '73ee7d0297e683c4c2e7798ef040fb2f' => $vendorDir . '/joomla/string/src/phputf8/strrev.php',
    'd55633c05ddb996e0005f35debaa7b5b' => $vendorDir . '/joomla/string/src/phputf8/strspn.php',
    '944e69d23b93558fc0714353cf0c8beb' => $vendorDir . '/joomla/string/src/phputf8/trim.php',
    '31264bab20f14a8fc7a9d4265d91ee98' => $vendorDir . '/joomla/string/src/phputf8/ucfirst.php',
    '05d739a990f75f0c44ebe1f032b33148' => $vendorDir . '/joomla/string/src/phputf8/ucwords.php',
    '4292e2fa66516089e6006723267587b4' => $vendorDir . '/joomla/string/src/phputf8/utils/ascii.php',
    '87465e33b7551b401bf051928f220e9a' => $vendorDir . '/joomla/string/src/phputf8/utils/validation.php',
    '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
    'e40631d46120a9c38ea139981f8dab26' => $vendorDir . '/ircmaxell/password-compat/lib/password.php',
    '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php',
    'edc6464955a37aa4d5fbf39d40fb6ee7' => $vendorDir . '/symfony/polyfill-php55/bootstrap.php',
    'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php',
    '3109cb1a231dcd04bee1f9f620d46975' => $vendorDir . '/paragonie/sodium_compat/autoload.php',
    'e277be14c90068cf94faed2c43dbe6d8' => $vendorDir . '/symfony/polyfill-php71/bootstrap.php',
    '0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
);
vendor/composer/ClassLoader.php000064400000032223152177723700012612 0ustar00<?php

/*
 * This file is part of Composer.
 *
 * (c) Nils Adermann <naderman@naderman.de>
 *     Jordi Boggiano <j.boggiano@seld.be>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Composer\Autoload;

/**
 * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
 *
 *     $loader = new \Composer\Autoload\ClassLoader();
 *
 *     // register classes with namespaces
 *     $loader->add('Symfony\Component', __DIR__.'/component');
 *     $loader->add('Symfony',           __DIR__.'/framework');
 *
 *     // activate the autoloader
 *     $loader->register();
 *
 *     // to enable searching the include path (eg. for PEAR packages)
 *     $loader->setUseIncludePath(true);
 *
 * In this example, if you try to use a class in the Symfony\Component
 * namespace or one of its children (Symfony\Component\Console for instance),
 * the autoloader will first look for the class under the component/
 * directory, and it will then fallback to the framework/ directory if not
 * found before giving up.
 *
 * This class is loosely based on the Symfony UniversalClassLoader.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 * @author Jordi Boggiano <j.boggiano@seld.be>
 * @see    http://www.php-fig.org/psr/psr-0/
 * @see    http://www.php-fig.org/psr/psr-4/
 */
class ClassLoader
{
    // PSR-4
    private $prefixLengthsPsr4 = array();
    private $prefixDirsPsr4 = array();
    private $fallbackDirsPsr4 = array();

    // PSR-0
    private $prefixesPsr0 = array();
    private $fallbackDirsPsr0 = array();

    private $useIncludePath = false;
    private $classMap = array();
    private $classMapAuthoritative = false;
    private $missingClasses = array();
    private $apcuPrefix;

    public function getPrefixes()
    {
        if (!empty($this->prefixesPsr0)) {
            return call_user_func_array('array_merge', $this->prefixesPsr0);
        }

        return array();
    }

    public function getPrefixesPsr4()
    {
        return $this->prefixDirsPsr4;
    }

    public function getFallbackDirs()
    {
        return $this->fallbackDirsPsr0;
    }

    public function getFallbackDirsPsr4()
    {
        return $this->fallbackDirsPsr4;
    }

    public function getClassMap()
    {
        return $this->classMap;
    }

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix, either
     * appending or prepending to the ones previously set for this prefix.
     *
     * @param string       $prefix  The prefix
     * @param array|string $paths   The PSR-0 root directories
     * @param bool         $prepend Whether to prepend the directories
     */
    public function add($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            if ($prepend) {
                $this->fallbackDirsPsr0 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr0
                );
            } else {
                $this->fallbackDirsPsr0 = array_merge(
                    $this->fallbackDirsPsr0,
                    (array) $paths
                );
            }

            return;
        }

        $first = $prefix[0];
        if (!isset($this->prefixesPsr0[$first][$prefix])) {
            $this->prefixesPsr0[$first][$prefix] = (array) $paths;

            return;
        }
        if ($prepend) {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                (array) $paths,
                $this->prefixesPsr0[$first][$prefix]
            );
        } else {
            $this->prefixesPsr0[$first][$prefix] = array_merge(
                $this->prefixesPsr0[$first][$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string       $prefix  The prefix/namespace, with trailing '\\'
     * @param array|string $paths   The PSR-4 base directories
     * @param bool         $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public function addPsr4($prefix, $paths, $prepend = false)
    {
        if (!$prefix) {
            // Register directories for the root namespace.
            if ($prepend) {
                $this->fallbackDirsPsr4 = array_merge(
                    (array) $paths,
                    $this->fallbackDirsPsr4
                );
            } else {
                $this->fallbackDirsPsr4 = array_merge(
                    $this->fallbackDirsPsr4,
                    (array) $paths
                );
            }
        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                (array) $paths,
                $this->prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            $this->prefixDirsPsr4[$prefix] = array_merge(
                $this->prefixDirsPsr4[$prefix],
                (array) $paths
            );
        }
    }

    /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */
    public function set($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

    /**
     * Turns on searching the include path for class files.
     *
     * @param bool $useIncludePath
     */
    public function setUseIncludePath($useIncludePath)
    {
        $this->useIncludePath = $useIncludePath;
    }

    /**
     * Can be used to check if the autoloader uses the include path to check
     * for classes.
     *
     * @return bool
     */
    public function getUseIncludePath()
    {
        return $this->useIncludePath;
    }

    /**
     * Turns off searching the prefix and fallback directories for classes
     * that have not been registered with the class map.
     *
     * @param bool $classMapAuthoritative
     */
    public function setClassMapAuthoritative($classMapAuthoritative)
    {
        $this->classMapAuthoritative = $classMapAuthoritative;
    }

    /**
     * Should class lookup fail if not found in the current class map?
     *
     * @return bool
     */
    public function isClassMapAuthoritative()
    {
        return $this->classMapAuthoritative;
    }

    /**
     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
     *
     * @param string|null $apcuPrefix
     */
    public function setApcuPrefix($apcuPrefix)
    {
        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
    }

    /**
     * The APCu prefix in use, or null if APCu caching is not enabled.
     *
     * @return string|null
     */
    public function getApcuPrefix()
    {
        return $this->apcuPrefix;
    }

    /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Unregisters this instance as an autoloader.
     */
    public function unregister()
    {
        spl_autoload_unregister(array($this, 'loadClass'));
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

    /**
     * Finds the path to the file where the class is defined.
     *
     * @param string $class The name of the class
     *
     * @return string|false The path if found, false otherwise
     */
    public function findFile($class)
    {
        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;
    }

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            $subPath = $class;
            while (false !== $lastPos = strrpos($subPath, '\\')) {
                $subPath = substr($subPath, 0, $lastPos);
                $search = $subPath . '\\';
                if (isset($this->prefixDirsPsr4[$search])) {
                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
                        if (file_exists($file = $dir . $pathEnd)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }

        return false;
    }
}

/**
 * Scope isolated include.
 *
 * Prevents access to $this/self from included files.
 */
function includeFile($file)
{
    include $file;
}
vendor/composer/autoload_classmap.php000064400000052771152177723700014123 0ustar00<?php

// autoload_classmap.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));

return array(
    'Brumann\\Polyfill\\Unserialize' => $vendorDir . '/brumann/polyfill-unserialize/src/Unserialize.php',
    'CallbackFilterIterator' => $vendorDir . '/joomla/compat/src/CallbackFilterIterator.php',
    'EasyPeasyICS' => $vendorDir . '/phpmailer/phpmailer/extras/EasyPeasyICS.php',
    'Joomla\\Application\\AbstractApplication' => $vendorDir . '/joomla/application/src/AbstractApplication.php',
    'Joomla\\Application\\AbstractCliApplication' => $vendorDir . '/joomla/application/src/AbstractCliApplication.php',
    'Joomla\\Application\\AbstractDaemonApplication' => $vendorDir . '/joomla/application/src/AbstractDaemonApplication.php',
    'Joomla\\Application\\AbstractWebApplication' => $vendorDir . '/joomla/application/src/AbstractWebApplication.php',
    'Joomla\\Application\\Cli\\CliInput' => $vendorDir . '/joomla/application/src/Cli/CliInput.php',
    'Joomla\\Application\\Cli\\CliOutput' => $vendorDir . '/joomla/application/src/Cli/CliOutput.php',
    'Joomla\\Application\\Cli\\ColorProcessor' => $vendorDir . '/joomla/application/src/Cli/ColorProcessor.php',
    'Joomla\\Application\\Cli\\ColorStyle' => $vendorDir . '/joomla/application/src/Cli/ColorStyle.php',
    'Joomla\\Application\\Cli\\Output\\Processor\\ColorProcessor' => $vendorDir . '/joomla/application/src/Cli/Output/Processor/ColorProcessor.php',
    'Joomla\\Application\\Cli\\Output\\Processor\\ProcessorInterface' => $vendorDir . '/joomla/application/src/Cli/Output/Processor/ProcessorInterface.php',
    'Joomla\\Application\\Cli\\Output\\Stdout' => $vendorDir . '/joomla/application/src/Cli/Output/Stdout.php',
    'Joomla\\Application\\Cli\\Output\\Xml' => $vendorDir . '/joomla/application/src/Cli/Output/Xml.php',
    'Joomla\\Application\\Web\\WebClient' => $vendorDir . '/joomla/application/src/Web/WebClient.php',
    'Joomla\\Archive\\Archive' => $vendorDir . '/joomla/archive/src/Archive.php',
    'Joomla\\Archive\\Bzip2' => $vendorDir . '/joomla/archive/src/Bzip2.php',
    'Joomla\\Archive\\ExtractableInterface' => $vendorDir . '/joomla/archive/src/ExtractableInterface.php',
    'Joomla\\Archive\\Gzip' => $vendorDir . '/joomla/archive/src/Gzip.php',
    'Joomla\\Archive\\Tar' => $vendorDir . '/joomla/archive/src/Tar.php',
    'Joomla\\Archive\\Zip' => $vendorDir . '/joomla/archive/src/Zip.php',
    'Joomla\\DI\\Container' => $vendorDir . '/joomla/di/src/Container.php',
    'Joomla\\DI\\ContainerAwareInterface' => $vendorDir . '/joomla/di/src/ContainerAwareInterface.php',
    'Joomla\\DI\\ContainerAwareTrait' => $vendorDir . '/joomla/di/src/ContainerAwareTrait.php',
    'Joomla\\DI\\Exception\\DependencyResolutionException' => $vendorDir . '/joomla/di/src/Exception/DependencyResolutionException.php',
    'Joomla\\DI\\Exception\\KeyNotFoundException' => $vendorDir . '/joomla/di/src/Exception/KeyNotFoundException.php',
    'Joomla\\DI\\Exception\\ProtectedKeyException' => $vendorDir . '/joomla/di/src/Exception/ProtectedKeyException.php',
    'Joomla\\DI\\ServiceProviderInterface' => $vendorDir . '/joomla/di/src/ServiceProviderInterface.php',
    'Joomla\\Data\\DataObject' => $vendorDir . '/joomla/data/src/DataObject.php',
    'Joomla\\Data\\DataSet' => $vendorDir . '/joomla/data/src/DataSet.php',
    'Joomla\\Data\\DumpableInterface' => $vendorDir . '/joomla/data/src/DumpableInterface.php',
    'Joomla\\Event\\AbstractEvent' => $vendorDir . '/joomla/event/src/AbstractEvent.php',
    'Joomla\\Event\\DelegatingDispatcher' => $vendorDir . '/joomla/event/src/DelegatingDispatcher.php',
    'Joomla\\Event\\Dispatcher' => $vendorDir . '/joomla/event/src/Dispatcher.php',
    'Joomla\\Event\\DispatcherAwareInterface' => $vendorDir . '/joomla/event/src/DispatcherAwareInterface.php',
    'Joomla\\Event\\DispatcherAwareTrait' => $vendorDir . '/joomla/event/src/DispatcherAwareTrait.php',
    'Joomla\\Event\\DispatcherInterface' => $vendorDir . '/joomla/event/src/DispatcherInterface.php',
    'Joomla\\Event\\Event' => $vendorDir . '/joomla/event/src/Event.php',
    'Joomla\\Event\\EventImmutable' => $vendorDir . '/joomla/event/src/EventImmutable.php',
    'Joomla\\Event\\EventInterface' => $vendorDir . '/joomla/event/src/EventInterface.php',
    'Joomla\\Event\\ListenersPriorityQueue' => $vendorDir . '/joomla/event/src/ListenersPriorityQueue.php',
    'Joomla\\Event\\Priority' => $vendorDir . '/joomla/event/src/Priority.php',
    'Joomla\\Filesystem\\Buffer' => $vendorDir . '/joomla/filesystem/src/Buffer.php',
    'Joomla\\Filesystem\\Clients\\FtpClient' => $vendorDir . '/joomla/filesystem/src/Clients/FtpClient.php',
    'Joomla\\Filesystem\\Exception\\FilesystemException' => $vendorDir . '/joomla/filesystem/src/Exception/FilesystemException.php',
    'Joomla\\Filesystem\\File' => $vendorDir . '/joomla/filesystem/src/File.php',
    'Joomla\\Filesystem\\Folder' => $vendorDir . '/joomla/filesystem/src/Folder.php',
    'Joomla\\Filesystem\\Helper' => $vendorDir . '/joomla/filesystem/src/Helper.php',
    'Joomla\\Filesystem\\Patcher' => $vendorDir . '/joomla/filesystem/src/Patcher.php',
    'Joomla\\Filesystem\\Path' => $vendorDir . '/joomla/filesystem/src/Path.php',
    'Joomla\\Filesystem\\Stream' => $vendorDir . '/joomla/filesystem/src/Stream.php',
    'Joomla\\Filesystem\\Stream\\String' => $vendorDir . '/joomla/filesystem/src/Stream/String.php',
    'Joomla\\Filesystem\\Stream\\StringWrapper' => $vendorDir . '/joomla/filesystem/src/Stream/StringWrapper.php',
    'Joomla\\Filesystem\\Support\\StringController' => $vendorDir . '/joomla/filesystem/src/Support/StringController.php',
    'Joomla\\Filter\\InputFilter' => $vendorDir . '/joomla/filter/src/InputFilter.php',
    'Joomla\\Filter\\OutputFilter' => $vendorDir . '/joomla/filter/src/OutputFilter.php',
    'Joomla\\Image\\Filter\\Backgroundfill' => $vendorDir . '/joomla/image/src/Filter/Backgroundfill.php',
    'Joomla\\Image\\Filter\\Brightness' => $vendorDir . '/joomla/image/src/Filter/Brightness.php',
    'Joomla\\Image\\Filter\\Contrast' => $vendorDir . '/joomla/image/src/Filter/Contrast.php',
    'Joomla\\Image\\Filter\\Edgedetect' => $vendorDir . '/joomla/image/src/Filter/Edgedetect.php',
    'Joomla\\Image\\Filter\\Emboss' => $vendorDir . '/joomla/image/src/Filter/Emboss.php',
    'Joomla\\Image\\Filter\\Grayscale' => $vendorDir . '/joomla/image/src/Filter/Grayscale.php',
    'Joomla\\Image\\Filter\\Negate' => $vendorDir . '/joomla/image/src/Filter/Negate.php',
    'Joomla\\Image\\Filter\\Sketchy' => $vendorDir . '/joomla/image/src/Filter/Sketchy.php',
    'Joomla\\Image\\Filter\\Smooth' => $vendorDir . '/joomla/image/src/Filter/Smooth.php',
    'Joomla\\Image\\Image' => $vendorDir . '/joomla/image/src/Image.php',
    'Joomla\\Image\\ImageFilter' => $vendorDir . '/joomla/image/src/ImageFilter.php',
    'Joomla\\Input\\Cli' => $vendorDir . '/joomla/input/src/Cli.php',
    'Joomla\\Input\\Cookie' => $vendorDir . '/joomla/input/src/Cookie.php',
    'Joomla\\Input\\Files' => $vendorDir . '/joomla/input/src/Files.php',
    'Joomla\\Input\\Input' => $vendorDir . '/joomla/input/src/Input.php',
    'Joomla\\Input\\Json' => $vendorDir . '/joomla/input/src/Json.php',
    'Joomla\\Ldap\\LdapClient' => $vendorDir . '/joomla/ldap/src/LdapClient.php',
    'Joomla\\Registry\\AbstractRegistryFormat' => $vendorDir . '/joomla/registry/src/AbstractRegistryFormat.php',
    'Joomla\\Registry\\Factory' => $vendorDir . '/joomla/registry/src/Factory.php',
    'Joomla\\Registry\\FormatInterface' => $vendorDir . '/joomla/registry/src/FormatInterface.php',
    'Joomla\\Registry\\Format\\Ini' => $vendorDir . '/joomla/registry/src/Format/Ini.php',
    'Joomla\\Registry\\Format\\Json' => $vendorDir . '/joomla/registry/src/Format/Json.php',
    'Joomla\\Registry\\Format\\Php' => $vendorDir . '/joomla/registry/src/Format/Php.php',
    'Joomla\\Registry\\Format\\Xml' => $vendorDir . '/joomla/registry/src/Format/Xml.php',
    'Joomla\\Registry\\Format\\Yaml' => $vendorDir . '/joomla/registry/src/Format/Yaml.php',
    'Joomla\\Registry\\Registry' => $vendorDir . '/joomla/registry/src/Registry.php',
    'Joomla\\Session\\Session' => $vendorDir . '/joomla/session/Joomla/Session/Session.php',
    'Joomla\\Session\\Storage' => $vendorDir . '/joomla/session/Joomla/Session/Storage.php',
    'Joomla\\Session\\Storage\\Apc' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Apc.php',
    'Joomla\\Session\\Storage\\Apcu' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Apcu.php',
    'Joomla\\Session\\Storage\\Database' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Database.php',
    'Joomla\\Session\\Storage\\Memcache' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Memcache.php',
    'Joomla\\Session\\Storage\\Memcached' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Memcached.php',
    'Joomla\\Session\\Storage\\None' => $vendorDir . '/joomla/session/Joomla/Session/Storage/None.php',
    'Joomla\\Session\\Storage\\Wincache' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Wincache.php',
    'Joomla\\Session\\Storage\\Xcache' => $vendorDir . '/joomla/session/Joomla/Session/Storage/Xcache.php',
    'Joomla\\Session\\Tests\\StorageCase' => $vendorDir . '/joomla/session/Joomla/Session/Tests/StorageCase.php',
    'Joomla\\Session\\Tests\\StorageTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/StorageTest.php',
    'Joomla\\Session\\Tests\\Storage\\ApcTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/ApcTest.php',
    'Joomla\\Session\\Tests\\Storage\\DatabaseTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/DatabaseTest.php',
    'Joomla\\Session\\Tests\\Storage\\MemcacheTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/MemcacheTest.php',
    'Joomla\\Session\\Tests\\Storage\\MemcachedTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/MemcachedTest.php',
    'Joomla\\Session\\Tests\\Storage\\NoneTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/NoneTest.php',
    'Joomla\\Session\\Tests\\Storage\\WincacheTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/WincacheTest.php',
    'Joomla\\Session\\Tests\\Storage\\XcacheTest' => $vendorDir . '/joomla/session/Joomla/Session/Tests/Storage/XcacheTest.php',
    'Joomla\\String\\Inflector' => $vendorDir . '/joomla/string/src/Inflector.php',
    'Joomla\\String\\Normalise' => $vendorDir . '/joomla/string/src/Normalise.php',
    'Joomla\\String\\String' => $vendorDir . '/joomla/string/src/String.php',
    'Joomla\\String\\StringHelper' => $vendorDir . '/joomla/string/src/StringHelper.php',
    'Joomla\\Uri\\AbstractUri' => $vendorDir . '/joomla/uri/src/AbstractUri.php',
    'Joomla\\Uri\\Uri' => $vendorDir . '/joomla/uri/src/Uri.php',
    'Joomla\\Uri\\UriHelper' => $vendorDir . '/joomla/uri/src/UriHelper.php',
    'Joomla\\Uri\\UriImmutable' => $vendorDir . '/joomla/uri/src/UriImmutable.php',
    'Joomla\\Uri\\UriInterface' => $vendorDir . '/joomla/uri/src/UriInterface.php',
    'Joomla\\Utilities\\ArrayHelper' => $vendorDir . '/joomla/utilities/src/ArrayHelper.php',
    'Joomla\\Utilities\\IpHelper' => $vendorDir . '/joomla/utilities/src/IpHelper.php',
    'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
    'JsonSerializable' => $vendorDir . '/joomla/compat/src/JsonSerializable.php',
    'PHPMailer' => $vendorDir . '/phpmailer/phpmailer/class.phpmailer.php',
    'PHPMailerOAuth' => $vendorDir . '/phpmailer/phpmailer/class.phpmaileroauth.php',
    'PHPMailerOAuthGoogle' => $vendorDir . '/phpmailer/phpmailer/class.phpmaileroauthgoogle.php',
    'POP3' => $vendorDir . '/phpmailer/phpmailer/class.pop3.php',
    'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php',
    'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php',
    'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php',
    'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
    'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
    'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
    'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
    'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
    'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
    'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
    'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
    'ReCaptcha\\ReCaptcha' => $vendorDir . '/google/recaptcha/src/ReCaptcha/ReCaptcha.php',
    'ReCaptcha\\RequestMethod' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod.php',
    'ReCaptcha\\RequestMethod\\Curl' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Curl.php',
    'ReCaptcha\\RequestMethod\\CurlPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/CurlPost.php',
    'ReCaptcha\\RequestMethod\\Post' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Post.php',
    'ReCaptcha\\RequestMethod\\Socket' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/Socket.php',
    'ReCaptcha\\RequestMethod\\SocketPost' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestMethod/SocketPost.php',
    'ReCaptcha\\RequestParameters' => $vendorDir . '/google/recaptcha/src/ReCaptcha/RequestParameters.php',
    'ReCaptcha\\Response' => $vendorDir . '/google/recaptcha/src/ReCaptcha/Response.php',
    'SMTP' => $vendorDir . '/phpmailer/phpmailer/class.smtp.php',
    'SimplePie' => $vendorDir . '/simplepie/simplepie/library/SimplePie.php',
    'SimplePie_Author' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Author.php',
    'SimplePie_Cache' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache.php',
    'SimplePie_Cache_Base' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache/Base.php',
    'SimplePie_Cache_DB' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache/DB.php',
    'SimplePie_Cache_File' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache/File.php',
    'SimplePie_Cache_Memcache' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache/Memcache.php',
    'SimplePie_Cache_MySQL' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Cache/MySQL.php',
    'SimplePie_Caption' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Caption.php',
    'SimplePie_Category' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Category.php',
    'SimplePie_Content_Type_Sniffer' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Content/Type/Sniffer.php',
    'SimplePie_Copyright' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Copyright.php',
    'SimplePie_Core' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Core.php',
    'SimplePie_Credit' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Credit.php',
    'SimplePie_Decode_HTML_Entities' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Decode/HTML/Entities.php',
    'SimplePie_Enclosure' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Enclosure.php',
    'SimplePie_Exception' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Exception.php',
    'SimplePie_File' => $vendorDir . '/simplepie/simplepie/library/SimplePie/File.php',
    'SimplePie_HTTP_Parser' => $vendorDir . '/simplepie/simplepie/library/SimplePie/HTTP/Parser.php',
    'SimplePie_IRI' => $vendorDir . '/simplepie/simplepie/library/SimplePie/IRI.php',
    'SimplePie_Item' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Item.php',
    'SimplePie_Locator' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Locator.php',
    'SimplePie_Misc' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Misc.php',
    'SimplePie_Net_IPv6' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Net/IPv6.php',
    'SimplePie_Parse_Date' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Parse/Date.php',
    'SimplePie_Parser' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Parser.php',
    'SimplePie_Rating' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Rating.php',
    'SimplePie_Registry' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Registry.php',
    'SimplePie_Restriction' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Restriction.php',
    'SimplePie_Sanitize' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Sanitize.php',
    'SimplePie_Source' => $vendorDir . '/simplepie/simplepie/library/SimplePie/Source.php',
    'SimplePie_XML_Declaration_Parser' => $vendorDir . '/simplepie/simplepie/library/SimplePie/XML/Declaration/Parser.php',
    'SimplePie_gzdecode' => $vendorDir . '/simplepie/simplepie/library/SimplePie/gzdecode.php',
    'Symfony\\Component\\Yaml\\Dumper' => $vendorDir . '/symfony/yaml/Dumper.php',
    'Symfony\\Component\\Yaml\\Escaper' => $vendorDir . '/symfony/yaml/Escaper.php',
    'Symfony\\Component\\Yaml\\Exception\\DumpException' => $vendorDir . '/symfony/yaml/Exception/DumpException.php',
    'Symfony\\Component\\Yaml\\Exception\\ExceptionInterface' => $vendorDir . '/symfony/yaml/Exception/ExceptionInterface.php',
    'Symfony\\Component\\Yaml\\Exception\\ParseException' => $vendorDir . '/symfony/yaml/Exception/ParseException.php',
    'Symfony\\Component\\Yaml\\Exception\\RuntimeException' => $vendorDir . '/symfony/yaml/Exception/RuntimeException.php',
    'Symfony\\Component\\Yaml\\Inline' => $vendorDir . '/symfony/yaml/Inline.php',
    'Symfony\\Component\\Yaml\\Parser' => $vendorDir . '/symfony/yaml/Parser.php',
    'Symfony\\Component\\Yaml\\Unescaper' => $vendorDir . '/symfony/yaml/Unescaper.php',
    'Symfony\\Component\\Yaml\\Yaml' => $vendorDir . '/symfony/yaml/Yaml.php',
    'Symfony\\Polyfill\\Ctype\\Ctype' => $vendorDir . '/symfony/polyfill-ctype/Ctype.php',
    'Symfony\\Polyfill\\Php55\\Php55' => $vendorDir . '/symfony/polyfill-php55/Php55.php',
    'Symfony\\Polyfill\\Php55\\Php55ArrayColumn' => $vendorDir . '/symfony/polyfill-php55/Php55ArrayColumn.php',
    'Symfony\\Polyfill\\Php56\\Php56' => $vendorDir . '/symfony/polyfill-php56/Php56.php',
    'Symfony\\Polyfill\\Php71\\Php71' => $vendorDir . '/symfony/polyfill-php71/Php71.php',
    'Symfony\\Polyfill\\Php73\\Php73' => $vendorDir . '/symfony/polyfill-php73/Php73.php',
    'Symfony\\Polyfill\\Util\\Binary' => $vendorDir . '/symfony/polyfill-util/Binary.php',
    'Symfony\\Polyfill\\Util\\BinaryNoFuncOverload' => $vendorDir . '/symfony/polyfill-util/BinaryNoFuncOverload.php',
    'Symfony\\Polyfill\\Util\\BinaryOnFuncOverload' => $vendorDir . '/symfony/polyfill-util/BinaryOnFuncOverload.php',
    'TYPO3\\PharStreamWrapper\\Assertable' => $vendorDir . '/typo3/phar-stream-wrapper/src/Assertable.php',
    'TYPO3\\PharStreamWrapper\\Behavior' => $vendorDir . '/typo3/phar-stream-wrapper/src/Behavior.php',
    'TYPO3\\PharStreamWrapper\\Collectable' => $vendorDir . '/typo3/phar-stream-wrapper/src/Collectable.php',
    'TYPO3\\PharStreamWrapper\\Exception' => $vendorDir . '/typo3/phar-stream-wrapper/src/Exception.php',
    'TYPO3\\PharStreamWrapper\\Helper' => $vendorDir . '/typo3/phar-stream-wrapper/src/Helper.php',
    'TYPO3\\PharStreamWrapper\\Interceptor\\ConjunctionInterceptor' => $vendorDir . '/typo3/phar-stream-wrapper/src/Interceptor/ConjunctionInterceptor.php',
    'TYPO3\\PharStreamWrapper\\Interceptor\\PharExtensionInterceptor' => $vendorDir . '/typo3/phar-stream-wrapper/src/Interceptor/PharExtensionInterceptor.php',
    'TYPO3\\PharStreamWrapper\\Interceptor\\PharMetaDataInterceptor' => $vendorDir . '/typo3/phar-stream-wrapper/src/Interceptor/PharMetaDataInterceptor.php',
    'TYPO3\\PharStreamWrapper\\Manager' => $vendorDir . '/typo3/phar-stream-wrapper/src/Manager.php',
    'TYPO3\\PharStreamWrapper\\PharStreamWrapper' => $vendorDir . '/typo3/phar-stream-wrapper/src/PharStreamWrapper.php',
    'TYPO3\\PharStreamWrapper\\Phar\\Container' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/Container.php',
    'TYPO3\\PharStreamWrapper\\Phar\\DeserializationException' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/DeserializationException.php',
    'TYPO3\\PharStreamWrapper\\Phar\\Manifest' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/Manifest.php',
    'TYPO3\\PharStreamWrapper\\Phar\\Reader' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/Reader.php',
    'TYPO3\\PharStreamWrapper\\Phar\\ReaderException' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/ReaderException.php',
    'TYPO3\\PharStreamWrapper\\Phar\\Stub' => $vendorDir . '/typo3/phar-stream-wrapper/src/Phar/Stub.php',
    'TYPO3\\PharStreamWrapper\\Resolvable' => $vendorDir . '/typo3/phar-stream-wrapper/src/Resolvable.php',
    'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocation' => $vendorDir . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocation.php',
    'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocationCollection' => $vendorDir . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocationCollection.php',
    'TYPO3\\PharStreamWrapper\\Resolver\\PharInvocationResolver' => $vendorDir . '/typo3/phar-stream-wrapper/src/Resolver/PharInvocationResolver.php',
    'lessc' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
    'lessc_formatter_classic' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
    'lessc_formatter_compressed' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
    'lessc_formatter_lessjs' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
    'lessc_parser' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
    'ntlm_sasl_client_class' => $vendorDir . '/phpmailer/phpmailer/extras/ntlm_sasl_client.php',
    'phpmailerException' => $vendorDir . '/phpmailer/phpmailer/class.phpmailer.php',
);
vendor/composer/installed.json000064400000162035152177723700012564 0ustar00[
    {
        "name": "brumann/polyfill-unserialize",
        "version": "v1.0.3",
        "version_normalized": "1.0.3.0",
        "source": {
            "type": "git",
            "url": "https://github.com/dbrumann/polyfill-unserialize.git",
            "reference": "844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/dbrumann/polyfill-unserialize/zipball/844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b",
            "reference": "844d7e44b62a1a3d5c68cfb7ebbd59c17ea0fd7b",
            "shasum": ""
        },
        "require": {
            "php": "^5.3|^7.0"
        },
        "time": "2017-02-03T09:55:47+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Brumann\\Polyfill\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Denis Brumann",
                "email": "denis.brumann@sensiolabs.de"
            }
        ],
        "description": "Backports unserialize options introduced in PHP 7.0 to older PHP versions."
    },
    {
        "name": "google/recaptcha",
        "version": "1.1.2",
        "version_normalized": "1.1.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/google/recaptcha.git",
            "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/google/recaptcha/zipball/2b7e00566afca82a38a1d3adb8e42c118006296e",
            "reference": "2b7e00566afca82a38a1d3adb8e42c118006296e",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.2"
        },
        "require-dev": {
            "phpunit/phpunit": "4.5.*"
        },
        "time": "2015-09-02T17:23:59+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "ReCaptcha\\": "src/ReCaptcha"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "description": "Client library for reCAPTCHA, a free service that protect websites from spam and abuse.",
        "homepage": "http://www.google.com/recaptcha/",
        "keywords": [
            "Abuse",
            "captcha",
            "recaptcha",
            "spam"
        ]
    },
    {
        "name": "ircmaxell/password-compat",
        "version": "v1.0.4",
        "version_normalized": "1.0.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/ircmaxell/password_compat.git",
            "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c",
            "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c",
            "shasum": ""
        },
        "require-dev": {
            "phpunit/phpunit": "4.*"
        },
        "time": "2014-11-20T16:49:30+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "lib/password.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Anthony Ferrara",
                "email": "ircmaxell@php.net",
                "homepage": "http://blog.ircmaxell.com"
            }
        ],
        "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash",
        "homepage": "https://github.com/ircmaxell/password_compat",
        "keywords": [
            "hashing",
            "password"
        ]
    },
    {
        "name": "joomla/application",
        "version": "1.9.2",
        "version_normalized": "1.9.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/application.git",
            "reference": "6c89fdde878f7ebb7d6455f664133e9497163e2e"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/application/zipball/6c89fdde878f7ebb7d6455f664133e9497163e2e",
            "reference": "6c89fdde878f7ebb7d6455f664133e9497163e2e",
            "shasum": ""
        },
        "require": {
            "joomla/input": "~1.2",
            "joomla/registry": "^1.4.5|~2.0",
            "php": "^5.3.10|~7.0",
            "psr/log": "~1.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/event": "~1.2|~2.0",
            "joomla/session": "^1.2.1|~2.0",
            "joomla/test": "~1.1",
            "joomla/uri": "~1.1",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "suggest": {
            "joomla/session": "To use AbstractWebApplication with session support, install joomla/session",
            "joomla/uri": "To use AbstractWebApplication, install joomla/uri"
        },
        "time": "2019-03-28T14:55:36+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Application\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Application Package",
        "homepage": "https://github.com/joomla-framework/application",
        "keywords": [
            "application",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/archive",
        "version": "1.1.6",
        "version_normalized": "1.1.6.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/archive.git",
            "reference": "b1d496e8c7814f1e376cb14296c38d5ef4e08c78"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/archive/zipball/b1d496e8c7814f1e376cb14296c38d5ef4e08c78",
            "reference": "b1d496e8c7814f1e376cb14296c38d5ef4e08c78",
            "shasum": ""
        },
        "require": {
            "joomla/filesystem": "~1.3|~2.0",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "suggest": {
            "ext-bz2": "To extract bzip2 compressed packages",
            "ext-zip": "To extract zip compressed packages",
            "ext-zlib": "To extract gzip or zip compressed packages"
        },
        "time": "2019-03-10T15:17:48+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Archive\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Archive Package",
        "homepage": "https://github.com/joomla-framework/archive",
        "keywords": [
            "archive",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/compat",
        "version": "1.2.0",
        "version_normalized": "1.2.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/compat.git",
            "reference": "f23565fe0184517778996226eb4b2333deb369c4"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/compat/zipball/f23565fe0184517778996226eb4b2333deb369c4",
            "reference": "f23565fe0184517778996226eb4b2333deb369c4",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.10"
        },
        "time": "2015-02-24T00:21:06+00:00",
        "type": "joomla-package",
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "src/JsonSerializable.php",
                "src/CallbackFilterIterator.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0+"
        ],
        "description": "Joomla Compat Package",
        "homepage": "https://github.com/joomla-framework/compat",
        "keywords": [
            "compat",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/data",
        "version": "1.2.0",
        "version_normalized": "1.2.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/data.git",
            "reference": "57ee292ba23307a6a6059e69b7b19ca5b624ab80"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/data/zipball/57ee292ba23307a6a6059e69b7b19ca5b624ab80",
            "reference": "57ee292ba23307a6a6059e69b7b19ca5b624ab80",
            "shasum": ""
        },
        "require": {
            "joomla/compat": "~1.0",
            "joomla/registry": "~1.0",
            "php": ">=5.3.10|>=7.0"
        },
        "require-dev": {
            "joomla/test": "~1.0",
            "phpunit/phpunit": "~4.8|~5.0",
            "squizlabs/php_codesniffer": "1.*"
        },
        "time": "2016-04-02T22:20:43+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Data\\": "src/",
                "Joomla\\Data\\Tests\\": "Tests/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0+"
        ],
        "description": "Joomla Data Package",
        "homepage": "https://github.com/joomla-framework/data",
        "keywords": [
            "data",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/di",
        "version": "1.5.1",
        "version_normalized": "1.5.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/di.git",
            "reference": "33c66e4091e4433f33ddf4a0ac36604cf3b73c41"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/di/zipball/33c66e4091e4433f33ddf4a0ac36604cf3b73c41",
            "reference": "33c66e4091e4433f33ddf4a0ac36604cf3b73c41",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.10|~7.0",
            "psr/container": "~1.0"
        },
        "provide": {
            "psr/container-implementation": "~1.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "time": "2018-02-25T16:30:45+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\DI\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla DI Package",
        "homepage": "https://github.com/joomla-framework/di",
        "keywords": [
            "container",
            "dependency injection",
            "di",
            "framework",
            "ioc",
            "joomla"
        ]
    },
    {
        "name": "joomla/event",
        "version": "1.2.0",
        "version_normalized": "1.2.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/event.git",
            "reference": "d8cc2a4757c4556f0ab12e58903e9d168077018b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/event/zipball/d8cc2a4757c4556f0ab12e58903e9d168077018b",
            "reference": "d8cc2a4757c4556f0ab12e58903e9d168077018b",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.10|>=7.0"
        },
        "require-dev": {
            "phpunit/phpunit": "~4.8|~5.0",
            "squizlabs/php_codesniffer": "1.*"
        },
        "time": "2016-03-13T19:41:09+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Event\\": "src/",
                "Joomla\\Event\\Tests\\": "Tests/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0+"
        ],
        "description": "Joomla Event Package",
        "homepage": "https://github.com/joomla-framework/event",
        "keywords": [
            "event",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/filesystem",
        "version": "1.5.0",
        "version_normalized": "1.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/filesystem.git",
            "reference": "d8757954138b245d5a0f1f1272051eb9d954f875"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/filesystem/zipball/d8757954138b245d5a0f1f1272051eb9d954f875",
            "reference": "d8757954138b245d5a0f1f1272051eb9d954f875",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "mikey179/vfsstream": "~1.0",
            "paragonie/random_compat": "~1.0|~2.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "suggest": {
            "paragonie/random_compat": "Required to use Joomla\\Filesystem\\Path::isOwner()"
        },
        "time": "2019-03-10T15:19:21+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Filesystem\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Filesystem Package",
        "homepage": "https://github.com/joomla/joomla-framework-filesystem",
        "keywords": [
            "filesystem",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/filter",
        "version": "1.3.5",
        "version_normalized": "1.3.5.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/filter.git",
            "reference": "ee1d870b5c188056745e1dd3cece21522e2158b8"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/filter/zipball/ee1d870b5c188056745e1dd3cece21522e2158b8",
            "reference": "ee1d870b5c188056745e1dd3cece21522e2158b8",
            "shasum": ""
        },
        "require": {
            "joomla/string": "~1.3|~2.0",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/language": "~1.3",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "suggest": {
            "joomla/language": "Required only if you want to use `OutputFilter::stringURLSafe`."
        },
        "time": "2018-05-26T15:48:53+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Filter\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Filter Package",
        "homepage": "https://github.com/joomla-framework/filter",
        "keywords": [
            "filter",
            "framework",
            "joomla"
        ]
    },
    {
        "name": "joomla/image",
        "version": "1.5.0",
        "version_normalized": "1.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/image.git",
            "reference": "8885c6db5d5b3653ad30d4ad4f73607925a725a5"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/image/zipball/8885c6db5d5b3653ad30d4ad4f73607925a725a5",
            "reference": "8885c6db5d5b3653ad30d4ad4f73607925a725a5",
            "shasum": ""
        },
        "require": {
            "ext-gd": "*",
            "php": "^5.3.10|~7.0",
            "psr/log": "~1.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "time": "2018-05-25T02:29:30+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Image\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Image Package",
        "homepage": "https://github.com/joomla-framework/image",
        "keywords": [
            "framework",
            "image",
            "joomla"
        ]
    },
    {
        "name": "joomla/input",
        "version": "1.4.0",
        "version_normalized": "1.4.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/input.git",
            "reference": "a89927d412cdc8172889e3e0e3e66a134f367be1"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/input/zipball/a89927d412cdc8172889e3e0e3e66a134f367be1",
            "reference": "a89927d412cdc8172889e3e0e3e66a134f367be1",
            "shasum": ""
        },
        "require": {
            "joomla/filter": "~1.0",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "time": "2019-06-15T22:13:58+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Input\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Input Package",
        "homepage": "https://github.com/joomla-framework/input",
        "keywords": [
            "framework",
            "input",
            "joomla"
        ]
    },
    {
        "name": "joomla/ldap",
        "version": "1.5.0",
        "version_normalized": "1.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/ldap.git",
            "reference": "2b81fb2bb0a95b66d8aa1e3a4b6875990f5adf46"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/ldap/zipball/2b81fb2bb0a95b66d8aa1e3a4b6875990f5adf46",
            "reference": "2b81fb2bb0a95b66d8aa1e3a4b6875990f5adf46",
            "shasum": ""
        },
        "require": {
            "ext-ldap": "*",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/registry": "^1.4.5|~2.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0",
            "symfony/polyfill-php56": "~1.0"
        },
        "suggest": {
            "symfony/polyfill-php56": "If using PHP 5.5 or earlier to use ldap_escape() function"
        },
        "time": "2019-03-10T15:16:38+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Ldap\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla LDAP Package",
        "homepage": "https://github.com/joomla-framework/ldap",
        "keywords": [
            "framework",
            "joomla",
            "ldap"
        ]
    },
    {
        "name": "joomla/registry",
        "version": "1.6.2",
        "version_normalized": "1.6.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/registry.git",
            "reference": "182eed3a56b2b7e14cef11fdbc63c253ddcfd924"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/registry/zipball/182eed3a56b2b7e14cef11fdbc63c253ddcfd924",
            "reference": "182eed3a56b2b7e14cef11fdbc63c253ddcfd924",
            "shasum": ""
        },
        "require": {
            "joomla/compat": "~1.0",
            "joomla/utilities": "^1.4.1|~2.0",
            "php": "^5.3.10|~7.0",
            "symfony/polyfill-php55": "~1.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0",
            "symfony/yaml": "~2.0|~3.0|~4.0"
        },
        "suggest": {
            "symfony/yaml": "Install symfony/yaml if you require YAML support."
        },
        "time": "2018-06-06T16:48:30+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Registry\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Registry Package",
        "homepage": "https://github.com/joomla-framework/registry",
        "keywords": [
            "framework",
            "joomla",
            "registry"
        ]
    },
    {
        "name": "joomla/session",
        "version": "1.5.0",
        "version_normalized": "1.5.0.0",
        "target-dir": "Joomla/Session",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/session.git",
            "reference": "ae55b6cc56778003ce59ac314335ed38a451b2c7"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/session/zipball/ae55b6cc56778003ce59ac314335ed38a451b2c7",
            "reference": "ae55b6cc56778003ce59ac314335ed38a451b2c7",
            "shasum": ""
        },
        "require": {
            "joomla/event": "~1.1",
            "joomla/filter": "~1.0",
            "joomla/input": "~1.4",
            "paragonie/random_compat": "~1.0|~2.0",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/database": "~1.0",
            "joomla/test": "~1.0",
            "phpunit/dbunit": "~1.3",
            "phpunit/phpunit": "~4.8|~5.0"
        },
        "suggest": {
            "joomla/database": "Install joomla/database if you want to use Database session storage."
        },
        "time": "2019-06-15T22:14:06+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "Joomla\\Session": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Session Package",
        "homepage": "https://github.com/joomla-framework/session",
        "keywords": [
            "framework",
            "joomla",
            "session"
        ]
    },
    {
        "name": "joomla/string",
        "version": "1.4.2",
        "version_normalized": "1.4.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/string.git",
            "reference": "64ed484157262578b8daddb488bb9bd3552bc4fe"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/string/zipball/64ed484157262578b8daddb488bb9bd3552bc4fe",
            "reference": "64ed484157262578b8daddb488bb9bd3552bc4fe",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "suggest": {
            "ext-mbstring": "For improved processing"
        },
        "time": "2019-06-16T18:18:09+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\String\\": "src/"
            },
            "files": [
                "src/phputf8/utf8.php",
                "src/phputf8/ord.php",
                "src/phputf8/str_ireplace.php",
                "src/phputf8/str_pad.php",
                "src/phputf8/str_split.php",
                "src/phputf8/strcasecmp.php",
                "src/phputf8/strcspn.php",
                "src/phputf8/stristr.php",
                "src/phputf8/strrev.php",
                "src/phputf8/strspn.php",
                "src/phputf8/trim.php",
                "src/phputf8/ucfirst.php",
                "src/phputf8/ucwords.php",
                "src/phputf8/utils/ascii.php",
                "src/phputf8/utils/validation.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla String Package",
        "homepage": "https://github.com/joomla-framework/string",
        "keywords": [
            "framework",
            "joomla",
            "string"
        ]
    },
    {
        "name": "joomla/uri",
        "version": "1.2.0",
        "version_normalized": "1.2.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/uri.git",
            "reference": "848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/uri/zipball/848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1",
            "reference": "848a31dc895a9c8c9d7ea67571d6a4dd634a9dc1",
            "shasum": ""
        },
        "require": {
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "joomla/test": "~1.0",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "time": "2018-07-01T00:12:15+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Uri\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Uri Package",
        "homepage": "https://github.com/joomla-framework/uri",
        "keywords": [
            "framework",
            "joomla",
            "uri"
        ]
    },
    {
        "name": "joomla/utilities",
        "version": "1.6.0",
        "version_normalized": "1.6.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/joomla-framework/utilities.git",
            "reference": "181fe644149ca0bd4a31f12212d3840147552b30"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/joomla-framework/utilities/zipball/181fe644149ca0bd4a31f12212d3840147552b30",
            "reference": "181fe644149ca0bd4a31f12212d3840147552b30",
            "shasum": ""
        },
        "require": {
            "joomla/string": "~1.3|~2.0",
            "php": "^5.3.10|~7.0"
        },
        "require-dev": {
            "joomla/coding-standards": "~2.0@alpha",
            "phpunit/phpunit": "^4.8.35|^5.4.3|~6.0"
        },
        "time": "2018-10-16T23:36:52+00:00",
        "type": "joomla-package",
        "extra": {
            "branch-alias": {
                "dev-master": "1.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Joomla\\Utilities\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "GPL-2.0-or-later"
        ],
        "description": "Joomla Utilities Package",
        "homepage": "https://github.com/joomla-framework/utilities",
        "keywords": [
            "framework",
            "joomla",
            "utilities"
        ]
    },
    {
        "name": "leafo/lessphp",
        "version": "v0.5.0",
        "version_normalized": "0.5.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/leafo/lessphp.git",
            "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/leafo/lessphp/zipball/0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
            "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
            "shasum": ""
        },
        "time": "2014-11-24T18:39:20+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "0.4.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "lessc.inc.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT",
            "GPL-3.0"
        ],
        "authors": [
            {
                "name": "Leaf Corcoran",
                "email": "leafot@gmail.com",
                "homepage": "http://leafo.net"
            }
        ],
        "description": "lessphp is a compiler for LESS written in PHP.",
        "homepage": "http://leafo.net/lessphp/"
    },
    {
        "name": "paragonie/random_compat",
        "version": "v1.4.3",
        "version_normalized": "1.4.3.0",
        "source": {
            "type": "git",
            "url": "https://github.com/paragonie/random_compat.git",
            "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/paragonie/random_compat/zipball/9b3899e3c3ddde89016f576edb8c489708ad64cd",
            "reference": "9b3899e3c3ddde89016f576edb8c489708ad64cd",
            "shasum": ""
        },
        "require": {
            "php": ">=5.2.0"
        },
        "require-dev": {
            "phpunit/phpunit": "4.*|5.*"
        },
        "suggest": {
            "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
        },
        "time": "2018-04-04T21:48:54+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "lib/random.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Paragon Initiative Enterprises",
                "email": "security@paragonie.com",
                "homepage": "https://paragonie.com"
            }
        ],
        "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
        "keywords": [
            "csprng",
            "pseudorandom",
            "random"
        ]
    },
    {
        "name": "paragonie/sodium_compat",
        "version": "v1.9.1",
        "version_normalized": "1.9.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/paragonie/sodium_compat.git",
            "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/87125d5b265f98c4d1b8d83a1f0726607c229421",
            "reference": "87125d5b265f98c4d1b8d83a1f0726607c229421",
            "shasum": ""
        },
        "require": {
            "paragonie/random_compat": ">=1",
            "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
        },
        "require-dev": {
            "phpunit/phpunit": "^3|^4|^5"
        },
        "suggest": {
            "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
            "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
        },
        "time": "2019-03-20T17:19:05+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "files": [
                "autoload.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "ISC"
        ],
        "authors": [
            {
                "name": "Paragon Initiative Enterprises",
                "email": "security@paragonie.com"
            },
            {
                "name": "Frank Denis",
                "email": "jedisct1@pureftpd.org"
            }
        ],
        "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
        "keywords": [
            "Authentication",
            "BLAKE2b",
            "ChaCha20",
            "ChaCha20-Poly1305",
            "Chapoly",
            "Curve25519",
            "Ed25519",
            "EdDSA",
            "Edwards-curve Digital Signature Algorithm",
            "Elliptic Curve Diffie-Hellman",
            "Poly1305",
            "Pure-PHP cryptography",
            "RFC 7748",
            "RFC 8032",
            "Salpoly",
            "Salsa20",
            "X25519",
            "XChaCha20-Poly1305",
            "XSalsa20-Poly1305",
            "Xchacha20",
            "Xsalsa20",
            "aead",
            "cryptography",
            "ecdh",
            "elliptic curve",
            "elliptic curve cryptography",
            "encryption",
            "libsodium",
            "php",
            "public-key cryptography",
            "secret-key cryptography",
            "side-channel resistant"
        ]
    },
    {
        "name": "phpmailer/phpmailer",
        "version": "v5.2.27",
        "version_normalized": "5.2.27.0",
        "source": {
            "type": "git",
            "url": "https://github.com/PHPMailer/PHPMailer.git",
            "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/dde1db116511aa4956389d75546c5be4c2beb2a6",
            "reference": "dde1db116511aa4956389d75546c5be4c2beb2a6",
            "shasum": ""
        },
        "require": {
            "ext-ctype": "*",
            "php": ">=5.0.0"
        },
        "require-dev": {
            "doctrine/annotations": "1.2.*",
            "jms/serializer": "0.16.*",
            "phpdocumentor/phpdocumentor": "2.*",
            "phpunit/phpunit": "4.8.*",
            "symfony/debug": "2.8.*",
            "symfony/filesystem": "2.8.*",
            "symfony/translation": "2.8.*",
            "symfony/yaml": "2.8.*",
            "zendframework/zend-cache": "2.5.1",
            "zendframework/zend-config": "2.5.1",
            "zendframework/zend-eventmanager": "2.5.1",
            "zendframework/zend-filter": "2.5.1",
            "zendframework/zend-i18n": "2.5.1",
            "zendframework/zend-json": "2.5.1",
            "zendframework/zend-math": "2.5.1",
            "zendframework/zend-serializer": "2.5.*",
            "zendframework/zend-servicemanager": "2.5.*",
            "zendframework/zend-stdlib": "2.5.1"
        },
        "suggest": {
            "league/oauth2-google": "Needed for Google XOAUTH2 authentication"
        },
        "time": "2018-11-15T22:32:31+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "classmap": [
                "class.phpmailer.php",
                "class.phpmaileroauth.php",
                "class.phpmaileroauthgoogle.php",
                "class.smtp.php",
                "class.pop3.php",
                "extras/EasyPeasyICS.php",
                "extras/ntlm_sasl_client.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "LGPL-2.1"
        ],
        "authors": [
            {
                "name": "Jim Jagielski",
                "email": "jimjag@gmail.com"
            },
            {
                "name": "Marcus Bointon",
                "email": "phpmailer@synchromedia.co.uk"
            },
            {
                "name": "Andy Prevost",
                "email": "codeworxtech@users.sourceforge.net"
            },
            {
                "name": "Brent R. Matzelle"
            }
        ],
        "description": "PHPMailer is a full-featured email creation and transfer class for PHP"
    },
    {
        "name": "psr/container",
        "version": "1.0.0",
        "version_normalized": "1.0.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-fig/container.git",
            "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
            "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2017-02-14T16:28:37+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Psr\\Container\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "PHP-FIG",
                "homepage": "http://www.php-fig.org/"
            }
        ],
        "description": "Common Container Interface (PHP FIG PSR-11)",
        "homepage": "https://github.com/php-fig/container",
        "keywords": [
            "PSR-11",
            "container",
            "container-interface",
            "container-interop",
            "psr"
        ]
    },
    {
        "name": "psr/log",
        "version": "1.1.0",
        "version_normalized": "1.1.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/php-fig/log.git",
            "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
            "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.0"
        },
        "time": "2018-11-20T15:27:04+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.0.x-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Psr\\Log\\": "Psr/Log/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "PHP-FIG",
                "homepage": "http://www.php-fig.org/"
            }
        ],
        "description": "Common interface for logging libraries",
        "homepage": "https://github.com/php-fig/log",
        "keywords": [
            "log",
            "psr",
            "psr-3"
        ]
    },
    {
        "name": "simplepie/simplepie",
        "version": "1.3.1",
        "version_normalized": "1.3.1.0",
        "source": {
            "type": "git",
            "url": "https://github.com/simplepie/simplepie.git",
            "reference": "ce53709778bc1e2e4deda1651b66e5081398d5cc"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/simplepie/simplepie/zipball/ce53709778bc1e2e4deda1651b66e5081398d5cc",
            "reference": "ce53709778bc1e2e4deda1651b66e5081398d5cc",
            "shasum": ""
        },
        "require": {
            "php": ">=5.2.0"
        },
        "time": "2012-10-30T17:54:03+00:00",
        "type": "library",
        "installation-source": "dist",
        "autoload": {
            "psr-0": {
                "SimplePie": "library"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "BSD-3-Clause"
        ],
        "authors": [
            {
                "name": "Ryan Parman",
                "homepage": "http://ryanparman.com/",
                "role": "Creator, alumnus developer"
            },
            {
                "name": "Geoffrey Sneddon",
                "homepage": "http://gsnedders.com/",
                "role": "Alumnus developer"
            },
            {
                "name": "Ryan McCue",
                "email": "me@ryanmccue.info",
                "homepage": "http://ryanmccue.info/",
                "role": "Developer"
            }
        ],
        "description": "A simple Atom/RSS parsing library for PHP",
        "homepage": "http://simplepie.org/",
        "keywords": [
            "atom",
            "feeds",
            "rss"
        ]
    },
    {
        "name": "symfony/polyfill-ctype",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-ctype.git",
            "reference": "82ebae02209c21113908c229e9883c419720738a"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
            "reference": "82ebae02209c21113908c229e9883c419720738a",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "suggest": {
            "ext-ctype": "For best performance"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Ctype\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            },
            {
                "name": "Gert de Pagter",
                "email": "backendtea@gmail.com"
            }
        ],
        "description": "Symfony polyfill for ctype functions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "ctype",
            "polyfill",
            "portable"
        ]
    },
    {
        "name": "symfony/polyfill-php55",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-php55.git",
            "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/96fa25cef405ea452919559a0025d5dc16e30e4c",
            "reference": "96fa25cef405ea452919559a0025d5dc16e30e4c",
            "shasum": ""
        },
        "require": {
            "ircmaxell/password-compat": "~1.0",
            "php": ">=5.3.3"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Php55\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "polyfill",
            "portable",
            "shim"
        ]
    },
    {
        "name": "symfony/polyfill-php56",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-php56.git",
            "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/f4dddbc5c3471e1b700a147a20ae17cdb72dbe42",
            "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3",
            "symfony/polyfill-util": "~1.0"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Php56\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "polyfill",
            "portable",
            "shim"
        ]
    },
    {
        "name": "symfony/polyfill-php71",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-php71.git",
            "reference": "8fc094a6b4f646b3ecd2400069420ef82f23a93e"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-php71/zipball/8fc094a6b4f646b3ecd2400069420ef82f23a93e",
            "reference": "8fc094a6b4f646b3ecd2400069420ef82f23a93e",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Php71\\": ""
            },
            "files": [
                "bootstrap.php"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony polyfill backporting some PHP 7.1+ features to lower PHP versions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "polyfill",
            "portable",
            "shim"
        ]
    },
    {
        "name": "symfony/polyfill-php73",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-php73.git",
            "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
            "reference": "d1fb4abcc0c47be136208ad9d68bf59f1ee17abd",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2019-02-06T07:57:58+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Php73\\": ""
            },
            "files": [
                "bootstrap.php"
            ],
            "classmap": [
                "Resources/stubs"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
        "homepage": "https://symfony.com",
        "keywords": [
            "compatibility",
            "polyfill",
            "portable",
            "shim"
        ]
    },
    {
        "name": "symfony/polyfill-util",
        "version": "v1.11.0",
        "version_normalized": "1.11.0.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/polyfill-util.git",
            "reference": "b46c6cae28a3106735323f00a0c38eccf2328897"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/b46c6cae28a3106735323f00a0c38eccf2328897",
            "reference": "b46c6cae28a3106735323f00a0c38eccf2328897",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.3"
        },
        "time": "2019-02-08T14:16:39+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "1.11-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Polyfill\\Util\\": ""
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Nicolas Grekas",
                "email": "p@tchwork.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony utilities for portability of PHP codes",
        "homepage": "https://symfony.com",
        "keywords": [
            "compat",
            "compatibility",
            "polyfill",
            "shim"
        ]
    },
    {
        "name": "symfony/yaml",
        "version": "v2.8.50",
        "version_normalized": "2.8.50.0",
        "source": {
            "type": "git",
            "url": "https://github.com/symfony/yaml.git",
            "reference": "02c1859112aa779d9ab394ae4f3381911d84052b"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/symfony/yaml/zipball/02c1859112aa779d9ab394ae4f3381911d84052b",
            "reference": "02c1859112aa779d9ab394ae4f3381911d84052b",
            "shasum": ""
        },
        "require": {
            "php": ">=5.3.9",
            "symfony/polyfill-ctype": "~1.8"
        },
        "time": "2018-11-11T11:18:13+00:00",
        "type": "library",
        "extra": {
            "branch-alias": {
                "dev-master": "2.8-dev"
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "Symfony\\Component\\Yaml\\": ""
            },
            "exclude-from-classmap": [
                "/Tests/"
            ]
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Fabien Potencier",
                "email": "fabien@symfony.com"
            },
            {
                "name": "Symfony Community",
                "homepage": "https://symfony.com/contributors"
            }
        ],
        "description": "Symfony Yaml Component",
        "homepage": "https://symfony.com"
    },
    {
        "name": "typo3/phar-stream-wrapper",
        "version": "v2.1.2",
        "version_normalized": "2.1.2.0",
        "source": {
            "type": "git",
            "url": "https://github.com/TYPO3/phar-stream-wrapper.git",
            "reference": "057622f5a3b92a5ffbea0fbaadce573500a62870"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/TYPO3/phar-stream-wrapper/zipball/057622f5a3b92a5ffbea0fbaadce573500a62870",
            "reference": "057622f5a3b92a5ffbea0fbaadce573500a62870",
            "shasum": ""
        },
        "require": {
            "brumann/polyfill-unserialize": "^1.0",
            "ext-json": "*",
            "php": "^5.3.3|^7.0"
        },
        "require-dev": {
            "ext-xdebug": "*",
            "phpunit/phpunit": "^4.8.36"
        },
        "suggest": {
            "ext-fileinfo": "For PHP builtin file type guessing, otherwise uses internal processing"
        },
        "time": "2019-05-14T13:14:31+00:00",
        "type": "library",
        "installation-source": "source",
        "autoload": {
            "psr-4": {
                "TYPO3\\PharStreamWrapper\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "description": "Interceptors for PHP's native phar:// stream handling",
        "homepage": "https://typo3.org/",
        "keywords": [
            "phar",
            "php",
            "security",
            "stream-wrapper"
        ]
    }
]
vendor/composer/autoload_psr4.php000064400000004233152177723700013176 0ustar00<?php

// autoload_psr4.php @generated by Composer

$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname(dirname($vendorDir));

return array(
    'TYPO3\\PharStreamWrapper\\' => array($vendorDir . '/typo3/phar-stream-wrapper/src'),
    'Symfony\\Polyfill\\Util\\' => array($vendorDir . '/symfony/polyfill-util'),
    'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
    'Symfony\\Polyfill\\Php71\\' => array($vendorDir . '/symfony/polyfill-php71'),
    'Symfony\\Polyfill\\Php56\\' => array($vendorDir . '/symfony/polyfill-php56'),
    'Symfony\\Polyfill\\Php55\\' => array($vendorDir . '/symfony/polyfill-php55'),
    'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
    'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
    'ReCaptcha\\' => array($vendorDir . '/google/recaptcha/src/ReCaptcha'),
    'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
    'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
    'Joomla\\Utilities\\' => array($vendorDir . '/joomla/utilities/src'),
    'Joomla\\Uri\\' => array($vendorDir . '/joomla/uri/src'),
    'Joomla\\String\\' => array($vendorDir . '/joomla/string/src'),
    'Joomla\\Registry\\' => array($vendorDir . '/joomla/registry/src'),
    'Joomla\\Ldap\\' => array($vendorDir . '/joomla/ldap/src'),
    'Joomla\\Input\\' => array($vendorDir . '/joomla/input/src'),
    'Joomla\\Image\\' => array($vendorDir . '/joomla/image/src'),
    'Joomla\\Filter\\' => array($vendorDir . '/joomla/filter/src'),
    'Joomla\\Filesystem\\' => array($vendorDir . '/joomla/filesystem/src'),
    'Joomla\\Event\\Tests\\' => array($vendorDir . '/joomla/event/Tests'),
    'Joomla\\Event\\' => array($vendorDir . '/joomla/event/src'),
    'Joomla\\Data\\Tests\\' => array($vendorDir . '/joomla/data/Tests'),
    'Joomla\\Data\\' => array($vendorDir . '/joomla/data/src'),
    'Joomla\\DI\\' => array($vendorDir . '/joomla/di/src'),
    'Joomla\\Archive\\' => array($vendorDir . '/joomla/archive/src'),
    'Joomla\\Application\\' => array($vendorDir . '/joomla/application/src'),
    'Brumann\\Polyfill\\' => array($vendorDir . '/brumann/polyfill-unserialize/src'),
);
cms/html/bootstrap/starttabsetscript.php000064400000001017152200040760014600 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

$selector = empty($displayData['selector']) ? '' : $displayData['selector'];

echo
	'jQuery(function($){ ',
		'$(', json_encode('#' . $selector . ' a'), ')',
			'.click(function (e) {',
				'e.preventDefault();',
				'$(this).tab("show");',
			'});',
	'});';
cms/html/bootstrap/addtabscript.php000064400000001371152200040760013462 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

$selector = empty($displayData['selector']) ? '' : $displayData['selector'];
$id = empty($displayData['id']) ? '' : $displayData['id'];
$active = empty($displayData['active']) ? '' : $displayData['active'];
$title = empty($displayData['title']) ? '' : $displayData['title'];

$li = '<li class="' . $active . '"><a href="#' . $id . '" data-toggle="tab">' . $title . '</a></li>';

echo 'jQuery(function($){ $(', json_encode('#' . $selector . 'Tabs'), ').append($(', json_encode($li), ')); });';
cms/html/bootstrap/addtab.php000064400000000730152200040760012233 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

$id = empty($displayData['id']) ? '' : $displayData['id'];
$active = empty($displayData['active']) ? '' : $displayData['active'];

?>

<div id="<?php echo $id; ?>" class="tab-pane<?php echo $active; ?>">
cms/html/bootstrap/endtab.php000064400000000426152200040760012253 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

?>

</div>cms/html/bootstrap/endtabset.php000064400000000426152200040760012767 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

?>

</div>cms/html/bootstrap/starttabset.php000064400000000735152200040760013361 0ustar00<?php
/**
 * @package     Joomla.Site
 * @subpackage  Layout
 *
 * @copyright   Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('JPATH_BASE') or die;

$selector = empty($displayData['selector']) ? '' : $displayData['selector'];

?>

<ul class="nav nav-tabs" id="<?php echo $selector; ?>Tabs"></ul>
<div class="tab-content" id="<?php echo $selector; ?>Content">